From 730007f38146f56b19218f9bd7d759b7c6890b8f Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 6 Apr 2023 15:46:08 +1000 Subject: [PATCH 001/153] GitHub actions fixes (#1433) * Github action fixes, backported from develop-1.9 * Updates to whats_new.rst * Capitalise "Dependabot" in whats_new.rst --- .github/workflows/main.yml | 9 ++++++--- docs/about/whats_new.rst | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2049f7e0..7b8e9333a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,12 +92,16 @@ jobs: EOF - name: DockerHub Push - if: steps.changes.outputs.docker == 'true' + if: | + github.event_name == 'push' + && github.ref == 'refs/heads/develop' + && steps.changes.outputs.docker == 'true' uses: docker/build-push-action@v4 with: + file: docker/Dockerfile context: . push: true - tags: ${DOCKER_IMAGE} + tags: ${{ env.DOCKER_IMAGE }} - name: Build Packages run: | @@ -135,7 +139,6 @@ jobs: TWINE_PASSWORD: ${{ secrets.PyPiToken }} - name: Upload coverage to Codecov - if: steps.cfg.outputs.primary == 'yes' uses: codecov/codecov-action@v3 with: file: ./coverage.xml diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 1df386334..984bf8431 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,7 +8,10 @@ What's New v1.8.next ========= -- Documentation fixes (:pull:`1417`, :pull:`1418`) +- Fix broken Github action workflows (:pull:`1425`, :pull:`1433`) +- Setup Dependabot, and Dependabot-generated updates (:pull:`1416`, :pull:`1420`, :pull:`1423`, + :pull:`1428`) +- Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) v1.8.12 (7th March 2023) From ab1c06bb6c22ed996f74f3e768b9c141e21ec770 Mon Sep 17 00:00:00 2001 From: Damien Ayers Date: Sun, 26 Mar 2023 14:25:57 +1100 Subject: [PATCH 002/153] Remove duplicate GHA Workflow for docs We used to deploy to netlify for docs previews, but now that's done to Read The Docs. The old Workflow should be removed. --- .github/workflows/build-docs.yml | 46 -------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 .github/workflows/build-docs.yml diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml deleted file mode 100644 index 5a1dcc41f..000000000 --- a/.github/workflows/build-docs.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: build-docs - -on: - pull_request: - push: - branches: - - develop - - dra/docs-updates - -env: - DKR: opendatacube/datacube-tests:latest - -jobs: - main: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Pull Docker - run: | - docker pull ${DKR} - - - name: Build Docs - run: | - cat < Date: Mon, 24 Apr 2023 18:10:12 +0000 Subject: [PATCH 003/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adrienverge/yamllint.git: v1.30.0 → v1.31.0](https://github.com/adrienverge/yamllint.git/compare/v1.30.0...v1.31.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3a041264..397da9c98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/adrienverge/yamllint.git - rev: v1.30.0 + rev: v1.31.0 hooks: - id: yamllint - repo: https://github.com/pre-commit/pre-commit-hooks From 04ffcd6db0cad0409056ab2c19e389d96ca88245 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 8 May 2023 01:25:46 +0000 Subject: [PATCH 004/153] Display error message instead of help message if a required argument isn't provided --- datacube/scripts/dataset.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index 2a318a854..24c00c5fb 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -22,7 +22,7 @@ from datacube.index import Index from datacube.model import Dataset from datacube.ui import click as ui -from datacube.ui.click import cli, print_help_msg +from datacube.ui.click import cli from datacube.ui.common import ui_path_doc_stream from datacube.utils import changes, SimpleDocNav from datacube.utils.serialise import SafeDatacubeDumper @@ -170,7 +170,7 @@ def index_cmd(index, product_names, dataset_paths): if not dataset_paths: - print_help_msg(index_cmd) + click.echo('No datasets provided') sys.exit(1) if confirm_ignore_lineage is False and ignore_lineage is True: @@ -249,7 +249,7 @@ def parse_update_rules(keys_that_can_change): @ui.pass_index() def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths): if not dataset_paths: - print_help_msg(update_cmd) + click.echo('No datasets provided') sys.exit(1) def loc_action(action, new_ds, existing_ds, action_name): @@ -416,7 +416,7 @@ def info_cmd(index: Index, show_sources: bool, show_derived: bool, max_depth: int, ids: Iterable[str]) -> None: if not ids: - print_help_msg(info_cmd) + click.echo('No datasets provided') sys.exit(1) # Using an array wrapper to get around the lack of "nonlocal" in py2 @@ -486,7 +486,7 @@ def uri_search_cmd(index: Index, paths: List[str], search_mode): PATHS may be either file paths or URIs """ if not paths: - print_help_msg(uri_search_cmd) + click.echo('No locations provided') sys.exit(1) if search_mode == 'guess': @@ -511,7 +511,7 @@ def uri_search_cmd(index: Index, paths: List[str], search_mode): @ui.pass_index() def archive_cmd(index: Index, archive_derived: bool, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - print_help_msg(archive_cmd) + click.echo('No datasets provided') sys.exit(1) derived_dataset_ids: List[UUID] = [] @@ -559,7 +559,7 @@ def archive_cmd(index: Index, archive_derived: bool, dry_run: bool, all_ds: bool def restore_cmd(index: Index, restore_derived: bool, derived_tolerance_seconds: int, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - print_help_msg(restore_cmd) + click.echo('No datasets provided') sys.exit(1) tolerance = datetime.timedelta(seconds=derived_tolerance_seconds) @@ -606,7 +606,7 @@ def within_tolerance(dataset): @ui.pass_index() def purge_cmd(index: Index, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - print_help_msg(purge_cmd) + click.echo('No datasets provided') sys.exit(1) if all_ds: From c435a91638cc4ca2aaa21de0e3064cdb187b4c8e Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 8 May 2023 01:31:15 +0000 Subject: [PATCH 005/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 984bf8431..ddc5553e1 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,6 +12,7 @@ v1.8.next - Setup Dependabot, and Dependabot-generated updates (:pull:`1416`, :pull:`1420`, :pull:`1423`, :pull:`1428`) - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) +- ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) v1.8.12 (7th March 2023) From 8c259c141e7f6a1b64675b3cb054e6640a583210 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 8 May 2023 02:25:55 +0000 Subject: [PATCH 006/153] print error message as well as usage info --- datacube/scripts/dataset.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index 24c00c5fb..5a3dc45e3 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -22,7 +22,7 @@ from datacube.index import Index from datacube.model import Dataset from datacube.ui import click as ui -from datacube.ui.click import cli +from datacube.ui.click import cli, print_help_msg from datacube.ui.common import ui_path_doc_stream from datacube.utils import changes, SimpleDocNav from datacube.utils.serialise import SafeDatacubeDumper @@ -170,7 +170,8 @@ def index_cmd(index, product_names, dataset_paths): if not dataset_paths: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(index_cmd) sys.exit(1) if confirm_ignore_lineage is False and ignore_lineage is True: @@ -249,7 +250,8 @@ def parse_update_rules(keys_that_can_change): @ui.pass_index() def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths): if not dataset_paths: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(update_cmd) sys.exit(1) def loc_action(action, new_ds, existing_ds, action_name): @@ -416,7 +418,8 @@ def info_cmd(index: Index, show_sources: bool, show_derived: bool, max_depth: int, ids: Iterable[str]) -> None: if not ids: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(info_cmd) sys.exit(1) # Using an array wrapper to get around the lack of "nonlocal" in py2 @@ -486,7 +489,8 @@ def uri_search_cmd(index: Index, paths: List[str], search_mode): PATHS may be either file paths or URIs """ if not paths: - click.echo('No locations provided') + click.echo('Error: no locations provided\n') + print_help_msg(uri_search_cmd) sys.exit(1) if search_mode == 'guess': @@ -511,7 +515,8 @@ def uri_search_cmd(index: Index, paths: List[str], search_mode): @ui.pass_index() def archive_cmd(index: Index, archive_derived: bool, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(archive_cmd) sys.exit(1) derived_dataset_ids: List[UUID] = [] @@ -559,7 +564,8 @@ def archive_cmd(index: Index, archive_derived: bool, dry_run: bool, all_ds: bool def restore_cmd(index: Index, restore_derived: bool, derived_tolerance_seconds: int, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(restore_cmd) sys.exit(1) tolerance = datetime.timedelta(seconds=derived_tolerance_seconds) @@ -606,7 +612,8 @@ def within_tolerance(dataset): @ui.pass_index() def purge_cmd(index: Index, dry_run: bool, all_ds: bool, ids: List[str]): if not ids and not all_ds: - click.echo('No datasets provided') + click.echo('Error: no datasets provided\n') + print_help_msg(purge_cmd) sys.exit(1) if all_ds: From 84691de3a06b5e4d074452d2e7902d4ea310c5f6 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 8 May 2023 02:44:38 +0000 Subject: [PATCH 007/153] add license hook to pre-commit --- .pre-commit-config.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 397da9c98..60c4d04a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,3 +22,21 @@ repos: rev: v3.0.0a5 # Use the sha / tag you want to point at hooks: - id: pylint + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.1 + hooks: + - id: forbid-crlf + - id: remove-crlf + - id: forbid-tabs + - id: remove-tabs + args: [--whitespaces-count, '2'] + - id: chmod + args: ['644'] + files: \.md$ + - id: insert-license + files: ./(.*).py$ + args: + - --license-filepath + - license-template.txt + - --use-current-year + - --no-extra-eol From 08c6d70f3cc6b9a196df7cda68b9313a9cc0b57b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 8 May 2023 04:32:58 +0000 Subject: [PATCH 008/153] update license template and instructions, and whats_new --- .pre-commit-config.yaml | 3 --- license-headers.md | 30 ++++++++++++------------------ license-template.txt | 2 +- 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60c4d04a7..3216b3db5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,9 +30,6 @@ repos: - id: forbid-tabs - id: remove-tabs args: [--whitespaces-count, '2'] - - id: chmod - args: ['644'] - files: \.md$ - id: insert-license files: ./(.*).py$ args: diff --git a/license-headers.md b/license-headers.md index 92090fad0..e5cd086bb 100644 --- a/license-headers.md +++ b/license-headers.md @@ -1,29 +1,23 @@ # Applying or updating license headers -To add or update license headers in this or other Open Data Cube -projects, you can do the following: +To add or update license headers in this or other Open Data Cube projects, you can do the following: -Download the tool from [https://github.com/johann-petrak/licenseheaders](https://github.com/johann-petrak/licenseheaders) and make it executable. +Ensure you have pre-commit hooks installed ```bash -wget https://raw.githubusercontent.com/johann-petrak/licenseheaders/master/licenseheaders.py -chmod +x licenseheaders.py +pre-commit install ``` -Change the sections on `python` files, to remove the `headerStartLine` and -`headerEndLine`, like: +Run the `insert-license` hook: -```python - "headerStartLine": "", - "headerEndLine": "", +```bash +pre-commit run insert-license --all-files ``` -Run the tool: +To remove the license headers, add the `--remove-header` arg to `.pre-commit-config.yaml` before running the hook. -```bash -python3 ./licenseheaders.py --tmpl license-template.txt --years 2015-2020 --ext py --dir datacube -python3 ./licenseheaders.py --tmpl license-template.txt --years 2015-2020 --ext py --dir integration_tests -python3 ./licenseheaders.py --tmpl license-template.txt --years 2015-2020 --ext py --dir tests -python3 ./licenseheaders.py --tmpl license-template.txt --years 2015-2020 --ext py --dir docs -python3 ./licenseheaders.py --tmpl license-template.txt --years 2015-2020 --ext py --dir examples -``` +To make updates to the license text, first remove the headers, then update `license-template.txt` before rerunning the hook as usual to add them back. + +Note that the date range is automatically updated to include the current year. + +See the full documentation here: [https://github.com/Lucas-C/pre-commit-hooks#insert-license](https://github.com/Lucas-C/pre-commit-hooks#insert-license) diff --git a/license-template.txt b/license-template.txt index 649c11701..f15b36fd2 100644 --- a/license-template.txt +++ b/license-template.txt @@ -1,4 +1,4 @@ This file is part of the Open Data Cube, see https://opendatacube.org for more information -Copyright (c) ${years} ODC Contributors +Copyright (c) 2015-2023 ODC Contributors SPDX-License-Identifier: Apache-2.0 From 9074c8908b0e02480f2277d9f58397db67589817 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 04:33:48 +0000 Subject: [PATCH 009/153] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- datacube/__init__.py | 2 +- datacube/__main__.py | 2 +- datacube/api/__init__.py | 2 +- datacube/api/core.py | 2 +- datacube/api/grid_workflow.py | 2 +- datacube/api/query.py | 2 +- datacube/config.py | 2 +- datacube/drivers/__init__.py | 2 +- datacube/drivers/_tools.py | 2 +- datacube/drivers/_types.py | 2 +- datacube/drivers/datasource.py | 2 +- datacube/drivers/driver_cache.py | 2 +- datacube/drivers/indexes.py | 2 +- datacube/drivers/netcdf/__init__.py | 2 +- datacube/drivers/netcdf/_safestrings.py | 2 +- datacube/drivers/netcdf/_write.py | 2 +- datacube/drivers/netcdf/driver.py | 2 +- datacube/drivers/netcdf/writer.py | 2 +- datacube/drivers/postgis/__init__.py | 2 +- datacube/drivers/postgis/_api.py | 2 +- datacube/drivers/postgis/_connections.py | 2 +- datacube/drivers/postgis/_core.py | 2 +- datacube/drivers/postgis/_fields.py | 2 +- datacube/drivers/postgis/_schema.py | 2 +- datacube/drivers/postgis/_spatial.py | 2 +- datacube/drivers/postgis/sql.py | 2 +- datacube/drivers/postgres/__init__.py | 2 +- datacube/drivers/postgres/_api.py | 2 +- datacube/drivers/postgres/_connections.py | 2 +- datacube/drivers/postgres/_core.py | 2 +- datacube/drivers/postgres/_dynamic.py | 2 +- datacube/drivers/postgres/_fields.py | 2 +- datacube/drivers/postgres/_schema.py | 2 +- datacube/drivers/postgres/sql.py | 2 +- datacube/drivers/readers.py | 2 +- datacube/drivers/rio/__init__.py | 2 +- datacube/drivers/rio/_reader.py | 2 +- datacube/drivers/writers.py | 2 +- datacube/execution/__init__.py | 2 +- datacube/execution/worker.py | 2 +- datacube/executor.py | 2 +- datacube/helpers.py | 2 +- datacube/index/__init__.py | 2 +- datacube/index/_api.py | 2 +- datacube/index/abstract.py | 2 +- datacube/index/eo3.py | 2 +- datacube/index/exceptions.py | 2 +- datacube/index/fields.py | 2 +- datacube/index/hl.py | 2 +- datacube/index/memory/__init__.py | 2 +- datacube/index/memory/_datasets.py | 2 +- datacube/index/memory/_fields.py | 4 + datacube/index/memory/_metadata_types.py | 2 +- datacube/index/memory/_products.py | 2 +- datacube/index/memory/_users.py | 2 +- datacube/index/memory/index.py | 2 +- datacube/index/null/__init__.py | 2 +- datacube/index/null/_datasets.py | 2 +- datacube/index/null/_metadata_types.py | 2 +- datacube/index/null/_products.py | 2 +- datacube/index/null/_users.py | 2 +- datacube/index/null/index.py | 2 +- datacube/index/postgis/__init__.py | 4 + datacube/index/postgis/_datasets.py | 2 +- datacube/index/postgis/_metadata_types.py | 2 +- datacube/index/postgis/_products.py | 2 +- datacube/index/postgis/_transaction.py | 2 +- datacube/index/postgis/_users.py | 2 +- datacube/index/postgis/index.py | 2 +- datacube/index/postgres/__init__.py | 4 + datacube/index/postgres/_datasets.py | 2 +- datacube/index/postgres/_metadata_types.py | 2 +- datacube/index/postgres/_products.py | 2 +- datacube/index/postgres/_transaction.py | 2 +- datacube/index/postgres/_users.py | 2 +- datacube/index/postgres/index.py | 2 +- datacube/model/__init__.py | 2 +- datacube/model/_base.py | 2 +- datacube/model/eo3.py | 2 +- datacube/model/fields.py | 2 +- datacube/model/utils.py | 2 +- datacube/scripts/__init__.py | 2 +- datacube/scripts/cli_app.py | 2 +- datacube/scripts/dataset.py | 2 +- datacube/scripts/ingest.py | 2 +- datacube/scripts/metadata.py | 2 +- datacube/scripts/product.py | 2 +- datacube/scripts/search_tool.py | 2 +- datacube/scripts/system.py | 2 +- datacube/scripts/user.py | 2 +- datacube/storage/__init__.py | 2 +- datacube/storage/_base.py | 2 +- datacube/storage/_hdf5.py | 2 +- datacube/storage/_load.py | 2 +- datacube/storage/_read.py | 2 +- datacube/storage/_rio.py | 2 +- datacube/testutils/__init__.py | 2 +- datacube/testutils/geom.py | 2 +- datacube/testutils/io.py | 2 +- datacube/testutils/iodriver.py | 2 +- datacube/testutils/threads.py | 2 +- datacube/ui/__init__.py | 2 +- datacube/ui/click.py | 2 +- datacube/ui/common.py | 2 +- datacube/ui/expression.py | 2 +- datacube/ui/task_app.py | 2 +- datacube/utils/__init__.py | 2 +- datacube/utils/_misc.py | 2 +- datacube/utils/aws/__init__.py | 2 +- datacube/utils/changes.py | 2 +- datacube/utils/cog.py | 2 +- datacube/utils/dask.py | 2 +- datacube/utils/dates.py | 2 +- datacube/utils/documents.py | 2 +- datacube/utils/generic.py | 2 +- datacube/utils/geometry/__init__.py | 2 +- datacube/utils/geometry/_base.py | 2 +- datacube/utils/geometry/_warp.py | 2 +- datacube/utils/geometry/gbox.py | 2 +- datacube/utils/geometry/tools.py | 2 +- datacube/utils/io.py | 2 +- datacube/utils/masking.py | 2 +- datacube/utils/math.py | 2 +- datacube/utils/py.py | 2 +- datacube/utils/rio/__init__.py | 2 +- datacube/utils/rio/_rio.py | 2 +- datacube/utils/serialise.py | 2 +- datacube/utils/uris.py | 2 +- datacube/utils/xarray_geoextensions.py | 2 +- datacube/virtual/__init__.py | 2 +- datacube/virtual/catalog.py | 2 +- datacube/virtual/expr.py | 4 + datacube/virtual/impl.py | 2 +- datacube/virtual/transformations.py | 2 +- datacube/virtual/utils.py | 2 +- docs/click_utils.py | 2 +- docs/conf.py | 2 +- .../ingester/ls5_nbar_albers.yaml | 66 +-- .../ingester/ls5_nbart_albers.yaml | 66 +-- .../ingester/ls5_pq_albers.yaml | 20 +- .../ingester/ls7_nbar_albers.yaml | 66 +-- .../ingester/ls7_nbart_albers.yaml | 66 +-- .../ingester/ls7_pq_albers.yaml | 20 +- .../ingester/ls8_nbar_albers.yaml | 66 +-- .../ingester/ls8_nbart_albers.yaml | 66 +-- .../ingester/ls8_pq_albers.yaml | 20 +- .../ingester/s2amsil1c_albers_10.yaml | 2 +- docs/make.bat | 20 +- examples/io_plugin/dcio_example/__init__.py | 2 +- examples/io_plugin/dcio_example/pickles.py | 2 +- examples/io_plugin/dcio_example/xarray_3d.py | 2 +- examples/io_plugin/dcio_example/zeros.py | 2 +- examples/io_plugin/setup.py | 2 +- integration_tests/__init__.py | 2 +- integration_tests/conftest.py | 2 +- integration_tests/data_utils.py | 2 +- integration_tests/index/__init__.py | 2 +- integration_tests/index/search_utils.py | 2 +- integration_tests/index/test_config_docs.py | 2 +- integration_tests/index/test_index_data.py | 2 +- integration_tests/index/test_memory_index.py | 2 +- integration_tests/index/test_null_index.py | 2 +- .../index/test_pluggable_indexes.py | 2 +- integration_tests/index/test_postgis_index.py | 2 +- integration_tests/index/test_search_eo3.py | 2 +- integration_tests/index/test_search_legacy.py | 2 +- .../index/test_update_columns.py | 2 +- integration_tests/test_3d.py | 2 +- integration_tests/test_cli_output.py | 2 +- integration_tests/test_config_tool.py | 2 +- integration_tests/test_dataset_add.py | 2 +- integration_tests/test_double_ingestion.py | 2 +- integration_tests/test_end_to_end.py | 2 +- integration_tests/test_environments.py | 2 +- integration_tests/test_full_ingestion.py | 2 +- .../test_index_datasets_search.py | 2 +- integration_tests/test_index_out_of_bound.py | 2 +- integration_tests/test_model.py | 2 +- integration_tests/test_validate_ingestion.py | 2 +- integration_tests/utils.py | 2 +- tests/__init__.py | 2 +- tests/api/__init__.py | 2 +- tests/api/test_core.py | 2 +- tests/api/test_grid_workflow.py | 2 +- tests/api/test_masking.py | 2 +- tests/api/test_query.py | 2 +- tests/api/test_virtual.py | 2 +- tests/conftest.py | 2 +- .../metadata.xml | 448 +++++++++--------- .../metadata.xml | 436 ++++++++--------- .../metadata.xml | 438 ++++++++--------- .../fail_drivers/dc_tests_io/__init__.py | 2 +- .../drivers/fail_drivers/dc_tests_io/dummy.py | 2 +- tests/drivers/fail_drivers/setup.py | 2 +- tests/drivers/test_rio_reader.py | 2 +- tests/index/__init__.py | 2 +- tests/index/test_api_index_dataset.py | 2 +- tests/index/test_fields.py | 2 +- tests/index/test_hl_index.py | 4 + tests/index/test_postgis_fields.py | 2 +- tests/index/test_query.py | 2 +- tests/index/test_validate_dataset_type.py | 2 +- tests/scripts/__init__.py | 2 +- tests/scripts/test_search_tool.py | 2 +- tests/storage/test_base.py | 2 +- tests/storage/test_netcdfwriter.py | 2 +- tests/storage/test_storage.py | 2 +- tests/storage/test_storage_load.py | 2 +- tests/storage/test_storage_read.py | 2 +- tests/test_3d.py | 2 +- tests/test_concurrent_executor.py | 2 +- tests/test_config.py | 2 +- tests/test_driver.py | 2 +- tests/test_dynamic_db_passwd.py | 2 +- tests/test_eo3.py | 2 +- tests/test_gbox_ops.py | 2 +- tests/test_geometry.py | 2 +- tests/test_load_data.py | 2 +- tests/test_metadata_fields.py | 2 +- tests/test_model.py | 2 +- tests/test_testutils.py | 2 +- tests/test_utils_aws.py | 2 +- tests/test_utils_changes.py | 2 +- tests/test_utils_cog.py | 2 +- tests/test_utils_dask.py | 2 +- tests/test_utils_dates.py | 2 +- tests/test_utils_docs.py | 2 +- tests/test_utils_generic.py | 2 +- tests/test_utils_other.py | 2 +- tests/test_utils_rio.py | 2 +- tests/test_warp.py | 2 +- tests/test_xarray_extension.py | 2 +- tests/ui/__init__.py | 2 +- tests/ui/test_common.py | 2 +- tests/ui/test_expression_parsing.py | 2 +- tests/ui/test_task_app.py | 2 +- 236 files changed, 1137 insertions(+), 1117 deletions(-) diff --git a/datacube/__init__.py b/datacube/__init__.py index 55b5d6924..342980430 100644 --- a/datacube/__init__.py +++ b/datacube/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Datacube diff --git a/datacube/__main__.py b/datacube/__main__.py index 8af90e515..543af0ede 100644 --- a/datacube/__main__.py +++ b/datacube/__main__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 if __name__ == "__main__": from .config import auto_config diff --git a/datacube/api/__init__.py b/datacube/api/__init__.py index 3154c299f..b7676523e 100644 --- a/datacube/api/__init__.py +++ b/datacube/api/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for the Storage and Access Query API diff --git a/datacube/api/core.py b/datacube/api/core.py index 8d9941995..47a8c1798 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2021 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import uuid import collections.abc diff --git a/datacube/api/grid_workflow.py b/datacube/api/grid_workflow.py index d61595b38..4634d2c85 100644 --- a/datacube/api/grid_workflow.py +++ b/datacube/api/grid_workflow.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import xarray diff --git a/datacube/api/query.py b/datacube/api/query.py index 5bf50f351..f82d9428d 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Storage Query and Access API module diff --git a/datacube/config.py b/datacube/config.py index 34933d2d3..6c901a8f3 100755 --- a/datacube/config.py +++ b/datacube/config.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ User configuration. diff --git a/datacube/drivers/__init__.py b/datacube/drivers/__init__.py index 454415002..0ab9b5178 100644 --- a/datacube/drivers/__init__.py +++ b/datacube/drivers/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This module implements a simple plugin manager for storage and index drivers. diff --git a/datacube/drivers/_tools.py b/datacube/drivers/_tools.py index d43e3db51..39ec17306 100644 --- a/datacube/drivers/_tools.py +++ b/datacube/drivers/_tools.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from threading import Lock from typing import Any diff --git a/datacube/drivers/_types.py b/datacube/drivers/_types.py index f46274399..7fdafadd1 100644 --- a/datacube/drivers/_types.py +++ b/datacube/drivers/_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Defines abstract types for IO drivers. """ diff --git a/datacube/drivers/datasource.py b/datacube/drivers/datasource.py index 7ad98e775..91e64a036 100644 --- a/datacube/drivers/datasource.py +++ b/datacube/drivers/datasource.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Defines abstract types for IO reader drivers. """ diff --git a/datacube/drivers/driver_cache.py b/datacube/drivers/driver_cache.py index 75c70b2bf..6324170f4 100644 --- a/datacube/drivers/driver_cache.py +++ b/datacube/drivers/driver_cache.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from typing import Dict, Any, Tuple, Iterable diff --git a/datacube/drivers/indexes.py b/datacube/drivers/indexes.py index 5c231b22c..1cda1d6e8 100644 --- a/datacube/drivers/indexes.py +++ b/datacube/drivers/indexes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List, Optional diff --git a/datacube/drivers/netcdf/__init__.py b/datacube/drivers/netcdf/__init__.py index b0148d4ca..9b0321f12 100644 --- a/datacube/drivers/netcdf/__init__.py +++ b/datacube/drivers/netcdf/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from ._write import write_dataset_to_netcdf, create_netcdf_storage_unit from . import writer as netcdf_writer diff --git a/datacube/drivers/netcdf/_safestrings.py b/datacube/drivers/netcdf/_safestrings.py index 7ac7630a3..ab58334ef 100644 --- a/datacube/drivers/netcdf/_safestrings.py +++ b/datacube/drivers/netcdf/_safestrings.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Provides `SafeStringsDataset`, a replacement netCDF4.Dataset class which works diff --git a/datacube/drivers/netcdf/_write.py b/datacube/drivers/netcdf/_write.py index 126dfb27d..702689cd9 100644 --- a/datacube/drivers/netcdf/_write.py +++ b/datacube/drivers/netcdf/_write.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from pathlib import Path import logging diff --git a/datacube/drivers/netcdf/driver.py b/datacube/drivers/netcdf/driver.py index 56910c40e..d68b04ab8 100644 --- a/datacube/drivers/netcdf/driver.py +++ b/datacube/drivers/netcdf/driver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from urllib.parse import urlsplit diff --git a/datacube/drivers/netcdf/writer.py b/datacube/drivers/netcdf/writer.py index 11bbbc6bc..5beadbe48 100644 --- a/datacube/drivers/netcdf/writer.py +++ b/datacube/drivers/netcdf/writer.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Create netCDF4 Storage Units and write data to them diff --git a/datacube/drivers/postgis/__init__.py b/datacube/drivers/postgis/__init__.py index 50348f828..a43833f90 100644 --- a/datacube/drivers/postgis/__init__.py +++ b/datacube/drivers/postgis/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Lower-level database access. diff --git a/datacube/drivers/postgis/_api.py b/datacube/drivers/postgis/_api.py index 052d5c6b9..df87318b6 100644 --- a/datacube/drivers/postgis/_api.py +++ b/datacube/drivers/postgis/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgis/_connections.py b/datacube/drivers/postgis/_connections.py index 6d189a65a..51810987a 100755 --- a/datacube/drivers/postgis/_connections.py +++ b/datacube/drivers/postgis/_connections.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgis/_core.py b/datacube/drivers/postgis/_core.py index b0922bd65..00870799a 100644 --- a/datacube/drivers/postgis/_core.py +++ b/datacube/drivers/postgis/_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core SQL schema settings. diff --git a/datacube/drivers/postgis/_fields.py b/datacube/drivers/postgis/_fields.py index c0066d414..8da248970 100755 --- a/datacube/drivers/postgis/_fields.py +++ b/datacube/drivers/postgis/_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Build and index fields within documents. diff --git a/datacube/drivers/postgis/_schema.py b/datacube/drivers/postgis/_schema.py index 1aa812e35..ed3643630 100644 --- a/datacube/drivers/postgis/_schema.py +++ b/datacube/drivers/postgis/_schema.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tables for indexing the datasets which were ingested into the AGDC. diff --git a/datacube/drivers/postgis/_spatial.py b/datacube/drivers/postgis/_spatial.py index 7481745a0..61395dc92 100644 --- a/datacube/drivers/postgis/_spatial.py +++ b/datacube/drivers/postgis/_spatial.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tracking spatial indexes diff --git a/datacube/drivers/postgis/sql.py b/datacube/drivers/postgis/sql.py index 321f97b9d..0d34e96e9 100644 --- a/datacube/drivers/postgis/sql.py +++ b/datacube/drivers/postgis/sql.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Custom types for postgres & sqlalchemy diff --git a/datacube/drivers/postgres/__init__.py b/datacube/drivers/postgres/__init__.py index 6fe10be3c..6f3e32134 100644 --- a/datacube/drivers/postgres/__init__.py +++ b/datacube/drivers/postgres/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Lower-level database access. diff --git a/datacube/drivers/postgres/_api.py b/datacube/drivers/postgres/_api.py index f0d19e992..5da9d1bd3 100644 --- a/datacube/drivers/postgres/_api.py +++ b/datacube/drivers/postgres/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgres/_connections.py b/datacube/drivers/postgres/_connections.py index 68f5d080a..003617db3 100755 --- a/datacube/drivers/postgres/_connections.py +++ b/datacube/drivers/postgres/_connections.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgres/_core.py b/datacube/drivers/postgres/_core.py index 52cd92696..61220ebe2 100644 --- a/datacube/drivers/postgres/_core.py +++ b/datacube/drivers/postgres/_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core SQL schema settings. diff --git a/datacube/drivers/postgres/_dynamic.py b/datacube/drivers/postgres/_dynamic.py index d7401038b..8c0765bad 100644 --- a/datacube/drivers/postgres/_dynamic.py +++ b/datacube/drivers/postgres/_dynamic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Methods for managing dynamic dataset field indexes and views. diff --git a/datacube/drivers/postgres/_fields.py b/datacube/drivers/postgres/_fields.py index 8e58bb020..4bf682562 100755 --- a/datacube/drivers/postgres/_fields.py +++ b/datacube/drivers/postgres/_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Build and index fields within documents. diff --git a/datacube/drivers/postgres/_schema.py b/datacube/drivers/postgres/_schema.py index 56bedf3ed..d555bb6b4 100644 --- a/datacube/drivers/postgres/_schema.py +++ b/datacube/drivers/postgres/_schema.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tables for indexing the datasets which were ingested into the AGDC. diff --git a/datacube/drivers/postgres/sql.py b/datacube/drivers/postgres/sql.py index 5801d95ed..6d9679ac8 100644 --- a/datacube/drivers/postgres/sql.py +++ b/datacube/drivers/postgres/sql.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Custom types for postgres & sqlalchemy diff --git a/datacube/drivers/readers.py b/datacube/drivers/readers.py index 16d0f9e20..230e63b4c 100644 --- a/datacube/drivers/readers.py +++ b/datacube/drivers/readers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List, Optional, Callable from .driver_cache import load_drivers diff --git a/datacube/drivers/rio/__init__.py b/datacube/drivers/rio/__init__.py index b74ea31e5..f41dba762 100644 --- a/datacube/drivers/rio/__init__.py +++ b/datacube/drivers/rio/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ RasterIO based driver """ diff --git a/datacube/drivers/rio/_reader.py b/datacube/drivers/rio/_reader.py index fe99c641b..6950c06fc 100644 --- a/datacube/drivers/rio/_reader.py +++ b/datacube/drivers/rio/_reader.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ reader """ diff --git a/datacube/drivers/writers.py b/datacube/drivers/writers.py index 970eede77..cc652c409 100644 --- a/datacube/drivers/writers.py +++ b/datacube/drivers/writers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List diff --git a/datacube/execution/__init__.py b/datacube/execution/__init__.py index c081ad5b4..45970a2a5 100644 --- a/datacube/execution/__init__.py +++ b/datacube/execution/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/execution/worker.py b/datacube/execution/worker.py index 9cd20faf6..cacc97e64 100644 --- a/datacube/execution/worker.py +++ b/datacube/execution/worker.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This app launches workers for distributed work loads diff --git a/datacube/executor.py b/datacube/executor.py index 9a86b2897..b39b03f62 100644 --- a/datacube/executor.py +++ b/datacube/executor.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # # type: ignore diff --git a/datacube/helpers.py b/datacube/helpers.py index cf9c3da5b..6f397fadf 100644 --- a/datacube/helpers.py +++ b/datacube/helpers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Useful functions for Datacube users diff --git a/datacube/index/__init__.py b/datacube/index/__init__.py index 1c4050cf5..c6a25f301 100644 --- a/datacube/index/__init__.py +++ b/datacube/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for interfacing with the index/database. diff --git a/datacube/index/_api.py b/datacube/index/_api.py index abd7e4c65..2ffb67919 100644 --- a/datacube/index/_api.py +++ b/datacube/index/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Access methods for indexing datasets & products. diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 69a6842bf..75124f9ba 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import logging diff --git a/datacube/index/eo3.py b/datacube/index/eo3.py index c393c318f..a5fcc3928 100644 --- a/datacube/index/eo3.py +++ b/datacube/index/eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # # type: ignore diff --git a/datacube/index/exceptions.py b/datacube/index/exceptions.py index e75a4135a..c12344c49 100644 --- a/datacube/index/exceptions.py +++ b/datacube/index/exceptions.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/fields.py b/datacube/index/fields.py index 5c2c0fe7f..726c2cae6 100644 --- a/datacube/index/fields.py +++ b/datacube/index/fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common datatypes for DB drivers. diff --git a/datacube/index/hl.py b/datacube/index/hl.py index e30d02d66..75a89f5d5 100644 --- a/datacube/index/hl.py +++ b/datacube/index/hl.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ High level indexing operations/utilities diff --git a/datacube/index/memory/__init__.py b/datacube/index/memory/__init__.py index bea658a52..7bd83106e 100644 --- a/datacube/index/memory/__init__.py +++ b/datacube/index/memory/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/datacube/index/memory/_datasets.py b/datacube/index/memory/_datasets.py index fafb628a5..4ea1fc6e2 100755 --- a/datacube/index/memory/_datasets.py +++ b/datacube/index/memory/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import logging diff --git a/datacube/index/memory/_fields.py b/datacube/index/memory/_fields.py index 1ad6a7cd6..ba60fc7f3 100644 --- a/datacube/index/memory/_fields.py +++ b/datacube/index/memory/_fields.py @@ -1,3 +1,7 @@ +# This file is part of the Open Data Cube, see https://opendatacube.org for more information +# +# Copyright (c) 2015-2023 ODC Contributors +# SPDX-License-Identifier: Apache-2.0 from typing import Any, Mapping, MutableMapping from datacube.model.fields import SimpleField, Field, get_dataset_fields as generic_get_dataset_fields from datacube.index.abstract import Offset diff --git a/datacube/index/memory/_metadata_types.py b/datacube/index/memory/_metadata_types.py index 83501d7cb..8f22ecced 100644 --- a/datacube/index/memory/_metadata_types.py +++ b/datacube/index/memory/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from copy import deepcopy diff --git a/datacube/index/memory/_products.py b/datacube/index/memory/_products.py index bad72c537..75b953920 100644 --- a/datacube/index/memory/_products.py +++ b/datacube/index/memory/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/memory/_users.py b/datacube/index/memory/_users.py index 6e597853f..579829af5 100644 --- a/datacube/index/memory/_users.py +++ b/datacube/index/memory/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/memory/index.py b/datacube/index/memory/index.py index 1a1385ffe..f2baaec80 100644 --- a/datacube/index/memory/index.py +++ b/datacube/index/memory/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from threading import Lock diff --git a/datacube/index/null/__init__.py b/datacube/index/null/__init__.py index bea658a52..7bd83106e 100644 --- a/datacube/index/null/__init__.py +++ b/datacube/index/null/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/datacube/index/null/_datasets.py b/datacube/index/null/_datasets.py index f8686669d..062ebae65 100755 --- a/datacube/index/null/_datasets.py +++ b/datacube/index/null/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.index.abstract import AbstractDatasetResource, DSID diff --git a/datacube/index/null/_metadata_types.py b/datacube/index/null/_metadata_types.py index 3346203f4..808c18ee1 100644 --- a/datacube/index/null/_metadata_types.py +++ b/datacube/index/null/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.index.abstract import AbstractMetadataTypeResource diff --git a/datacube/index/null/_products.py b/datacube/index/null/_products.py index 6e4f3892b..7b3f1645e 100644 --- a/datacube/index/null/_products.py +++ b/datacube/index/null/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/null/_users.py b/datacube/index/null/_users.py index cc91c5dae..e058c5158 100644 --- a/datacube/index/null/_users.py +++ b/datacube/index/null/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/null/index.py b/datacube/index/null/index.py index a678f7998..7921f9f11 100644 --- a/datacube/index/null/index.py +++ b/datacube/index/null/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgis/__init__.py b/datacube/index/postgis/__init__.py index e69de29bb..45970a2a5 100644 --- a/datacube/index/postgis/__init__.py +++ b/datacube/index/postgis/__init__.py @@ -0,0 +1,4 @@ +# This file is part of the Open Data Cube, see https://opendatacube.org for more information +# +# Copyright (c) 2015-2023 ODC Contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 8a7fdcb9e..730c94220 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ API for dataset indexing, access and search. diff --git a/datacube/index/postgis/_metadata_types.py b/datacube/index/postgis/_metadata_types.py index 9c593f9b9..7e9593552 100644 --- a/datacube/index/postgis/_metadata_types.py +++ b/datacube/index/postgis/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from time import monotonic diff --git a/datacube/index/postgis/_products.py b/datacube/index/postgis/_products.py index 7e3cd86b7..7f50a218b 100644 --- a/datacube/index/postgis/_products.py +++ b/datacube/index/postgis/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgis/_transaction.py b/datacube/index/postgis/_transaction.py index 3fabcccb5..ac48a64f8 100644 --- a/datacube/index/postgis/_transaction.py +++ b/datacube/index/postgis/_transaction.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/datacube/index/postgis/_users.py b/datacube/index/postgis/_users.py index c604a676b..ca2c6ec3a 100644 --- a/datacube/index/postgis/_users.py +++ b/datacube/index/postgis/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/postgis/index.py b/datacube/index/postgis/index.py index 840023675..2b6b1764b 100644 --- a/datacube/index/postgis/index.py +++ b/datacube/index/postgis/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from contextlib import contextmanager diff --git a/datacube/index/postgres/__init__.py b/datacube/index/postgres/__init__.py index e69de29bb..45970a2a5 100644 --- a/datacube/index/postgres/__init__.py +++ b/datacube/index/postgres/__init__.py @@ -0,0 +1,4 @@ +# This file is part of the Open Data Cube, see https://opendatacube.org for more information +# +# Copyright (c) 2015-2023 ODC Contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 0df96cc7d..ac1a385f7 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ API for dataset indexing, access and search. diff --git a/datacube/index/postgres/_metadata_types.py b/datacube/index/postgres/_metadata_types.py index 4c1f4193f..de77c23c1 100644 --- a/datacube/index/postgres/_metadata_types.py +++ b/datacube/index/postgres/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgres/_products.py b/datacube/index/postgres/_products.py index a639f6336..e3b7fd773 100644 --- a/datacube/index/postgres/_products.py +++ b/datacube/index/postgres/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgres/_transaction.py b/datacube/index/postgres/_transaction.py index 5b02a5584..6ff35d942 100644 --- a/datacube/index/postgres/_transaction.py +++ b/datacube/index/postgres/_transaction.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/datacube/index/postgres/_users.py b/datacube/index/postgres/_users.py index 2dd4c8187..eeb76c4fc 100644 --- a/datacube/index/postgres/_users.py +++ b/datacube/index/postgres/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/postgres/index.py b/datacube/index/postgres/index.py index d56016bf6..49ebdd362 100644 --- a/datacube/index/postgres/index.py +++ b/datacube/index/postgres/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from contextlib import contextmanager diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 03352eecc..950477d34 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core classes used across modules. diff --git a/datacube/model/_base.py b/datacube/model/_base.py index 5223c18fa..3a004f763 100644 --- a/datacube/model/_base.py +++ b/datacube/model/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import namedtuple diff --git a/datacube/model/eo3.py b/datacube/model/eo3.py index 668f8c44c..8897ea933 100644 --- a/datacube/model/eo3.py +++ b/datacube/model/eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.utils.documents import InvalidDocException diff --git a/datacube/model/fields.py b/datacube/model/fields.py index 3f94ff280..48826eb01 100644 --- a/datacube/model/fields.py +++ b/datacube/model/fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """Non-db specific implementation of metadata search fields. diff --git a/datacube/model/utils.py b/datacube/model/utils.py index 90746d519..6f0b66190 100644 --- a/datacube/model/utils.py +++ b/datacube/model/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import os diff --git a/datacube/scripts/__init__.py b/datacube/scripts/__init__.py index c081ad5b4..45970a2a5 100644 --- a/datacube/scripts/__init__.py +++ b/datacube/scripts/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/scripts/cli_app.py b/datacube/scripts/cli_app.py index 820d8179e..6ab3901b2 100644 --- a/datacube/scripts/cli_app.py +++ b/datacube/scripts/cli_app.py @@ -2,7 +2,7 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Datacube command-line interface diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index 2a318a854..025a252cf 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import datetime diff --git a/datacube/scripts/ingest.py b/datacube/scripts/ingest.py index 184edacc6..e504a8a4e 100644 --- a/datacube/scripts/ingest.py +++ b/datacube/scripts/ingest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import time import logging diff --git a/datacube/scripts/metadata.py b/datacube/scripts/metadata.py index a4efd7f80..3b3f735c1 100644 --- a/datacube/scripts/metadata.py +++ b/datacube/scripts/metadata.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import json import logging diff --git a/datacube/scripts/product.py b/datacube/scripts/product.py index 294291493..ef9095fb3 100644 --- a/datacube/scripts/product.py +++ b/datacube/scripts/product.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import json diff --git a/datacube/scripts/search_tool.py b/datacube/scripts/search_tool.py index aba233bb2..25acc2204 100755 --- a/datacube/scripts/search_tool.py +++ b/datacube/scripts/search_tool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Query datasets. diff --git a/datacube/scripts/system.py b/datacube/scripts/system.py index dfece6f82..f0a07ef45 100644 --- a/datacube/scripts/system.py +++ b/datacube/scripts/system.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/scripts/user.py b/datacube/scripts/user.py index 1c3b8ba5b..6f4f08418 100644 --- a/datacube/scripts/user.py +++ b/datacube/scripts/user.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import click diff --git a/datacube/storage/__init__.py b/datacube/storage/__init__.py index 689d71de6..64388c59c 100644 --- a/datacube/storage/__init__.py +++ b/datacube/storage/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for creating and accessing Data Store Units diff --git a/datacube/storage/_base.py b/datacube/storage/_base.py index a7da9a165..48508528b 100644 --- a/datacube/storage/_base.py +++ b/datacube/storage/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Optional, Dict, Any, Tuple, Callable from urllib.parse import urlparse diff --git a/datacube/storage/_hdf5.py b/datacube/storage/_hdf5.py index 4029c054e..2b9a28983 100644 --- a/datacube/storage/_hdf5.py +++ b/datacube/storage/_hdf5.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from threading import RLock HDF5_LOCK = RLock() diff --git a/datacube/storage/_load.py b/datacube/storage/_load.py index 8f071641f..4ee9a53c2 100644 --- a/datacube/storage/_load.py +++ b/datacube/storage/_load.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Important functions are: diff --git a/datacube/storage/_read.py b/datacube/storage/_read.py index b3e8ae88e..c88aaeded 100644 --- a/datacube/storage/_read.py +++ b/datacube/storage/_read.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Dataset -> Raster """ diff --git a/datacube/storage/_rio.py b/datacube/storage/_rio.py index 8f3e0a3bd..d777874a9 100644 --- a/datacube/storage/_rio.py +++ b/datacube/storage/_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Driver implementation for Rasterio based reader. diff --git a/datacube/testutils/__init__.py b/datacube/testutils/__init__.py index 097e89c58..0ffbdfd22 100644 --- a/datacube/testutils/__init__.py +++ b/datacube/testutils/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Useful methods for tests (particularly: reading/writing and checking files) diff --git a/datacube/testutils/geom.py b/datacube/testutils/geom.py index 06b51e7f4..5e1ae2d99 100644 --- a/datacube/testutils/geom.py +++ b/datacube/testutils/geom.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np from affine import Affine diff --git a/datacube/testutils/io.py b/datacube/testutils/io.py index 614dc2da2..9ae46ca37 100644 --- a/datacube/testutils/io.py +++ b/datacube/testutils/io.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import toolz diff --git a/datacube/testutils/iodriver.py b/datacube/testutils/iodriver.py index 4accaadb5..87b204673 100644 --- a/datacube/testutils/iodriver.py +++ b/datacube/testutils/iodriver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Reader driver construction for tests """ diff --git a/datacube/testutils/threads.py b/datacube/testutils/threads.py index 17ae13bba..22bb22aeb 100644 --- a/datacube/testutils/threads.py +++ b/datacube/testutils/threads.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ threads related stuff """ diff --git a/datacube/ui/__init__.py b/datacube/ui/__init__.py index 51ff28cf4..08fd367b1 100644 --- a/datacube/ui/__init__.py +++ b/datacube/ui/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ User Interface Utilities diff --git a/datacube/ui/click.py b/datacube/ui/click.py index c339b8ae8..d2988989a 100644 --- a/datacube/ui/click.py +++ b/datacube/ui/click.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common functions for click-based cli scripts. diff --git a/datacube/ui/common.py b/datacube/ui/common.py index 33bc5933c..7e8c67d28 100644 --- a/datacube/ui/common.py +++ b/datacube/ui/common.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common methods for UI code. diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index c36963162..e16829913 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Search expression parsing for command line applications. diff --git a/datacube/ui/task_app.py b/datacube/ui/task_app.py index 789ebddb4..2077b1d3e 100644 --- a/datacube/ui/task_app.py +++ b/datacube/ui/task_app.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import os diff --git a/datacube/utils/__init__.py b/datacube/utils/__init__.py index 8a6e2c7ef..571ca321d 100644 --- a/datacube/utils/__init__.py +++ b/datacube/utils/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utility functions diff --git a/datacube/utils/_misc.py b/datacube/utils/_misc.py index b797b3ace..65ca45155 100644 --- a/datacube/utils/_misc.py +++ b/datacube/utils/_misc.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utility functions diff --git a/datacube/utils/aws/__init__.py b/datacube/utils/aws/__init__.py index 48e36676a..a5061f7a7 100644 --- a/datacube/utils/aws/__init__.py +++ b/datacube/utils/aws/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Helper methods for working with AWS diff --git a/datacube/utils/changes.py b/datacube/utils/changes.py index 74654de8b..5e1cb0341 100644 --- a/datacube/utils/changes.py +++ b/datacube/utils/changes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Validation of document/dictionary changes. diff --git a/datacube/utils/cog.py b/datacube/utils/cog.py index 9a4bd77ae..cb424fd98 100644 --- a/datacube/utils/cog.py +++ b/datacube/utils/cog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import warnings import toolz # type: ignore[import] diff --git a/datacube/utils/dask.py b/datacube/utils/dask.py index 9bb51a076..e7affe0d1 100644 --- a/datacube/utils/dask.py +++ b/datacube/utils/dask.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Dask Distributed Tools diff --git a/datacube/utils/dates.py b/datacube/utils/dates.py index fb6671106..293bbf38c 100644 --- a/datacube/utils/dates.py +++ b/datacube/utils/dates.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Date and time utility functions diff --git a/datacube/utils/documents.py b/datacube/utils/documents.py index 1fe872412..874a1f213 100644 --- a/datacube/utils/documents.py +++ b/datacube/utils/documents.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Functions for working with YAML documents and configurations diff --git a/datacube/utils/generic.py b/datacube/utils/generic.py index 327a10f9d..0bef2fd97 100644 --- a/datacube/utils/generic.py +++ b/datacube/utils/generic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import itertools import threading diff --git a/datacube/utils/geometry/__init__.py b/datacube/utils/geometry/__init__.py index dbe6a6110..60e5c8cdc 100644 --- a/datacube/utils/geometry/__init__.py +++ b/datacube/utils/geometry/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Geometric shapes and operations on them """ diff --git a/datacube/utils/geometry/_base.py b/datacube/utils/geometry/_base.py index 7647ca966..a0cc2a4c9 100644 --- a/datacube/utils/geometry/_base.py +++ b/datacube/utils/geometry/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import functools import itertools diff --git a/datacube/utils/geometry/_warp.py b/datacube/utils/geometry/_warp.py index bbe2862a9..c2e2e6e67 100644 --- a/datacube/utils/geometry/_warp.py +++ b/datacube/utils/geometry/_warp.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Union, Optional import rasterio.warp # type: ignore[import] diff --git a/datacube/utils/geometry/gbox.py b/datacube/utils/geometry/gbox.py index 11a2efab6..723d9a7e9 100644 --- a/datacube/utils/geometry/gbox.py +++ b/datacube/utils/geometry/gbox.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Geometric operations on GeoBox class """ diff --git a/datacube/utils/geometry/tools.py b/datacube/utils/geometry/tools.py index e6dc8516e..96bf31fd1 100644 --- a/datacube/utils/geometry/tools.py +++ b/datacube/utils/geometry/tools.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import collections.abc diff --git a/datacube/utils/io.py b/datacube/utils/io.py index 85d50eb64..983ffeeb2 100644 --- a/datacube/utils/io.py +++ b/datacube/utils/io.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os from pathlib import Path diff --git a/datacube/utils/masking.py b/datacube/utils/masking.py index 3c35b1c42..5a6c8467b 100644 --- a/datacube/utils/masking.py +++ b/datacube/utils/masking.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tools for masking data based on a bit-mask variable with attached definition. diff --git a/datacube/utils/math.py b/datacube/utils/math.py index 037845085..002d18ee5 100644 --- a/datacube/utils/math.py +++ b/datacube/utils/math.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Tuple, Union, Optional, Any, cast from math import ceil, fmod diff --git a/datacube/utils/py.py b/datacube/utils/py.py index 456f24bf2..f869c8052 100644 --- a/datacube/utils/py.py +++ b/datacube/utils/py.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import importlib import logging diff --git a/datacube/utils/rio/__init__.py b/datacube/utils/rio/__init__.py index 55db80b77..93128d0c2 100644 --- a/datacube/utils/rio/__init__.py +++ b/datacube/utils/rio/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This will move into IO driver eventually. diff --git a/datacube/utils/rio/_rio.py b/datacube/utils/rio/_rio.py index 5d176bbdc..04b2e9c7f 100644 --- a/datacube/utils/rio/_rio.py +++ b/datacube/utils/rio/_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ rasterio environment management tools """ diff --git a/datacube/utils/serialise.py b/datacube/utils/serialise.py index 8f180d7a2..8306bc038 100644 --- a/datacube/utils/serialise.py +++ b/datacube/utils/serialise.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Serialise function used in YAML output diff --git a/datacube/utils/uris.py b/datacube/utils/uris.py index 0abfb6cde..7644e954a 100644 --- a/datacube/utils/uris.py +++ b/datacube/utils/uris.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os diff --git a/datacube/utils/xarray_geoextensions.py b/datacube/utils/xarray_geoextensions.py index 3a671f52f..4f41981a8 100644 --- a/datacube/utils/xarray_geoextensions.py +++ b/datacube/utils/xarray_geoextensions.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Add geometric extensions to :class:`xarray.Dataset` and :class:`xarray.DataArray` for use diff --git a/datacube/virtual/__init__.py b/datacube/virtual/__init__.py index 16793105c..b3de02351 100644 --- a/datacube/virtual/__init__.py +++ b/datacube/virtual/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Mapping, Any, cast import copy diff --git a/datacube/virtual/catalog.py b/datacube/virtual/catalog.py index aab900604..e908367ee 100644 --- a/datacube/virtual/catalog.py +++ b/datacube/virtual/catalog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Catalog of virtual products. diff --git a/datacube/virtual/expr.py b/datacube/virtual/expr.py index 23fb9f08a..e19894ac7 100644 --- a/datacube/virtual/expr.py +++ b/datacube/virtual/expr.py @@ -1,3 +1,7 @@ +# This file is part of the Open Data Cube, see https://opendatacube.org for more information +# +# Copyright (c) 2015-2023 ODC Contributors +# SPDX-License-Identifier: Apache-2.0 import lark import numpy diff --git a/datacube/virtual/impl.py b/datacube/virtual/impl.py index 6a7bb23e3..dc7df703e 100644 --- a/datacube/virtual/impl.py +++ b/datacube/virtual/impl.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Implementation of virtual products. Provides an interface for the products in the datacube diff --git a/datacube/virtual/transformations.py b/datacube/virtual/transformations.py index 9c929eacc..2aeaf64b6 100644 --- a/datacube/virtual/transformations.py +++ b/datacube/virtual/transformations.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Optional, Collection import warnings diff --git a/datacube/virtual/utils.py b/datacube/virtual/utils.py index 27cb0acad..c26a7bde8 100644 --- a/datacube/virtual/utils.py +++ b/datacube/virtual/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utilities to facilitate virtual product implementation. """ diff --git a/docs/click_utils.py b/docs/click_utils.py index 0b658d8ee..755848089 100644 --- a/docs/click_utils.py +++ b/docs/click_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pkg_resources from docutils.nodes import literal_block, section, title, make_id diff --git a/docs/conf.py b/docs/conf.py index bbcbec7b1..3b63a7cdb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os import sys diff --git a/docs/config_samples/ingester/ls5_nbar_albers.yaml b/docs/config_samples/ingester/ls5_nbar_albers.yaml index 42cfc4412..d37747848 100644 --- a/docs/config_samples/ingester/ls5_nbar_albers.yaml +++ b/docs/config_samples/ingester/ls5_nbar_albers.yaml @@ -39,10 +39,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -57,11 +57,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-N_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: TM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -77,30 +77,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+ 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls5_nbart_albers.yaml b/docs/config_samples/ingester/ls5_nbart_albers.yaml index 89d942cb8..a848a6af7 100644 --- a/docs/config_samples/ingester/ls5_nbart_albers.yaml +++ b/docs/config_samples/ingester/ls5_nbart_albers.yaml @@ -45,10 +45,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -63,11 +63,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-NT_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: TM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -83,30 +83,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+T 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls5_pq_albers.yaml b/docs/config_samples/ingester/ls5_pq_albers.yaml index 491f30b02..4eda5e55e 100644 --- a/docs/config_samples/ingester/ls5_pq_albers.yaml +++ b/docs/config_samples/ingester/ls5_pq_albers.yaml @@ -19,11 +19,11 @@ global_attributes: or only sea pixels depending on their analytical requirements, leading to enhanced computationally efficient. PQ provides an assessment of the quality of observations at a pixel level and includes information about: - - Spectral Contiguity (lack of signal in any band) - - Saturation in any band - - Presence of cloud - - Presence of cloud shadow - - Land or sea + - Spectral Contiguity (lack of signal in any band) + - Saturation in any band + - Presence of cloud + - Presence of cloud shadow + - Land or sea As Landsat Imagery becomes more readily available, there has been a rapid increase in the amount of analyses undertaken by researchers around the globe. Most researchers use some form of quality masking schema in order to remove undesirable @@ -43,11 +43,11 @@ global_attributes: with OLI data, however it uses additional algorithms to detect cloud and cloud shadow, and is available for Landsat 5, 7 and 8. source: PQ_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: TM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,ETM+,TM,OLI,EARTH SCIENCE diff --git a/docs/config_samples/ingester/ls7_nbar_albers.yaml b/docs/config_samples/ingester/ls7_nbar_albers.yaml index 3ddba8543..fd3aa6b9e 100644 --- a/docs/config_samples/ingester/ls7_nbar_albers.yaml +++ b/docs/config_samples/ingester/ls7_nbar_albers.yaml @@ -39,10 +39,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -57,11 +57,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-N_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: ETM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -77,30 +77,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+ 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls7_nbart_albers.yaml b/docs/config_samples/ingester/ls7_nbart_albers.yaml index 136927ad2..0f31c9100 100644 --- a/docs/config_samples/ingester/ls7_nbart_albers.yaml +++ b/docs/config_samples/ingester/ls7_nbart_albers.yaml @@ -45,10 +45,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -63,11 +63,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-NT_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: ETM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -83,30 +83,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+T 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls7_pq_albers.yaml b/docs/config_samples/ingester/ls7_pq_albers.yaml index 02b4a4bde..ecd6c2dee 100644 --- a/docs/config_samples/ingester/ls7_pq_albers.yaml +++ b/docs/config_samples/ingester/ls7_pq_albers.yaml @@ -19,11 +19,11 @@ global_attributes: or only sea pixels depending on their analytical requirements, leading to enhanced computationally efficient. PQ provides an assessment of the quality of observations at a pixel level and includes information about: - - Spectral Contiguity (lack of signal in any band) - - Saturation in any band - - Presence of cloud - - Presence of cloud shadow - - Land or sea + - Spectral Contiguity (lack of signal in any band) + - Saturation in any band + - Presence of cloud + - Presence of cloud shadow + - Land or sea As Landsat Imagery becomes more readily available, there has been a rapid increase in the amount of analyses undertaken by researchers around the globe. Most researchers use some form of quality masking schema in order to remove undesirable @@ -43,11 +43,11 @@ global_attributes: with OLI data, however it uses additional algorithms to detect cloud and cloud shadow, and is available for Landsat 5, 7 and 8. source: PQ_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: ETM keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,ETM+,TM,OLI,EARTH SCIENCE diff --git a/docs/config_samples/ingester/ls8_nbar_albers.yaml b/docs/config_samples/ingester/ls8_nbar_albers.yaml index 313900779..0e7b6205f 100644 --- a/docs/config_samples/ingester/ls8_nbar_albers.yaml +++ b/docs/config_samples/ingester/ls8_nbar_albers.yaml @@ -39,10 +39,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -57,11 +57,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-N_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: OLI keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -77,30 +77,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+ 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls8_nbart_albers.yaml b/docs/config_samples/ingester/ls8_nbart_albers.yaml index 42899fd4c..27f22bf8c 100644 --- a/docs/config_samples/ingester/ls8_nbart_albers.yaml +++ b/docs/config_samples/ingester/ls8_nbart_albers.yaml @@ -45,10 +45,10 @@ global_attributes: Surface Reflectance Correction Models Image radiance values recorded by passive EO sensors are a composite of - - surface reflectance; - - atmospheric condition; - - interaction between surface land cover, solar radiation and sensor view angle; - - land surface orientation relative to the imaging sensor. + - surface reflectance; + - atmospheric condition; + - interaction between surface land cover, solar radiation and sensor view angle; + - land surface orientation relative to the imaging sensor. It has been traditionally assumed that Landsat imagery display negligible variation in sun and sensor view angles, however these can vary significantly both within and between scenes, especially in different seasons and geographic regions (Li et al., 2012). The SR product delivers modeled surface reflectance from Landsat TM/ETM+/OLI @@ -63,11 +63,11 @@ global_attributes: Given the growing time series of EO imagery, this landmark facility will streamline the process of reliably monitoring long-term chnges in land and water resources. source: SR-NT_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: OLI keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,REFLECTANCE,ETM+,TM,OLI,EARTH SCIENCE @@ -83,30 +83,30 @@ global_attributes: product_suite: Surface Reflectance NBAR+T 25m acknowledgment: Landsat data is provided by the United States Geological Survey (USGS) through direct reception of the data at Geoscience Australias satellite reception facility or download. references: | - - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. - - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. - - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. - - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. - - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. - - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. - - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. - - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. - - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. - - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. - - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. - - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. - - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. - - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. - - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. - - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. - - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf - - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. - - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. - - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. - - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. - - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. - - http://dx.doi.org/10.4225/25/5487CC0D4F40B - - http://dx.doi.org/10.1109/JSTARS.2010.2042281 + - Berk, A., Anderson, G.P., Acharya, P.K., Hoke, M.L., Chetwynd, J.H., Bernstein, L.S., Shettle, E.P., Matthew, M.W., and Adler-Golden, S.M. (2003) Modtran 4 Version 3 Revision 1 User s manual. Airforce Research Laboratory, Hanscom, MA, USA. + - Chander, G., Markham, B.L., and Helder, D.L. (2009) Summary of current radiometric calibration coefficients for Landsat MSS, TM, ETM+, and EO-1 ALI sensors. Remote Sensing of Environment 113, 893-903. + - Edberg, R., and Oliver, S. (2013) Projection-Independent Earth-Solar-Sensor Geometry for Surface Reflectance Correction. Submitted to IGARSS 2013, Melbourne. + - GA and CSIRO (2010) 1 second SRTM Derived Digital Elevation Models User Guide. Version 1.03. GA, Canberra. + - Forrest, R.B. (1981) Simulation of orbital image-sensor geometry, Photogrammetric Engineering and Remote Sensing 47, 1187-93. + - Irish, R. (2000) Landsat 7 Automatic Cloud Cover Assessment, sourced: http://landsathandbook.gsfc.nasa.gov/pdfs/ACCA_SPIE_paper.pdf, last accessed 12/11/2012. + - Irish, R.R., Barker, J.L., Goward, S.N., Arvidson, T. (2006) Characterization of the Landsat-7 ETM+ Automated Cloud -Cover Assessment (ACCA) Algorithm, Photogrammetric Engineering & Remote Sensing 72 (10), 1179-88. + - Irons, J.R., Dwyer, J.L., and Barsi, J.A. (2012) The next Landsat satellite: The Landsat Data Continuity Mission. Remote Sensing of Environment (2012), doi:10.1016/j.rse.2011.08.026. + - Kalnay, E. Kanamitsu, M., Kistler, R., Collins, W., Deaven, D., Gandin, L., Iredell, M., Saha, S., White, G., Woollen, J., Zhu, Y., Chelliah, M., Ebisuzaki, W., Higgins, W., Janowiak, J., Mo, K.C., Ropelewski, C., Wang, J., Leetmaa, A., Reynolds, R. Jenne, R., Joseph, D. (1996) The NCEP/NCAR 40-Year Reanalysis Project. Bulletin of the American Meteorological Society 77, 437-71. + - Li, F., Jupp, D.L.B., Reddy, S., Lymburner, L., Mueller, N., Tan, P., and Islam, A. (2010) An Evaluation of the Use of Atmospheric and BRDF Correction to Standardize Landsat Data. IEEE J. Selected Topics in Applied Earth Observations and Remote Sensing 3, 257-70. + - Li, F. (2010) ARG25 Algorithm Theoretical Basis Document. GA, Canberra. + - Li, F., Jupp, D.L.B., Thankappan, M., Lymburner, L., Mueller, N., Lewis, A., and Held, A. (2012) A physics-based atmopheric and BRDF correction for Landsat data over mountainous terrain. Remote Sensing of Environment 124, 756-70. + - Lubke, M. (2012) Landsat Geometry Calibration/Validation Update. Presentation at LTWG #21, 25 September 2012, Sioux Falls. USGS, USA. + - OGC (2006) OpenGIS Web Map Server Implementation Specification (Ed: Jeff de la Beaujardiere) Ref. OGC 06-042. + - OGC (2010) OGC WCS 2.0 Interface Standard - Core. (Ed: Peter Baumann) Ref. OGC 09-110r3. + - OGC (2013) CF-netCDF3 Data Model Extension Standard (Eds: Ben Domenico and Stefano Nativi) Ref. OGC 11-165r2. + - Strahler, A.H., and Muller, J.-P. (1999) MODIS BRDF/Albedo Product: Algorithm Theoretical Basis Document Version 5.0. http://modis.gsfc.nasa.gov/data/atbd/atbd_mod09.pdf + - TM World Borders vector file: http://thematicmapping.org/downloads/world_borders.php. + - USGS (2012a) Landsat Thematic Mapper (TM) Level 1 (L1) Data Format Control Book (DFCB). LS-DFCB-20 Version 4.0. USGS, USA. http://landsat.usgs.gov/documents/LS-DFCB-20.pdf. + - USGS (2012b) Landsat 7 ETM+ Level 1 Product Data Format Control Book (DFCB). LS-DFCB-04 Version 15.0. http://landsat.usgs.gov/documents/LS-DFCB-04.pdf. + - Vincenty, T. (1975) Direct and Inverse Solutions of Geodesies on the Ellipsoid with Application of Nested Equations. Survey Review 23, 88-93. + - Zhu, Z. and Woodcock, C. E. (2012) Object-based cloud and cloud shadow detection in Landsat imagery. Remote Sensing of Environment 118, 83-94. + - http://dx.doi.org/10.4225/25/5487CC0D4F40B + - http://dx.doi.org/10.1109/JSTARS.2010.2042281 storage: driver: NetCDF CF diff --git a/docs/config_samples/ingester/ls8_pq_albers.yaml b/docs/config_samples/ingester/ls8_pq_albers.yaml index 184cb8198..6caad8979 100644 --- a/docs/config_samples/ingester/ls8_pq_albers.yaml +++ b/docs/config_samples/ingester/ls8_pq_albers.yaml @@ -19,11 +19,11 @@ global_attributes: or only sea pixels depending on their analytical requirements, leading to enhanced computationally efficient. PQ provides an assessment of the quality of observations at a pixel level and includes information about: - - Spectral Contiguity (lack of signal in any band) - - Saturation in any band - - Presence of cloud - - Presence of cloud shadow - - Land or sea + - Spectral Contiguity (lack of signal in any band) + - Saturation in any band + - Presence of cloud + - Presence of cloud shadow + - Land or sea As Landsat Imagery becomes more readily available, there has been a rapid increase in the amount of analyses undertaken by researchers around the globe. Most researchers use some form of quality masking schema in order to remove undesirable @@ -43,11 +43,11 @@ global_attributes: with OLI data, however it uses additional algorithms to detect cloud and cloud shadow, and is available for Landsat 5, 7 and 8. source: PQ_25_2 history: | - - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing - - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. - - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. - - DEM: 1 second SRTM DSM is used for Ortho-rectification. - - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. + - Ground Control Points (GCP): new GCP chips released by USGS in Dec 2015 are used for re-processing + - Geometric QA: each product undergoes geometric assessment and the assessment result will be recorded within v2 AGDC for filtering/masking purposes. + - Processing parameter settings: the minimum number of GCPs for Ortho-rectified product generation has been reduced from 30 to 10. + - DEM: 1 second SRTM DSM is used for Ortho-rectification. + - Updated Calibration Parameter File (CPF): the latest/current CPF is used for processing. institution: Commonwealth of Australia (Geoscience Australia) instrument: OLI keywords: AU/GA,NASA/GSFC/SED/ESD/LANDSAT,ETM+,TM,OLI,EARTH SCIENCE diff --git a/docs/config_samples/ingester/s2amsil1c_albers_10.yaml b/docs/config_samples/ingester/s2amsil1c_albers_10.yaml index c9701a6d2..d7612e8b7 100644 --- a/docs/config_samples/ingester/s2amsil1c_albers_10.yaml +++ b/docs/config_samples/ingester/s2amsil1c_albers_10.yaml @@ -81,5 +81,5 @@ measurements: src_varname: '08' zlib: True attrs: - long_name: " Level 1 Top of Atmosphere Reflectance 842 nanometers (Near infrared)" + long_name: " Level 1 Top of Atmosphere Reflectance 842 nanometers (Near infrared)" alias: "Band8" diff --git a/docs/make.bat b/docs/make.bat index 4d607534c..34f6c8c04 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -5,7 +5,7 @@ echo %cd% REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build + set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build @@ -17,15 +17,15 @@ if "%1" == "livehtml" goto livehtml %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% diff --git a/examples/io_plugin/dcio_example/__init__.py b/examples/io_plugin/dcio_example/__init__.py index c081ad5b4..45970a2a5 100644 --- a/examples/io_plugin/dcio_example/__init__.py +++ b/examples/io_plugin/dcio_example/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/examples/io_plugin/dcio_example/pickles.py b/examples/io_plugin/dcio_example/pickles.py index 2f6eed8df..f209b12e3 100644 --- a/examples/io_plugin/dcio_example/pickles.py +++ b/examples/io_plugin/dcio_example/pickles.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Example reader plugin """ diff --git a/examples/io_plugin/dcio_example/xarray_3d.py b/examples/io_plugin/dcio_example/xarray_3d.py index d38948824..2f997cde4 100644 --- a/examples/io_plugin/dcio_example/xarray_3d.py +++ b/examples/io_plugin/dcio_example/xarray_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ xarray 3D driver plugin for 3D support testing """ diff --git a/examples/io_plugin/dcio_example/zeros.py b/examples/io_plugin/dcio_example/zeros.py index 1cd088e5c..71b302c13 100644 --- a/examples/io_plugin/dcio_example/zeros.py +++ b/examples/io_plugin/dcio_example/zeros.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Sample plugin "reads" zeros each time every time diff --git a/examples/io_plugin/setup.py b/examples/io_plugin/setup.py index e5f41105c..402dc1e06 100644 --- a/examples/io_plugin/setup.py +++ b/examples/io_plugin/setup.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from setuptools import setup, find_packages diff --git a/integration_tests/__init__.py b/integration_tests/__init__.py index d0e5e97db..7bd83106e 100644 --- a/integration_tests/__init__.py +++ b/integration_tests/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index e6db0b7ab..1e5608276 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common methods for index integration tests. diff --git a/integration_tests/data_utils.py b/integration_tests/data_utils.py index 64cefbf27..cf8a6d02b 100644 --- a/integration_tests/data_utils.py +++ b/integration_tests/data_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ The start of some general purpose utilities for generating test data. diff --git a/integration_tests/index/__init__.py b/integration_tests/index/__init__.py index d0e5e97db..7bd83106e 100644 --- a/integration_tests/index/__init__.py +++ b/integration_tests/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/search_utils.py b/integration_tests/index/search_utils.py index 62595bb73..fc0e4bf77 100644 --- a/integration_tests/index/search_utils.py +++ b/integration_tests/index/search_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import io diff --git a/integration_tests/index/test_config_docs.py b/integration_tests/index/test_config_docs.py index 0ae002e5c..02077cebc 100644 --- a/integration_tests/index/test_config_docs.py +++ b/integration_tests/index/test_config_docs.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index f69af1b1c..e29b15946 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test database methods. diff --git a/integration_tests/index/test_memory_index.py b/integration_tests/index/test_memory_index.py index 41758bdb8..f5ed510a5 100644 --- a/integration_tests/index/test_memory_index.py +++ b/integration_tests/index/test_memory_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime diff --git a/integration_tests/index/test_null_index.py b/integration_tests/index/test_null_index.py index 8c235f005..2cf9ebd03 100644 --- a/integration_tests/index/test_null_index.py +++ b/integration_tests/index/test_null_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest.mock import MagicMock diff --git a/integration_tests/index/test_pluggable_indexes.py b/integration_tests/index/test_pluggable_indexes.py index c2e8e1d11..7d2b711d8 100644 --- a/integration_tests/index/test_pluggable_indexes.py +++ b/integration_tests/index/test_pluggable_indexes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/index/test_postgis_index.py b/integration_tests/index/test_postgis_index.py index 5bc3e4726..1a2d24b9e 100644 --- a/integration_tests/index/test_postgis_index.py +++ b/integration_tests/index/test_postgis_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/index/test_search_eo3.py b/integration_tests/index/test_search_eo3.py index dfd40f14f..d4cf1b32a 100644 --- a/integration_tests/index/test_search_eo3.py +++ b/integration_tests/index/test_search_eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_search_legacy.py b/integration_tests/index/test_search_legacy.py index 5d9c7151e..5eebc9963 100644 --- a/integration_tests/index/test_search_legacy.py +++ b/integration_tests/index/test_search_legacy.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_update_columns.py b/integration_tests/index/test_update_columns.py index e64b51824..2b94f599a 100644 --- a/integration_tests/index/test_update_columns.py +++ b/integration_tests/index/test_update_columns.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test creation of added/updated columns during diff --git a/integration_tests/test_3d.py b/integration_tests/test_3d.py index b8c6c7aa8..6a4db6c97 100644 --- a/integration_tests/test_3d.py +++ b/integration_tests/test_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from copy import deepcopy diff --git a/integration_tests/test_cli_output.py b/integration_tests/test_cli_output.py index 44c629247..e2050e04b 100644 --- a/integration_tests/test_cli_output.py +++ b/integration_tests/test_cli_output.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/integration_tests/test_config_tool.py b/integration_tests/test_config_tool.py index 991c6fb62..264254d24 100644 --- a/integration_tests/test_config_tool.py +++ b/integration_tests/test_config_tool.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/test_dataset_add.py b/integration_tests/test_dataset_add.py index 5d8e87441..62db39e5b 100644 --- a/integration_tests/test_dataset_add.py +++ b/integration_tests/test_dataset_add.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import math diff --git a/integration_tests/test_double_ingestion.py b/integration_tests/test_double_ingestion.py index 3534def55..39485c940 100644 --- a/integration_tests/test_double_ingestion.py +++ b/integration_tests/test_double_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import netCDF4 diff --git a/integration_tests/test_end_to_end.py b/integration_tests/test_end_to_end.py index 43bcdf425..32ea3b296 100644 --- a/integration_tests/test_end_to_end.py +++ b/integration_tests/test_end_to_end.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import shutil from pathlib import Path diff --git a/integration_tests/test_environments.py b/integration_tests/test_environments.py index 2ce64f07a..78bb9a589 100644 --- a/integration_tests/test_environments.py +++ b/integration_tests/test_environments.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_full_ingestion.py b/integration_tests/test_full_ingestion.py index ab71d5608..eeb728a94 100644 --- a/integration_tests/test_full_ingestion.py +++ b/integration_tests/test_full_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import hashlib import warnings diff --git a/integration_tests/test_index_datasets_search.py b/integration_tests/test_index_datasets_search.py index 0a8373faf..50b6a213f 100644 --- a/integration_tests/test_index_datasets_search.py +++ b/integration_tests/test_index_datasets_search.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from pathlib import PurePosixPath diff --git a/integration_tests/test_index_out_of_bound.py b/integration_tests/test_index_out_of_bound.py index 8cbd34325..7054830d0 100644 --- a/integration_tests/test_index_out_of_bound.py +++ b/integration_tests/test_index_out_of_bound.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_model.py b/integration_tests/test_model.py index 6fe0dd06f..189150fdf 100644 --- a/integration_tests/test_model.py +++ b/integration_tests/test_model.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_validate_ingestion.py b/integration_tests/test_validate_ingestion.py index e204cb299..68358fced 100644 --- a/integration_tests/test_validate_ingestion.py +++ b/integration_tests/test_validate_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/utils.py b/integration_tests/utils.py index 4744c54ef..c29b126c0 100644 --- a/integration_tests/utils.py +++ b/integration_tests/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import os diff --git a/tests/__init__.py b/tests/__init__.py index c081ad5b4..45970a2a5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/api/__init__.py b/tests/api/__init__.py index c081ad5b4..45970a2a5 100644 --- a/tests/api/__init__.py +++ b/tests/api/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/api/test_core.py b/tests/api/test_core.py index c96f58824..2bada262c 100644 --- a/tests/api/test_core.py +++ b/tests/api/test_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import xarray as xr import numpy as np diff --git a/tests/api/test_grid_workflow.py b/tests/api/test_grid_workflow.py index 81ab25460..5924af0ad 100644 --- a/tests/api/test_grid_workflow.py +++ b/tests/api/test_grid_workflow.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import numpy diff --git a/tests/api/test_masking.py b/tests/api/test_masking.py index 456680a00..63f9299ad 100644 --- a/tests/api/test_masking.py +++ b/tests/api/test_masking.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import yaml import pytest diff --git a/tests/api/test_query.py b/tests/api/test_query.py index fc982a1fc..78b138908 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import pandas diff --git a/tests/api/test_virtual.py b/tests/api/test_virtual.py index 598095d13..97b13d237 100644 --- a/tests/api/test_virtual.py +++ b/tests/api/test_virtual.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import OrderedDict from datetime import datetime diff --git a/tests/conftest.py b/tests/conftest.py index 8a52f369a..e9a60532c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ py.test configuration fixtures diff --git a/tests/data/lbg/LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323/metadata.xml b/tests/data/lbg/LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323/metadata.xml index 6c1abf738..880c39e0a 100644 --- a/tests/data/lbg/LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323/metadata.xml +++ b/tests/data/lbg/LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323/metadata.xml @@ -1,233 +1,233 @@ - - - - 19920323T23:09:13 - ALSP - acquisition - - D - 19920323T23:17:56 - - 42876 - completed - instantaneous - - preProgrammed - - TM - Multi-spectral - - Landsat-5 - - - 1 - Color JPEG Image - LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323.jpg - JPG - 4 - 7 - 237.329101562 - - - - - - - - - - Ortho-rectified, processed by Geoscience Australia - - - - 20120917T01:25:07 - creation - - - - - - - - </SOURCECITATION> - <SOURCEREFERENCESYSTEM/> - <SOURCERESOURCEID/> - <SOURCESCALE/> - <SOURCESTEP/> - </LINEAGESOURCE> - <PROCESSINGSTEP> - <ALGORITHMCITATION> - <EDITION>Git version ed0cd604d3b171ded4d8d3767feedb76e1b8ce58</EDITION> - <TITLE>Pinkmatter Landsat Processor - - - NBAR version GANBARv3.00.00-dev + + + + 19920323T23:09:13 + ALSP + acquisition + + D + 19920323T23:17:56 + + 42876 + completed + instantaneous + + preProgrammed + + TM + Multi-spectral + + Landsat-5 + + + 1 + Color JPEG Image + LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323.jpg + JPG + 4 + 7 + 237.329101562 + + + + + + + + + + Ortho-rectified, processed by Geoscience Australia + + + + 20120917T01:25:07 + creation + + + + + + + + </SOURCECITATION> + <SOURCEREFERENCESYSTEM/> + <SOURCERESOURCEID/> + <SOURCESCALE/> + <SOURCESTEP/> + </LINEAGESOURCE> + <PROCESSINGSTEP> + <ALGORITHMCITATION> + <EDITION>Git version ed0cd604d3b171ded4d8d3767feedb76e1b8ce58</EDITION> + <TITLE>Pinkmatter Landsat Processor + + + NBAR version GANBARv3.00.00-dev BRDF (MODIS_BRDF) = BAND 1 [0.008570 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b09.500m_0459_0479nm_brdf_par_fgeo.hdf), 0.043934 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b07.500m_0459_0479nm_brdf_par_fiso.hdf), 0.016253 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b08.500m_0459_0479nm_brdf_par_fvol.hdf)] - BAND 2 [0.015374 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b12.500m_0545_0565nm_brdf_par_fgeo.hdf), 0.078902 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b10.500m_0545_0565nm_brdf_par_fiso.hdf), 0.037976 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b11.500m_0545_0565nm_brdf_par_fvol.hdf)] - BAND 3 [0.017559 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b03.500m_0620_0670nm_brdf_par_fgeo.hdf), 0.085431 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b01.500m_0620_0670nm_brdf_par_fiso.hdf), 0.034050 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b02.500m_0620_0670nm_brdf_par_fvol.hdf)] - BAND 4 [0.023475 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b06.500m_0841_0876nm_brdf_par_fgeo.hdf), 0.273303 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b04.500m_0841_0876nm_brdf_par_fiso.hdf), 0.201959 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b05.500m_0841_0876nm_brdf_par_fvol.hdf)] - BAND 5 [0.043315 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b18.500m_1628_1652nm_brdf_par_fgeo.hdf), 0.279249 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b16.500m_1628_1652nm_brdf_par_fiso.hdf), 0.106509 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b17.500m_1628_1652nm_brdf_par_fvol.hdf)] - BAND 7 [0.032661 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b21.500m_2105_2155nm_brdf_par_fgeo.hdf), 0.165904 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b19.500m_2105_2155nm_brdf_par_fiso.hdf), 0.045391 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b20.500m_2105_2155nm_brdf_par_fvol.hdf)] + BAND 2 [0.015374 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b12.500m_0545_0565nm_brdf_par_fgeo.hdf), 0.078902 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b10.500m_0545_0565nm_brdf_par_fiso.hdf), 0.037976 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b11.500m_0545_0565nm_brdf_par_fvol.hdf)] + BAND 3 [0.017559 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b03.500m_0620_0670nm_brdf_par_fgeo.hdf), 0.085431 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b01.500m_0620_0670nm_brdf_par_fiso.hdf), 0.034050 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b02.500m_0620_0670nm_brdf_par_fvol.hdf)] + BAND 4 [0.023475 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b06.500m_0841_0876nm_brdf_par_fgeo.hdf), 0.273303 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b04.500m_0841_0876nm_brdf_par_fiso.hdf), 0.201959 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b05.500m_0841_0876nm_brdf_par_fvol.hdf)] + BAND 5 [0.043315 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b18.500m_1628_1652nm_brdf_par_fgeo.hdf), 0.279249 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b16.500m_1628_1652nm_brdf_par_fiso.hdf), 0.106509 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b17.500m_1628_1652nm_brdf_par_fvol.hdf)] + BAND 7 [0.032661 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b21.500m_2105_2155nm_brdf_par_fgeo.hdf), 0.165904 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b19.500m_2105_2155nm_brdf_par_fiso.hdf), 0.045391 (/g/data1/v10/eoancillarydata/brdf-jl/data/073/MCD43A1.JLWKAV.073.aust.005.b20.500m_2105_2155nm_brdf_par_fvol.hdf)] OZONE (GA 2 deg pixel size) = 0.261 WATER VAPOUR (NOAA) = 2.27 DEM (GA 1 deg pixel size) = 0.409 AOD (/g/data1/v10/eoancillarydata/aerosol/AATSR/2.0/aot_mean_Mar_All_Aerosols.cmp) = 0.043009 - - dataset - - - - 4326 - 151.1469421 - - - -35.6039314 - 148.4684601 - -35.5414848 - 151.1469421 - -33.5805855 - -34.5911 - 149.775 - -35.6039314 - 19920323 23:14:13 - 19920323 23:14:38 - - -33.6386375 - 148.4342346 - -33.5805855 - 151.0505676 - - - - - - 148.4342346 - - - area - - sample - 25.0 - 9721 - - - line - 25.0 - 8721 - - - 754512.500000 - 6167987.500000 - 0 - - - GDA94 - GRS80 - 633012.5 - 6058987.5 - - 876012.5 - 6058987.5 - - 633012.5 - 6276987.5 - - 876012.5 - 6276987.5 - - UpperLeft - UTM - 55 - - 2 - 1 - - - - 1, 2, 3, 4, 5, 7 - - - 23.0 - 35.8386447 - 57.6238557 - - NBAR - 90 - 84 - SAM - 0 - 0 - - - - - - - - - - notPlanned - - - - - - - - Landsat-5 NBAR - usAscii - - Landsat-5 TM NBAR x090 y084 19920323 version 1 status completed - 2014-03-17 05:53:14 - creation - - - - Medhavy Thankappan - Geoscience Australia - Science and Strategy Project Leader - pointOfContact - - - Landsat-5 TM NBAR x090 y084 19920323 version 1 status completed - - - - - - - - 9 - - eng - NCI PBS 1.0.0 | Linux r3398 2.6.32-358.23.2.el6.x86_64 #1 SMP Wed Oct 16 18:37:12 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux - 161 - - GEOTIFF - 1 - - dataset - dataset - - - Medhavy Thankappan - Geoscience Australia - Science and Strategy Project Leader - pointOfContact - - LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323 - GA Metadata Profile: A Geoscience Australia Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata - 1.0 - - LS5_March_1992_GA/ULA_STND_v2.1.5 - 25.0 - completed - Processed Image - - - environment - + + dataset + + + + 4326 + 151.1469421 + + + -35.6039314 + 148.4684601 + -35.5414848 + 151.1469421 + -33.5805855 + -34.5911 + 149.775 + -35.6039314 + 19920323 23:14:13 + 19920323 23:14:38 + + -33.6386375 + 148.4342346 + -33.5805855 + 151.0505676 + + + + + + 148.4342346 + + + area + + sample + 25.0 + 9721 + + + line + 25.0 + 8721 + + + 754512.500000 + 6167987.500000 + 0 + + + GDA94 + GRS80 + 633012.5 + 6058987.5 + + 876012.5 + 6058987.5 + + 633012.5 + 6276987.5 + + 876012.5 + 6276987.5 + + UpperLeft + UTM + 55 + + 2 + 1 + + + + 1, 2, 3, 4, 5, 7 + + + 23.0 + 35.8386447 + 57.6238557 + + NBAR + 90 + 84 + SAM + 0 + 0 + + + + + + + + + + notPlanned + + + + + + + + Landsat-5 NBAR + usAscii + + Landsat-5 TM NBAR x090 y084 19920323 version 1 status completed + 2014-03-17 05:53:14 + creation + + + + Medhavy Thankappan + Geoscience Australia + Science and Strategy Project Leader + pointOfContact + + + Landsat-5 TM NBAR x090 y084 19920323 version 1 status completed + + + + + + + + 9 + + eng + NCI PBS 1.0.0 | Linux r3398 2.6.32-358.23.2.el6.x86_64 #1 SMP Wed Oct 16 18:37:12 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux + 161 + + GEOTIFF + 1 + + dataset + dataset + + + Medhavy Thankappan + Geoscience Australia + Science and Strategy Project Leader + pointOfContact + + LS5_TM_NBAR_P54_GANBAR01-002_090_084_19920323 + GA Metadata Profile: A Geoscience Australia Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata + 1.0 + + LS5_March_1992_GA/ULA_STND_v2.1.5 + 25.0 + completed + Processed Image + + + environment + diff --git a/tests/data/lbg/LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323/metadata.xml b/tests/data/lbg/LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323/metadata.xml index 383b8c68a..9116dae37 100644 --- a/tests/data/lbg/LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323/metadata.xml +++ b/tests/data/lbg/LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323/metadata.xml @@ -1,70 +1,70 @@ - - - - 19920323T23:09:13 - ALSP - acquisition - - - 19920323T23:17:56 - - 42876 - - instantaneous - - automatic - - TM - Multi-spectral - - Landsat-5 - - - - - - - - - - - - - - - - - - - The pixel quality algorithm uses data from both the L1T (Systematic Terrain Correction) and ARG25 (Australian Reflectance Grid 25m) products. - - - - 20120917T01:25:07 - creation - - - - - - - - </SOURCECITATION> - <SOURCEREFERENCESYSTEM/> - <SOURCERESOURCEID/> - <SOURCESCALE/> - <SOURCESTEP/> - </LINEAGESOURCE> - <PROCESSINGSTEP> - <ALGORITHMCITATION> - <EDITION>Git version 3.2.0</EDITION> - <TITLE>Pixel Quality - - - + + + + 19920323T23:09:13 + ALSP + acquisition + + + 19920323T23:17:56 + + 42876 + + instantaneous + + automatic + + TM + Multi-spectral + + Landsat-5 + + + + + + + + + + + + + + + + + + + The pixel quality algorithm uses data from both the L1T (Systematic Terrain Correction) and ARG25 (Australian Reflectance Grid 25m) products. + + + + 20120917T01:25:07 + creation + + + + + + + + </SOURCECITATION> + <SOURCEREFERENCESYSTEM/> + <SOURCERESOURCEID/> + <SOURCESCALE/> + <SOURCESTEP/> + </LINEAGESOURCE> + <PROCESSINGSTEP> + <ALGORITHMCITATION> + <EDITION>Git version 3.2.0</EDITION> + <TITLE>Pixel Quality + + + The pixel quality algorithm assesses quality aspects such as saturation, band/spectral contiguity, land/sea, cloud and cloud shadow. Saturation Band1 (Bit 0): Run Saturation Band2 (Bit 1): Run @@ -83,158 +83,158 @@ Cloud Shadow (Fmask) (Bit 13): Run Empty Test (Bit 14): Not Run Empty Test (Bit 15): Not Run - - dataset - - - - 4326 - 151.1469421 - - - -35.6039314 - 148.4684601 - -35.5414848 - 151.1469421 - -33.5805855 - -34.5911 - 149.775 - -35.6039314 - 19920323 23:14:13 - 19920323 23:14:38 - - -33.6386375 - 148.4342346 - -33.5805855 - 151.0505676 - - - - - - 148.4342346 - - - area - - sample - 25.0 - 9721 - - - line - 25.0 - 8721 - - - 754512.500000 - 6167987.500000 - 0 - - - GDA94 - GRS80 - 633012.5 - 6058987.5 - - 876012.5 - 6058987.5 - - 633012.5 - 6276987.5 - - 876012.5 - 6276987.5 - - UpperLeft - UTM - 55 - - 2 - 1 - - - - PQ - - 23.0 - 35.8386447 - 57.6238557 - - Pixel Quality - 90 - 84 - SAM - 0 - 0 - - - - - - - - - - notPlanned - - - - - - - - Landsat-5 PQ - utf8 - - Landsat-5 TM PQ x090 y084 19920323 - 20140601T01:22:30 - creation - - - - NEMO Group - Geoscience Australia - NEMO Operation - pointOfContact - - - Landsat-5 TM PQ x090 y084 19920323 - - - - - - - - 1 - - eng - NCI PBS 1.0.0 | Linux r3398 2.6.32-431.11.2.el6.x86_64 #1 SMP Tue Mar 25 19:59:55 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux - 161 - - GEOTIFF - 1 - - dataset - Satellite Imagery - - - NEMO Group - Geoscience Australia - NEMO Operation - pointOfContact - - LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323 - ANZLIC Metadata Profile: An Australian/New Zealand Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata - 1.0 - - Pixel Quality - 25.0 - completed - Processed Image - grid - + + dataset + + + + 4326 + 151.1469421 + + + -35.6039314 + 148.4684601 + -35.5414848 + 151.1469421 + -33.5805855 + -34.5911 + 149.775 + -35.6039314 + 19920323 23:14:13 + 19920323 23:14:38 + + -33.6386375 + 148.4342346 + -33.5805855 + 151.0505676 + + + + + + 148.4342346 + + + area + + sample + 25.0 + 9721 + + + line + 25.0 + 8721 + + + 754512.500000 + 6167987.500000 + 0 + + + GDA94 + GRS80 + 633012.5 + 6058987.5 + + 876012.5 + 6058987.5 + + 633012.5 + 6276987.5 + + 876012.5 + 6276987.5 + + UpperLeft + UTM + 55 + + 2 + 1 + + + + PQ + + 23.0 + 35.8386447 + 57.6238557 + + Pixel Quality + 90 + 84 + SAM + 0 + 0 + + + + + + + + + + notPlanned + + + + + + + + Landsat-5 PQ + utf8 + + Landsat-5 TM PQ x090 y084 19920323 + 20140601T01:22:30 + creation + + + + NEMO Group + Geoscience Australia + NEMO Operation + pointOfContact + + + Landsat-5 TM PQ x090 y084 19920323 + + + + + + + + 1 + + eng + NCI PBS 1.0.0 | Linux r3398 2.6.32-431.11.2.el6.x86_64 #1 SMP Tue Mar 25 19:59:55 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux + 161 + + GEOTIFF + 1 + + dataset + Satellite Imagery + + + NEMO Group + Geoscience Australia + NEMO Operation + pointOfContact + + LS5_TM_PQ_P55_GAPQ01-002_090_084_19920323 + ANZLIC Metadata Profile: An Australian/New Zealand Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata + 1.0 + + Pixel Quality + 25.0 + completed + Processed Image + grid + The pixel quality algorithm uses data from both the L1T (Systematic Terrain Correction) and ARG25 (Australian Reflectance Grid 25m) products. ACCA cloud cover is reported as a percentage of the entire data grid, while Fmask is reported as a percentage of the valid image data only. @@ -245,6 +245,6 @@ Cloud shadow is reported as a percentage of the entire data grid for both analys CLOUD SHADOW PERCENTAGE ACCA 0.38 CLOUD SHADOW PERCENTAGE Fmask 0.38 - environment - + environment + diff --git a/tests/data/ls8-eods-nbar/data/LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012/metadata.xml b/tests/data/ls8-eods-nbar/data/LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012/metadata.xml index b0eb00c2e..b97606219 100644 --- a/tests/data/ls8-eods-nbar/data/LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012/metadata.xml +++ b/tests/data/ls8-eods-nbar/data/LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012/metadata.xml @@ -1,223 +1,223 @@ - - - - 20141012T03:23:36 - LGS - acquisition - - D - 20141012T03:29:10 - - 8846 - completed - instantaneous - - preProgrammed - - OLI-TIRS - Multi-spectral - - Landsat-8 - - - 2 - Color JPEG Image - LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012.jpg - JPG - 5 - 7 - 223.657226562 - - - - - - - - - - Ortho-rectified, processed by Geoscience Australia - - - - - - - - - - - - - </SOURCECITATION> - <SOURCEREFERENCESYSTEM/> - <SOURCERESOURCEID/> - <SOURCESCALE/> - <SOURCESTEP/> - </LINEAGESOURCE> - <PROCESSINGSTEP> - <ALGORITHMCITATION> - <EDITION>Git version 3.2.1</EDITION> - <TITLE>NBAR - - - Resampling=CC,RadiometricCorrection=CPF,Orientation=NUP,LPGS_version=3.3.3104,Hemisphere=S,CPF_NAME=L8CPF20141001_20141231.01,BPF_NAME_TIRS=LT8BPF20141012002432_20141012011154.02,BPF_NAME_OLI=LO8BPF20141012002825_20141012011100.01 - - dataset - - - - 4326 - 136.24838 - - - -26.96528 - 133.96233 - -26.96338 - 136.26962 - -24.97000 - -25.967 - 135.125 - -26.96338 - 20141012 00:55:54 - 20141012 00:56:18 - - -24.97 - 133.97969 - -24.96826 - 136.24838 - - - - - - 133.96233 - - - area - - sample - 25.0 - 9161 - - - line - 25.0 - 8841 - - - 511512.500000 - 7127487.500000 - 0 - eoancillarydataGCP - GDA94 / MGA zone 53 - GDA94 - GRS80 - 397012.5 - 7016987.5 - - 626012.5 - 7016987.5 - - 397012.5 - 7237987.5 - - 626012.5 - 7237987.5 - - UpperLeft - UTM - 53 - - 2 - 1 - - - - 1, 2, 3, 4, 5, 6, 7 - - - 0.0 - 58.00268508 - 59.41814014 - - NBAR - 101 - 78 - PUSH-BROOM - 0 - 0 - - - - - - - - - - notPlanned - - - - - - - - Processed Image: Landsat Nadir BRDF-Adjusted Reflectance products generated by Geoscience Australia. Data Source: GA - usAscii - - Landsat-8 OLI_TIRS NBAR x101 y078 20141012 - 2014-11-06 00:32:06 - creation - - - - Medhavy Thankappan - Geoscience Australia - Science and Strategy Project Leader - pointOfContact - - - Landsat-8 OLI_TIRS NBAR x101 y078 20141012 - - - - - - - - 9 - - eng - NCI PBS 1.0.0 | Linux r1699 2.6.32-431.29.2.el6.x86_64 #1 SMP Tue Sep 9 21:36:05 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux - 154 - - GEOTIFF - 1 - - dataset - Satellite Imagery - - - Medhavy Thankappan - Geoscience Australia - Science and Strategy Project Leader - pointOfContact - - LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012 - GA Metadata Profile: A Geoscience Australia Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata - 1.0 - - - 25.0 - completed - Processed Image - - - environment - + + + + 20141012T03:23:36 + LGS + acquisition + + D + 20141012T03:29:10 + + 8846 + completed + instantaneous + + preProgrammed + + OLI-TIRS + Multi-spectral + + Landsat-8 + + + 2 + Color JPEG Image + LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012.jpg + JPG + 5 + 7 + 223.657226562 + + + + + + + + + + Ortho-rectified, processed by Geoscience Australia + + + + + + + + + + + + + </SOURCECITATION> + <SOURCEREFERENCESYSTEM/> + <SOURCERESOURCEID/> + <SOURCESCALE/> + <SOURCESTEP/> + </LINEAGESOURCE> + <PROCESSINGSTEP> + <ALGORITHMCITATION> + <EDITION>Git version 3.2.1</EDITION> + <TITLE>NBAR + + + Resampling=CC,RadiometricCorrection=CPF,Orientation=NUP,LPGS_version=3.3.3104,Hemisphere=S,CPF_NAME=L8CPF20141001_20141231.01,BPF_NAME_TIRS=LT8BPF20141012002432_20141012011154.02,BPF_NAME_OLI=LO8BPF20141012002825_20141012011100.01 + + dataset + + + + 4326 + 136.24838 + + + -26.96528 + 133.96233 + -26.96338 + 136.26962 + -24.97000 + -25.967 + 135.125 + -26.96338 + 20141012 00:55:54 + 20141012 00:56:18 + + -24.97 + 133.97969 + -24.96826 + 136.24838 + + + + + + 133.96233 + + + area + + sample + 25.0 + 9161 + + + line + 25.0 + 8841 + + + 511512.500000 + 7127487.500000 + 0 + eoancillarydataGCP + GDA94 / MGA zone 53 + GDA94 + GRS80 + 397012.5 + 7016987.5 + + 626012.5 + 7016987.5 + + 397012.5 + 7237987.5 + + 626012.5 + 7237987.5 + + UpperLeft + UTM + 53 + + 2 + 1 + + + + 1, 2, 3, 4, 5, 6, 7 + + + 0.0 + 58.00268508 + 59.41814014 + + NBAR + 101 + 78 + PUSH-BROOM + 0 + 0 + + + + + + + + + + notPlanned + + + + + + + + Processed Image: Landsat Nadir BRDF-Adjusted Reflectance products generated by Geoscience Australia. Data Source: GA + usAscii + + Landsat-8 OLI_TIRS NBAR x101 y078 20141012 + 2014-11-06 00:32:06 + creation + + + + Medhavy Thankappan + Geoscience Australia + Science and Strategy Project Leader + pointOfContact + + + Landsat-8 OLI_TIRS NBAR x101 y078 20141012 + + + + + + + + 9 + + eng + NCI PBS 1.0.0 | Linux r1699 2.6.32-431.29.2.el6.x86_64 #1 SMP Tue Sep 9 21:36:05 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux + 154 + + GEOTIFF + 1 + + dataset + Satellite Imagery + + + Medhavy Thankappan + Geoscience Australia + Science and Strategy Project Leader + pointOfContact + + LS8_OLI_TIRS_NBAR_P54_GANBAR01-015_101_078_20141012 + GA Metadata Profile: A Geoscience Australia Profile of AS/NZS ISO 19115:2005, Geographic information - Metadata + 1.0 + + + 25.0 + completed + Processed Image + + + environment + diff --git a/tests/drivers/fail_drivers/dc_tests_io/__init__.py b/tests/drivers/fail_drivers/dc_tests_io/__init__.py index c081ad5b4..45970a2a5 100644 --- a/tests/drivers/fail_drivers/dc_tests_io/__init__.py +++ b/tests/drivers/fail_drivers/dc_tests_io/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/drivers/fail_drivers/dc_tests_io/dummy.py b/tests/drivers/fail_drivers/dc_tests_io/dummy.py index f4efb6024..2d3143fd4 100644 --- a/tests/drivers/fail_drivers/dc_tests_io/dummy.py +++ b/tests/drivers/fail_drivers/dc_tests_io/dummy.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/drivers/fail_drivers/setup.py b/tests/drivers/fail_drivers/setup.py index 9ca7932ce..ed71cb127 100644 --- a/tests/drivers/fail_drivers/setup.py +++ b/tests/drivers/fail_drivers/setup.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from setuptools import setup, find_packages diff --git a/tests/drivers/test_rio_reader.py b/tests/drivers/test_rio_reader.py index 1402b5823..8f2a7a57d 100644 --- a/tests/drivers/test_rio_reader.py +++ b/tests/drivers/test_rio_reader.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tests for new RIO reader driver """ diff --git a/tests/index/__init__.py b/tests/index/__init__.py index d0e5e97db..7bd83106e 100644 --- a/tests/index/__init__.py +++ b/tests/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_api_index_dataset.py b/tests/index/test_api_index_dataset.py index 93a57a84e..6c86f9add 100644 --- a/tests/index/test_api_index_dataset.py +++ b/tests/index/test_api_index_dataset.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime from collections import namedtuple diff --git a/tests/index/test_fields.py b/tests/index/test_fields.py index af2cc12da..3f26ac33a 100644 --- a/tests/index/test_fields.py +++ b/tests/index/test_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_hl_index.py b/tests/index/test_hl_index.py index bfe051058..a6251dca7 100644 --- a/tests/index/test_hl_index.py +++ b/tests/index/test_hl_index.py @@ -1,3 +1,7 @@ +# This file is part of the Open Data Cube, see https://opendatacube.org for more information +# +# Copyright (c) 2015-2023 ODC Contributors +# SPDX-License-Identifier: Apache-2.0 import pytest from unittest.mock import MagicMock diff --git a/tests/index/test_postgis_fields.py b/tests/index/test_postgis_fields.py index d5725fbdb..cfa606b91 100644 --- a/tests/index/test_postgis_fields.py +++ b/tests/index/test_postgis_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2022 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime diff --git a/tests/index/test_query.py b/tests/index/test_query.py index 60f696765..477d6ed2d 100644 --- a/tests/index/test_query.py +++ b/tests/index/test_query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_validate_dataset_type.py b/tests/index/test_validate_dataset_type.py index 058c2e465..565ad6dce 100644 --- a/tests/index/test_validate_dataset_type.py +++ b/tests/index/test_validate_dataset_type.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/scripts/__init__.py b/tests/scripts/__init__.py index d0e5e97db..7bd83106e 100644 --- a/tests/scripts/__init__.py +++ b/tests/scripts/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/scripts/test_search_tool.py b/tests/scripts/test_search_tool.py index 8e2b44e2b..57bf4ffeb 100644 --- a/tests/scripts/test_search_tool.py +++ b/tests/scripts/test_search_tool.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/storage/test_base.py b/tests/storage/test_base.py index f14b05c26..823c71b2e 100644 --- a/tests/storage/test_base.py +++ b/tests/storage/test_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.storage import BandInfo diff --git a/tests/storage/test_netcdfwriter.py b/tests/storage/test_netcdfwriter.py index ccf30b098..46788e9c4 100644 --- a/tests/storage/test_netcdfwriter.py +++ b/tests/storage/test_netcdfwriter.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import netCDF4 import numpy diff --git a/tests/storage/test_storage.py b/tests/storage/test_storage.py index 65b930e8e..37c981b7f 100644 --- a/tests/storage/test_storage.py +++ b/tests/storage/test_storage.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/tests/storage/test_storage_load.py b/tests/storage/test_storage_load.py index 8aafb550d..396003dbc 100644 --- a/tests/storage/test_storage_load.py +++ b/tests/storage/test_storage_load.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test New IO driver loading """ diff --git a/tests/storage/test_storage_read.py b/tests/storage/test_storage_read.py index 2acbaa3f3..8beb60172 100644 --- a/tests/storage/test_storage_read.py +++ b/tests/storage/test_storage_read.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import numpy as np diff --git a/tests/test_3d.py b/tests/test_3d.py index 6b2d1a31b..06df9763d 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import OrderedDict diff --git a/tests/test_concurrent_executor.py b/tests/test_concurrent_executor.py index c8dac0132..f11212da4 100644 --- a/tests/test_concurrent_executor.py +++ b/tests/test_concurrent_executor.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tests for MultiprocessingExecutor diff --git a/tests/test_config.py b/tests/test_config.py index dc8c20dc0..0d134e532 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/test_driver.py b/tests/test_driver.py index 19bf25d68..3c9331d68 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import yaml diff --git a/tests/test_dynamic_db_passwd.py b/tests/test_dynamic_db_passwd.py index 62579b99b..17b7b9640 100644 --- a/tests/test_dynamic_db_passwd.py +++ b/tests/test_dynamic_db_passwd.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from sqlalchemy.exc import OperationalError diff --git a/tests/test_eo3.py b/tests/test_eo3.py index 36750ee49..44cb5e0da 100644 --- a/tests/test_eo3.py +++ b/tests/test_eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import pytest diff --git a/tests/test_gbox_ops.py b/tests/test_gbox_ops.py index f6deacba4..f063be196 100644 --- a/tests/test_gbox_ops.py +++ b/tests/test_gbox_ops.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import numpy as np diff --git a/tests/test_geometry.py b/tests/test_geometry.py index f123bd632..5e63ba0f8 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import math import numpy as np diff --git a/tests/test_load_data.py b/tests/test_load_data.py index e4589cdd5..e0f4f2cdd 100644 --- a/tests/test_load_data.py +++ b/tests/test_load_data.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube import Datacube from datacube.api.query import query_group_by diff --git a/tests/test_metadata_fields.py b/tests/test_metadata_fields.py index 00baa21e3..20b9ad174 100644 --- a/tests/test_metadata_fields.py +++ b/tests/test_metadata_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import yaml import datetime diff --git a/tests/test_model.py b/tests/test_model.py index 0c41ecaba..da5ae78c4 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import numpy diff --git a/tests/test_testutils.py b/tests/test_testutils.py index 91ee584ce..a45661ee4 100644 --- a/tests/test_testutils.py +++ b/tests/test_testutils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.testutils.threads import FakeThreadPoolExecutor diff --git a/tests/test_utils_aws.py b/tests/test_utils_aws.py index 359acfbf4..a86cdc296 100644 --- a/tests/test_utils_aws.py +++ b/tests/test_utils_aws.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest import mock diff --git a/tests/test_utils_changes.py b/tests/test_utils_changes.py index 5499a0e41..9f5108a09 100644 --- a/tests/test_utils_changes.py +++ b/tests/test_utils_changes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.utils.changes import ( diff --git a/tests/test_utils_cog.py b/tests/test_utils_cog.py index e2260e3e1..363ae4212 100644 --- a/tests/test_utils_cog.py +++ b/tests/test_utils_cog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from pathlib import Path diff --git a/tests/test_utils_dask.py b/tests/test_utils_dask.py index 093e62987..c7b2d2d29 100644 --- a/tests/test_utils_dask.py +++ b/tests/test_utils_dask.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import moto diff --git a/tests/test_utils_dates.py b/tests/test_utils_dates.py index a6fdd0c47..b7547f864 100644 --- a/tests/test_utils_dates.py +++ b/tests/test_utils_dates.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import pytest diff --git a/tests/test_utils_docs.py b/tests/test_utils_docs.py index 80d4f0e01..0341abbc9 100644 --- a/tests/test_utils_docs.py +++ b/tests/test_utils_docs.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test utility functions from :module:`datacube.utils` diff --git a/tests/test_utils_generic.py b/tests/test_utils_generic.py index d8c899538..15392bd0b 100644 --- a/tests/test_utils_generic.py +++ b/tests/test_utils_generic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from queue import Queue from datacube.utils.generic import ( diff --git a/tests/test_utils_other.py b/tests/test_utils_other.py index 76646dcb5..5e383b212 100644 --- a/tests/test_utils_other.py +++ b/tests/test_utils_other.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test utility functions from :module:`datacube.utils` diff --git a/tests/test_utils_rio.py b/tests/test_utils_rio.py index 558b3523b..e834ac2fe 100644 --- a/tests/test_utils_rio.py +++ b/tests/test_utils_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest import mock diff --git a/tests/test_warp.py b/tests/test_warp.py index b2b10d136..6903702e3 100644 --- a/tests/test_warp.py +++ b/tests/test_warp.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np from affine import Affine diff --git a/tests/test_xarray_extension.py b/tests/test_xarray_extension.py index 3c3812f00..b1aeef6a5 100644 --- a/tests/test_xarray_extension.py +++ b/tests/test_xarray_extension.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import xarray as xr diff --git a/tests/ui/__init__.py b/tests/ui/__init__.py index c081ad5b4..45970a2a5 100644 --- a/tests/ui/__init__.py +++ b/tests/ui/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/ui/test_common.py b/tests/ui/test_common.py index 51c4d3865..8c8352002 100644 --- a/tests/ui/test_common.py +++ b/tests/ui/test_common.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/ui/test_expression_parsing.py b/tests/ui/test_expression_parsing.py index 40526ecfe..3f30748c2 100644 --- a/tests/ui/test_expression_parsing.py +++ b/tests/ui/test_expression_parsing.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datetime import datetime from dateutil.tz import tzutc diff --git a/tests/ui/test_task_app.py b/tests/ui/test_task_app.py index eac53d2ea..6d5a4a754 100644 --- a/tests/ui/test_task_app.py +++ b/tests/ui/test_task_app.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2020 ODC Contributors +# Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module From a94629c5ff988f252fa265566aeebc3c84314ca3 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 9 May 2023 01:53:11 +0000 Subject: [PATCH 010/153] add > and < to lark grammar --- datacube/ui/expression.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index c36963162..d7ffd2e69 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -27,10 +27,14 @@ ?expression: equals_expr | time_in_expr | field_in_expr + | time_gt_expr + | time_lt_expr equals_expr: field "=" value time_in_expr: time "in" date_range field_in_expr: field "in" "[" orderable "," orderable "]" + time_gt_expr: time ">" date_range + time_lt_expr: time "<" date_range field: FIELD time: TIME @@ -86,6 +90,12 @@ def field_in_expr(self, field, lower, upper): def time_in_expr(self, time_field, date_range): return {str(time_field): date_range} + + def time_gt_expr(self, time_field, date_range): + return {str(time_field): date_range} + + def time_lt_expr(self, time_field, date_range): + return {str(time_field): date_range} # Convert the literals def string(self, val): From 305b90f88f2253666090b1b1fba80361bdd269dd Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 9 May 2023 02:32:37 +0000 Subject: [PATCH 011/153] refine logic --- datacube/api/query.py | 13 +++++++++++++ datacube/ui/expression.py | 25 ++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index 5bf50f351..260622b52 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -354,6 +354,19 @@ def _time_to_search_dims(time_range): return tr +def _time_to_open_range(time, lower_bound: bool): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + + if lower_bound: # from date provided (not inclusive) to latest available + start = _to_datetime(pandas.Period(time).end_time.to_pydatetime()) + end = _to_datetime(datetime.datetime.now()) + else: # from earliest available to date provided (not inclusive) + start = _to_datetime(pandas.Period('1980-01-01').start_time.to_pydatetime()) + end = _to_datetime(pandas.Period(time).start_time.to_pydatetime()) + return Range(start, end) + + def _convert_to_solar_time(utc, longitude): seconds_per_degree = 240 offset_seconds = int(longitude * seconds_per_degree) diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index d7ffd2e69..a75b34bf5 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -18,7 +18,7 @@ from lark import Lark, v_args, Transformer -from datacube.api.query import _time_to_search_dims +from datacube.api.query import _time_to_search_dims, _time_to_open_range from datacube.model import Range @@ -33,8 +33,8 @@ equals_expr: field "=" value time_in_expr: time "in" date_range field_in_expr: field "in" "[" orderable "," orderable "]" - time_gt_expr: time ">" date_range - time_lt_expr: time "<" date_range + time_gt_expr: time ">" date_gt + time_lt_expr: time "<" date_lt field: FIELD time: TIME @@ -52,6 +52,10 @@ ?date_range: date -> single_date | "[" date "," date "]" -> date_pair + date_gt: date -> range_lower_bound + + date_lt: date -> range_upper_bound + date: YEAR ["-" MONTH ["-" DAY ]] TIME: "time" @@ -91,11 +95,11 @@ def field_in_expr(self, field, lower, upper): def time_in_expr(self, time_field, date_range): return {str(time_field): date_range} - def time_gt_expr(self, time_field, date_range): - return {str(time_field): date_range} + def time_gt_expr(self, time_field, date_gt): + return {str(time_field): date_gt} - def time_lt_expr(self, time_field, date_range): - return {str(time_field): date_range} + def time_lt_expr(self, time_field, date_lt): + return {str(time_field): date_lt} # Convert the literals def string(self, val): @@ -111,6 +115,13 @@ def single_date(self, date): def date_pair(self, start, end): return _time_to_search_dims((start, end)) + + def range_lower_bound(self, date): + return _time_to_open_range(date, lower_bound=True) + + def range_upper_bound(self, date): + return _time_to_open_range(date, lower_bound=False) + def date(self, y, m=None, d=None): return "-".join(x for x in [y, m, d] if x is not None) From 0e9c2b8eca46979fc94d2440beef250ac5c82f2f Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 9 May 2023 02:42:01 +0000 Subject: [PATCH 012/153] use timestamp 0 as lowest bound instead of hardcoded date --- datacube/api/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index 260622b52..1f73d01cc 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -362,7 +362,7 @@ def _time_to_open_range(time, lower_bound: bool): start = _to_datetime(pandas.Period(time).end_time.to_pydatetime()) end = _to_datetime(datetime.datetime.now()) else: # from earliest available to date provided (not inclusive) - start = _to_datetime(pandas.Period('1980-01-01').start_time.to_pydatetime()) + start = _to_datetime(datetime.datetime.fromtimestamp(0)) end = _to_datetime(pandas.Period(time).start_time.to_pydatetime()) return Range(start, end) From 583901d2fede0a8a41387f2b58facfb656267567 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 9 May 2023 02:49:26 +0000 Subject: [PATCH 013/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 984bf8431..04b3d6082 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,6 +12,7 @@ v1.8.next - Setup Dependabot, and Dependabot-generated updates (:pull:`1416`, :pull:`1420`, :pull:`1423`, :pull:`1428`) - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) +- Support open-ended date ranges in `datacube dataset search` (:pull:`1439`) v1.8.12 (7th March 2023) From 18c1f2c6da59b68bf09ab8241a206066d6f40239 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 10 May 2023 02:06:21 +0000 Subject: [PATCH 014/153] update doco --- datacube/ui/click.py | 5 ++++- datacube/ui/expression.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/datacube/ui/click.py b/datacube/ui/click.py index c339b8ae8..b04518626 100644 --- a/datacube/ui/click.py +++ b/datacube/ui/click.py @@ -340,16 +340,19 @@ def parsed_search_expressions(f): FIELD = VALUE FIELD in DATE-RANGE FIELD in [START, END] + TIME < DATE + TIME > DATE \b - DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD START and END can be either numbers or dates + Dates follow YYYY, YYYY-MM, or YYYY-MM-DD format FIELD: x, y, lat, lon, time, product, ... \b eg. 'time in [1996-01-01, 1996-12-31]' 'time in 1996' + 'time > 2020-01' 'lon in [130, 140]' 'lat in [-40, -30]' product=ls5_nbar_albers diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index a75b34bf5..eb97d0027 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -5,13 +5,15 @@ """ Search expression parsing for command line applications. -Three types of expressions are available: +Four types of expressions are available: FIELD = VALUE FIELD in DATE-RANGE FIELD in [START, END] + TIME > DATE + TIME < DATE -Where DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD +Where DATE or DATE-RANGE is one of YYYY, YYYY-MM or YYYY-MM-DD and START, END are either numbers or dates. """ # flake8: noqa From df7f48ffb35b68ad7f8d21415dc5ce298db8ab85 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 10 May 2023 05:54:21 +0000 Subject: [PATCH 015/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 984bf8431..0806e6617 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,6 +12,7 @@ v1.8.next - Setup Dependabot, and Dependabot-generated updates (:pull:`1416`, :pull:`1420`, :pull:`1423`, :pull:`1428`) - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) +- Add pre-commit hook to verify license headers (:pull:`1438`) v1.8.12 (7th March 2023) From 3506cff93f95067cd4106dcf108dce9c4b0fb7cc Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Mon, 22 May 2023 12:44:08 +1000 Subject: [PATCH 016/153] Allow open date range in dc load and find_datasets (#1443) * support open ended date range in query init * allow open ended time ranges in load() and find_datasets(), also simplify logic for cli * update doco and whats_new * get end of datetime.now() to avoid failing tests due to second mismatches * Minor update to documentation Even with open bounds, dates are still inclusive of the start and end dates. Minor update to wording to make this clearer --------- Co-authored-by: Ariana Barzinpour Co-authored-by: Robbi Bishop-Taylor --- datacube/api/core.py | 9 +++++++-- datacube/api/query.py | 21 ++++++--------------- datacube/ui/expression.py | 7 +++---- docs/about/whats_new.rst | 2 +- tests/api/test_query.py | 6 +++++- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/datacube/api/core.py b/datacube/api/core.py index 47a8c1798..a07d45008 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -232,12 +232,17 @@ def load(self, product=None, measurements=None, output_crs=None, resolution=None x=(1516200, 1541300), y=(-3867375, -3867350), crs='EPSG:3577' - The ``time`` dimension can be specified using a tuple of datetime objects or strings with - ``YYYY-MM-DD hh:mm:ss`` format. Data will be loaded inclusive of the start and finish times. E.g:: + The ``time`` dimension can be specified using a single or tuple of datetime objects or strings with + ``YYYY-MM-DD hh:mm:ss`` format. Data will be loaded inclusive of the start and finish times. + A ``None`` value in the range indicates an open range, with the provided date serving as either the + upper or lower bound. E.g:: time=('2000-01-01', '2001-12-31') time=('2000-01', '2001-12') time=('2000', '2001') + time=('2000') + time=('2000', None) # all data from 2000 onward + time=(None, '2000') # all data up to and including 2000 For 3D datasets, where the product definition contains an ``extra_dimension`` specification, these dimensions can be queried using that dimension's name. E.g.:: diff --git a/datacube/api/query.py b/datacube/api/query.py index f72a41479..fe77c9f12 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -128,8 +128,8 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc if time_coord is not None: self.search['time'] = _time_to_search_dims( (pandas_to_datetime(time_coord.values[0]).to_pydatetime(), - pandas_to_datetime(time_coord.values[-1]).to_pydatetime() - + datetime.timedelta(milliseconds=1)) # TODO: inclusive time searches + pandas_to_datetime(time_coord.values[-1]).to_pydatetime() + + datetime.timedelta(milliseconds=1)) # TODO: inclusive time searches ) @property @@ -342,7 +342,11 @@ def _time_to_search_dims(time_range): if hasattr(tr_end, 'isoformat'): tr_end = tr_end.isoformat() + if tr_start is None: + tr_start = datetime.datetime.fromtimestamp(0) start = _to_datetime(tr_start) + if tr_end is None: + tr_end = datetime.datetime.now().strftime("%Y-%m-%d") end = _to_datetime(pandas.Period(tr_end) .end_time .to_pydatetime()) @@ -354,19 +358,6 @@ def _time_to_search_dims(time_range): return tr -def _time_to_open_range(time, lower_bound: bool): - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - - if lower_bound: # from date provided (not inclusive) to latest available - start = _to_datetime(pandas.Period(time).end_time.to_pydatetime()) - end = _to_datetime(datetime.datetime.now()) - else: # from earliest available to date provided (not inclusive) - start = _to_datetime(datetime.datetime.fromtimestamp(0)) - end = _to_datetime(pandas.Period(time).start_time.to_pydatetime()) - return Range(start, end) - - def _convert_to_solar_time(utc, longitude): seconds_per_degree = 240 offset_seconds = int(longitude * seconds_per_degree) diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index 4e9c44d56..8ef1fdf73 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -20,7 +20,7 @@ from lark import Lark, v_args, Transformer -from datacube.api.query import _time_to_search_dims, _time_to_open_range +from datacube.api.query import _time_to_search_dims from datacube.model import Range @@ -119,11 +119,10 @@ def date_pair(self, start, end): return _time_to_search_dims((start, end)) def range_lower_bound(self, date): - return _time_to_open_range(date, lower_bound=True) + return _time_to_search_dims((date, None)) def range_upper_bound(self, date): - return _time_to_open_range(date, lower_bound=False) - + return _time_to_search_dims((None, date)) def date(self, y, m=None, d=None): return "-".join(x for x in [y, m, d] if x is not None) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index d897720a9..fe863fe8d 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -14,7 +14,7 @@ v1.8.next - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) - ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) - Add pre-commit hook to verify license headers (:pull:`1438`) -- Support open-ended date ranges in `datacube dataset search` (:pull:`1439`) +- Support open-ended date ranges in `datacube dataset search`, `dc.load`, and `dc.find_datasets` (:pull:`1439`, :pull:`1443`) v1.8.12 (7th March 2023) diff --git a/tests/api/test_query.py b/tests/api/test_query.py index 78b138908..d7d8e0b54 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -140,7 +140,11 @@ def format_test(start_out, end_out): ((datetime.datetime(2008, 1, 1), datetime.datetime(2008, 1, 10, 23, 59, 40)), format_test('2008-01-01T00:00:00', '2008-01-10T23:59:40.999999')), ((datetime.date(2008, 1, 1)), - format_test('2008-01-01T00:00:00', '2008-01-01T23:59:59.999999')) + format_test('2008-01-01T00:00:00', '2008-01-01T23:59:59.999999')), + ((datetime.date(2008, 1, 1), None), + format_test('2008-01-01T00:00:00', datetime.datetime.now().strftime("%Y-%m-%dT23:59:59.999999"))), + ((None, '2008'), + format_test(datetime.datetime.fromtimestamp(0).strftime("%Y-%m-%dT%H:%M:%S"), '2008-12-31T23:59:59.999999')) ] From 032265a4d18189678d4bc1c8846648a5bc1d9fcf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 18:02:11 +0000 Subject: [PATCH 017/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adrienverge/yamllint.git: v1.31.0 → v1.32.0](https://github.com/adrienverge/yamllint.git/compare/v1.31.0...v1.32.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3216b3db5..59143ce98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/adrienverge/yamllint.git - rev: v1.31.0 + rev: v1.32.0 hooks: - id: yamllint - repo: https://github.com/pre-commit/pre-commit-hooks From 00ce7fe855d483056c5b03a8200dc820e1a15787 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 30 May 2023 07:34:40 +0000 Subject: [PATCH 018/153] add archive_less_mature option to add and update --- datacube/index/abstract.py | 11 +- datacube/index/memory/_datasets.py | 6 +- datacube/index/null/_datasets.py | 5 +- datacube/index/postgis/_datasets.py | 40 ++++- datacube/index/postgres/_datasets.py | 12 +- datacube/scripts/dataset.py | 17 ++- integration_tests/conftest.py | 40 +++++ integration_tests/data/eo3/final_dataset.yaml | 141 ++++++++++++++++++ integration_tests/data/eo3/nrt_dataset.yaml | 138 +++++++++++++++++ integration_tests/index/test_index_data.py | 17 +++ 10 files changed, 412 insertions(+), 15 deletions(-) create mode 100644 integration_tests/data/eo3/final_dataset.yaml create mode 100644 integration_tests/data/eo3/nrt_dataset.yaml diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 75124f9ba..50e2bfb38 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -825,7 +825,8 @@ def bulk_has(self, ids_: Iterable[DSID]) -> Iterable[bool]: @abstractmethod def add(self, dataset: Dataset, - with_lineage: bool = True + with_lineage: bool = True, + archive_less_mature: bool = False, ) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -837,6 +838,10 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db + :param archive_less_mature: + - ``True`` search for less mature versions of the dataset + and archive them + :return: Persisted Dataset model """ @@ -874,12 +879,14 @@ def can_update(self, @abstractmethod def update(self, dataset: Dataset, - updates_allowed: Optional[Mapping[Offset, AllowPolicy]] = None + updates_allowed: Optional[Mapping[Offset, AllowPolicy]] = None, + archive_less_mature: bool = False, ) -> Dataset: """ Update dataset metadata and location :param Dataset dataset: Dataset model with unpersisted updates :param updates_allowed: Allowed updates + :param archive_less_mature: Find and archive less mature datasets :return: Persisted dataset model """ diff --git a/datacube/index/memory/_datasets.py b/datacube/index/memory/_datasets.py index 4ea1fc6e2..7b3245b76 100755 --- a/datacube/index/memory/_datasets.py +++ b/datacube/index/memory/_datasets.py @@ -72,7 +72,8 @@ def bulk_has(self, ids_: Iterable[DSID]) -> Iterable[bool]: return (self.has(id_) for id_ in ids_) def add(self, dataset: Dataset, - with_lineage: bool = True) -> Dataset: + with_lineage: bool = True, + archive_less_mature: bool = False) -> Dataset: if with_lineage is None: with_lineage = True _LOG.info('indexing %s', dataset.id) @@ -186,7 +187,8 @@ def can_update(self, def update(self, dataset: Dataset, - updates_allowed: Optional[Mapping[Offset, AllowPolicy]] = None + updates_allowed: Optional[Mapping[Offset, AllowPolicy]] = None, + archive_less_mature: bool = False ) -> Dataset: existing = self.get(dataset.id) if not existing: diff --git a/datacube/index/null/_datasets.py b/datacube/index/null/_datasets.py index 062ebae65..07317efae 100755 --- a/datacube/index/null/_datasets.py +++ b/datacube/index/null/_datasets.py @@ -28,7 +28,8 @@ def bulk_has(self, ids_): return [False for id_ in ids_] def add(self, dataset: Dataset, - with_lineage: bool = True) -> Dataset: + with_lineage: bool = True, + archive_less_mature: bool = False) -> Dataset: raise NotImplementedError() def search_product_duplicates(self, product: DatasetType, *args): @@ -37,7 +38,7 @@ def search_product_duplicates(self, product: DatasetType, *args): def can_update(self, dataset, updates_allowed=None): raise NotImplementedError() - def update(self, dataset: Dataset, updates_allowed=None): + def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=False): raise NotImplementedError() def archive(self, ids): diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 730c94220..b5227d726 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -137,7 +137,7 @@ def bulk_has(self, ids_): map((lambda x: UUID(x) if isinstance(x, str) else x), ids_)] def add(self, dataset: Dataset, - with_lineage: bool = True) -> Dataset: + with_lineage: bool = True, archive_less_mature: bool = False) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -148,6 +148,10 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db + :param archive_less_mature: + - ``True`` search for less mature versions of the dataset + and archive them + :rtype: Dataset """ @@ -170,8 +174,37 @@ def add(self, dataset: Dataset, if dataset.uris is not None: self._ensure_new_locations(dataset, transaction=transaction) + if archive_less_mature: + self._archive_less_mature(dataset) + return dataset + def _archive_less_mature(self, ds: Dataset): + less_mature = [] + dupes = self.search(product=ds.product.name, + region_code=ds.metadata.region_code, + time=ds.metadata.time) + for dupe in dupes: + if dupe.id == ds.id: + continue + if dupe.metadata.dataset_maturity == ds.metadata.dataset_maturity: + # Duplicate has the same maturity, which one should be archived is unclear + raise ValueError( + f"A dataset with the same maturity as dataset {ds.id} already exists, " + f"with id: {dupe.id}" + ) + if dupe.metadata.dataset_maturity < ds.metadata.dataset_maturity: + # Duplicate is more mature than dataset + # Note that "final" < "nrt" + raise ValueError( + f"A more mature version of dataset {ds.id} already exists, with id: " + f"{dupe.id} and maturity: {dupe.metadata.dataset_maturity}" + ) + less_mature.append(dupe.id) + self.archive(less_mature) + for lm_ds in less_mature: + _LOG.info(f"Archived less mature dataset: {lm_ds}") + def _init_bulk_add_cache(self): return {} @@ -305,11 +338,12 @@ def can_update(self, dataset, updates_allowed=None): return not bad_changes, good_changes, bad_changes - def update(self, dataset: Dataset, updates_allowed=None): + def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=False): """ Update dataset metadata and location :param Dataset dataset: Dataset to update :param updates_allowed: Allowed updates + :param archive_less_mature: Find and archive less mature datasets :rtype: Dataset """ existing = self.get(dataset.id) @@ -342,6 +376,8 @@ def update(self, dataset: Dataset, updates_allowed=None): raise ValueError("Failed to update dataset %s..." % dataset.id) transaction.update_spindex(dsids=[dataset.id]) transaction.update_search_index(dsids=[dataset.id]) + if archive_less_mature: + self._archive_less_mature(dataset) self._ensure_new_locations(dataset, existing) diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index ac1a385f7..42d4da2c3 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -132,7 +132,7 @@ def bulk_has(self, ids_): map((lambda x: UUID(x) if isinstance(x, str) else x), ids_)] def add(self, dataset: Dataset, - with_lineage: bool = True) -> Dataset: + with_lineage: bool = True, archive_less_mature: bool = False) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -143,6 +143,10 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db + :param archive_less_mature: + - ``True`` search for less mature versions of the dataset + and archive them + :rtype: Dataset """ @@ -188,6 +192,9 @@ def process_bunch(dss, main_ds, transaction): with self._db_connection(transaction=True) as transaction: process_bunch(dss, dataset, transaction) + if archive_less_mature: + _LOG.warning("archive-less-mature functionality is not implemented for postgres driver") + return dataset def _add_batch(self, batch_ds: Iterable[DatasetTuple], cache: Mapping[str, Any]) -> BatchStatus: @@ -278,11 +285,12 @@ def can_update(self, dataset, updates_allowed=None): return not bad_changes, good_changes, bad_changes - def update(self, dataset: Dataset, updates_allowed=None): + def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=False): """ Update dataset metadata and location :param Dataset dataset: Dataset to update :param updates_allowed: Allowed updates + :param archive_less_mature: Find and archive less mature datasets :rtype: Dataset """ existing = self.get(dataset.id) diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index 6041dbf0d..c7a4e5ba6 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -158,6 +158,8 @@ def mk_dataset(ds, uri): @click.option('--confirm-ignore-lineage', help="Pretend that there is no lineage data in the datasets being indexed, without confirmation", is_flag=True, default=False) +@click.option('--archive-less-mature', help='Archive less mature versions of the dataset', + is_flag=True, default=False) @click.argument('dataset-paths', type=str, nargs=-1) @ui.pass_index() def index_cmd(index, product_names, @@ -167,6 +169,7 @@ def index_cmd(index, product_names, dry_run, ignore_lineage, confirm_ignore_lineage, + archive_less_mature, dataset_paths): if not dataset_paths: @@ -204,7 +207,7 @@ def run_it(dataset_paths): index_datasets(dss, index, auto_add_lineage=auto_add_lineage and not confirm_ignore_lineage, - dry_run=dry_run) + dry_run=dry_run, archive_less_mature=archive_less_mature) # If outputting directly to terminal, show a progress bar. if sys.stdout.isatty(): @@ -214,12 +217,13 @@ def run_it(dataset_paths): run_it(dataset_paths) -def index_datasets(dss, index, auto_add_lineage, dry_run): +def index_datasets(dss, index, auto_add_lineage, dry_run, archive_less_mature): for dataset in dss: _LOG.info('Matched %s', dataset) if not dry_run: try: - index.datasets.add(dataset, with_lineage=auto_add_lineage) + index.datasets.add(dataset, with_lineage=auto_add_lineage, + archive_less_mature=archive_less_mature) except (ValueError, MissingRecordError) as e: _LOG.error('Failed to add dataset %s: %s', dataset.local_uri, e) @@ -246,9 +250,11 @@ def parse_update_rules(keys_that_can_change): - 'keep': keep as alternative location [default] - 'archive': mark as archived - 'forget': remove from the index''')) +@click.option('--archive-less-mature', help='Archive less mature versions of the dataset', + is_flag=True, default=False) @click.argument('dataset-paths', nargs=-1) @ui.pass_index() -def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths): +def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths, archive_less_mature): if not dataset_paths: click.echo('Error: no datasets provided\n') print_help_msg(update_cmd) @@ -303,7 +309,8 @@ def loc_keep(new_ds, existing_ds): if not dry_run: try: - index.datasets.update(dataset, updates_allowed=updates_allowed) + index.datasets.update(dataset, updates_allowed=updates_allowed, + archive_less_mature=archive_less_mature) update_loc(dataset, existing_ds) success += 1 echo('Updated %s' % dataset.id) diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 1e5608276..f623711bc 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -197,6 +197,24 @@ def africa_s2_product_doc(): return get_eo3_test_data_doc("s2_africa_product.yaml") +@pytest.fixture +def final_dataset_doc(): + return ( + get_eo3_test_data_doc("final_dataset.yaml"), + 's3://dea-public-data/baseline/ga_ls8c_ard_3/090/086/2023/04/30' + 'ga_ls8c_ard_3-2-1_090086_2023-04-30_final.stac-item.json' + ) + + +@pytest.fixture +def nrt_dataset_doc(): + return ( + get_eo3_test_data_doc("nrt_dataset.yaml"), + 's3://dea-public-data/baseline/ga_ls8c_ard_3/090/086/2023/04/30_nrt' + 'ga_ls8c_ard_3-2-1_090086_2023-04-30_nrt.stac-item.json' + ) + + def doc_to_ds(index, product_name, ds_doc, ds_path): from datacube.index.hl import Doc2Dataset resolver = Doc2Dataset(index, products=[product_name], verify_lineage=False) @@ -281,6 +299,28 @@ def africa_eo3_dataset(index, africa_s2_eo3_product, eo3_africa_dataset_doc): *eo3_africa_dataset_doc) +@pytest.fixture +def nrt_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, nrt_dataset_doc): + ds_doc = nrt_dataset_doc[0] + ds_path = nrt_dataset_doc[1] + from datacube.index.hl import Doc2Dataset + resolver = Doc2Dataset(index, products=[ls8_eo3_product.name], verify_lineage=False) + ds, err = resolver(ds_doc, ds_path) + assert err is None and ds is not None + return ds + + +@pytest.fixture +def final_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, final_dataset_doc): + ds_doc = final_dataset_doc[0] + ds_path = final_dataset_doc[1] + from datacube.index.hl import Doc2Dataset + resolver = Doc2Dataset(index, products=[ls8_eo3_product.name], verify_lineage=False) + ds, err = resolver(ds_doc, ds_path) + assert err is None and ds is not None + return ds + + @pytest.fixture def mem_index_fresh(in_memory_config): from datacube import Datacube diff --git a/integration_tests/data/eo3/final_dataset.yaml b/integration_tests/data/eo3/final_dataset.yaml new file mode 100644 index 000000000..f1c659cf3 --- /dev/null +++ b/integration_tests/data/eo3/final_dataset.yaml @@ -0,0 +1,141 @@ +--- +# Dataset +$schema: https://schemas.opendatacube.org/dataset +id: 6241395f-f341-41b0-96b7-60f486c577d0 + +label: ga_ls8c_ard_3-2-1_090086_2023-04-30_final +product: + name: ga_ls8c_ard_3 + href: https://collections.dea.ga.gov.au/product/ga_ls8c_ard_3 + +crs: epsg:32655 +geometry: + type: Polygon + coordinates: [[[559816.0052888468, -4219857.620084257], [559721.8933982822, -4219833.106601718], + [559732.9800465275, -4219728.735567618], [560962.9813034026, -4214988.730722999], + [571612.9871046449, -4174023.7084477567], [604522.9880076076, -4048158.7049931744], + [608708.0157636222, -4032408.6003978983], [608918.1498917936, -4031628.1325757634], + [609157.5, -4030897.5], [609243.8473913191, -4030914.6899256567], [609255.0, + -4030905.0], [609449.0888722979, -4030955.549131081], [609461.1380343755, + -4030957.947862498], [611246.2821331235, -4031422.984647127], [614491.7279428138, + -4032268.87029345], [795172.5726569144, -4079325.971481828], [796033.5623962873, + -4079585.522912585], [796173.1066017178, -4079621.893398282], [747082.5, -4268617.5], + [746957.9666764413, -4268587.428373786], [746857.7239312489, -4268594.104275004], + [746137.4401493662, -4268414.031855924], [560077.4396544178, -4219964.0317270355], + [559816.0052888468, -4219857.620084257]]] +grids: + default: + shape: [7931, 7901] + transform: [30.0, 0.0, 559485.0, 0.0, -30.0, -4030785.0, 0.0, 0.0, 1.0] + panchromatic: + shape: [15861, 15801] + transform: [15.0, 0.0, 559492.5, 0.0, -15.0, -4030792.5, 0.0, 0.0, 1.0] + +properties: + datetime: 2023-04-30 23:50:34.384549Z + dea:dataset_maturity: final + dea:product_maturity: stable + dtr:end_datetime: 2023-04-30 23:50:48.771747Z + dtr:start_datetime: 2023-04-30 23:50:19.818416Z + eo:cloud_cover: 93.24450316290117 + eo:gsd: 15.0 # Ground sample distance (m) + eo:instrument: OLI_TIRS + eo:platform: landsat-8 + eo:sun_azimuth: 36.68926293 + eo:sun_elevation: 29.26893394 + fmask:clear: 6.177487638246016 + fmask:cloud: 93.24450316290117 + fmask:cloud_shadow: 0.14004371437490343 + fmask:snow: 0.006964025007482182 + fmask:water: 0.43100145947042295 + gqa:abs_iterative_mean_x: 0.19 + gqa:abs_iterative_mean_xy: 0.26 + gqa:abs_iterative_mean_y: 0.18 + gqa:abs_x: 0.65 + gqa:abs_xy: 0.73 + gqa:abs_y: 0.33 + gqa:cep90: 0.4 + gqa:iterative_mean_x: -0.15 + gqa:iterative_mean_xy: 0.18 + gqa:iterative_mean_y: 0.1 + gqa:iterative_stddev_x: 0.16 + gqa:iterative_stddev_xy: 0.24 + gqa:iterative_stddev_y: 0.18 + gqa:mean_x: 0.23 + gqa:mean_xy: 0.23 + gqa:mean_y: -0.02 + gqa:stddev_x: 1.42 + gqa:stddev_xy: 1.48 + gqa:stddev_y: 0.42 + landsat:collection_category: T1 + landsat:collection_number: 2 + landsat:landsat_product_id: LC08_L1TP_090086_20230430_20230509_02_T1 + landsat:landsat_scene_id: LC80900862023120LGN00 + landsat:wrs_path: 90 + landsat:wrs_row: 86 + odc:dataset_version: 3.2.1 + odc:file_format: GeoTIFF + odc:processing_datetime: 2023-05-10 10:57:11.431704Z + odc:producer: ga.gov.au + odc:product_family: ard + odc:region_code: '090086' + +measurements: + nbart_blue: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band02.tif + nbart_coastal_aerosol: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band01.tif + nbart_green: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band03.tif + nbart_nir: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band05.tif + nbart_panchromatic: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band08.tif + grid: panchromatic + nbart_red: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band04.tif + nbart_swir_1: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band06.tif + nbart_swir_2: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_band07.tif + oa_azimuthal_exiting: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_azimuthal-exiting.tif + oa_azimuthal_incident: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_azimuthal-incident.tif + oa_combined_terrain_shadow: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_combined-terrain-shadow.tif + oa_exiting_angle: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_exiting-angle.tif + oa_fmask: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_fmask.tif + oa_incident_angle: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_incident-angle.tif + oa_nbart_contiguity: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_nbart-contiguity.tif + oa_relative_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_relative-azimuth.tif + oa_relative_slope: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_relative-slope.tif + oa_satellite_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_satellite-azimuth.tif + oa_satellite_view: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_satellite-view.tif + oa_solar_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_solar-azimuth.tif + oa_solar_zenith: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_solar-zenith.tif + oa_time_delta: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_final_time-delta.tif + +accessories: + thumbnail:nbart: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_final_thumbnail.jpg + metadata:processor: + path: ga_ls8c_ard_3-2-1_090086_2023-04-30_final.proc-info.yaml + checksum:sha1: + path: ga_ls8c_ard_3-2-1_090086_2023-04-30_final.sha1 + +lineage: + level1: + - 1362eb23-4bbc-5686-a803-cdf7cd6158d8 +... diff --git a/integration_tests/data/eo3/nrt_dataset.yaml b/integration_tests/data/eo3/nrt_dataset.yaml new file mode 100644 index 000000000..cfd38d8e2 --- /dev/null +++ b/integration_tests/data/eo3/nrt_dataset.yaml @@ -0,0 +1,138 @@ +--- +# Dataset +$schema: https://schemas.opendatacube.org/dataset +id: 94db51ab-4279-4402-8bcf-ec2f4ec494e3 + +label: ga_ls8c_ard_3-2-1_090086_2023-04-30_nrt +product: + name: ga_ls8c_ard_3 + href: https://collections.dea.ga.gov.au/product/ga_ls8c_ard_3 + +crs: epsg:32655 +geometry: + type: Polygon + coordinates: [[[559721.8933982822, -4219833.106601718], [559732.9800465275, -4219728.735567618], + [560962.9813034026, -4214988.730722999], [571612.9871046449, -4174023.7084477567], + [604522.9880076076, -4048158.7049931744], [608903.006475906, -4031688.6350602414], + [609157.5, -4030897.5], [609243.8827279789, -4030914.696960433], [609255.0, + -4030905.0], [609449.0888722979, -4030955.549131081], [609461.1380343755, + -4030957.947862498], [611246.2821331235, -4031422.984647127], [614491.7279428138, + -4032268.87029345], [795172.5726569144, -4079325.971481828], [796033.5623962873, + -4079585.522912585], [796173.1066017178, -4079621.893398282], [747082.5, -4268617.5], + [746957.9666764413, -4268587.428373786], [746857.7239312489, -4268594.104275004], + [746137.4401493662, -4268414.031855924], [560077.4396544178, -4219964.0317270355], + [559816.0052888468, -4219857.620084257], [559721.8933982822, -4219833.106601718]]] +grids: + default: + shape: [7931, 7901] + transform: [30.0, 0.0, 559485.0, 0.0, -30.0, -4030785.0, 0.0, 0.0, 1.0] + nbart:panchromatic: + shape: [15861, 15801] + transform: [15.0, 0.0, 559492.5, 0.0, -15.0, -4030792.5, 0.0, 0.0, 1.0] + +properties: + datetime: 2023-04-30 23:50:34.384549Z + dea:dataset_maturity: nrt + dea:product_maturity: stable + eo:cloud_cover: 93.23672818080765 + eo:gsd: 15.0 # Ground sample distance (m) + eo:instrument: OLI_TIRS + eo:platform: landsat-8 + eo:sun_azimuth: 36.68926293 + eo:sun_elevation: 29.26893394 + fmask:clear: 6.182802588011784 + fmask:cloud: 93.23672818080765 + fmask:cloud_shadow: 0.14184073083015938 + fmask:snow: 0.006917692167185185 + fmask:water: 0.4317108081832226 + gqa:abs_iterative_mean_x: 0.19 + gqa:abs_iterative_mean_xy: 0.26 + gqa:abs_iterative_mean_y: 0.18 + gqa:abs_x: 0.65 + gqa:abs_xy: 0.73 + gqa:abs_y: 0.33 + gqa:cep90: 0.4 + gqa:iterative_mean_x: -0.15 + gqa:iterative_mean_xy: 0.18 + gqa:iterative_mean_y: 0.1 + gqa:iterative_stddev_x: 0.16 + gqa:iterative_stddev_xy: 0.24 + gqa:iterative_stddev_y: 0.18 + gqa:mean_x: 0.23 + gqa:mean_xy: 0.23 + gqa:mean_y: -0.02 + gqa:stddev_x: 1.42 + gqa:stddev_xy: 1.48 + gqa:stddev_y: 0.42 + landsat:collection_category: RT + landsat:collection_number: 2 + landsat:landsat_product_id: LC08_L1TP_090086_20230430_20230501_02_RT + landsat:landsat_scene_id: LC80900862023120LGN00 + landsat:wrs_path: 90 + landsat:wrs_row: 86 + odc:dataset_version: 3.2.1 + odc:file_format: GeoTIFF + odc:processing_datetime: 2023-05-01 08:53:27.948360Z + odc:producer: ga.gov.au + odc:product_family: ard + odc:region_code: '090086' + +measurements: + nbart_blue: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band02.tif + nbart_coastal_aerosol: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band01.tif + nbart_green: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band03.tif + nbart_nir: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band05.tif + nbart_panchromatic: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band08.tif + grid: nbart:panchromatic + nbart_red: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band04.tif + nbart_swir_1: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band06.tif + nbart_swir_2: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_band07.tif + oa_azimuthal_exiting: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_azimuthal-exiting.tif + oa_azimuthal_incident: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_azimuthal-incident.tif + oa_combined_terrain_shadow: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_combined-terrain-shadow.tif + oa_exiting_angle: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_exiting-angle.tif + oa_fmask: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_fmask.tif + oa_incident_angle: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_incident-angle.tif + oa_nbart_contiguity: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_nbart-contiguity.tif + oa_relative_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_relative-azimuth.tif + oa_relative_slope: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_relative-slope.tif + oa_satellite_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_satellite-azimuth.tif + oa_satellite_view: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_satellite-view.tif + oa_solar_azimuth: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_solar-azimuth.tif + oa_solar_zenith: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_solar-zenith.tif + oa_time_delta: + path: ga_ls8c_oa_3-2-1_090086_2023-04-30_nrt_time-delta.tif + +accessories: + thumbnail:nbart: + path: ga_ls8c_nbart_3-2-1_090086_2023-04-30_nrt_thumbnail.jpg + metadata:processor: + path: ga_ls8c_ard_3-2-1_090086_2023-04-30_nrt.proc-info.yaml + checksum:sha1: + path: ga_ls8c_ard_3-2-1_090086_2023-04-30_nrt.sha1 + +lineage: + level1: + - 1090d7f4-2db2-58aa-bc09-cf8c446afdbd +... diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index e29b15946..5d75b75ea 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -94,6 +94,23 @@ def test_archive_datasets(index, local_config, ls8_eo3_dataset): assert not indexed_dataset.is_archived +@pytest.mark.parametrize('datacube_env_name', ('experimental',)) +def test_archive_less_mature(index, final_dataset, nrt_dataset): + # case 1: add nrt then final; nrt should get archived + index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) + index.datasets.get(nrt_dataset.id).is_active + index.datasets.add(final_dataset, with_lineage=False, archive_less_mature=True) + assert index.datasets.get(nrt_dataset.id).is_archived + assert index.datasets.get(final_dataset.id).is_active + + # case 2: purge nrt; re-add with final already there + index.datasets.purge([nrt_dataset.id]) + assert index.datasets.get(nrt_dataset.id) is None + with pytest.raises(ValueError): + # should error as more mature version of dataset already exists + index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) + + def test_purge_datasets(index, ls8_eo3_dataset): assert index.datasets.has(ls8_eo3_dataset.id) datasets = index.datasets.search_eager() From 6a20bfb19eb347496260f0158bc987593aec15d3 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 30 May 2023 07:40:44 +0000 Subject: [PATCH 019/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index fe863fe8d..ba23c6672 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) - Add pre-commit hook to verify license headers (:pull:`1438`) - Support open-ended date ranges in `datacube dataset search`, `dc.load`, and `dc.find_datasets` (:pull:`1439`, :pull:`1443`) +- Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`) v1.8.12 (7th March 2023) From 0639c5854554b0a4c8d19488d0b1106c24bf56ed Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 31 May 2023 00:30:25 +0000 Subject: [PATCH 020/153] add warning message in memory driver --- datacube/index/memory/_datasets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacube/index/memory/_datasets.py b/datacube/index/memory/_datasets.py index 7b3245b76..d294de191 100755 --- a/datacube/index/memory/_datasets.py +++ b/datacube/index/memory/_datasets.py @@ -101,6 +101,8 @@ def add(self, dataset: Dataset, self.by_product[dataset.product.name].append(dataset.id) else: self.by_product[dataset.product.name] = [dataset.id] + if archive_less_mature: + _LOG.warning("archive-less-mature functionality is not implemented for memory driver") return cast(Dataset, self.get(dataset.id)) def persist_source_relationship(self, ds: Dataset, src: Dataset, classifier: str) -> None: From 691e6897b4cad4217d60b42c252948f996f41782 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 31 May 2023 10:47:40 +1000 Subject: [PATCH 021/153] Pass X and Y Scale factors through to rasterio.warp.project. (#1450) * Pass X and Y Scale factors through to rasterio.warp.project. Update whats_new * Update PR number in whats_new.rst * Remove unused import. * Cleanup. * Cleanup. * Should probably just add it to the dictionary tbh. * Respond to Kirill's comments. --- datacube/storage/_read.py | 4 +++- docs/about/whats_new.rst | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/datacube/storage/_read.py b/datacube/storage/_read.py index c88aaeded..2c1dd05f9 100644 --- a/datacube/storage/_read.py +++ b/datacube/storage/_read.py @@ -129,6 +129,7 @@ def read_time_slice(rdr, is_nn = is_resampling_nn(resampling) scale = pick_read_scale(rr.scale, rdr) + scale_x, scale_y = [pick_read_scale(s) for s in rr.scale2] paste_ok, _ = can_paste(rr, ttol=0.9 if is_nn else 0.01) @@ -188,7 +189,8 @@ def norm_read_args(roi, shape, extra_dim_index): src_nodata=rdr.nodata, dst_nodata=dst_nodata) else: rio_reproject(pix, dst, src_gbox, dst_gbox, resampling, - src_nodata=rdr.nodata, dst_nodata=dst_nodata) + src_nodata=rdr.nodata, dst_nodata=dst_nodata, + XSCALE=scale_x, YSCALE=scale_y) return rr.roi_dst diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index fe863fe8d..17ab15fdf 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,9 @@ v1.8.next - ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) - Add pre-commit hook to verify license headers (:pull:`1438`) - Support open-ended date ranges in `datacube dataset search`, `dc.load`, and `dc.find_datasets` (:pull:`1439`, :pull:`1443`) +- Pass Y and Y Scale factors through to rasterio.warp.reproject, to eliminate projection bug affecting + non-square Areas Of Interest (See `Issue #1448`_) (:pull:`1450`) +.. _`Issue #1448`: https://github.com/opendatacube/datacube-core/issues/1448 v1.8.12 (7th March 2023) From f95ae0e549a0133b1c22d46db105f18b76e03588 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 31 May 2023 01:20:26 +0000 Subject: [PATCH 022/153] remove lineage from docs --- integration_tests/data/eo3/final_dataset.yaml | 4 +--- integration_tests/data/eo3/nrt_dataset.yaml | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/integration_tests/data/eo3/final_dataset.yaml b/integration_tests/data/eo3/final_dataset.yaml index f1c659cf3..19c27ffca 100644 --- a/integration_tests/data/eo3/final_dataset.yaml +++ b/integration_tests/data/eo3/final_dataset.yaml @@ -135,7 +135,5 @@ accessories: checksum:sha1: path: ga_ls8c_ard_3-2-1_090086_2023-04-30_final.sha1 -lineage: - level1: - - 1362eb23-4bbc-5686-a803-cdf7cd6158d8 +lineage: {} ... diff --git a/integration_tests/data/eo3/nrt_dataset.yaml b/integration_tests/data/eo3/nrt_dataset.yaml index cfd38d8e2..2388e62fa 100644 --- a/integration_tests/data/eo3/nrt_dataset.yaml +++ b/integration_tests/data/eo3/nrt_dataset.yaml @@ -132,7 +132,5 @@ accessories: checksum:sha1: path: ga_ls8c_ard_3-2-1_090086_2023-04-30_nrt.sha1 -lineage: - level1: - - 1090d7f4-2db2-58aa-bc09-cf8c446afdbd +lineage: {} ... From e964ed1ad8ed6ec8d8b8e0a01ba6dbd6b1d59af8 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 31 May 2023 02:37:56 +0000 Subject: [PATCH 023/153] move archive_less_mature to abstract and allow for postgres --- datacube/index/abstract.py | 31 ++++++++++++++++++++++++++++ datacube/index/postgis/_datasets.py | 29 +------------------------- datacube/index/postgres/_datasets.py | 5 ++--- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 50e2bfb38..99b4c7de3 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -898,6 +898,37 @@ def archive(self, ids: Iterable[DSID]) -> None: :param Iterable[Union[str,UUID]] ids: list of dataset ids to archive """ + def archive_less_mature(self, ds: Dataset) -> None: + """ + Archive less mature versions of a dataset + + :param Dataset ds: dataset to search + """ + less_mature = [] + dupes = self.search(product=ds.product.name, + region_code=ds.metadata.region_code, + time=ds.metadata.time) + for dupe in dupes: + if dupe.id == ds.id: + continue + if dupe.metadata.dataset_maturity == ds.metadata.dataset_maturity: + # Duplicate has the same maturity, which one should be archived is unclear + raise ValueError( + f"A dataset with the same maturity as dataset {ds.id} already exists, " + f"with id: {dupe.id}" + ) + if dupe.metadata.dataset_maturity < ds.metadata.dataset_maturity: + # Duplicate is more mature than dataset + # Note that "final" < "nrt" + raise ValueError( + f"A more mature version of dataset {ds.id} already exists, with id: " + f"{dupe.id} and maturity: {dupe.metadata.dataset_maturity}" + ) + less_mature.append(dupe.id) + self.archive(less_mature) + for lm_ds in less_mature: + _LOG.info(f"Archived less mature dataset: {lm_ds}") + @abstractmethod def restore(self, ids: Iterable[DSID]) -> None: """ diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index b5227d726..7f39b1883 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -173,38 +173,11 @@ def add(self, dataset: Dataset, # 1c. Store locations if dataset.uris is not None: self._ensure_new_locations(dataset, transaction=transaction) - if archive_less_mature: - self._archive_less_mature(dataset) + self.archive_less_mature(dataset) return dataset - def _archive_less_mature(self, ds: Dataset): - less_mature = [] - dupes = self.search(product=ds.product.name, - region_code=ds.metadata.region_code, - time=ds.metadata.time) - for dupe in dupes: - if dupe.id == ds.id: - continue - if dupe.metadata.dataset_maturity == ds.metadata.dataset_maturity: - # Duplicate has the same maturity, which one should be archived is unclear - raise ValueError( - f"A dataset with the same maturity as dataset {ds.id} already exists, " - f"with id: {dupe.id}" - ) - if dupe.metadata.dataset_maturity < ds.metadata.dataset_maturity: - # Duplicate is more mature than dataset - # Note that "final" < "nrt" - raise ValueError( - f"A more mature version of dataset {ds.id} already exists, with id: " - f"{dupe.id} and maturity: {dupe.metadata.dataset_maturity}" - ) - less_mature.append(dupe.id) - self.archive(less_mature) - for lm_ds in less_mature: - _LOG.info(f"Archived less mature dataset: {lm_ds}") - def _init_bulk_add_cache(self): return {} diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 42d4da2c3..c1cda8ddb 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -191,9 +191,8 @@ def process_bunch(dss, main_ds, transaction): with self._db_connection(transaction=True) as transaction: process_bunch(dss, dataset, transaction) - - if archive_less_mature: - _LOG.warning("archive-less-mature functionality is not implemented for postgres driver") + if archive_less_mature: + self.archive_less_mature(dataset) return dataset From 6d4dee7ab67cd32ef08b7430e9fd8b36b3b805e5 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 31 May 2023 06:09:49 +0000 Subject: [PATCH 024/153] move find dupes logic into a separate function --- datacube/index/abstract.py | 20 ++++++++++++++++---- integration_tests/index/test_index_data.py | 1 - 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 99b4c7de3..f35ad0674 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -904,6 +904,20 @@ def archive_less_mature(self, ds: Dataset) -> None: :param Dataset ds: dataset to search """ + less_mature = self.find_less_mature(ds) + less_mature_ids = map(lambda x: x.id, less_mature) + + self.archive(less_mature_ids) + for lm_ds in less_mature_ids: + _LOG.info(f"Archived less mature dataset: {lm_ds}") + + def find_less_mature(self, ds: Dataset) -> Iterable[Dataset]: + """ + Find less mature versions of a dataset + + :param Dataset ds: Dataset to search + :return: Iterable of less mature datasets + """ less_mature = [] dupes = self.search(product=ds.product.name, region_code=ds.metadata.region_code, @@ -924,10 +938,8 @@ def archive_less_mature(self, ds: Dataset) -> None: f"A more mature version of dataset {ds.id} already exists, with id: " f"{dupe.id} and maturity: {dupe.metadata.dataset_maturity}" ) - less_mature.append(dupe.id) - self.archive(less_mature) - for lm_ds in less_mature: - _LOG.info(f"Archived less mature dataset: {lm_ds}") + less_mature.append(dupe) + return less_mature @abstractmethod def restore(self, ids: Iterable[DSID]) -> None: diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index 5d75b75ea..bf82da269 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -94,7 +94,6 @@ def test_archive_datasets(index, local_config, ls8_eo3_dataset): assert not indexed_dataset.is_archived -@pytest.mark.parametrize('datacube_env_name', ('experimental',)) def test_archive_less_mature(index, final_dataset, nrt_dataset): # case 1: add nrt then final; nrt should get archived index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) From e3fab2b1dc2eeecc8660968d237c1f146b775d8b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 31 May 2023 06:40:22 +0000 Subject: [PATCH 025/153] update whats_new --- docs/about/whats_new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 4ff72d808..7f99fffe5 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -18,7 +18,7 @@ v1.8.next - Pass Y and Y Scale factors through to rasterio.warp.reproject, to eliminate projection bug affecting non-square Areas Of Interest (See `Issue #1448`_) (:pull:`1450`) .. _`Issue #1448`: https://github.com/opendatacube/datacube-core/issues/1448 -- Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`) +- Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`, :pull:`1452`) v1.8.12 (7th March 2023) From 2807c44d10b38650e6c96f3b26f899bb1ac0f53c Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 5 Jun 2023 06:59:54 +0000 Subject: [PATCH 026/153] allow for a bit of leniency in datetime comparison when searching for less mature, add test case --- datacube/index/abstract.py | 6 ++- datacube/index/fields.py | 2 +- integration_tests/conftest.py | 63 ++++++++++++++++++++++ integration_tests/index/test_index_data.py | 9 ++++ 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index f35ad0674..f46aa6850 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -16,6 +16,7 @@ NamedTuple, Optional, Tuple, Union, Sequence) from uuid import UUID +from datetime import timedelta from datacube.config import LocalConfig from datacube.index.exceptions import TransactionException @@ -919,9 +920,12 @@ def find_less_mature(self, ds: Dataset) -> Iterable[Dataset]: :return: Iterable of less mature datasets """ less_mature = [] + # 'expand' the date range by a millisecond to give a bit more leniency in datetime comparison + expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=1), + ds.metadata.time.end + timedelta(milliseconds=1)) dupes = self.search(product=ds.product.name, region_code=ds.metadata.region_code, - time=ds.metadata.time) + time=expanded_time_range) for dupe in dupes: if dupe.id == ds.id: continue diff --git a/datacube/index/fields.py b/datacube/index/fields.py index 726c2cae6..85d3e59d8 100644 --- a/datacube/index/fields.py +++ b/datacube/index/fields.py @@ -38,7 +38,7 @@ def evaluate(self, ctx): def as_expression(field: Field, value) -> Expression: """ - Convert a single field/value to expression, following the "simple" convensions. + Convert a single field/value to expression, following the "simple" conventions. """ if isinstance(value, Range): return field.between(value.begin, value.end) diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index f623711bc..bdbb46c26 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -78,6 +78,7 @@ def eo3_product_paths(): str(EO3_TESTDIR / "ard_ls8.odc-product.yaml"), str(EO3_TESTDIR / "ga_ls_wo_3.odc-product.yaml"), str(EO3_TESTDIR / "s2_africa_product.yaml"), + str(EO3_TESTDIR / "ga_s2am_ard_3.odc-product.yaml"), ] @@ -182,6 +183,11 @@ def extended_eo3_metadata_type_doc(): return get_eo3_test_data_doc("eo3_landsat_ard.odc-type.yaml") +@pytest.fixture +def eo3_sentinel_metadata_type_doc(): + return get_eo3_test_data_doc("eo3_sentinel_ard.odc-type.yaml") + + @pytest.fixture def extended_eo3_product_doc(): return get_eo3_test_data_doc("ard_ls8.odc-product.yaml") @@ -197,6 +203,11 @@ def africa_s2_product_doc(): return get_eo3_test_data_doc("s2_africa_product.yaml") +@pytest.fixture +def s2_ard_product_doc(): + return get_eo3_test_data_doc("ga_s2am_ard_3.odc-product.yaml") + + @pytest.fixture def final_dataset_doc(): return ( @@ -215,6 +226,24 @@ def nrt_dataset_doc(): ) +@pytest.fixture +def ga_s2am_ard_3_interim_doc(): + return ( + get_eo3_test_data_doc("ga_s2am_ard_3_interim.yaml"), + 's3://dea-public-data/baseline/ga_s2am_ard_3/53/JNN/2021/07/24_interim' + '20230222T235626/ga_s2am_ard_3-2-1_53JNN_2021-07-24_interim.odc-metadata.yaml' + ) + + +@pytest.fixture +def ga_s2am_ard_3_final_doc(): + return ( + get_eo3_test_data_doc("ga_s2am_ard_3_final.yaml"), + 's3://dea-public-data/baseline/ga_s2am_ard_3/53/JNN/2021/07/24' + '20210724T023436/ga_s2am_ard_3-2-1_53JNN_2021-07-24_final.odc-metadata.yaml' + ) + + def doc_to_ds(index, product_name, ds_doc, ds_path): from datacube.index.hl import Doc2Dataset resolver = Doc2Dataset(index, products=[product_name], verify_lineage=False) @@ -231,6 +260,13 @@ def extended_eo3_metadata_type(index, extended_eo3_metadata_type_doc): ) +@pytest.fixture +def eo3_sentinel_metadata_type(index, eo3_sentinel_metadata_type_doc): + return index.metadata_types.add( + index.metadata_types.from_doc(eo3_sentinel_metadata_type_doc) + ) + + @pytest.fixture def ls8_eo3_product(index, extended_eo3_metadata_type, extended_eo3_product_doc): return index.products.add_document(extended_eo3_product_doc) @@ -246,6 +282,11 @@ def africa_s2_eo3_product(index, africa_s2_product_doc): return index.products.add_document(africa_s2_product_doc) +@pytest.fixture +def ga_s2am_ard_3_product(index, eo3_sentinel_metadata_type, s2_ard_product_doc): + return index.products.add_document(s2_ard_product_doc) + + @pytest.fixture def eo3_products(index, extended_eo3_metadata_type, ls8_eo3_product, wo_eo3_product, @@ -321,6 +362,28 @@ def final_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, final_data return ds +@pytest.fixture +def ga_s2am_ard3_final(index, eo3_sentinel_metadata_type, ga_s2am_ard_3_product, ga_s2am_ard_3_final_doc): + ds_doc = ga_s2am_ard_3_final_doc[0] + ds_path = ga_s2am_ard_3_final_doc[1] + from datacube.index.hl import Doc2Dataset + resolver = Doc2Dataset(index, products=[ga_s2am_ard_3_product.name], verify_lineage=False) + ds, err = resolver(ds_doc, ds_path) + assert err is None and ds is not None + return ds + + +@pytest.fixture +def ga_s2am_ard3_interim(index, eo3_sentinel_metadata_type, ga_s2am_ard_3_product, ga_s2am_ard_3_interim_doc): + ds_doc = ga_s2am_ard_3_interim_doc[0] + ds_path = ga_s2am_ard_3_interim_doc[1] + from datacube.index.hl import Doc2Dataset + resolver = Doc2Dataset(index, products=[ga_s2am_ard_3_product.name], verify_lineage=False) + ds, err = resolver(ds_doc, ds_path) + assert err is None and ds is not None + return ds + + @pytest.fixture def mem_index_fresh(in_memory_config): from datacube import Datacube diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index bf82da269..5fde18d6f 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -110,6 +110,15 @@ def test_archive_less_mature(index, final_dataset, nrt_dataset): index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) +def test_archive_less_mature_approx_timestamp(index, ga_s2am_ard3_final, ga_s2am_ard3_interim): + # test archive_less_mature where there's a slight difference in timestamps + index.datasets.add(ga_s2am_ard3_interim, with_lineage=False) + index.datasets.get(ga_s2am_ard3_interim.id).is_active + index.datasets.add(ga_s2am_ard3_final, with_lineage=False, archive_less_mature=True) + assert index.datasets.get(ga_s2am_ard3_interim.id).is_archived + assert index.datasets.get(ga_s2am_ard3_final.id).is_active + + def test_purge_datasets(index, ls8_eo3_dataset): assert index.datasets.has(ls8_eo3_dataset.id) datasets = index.datasets.search_eager() From 1e65f380a2b3ff433b611d4d3ae74e92ebf31cdf Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 5 Jun 2023 07:09:15 +0000 Subject: [PATCH 027/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 7f99fffe5..79e53331e 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -19,6 +19,7 @@ v1.8.next non-square Areas Of Interest (See `Issue #1448`_) (:pull:`1450`) .. _`Issue #1448`: https://github.com/opendatacube/datacube-core/issues/1448 - Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`, :pull:`1452`) +- Allow for +-1ms leniency in finding other maturity versions of a dataset (:pull:`1452`) v1.8.12 (7th March 2023) From 1b9b89a79744e1433e9482c29208c540deacd206 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 6 Jun 2023 00:25:21 +0000 Subject: [PATCH 028/153] properly add new files --- .../data/eo3/eo3_sentinel_ard.odc-type.yaml | 361 ++++++++++++++++++ .../data/eo3/ga_s2am_ard_3.odc-product.yaml | 240 ++++++++++++ .../data/eo3/ga_s2am_ard_3_final.yaml | 150 ++++++++ .../data/eo3/ga_s2am_ard_3_interim.yaml | 151 ++++++++ 4 files changed, 902 insertions(+) create mode 100644 integration_tests/data/eo3/eo3_sentinel_ard.odc-type.yaml create mode 100644 integration_tests/data/eo3/ga_s2am_ard_3.odc-product.yaml create mode 100644 integration_tests/data/eo3/ga_s2am_ard_3_final.yaml create mode 100644 integration_tests/data/eo3/ga_s2am_ard_3_interim.yaml diff --git a/integration_tests/data/eo3/eo3_sentinel_ard.odc-type.yaml b/integration_tests/data/eo3/eo3_sentinel_ard.odc-type.yaml new file mode 100644 index 000000000..307eb9dd4 --- /dev/null +++ b/integration_tests/data/eo3/eo3_sentinel_ard.odc-type.yaml @@ -0,0 +1,361 @@ +--- +# Metadata Type +# url: https://explorer-aws.dea.ga.gov.au/metadata-types/eo3_sentinel_ard.odc-type.yaml +name: eo3_sentinel_ard +description: EO3 for Sentinel 2 ARD +dataset: + id: + - id + label: + - label + format: + - properties + - odc:file_format + sources: + - lineage + - source_datasets + creation_dt: + - properties + - odc:processing_datetime + grid_spatial: + - grid_spatial + - projection + measurements: + - measurements + search_fields: + lat: + type: double-range + max_offset: + - - extent + - lat + - end + min_offset: + - - extent + - lat + - begin + description: Latitude range + lon: + type: double-range + max_offset: + - - extent + - lon + - end + min_offset: + - - extent + - lon + - begin + description: Longitude range + time: + type: datetime-range + max_offset: + - - properties + - dtr:end_datetime + - - properties + - datetime + min_offset: + - - properties + - dtr:start_datetime + - - properties + - datetime + description: Acquisition time range + eo_gsd: + type: double + offset: + - properties + - eo:gsd + indexed: false + description: "Ground sampling distance of the sensor’s best resolution band\n\ + in metres; represents the size (or spatial resolution) of one pixel.\n" + crs_raw: + offset: + - crs + indexed: false + description: "The raw CRS string as it appears in metadata\n\n(e.g. ‘epsg:32654’)\n" + platform: + offset: + - properties + - eo:platform + indexed: false + description: Platform code + gqa_abs_x: + type: double + offset: + - properties + - gqa:abs_x + indexed: false + description: "Absolute value of the x-axis (east-to-west) GCP residuals, in\ + \ pixel units based on a 25 metre resolution reference image (i.e. 0.2 = 5\ + \ metres)\n" + gqa_abs_y: + type: double + offset: + - properties + - gqa:abs_y + indexed: false + description: "Absolute value of the y-axis (north-to-south) GCP residuals, in\ + \ pixel units based on a 25 metre resolution reference image (i.e. 0.2 = 5\ + \ metres)\n" + gqa_cep90: + type: double + offset: + - properties + - gqa:cep90 + indexed: false + description: "Circular error probable (90%) of the values of the GCP residuals,\ + \ in pixel units based on a 25 metre resolution reference image (i.e. 0.2\ + \ = 5 metres)\n" + fmask_snow: + type: double + offset: + - properties + - fmask:snow + indexed: false + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains clear snow pixels according to the Fmask algorithm\n" + gqa_abs_xy: + type: double + offset: + - properties + - gqa:abs_xy + indexed: false + description: "Absolute value of the total GCP residuals, in pixel units based\ + \ on a 25 metre resolution reference image (i.e. 0.2 = 5 metres)\n" + gqa_mean_x: + type: double + offset: + - properties + - gqa:mean_x + indexed: false + description: "Mean of the values of the x-axis (east-to-west) GCP residuals,\ + \ in pixel units based on a 25 metre resolution reference image (i.e. 0.2\ + \ = 5 metres)\n" + gqa_mean_y: + type: double + offset: + - properties + - gqa:mean_y + indexed: false + description: "Mean of the values of the y-axis (north-to-south) GCP residuals,\ + \ in pixel units based on a 25 metre resolution reference image (i.e. 0.2\ + \ = 5 metres)\n" + instrument: + offset: + - properties + - eo:instrument + indexed: false + description: Instrument name + cloud_cover: + type: double + offset: + - properties + - eo:cloud_cover + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains cloud pixels.\n\nFor these ARD products, this value comes\ + \ from the Fmask algorithm.\n" + fmask_clear: + type: double + offset: + - properties + - fmask:clear + indexed: false + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains clear land pixels according to the Fmask algorithm\n" + fmask_water: + type: double + offset: + - properties + - fmask:water + indexed: false + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains clear water pixels according to the Fmask algorithm\n" + gqa_mean_xy: + type: double + offset: + - properties + - gqa:mean_xy + indexed: false + description: "Mean of the values of the GCP residuals, in pixel units based\ + \ on a 25 metre resolution reference image (i.e. 0.2 = 5 metres)\n" + region_code: + offset: + - properties + - odc:region_code + description: "Spatial reference code from the provider.\nFor Sentinel it is\ + \ MGRS code.\n" + gqa_stddev_x: + type: double + offset: + - properties + - gqa:stddev_x + indexed: false + description: "Standard Deviation of the values of the x-axis (east-to-west)\ + \ GCP residuals, in pixel units based on a 25 metre resolution reference image\ + \ (i.e. 0.2 = 5 metres)\n" + gqa_stddev_y: + type: double + offset: + - properties + - gqa:stddev_y + indexed: false + description: "Standard Deviation of the values of the y-axis (north-to-south)\ + \ GCP residuals, in pixel units based on a 25 metre resolution reference image\ + \ (i.e. 0.2 = 5 metres)\n" + gqa_stddev_xy: + type: double + offset: + - properties + - gqa:stddev_xy + indexed: false + description: "Standard Deviation of the values of the GCP residuals, in pixel\ + \ units based on a 25 metre resolution reference image (i.e. 0.2 = 5 metres)\n" + eo_sun_azimuth: + type: double + offset: + - properties + - eo:sun_azimuth + indexed: false + description: "The azimuth angle of the sun at the moment of acquisition, in\ + \ degree units measured clockwise from due north\n" + product_family: + offset: + - properties + - odc:product_family + indexed: false + description: Product family code + dataset_maturity: + offset: + - properties + - dea:dataset_maturity + indexed: false + description: One of - final|interim|nrt (near real time) + eo_sun_elevation: + type: double + offset: + - properties + - eo:sun_elevation + indexed: false + description: "The elevation angle of the sun at the moment of acquisition, in\ + \ degree units relative to the horizon.\n" + sentinel_tile_id: + offset: + - properties + - sentinel:sentinel_tile_id + indexed: false + description: "Granule ID according to the ESA naming convention\n\n(e.g. ‘S2A_OPER_MSI_L1C_TL_SGS__20161214T040601_A007721_T53KRB_N02.04’)\n" + s2cloudless_clear: + type: double + offset: + - properties + - s2cloudless:clear + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains clear land pixels according to s3cloudless\n" + s2cloudless_cloud: + type: double + offset: + - properties + - s2cloudless:cloud + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains cloud land pixels according to s3cloudless\n" + fmask_cloud_shadow: + type: double + offset: + - properties + - fmask:cloud_shadow + indexed: false + description: "The proportion (from 0 to 100) of the dataset's valid data area\ + \ that contains cloud shadow pixels according to the Fmask algorithm\n" + gqa_iterative_mean_x: + type: double + offset: + - properties + - gqa:iterative_mean_x + indexed: false + description: "Mean of the values of the x-axis (east-to-west) GCP residuals\ + \ after removal of outliers, in pixel units based on a 25 metre resolution\ + \ reference image (i.e. 0.2 = 5 metres)\n" + gqa_iterative_mean_y: + type: double + offset: + - properties + - gqa:iterative_mean_y + indexed: false + description: "Mean of the values of the y-axis (north-to-south) GCP residuals\ + \ after removal of outliers, in pixel units based on a 25 metre resolution\ + \ reference image (i.e. 0.2 = 5 metres)\n" + gqa_iterative_mean_xy: + type: double + offset: + - properties + - gqa:iterative_mean_xy + indexed: false + description: "Mean of the values of the GCP residuals after removal of outliers,\ + \ in pixel units based on a 25 metre resolution reference image (i.e. 0.2\ + \ = 5 metres)\n" + sentinel_datastrip_id: + offset: + - properties + - sentinel:datastrip_id + indexed: false + description: "Unique identifier for a datastrip relative to a given Datatake.\n\ + \n(e.g. ‘S2A_OPER_MSI_L1C_DS_SGS__20161214T040601_S20161214T005840_N02.04’)\n" + sentinel_product_name: + offset: + - properties + - sentinel:product_name + indexed: false + description: "ESA product URI, with the '.SAFE' ending removed.\n\n(e.g. 'S2A_MSIL1C_20220303T000731_N0400_R073_T56LNM_20220303T012845')\n" + gqa_iterative_stddev_x: + type: double + offset: + - properties + - gqa:iterative_stddev_x + indexed: false + description: "Standard Deviation of the values of the x-axis (east-to-west)\ + \ GCP residuals after removal of outliers, in pixel units based on a 25 metre\ + \ resolution reference image (i.e. 0.2 = 5 metres)\n" + gqa_iterative_stddev_y: + type: double + offset: + - properties + - gqa:iterative_stddev_y + indexed: false + description: "Standard Deviation of the values of the y-axis (north-to-south)\ + \ GCP residuals after removal of outliers, in pixel units based on a 25 metre\ + \ resolution reference image (i.e. 0.2 = 5 metres)\n" + gqa_iterative_stddev_xy: + type: double + offset: + - properties + - gqa:iterative_stddev_xy + indexed: false + description: "Standard Deviation of the values of the GCP residuals after removal\ + \ of outliers, in pixel units based on a 25 metre resolution reference image\ + \ (i.e. 0.2 = 5 metres)\n" + gqa_abs_iterative_mean_x: + type: double + offset: + - properties + - gqa:abs_iterative_mean_x + indexed: false + description: "Mean of the absolute values of the x-axis (east-to-west) GCP residuals\ + \ after removal of outliers, in pixel units based on a 25 metre resolution\ + \ reference image (i.e. 0.2 = 5 metres)\n" + gqa_abs_iterative_mean_y: + type: double + offset: + - properties + - gqa:abs_iterative_mean_y + indexed: false + description: "Mean of the absolute values of the y-axis (north-to-south) GCP\ + \ residuals after removal of outliers, in pixel units based on a 25 metre\ + \ resolution reference image (i.e. 0.2 = 5 metres)\n" + gqa_abs_iterative_mean_xy: + type: double + offset: + - properties + - gqa:abs_iterative_mean_xy + indexed: false + description: "Mean of the absolute values of the GCP residuals after removal\ + \ of outliers, in pixel units based on a 25 metre resolution reference image\ + \ (i.e. 0.2 = 5 metres)\n" +... diff --git a/integration_tests/data/eo3/ga_s2am_ard_3.odc-product.yaml b/integration_tests/data/eo3/ga_s2am_ard_3.odc-product.yaml new file mode 100644 index 000000000..edc86a770 --- /dev/null +++ b/integration_tests/data/eo3/ga_s2am_ard_3.odc-product.yaml @@ -0,0 +1,240 @@ +--- +name: ga_s2am_ard_3 +license: CC-BY-4.0 +metadata_type: eo3_sentinel_ard +description: Geoscience Australia Sentinel 2A MSI Analysis Ready Data Collection 3 +metadata: + product: + name: ga_s2am_ard_3 + properties: + eo:platform: sentinel-2a + odc:producer: ga.gov.au + eo:instrument: MSI + odc:product_family: ard + dea:product_maturity: stable +measurements: +- name: nbart_coastal_aerosol + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band01 + - coastal_aerosol +- name: nbart_blue + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band02 + - blue +- name: nbart_green + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band03 + - green +- name: nbart_red + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band04 + - red +- name: nbart_red_edge_1 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band05 + - red_edge_1 +- name: nbart_red_edge_2 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band06 + - red_edge_2 +- name: nbart_red_edge_3 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band07 + - red_edge_3 +- name: nbart_nir_1 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band08 + - nir_1 + - nbart_common_nir +- name: nbart_nir_2 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band8a + - nir_2 +- name: nbart_swir_2 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band11 + - swir_2 + - nbart_common_swir_1 + - swir2 +- name: nbart_swir_3 + dtype: int16 + units: '1' + nodata: -999 + aliases: + - nbart_band12 + - swir_3 + - nbart_common_swir_2 +- name: oa_fmask + dtype: uint8 + units: '1' + nodata: 0 + aliases: + - fmask + flags_definition: + fmask: + bits: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + values: + '0': nodata + '1': valid + '2': cloud + '3': shadow + '4': snow + '5': water + description: Fmask +- name: oa_nbart_contiguity + dtype: uint8 + units: '1' + nodata: 255 + aliases: + - nbart_contiguity + flags_definition: + contiguous: + bits: + - 0 + values: + '0': false + '1': true +- name: oa_azimuthal_exiting + dtype: float32 + units: '1' + nodata: NaN + aliases: + - azimuthal_exiting +- name: oa_azimuthal_incident + dtype: float32 + units: '1' + nodata: NaN + aliases: + - azimuthal_incident +- name: oa_combined_terrain_shadow + dtype: uint8 + units: '1' + nodata: 255 + aliases: + - combined_terrain_shadow +- name: oa_exiting_angle + dtype: float32 + units: '1' + nodata: NaN + aliases: + - exiting_angle +- name: oa_incident_angle + dtype: float32 + units: '1' + nodata: NaN + aliases: + - incident_angle +- name: oa_relative_azimuth + dtype: float32 + units: '1' + nodata: NaN + aliases: + - relative_azimuth +- name: oa_relative_slope + dtype: float32 + units: '1' + nodata: NaN + aliases: + - relative_slope +- name: oa_satellite_azimuth + dtype: float32 + units: '1' + nodata: NaN + aliases: + - satellite_azimuth +- name: oa_satellite_view + dtype: float32 + units: '1' + nodata: NaN + aliases: + - satellite_view +- name: oa_solar_azimuth + dtype: float32 + units: '1' + nodata: NaN + aliases: + - solar_azimuth +- name: oa_solar_zenith + dtype: float32 + units: '1' + nodata: NaN + aliases: + - solar_zenith +- name: oa_time_delta + dtype: float32 + units: '1' + nodata: NaN + aliases: + - time_delta +- name: oa_s2cloudless_mask + dtype: uint8 + units: '1' + nodata: 0 + aliases: + - s2cloudless_mask + flags_definition: + s2cloudless_mask: + bits: + - 0 + - 1 + - 2 + values: + '0': nodata + '1': valid + '2': cloud + description: s2cloudless mask +- name: oa_s2cloudless_prob + dtype: float64 + units: '1' + nodata: NaN + aliases: + - s2cloudless_prob +# Product +# url: https://explorer-aws.dea.ga.gov.au/products/ga_s2am_ard_3.odc-product.yaml +load: + crs: EPSG:3577 + align: + x: 0 + y: 0 + resolution: + x: 10 + y: -10 +... diff --git a/integration_tests/data/eo3/ga_s2am_ard_3_final.yaml b/integration_tests/data/eo3/ga_s2am_ard_3_final.yaml new file mode 100644 index 000000000..0ee56c2ad --- /dev/null +++ b/integration_tests/data/eo3/ga_s2am_ard_3_final.yaml @@ -0,0 +1,150 @@ +--- +# Dataset +$schema: https://schemas.opendatacube.org/dataset +id: 4123766b-5779-4f96-b71f-28e28a326434 + +label: ga_s2am_ard_3-2-1_53JNN_2021-07-24_final +product: + name: ga_s2am_ard_3 + href: https://collections.dea.ga.gov.au/product/ga_s2am_ard_3 + +crs: epsg:32753 +geometry: + type: Polygon + coordinates: [[[499980.0, 7300000.0], [609780.0, 7300000.0], [609780.0, 7190200.0], + [499980.0, 7190200.0], [499980.0, 7300000.0]]] +grids: + default: + shape: [5490, 5490] + transform: [20.0, 0.0, 499980.0, 0.0, -20.0, 7300000.0, 0.0, 0.0, 1.0] + '10': + shape: [10980, 10980] + transform: [10.0, 0.0, 499980.0, 0.0, -10.0, 7300000.0, 0.0, 0.0, 1.0] + '60': + shape: [1830, 1830] + transform: [60.0, 0.0, 499980.0, 0.0, -60.0, 7300000.0, 0.0, 0.0, 1.0] + +properties: + datetime: 2021-07-24 01:14:10.195987Z + dea:dataset_maturity: final + dea:product_maturity: stable + eo:cloud_cover: 0.0 + eo:constellation: sentinel-2 + eo:gsd: 10.0 # Ground sample distance (m) + eo:instrument: MSI + eo:platform: sentinel-2a + eo:sun_azimuth: 33.4140301515032 + eo:sun_elevation: 52.1651784839186 + fmask:clear: 99.9999469145756 + fmask:cloud: 0.0 + fmask:cloud_shadow: 0.0 + fmask:snow: 0.0 + fmask:water: 5.30854244013789e-05 + gqa:abs_iterative_mean_x: 0.88 + gqa:abs_iterative_mean_xy: 2.17 + gqa:abs_iterative_mean_y: 1.98 + gqa:abs_x: 1.48 + gqa:abs_xy: 4.03 + gqa:abs_y: 3.74 + gqa:cep90: 4.78 + gqa:iterative_mean_x: 0.52 + gqa:iterative_mean_xy: 0.55 + gqa:iterative_mean_y: 0.18 + gqa:iterative_stddev_x: 1.41 + gqa:iterative_stddev_xy: 4.21 + gqa:iterative_stddev_y: 3.97 + gqa:mean_x: 0.69 + gqa:mean_xy: 0.74 + gqa:mean_y: -0.25 + gqa:stddev_x: 14.77 + gqa:stddev_xy: 39.88 + gqa:stddev_y: 37.04 + odc:dataset_version: 3.2.1 + odc:file_format: GeoTIFF + odc:processing_datetime: 2022-07-12 17:54:39.369087Z + odc:producer: ga.gov.au + odc:product_family: ard + odc:region_code: 53JNN + s2cloudless:clear: 100.0 + s2cloudless:cloud: 0.0 + sat:orbit_state: descending + sat:relative_orbit: 45 + sentinel:datastrip_id: S2A_OPER_MSI_L1C_DS_VGS4_20210724T023436_S20210724T010731_N03.01 + sentinel:datatake_start_datetime: 2021-07-24 02:34:36Z + sentinel:product_name: S2A_MSIL1C_20210724T010731_N0301_R045_T53JNN_20210724T023436 + sentinel:sentinel_tile_id: S2A_OPER_MSI_L1C_TL_VGS4_20210724T023436_A031788_T53JNN_N03.01 + +measurements: + nbart_blue: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band02.tif + grid: '10' + nbart_coastal_aerosol: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band01.tif + grid: '60' + nbart_green: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band03.tif + grid: '10' + nbart_nir_1: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band08.tif + grid: '10' + nbart_nir_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band08a.tif + nbart_red: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band04.tif + grid: '10' + nbart_red_edge_1: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band05.tif + nbart_red_edge_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band06.tif + nbart_red_edge_3: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band07.tif + nbart_swir_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band11.tif + nbart_swir_3: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_band12.tif + oa_azimuthal_exiting: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_azimuthal-exiting.tif + oa_azimuthal_incident: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_azimuthal-incident.tif + oa_combined_terrain_shadow: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_combined-terrain-shadow.tif + oa_exiting_angle: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_exiting-angle.tif + oa_fmask: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_fmask.tif + oa_incident_angle: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_incident-angle.tif + oa_nbart_contiguity: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_nbart-contiguity.tif + grid: '10' + oa_relative_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_relative-azimuth.tif + oa_relative_slope: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_relative-slope.tif + oa_s2cloudless_mask: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_s2cloudless-mask.tif + grid: '60' + oa_s2cloudless_prob: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_s2cloudless-prob.tif + grid: '60' + oa_satellite_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_satellite-azimuth.tif + oa_satellite_view: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_satellite-view.tif + oa_solar_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_solar-azimuth.tif + oa_solar_zenith: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_solar-zenith.tif + oa_time_delta: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_final_time-delta.tif + +accessories: + thumbnail:nbart: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_final_thumbnail.jpg + metadata:processor: + path: ga_s2am_ard_3-2-1_53JNN_2021-07-24_final.proc-info.yaml + checksum:sha1: + path: ga_s2am_ard_3-2-1_53JNN_2021-07-24_final.sha1 + +lineage: {} +... diff --git a/integration_tests/data/eo3/ga_s2am_ard_3_interim.yaml b/integration_tests/data/eo3/ga_s2am_ard_3_interim.yaml new file mode 100644 index 000000000..41e828da2 --- /dev/null +++ b/integration_tests/data/eo3/ga_s2am_ard_3_interim.yaml @@ -0,0 +1,151 @@ +--- +# Dataset +$schema: https://schemas.opendatacube.org/dataset +id: c0324cbb-f499-4e8d-8001-a38206ff6904 + +label: ga_s2am_ard_3-2-1_53JNN_2021-07-24_interim +product: + name: ga_s2am_ard_3 + href: https://collections.dea.ga.gov.au/product/ga_s2am_ard_3 + +crs: epsg:32753 +geometry: + type: Polygon + coordinates: [[[499980.0, 7300000.0], [609780.0, 7300000.0], [609780.0, 7190200.0], + [499980.0, 7190200.0], [499980.0, 7300000.0]]] +grids: + default: + shape: [5490, 5490] + transform: [20.0, 0.0, 499980.0, 0.0, -20.0, 7300000.0, 0.0, 0.0, 1.0] + '10': + shape: [10980, 10980] + transform: [10.0, 0.0, 499980.0, 0.0, -10.0, 7300000.0, 0.0, 0.0, 1.0] + '60': + shape: [1830, 1830] + transform: [60.0, 0.0, 499980.0, 0.0, -60.0, 7300000.0, 0.0, 0.0, 1.0] + +properties: + datetime: 2021-07-24 01:14:10.195809Z + dea:dataset_maturity: interim + dea:product_maturity: stable + eo:cloud_cover: 0.0 + eo:constellation: sentinel-2 + eo:gsd: 10.0 # Ground sample distance (m) + eo:instrument: MSI + eo:platform: sentinel-2a + eo:sun_azimuth: 33.4134402697606 + eo:sun_elevation: 52.1647723037447 + fmask:clear: 99.99995355025365 + fmask:cloud: 0.0 + fmask:cloud_shadow: 0.0 + fmask:snow: 0.0 + fmask:water: 4.6449746351206534e-05 + gqa:abs_iterative_mean_x: 0.48 + gqa:abs_iterative_mean_xy: 1.02 + gqa:abs_iterative_mean_y: 0.91 + gqa:abs_x: 0.81 + gqa:abs_xy: 2.04 + gqa:abs_y: 1.87 + gqa:cep90: 2.56 + gqa:iterative_mean_x: 0.33 + gqa:iterative_mean_xy: 0.41 + gqa:iterative_mean_y: 0.24 + gqa:iterative_stddev_x: 0.6 + gqa:iterative_stddev_xy: 1.7 + gqa:iterative_stddev_y: 1.6 + gqa:mean_x: 0.41 + gqa:mean_xy: 0.41 + gqa:mean_y: 0.03 + gqa:stddev_x: 3.58 + gqa:stddev_xy: 10.73 + gqa:stddev_y: 10.12 + odc:dataset_version: 3.2.1 + odc:file_format: GeoTIFF + odc:processing_datetime: 2023-05-31 21:12:25.979068Z + odc:producer: ga.gov.au + odc:product_family: ard + odc:region_code: 53JNN + s2cloudless:clear: 100.0 + s2cloudless:cloud: 0.0 + sentinel:datastrip_id: S2A_OPER_MSI_L1C_DS_S2RP_20230222T235626_S20210724T010731_N05.00 + sentinel:datatake_start_datetime: 2023-02-22 23:56:26Z + sentinel:grid_square: NN + sentinel:latitude_band: J + sentinel:product_name: S2A_MSIL1C_20210724T010731_N0500_R045_T53JNN_20230222T235626.SAFE + sentinel:sentinel_tile_id: S2A_OPER_MSI_L1C_TL_S2RP_20230222T235626_A031788_T53JNN_N05.00 + sentinel:utm_zone: 53 + +measurements: + nbart_blue: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band02.tif + grid: '10' + nbart_coastal_aerosol: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band01.tif + grid: '60' + nbart_green: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band03.tif + grid: '10' + nbart_nir_1: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band08.tif + grid: '10' + nbart_nir_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band08a.tif + nbart_red: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band04.tif + grid: '10' + nbart_red_edge_1: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band05.tif + nbart_red_edge_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band06.tif + nbart_red_edge_3: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band07.tif + nbart_swir_2: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band11.tif + nbart_swir_3: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_band12.tif + oa_azimuthal_exiting: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_azimuthal-exiting.tif + oa_azimuthal_incident: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_azimuthal-incident.tif + oa_combined_terrain_shadow: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_combined-terrain-shadow.tif + oa_exiting_angle: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_exiting-angle.tif + oa_fmask: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_fmask.tif + oa_incident_angle: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_incident-angle.tif + oa_nbart_contiguity: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_nbart-contiguity.tif + grid: '10' + oa_relative_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_relative-azimuth.tif + oa_relative_slope: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_relative-slope.tif + oa_s2cloudless_mask: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_s2cloudless-mask.tif + grid: '60' + oa_s2cloudless_prob: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_s2cloudless-prob.tif + grid: '60' + oa_satellite_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_satellite-azimuth.tif + oa_satellite_view: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_satellite-view.tif + oa_solar_azimuth: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_solar-azimuth.tif + oa_solar_zenith: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_solar-zenith.tif + oa_time_delta: + path: ga_s2am_oa_3-2-1_53JNN_2021-07-24_interim_time-delta.tif + +accessories: + thumbnail:nbart: + path: ga_s2am_nbart_3-2-1_53JNN_2021-07-24_interim_thumbnail.jpg + metadata:processor: + path: ga_s2am_ard_3-2-1_53JNN_2021-07-24_interim.proc-info.yaml + checksum:sha1: + path: ga_s2am_ard_3-2-1_53JNN_2021-07-24_interim.sha1 + +lineage: {} +... From 11af4db7a42ffa9a4e5f7da917b18744bb519515 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 6 Jun 2023 00:50:38 +0000 Subject: [PATCH 029/153] fix failing tests --- integration_tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index bdbb46c26..21ad7cc12 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -78,7 +78,6 @@ def eo3_product_paths(): str(EO3_TESTDIR / "ard_ls8.odc-product.yaml"), str(EO3_TESTDIR / "ga_ls_wo_3.odc-product.yaml"), str(EO3_TESTDIR / "s2_africa_product.yaml"), - str(EO3_TESTDIR / "ga_s2am_ard_3.odc-product.yaml"), ] From c6f73fe838e50876a044992de4637d100783d9db Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 6 Jun 2023 01:04:34 +0000 Subject: [PATCH 030/153] refactor doc_to_ds without adding dataset logic --- integration_tests/conftest.py | 52 ++++++++++++++++------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 21ad7cc12..dc6b62421 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -252,6 +252,14 @@ def doc_to_ds(index, product_name, ds_doc, ds_path): return index.datasets.get(ds.id) +def doc_to_ds_no_add(index, product_name, ds_doc, ds_path): + from datacube.index.hl import Doc2Dataset + resolver = Doc2Dataset(index, products=[product_name], verify_lineage=False) + ds, err = resolver(ds_doc, ds_path) + assert err is None and ds is not None + return ds + + @pytest.fixture def extended_eo3_metadata_type(index, extended_eo3_metadata_type_doc): return index.metadata_types.add( @@ -341,46 +349,34 @@ def africa_eo3_dataset(index, africa_s2_eo3_product, eo3_africa_dataset_doc): @pytest.fixture def nrt_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, nrt_dataset_doc): - ds_doc = nrt_dataset_doc[0] - ds_path = nrt_dataset_doc[1] - from datacube.index.hl import Doc2Dataset - resolver = Doc2Dataset(index, products=[ls8_eo3_product.name], verify_lineage=False) - ds, err = resolver(ds_doc, ds_path) - assert err is None and ds is not None - return ds + return doc_to_ds_no_add( + index, + ls8_eo3_product.name, + *nrt_dataset_doc) @pytest.fixture def final_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, final_dataset_doc): - ds_doc = final_dataset_doc[0] - ds_path = final_dataset_doc[1] - from datacube.index.hl import Doc2Dataset - resolver = Doc2Dataset(index, products=[ls8_eo3_product.name], verify_lineage=False) - ds, err = resolver(ds_doc, ds_path) - assert err is None and ds is not None - return ds + return doc_to_ds_no_add( + index, + ls8_eo3_product.name, + *final_dataset_doc) @pytest.fixture def ga_s2am_ard3_final(index, eo3_sentinel_metadata_type, ga_s2am_ard_3_product, ga_s2am_ard_3_final_doc): - ds_doc = ga_s2am_ard_3_final_doc[0] - ds_path = ga_s2am_ard_3_final_doc[1] - from datacube.index.hl import Doc2Dataset - resolver = Doc2Dataset(index, products=[ga_s2am_ard_3_product.name], verify_lineage=False) - ds, err = resolver(ds_doc, ds_path) - assert err is None and ds is not None - return ds + return doc_to_ds_no_add( + index, + ga_s2am_ard_3_product.name, + *ga_s2am_ard_3_final_doc) @pytest.fixture def ga_s2am_ard3_interim(index, eo3_sentinel_metadata_type, ga_s2am_ard_3_product, ga_s2am_ard_3_interim_doc): - ds_doc = ga_s2am_ard_3_interim_doc[0] - ds_path = ga_s2am_ard_3_interim_doc[1] - from datacube.index.hl import Doc2Dataset - resolver = Doc2Dataset(index, products=[ga_s2am_ard_3_product.name], verify_lineage=False) - ds, err = resolver(ds_doc, ds_path) - assert err is None and ds is not None - return ds + return doc_to_ds_no_add( + index, + ga_s2am_ard_3_product.name, + *ga_s2am_ard_3_interim_doc) @pytest.fixture From 0dd82926836ffe1825d2bca5bcb21efd1d1fea82 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 6 Jun 2023 12:34:56 +1000 Subject: [PATCH 031/153] Add missing PR's to whats_new.rst and prepare for 1.8.13 release. (#1453) --- docs/about/whats_new.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 79e53331e..5809cb730 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,19 +8,23 @@ What's New v1.8.next ========= -- Fix broken Github action workflows (:pull:`1425`, :pull:`1433`) +v1.8.13 (6th June 2023) +======================= + +- Fix broken Github action workflows (:pull:`1425`, :pull:`1427`, :pull:`1433`) - Setup Dependabot, and Dependabot-generated updates (:pull:`1416`, :pull:`1420`, :pull:`1423`, - :pull:`1428`) + :pull:`1428`, :pull:`1436`, :pull:`1447`) - Documentation fixes (:pull:`1417`, :pull:`1418`, :pull:`1430`) - ``datacube dataset`` cli commands print error message if missing argument (:pull:`1437`) - Add pre-commit hook to verify license headers (:pull:`1438`) - Support open-ended date ranges in `datacube dataset search`, `dc.load`, and `dc.find_datasets` (:pull:`1439`, :pull:`1443`) - Pass Y and Y Scale factors through to rasterio.warp.reproject, to eliminate projection bug affecting non-square Areas Of Interest (See `Issue #1448`_) (:pull:`1450`) -.. _`Issue #1448`: https://github.com/opendatacube/datacube-core/issues/1448 -- Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`, :pull:`1452`) +- Add `archive_less_mature` option to `datacube dataset add` and `datacube dataset update` (:pull:`1451`) - Allow for +-1ms leniency in finding other maturity versions of a dataset (:pull:`1452`) +- Update whats_new.rst for release (:pull:`1453`) +.. _`Issue #1448`: https://github.com/opendatacube/datacube-core/issues/1448 v1.8.12 (7th March 2023) ======================== From c8795e3144fcf6cdabed23eecc9e8cb04626bcef Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:45:48 +1000 Subject: [PATCH 032/153] Fix gha pypi publishing condition (#1454) * fix gha pypi publishing condition * update whats_new --------- Co-authored-by: Ariana Barzinpour --- .github/workflows/main.yml | 9 ++++++++- docs/about/whats_new.rst | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b8e9333a..032ddbd10 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,13 @@ jobs: with: fetch-depth: 0 + - name: Config + id: cfg + run: | + if "${GITHUB_REF}" in "refs/tags/"*; then + echo "push_pypi=yes" >> $GITHUB_OUTPUT + fi + - uses: dorny/paths-filter@v2 id: changes if: | @@ -117,7 +124,7 @@ jobs: - name: Publish to PyPi if: | github.event_name == 'push' - && github.ref == 'refs/heads/pypi/publish' + && steps.cfg.outputs.push_pypi == 'yes' run: | if [ -n "${TWINE_PASSWORD}" ]; then docker run --rm \ diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 5809cb730..0b5d561ab 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,9 @@ What's New v1.8.next ========= +- Fix broken pypi publishing Github action (:pull:`1454`) + + v1.8.13 (6th June 2023) ======================= From e3acb3928bfdded8b9bce5be4869eeed97243abf Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 7 Jun 2023 04:28:54 +0000 Subject: [PATCH 033/153] update ubntu installation instructions --- docs/installation/setup/common_install.rst | 75 +++++++++++++--------- docs/installation/setup/ubuntu.rst | 8 +-- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 5e5966fd0..34f81d715 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -7,33 +7,30 @@ Python and packages Python 3.8+ is required. -Anaconda Python ---------------- +Conda environment setup +----------------------- -`Install Anaconda Python `_ +Conda environments are recommended for use in isolating your ODC development environment from your system installation and other Python environments. -Add conda-forge to package channels:: +We recommend you use Mambaforge to set up your conda virtual environment, as all the required packages are obtained from the conda-forge channel. - conda config --append channels conda-forge +Download and install `Mambaforge `_ -Conda environments are recommended for use in isolating your ODC development environment from your system installation and other Python environments. +Download the latest version of the Open Data Cube from the `repository `_ :: -Install required Python packages and create a conda environment named ``odc_env``. + git clone https://github.com/opendatacube/datacube-core + cd datacube-core -Python:: +Create a conda environment named ``cubeenv``:: - conda create --name odc_env python=3.8 datacube + mamba env create -f conda-environment.yml -Activate the ``odc_env`` conda environment:: +Activate the ``cubeenv`` conda environment:: - conda activate odc_env + conda activate cubeenv Find out more about conda environments `here `_. -Install other packages:: - - conda install jupyter matplotlib scipy pytest-cov hypothesis - Postgres database configuration =============================== @@ -44,7 +41,7 @@ If this is a new installation of Postgres on your system it is probably wise to In a terminal, type:: - sudo -u postgres psql postgres + sudo -iu postgres psql postgres Set a password for the "postgres" database role using the command:: @@ -52,43 +49,52 @@ Set a password for the "postgres" database role using the command:: and set the password when prompted. The password text will be hidden from the console for security purposes. -Type **Control+D** or **\q** to exit the posgreSQL prompt. +Type **Control+D** or **\\q** to exit the posgreSQL prompt. By default in Ubuntu, Postgresql is configured to use ``ident sameuser`` authentication for any connections from the same machine which is useful for development. Check out the excellent Postgresql documentation for more information, but essentially this means that if your Ubuntu username is ``foo`` and you add ``foo`` as a Postgresql user then you can connect to a database without requiring a password for many functions. Since the only user who can connect to a fresh install is the postgres user, here is how to create yourself a database account (which is in this case also a database superuser) with the same name as your login name and then create a password for the user:: - sudo -u postgres createuser --superuser $USER - sudo -u postgres psql + sudo -iu postgres createuser --superuser $USER + sudo -iu postgres psql + + postgres=# \password - postgres=# \password $USER +Now we can create the ``agdcintegration`` and ``odcintegration`` databases for testing:: -Now we can create an ``agdcintegration`` database for testing:: + postgres=# create database agdcintegration; + postgres=# create database odcintegration; + +Or, directly from the bash terminal:: - createdb agdcintegration + sudo -iu postgres createdb agdcintegration + sudo -iu postgres createdb odcintegration Connecting to your own database to try out some SQL should now be as easy as:: - psql -d agdcintegration + sudo -iu postgres psql -d agdcintegration Open Data Cube source and development configuration =================================================== -Download the latest version of the software from the `repository `_ :: - - git clone https://github.com/opendatacube/datacube-core - cd datacube-core - We need to specify the database user and password for the ODC integration testing. To do this:: cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf -Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: +Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: [datacube] db_hostname: localhost db_database: agdcintegration + index_driver: default + db_username: + db_password: + + [experimental] + db_hostname: localhost + db_database: odcintegration + index_driver: postgis db_username: db_password: @@ -97,15 +103,22 @@ Note: For Ubuntu Setup the db_hostname should be set to "/var/run/postgresql". F Verify it all works =================== +Install additional test dependencies:: + + cd datacube-core + pip install --upgrade -e '.[test]' + Run the integration tests:: - cd datacube-core ./check-code.sh integration_tests Build the documentation:: - cd datacube-core/docs + pip install --upgrade -e '.[doc]' + cd docs pip install -r requirements.txt + sudo apt install make + sudo apt install pandoc make html Then open :file:`_build/html/index.html` in your browser to view the Documentation. diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index 56c19ed4b..061faa170 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -6,7 +6,7 @@ Base OS: Ubuntu 20.04 LTS This guide will setup an ODC core development environment and includes: - - Anaconda python using conda environments to isolate the odc development environment + - Mambaforge using conda environments to isolate the odc development environment - installation of required software and useful developer manuals for those libraries - Postgres database installation with a local user configuration - Integration tests to confirm both successful development setup and for ongoing testing @@ -20,13 +20,13 @@ GDAL, HDF5, and netCDF4:: sudo apt-get install libgdal-dev libhdf5-serial-dev libnetcdf-dev -Install the latest Postgres version `available ` for your +Install the latest Postgres version `available `_ for your Ubuntu distribution, eg:: - sudo apt-get install postgresql-12 + sudo apt-get install postgresql-14 # Optionally, Postgis too - sudo apt-get install postgresql-12-postgis-3 + sudo apt-get install postgresql-14-postgis-3 Ubuntu's official repositories usually ship older versions of Postgres. You can alternatively get the most recent version from `the official PostgreSQL repository `_. From 26ee1b9cc71518df6510b56ecb21e6fcf1271c7a Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 7 Jun 2023 05:17:24 +0000 Subject: [PATCH 034/153] update wordlist --- wordlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wordlist.txt b/wordlist.txt index a3d3f93d2..4c6bc92ad 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -219,6 +219,7 @@ io ipynb isel isinstance +iu jfEZEOkxRXgNsAsHEC jpg JSON @@ -258,6 +259,7 @@ LTS lv macosx MakeMask +Mambaforge ManagingODC mapbox matplotlib @@ -312,6 +314,7 @@ nx ny ODC odc +odcintegration ODCv OGC ogr @@ -325,6 +328,7 @@ osgeo osr OSX ows +pandoc param params pc From e063f051e7306a1933c5bb8d7f57e9a5c21a9d9c Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 7 Jun 2023 06:52:09 +0000 Subject: [PATCH 035/153] update readme --- README.rst | 28 +++++++++++++++------- docs/installation/setup/common_install.rst | 12 ++++++---- wordlist.txt | 1 - 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index d64e696dc..b07dfc962 100644 --- a/README.rst +++ b/README.rst @@ -49,12 +49,12 @@ Developer setup - ``git clone https://github.com/opendatacube/datacube-core.git`` -2. Create a Python environment for using the ODC. We recommend `conda `__ as the +2. Create a Python environment for using the ODC. We recommend `Mambaforge `__ as the easiest way to handle Python dependencies. :: - conda create -f conda-environment.yml + mamba env create -f conda-environment.yml conda activate cubeenv 3. Install a develop version of datacube-core. @@ -72,14 +72,19 @@ Developer setup pre-commit install 5. Run unit tests + PyLint - ``./check-code.sh`` - - (this script approximates what is run by Travis. You can - alternatively run ``pytest`` yourself). Some test dependencies may need to be installed, attempt to install these using: +Install test dependencies using: + ``pip install --upgrade -e '.[test]'`` - If install for these fails please lodge them as issues. +If install for these fails, please lodge them as issues. + +Run unit tests with: + + ``./check-code.sh`` + + (this script approximates what is run by Travis. You can + alternatively run ``pytest`` yourself). 6. **(or)** Run all tests, including integration tests. @@ -92,6 +97,9 @@ Developer setup - Otherwise copy ``integration_tests/agdcintegration.conf`` to ``~/.datacube_integration.conf`` and edit to customise. + - For instructions on setting up a password-less Postgres database, see + the `developer setup instructions `__. + Alternatively one can use the ``opendatacube/datacube-tests`` docker image to run tests. This docker includes database server pre-configured for running @@ -103,11 +111,13 @@ to ``./check-code.sh`` script. ./check-code.sh --with-docker integration_tests -To run individual test in docker container +To run individual tests in a docker container :: - docker run -ti -v /home/ubuntu/datacube-core:/code opendatacube/datacube-tests:latest pytest integration_tests/test_filename.py::test_function_name + docker build --tag=opendatacube/datacube-tests-local --no-cache --progress plain -f docker/Dockerfile . + + docker run -ti -v $(pwd):/code opendatacube/datacube-tests-local:latest pytest integration_tests/test_filename.py::test_function_name Developer setup on Ubuntu diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 34f81d715..2e0e48f72 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -55,8 +55,8 @@ By default in Ubuntu, Postgresql is configured to use ``ident sameuser`` authent Since the only user who can connect to a fresh install is the postgres user, here is how to create yourself a database account (which is in this case also a database superuser) with the same name as your login name and then create a password for the user:: - sudo -iu postgres createuser --superuser $USER - sudo -iu postgres psql + sudo -u postgres createuser --superuser $USER + sudo -u postgres psql postgres=# \password @@ -67,12 +67,12 @@ Now we can create the ``agdcintegration`` and ``odcintegration`` databases for t Or, directly from the bash terminal:: - sudo -iu postgres createdb agdcintegration - sudo -iu postgres createdb odcintegration + sudo -u postgres createdb agdcintegration + sudo -u postgres createdb odcintegration Connecting to your own database to try out some SQL should now be as easy as:: - sudo -iu postgres psql -d agdcintegration + sudo -u postgres psql -d agdcintegration Open Data Cube source and development configuration @@ -112,6 +112,8 @@ Run the integration tests:: ./check-code.sh integration_tests +Note: if moto-based AWS-mock tests fail, you may need to unset all AWS environment variables. + Build the documentation:: pip install --upgrade -e '.[doc]' diff --git a/wordlist.txt b/wordlist.txt index 4c6bc92ad..6b59f93fb 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -219,7 +219,6 @@ io ipynb isel isinstance -iu jfEZEOkxRXgNsAsHEC jpg JSON From 5a33ad793c40916aa2c22a69905869e09d78dff8 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 7 Jun 2023 06:54:41 +0000 Subject: [PATCH 036/153] update wordlist again --- docs/installation/setup/common_install.rst | 2 +- wordlist.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 2e0e48f72..104e7a6f6 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -41,7 +41,7 @@ If this is a new installation of Postgres on your system it is probably wise to In a terminal, type:: - sudo -iu postgres psql postgres + sudo -u postgres psql postgres Set a password for the "postgres" database role using the command:: diff --git a/wordlist.txt b/wordlist.txt index 6b59f93fb..e6b6c2f24 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -270,6 +270,7 @@ mk mkdir MODIS mongolia +moto MTL multiband multigeom @@ -357,6 +358,7 @@ Proj provence psql pts +pwd py pydata pyenv From f8f29c023eb828e528f344dc4399d552f45242d0 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 9 Jun 2023 06:15:27 +0000 Subject: [PATCH 037/153] add a bit more info on db env variables; other misc improvements --- .../installation/data-preparation-scripts.rst | 2 +- docs/installation/database/setup.rst | 12 ++++ docs/installation/setup/common_install.rst | 66 ++----------------- docs/installation/setup/macosx.rst | 47 +++++++++++++ docs/installation/setup/ubuntu.rst | 56 ++++++++++++++++ 5 files changed, 123 insertions(+), 60 deletions(-) diff --git a/docs/installation/data-preparation-scripts.rst b/docs/installation/data-preparation-scripts.rst index b80dcde15..abf74fca4 100644 --- a/docs/installation/data-preparation-scripts.rst +++ b/docs/installation/data-preparation-scripts.rst @@ -1,4 +1,4 @@ -Data Preperation Scripts +Data Preparation Scripts ======================== .. note:: diff --git a/docs/installation/database/setup.rst b/docs/installation/database/setup.rst index 74f8154cf..44fe2c60f 100644 --- a/docs/installation/database/setup.rst +++ b/docs/installation/database/setup.rst @@ -81,6 +81,18 @@ Alternately, you can configure the ODC connection to Postgres using environment DB_PASSWORD DB_DATABASE +To configure a database as a single connection url instead of individual environment varialbes:: + + export DATACUBE_DB_URL=postgresql://[username]:[password]@[hostname]:[port]/[database] + +Alternatively, for password-less access to a database on localhost:: + + export DATACUBE_DB_URL=postgresql:///[database] + +Further information on database configuration can be found `here `__. +Although the enhancement proposal details incoming changes in v1.9 and beyond, it should largely be compatible with the current behaviour, barring a few +slight discrepancies. + The desired environment can be specified: 1. in code, with the ``env`` argument to the ``datacube.Datacube`` constructor; diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 104e7a6f6..44278f374 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -13,10 +13,9 @@ Conda environment setup Conda environments are recommended for use in isolating your ODC development environment from your system installation and other Python environments. We recommend you use Mambaforge to set up your conda virtual environment, as all the required packages are obtained from the conda-forge channel. +Download and install it from `here `_. -Download and install `Mambaforge `_ - -Download the latest version of the Open Data Cube from the `repository `_ :: +Download the latest version of the Open Data Cube from the `repository `_:: git clone https://github.com/opendatacube/datacube-core cd datacube-core @@ -32,8 +31,8 @@ Activate the ``cubeenv`` conda environment:: Find out more about conda environments `here `_. -Postgres database configuration -=============================== +Postgres testing database configuration +======================================= This configuration supports local development using your login name. @@ -67,60 +66,9 @@ Now we can create the ``agdcintegration`` and ``odcintegration`` databases for t Or, directly from the bash terminal:: - sudo -u postgres createdb agdcintegration - sudo -u postgres createdb odcintegration + createdb agdcintegration + createdb odcintegration Connecting to your own database to try out some SQL should now be as easy as:: - sudo -u postgres psql -d agdcintegration - - -Open Data Cube source and development configuration -=================================================== - -We need to specify the database user and password for the ODC integration testing. To do this:: - - cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf - -Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: - - [datacube] - db_hostname: localhost - db_database: agdcintegration - index_driver: default - db_username: - db_password: - - [experimental] - db_hostname: localhost - db_database: odcintegration - index_driver: postgis - db_username: - db_password: - -Note: For Ubuntu Setup the db_hostname should be set to "/var/run/postgresql". For more refer: https://github.com/opendatacube/datacube-core/issues/1329 - -Verify it all works -=================== - -Install additional test dependencies:: - - cd datacube-core - pip install --upgrade -e '.[test]' - -Run the integration tests:: - - ./check-code.sh integration_tests - -Note: if moto-based AWS-mock tests fail, you may need to unset all AWS environment variables. - -Build the documentation:: - - pip install --upgrade -e '.[doc]' - cd docs - pip install -r requirements.txt - sudo apt install make - sudo apt install pandoc - make html - -Then open :file:`_build/html/index.html` in your browser to view the Documentation. + psql -d agdcintegration diff --git a/docs/installation/setup/macosx.rst b/docs/installation/setup/macosx.rst index e1dfe79ca..af78a9ca0 100644 --- a/docs/installation/setup/macosx.rst +++ b/docs/installation/setup/macosx.rst @@ -23,3 +23,50 @@ Postgres: .. include:: common_install.rst + + +You can now specify the database user and password for ODC integration testing. To do this:: + + cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf + +Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: + + [datacube] + db_hostname: localhost + db_database: agdcintegration + index_driver: default + db_username: + db_password: + + [experimental] + db_hostname: localhost + db_database: odcintegration + index_driver: postgis + db_username: + db_password: + + +Verify it all works +=================== + +Install additional test dependencies:: + + cd datacube-core + pip install --upgrade -e '.[test]' + +Run the integration tests:: + + ./check-code.sh integration_tests + +Note: if moto-based AWS-mock tests fail, you may need to unset all AWS environment variables. + +Build the documentation:: + + pip install --upgrade -e '.[doc]' + cd docs + pip install -r requirements.txt + sudo apt install make + sudo apt install pandoc + make html + +Then open :file:`_build/html/index.html` in your browser to view the Documentation. diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index 061faa170..78c027189 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -37,3 +37,59 @@ Optional packages (useful utilities, docs):: sudo apt-get install hdf5-tools netcdf-bin gdal-bin pgadmin3 .. include:: common_install.rst + + +If createdb or psql cannot connect to server, check which postgresql installation is being run:: + + which psql + +If it is running the mambaforge installation, you may need to install it and ensure it is installed globally:: + + mamba remove postgresql + sudo apt install postgresql + + +You can now specify the database user and password for ODC integration testing. To do this:: + + cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf + +Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: + + [datacube] + db_hostname: /var/run/postgresql + db_database: agdcintegration + index_driver: default + db_username: + db_password: + + [experimental] + db_hostname: /var/run/postgresql + db_database: odcintegration + index_driver: postgis + db_username: + db_password: + +Verify it all works +=================== + +Install additional test dependencies:: + + cd datacube-core + pip install --upgrade -e '.[test]' + +Run the integration tests:: + + ./check-code.sh integration_tests + +Note: if moto-based AWS-mock tests fail, you may need to unset all AWS environment variables. + +Build the documentation:: + + pip install --upgrade -e '.[doc]' + cd docs + pip install -r requirements.txt + sudo apt install make + sudo apt install pandoc + make html + +Then open :file:`_build/html/index.html` in your browser to view the Documentation. From 1a073832af763454c29fc074051c166e838a5427 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 13 Jun 2023 05:51:54 +0000 Subject: [PATCH 038/153] update barebones metadata type requirements --- docs/about-core-concepts/metadata-types.rst | 2 +- docs/config_samples/metadata_types/bare_bone.yaml | 9 +++++++-- docs/installation/data-preparation-scripts.rst | 10 +++++----- docs/installation/metadata-types.rst | 7 ++++--- docs/installation/setup/ubuntu.rst | 5 ++--- wordlist.txt | 2 -- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/about-core-concepts/metadata-types.rst b/docs/about-core-concepts/metadata-types.rst index 323f62e47..fcae2f8aa 100644 --- a/docs/about-core-concepts/metadata-types.rst +++ b/docs/about-core-concepts/metadata-types.rst @@ -8,4 +8,4 @@ Metadata Types Metadata type yaml file must contain name, description and dataset keys. - Dataset key must contain id, sources, creation_dt, label and search_fields keys. + Dataset key must contain id, sources, grid_spatial, measurements, creation_dt, label, format, and search_fields keys. diff --git a/docs/config_samples/metadata_types/bare_bone.yaml b/docs/config_samples/metadata_types/bare_bone.yaml index 6f6c3eda3..5c373fd2b 100644 --- a/docs/config_samples/metadata_types/bare_bone.yaml +++ b/docs/config_samples/metadata_types/bare_bone.yaml @@ -2,10 +2,15 @@ name: barebone description: A minimalist metadata type file dataset: - id: [id] # No longer configurable in newer ODCs. - sources: [lineage, source_datasets] # No longer configurable in newer ODCs. + id: [id] # No longer configurable in newer ODCs. + sources: [lineage, source_datasets] # No longer configurable in newer ODCs. + + grid_spatial: [grid_spatial, projection] + measurements: [measurements] creation_dt: [properties, 'odc:processing_datetime'] label: [label] + format: [properties, 'odc:file_format'] + search_fields: platform: description: Platform code diff --git a/docs/installation/data-preparation-scripts.rst b/docs/installation/data-preparation-scripts.rst index abf74fca4..1762e3dab 100644 --- a/docs/installation/data-preparation-scripts.rst +++ b/docs/installation/data-preparation-scripts.rst @@ -42,11 +42,11 @@ Download the USGS Collection 1 landsat scenes from any of the links below: The prepare script for collection 1 - level 1 data is available in `ls_usgs_prepare.py -`_. +`_. :: - $ wget https://github.com/opendatacube/datacube-dataset-config/raw/master/old-prep-scripts/ls_usgs_prepare.py + $ wget https://github.com/opendatacube/datacube-dataset-config/raw/main/old-prep-scripts/ls_usgs_prepare.py $ python ls_usgs_prepare.py --help Usage: ls_usgs_prepare.py [OPTIONS] [DATASETS]... @@ -85,14 +85,14 @@ For Landsat collection 1 level 1 product: To prepare downloaded USGS LEDAPS Landsat scenes for use with the Data Cube, use the script provided in `usgs_ls_ard_prepare.py -`_ +`_ The following example generates the required Dataset Metadata files, named `agdc-metadata.yaml` for three landsat scenes. :: - $ wget https://github.com/opendatacube/datacube-dataset-config/raw/master/agdcv2-ingest/prepare_scripts/landsat_collection/usgs_ls_ard_prepare.py + $ wget https://github.com/opendatacube/datacube-dataset-config/raw/main/agdcv2-ingest/prepare_scripts/landsat_collection/usgs_ls_ard_prepare.py $ python USGS_precollection_oldscripts/usgslsprepare.py --help Usage: usgslsprepare.py [OPTIONS] [DATASETS]... @@ -134,7 +134,7 @@ Then :ref:`index the data `. To view an example of how to `index Sentinel-2 data from S3`_ check out the documentation available in the datacube-dataset-config_ repository. -.. _`index Sentinel-2 data from S3`: https://github.com/opendatacube/datacube-dataset-config/blob/master/sentinel-2-l2a-cogs.md +.. _`index Sentinel-2 data from S3`: https://github.com/opendatacube/datacube-dataset-config/blob/main/sentinel-2-l2a-cogs.md .. _datacube-dataset-config: https://github.com/opendatacube/datacube-dataset-config/ Custom Prepare Scripts diff --git a/docs/installation/metadata-types.rst b/docs/installation/metadata-types.rst index 0f206016b..dd52b5bbc 100644 --- a/docs/installation/metadata-types.rst +++ b/docs/installation/metadata-types.rst @@ -5,15 +5,16 @@ A Metadata Type defines which fields should be searchable in your product or dat Three metadata types are added by default called ``eo``, ``telemetry`` and ``eo3``. +You can see the default metadata types in the repository at `datacube/index/default-metadata-types.yaml `_. + You would create a new metadata type if you want custom fields to be searchable for your products, or if you want to structure your metadata documents differently. -You can see the default metadata type in the repository at `datacube/index/default-metadata-types.yaml `_. - To add or alter metadata types, you can use commands like: ``datacube metadata add `` and to update: ``datacube metadata update ``. Using ``--allow-unsafe`` will allow you to update metadata types where the changes may have unexpected consequences. +Note that from version 1.9 onwards, only eo3-compatible metadata types will be accepted. .. literalinclude:: ../config_samples/metadata_types/bare_bone.yaml :language: yaml @@ -22,4 +23,4 @@ you to update metadata types where the changes may have unexpected consequences. Metadata type yaml file must contain name, description and dataset keys. - Dataset key must contain id, sources, creation_dt, label and search_fields keys. + Dataset key must contain id, sources, grid_spatial, measurements, creation_dt, label, format, and search_fields keys. diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index 78c027189..a87cf230f 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -43,10 +43,9 @@ If createdb or psql cannot connect to server, check which postgresql installatio which psql -If it is running the mambaforge installation, you may need to install it and ensure it is installed globally:: +If it is running the mambaforge installation, you may need to run the global installation:: - mamba remove postgresql - sudo apt install postgresql + /usr/bin/psql -d agdcintegration You can now specify the database user and password for ODC integration testing. To do this:: diff --git a/wordlist.txt b/wordlist.txt index e6b6c2f24..51655a493 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -350,8 +350,6 @@ pq pre precollection prefetch -Preperation -preperation PRIMEM prog Proj From 4d7d587dcdd3a98a0a070d88cde821cf21e11902 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 13 Jun 2023 06:01:00 +0000 Subject: [PATCH 039/153] fix typos, update wordlist --- docs/installation/database/setup.rst | 2 +- docs/installation/metadata-types.rst | 2 +- wordlist.txt | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/installation/database/setup.rst b/docs/installation/database/setup.rst index 44fe2c60f..72040ee08 100644 --- a/docs/installation/database/setup.rst +++ b/docs/installation/database/setup.rst @@ -81,7 +81,7 @@ Alternately, you can configure the ODC connection to Postgres using environment DB_PASSWORD DB_DATABASE -To configure a database as a single connection url instead of individual environment varialbes:: +To configure a database as a single connection url instead of individual environment variables:: export DATACUBE_DB_URL=postgresql://[username]:[password]@[hostname]:[port]/[database] diff --git a/docs/installation/metadata-types.rst b/docs/installation/metadata-types.rst index dd52b5bbc..874569ca3 100644 --- a/docs/installation/metadata-types.rst +++ b/docs/installation/metadata-types.rst @@ -14,7 +14,7 @@ To add or alter metadata types, you can use commands like: ``datacube metadata a and to update: ``datacube metadata update ``. Using ``--allow-unsafe`` will allow you to update metadata types where the changes may have unexpected consequences. -Note that from version 1.9 onwards, only eo3-compatible metadata types will be accepted. +Note that from version 1.9 onward, only eo3-compatible metadata types will be accepted. .. literalinclude:: ../config_samples/metadata_types/bare_bone.yaml :language: yaml diff --git a/wordlist.txt b/wordlist.txt index 51655a493..3b23a0cef 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -259,6 +259,7 @@ lv macosx MakeMask Mambaforge +mambaforge ManagingODC mapbox matplotlib @@ -465,6 +466,7 @@ usgs USGS's usgslsprepare UsingODC +usr utils UUID uuid From 3a7c32255492b77594a56e211c0cae249b55a6d2 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 14 Jun 2023 02:08:50 +0000 Subject: [PATCH 040/153] fix some wording --- docs/about-core-concepts/metadata-types.rst | 5 ++++- docs/config_samples/metadata_types/bare_bone.yaml | 7 ++++--- docs/installation/database/setup.rst | 2 +- docs/installation/metadata-types.rst | 8 ++++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/about-core-concepts/metadata-types.rst b/docs/about-core-concepts/metadata-types.rst index fcae2f8aa..a101af420 100644 --- a/docs/about-core-concepts/metadata-types.rst +++ b/docs/about-core-concepts/metadata-types.rst @@ -8,4 +8,7 @@ Metadata Types Metadata type yaml file must contain name, description and dataset keys. - Dataset key must contain id, sources, grid_spatial, measurements, creation_dt, label, format, and search_fields keys. + Dataset key must contain id, sources, creation_dt, label, and search_fields keys. + + For metadata types of spatial datasets, the dataset key must also contain grid_spatial, measurements, and format keys. + Support for non-spatial datasets is likely to be dropped in version 2.0. diff --git a/docs/config_samples/metadata_types/bare_bone.yaml b/docs/config_samples/metadata_types/bare_bone.yaml index 5c373fd2b..8ffc64248 100644 --- a/docs/config_samples/metadata_types/bare_bone.yaml +++ b/docs/config_samples/metadata_types/bare_bone.yaml @@ -5,11 +5,12 @@ dataset: id: [id] # No longer configurable in newer ODCs. sources: [lineage, source_datasets] # No longer configurable in newer ODCs. - grid_spatial: [grid_spatial, projection] - measurements: [measurements] creation_dt: [properties, 'odc:processing_datetime'] label: [label] - format: [properties, 'odc:file_format'] + # The following keys are necessary if describing spatial datasets + # grid_spatial: [grid_spatial, projection] + # measurements: [measurements] + # format: [properties, 'odc:file_format'] search_fields: platform: diff --git a/docs/installation/database/setup.rst b/docs/installation/database/setup.rst index 72040ee08..4a758fa26 100644 --- a/docs/installation/database/setup.rst +++ b/docs/installation/database/setup.rst @@ -91,7 +91,7 @@ Alternatively, for password-less access to a database on localhost:: Further information on database configuration can be found `here `__. Although the enhancement proposal details incoming changes in v1.9 and beyond, it should largely be compatible with the current behaviour, barring a few -slight discrepancies. +obscure corner cases. The desired environment can be specified: diff --git a/docs/installation/metadata-types.rst b/docs/installation/metadata-types.rst index 874569ca3..9a4ac538a 100644 --- a/docs/installation/metadata-types.rst +++ b/docs/installation/metadata-types.rst @@ -14,7 +14,8 @@ To add or alter metadata types, you can use commands like: ``datacube metadata a and to update: ``datacube metadata update ``. Using ``--allow-unsafe`` will allow you to update metadata types where the changes may have unexpected consequences. -Note that from version 1.9 onward, only eo3-compatible metadata types will be accepted. +Note that the postgis driver only supports eo3-compatible metadata types, and from version 2.0 onward, support for non-eo3-compatible metadata types +will be fully deprecated. .. literalinclude:: ../config_samples/metadata_types/bare_bone.yaml :language: yaml @@ -23,4 +24,7 @@ Note that from version 1.9 onward, only eo3-compatible metadata types will be ac Metadata type yaml file must contain name, description and dataset keys. - Dataset key must contain id, sources, grid_spatial, measurements, creation_dt, label, format, and search_fields keys. + Dataset key must contain id, sources, creation_dt, label, and search_fields keys. + + For metadata types of spatial datasets, the dataset key must also contain grid_spatial, measurements, and format keys. + Support for non-spatial datasets is likely to be dropped in version 2.0. From c60daaee562065a02bd6e913cebb770dbede33fe Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 14 Jun 2023 02:43:55 +0000 Subject: [PATCH 041/153] update integration db names --- docs/installation/setup/common_install.rst | 13 +++++++------ docs/installation/setup/macosx.rst | 4 ++-- docs/installation/setup/ubuntu.rst | 8 ++++---- wordlist.txt | 2 ++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 44278f374..5018369ae 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -59,16 +59,17 @@ Since the only user who can connect to a fresh install is the postgres user, her postgres=# \password -Now we can create the ``agdcintegration`` and ``odcintegration`` databases for testing:: +Now we can create databases for integration testing. You will need 2 databases - one for the Postgres driver and one for the PostGIS driver. You can name these databases however you want. +In this example we will call the databases ``pgintegration`` and ``pgisintegration``:: - postgres=# create database agdcintegration; - postgres=# create database odcintegration; + postgres=# create database pgintegration; + postgres=# create database pgisintegration; Or, directly from the bash terminal:: - createdb agdcintegration - createdb odcintegration + createdb pgintegration + createdb pgisintegration Connecting to your own database to try out some SQL should now be as easy as:: - psql -d agdcintegration + psql -d pgintegration diff --git a/docs/installation/setup/macosx.rst b/docs/installation/setup/macosx.rst index af78a9ca0..e4db80bd3 100644 --- a/docs/installation/setup/macosx.rst +++ b/docs/installation/setup/macosx.rst @@ -33,14 +33,14 @@ Then edit the ``~/.datacube_integration.conf`` with a text editor and add the fo [datacube] db_hostname: localhost - db_database: agdcintegration + db_database: pgintegration index_driver: default db_username: db_password: [experimental] db_hostname: localhost - db_database: odcintegration + db_database: pgisintegration index_driver: postgis db_username: db_password: diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index a87cf230f..9e57a6e31 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -25,7 +25,7 @@ Ubuntu distribution, eg:: sudo apt-get install postgresql-14 - # Optionally, Postgis too + # Optionally, Postgis too (required for the postgis/experimental index driver) sudo apt-get install postgresql-14-postgis-3 Ubuntu's official repositories usually ship older versions of Postgres. You can alternatively get the most recent version from @@ -45,7 +45,7 @@ If createdb or psql cannot connect to server, check which postgresql installatio If it is running the mambaforge installation, you may need to run the global installation:: - /usr/bin/psql -d agdcintegration + /usr/bin/psql -d pgintegration You can now specify the database user and password for ODC integration testing. To do this:: @@ -56,14 +56,14 @@ Then edit the ``~/.datacube_integration.conf`` with a text editor and add the fo [datacube] db_hostname: /var/run/postgresql - db_database: agdcintegration + db_database: pgintegration index_driver: default db_username: db_password: [experimental] db_hostname: /var/run/postgresql - db_database: odcintegration + db_database: pgisintegration index_driver: postgis db_username: db_password: diff --git a/wordlist.txt b/wordlist.txt index 3b23a0cef..4cd2ffb99 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -335,6 +335,8 @@ params pc petewa pgadmin +pgintegration +pgisintegration pixelquality pkgs Pluggable From 075bcdad1b4f85f2c588bb9e1bd20e3d043c6b8e Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 14 Jun 2023 02:58:42 +0000 Subject: [PATCH 042/153] rename agdcintegration.conf --- README.rst | 6 +++--- docker/assets/with_bootstrap | 8 ++++---- docs/installation/setup/common_install.rst | 4 ++-- docs/installation/setup/macosx.rst | 2 +- docs/installation/setup/ubuntu.rst | 2 +- docs/installation/setup/windows.rst | 12 ++++++++++-- integration_tests/conftest.py | 2 +- .../{agdcintegration.conf => integration.conf} | 4 ++-- 8 files changed, 24 insertions(+), 16 deletions(-) rename integration_tests/{agdcintegration.conf => integration.conf} (79%) diff --git a/README.rst b/README.rst index b07dfc962..a730c11b4 100644 --- a/README.rst +++ b/README.rst @@ -83,7 +83,7 @@ Run unit tests with: ``./check-code.sh`` - (this script approximates what is run by Travis. You can + (this script approximates what is run by GitHub Actions. You can alternatively run ``pytest`` yourself). 6. **(or)** Run all tests, including integration tests. @@ -92,9 +92,9 @@ Run unit tests with: - Assumes a password-less Postgres database running on localhost called - ``agdcintegration`` + ``pgintegration`` - - Otherwise copy ``integration_tests/agdcintegration.conf`` to + - Otherwise copy ``integration_tests/integration.conf`` to ``~/.datacube_integration.conf`` and edit to customise. - For instructions on setting up a password-less Postgres database, see diff --git a/docker/assets/with_bootstrap b/docker/assets/with_bootstrap index 5da8866f6..02bdc7791 100755 --- a/docker/assets/with_bootstrap +++ b/docker/assets/with_bootstrap @@ -14,8 +14,8 @@ launch_db () { sudo -u postgres createuser --superuser "${dbuser}" sudo -u postgres createdb "${dbuser}" sudo -u postgres createdb datacube - sudo -u postgres createdb agdcintegration - sudo -u postgres createdb odcintegration + sudo -u postgres createdb pgintegration + sudo -u postgres createdb pgisintegration } # Become `odc` user with UID/GID compatible to datacube-core volume @@ -58,12 +58,12 @@ launch_db () { cat < $HOME/.datacube_integration.conf [datacube] db_hostname: -db_database: agdcintegration +db_database: pgintegration index_driver: default [experimental] db_hostname: -db_database: odcintegration +db_database: pgisintegration index_driver: postgis [no_such_driver_env] diff --git a/docs/installation/setup/common_install.rst b/docs/installation/setup/common_install.rst index 5018369ae..49deaa20a 100644 --- a/docs/installation/setup/common_install.rst +++ b/docs/installation/setup/common_install.rst @@ -59,8 +59,8 @@ Since the only user who can connect to a fresh install is the postgres user, her postgres=# \password -Now we can create databases for integration testing. You will need 2 databases - one for the Postgres driver and one for the PostGIS driver. You can name these databases however you want. -In this example we will call the databases ``pgintegration`` and ``pgisintegration``:: +Now we can create databases for integration testing. You will need 2 databases - one for the Postgres driver and one for the PostGIS driver. +By default, these databases are called ``pgintegration`` and ``pgisintegration``, but you can name them however you want:: postgres=# create database pgintegration; postgres=# create database pgisintegration; diff --git a/docs/installation/setup/macosx.rst b/docs/installation/setup/macosx.rst index e4db80bd3..09deab701 100644 --- a/docs/installation/setup/macosx.rst +++ b/docs/installation/setup/macosx.rst @@ -27,7 +27,7 @@ Postgres: You can now specify the database user and password for ODC integration testing. To do this:: - cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf + cp integration_tests/integration.conf ~/.datacube_integration.conf Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index 9e57a6e31..2f999ae6e 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -50,7 +50,7 @@ If it is running the mambaforge installation, you may need to run the global ins You can now specify the database user and password for ODC integration testing. To do this:: - cp integration_tests/agdcintegration.conf ~/.datacube_integration.conf + cp integration_tests/integration.conf ~/.datacube_integration.conf Then edit the ``~/.datacube_integration.conf`` with a text editor and add the following lines, replacing ```` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: diff --git a/docs/installation/setup/windows.rst b/docs/installation/setup/windows.rst index c4a467f3d..2a1f558d8 100644 --- a/docs/installation/setup/windows.rst +++ b/docs/installation/setup/windows.rst @@ -62,14 +62,22 @@ Download the latest version of the software from the `repository `` with your username and ```` with the database user password you set above (not the postgres one, your ```` one):: [datacube] db_hostname: localhost - db_database: agdcintegration + db_database: pgintegration + index_driver: default + db_username: + db_password: + + [experimental] + db_hostname: localhost + db_database: pgisintegration + index_driver: postgis db_username: db_password: diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index dc6b62421..443a30005 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -47,7 +47,7 @@ PROJECT_ROOT = Path(__file__).parents[1] CONFIG_SAMPLES = PROJECT_ROOT / 'docs' / 'config_samples' -CONFIG_FILE_PATHS = [str(INTEGRATION_TESTS_DIR / 'agdcintegration.conf'), +CONFIG_FILE_PATHS = [str(INTEGRATION_TESTS_DIR / 'integration.conf'), os.path.expanduser('~/.datacube_integration.conf')] # Configure Hypothesis to allow slower tests, because we're testing datasets diff --git a/integration_tests/agdcintegration.conf b/integration_tests/integration.conf similarity index 79% rename from integration_tests/agdcintegration.conf rename to integration_tests/integration.conf index ac3df9a70..10d0599d1 100644 --- a/integration_tests/agdcintegration.conf +++ b/integration_tests/integration.conf @@ -1,11 +1,11 @@ [datacube] db_hostname: -db_database: agdcintegration +db_database: pgintegration index_driver: default [experimental] db_hostname: -db_database: odcintegration +db_database: pgisintegration index_driver: postgis [no_such_driver_env] From 7c7f460c2be6c01de915e0a47d03309904694c42 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Mon, 19 Jun 2023 16:23:36 +1000 Subject: [PATCH 043/153] Always use XSCALE=1,YSCALE=1 in warp. (#1457) * Use SCALEX=1,SCALEY=1 in both warp code-paths. --- datacube/storage/_read.py | 23 ++++++++++++++++++++--- docs/about/whats_new.rst | 2 ++ tests/storage/test_storage.py | 3 ++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/datacube/storage/_read.py b/datacube/storage/_read.py index 2c1dd05f9..fb5b03b8f 100644 --- a/datacube/storage/_read.py +++ b/datacube/storage/_read.py @@ -129,7 +129,6 @@ def read_time_slice(rdr, is_nn = is_resampling_nn(resampling) scale = pick_read_scale(rr.scale, rdr) - scale_x, scale_y = [pick_read_scale(s) for s in rr.scale2] paste_ok, _ = can_paste(rr, ttol=0.9 if is_nn else 0.01) @@ -183,14 +182,32 @@ def norm_read_args(roi, shape, extra_dim_index): pix = rdr.read(*norm_read_args(rr.roi_src, src_gbox.shape, extra_dim_index)) + # XSCALE and YSCALE are (currently) undocumented arguments that rasterio passed through to + # GDAL. Not using them results in very inaccurate warping in images with highly + # non-square (i.e. long and thin) aspect ratios. + # + # See https://github.com/OSGeo/gdal/issues/7750 as well as + # https://github.com/opendatacube/datacube-core/pull/1450 and + # https://github.com/opendatacube/datacube-core/issues/1456 + # + # In theory we might be able to get better results for queries with significantly + # different vertical and horizontal scales, but explicitly using XSCALE=1, YSCALE=1 + # appears to be most appropriate for most requests, and is demonstrably better + # than not setting them at all. + gdal_scale_params = { + "XSCALE": 1, + "YSCALE": 1, + } if rr.transform.linear is not None: A = (~src_gbox.transform)*dst_gbox.transform warp_affine(pix, dst, A, resampling, - src_nodata=rdr.nodata, dst_nodata=dst_nodata) + src_nodata=rdr.nodata, dst_nodata=dst_nodata, + **gdal_scale_params) else: + rio_reproject(pix, dst, src_gbox, dst_gbox, resampling, src_nodata=rdr.nodata, dst_nodata=dst_nodata, - XSCALE=scale_x, YSCALE=scale_y) + **gdal_scale_params) return rr.roi_dst diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 0b5d561ab..3747a54ca 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,8 @@ What's New v1.8.next ========= +- Second attempt to address unexpected handling of image aspect ratios in rasterio and + GDAL. (:pull:`1457`) - Fix broken pypi publishing Github action (:pull:`1454`) diff --git a/tests/storage/test_storage.py b/tests/storage/test_storage.py index 37c981b7f..b784f8764 100644 --- a/tests/storage/test_storage.py +++ b/tests/storage/test_storage.py @@ -233,7 +233,8 @@ def assert_same_read_results(source, dst_shape, dst_dtype, dst_transform, dst_no dst_transform=dst_transform, dst_crs=str(dst_projection), dst_nodata=dst_nodata, - resampling=resampling) + resampling=resampling, + XSCALE=1, YSCALE=1) result = np.full(dst_shape, dst_nodata, dtype=dst_dtype) H, W = dst_shape From e632f9a40c5add9296f1741d194ea9ac29997c3c Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Mon, 19 Jun 2023 07:06:31 +0000 Subject: [PATCH 044/153] remove data preparation page, add links to indexing guide --- docs/installation/index.rst | 1 - .../installation/indexing-data/step-guide.rst | 13 +++--------- docs/installation/ingesting-data/index.rst | 20 ++----------------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index f2422569c..7a811cbaf 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -53,5 +53,4 @@ This section contains information on setting up and managing the Open Data Cube. .. toctree:: :caption: Legacy Approaches - data-preparation-scripts ingesting-data/index diff --git a/docs/installation/indexing-data/step-guide.rst b/docs/installation/indexing-data/step-guide.rst index 48010c57d..71c685dce 100644 --- a/docs/installation/indexing-data/step-guide.rst +++ b/docs/installation/indexing-data/step-guide.rst @@ -72,16 +72,9 @@ for searching, querying and accessing the data. The data from Geoscience Australia already comes with relevant files (named ``ga-metadata.yaml``), so no further steps are required for indexing them. -For third party datasets, see :ref:`prepare-scripts`. - - -.. admonition:: Note - - :class: info - - Some metadata requires cleanup before they are ready to be loaded. - -For more information see :ref:`dataset-metadata-doc`. +For third party datasets, see the examples detailed `here `__. +For common distribution formations, data can be indexed using one of the tools from `odc-apps-dc-tools `__. +In other cases, the metadata may need to be mapped to an ODC-compatible format. You can find examples of data preparation scripts `here `__. Step 3. Run the Indexing process diff --git a/docs/installation/ingesting-data/index.rst b/docs/installation/ingesting-data/index.rst index 021a2a3ce..737be508d 100644 --- a/docs/installation/ingesting-data/index.rst +++ b/docs/installation/ingesting-data/index.rst @@ -7,25 +7,9 @@ Ingesting Data .. note:: Ingestion is no longer recommended. While it was used as an optimised on-disk - storage mechanism, there are a range of reasons why this is no longer ideal. For example + storage mechanism, there are a range of reasons why this is no longer ideal. For example, the emergence of cloud optimised storage formats means that software such - as GDAL and Rasterio are optimised for reading many files over the network. Additionally - the limitation of NetCDF reading to a single thread means that reading from .TIF - files on disk could be faster in some situations. - - In addition to limited performance improvements, ingestion leads to duplication - of data and opinionated decisions, such as reprojection of data, which can lead - to a loss of data fidelity. - - The section below is being retained for completion, but should be considered optional. - - -.. note:: - - Ingestion is no longer recommended. While it was used as an optimised on-disk - storage mechanism, there are a range of reasons why this is no longer ideal. For example - the emergence of cloud optimised storage formats means that software such - as GDAL and Rasterio are optimised for reading many files over the network. Additionally + as GDAL and Rasterio are optimised for reading many files over the network. Additionally, the limitation of NetCDF reading to a single thread means that reading from .TIF files on disk could be faster in some situations. From acac9c07beb0537fcc95b7d4612049f7727eac2a Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 20 Jun 2023 00:45:44 +0000 Subject: [PATCH 045/153] fix typo, del data preparation scripts page --- .../installation/data-preparation-scripts.rst | 147 ------------------ .../installation/indexing-data/step-guide.rst | 2 +- 2 files changed, 1 insertion(+), 148 deletions(-) delete mode 100644 docs/installation/data-preparation-scripts.rst diff --git a/docs/installation/data-preparation-scripts.rst b/docs/installation/data-preparation-scripts.rst deleted file mode 100644 index 1762e3dab..000000000 --- a/docs/installation/data-preparation-scripts.rst +++ /dev/null @@ -1,147 +0,0 @@ -Data Preparation Scripts -======================== - -.. note:: - - Much of the below content is not updated and has not been tested recently. - - -Sometimes data to load into an Open Data Cube will come packaged with -compatible :ref:`dataset-metadata-doc` and be ready to :ref:`index ` -immediately. - -In other cases you will need to generate these :ref:`dataset-metadata-doc` yourself. -This is done using a ``Dataset Preparation Script``, which reads whatever format metadata -has been supplied with the data, and either writes out ODC compatible files, or adds -records directly to an ODC database. - -For common distribution formats there is likely to be a script already, but -other formats will require one to be modified. - -.. admonition:: Existing dataset-metadata-docs - :class: tip - - Examples of prepare scripts are found in the `datacube-dataset-config `_ repository - on Github. - - -Landsat Samples -=============== - -The two examples below show preparing USGS Landsat data for indexing into an Open Data Cube: - - -1. Preparing USGS Landsat Collection 1 - LEVEL1 -=============================================== - -Download the USGS Collection 1 landsat scenes from any of the links below: - -* `Earth-Explorer `_ -* `GloVis `_ -* `ESPA ordering `_ - -The prepare script for collection 1 - level 1 data is available in -`ls_usgs_prepare.py -`_. - -:: - - $ wget https://github.com/opendatacube/datacube-dataset-config/raw/main/old-prep-scripts/ls_usgs_prepare.py - $ python ls_usgs_prepare.py --help - Usage: ls_usgs_prepare.py [OPTIONS] [DATASETS]... - - Prepare USGS Landsat Collection 1 data for ingestion into the Data Cube. - This prepare script supports only for MTL.txt metadata file - To Set the Path for referring the datasets - - Download the Landsat scene data from Earth Explorer or GloVis into - 'some_space_available_folder' and unpack the file. - For example: yourscript.py --output [Yaml- which writes datasets into this file for indexing] - [Path for dataset as : /home/some_space_available_folder/] - - Options: - --output PATH Write datasets into this file - --help Show this message and exit. - - $ python ls_usgs_prepare.py --output ls8_usgs_lv1 ~/earth_explorer/Collection1/LANDSAT8 - -*ls8_usgs_lv1* is the output for required dataset for landsat 8 scene. - -To add the product definitions: - -For Landsat collection 1 level 1 product: - -:: - - $ datacube product add docs/config_samples/dataset_types/ls_usgs.yaml - Added "ls8_level1_usgs" - Added "ls7_level1_usgs" - Added "ls5_level1_usgs" - Added "ls8_l1_pc_usgs" - - -2. Preparing USGS Landsat Surface Reflectance - LEDAPS -====================================================== - -To prepare downloaded USGS LEDAPS Landsat scenes for use with the Data Cube, use -the script provided in -`usgs_ls_ard_prepare.py -`_ - -The following example generates the required Dataset Metadata files, named -`agdc-metadata.yaml` for three landsat scenes. - -:: - - $ wget https://github.com/opendatacube/datacube-dataset-config/raw/main/agdcv2-ingest/prepare_scripts/landsat_collection/usgs_ls_ard_prepare.py - $ python USGS_precollection_oldscripts/usgslsprepare.py --help - Usage: usgslsprepare.py [OPTIONS] [DATASETS]... - - Prepare USGS LS dataset for ingestion into the Data Cube. - - Options: - --help Show this message and exit. - - $ python usgslsprepare.py ~/USGS_LandsatLEDAPS/*/ - 2016-06-09 15:32:51,641 INFO Processing ~/USGS_LandsatLEDAPS/LC80960852015365-SC20160211222236 - 2016-06-09 15:32:52,096 INFO Writing ~/USGS_LandsatLEDAPS/LC80960852015365-SC20160211222236/agdc-metadata.yaml - 2016-06-09 15:32:52,119 INFO Processing ~/USGS_LandsatLEDAPS/LE70960852016024-SC20160211221824 - 2016-06-09 15:32:52,137 INFO Writing ~/USGS_LandsatLEDAPS/LE70960852016024-SC20160211221824/agdc-metadata.yaml - 2016-06-09 15:32:52,151 INFO Processing ~/USGS_LandsatLEDAPS/LT50960852011290-SC20160211221617 - 2016-06-09 15:32:52,157 INFO Writing ~/USGS_LandsatLEDAPS/LT50960852011290-SC20160211221617/agdc-metadata.yaml - - -The scenes are now ready to be :ref:`indexed ` and accessed using -the Data Cube. - -For Landsat Surface reflectance LEDAPS add: - -:: - - $ datacube product add docs/config_samples/dataset_types/* - ... - Added "ls5_ledaps_scene" - ... - Added "ls7_ledaps_scene" - ... - Added "ls8_ledaps_scene" - ... - -Then :ref:`index the data `. - -3. Indexing data on AWS, an example using Sentinel-2 -==================================================== - -To view an example of how to `index Sentinel-2 data from S3`_ check out the documentation -available in the datacube-dataset-config_ repository. - -.. _`index Sentinel-2 data from S3`: https://github.com/opendatacube/datacube-dataset-config/blob/main/sentinel-2-l2a-cogs.md -.. _datacube-dataset-config: https://github.com/opendatacube/datacube-dataset-config/ - -Custom Prepare Scripts -====================== - -We expect that many new Data Cube instances will require custom prepare scripts -to be written. It is generally a straightforward task of mapping metadata from -one form to another and writing out a YAML document. The code need not even be -written in Python, although starting with one of our examples is generally -the easiest way. diff --git a/docs/installation/indexing-data/step-guide.rst b/docs/installation/indexing-data/step-guide.rst index 71c685dce..d005d8265 100644 --- a/docs/installation/indexing-data/step-guide.rst +++ b/docs/installation/indexing-data/step-guide.rst @@ -73,7 +73,7 @@ The data from Geoscience Australia already comes with relevant files (named ``ga no further steps are required for indexing them. For third party datasets, see the examples detailed `here `__. -For common distribution formations, data can be indexed using one of the tools from `odc-apps-dc-tools `__. +For common distribution formats, data can be indexed using one of the tools from `odc-apps-dc-tools `__. In other cases, the metadata may need to be mapped to an ODC-compatible format. You can find examples of data preparation scripts `here `__. From d7e20c929c28946ff091c0d46fafe5e0524dad4d Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 21 Jun 2023 00:18:32 +0000 Subject: [PATCH 046/153] increase buffer to 500ms --- datacube/index/abstract.py | 4 ++-- docs/about/whats_new.rst | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index f46aa6850..cfb8de9af 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -921,8 +921,8 @@ def find_less_mature(self, ds: Dataset) -> Iterable[Dataset]: """ less_mature = [] # 'expand' the date range by a millisecond to give a bit more leniency in datetime comparison - expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=1), - ds.metadata.time.end + timedelta(milliseconds=1)) + expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=500), + ds.metadata.time.end + timedelta(milliseconds=500)) dupes = self.search(product=ds.product.name, region_code=ds.metadata.region_code, time=expanded_time_range) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 3747a54ca..5ff1728e6 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -11,6 +11,8 @@ v1.8.next - Second attempt to address unexpected handling of image aspect ratios in rasterio and GDAL. (:pull:`1457`) - Fix broken pypi publishing Github action (:pull:`1454`) +- Documentation improvements (:pull:`1455`) +- Increase maturity leniency to +-500ms (:pull:`1458`) v1.8.13 (6th June 2023) From 137d51b81587c3809a264b7d95ee0a3d71541428 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 21 Jun 2023 06:11:20 +0000 Subject: [PATCH 047/153] allow for a ms delta to be specified as part of the archive-less-mature cli option --- datacube/index/abstract.py | 26 ++++++++++---------- datacube/index/postgis/_datasets.py | 16 ++++++------- datacube/index/postgres/_datasets.py | 18 +++++++------- datacube/scripts/dataset.py | 28 ++++++++++++++++++---- integration_tests/index/test_index_data.py | 8 +++---- 5 files changed, 60 insertions(+), 36 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index cfb8de9af..7261e5c54 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -827,7 +827,7 @@ def bulk_has(self, ids_: Iterable[DSID]) -> Iterable[bool]: @abstractmethod def add(self, dataset: Dataset, with_lineage: bool = True, - archive_less_mature: bool = False, + archive_less_mature: Optional[int] = None, ) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -839,9 +839,9 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db - :param archive_less_mature: - - ``True`` search for less mature versions of the dataset - and archive them + :param archive_less_mature: if integer, search for less + mature versions of the dataset with the int value as a millisecond + delta in timestamp comparison :return: Persisted Dataset model """ @@ -881,13 +881,13 @@ def can_update(self, def update(self, dataset: Dataset, updates_allowed: Optional[Mapping[Offset, AllowPolicy]] = None, - archive_less_mature: bool = False, + archive_less_mature: Optional[int] = None, ) -> Dataset: """ Update dataset metadata and location :param Dataset dataset: Dataset model with unpersisted updates :param updates_allowed: Allowed updates - :param archive_less_mature: Find and archive less mature datasets + :param archive_less_mature: Find and archive less mature datasets with ms delta :return: Persisted dataset model """ @@ -899,30 +899,32 @@ def archive(self, ids: Iterable[DSID]) -> None: :param Iterable[Union[str,UUID]] ids: list of dataset ids to archive """ - def archive_less_mature(self, ds: Dataset) -> None: + def archive_less_mature(self, ds: Dataset, delta: int) -> None: """ Archive less mature versions of a dataset :param Dataset ds: dataset to search """ - less_mature = self.find_less_mature(ds) + less_mature = self.find_less_mature(ds, delta) less_mature_ids = map(lambda x: x.id, less_mature) self.archive(less_mature_ids) for lm_ds in less_mature_ids: _LOG.info(f"Archived less mature dataset: {lm_ds}") - def find_less_mature(self, ds: Dataset) -> Iterable[Dataset]: + def find_less_mature(self, ds: Dataset, delta: int) -> Iterable[Dataset]: """ Find less mature versions of a dataset :param Dataset ds: Dataset to search + :param int delta: millisecond delta for time range :return: Iterable of less mature datasets """ less_mature = [] - # 'expand' the date range by a millisecond to give a bit more leniency in datetime comparison - expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=500), - ds.metadata.time.end + timedelta(milliseconds=500)) + assert delta >= 0 + # 'expand' the date range by `delta` milliseconds to give a bit more leniency in datetime comparison + expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=delta), + ds.metadata.time.end + timedelta(milliseconds=delta)) dupes = self.search(product=ds.product.name, region_code=ds.metadata.region_code, time=expanded_time_range) diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 7f39b1883..7d62b5c5b 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -137,7 +137,7 @@ def bulk_has(self, ids_): map((lambda x: UUID(x) if isinstance(x, str) else x), ids_)] def add(self, dataset: Dataset, - with_lineage: bool = True, archive_less_mature: bool = False) -> Dataset: + with_lineage: bool = True, archive_less_mature: Optional[int] = None) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -148,9 +148,9 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db - :param archive_less_mature: - - ``True`` search for less mature versions of the dataset - and archive them + :param archive_less_mature: if integer, search for less + mature versions of the dataset with the int value as a millisecond + delta in timestamp comparison :rtype: Dataset """ @@ -174,7 +174,7 @@ def add(self, dataset: Dataset, if dataset.uris is not None: self._ensure_new_locations(dataset, transaction=transaction) if archive_less_mature: - self.archive_less_mature(dataset) + self.archive_less_mature(dataset, archive_less_mature) return dataset @@ -311,12 +311,12 @@ def can_update(self, dataset, updates_allowed=None): return not bad_changes, good_changes, bad_changes - def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=False): + def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=None): """ Update dataset metadata and location :param Dataset dataset: Dataset to update :param updates_allowed: Allowed updates - :param archive_less_mature: Find and archive less mature datasets + :param archive_less_mature: Find and archive less mature datasets with ms delta :rtype: Dataset """ existing = self.get(dataset.id) @@ -350,7 +350,7 @@ def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=Fal transaction.update_spindex(dsids=[dataset.id]) transaction.update_search_index(dsids=[dataset.id]) if archive_less_mature: - self._archive_less_mature(dataset) + self.archive_less_mature(dataset, archive_less_mature) self._ensure_new_locations(dataset, existing) diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index c1cda8ddb..0ee041c96 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -10,7 +10,7 @@ import warnings from collections import namedtuple from time import monotonic -from typing import Iterable, List, Union, Mapping, Any +from typing import Iterable, List, Union, Mapping, Optional, Any from uuid import UUID from sqlalchemy import select, func @@ -132,7 +132,7 @@ def bulk_has(self, ids_): map((lambda x: UUID(x) if isinstance(x, str) else x), ids_)] def add(self, dataset: Dataset, - with_lineage: bool = True, archive_less_mature: bool = False) -> Dataset: + with_lineage: bool = True, archive_less_mature: Optional[int] = None) -> Dataset: """ Add ``dataset`` to the index. No-op if it is already present. @@ -143,9 +143,9 @@ def add(self, dataset: Dataset, - ``False`` record lineage relations, but do not attempt adding lineage datasets to the db - :param archive_less_mature: - - ``True`` search for less mature versions of the dataset - and archive them + :param archive_less_mature: if integer, search for less + mature versions of the dataset with the int value as a millisecond + delta in timestamp comparison :rtype: Dataset """ @@ -192,7 +192,7 @@ def process_bunch(dss, main_ds, transaction): with self._db_connection(transaction=True) as transaction: process_bunch(dss, dataset, transaction) if archive_less_mature: - self.archive_less_mature(dataset) + self.archive_less_mature(dataset, archive_less_mature) return dataset @@ -284,12 +284,12 @@ def can_update(self, dataset, updates_allowed=None): return not bad_changes, good_changes, bad_changes - def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=False): + def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=None): """ Update dataset metadata and location :param Dataset dataset: Dataset to update :param updates_allowed: Allowed updates - :param archive_less_mature: Find and archive less mature datasets + :param archive_less_mature: Find and archive less mature datasets with ms delta :rtype: Dataset """ existing = self.get(dataset.id) @@ -320,6 +320,8 @@ def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=Fal with self._db_connection(transaction=True) as transaction: if not transaction.update_dataset(dataset.metadata_doc_without_lineage(), dataset.id, product.id): raise ValueError("Failed to update dataset %s..." % dataset.id) + if archive_less_mature: + self.archive_less_mature(dataset, archive_less_mature) self._ensure_new_locations(dataset, existing) diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index c7a4e5ba6..d265ff63e 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -158,8 +158,10 @@ def mk_dataset(ds, uri): @click.option('--confirm-ignore-lineage', help="Pretend that there is no lineage data in the datasets being indexed, without confirmation", is_flag=True, default=False) -@click.option('--archive-less-mature', help='Archive less mature versions of the dataset', - is_flag=True, default=False) +@click.option('--archive-less-mature', is_flag=False, flag_value=0, default=None, + help=('Find and archive less mature versions of the dataset, will fail if more mature versions ' + 'of the dataset already exist. Can also specify a millisecond buffer amount to be taken ' + 'into acount when comparing timestamps. Default buffer is 0.')) @click.argument('dataset-paths', type=str, nargs=-1) @ui.pass_index() def index_cmd(index, product_names, @@ -200,6 +202,14 @@ def index_cmd(index, product_names, _LOG.error(e) sys.exit(2) + if archive_less_mature is not None: + if type(archive_less_mature) is not int: + click.echo('Error: millisecond delta value must be an integer') + sys.exit(1) + if archive_less_mature < 0: + click.echo('Error: millisecond delta value must be a positive integer') + sys.exit(1) + def run_it(dataset_paths): doc_stream = ui_path_doc_stream(dataset_paths, logger=_LOG, uri=True) doc_stream = remap_uri_from_doc(doc_stream) @@ -250,8 +260,10 @@ def parse_update_rules(keys_that_can_change): - 'keep': keep as alternative location [default] - 'archive': mark as archived - 'forget': remove from the index''')) -@click.option('--archive-less-mature', help='Archive less mature versions of the dataset', - is_flag=True, default=False) +@click.option('--archive-less-mature', is_flag=False, flag_value=0, default=None, + help=('Find and archive less mature versions of the dataset, will fail if more mature versions ' + 'of the dataset already exist. Can also specify a millisecond buffer amount to be taken ' + 'into acount when comparing timestamps. Default buffer is 0.')) @click.argument('dataset-paths', nargs=-1) @ui.pass_index() def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths, archive_less_mature): @@ -299,6 +311,14 @@ def loc_keep(new_ds, existing_ds): doc_stream = ui_path_doc_stream(dataset_paths, logger=_LOG, uri=True) doc_stream = remap_uri_from_doc(doc_stream) + if archive_less_mature is not None: + if type(archive_less_mature) is not int: + click.echo('Error: millisecond delta value must be an integer') + sys.exit(1) + if archive_less_mature < 0: + click.echo('Error: millisecond delta value must be a positive integer') + sys.exit(1) + for dataset, existing_ds in load_datasets_for_update(doc_stream, index): _LOG.info('Matched %s', dataset) diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index 5fde18d6f..c2b2bdf94 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -96,9 +96,9 @@ def test_archive_datasets(index, local_config, ls8_eo3_dataset): def test_archive_less_mature(index, final_dataset, nrt_dataset): # case 1: add nrt then final; nrt should get archived - index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) + index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=0) index.datasets.get(nrt_dataset.id).is_active - index.datasets.add(final_dataset, with_lineage=False, archive_less_mature=True) + index.datasets.add(final_dataset, with_lineage=False, archive_less_mature=0) assert index.datasets.get(nrt_dataset.id).is_archived assert index.datasets.get(final_dataset.id).is_active @@ -107,14 +107,14 @@ def test_archive_less_mature(index, final_dataset, nrt_dataset): assert index.datasets.get(nrt_dataset.id) is None with pytest.raises(ValueError): # should error as more mature version of dataset already exists - index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=True) + index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=0) def test_archive_less_mature_approx_timestamp(index, ga_s2am_ard3_final, ga_s2am_ard3_interim): # test archive_less_mature where there's a slight difference in timestamps index.datasets.add(ga_s2am_ard3_interim, with_lineage=False) index.datasets.get(ga_s2am_ard3_interim.id).is_active - index.datasets.add(ga_s2am_ard3_final, with_lineage=False, archive_less_mature=True) + index.datasets.add(ga_s2am_ard3_final, with_lineage=False, archive_less_mature=1) assert index.datasets.get(ga_s2am_ard3_interim.id).is_archived assert index.datasets.get(ga_s2am_ard3_final.id).is_active From 7f24aa0b600e15b68ac615aecc0489c92b0f3176 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 21 Jun 2023 06:17:14 +0000 Subject: [PATCH 048/153] change default to be 500ms --- datacube/scripts/dataset.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index d265ff63e..bc5f0a73e 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -158,10 +158,10 @@ def mk_dataset(ds, uri): @click.option('--confirm-ignore-lineage', help="Pretend that there is no lineage data in the datasets being indexed, without confirmation", is_flag=True, default=False) -@click.option('--archive-less-mature', is_flag=False, flag_value=0, default=None, +@click.option('--archive-less-mature', is_flag=False, flag_value=500, default=None, help=('Find and archive less mature versions of the dataset, will fail if more mature versions ' - 'of the dataset already exist. Can also specify a millisecond buffer amount to be taken ' - 'into acount when comparing timestamps. Default buffer is 0.')) + 'of the dataset already exist. Can also specify a millisecond delta amount to be taken ' + 'into acount when comparing timestamps. Default delta is 500ms.')) @click.argument('dataset-paths', type=str, nargs=-1) @ui.pass_index() def index_cmd(index, product_names, @@ -260,10 +260,10 @@ def parse_update_rules(keys_that_can_change): - 'keep': keep as alternative location [default] - 'archive': mark as archived - 'forget': remove from the index''')) -@click.option('--archive-less-mature', is_flag=False, flag_value=0, default=None, +@click.option('--archive-less-mature', is_flag=False, flag_value=500, default=None, help=('Find and archive less mature versions of the dataset, will fail if more mature versions ' - 'of the dataset already exist. Can also specify a millisecond buffer amount to be taken ' - 'into acount when comparing timestamps. Default buffer is 0.')) + 'of the dataset already exist. Can also specify a millisecond delta amount to be taken ' + 'into acount when comparing timestamps. Default delta is 500ms.')) @click.argument('dataset-paths', nargs=-1) @ui.pass_index() def update_cmd(index, keys_that_can_change, dry_run, location_policy, dataset_paths, archive_less_mature): From 2ce5dafa3fe54bedc458c58d86424ac4f825fcd1 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 21 Jun 2023 07:12:19 +0000 Subject: [PATCH 049/153] fix test, update whats_new --- datacube/index/postgis/_datasets.py | 4 ++-- datacube/index/postgres/_datasets.py | 4 ++-- docs/about/whats_new.rst | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 7d62b5c5b..b656e923c 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -173,7 +173,7 @@ def add(self, dataset: Dataset, # 1c. Store locations if dataset.uris is not None: self._ensure_new_locations(dataset, transaction=transaction) - if archive_less_mature: + if archive_less_mature is not None: self.archive_less_mature(dataset, archive_less_mature) return dataset @@ -349,7 +349,7 @@ def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=Non raise ValueError("Failed to update dataset %s..." % dataset.id) transaction.update_spindex(dsids=[dataset.id]) transaction.update_search_index(dsids=[dataset.id]) - if archive_less_mature: + if archive_less_mature is not None: self.archive_less_mature(dataset, archive_less_mature) self._ensure_new_locations(dataset, existing) diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 0ee041c96..743bf279f 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -191,7 +191,7 @@ def process_bunch(dss, main_ds, transaction): with self._db_connection(transaction=True) as transaction: process_bunch(dss, dataset, transaction) - if archive_less_mature: + if archive_less_mature is not None: self.archive_less_mature(dataset, archive_less_mature) return dataset @@ -320,7 +320,7 @@ def update(self, dataset: Dataset, updates_allowed=None, archive_less_mature=Non with self._db_connection(transaction=True) as transaction: if not transaction.update_dataset(dataset.metadata_doc_without_lineage(), dataset.id, product.id): raise ValueError("Failed to update dataset %s..." % dataset.id) - if archive_less_mature: + if archive_less_mature is not None: self.archive_less_mature(dataset, archive_less_mature) self._ensure_new_locations(dataset, existing) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 5ff1728e6..be7980f40 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,7 +12,8 @@ v1.8.next GDAL. (:pull:`1457`) - Fix broken pypi publishing Github action (:pull:`1454`) - Documentation improvements (:pull:`1455`) -- Increase maturity leniency to +-500ms (:pull:`1458`) +- Increase default maturity leniency to +-500ms (:pull:`1458`) +- Add option to specify maturity timedelta when using ``--archive-less-mature`` option (:pull:`1460`) v1.8.13 (6th June 2023) From 212961c8211ce1bf52192633b40ee7eedbd7cb76 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 21 Jun 2023 07:17:13 +0000 Subject: [PATCH 050/153] add timedelta to wordlist --- wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wordlist.txt b/wordlist.txt index 4cd2ffb99..df3656121 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -440,6 +440,7 @@ Terria th TIF tif +timedelta timeslice timeslot TIRS From 9d91cb9b9832f216d9493d313b7cc08d29a9f138 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 22 Jun 2023 01:29:02 +0000 Subject: [PATCH 051/153] Mark executors as deprecated --- datacube/executor.py | 5 +++++ datacube/ui/click.py | 4 +++- docker/constraints.in | 2 ++ docker/constraints.txt | 21 +++++++-------------- setup.py | 1 + 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/datacube/executor.py b/datacube/executor.py index b39b03f62..ee2bc7299 100644 --- a/datacube/executor.py +++ b/datacube/executor.py @@ -5,10 +5,12 @@ # # type: ignore import sys +from deprecat import deprecat _REMOTE_LOG_FORMAT_STRING = '%(asctime)s {} %(process)d %(name)s %(levelname)s %(message)s' +@deprecat(reason="Executors have been deprecated and will be removed in v1.9", version='1.8.14') class SerialExecutor(object): def __repr__(self): return 'SerialExecutor' @@ -73,6 +75,7 @@ def setup_logging(): logging.root.handlers = [handler] +@deprecat(reason="Executors have been deprecated and will be removed in v1.9", version='1.8.14') def _get_distributed_executor(scheduler): """ :param scheduler: Address of a scheduler @@ -144,6 +147,7 @@ def _run_cloud_pickled_function(f_data, *args, **kwargs): return func(*args, **kwargs) +@deprecat(reason="Executors have been deprecated and will be removed in v1.9", version='1.8.14') def _get_concurrent_executor(workers, use_cloud_pickle=False): try: from concurrent.futures import ProcessPoolExecutor, as_completed @@ -221,6 +225,7 @@ def release(future): return MultiprocessingExecutor(ProcessPoolExecutor(workers), use_cloud_pickle) +@deprecat(reason="Executors have been deprecated and will be removed in v1.9", version='1.8.14') def get_executor(scheduler, workers, use_cloud_pickle=True): """ Return a task executor based on input parameters. Falling back as required. diff --git a/datacube/ui/click.py b/datacube/ui/click.py index 75ab563cf..25169edf8 100644 --- a/datacube/ui/click.py +++ b/datacube/ui/click.py @@ -285,7 +285,9 @@ def _setup_executor(ctx, param, value): executor_cli_options = click.option('--executor', # type: ignore type=(click.Choice(list(EXECUTOR_TYPES)), str), default=['serial', None], - help="Run parallelized, either locally or distributed. eg:\n" + help="WARNING: executors have been deprecated in v1.8.14, " + "and will be removed in v1.9.\n" + "Run parallelized, either locally or distributed. eg:\n" "--executor multiproc 4 (OR)\n" "--executor distributed 10.0.0.8:8888", callback=_setup_executor) diff --git a/docker/constraints.in b/docker/constraints.in index ffb015b5c..4d21517cd 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -57,3 +57,5 @@ setuptools_scm>=3.4 toml wheel twine + +deprecat diff --git a/docker/constraints.txt b/docker/constraints.txt index 35c1848e9..f49146132 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: # # pip-compile --strip-extras constraints.in # @@ -103,6 +103,8 @@ dask==2023.2.0 # distributed decorator==5.1.1 # via validators +deprecat==2.1.1 + # via -r constraints.in distributed==2023.2.0 # via -r constraints.in docutils==0.18.1 @@ -112,10 +114,6 @@ docutils==0.18.1 # sphinx # sphinx-click # sphinx-rtd-theme -exceptiongroup==1.1.0 - # via - # hypothesis - # pytest fiona==1.9.1 # via -r constraints.in fonttools==4.38.0 @@ -328,8 +326,6 @@ rich==13.3.1 # via twine ruamel-yaml==0.17.21 # via -r constraints.in -ruamel-yaml-clib==0.2.7 - # via ruamel-yaml s3transfer==0.6.0 # via boto3 secretstorage==3.3.3 @@ -390,11 +386,6 @@ toml==0.10.2 # via # -r constraints.in # responses -tomli==2.0.1 - # via - # coverage - # pytest - # setuptools-scm toolz==0.12.0 # via # -r constraints.in @@ -429,7 +420,9 @@ werkzeug==2.2.2 wheel==0.38.4 # via -r constraints.in wrapt==1.11.2 - # via astroid + # via + # astroid + # deprecat xarray==2023.2.0 # via -r constraints.in xmltodict==0.13.0 diff --git a/setup.py b/setup.py index 3d53532bc..4593a905b 100755 --- a/setup.py +++ b/setup.py @@ -111,6 +111,7 @@ 'toolz', 'xarray>=0.9', # >0.9 fixes most problems with `crs` attributes being lost 'packaging', + 'deprecat', ], extras_require=extras_require, tests_require=tests_require, From 84034476e242fa0e8e98251c6d011ff33311e341 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 22 Jun 2023 01:55:27 +0000 Subject: [PATCH 052/153] remove build docker condition --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 032ddbd10..f833b5893 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,7 +70,6 @@ jobs: password: ${{ secrets.GADOCKERSVC_PASSWORD }} - name: Build Docker - if: steps.changes.outputs.docker == 'true' uses: docker/build-push-action@v4 with: file: docker/Dockerfile From d0cd7d369cf0c8acdd10a7f437ca2fb26784a0d9 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 22 Jun 2023 02:15:35 +0000 Subject: [PATCH 053/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 5ff1728e6..4db18f616 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -13,6 +13,7 @@ v1.8.next - Fix broken pypi publishing Github action (:pull:`1454`) - Documentation improvements (:pull:`1455`) - Increase maturity leniency to +-500ms (:pull:`1458`) +- Mark executors as deprecated (:pull:`1461`) v1.8.13 (6th June 2023) From 1df017d4237bd608a72c72423b6de0b0d5c9bcd2 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 22 Jun 2023 04:57:58 +0000 Subject: [PATCH 054/153] deprecation warnings in worker.py, remove celery worker --- datacube/execution/worker.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/datacube/execution/worker.py b/datacube/execution/worker.py index cacc97e64..aff3f985d 100644 --- a/datacube/execution/worker.py +++ b/datacube/execution/worker.py @@ -7,6 +7,7 @@ """ import click +from deprecat import deprecat KNOWN_WORKER_TYPES = ['distributed', 'dask', 'celery'] @@ -25,11 +26,7 @@ def parse_executor_opt(ctx, param, value): return ex_type, host, port -def launch_celery_worker(host, port, nprocs, password=''): - from datacube import _celery_runner as cr - cr.launch_worker(host, port, password=password, nprocs=nprocs) - - +@deprecat(reason="Executors have been deprecated and will be removed in v1.9", version='1.8.14') def launch_distributed_worker(host, port, nprocs, nthreads=1): import subprocess @@ -45,13 +42,13 @@ def launch_distributed_worker(host, port, nprocs, nthreads=1): @click.command(name='worker') @click.option('--executor', type=(click.Choice(KNOWN_WORKER_TYPES), str), # type: ignore - help="(distributed|dask(alias for distributed)|celery) host:port", + help="WARNING: executors have been deprecated in v1.8.14, and will be removed in v1.9.\n" + "(distributed|dask(alias for distributed)|celery) host:port", default=(None, None), callback=parse_executor_opt) @click.option('--nprocs', type=int, default=0, help='Number of worker processes to launch') def main(executor, nprocs): - launchers = dict(celery=launch_celery_worker, - dask=launch_distributed_worker, + launchers = dict(dask=launch_distributed_worker, distributed=launch_distributed_worker) ex_type, host, port = executor return launchers[ex_type](host, port, nprocs) From 30b23675937f50dbc395357a896de8ca1937b5c8 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 22 Jun 2023 05:11:34 +0000 Subject: [PATCH 055/153] remove celery from help message --- datacube/execution/worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacube/execution/worker.py b/datacube/execution/worker.py index aff3f985d..8ca9743a1 100644 --- a/datacube/execution/worker.py +++ b/datacube/execution/worker.py @@ -43,7 +43,7 @@ def launch_distributed_worker(host, port, nprocs, nthreads=1): @click.command(name='worker') @click.option('--executor', type=(click.Choice(KNOWN_WORKER_TYPES), str), # type: ignore help="WARNING: executors have been deprecated in v1.8.14, and will be removed in v1.9.\n" - "(distributed|dask(alias for distributed)|celery) host:port", + "(distributed|dask(alias for distributed)) host:port", default=(None, None), callback=parse_executor_opt) @click.option('--nprocs', type=int, default=0, help='Number of worker processes to launch') From 38c62942cb7272b25ec8e057e6c79ebe9e9371ff Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 01:53:08 +0000 Subject: [PATCH 056/153] mark ingestion as deprecated --- datacube/model/__init__.py | 3 +++ datacube/scripts/ingest.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 950477d34..351d58809 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -24,6 +24,8 @@ from ._base import Range, ranges_overlap # noqa: F401 from .eo3 import validate_eo3_compatible_type +from deprecat import deprecat + _LOG = logging.getLogger(__name__) DEFAULT_SPATIAL_DIMS = ('y', 'x') # Used when product lacks grid_spec @@ -713,6 +715,7 @@ def __hash__(self): DatasetType = Product +@deprecat(reason="Ingestion has been deprecated and will be removed in a future version.", version="1.8.14") @schema_validated(SCHEMA_PATH / 'ingestor-config-type-schema.yaml') class IngestorConfig: """ diff --git a/datacube/scripts/ingest.py b/datacube/scripts/ingest.py index e504a8a4e..a96e3bcb9 100644 --- a/datacube/scripts/ingest.py +++ b/datacube/scripts/ingest.py @@ -391,7 +391,8 @@ def get_driver_from_config(config): return driver -@cli.command('ingest', help="Ingest datasets") +@cli.command('ingest', help="WARNING: Ingestion has been deprecated in v1.8.14 and will be removed in v1.9\n" + "Ingest datasets") @click.option('--config-file', '-c', type=click.Path(exists=True, readable=True, writable=False, dir_okay=False), help='Ingest configuration file') From 9ecf55844dbb841d5ab11dac8b709bf64f51e41f Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 01:56:27 +0000 Subject: [PATCH 057/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index c37479ae6..b19dddb55 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - Increase default maturity leniency to +-500ms (:pull:`1458`) - Add option to specify maturity timedelta when using ``--archive-less-mature`` option (:pull:`1460`) - Mark executors as deprecated (:pull:`1461`) +- Mark ingestion as deprecated (:pull:`1463`) v1.8.13 (6th June 2023) From 3b586ce17aecfd28a122a7c8f6f89e96765039f9 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 03:42:53 +0000 Subject: [PATCH 058/153] mark 'managed' property as deprecated --- datacube/model/__init__.py | 1 + datacube/model/schema/dataset-type-schema.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 351d58809..db975bbce 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -439,6 +439,7 @@ def description(self) -> str: def license(self) -> str: return self.definition.get("license", None) + @deprecat(reason="Ingestion has been deprecated and will be removed in a future version.", version="1.8.14") @property def managed(self) -> bool: return self.definition.get('managed', False) diff --git a/datacube/model/schema/dataset-type-schema.yaml b/datacube/model/schema/dataset-type-schema.yaml index 21fceda40..4ce94cbcb 100644 --- a/datacube/model/schema/dataset-type-schema.yaml +++ b/datacube/model/schema/dataset-type-schema.yaml @@ -36,6 +36,7 @@ properties: items: "$ref": "#/definitions/measurement" managed: + # Indicates ingested product - deprecated type: boolean required: From 0c7d777b78dd40e87f679352924f1b7a46ba5a90 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 04:33:05 +0000 Subject: [PATCH 059/153] fix deprecat decorator --- datacube/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index db975bbce..219d2c2d6 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -439,8 +439,8 @@ def description(self) -> str: def license(self) -> str: return self.definition.get("license", None) - @deprecat(reason="Ingestion has been deprecated and will be removed in a future version.", version="1.8.14") @property + @deprecat(reason="Ingestion has been deprecated and will be removed in a future version.", version="1.8.14") def managed(self) -> bool: return self.definition.get('managed', False) From 3ea56f39e96e4afdf9e0cef306db5579fdada2a5 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 05:19:13 +0000 Subject: [PATCH 060/153] replace pkg_resources --- datacube/drivers/driver_cache.py | 8 ++++---- docs/click_utils.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/datacube/drivers/driver_cache.py b/datacube/drivers/driver_cache.py index 6324170f4..9e4d6f035 100644 --- a/datacube/drivers/driver_cache.py +++ b/datacube/drivers/driver_cache.py @@ -26,11 +26,11 @@ def load_drivers(group: str) -> Dict[str, Any]: """ def safe_load(ep): - from pkg_resources import DistributionNotFound + from importlib.metadata import PackageNotFoundError # pylint: disable=broad-except,bare-except try: driver_init = ep.load() - except DistributionNotFound: + except PackageNotFoundError: # This happens when entry points were marked with extra features, # but extra feature were not requested for installation return None @@ -51,8 +51,8 @@ def safe_load(ep): return driver def resolve_all(group: str) -> Iterable[Tuple[str, Any]]: - from pkg_resources import iter_entry_points - for ep in iter_entry_points(group=group, name=None): + from importlib.metadata import entry_points + for ep in entry_points(group=group, name=None): driver = safe_load(ep) if driver is not None: yield (ep.name, driver) diff --git a/docs/click_utils.py b/docs/click_utils.py index 755848089..434af6e0e 100644 --- a/docs/click_utils.py +++ b/docs/click_utils.py @@ -2,7 +2,7 @@ # # Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 -import pkg_resources +from importlib.metadata import entry_points from docutils.nodes import literal_block, section, title, make_id from sphinx.domains import Domain from docutils.parsers.rst import Directive @@ -34,8 +34,8 @@ def find_script_callable_from_env(name, env): def find_script_callable(name): - return list(pkg_resources.iter_entry_points( - 'console_scripts', name))[0].load() + return list(entry_points( + group='console_scripts', name=name))[0].load() def generate_help_text(command, prefix): From b427d7c017589c45dfbc75d0d1db3d7c883fd8d0 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 06:22:33 +0000 Subject: [PATCH 061/153] fix error, update whats_new --- datacube/drivers/driver_cache.py | 7 +------ docs/about/whats_new.rst | 1 + 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/datacube/drivers/driver_cache.py b/datacube/drivers/driver_cache.py index 9e4d6f035..0e026a966 100644 --- a/datacube/drivers/driver_cache.py +++ b/datacube/drivers/driver_cache.py @@ -26,14 +26,9 @@ def load_drivers(group: str) -> Dict[str, Any]: """ def safe_load(ep): - from importlib.metadata import PackageNotFoundError # pylint: disable=broad-except,bare-except try: driver_init = ep.load() - except PackageNotFoundError: - # This happens when entry points were marked with extra features, - # but extra feature were not requested for installation - return None except Exception as e: _LOG.warning('Failed to resolve driver %s::%s', group, ep.name) _LOG.warning('Error was: %s', repr(e)) @@ -52,7 +47,7 @@ def safe_load(ep): def resolve_all(group: str) -> Iterable[Tuple[str, Any]]: from importlib.metadata import entry_points - for ep in entry_points(group=group, name=None): + for ep in entry_points(group=group): driver = safe_load(ep) if driver is not None: yield (ep.name, driver) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index c37479ae6..a436ab5ac 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - Increase default maturity leniency to +-500ms (:pull:`1458`) - Add option to specify maturity timedelta when using ``--archive-less-mature`` option (:pull:`1460`) - Mark executors as deprecated (:pull:`1461`) +- Replace deprecated ``pgk_resources`` with ``importlib.resources`` and ``importlib.metadata`` (:pull:`1466`) v1.8.13 (6th June 2023) From 4f30fff7a3e5498f3bec1916dc2e6c09a8ef29c3 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 23 Jun 2023 06:24:51 +0000 Subject: [PATCH 062/153] update wordlist --- docs/about/whats_new.rst | 2 +- wordlist.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index a436ab5ac..0f2454ec7 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,7 +15,7 @@ v1.8.next - Increase default maturity leniency to +-500ms (:pull:`1458`) - Add option to specify maturity timedelta when using ``--archive-less-mature`` option (:pull:`1460`) - Mark executors as deprecated (:pull:`1461`) -- Replace deprecated ``pgk_resources`` with ``importlib.resources`` and ``importlib.metadata`` (:pull:`1466`) +- Replace deprecated ``pkg_resources`` with ``importlib.resources`` and ``importlib.metadata`` (:pull:`1466`) v1.8.13 (6th June 2023) diff --git a/wordlist.txt b/wordlist.txt index df3656121..e96a7904d 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -206,6 +206,7 @@ IAM ident identifer img +importlib INEGI inegi ing @@ -338,6 +339,7 @@ pgadmin pgintegration pgisintegration pixelquality +pkg pkgs Pluggable pmap From 5fed4a688c85c198c5d25f2570b0c92ef91dfc04 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 28 Jun 2023 11:26:13 +1000 Subject: [PATCH 063/153] Update whats_new.rst for release. (#1467) --- docs/about/whats_new.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 65db6ad9b..aad080ff2 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,9 @@ What's New v1.8.next ========= +v1.8.14 (28th June 2023) +======================== + - Second attempt to address unexpected handling of image aspect ratios in rasterio and GDAL. (:pull:`1457`) - Fix broken pypi publishing Github action (:pull:`1454`) @@ -17,6 +20,7 @@ v1.8.next - Mark executors as deprecated (:pull:`1461`) - Mark ingestion as deprecated (:pull:`1463`) - Replace deprecated ``pkg_resources`` with ``importlib.resources`` and ``importlib.metadata`` (:pull:`1466`) +- Update whats_new.rst for release (:pull:`1467`) v1.8.13 (6th June 2023) From 353bb99e2c592c414474c02928f0d21a03e9ed98 Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:16:40 +1000 Subject: [PATCH 064/153] Use entry_points selection interface for <3.10 compatibility (#1469) * use entry_points selection interface for <3.10 compatibility * use importlib_metadata if python<3.10 --------- Co-authored-by: Ariana Barzinpour --- datacube/drivers/driver_cache.py | 5 ++++- docs/click_utils.py | 5 ++++- docs/requirements.txt | 8 +++++--- setup.py | 1 + 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/datacube/drivers/driver_cache.py b/datacube/drivers/driver_cache.py index 0e026a966..6c40a9462 100644 --- a/datacube/drivers/driver_cache.py +++ b/datacube/drivers/driver_cache.py @@ -46,7 +46,10 @@ def safe_load(ep): return driver def resolve_all(group: str) -> Iterable[Tuple[str, Any]]: - from importlib.metadata import entry_points + try: + from importlib_metadata import entry_points + except ModuleNotFoundError: + from importlib.metadata import entry_points for ep in entry_points(group=group): driver = safe_load(ep) if driver is not None: diff --git a/docs/click_utils.py b/docs/click_utils.py index 434af6e0e..fca8435ce 100644 --- a/docs/click_utils.py +++ b/docs/click_utils.py @@ -2,7 +2,6 @@ # # Copyright (c) 2015-2023 ODC Contributors # SPDX-License-Identifier: Apache-2.0 -from importlib.metadata import entry_points from docutils.nodes import literal_block, section, title, make_id from sphinx.domains import Domain from docutils.parsers.rst import Directive @@ -34,6 +33,10 @@ def find_script_callable_from_env(name, env): def find_script_callable(name): + try: + from importlib_metadata import entry_points + except ModuleNotFoundError: + from importlib.metadata import entry_points return list(entry_points( group='console_scripts', name=name))[0].load() diff --git a/docs/requirements.txt b/docs/requirements.txt index ce8a5dcc8..49ca72d6d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile --extra=doc,s3 --output-file=docs/requirements.txt @@ -70,6 +70,8 @@ dask[array]==2023.1.1 # distributed defusedxml==0.7.1 # via nbconvert +deprecat==2.1.1 + # via datacube (setup.py) distributed==2023.1.1 # via datacube (setup.py) docutils==0.17.1 @@ -216,8 +218,6 @@ requests==2.28.2 # via sphinx ruamel-yaml==0.17.21 # via datacube (setup.py) -ruamel-yaml-clib==0.2.7 - # via ruamel-yaml s3transfer==0.6.0 # via boto3 shapely==2.0.1 @@ -294,6 +294,8 @@ webencodings==0.5.1 # via # bleach # tinycss2 +wrapt==1.15.0 + # via deprecat xarray==2023.1.0 # via datacube (setup.py) zict==2.2.0 diff --git a/setup.py b/setup.py index 4593a905b..87115bad4 100755 --- a/setup.py +++ b/setup.py @@ -112,6 +112,7 @@ 'xarray>=0.9', # >0.9 fixes most problems with `crs` attributes being lost 'packaging', 'deprecat', + 'importlib_metadata>3.5;python_version<"3.10"', ], extras_require=extras_require, tests_require=tests_require, From b3bc53531728da597e3a744abfe3b373ce46bfe9 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 11 Jul 2023 10:51:41 +1000 Subject: [PATCH 065/153] Update whats_new.rst for 1.8.15 release. (#1470) * Update whats_new.rst for 1.8.15 release. * Tweak wording. --- docs/about/whats_new.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index aad080ff2..3c3b29982 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,12 @@ What's New v1.8.next ========= +v1.8.15 (11th July 2023) +======================== +- Replace `importlib_metadata` for python <3.10 compatibility + (:pull:`1469`) +- Update whats_new.rst for release (:pull:`1470`) + v1.8.14 (28th June 2023) ======================== @@ -22,7 +28,6 @@ v1.8.14 (28th June 2023) - Replace deprecated ``pkg_resources`` with ``importlib.resources`` and ``importlib.metadata`` (:pull:`1466`) - Update whats_new.rst for release (:pull:`1467`) - v1.8.13 (6th June 2023) ======================= From 95b6f431f4890b4c7c6bf67aa99d7644b04c25e5 Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:41:39 +1000 Subject: [PATCH 066/153] Various minor improvements (#1472) * various minor improvements * put back _resolve_uri * update whats_new --------- Co-authored-by: Ariana Barzinpour --- datacube/index/abstract.py | 4 +-- datacube/index/hl.py | 3 +- datacube/scripts/dataset.py | 23 ++++-------- datacube/ui/common.py | 52 +++++++++++---------------- docs/about/whats_new.rst | 3 ++ integration_tests/test_3d.py | 6 ++-- integration_tests/test_cli_output.py | 6 ++-- integration_tests/test_dataset_add.py | 11 ++---- integration_tests/test_end_to_end.py | 2 +- 9 files changed, 43 insertions(+), 67 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 7261e5c54..a4a6a15ae 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -899,7 +899,7 @@ def archive(self, ids: Iterable[DSID]) -> None: :param Iterable[Union[str,UUID]] ids: list of dataset ids to archive """ - def archive_less_mature(self, ds: Dataset, delta: int) -> None: + def archive_less_mature(self, ds: Dataset, delta: int = 500) -> None: """ Archive less mature versions of a dataset @@ -912,7 +912,7 @@ def archive_less_mature(self, ds: Dataset, delta: int) -> None: for lm_ds in less_mature_ids: _LOG.info(f"Archived less mature dataset: {lm_ds}") - def find_less_mature(self, ds: Dataset, delta: int) -> Iterable[Dataset]: + def find_less_mature(self, ds: Dataset, delta: int = 500) -> Iterable[Dataset]: """ Find less mature versions of a dataset diff --git a/datacube/index/hl.py b/datacube/index/hl.py index 75a89f5d5..2b5af66bc 100644 --- a/datacube/index/hl.py +++ b/datacube/index/hl.py @@ -63,11 +63,10 @@ def match(doc: Mapping[str, Any]) -> bool: if matches(doc, rule): return rule.product - relevant_doc = {k: v for k, v in doc.items() if k in rule.signature} raise BadMatch('Dataset metadata did not match product signature.' '\nDataset definition:\n %s\n' '\nProduct signature:\n %s\n' - % (json.dumps(relevant_doc, indent=4), + % (json.dumps(doc, indent=4), json.dumps(rule.signature, indent=4))) return match diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index bc5f0a73e..11a1a911e 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -53,8 +53,6 @@ def _resolve_uri(uri, doc): if isinstance(loc, (list, tuple)): if len(loc) > 0: return loc[0] - else: - return uri return uri @@ -144,11 +142,13 @@ def mk_dataset(ds, uri): 'you can supply several by repeating this option with a new product name'), multiple=True) @click.option('--auto-add-lineage/--no-auto-add-lineage', is_flag=True, default=True, - help=('Default behaviour is to automatically add lineage datasets if they are missing from the database, ' + help=('WARNING: will be deprecated in datacube v1.9.\n' + 'Default behaviour is to automatically add lineage datasets if they are missing from the database, ' 'but this can be disabled if lineage is expected to be present in the DB, ' 'in this case add will abort when encountering missing lineage dataset')) @click.option('--verify-lineage/--no-verify-lineage', is_flag=True, default=True, - help=('Lineage referenced in the metadata document should be the same as in DB, ' + help=('WARNING: will be deprecated in datacube v1.9.\n' + 'Lineage referenced in the metadata document should be the same as in DB, ' 'default behaviour is to skip those top-level datasets that have lineage data ' 'different from the version in the DB. This option allows omitting verification step.')) @click.option('--dry-run', help='Check if everything is ok', is_flag=True, default=False) @@ -156,7 +156,8 @@ def mk_dataset(ds, uri): help="Pretend that there is no lineage data in the datasets being indexed", is_flag=True, default=False) @click.option('--confirm-ignore-lineage', - help="Pretend that there is no lineage data in the datasets being indexed, without confirmation", + help=('WARNING: this flag has been deprecated and will be removed in datacube v1.9.\n' + 'Pretend that there is no lineage data in the datasets being indexed, without confirmation'), is_flag=True, default=False) @click.option('--archive-less-mature', is_flag=False, flag_value=500, default=None, help=('Find and archive less mature versions of the dataset, will fail if more mature versions ' @@ -179,17 +180,7 @@ def index_cmd(index, product_names, print_help_msg(index_cmd) sys.exit(1) - if confirm_ignore_lineage is False and ignore_lineage is True: - if sys.stdin.isatty(): - confirmed = click.confirm("Requested to skip lineage information, Are you sure?", default=False) - if not confirmed: - click.echo('OK aborting', err=True) - sys.exit(1) - else: - click.echo("Use --confirm-ignore-lineage from non-interactive scripts. Aborting.") - sys.exit(1) - - confirm_ignore_lineage = True + confirm_ignore_lineage = ignore_lineage try: ds_resolve = Doc2Dataset(index, diff --git a/datacube/ui/common.py b/datacube/ui/common.py index 7e8c67d28..8df1bff73 100644 --- a/datacube/ui/common.py +++ b/datacube/ui/common.py @@ -89,34 +89,24 @@ def ui_path_doc_stream(paths, logger=None, uri=True, raw=False): """ - def on_error1(p, e): - if logger is not None: - logger.error(str(e)) - - def on_error2(p, e): - if logger is not None: - logger.error('Failed reading documents from %s', str(p)) - - yield from _path_doc_stream(_resolve_doc_files(paths, on_error=on_error1), - on_error=on_error2, uri=uri, raw=raw) - - -def _resolve_doc_files(paths, on_error): - for p in paths: - try: - yield get_metadata_path(p) - except ValueError as e: - on_error(p, e) - - -def _path_doc_stream(files, on_error, uri=True, raw=False): - """See :func:`ui_path_doc_stream` for documentation""" - maybe_wrap = identity if raw else SimpleDocNav - - for fname in files: - try: - for p, doc in read_documents(fname, uri=uri): - yield p, maybe_wrap(doc) - - except InvalidDocException as e: - on_error(fname, e) + def _resolve_doc_files(paths): + for p in paths: + try: + yield get_metadata_path(p) + except ValueError as e: + if logger is not None: + logger.error(str(e)) + + def _path_doc_stream(files, uri=True, raw=False): + maybe_wrap = identity if raw else SimpleDocNav + + for fname in files: + try: + for p, doc in read_documents(fname, uri=uri): + yield p, maybe_wrap(doc) + + except InvalidDocException as e: + if logger is not None: + logger.error('Failed reading documents from %s', str(fname)) + + yield from _path_doc_stream(_resolve_doc_files(paths), uri=uri, raw=raw) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 3c3b29982..e8509c288 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,6 +7,9 @@ What's New v1.8.next ========= +- Improve error message for mismatch between dataset metadata and product signature (:pull:`1472`) +- Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated or to be deprecated (:pull:`1472`) +- Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) v1.8.15 (11th July 2023) ======================== diff --git a/integration_tests/test_3d.py b/integration_tests/test_3d.py index 6a4db6c97..13abb9236 100644 --- a/integration_tests/test_3d.py +++ b/integration_tests/test_3d.py @@ -265,7 +265,7 @@ def test_indexing(clirunner, index, product_def): "-v", "dataset", "add", - "--confirm-ignore-lineage", + "--ignore-lineage", str(index_yaml), ] ) @@ -315,7 +315,7 @@ def test_indexing_with_spectral_map(clirunner, index, dataset_types): clirunner(["-v", "product", "add", str(dataset_types)]) # Index the Dataset - clirunner(["-v", "dataset", "add", '--confirm-ignore-lineage', str(index_yaml)]) + clirunner(["-v", "dataset", "add", '--ignore-lineage', str(index_yaml)]) dc = Datacube(index=index) check_open_with_dc_simple(dc, product_def, [product_id], measurement) @@ -335,7 +335,7 @@ def test_end_to_end_multitime(clirunner, index, product_def, original_data): measurement=measurement, ) # Index the Datasets - clirunner(["-v", "dataset", "add", '--confirm-ignore-lineage', str(index_yaml)]) + clirunner(["-v", "dataset", "add", '--ignore-lineage', str(index_yaml)]) if idx == 0: # Full check for the first measurement only # Check data for all product IDs diff --git a/integration_tests/test_cli_output.py b/integration_tests/test_cli_output.py index e2050e04b..e11365d04 100644 --- a/integration_tests/test_cli_output.py +++ b/integration_tests/test_cli_output.py @@ -81,7 +81,7 @@ def test_cli_dataset_subcommand(index, clirunner, # Insert datasets for path in eo3_dataset_paths: - result = clirunner(['dataset', 'add', "--confirm-ignore-lineage", path]) + result = clirunner(['dataset', 'add', "--ignore-lineage", path]) runner = clirunner(['dataset', 'archive'], verbose_flag=False, expect_success=False) assert "Completed dataset archival." not in runner.output @@ -146,8 +146,8 @@ def test_readd_and_update_metadata_product_dataset_command(index, clirunner, assert "No such dataset in the database" in update.output assert "Failure while processing" in update.output - clirunner(['dataset', 'add', '--confirm-ignore-lineage', ds_path]) - rerun_add = clirunner(['dataset', 'add', '--confirm-ignore-lineage', ds_path]) + clirunner(['dataset', 'add', '--ignore-lineage', ds_path]) + rerun_add = clirunner(['dataset', 'add', '--ignore-lineage', ds_path]) assert "WARNING Dataset" in rerun_add.output assert "is already in the database" in rerun_add.output diff --git a/integration_tests/test_dataset_add.py b/integration_tests/test_dataset_add.py index 62db39e5b..9ee58deb8 100644 --- a/integration_tests/test_dataset_add.py +++ b/integration_tests/test_dataset_add.py @@ -21,7 +21,7 @@ def check_skip_lineage_test(clirunner, index): prefix = write_files({'agdc-metadata.yml': yaml.safe_dump(ds.doc)}) - clirunner(['dataset', 'add', '--confirm-ignore-lineage', '--product', 'A', str(prefix)]) + clirunner(['dataset', 'add', '--ignore-lineage', '--product', 'A', str(prefix)]) ds_ = index.datasets.get(ds.id, include_sources=True) assert ds_ is not None @@ -58,7 +58,7 @@ def check_no_product_match(clirunner, index): # Ignore lineage but fail to match main dataset r = clirunner(['dataset', 'add', '--product', 'B', - '--confirm-ignore-lineage', + '--ignore-lineage', str(prefix)]) assert 'ERROR' in r.output @@ -184,12 +184,6 @@ def check_missing_metadata_doc(clirunner): assert "ERROR No supported metadata docs found for dataset" in r.output -def check_no_confirm(clirunner, path): - r = clirunner(['dataset', 'add', '--ignore-lineage', str(path)], expect_success=False) - assert r.exit_code != 0 - assert 'Use --confirm-ignore-lineage from non-interactive scripts' in r.output - - def check_bad_yaml(clirunner, index): prefix = write_files({'broken.yml': '"'}) r = clirunner(['dataset', 'add', str(prefix / 'broken.yml')]) @@ -266,7 +260,6 @@ def test_dataset_add(dataset_add_configs, index_empty, clirunner): check_inconsistent_lineage(clirunner, index) check_missing_metadata_doc(clirunner) check_missing_lineage(clirunner, index) - check_no_confirm(clirunner, p.datasets) check_bad_yaml(clirunner, index) # check --product=nosuchproduct diff --git a/integration_tests/test_end_to_end.py b/integration_tests/test_end_to_end.py index 32ea3b296..7df414833 100644 --- a/integration_tests/test_end_to_end.py +++ b/integration_tests/test_end_to_end.py @@ -76,7 +76,7 @@ def test_end_to_end(clirunner, index, testdata_dir, ingest_configs, datacube_env # - this will be no-op but with ignore lineage clirunner(['-v', 'dataset', 'add', - '--confirm-ignore-lineage', + '--ignore-lineage', str(lbg_nbar), str(lbg_pq)]) # Test no-op update From 53eb54d6200aa85cd32b3ce4b0a0cded391f5d1f Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Mon, 24 Jul 2023 12:23:53 +1000 Subject: [PATCH 067/153] Fix deprecation warnings (#1476) * Suppress SQLAlchemy warnings. * A couple more SQLAlchemy warning suppressed. * Pin jsonschema to <4.18 to prevent deprecation warnings. * Update whats_new.rst. * Fixed spelling mistake in whats_new and fixed drop schema where no schema does not exist. * SQLAlchema create/drop schema doesn't seem to support "if (not) exists" clauses properly in 1.4. --- conda-environment.yml | 2 +- datacube/drivers/postgis/_api.py | 31 +++++++++-------- datacube/drivers/postgis/_core.py | 5 ++- datacube/drivers/postgres/_core.py | 53 +++++++++++++++-------------- datacube/index/postgis/_datasets.py | 2 +- docker/constraints.in | 2 +- docker/constraints.txt | 13 ++++++- docs/about/whats_new.rst | 1 + setup.py | 2 +- wordlist.txt | 1 + 10 files changed, 65 insertions(+), 47 deletions(-) diff --git a/conda-environment.yml b/conda-environment.yml index 960faf44b..bd674c917 100644 --- a/conda-environment.yml +++ b/conda-environment.yml @@ -22,7 +22,7 @@ dependencies: - dask - pyproj >=2.5 - shapely >=2.0 - - jsonschema + - jsonschema <4.18 - lark - netcdf4 - numpy diff --git a/datacube/drivers/postgis/_api.py b/datacube/drivers/postgis/_api.py index df87318b6..cc5e48c17 100644 --- a/datacube/drivers/postgis/_api.py +++ b/datacube/drivers/postgis/_api.py @@ -389,9 +389,9 @@ def spatial_extent(self, ids, crs): if SpatialIndex is None: return None result = self._connection.execute( - select([ + select( func.ST_AsGeoJSON(func.ST_Union(SpatialIndex.extent)) - ]).select_from( + ).select_from( SpatialIndex ).where( SpatialIndex.dataset_ref.in_(ids) @@ -528,12 +528,12 @@ def delete_dataset(self, dataset_id): def get_dataset(self, dataset_id): return self._connection.execute( - select(_dataset_select_fields()).where(Dataset.id == dataset_id) + select(*_dataset_select_fields()).where(Dataset.id == dataset_id) ).first() def get_datasets(self, dataset_ids): return self._connection.execute( - select(_dataset_select_fields()).where(Dataset.id.in_(dataset_ids)) + select(*_dataset_select_fields()).where(Dataset.id.in_(dataset_ids)) ).fetchall() def get_derived_datasets(self, dataset_id): @@ -551,7 +551,7 @@ def search_datasets_by_metadata(self, metadata): """ # Find any storage types whose 'dataset_metadata' document is a subset of the metadata. return self._connection.execute( - select(_dataset_select_fields()).where(Dataset.metadata_doc.contains(metadata)) + select(*_dataset_select_fields()).where(Dataset.metadata_doc.contains(metadata)) ).fetchall() def search_products_by_metadata(self, metadata): @@ -622,7 +622,7 @@ def search_datasets_query(self, raw_expressions = PostgisDbAPI._alchemify_expressions(expressions) join_tables = PostgisDbAPI._join_tables(expressions, select_fields) where_expr = and_(Dataset.archived == None, *raw_expressions) - query = select(select_columns).select_from(Dataset) + query = select(*select_columns).select_from(Dataset) for joins in join_tables: query = query.join(*joins) if spatialquery is not None: @@ -664,7 +664,7 @@ def bulk_simple_dataset_search(self, products=None, batch_size=0): if batch_size > 0 and not self.in_transaction: raise ValueError("Postgresql bulk reads must occur within a transaction.") query = select( - _dataset_bulk_select_fields() + *_dataset_bulk_select_fields() ).select_from(Dataset).where( Dataset.archived == None ) @@ -709,8 +709,9 @@ def get_duplicates(self, match_fields: Sequence[PgField], expressions: Sequence[ group_expressions = tuple(f.alchemy_expression for f in match_fields) join_tables = PostgisDbAPI._join_tables(expressions, match_fields) + cols = (func.array_agg(Dataset.id),) + group_expressions query = select( - (func.array_agg(Dataset.id),) + group_expressions + *cols ).select_from(Dataset) for joins in join_tables: query = query.join(*joins) @@ -763,24 +764,24 @@ def count_datasets_through_time(self, start, end, period, time_field, expression def count_datasets_through_time_query(self, start, end, period, time_field, expressions): raw_expressions = self._alchemify_expressions(expressions) - start_times = select(( + start_times = select( func.generate_series(start, end, cast(period, INTERVAL)).label('start_time'), - )).alias('start_times') + ).alias('start_times') time_range_select = ( - select(( + select( func.tstzrange( start_times.c.start_time, func.lead(start_times.c.start_time).over() ).label('time_period'), - )) + ) ).alias('all_time_ranges') # Exclude the trailing (end time to infinite) row. Is there a simpler way? time_ranges = ( - select(( + select( time_range_select, - )).where( + ).where( ~func.upper_inf(time_range_select.c.time_period) ) ).alias('time_ranges') @@ -797,7 +798,7 @@ def count_datasets_through_time_query(self, start, end, period, time_field, expr ) ) - return select((time_ranges.c.time_period, count_query.label('dataset_count'))) + return select(time_ranges.c.time_period, count_query.label('dataset_count')) def update_search_index(self, product_names: Sequence[str] = [], dsids: Sequence[DSID] = []): """ diff --git a/datacube/drivers/postgis/_core.py b/datacube/drivers/postgis/_core.py index 00870799a..1c40aa353 100644 --- a/datacube/drivers/postgis/_core.py +++ b/datacube/drivers/postgis/_core.py @@ -11,6 +11,7 @@ from sqlalchemy import MetaData, inspect, text from sqlalchemy.engine import Engine from sqlalchemy.schema import CreateSchema +from sqlalchemy.sql.ddl import DropSchema from datacube.drivers.postgis.sql import (INSTALL_TRIGGER_SQL_TEMPLATE, SCHEMA_NAME, TYPES_INIT_SQL, @@ -225,7 +226,9 @@ def has_schema(engine): def drop_db(connection): - connection.execute(text(f'drop schema if exists {SCHEMA_NAME} cascade')) + # if_exists parameter seems to not be working in SQLA1.4? + if has_schema(connection.engine): + connection.execute(DropSchema(SCHEMA_NAME, cascade=True, if_exists=True)) def to_pg_role(role): diff --git a/datacube/drivers/postgres/_core.py b/datacube/drivers/postgres/_core.py index 61220ebe2..20c1a9e07 100644 --- a/datacube/drivers/postgres/_core.py +++ b/datacube/drivers/postgres/_core.py @@ -17,7 +17,7 @@ pg_column_exists) from sqlalchemy import MetaData, inspect, text from sqlalchemy.engine import Engine -from sqlalchemy.schema import CreateSchema +from sqlalchemy.schema import CreateSchema, DropSchema USER_ROLES = ('agdc_user', 'agdc_ingest', 'agdc_manage', 'agdc_admin') @@ -96,30 +96,29 @@ def ensure_db(engine, with_permissions=True): c.execute(text(""" grant all on database {db} to agdc_admin; """.format(db=quoted_db_name))) - - if not has_schema(engine): - is_new = True - try: - sqla_txn = c.begin() - if with_permissions: - # Switch to 'agdc_admin', so that all items are owned by them. - c.execute(text('set role agdc_admin')) - _LOG.info('Creating schema.') - c.execute(CreateSchema(SCHEMA_NAME)) - _LOG.info('Creating tables.') - c.execute(text(TYPES_INIT_SQL)) - METADATA.create_all(c) - _LOG.info("Creating triggers.") - install_timestamp_trigger(c) - _LOG.info("Creating added column.") - install_added_column(c) - sqla_txn.commit() - except: # noqa: E722 - sqla_txn.rollback() - raise - finally: - if with_permissions: - c.execute(text('set role {}'.format(quoted_user))) + if not has_schema(engine): + is_new = True + try: + sqla_txn = c.begin() + if with_permissions: + # Switch to 'agdc_admin', so that all items are owned by them. + c.execute(text('set role agdc_admin')) + _LOG.info('Creating schema.') + c.execute(CreateSchema(SCHEMA_NAME, if_not_exists=True)) + _LOG.info('Creating tables.') + c.execute(text(TYPES_INIT_SQL)) + METADATA.create_all(c) + _LOG.info("Creating triggers.") + install_timestamp_trigger(c) + _LOG.info("Creating added column.") + install_added_column(c) + sqla_txn.commit() + except: # noqa: E722 + sqla_txn.rollback() + raise + finally: + if with_permissions: + c.execute(text('set role {}'.format(quoted_user))) if with_permissions: _LOG.info('Adding role grants.') @@ -239,7 +238,9 @@ def has_schema(engine): def drop_db(connection): - connection.execute(text(f'drop schema if exists {SCHEMA_NAME} cascade;')) + # if_exists parameter seems to not be working in SQLA1.4? + if has_schema(connection.engine): + connection.execute(DropSchema(SCHEMA_NAME, cascade=True, if_exists=True)) def to_pg_role(role): diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index b656e923c..46eb4dfae 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -769,7 +769,7 @@ def search_summaries(self, **query): """ for _, results in self._do_search_by_product(query, return_fields=True): for columns in results: - output = dict(columns) + output = columns._asdict() _LOG.warning("search results: %s (%s)", output["id"], output["product"]) yield output diff --git a/docker/constraints.in b/docker/constraints.in index 4d21517cd..67b98b60e 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -14,7 +14,7 @@ dask>=2021.10.1 distributed>=2021.10.0 fiona geoalchemy2 -jsonschema +jsonschema<4.18 # Was lark-parser>=0.6.7 lark matplotlib diff --git a/docker/constraints.txt b/docker/constraints.txt index f49146132..98de0175e 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile --strip-extras constraints.in @@ -114,6 +114,10 @@ docutils==0.18.1 # sphinx # sphinx-click # sphinx-rtd-theme +exceptiongroup==1.1.2 + # via + # hypothesis + # pytest fiona==1.9.1 # via -r constraints.in fonttools==4.38.0 @@ -326,6 +330,8 @@ rich==13.3.1 # via twine ruamel-yaml==0.17.21 # via -r constraints.in +ruamel-yaml-clib==0.2.7 + # via ruamel-yaml s3transfer==0.6.0 # via boto3 secretstorage==3.3.3 @@ -386,6 +392,11 @@ toml==0.10.2 # via # -r constraints.in # responses +tomli==2.0.1 + # via + # coverage + # pytest + # setuptools-scm toolz==0.12.0 # via # -r constraints.in diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index e8509c288..dd83d2537 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -10,6 +10,7 @@ v1.8.next - Improve error message for mismatch between dataset metadata and product signature (:pull:`1472`) - Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated or to be deprecated (:pull:`1472`) - Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) +- Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) v1.8.15 (11th July 2023) ======================== diff --git a/setup.py b/setup.py index 87115bad4..ef65bcb94 100755 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ 'cloudpickle>=0.4', 'dask[array]', 'distributed', - 'jsonschema', + 'jsonschema<4.18', 'netcdf4', 'numpy', 'psycopg2', diff --git a/wordlist.txt b/wordlist.txt index e96a7904d..2f935c5da 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -224,6 +224,7 @@ jfEZEOkxRXgNsAsHEC jpg JSON jsonify +jsonschema Jupyter jupyter JupyterLab From 0acd2a98ab801da0c6911b3385707ae861b2d090 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:25:54 +0000 Subject: [PATCH 068/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/Lucas-C/pre-commit-hooks: v1.5.1 → v1.5.3](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.1...v1.5.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59143ce98..c542bdabb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: hooks: - id: pylint - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.1 + rev: v1.5.3 hooks: - id: forbid-crlf - id: remove-crlf From 24b046e737ea94bc088ee0f0566e839370152be9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:28:25 +0000 Subject: [PATCH 069/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/Lucas-C/pre-commit-hooks: v1.5.3 → v1.5.4](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.3...v1.5.4) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c542bdabb..ba8832218 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: hooks: - id: pylint - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.3 + rev: v1.5.4 hooks: - id: forbid-crlf - id: remove-crlf From 5ffbb784a61bb943c1362a44e4f2723404023a85 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 8 Sep 2023 05:59:12 +0000 Subject: [PATCH 070/153] give better error msg if dataset is not compatible with archive-less-mature --- .pre-commit-config.yaml | 2 +- datacube/index/abstract.py | 23 +++++++++++++++++++++- integration_tests/conftest.py | 10 ++++++++++ integration_tests/index/test_index_data.py | 12 ++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba8832218..87e8c3106 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: hooks: - id: pylint - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 + rev: v1.5.4 #v1.5.2 and above refuse to cooperate hooks: - id: forbid-crlf - id: remove-crlf diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index a4a6a15ae..a7eb3de5f 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -22,7 +22,7 @@ from datacube.index.exceptions import TransactionException from datacube.index.fields import Field from datacube.model import Dataset, MetadataType, Range -from datacube.model import DatasetType as Product +from datacube.model import Product from datacube.utils import cached_property, jsonify_document, read_documents, InvalidDocException from datacube.utils.changes import AllowPolicy, Change, Offset, DocumentMismatchError, check_doc_unchanged from datacube.utils.generic import thread_local_cache @@ -922,21 +922,41 @@ def find_less_mature(self, ds: Dataset, delta: int = 500) -> Iterable[Dataset]: """ less_mature = [] assert delta >= 0 + + def check_maturity_information(dataset, props): + # check that the dataset metadata includes all maturity-related properties + # passing in the required props to enable greater extensibility should it be needed + for prop in props: + if hasattr(dataset.metadata, prop) and (getattr(dataset.metadata, prop) is not None): + return + raise ValueError( + f"Dataset {dataset.id} is missing property {prop} required for maturity check" + ) + + check_maturity_information(ds, ["region_code", "time", "dataset_maturity"]) + # 'expand' the date range by `delta` milliseconds to give a bit more leniency in datetime comparison expanded_time_range = Range(ds.metadata.time.begin - timedelta(milliseconds=delta), ds.metadata.time.end + timedelta(milliseconds=delta)) dupes = self.search(product=ds.product.name, region_code=ds.metadata.region_code, time=expanded_time_range) + for dupe in dupes: if dupe.id == ds.id: continue + + # only need to check that dupe has dataset maturity, missing/null region_code and time + # would already have been filtered out during the search query + check_maturity_information(dupe, ["dataset_maturity"]) + if dupe.metadata.dataset_maturity == ds.metadata.dataset_maturity: # Duplicate has the same maturity, which one should be archived is unclear raise ValueError( f"A dataset with the same maturity as dataset {ds.id} already exists, " f"with id: {dupe.id}" ) + if dupe.metadata.dataset_maturity < ds.metadata.dataset_maturity: # Duplicate is more mature than dataset # Note that "final" < "nrt" @@ -944,6 +964,7 @@ def find_less_mature(self, ds: Dataset, delta: int = 500) -> Iterable[Dataset]: f"A more mature version of dataset {ds.id} already exists, with id: " f"{dupe.id} and maturity: {dupe.metadata.dataset_maturity}" ) + less_mature.append(dupe) return less_mature diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 443a30005..8f253f014 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -363,6 +363,16 @@ def final_dataset(index, extended_eo3_metadata_type, ls8_eo3_product, final_data *final_dataset_doc) +@pytest.fixture +def ds_no_region(index, extended_eo3_metadata_type, ls8_eo3_product, final_dataset_doc): + doc_no_region = deepcopy(final_dataset_doc) + doc_no_region[0]["properties"]["odc:region_code"] = None + return doc_to_ds_no_add( + index, + ls8_eo3_product.name, + *doc_no_region) + + @pytest.fixture def ga_s2am_ard3_final(index, eo3_sentinel_metadata_type, ga_s2am_ard_3_product, ga_s2am_ard_3_final_doc): return doc_to_ds_no_add( diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index c2b2bdf94..d3648a591 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -94,7 +94,7 @@ def test_archive_datasets(index, local_config, ls8_eo3_dataset): assert not indexed_dataset.is_archived -def test_archive_less_mature(index, final_dataset, nrt_dataset): +def test_archive_less_mature(index, final_dataset, nrt_dataset, ds_no_region): # case 1: add nrt then final; nrt should get archived index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=0) index.datasets.get(nrt_dataset.id).is_active @@ -110,6 +110,16 @@ def test_archive_less_mature(index, final_dataset, nrt_dataset): index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=0) +def test_cannot_search_for_less_mature(index, nrt_dataset, ds_no_region): + # if a dataset is missing a property required for finding less mature datasets, + # it should error + index.datasets.add(nrt_dataset, with_lineage=False, archive_less_mature=0) + index.datasets.get(nrt_dataset.id).is_active + assert ds_no_region.metadata.region_code is None + with pytest.raises(ValueError, match="region_code"): + index.datasets.add(ds_no_region, with_lineage=False, archive_less_mature=0) + + def test_archive_less_mature_approx_timestamp(index, ga_s2am_ard3_final, ga_s2am_ard3_interim): # test archive_less_mature where there's a slight difference in timestamps index.datasets.add(ga_s2am_ard3_interim, with_lineage=False) From 60b23a66f7edf58dc39b2099a7aca0964592f73d Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 8 Sep 2023 05:59:59 +0000 Subject: [PATCH 071/153] give better error msg if dataset is not compatible with archive-less-mature --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87e8c3106..ba8832218 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: hooks: - id: pylint - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 #v1.5.2 and above refuse to cooperate + rev: v1.5.4 hooks: - id: forbid-crlf - id: remove-crlf From b507f9c1aba3bfcc5c227cd3af81d99dadeb91c7 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 8 Sep 2023 06:06:45 +0000 Subject: [PATCH 072/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index dd83d2537..85155b4dd 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -11,6 +11,7 @@ v1.8.next - Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated or to be deprecated (:pull:`1472`) - Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) - Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) +- Throw a better error if a dataset is not compatible ewith ``archive_less_mature`` logic (:pull:`1491`) v1.8.15 (11th July 2023) ======================== From ee8551fa71fde3c9b29b528d410ec6dcb7d913d1 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 8 Sep 2023 06:11:14 +0000 Subject: [PATCH 073/153] fix typo >:( --- docs/about/whats_new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 85155b4dd..bbb6ae6ca 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -11,7 +11,7 @@ v1.8.next - Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated or to be deprecated (:pull:`1472`) - Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) - Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) -- Throw a better error if a dataset is not compatible ewith ``archive_less_mature`` logic (:pull:`1491`) +- Throw a better error if a dataset is not compatible with ``archive_less_mature`` logic (:pull:`1491`) v1.8.15 (11th July 2023) ======================== From 62e4b9bcd6b01c6e3fceb98189a9428dccd9aceb Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 21 Sep 2023 02:14:05 +0000 Subject: [PATCH 074/153] attempt to fix push to pypi --- .github/workflows/main.yml | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f833b5893..39b185a37 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,10 +28,21 @@ jobs: - name: Config id: cfg run: | + push_test_pypi=no + push_pypi=no + + if "${GITHUB_REF}" in "refs/heads/fix_pypi_action"; then + push_test_pypi=yes + fi if "${GITHUB_REF}" in "refs/tags/"*; then - echo "push_pypi=yes" >> $GITHUB_OUTPUT + push_test_pypi=yes + push_pypi=yes fi + for x in push_test_pypi push_pypi; do + echo "${x}=${!x}" >> $GITHUB_OUTPUT + done + - uses: dorny/paths-filter@v2 id: changes if: | @@ -120,6 +131,30 @@ jobs: twine check ./dist/* EOF + - name: Publish to Test PyPi + if: | + steps.cfg.outputs.push_test_pypi == 'yes' + run: | + if [ -n "${TWINE_PASSWORD}" ]; then + docker run --rm \ + -v $(pwd):/code \ + -e SKIP_DB=yes \ + ${{ env.DOCKER_IMAGE }} \ + twine upload \ + --verbose \ + --non-interactive \ + --disable-progress-bar \ + --username=__token__ \ + --password=${TWINE_PASSWORD} \ + --repository-url=${TWINE_REPOSITORY_URL} \ + --skip-existing dist/* || true + else + echo "Skipping upload as 'TestPyPiToken' is not set" + fi + env: + TWINE_PASSWORD: ${{ secrets.TestPyPiToken }} + TWINE_REPOSITORY_URL: 'https://test.pypi.org/legacy/' + - name: Publish to PyPi if: | github.event_name == 'push' From e6d3fb88aa44d558a698834635d0b08b32f58832 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 21 Sep 2023 02:41:11 +0000 Subject: [PATCH 075/153] 2nd try --- .github/workflows/main.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 39b185a37..9a8d5be7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,6 +10,8 @@ on: - '**' - '!docs/**' - '!contrib/**' + + workflow_dispatch: env: DOCKER_USER: gadockersvc @@ -24,6 +26,9 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 + + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 - name: Config id: cfg @@ -32,17 +37,13 @@ jobs: push_pypi=no if "${GITHUB_REF}" in "refs/heads/fix_pypi_action"; then - push_test_pypi=yes + echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi if "${GITHUB_REF}" in "refs/tags/"*; then - push_test_pypi=yes - push_pypi=yes + echo "push_test_pypi=yes" >> $GITHUB_OUTPUT + echo "push_pypi=yes" >> $GITHUB_OUTPUT fi - for x in push_test_pypi push_pypi; do - echo "${x}=${!x}" >> $GITHUB_OUTPUT - done - - uses: dorny/paths-filter@v2 id: changes if: | From 59f3fc6a30a26078d9d4c27fdb2ed12d1304639b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 21 Sep 2023 03:07:27 +0000 Subject: [PATCH 076/153] try with upterm --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a8d5be7b..c93f2b9b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,8 @@ jobs: with: fetch-depth: 0 - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 + - name: Setup upterm session + uses: lhotari/action-upterm@v1 - name: Config id: cfg From afef8a7289f25189f6ff5720eb2177a7cbd0eb59 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 21 Sep 2023 05:24:13 +0000 Subject: [PATCH 077/153] fix test pypi condition --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c93f2b9b0..cb4757b45 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,9 +26,6 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - - name: Setup upterm session - uses: lhotari/action-upterm@v1 - name: Config id: cfg @@ -36,7 +33,7 @@ jobs: push_test_pypi=no push_pypi=no - if "${GITHUB_REF}" in "refs/heads/fix_pypi_action"; then + if "${GITHUB_REF}" in "refs/remotes/origin/fix_pypi_action"; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi if "${GITHUB_REF}" in "refs/tags/"*; then From 809161b90ec175dbb8d4bc9e470448d69c28f023 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 21 Sep 2023 05:33:19 +0000 Subject: [PATCH 078/153] try with generic refs/heads --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb4757b45..d36cccffc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: push_test_pypi=no push_pypi=no - if "${GITHUB_REF}" in "refs/remotes/origin/fix_pypi_action"; then + if "${GITHUB_REF}" in "refs/heads/*"; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi if "${GITHUB_REF}" in "refs/tags/"*; then From ebfa7f8e2afa0a428843fb4ef06b6c876d9db0a9 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 22 Sep 2023 01:36:51 +0000 Subject: [PATCH 079/153] fix bash syntax --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d36cccffc..b39191436 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,10 +33,10 @@ jobs: push_test_pypi=no push_pypi=no - if "${GITHUB_REF}" in "refs/heads/*"; then + if [ "${GITHUB_REF}" in "refs/heads/"* ]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi - if "${GITHUB_REF}" in "refs/tags/"*; then + if [ "${GITHUB_REF}" in "refs/tags/"* ]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT echo "push_pypi=yes" >> $GITHUB_OUTPUT fi From d14a681a12ec1f60088b23e7ccdde401bcfb56ed Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 22 Sep 2023 01:48:22 +0000 Subject: [PATCH 080/153] actually fix bash syntax this time --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b39191436..d568a4032 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,10 +33,10 @@ jobs: push_test_pypi=no push_pypi=no - if [ "${GITHUB_REF}" in "refs/heads/"* ]; then + if [[ "${GITHUB_REF}" == refs/heads/fix_pypi_action ]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi - if [ "${GITHUB_REF}" in "refs/tags/"* ]; then + if [[ "${GITHUB_REF}" =~ refs/tags/.* ]]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT echo "push_pypi=yes" >> $GITHUB_OUTPUT fi From a91d86362b4aa1d01c8e1acbb6736b721bcadbee Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 22 Sep 2023 01:49:27 +0000 Subject: [PATCH 081/153] ahh typo --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d568a4032..bc400dcc0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: push_test_pypi=no push_pypi=no - if [[ "${GITHUB_REF}" == refs/heads/fix_pypi_action ]; then + if [[ "${GITHUB_REF}" == refs/heads/fix_pypi_action ]]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT fi if [[ "${GITHUB_REF}" =~ refs/tags/.* ]]; then From e4541c1648cf731a32d277a53f8570973f9e504a Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 22 Sep 2023 02:47:18 +0000 Subject: [PATCH 082/153] remove testing trigger for test pypi --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc400dcc0..0024de53d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,9 +33,6 @@ jobs: push_test_pypi=no push_pypi=no - if [[ "${GITHUB_REF}" == refs/heads/fix_pypi_action ]]; then - echo "push_test_pypi=yes" >> $GITHUB_OUTPUT - fi if [[ "${GITHUB_REF}" =~ refs/tags/.* ]]; then echo "push_test_pypi=yes" >> $GITHUB_OUTPUT echo "push_pypi=yes" >> $GITHUB_OUTPUT From f45ba8c54a2c67d183c0246fc354e2c86bc76043 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 22 Sep 2023 03:09:50 +0000 Subject: [PATCH 083/153] udpate whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index bbb6ae6ca..16668280d 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,6 +12,7 @@ v1.8.next - Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) - Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) - Throw a better error if a dataset is not compatible with ``archive_less_mature`` logic (:pull:`1491`) +- Fix broken Github action workflow (:pull:`1496`) v1.8.15 (11th July 2023) ======================== From 1ac445c69598102c66863faf7d7764688d719e4f Mon Sep 17 00:00:00 2001 From: Imam Alam Date: Thu, 21 Sep 2023 04:11:29 +0000 Subject: [PATCH 084/153] support "like=" in virtual products --- datacube/virtual/impl.py | 9 ++++++--- docs/about/whats_new.rst | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/datacube/virtual/impl.py b/datacube/virtual/impl.py index dc7df703e..3ce82f431 100644 --- a/datacube/virtual/impl.py +++ b/datacube/virtual/impl.py @@ -356,9 +356,12 @@ def group(self, datasets: VirtualDatasetBag, **group_settings: Dict[str, Any]) - merged = merge_search_terms(self, group_settings) try: - geobox = output_geobox(datasets=selected, - grid_spec=datasets.product_definitions[self._product].grid_spec, - geopolygon=geopolygon, **select_keys(merged, self._GEOBOX_KEYS)) + if isinstance(merged.get("like"), GeoBox): + geobox = merged["like"] + else: + geobox = output_geobox(datasets=selected, + grid_spec=datasets.product_definitions[self._product].grid_spec, + geopolygon=geopolygon, **select_keys(merged, self._GEOBOX_KEYS)) load_natively = False except ValueError: diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 16668280d..ac640201f 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -13,6 +13,7 @@ v1.8.next - Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) - Throw a better error if a dataset is not compatible with ``archive_less_mature`` logic (:pull:`1491`) - Fix broken Github action workflow (:pull:`1496`) +- Support ``like=`` in virtual product ``load`` (:pull:`1497`) v1.8.15 (11th July 2023) ======================== From a6a0cb8a3d3524d6af684921610a826308282bf2 Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:20:04 +1100 Subject: [PATCH 085/153] Account for bools in archive_less_mature (#1498) * don't archive less mature if value is False instead of None * update whats_new * update delta typehint, improve type/value validation * add warning message if delta is bool * fix type check * add test for not archiving less mature, fix find less mature type check * fix logic for real this time --------- Authored-by: Ariana Barzinpour --- datacube/index/abstract.py | 27 ++++++++++++++++++---- docs/about/whats_new.rst | 1 + integration_tests/index/test_index_data.py | 18 +++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index a7eb3de5f..c715404af 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -899,11 +899,14 @@ def archive(self, ids: Iterable[DSID]) -> None: :param Iterable[Union[str,UUID]] ids: list of dataset ids to archive """ - def archive_less_mature(self, ds: Dataset, delta: int = 500) -> None: + def archive_less_mature(self, ds: Dataset, delta: Union[int, bool] = 500) -> None: """ Archive less mature versions of a dataset :param Dataset ds: dataset to search + :param Union[int,bool] delta: millisecond delta for time range. + If True, default to 500ms. If False, do not find or archive less mature datasets. + Bool value accepted only for improving backwards compatibility, int preferred. """ less_mature = self.find_less_mature(ds, delta) less_mature_ids = map(lambda x: x.id, less_mature) @@ -912,16 +915,29 @@ def archive_less_mature(self, ds: Dataset, delta: int = 500) -> None: for lm_ds in less_mature_ids: _LOG.info(f"Archived less mature dataset: {lm_ds}") - def find_less_mature(self, ds: Dataset, delta: int = 500) -> Iterable[Dataset]: + def find_less_mature(self, ds: Dataset, delta: Union[int, bool] = 500) -> Iterable[Dataset]: """ Find less mature versions of a dataset :param Dataset ds: Dataset to search - :param int delta: millisecond delta for time range + :param Union[int,bool] delta: millisecond delta for time range. + If True, default to 500ms. If None or False, do not find or archive less mature datasets. + Bool value accepted only for improving backwards compatibility, int preferred. :return: Iterable of less mature datasets """ - less_mature = [] - assert delta >= 0 + if isinstance(delta, bool): + _LOG.warning("received delta as a boolean value. Int is prefered") + if delta is True: # treat True as default + delta = 500 + else: # treat False the same as None + return [] + elif isinstance(delta, int): + if delta < 0: + raise ValueError("timedelta must be a positive integer") + elif delta is None: + return [] + else: + raise TypeError("timedelta must be None, a positive integer, or a boolean") def check_maturity_information(dataset, props): # check that the dataset metadata includes all maturity-related properties @@ -942,6 +958,7 @@ def check_maturity_information(dataset, props): region_code=ds.metadata.region_code, time=expanded_time_range) + less_mature = [] for dupe in dupes: if dupe.id == ds.id: continue diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index ac640201f..32f2cb5c5 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -14,6 +14,7 @@ v1.8.next - Throw a better error if a dataset is not compatible with ``archive_less_mature`` logic (:pull:`1491`) - Fix broken Github action workflow (:pull:`1496`) - Support ``like=`` in virtual product ``load`` (:pull:`1497`) +- Don't archive less mature if archive_less_mature is provided as `False` instead of `None` (:pull:`1498`) v1.8.15 (11th July 2023) ======================== diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index d3648a591..9e4f836e3 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -129,6 +129,24 @@ def test_archive_less_mature_approx_timestamp(index, ga_s2am_ard3_final, ga_s2am assert index.datasets.get(ga_s2am_ard3_final.id).is_active +def test_dont_archive_less_mature(index, final_dataset, nrt_dataset): + # ensure datasets aren't archive if no archive_less_mature value is provided + index.datasets.add(nrt_dataset, with_lineage=False) + index.datasets.get(nrt_dataset.id).is_active + index.datasets.add(final_dataset, with_lineage=False, archive_less_mature=None) + assert index.datasets.get(nrt_dataset.id).is_active + assert index.datasets.get(final_dataset.id).is_active + + +def test_archive_less_mature_bool(index, final_dataset, nrt_dataset): + # if archive_less_mature value gets passed as a bool via an outdated script + index.datasets.add(nrt_dataset, with_lineage=False) + index.datasets.get(nrt_dataset.id).is_active + index.datasets.add(final_dataset, with_lineage=False, archive_less_mature=False) + assert index.datasets.get(nrt_dataset.id).is_active + assert index.datasets.get(final_dataset.id).is_active + + def test_purge_datasets(index, ls8_eo3_dataset): assert index.datasets.has(ls8_eo3_dataset.id) datasets = index.datasets.search_eager() From 584db42c83d4e0f14fae4e0b832e6de5f51ce517 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 12 Oct 2023 11:31:17 +1100 Subject: [PATCH 086/153] Require Python 3.9 at minimum. (#1500) * Require Python >=3.9 Add tags for Py 3.10 and 3.11. * Remove conda tests for 3.8. Add for 3.10 and 3.11. * Updated Whats new.rst * Bump lint.yaml GHA to Py3.9 * Updated PR number * Require Python >=3.9 Add tags for Py 3.10 and 3.11. * Remove conda tests for 3.8. Add for 3.10 and 3.11. * Updated Whats new.rst * Bump lint.yaml GHA to Py3.9 * Updated PR number --- .github/workflows/lint.yaml | 4 ++-- .github/workflows/test-conda-build.yml | 2 +- docs/about/whats_new.rst | 1 + setup.py | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 65659ac1b..3733f3e50 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] + python-version: [3.9] name: Pylint steps: - name: checkout git @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] + python-version: [3.9] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/test-conda-build.yml b/.github/workflows/test-conda-build.yml index 5ff16a4a4..3143b678f 100644 --- a/.github/workflows/test-conda-build.yml +++ b/.github/workflows/test-conda-build.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: ["ubuntu-latest", "macos-latest", "windows-latest"] - python-version: ["3.8", "3.9"] + python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 32f2cb5c5..ada64552f 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - Fix broken Github action workflow (:pull:`1496`) - Support ``like=`` in virtual product ``load`` (:pull:`1497`) - Don't archive less mature if archive_less_mature is provided as `False` instead of `None` (:pull:`1498`) +- Raise minimum supported Python version to 3.9 (:pull:`1500`) v1.8.15 (11th July 2023) ======================== diff --git a/setup.py b/setup.py index ef65bcb94..0d069d829 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup( name='datacube', - python_requires='>=3.8.0', + python_requires='>=3.9.0', url='https://github.com/opendatacube/datacube-core', author='Open Data Cube', @@ -70,9 +70,10 @@ "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: GIS", "Topic :: Scientific/Engineering :: Information Analysis", ], From b6181fe97c7af3a53c838f8be75b2a14e9589f41 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 17 Oct 2023 10:14:27 +1100 Subject: [PATCH 087/153] For 1.8.16 release (#1501) * Update whats_new.rst with release notes. * Manually apply recent dependabot upgrades. * Removed some lingering references to Python3.8+ (including conda-environment.yml) --- .github/workflows/doc-qa.yaml | 4 ++-- .github/workflows/lint.yaml | 2 +- .github/workflows/main.yml | 8 ++++---- .github/workflows/test-conda-build.yml | 2 +- README.rst | 8 ++++---- conda-environment.yml | 2 +- docs/about/whats_new.rst | 7 ++++++- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/doc-qa.yaml b/.github/workflows/doc-qa.yaml index cdd07a73e..2b9d9751f 100644 --- a/.github/workflows/doc-qa.yaml +++ b/.github/workflows/doc-qa.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Spellcheck - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Create cache dir" run: mkdir .cache diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 3733f3e50..9d39f5668 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -23,7 +23,7 @@ jobs: name: Pylint steps: - name: checkout git - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup conda diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0024de53d..d24d86ddd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: - name: Set up Docker Buildx if: steps.changes.outputs.docker == 'true' - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Cache Docker layers if: steps.changes.outputs.docker == 'true' @@ -70,13 +70,13 @@ jobs: if: | github.event_name == 'push' && github.ref == 'refs/heads/develop' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ env.DOCKER_USER }} password: ${{ secrets.GADOCKERSVC_PASSWORD }} - name: Build Docker - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: file: docker/Dockerfile context: . @@ -108,7 +108,7 @@ jobs: github.event_name == 'push' && github.ref == 'refs/heads/develop' && steps.changes.outputs.docker == 'true' - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: file: docker/Dockerfile context: . diff --git a/.github/workflows/test-conda-build.yml b/.github/workflows/test-conda-build.yml index 3143b678f..26eba286e 100644 --- a/.github/workflows/test-conda-build.yml +++ b/.github/workflows/test-conda-build.yml @@ -17,7 +17,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Cache conda uses: actions/cache@v3 diff --git a/README.rst b/README.rst index a730c11b4..1e8c7e7d1 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ System ~~~~~~ - PostgreSQL 10+ -- Python 3.8+ +- Python 3.9+ Developer setup =============== @@ -74,17 +74,17 @@ Developer setup 5. Run unit tests + PyLint Install test dependencies using: - + ``pip install --upgrade -e '.[test]'`` If install for these fails, please lodge them as issues. - + Run unit tests with: ``./check-code.sh`` (this script approximates what is run by GitHub Actions. You can - alternatively run ``pytest`` yourself). + alternatively run ``pytest`` yourself). 6. **(or)** Run all tests, including integration tests. diff --git a/conda-environment.yml b/conda-environment.yml index bd674c917..623afe441 100644 --- a/conda-environment.yml +++ b/conda-environment.yml @@ -4,7 +4,7 @@ channels: - defaults dependencies: - pip - - python >=3.8 + - python >=3.9 - setuptools - setuptools_scm >=3.4 - toml diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index ada64552f..5df690b9d 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,8 +7,12 @@ What's New v1.8.next ========= + +v1.8.16 (17th October 2023) +=========================== - Improve error message for mismatch between dataset metadata and product signature (:pull:`1472`) -- Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated or to be deprecated (:pull:`1472`) +- Mark ``--confirm-ignore-lineage``, ``--auto-add-lineage``, and ``--verify-lineage`` as deprecated + or to be deprecated (:pull:`1472`) - Default delta values in ``archive_less_mature`` and ``find_less_mature`` (:pull:`1472`) - Fix SQLAlchemy calls and pin jsonschema version to suppress deprecation warnings (:pull:`1476`) - Throw a better error if a dataset is not compatible with ``archive_less_mature`` logic (:pull:`1491`) @@ -16,6 +20,7 @@ v1.8.next - Support ``like=`` in virtual product ``load`` (:pull:`1497`) - Don't archive less mature if archive_less_mature is provided as `False` instead of `None` (:pull:`1498`) - Raise minimum supported Python version to 3.9 (:pull:`1500`) +- Manually apply Dependabot updates, and update whats_new.rst for 1.8.16 release (:pull:`1501`) v1.8.15 (11th July 2023) ======================== From 7d0223265866b2c4971e11aa61c42d475bf797cd Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 20 Oct 2023 01:50:37 +0000 Subject: [PATCH 088/153] move schema creation logic out of if with_permissions --- datacube/drivers/postgres/_core.py | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/datacube/drivers/postgres/_core.py b/datacube/drivers/postgres/_core.py index 20c1a9e07..52a075148 100644 --- a/datacube/drivers/postgres/_core.py +++ b/datacube/drivers/postgres/_core.py @@ -96,29 +96,30 @@ def ensure_db(engine, with_permissions=True): c.execute(text(""" grant all on database {db} to agdc_admin; """.format(db=quoted_db_name))) - if not has_schema(engine): - is_new = True - try: - sqla_txn = c.begin() - if with_permissions: - # Switch to 'agdc_admin', so that all items are owned by them. - c.execute(text('set role agdc_admin')) - _LOG.info('Creating schema.') - c.execute(CreateSchema(SCHEMA_NAME, if_not_exists=True)) - _LOG.info('Creating tables.') - c.execute(text(TYPES_INIT_SQL)) - METADATA.create_all(c) - _LOG.info("Creating triggers.") - install_timestamp_trigger(c) - _LOG.info("Creating added column.") - install_added_column(c) - sqla_txn.commit() - except: # noqa: E722 - sqla_txn.rollback() - raise - finally: - if with_permissions: - c.execute(text('set role {}'.format(quoted_user))) + + if not has_schema(engine): + is_new = True + try: + sqla_txn = c.begin() + if with_permissions: + # Switch to 'agdc_admin', so that all items are owned by them. + c.execute(text('set role agdc_admin')) + _LOG.info('Creating schema.') + c.execute(CreateSchema(SCHEMA_NAME, if_not_exists=True)) + _LOG.info('Creating tables.') + c.execute(text(TYPES_INIT_SQL)) + METADATA.create_all(c) + _LOG.info("Creating triggers.") + install_timestamp_trigger(c) + _LOG.info("Creating added column.") + install_added_column(c) + sqla_txn.commit() + except: # noqa: E722 + sqla_txn.rollback() + raise + finally: + if with_permissions: + c.execute(text('set role {}'.format(quoted_user))) if with_permissions: _LOG.info('Adding role grants.') From 0cfe300d1b62911abd30c851cd133be0817a7f90 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Fri, 20 Oct 2023 01:55:06 +0000 Subject: [PATCH 089/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 5df690b9d..77d49116c 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,6 +7,7 @@ What's New v1.8.next ========= +- Fix schema creation with postgres driver when initialising system with ``--no-init-users`` (:pull:`1504`) v1.8.16 (17th October 2023) =========================== From 43903ea36a2ccdc30684617f6280abe77440374f Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Mon, 6 Nov 2023 13:24:21 +1100 Subject: [PATCH 090/153] Upgrade jsonschema (#1477) * Suppress SQLAlchemy warnings. * Fixed spelling mistake in whats_new and fixed drop schema where no schema does not exist. * Switch to jsonschema 4.18+ "references" API. * Rebase onto develop. * Add repin (as in pin again) to the dictionary. * Ugh - could have sworn this was working on Friday. --- conda-environment.yml | 2 +- datacube/utils/documents.py | 13 +++++++------ docker/constraints.in | 4 ++-- docker/constraints.txt | 15 ++++++++++++--- docs/about/whats_new.rst | 1 + setup.py | 2 +- wordlist.txt | 1 + 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/conda-environment.yml b/conda-environment.yml index 623afe441..fb867b0fb 100644 --- a/conda-environment.yml +++ b/conda-environment.yml @@ -22,7 +22,7 @@ dependencies: - dask - pyproj >=2.5 - shapely >=2.0 - - jsonschema <4.18 + - jsonschema >=4.18 - lark - netcdf4 - numpy diff --git a/datacube/utils/documents.py b/datacube/utils/documents.py index 874a1f213..3d7d6fb38 100644 --- a/datacube/utils/documents.py +++ b/datacube/utils/documents.py @@ -202,6 +202,7 @@ def read_strings_from_netcdf(path, variable): def validate_document(document, schema, schema_folder=None): import jsonschema + import referencing try: # Allow schemas to reference other schemas in the given folder. @@ -210,14 +211,14 @@ def doc_reference(path): if not path.exists(): raise ValueError("Reference not found: %s" % path) referenced_schema = next(iter(read_documents(path)))[1] - return referenced_schema + return referencing.Resource(referenced_schema, referencing.jsonschema.DRAFT4) + if schema_folder: + registry = referencing.Registry(retrieve=doc_reference) + else: + registry = referencing.Registry() jsonschema.Draft4Validator.check_schema(schema) - ref_resolver = jsonschema.RefResolver.from_schema( - schema, - handlers={'': doc_reference} if schema_folder else () - ) - validator = jsonschema.Draft4Validator(schema, resolver=ref_resolver) + validator = jsonschema.Draft4Validator(schema, registry=registry) validator.validate(document) except jsonschema.ValidationError as e: raise InvalidDocException(e) diff --git a/docker/constraints.in b/docker/constraints.in index 67b98b60e..f5c24dc46 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -14,8 +14,8 @@ dask>=2021.10.1 distributed>=2021.10.0 fiona geoalchemy2 -jsonschema<4.18 -# Was lark-parser>=0.6.7 +# New reference resolution API +jsonschema>=4.18 lark matplotlib moto diff --git a/docker/constraints.txt b/docker/constraints.txt index 98de0175e..d6ccf8e2a 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -24,6 +24,7 @@ attrs==22.2.0 # jsonschema # pytest # rasterio + # referencing babel==2.11.0 # via sphinx bleach==6.0.0 @@ -163,8 +164,10 @@ jmespath==1.0.1 # via # boto3 # botocore -jsonschema==4.17.3 +jsonschema==4.18.4 # via -r constraints.in +jsonschema-specifications==2023.7.1 + # via jsonschema keyring==23.13.1 # via twine kiwisolver==1.4.4 @@ -266,8 +269,6 @@ pyproj==3.4.1 # via # -r constraints.in # compliance-checker -pyrsistent==0.19.3 - # via jsonschema pytest==7.2.1 # via # -r constraints.in @@ -309,6 +310,10 @@ recommonmark==0.7.1 # via -r constraints.in redis==4.5.1 # via -r constraints.in +referencing==0.30.0 + # via + # jsonschema + # jsonschema-specifications regex==2022.10.31 # via compliance-checker requests==2.28.2 @@ -328,6 +333,10 @@ rfc3986==2.0.0 # via twine rich==13.3.1 # via twine +rpds-py==0.9.2 + # via + # jsonschema + # referencing ruamel-yaml==0.17.21 # via -r constraints.in ruamel-yaml-clib==0.2.7 diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 77d49116c..18b930986 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,7 @@ What's New v1.8.next ========= - Fix schema creation with postgres driver when initialising system with ``--no-init-users`` (:pull:`1504`) +- Switch to new jsonschema 'referencing' API and repin jsonschema to >=4.18 (:pull:`1477`) v1.8.16 (17th October 2023) =========================== diff --git a/setup.py b/setup.py index 0d069d829..8464f799a 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ 'cloudpickle>=0.4', 'dask[array]', 'distributed', - 'jsonschema<4.18', + 'jsonschema>=4.18', # New reference resolution API 'netcdf4', 'numpy', 'psycopg2', diff --git a/wordlist.txt b/wordlist.txt index 2f935c5da..a4ebca390 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -391,6 +391,7 @@ reampling redis Reflectance reflectance +repin Reproject reproject reprojected From 9fb8c8cf95953b6895561d4781f5f9250d0aa7b7 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 8 Nov 2023 10:58:21 +1100 Subject: [PATCH 091/153] Update whats_new.rst for 1.8.17 release. (#1510) --- docs/about/whats_new.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 18b930986..921d29730 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,8 +7,12 @@ What's New v1.8.next ========= + +v1.8.17 (8th November 2023) +=========================== - Fix schema creation with postgres driver when initialising system with ``--no-init-users`` (:pull:`1504`) - Switch to new jsonschema 'referencing' API and repin jsonschema to >=4.18 (:pull:`1477`) +- Update whats_new.rst for release (:pull:`1510`) v1.8.16 (17th October 2023) =========================== From ed3d1a7170f846879c80adc4886e2deee4ddb0b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:04:02 +0000 Subject: [PATCH 092/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adrienverge/yamllint.git: v1.32.0 → v1.33.0](https://github.com/adrienverge/yamllint.git/compare/v1.32.0...v1.33.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba8832218..4f102e6d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/adrienverge/yamllint.git - rev: v1.32.0 + rev: v1.33.0 hooks: - id: yamllint - repo: https://github.com/pre-commit/pre-commit-hooks From f3323b95eebae634c5e20d3af1d428d4f4a8ea9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:38:49 +0000 Subject: [PATCH 093/153] Bump conda-incubator/setup-miniconda from 2 to 3 Bumps [conda-incubator/setup-miniconda](https://github.com/conda-incubator/setup-miniconda) from 2 to 3. - [Release notes](https://github.com/conda-incubator/setup-miniconda/releases) - [Changelog](https://github.com/conda-incubator/setup-miniconda/blob/main/CHANGELOG.md) - [Commits](https://github.com/conda-incubator/setup-miniconda/compare/v2...v3) --- updated-dependencies: - dependency-name: conda-incubator/setup-miniconda dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test-conda-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-conda-build.yml b/.github/workflows/test-conda-build.yml index 26eba286e..744d212e8 100644 --- a/.github/workflows/test-conda-build.yml +++ b/.github/workflows/test-conda-build.yml @@ -30,7 +30,7 @@ jobs: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('conda-environment.yml') }} - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 with: environment-file: conda-environment.yml auto-update-conda: true From 7c738fd97e407cfa9967e71baaca5b05de1ffb9c Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Mon, 4 Dec 2023 15:25:03 +1100 Subject: [PATCH 094/153] Dataset CLI tool to find duplicates (#1517) * add dataset cli tool to find duplicates * convert timestamps to UTC * update whats_new * update memory driver search duplicates implementation --------- Co-authored-by: Ariana Barzinpour --- datacube/drivers/postgis/_api.py | 55 +++++++++++++++++- datacube/drivers/postgis/_fields.py | 15 ++++- datacube/drivers/postgres/_api.py | 60 +++++++++++++++++++- datacube/drivers/postgres/_fields.py | 15 ++++- datacube/index/memory/_datasets.py | 20 ++++--- datacube/index/postgis/_datasets.py | 9 ++- datacube/index/postgres/_datasets.py | 9 ++- datacube/scripts/dataset.py | 42 ++++++++++++++ docs/about/whats_new.rst | 1 + integration_tests/index/test_memory_index.py | 4 +- integration_tests/index/test_search_eo3.py | 30 +++++++++- integration_tests/test_cli_output.py | 26 +++++++++ 12 files changed, 257 insertions(+), 29 deletions(-) diff --git a/datacube/drivers/postgis/_api.py b/datacube/drivers/postgis/_api.py index cc5e48c17..03e93276c 100644 --- a/datacube/drivers/postgis/_api.py +++ b/datacube/drivers/postgis/_api.py @@ -706,10 +706,13 @@ def search_unique_datasets(self, expressions, select_fields=None, limit=None): def get_duplicates(self, match_fields: Sequence[PgField], expressions: Sequence[PgExpression]) -> Iterable[tuple]: # TODO + if "time" in [f.name for f in match_fields]: + return self.get_duplicates_with_time(match_fields, expressions) + group_expressions = tuple(f.alchemy_expression for f in match_fields) join_tables = PostgisDbAPI._join_tables(expressions, match_fields) - cols = (func.array_agg(Dataset.id),) + group_expressions + cols = (func.array_agg(Dataset.id).label('ids'),) + group_expressions query = select( *cols ).select_from(Dataset) @@ -725,6 +728,56 @@ def get_duplicates(self, match_fields: Sequence[PgField], expressions: Sequence[ ) return self._connection.execute(query) + def get_duplicates_with_time( + self, match_fields: Sequence[PgField], expressions: Sequence[PgExpression] + ) -> Iterable[tuple]: + fields = [] + for f in match_fields: + if f.name == "time": + time_field = f.expression_with_leniency + else: + fields.append(f.alchemy_expression) + + join_tables = PostgisDbAPI._join_tables(expressions, match_fields) + + cols = [Dataset.id, time_field.label('time'), *fields] + query = select( + *cols + ).select_from(Dataset) + for joins in join_tables: + query = query.join(*joins) + + query = query.where( + and_(Dataset.archived == None, *(PostgisDbAPI._alchemify_expressions(expressions))) + ) + + t1 = query.alias("t1") + t2 = query.alias("t2") + + time_overlap = select( + t1.c.id, + text("t1.time * t2.time as time_intersect"), + *fields + ).select_from( + t1.join( + t2, + and_(t1.c.time.overlaps(t2.c.time), t1.c.id != t2.c.id) + ) + ) + + query = select( + func.array_agg(func.distinct(time_overlap.c.id)).label("ids"), + *fields, + text("(lower(time_intersect) at time zone 'UTC', upper(time_intersect) at time zone 'UTC') as time") + ).select_from( + time_overlap + ).group_by( + *fields, text("time_intersect") + ).having( + func.count(time_overlap.c.id) > 1 + ) + return self._connection.execute(query) + def count_datasets(self, expressions): """ :type expressions: tuple[datacube.drivers.postgis._fields.PgExpression] diff --git a/datacube/drivers/postgis/_fields.py b/datacube/drivers/postgis/_fields.py index 8da248970..23cceadf6 100755 --- a/datacube/drivers/postgis/_fields.py +++ b/datacube/drivers/postgis/_fields.py @@ -16,6 +16,7 @@ from sqlalchemy import cast, func, and_ from sqlalchemy.dialects import postgresql as postgres from sqlalchemy.dialects.postgresql import NUMRANGE, TSTZRANGE +from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.sql import ColumnElement from sqlalchemy.orm import aliased @@ -223,7 +224,7 @@ def __init__(self, name, description, alchemy_column, indexed, offset=None, sele @property def alchemy_expression(self): - return self._alchemy_offset_value(self.offset, self.aggregation.pg_calc) + return self._alchemy_offset_value(self.offset, self.aggregation.pg_calc).label(self.name) def __eq__(self, value): """ @@ -360,7 +361,7 @@ def value_to_alchemy(self, value): @property def alchemy_expression(self): - return self.value_to_alchemy((self.lower.alchemy_expression, self.greater.alchemy_expression)) + return self.value_to_alchemy((self.lower.alchemy_expression, self.greater.alchemy_expression)).label(self.name) def __eq__(self, value): """ @@ -439,6 +440,16 @@ def between(self, low, high): raise ValueError("Unknown comparison type for date range: " "expecting datetimes, got: (%r, %r)" % (low, high)) + @property + def expression_with_leniency(self): + return func.tstzrange( + self.lower.alchemy_expression - cast('500 milliseconds', INTERVAL), + self.greater.alchemy_expression + cast('500 milliseconds', INTERVAL), + # Inclusive on both sides. + '[]', + type_=TSTZRANGE, + ) + def _number_implies_year(v: Union[int, datetime]) -> datetime: """ diff --git a/datacube/drivers/postgres/_api.py b/datacube/drivers/postgres/_api.py index 5da9d1bd3..ac4fe9450 100644 --- a/datacube/drivers/postgres/_api.py +++ b/datacube/drivers/postgres/_api.py @@ -725,10 +725,13 @@ def search_unique_datasets(self, expressions, select_fields=None, limit=None): return self._connection.execute(select_query) def get_duplicates(self, match_fields: Iterable[PgField], expressions: Iterable[PgExpression]) -> Iterable[Tuple]: + if "time" in [f.name for f in match_fields]: + return self.get_duplicates_with_time(match_fields, expressions) + group_expressions = tuple(f.alchemy_expression for f in match_fields) select_query = select( - (func.array_agg(DATASET.c.id),) + group_expressions + (func.array_agg(DATASET.c.id).label('ids'),) + group_expressions ).select_from( PostgresDbAPI._from_expression(DATASET, expressions, match_fields) ).where( @@ -740,6 +743,61 @@ def get_duplicates(self, match_fields: Iterable[PgField], expressions: Iterable[ ) return self._connection.execute(select_query) + def get_duplicates_with_time( + self, match_fields: Iterable[PgField], expressions: Iterable[PgExpression] + ) -> Iterable[Tuple]: + """ + If considering time when searching for duplicates, we need to grant some amount of leniency + in case timestamps are not exactly the same. + From the set of datasets that are active and have the correct product (candidates), + find all those whose extended timestamp range overlap (overlapping), + then group them by the other fields. + """ + fields = [] + for f in match_fields: + if f.name == "time": + time_field = f.expression_with_leniency + else: + fields.append(f.alchemy_expression) + + candidates_table = select( + DATASET.c.id, + time_field.label('time'), + *fields + ).select_from( + PostgresDbAPI._from_expression(DATASET, expressions, match_fields) + ).where( + and_(DATASET.c.archived == None, *(PostgresDbAPI._alchemify_expressions(expressions))) + ) + + t1 = candidates_table.alias("t1") + t2 = candidates_table.alias("t2") + + overlapping = select( + t1.c.id, + text("t1.time * t2.time as time_intersect"), + *fields + ).select_from( + t1.join( + t2, + and_(t1.c.time.overlaps(t2.c.time), t1.c.id != t2.c.id) + ) + ) + + final_query = select( + func.array_agg(func.distinct(overlapping.c.id)).label("ids"), + *fields, + text("(lower(time_intersect) at time zone 'UTC', upper(time_intersect) at time zone 'UTC') as time") + ).select_from( + overlapping + ).group_by( + *fields, text("time_intersect") + ).having( + func.count(overlapping.c.id) > 1 + ) + + return self._connection.execute(final_query) + def count_datasets(self, expressions): """ :type expressions: tuple[datacube.drivers.postgres._fields.PgExpression] diff --git a/datacube/drivers/postgres/_fields.py b/datacube/drivers/postgres/_fields.py index 4bf682562..5996517e4 100755 --- a/datacube/drivers/postgres/_fields.py +++ b/datacube/drivers/postgres/_fields.py @@ -15,6 +15,7 @@ from sqlalchemy.dialects import postgresql as postgres from sqlalchemy.dialects.postgresql import INT4RANGE from sqlalchemy.dialects.postgresql import NUMRANGE, TSTZRANGE +from sqlalchemy.dialects.postgresql import INTERVAL from sqlalchemy.sql import ColumnElement from datacube import utils @@ -193,7 +194,7 @@ def __init__(self, name, description, alchemy_column, indexed, offset=None, sele @property def alchemy_expression(self): - return self._alchemy_offset_value(self.offset, self.aggregation.pg_calc) + return self._alchemy_offset_value(self.offset, self.aggregation.pg_calc).label(self.name) def __eq__(self, value): """ @@ -322,7 +323,7 @@ def postgres_index_type(self): @property def alchemy_expression(self): - return self.value_to_alchemy((self.lower.alchemy_expression, self.greater.alchemy_expression)) + return self.value_to_alchemy((self.lower.alchemy_expression, self.greater.alchemy_expression)).label(self.name) def __eq__(self, value): """ @@ -431,6 +432,16 @@ def between(self, low, high): raise ValueError("Unknown comparison type for date range: " "expecting datetimes, got: (%r, %r)" % (low, high)) + @property + def expression_with_leniency(self): + return func.tstzrange( + self.lower.alchemy_expression - cast('500 milliseconds', INTERVAL), + self.greater.alchemy_expression + cast('500 milliseconds', INTERVAL), + # Inclusive on both sides. + '[]', + type_=TSTZRANGE, + ) + def _number_implies_year(v: Union[int, datetime]) -> datetime: """ diff --git a/datacube/index/memory/_datasets.py b/datacube/index/memory/_datasets.py index d294de191..d78ac7b9d 100755 --- a/datacube/index/memory/_datasets.py +++ b/datacube/index/memory/_datasets.py @@ -131,10 +131,11 @@ def search_product_duplicates(self, product: Product, *args: Union[str, Field] ) -> Iterable[Tuple[Tuple, Iterable[UUID]]]: - field_names: List[str] = [arg.name if isinstance(arg, Field) else arg for arg in args] - # Typing note: mypy cannot handle dynamically created namedtuples - GroupedVals = namedtuple('search_result', field_names) # type: ignore[misc] - + """ + Find dataset ids of a given product that have duplicates of the given set of field names. + Returns each set of those field values and the datasets that have them. + Note that this implementation does not account for slight timestamp discrepancies. + """ def to_field(f: Union[str, Field]) -> Field: if isinstance(f, str): f = product.metadata_type.dataset_fields[f] @@ -142,6 +143,8 @@ def to_field(f: Union[str, Field]) -> Field: return f fields = [to_field(f) for f in args] + # Typing note: mypy cannot handle dynamically created namedtuples + GroupedVals = namedtuple('search_result', list(f.name for f in fields)) # type: ignore[misc] def values(ds: Dataset) -> GroupedVals: vals = [] @@ -149,16 +152,17 @@ def values(ds: Dataset) -> GroupedVals: vals.append(field.extract(ds.metadata_doc)) # type: ignore[attr-defined] return GroupedVals(*vals) - dups: Dict[Tuple, List[UUID]] = {} + dups: Dict[Tuple, set[UUID]] = {} for ds in self.active_by_id.values(): if ds.product.name != product.name: continue vals = values(ds) if vals in dups: - dups[vals].append(ds.id) + dups[vals].add(ds.id) else: - dups[vals] = [ds.id] - return list(dups.items()) + dups[vals] = set([ds.id]) # avoid duplicate entries + # only return entries with more than one dataset + return list({k: v for k, v in dups.items() if len(v) > 1}) def can_update(self, dataset: Dataset, diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 46eb4dfae..f8153c2c5 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -269,15 +269,14 @@ def load_field(f: Union[str, fields.Field]) -> fields.Field: return f group_fields: List[fields.Field] = [load_field(f) for f in args] - result_type = namedtuple('search_result', list(f.name for f in group_fields)) # type: ignore - expressions = [product.metadata_type.dataset_fields.get('product') == product.name] with self._db_connection() as connection: for record in connection.get_duplicates(group_fields, expressions): - dataset_ids = set(record[0]) - grouped_fields = tuple(record[1:]) - yield result_type(*grouped_fields), dataset_ids + as_dict = dict(record) + if 'ids' in as_dict.keys(): + ids = as_dict.pop('ids') + yield namedtuple('search_result', as_dict.keys())(**as_dict), set(ids) def can_update(self, dataset, updates_allowed=None): """ diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 743bf279f..169cb6b2e 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -244,15 +244,14 @@ def load_field(f: Union[str, fields.Field]) -> fields.Field: return f group_fields: List[fields.Field] = [load_field(f) for f in args] - result_type = namedtuple('search_result', list(f.name for f in group_fields)) # type: ignore - expressions = [product.metadata_type.dataset_fields.get('product') == product.name] with self._db_connection() as connection: for record in connection.get_duplicates(group_fields, expressions): - dataset_ids = set(record[0]) - grouped_fields = tuple(record[1:]) - yield result_type(*grouped_fields), dataset_ids + as_dict = dict(record) + if "ids" in as_dict.keys(): + ids = as_dict.pop('ids') + yield namedtuple('search_result', as_dict.keys())(**as_dict), set(ids) def can_update(self, dataset, updates_allowed=None): """ diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index 11a1a911e..b95283e1c 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -668,3 +668,45 @@ def purge_cmd(index: Index, dry_run: bool, all_ds: bool, ids: List[str]): click.echo(f'{len(datasets_for_archive)} datasets not purged (dry run)') click.echo('Completed dataset purge.') + + +@dataset_cmd.command('find-duplicates', help="Search for duplicate indexed datasets") +@click.option('--product', '-p', 'product_names', + help=("Only search within product(s) specified with this option. " + "You can supply several by repeating this option with a new product name."), + multiple=True) +@click.argument('fields', nargs=-1) +@ui.pass_index() +def find_duplicates(index: Index, product_names, fields): + """ + Find dataset ids of two or more active datasets that have duplicate values in the specified fields. + If products are specified, search only within those products. Otherwise, search within any products that + have the fields. + """ + if not fields: + click.echo('Error: must provide field names to match on\n') + sys.exit(1) + + # if no products were specified, use whichever ones have the specified search fields + # if products were specified, check they all have the required fields + products_with_fields = list(index.products.get_with_fields(fields)) + if not products_with_fields: + click.echo(f'Error: no products found with fields {", ".join(fields)}\n') + sys.exit(1) + if not list(product_names): + products = products_with_fields + else: + products = [index.products.get_by_name(name) for name in product_names] + products_without_fields = set(products).difference(set(products_with_fields)) + if len(products_without_fields): + click.echo(f'Error: specified products {", ".join(p.name for p in products_without_fields)} ' + 'do not contain all required fields\n') + sys.exit(1) + + dupes = [] + for product in products: + dupes.extend(list(index.datasets.search_product_duplicates(product, *fields))) + if len(dupes): + print(dupes) + else: + click.echo('No potential duplicates found.') diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 921d29730..513eb1462 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,6 +7,7 @@ What's New v1.8.next ========= +- Add dataset cli tool ``find-duplicates`` to identify duplicate indexed datasets (:pull:`1517`) v1.8.17 (8th November 2023) =========================== diff --git a/integration_tests/index/test_memory_index.py b/integration_tests/index/test_memory_index.py index f5ed510a5..a1d9d8b88 100644 --- a/integration_tests/index/test_memory_index.py +++ b/integration_tests/index/test_memory_index.py @@ -201,9 +201,7 @@ def test_mem_ds_search_dups(mem_eo3_data): dc, ls8_id, wo_id = mem_eo3_data ls8_ds = dc.index.datasets.get(ls8_id) dup_results = dc.index.datasets.search_product_duplicates(ls8_ds.product, "cloud_cover", "dataset_maturity") - assert len(dup_results) == 1 - assert dup_results[0][0].cloud_cover == ls8_ds.metadata.cloud_cover - assert ls8_id in dup_results[0][1] + assert len(dup_results) == 0 def test_mem_ds_locations(mem_eo3_data): diff --git a/integration_tests/index/test_search_eo3.py b/integration_tests/index/test_search_eo3.py index d4cf1b32a..ba6934382 100644 --- a/integration_tests/index/test_search_eo3.py +++ b/integration_tests/index/test_search_eo3.py @@ -7,6 +7,7 @@ """ import datetime from typing import Any +from collections import namedtuple import pytest import yaml @@ -738,13 +739,14 @@ def test_find_duplicates_eo3(index, assert len(all_datasets) == 5 # First two ls8 datasets have the same path/row, last two have a different row. + dupe_fields = namedtuple('search_result', ['region_code', 'dataset_maturity']) expected_ls8_path_row_duplicates = [ ( - ('090086', 'final'), + dupe_fields('090086', 'final'), {ls8_eo3_dataset.id, ls8_eo3_dataset2.id} ), ( - ('101077', 'final'), + dupe_fields('101077', 'final'), {ls8_eo3_dataset3.id, ls8_eo3_dataset4.id} ), @@ -772,6 +774,30 @@ def test_find_duplicates_eo3(index, assert sat_res == [] +def test_find_duplicates_with_time(index, nrt_dataset, final_dataset, ls8_eo3_dataset): + index.datasets.add(nrt_dataset, with_lineage=False) + index.datasets.add(final_dataset, with_lineage=False) + index.datasets.get(nrt_dataset.id).is_active + index.datasets.get(final_dataset.id).is_active + + all_datasets = index.datasets.search_eager() + assert len(all_datasets) == 3 + + dupe_fields = namedtuple('search_result', ['region_code', 'time']) + expected_result = [ + ( + dupe_fields('090086', '("2023-04-30 23:50:33.884549","2023-04-30 23:50:34.884549")'), + {nrt_dataset.id, final_dataset.id} + ) + ] + res = sorted(index.datasets.search_product_duplicates( + nrt_dataset.product, + 'region_code', 'time', + )) + + assert res == expected_result + + def test_csv_search_via_cli_eo3(clirunner: Any, ls8_eo3_dataset: Dataset, ls8_eo3_dataset2: Dataset) -> None: diff --git a/integration_tests/test_cli_output.py b/integration_tests/test_cli_output.py index e11365d04..1c32b9e7a 100644 --- a/integration_tests/test_cli_output.py +++ b/integration_tests/test_cli_output.py @@ -83,6 +83,32 @@ def test_cli_dataset_subcommand(index, clirunner, for path in eo3_dataset_paths: result = clirunner(['dataset', 'add', "--ignore-lineage", path]) + runner = clirunner(['dataset', 'find-duplicates'], verbose_flag=False, expect_success=False) + assert "Error: must provide field names to match on" in runner.output + assert runner.exit_code == 1 + + runner = clirunner(['dataset', 'find-duplicates', 'region_code', 'fake_field'], + verbose_flag=False, expect_success=False) + assert "Error: no products found with fields region_code, fake_field" in runner.output + assert runner.exit_code == 1 + + runner = clirunner(['dataset', 'find-duplicates', 'region_code', 'landsat_scene_id', + '-p', 'ga_ls8c_ard_3', '-p', 'ga_ls_wo_3'], + verbose_flag=False, + expect_success=False) + assert "Error: specified products ga_ls_wo_3 do not contain all required fields" + assert runner.exit_code == 1 + + runner = clirunner(['dataset', 'find-duplicates', 'region_code', 'uri'], verbose_flag=False) + assert "No potential duplicates found." in runner.output + assert runner.exit_code == 0 + + runner = clirunner(['dataset', 'find-duplicates', 'region_code', 'dataset_maturity'], verbose_flag=False) + assert "No potential duplicates found." not in runner.output + assert "(region_code='090086', dataset_maturity='final')" in runner.output + assert "(region_code='101077', dataset_maturity='final')" in runner.output + assert runner.exit_code == 0 + runner = clirunner(['dataset', 'archive'], verbose_flag=False, expect_success=False) assert "Completed dataset archival." not in runner.output assert "Usage: [OPTIONS] [IDS]" in runner.output From b872aff6e06d7ae206597e45c1624e5ee61b757c Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 12 Dec 2023 14:10:30 +1100 Subject: [PATCH 095/153] Make solar_date() timezone aware. (#1521) --- datacube/api/query.py | 2 +- docs/about/whats_new.rst | 1 + tests/api/test_query.py | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index fe77c9f12..48248d37b 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -381,7 +381,7 @@ def solar_day(dataset: Dataset, longitude: Optional[float] = None) -> np.datetim :param longitude: If supplied correct timestamp for this longitude, rather than mid-point of the Dataset's footprint """ - utc = dataset.center_time + utc = dataset.center_time.astimezone(datetime.timezone.utc) if longitude is None: _lon = _ds_mid_longitude(dataset) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 513eb1462..51cba2f94 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,7 @@ What's New v1.8.next ========= - Add dataset cli tool ``find-duplicates`` to identify duplicate indexed datasets (:pull:`1517`) +- Make solar_day() timezone aware (:pull:`1521`) v1.8.17 (8th November 2023) =========================== diff --git a/tests/api/test_query.py b/tests/api/test_query.py index d7d8e0b54..0b5300baf 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -171,6 +171,13 @@ def test_solar_day(): assert 'Cannot compute solar_day: dataset is missing spatial info' in str(e.value) + # Test with Non-UTC timestamp in index. + ds = _s(center_time=parse_time('1997-05-22 22:07:44.2270250-7:00'), + metadata=_s(lon=Range(begin=-136.615, + end=-134.325))) + assert solar_day(ds) == np.datetime64('1997-05-22', 'D') + assert solar_day(ds, longitude=0) == np.datetime64('1997-05-23', 'D') + def test_solar_offset(): from datacube.utils.geometry import point From 6aaea5fcd0fc09b86c1a2019239e0bd555f54e3f Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:12:36 +1100 Subject: [PATCH 096/153] Warn if non-eo3 dataset has eo3 metadata type (#1523) * warn if non-eo3 dataset has eo3 metadata type * fix str contains * add test --------- Co-authored-by: Ariana Barzinpour --- datacube/index/hl.py | 13 +++++++++++++ docs/about/whats_new.rst | 1 + integration_tests/test_dataset_add.py | 26 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/datacube/index/hl.py b/datacube/index/hl.py index 2b5af66bc..d5ad1103d 100644 --- a/datacube/index/hl.py +++ b/datacube/index/hl.py @@ -5,6 +5,8 @@ """ High level indexing operations/utilities """ +import logging + import json import toolz from uuid import UUID @@ -17,6 +19,8 @@ from datacube.utils.changes import get_doc_changes from .eo3 import prep_eo3, is_doc_eo3, is_doc_geo # type: ignore[attr-defined] +_LOG = logging.getLogger(__name__) + class ProductRule: def __init__(self, product: Product, signature: Mapping[str, Any]): @@ -148,6 +152,13 @@ def dataset_resolver(index: AbstractIndex, skip_lineage: bool = False) -> Callable[[SimpleDocNav, str], DatasetOrError]: match_product = product_matcher(product_matching_rules) + def check_intended_eo3(ds: SimpleDocNav, product: Product) -> None: + # warn if it looks like dataset was meant to be eo3 but is not + if not is_doc_eo3(ds.doc) and ("eo3" in product.metadata_type.name): + _LOG.warning(f"Dataset {ds.id} has a product with an eo3 metadata type, " + "but the dataset definition does not include the $schema field " + "and so will not be recognised as an eo3 dataset.") + def resolve_no_lineage(ds: SimpleDocNav, uri: str) -> DatasetOrError: doc = ds.doc_without_lineage_sources try: @@ -155,6 +166,7 @@ def resolve_no_lineage(ds: SimpleDocNav, uri: str) -> DatasetOrError: except BadMatch as e: return None, e + check_intended_eo3(ds, product) return Dataset(product, doc, uris=[uri], sources={}), None def resolve(main_ds_doc: SimpleDocNav, uri: str) -> DatasetOrError: @@ -222,6 +234,7 @@ def resolve_ds(ds: SimpleDocNav, else: product = match_product(doc) + check_intended_eo3(ds, product) return with_cache(Dataset(product, doc, uris=uris, sources=sources), ds.id, cache) try: return remap_lineage_doc(main_ds, resolve_ds, cache={}), None diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 51cba2f94..e8684c563 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -9,6 +9,7 @@ v1.8.next ========= - Add dataset cli tool ``find-duplicates`` to identify duplicate indexed datasets (:pull:`1517`) - Make solar_day() timezone aware (:pull:`1521`) +- Warn if non-eo3 dataset has eo3 metadata type (:pull:`1523`) v1.8.17 (8th November 2023) =========================== diff --git a/integration_tests/test_dataset_add.py b/integration_tests/test_dataset_add.py index 9ee58deb8..c41669540 100644 --- a/integration_tests/test_dataset_add.py +++ b/integration_tests/test_dataset_add.py @@ -7,6 +7,7 @@ import pytest import toolz import yaml +import logging from datacube.index import Index from datacube.index.hl import Doc2Dataset @@ -15,6 +16,8 @@ from datacube.utils import SimpleDocNav from datacube.scripts.dataset import _resolve_uri +logger = logging.getLogger(__name__) + def check_skip_lineage_test(clirunner, index): ds = SimpleDocNav(gen_dataset_test_dag(11, force_tree=True)) @@ -206,6 +209,29 @@ def test_dataset_add_no_id(dataset_add_configs, index_empty, clirunner): assert _err == 'No id defined in dataset doc' +@pytest.mark.parametrize('datacube_env_name', ('datacube', )) +def test_dataset_eo3_no_schema(dataset_add_configs, index_empty, clirunner, caplog): + p = dataset_add_configs + index = index_empty + ds = load_dataset_definition(p.datasets_eo3).doc + + clirunner(['metadata', 'add', p.metadata]) + clirunner(['product', 'add', p.products]) + + # no warnings if eo3 dataset has $schema + doc2ds = Doc2Dataset(index) + doc2ds(ds, 'file:///something') + warnings = [record for record in caplog.records if record.levelno == logging.WARNING] + assert len(warnings) == 0 + + # warn if eo3 metadata type but no $schema + del ds["$schema"] + doc2ds(ds, 'file:///something') + warnings = [record for record in caplog.records if record.levelno == logging.WARNING] + assert len(warnings) == 1 + assert "will not be recognised as an eo3 dataset" in warnings[0].msg + + # Current formulation of this test relies on non-EO3 test data @pytest.mark.parametrize('datacube_env_name', ('datacube', )) def test_dataset_add(dataset_add_configs, index_empty, clirunner): From e927ff25d3d3d3dd0d8a75f81de430d2ac363b77 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 3 Jan 2024 06:23:38 +0000 Subject: [PATCH 097/153] update pandas version in docker image and update query docstring accordingly --- datacube/api/query.py | 4 ++-- docker/constraints.in | 2 +- docker/constraints.txt | 17 +++-------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index 48248d37b..a81701ab6 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Storage Query and Access API module @@ -69,7 +69,7 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc Use by accessing :attr:`search_terms`: >>> query.search_terms['time'] # doctest: +NORMALIZE_WHITESPACE - Range(begin=datetime.datetime(2001, 1, 1, 0, 0, tzinfo=), \ + Range(begin=datetime.datetime(2001, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), \ end=datetime.datetime(2002, 1, 1, 23, 59, 59, 999999, tzinfo=tzutc())) By passing in an ``index``, the search parameters will be validated as existing on the ``product``. diff --git a/docker/constraints.in b/docker/constraints.in index f5c24dc46..95758d9d1 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -37,7 +37,7 @@ sphinx_autodoc_typehints sphinx_rtd_theme sqlalchemy<2.0 toolz -xarray>=0.9 +xarray>=2023.9.0 # Previous pins were to very old versions # pytest Py3.10 requires >6.2.5 diff --git a/docker/constraints.txt b/docker/constraints.txt index d6ccf8e2a..1750eab67 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile --strip-extras constraints.in @@ -115,10 +115,6 @@ docutils==0.18.1 # sphinx # sphinx-click # sphinx-rtd-theme -exceptiongroup==1.1.2 - # via - # hypothesis - # pytest fiona==1.9.1 # via -r constraints.in fonttools==4.38.0 @@ -232,7 +228,7 @@ packaging==23.0 # setuptools-scm # sphinx # xarray -pandas==1.5.3 +pandas==2.1.1 # via xarray partd==1.3.0 # via dask @@ -339,8 +335,6 @@ rpds-py==0.9.2 # referencing ruamel-yaml==0.17.21 # via -r constraints.in -ruamel-yaml-clib==0.2.7 - # via ruamel-yaml s3transfer==0.6.0 # via boto3 secretstorage==3.3.3 @@ -401,11 +395,6 @@ toml==0.10.2 # via # -r constraints.in # responses -tomli==2.0.1 - # via - # coverage - # pytest - # setuptools-scm toolz==0.12.0 # via # -r constraints.in @@ -443,7 +432,7 @@ wrapt==1.11.2 # via # astroid # deprecat -xarray==2023.2.0 +xarray==2023.12.0 # via -r constraints.in xmltodict==0.13.0 # via moto From 4f1e636f8969e553d12835c73a6fedb28844f065 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 06:29:11 +0000 Subject: [PATCH 098/153] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- datacube/__init__.py | 2 +- datacube/__main__.py | 2 +- datacube/api/__init__.py | 2 +- datacube/api/core.py | 2 +- datacube/api/grid_workflow.py | 2 +- datacube/config.py | 2 +- datacube/drivers/__init__.py | 2 +- datacube/drivers/_tools.py | 2 +- datacube/drivers/_types.py | 2 +- datacube/drivers/datasource.py | 2 +- datacube/drivers/driver_cache.py | 2 +- datacube/drivers/indexes.py | 2 +- datacube/drivers/netcdf/__init__.py | 2 +- datacube/drivers/netcdf/_safestrings.py | 2 +- datacube/drivers/netcdf/_write.py | 2 +- datacube/drivers/netcdf/driver.py | 2 +- datacube/drivers/netcdf/writer.py | 2 +- datacube/drivers/postgis/__init__.py | 2 +- datacube/drivers/postgis/_api.py | 2 +- datacube/drivers/postgis/_connections.py | 2 +- datacube/drivers/postgis/_core.py | 2 +- datacube/drivers/postgis/_fields.py | 2 +- datacube/drivers/postgis/_schema.py | 2 +- datacube/drivers/postgis/_spatial.py | 2 +- datacube/drivers/postgis/sql.py | 2 +- datacube/drivers/postgres/__init__.py | 2 +- datacube/drivers/postgres/_api.py | 2 +- datacube/drivers/postgres/_connections.py | 2 +- datacube/drivers/postgres/_core.py | 2 +- datacube/drivers/postgres/_dynamic.py | 2 +- datacube/drivers/postgres/_fields.py | 2 +- datacube/drivers/postgres/_schema.py | 2 +- datacube/drivers/postgres/sql.py | 2 +- datacube/drivers/readers.py | 2 +- datacube/drivers/rio/__init__.py | 2 +- datacube/drivers/rio/_reader.py | 2 +- datacube/drivers/writers.py | 2 +- datacube/execution/__init__.py | 2 +- datacube/execution/worker.py | 2 +- datacube/executor.py | 2 +- datacube/helpers.py | 2 +- datacube/index/__init__.py | 2 +- datacube/index/_api.py | 2 +- datacube/index/abstract.py | 2 +- datacube/index/eo3.py | 2 +- datacube/index/exceptions.py | 2 +- datacube/index/fields.py | 2 +- datacube/index/hl.py | 2 +- datacube/index/memory/__init__.py | 2 +- datacube/index/memory/_datasets.py | 2 +- datacube/index/memory/_fields.py | 2 +- datacube/index/memory/_metadata_types.py | 2 +- datacube/index/memory/_products.py | 2 +- datacube/index/memory/_users.py | 2 +- datacube/index/memory/index.py | 2 +- datacube/index/null/__init__.py | 2 +- datacube/index/null/_datasets.py | 2 +- datacube/index/null/_metadata_types.py | 2 +- datacube/index/null/_products.py | 2 +- datacube/index/null/_users.py | 2 +- datacube/index/null/index.py | 2 +- datacube/index/postgis/__init__.py | 2 +- datacube/index/postgis/_datasets.py | 2 +- datacube/index/postgis/_metadata_types.py | 2 +- datacube/index/postgis/_products.py | 2 +- datacube/index/postgis/_transaction.py | 2 +- datacube/index/postgis/_users.py | 2 +- datacube/index/postgis/index.py | 2 +- datacube/index/postgres/__init__.py | 2 +- datacube/index/postgres/_datasets.py | 2 +- datacube/index/postgres/_metadata_types.py | 2 +- datacube/index/postgres/_products.py | 2 +- datacube/index/postgres/_transaction.py | 2 +- datacube/index/postgres/_users.py | 2 +- datacube/index/postgres/index.py | 2 +- datacube/model/__init__.py | 2 +- datacube/model/_base.py | 2 +- datacube/model/eo3.py | 2 +- datacube/model/fields.py | 2 +- datacube/model/model.py | 2 +- datacube/model/properties.py | 2 +- datacube/model/utils.py | 2 +- datacube/scripts/__init__.py | 2 +- datacube/scripts/cli_app.py | 2 +- datacube/scripts/dataset.py | 2 +- datacube/scripts/ingest.py | 2 +- datacube/scripts/metadata.py | 2 +- datacube/scripts/product.py | 2 +- datacube/scripts/search_tool.py | 2 +- datacube/scripts/system.py | 2 +- datacube/scripts/user.py | 2 +- datacube/storage/__init__.py | 2 +- datacube/storage/_base.py | 2 +- datacube/storage/_hdf5.py | 2 +- datacube/storage/_load.py | 2 +- datacube/storage/_read.py | 2 +- datacube/storage/_rio.py | 2 +- datacube/testutils/__init__.py | 2 +- datacube/testutils/geom.py | 2 +- datacube/testutils/io.py | 2 +- datacube/testutils/iodriver.py | 2 +- datacube/testutils/threads.py | 2 +- datacube/ui/__init__.py | 2 +- datacube/ui/click.py | 2 +- datacube/ui/common.py | 2 +- datacube/ui/expression.py | 2 +- datacube/ui/task_app.py | 2 +- datacube/utils/__init__.py | 2 +- datacube/utils/_misc.py | 2 +- datacube/utils/aws/__init__.py | 2 +- datacube/utils/changes.py | 2 +- datacube/utils/cog.py | 2 +- datacube/utils/dask.py | 2 +- datacube/utils/dates.py | 2 +- datacube/utils/documents.py | 2 +- datacube/utils/generic.py | 2 +- datacube/utils/geometry/__init__.py | 2 +- datacube/utils/geometry/_base.py | 2 +- datacube/utils/geometry/_warp.py | 2 +- datacube/utils/geometry/gbox.py | 2 +- datacube/utils/geometry/tools.py | 2 +- datacube/utils/io.py | 2 +- datacube/utils/masking.py | 2 +- datacube/utils/math.py | 2 +- datacube/utils/py.py | 2 +- datacube/utils/rio/__init__.py | 2 +- datacube/utils/rio/_rio.py | 2 +- datacube/utils/serialise.py | 2 +- datacube/utils/uris.py | 2 +- datacube/utils/xarray_geoextensions.py | 2 +- datacube/virtual/__init__.py | 2 +- datacube/virtual/catalog.py | 2 +- datacube/virtual/expr.py | 2 +- datacube/virtual/impl.py | 2 +- datacube/virtual/transformations.py | 2 +- datacube/virtual/utils.py | 2 +- docs/click_utils.py | 2 +- docs/conf.py | 2 +- examples/io_plugin/dcio_example/__init__.py | 2 +- examples/io_plugin/dcio_example/pickles.py | 2 +- examples/io_plugin/dcio_example/xarray_3d.py | 2 +- examples/io_plugin/dcio_example/zeros.py | 2 +- examples/io_plugin/setup.py | 2 +- integration_tests/__init__.py | 2 +- integration_tests/conftest.py | 2 +- integration_tests/data_utils.py | 2 +- integration_tests/index/__init__.py | 2 +- integration_tests/index/search_utils.py | 2 +- integration_tests/index/test_config_docs.py | 2 +- integration_tests/index/test_index_cloning.py | 2 +- integration_tests/index/test_index_data.py | 2 +- integration_tests/index/test_memory_index.py | 2 +- integration_tests/index/test_null_index.py | 2 +- integration_tests/index/test_pluggable_indexes.py | 2 +- integration_tests/index/test_postgis_index.py | 2 +- integration_tests/index/test_search_eo3.py | 2 +- integration_tests/index/test_search_legacy.py | 2 +- integration_tests/index/test_update_columns.py | 2 +- integration_tests/test_3d.py | 2 +- integration_tests/test_cli_output.py | 2 +- integration_tests/test_config_tool.py | 2 +- integration_tests/test_dataset_add.py | 2 +- integration_tests/test_double_ingestion.py | 2 +- integration_tests/test_end_to_end.py | 2 +- integration_tests/test_environments.py | 2 +- integration_tests/test_full_ingestion.py | 2 +- integration_tests/test_index_datasets_search.py | 2 +- integration_tests/test_index_out_of_bound.py | 2 +- integration_tests/test_model.py | 2 +- integration_tests/test_validate_ingestion.py | 2 +- integration_tests/utils.py | 2 +- tests/__init__.py | 2 +- tests/api/__init__.py | 2 +- tests/api/test_core.py | 2 +- tests/api/test_grid_workflow.py | 2 +- tests/api/test_masking.py | 2 +- tests/api/test_query.py | 2 +- tests/api/test_virtual.py | 2 +- tests/conftest.py | 2 +- tests/drivers/fail_drivers/dc_tests_io/__init__.py | 2 +- tests/drivers/fail_drivers/dc_tests_io/dummy.py | 2 +- tests/drivers/fail_drivers/setup.py | 2 +- tests/drivers/test_rio_reader.py | 2 +- tests/index/__init__.py | 2 +- tests/index/test_api_index_dataset.py | 2 +- tests/index/test_fields.py | 2 +- tests/index/test_hl_index.py | 2 +- tests/index/test_postgis_fields.py | 2 +- tests/index/test_query.py | 2 +- tests/index/test_validate_dataset_type.py | 2 +- tests/scripts/__init__.py | 2 +- tests/scripts/test_search_tool.py | 2 +- tests/storage/test_base.py | 2 +- tests/storage/test_netcdfwriter.py | 2 +- tests/storage/test_storage.py | 2 +- tests/storage/test_storage_load.py | 2 +- tests/storage/test_storage_read.py | 2 +- tests/test_3d.py | 2 +- tests/test_concurrent_executor.py | 2 +- tests/test_config.py | 2 +- tests/test_driver.py | 2 +- tests/test_dynamic_db_passwd.py | 2 +- tests/test_eo3.py | 2 +- tests/test_gbox_ops.py | 2 +- tests/test_geometry.py | 2 +- tests/test_load_data.py | 2 +- tests/test_metadata_fields.py | 2 +- tests/test_model.py | 2 +- tests/test_testutils.py | 2 +- tests/test_utils_aws.py | 2 +- tests/test_utils_changes.py | 2 +- tests/test_utils_cog.py | 2 +- tests/test_utils_dask.py | 2 +- tests/test_utils_dates.py | 2 +- tests/test_utils_docs.py | 2 +- tests/test_utils_generic.py | 2 +- tests/test_utils_other.py | 2 +- tests/test_utils_rio.py | 2 +- tests/test_warp.py | 2 +- tests/test_xarray_extension.py | 2 +- tests/ui/__init__.py | 2 +- tests/ui/test_common.py | 2 +- tests/ui/test_expression_parsing.py | 2 +- tests/ui/test_task_app.py | 2 +- 224 files changed, 224 insertions(+), 224 deletions(-) diff --git a/datacube/__init__.py b/datacube/__init__.py index 342980430..a29faa45e 100644 --- a/datacube/__init__.py +++ b/datacube/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Datacube diff --git a/datacube/__main__.py b/datacube/__main__.py index 543af0ede..13352473d 100644 --- a/datacube/__main__.py +++ b/datacube/__main__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 if __name__ == "__main__": from .config import auto_config diff --git a/datacube/api/__init__.py b/datacube/api/__init__.py index b7676523e..6e8932038 100644 --- a/datacube/api/__init__.py +++ b/datacube/api/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for the Storage and Access Query API diff --git a/datacube/api/core.py b/datacube/api/core.py index a07d45008..002ae690b 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import uuid import collections.abc diff --git a/datacube/api/grid_workflow.py b/datacube/api/grid_workflow.py index 4634d2c85..2f1011670 100644 --- a/datacube/api/grid_workflow.py +++ b/datacube/api/grid_workflow.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import xarray diff --git a/datacube/config.py b/datacube/config.py index 6c901a8f3..f2b08aba4 100755 --- a/datacube/config.py +++ b/datacube/config.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ User configuration. diff --git a/datacube/drivers/__init__.py b/datacube/drivers/__init__.py index 0ab9b5178..4242c46c9 100644 --- a/datacube/drivers/__init__.py +++ b/datacube/drivers/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This module implements a simple plugin manager for storage and index drivers. diff --git a/datacube/drivers/_tools.py b/datacube/drivers/_tools.py index 39ec17306..9f9c25655 100644 --- a/datacube/drivers/_tools.py +++ b/datacube/drivers/_tools.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from threading import Lock from typing import Any diff --git a/datacube/drivers/_types.py b/datacube/drivers/_types.py index 7fdafadd1..f40d5fcc9 100644 --- a/datacube/drivers/_types.py +++ b/datacube/drivers/_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Defines abstract types for IO drivers. """ diff --git a/datacube/drivers/datasource.py b/datacube/drivers/datasource.py index 91e64a036..e01faceb4 100644 --- a/datacube/drivers/datasource.py +++ b/datacube/drivers/datasource.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Defines abstract types for IO reader drivers. """ diff --git a/datacube/drivers/driver_cache.py b/datacube/drivers/driver_cache.py index 6c40a9462..2361e215a 100644 --- a/datacube/drivers/driver_cache.py +++ b/datacube/drivers/driver_cache.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from typing import Dict, Any, Tuple, Iterable diff --git a/datacube/drivers/indexes.py b/datacube/drivers/indexes.py index 1cda1d6e8..7bfb09abf 100644 --- a/datacube/drivers/indexes.py +++ b/datacube/drivers/indexes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List, Optional diff --git a/datacube/drivers/netcdf/__init__.py b/datacube/drivers/netcdf/__init__.py index 9b0321f12..dc33e2ed1 100644 --- a/datacube/drivers/netcdf/__init__.py +++ b/datacube/drivers/netcdf/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from ._write import write_dataset_to_netcdf, create_netcdf_storage_unit from . import writer as netcdf_writer diff --git a/datacube/drivers/netcdf/_safestrings.py b/datacube/drivers/netcdf/_safestrings.py index ab58334ef..a99f36bbd 100644 --- a/datacube/drivers/netcdf/_safestrings.py +++ b/datacube/drivers/netcdf/_safestrings.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Provides `SafeStringsDataset`, a replacement netCDF4.Dataset class which works diff --git a/datacube/drivers/netcdf/_write.py b/datacube/drivers/netcdf/_write.py index 702689cd9..3dc40c1c0 100644 --- a/datacube/drivers/netcdf/_write.py +++ b/datacube/drivers/netcdf/_write.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from pathlib import Path import logging diff --git a/datacube/drivers/netcdf/driver.py b/datacube/drivers/netcdf/driver.py index d68b04ab8..9ec5853ba 100644 --- a/datacube/drivers/netcdf/driver.py +++ b/datacube/drivers/netcdf/driver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from urllib.parse import urlsplit diff --git a/datacube/drivers/netcdf/writer.py b/datacube/drivers/netcdf/writer.py index 5beadbe48..23f749f2e 100644 --- a/datacube/drivers/netcdf/writer.py +++ b/datacube/drivers/netcdf/writer.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Create netCDF4 Storage Units and write data to them diff --git a/datacube/drivers/postgis/__init__.py b/datacube/drivers/postgis/__init__.py index a43833f90..00128c529 100644 --- a/datacube/drivers/postgis/__init__.py +++ b/datacube/drivers/postgis/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Lower-level database access. diff --git a/datacube/drivers/postgis/_api.py b/datacube/drivers/postgis/_api.py index 03e93276c..a15b51339 100644 --- a/datacube/drivers/postgis/_api.py +++ b/datacube/drivers/postgis/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgis/_connections.py b/datacube/drivers/postgis/_connections.py index 51810987a..474799579 100755 --- a/datacube/drivers/postgis/_connections.py +++ b/datacube/drivers/postgis/_connections.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgis/_core.py b/datacube/drivers/postgis/_core.py index 1c40aa353..15beda313 100644 --- a/datacube/drivers/postgis/_core.py +++ b/datacube/drivers/postgis/_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core SQL schema settings. diff --git a/datacube/drivers/postgis/_fields.py b/datacube/drivers/postgis/_fields.py index 23cceadf6..760e608c3 100755 --- a/datacube/drivers/postgis/_fields.py +++ b/datacube/drivers/postgis/_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Build and index fields within documents. diff --git a/datacube/drivers/postgis/_schema.py b/datacube/drivers/postgis/_schema.py index ed3643630..ce2c5b676 100644 --- a/datacube/drivers/postgis/_schema.py +++ b/datacube/drivers/postgis/_schema.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tables for indexing the datasets which were ingested into the AGDC. diff --git a/datacube/drivers/postgis/_spatial.py b/datacube/drivers/postgis/_spatial.py index 61395dc92..90b20e89c 100644 --- a/datacube/drivers/postgis/_spatial.py +++ b/datacube/drivers/postgis/_spatial.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tracking spatial indexes diff --git a/datacube/drivers/postgis/sql.py b/datacube/drivers/postgis/sql.py index 0d34e96e9..5d050ad8f 100644 --- a/datacube/drivers/postgis/sql.py +++ b/datacube/drivers/postgis/sql.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Custom types for postgres & sqlalchemy diff --git a/datacube/drivers/postgres/__init__.py b/datacube/drivers/postgres/__init__.py index 6f3e32134..7ba8061e4 100644 --- a/datacube/drivers/postgres/__init__.py +++ b/datacube/drivers/postgres/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Lower-level database access. diff --git a/datacube/drivers/postgres/_api.py b/datacube/drivers/postgres/_api.py index ac4fe9450..7b616bb5e 100644 --- a/datacube/drivers/postgres/_api.py +++ b/datacube/drivers/postgres/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgres/_connections.py b/datacube/drivers/postgres/_connections.py index 003617db3..987513d7c 100755 --- a/datacube/drivers/postgres/_connections.py +++ b/datacube/drivers/postgres/_connections.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # We often have one-arg-per column, so these checks aren't so useful. diff --git a/datacube/drivers/postgres/_core.py b/datacube/drivers/postgres/_core.py index 52a075148..31e562a5d 100644 --- a/datacube/drivers/postgres/_core.py +++ b/datacube/drivers/postgres/_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core SQL schema settings. diff --git a/datacube/drivers/postgres/_dynamic.py b/datacube/drivers/postgres/_dynamic.py index 8c0765bad..959dbceea 100644 --- a/datacube/drivers/postgres/_dynamic.py +++ b/datacube/drivers/postgres/_dynamic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Methods for managing dynamic dataset field indexes and views. diff --git a/datacube/drivers/postgres/_fields.py b/datacube/drivers/postgres/_fields.py index 5996517e4..41c33e185 100755 --- a/datacube/drivers/postgres/_fields.py +++ b/datacube/drivers/postgres/_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Build and index fields within documents. diff --git a/datacube/drivers/postgres/_schema.py b/datacube/drivers/postgres/_schema.py index d555bb6b4..85ab8ad81 100644 --- a/datacube/drivers/postgres/_schema.py +++ b/datacube/drivers/postgres/_schema.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tables for indexing the datasets which were ingested into the AGDC. diff --git a/datacube/drivers/postgres/sql.py b/datacube/drivers/postgres/sql.py index 6d9679ac8..e476fdb02 100644 --- a/datacube/drivers/postgres/sql.py +++ b/datacube/drivers/postgres/sql.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Custom types for postgres & sqlalchemy diff --git a/datacube/drivers/readers.py b/datacube/drivers/readers.py index 230e63b4c..55fce9818 100644 --- a/datacube/drivers/readers.py +++ b/datacube/drivers/readers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List, Optional, Callable from .driver_cache import load_drivers diff --git a/datacube/drivers/rio/__init__.py b/datacube/drivers/rio/__init__.py index f41dba762..7b1432ace 100644 --- a/datacube/drivers/rio/__init__.py +++ b/datacube/drivers/rio/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ RasterIO based driver """ diff --git a/datacube/drivers/rio/_reader.py b/datacube/drivers/rio/_reader.py index 6950c06fc..ab3b666c9 100644 --- a/datacube/drivers/rio/_reader.py +++ b/datacube/drivers/rio/_reader.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ reader """ diff --git a/datacube/drivers/writers.py b/datacube/drivers/writers.py index cc652c409..a6a99c77e 100644 --- a/datacube/drivers/writers.py +++ b/datacube/drivers/writers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import List diff --git a/datacube/execution/__init__.py b/datacube/execution/__init__.py index 45970a2a5..8318282ae 100644 --- a/datacube/execution/__init__.py +++ b/datacube/execution/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/execution/worker.py b/datacube/execution/worker.py index 8ca9743a1..ed4a115d5 100644 --- a/datacube/execution/worker.py +++ b/datacube/execution/worker.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This app launches workers for distributed work loads diff --git a/datacube/executor.py b/datacube/executor.py index ee2bc7299..5a6603b28 100644 --- a/datacube/executor.py +++ b/datacube/executor.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # # type: ignore diff --git a/datacube/helpers.py b/datacube/helpers.py index 6f397fadf..e2e17329b 100644 --- a/datacube/helpers.py +++ b/datacube/helpers.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Useful functions for Datacube users diff --git a/datacube/index/__init__.py b/datacube/index/__init__.py index c6a25f301..b53f7aa83 100644 --- a/datacube/index/__init__.py +++ b/datacube/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for interfacing with the index/database. diff --git a/datacube/index/_api.py b/datacube/index/_api.py index 2ffb67919..3aa8e31e2 100644 --- a/datacube/index/_api.py +++ b/datacube/index/_api.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Access methods for indexing datasets & products. diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index c715404af..87b671cfe 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import logging diff --git a/datacube/index/eo3.py b/datacube/index/eo3.py index a5fcc3928..e25ceb676 100644 --- a/datacube/index/eo3.py +++ b/datacube/index/eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 # # type: ignore diff --git a/datacube/index/exceptions.py b/datacube/index/exceptions.py index c12344c49..31a75f1d4 100644 --- a/datacube/index/exceptions.py +++ b/datacube/index/exceptions.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/fields.py b/datacube/index/fields.py index 85d3e59d8..c67a1115d 100644 --- a/datacube/index/fields.py +++ b/datacube/index/fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common datatypes for DB drivers. diff --git a/datacube/index/hl.py b/datacube/index/hl.py index d5ad1103d..ea8f789e8 100644 --- a/datacube/index/hl.py +++ b/datacube/index/hl.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ High level indexing operations/utilities diff --git a/datacube/index/memory/__init__.py b/datacube/index/memory/__init__.py index 7bd83106e..a642d3c36 100644 --- a/datacube/index/memory/__init__.py +++ b/datacube/index/memory/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/datacube/index/memory/_datasets.py b/datacube/index/memory/_datasets.py index d78ac7b9d..8c20c45f3 100755 --- a/datacube/index/memory/_datasets.py +++ b/datacube/index/memory/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import logging diff --git a/datacube/index/memory/_fields.py b/datacube/index/memory/_fields.py index ba60fc7f3..bc61e5683 100644 --- a/datacube/index/memory/_fields.py +++ b/datacube/index/memory/_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Any, Mapping, MutableMapping from datacube.model.fields import SimpleField, Field, get_dataset_fields as generic_get_dataset_fields diff --git a/datacube/index/memory/_metadata_types.py b/datacube/index/memory/_metadata_types.py index 8f22ecced..5893252cb 100644 --- a/datacube/index/memory/_metadata_types.py +++ b/datacube/index/memory/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from copy import deepcopy diff --git a/datacube/index/memory/_products.py b/datacube/index/memory/_products.py index 75b953920..9f63f9544 100644 --- a/datacube/index/memory/_products.py +++ b/datacube/index/memory/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/memory/_users.py b/datacube/index/memory/_users.py index 579829af5..26c81253c 100644 --- a/datacube/index/memory/_users.py +++ b/datacube/index/memory/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/memory/index.py b/datacube/index/memory/index.py index f2baaec80..f4436ad86 100644 --- a/datacube/index/memory/index.py +++ b/datacube/index/memory/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from threading import Lock diff --git a/datacube/index/null/__init__.py b/datacube/index/null/__init__.py index 7bd83106e..a642d3c36 100644 --- a/datacube/index/null/__init__.py +++ b/datacube/index/null/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/datacube/index/null/_datasets.py b/datacube/index/null/_datasets.py index 07317efae..0fb24879b 100755 --- a/datacube/index/null/_datasets.py +++ b/datacube/index/null/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.index.abstract import AbstractDatasetResource, DSID diff --git a/datacube/index/null/_metadata_types.py b/datacube/index/null/_metadata_types.py index 808c18ee1..8c8fb213c 100644 --- a/datacube/index/null/_metadata_types.py +++ b/datacube/index/null/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.index.abstract import AbstractMetadataTypeResource diff --git a/datacube/index/null/_products.py b/datacube/index/null/_products.py index 7b3f1645e..30ab28c91 100644 --- a/datacube/index/null/_products.py +++ b/datacube/index/null/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/null/_users.py b/datacube/index/null/_users.py index e058c5158..e76b4704e 100644 --- a/datacube/index/null/_users.py +++ b/datacube/index/null/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/null/index.py b/datacube/index/null/index.py index 7921f9f11..dff0a4712 100644 --- a/datacube/index/null/index.py +++ b/datacube/index/null/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgis/__init__.py b/datacube/index/postgis/__init__.py index 45970a2a5..8318282ae 100644 --- a/datacube/index/postgis/__init__.py +++ b/datacube/index/postgis/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index f8153c2c5..502146601 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ API for dataset indexing, access and search. diff --git a/datacube/index/postgis/_metadata_types.py b/datacube/index/postgis/_metadata_types.py index 7e9593552..cd81c238f 100644 --- a/datacube/index/postgis/_metadata_types.py +++ b/datacube/index/postgis/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from time import monotonic diff --git a/datacube/index/postgis/_products.py b/datacube/index/postgis/_products.py index 7f50a218b..a94129017 100644 --- a/datacube/index/postgis/_products.py +++ b/datacube/index/postgis/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgis/_transaction.py b/datacube/index/postgis/_transaction.py index ac48a64f8..d3bf058ce 100644 --- a/datacube/index/postgis/_transaction.py +++ b/datacube/index/postgis/_transaction.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/datacube/index/postgis/_users.py b/datacube/index/postgis/_users.py index ca2c6ec3a..a10fa275d 100644 --- a/datacube/index/postgis/_users.py +++ b/datacube/index/postgis/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/postgis/index.py b/datacube/index/postgis/index.py index 2b6b1764b..cf082f64c 100644 --- a/datacube/index/postgis/index.py +++ b/datacube/index/postgis/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from contextlib import contextmanager diff --git a/datacube/index/postgres/__init__.py b/datacube/index/postgres/__init__.py index 45970a2a5..8318282ae 100644 --- a/datacube/index/postgres/__init__.py +++ b/datacube/index/postgres/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 169cb6b2e..4ad2b0f76 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ API for dataset indexing, access and search. diff --git a/datacube/index/postgres/_metadata_types.py b/datacube/index/postgres/_metadata_types.py index de77c23c1..d56d5d516 100644 --- a/datacube/index/postgres/_metadata_types.py +++ b/datacube/index/postgres/_metadata_types.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgres/_products.py b/datacube/index/postgres/_products.py index e3b7fd773..6b9e24013 100644 --- a/datacube/index/postgres/_products.py +++ b/datacube/index/postgres/_products.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/index/postgres/_transaction.py b/datacube/index/postgres/_transaction.py index 6ff35d942..254850fb0 100644 --- a/datacube/index/postgres/_transaction.py +++ b/datacube/index/postgres/_transaction.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/datacube/index/postgres/_users.py b/datacube/index/postgres/_users.py index eeb76c4fc..95e1172ff 100644 --- a/datacube/index/postgres/_users.py +++ b/datacube/index/postgres/_users.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Iterable, Optional, Tuple from datacube.index.abstract import AbstractUserResource diff --git a/datacube/index/postgres/index.py b/datacube/index/postgres/index.py index 49ebdd362..d2d93636c 100644 --- a/datacube/index/postgres/index.py +++ b/datacube/index/postgres/index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from contextlib import contextmanager diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 219d2c2d6..1880b3345 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Core classes used across modules. diff --git a/datacube/model/_base.py b/datacube/model/_base.py index 3a004f763..103765866 100644 --- a/datacube/model/_base.py +++ b/datacube/model/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import namedtuple diff --git a/datacube/model/eo3.py b/datacube/model/eo3.py index 8897ea933..c276746a8 100644 --- a/datacube/model/eo3.py +++ b/datacube/model/eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube.utils.documents import InvalidDocException diff --git a/datacube/model/fields.py b/datacube/model/fields.py index 48826eb01..2b4ab44a7 100644 --- a/datacube/model/fields.py +++ b/datacube/model/fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """Non-db specific implementation of metadata search fields. diff --git a/datacube/model/model.py b/datacube/model/model.py index 7634393a4..d801ba853 100644 --- a/datacube/model/model.py +++ b/datacube/model/model.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from pathlib import Path from typing import Dict, List, Optional, Tuple, Union diff --git a/datacube/model/properties.py b/datacube/model/properties.py index 01bec1bdf..c5f9c8882 100644 --- a/datacube/model/properties.py +++ b/datacube/model/properties.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import collections.abc import warnings diff --git a/datacube/model/utils.py b/datacube/model/utils.py index 6f0b66190..17d352229 100644 --- a/datacube/model/utils.py +++ b/datacube/model/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import os diff --git a/datacube/scripts/__init__.py b/datacube/scripts/__init__.py index 45970a2a5..8318282ae 100644 --- a/datacube/scripts/__init__.py +++ b/datacube/scripts/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/datacube/scripts/cli_app.py b/datacube/scripts/cli_app.py index 6ab3901b2..c4a024ba4 100644 --- a/datacube/scripts/cli_app.py +++ b/datacube/scripts/cli_app.py @@ -2,7 +2,7 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Datacube command-line interface diff --git a/datacube/scripts/dataset.py b/datacube/scripts/dataset.py index b95283e1c..d9052d1e1 100644 --- a/datacube/scripts/dataset.py +++ b/datacube/scripts/dataset.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import datetime diff --git a/datacube/scripts/ingest.py b/datacube/scripts/ingest.py index a96e3bcb9..e338a4f42 100644 --- a/datacube/scripts/ingest.py +++ b/datacube/scripts/ingest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import time import logging diff --git a/datacube/scripts/metadata.py b/datacube/scripts/metadata.py index 3b3f735c1..ddd5df034 100644 --- a/datacube/scripts/metadata.py +++ b/datacube/scripts/metadata.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import json import logging diff --git a/datacube/scripts/product.py b/datacube/scripts/product.py index ef9095fb3..ea8c68232 100644 --- a/datacube/scripts/product.py +++ b/datacube/scripts/product.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import json diff --git a/datacube/scripts/search_tool.py b/datacube/scripts/search_tool.py index 25acc2204..d20c17b5d 100755 --- a/datacube/scripts/search_tool.py +++ b/datacube/scripts/search_tool.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Query datasets. diff --git a/datacube/scripts/system.py b/datacube/scripts/system.py index f0a07ef45..aee4faf1b 100644 --- a/datacube/scripts/system.py +++ b/datacube/scripts/system.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging diff --git a/datacube/scripts/user.py b/datacube/scripts/user.py index 6f4f08418..f05a7b608 100644 --- a/datacube/scripts/user.py +++ b/datacube/scripts/user.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import click diff --git a/datacube/storage/__init__.py b/datacube/storage/__init__.py index 64388c59c..2872494d4 100644 --- a/datacube/storage/__init__.py +++ b/datacube/storage/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Modules for creating and accessing Data Store Units diff --git a/datacube/storage/_base.py b/datacube/storage/_base.py index 48508528b..27540d573 100644 --- a/datacube/storage/_base.py +++ b/datacube/storage/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Optional, Dict, Any, Tuple, Callable from urllib.parse import urlparse diff --git a/datacube/storage/_hdf5.py b/datacube/storage/_hdf5.py index 2b9a28983..43dae4da5 100644 --- a/datacube/storage/_hdf5.py +++ b/datacube/storage/_hdf5.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from threading import RLock HDF5_LOCK = RLock() diff --git a/datacube/storage/_load.py b/datacube/storage/_load.py index 4ee9a53c2..2c411aab0 100644 --- a/datacube/storage/_load.py +++ b/datacube/storage/_load.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Important functions are: diff --git a/datacube/storage/_read.py b/datacube/storage/_read.py index fb5b03b8f..af1517e92 100644 --- a/datacube/storage/_read.py +++ b/datacube/storage/_read.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Dataset -> Raster """ diff --git a/datacube/storage/_rio.py b/datacube/storage/_rio.py index d777874a9..e18a8839c 100644 --- a/datacube/storage/_rio.py +++ b/datacube/storage/_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Driver implementation for Rasterio based reader. diff --git a/datacube/testutils/__init__.py b/datacube/testutils/__init__.py index 0ffbdfd22..29637521b 100644 --- a/datacube/testutils/__init__.py +++ b/datacube/testutils/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Useful methods for tests (particularly: reading/writing and checking files) diff --git a/datacube/testutils/geom.py b/datacube/testutils/geom.py index 5e1ae2d99..f1928c3af 100644 --- a/datacube/testutils/geom.py +++ b/datacube/testutils/geom.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np from affine import Affine diff --git a/datacube/testutils/io.py b/datacube/testutils/io.py index 9ae46ca37..d46c51611 100644 --- a/datacube/testutils/io.py +++ b/datacube/testutils/io.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import toolz diff --git a/datacube/testutils/iodriver.py b/datacube/testutils/iodriver.py index 87b204673..ddc8ced47 100644 --- a/datacube/testutils/iodriver.py +++ b/datacube/testutils/iodriver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Reader driver construction for tests """ diff --git a/datacube/testutils/threads.py b/datacube/testutils/threads.py index 22bb22aeb..ab37711f5 100644 --- a/datacube/testutils/threads.py +++ b/datacube/testutils/threads.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ threads related stuff """ diff --git a/datacube/ui/__init__.py b/datacube/ui/__init__.py index 08fd367b1..c9bf934ba 100644 --- a/datacube/ui/__init__.py +++ b/datacube/ui/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ User Interface Utilities diff --git a/datacube/ui/click.py b/datacube/ui/click.py index 25169edf8..49bf88773 100644 --- a/datacube/ui/click.py +++ b/datacube/ui/click.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common functions for click-based cli scripts. diff --git a/datacube/ui/common.py b/datacube/ui/common.py index 8df1bff73..b7552d792 100644 --- a/datacube/ui/common.py +++ b/datacube/ui/common.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common methods for UI code. diff --git a/datacube/ui/expression.py b/datacube/ui/expression.py index 8ef1fdf73..f7253612c 100644 --- a/datacube/ui/expression.py +++ b/datacube/ui/expression.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Search expression parsing for command line applications. diff --git a/datacube/ui/task_app.py b/datacube/ui/task_app.py index 2077b1d3e..386d9a284 100644 --- a/datacube/ui/task_app.py +++ b/datacube/ui/task_app.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import os diff --git a/datacube/utils/__init__.py b/datacube/utils/__init__.py index 571ca321d..2ab5b5156 100644 --- a/datacube/utils/__init__.py +++ b/datacube/utils/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utility functions diff --git a/datacube/utils/_misc.py b/datacube/utils/_misc.py index 65ca45155..c4fc098b2 100644 --- a/datacube/utils/_misc.py +++ b/datacube/utils/_misc.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utility functions diff --git a/datacube/utils/aws/__init__.py b/datacube/utils/aws/__init__.py index a5061f7a7..10722c677 100644 --- a/datacube/utils/aws/__init__.py +++ b/datacube/utils/aws/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Helper methods for working with AWS diff --git a/datacube/utils/changes.py b/datacube/utils/changes.py index 5e1cb0341..34eb9806e 100644 --- a/datacube/utils/changes.py +++ b/datacube/utils/changes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Validation of document/dictionary changes. diff --git a/datacube/utils/cog.py b/datacube/utils/cog.py index cb424fd98..c3611eff2 100644 --- a/datacube/utils/cog.py +++ b/datacube/utils/cog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import warnings import toolz # type: ignore[import] diff --git a/datacube/utils/dask.py b/datacube/utils/dask.py index e7affe0d1..60ece9f54 100644 --- a/datacube/utils/dask.py +++ b/datacube/utils/dask.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Dask Distributed Tools diff --git a/datacube/utils/dates.py b/datacube/utils/dates.py index 293bbf38c..c3979ada9 100644 --- a/datacube/utils/dates.py +++ b/datacube/utils/dates.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Date and time utility functions diff --git a/datacube/utils/documents.py b/datacube/utils/documents.py index 3d7d6fb38..c15fbf33e 100644 --- a/datacube/utils/documents.py +++ b/datacube/utils/documents.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Functions for working with YAML documents and configurations diff --git a/datacube/utils/generic.py b/datacube/utils/generic.py index 0bef2fd97..0146fe315 100644 --- a/datacube/utils/generic.py +++ b/datacube/utils/generic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import itertools import threading diff --git a/datacube/utils/geometry/__init__.py b/datacube/utils/geometry/__init__.py index 60e5c8cdc..7c1f716c1 100644 --- a/datacube/utils/geometry/__init__.py +++ b/datacube/utils/geometry/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Geometric shapes and operations on them """ diff --git a/datacube/utils/geometry/_base.py b/datacube/utils/geometry/_base.py index a0cc2a4c9..b115b2889 100644 --- a/datacube/utils/geometry/_base.py +++ b/datacube/utils/geometry/_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import functools import itertools diff --git a/datacube/utils/geometry/_warp.py b/datacube/utils/geometry/_warp.py index c2e2e6e67..307f09fad 100644 --- a/datacube/utils/geometry/_warp.py +++ b/datacube/utils/geometry/_warp.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Union, Optional import rasterio.warp # type: ignore[import] diff --git a/datacube/utils/geometry/gbox.py b/datacube/utils/geometry/gbox.py index 723d9a7e9..ddedb8e3e 100644 --- a/datacube/utils/geometry/gbox.py +++ b/datacube/utils/geometry/gbox.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Geometric operations on GeoBox class """ diff --git a/datacube/utils/geometry/tools.py b/datacube/utils/geometry/tools.py index 96bf31fd1..c4c7018ee 100644 --- a/datacube/utils/geometry/tools.py +++ b/datacube/utils/geometry/tools.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import collections.abc diff --git a/datacube/utils/io.py b/datacube/utils/io.py index 983ffeeb2..78d2da1fe 100644 --- a/datacube/utils/io.py +++ b/datacube/utils/io.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os from pathlib import Path diff --git a/datacube/utils/masking.py b/datacube/utils/masking.py index 5a6c8467b..a3446ae67 100644 --- a/datacube/utils/masking.py +++ b/datacube/utils/masking.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tools for masking data based on a bit-mask variable with attached definition. diff --git a/datacube/utils/math.py b/datacube/utils/math.py index 002d18ee5..8181bd2c3 100644 --- a/datacube/utils/math.py +++ b/datacube/utils/math.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Tuple, Union, Optional, Any, cast from math import ceil, fmod diff --git a/datacube/utils/py.py b/datacube/utils/py.py index f869c8052..33e570bce 100644 --- a/datacube/utils/py.py +++ b/datacube/utils/py.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import importlib import logging diff --git a/datacube/utils/rio/__init__.py b/datacube/utils/rio/__init__.py index 93128d0c2..1fc438239 100644 --- a/datacube/utils/rio/__init__.py +++ b/datacube/utils/rio/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ This will move into IO driver eventually. diff --git a/datacube/utils/rio/_rio.py b/datacube/utils/rio/_rio.py index 04b2e9c7f..c2aba0a44 100644 --- a/datacube/utils/rio/_rio.py +++ b/datacube/utils/rio/_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ rasterio environment management tools """ diff --git a/datacube/utils/serialise.py b/datacube/utils/serialise.py index 8306bc038..2001e46d5 100644 --- a/datacube/utils/serialise.py +++ b/datacube/utils/serialise.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Serialise function used in YAML output diff --git a/datacube/utils/uris.py b/datacube/utils/uris.py index 7644e954a..97d8140ce 100644 --- a/datacube/utils/uris.py +++ b/datacube/utils/uris.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os diff --git a/datacube/utils/xarray_geoextensions.py b/datacube/utils/xarray_geoextensions.py index 4f41981a8..708286f01 100644 --- a/datacube/utils/xarray_geoextensions.py +++ b/datacube/utils/xarray_geoextensions.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Add geometric extensions to :class:`xarray.Dataset` and :class:`xarray.DataArray` for use diff --git a/datacube/virtual/__init__.py b/datacube/virtual/__init__.py index b3de02351..51a1e1933 100644 --- a/datacube/virtual/__init__.py +++ b/datacube/virtual/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Mapping, Any, cast import copy diff --git a/datacube/virtual/catalog.py b/datacube/virtual/catalog.py index e908367ee..2847f0b7a 100644 --- a/datacube/virtual/catalog.py +++ b/datacube/virtual/catalog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Catalog of virtual products. diff --git a/datacube/virtual/expr.py b/datacube/virtual/expr.py index e19894ac7..3044b07c3 100644 --- a/datacube/virtual/expr.py +++ b/datacube/virtual/expr.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import lark import numpy diff --git a/datacube/virtual/impl.py b/datacube/virtual/impl.py index 3ce82f431..7388177cc 100644 --- a/datacube/virtual/impl.py +++ b/datacube/virtual/impl.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Implementation of virtual products. Provides an interface for the products in the datacube diff --git a/datacube/virtual/transformations.py b/datacube/virtual/transformations.py index 2aeaf64b6..6c4047841 100644 --- a/datacube/virtual/transformations.py +++ b/datacube/virtual/transformations.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from typing import Optional, Collection import warnings diff --git a/datacube/virtual/utils.py b/datacube/virtual/utils.py index c26a7bde8..0f59795a4 100644 --- a/datacube/virtual/utils.py +++ b/datacube/virtual/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Utilities to facilitate virtual product implementation. """ diff --git a/docs/click_utils.py b/docs/click_utils.py index fca8435ce..ee722544c 100644 --- a/docs/click_utils.py +++ b/docs/click_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from docutils.nodes import literal_block, section, title, make_id from sphinx.domains import Domain diff --git a/docs/conf.py b/docs/conf.py index 3b63a7cdb..04e8f71e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import os import sys diff --git a/examples/io_plugin/dcio_example/__init__.py b/examples/io_plugin/dcio_example/__init__.py index 45970a2a5..8318282ae 100644 --- a/examples/io_plugin/dcio_example/__init__.py +++ b/examples/io_plugin/dcio_example/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/examples/io_plugin/dcio_example/pickles.py b/examples/io_plugin/dcio_example/pickles.py index f209b12e3..ec1a2b82b 100644 --- a/examples/io_plugin/dcio_example/pickles.py +++ b/examples/io_plugin/dcio_example/pickles.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Example reader plugin """ diff --git a/examples/io_plugin/dcio_example/xarray_3d.py b/examples/io_plugin/dcio_example/xarray_3d.py index 2f997cde4..76ac2c61b 100644 --- a/examples/io_plugin/dcio_example/xarray_3d.py +++ b/examples/io_plugin/dcio_example/xarray_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ xarray 3D driver plugin for 3D support testing """ diff --git a/examples/io_plugin/dcio_example/zeros.py b/examples/io_plugin/dcio_example/zeros.py index 71b302c13..2e221c0b7 100644 --- a/examples/io_plugin/dcio_example/zeros.py +++ b/examples/io_plugin/dcio_example/zeros.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Sample plugin "reads" zeros each time every time diff --git a/examples/io_plugin/setup.py b/examples/io_plugin/setup.py index 402dc1e06..698eaf6b9 100644 --- a/examples/io_plugin/setup.py +++ b/examples/io_plugin/setup.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from setuptools import setup, find_packages diff --git a/integration_tests/__init__.py b/integration_tests/__init__.py index 7bd83106e..a642d3c36 100644 --- a/integration_tests/__init__.py +++ b/integration_tests/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 8f253f014..3c0e7e0a7 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Common methods for index integration tests. diff --git a/integration_tests/data_utils.py b/integration_tests/data_utils.py index cf8a6d02b..d92c9daaa 100644 --- a/integration_tests/data_utils.py +++ b/integration_tests/data_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ The start of some general purpose utilities for generating test data. diff --git a/integration_tests/index/__init__.py b/integration_tests/index/__init__.py index 7bd83106e..a642d3c36 100644 --- a/integration_tests/index/__init__.py +++ b/integration_tests/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/search_utils.py b/integration_tests/index/search_utils.py index fc0e4bf77..66fe06b5f 100644 --- a/integration_tests/index/search_utils.py +++ b/integration_tests/index/search_utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import csv import io diff --git a/integration_tests/index/test_config_docs.py b/integration_tests/index/test_config_docs.py index 02077cebc..a5483dc1c 100644 --- a/integration_tests/index/test_config_docs.py +++ b/integration_tests/index/test_config_docs.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_index_cloning.py b/integration_tests/index/test_index_cloning.py index cacb5de4d..a53adaefe 100644 --- a/integration_tests/index/test_index_cloning.py +++ b/integration_tests/index/test_index_cloning.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index 9e4f836e3..40ed4fe05 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test database methods. diff --git a/integration_tests/index/test_memory_index.py b/integration_tests/index/test_memory_index.py index a1d9d8b88..a19ae12c3 100644 --- a/integration_tests/index/test_memory_index.py +++ b/integration_tests/index/test_memory_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime diff --git a/integration_tests/index/test_null_index.py b/integration_tests/index/test_null_index.py index 2cf9ebd03..dd248724b 100644 --- a/integration_tests/index/test_null_index.py +++ b/integration_tests/index/test_null_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest.mock import MagicMock diff --git a/integration_tests/index/test_pluggable_indexes.py b/integration_tests/index/test_pluggable_indexes.py index 7d2b711d8..06f623199 100644 --- a/integration_tests/index/test_pluggable_indexes.py +++ b/integration_tests/index/test_pluggable_indexes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/index/test_postgis_index.py b/integration_tests/index/test_postgis_index.py index 1a2d24b9e..4a6d07c8e 100644 --- a/integration_tests/index/test_postgis_index.py +++ b/integration_tests/index/test_postgis_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/index/test_search_eo3.py b/integration_tests/index/test_search_eo3.py index ba6934382..942bc9517 100644 --- a/integration_tests/index/test_search_eo3.py +++ b/integration_tests/index/test_search_eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_search_legacy.py b/integration_tests/index/test_search_legacy.py index 5eebc9963..abac793a9 100644 --- a/integration_tests/index/test_search_legacy.py +++ b/integration_tests/index/test_search_legacy.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/index/test_update_columns.py b/integration_tests/index/test_update_columns.py index 2b94f599a..92f84a3d0 100644 --- a/integration_tests/index/test_update_columns.py +++ b/integration_tests/index/test_update_columns.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test creation of added/updated columns during diff --git a/integration_tests/test_3d.py b/integration_tests/test_3d.py index 13abb9236..1f14f0db7 100644 --- a/integration_tests/test_3d.py +++ b/integration_tests/test_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging from copy import deepcopy diff --git a/integration_tests/test_cli_output.py b/integration_tests/test_cli_output.py index 1c32b9e7a..ba5312a19 100644 --- a/integration_tests/test_cli_output.py +++ b/integration_tests/test_cli_output.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/integration_tests/test_config_tool.py b/integration_tests/test_config_tool.py index 264254d24..81ffdc0a5 100644 --- a/integration_tests/test_config_tool.py +++ b/integration_tests/test_config_tool.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/integration_tests/test_dataset_add.py b/integration_tests/test_dataset_add.py index c41669540..3bef05845 100644 --- a/integration_tests/test_dataset_add.py +++ b/integration_tests/test_dataset_add.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import math diff --git a/integration_tests/test_double_ingestion.py b/integration_tests/test_double_ingestion.py index 39485c940..918b5bcc6 100644 --- a/integration_tests/test_double_ingestion.py +++ b/integration_tests/test_double_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import netCDF4 diff --git a/integration_tests/test_end_to_end.py b/integration_tests/test_end_to_end.py index 7df414833..79157125c 100644 --- a/integration_tests/test_end_to_end.py +++ b/integration_tests/test_end_to_end.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import shutil from pathlib import Path diff --git a/integration_tests/test_environments.py b/integration_tests/test_environments.py index 78bb9a589..42e957300 100644 --- a/integration_tests/test_environments.py +++ b/integration_tests/test_environments.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_full_ingestion.py b/integration_tests/test_full_ingestion.py index eeb728a94..a4e3332ff 100644 --- a/integration_tests/test_full_ingestion.py +++ b/integration_tests/test_full_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import hashlib import warnings diff --git a/integration_tests/test_index_datasets_search.py b/integration_tests/test_index_datasets_search.py index 50b6a213f..df572d2f0 100644 --- a/integration_tests/test_index_datasets_search.py +++ b/integration_tests/test_index_datasets_search.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from pathlib import PurePosixPath diff --git a/integration_tests/test_index_out_of_bound.py b/integration_tests/test_index_out_of_bound.py index 7054830d0..641662d3b 100644 --- a/integration_tests/test_index_out_of_bound.py +++ b/integration_tests/test_index_out_of_bound.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_model.py b/integration_tests/test_model.py index 189150fdf..69366f49e 100644 --- a/integration_tests/test_model.py +++ b/integration_tests/test_model.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/test_validate_ingestion.py b/integration_tests/test_validate_ingestion.py index 68358fced..83cec3a78 100644 --- a/integration_tests/test_validate_ingestion.py +++ b/integration_tests/test_validate_ingestion.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/integration_tests/utils.py b/integration_tests/utils.py index c29b126c0..e15bcb700 100644 --- a/integration_tests/utils.py +++ b/integration_tests/utils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import logging import os diff --git a/tests/__init__.py b/tests/__init__.py index 45970a2a5..8318282ae 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/api/__init__.py b/tests/api/__init__.py index 45970a2a5..8318282ae 100644 --- a/tests/api/__init__.py +++ b/tests/api/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/api/test_core.py b/tests/api/test_core.py index 2bada262c..38b272b44 100644 --- a/tests/api/test_core.py +++ b/tests/api/test_core.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import xarray as xr import numpy as np diff --git a/tests/api/test_grid_workflow.py b/tests/api/test_grid_workflow.py index 5924af0ad..cad0f0a7a 100644 --- a/tests/api/test_grid_workflow.py +++ b/tests/api/test_grid_workflow.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import numpy diff --git a/tests/api/test_masking.py b/tests/api/test_masking.py index 63f9299ad..378689ccb 100644 --- a/tests/api/test_masking.py +++ b/tests/api/test_masking.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import yaml import pytest diff --git a/tests/api/test_query.py b/tests/api/test_query.py index 0b5300baf..a2ac72276 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime import pandas diff --git a/tests/api/test_virtual.py b/tests/api/test_virtual.py index 97b13d237..03206172a 100644 --- a/tests/api/test_virtual.py +++ b/tests/api/test_virtual.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import OrderedDict from datetime import datetime diff --git a/tests/conftest.py b/tests/conftest.py index e9a60532c..57a2088af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ py.test configuration fixtures diff --git a/tests/drivers/fail_drivers/dc_tests_io/__init__.py b/tests/drivers/fail_drivers/dc_tests_io/__init__.py index 45970a2a5..8318282ae 100644 --- a/tests/drivers/fail_drivers/dc_tests_io/__init__.py +++ b/tests/drivers/fail_drivers/dc_tests_io/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/drivers/fail_drivers/dc_tests_io/dummy.py b/tests/drivers/fail_drivers/dc_tests_io/dummy.py index 2d3143fd4..7d3befd9e 100644 --- a/tests/drivers/fail_drivers/dc_tests_io/dummy.py +++ b/tests/drivers/fail_drivers/dc_tests_io/dummy.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/drivers/fail_drivers/setup.py b/tests/drivers/fail_drivers/setup.py index ed71cb127..60be7a42d 100644 --- a/tests/drivers/fail_drivers/setup.py +++ b/tests/drivers/fail_drivers/setup.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from setuptools import setup, find_packages diff --git a/tests/drivers/test_rio_reader.py b/tests/drivers/test_rio_reader.py index 8f2a7a57d..c27a2b0e0 100644 --- a/tests/drivers/test_rio_reader.py +++ b/tests/drivers/test_rio_reader.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tests for new RIO reader driver """ diff --git a/tests/index/__init__.py b/tests/index/__init__.py index 7bd83106e..a642d3c36 100644 --- a/tests/index/__init__.py +++ b/tests/index/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_api_index_dataset.py b/tests/index/test_api_index_dataset.py index 6c86f9add..63f68405c 100644 --- a/tests/index/test_api_index_dataset.py +++ b/tests/index/test_api_index_dataset.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime from collections import namedtuple diff --git a/tests/index/test_fields.py b/tests/index/test_fields.py index 3f26ac33a..b43284cac 100644 --- a/tests/index/test_fields.py +++ b/tests/index/test_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_hl_index.py b/tests/index/test_hl_index.py index a6251dca7..840257696 100644 --- a/tests/index/test_hl_index.py +++ b/tests/index/test_hl_index.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest diff --git a/tests/index/test_postgis_fields.py b/tests/index/test_postgis_fields.py index cfa606b91..e826d785f 100644 --- a/tests/index/test_postgis_fields.py +++ b/tests/index/test_postgis_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import datetime diff --git a/tests/index/test_query.py b/tests/index/test_query.py index 477d6ed2d..88f2324da 100644 --- a/tests/index/test_query.py +++ b/tests/index/test_query.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/index/test_validate_dataset_type.py b/tests/index/test_validate_dataset_type.py index 565ad6dce..39da10b04 100644 --- a/tests/index/test_validate_dataset_type.py +++ b/tests/index/test_validate_dataset_type.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/scripts/__init__.py b/tests/scripts/__init__.py index 7bd83106e..a642d3c36 100644 --- a/tests/scripts/__init__.py +++ b/tests/scripts/__init__.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/scripts/test_search_tool.py b/tests/scripts/test_search_tool.py index 57bf4ffeb..5edc7dc3d 100644 --- a/tests/scripts/test_search_tool.py +++ b/tests/scripts/test_search_tool.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/storage/test_base.py b/tests/storage/test_base.py index 823c71b2e..c5f483910 100644 --- a/tests/storage/test_base.py +++ b/tests/storage/test_base.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.storage import BandInfo diff --git a/tests/storage/test_netcdfwriter.py b/tests/storage/test_netcdfwriter.py index 46788e9c4..bd3b684b2 100644 --- a/tests/storage/test_netcdfwriter.py +++ b/tests/storage/test_netcdfwriter.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import netCDF4 import numpy diff --git a/tests/storage/test_storage.py b/tests/storage/test_storage.py index b784f8764..7085a24e3 100644 --- a/tests/storage/test_storage.py +++ b/tests/storage/test_storage.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from contextlib import contextmanager diff --git a/tests/storage/test_storage_load.py b/tests/storage/test_storage_load.py index 396003dbc..781560d9c 100644 --- a/tests/storage/test_storage_load.py +++ b/tests/storage/test_storage_load.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test New IO driver loading """ diff --git a/tests/storage/test_storage_read.py b/tests/storage/test_storage_read.py index 8beb60172..8251481ae 100644 --- a/tests/storage/test_storage_read.py +++ b/tests/storage/test_storage_read.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import numpy as np diff --git a/tests/test_3d.py b/tests/test_3d.py index 06df9763d..072aa8d9d 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from collections import OrderedDict diff --git a/tests/test_concurrent_executor.py b/tests/test_concurrent_executor.py index f11212da4..bbab3e50b 100644 --- a/tests/test_concurrent_executor.py +++ b/tests/test_concurrent_executor.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Tests for MultiprocessingExecutor diff --git a/tests/test_config.py b/tests/test_config.py index 0d134e532..5cb83b8a0 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/test_driver.py b/tests/test_driver.py index 3c9331d68..6e13ddcb7 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import yaml diff --git a/tests/test_dynamic_db_passwd.py b/tests/test_dynamic_db_passwd.py index 17b7b9640..cc2e502c7 100644 --- a/tests/test_dynamic_db_passwd.py +++ b/tests/test_dynamic_db_passwd.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from sqlalchemy.exc import OperationalError diff --git a/tests/test_eo3.py b/tests/test_eo3.py index 44cb5e0da..e56e7470a 100644 --- a/tests/test_eo3.py +++ b/tests/test_eo3.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import pytest diff --git a/tests/test_gbox_ops.py b/tests/test_gbox_ops.py index f063be196..4d8c92aa2 100644 --- a/tests/test_gbox_ops.py +++ b/tests/test_gbox_ops.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from affine import Affine import numpy as np diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 5e63ba0f8..5c5721b93 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import math import numpy as np diff --git a/tests/test_load_data.py b/tests/test_load_data.py index e0f4f2cdd..f9bf98d28 100644 --- a/tests/test_load_data.py +++ b/tests/test_load_data.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datacube import Datacube from datacube.api.query import query_group_by diff --git a/tests/test_metadata_fields.py b/tests/test_metadata_fields.py index 20b9ad174..03a4d17c6 100644 --- a/tests/test_metadata_fields.py +++ b/tests/test_metadata_fields.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import yaml import datetime diff --git a/tests/test_model.py b/tests/test_model.py index da5ae78c4..1c9603028 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import numpy diff --git a/tests/test_testutils.py b/tests/test_testutils.py index a45661ee4..f625f3062 100644 --- a/tests/test_testutils.py +++ b/tests/test_testutils.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.testutils.threads import FakeThreadPoolExecutor diff --git a/tests/test_utils_aws.py b/tests/test_utils_aws.py index a86cdc296..b7e287aba 100644 --- a/tests/test_utils_aws.py +++ b/tests/test_utils_aws.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest import mock diff --git a/tests/test_utils_changes.py b/tests/test_utils_changes.py index 9f5108a09..5483b5d99 100644 --- a/tests/test_utils_changes.py +++ b/tests/test_utils_changes.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from datacube.utils.changes import ( diff --git a/tests/test_utils_cog.py b/tests/test_utils_cog.py index 363ae4212..d73e33fb6 100644 --- a/tests/test_utils_cog.py +++ b/tests/test_utils_cog.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from pathlib import Path diff --git a/tests/test_utils_dask.py b/tests/test_utils_dask.py index c7b2d2d29..c0faefb42 100644 --- a/tests/test_utils_dask.py +++ b/tests/test_utils_dask.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import moto diff --git a/tests/test_utils_dates.py b/tests/test_utils_dates.py index b7547f864..c45cd90c6 100644 --- a/tests/test_utils_dates.py +++ b/tests/test_utils_dates.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np import pytest diff --git a/tests/test_utils_docs.py b/tests/test_utils_docs.py index 0341abbc9..7ea7eb93d 100644 --- a/tests/test_utils_docs.py +++ b/tests/test_utils_docs.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test utility functions from :module:`datacube.utils` diff --git a/tests/test_utils_generic.py b/tests/test_utils_generic.py index 15392bd0b..ecddb5706 100644 --- a/tests/test_utils_generic.py +++ b/tests/test_utils_generic.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from queue import Queue from datacube.utils.generic import ( diff --git a/tests/test_utils_other.py b/tests/test_utils_other.py index 5e383b212..60ca28fae 100644 --- a/tests/test_utils_other.py +++ b/tests/test_utils_other.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Test utility functions from :module:`datacube.utils` diff --git a/tests/test_utils_rio.py b/tests/test_utils_rio.py index e834ac2fe..62b9455e6 100644 --- a/tests/test_utils_rio.py +++ b/tests/test_utils_rio.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest from unittest import mock diff --git a/tests/test_warp.py b/tests/test_warp.py index 6903702e3..9bd6b743c 100644 --- a/tests/test_warp.py +++ b/tests/test_warp.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import numpy as np from affine import Affine diff --git a/tests/test_xarray_extension.py b/tests/test_xarray_extension.py index b1aeef6a5..76bd86dc5 100644 --- a/tests/test_xarray_extension.py +++ b/tests/test_xarray_extension.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest import xarray as xr diff --git a/tests/ui/__init__.py b/tests/ui/__init__.py index 45970a2a5..8318282ae 100644 --- a/tests/ui/__init__.py +++ b/tests/ui/__init__.py @@ -1,4 +1,4 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/tests/ui/test_common.py b/tests/ui/test_common.py index 8c8352002..3abbcac98 100644 --- a/tests/ui/test_common.py +++ b/tests/ui/test_common.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module diff --git a/tests/ui/test_expression_parsing.py b/tests/ui/test_expression_parsing.py index 3f30748c2..214afcf40 100644 --- a/tests/ui/test_expression_parsing.py +++ b/tests/ui/test_expression_parsing.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 from datetime import datetime from dateutil.tz import tzutc diff --git a/tests/ui/test_task_app.py b/tests/ui/test_task_app.py index 6d5a4a754..4a0f5408a 100644 --- a/tests/ui/test_task_app.py +++ b/tests/ui/test_task_app.py @@ -1,6 +1,6 @@ # This file is part of the Open Data Cube, see https://opendatacube.org for more information # -# Copyright (c) 2015-2023 ODC Contributors +# Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 """ Module From a418c82a22ce9e435cc7018b0a64b335c7d01fc0 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 3 Jan 2024 06:37:58 +0000 Subject: [PATCH 099/153] update whats_new --- docs/about/whats_new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index e8684c563..6168d88be 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -10,6 +10,8 @@ v1.8.next - Add dataset cli tool ``find-duplicates`` to identify duplicate indexed datasets (:pull:`1517`) - Make solar_day() timezone aware (:pull:`1521`) - Warn if non-eo3 dataset has eo3 metadata type (:pull:`1523`) +- Update pandas version in docker image to be consistent with conda environment and default to stdlib + timezone instead of pytz when converting timestamps; automatically update copyright years (:pull:`1527`) v1.8.17 (8th November 2023) =========================== From 01f03c2ed4a543e8c27d9b6843ad4f1ddb27413b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 4 Jan 2024 00:50:33 +0000 Subject: [PATCH 100/153] update wordlist --- wordlist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wordlist.txt b/wordlist.txt index a4ebca390..10a01d780 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -375,6 +375,7 @@ pyproj pyspellcheck pytest PythonAPI +pytz QGIS RasterDatasetDataSource RasterIO @@ -432,6 +433,7 @@ STAC stac stacker stacspec +stdlib str subcommands sudo From b48304f49bfb891ecc0739294cfbb3fa03ff366b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 4 Jan 2024 01:32:22 +0000 Subject: [PATCH 101/153] add pandas to constraints.in, fix docstring --- datacube/api/core.py | 2 +- docker/constraints.in | 3 ++- docker/constraints.txt | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/datacube/api/core.py b/datacube/api/core.py index 002ae690b..8622c3fb4 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -191,7 +191,7 @@ def _list_measurements(self): def load(self, product=None, measurements=None, output_crs=None, resolution=None, resampling=None, skip_broken_datasets=False, dask_chunks=None, like=None, fuse_func=None, align=None, datasets=None, dataset_predicate=None, progress_cbk=None, patch_url=None, **query): - """ + r""" Load data as an ``xarray.Dataset`` object. Each measurement will be a data variable in the :class:`xarray.Dataset`. diff --git a/docker/constraints.in b/docker/constraints.in index 95758d9d1..b25b22227 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -21,6 +21,7 @@ matplotlib moto netcdf4>=1.5.8 numpy>=1.22.2 +pandas>=2.0 psycopg2 pycodestyle pylint @@ -37,7 +38,7 @@ sphinx_autodoc_typehints sphinx_rtd_theme sqlalchemy<2.0 toolz -xarray>=2023.9.0 +xarray>=0.9 # Previous pins were to very old versions # pytest Py3.10 requires >6.2.5 diff --git a/docker/constraints.txt b/docker/constraints.txt index 1750eab67..39f1261d0 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -228,8 +228,10 @@ packaging==23.0 # setuptools-scm # sphinx # xarray -pandas==2.1.1 - # via xarray +pandas==2.1.4 + # via + # -r constraints.in + # xarray partd==1.3.0 # via dask pendulum==2.1.2 @@ -411,6 +413,8 @@ typing-extensions==4.4.0 # via # pygeoif # setuptools-scm +tzdata==2023.4 + # via pandas urllib3==1.26.14 # via # botocore From 47f0aca621fd2334298181f0647febc337943f49 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Fri, 5 Jan 2024 16:16:56 +1100 Subject: [PATCH 102/153] GHA: Update dockerhub credential passing mechanism. (#1528) * GHA: Update dockerhub credential passing mechanism. * Add dockerhub to word list. --- .github/workflows/main.yml | 4 ++-- docs/about/whats_new.rst | 1 + wordlist.txt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d24d86ddd..277d5e7fb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,8 +72,8 @@ jobs: && github.ref == 'refs/heads/develop' uses: docker/login-action@v3 with: - username: ${{ env.DOCKER_USER }} - password: ${{ secrets.GADOCKERSVC_PASSWORD }} + username: ${{ secrets.DOCKERHUBUSER }} + password: ${{ secrets.DOCKERHUBPASSWD }} - name: Build Docker uses: docker/build-push-action@v5 diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 6168d88be..6b69abcf2 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -12,6 +12,7 @@ v1.8.next - Warn if non-eo3 dataset has eo3 metadata type (:pull:`1523`) - Update pandas version in docker image to be consistent with conda environment and default to stdlib timezone instead of pytz when converting timestamps; automatically update copyright years (:pull:`1527`) +- Update github-Dockerhub credential-passing mechanism. (:pull:`1528`) v1.8.17 (8th November 2023) =========================== diff --git a/wordlist.txt b/wordlist.txt index 10a01d780..696310899 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -120,6 +120,7 @@ digitalearthafrica Dingley distutils Dockerfile +Dockerhub dropdb ds dsm From fe0dc37d88f72e84bbd3510693395df3d7bdfdd3 Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:53:57 +1100 Subject: [PATCH 103/153] tweak list_products to consider load_hints (#1535) * tweak list_products to check for crs and resolution in load_hints * update whats_new * get osgeo/gdal from ghcr --------- Authored-by: Ariana Barzinpour --- datacube/api/core.py | 14 +++++++++++--- docker/Dockerfile | 2 +- docs/about/whats_new.rst | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/datacube/api/core.py b/datacube/api/core.py index 8622c3fb4..f638347f3 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -113,6 +113,14 @@ def list_products(self, with_pandas=True, dataset_count=False): :return: A table or list of every product in the datacube. :rtype: pandas.DataFrame or list(dict) """ + def _get_non_default(product, col): + load_hints = product.load_hints() + if load_hints: + if col == 'crs': + return load_hints.get('output_crs', None) + return load_hints.get(col, None) + return getattr(product.grid_spec, col, None) + # Read properties from each datacube product cols = [ 'name', @@ -125,10 +133,10 @@ def list_products(self, with_pandas=True, dataset_count=False): getattr(pr, col, None) # if 'default_crs' and 'default_resolution' are not None # return 'default_crs' and 'default_resolution' - if getattr(pr, col, None) and 'default' not in col - # else try 'grid_spec.crs' and 'grid_spec.resolution' + if getattr(pr, col, None) or 'default' not in col + # else get crs and resolution from load_hints or grid_spec # as per output_geobox() handling logic - else getattr(pr.grid_spec, col.replace('default_', ''), None) + else _get_non_default(pr, col.replace('default_', '')) for col in cols] for pr in self.index.products.get_all()] diff --git a/docker/Dockerfile b/docker/Dockerfile index 226df3bf4..4428fb135 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ ## Copyright (c) 2015-2020 ODC Contributors ## SPDX-License-Identifier: Apache-2.0 ## -FROM osgeo/gdal:ubuntu-small-latest +FROM ghcr.io/osgeo/gdal:ubuntu-small-latest ARG V_PG=14 ARG V_PGIS=14-postgis-3 diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 6b69abcf2..7488e7d70 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -13,6 +13,7 @@ v1.8.next - Update pandas version in docker image to be consistent with conda environment and default to stdlib timezone instead of pytz when converting timestamps; automatically update copyright years (:pull:`1527`) - Update github-Dockerhub credential-passing mechanism. (:pull:`1528`) +- Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) v1.8.17 (8th November 2023) =========================== From 0a10939faad1f099f9a28d72c1e8da138902ac79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:27:50 +0000 Subject: [PATCH 104/153] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/doc-qa.yaml | 2 +- .github/workflows/test-conda-build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/doc-qa.yaml b/.github/workflows/doc-qa.yaml index 2b9d9751f..e850ea93f 100644 --- a/.github/workflows/doc-qa.yaml +++ b/.github/workflows/doc-qa.yaml @@ -35,7 +35,7 @@ jobs: id: extract_base_branch - name: "Cache DOCtor-RST" - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .cache key: ${{ runner.os }}-doctor-rst-${{ steps.extract_base_branch.outputs.branch }} diff --git a/.github/workflows/test-conda-build.yml b/.github/workflows/test-conda-build.yml index 744d212e8..3b20679bd 100644 --- a/.github/workflows/test-conda-build.yml +++ b/.github/workflows/test-conda-build.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: Cache conda - uses: actions/cache@v3 + uses: actions/cache@v4 env: # Increase this value to reset cache if setup.py has not changed CACHE_NUMBER: 0 From 0c5475e54df968aac60864f4542630da20ce7767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:59:53 +0000 Subject: [PATCH 105/153] Bump dorny/paths-filter from 2 to 3 Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 2 to 3. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](https://github.com/dorny/paths-filter/compare/v2...v3) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 277d5e7fb..483719ea0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: echo "push_pypi=yes" >> $GITHUB_OUTPUT fi - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes if: | github.event_name == 'push' From 32438088162c77f9ccadf4cddc7b06f1f8d59117 Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Mon, 12 Feb 2024 11:31:36 +1100 Subject: [PATCH 106/153] Add ODC Cheatsheet to intro docs (#1543) * Add cheatsheet to intro docs * Update wordlist.txt * Test direct link * Add readme, update whats new * Fix link * Fix typo * Fix pyspelling by using link? * Update wordlist.txt --- docs/about/whats_new.rst | 1 + docs/cheatsheets/ODC_Cheatsheet.jpg | Bin 0 -> 304570 bytes docs/cheatsheets/ODC_Cheatsheet.pdf | Bin 0 -> 433093 bytes docs/cheatsheets/README.rst | 9 +++++++++ docs/data-access-analysis/index.rst | 10 ++++++++++ wordlist.txt | 11 +++++++++++ 6 files changed, 31 insertions(+) create mode 100644 docs/cheatsheets/ODC_Cheatsheet.jpg create mode 100644 docs/cheatsheets/ODC_Cheatsheet.pdf create mode 100644 docs/cheatsheets/README.rst diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 7488e7d70..58550dca5 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -14,6 +14,7 @@ v1.8.next timezone instead of pytz when converting timestamps; automatically update copyright years (:pull:`1527`) - Update github-Dockerhub credential-passing mechanism. (:pull:`1528`) - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) +- Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) v1.8.17 (8th November 2023) =========================== diff --git a/docs/cheatsheets/ODC_Cheatsheet.jpg b/docs/cheatsheets/ODC_Cheatsheet.jpg new file mode 100644 index 0000000000000000000000000000000000000000..599c54dad7918678655a0249b24b6ff21c6c86a1 GIT binary patch literal 304570 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

)RW}BRaH?bcE51Q5)Ft`JRD+nx$Pex zf`C{qE}t~b(;VI1%C_VwY_}oAa(&&6J0Q_l-`_dwy0U8L9l=mU)FHo@!~gWB`JvTu zbx9%i_5ub97vq|2c|Jb=rAgj^L-8v41yt8+ACFU!A>Ntk6VQ&Zf|kO|IQi|j@4htL zb*V&2gPkZe>a1JrvF`Lh1O&>gd@V+yJ`Jh&I}Oj5tliIB!vdEU(HBGjhW_wyS^iZ3|gs z9CXEJIzqEtJsn_-8s14dy0f=35blLG1w5W-0~>xJWC5ct(^~v_uqVLAl@b^IK#N4* zwIHG&+iK{*qM*z5TJD^AW9YSt!_G-xe$6dgld6-~If%I}2zHmk1fvGlUa3Rwv>Xei z%J2HDt6JYA{j4-izj}42_Hx!0-7~d2G0D+=BO*79-r^JOeail*!2LvjR|YvZ!3epY z2H2xLWgc%N+2QIQ07~Zu=je* zwA1Ttke-w?9+)H#XEa3mbmP`(3?&K96aF=Gii)&NT=9_GH)jG?dsnt zV*f)};y-xt%DVq097fH$=^q3X1v9_-@E}${Pak1SvfR$PO-(i9b?7AwtqYB|c@U!0 zj3fL+qH8J*;XhR-zOGg|&Y$TiYJ@!H7F+7(-7P(QAoY9`vut^OjSS5Oz>T7cPXz>| z9zL*p<_kx6&x+6{=x9iJZa_B8*wddwzKTeA-Z}`l5KQ<3+KKW2SV074M1;#U6Gsn( zJXI7q@VnQVV;f_fo^rRUfZG!%CA+#o>(t&f7?q_o^JmvaY6v+43oF4O<~1q`E`>v% zuv!Jdu91KSHphNOFOp_DyI9OGciB)b!;1QV7xG;$l>ZD;dIKl%`v=%~AOS zdAWggb5e?lkPSvMAv`5*En@kZhn6t5&_#s|6DcuZWjhsCU?v6}A4hcS<*5tqd6hvd zrfIOy5arEUZ0ofT6IO8dGFy~crj!?Dy-Xa=WEl>+JRTyR^nsk2qC>#|0^nxnI8tU5XO z1ijg~nbTx|`c00WWGpfs+2a^SuB_)H-s=m`4qxTnb1#i7^1etgL^W0XNNq44+$~f% zbZ#1xeMym0Yqu1TnUW1arNZ(WM@g{~*qkb8vZ5$;HIT8IBgB2DZOGb9OzD1YgiZJK zK4gQ8mmYrv)T?S>mD+kIU9pXBh`~LUf{v`-xUntFi^U=6UZIRe$OkapSZ@OWULzv` z9F~D$x+k+i-rlxlO9r%KHjujBaodjM=W^xv!KzVwPzhUB{mbRz(hRbXemLD%W6lom zlR6&^1ekPMGxg+aEw+_Ci933|w8|!t&|YT)1knIzI(3KUP@=Y77l`K zEagQE7k~(lZ6Ys5p?uDI`8|O95v<=_t=IB=d+}oc1{f$&SWKkaO|5*3`!Fj{;&{XiS@R8UGp@{ z@sf_6ExM;F(CF}esM=m3VC7obTLn4U*1mYjnyGY7iz)o*Frz3p*idSK9--yA7~$V> z5C6OWH;=|16bk>4d60AyT{9ECzvS{|CTvPzk@}-p{U31~wf|+@X0mZ4n&=O(jQX9a z=pL*X$puyb<0WsB0((XM7Ge9JNmZoL{}rA||a`2&!qs&KlZm2tI9K=8f=y=BqUY0TG$HAxyl2(MK3 zs>hW(q2%TR=DVZOgCzs#A{6xnzim3RjgP%roQI%Rd?0%VtR~xbeSy*D(B6)989KQw z=POHRf%5oHsQ=~&P8G{@-~y(`+a2x_1#-5sB^c&mUp83KH_TIIJ5fr;i@*PYJk>WS z;GCd4Fh`vKc}bYF_68 zuc0azic!kJ%k!*by9{8l=uv&K=bqGT7s7g>3C2i}s|jI&U_^fm)bStaXkV|ehSj=| zfC@b_f662Y63Uk2&3n4<&7qBQpi4G{j8S?EpX$v*OR4J1jp|n0aWWwyOUEd+kyprC@nJgw&rE*MZGEG zv;~F4MU>@E&zkj1NG*qkWEy^Y?)o9_^Lx9leZ{*stM?G`OG01qinw-XNa))WnMxnJUxO0iR= ztXFb&cA3ZviC3s@30VqAM+T(3Wn*Frc+%HCo+4{6eQ`pDW0DzLk9y*^EBT>Es;3d1 z<_$4ep!AFw5KSrh16TFG#)yQL#w5bzKMlTWxHUUGDtvW88~OeV5E}ra9AWx*%767tO*P-T4HzrW289mSGf}OyWL>8Yi$Xe14kHZ{(vte)HvhN*Z{llgTZ&#lP2F?U zHw?K#1ScTwqG`n{gWMXR3Lj;wT6;O zl@o_H2PO?3>z_*PN%8#Q%NIV|9&kcSb+vW1lX=F2U-^$fWD&9lwh<S6BuHr*79xfN92)P!G+dK=P)T}6?bS|So+!C3nyT9t^-N{4@_N|m#Pe^iElzsL1Y z?}+{LTU2E^dRw}^rm3zAc(0xF24{N+;l_FHyyQPS#ueU?s69om^d1b7`KJ^8;W(-= zmx{^(BgYjA2)T6scMd~@_n#a~DJse*0ocXy=42_W)rJaMM57%7H5@4(km zwtslyfB4mJhwSYPe`v-0ybx=h|1!E@u!9$n(VHMOSn0IJD|LXg~!7X78u_ozhiq(x6 zwEpCix7~r_Kckxv$$|o67-AJFBM5-?(JC6#3}k?f7nCtbE`EQG{h!vaOm?`MIB?R^ zB}ktgKy>^{8$qproB*;Qe-wg=|7`8wvv{~ZzOWXz z=gQ#)ha7=%DeC??LMBWjL1@kktMV5ZmOLAyw@ z6W7?rl39vY>(88Hp26hN0`g$!uIzih`B5@Rcm3-4(WsM1-Q+)c8bz;f&tcB|c1~q& z1FolX$k|7Z^z_YUhVTK zw~9|YeIKk*G@&d0)If>|Q!CFJS%B3Hr>l=u*!}_}8~d&JJzAF!r3S2ypee%JzW4=L zg}s&vC4T|@MDM2kIzhhxZ^NFR{4Ps`Iu1cU9*fV}&p9QZ@I>!iM6b_b>;|9*TEB}O zzvJy+0Nt9WvERoOZM`2J!M>6C1=t;jrOC&h>L^d*e*va)egVE`Jz9_5i`G$ET;-;O zadL_NtE-6~JWcy%L1WCxhGBgK$3A6XKR)~d?5n^6r<9KZl=rv40JEjAy!XAzm7g{L zXXk(4)W0qDZ=d?#n0>2jFvX*xKj-KOY>x69{mq_a$J>jo3vS2!y!%2~SpWB{u=gw5hSk8@=%;zK>AsUr{#{S)l>Vxb9aG5iZ~Su9!) zdxp%zo*}S@RK=C!F6c*JJ_XdbEB?h__x!c}@1y?D+sp4SqvC$`?Go4z1X6m#-US*W zV~46Ne21X=EQbvViADlWEXMxNg^6p`44TvR7@oB2j}=dKZ(P&;hMHgqt6zY->A8>G z=;DXE_g2^R@z4EWucWD}Kll9g!;Im<`ebZPtekM|eHabPUw2La^~1l9`~QOdSTVt( zXGea=-=FHFgvS<$v6E};yti_3ywwL@Fv^vt)*#`Bb+c=t$VF=r(3xug&lJz3w<956 zJ|yX-jEp6xhK0Ui(Qj1@6_&NiVv zVGZdrD@1vwP``7?bw;2%PiBFVRc14Thp#DgzNlD+?u;}Z0mKk$(hwgqalEYtk&DXF z%;rN}q-!;#%}85^(>Xb?*!R9C0&G-tD)+vbXS2$6c3j(9=CekBYsDT<4ZD9UU&e9U z9zzTan5T-njZdd+eMXdx8OfBH-Th<}{1wBG=L`rM+4k|=Toi<=8@J#9b__U85 zi_Jy!$3i9NVipK6f1SHh=CmpJBQfjsClh;BUj~HAFO9xx8VQ;wM*$UjZHK`XMbJ0n zEC*jD7xSE$;r!Q$0rBJ8QM2C~9EHciFKqFMHv;*S^cKRRBWpmYQHY36yh0J_ZmNRg zD3Vf6yd)J7VX-l3_V$IsVe_q}wM1zA(%7cf_u*Z@R|DPo zF)(*sRXkm@Wt6o%5@K+C44h47I>HD{xAE=0+RP;@$=W3MGwH;MUyc2_^6`RJmyY`=V)*X%%9&YG)0<-9n<5Fc%Sh06uHhlUnze< zd!BA{G{G$VDsL&0EEP%4v#NZj-(|_Rb$+D4gqxB`AR|457dr`7ZQlM|1@)*W6DA&G1#Hq?ka z#Z*|#zf**Ou4!Wp@T(L}DMD~m1yi2kQC<%FaVo!BDksT$E?E4)urw88ld$KPLCC8L z64E?*Q>{o-gLzyhQO+UTaYFH`m`;%!(}!?Y-tWV( zp4C0v&n=rhWuq$=k}siamB-{CG+vKQFMK*#A21ly2dw&Pp%8XZY*Vce_vI5b)?fU< z+PpR*x`}jJ%qc)SV2bQ+2G8Q%nB&Vbf}+6vLEM(7Jq^WeCeDoZ71PsW(AD%8rz}%L z7CAM)lu^V?9lpL|GGDMld+4qmmQRmFQ08kH`dc*rUAOLp(9Ms&N1L!8Dm&_MXnWsJ zIprlPt6Qp(V^q6wP&Z5va*lgw1rioNCNh&LlCb~SqI_-QFSp`&gj$?CdAOFzuRe6& zV1+B6@l}t%{TCpB#A){wRUziA|DG7meRSMw{R|%tE8gq6zF*=f8mM8VeKr=5DOx7A zrf95)@+Lt!a(t=3x(lz9jM|yDgi@{j{>S!3N&VI91=(*@gKkW?nM7D{A~(m{Bo)EI znYam(Q|0ueM{8NbnX4&tgy_txF*1B86gPL{2&5`4vo4R$iqq7-0{2Q9PP4o*;uvY; zB_Yy#&w%7DL5kIL$HpheAuEAM-)6W4`Q}L@hON%JiI?V?Dc1e5l`G)O{iEoVk?S|z&+0XO-*%5pvs!eI$`7K!VR64e5 z1Qw)>wwVCYV5$H6u>b!VWaMu{|HkAaEb_OVQoJ@bpWZJKD?STjM2<&c!o^jQC~C*A zRnb(|(D^=_TK6~NXfOt-(?GaaMasz!&(xz8+vT(ch@K{%$S1Vejp;<^Zes3gX4S%k zDYpd{=Gfap_p4RJSJ*|3H^CC?M@kE!*v@xs7L9rJs#WSml${In4?Ej@2qV{iXuMH8 z`~!qy z4_sSX+=DcxO1p?2fcEa>@rfx8WRUE1h}#Cdr;ysld*^8$7*Tn5%_+&=2dajusR_DT zTVeZx#<3+lvNyR){>~Q&vz2v^E*{>!%}KdT16-O=c!Bn^=pzd=l^s-Cj7b%5q|x6D z?B(w;z4a?+OSul62KzIlXn96VfaKLQ$L#eF7ED+~CWYcUJQSiW35JuUY#Za7Pvf6D zuj&aiU1n{oKBE9P?OTGPoTs>)Dw}@(mblpRnhWP1aqe;xsO71z+?mtOFA6U$y9$ZY z&(^ZziJQOA&3-?y)#8V@uavVJKZ4Y}Xt1a+1-5<8Gh=0HlOxvc2J9ya=A<=A#&WlKEpFrU zrlkmBzLj$W(I6k0C;CY{Hd8%Ey1K=r(yLKjMxK>0326sOtlD&4#F0nXeKpBv5iW|a zR^VF)&eGg*cc1iz#L(h?jZ-Rc*TA&s)2bJtX`G@qU|PzNS!SpshQ9c=m=pVH!-i;d z*}4BvBIvz~yB>`0+yPn`u`%I_jXP#l0AihfzI%#u{`x$sn*pUr-|j%9shG{jqIid6 zZya2&2~o5%POc;+Cbh{)?*$5mp|~M?8EumnpS^B^{#HU5(%sLbu0It?$pgR8^^m_Y zPc*NMAMGMiRSu;*#PYtw&@d#@Uf9dzJV^{$<4USd>!N-CL(-T^+5slgm8{(K<98=r z`KcbDK3%gIh#h8V3E0WB+Um!QX*e@;!I>L`sCWY_5i4tbzez|uU)x|HXXr79G32n6 z?P0fc!Q?fTw8pn1EcP*lsZC$_ZQtK29GI)W0AxSGx{Ge5srudp5XMrBgXH=S_xCB< zfJbDB7+zOy`PJh{+V7D+#j8-cW~ibe#}yF=F$BZErV*E1v87Xv^*^vvk$WLt4l z`zWW!Cf1pUxwD5*b7Y~1ybbI$a*)Dx2{#Z2O_w1LL2lBfa6$|NMwridp^Wf*didf4 zMquNWB?aJwdHVl7J5@tqw93f8fdWj2swH(ZVS8q3*_yDG_ixG@DXV|94E+4RQW9&z z)=aE#9$^LHJS?K?y{~efT=eI=Txt3@X97Rbk(F->i}%?vRKn7PMBl=0XHqYyPjbhS zy9bpDB(4T*-o1yHra9|c;RuK#$vDjQ4mYRkl6a;}6wu!XY=e=N)4r4NEn0Nm!v8TB zWx)-Y>XVt-Wi{;sOzlQI$mSBtl&iHnt=8vmDi@r2CM0MNUfsFS^KQ9WiF$8Fh&U46o!&Gc z?0B(O2O~RX7DewU+)=iA(HxUc(AH!~*V8u|SyD^Icb|>*>F15)MQu%W;8JU|5UYI&6sotlM-nG*E{mVvi!M-UX3n<}3WziS zNoC4s(A@Pp*mG^2UF)-pxUj+Zmx_(PoiTE}j&pn2z>tm6@`wzBt*SLl`|Ou^H!642 zHheC)pv5Z{z>UBBdUj*HY>3lWmUbwn-;2h?auY?(Bxl>VL`MjO{yrJq55*Augb_`Qz7X?O0c6{(&M|D*8W2gTmoRYnGI@U~$6ij_<^^pQB zp9Yhv(P0#iA>ddWHVREk2kRk*j>9wWeyoSZ{0KNKxdt z(@m>@n6`QP#VQrgn2chIuU8BT1@rw>K$V zsLwKUys|nnZ(`W&47Cl%2}EAuf1i+5yj&V`vYDsb-JIBZoph#1mE>WZk;JGEXh{*U z03bwJAn<2nq=^z4=#Ra3e3?S#C7t7Tl{5#xQQH#wSUPbCV7B4w!sFoJ=lc*wn?$l5 zu>$_slIE=$Dl=&{9M$q=u|uAM!`l?C#cGwDZS+a7=xI`wIMahQ)6MdI{CQKn0Kz$M z<)ULG*D)G&7n(S8wrsd4>%uOeuhd4{-rnE$z6Ss@sffj)n@Mad+x%33%*<7W3snL{ zL)iOa($Hhg^{3cjLae1rJw@d?ZuNtw+`~~{Ua>7{%sc#`o5?~nO4^uHvMreZ(QjH; zT_4cjVfjUa*2@$vb#0ls#mW{|vojdVk8@!LMEsx*Hn}7@D;Atm|>q5tkB%WwN8yvG-LpYXu#r{^|@cnz(x5gE@8V`-B+(bi2OQ zaGD82IRlW|JwKczDa1e-!xlr@F|u3;H=mq|RY1p{K9JPP*5urLh((pDSlMyv8T+mn zF{h7sDkaUVqR1?{oUEP7B)A?YJ0wwdxe6l7)=28>xt`|!dhY}K_yIR7uS(Oz!#-o; zmjR4wk)^zK;(3iqKWG_aD_x;H$D;JL=CwSG{kZ!%frD9x>jjfr1tLYzWWDX`y>P3; zEkw3T;yAv6(>q~t9PyLp%fj3xYZ20f914KNg?LJy>>gp=I19`tKUID z9@3lU7B=2vW))f7txtXl1*eqD^^XErt6vA>pdus@cRW~Xdvxv{`EsloaPZY;sIDF! zvc1@-ZG!uuvknDd0Bzn!5EJh?mQjYWQq62m+itf86eNtMJ}Z2WQQ^;vznc!1Zo;?C zjhP(m(t^HIZhSARP3viUxa?+We&ouugQD)i#|^{vF3 z`KN44m2jj8z5yZ4@}{rkl!}Cbl-zGl-t4#75p1fnPTfep==0HZEe!nysA99{CKp+| zI=fndKYjNOmn;gPEDLpzh{9r=d(oWKX5pB>FJMsD7Ts+|3t3RMi1RY?1P5i_!su-5 zKF}<1m-WmhTH3FK5^eM+m~aaB#o@yXu71YzY(H?;eSQ<9Q8i_^HGT0Y(rcOjCZgcG z$eSGhou_w)?HjfW12C0ph$y0^!^c(3mfnUh^KxG|08|}O42M%$@4)g}6J>Qp)G@0C z(b*{$q1yVny(>7+L?u8lafD&$&EC06-BePaY#?~3W#C{it-3Pz4JsR@`C1;vxOTH0J8~ z3aULkLTe_AAwDX*EppjnyL$K%+KbNq9wV-+#Uk^YiCN4RT^cO}Mn(G4pC6mpV|0&| zq2QxS60eG0+2PR0W{q_4DiDyQI_f;dry)F;`g5*)bLx_uP@}%dygqZ2rJNf3^M>qO z-1Z$}LwjHmi!eJ_{iGnQR7V!N_CU_z7T`$hODKc5PxE8psHI~+*# ziYX9C)C+9TRG4Yw@2lbBXKQ60PtBs&Vdv#^ zP4j=kiG}_Hk(dinPRCMYl$A1wW`^JaHr76yi9cxQe6AYc&nveG_r?8ExLCJMU$F~H z&-|XR@uhGdkFrLpf?wEAPH5xuk*N&d*I4{u0z9f@I2rqUYl#%4cAPht?s>x8Megsd zUnfAbv=k#RT49GX{NDPtR^>7R<=>tBzrZHP9}+bqMXIQ-EE|bGRAC!7bR!`k7lX-D5!xZ@9XF&VmvQJ$azX_vIWPbCs9!GfrVS7JGmJ zVrpGmEpHiP{I^aGb7~N6k&xd{35c*caYZVWkBwMW4PBuKjX3~<%}=Syeyrh%E{P&} z@}cC*57xl_(;;KY+u|z#g{G0aAitA5o|B?{;f)hEdj7ZU^D&ns5-wfG7(u5)nqKEd zrVWT+GhCUrkfa5tw45S7Ug+yZnQRb)vTVnw7rZ9|m7@xb@W-?C$3l<2kg}pZ%a2sy zd|0_*h3Fv#wlH7r@*XQjR+s>SWixsASr-?_yG5?}9tU<>p8c~fV)9CV$N8!f=vdGp z_lU&Z+L0%smX|}N<+xEfS);>pc6^MW+R(Ktnk%$8o{s>qua?C%Mt$yeD@aD^lHqkr{F()W1XIoE+D37KAjZ=Jjy zH4la8yqdQ%zV4B9>T^eNbda~cFn(4q1K=Z#z)U3DCUZcNw^(C@)3Mii0cX!uu7TxL zV*~g=-jZ75UV)3My^^%NYtf(?xhn0`M2FgF6z8$@GiWxet+Mtt%M;~0t5eIZ1q12E zNfXHOk0X{hk<`A8NbcC_w)ZPh+xadOBq#SCUg#Izx3AuvogP!@;4s@6WykRKY5@bH z*6UE->48z%@6*V=^j zUHZ(0hSGRookLB(SLn5$wX>EW{5$hPk>6Z@=qWeXTQ1{=9ueUwvAp(O^|)WO5cbCK zWtt|rEIscIWDbe6VDEAm2;X?02{-CZh6mtV=XzWj?`=F`c}}{LTuq}tWW0jEsH{go zWg&F5FjptoLqQz+u-E*lO5flXYB>MYhx|esx+T`%{dkZG<0LOBya`-Tf&4HD+~RJI z8bBB#LZ<@Yp}=&;KeW}T8SV`j4HwMr`NrQ?6Di|-9HWH@V zFEO_m)Z%0gn7W_nYWxS^@N)EFSaDVNQE1?XN|I%MzSxdQd{ME6UEL!9mE?-Wi0?gr zc22@ca4S3N9D4AYgHvhQUffc>;RHP7=&sC__>%xR|m&4=VKA9wS?7bE^aW;6HQ!G`;f-JKcUzj`)O`{K4 z(19h}V^Rxk*OsNnMA;#Lx;JSu^4b#1+%&q<1JE-zw4_+NTPR2&`G$i-muGq!_-qk>jzQ5Q zUS@WR)D*8N4=zfa*ofGOf}>JN2uP3N-$0Z1!_^%d2;8IN+1lcyHQYBL8c;?R?jsuG z^6toS>qRf|*wkz3av9-lW)FYaP0#e1+<|XQO|yjl36IpWdu?lZ>&L28!cZdeHildI zQr<>FufWE%Z3T!n`dQ4{tVclrtecXG{S|?1T2b188+X$*i7g+f?;tcT4Uw_49I7bCdT^)$9R9|%h7h*=9HvZ#YD=R;puu-IOb31oa5x<&<YztM9d&Zz zMTqL9fiGMvBOmt6bKW74B*&zEHog?s$V<5v#MjMU;AM(LFKFymB78SOyZW-b@mM{2+e z1JuyQ4LS7>fyuh3DD$?*FJ_7Jl73pZei7;;CBq?2zW`?Q^^Y>1wal~GP3Sm*>n9dl z%^8PP2B{${RA&>T09EJAJd6N;rI%^g;j9Jepbh;z^VPE81AqF@rCN5*nKCQu3P$An7#)_EL{1KF_$WWNSx%++}(_|fCtQn^Wz5U;%1kn*lXR}Hr&ObaC_Oz=?}FBS1i%gS=n!(fEVjGc2lH=BN3OkT z04yq!m{IqSjf{F3r6UB)!rY&mp)~9i^Q-;~Jf90wNQlmPtt@C*o z!wBXp9&?KP#)h6g`B@smiW-KV5_#i<8P1SI+!y(Q^I`WAunT-JyvY4~qqsEy6vwdb z+XWF0_p5ycOXtAku;fJl(Ms#b_k@r6_sRdlVBFZZw|>!JURW(xhT3%O;o3+e_7Bd z6mT=m-S(u%L^~^?8MRDEHf{ew~fqmI3TBrp&@j*raDPnna9 z`T#4t=CSZxkeY~s13aPVJK61V3g+}y7=S)TG=>7F;gnfN(V5JCQ~yU`2@}qv_)h!A z2X!Xjj{YlQQNzhPz59Jq()sP?O5bFaBL0YKX)Ms!tijrn0T*mMeNf56TbQP|z_Kli zEUjvn4eqAdc{m5|^{vWnmhJ$B;7x?{$(bl|r;A6a?I>+Tt_fZHInRqeDL}dr*~we0 zdtP6J&(~jdKSl5BwlpL?*%+nY4GWWwOqxA%^*VR5WlkQf$ z-|)@s)v-r6yz-LC*Znab;IJvv##OGrSe`MKSu?t`#F3OLH(HGi0m7l4rSAz9r*`yY z5qfItzCUQG3}a4}?OL|Ft{~Oc%FLJThp$kga@$SIJ2fL-)W5Nz&8eaND+Bi@&&9ub zUyZhHCnPDSVZs~#QNyOyYB-hu^Qcb9FG}iCjbC{g2yg?{C%Wv|eo-!i^D}PSgd9m1 z`4eR{k;5h&S&Y@Be#HFz-f5j&Db)AcXJE6j1Ld4C9(TRx$3q{^6x_SdWwAEnU2J;60lf(gu{_a&?vb_pHAzHS znW92c&mTKm&GX+&`dJIjY_sQ=VLhXNt6pW-!Nw=}7?f{ok86+1$Bi$g_$7zvA+7l9 zEnRc006=YN$Q0Qq_W^a-_=1*@ZG?$DntQ#|N=Oa2!aHt{@vg32+3|(ra zUT@Yh#DU_iwOVX019%BYLR`~Ah(d^u7-O9tX{qb2sGAYxaip_Y1na&U@d}BOqk9xe zZxop9h+G8x!4cC5D3G@QCkQ`Dj;6 zS2710kaAcOBva?g5&AS9+^4wgFoZbX_%2NA$75>#5je$ek&Xfzp(%K9hxDTvAv_JR z=twxJ^|#ee9cx&VyT>?3-%c!79JX>cEWZq4Ae6tl8Ks^L@}OMqbnwD;Z{%P+sl>KK z=iY`-H##P1+WWEk%jCPpX6j#ScwFXXh*=iw1vl9;)_@fvXOB;KGFG<0h>sI+a(683 zuWDc2ea!WHZ!-^*p)&}H)m`gy?xn)K!SCISm08sAze}`#+nN!Dqp0Wvt=#b56NUKr zSZ%ejwsEuTKF3ycUJ^DQe;^UMpIwC+TPY(XVQ_q-upx5yJ0pD`ZCAgkT_6b+CK+PQ z9x!1#AV8_Ms(pr~;^f4PzCce(3S<%H@#&}s!L$j}JM*rh&@tDsaICA3{kw#Y&Qe`x zNc(g$#`(`5tCquaaaS3>r7W_ccUL6zZ!Vrjjfsk1>T*gVt&|W(P>i>nb#l#~zyiJb zv-W7)RO-ROxiRhgIvw9n7|n~${UWzF`x-25+$^~ZR0n|iSh&wO7y}!fd~CfV;t!nx%DI*DmW}xu%}|z) zOD;koM~-N98p$8@QcweVCr6cgAP^B%-Y4Q4xhr4iRq^(ZM=*%a+oI7R@i{^S(qLrVorU%b)SuFd zLWdr|w15R!N~txxI%G(U1!A{@{%AQOAEUgb&*$`aYS zz{Bj*@0sfZ2L`4trJyI2pT@|{u<)O43FNV4&Ea7@g?Kn!-Lork%zsiGKTN>;{ea!qdR7jyeS~A`%o=$1!YC$(|ml&bEaR+cYo^MTgdv?oZ@T zmy+apN6#@}2S;GOv3Q@xyQoOSTaoxTwMXYYHEhj7UTB9Kp<~aEN{pb8Bf0En<|1qz zrtJJ!T(-XEc2W9#e3UskRDotMY0wQl+5H`#eDgYln%-{rHc`Gl9_YPxBXB0cg=g?h z+b}R$b*)tDk?|sF2QOmHmbk|c3=%riM<~6RqC27om{`Iy^?&;S7x*r*u&P(qDcQU1 zf$IKqqCh0MQpDw|R5u2OU9-5nB%h$a#DRP9qNd6?5Cwq@_I>c{n*;WdVAsawy#FfA z&CXGxR+h>|R9?}He9y|hG!RL4Jylzf!^O_d0iEZNNZP^4eu`0Bz}%=a8F`0Z6xNog zC7mE;eNk1zw}gRl8C6@%+W1C5DJ?Ut`5a;O zy`8OmH^?fmeael*a8K*yFsH72+EW;j({+k`qy`)>Viq9F!RA1aDOVPDdkd=*aUQB^ zZ!$7Kf*Zzvs!u8P6uQ#;X|i`5h~E|qJvu3F>Sn||DU)`B5PMtHJ1eBn)`fFn;o8hr z87>&?vn|+Ibm^6{C!1(*qabZGWp^{LJJkgp>f8%AZ!*Myx*#_%B)1Lrl>}j3Tsq9s z$q(JWl9jj?0gD4AAp+tjhL0-~@I!Xg;#Hu@oc zvM@=r&oF+ne1i{NSI>r7NP|(OAUBZ2nJ@oj{^QjUKTni>KJ=1-iA6T^dh2_B&*l!B5vRCuWV-OkIjQBhKrA z&3BF~>|ZZ^%D=OCF)0>yw;U}(50sbpk4=R_#uxIUhp3j_8O&ZFlkt$|BF6@ishzd2 zM7KbDzYrePS-&+Hzs}ApmULEe&*(woLx>H^bR=6xY)#@Cl{$FOYk2TRrr^bAh&p!8 zi3T5qa~}E?JQvqZaAE6Io0e6h3aq^CRZL{M^k=ZJub2E2rw&cg>6<#BAHm9^(7g>m zMS~~jpc^l)0Wy0(DSLUy9Z}MLlgspG-OgN>p_TiZ1>cNmLN^Q#L8C$FDgEBPh|2*j zLr)Ft#r#Z+Il-x*2@OIDqF5FVP});3`@mI^p(fEXeTDWaN;XyoEMTQ0|0Q>!If5n= z89uh%hsE{dS=fexgF)(6=k-Y6=Hi|-*e8OzNnj;N3N5{uD*6Y<*cO!dyVX~Q*3|B| z)l-+nm2cXhUuQ|r{{WoO8WoNt8Z&wkZzV1@)#rP{(kL>*uRR@@GU{4+YMQW!Oe9Q# zKXtChWL(VFFmeik#Ltj|3a#hx%ef#SRH}k!)sgz|(#?pAPuISXTxgusXt6)FV_@CN z>Z!~L3NTyLXk=ca9h%aho=G_q5wjwgkvUq=io5`}BrB!r5tu;hpF@beBoErYb)cAq zV|`-lm!dK=L(QD)RKn}a;ObpeqHWf?1%cVTIkLxql0sqh zUgQTunl{bpa^_2&R~ogEe2}R{W7-VjBu!}w&t()oc!F^*hn{<`-CB@I!z(8}eO<$T z*Gl?VYPEtdP{v#i7+$LUuqZ6ovq^p(^0VfEC!rISk*zU4L1O2zVQ{%~&~Am7_jpLN zA=l$;aMV_WahgWMS7#w zJ%#H8*l$}e99osS$MGmD6x@_MY4Iy*p6E6|@~)Yh|6G_8jlKfdewuCYZfG;kkH+z|jY4OmLQ*~Vlb z9q(K$&ui)Jd9~=9H+QC2=Cq^}fNrS9t^DO-ptDK|IWgKv)_F-ai|dldmBjE=Q)hH0 zniXNuGh;IhGLl+}FtW6`^Y-L3R> z^jI4*bWdFaB8`sed_6Ra))*jns;d;Qm}-MW^h@4P%pvfNb9`9C`uFz6gyHQ+U?E#Lpiq$<^OR@^|X*Cd(fYX-^tAUw4`R0fv zl3{o2qy73iHo0J>Jnw=kRGTn}U|X8D8(1`y38Lx#h|@I5?3!ZU5~*UE&zU$(zjnigy=^_9Bb-GCC?CPggz_Ib~Gdx|LYVjEb`HP+~RbtW_TZzp|7B9C^`&pn7Q zuMD@huV=xJx@9@VN2&vFlus~5zwjGpMJ6Ui^kB)pjne)KT{D>G+^(-~mdlUqLdIZ> z%4kiQUw4_n>MLEi_3QOEnzk$BVj>mLYaKk%l(owP%RJ68K|NKatQ6qo_g%eqn}1w| z^o~p_2tumcYDB-~HY;lv6C0&(HVcvUHagE+LHG?5xsH2RQ?6$~txp=_hyvU6a-`C{ z+nE`MB@>rcb!1pN`!8gsq(La*0#^>Iey@9ftOXZNoVcc#wZy8J=4A*4789j;H*6fA zPnX)LeGTyy=;z_7-q!-dn86ATUOk(%041?gt+77%JGXv{S*l&bJ(lZB)>U7%Ta6>u zB(u?ToLVheW@UaMQ+2%K>~d2Z>!haVDdowD@k)_LB^i2!?RkQ`(f|3l&Ig9kkZ&UQ z<51ZLEs8qp-SCQU-dcSzkJ{7C2EitrCbF(e8?>H@a1&B8itG_7A9M|A>J0Pp|d&;#|{4J-#hAq!jj1wKj#B*=W%(6qn5l!`7$ub}4fS|hgt%Fo8)Ej3k35G_g%YRV&vi_`Et+u7 z9pGXC+{WKJbhgB%pg-Tl!ji&1rjED!^o(~IQ@}fx;2fp+W%oy(*{{^MKE1DJvn)Xl zR)nOh&$~ar!WiVci(p#v2UP0i#94JGk?Q1oGU(U0-zHKu47}2pMGU|xzIcwaCHUCk z>wQ198>`e#lDk{;tUE4Dqzz0d=sl-oO2vB&0kFU(XedW|b>bhANym|zYvpuyAR*^Y*+*r}A3+#7!s34v5jjr&gczyn34$e3r+;_B> zab4x=nsZLh2UfR6@dZwI@%PFc2zCvvM1yGF8S1Yq&+HBqDre-aKT?)=7hXF50Kyosau7w?1Sj z4(~@tK$~5@r(RDcwvUgVk3c79!+qfLGehy}x9h|q=4xPA3IzXQqa&IN3x4r?{H>NW zbhkFaP%gMhT<8!z8qAN*MnhB8$FDWiIHg^#!QGed{nMFr*yq0*ncybfM{rO1%nS=a+(hs%^`Tvy3*ODHa)$G?N z-pr7$2{YO?BQsMv71@*iEjWGYA1H%h=7#G->yBw5{(Bf#-vkj@Hai0S9tk^&___D^{cq}~=opAIAZZdJ5C_uzc|Pc$&L?HOB>K#(h@{BwOZVs4V*fn0 z!fK|)X!l2sM!~JwKXv>1+iHK)?R)Nmc$Et~)^AO7e-FJsrQeBOElD+*2L=PBjL_oQ z9R;S!dzfe-wNme??9e$9qR);K?k11E%=3L7{|CVX?^uR#%ke}`XCwG?RzcFRxO~zf zlAx66e=_}lX`=r^jRSAm-}d%j8P7DzLK24P`QJQ}ZpQx;kEE3HkoqMMug{&ZA+_hF*q;31>ox}` z7=r~KxjoGp@)>T;A$I{S>GO%ZE}FMP^Ry?pq{s++@Iv>OT#!`xxy3gbHYBF013y!@ z97Q;d>>Nc_;bb1^wcCUQofHeK=IfW0UcHClCXb+9?R31Gn&fDU(*0P;w6m>TC-Y@? zBZBY`0K^5Qe;3oHceU2soS|PZ7Tt(5#Kc0o^SieH+b1q9#VMNF_DD<5p&b2UR7F28 z)_C_z;)fYKkVMCH<`us7BHiY5{JU)xtl^+@+HSK8u;+L^gJkupED=`GqEs^G)P~(Q z_B;2iq}VBB#gFHgS0#>z*JI4`rHmgn1{*cb)9bf@0=0H7+U3GJDlVQp4IaAdC6T>y;w&637lidIFQIF(;w z`e-|LQS!5GGeXb1gjPV}QY_q|a(~7C6tE+GAc`=}HK7JcZ01wK1!e}vwD+u;F5ZQt zrM19Pd40(+f#*V0OAUKaAV8G^N^_{t@Z)Mox<=+0E_UUMl(pGsK63OVTSS5bB=4)u zC<(1(Q*Dr@p<1)`r(F6^o+SE^hbC2amm>kPceNl7jBR0C>c~)faXQsJHw5Arpy!+d zYqd&5)Q#Xic1WBPRS_y!7E@}Q21tNRP;NjuwL3PM+nnshjxTwktHk8lb!xBIK{Feg zELLz@jQf!sS`hFiQr3WqT)JWPt=X0c?NVZ_OKir0)NC0(tr<>hga@AXrn@a&x4Gy* z=lZrIh^tmMsw{I93&QcOQIWexFa!2qdlGCRr{X9C z*#WKVM}^)%-ed%Xc)VNHvlCTk9X5q0KRZ-3u<7aMEC=>cq8gY-OopDk+5ytg1OyCj z77c!p6N=4;ABeP%mJhbvp$+GIRBJTV$m({{7w6y?LtlvQ!{5~5sBSb)U-$P7BXPE> zdwb5M*lbjlLaOr;2PKk|nV63d-kxLJp0AYKqw2lHl<3IYYAMVRLTxvM7brpuq!_4f z32)2-X&&UAlXNoQC&6R2Ehj>RLTt1b+RJWEj`H;Ny-H)dO<08Q)MWD~tiR?tfiyYv z8R>vK!w9A?gPofPG=azjLRh8>4RfxCIfBje3=-yA;5U+HBr=Qu-hojIu`f1cwzZkN z+IgN-?q0f^((Fi5hPW}sH(~G>R}dwvVeaI@1zCF9z`Q%!i4ejI97ge=vCOV2IqTjX(9n_Rp_fvzR2eQxWAB651ZO2Alvo%_in_|obBuFOp zducb?l~6S%E)55}HM712(0l}Hi6}mQEItR}s{}msyR!?50V6V^D7uYB$B--Ejq?jF zzaq14U=_&U$iR`NCq$;C*E!z?3{jWdA`hu5A^N{oOc_LpgQ>1OE@V5Dus zrxj?Ank*Gqf?}af4lNh3hw3Qhs6<$ZVc*kDRUrFn*ojDLqUGz>1Om`$+!l3^l&sa7 znMzpRM8%%_?);r}GV=w*I5FYLXJ)vaa#!K4-n=0Ts~m1M5DZ6Omd6Gk9m*>>X%(`~ zztn?&;hUe%tKNK0sSz^Vj9p4x!I6--RBs6So+S^yCVVxFT^s5~jt)ODTFctGs|b*W6mbv0I>y?a#_QilDYd}r9=!Dk_i&a4Ro`5je6t^eqe=7^ z9o*gw&3Qgx4k=|ispcvvdeM!;X8JPbs9TWj8~l|>h!{`!6kom)byx3)hBA43XH@%v zt;9@Lj`~q4Zjk*-+9#gI@hH4+$9<_?!?q zb+C1U2C{Ra-gkW6kYdW-W}K9(PF@oolr%1`;ZQfoborpConkWmrpY-|^9Jtyj$cYM zujZH{ySkHPPVXp{tGIGKzu(g1I+Oe91q!+8Z1(G!h=^@#s%R;7UdXGrt+BZAQli4` zw9|Fqq^u9^1BHVvt-8pVzPM7ceKSrPB=IVu zoH#L#U0gBq@3b-3mZPJ}r9dSxMF{lTix*N5O-1|zFn^M*8H(UJE&U93$A7Ac>@gxN z*2l`BIElAyufGy*+UPixqoacSx$OC}w<#g=;q5f{{YuD8k7G9jB)%q>AVVW^kDQHw z%IGY!FT>ZGYnwZ0TRPTr#|bPwPJ z*7Bg5u1_#^x07HTzm~O!w=-#$H^M>U8{89gI-s-TIwT55Yb&6vdcHOi6K{9&t%{KF zghOG5b1AL0nh}Yq+?uO4f*_FRm^>OjQ){VQ%rAS`7e{(G2eBIT;o|0o0(t*jO~2vg zZgZh^(UM_no|Nc!cwESvm@04LU+E(oNMWUz87c3LEWa$VnU}3Hz7eZ7I($&N5rqLSIvipZATi|=b> zm)u2m9+K4QDqVGC%95<%WtD;`^AiextIV3_1-nt3T~#CAoPVteJlx8N3Bc zVJUsq3~~I3GkclA|In+vX5+3|li}|ET&OD-yGGbiNfG0nmb{0+5W`4?R`)%md9(O~ ziU-c1jq(B^&BFE)fd)jz>)^fLNZYpQVok`xtm)M8)vz94<@zLf=KfVH6rHZaW2`!a zI*<1*4M>bh-;6logQq-o<>RYwNaPmp3qFvOkpkb!04d}2b5WpON z{mO#Eg#vrnF%eODK`<%KePZ=zuiF}WZ|e7Z81@v0PK?R6IEC{=B9Opz+M9GU42@iI z4V-u~p`{F0(;fTP9a>1#PnxGExJ z!dUSvwRrwBJsv+(sGFX}55Zxso<)W2Ml^(i_dM7^=wN5(U3q{K4UQPQR`1LAwi!8} z(n8Z)kt603cLL(I2v9ltb%_~?`z^gT2gZO?SGgM8wsi_IN z)db-+v|wZ<-UgDj`ES$h1=jr!TU-eX6B&D~GKE=G*i?=*Q+M>ynDbNGq=YBIJtmc7L zT3Y!zh`sFG{dkGjCJ@A;(qCD0l^f3&XHeqKpHY!dy{X&mV(DmCFq9DStQ33Pkif;z z;l;!o1(3=a#&Y!`4cZ-ChYS1ZADNRLEZ^-Q3>Jp$at6;Z(@u&YDqK+FC!m0CX<%(slj zWiZ7#{2tdcPU&eut39HP$&>@uz)`<#naC_E*l6fGDy+c#O6YBowQl>@ibgy88ru36 z@RY(jdJ8cQtG8D2cLj%{{tZ159Hm`r5u=uK$>}_4rY?Craqvfz>L7pLahrp$gi&HS zHaT6UT%Tu7+EUPxm(xNqw+0DLR}*96XsGb?cI5b{UE{>s(3ewwkl_7LCbPsCDvUb zo5*tmq4kL?QzuK8{7%~zjgcGfMW&J3wzHDJ8sdr>M{f?|hE2W_Z;npO%?{*oYy32; zx$-wx^&LKG5nHSR7VeAiB;QCMF9lTwi2G}QZLhF;sM{!}(Y0<$TQiupof$e7XX3$po%RRd_|P*Gi{r+>d~Jl#J66b7x|}#*#QPX;n@1A^ zIrIala^bFDD@wH`P#^$!5{Shn9t?l7A=e-}?yV=I+V`Xmt*Eo2OqOZ!Dz9rzDZH2_ zUjTwzH3Gi(1TKZ|#)|F=xksMMVPRMo_`!w7YCb`$U5Z|Ez;geah>lcyyKv3@iYvS& zTEZXj8y5LF(Q;_QE16OS23L8Xl|UkCjKMFRcuAfER0em6`|C6eSoqA$t(^#Z}jgPrlKo0ou7AhstoKKoQCaAxgku|Q!%HeFRJ23ZaQ1OAjyO1 zUtE|n#kSw&ZJ9e2xT>p2ivDk^j}hO06|Q$Ku3RJ2Z8q@qO-T~{3m_NT^MSIkse39r zIbYFR!>a|Bk}T?Q^kqQPYVy*&=5E5KVtQR>gg&5%spxeYD}-VHixx8GI2)|Qwh@y> zusrmcgqXqslmT@D97$3qa4(zipV%JdpT5~BZ+3KV!trAF`H%Q1lbm9EkO z0bEvzZb$iZj{Z5BxDs}D`~bFiLg4&%ocsimnSH~}Vs205yS`XFzi|J)9EG>q+=6p~ zR@?Pn%OzMVTg1`s+gF$6GDpWbi6gPAxPGVf06%{1V? z?EQBQ6I2Q~-LLz)tu>V4NIwB)@UQWW?6ub;K3)(5gk%c`ozPkDrrt%oA`{?77UC-q zw@Fw;vwWldZ7G7)xjo=&D>4CA&G*V|vc01wem9Hz%&stuK z&ds)#fk;AZqQn{wOm=SUdFcl&)2aMsT96Wr2>K;1*6DD?OpeD!I$;1TxK!HI3G3*Y9>GdH7vJeT%eZ zs4+X}HWEiG-Od0*Boq;D(eHvfCF41sCO66r=RK43A=XH$LL<1XSdz$2*LXIk@>tJ) z27IY$iDkppLbIe89~@;N_P#}$ia7eFlVIvLR}JU8 zcPjyS_fT@d&0CF4H#7xaae&`nS}}-^iuO&rDg%)W;cEtMAlvD@Dl7LXpRKpqxvv73 z>jRZ?v9)~(kPQF?AP8v{D%pR68@x@^n7;Hm1f{T?sELQJpx@v}zz=pfA?9ll&P%Ob znewglv1xLN;MiQJJ}ih8-a$GiC9+IfKcW!cf!Q<0KX~7iFL08O>KyMS%2s=of1<+S zYQc|?7A+)H7BH!!g@@FRj`ze!o|HrAnCfm-=c_RdUd!_Eigbek{f3B!L0v6w6=jQg zq&5bSCdY>WxPtSWbknDlHzH)Z)y#3NJu09VE3&wOShb@sH0ao$>9{1si_ygU%tn_S zzbtd89*VDwJ{E2|6(zO|6_hLi@z;M%6UZ4a5TiUKQvPre#@2^?BaYz3d!$ue-j3XL z?-0i0XPFn(f?_vzJwAwk+z~fsWAIWuN=mO7{oKINP+@Py;2kb!rGKtJ06+_Xo50df zoykTYMbddQ)je3+S<;wB{-BOhk_ThJrUNqEC<#f5Z$`L!y7E!Y;R?l@{~VLO$P%GP zMBbyiG!!eBgFR5<{g7rw8OR7(z(FLWp<4H#N|bM}s+~4EXDKId33ESX_Ux|kb(Gcx zoIj+2T7?9MLt)D3Ed-qiKiwWh`TvJCaHyNzKx*tzlQ9rBRH9%rZHOe^?z)mmNmHivt<&uNy> zceezZR*hd=Ue4#9m6y=MhfX|77p-{?+2QznPo15T9r2)K#du&WwbuQ18HxI0RPLy*dnQo~(WN%se!KhL2XFDKRa6X;ofMoW1selX)Z1hYMA&-L`o z%!0BBB#S6UaKE?N%=&c0>boyrB;S+6_b}C^*IZJq-$x3Hze&aqaeGs@;d%uag%~$I zW9?v}p@DZevn1f_H4L0xot9x*kHiM15K9Gk@E!D%$evg?y1O@CA3J#2VT@n|PL2dj zZzaTgW@9lmNBWA0POU%S{}fP}T?tLP4pEQfju=rMSm>&_(lRFmWMuQa9hMuu{UGG> zMZVE_|Fmd1FIm^T3@T01g2;qmF3yT1<=IISqS=Ru=xyeW8He|&L1mwt&YEqcim3&` z?i-be2%+s>d{o1|CaQRK}$aQLym62U_ zkKlwN8m}!PFL|hGv-g~wV8Zaa2=W29iQdxZ{2GvEbJr*zIUV})F*OYPLF{JKJ(-_5 z$;S?{em2EY@CAS(QX3BnpRjfrd%raY;o+y>qDNse6{Qej_`D#2S3LzV4wgj_^-M<7z8v$ z7)8`{xzb8~=RX$Imi}PRZAO1nj+vy1nPlZ!@zTlW7Ze=ye}ICEo|MDH=rKA+!`jo2 zFvzf5&^NYGm}%bbg#ngApYCwI*!;$+VCe0V4p$z_-G^w^VXdn%t5<(skob=mkZJk; z`~1J{^Y8fh|F%COU{qC&|FQk>l8TOF_ccV}jdQvI zD!;GA$hRJMp&r)Ot?!Ad-eE1aF#JqINLlttoG`lN6OhQMPl&Fym#7zDdhmo_HT{93 z=sP4qtgzP?J7#qik$~na5<5tBv2SWL9|Y1Sx7KqFrR_=TWXUxs$-`qK zaWQY_JPfO z%-mBa$Z)5X!{f^?cFO)-E??%||AI)&h0|AbJ?+b(3ukL35!V zsBU;UAm$|@*Bs7=kZ!i_Z(|3W!JpDsct~!#4Lr7T5^qdn^{FM!qEAu=(r~ss=o~Lm zI{@%{3m8F`&n!sx%=OGhF@xG#wCOe5mS^rr&7|b0u6rZ(*=QeU2M6I|e?b8vDx=eW z?-1Mn2fyab!u-nAIS{d_DSy8ntqyZ4>ffDu2y+$8Srqk+Y;6`Okpd3pw5+4)+p&9` zr}QG6!#jnawXA*H685tct$BX05vovKbs^kuZs)K|+yPtu#c9AO_nIuUTx4rWsXlD) zLc{~La`fj3vZ0djmFaJlO`x{qt>6w-h2QsHhtF)`M&v?ri*Bj+L z?Z{(M=Gi3@{7b|J$T8tYWhUO9oJ>mTKuvM7A%>p9xrzPlzm1}iC^fYcORx>KUF8*- zVGzxee)`@|7NU1-EJQd6CENF#2BA0TzgG)Oe(4QfD&HX^U=t844U3_iOS0 z`?vY4v~ZVd;%Y^3TBmX4`X!*7{@DsGLoy3eQqAI+y#31`aF>uIu!q4xuRF=R{1e&ck0 z6i-~@e-e1bIZ!xL{sH*!E+F5QgypM>MD{*)#KRauMH;eUIOwWR;*U0AFfK6|5fSOC z*g(`Dfd3ep)6Y%}_KPF`G3<5*>rehKp|+reSAPKhE@}0&81&>NcZYRW@{4H2Y_RTjShhv_ieNNkaj$K7{?edU6{inVeyYxsL<|hsvnbwph+LU3CgRq1xzv3!PGG)Zg++fIYms@B{LWb7~4_z>5`iB-?)|z|_?-MkA*RidpNyx+ILF}rj-B88bv-B*nUdscB zlbnmS^EJq^Xj?YCn|rE|#5+1c{;S3-O`Kj+3aig-X5r>)AdHR_Vr8VS(&dDEB!_A3 zQ0i2l;*eU7##D{gtR(KI?5))Td^Hk{RSicN*98t$DWRM0(gS~IZo-ul#)yItfXdVS=<%=@> zc!h5VDqyjpS?=>fJbRK!bQzzj!0Xf^?Z^9&$HAwfmTaLY`i;1AvJfLsOwnyn=TWzG zYsl9*!Y1gp`2|g*Z0HpMuRuI0#{loCD2bJkmLqwbvQ1`I$&!u9QD-ZDBZdQoaX_;u z4e4^^#lEZTV{36aC*j7AOBlX!jWq*KF@!oMIA%L2CV#}NQf((O*30%a#l7y8!??q%4lWid#!}HL6tK^COcYEp-BO z;9_2lEnAg*&@ip7(-=p(GP0TWGdS{>&{as=T#q+ZvWISy1Lmj~(@eC=_gX3$$3 zy~_i*GL*`_UzN=;=Gg$`tgP~@q%ui$Oh0id;G-xx4!buh4|+mE!n77q{_WebE$x|l zCaIqC^_eHAwX@O~wkA7w=8%k$XrI@pvmsvSlpoH1YG$q((3+)ExYyG*08~tpNB`Ql z-w~zIdXXRVb0^?R2&mpTTNfqtZq6gYmIo{kr{eavz3MySxt~J2_IF z5~)KH!q!e~^jH<#npQ>z*;RD=6d0~8Ea@+c+n!T;*>Jej7RS*f#H+uIj(6&N-fHY? zS3ED~Ye@@P+(1ZZdZLEWmw&)>u@^+k_nk+~$XQwjrhBnVw3=yNQheIGDEW$)3SxmD z_rpksmL(djF6w5(tVt=!DI!*;#zAIr7n|a-RbVFW7=^)ti zAr}I)`k2-;Yk56%;T~vMdr8yUIAQ-K z)JLgy)E)9gRrbTki3N$2NcaS@Ws@Z#;bqBU=oh54g4OqmvXN7-QkByiW>ov(=bp87 zbbZBaojP?z%gt-!5WD8!40aAXs%2bDBj~*9iuE_?%lfjaw{J)RJ#zH`d0mzU$DZu= z8nt?TCaLBKdN|t7m?w^vrSZp73^Z#Bp%y=H2_?-13PQV)2vt$8@OB%YUbfW4K4H9+ zw=&LpSFf`b9L&R<_>vwnFcL#(zO1NtqM+3}S&x#}I`e@mThZ$VA&i5utjJ z1;VS+ppI}euT&r^#C>ppM#NWflCNF9E=~nz`=$X;mDN>Sc!tql5aCd|qGuomqEmP> z0ZcGBl8R*_%U=Nnw~&cBQw^KG;+-+|Rif7#NDH;uK1dx7gDj*h0e-$8#)M??7H*T3 ztA(FW+b=*iXE#S7tXVu+yUf^H)73`E6_Nn^N%cA(uVYR*G*H2c6JDLu(88EDEy9zV zyFV&z&c8moXNg|z|B3$Ito?fKF<9-X8EY3rzxKSGEZod8vi3g^j7oZ^%KvtkUBT5n z`=4QuD@E?dyK=Y~e^;=CwES7TVtl=KGk;y`CIzFq%zHvAA7*rXd-3idp)WsUew~&W ztl&Cvi@w)%&yd(~)?PkV%Wn(8pvPdVT;1JWl!;JY^YVa8T1uR1bZysU(#3}`fCkjD zc9wQYVA3i2JL`M4E)zgp{4froQF%G z0dYKby`dUrY1?9;U?iYk(&8X>Co_=>TagB`>Dx!klntXeX+moXy zW(1WA_wPTb3dM}(_x3NEH@f23S_OVys3KJQyc`YtL?I@1xb;zQn*Gu|O=v2IoB|#c z6-arJ#N02^BSvl-(q9Q6;#83Yz>1Xqj~^C8u%>uSq5T6F4GV|MLFoq#LCeUC!ZVI9 zw(=bxd~J5r-nVtY9vSocreuf~6)1E&9?V364mi_z5?T*4jX{uIacJgx=xvKVM@gq#y>bC&cASmSpOgxU!Tm4Dy{$rijFM&bk z&XI7KxHLrQuj#C|lZ|#IVB)#Mq)8&+r>t_)Ujoi@RW6iRjNU4+8MA)PANNZ?KI(bC z1Y`&dJFh=q>G%A<1c<>$!~;RY|5o___ZGIr{bJ+rAAx3d?F{oqFaKs%`FTD) zOU>t*P?WKygDiWaR*M4AF+XXU^ExvS46cM&z+mC}zx@Gl)lB;POLeVV466_3;!|m_ z9(}u??qLm9PWvigm6^Gc^vG?+kwfp?FF93Ynr((@BB!arhgyFCF2|qlD#qnl;aDOpdkL8d$F7Ul{DaRcIBKhLou=@sW8gkfYE=(D@SQ8Mynl3E(o1 zS_6QG576)b%lv;=kaUsa$jnt$PmJ@&C>8B_mu$p>7_Cfa=P@j?{ND&%YO($0rN8Xg z5bi@rB7`^cSDgIi)#Dt}K4~RMm%jq#|6=bg!{S<&z40MdwfbBcH#z=)>F`q5?I&&+3T*$_q&|QX_hn#Zy-?wSw@{2;=bvNwSIhqp!T<5h z{tpEX{QrB0-?=MpQ3@Vi$cLt=_1Z+wVS1Y{CsgkeK?_;s=;*Oj%QYvae&@DDWElT1 z&#Cw{>UrfK9#!Ma_VHiJEdKMb0%=eE_f%-&JSWYY`4$iC*(Z^9c(whym-|gpN9oL* zGM)&rZ`f5x-^Ggx0Du2c4{uU(^$mE3)^si1M6jRT9YdmP-|=Ab1Bt{~cx9|7AB((Y zLm%O)PwGd!lS4=#yAU+o@7p}OmPcnd{tW_Y(0pau84@x)e3397UL_){vSN%zE&iFZ z>+>*-re$?vXE3Lrt9-t7<(^0WGXIfO#LdcuW{$UFpFGswRH#fhza{|*P-G{f4pf>+vJ(TKlRibvZu zsSmGg4ew0gV-CdK+5F+UTwRMnuHAI18CHb21k|6;jVo0~OEyoiIQVY-=FGX*$Gie` zfmUdiW-yG4YU7BnTec|`|3ZxAQ%ox27w5$&ORD@B$SX;G!{IjuQ0{^x^RV>=*{$x+ zc3u*k&=`2Y=IJaDFrtjI!t&E3 zxK`db+^qzag>?fs2hr8(`D~)|Fc)ryDa>^I$=<=Oj8hSR8(FnrV7!w@L@tY_#ty3x z*_sr1_fo}{j4M)JK$>g#op_=0*2^n?Nok(Oy?WZJ49{{*AFVG>^y4fOIE6MzDSipk zw6cyCIy4Kui7qI+GHYV5rnE(iY+k|kuCoNV)AMFBsKl1N?RMM-Xef?|o>A%ef*u*& zu?Vg8-0$ZH*f!(nK+Do$^Gn#e9F=Hr(Y-FGPh*$3aP3lNa_fQ&++H>FIW0iB+%uCT zd2OBC1Z(@OzD_>mxeefK<}={Vg`v@ze3{3JR?1j3r@OKUN(wE*2RTkjLAPZYqp5j6 zL$n*0-vByQlgHcgRtTr1b;ktYTr)fxvnUr?grJ2$0Vd{z@ds;+X;6W8p>S;Vci5R; z{@l=HC)??@#vwuj4MvVo)iqyvh~kv#)M3;qbg2X0p3&W5tX|AD44xX?{$5gyfT36~ zDUa;ivw^E?*CYEZR{I65U1drccu;;d%_%r4>CUf+wtGhvB*$tRwjdL)7%CZ^Y4)2bn#R1nfxQ)pts=OvHvp}d+NYcrI`fyO@mu+&F&cA+Q=v(nEitTF^U{~6N_xZnJ$o_x)hR)x|$8xHZ;Hw=G@T591=SoXWVa&kyKRL081Wo>>pvr zwtgw1|9nx*rLCu!sp5~qqnt$Bs%-&!W z`kCul_xV>YpElJTh219-UsNI>S34vk1Pb$4?JeQ#6=9V>n|b3m}lf&%X21<0*0V z#;ft<{DJPA@Jhrde$~u4>Y3aj*r{TJR7Cb5pt`L5lE1>3mqXktvgIu%8qbwVOPX=o z4p7UqiTeR41nf%g6)@4$8N|qY9GZ>qNAURGCA5_!+7^`?oojsEOEkit^8-xPGUF#z zu4`aD%QL3iBEio60uPDXgc$v!iB24paM!9DEyuGjr>}HLx%))N21n$o0HbHMLaCH~f z+0QsUJLP+p;`a2vEign2F5ZmH%*c3i z4J#kKTtu^+63Tl~ax_0ych(HzMTAwLFz8!=v5V1N@P?~Q!1iZoTu!)IsjT7Ggg^kP z9L;lxGU0qL?SW5p=+igayd0w@Ln_j-qE2&$Axumpfip&$IjHO$tN|uaLPfON&x7N< zeIB+CI1q;%!zwzlJ~IoVO($8Qiz04AdpBrQ3w=#!|Ix&;R(()n8VaM zV43&C`wXyI&;G@nq>^mi;zi}7kRfw>B&bN}W=G5dx;@4=mK zr>(y$_eGUBX^lz@w|UHQP8bCvp|BF)i1RT12RHj)G!6VD-Ia>2P=R_ z6@J>8x-zDP)>pHULZEu5%=2;6fmm^q*S@5%Dl~dV^ivgOM{Z@*@8bC!Yvt1M*WWHv zM&njD80`=O$T+R4@y5$w(xipaT3(T7s}&c6jiIzG!|3klzeN*~NF!v9r{o$`6ViF8 zCK_#rtxJC3xuXQyI2bKLnLXIQuLFC+gla$sCREv@nMX~jMp9%_rYl~Mk%9F~9OWs8 z!vjp{V2M>?cHk-pG7C{S0!|GNGkedO%~f5j&4PABxk-I7#|sv(BSmif&VK5#78v8#xuY#?MG5=!-g}MSlg*F{ zpsFshei@~-M3-|DS`+W+xGL|J=uS~kEC2597MmnrMNJIx!D2z|=*^=`(MC8rQ2fOz zTr$4O2GEt)#i0sRw;FQ(n1<(#ugpGZz@r;M4fDq0UprIX3=B~Fr6fkfOhI~9i(AuM z+AL(mFFCJ;PNR0>q`n?SXHdmZeP??xWU{(TJKar5tevh+FqBBybHW$^;*Usa6F52IysjJA)|3}f%T`a=}A5cZ4_pY zT#-(f+k3;5rrXfg2b7v?Y!iqnbkKXDbG~GesY3m2} zv#?QCi1V(L0aQ&@88*Kqsa8-iodOtNHHT~LGg;Js8G-Euo-usJUpnk+Pi%#Thk(b_ z3rw{q%zXx@_D623`X9l0llmclpXD0Vr16i)JfyvU1m%6Ig;kub=$Hw$T!8JQ&92%? zG+Fr}xh}#2f9C>W*|C5Mk(1=mOWg@w~<% zPIdf_!28lbUqCkNX&X1HZleR@;`LLENT%ym30g`0G;4-hvU;Sd^gYK*M%g4$mhyuQ zE1?}>3>G8IsZ1bq?zBBr^RrsAik#luE$cR=v_w!965;9&HGBpCQq21rE7rUe=4Qv5 z#YmlrbtGvvaq18#;Cv#p`P3uMA}B9OP4Bc6&opQ$#a*oj07&RcQ-S9;Bv*%_eQ`g{ zTcxB&Pb@Kq6Zd;OAFL@q?67wo@i{+KGx7?!*X~QXyE|h35>6e-xWLfA8Bahz+bRP) zW&p*{B%SwMHGL*(L7^c z$8tnD@v&A0BBucXdz4X<2+l|rC|Tk#>{>zac1zvesCp#iE`5Ekdg;x+lRRQ>(kvan zMD9D2huqIuM3g8S7JRr?gLT zkLi9H7FOftJjcO0*zzPFWJt&^eR{jK1fmyDptOTBXtL2R#^xa|9NKVwot+3Dkpda4 z1=woMi);xzwSC-jR6kI@t#^7kLlfohn%HFqYPl?J4tTcAmRLHEA+Zc$zR_YhEH!m@ zL`8`2BpTs;hFg3{NY*l|Y`V^hrO|LNl2N*M?Rv<%1zn@a2dRr*MRO$OAw04yVj`K`dX_w(5 zgj;CMxoH!W^1*dcL*Ir?phmQ=E843h5C+|nvT*bzEtf}QmBGU)x06i4gozi*as_hTiYJ)YDyFTG)Tx z@>57_f_DJ-We`YN2$a+6ZUr6JdB*uB__Y+kRRHwayTV4xle6C(FAC>}I}VzRetT>f zif;IPLaf&aDj^Z~Eg z`D<9O%hhuo+Eo*h_gjC#HpMn|SPNMxq%Q0M&?WFxinNxtt`lBlj^_o-0xM!sVo&6l zlqOI+e?mC)-3QsDOj=Iak_)WI`)SK2m8uL{n~Qmqa-R-I;Qf9eM1?GrS@C|PZD=;1 zDYds>#a|1NoEqHjd;WOdn^Z|&Lgov=xML`NbTE8}LAI_)hEmY3BJ_3ZzSQLnSjO%; z6XQ8ESOk_@TdC_OMG(gg$>J`{^{C_u)5M^R3|;i(vd$>l70_g^Ac4}(L|u&!vzDD3 z^w5~hNH^%3bj-bQL9y@H^caOt0sJg1<%dSZkwG3>~;d(#%g@mCuz)@9<6a#OFH)<6IlL9-+6C|F` ziXSsz?bbT#tm;5Fc4-jxJt+qVK4(rCYq7TvaGB9AJ~bqj-XGxA_eP-2NfL`_+4B4% zHaA+KlxQnIeGI9n(6gypki!R4CGoQ!F1)Fn{u+h|r77 z>M#mFyHlVLIOu@qmRsD5E7ql_+=f-% zXJuD&S=U;}AK&XO6Y8?*+CP-X;7|V`Q|4#go6NHwgOf7*S^sLf#pAP7ZSWdcTeo|q z1FW7%y-j{LCf#~tdP%7F8z3rg?Bem&!oDrDqRmZrHEJAK&@YF;zqsQ&;`5;9B3K5Y zrId?{*S*Z-1Rr|^+4in<-eyfzf@zW@)gd_#XVzDAFz$Je)sk0%Qg!^ILcIyw!GGrG*dTmORyL-w-_2<;53}U-X``ng-(Rh{h0L#A|f#0vn7V+Z6 z-6HFc_`Kh7O0@$+TJh$X`U-^c!uxt9lpQ>*;Ga9*Jf%MF*Al!d;e7~De7e+&%=13K z`8HNZ|NEwu#^f|Yu}Zd5+HeHdy+F&Tgkv!_1j74jmyYSE$2PT}jWFs^ zX@IE}S6u+Rt`i1H`ua~I{vhVp-4k2E zJ}wryTWJW{zNAS4M7OoPO3=B9UK_?Ji$B`d00!o|O1hy1f%jO1xpdXArH z<@BYY!-i_G?!rr-t1aERYGb?|!UbvnLIO^~Cg!;6n{R8jk1oP=XjgBB?kQutyZH6_ zy6RrLcp+yUY6(eh9An*BJPD%Rf5U*5AJd^kMV0J3Pu`9R=HzF8cf%sG0@F1&)lE1= z|IlZuiFy(G!>vSG7m_!GA4uw@)dsh>%`@E|J! zPJv?Ppi}zpVV=nj`-~iFWT$j_kLl(@F(X@vPx`?7+SzOl&tQKE*uDe z`hfu-OSCbc^`>aS_4ZHh!tVqL-H~My>)xJA^nv{0ghfeIXlX`ffZOh{4K1&L9|95I zr`f>7LC9c0)ORL1z%#*3GGj7b0 zhVOh)k1UgXT_YaCiDsyGd-LA6okD^>tlbNH=yxr+Zw511M57MASdvk_*5}^?KHJ?b zESLVV{yy}Q6*kk$w?k)jbB5XGAobl+8Ih$Jn}qi_5o;@$HZT=!NRjVRImTO9!6Ja4h7reNw@HbFul`;2^9W{A^sMYAiwec0W; zFuUNU{oD|fcF3FFQ{kaZtTAz<9M}_px5v0H=Z2r&wuFwfuKN8T8Ze^Ugo*OdV_eZw zfy&*L)!d+?`%v3tYxyGK>574Ld{NcFF`S#7zSoGf&hVqHW&`@i9umZfpZmAB)(iFB zD;UaqFkyNCtf<>0A>hj%9qsQhmOs^jO z%*?;DAw^UvvgB@d;*S_IQqOqH9JIEDe}s{H`Dk2*nsbEh@vng<^=qQ&iC>pOe*@gy zkRJgj{=9>rTRI=QR>K8R-N?**JhTh~iqtSYz*ho1E`dCQRc+w_JD z@s{2sJ)zCNLL=hpi}&Y;M}=!%``XyMNH`C=rJrb)m`dGmtZgJIp5fzT=iY5`FBQ&D zv=?qZY;SEw#)aqU^>@q+5Xv!?#ii%+B z+R)icc&BYwggtc5Vnyo&&j-(f@)6L|^!MiD+|gT$Eg&HOeoo+~O#A)rdhRUi7eyK*eo zUs;&zO7QAqecnxi`V?2Hylr1C-}=&ylIiPrKnS^_O>A6_{Ew- zKlc~UVP}$w_y#kz*MWeg*D(Bz7#yp+{HzLnrMg~~iJ3WwUC%ufG1bEl0=5DNbkRhaZs%wCAb zh7gNe)jKlTu=Bj@I{QYX;I(zq6+VS*w^K*c8*gKpc@3TX^*#mh_YnVmDQiX7o&YHe;lj13gnbU`LM2D#TjgJ)JTqGT1+B$zlgVt4IjPOk;6Run;0L$gXF~p4Od`oqQNey4m`TxBzBcSa&WvTJ z&buX_*r2JY5`ny>BLp-(I9OnFM^?MGr$ua=u5_-_){?ULPK%>W=!-UWp$b2Byr4xZ zb3iLcJbgSr8J9Kn2!sN=k&sWgPz@(~gzET*-qLjKcCm5Y(^SJWt7FgCjLnyw-x1DX zBU6)uaQYW(pR^TmU(~b2R-{(w?+f}3t#gIqeO78jYBv0RHqt*c@%y%8y;peRVB9j* zs?#Z}TIxm=aa2-N2|%-nBrBGx%Wz|}V`1%Sozdf05Pawna*q9hF@nXn-t7O6`HV>< zbNmf}!dBp&XOk2M;eyHueXg0zZ}a`6YwEw5v=97&7CKsyS@b!WZrWtZBvr0<0^1jr z)%1hgHRmlzW;ovIg|Tx~hRp@*ul5Q?dip@`h+%jSA~fUd?#yU{I$T>{6-*k>(aTQ{ z7jvw-b}Dx=9NhfmvzKkKtrtMaEFAkjc4air{BB*C8MURUV^M^qoy5w1m$tkiLF&*{ zv1#{5U!}H|ldFW>+OkDYb;HtZ--rP>F$s23e){k0)d?NEPLgb2xO|BV!CUF>sK+!5 zRMu#*W5JxO*nAXZn6?xDl92wIm=}9{<$DYEd3nQiB|_Cb?yQ~4)!#IbFjM7>F=ab< z3h;cjPNvSh)aAxY(+%c^Ej(pCj${o{MGaWpnt!R)FVP@udP;CD-1kB3Cg#yuA=+M? z6Z9)E5?0dDFd`l!hQ&qP+r1E6-1p>FjfM9E-5Q+El9mMglKMTvg-&zdud2H)wkd8qVOqAY zgC5{H!aU~rOHWGD6Z&FAC;h04vZko(s})~CHk4B4OGED$-=6bzH5CzxA^EttPUIwX z`J~0~`J~T8BTkL&l@yGcu`h5o6Ml&^0HE;mBNO)h1YRiqQwXW}mZ-bx>$IB?2leOm zBXu3N8-Yu6#YEcA;p~!xIlV^n?7f=h8)sUW0}Ax)&UeIcygSx{HlOe+e;;FG`jLpH zEr~pkaMrH-cb9A=7Hy37jzHbCwQM1ONjT)a{r4@ z4a7;Zv&R4zX@L+B^Z4(>OG%Y*QphvKo6fD&iS)wnLerV1m7z!`!JBgp1)!6FYqJzq zj-`JC_ym6*|K}Z&2oMGp=)-$I2EUsRGjRc}fdBTNJPa|+KLPpv%cf<-f!(+i-Y(cl zRSFjXyWos|r4NfOF~LC6XFlSgxs^X}C*}qHun*YydBUBgd$pyu!P0?FCRbvy4L9S} z`+%-3h1969T}zvtb#GYz@Bm8%^aqN}+W!U+eHpM~QrAS)J*6RPTlX6vBfR~%QfA?> zeP^Bh{y^0YANy``Y0dzOv|tgDvlO*K5j9I9LKZ=7<-mR|Nx?`Vd-5)oww8~7^+f-) z$K(CqHo*sFhc|>;Yu9COLl(~T-&o^4reV`$lDN21LYz2pV@w$DEQ8BgsAj&kSDE=M z5mNC$#)lhF-U|k~yr;a|bTth4lb)+)$J?98r+ZvOM36KV(mu}umy=A91e@`c*3&!@ zjTa@H#O*txL3>e-pPdc>63uAQE?$qxc>_Zmj!~gkH49&&V(qZDC24#C{SdUBqScSw zlm6wzxp5$Hw}(lE2@`w!?wN- zw_`0mQoCB3!@>3CWt$~=y1Mz7>>XFwTvZOo70PGY)TqyxhZTP3Y&yq9ra09Kq9E!-q`#i^~7TiL8QMxdMaRpN0FL$kv9*yf9!ZC*PTXAj5b#(0H~ zg3dQH+{d4eY1$jxtJ{Lu$hBhBqAfmEMb{0&NroY(;rBh1W|d3Z1d;ieUNU)krvEtLE!3bxHKp$nD62P>A>- zL#E)`|1{|SnHt-{lEXJD6GyC>jo&!|3Eg_Ua=6b#)KI7aAy3>i%QV$BiDf&V)va@G z4YMX3(ov6VlQ>cCaPr4|}6+Wr1|F6Yo=<@}W3@rt1d zjU+#M^-)H#b-d+J4b{%gJxS0iezBxUAQ5q{au6=413uXIbhg<}+75pKwJR#&qVVZY zt&jPmz3g2U3oq--2)DhhnXq}IeXV+FDQz8Iauz-{=>y+8%e0x{zn40bq>?tJb_Lt9G|CaCf3eJ| zbOuQovHt#6A~3#57>a)Xk__d#O!N;H{Wq4kW_+s1ZV47lmsnQhfvRVy17StL1}0?B z__R#AKPVJr?!XuDS6XJ;l$7qzde$c?I~KjI?2GN%@59rTMl}(Q1T$y;hv9b@1Ueoa zM1#vp#A%pwMB>Qj%Yb-l>ewWegMMkCjB<|_?zMR?>jLmPEAMC=J_l#%3t*Nf}*JF(-yw~+mGiQqP|DmZK5dmjA45VJ-j}@(LDZJZp{>} z-g09Y*{=B%(qs(JnU08#)7C>=f6^WQz)SQ$IlH=Y$0~vfJ!kbG%JlT|i~XkaB6aq4X&mU*Vwv+*;q}D0)AzNea z-tK6E(@00_>+-u%0W1UOax`YHXHDLzu47S~ZoP_sD=#KCTNjKeioV?`!3)c*EzwxX z+Y9|Ta#cd4#j7}@WQpVdfvDk=L0haGMvB@Je4wUOqS2 ze?TFcY*mcf3E~?ymGQT>mcviq*1332JR90^P(Milw)2baiX!S4$;BuZ))$5=5B{&7 zz_dfa)<3`ZoOG~*&pnv;rs`IFwpPArP7%ELQp=P?J3}-g9nHk-e(+b}m*1o9+}5rv z+@Do)<2TAs7>;vm_K`c(nRX`n)VCYU zd7aP=m5mw;adH7oGsXZ)?ll!uEdQrLYplvAYVRNa*fg+?= z!IUtkTZH=LD(A#eZtOspW?BLYNs1sk$5xF#jPkr~JZ@^$hZw!`_w6J|!A!mKwfG`U zh0X&BGSh%k#L87t_Rsb0 znKz38UxlTYziv)pZpQ+~Rn5 zREUHNnY|rXtD3U4w&bXOwzK6AZ+`<=g3gu{^9HlbnL}4!U|q}p1_)elwOEHt9A?|5 z>d;x~yGhIbz!k|6#v(0=Jmvk|VkjCOPy?$U!F1MF*b`QQxfyYSq!I+4-{%+JMQTdB z?kXvTLk+t+h&=N#~+Wr}Gr?{p{SRV|prCkD$2;Dbq>URy=9( zhzs?FRLe9r5hBB-RuqytgD`yj^e|%(CpKY7qYiKp6WVqSe0tmrH4 zCq4fSEUF3Xqt$cM>Nrz1IW>`!r?6HJVV*9Ubn2?Qw+%EyCD=iX!k*5f>N8o2$TFaw z1JemRwGqk2!uy9Yqm$NTqW886>No@QYKT1J6A>#WV zFMY0oqMK?n0Y4!DkNDF)fTuD5jx7sp@x;kbr ze1aJmnaK#Qz3@MR|eBJzp2NY;_!G~AS0g?`Pr^3W88$SjU;662C;F|Td6Hf zFpS*D5`LJBQTCgyh3Y})p=GUQEh-X?-Od!6M4Kmk#WM?Z36>tS9!-W{2w2mG@tT;^ zego7XSrIU96W~j=@!wdWom4Ag#^w#WpaZMUZ_kaIiVL$eB?@-|9e8Lu3*7t5wWUaV z1U4h4#2ptHvm_ZK1)0`5C#j>jB1N$H0~3+5f*TKpWgW#&JhBe#AVR>9Hw_q5!0e(l zT_fxfd;yoXp=s>jKC9hc7oFD%3s;9KR`X_@pBSstKVC*nkP^J(2_D-^lAaU&N_j?L z?2uZwL3Eu7miC_QzM%@~egeFdE~K8)e8B*j#=-|&6$>N-jo!mtbs4S67IwVsP~RMj z7*`Lz4OjO|fNPZTtILu|V);hO`0W`~F#Zyb9HA-ErJ4^JvhusI4Vg){W9pX1$;nf8?l2n(S!9JT*_T*pUWKuaNiPFqx5e z!(EsfPgq;@mOe*5ye|?Lirt6ksPWBm`hOEgU@w9Y>?d{y*zg3cbvKR) zy|*p2E@JP`-uKv_h{?$R(cHKct9`!SdM`G%McK-gwDC=x@%;$O>=p)$L{znVnZvd@svx-%(Qc|fh zmKK?H#s}f`^Xx^U^rMSMH45r)BXZERFoXoi;_5Ztk=opXvMb)u%y&5<(fuv2PP03l zzT(*?{J0{8>i5=KylNFo-iPCJ{;0SZ-8)Z2wgf&q!S;@92Wz$dM?i|0K{;IV7F?${ zeH#tjbLy(tTpH1us?vBG#&ElgQ@C}oPi0UjuBsT~U#{l`=vb|8iNm1mrgzfZK8Sy& z^Zl31J}vGixUrjon(wg=)W}&M9`*|IXy_lA$gg|P%Vm#1MRxYLs=oo81DJ$hR%|CF zV1?^0%=YVA5+PJD-yeuvj=BTg;@;IIAuxUT=L1wROrp2j8P|OU4r)E(M5NNzR#j+~ zV2yzDVbS9_nMCr3bLIO@HSyPA&NWhiNrQ8+lXFI%5NBL$M~up^N8A4LsD`$ut+mZ8 z5^o|dz9Tyk06n1}cdy(ucy3X|BM=2(7ViUhq>QVJ2+Pg>XU{nUF;CtFk7tb0t|a!) zWj%yim@*8^egja;)2#DTDT0F$Ql;M5B!4*R2iDEvZ3V`FChnV2t^EHP6CNE#!oOJ=0*QGK-Dc+VA1cWi-Wdb z8l}zAOQTfBn~F$dZjaUCaj}&En<*^82abPz8V{pveW_;P1zi==4A`6rtg z{x!aW!u_izgCyq>oz|r;eMnKTCy$e|FyBvYzwUFP)}QS}P_Xk)AdGP3zp}bJkW&e{ z6*18#R`BfaW>Jl6yjbuEfqofqA(EjtD)5*lfZ#K{K5FhTUQ%Ts{Wc;EKqT$mzX#)Y zipXCiK*c;t+KiJze7YVT3zCr*^@{+@zj6+@f=aqMeOlT%i7zWx%3RAGeZ>u@{|?3o z6K|LZk8;8GX{5;h*|MbkD?aHs*bzHoAYD;g8DM5(LX{?dv&%9zllX(enmoyALok4d z-ui%{3ax7AuU+9q7TlEgs?Ds(g%RhaV|1|WJ|m|bCr{fcjRXn%Xw4<@u)r5FUVs01 zJoEQ#mMZ`*6C~+A%VxBo|9)9m>Uda7Ovr9;js?s5iK|yAK?h+`hfKiUrTf>^{3&{g zW-=-R`vYDGBq5rPwerH;l)jDKCf4x?t7*+VV|>@bF!ooC{*`9j)y^=DU@T|dCP*zN zh*h=hh^?G$i$fm@WlD|8-$PT}TV2DfW*5^NhYlVuH8C0EhhVF&QEz3k1RJWe9;lB|Xm6`vk zJb>#h5g8gaT0MT{GkF$Drjos8`sU!mA6)a0CG&-$;eTKHe_+H)_2dku&2NA&jBmZN z*GvdEdURER&(^_+l)!(2P5i1-&~$SDhABuV<@3YG_O*XoHRHvtSILB2R`H1%XZ!|` zs(oW>1YBovkG9X7xKLzbei@CARtG-z2BeiQG7^m-JA-^K3h7U`RwuZ8>p-K*`kk-A zM|)=RD7n#BcDK&a-5iMFgrvjIwq^XKoDbK99o7w>%?yyf9xak|rm03%7=utVhN>52qz*eo_X~kv8Ba>w{Feet?;gOGMe4A`EzKhf{wXtrX{^;9#iBJzwx1)|*F~X&(F*ef)t8(OS zFn{aVPJ4i$v}Uyf3z^CF8w!a27GI3e>I)xNAedA}r~cFvn1|))>}YT4NTYR38TCv{ zqWukNF`pnvSeOCRv_89`3=Q&T#R~=;W7|si$i}*vXfY`theK&lL=!u|G-X=wZu(TD z-Z>{y6%X>wnxv;9dSKus0vc$CBo%>}vM1mFW!4-e{rs5w>S#KV0d! z)c?wnqwV80(QTLj8Ei=9*^NQ#^7WH`%ue6CXl4{lRK&Y|Gp$98{53Frwcq4>RjZA+ zym4?WXW49Sn>xx2vmX2u&Js^x$xjnh{~QJ|2aA&-OON7su6#e<`Y=w>3CF+9YT>lAwW_#w)fhK-Ou~~Kz<%orb*5GBvH&j{_;fPG@oR7c_ zA;tCPC9=D1hH^K3^Ds&pRl@-z+niuo4aqO0RpR}J^5hYF?qN*YnGJLzKuw##hWSQFk()e`E>TL5R3fO`Nw z%eXDr$#y0Y+K6BZvYR`X_KVN@k;m#O>(d+)XrEh_tq_Hm%BO(WfJ z1I3dpM8%wP;(a_RFwJ~u@w)cOC>kcXNxcsaSo50Y2hm(~zrBU1l#J3l@ERNYyQ&-9 z)N71wPTOBb+R7cV+EX>v^Oz@^ozrj>^Bk-m!)HiN1a8Y%OSxPv)V15M`-{p=f0#@W z9Q4k|(K{w&tY!`qu6IA)<ot4-j#zN9SaueYL8R|EFdmn|$sb2PUjk$8KavB%2-EfN=h5?lwyi1~|=iMHqs%LNIliz0q^G^y$@*rw31%zksh-%iP;& zLOCRb(j3+r_%y81R#=4KHgKEE&`}+RmDgx@#HHW35vPR~PHT!*)M-C}YG(YVlfKsN z0z>fEO1A4S+1!YvQvYx3*w250V7hJm7qFTw(C&T`KQLeXxEIDWF zy^~K|aJz!mCEhNXcT9nPYg_h{VKa&jzcp=yW+y%0BGwR=D9jhaw@4C<;hFBr;SA*y zG1(|2d%83M0(xpc)g^n|(&lwDj8Z-?hFcht?d98B$hOsQrSF0RL>I8>z=pz<1UC@)5`bd=08x%7@TQqVh3zkNxuy-+k-fyF`~r=^I(u_i)I zGg?VI3S%CIsR3Nh4t$dY**HX8aE#rF;gOu3OVcFf?D`#_lHnIV<)$6H!&o8Q-ipXd zsN=X8E=z!SKmca|Y=MAW%^Ahl`nlU{@qg%c&}MjD5I#Vok*cFQmOa-#n=^s7~hxy8_jzQ>8+4xCvhii|Qh zE)`@U7PD8H+42Z&cFL3*XGv0k$`11pftOKajMrM83AHU?i9&s2x*$xvTLcJ};}%3f z3x>&%;q341=$#Mk%va_jU>RiT)6|+n>|;S4r3b+*4j0;By1KSxwwy?jdjR^v$I@qb zbrEj^AWEBanolYscGrux6AYPHZ|pHIA0*KR*RGT9g#F7mr*%!K51^@o&*zuA@C+L| z7W}I#*P^-Y+--vjcd>a&XFajg4SI+_OIt9!{kDkIRa8%6O4!T2pejZ((p0~2{n<}3 zFPhWW8=r`>CT?D~!IVrh899&`K0|pB)HWfcU#j<0u}HU{x<7k4m3Cl@Y*QxkBi6Dq zOVq_}s81^{+z2_QRSIj~R!>{RPVPw$?o}+LV&~9A63;Zuu8}r|o>YnXTcmj7w8)Fz zTmvgMZenIP+0~|KPD3Z9E;)E=YJR*u7(c;3?+lV>1h|?kKGW8vI=WIX&iRK?xu}FD z^t0f7cRd~vTW^X%weEd)GF=9?TnLjW5i;SCva~>}!N!Gsg}eY-(-tc?zHg@;5Ft4^ zd)EnwC^QXqa)j*pIyfNZ9Wg>On#ql5p+ z+RxIO1iudFba6r^wdnzPUFG1ljz-qXmubxoPukZ;zURC@kmt~rU3=ao^`1;j;>K(K?Lk z=R086k^M_Okgpe;s+yN+qUMYgHiI#dEYlC+PC&>U5OCgbVLM1~s5B@EUhd@XJEoe< zdW%gXG1oQnf&{&BsfJ_&oC`F2B57pI>kqLKc)`o`MgF zay3eZ)#aM#M$7MDEme%-6J#Bd;0&R^k%6B*E%)qh!~d*(DjniAX3*k1%+Tm{lq(jo z*#HAf6){1Qp>M})3|L#jfn{|`XXRMKvbxZ=4fJxOgT~B)owW`Q%D8@N)K=DpEiApM zVxUHeOh^diBZ;?ykDWPu8s5M_+Mg6_X}CvqPdOq?jEK%Q(_GefHI7Srv>AT54mhdg zYMhjvqN%N~X{e3gQuS+{XhBq79vP#yOLGSPw93Tdn(*TdAdeJ!lpuzo=xc!Y9I0xe zB1}x~HS?F69Uz)>GQ?sK05@CEnr%ACjmO!FoNPsoe5EfvT&fc-9<+!E4b;$aQ6Zfj zDC9tnVavR;Dvq5>lsrIah~v7mPS;B>b+$bw2$kk0Bf2eLoGA^%^o)FYz1IqLPyPvHv z&}?KD32El{uR9{huryKlo^BeT@lU^!J*5)Nq>t{=s89tb&cZARIk2?s*bn2%P_V1| z79>|!uP-~*(HX=VwdOiXol?DSh$tk_{`ojM`i#>k3(S+gZk3HnC#UHL-vE*ky6o|A zQ$cv4+v(_ti}Fo2mg?AK2Y#wfZdV|sB=tY3Bd1_m)v{ZQHtN5iG$& za1Rok;O@Z*mf#-T3M*WK1qf~dLIrm%f)wuVu7!JWcUiBp*4q2refD|x-q&7xKi+$% zwW?M&=a^&m(dVc!d;j|PInD5Y;KS-2Bj3x=f`D^yB-uz-rHRsFY2>TznO;!6ngt~E z*24+yv%>+*i131)*l@qu*AK%EGbr$~*VlOPP{X@h@IY;ct?W8YM4oQwql@1$_Kt?h zu4dLB0E6E$7qcXb``*848@=e$|3TcS^`}y-devVN7poa?!}bklU41=W4nMi{aTiHf zb40i#j3f+dvA#A|UP%QIq6UI=WV^3nOOOQCnmku?x|tv3lQ3(rAFKeYE3(4eT<)fA zVBWAqm1KV@umX&eJ5}oHD$>u6 z_V{0>J=9A{Xy`H3(>tx+VQ98?_8n3GoY`nhc~fU#@k^eGZES}o)orxEh71TViPr+T zI+z3LhNn&?;L!3xX1I(|ZaT7}p~`w=nS;>@Ww@*KGyyuld0mYn_G`3iC(#a_maFsX zxS)Wx_A(>9o1l-OAE&b?p%J;ZMDOToGV;SZc!yVZaRn3g?@&$aOGtdzR8E-mTF$aGEwx{%i9SX#DLZIxE{a=KfPY@Q6rdR*m$v1>egBqQB4l9I9 zHid79&pasF7WX=!B(n~57LdAd43anEESs7-TFkAu_;d1eGb~~4WRL3`#uVA=9!K(! zgOlg0ODL-ZVG%AC-xSg6H9>uV)YH*7r5=r%bydwjD{H3TR9Vl+S{;-|O;7!BM45&S z6mt!-U*Ofd{?y-RfEcxbhp(!#mw2;Bt_f-MjJXKl1Ud`Xe2s>f~B+O%j9;5(^rg+Ycb8KEB_&aS#|Y??M8r_49PzSQPCunKL0aiJbzJx<(PbE(-doi5}*H zF~gPjwGH)Z_VIES$?pAO2nd9q4inO|E1J~2i}iO~URN|URiEda)MjwcI2GZF-X^wL z5W^%d4&g-JS?~0aN2MpB!}MAkN>c~N&kb&B)ASGfiB~WBXy8mScju&K&i5UI?qqLB zlc8g*=E0i*I4QJg8wTBgTdl2)xog_lPkFkwUZ3F!MV>h*skJd{jrIq|58^TkNC3R{ z9RaG11TdK$_wXL`m(!4PIEs1l`A%!)gyxj6yQU_(Teg19F~4zTCc@`m#5-VZD66f} zDJ`!i$%Ez$y>x907fdfFh0Z#1JgUabiI(djp9@nnu&;r5^rgl z`g8`3CDV^I!59aQ#avVIj;LPeJE!=IZtfM3Eb_>ha(!4)O~pBcR*JaGsW@$PlBI3c zEPXne4O+OT={&cGyZGKtST>*b!;)Uc>qi)0skuWv}4nazM5O zT_8t8V`KOPpLWwE#rYu86E%RJe1w@)e(FKd>kg7yOY+5rrRxfuo>#zvA|TN&qx!7! zLI6N!e}R%G4Ru;PPD#Y8!YM|P7X~mO;XH3g%_^sDJf+CnPzhOPnkHEnVwSVhMXrvp zt#JI2>t^x-Hs7&vXWIWO+ z2IqzEc`0oL2}IdvdMF0OXMKx&g4#z;@^QGpCqmhRJx9Z`P9f<6Sz|m1!t<`9yVQ$f zb`Ryfj-CljKE3p%!-v)ObzEttzO9j8{XG)GIH1{?29v2DzwqNa|X!bnR^wCrTj+I5+hqoSDxGtl5wmJ-4wGujtG5X3_J9h ztLm`U(5==P7O1VzAAm|j-CkO%YHAIJ%~$O-s7wN==VfIV4w-JU48mCByTeFcaOP>h zYB-)_VoxsdyMoMf<~FKxxTJS}M*FgB_wCa}te&CKyU(A_0_IuFUv#>*!;9mmCysM` zpH;H+jK1hD8!a5TRv}z)Su<`|CC~BZT{}K^$zJI=6M1kKgcR}STzOESn_10>gs-Al z&>RaO60f>$fLGvyi)!XvBZ^)JO_n7j*}Uex6WX45_3Z7y%UBeeB&wIAlkdTQP>OORMKjC*Hg+W>c=lMI%DU zEn^*3sH%TA>_QAXjp^xAM9)twyXbRSMMJ1Jr48Ai+Q|dL z`+;Bv!tr3ATiD%EncA#vsXULf$B^iX8@Qh*ez9puwCPeNhHYgjSzRAiPS+H_y>(=k zqyb3su?*<7nSxv5S>Ja4No0>+AJFy34+Gw-)kS?hnIN}q0mpb()h@HkJx3X9vlC~Z zbG?;8&W2n|Dtgfc zF}MKw?`v&m^h>s&FxVZoV!LMqBfY{1+PPi$oU*}_;knAlX6Fjz6|u46K2)%#kEad5 z2v|I#s{i`}m~;5;SOU#P9qzh?&2TMs^PerMwFDEAm4~;I84R^|O##0gon^TVJeWqd zczEumDxhltYp#?cRUH9pQ`a=a54~Q$jS(o?@-dzeEg4qgkZWK1@E}e*Y>5p`A}_yQHMAoeiJ= zJ%gDhIv^oO&v>{BpcEi)CYQOhFsD!IcY4``|t z*vDeU0giaVhzsQVMSprn*R&6_b*YizqJiR@!AAT_Gi9_xH^pg%Y6eq z)tTI8Fzf|YlyGpATm1(W%I>o>FA9F5vd7;JE=@X2_m-!O>jP&cH1r1)uy$Q&sA6aG zGxOx#TpBM>OvB2))D#RaP~WGnl)E1tP(h+toz;R5CUP7+uuS5M)&s)mJ`EHYn*Nt51=<<=hi8(@-N`eLf;f^^-Q<^9p}_^7jD7WT(In zkKv0q4DUZGybws{4)FU!Rw2Ta8khk=R?UD8WLgM`kBc%4(18W6YuI<0RNIgi`uvik z^l)Yttk{l1>njE{yit8c33y5+~|(_76>I}qm3Kc1_mSv>gR!a+UT5j@2H zUYh9>27Omf2XC)tvK}|#cnO9l*pygP#&tJe-&GF$0aL8==D#9;gckaFiXDfG)j+Y2 z=^sy(&O{2~kE{42iV=}eKcEfevdaB>%wgHP`+hiT=A%!Hjc|;ONN!_jRm`WA1>Ml= zgr}RFdt23~GrtwQ6&hF!{9(DI0rxNUxYgW*h`lHROxG544y#WY!mVLzU6sUGSBUiV38|Lx zo^Pr$+FRFIGebe%m8l6W&FiV4ohl{?B? z0Tf3f_a0?zB+D|B)_}Ms{i+3?nT-5}Ung@5&58X{A@j6!*v`C(0wVX@jb4mJ+yt@$ z^not1ESL!fyT|6}dfjxXTSPrw1e)}4UN8aSozyA6lYn3Sc~PPS%^^H3_dd?D&dYY7 zmx1jgDpaXEVji<9(qwRla@%Ov)$fugU0PUV-K;HgLh3e?rzP^C_)ZgvWBt>ER5oaY z=1_Ukpxu*s&~apxTMFMqMMS)Bx7TKu;kQG{YE7_Q4vQR==U9ZL=;N70Mi(#*bjG&% zOW?=BYt?1GUtNzc>KiR7`r@{WbY0WsSp|$Hk>03pZem!{^GuA(^9J^ikA89SPI1s~ z8_zq8t*8rsGZ!y~fF3LM^_e(LEPwHt2UB-&Ro+z*<|2-7#$lX6B3S!OPRn7vou16h z4HEzR!icoRMz_{AgSjL*avRN`$RnYV#S%!CZtld(t2%q{%#ZtkRb%49z_dc-Il2TnTpLt zfO7@??>?WEuTbjNtIz&cFNHLT4h>{#zJW)6Da&vCm=^wdBR+Z5`gzR+sjvwER5xzNtSN)~S|B3hg+PV|(w?dtxcrP2Ar}uTprd_Aj zG4}#>varx%`XCF+_slE~5&2f-vsf=T@}S}0hmnmVd^r1!a_;5^_0WcOW*spmEG@t` zMpI}OUm!$tMOspR&twzoi+J*+e}q#}{_}XziuZC!yx!jv%}8UDh-hYakJXI!9JPDg z1-a{xWE+t-*Zu1)|9!XkD$`S}7`9TE+^Iv|2i$xFs--%N#NI-(xLG{p!R7zdD^23l z&p+OVm0k!5tTup6*}wI&`6LkaBO&yUqNa0b9l%i^ADh4vy{fhK>14NIuR<1rf+ojK zd@&K6kN-X~wSNx5VB?@Ix>kK-IzQEW`*<0&l7xVG-#$3J;m5lS^zSz5nDVjBf+ySbIaeeH3m?W6qS27% zM#$3l+>Bo?hSz(p1998h*e(^dg)ekyIOPCLizrv9Q)7hOK7WsS4E5XFO*oFdh#`|r z_MhkbPnVOIMsODUdy|~M56`6VUpd_eIBF;6-#Zu|{%rH}e;OL~Z>oee|KSe8e|IG} z+rOUjKbsz(4iCBS;r}z8#ZN5#5|s(d0ySp#=Go0qe#N4#x#PRYtn-oPv76j`9)r)n z`+iYvN=sgUZjU)P*IX+T&SyZ3Z_kQU;PuJqf=5!$Eb`%(Y_r|Cj&h`VQ7N;6Adz+s z&>m^*(DjG@5>lG=jcxJ;MN>OiBTn1Ps*+t3-|NRet_*>K6@_}>kSmTy!3ce`Rt z|39|FR#fVmdcpy-?O)DP%_8>OsdA9R`mFtq!Oaul%pUF1Iz$Tg$_Mb=X}09wLF~VU za+s2S({f1vMIa0hc$W^K4b)FVXr^f^pp)sl*1nVK|SzikzS|r04;2)9JPI?-7 zo|9x-M-%1l0l$DjfsIyIfx-ldDSJDm zmW4U<=nh45>vZHu0szp6)p@{aDlS?ub$p;Pcz#y8aBdVYzSGlz*2%BbB~ebL2sJz& zwzqutmUqUZATrekW5AI;>wH! zU>-;wLdH9q{Cm!6ZMhyYaqsy>*s%>6`Ex^3gaxM)w9A&dPIY@GXnMR)2! znpHqKZI(U@_S;SjcCGV=&xs>>AqDoVvjJDPr?}gF1@Kg)|k|jA~ z#jX|~Ni|=&`IcTw)>ROCA{`(g!4nV{KewCS1Fyjl)V2)Y#s|Onp#9z4fQU0A3!lvW z3M9|sWYBgd{ZyeTuHY+cQ;?^l1g8;w^)_MK;Qi?a88cZR$L-AP$+wncYm-`X+W9rm zGq5$e(`#hsLJ8VXn6t7Vye3{Kjax7{u?t(6FzKwD!o4p(MB^^jBiBHo`>9X zF~taWe@SsT(uOM*CaeEY?F?COJ9LeQYp zHuEXn*4RYxM?D`d7UChykHUIhn{BuV%1r7(lWx1dDl^m)cZ8_Fcd9@hQlCFnX;D!G4xSLXhuLcqlqFNVCgq zqc6BdE>EW-@_d8p<-RP_5uv3XL7pi$t_uEPQ*7_`ro4Lbqq0X4>ydHe7bT!DNXkpt zo>*m_`P)-}L!Dw#9(2m=4MPq(_fMG}yMPu6lEZ-ws*3jAN$~vG>!xs_9Jk1Apy3XD8#`~b!|B2n7+P!^>bpL zc0B8HO6jsew<+sZ^Lm!(JGPr8nMIr~M;SQRT+s_Kf!67=1nGf?neR&dH=v{XTHSHb zqEp{xUh;>ZUu4XiYa$h#C~nmq?m)w4?9-a}a7KO>3`oT&X{Y^Q1N%WRr;fkyn-}E6 z=H&DrB2;S8%^lgIN~D%QN>g*N^_qQs0PYB&)?ejk2ZIV@v?}BG&Q?aHy@fryQvsY@ z>CLEKiLRssHH6IG_P~BB1v-P=Xs;deC{V2+@ZLaQNIOg-3AS3y`6b!JzmL-^kdvzy z5$rT^ z>8`j1yA)jYm|-0)TYH0fRN*^gkWReS^|P5X>1wTSdy+eY1KU=ej6Gg%eW-PKeHZur zm-LM!-g>fcvM}y-65OUEi%bwiepRAZWreC%)}^*%E;S_}FPh3D7>hV!D}*T7Dk*xW zBwZ~_*adHCZ)q~27LMw6)r=nMBzt6U)T=H7Y= zQ_$Ol4kw%Dr#=(X6IoFIn`n(iwvjAXpm3)HjiFu-n>Yt*qJ-b4QL$j%B&*EGe(o?5 ztkMI(!?07;u5M(ACGTuITj@qn|JQ|wJPBm7I=g4e&h>lVGjJ|Ec~A~a_xaEQeGMn7 zv2zJ^5^bubzc*-CzT2IA-uqrfWeM%EVZmjC1Dgj#1*Zo{_d_efYP99e@pWjS4HJ~&jhR`>ukLJR{%w&(QB`@KAGvT}^%uH(Dn zS8X`-V_14yq_j2UN!#^9b}+<3ca>yB;Giqn4;2I@&JMufM&J4OB|jgroRkT#fG(`k zizus69j~dDW%W18KuKVp&SluBNYf#4tLG-6(n~u}Iz)VNJoAdS{v+=xwO_UUK4X2g zrJ<^P662ZLZpVvQ3MrhZgSD;i1OA*&R4B5{Sx4tLnrb~yGZ@W~D=tQ?O60X9J6U7gYuB0y!{ zhTQYn#RswNIC{#tBzi~o>W6b&YX(I>idt$5C_G}(oN?g`i<1iPAjvBERRlOoeKpSP$WH;U8_9A-9gCb&sOd5=Dtf zSMDLd%(+{|MV+D3z*<`4me9-c+0cH+#ZYu# zuG`C-WTMFX8ypTth5z~HZh9d9&#Et1I8BNMoF?TjZWPTW6C27?cp^9S4B}rRu3Pxu!V&8_? zKt3%%p8uIYM{8xlJ$Vz&ATkSWjlIWmyO_$i$(CN94tZ5+2hN4BCTR1=@$$VCK|z|- zQ!_gGe*iwzoN{Q!<==*pD=)fS4=YjRSTdNB550e>$bmVAb%6lpZcL8;B}aHYA=z&D z;eqCvv)l##=cm!p>a^GDI%Z7s!uCuO#9|>#QY}yqL-V@Go>U?G`&M1kmEH)q2waxR zTOCxQ)r?(nG>7Z}b;CjA>d@FI_T8KH9CY&SETAV+oFblDUvOg3r|$*ty%9CnlLQic zlp*yrOwOEw)2tu$CoVLipv=F3Qa{2yqap($Gcx=7;bQUS?s@eM76AwWt2p12jNSrX z{lb!1QmUJToPzMsUxxd!9Jvb^WMY_-UF;v(kiY8zQ~W5=*}W#LKy3)i(?N_1&tyc( zxT4UM38`XdBhXnjm81+yy9OE9jtDK5N#BMD#Q^SBT@pMJq-v&QGI)!u z$=ANU+vLvVcg72*sd$}CQIAjSb(G9j2s3jZYu<||!M97KG7I|UG$J^LN|$|OWB^T; zgfBI8^GCd^?XNNOgR3 zl3LL$b&xyMemh=@aiE+^rtHG0*BJq;B;Qu>`pYp3&vUyEM~ZP3CT>J|H}zGl_QggztF<9r;Bm*eE%vT(3*{X-N^0!D8yK(kmQhJBMi3ny9?FlL#Zt>ued zFo4#9SV~Xh-gLBUIrXikM%T6P$%b)%%*T|oaqAiC-+CbxgnS;A-q_qQW!kO#VlqM1 zKzF)~HnuB1jFY^VPPS2+;0G$30~ubHn;PWoIbR{7mAEA+)T994{1(T{P%?(8b~(@k19{2u1w zzcz9hiBZo=;egR9r4vY^tE;Q}W0`jGKF*f|hs)lm*<)skEZU@wzrA}u26Z#|tN z_SNNwVjs&96y1t#8%>ii7UedYa5tfC^y5UKp+{;#Sc25g@BvRvI`Fz;#0o8yB?G>= z49gz?W~W|>QEr5BH632vO|50B*2&jrTet{j)dNP8DCBYM9uKdNns^R-%o``{j8;$Zndn>!9oGlmHxS0#+bQB z_XPzM)x(56f}-_VY-Pw3w1x^wOhTArA!I1M@5D(jGW$gr9|E_9RN?w+pHlv%KvgGdz2i7bf+Vy^0hT{AIlm=V=ZnPoqH^3Itj zH(Wl^SC`mdlt$n)>#8?()A%S4})Z;TWCdiss(qPNtn)( zGa%+<|BoE{{~Pa1v0uW`&QN%WtL(1rl=5!SD929$EwoEDG*6qHsY!&XZTI~zGQsd& z<{$9tr*DzP7x0u|5LIR@ihQ@l(w-hsFUkvYy$5EFO;0R?K}wLGOoUe@xm0w_P|W@Q z?fACr``Cq?``Ls(2yGh9)8`tL3O2h;tx zmHze+2IHmYV(;{a8R(QHsP4iIJBLIH;wII!uVCNeVykd>_ZKxADjPyRpruRTe_P$C zcZxTGlg=XlgO3Iu4${}TQP+Ua6Z>Ll;dM+8cFRKWA9a4OcL4Yiex9Zx1MMLAIzVkP zuRdN%^92pY$@4z|j=p-Zni4QU{Ak|3rd54o^x;nAtH>IuxYRor;w#ZawO4O@%k@Zy zW#)SNYB-MBoS<#Qc7BaxlUjO7ZgOSZw-$A0A9P|HiaYDf7)_^}{Yy*I(5?*ll+h{g zq=t5V_^^`%z6&>9w-c4j;uy^B34W=7>FdK*kZ56jB`nHtX`HXC{_GN~+r{Gqd7-mT zKzq#rkgM?UvjtJSP*p|qupai@O-o^ukXZ~($oZVUtaBCZvuCm+hf$4s{50v!KW#be zcqk8slVP5%0Y|#;fZQGO8!=Ys&@emF<7a5O-FF)ugTL4uQ zg?yfWM2JaI z7pOUA&K&OrnXi);w#Aod(pT5lCd@a~drW#vEz4e-P^@w75gH0~v9?F(w?!X^tJLs`ZL(P}??&W?UNYgfKx<8y%b8&&LQ_1hui>|OUT+@zN<-^XO zfD_qf*C(GAgHB(t(m3u*dCKOtKALn+j~#MG{`qI-ay*6fGX;~9>{bC?p-1%yiU>!1 z@VZJZ=^|=G4G4NTAe*japOn^#%@uA0PL<>*rp9e*He{eGBbOtG^WOrQ;LANro{K977ROPt2$I}*zyxmi9{`5E&sSn-ovmX~QnMySYqMs>VNpFT zjEejobx|#GguTI==&4#=;6YTWE(F|No)x#BGrE9Hd(ELg|E2J0uZSN>QYm-uJ0#xITSuw5H}bG> zJhMxFcT$%=ZOV2f|(64v@iE^Of zS^Czyi7dY+Q7Ff*8s9RduEo&aKD8}+Cre@AtN$VD5Od|V4gHntoKj-z49@uR9H(PM zS8e+U7sJn;jBF$8a2fJ8eR8CV*2uE$Z>w)GVUB1f|Bt zifHt(m{&=CK9%_s9F2(da3&lBJ2i1)K2x3AF$3;C7NZX_HeFR*v@D1Kc^Idt{^`C> z3*#iuq0HWr%dSKPyIgz+8*K6R3uKn1xceJsr&X`vV?#ItVq}o`4EF-|3wa=B+3-x9 z>6PMFLlBW&SGYpW4kxsA% zCI&=Am6N+SMeD@JWry1lYRw_*?CKoMWE@OKeR~wVG?PK9P4L8x76`rbOJ z{PyhfB^zza?Q5gTZ=^f%ynuwb^VcJe98&CCK7M_N|4Pt%QM~(v}R9Pw^A9AGg>}1^a2ZF7`tVy-p3_ zCK_DO-4E(b)=h12E!I}DCC`j=K<9tiR7z<-a^KeDamk}4ebRiDj2+yr2M;cmb%Bao z5QTz!u!LfrZ7Z|=1v1cKwwmCOiZ=zHY>~8PEe=UYs~A6S_56^V6RT79z5D3HV-2w~ z#1B$P>X^`}zM%DI*4qht_)Ijxnpa(^H%xtNpA&H? zol|d=ilb*aAe$1`z9p?)eNvMRn}(EmBo!W(^cN~QkS1$?gAw5|GV$?BR`8Q0?2g(> z159r$-)G28z**=XbZ$RS+EIx9GDfxrh=)~0LrZl10<7LKL>-4Aq< zl0p6a7m)n_m>T{uMf|T`nGw7E_E|qiJu-j(=_AEOS3BOChx~4x3obl^s!)zof|$pZ z=?F(|KIDQJUN##1daW6@XDdFH_Y7YDZpgR%Y6#3>L9DZ})Mz;1} zLeKEGmKk(>j80j7m_hEsB&<=%u%qnY@RU(9Tp^#qyQ%&;YyC^WJB+~Rmw3=^QJj_& zl!(WQLJ#}(eS(<~+d^%{sE849r|U-7WA&JBR*RXQS%an+Ng$J_xGfQzqM_86lit)< zhcU{jYamF@mSx$vK3nbjq5Vd8Xs01wRVDa{)X53tKcC__j0DOj6l zTPQplRi;^Ks-`cRQKC` z9TYAnQ)X}M9dVo&0AOJ`Eo$ZN8M2-dH*!~7l9jUy|lHugkL^?wQI{Hp%=#!`;fBj=n}gYKgnADJT;tjR&| zbS3gZ4D~ANDQ|7-n-a9zYEihjzDNFp%Kj6cf`U*ObNZ;X=V(}yEd=I~)7jt_hFlV* zxiIu{5z&7L>&2%LRHO|E<1*Q)%GbjOKi4$}T#;{^dcN7r5oSz))s6JI(H2=R{keWHDo~`-NjC$jbu+!-(O2=2;bUVZDk;sl6$HKeiD_oz)c(hs7-m_e z(UO#9()K2S_T=_25y#x?L-mUcLXRZMuNfy%`|I5%A;bCcdM^A4h_8D*$sA=1xj`j! z0y?|gT9MdcqI6RS4i6Fq%T}5-1dwITAMh%{))c~cuO0r29?u6aOow&pL95Rcg2=hf zHVxM}xxI|ouk}Q@LiitEz-uR{wk+Y)S`L=$8F(I`?gsnN^_8Fc?ZrMdvu?(ac}|px z=fs-f6L0Z*t-2ouk+{mef_2)0#_wr=*w=zz9f=}GuQe>r9g3Kci z<{PZ;oh=9xmoaaKd5DibFi!BEn=omN4uDpyL{8H$71d5HBOe%xc9YYoBb0pURliG} zETF8|sqim)#foOGUaW_52PLNG$PE<+rvw&9#w!Ex+iq%gPBrqYXf+!`!m@Xrx=4h| zB+Xfc+z~)=qvS%~>-CsM-Qbl-QS?2&5mst%?+OPm^_SZ{XgI2%p!#It4ZtGve>K|c!6K=8rZ^%LzzjUgof4+z|7Vp9mD$c0)H$nSI=#dIA@xZosf=&ZD`XpUy}TkA06)T_7R%-J%Sq z*zl5d)DELhHKFg@>Khx8YsHi@0Zx511tuU_e&_T!Y0r^}Q|fgQnlAKpX1Vog?zfly z2pvizg!Z7w8q{@cs9A0J)9d!N>eDYjcUkk;aen|xXuE^vOz8%U#aFYFDfSm3hFkqWK&GHS1!qHq$ug0TiaLrJkCP<99BxyMVH*^C#wCA z@QV2fAQVV1rARDXr}@M?+)qB3ktnfxwHnWQ5>n1X`uJ>SM zP8v_^#n^wz`ta&6@RL8-+ADtX=??(Kv@;})!qq$cV^u|3liD`gcy{^TlNmf5E;NKU z{!`8jEvpYuBS2+|C>HjxuN)4?o>5HpjhrJ>7?+VSz%hN^e{Sg+SZ(zi%XcUodCdN2 zE59Lp)n{By?XusQW&SVkMrjrSoz_vYAjUgp*NnpRHToS3OWQ^hZr9~jyYyqfN|$jv zRmRlXOFIWzJ)S=@(aR?B)lD58aFe_!oUUZ*E*cbD)9DbNr~uCEe`$Vms>`kU2SDqb z3%;ZKcZPFht8v-shfbI$@v?Ew5Pps{rPevN^`-@R@bo~DD-sG#EVj|67?b;d^u5R{4@(&wH1E%);<&(s86O}+DF-E_+EsOpHjvHwErX-606DR(!1>1$G7J0eUDHY1% zJ2yYhiw|NRw;%S!^R4q=i))X<>7SP!MxuUyw+H9ZWH<|cR`JgC4}fQ@Ag|cphdsL#4Fe|O^7UyYXEO2#m#5-|MPkj(EkUHyJDruzSP z=l&dm{|7Cnrv7{IK4x?D$cyP_4B(x7&oqt*|MpS5K$YWPiYK*U+}7)<%;#FcU=UIyE!_QdK%G;U-F)-X4S#}$S9yHGAAvyc`&_Q zcR%bVp+X|L$i4St5BnKS>X3D(+u5@5kA0y-qaxE6lrMj^2xoLXkyh4B(KOd+vjOS8 zOQ{zE@aZEzXYg$B6pJ}|F0Moa}jmptQIMqfq6G1W*O30o%IV%bjp%85he5{}>L zNar|}NzD4Cn-?6CQm{Mx(mOa~D6xZWC7GO)>kiayvt*UdcrqL1YGB)0L(q_xUs`M) zsMDo{7cRJYNlg1BIu9pDik2elP2CkGKNSmkpf6d}l}4TobM;P2SBgiDem1|uI!jEy zC#%Y(!L9OaKmEll_x>aGyFzJK%H+~++AKv;&^FtFB~q&U#$%3Qjx^adOG!|$w5#Fn-Yft-RDQ=Oxt`g8NjG;`ZVx?t4OT-55VF1vo!WO zPZXsMp^8Zd-J`-kt@1_jEn)H0W*L;5bD5694u&yU)kqRPx%D7{lr0=_GXWl zE{NNPFLL*@M^-|Igv=}L4?xenOf6k$?c4m?4Dzfu{ObPC%+6C^ChSe`wosjR*7}vOf>S4RWIC#{qx`-B{nv*V#lr z@DqkLiN;pPpd%REXh&O2Ag?@rAeL^MHSNQ_Gn$jI zc+;|wsQ*qY%Ql^I;z8Cd$qSlUUn`GP96ayXhJ&RZGzOl?JGeZfW?P68Y zj(9ie+V@tR;A}-9QI$7XWuX6GwsRNkZRf1^lX~_y7&iiUWz5I zsun@`6{a$u1>)-C$w0elHN4R)_@zanjXRTaQBNS-z#Nz8C6cY3I#I#dVe?a+;jTu0PocZ+g4I`cdzsxhy}&ws>s^3RWYf z*%9ccGNny#?(6ls7+`d!+7=#_y#e`p=G?>^z0{X#fHYrsQF7|79vPJnM~eWqt4PIf zqMX*$PwbMsY_x;5v2y80j{;VKBVGW}W0jHS&*36qEDO(;vQ^t(&e#)Pa(ZPJqGt)gRZdAZ%`M6+bL01k*N z_6E4-z4F3C$>mYfJbrE4I^|_$g&9IMWvAE&+w$!2+|trFNASfV$3HTM|9q8)%fC`` z__c_o$$o&dGW8A_9mnj*AMn(V!xN2dF2R2*U;LeEWSz+W%_pYHr|=h(7(wqN{Rz+K zs>44;F5Rp@Jf{V}`x2$dafQe;`EHdfedAYYK#>yp8;ZL4zr}@@ZS-_ zf}$&&Zd)bsjV@^|iV35YlyJw*36CqBHsHui&c+H9>5eu3SZE$PY{D^LM2yJ-n8fe( z+aGpeau=pu#UQ*U#jRK94#$?8woa6A{7yBdF_+eoSQ`h_r(({+RZV3QjUM zyDZJTH@J!|wX(7mW+^Y}38aoiFW0BwxHZX6mZ^y&#@`C(`y$y-3Luto6d*qGD+5Ru z+Pp$a0p{F(`adZ9%CNYWZ0*Jg4nczknn1AN?gZCB(8hv8(~Ua>NgxE5ph1JXyF+ky zXf$YO9D>XF`kXm4_s*P|d%ydG2k2+-s#;a6s(RV`#_`5!E_5-f0s2J=_vT7V>CBL9z=AX74sqlt_8bk?l@;BPWJoN8doG${VBz8l_9H8ZgB1&8Y*Z%F3WM zHk>;FD|Qr2t|HT$4#Y6(RR6-!@(&IFZ`>{9Hfth&b}$;2kN*3VaJyfAtfM~Zm?{4~ zFYw*p=+pjfUZ74GOL;+|OrEWWcjVYH{TykkY7UcKM4Hd3%5t3?Yf}9wM>_5rHJ)_dI}c@xjPakbK!ad z5-$O}qBLTW0Ha>=LMX>!8G+d7ca0Jvq}c8tN#MmL>H|{Y;RP#Y3x8{UTYc)ROS8?k zj#N|!r{G2aJEAMFm|VX`aDH`LL(^w9iXRC^Qde|ycGnqwo5oesY^aw#uSM*pGo{;&5k#jz_*mn944$m!l&@Bv9nA1H!o#r_ zf-@z{pvV%?Vps^=1yEV|5K0=t5aVP!F5|-#y<;upc*jIz!Hr>fYw=blhHaG zc;sHZ+`+uUU2=(BK=}5vo;b;|ih{j;FOysrjX}ngZ>?fYQ$VQ5j!tQ=#g`et>D#TRO%6soGm0*;S$17ZeR~5aTsai_+@S@?L9IR!#Uhffu3nwN%u!L6m1=U~ zG|XKhG4V8BlC(C)<9(TukQ zl3%wHPSl9~kbr3bG))uIz05@y69o=>8*9=jkf{9FQz}TbDO~MGgrn4$4aw;8o3QTi z-c;L~j>cv(Qh9Z(dCukj`q#KuYUm!&pInG5ekM`;dGwo9AHhQoNX6!-F=d~H`)ar+ z4(2$;`W1CAlB@kjgf=y;R=D0&R{Ljt(N3AZ6>~?WYFfw6=;5qHc(7BE0%WA3<>>Y2|`bxC* z-aBGPmNx2WI&~fS75+YNg?eCqgOCJ=B6;R0n9~;?f=}fVc``-U;p$Uv?A(1va{6ve zHiruwx{m#*mxHDlO<5dv8xcCb&t@aeje2)6VRKssLSKS$pKF_vi8)if=gXEvXYM7| z;Or$w1njuvn7;+!yg}RyD8y!nG_(KV!#`c;$iMSH?ertiVi=yaAy?IzT;I_i;qQa^ zB8w3>DZ)y1^f#D`{_ZlfkfVKkOr}0v?qK|qQQ0$9Mz&B@V ztz53x^J8w_K-ylm0oD*WsM->OQAAw~!j(DPkhv5o@%t*V`2+cAIk2%ysL#=_s~mZi z6kOC=M_s{ zeUQ8vSlp~hUGrOOq25@`ewA&_9^C$Ly*+AJEm4sn%5%AqE&(fV);$3u15apF`|I0Q z1b$v?eX0Xf?_KG^xI_!#KovUe$_Rwvk@%+P%f&5g#wnz?J$`+-dS9?XVTz#s>_#=j zQc?g>tiNS(Nl|XngT&TOA1?S*rC!ms37EK~)@ZBc7$=rD2q5bctNC!OIyBa;EHh;n zl~gQMA|qPAPp8LWSr1vqPvvJ>*v*$G8QtGA_t^Ad6?2QfV_Q%J5{})OdG!P=ZXtJZ zN{VqSl_XLthUScnW?0BO!XZR=~+^DI0 zLkz=Itieni2PE<|TiV5^(Huj5C~t$-7uzB;Lm~W_7VN0ZeRMytU5!CY@w%ew9GRY? z@}iu2(Uk>pNn{NpjFBHDvl=4h0b8a=H=o69U828SG_wv~_;Z}j-EM5EceqYLwo>OF ze;jUyk}!<~sgMkN2(BX>&~78Up!p9b@|KtC2PL)#Kjxqr?8QUMjh$VNjug!i_y)ck z+hW`hIT)_elDY@|&NeU(2=jWo)VP~OubeFNt}mr< z;6(1J)5De;`oyy7X(vj)&5loS)TP&EeM6#`+QNqPkspA9FQT1eN}~Fxjx5u%ts!ZL z>JzY;wGc^h@c9gmn;4I#r!kyez1zTkpSYU>GK1RW&o$5|ZuQ8vS>ZHlaXmG~N-d$> z9x|&l)vpx{F<_w5^0=#Ub7GLhzH@SO zDfV=RD4QfHH2R@SLvy)4#dr8=gq~2UdK8ZFU1q2%Ld@^5jF>4)JAzL$ zFM>afFH_h3Wd8i@P*$6Q31A-QtiG!%0)J7eHCw003r6vBuI80i*!XVxG|5eBChCCY z!t-IoTV<6fH^9f(3%x17sZ8Dhju3O!%w}Q>98=S0?uEeSmQ%5@Y^QuF53n1( zjFX!ii=jau;4BrW;VDYka7-jMsxD+$kZni97&ffq$ImYOccP-feIKBLLsC!dkd-!zZ=d3aN(@wMl)^G{R}i&} z$$Ur-rCN-h0eNh$XyRdh$2{dj&Pw%(-WaUBhbWN!4-n^*+Dpqi=RM*n$?rw9!dyC3 zw*;~V74$(pDympu5uXxl#f~3zjq(l6D_i4r+l$0`?|=3)h!W*|Fp3U1qW8g-o?_K* zxMSaB01CYD6d=)gm^OXqb=MYUE2oPdRULK~seM@LHY)$tqjn~dT~W%i(UT9`YB@_T zOpaO%&}r&(QDBrGa_Tv^b9ul~0`YiW_reVL@y5=a3+F`n){TMK<5m z*8I6|>r&!r_jnU~{NiModz968N(sl5KwYQHVJ^yXTTFOt69GZD?8PIsljYB13EPXE z{A8|u=0(`H_JFkWBi4~a#=4GaUlDT`2U{ng0WnY0>?;+WfwI2! zY(S{95mal#K$kc6wXY7oFrJQZRlm(| z1wd7HzhI0kF1a5ylEm8~pa_kWeA+mcK#vm(^W26i^WazM(X7MT5-({vy85Owy@e3> zY@~?x2Q8nOKnwJ-?Xuiez;Ljp)3^#}$?V9r%%ixk@^655DgM67}-do2}C?-C-cMJSxoaH#5ScB&geT65|34E)%p*4Q%-EUe1Wb&EYtTca4M zf%ip8;Cwm9o+^@VeOveDPhoY#C3RjF)wQ)grbhLBeJ_Rqcs!l^`prapS8EH7o?&nA zh_1a?L9&fW_7EmtAc&=R~*udEY#1{TkG!!)e<%_$_L$sKF7WAO`M7etW&Yrx*$pZy#68fBjP@8oM)(1b4CO{>!dWs~dMdtBo< z-c+W~eQ#z;@HOlr5$@^t=(Vi*pE|9@h6wjKIer6hD;BJg#jS#^w|=c-Wni1**oW+R zNs*&VbLT7su=J8tp}ny{3xkCt*o)UVD>>ww=yRT#VbVnysy)NMa7J$VVotsn#06(T zP-V#6u@G*PZ>I`G`;V0h@NXs#GBi=k^8TEA)Re1M39GyTvTl0g1)N^&Mb)mD>lsm( zKGRcPS~1Ei8KR_7^!;ci5_G5gh|2QOm?QXL^K@aEdV>tzZu_A!g^^G*shha_j-|ytGH#LlP0pBA8^Tr3trndFpip#Ei~z|pLQZhI58pL+n>=u%Z8#Y zHl70qRlf@Orn{th-%TX`;IPabiAh0@j9Ap}&pI@02)_V5JUbfOnAd2Ojq^0;-bpcgix&{QxSTqt=tHSR{xpx2u@@{NsfeDpkm3wQ`LCq|l?l$94f1%U=dkE(i! zl03`<5b3^ zQO&jrJ9`^<1hQ1rAs(bL6Bwr4MBVl_-=y_dHl+N*UI+^V+h9&%BZKApgTf_wG9UdzGgpMh~Dti1PfX_Y6q;jH_2;lBY0e%>0DRxil{*$bB|DGmFAk$VrD z(ySLTk9DGhUG)#kzRkGaK{S;;KfZJ-l#Ew;>xve3uF^4?rwUS>sjUkUsKEsTF}CFD z{aOM#*Na}i`^6Bk1#l4_54#W(&};L8X5iPDEOnu0K(N_I@XT?UQX+OauIU1>08*ny z{!iD8Ja<*=Yf^!e23}>fEZBxhu{B&6)g{cv!s#mkV-Kr=xQ_+YD=RC@ldt#K+#z%X ztmRns6G<0A?f89gLcC-NcNv+R6?IePsrU{;kf>RNEy00aBkd62UhCVGHS8A@+z z-fw`2Z_@=WOs-e9qU>1gjS7Q{TS`K)k4Hb^qcfj7qS?w8?kA@16qxae_$h$)%A6Pm zv3jWM`uyELe(0_~0&)S-FhpnXXweoHj6o_%NmDY`Z1DyLgY?N|D?R9}!8o4oC{BXo z#G?|Om+qtjI=Dr4x;nbHEQV4}^n|aHG2N{&$*Tr;d65QeAH`?nVvCLW_F`nL?SIr! zx(4*}PT4ObZ@Mzj9sP9bp>dkJmibQB98yzcJI|x;1|cR`h8e3vD{BX~XVPhW+(0$%m(d@{sP)QykxwEWqp)_u7;n~f$2i;oM-Oo*r7TS5d zDdxSJKCsdrK$P(NW6)=4(aER4QJ+3OIbj0Z_E-L|pug0VF zIz)`8JLxxoeWl*9Wlh*AUG&Jgv3gt8l_^s`64U&=u>;1!PH!oP1MeVP7T9L1)@|9$ z44c(K2ZK>+-0(cZjYi#7vDE88Z_4dA$C&V0A`)PF-O-zxAHShk zT6Q$@+`Iv7srTlGl?Th3t;>i^KAx`VdeufPpUKlTTGGAxX5YydDb&~1t?n8sQ?=0T zw;6%_5t%yuC#e%NqEF3>RGrUF?664=oO=DR6tX$Gad3YFa7ZzHs4TZLlIfzMoT+%% znkiRx_u7Nj)|M2;In_>1y$jW&UfgX&Xrr<}mEF^QooN}zdD1DnvUe`F)Xg_KRVfC{ z!^-ocKvoFqec1#K7yJNv1Q9j&%T*T{cW{EfDpO8NZimFlXTYDx(R>a4iAHcO-c7Ay zRC;BF)(oDh=Ei@{WWOWH)gol(s0c_4OwH(5ZbE#Gk3tj^wQ+^m%*{>l3N2#A^?rUl z=NhCAszAh^CiPO$QwsIQi$%15^1cH<$uA=bbG>Ub!9`19`+<9?L{5#iJWo>E+=_ z0V0>`Pcs^eQwKl~B5yO*gI8D8vDussJ|}T7`WBM!r=1#?G)K;0yzJgjTh{S+ki;_IJFCO%8a%#xt{P z%ePmZ>(YHPfFe(y@Gc|k1dP|yotUVbm_%e^N0w=ZY9Q~%>GpvEDhEJZe2CyAFN&-` z`6A=Pafn&s+W1Gip|h7pLx2#&r-x~}e5HG3qe=MDtJpe zNEVE3gux^1ze?(2WT2DiKd@xea0FJ#;XKCSf5^VoY!S4nb@HKeZX{+I;^V{4x1lUK z(YSEN1x!mb-4q0d`Ra1&+tgpmjjg_$juB6BZ4U5q=cg$}fMSP&5JqR_XQAO=PD z^1COd#O2y&!x}}Z>MnZKOBra~<1tzSzI}vsJI`v-xKFnJ^?dIK-8$~pNzgYG=#*E< zw#&11+T=8+wP-ald^gRyX~-d;$ldku&3rx&hdM3&A^ES3z=d_!1>jVWf43BvuUqzq z=ewrxcPyT=|$gO zEw&BCt6vE8dyrwFR&t-XrK92i`ItO9jOJS|E^0bLrbI$ELd+}asD&kp%t8+;op-)W~CtAU?qX3&7 zf4VvSIT(#@otPGajnyWKe(TkR@~+WF*eB>xGnUs3(y6)XJY8TXK+ zYoG42bsLg-`ib6)CH(~d8Rb$VUZyIeYoHGl*U(1>N7;k89y)jVo)Mfbjn-iP)qQQd z3E=ya5^RboT@M$~;Lr*@aa&KFf7^75_Bm|F$clH7vB{AG_05az zPC?3U1BNq6y_i0xS>$X3Lx|vX-3QAO>@Bi@gGenu~h&m!t?&mbd=_H z9jBKP%d~U#U`lO-wO94H{#aK=8@gLmB^UP6+=TmW1QnFds(A#nHTm_9|4Y%07M!ng z8%<+8^E5LdtIMCILU?xl%Wg2n`@|iL1$XNFs}R`h8N)Z4{^ke}Qn947(uY!!q(O!k zu(7G3r?`2woUhf~Wy!V2Cp7943oDD4x=S!_vj~uA$#485u;w27;van~w&gE*GtXE2 zS;TThJ^>>ZiG<8;wq#x3h3HVAdJ}4Z_AdDY(dyD$=|vCDHnf-0tjVdYQO?RT4{RlN zn;+l)t?T)-9GFa_v%+9gZCzXVP)kFI=XrWu*1Jr@XBN$FA1`-S!K}8=*<1TgUuv#4 zGEiLohxhyW`Q?Nd_+z}B0p;y_JpfK5>Qhx=tBmQ=oxX*UqACl{^7Y0(q<$6mvWmY9 zKvD>3Hk@xe9r}8XzZw%jh!L!wh@sj<^`3aYgi>T%wzW2B{rS-69Q@kbh+k#V|L%QD zMTi4e=rF39nd#(_q&wk6w2VqiRdomED|Wg@Oy_SlJ%{2sVyxzO&(v>@Iud9Xru@g< z?h7wtj;P{z`iKDlfFmG>zE+|t7qKw=H*X#yneMMlhN8iEKL!~{5qss3$@Z`mlAZGS zE4I71A1+y-`p(kHg?i*N@ob#-w#k^!N;`Y@l+bqE?tUe)cg1Nt!OI{BAhp8Qo!leH&7)3tCW^Pu99i1mblTy%Qe>tfK z%WFxiRmKW7hs$w-!T(mWXXM5fDc@vRkO_x^hVTt(7K?ave*^qmAt`?CKjC<`q0DNP z5`fF_IxiHJFh1f$^1jHbvbwP;YR5;}%R2f<>9mB~?+FD-)!!%R&&BXjzyL-R)HlQo zSOmS`&L7046PeGzFWfpkR;rYaIH-~{3a>+EdV9LQ1_{!F&|7c7Iz}9uu*INnkPee{vTTQUz5ld;bm{1 z-y8)8DF7RPpYEXjUt+SfrqxSW4{^Aa{>3zU4{LYrnZfq|*uQ@+Jk1n!Pn;ZEs#PX; zclE-OaMxolFUxEHqtpH$nv7+&(DKAn)*>q$0%UBz%?q&yFzDYYj8K`fbi*tkMEr8S zq~E6IW#A@G^%DTq?li=NH1QkrH_}tg4Z6NSbb?>&?(l!p1fC*=gn2FLp2MY$Z}PxP z+C=^$47$JxE!E)IwmaL}hC2=N^ZPmkid0qV!oRG*-%hcGjWGHh!Ab1;D_9oPK@09l zG7N7gY?`%di83;M2s(%bLhQ@9pWjX9=H3^3ZDLUHt^DgmFlpi!Iu8shdoplnlFu|^ zeP8&xn(HsJwmGXs62?IeA|E!IFTgyR^vi8~KQnzS6I}L&F z$6cWJ)A*a2o08uE|NON4AG#~UYgO?Z;E$smai0EtJ?0SWA&WDa+VQVjMb86Ik(fu$ z?l-`nTKtbg3#Q*m;4muLFS^{7y3|2TwM+3w&8!Its2FsTJl_>QZSNR`F zQ(6nk{|DcrjgH@ccQ|&3F_(q@?z80dH7QuLyG6a#j%-!C3g1rIPBN`O0s?TVw1NQr zbj~a`_w3&gk}lYK2t&#a+rCH(%FeKIZ~~ZiFyf=sy_1aRBr0o<51+8ZKzeC@MYByI zuQuYzZ-OourkUzlBE(tcrXUwDU59_*PIEQrnQ^zs-g9)~Cpm7REJU1l0*p+0hFn`1 zC)Vq0o2EI?X1^!}2{yd@@El7JuY5g5kUhf*w8=lNemqqHCTYfw zFb`g|2f=J$)Mtuqp%PqzC;;~{pQv@R3lqHj8ZNTb$a8)EHSR8bQVLF&!KDt=D7k}Y zfvqoT?|}l(X1wbQNWR~pd~3DjfAyL&CZ0)Ea5GmoR_nZq7k+m$8mU@=mhvKOzpXfJp*?H`w_oa(9|UjfQV7ve ze$FdK01#)|wY*}(MryGA94rYuda~HmC7$p#=UP*Y=oXaB2T^;&TfsZ2a#}rfz?CS1 zLgf_Y*-4GgnpLhdcdYZFZ!f<+zxg9Rgzf4LwwsjoIdeFw%en6+j-rSnc`pD#jE1n8 zm5UAS1jNl(S5l_XQu37DU?p`3)W!t%38XZnKD8ARH26wgT8TN3YlSd$)q*X|PXYDm zuYoM|HQ1n+%|x${lhszxi-yHk)63OT38n7l6WQR^m|NeZ=796^-tF%jvfPFW0F;rT z1lC}aBpX_m(_YOfQFSk}K>8Us4A7{Wa?#;)?KiYAM*XcwGL&F)4aEZ_raX=hJ|*z? zL>bD5?v&sb(0(D*At#V)yO*_G(jmc9!&z#bwhRC<^z>b^(X`cq-)5ha3&5tFs5WsZ zkz(aA$oD2vlXNGSbY&ypNtZDR7KtJ{vP2&i;_^PtDu~X|5Vg0@wDQ<`6rPKIJ$W=q z+UcYI=uGlRG(b{ytl!Q)!zV(m)Y;=wXkhDaoqkkMT}8%Lo-g(_P=^k}O@mO9(UIzg(~d`l$_TA0E*jScN-KSVUsvr^@yGOXGjCr1`OI zf4UtM4{EaYwV48u6@Cjl;`$>3F^HgVbRD%C8#24EjwbH4&Aq{_7k5^5a<^jnne#lB7*Dy#0vH1@pU(p*<3}(ELxPuYb-qP-T*!>Pjy!C!+FS;~7()#wiCHPa|3P zeMzgiG0l7lFtM_uxSbj^jcbvDzkxlR_O)ax>^$nHYUhnr%X;H_QC>$aDJj?Ne1E5R zzXBEAN)!CKrN4&SYq5YCXgA9qYL^V z*$~R=5atgNoLI#?35?E5Q3y~}$qdw!5rdEOu{LvWMCbBn&zy@ukRrt+!SS>o_7^ME^#ltq_PqjGILxofe~a zNOh`#hyb>q{Bkm3&r4ktTlwxG4k-pgu={JQMqUAkaX-Yyse1!<%um}k_LO(+B5}0f zhnCcbqt#_2Z`fYQ;bW2$C2AJuIm^6U+Ml^*)o-8U9iA+bXIWlc6|1r}*ISvlYvy5z zBdKrR!dpKP23)iBmTjHCdX;f=6nB0nm1`nq^>(3@vjzR>cHgpTT9lobR`(U{NtoeJ znAmdVvayn}&kdBqO2@n@4-FBN1fbo>2@aqjA}q7G@(A3%Pr-cHKiO05Z_IT2?#%`7 z`*(E%x&z}Jg)@HDA`N)9!Yr`dGJCCi|WelxS#+}hEWx+xMtxgt@WnnT>rF< z_LO{_N?ggd(J;8d3JzIpPH8{V62kBp0*snW-76P@#d!&OU8oG$=Q15HCFzq~sA8GF zGSzrR*1EC~Uu~+2k`$C(rz7&T79D7A?2TnP*$(t-HlatQ{b*J3Q+W+1x}VtjTKo*A z<*OT-8a{7E4>r7h(^#|;(BTQ`6bp1P0E|wJwfbuNoAOT5<>GE1ch%xvvA;{0z#I_yQ1n`> zg+T~-J_@kDcG2mWx1Q&-Viud@jA5$(gr+YA@@+WB08YlwJzQuv&n)5R&rCTKM2KK$cx&;? zc`Q7L#YKwz*%8WuvKWe1!3999ak=?pl#fErG1TFviBN48YU<4yRNMg@O9 zU;F3t-vW00hWr=ZGnXZ?ui9`VmO)sKVhqX3v{*bZ6lHm427~8F>v8zcR%!oiuOtDtY=*3U-wDjEifs+f z9uO21eUQo$%5Z; zv$5=F^pg}d>r2eN&y#9X|ru?k{nT7!y z#EJVgFxE_ZmohzyM81g^A%V<4MkDd(Fm#!JA8qDe=Pf^l&_Zvx&>gineh{S`V}lvb zdb#8;uWJtbfpNFn(i*rJM?1}NRMvR|9YHq75fKk|Xi-;WUY}49WKYg&>(wZU2o~?PN%z*Y5rRfDi0S5g4 zb=F~(ZaBFl40Ip5h-?4-(ti05llw-}MSNT0FUy-c2ghF0JAzm#W+iw4L!C>-q>2g= zb5`)zIa^#afRiFXGpn1zHi$+tEYv8Rb4zfdr7`bWy#Kvg&JN`jf}gp6nP7i7;P3l! zDZA5XbQr@ac0-$v(39}LcClh&ut<(JJEC!O7HW1*^(y807_oJcps0`LijkB#m~nPy z5QmUsH+GCfzUV(F1`EYeRvp)q9fgr8%$#MXrZLBywGRl{w(Y zqPo!(-hb3W8n2P76R@Q(CC`6z)_9jz}zieZTLD*ng<*-}m8cUMzLGb|ABR zB}O&^P$iIrWcIKw=5x8V!s}`_1;cfoh82?{Q7lAH^ncxy|J%x${JzuY+zT8?N?y~W zLdU$u^PqDy?F(^BKi8%Z8RnLJLIpE~^|ER&zheCps^mXP9_{D&5hg6H`9s$O*g%4C zT+1x507Uz1L1G_a)d|~vTYHqq`VCMXe!pJ~A}IA8|nKEtfw-=b>RAM51P6 zEYda`RJ?VOrXX+h=R9hZJx`YHKet0FlNY!E7dUF<=F@X28@TUW)$!NhVDj`bb2f&R z$kBzT3l-2r^ePD zYuoU1k*wZ@`$N*dQRlbw)Q+aZ}Ze z9r5LrEe==5Pvr3G$wRt}pr#rrd+hd?3$*0Z=$0|GuhFFYE=$HMdJ8aEq<~t;<{mDMcf}I(=M~{#=^D`pcBMvtDfEd zxUKx*VqaSucvvlsab8By?>>KYTgJzZ_zgfbZ}k6FX!7rhPyWZxTy8hbHKcw6sPv!n zP3Q_#WL~Gp{{}Gpbk2wHcYi*R{6t?0^8Z_LmNxxQmX(Hk-`>aTN4J?vE>Jo(@f$;o zzas`98UIdrBDuoi^#`#bifcJm2cm zohkOZ63?;#rY{c&L51$1KrC5Uxz8;o<9dhJcyMzBS4GlBv3s)^cJpQM!ECIdU-yH4bh`t zvQaT0tsfdtJt&AjbaVDssFrjU+i|LL%G%`E+*f{{;>02D(`I5DKcellm_sN?UY27heM>&tf85xZDSr_)b;+hA1O zqMeK~sJQ~5=%liMuXN7yS4%e#E!9LkdB@;=7ZD_(l!_QV?;4t|jhThu6%z*!k-Mbe5lQl7^FOsp!JB+z}li-hc{j5Prh(wI(gVOdze{Z18 zrtNEm!sOMF1wlY{6q3+85J^JGPYw9A!SX{seC%wZA>PGqh*}0f;n-P(ff9r-wI@l; zhfidu(;Qr%0?B^Je+0j1alO-c9c>FJf)t3>qhl~lScF1-%}(uL&@?uNhQu?NNQ51k zgq4I4UEc?=L#)%#X+*Ta;wSkKYNa&|+k!+mrHpO69ENwI4r3+8XaY^9Qc2mh9lKv^ z%kbT#Mc3=ZH_6{MAwsBe!Tk=+J$V4(-S3H)u-Gavwd^vd3Ac!j%}u{Zvw3Q$ZR*77 z^ETmn*W+3;(&XO&iSH#<+az|gUR}=?-3w$JnNaRWlBp#gRGK?67W00L`s740(0N~? z86fC@4c0|)z`M>HMCCpjt!zo-AFR}OnWw&1QQcmTA@(OTjJ6v-sR*lk(CHphX1%^B z36Fe!WGx?P4g!^hy}>R-!5QhX-Hq7&U=)?~Bl*|Y6q+@y8Qj*mQLb7K83J3Q^r|!H z-H5_YxxUTG-YQMQWZ8=hX7Kp*{hpz4^( z=mMNH916)xlzLV_3#j!;cE&~~3n^Q{x_#qcw0CZIWsrVRYIBTpNo%$VKm#nRG5#VN*fX=rH3!d9tI5(R2(uUn2>oEXuP>Ic10?EUI5zfW2JaLX>XtW$gQ8&8SB@YVH-@@N%c&BroZx;&9IqsE2Gmq!qiEULO4H% z$5Wc!z32P9^eXd{<-}OB=6Dfp_iEpA(DqW`U>^&$$0L!EKo4ny>IR1D+b)vEhOjxJ zBr>R@5Q_p@wBR~R{61*!M)^DT-VAVM3iZ#ES!>2rteRUVZ-S444)b5^GprLC)hz`pj6oSpj;4*U0!kbnQwL&r}O#Sq}+1pGe{}4)m!{Sf6KMzeNE4A`bI^ z`f)i$;9xrxl}!FB=89ewn3=u4)lD7ysPqQ|g4Q1vR&(VYk8q$~>Zkkn9Y5|%pnqpX z(3R9$F8mw+LB1FZ**pVB+^4>Q?VT`qO zem8NxCpGUoo~B+IqpQ=b-gCM~H?K{+C?w{Q#?>}iOVCdjDJQD3a(E3Y0&qyw614CG zseelAwpKcSh=8dezK=!?D^@9I=0>r>|?kaN5R%UyJ&~i9pOU%&@zgbVk3RX?(@Kv zb7+MsfEh^=;7**2%;`mpv}0RlBx{+|Q*s@JSzGeC{K}5sqvXYUGMBV93<+uygCwp; ztvIfDvQK*wxR=aL_?goJRp7QCjxSW6Y12tgPL0M6a1p0#KP9QpD#|lZ=WB`}nq2H< zUWt!QY>fETPw7vq7KVI?3ZbKy&Z=Y}7u=1=+pxl7xaXqL6Pmf0jCqib~Vq3!$Un7 zS(uy9QN@~4GkE=$_-XHKdwJj-aLxKjPJBkfn8F0#d{A9F*s!T4 zYG1*hfA8r23sLgAJyaBV!942DuVf|^gMwOElK`^|5bX`vs7tq2oo@Bd&P_catPN*T zbzq+YWc6WD=icWuEY2p}w+EUy+U4=~tLDuHh60 zM#WMgGEZEE7%-|f`KE#aaezU%pRJQ5jV^j!;m_NU9LAlZBI6|~^eh~^x9#+LRNSYL ziwQSBkx;S z9B&Pb#L<#r{~ye$UB>XUdBghGJSGqM6uHY0$Z8lb=3ba~V=JUce z70R)1&e-r8%=jg&7d$&fQFrN3635hY0)EdIa3`)s>Rnx>_Rv2$tj{bPYW7>nn~7I^ z$DPTW{Sm3vnw)J#2+kVQacY%yi{Sj2t6(Eq{y9!R#<5Y0zU!zRyEuT8gBsJ+=Tf_6 zzOYZGbu!zYQ$#K0;9!4bfCOQH4Umn55=qBM^|LJ_$hs#`vS&+O2Y5l)qms&QzCj{h z;?{REEKoCYWMO1y@HpRtu0go=Z2l)u>t{NFGAib`bOdEBbbAkbl;Sy232+@!QkYdH z|2 z5^bmx5?#u9dT>ehtZ*j7t6a3AYOVA$G(3*T2Jo<`!GAP3xF6(Xw1ckL&(ww_2@K%& z&NKj=`-e0K5BI*aEHV^p1fdGMA!<=e8lzEszjl1?any~>!AM`MkZSj2sBHB48VdnL zwhoR2iq`eBH~#7`6NaNV)qCoAnX+**h6W7(8esTU%x@rC??G=p$Hy9jL}PeNp>;zi zQ3i0VH@Jd{QyX$5l$Wf^l!p6@KhacV_ZP+wEjF^6+76!XILy$XC;Lys#03TWT(! zWXpoZl7Jhyp_wa)JZUF(*|0+v}XxY!ze;-lSCBKj{F6Fgl0UUj!-W!kfK9A zG*YzXM7J=Epcx_)wdRA*{Cz4dWa#^P0h`VT zrlT_*GzlxCS>tSH3VeqKoCkXJqCUDzSwpU6!A3bVo2VK20ir!B2%|VD@?gDg<^>46 zh{(sOM*!_`>s^*4j7V`kK@&Im3UtFLtos3ul#r`G#cD6{;Co0Py{{;E$QAA5Vn7_L zTXA^`_NIwf6|#stTRiW{z_fMixd73nZsCWEouXnHTi=G5-vCyOBbcuq7wgUROxt?_ z56f9>7}PwVN9s6Mhfk@%#2S()kS7g-?mqn02ZlKR&?+;y~ zHDCqus7&8jRu_&AqfrM63GT(&ps>6|hQVA zl~&f>u`R92FYDScIYO@)=Z+HFSw)d$V{>qW<823U;Z0o(+5Ms# zD7-?@f(hP52PrZK@KZH>jcqB1dA|35l6#cW68is*zXRTNO7;)_4(pz&QoICXngmnV zd{;aKbw|SJ?*-U$+TLjYL#CMI)bFq+7ab3-Cc8?u?7smvZ`U51U!9j1*_$;8rxrIu zz8OB5BLLzUQe`}WOaA1^u$wijo9!JP{n!-A4m+IGuy%q~iF5AFdj@ z@NQvZegPe`eqRBlBCb^3*hSq0%EfR%K@8IRuUriM`jD9GJkf7+DRe#PLh+|+!7)Im82ng-~)aQ4h=sJ=Tq`6s@nkY`<}x|{rKW>FmOnb z8`L+Mzm~&8xYlNNUGd$;roq6i^r^wp*ABka9XL}00UZVFdE@xc|w&BFf6)pN836?T<plf+&HB8J7C-(+xDofzUT?9wEp-XMN1TV;qL6bA z1|7$d98wluepv6xSm|whCR@Bwmy@Vw@)n_iqP>|Q2Dtw@TiKWw9J8z#z+4(>cMFnl zAXF(D=0PrXMs6su-zeW3W!RfLWc}p#fjx-lW&*94B_fH8+oY|o;Y+(FYm35@u?

GhI--h^QIKiS2asY3~u(v&=k5~v_AQGHV9^;p~uO_A^(sD<@4G&VKOuvx|CyiJ8E67gYdk97-B zozE||35yg#Tvwzag=alHSN2@%S4`C28t82#DVH}JcW*t|krfFdN{Xv!h3IhHm>3!i zXZ4SdUrZfi3HP!Qp|WpRZxIyGJx#b<{e{#bYqhevY=G^VoTJLaxf;`g2CzN~Qj~}^ zsLD|O`a01j!kI`)GuIVpjgdyvdOLv@#u7X`y0#QtR@@W+B;GYe438^N8_EaHO;y-z zVEpmgI^y+_e(0daaOZ7CEmDoFx#2_pCP=|}g$5W}U6h`5{b@wfxP7fmm|fGWf~EhAx)lk7b{($j$m?L^H`8*RBmK$W>4l+UQDLJ=TXa4jqe9^5s!ySpa@w*bN2-QC@TJHg$ZAnBc)bKaBhyYKmKcaOf~-qE*5 z;0Lu+bI&!`T&rr${XEY+Tod-?ZL1-!!JTDvndgG6*z617QWwok!6L&}K&-4N=$1{P znFmEFNdzZ=i1EjR; z{bSjhLayd0&h%GAc2DBpGKlTQF7nBST=0E{DizY{EL`U8fDePhWJYC4VyW;tsDI$y zxAjJ%B?RnezuyzLV>)NwjtKHE2=T<{mzQq&a8MP$8Md_rfF_0v`XON=IybuBYQEzo7T$ng=%!ySfIc)ROaN#8dm8W`ny;Uf zq{%h!oNPjjuu2QDe}LlLe}LKm{oP4!!!&2Rx2EDWeMIsk4i)^H3&8UUz=WKaMe6O< z4^Us~gQ@qzo$QsDX{iO@+Q!=-par8xkJd9PKt??Lu<{3p@7pcr4^Xj;N9ufd<(=$d z?jjdD|JB?NP^#5^AW(YFyfgm+8m)Z<-np+pYIK!(@Hpvaa*g^{`k}^a8@L_0*Ja-0 z0{=OXpppFFrx4~I^6vPnN#;E|BLTyIyTM;GdVlA&A^lh_2zbLcdMHwpYW=^pLn;Jg z{AWN|!he7+3%HIXS6}PR)$*QCe<8cdm!p%5QDrl17kV!1oDWVt*bEzfMc6r zLz3jlNT_2kQGV1s{{e#e8F;T>vj)_G9;=yFM@_>I9;b3^Qf*!S&s6>IpUnA#MZja@ zIIDXvV4W(qj2=pD?``I~28o=flDC-W$TX_Y^-s z7aLy3+dp@f8-SOw3p9lJHu_j?a7!}jLNbX!&c?qAY&*H&A0RLO?~`5~(my~_B9D(= zmxZtTt}^?s(zwv`e>a}_h~f3!_D^R#MXE_es-gNqrGK|gdJ_EuG!Y5N%mW+U;2Cco z@Pqo+^f%jizx%HAKJri`@=(AQEdQHrk>6dfxDPjlgqy-d5~Tia`*+u;;x^o5H{4{1 zK282^{Qm^z{{-f@Z2W&aV+tUWsedhHMXO_gz^De^ba7+3R~@l=#8wbIl*>{{GetDz zV+u;L&!JgM6@2}-4jRpWt%H{K+W|bAHTDPS^Z|gxmX79urW9*cg|W_W;jz$`4SBk)XUl^B(fu#icwg)mzoaX4nf zQE^lFO1JIh2FRJ0Ndniuin@s5AWr%X+?-M!>d=<3R)|WJ1T@rA(uNUN5zjYlGXkP( zCGOAcxEv{pjSIYG&g)C8YoHRmau5ql9f`0rtRgjAvAXD_yUE zK^G*edJdrx=98Sj zykVkRM=iZ93B;ii*vQi7`k*x-d3 z7x#>06GlBcxkcw9uUN>S@S_2&JsyR=Hn5F8=?Q%(a^NpVtHyUFwffj?DxlpvUM_B|n;p>3>{ziHTVWp!q`f9IA4}_9s##>kp&|_CF#;z9I`2C~h%F5mFSzBwe9EaL7BR)`vi1GNrQ$6?8%yM7EWXlb7 z7o?9Ztra&L3|6;Km=`Kb8GB(!N5C6xE51U4HuQvu0JK{*Ox`QE zg-T=XQ^}*As66GkGpK6z=8A9dp|tby^QlFEB!$Jye}xmZALMcRhQ*_9BJhR;>x$@v zH$WWoUDewdSaYTKEd;_mtt|2*j~b|VzAHu)7ziu0gs+?H8k24-3rpwLEgDs{lcHx@ z!53*yP0Wk8&mn#7%y1~Sg11s46R;EV($!&zIw~S@ub8357HvY39$wBwNj}Yr62wiU z36J454ZjN6ng?Tc(v5QZZ?T6^!JcJppbOwENY@xEQHszM*Na64zq~Tv@N|IK6v_I( zJ0xcAN*y{^ZJlTNgpa?b{vkQzR@Vv5C+PzM?~~(RHe{`3O~G7A%wF6?E)ls9TLCYd z-obHic1RxZg^x<2+5`4wCbx^XNHUyxF%?u5W(|>r=zf*8Gt+}w0ch8^-9wHk;0!d z6Ag~!4FXuNB6SE|vC5D>V~zzw>;ocIE7tx(;A-qVtwTuDkutLp@&_u22Q@TLD4P+^ z2^4{9$G{!*ndlD7tQU8=jRD+*p@HE#oKqRB>oZiebp^^v%T;HKWmff%B963}eRWTL zyqiKUw{x56=3@qgENI`-XF6Z^opp_%w>DS{f6HITqgU`H2Zf;bA9M%oi50RXG=zkdyXPSOsUz&41xd0UlbDrq z8!urvYQU0R5Ob}_z~#}yzOt@=3On?E5S-5McEW3OWKzBx8`(2fB%%jGiqmD}0_mUL zD2kS(jC@l_P*iq)0qAGI^I!p0Jrk-)E4urgO;L&%J=JG93byT{T%)D$$GuVu*)jLy$?<7)$V33=C1dGUs_y} zUemF$q3a;FG|s@_viO9-jBWST)|sRlhkCaLi5+Rn3L>9ZlbNq>JdZaCo?xt%mJsM9ow(tU(v$#W|aPZ@-9 zMJxb&icjrS{RF=rRjYq?3WHrLhga<+6rM)I8Iv!cC1QWNP+9%r0hT#-ulmjM^_`K_#PNljoARVn(US(>epbt8b*g%^ z?D{Y9;mS;NKQ>9OAwno?ow#+EH3#!FUWZ<#l@}CMK#%i{Mk-s5d5f)wS)&4exd47C zA$+(8&aQ|sd+cWmZ{Vg=nY*cL>v7T9H&Kx4U?3!dAYWQUhhf*59zNQj-gh2S^OFlL z{jVU|A$Wqc&uHd!ZEeoN<%fw%wd$UrIsyyh~L>Y)%r(fH(f=ORW z$APJik?Ji9wcg+GvN;#M#g$j{$lx(~aaWHoqi%(OH%erm8S`N{)Hl?(XmdrK8xQrfH{~m)^s24|+dYs5^rihz!ywVhBqF%;n0;F*U*w0?P?x z*)EO~3y3s}1*uU(H5$*poRooa1hU!z3$z4`B3TH;@#_r&PiLwBmtD*N%u>EI&hWGvO?fU^?s+u19T-YFs0PkiI1j-GJBf?kf@tjje|987m0<6GrSp(c%EJihVV zO%iWD_2#?+^X{PPJGoKMX?O(~%#ijHY^cQy7X@Nhmt1_%peaZ7lpL~VY2<5CEXBC_ zxG&!Nah`UGyoV#$IrcWVDm2Mlbjr_uqn1|$3+|$R!aUPJ|3+Qa>zT9Zs6y0>Ah8&~ zj3@yE-CX=_e2sHeSH9XcVnae+wdL{ds>J-VG6VlzR8b5ZDies2F)^CPSDs|00e0n- zoA=6-!r_@a4RQ+}CHjr^QEq8Nk;1hotq!7eV%ppx+SA#!!Bz=JBOQCwORwMy<{hRx z#GgGkQJjE;gf2>MTF>qv5`-u&WLv+Xmfskobc8Wxu)H6 zXhvriS~JcPE3A%aJIr<3GJ0*v^o$?P6xXqaY4`h3ig-7^9i8N6B|M!o*#Sf^Buo;@ z-f;nlFUm4^bIs)8depjbq<*V}DxJ1tl(S;0wqc~0Mu;!)S)4TW;WXyXIYpD}+q>$Q zE*tbQ%Qv|RbnC++RC@aE?E-x9!%#zE3kHO=qY(ZzeB3Ue-|PXbq#Hx29i)`cQ3UB= zWH~Wu`vWm|3isUmXUp^b6a<*fMd|kLqXTV16&I3XY|uR(ytS)DIBH5!9f!O@JBePu;N32=@P)V0m|u(we{`GhCUuwriXO za*d)eb^5y*LNv}^{L3xx`Z*v#wsK~C&ub^+BwHl8^qb35)}~g_?&88N70*0%&clS4 zlWAEx)t2iS0#=79=ZZEa8SIa1c!&EO75pt^BO4huY9T)DtZ<_ObHW3?Cr9Nr1ww9c z2Ta`_I=0gjp8~17Xvz#m$*^_WcZcoxz^7A3f$Zs2^Q;y%R^2|1QCliK%w73i`ey<( z9y`dzBX_{mC*Al;-_t(WnQ+~K*RhqnF!mbY#?hV$8d_{urL3_SHQGyJSm|5zZr$2_ z#M`h>V{m8$a_=+W^BH>7NTzwDr!qJtN*yN_BOFeL1yV=(ei zhGsV|+F+t z3*9Yb?2M)y>xPK8dvvhA)wwE_*ldGy@5Y(51!QJVrH1aVH#F zGYsHN+8V{|==AV)71o$t$gue?^;Azoff@j9GK4SQuX!7;w3?1 zZpBcJuK9KTfD#8<4_@=??2atL`#!NxeOXb~Z6CPs@J8hY>LY(-G5#yoPLqi05x0quHIi(7#Z;O3bYLq zJ?>qf`E%O$*Z2DlF2_lwCMn7))cdHaL`1XT;vFNpQjjhIUD#rV)?yQma`nBhv$gYw zh6#%c2qX%TCF_J6$(^KKOkYn0?7#)U5?DWgh?UTBKfNln(yeiAq|oKDe$+?ZQAMPP zXj_K`F-1=2-|(m=^h%yQeBLLWEsZ)<(v{kbe_@Cu{SFJoYlRhV%Bo$bMb z<^-ouBc}wG$>5Z;(raDq%T87$OL4*-2E_JAxPRIV6A&*$5tEP67M7N3nm&X91Oa@4 z{$_=lULgHftgv6A#`|91cUnh2|At%Al$-6gbYN4oUwB)GSwtp^SH}gxob>r>1eq%6 zHY*HHOhj}N{vR%mK5=-dW!9w)6i4xN1utQ8#npM{vYVVO+24L+pM7VJ=k5Hw@*~~_ z@ooy)H16AzhsT`q!1-zB4&GU~Mrb#b$mm%aSafHRHy?ZUhE$F}?kZBA8@Le1xX3mK zYGa*&AS7S1gFDo)lyDs-a)q@UX=b42zVvlw;!s4jc{>?#;g6W1{zXXx;9lRY7z#@>7@^OKU6ltkiDDL`y@V<#T>Hub-&ILh&*FktQ+ zA=D2VT93#tRpzPHtVaVavxDlPe!Li^g!4S>!iAwOKX>=(`%l|rmR0*IFRE)fJk8jK zhoouJ1W?(Z!$zr~Y{M)@Jb@{?CpCG9$z9lsmUeS93<-9v9K@E@aI=qZDMzEqk z1nax#^9mY5^p4Jb4A;9Gmb$ACr($A?(pr|x@<-aP;GOn5lmrp|BFk{TvG zwUdB%B*y{2=z5VQD>R-|j~Rp>&r%iX<0~u-M|u)s{B6Eh{_feGY}^QyoB26YIE$g< z@o!kA0065b^q;Xx-v02TT>YGF`)X0ng^H>alS+iI4!&F(jL%~b9>T`^!wbwUT#q;& z-eq#7q58f;-DMQBqEIreSQvfLWeabyCdSefJ0f*(hAJX0D7UcF;E+9?ZU{`L}~M7zwYfWc9ApauEfEErA7*DLMPXxQvpJ|8)+dxKdw4uY9nm2HA-vkbsNXB z+(IpL%AOdkwqB6?GTzwM1l`5CQN?_ppE-HVy0P_>TTM}&cCg>q8Ej&pY2!UDb%{X= z{(ZBYRh1o0JpaU;aE~&a0B@irJZ<_ZYGbjQ=`{|wf+LjngxbSKgR*&yE;}g8p;zN{ z#~9{~%t=aUfwUFd24++29u5w!9pw--Gke)Kim+>hG~0{bkE~|J@^|89WHT?uW{I0(g+L!7r`RQ3!LatT zC0mJ1yUV+#>Lql{$X2OlMtgm^Td7q-KlkrB?BuDC;@lJ9kv@>*0C~h%GqfyRhkrlV z_R?z2Hde+3j@n8E2ATqI3?3rpTd$O(Ki&zuAA^UMl3Tr!8$FM?@~cX*zJ0rC*zPQS znlY?Ba%~4NKFOhi2Q@Z0MmUU))*rIdP^-KJ5z_NkYl9ZiG2r@O!UY)l#bXOiF%!)Y zbCi%|#o&B^aw}qXpkYYQ?Y!%uZtgkJ6jxQA7Ij8AbueSh$kk#mtjEDNFW0DPar)w@ z^+cc%#*-`RTNR=MEthMm)57ZsX}Ekh=lkA1ym@sPND`_PFB}MLG98K`vZ*5*{8EX` z*EPv6#qIleGT-EUacvZ==t7J_WYsnotSxs!{?+YN@m5*@Z#St78>C((w1LloNE@y5pU7 zF-V?zz6kF^&P4JHNX?oWqTeOpg991J8Du}FIQor(*-SV38d6g0@|6s=IG|B`C@$NH z?8&_;qor$NcQhK9KHFO{edKl=;X$faRd!8^(1H!giVT#alEvJVc*f<`fJU~KB|6RP za;0xoyVywud$ccrRz5*qsYqe<+48oZ3ZH8Qvv*Z0{9V^A=1w2tE`Kc94JSYDD$4NFdW zJfcpNgdwf9WDFxdJx<8!o`+R{8U$!+u}C}XMTsuO(c`~BO9zwoO{+{=A(1~(5WR-R zb^$w2ea^RNmxk!!UG2Y1Il59;iKhtS!Z~=o@v>9rv=@RVD?s3cW37p0B6IZa&_K6{ zWhO!}XE=933fn;0KyjgV$Q*q~*yY~TxyzUFg@MtLg0`nB+v~lv4Gn4HGJj-e|1blD ztr2T@G*YKhesNGaL#i3;vU!B>y-^g5UXI9{a95Fu!;~G33Z^f>9MtVDa30Vy@2Mv) zEWE(!`znS!kT(&tbj`)7wGdvawgdx+yC|5{_--FUqi-Dpk6KY&Be+o}x|5Kt&kL|4 zAFqJV{vOd0maxk&*UGpYIC4j!^uz1IKOemY;8St#q2XQRlTU8N-s%X4u7Bo_UBi`m zvfIz8bI}b_4}MqW-(NxQ{#CJ)hf&*k1M}deyku1vhak?Ib;m6_Q3Nvmq!m|1Np828 z@&vcSPa($VwhAE@jzmcp>$%xL?}b4d2UZMkNAkWBa^o{ZaHictF-S`#Q%p-p1M+3j z!)847Kea_yW?MtMMeO~9EEIY7ec6NJT*)o|sc1id|UUA_?UkZXAY&$e-;w|k+wna}6z`PHp7X0l_)5m&jTiGb*?M1@{;{AvMkNW0`0{uP6t|@SipJhin z`Zjb=%*SrtNR9E$;k!xP91S(1;?ege*OE%nemVtfZ`aO!X zpW@{Bwc=DUx$(taB6W8NT{eMCYBOA7=>X@U&#1E9F2U_+}^MrSe zbLJLEY@Y8re(1CfB!*{_lYJA@(Tkkx43_mB6sv7)t+vbAt>w05 zm1SIxqB9M#l?EH}t4&s)RMl*E=r28g?DX#V0ebnc%lPSxmusuRl-kyuE{x#3jrpC{ z7EiE9lcbWxYq)y9p%1Z&NMjA#6LmWRY}r{mYAx|bUUYW5GT)ZO@I_ZD=d6$zptKlA zL>FzP<=bFNrG8@$~~7z9l*3nw0xpPL(?a(1}W+u;F;SgyQgP z&Ec;_Trg;Sq>6h6InZ2PjsQc+`ondOl*`$e0Vp~X36)Ftt85cz?$5`dSU>>OR zmzsm#c76CgJYNZL+0dRAjhkE(SS;+$IHKb{k77m5AqPBsA@C4I zvtv|g_EySLodPif$n?GisMFyhHN8^ipSGCidO#p6sh>c zo3?^$Cyeas3Vz|35qOUPIuD;bD|f|A6DLU%4^Ir|j@z!co8YAhH;^LB%oe%#*K}Mz zxR_?2iE6ImQgi>lI1;rw=S`1;uv}jjUv51t4&Uelrcc#8A0}h(?gYLu)3Ur%NFUH6 zbWCm(W&{-zOLP;kM><@YEeuD)VsK|!QN^Y%+ot&wk*GuGYdZIg&vd2E8Q`zo!DzXR z^&AmKdb8)lTJ(>8bx31BEMt}|w`VrkzgmB%G^G?|>^(aG>VXHf6LW4CYmGPY z^2XNkmAVy3csQ^%EF_Q785b0(Sit1U?<-tb;ffTh?p%jb;Pktg(VeyJQr@S%b|eBR zI@!8y=rwDVoqGBeXmy$x<Nn!cxSf8#g@v})LW(g z{uPln54&e8o6-D@6tX6*V3~S8zP*codAV%-6OnhM(k)&zZqB2fyG~bzlL}(nPhecE z%CqQebz9gg_0OOrT#eBxqM%ki)cEsmri@k)v zSbVCb!M-ADaOraI5!9P(tDJ1R9UUkqwd)iK(;eGrcol%};d!h#_b*_bY&1LyUh7R! zOnT3A8NL4CnaXaaVHzu}KI^~k5dpuV{dj=A$Z})XE!%La6=;NA6s>h%D-*bi-gWfO z2*CvjQE|i8ni?5Q)ulxu6f8dp4m#U1D@-(LtDb*RYwRscp?%V&$0IEpDdQ|RD3jQ& z_Oa6OyMIJ;l2ZSR3ut&plp)6+5KS8Tq^(%-H>7;z2eh9x@VIJj@8&DmUvEF}x(W1ba# zl3`&NwY;@NGj|$RkcQ2U-ksQW{aiDnWFvtdt1@lHD%!sEiL(=D8%%5yVxz{sOjeC4 zhP`xHv)$^(38}K?@QEbE?0IbItrmL~SmjJl)evKY3w*2-YiOC076 zdvCjqDqACax(!cfeoJ4Ex#Z zG%$nf#RjR|%Y=Yn;OkQbdCRzdnESVddGU%&T9FnZIeaO6^{0`MyUK*!uH>d(bBFG* zk4}-PFYjA9?otfdEe}JsAu`%4Z6^ILn6Rodr3%syyd?0KY$$}Zo(X28BaC(Dva#Bz`h6*(zq6ZKS-J?mRmBMG^YFY{^+f{_fyb){2I7>><2D>GA}^d=)>j5ZYGTi{pg zt~ybe_sB`2XR9ASmkKRjsb_pXgh1Npu+wIN03iz6IV#Pp9p7$!CrM<@I#^z3jSC_5 zQfr@pm|5lTa+@pIwRrr6978DTf#2HJ~V zzR3|PP;1^Xl%^16R&~w01S5b;u>(oNi?hm3v9<<;;)G(~8cT$KBd;WL;9S8GCO!>< zn*BC?;XlKQ+)QHf`w z*%@&i?O^U^PpO^n?!HV(Xg{O}gk@2%3gWbxHw8I&_v~baR=i_Rc8d!&_ySNmj1N%E z=4uOY_hi^nxliEDiZZ+_xZ>T3(UoJDzZ<`TPp3?W>nMoEteYS7>wJk1E6?Cg%61tN z=vKTm>N+E(ExwcO(?z|@9Bk2fR$Yd5mt_1fiF0TE4 zag-0!dlxO&qd+ngz<(QC&9gL)p4U9EblbDa;BYRCLa|MK%+Fz{wn0tI8>TpW9g%Z# zm&OJBcuD?$4nYmcq?x z^yZhQ(h&Cetl8g9k0QZ|yxC(!+v1rHtIWu0T;07$tY1XTRCLJ%5x4m@5_5;8elV34 zP$g=;CzoF|W%{;9Ff_xGT{P{ZgcZgZ1r>#5&js41Iw=fZ%~P5Y@H<2m4pHc_=69)~ z3{RCJWCNrG^=-jz2jyz2l-0Hkiph&kN>`q0?%70~uOGz21c>W4)@mSv={e0hSnxp0 zTHiIEF9wQFtG{j-NTcu;ss%y!tu#rj@=Rnn``&9U?y1FWC0@xs3sfLtqCr>AmG9+s zoz7auuSewFJj%AoUlcTA#W6lDI9RTuI3#6Bf=Zo8*fUO35+PhIMb<2dOA{+J3dknxmQA{ggz)961%F44tky8gB0|t5CbA`@V`OLvq3;NE zoTTg_1U2yKa3yGZp4M50LYC~U?3e-yltz;$BBMYcd@;mUT}#YKk2(;s_8b}a&BGM$DNZupAMg|viQqt~T`6W-e?WHdGV@BJ>dPmU z#YB5b1&S<`ANhLk{Ju{Q|BGT=oq0-h$xclm;dS#^`%ATLSCYaUTpuKSSFn;g(vV#q zw76$luv-&oO{F3b!DMagOqCClHsVrnSTgrYp6cQJ9pCdZ;tfEak~TO>Gb~JBGpJ`7 zziC65S=(E6t_)4%Cu&Ir!k@L!p4Uzs@|sv&IpGw~QZ=~^MTJy`+aAcwRq(_E@L|(J z4In(jr7xo&Z!)7a{o>L8!Yb>M{E1aI{E1b5;Vs_>I$gjjcb0hlvE!)UerE{RKy6hs zJ|u*jNmq>S34)_tWYSjF-^Ly>c=@NL@7BV^98vXYZ|o%E-qww6)w&chKw zr#k5gx64l!He)|NQI4pvxtGMwL*s9N997 zbn=IJmK&SyH$VZBBz2$a;S^ws{NXB5)B#bSL8<=*506=}^8xl+UBJOvU~={nF6X;? zidR&dL~0NtgLEsuYa~%;@e+nG8h^O!~ll z-p(yAK!)^&=8_wKVZQm$P2OkmR$iA!C#ypXtf@%TAfAP)T?+L0wSW*~KlXxi#8 zlm9WaF7|nv#WBp0uZOy58Ro{1gpf^{otE4=WhC7Ca!Xk~N%?ReC z!0WH2=k>4Y>1Svk3Vm`&79@GvRp9vKX2pUSp0|K)!UAI{4c&9M{0xTz#lA-x@9Ll4 z9NcdJO#>=&*kJY9XO=1@G-vW+SJ`itM zr_ng@Gww=S<;C;nq|7y`)|;2I36JR?R=a!4`DR-rfK&R5d+)+@Ih!UH zLx2-3nv?AWUb|SXi!&->&zen-X-ayI5ZrV+{@8Gr{6dkVE}o#u2qEob?~s#`&!|RF zAtkPnjA5Y0^GKLz@9d9a`{Gd)g$*3a8tXK+EE@LTQO4)@gtsO}8;z=wUuslZsB~iU zx&mT)41))1IeFmPXA6C&RXVt)(PhvAq~b)1wa5vc_F7gkTR@Z3gGH9`Yzzq^0elJt z*cBYkt-7hoCJ(iFt1^fjWyZMJ<^l*CQKxP|>{Wz_6uc{lR=O+Jf_Jzm1OH2wEtx^B zy8ms>eKG!alE;cLqp(Y6j8;c|%f#Cd>?|Q&0ZbG&cUbc7Q?D%xXxNv8o#eUtxO)$XyqJM0IRGZ)UvBz1Y|J=ntHEur$OIgMuxjmMN zETkboCnD?23rRVWTyvZ|V;f)3M`LEry;*!mW0zS|W6(LO(;6OI#-jb(J&(2X@o_?Z zk@~hmMSWd`+aTv!3;|pC(EMSonZt&{B zE4RH(aZ<1YWjGe$RY-uGQ@UkWDbZ!YhGw^;<35BQKhof}_ez({$VM;7I>=&2AveI0r9%#5 z$HFKBFG?(Wth7(nG%xPX9I~No|>Hyhiown1vJ9K z{G9>Yr?z&+!aDZnDuIR`O1KPth{gf2ct1dmp>w2ab7K}yljbv?xPMVQcNfP+Qs^ZS zB}w%26mYI5xS|{>GCRzxLEr$LH6j6i(;5}KPJ4e4yA#o0^#6Ojn`{BNvCLPZK-M}I8#=utezE5Ydll8J->|Ksl;DOVG_Q)1yF*vT>Gj(S;71 zp0^1vpEZbv_!ZJZ!;#f}$UKJ~15gl6czWk0oU4_p zNPTvP*@-D#wqqzNB8#(HYU9vAp_;EFdbM{r@exwubNd>DwoRibQf;Oh?78-(3Z}=} zu_i>|()=u^=)g!saCq&+)|^+}s(C-Ra)G~ODQO-~FAHNNe?h7DI}e5m;Qj@jE^g?W z~`qs(qyBmADsD<|_=p`1T-nWk{Nk*U(DO#-{!`@UACAE-c zws8l9aP@>~TC-0Zb(6l7#Jld6?q8?`4=I^kwU$+TIvzZ$)8Y58ea*beX%D zUS3Xl!D#lJ>DBc_!R;$kIWzT+XVmABJ(zg&uGUcfC09q&%iqV%B=TZaC|^f`?X^u6 zp%kb`KyCf~_dD<bmtCI%hRVU53853GbCuESTc9+IDn zPFFeq0977&j@ZA^Rn-tDi(s@US+wy9S!+xhOMv-&Zb2(CMJjzVDQ6NV!g)Tlf2)dTExzEeugtDy7B%CzYa?QqV8ZLrP2E*K9?mkze1IxYFX5TpRprn zVq0H3S*&+w+rWweWnE{lJ*T_;rEkzEm$x;p~2oJtDj6o6sW$b4mySE`?#cY?A*o0Lk$55Uzx-%ZX^1T#U)VE!E~=zKJK8Y zUAX_uk?ngc6Faw6iBCm`%WDP+%d%L`i|v#m+AQCY%*YXstCHVn#8(mCbxp-HSln01 zxdh7h$5CyNm3lHtvn_0ed}R-_lO8*|I1XUiIeWZ1qi1bcj$${8)P-O9dM&3v0oS9L=k>kCHtNkhlS{b%r9U2h%dZ2TlKHaSRYdwaF zR(*TMTKW=y_+zRaq}_Gxbx`o-3&O|Sl8+;+B}M6VZY~z{Z&7ribN20Ti5!HW@(1 z2N3)zyaSj!IM4xq==!_|XgSnnSU=@^<{kbN&!|DmI~4zVnYAFzL`JC6t_@Ns&+QAz>`dD-qt_6 z0g%uDMakRXhtvBVH|WKkos6Ir?$>{U0Fcgq2}9AudI9YLg#M6+@{MMV-$1IoG9E46 ze*3ci09DLVc}aLx{vnqCs+F5>ko4|-^82zc^+4b6|MV(Jht9|+4kQkw)PwRLpz7PP zE0rIh`TLzKOjLWpI@vk}prJ?jL+MC(MepOxwn*6*yNstDp&hw*bY6#^_8!0N^ZIut zKeNQL#O9yV`vEdL&-~NW0VUB+UU^qEWFY881n3BX4&Ej;M|xZC-~LBG>rCtX$6ub3 z{&7bg#_hL+9|%84|LM{nw{$}c;3iw7UZnB*!=pcMIs1<5f6?GVCjT6ABE^$P7L*N? z%d8zS+5m3wFg8YusnGLe!6O^h7q*?OmW8B-A>SYEQ-8TzbA<{1$0+=NWN`IlzG!5V zN;OwkGITg&6tWD~pBT9|roR0@6v@!3f0Gb?tW|U6Sd9qpoYJMtaEcBFq}FGS!;3Wv z%zjE2DJzj08p+W9ORC6Xnlc>#d6nLH(d;0(#h4&w=alDIs#lkD*x+IucIpw@$$RN> z+~1?!ZT|q3${Y8xhXXPsXlK~zq8;)k@%*y@e}f<9@dl7B8hZ%&c7DQt9RAp>-ZI7d zz@%sK`up^lfkk9uU@YU|4zsYNdYZ-=vsZcIqbI_{=?~CM<{f_P_KMdx(g)Y+yzLZ1 z<9D?fy;u?+65) zvAVq>y#|1CTHyzP1F1ehHP!2KUo8kQApH*pWTc-?BJwts4`PNCMV1-p9EhkzgE>c8 z5g;L{zZu#2&3JVlFhHP2^$!D-ZHI8kP+Xm5mGz}ZiOw>Y@wOBH95ly244N9%1^*u= z#0&#Okp#7zQCIIfzAOQ{+5Q*)mZ=2zGZKBUWHfQ=5HT-+dLwy*~ahHFbbLc{6B54 zSTa^+HE=U#nsZEor@l>wJId{6wBP^xXshMzlhgh;_TDlou5IfUErJFO1PBBT5}e@f zUcuchxD@ULmk=nViU7e0?gTB|o#2|nJ;9w2B(L_~XYaGmx!*hQ-P`W>-k;lAtp+s{ z=2~m4G3T0dj6QnLL(?I}#+9V3C+Fud;weL!95fGNmjL4qbPB6}>LT}_AV1^o_@}G? zeFWtqjFR}j-zfiap#3MGLkN2P%UDW(`C(G#%y;cjV`1_8kGVc+A6E;PpzrBH%t$Ko zLKSH~rPU4g-D%dF1M0@~?iPsjA|%NqL=TQo0*=2%zpP06t02fhenW82QN7q%d~bI@ohP4J>%kX zooX>w?1TC7ZWl(-KV;)<5pybf`uahI26w^CCFpb|bcy})Xt3$-Ir*;YAJ%JiSMMF! zt`m@51kArl0^(h@<0FnX(^k%=jj||Y*>vXoBO{EAXrNj(*&8TX@+IXn1`92P*6<%4 z+H-Od#3>*S9%Phmg>6p8MX$%s>b~s06z?)J(=>9Ge{9Ui@zUzL zA#_4!@Sdur>c4WWd!RNJ#Sp^gmq@0OGSF4W7v&&p7fxGkJ?TBOe+Dz~s&*}km3Rf; zjN@_n^33UiRZI>J7#Bq+igjL3H(Yj5n?{YDdCnswBV<3gr&h}v_f;zJimS+5Y2qeK zGA$_ntFsdS{PTbK`o5?41yd&Kf)y+X=4qm3y&hk>=k|*$hCIRY z9RZ)1n)nG;TMuw28pUSaR(n!ZIYEX7%`JE-^l5Zyjc&twE$@W%76yII&myu}AkEj%q@_-x_Os!g{oyYkfG!)Rh#P`WM)#BVv30CF95Ya)h*`Npl=tY z+&|Kd{3$dIY8t9REGo&C93vfLndWo~T_Nxe&(9cw1CI0Z<=;8?`962iRggsQkOgbm zyyVA4-vsN3j)!571li$MxjPrw6V$=@Y@^*wQ!uxzw3`NZOZvH`RenCImNq#}S_V(m zSM0<)KVK|Ak@heE9!VN6(Qq&dVJG;{mN^sH!wWWSh()(MH_+HDpNE7G$X3r|jwh(-4b|)8k%aW93>^ z-y-fi#At{-wkc`mM9#&ZJUaD`@z}=6P_GrFva(&7*Z!7M*G&7viyJ8-0XXN)IgknW zTgkdO3_(@6k$p%MY;-fX4klgb86u&kl#_RI-Lp&UuPEP>?j?q6>6lth=W&c&#rEyG zKk>qp>^9>drQZ6fGxjd09pYJRJX);m?;iM_u8M|vt@IFpG+_ZdFKvDoVcFC=Ltisn z$@*sI1KXB*e?@D=ViS;xmZ+1?5BF}m_H$*Wx#R{`1xrsmWkd9}LUrs#F*)$T`U7dur`0uLW;s+!JB2Zw&cvB^g*Z-|w_?NV>};Q&^rHQ_t$(QMIXUxjA1GfjGOikCV%dLzz75wkEl@SXz_1c*on8TuW$n=OVd=CSN zB(misLLRn~Sumq3_eIP(kt(xx6wZGzw!E-QdYBf#Md-5@OP4Ubm%|ed(A!K5*;g&2 z`!l)rzxwm9E#{sOboEa{Vpa2su{eYf7;^rf-ByYJ9PiHMWksx@#WBM#{t%(-bqqAW ztY97qYVS}tNNS+6mX#8;jsq0R!AV1LxuGh65|->t1I5W5d6#)CzX%^t3<)jzZR0~z znU|sRUAEn=Jp68cG4qzs9Y)m?+jtkSpZ{f61TzwTL{{e&-Z8jHBE&$}JBXL8o7r?zonb?Hfz--wZkcq~Nlc(4 z<%20PR3u9?N}$jL@5DzahfK_2NTexG;twJS4jpyOsi{B0UaE?0lHRd6?W}`7o$J%N zCU8DK63Ot$d0brV#7?|AvION%r3O11FEBJ+P+lmyd>+NNqO)KjLe+iY!aZ~-@$|PK z&3`(y_~nUfsIKhoL(d;XwtoRUrjoxnxbrb=6vcJ~P~CQybo&rli(ewPC#@{waXZHr zI(bmr4cnweG@XSEPXPw11YUag(=uR??G(cJirz1hKJxV$S3aYtj&1DoV-TFs;6{-?Pe4iLRsZmcArPt?*Wx~%Cbc8L-ukh~pU-EO zKNW5IPX2b$FR%`&LoJ{NuNLUKgU8I(^)akVL-Jm-T*1KHDMlPFHq8E!Z#ID<-L2yg zw5%E-Qo%m`)|p)DKBhc)iwV-OE4FQm>}%*22>^r7IPC1nVj^JpM?kVK337bw2TzxN z;`z}x>RCl{K{=D6L(38b#u@4X%Cb_|m?`QK8d6h_q)~^#ep$1;e^GY-nalY5Ppg%N zR~T1>-^OI65lp*OyZ4hCvQv%BZRhJAMFT#|G$+n7JnVU`BRgh zw@&rPIimhMD53nAWY7D#XL8o(mHSYZmT!lZuUv`_PLnJeolqrNOM??6UKt26-1#De zEyM?P!Xf$9b#64x96c=9p`v}XX@(i9F}pSI)+_cJKBufFL>*Ztb4@+jq$UCo2)S?% zh|Hv2)zHtc*NV|T7t8RtYL44`8FPJx4wu`-#*hq^*l(H8)te)B#*~^%AW1xW0S1}o z3=mdW*`CTjHRzt~_ogA~!VHi&lw?RB*$j|dl$>$$WchaBOxAsM-7wi)nqAMIMN$fZ z0`cD#32esM;}JU$nDmqxn(V)_2U*v|!MjANuW~k+NEe7$d*}y-B`Ec;tJIU>E8ee)v1qQ6D2~D62uidL0s|QHfbAPO&PLT6B%)zo9STb`>SrG#*3j;9I zX6KKN%j;H!Rl!4ICmd{Pfx>s6K0F9k;1j-4<)C@D2=fvgC=6iIp9QdXxk-=;3?V8; zt$z4jJ>07LdXj+MAHNO;8LV!1J`j5`Kz+y8`>cG5v>l8cl%{uP$KuUJo;@A$D&+Vw zdE;+^y%v&iu*~J{)m`0aCi)Orsi}6j)e*zuSQ#Dld15c{He&(2sJ@K`F z&W7HL*kA!v#0nu@ygRRH+rV)2->cCLjMtouy z{YIV6pD@yF*cg^9KpppyJj~b-ZV2ShxH{!&@j7P2vJmVK;C?+fA@W_gi%cmD>xG2A zeDxk9a>VpoebK(-cL!9OvBx%Z&WA^9WZpNNF_|I3q(+W0S&uvS6ZOOh=Xi1WB_{ea zOEY!kCU?_}1z4@j&Nk6RKx84y;ll5i^ezS{OPrFqxtA8%TB;KwlR5HJj6Mxf416h? zhG-ySH`s^Roc&{N+k}q>W;QDTu3~7jo&m3q1f6&ZTWivKU*8^4d+XP0} zQ2Wm%w`|x_b>o!V!o!s@O(e;dUSgKs7Z4cj2J;=EJcwP^7OCbf0a?>58@LpPYZT_b zgeQE3_f}big{`6$Hu+;=JvRAXQE~9$Da&hZT0oY2mLNk%1;BUpeLWNJF97b3#e<)< z^-1ATG<=JgNl)^G66?0mSXFQ(DSZ0ue>B^^_yHN|pBp_~e2;C)q*f{6sCLz+uk4?4D zL8!hR4h<}Hb2~53pW(dUT!zh4oKAf@t*mu@19ApkI=_0YK$pDET4|>E?tA_8D5|jp zPA4@~-Xv+BFWd5zPT9t3ve4_rdj+G*&Hr|tDcU3{4+^NNH&BLhdsvsqqC9Z}<{v5fIn`1f+yH-i%#G-a`Ie1;zNkQ#sH zZJ&fTZ4G}F+%j{WYZKhAb68mO4qq-M)!>pl>G1eW;X1~BtjagRN&gAFD>xkI4u0^V zN&@(9&qr5^uk+SNkEl0sG?ZOEuCPhQ$wz(BAouJGfc!~c2STFO?s?yo58PN>eI~!j z;XT7GGkhFtF=}Rug<73Ps1k`TLB)3q^PqhQ!M}sZqr{9lIwhJdxlS{{5$Ud+G~g## zq@d;PQ4})&Ok5q(ZV-;AVp%Nv*zm)_LP2{9etiRyE03(46F+1OZ=}wpaaTBkt-YQ; zB}_#BWoD@~+hM_0KWVp&UVI{M7XSyUDzMY`^kBjq9YTr!x_CiM%`fB|6Xg4qIvPIQ zFvm3#i-eT&T{K{gH`YP-DEGJ6?iYly)j`=!e?dedTqmQOG zR1{Bpj#t@uXBz75L)V!6jAOAB`TS!fcjiEez~U+|tm2yv{P2B4>Q+eW!nXb=tVSMC zlL`ru8J;zYmH~g{p-0Hu6~`2JQx1nz1}(Ca772KCDVk{0aT;K!)wRl=W5C??(_Xs_ zf!#d+rzu{~bPLJt`}OM}A3qD~2U;;iO`*%eK+$-GDgB<2X++aGC`UJr)C-wb=!wEo zqc-_@R?>H66n21Z9yR*}W*mCabjXdS$rN0<IkI@lzu&mvWFw=STsHy{x#mkTiwl(M%MOPHpC1RA6 zM4zQSg~c;`Fy)qRZj6t%%L_10ItO9Es%lX^mm(SPZ57uX`dP>95tt%d^)(=?wQ$_z z6wChe$jf@}?s+w2=dJ!{4y!}XFk$!DnElKJK?;_(6xj}_jpwOc1wkzVtwM!Y@dasc z2A-R;J(ewisyUvP{D2}ZC|evM_clt_=lyJjZ+fF)#_45shkt>Ce~Fp3T1eCjd}%o( z4YUm)g>Nz2WlYmWJiZ+~tAPISW{RC*73E~ab`Xgy@lB-k;EgHFtBdzLlO2e>g4wXQ zhTF+n3y?TX_j5|x!p^CNmG84mO~45x0_SqT0I(_YsvJ>DuTnfcsHs+4{|M)mZ8x=) zMWVpS&Brl6ycTd+<}q3iiWtrmPRmUUTe*8((@@e9x(!2<^&8cu3JBpFN$ObS2sjDj zpsCVNZDkf9La#Qzp^gj7E2%)? z&Rh0=RRM@d#BKJ^t<}K^-lm~W`weYI<~>azAo~BykQ+jAy}Xk#x5hJ5@}RIr(i64Z zU6^H5V7QVHG81=^{&}vjp6H!Tg~cIAv{zT>P;fx{PO5?r=KP!! z$`YPrsrQAR>&CgIQ-FH2(soDE+D=_RzS1z4-MsM}Tu4PNpMnST;>_^a73>pm(CIhJ zGTIR5AL1swK}qdjy7w7ZI!zNy&`;yhF*4{4)Ut4daSJ9KMv6Qs-FA}{@$=lR7p!kF zE}kf#E{Rg=XFRXQby3~&+3(tjvm~}oB9c~}d49F`lNO7{a2TH?z7><$mem`YZV}B>cpfT-}9L-^bW<_-ga;j>NskW1G=ZzZB1igBMhUZ(k1zkYl}zG#5Kq)2rRqUFS(cXsQb=@6KOgz_yY?Ye(jTJ`81ht0 zx+cTy)>qPHasVoV*Hly4Tv*(1qQ??7IlhU*HKZ29U);5)T1+Ge3GZ1}U=5;rk|^1sK~#IpNGg zaL>&Q7{bI}=j#$EGI`=NzwIpKM@|euNY7_6{^m@R`el^{tqF8Mo>;DAB+VnhqF#fa z*@<145e!PBZ5<_|Oh+(8HA0OnNdD733>^KNFV*Sj&mC?k)r?Tb@%nBnJm__ij<|mx zLvvx)nawdwes7T`E05n=oGNPpeiVd%z&-j*(%Lk)*v>CJ<)>Y~EJSR(16E{|Q9yd{5@O*%Z?}L^xYOD|o|E>KJUHV2QJ&;)ZMdGftIV2btSI+-R zu()6zJ=H#8ao9vLo5s?!N6w4*@}ldA^Y_7Vp~IJ6$<{3e;~b1<8H{27BG;$rf*U}1 z>tb&?&t~lXA(Pe?u9rFeNLwAMd$OBhss1JO%XDt3p*_CQSKgH&n(#-91;$NAHr~@j zvbX1665k|i$qm{9Ge1=XTDJrj$ z|K^ji+vBGmuBz|kd`J_q_|O7P0t9zS35`)xOJpr2OGmCSc(Fb(VNVP+(3^f79w2w0 z*s>6Rs30?hc;dSfRzK;}r#E^cfBrN2sS>NQtXbu!Aa{%VJc`-OR(4eg_Q-YT7(d^7 zsB0EF<)xRgHHLe$psD2>U>f~OH52KO=xrAF0FOI$b)Kw^a)m(;ZEQ`e`V;!hr%a^O z_=y+Prx`YB1I?!>CyIfx_i1gdeYABx?|og+(7auM0^fl{)WA$-qD5Ag5b0*1XK_0CgTCEvh+(i}Pl7-bUbuo^&qQBHtEbR%%e1$)^wk|T) zr7}Pris|N_1~_DOKIHX(RcBD0yqvl@(mw(%*N!AXp20uXp^1I*o)&EW1XVp_O*h>R zI#lvQXIwe9Ud>{j!g{5)A0fr*pZXf-0EyWGE_A|~p(9$3SV^5MHz$_VX~YT{o(pC! zI><7lUkAQ*#z(`N!1Wmg_|x0iHy@?vV#MXSm=nCm3_MJy5rE6z=3K~F9D{KklBN!M zSAWQ>dn~`+P;<)n;DjY@_eykI?=X=D3xD=m+Y}(0()%9kSztI&DFyRZze#n7XV>6& z(j3lx#n|hT7Z#QU1bD8#uMn!X zhW@G5+kJE)`ZX>B*CKeDeePm{Gda;;MR?ebXx^#8Y%AF}YM_>(OLqmjZ!(%lF@f;R%03uS@okoSe@M%?;_hn@^ZRjE*t0 z^6ObobZRVj#uXqbGG)GdTw}rWD=55aO0K}(6X_R#{lRQC!-nK?Kwv-40XpZY$9|`1 z@oBlwo2V_K_nGEW1$g`_bsm+7R4q8?khS*;Yfp}-J-u|atgA?6-oe#j<5_DQNrkb6 zE%=Z{m9E#kmW)sv#X_J<6d>wUTkd&JtJ#>fGN4i4n1+-}z$8sX+8>yTVl!8|WatxW zQQSZ8MP-%zm|*rz$duL_F^w@J#5+OVh$^DI(#iS4t{&q^$uBU8x9u=*q1DAuzMfD5 znceD*7F3e@a19_QAV61#HTpp ztmQ;n{)tA;q3n5{r$qH&4a32o;kEGh2w^YL2z<4*h{3^c7$RN;r^c`-MBN`rq#bc?FtFG+L;J>i4zx zFv~q<#Ld4M6d(2)?4}=uG*WLnCrAph_(KdL&w(Ot!T6qSz7jV83aVdxPPd4xg zQoDo64@Ho(SQ6rurZ0})>rO9AzAZds(XC5TIwl*YG+s-hm*aYe(}_OKBV8={EUvzo zO(mB$t54fo+QovO`TQAN4@B;`Wk$&dSXl?C8-rYOvpS zaFB|CEh>FV4aBlbM{y2eQO^}y>^9Ht%2Lge^i`xE5s>_#KF^qeUwv{|4E&#`KPmmKjv4e>* zOq5^T`rpPHj0r13K3a3*)ZlKbx(NT&&1%(svhb}rIoq9Sv3MC+Y@EsEXgJD6V5y)l zO+*&W@*Yl{$HH(X{So=Hax#sJjha;R>;vQcWqlq~Ow1WcuJulwjO5 zL{deFF9n-m)jk;k0mqqWg$7gj2!|XAl3#3T`NhBYh-Kae9kbsxJ?Hs=^z<|H8S87e zoH*l|K*8^$zK6>nr;o}l>Jrsd@ep+G7Ub0niuAo%nquE17H=C8xZFm@Ryb$89w;Vg z-*q>$HB{K4%+JjcpS+%;b|Zb%z6`ayjBHaGuWPVprk0z)Kz6b+>e{YOMt^M5%a zT){j#DZYsaq3wTqkg6*opITxpUe6+nunyJR*7lNP>rI#0{UJB(0Enblc4!%Qy5F|(r^$Tp?*(&bG_!c_El+e8=FglbSgg@DG zo!=2#W(l%ii_OECkE?%{>clSF{v&)FE9#7&Xb@$yk~<{30AR6bVhp_cq4C|DPGPdO zhSt$~NsyUy!Ef5K+1F#e_DiyDL#5k7+Q;vhsg0Sp4bA*;! zUIJiE4eia|+?)_OJS)$KfJ9ofH?9%iqIAuT^GDiroJ=wgln*-&nFTpP@|%5fS-zoz z(A$P*a*`nNr*tZsqZ%CAQM)KE_M>&lXA)w=ry>eU7Q@t~N%XHze>Zj?z>QL{ex{Pw z89T<|*2gSBS=%Cc{!4Et=|@4~NO|P#0VDFOa%F$bQ=;}-(R_o01Ud~2PSjR=ff}fn z83`w;Prsl5YV&ElilscWzIJp*rJPHE{El4(3Ts~)9jgkEnR}XQ5F$ZoUiM@7lpR!n zI2UbG#DRtnT{3F4^sD4A#9W2)!-8A*Vbh-~nmp6X#3?JP2j7d{;LayEGtedx^yZux73hENOQJjMd@<^}=ugRadUhvYu4cUg841K~6IIJ-E z@h|r!DE-%#RgQ0+l=(K?ZEUWVyFZU;Dm32%|VLZg#T|z&z>}<#R}VtJT~}PwQ))ruRrbV=XqiMVfLzRN&aK#1kcj zYmsge0iWlRE61XCJRCgCAP-;)?Vq%OaykHjxW+OT*Z~TNxQ=K3z5{ws2ac>wsjLI` zHPFG=myDLol1A=OtS#ia|xM)J*e{*`saC zr6!WG@d__*8-*#48nHfRS=%Ynbh%SdT@n8vt$WoSHSV%A4`VF*myQb8|<8Jop^)D>*sBhJu6FTj#HpbS4O1%VIk~Rfx3kwiuB! zPlKNf<*fAeZ5fo~%gAd$`G$0t99b6-9H*4C(>ia{Km`wZR;41b{9hrlHIZl>MiyTH zo}ebc?k9mg2dQ90`B^X~v%r4^PHSiiJ%bCU>*u9CM%zLRj&e~+3fo5pzx~1g%<;Fl z%xv`EzWf%Xk<`^FK^&=53jbtBF8z<}$gzYP{q2?|Bmzqkqn;lhrBLQQ7>VtA(nUna|B#tX8uaxU=ThCtk_-hqq7Yh6p(j4oFuM@rqO#~8w?(xu6*TU7S90Fd z*koz`RbRBeZtvVF%iE4((gm4PfcydwsGV8#jmy9UzSNpP96?G7xKFjry3g0$xqZ3gH5p?YQXu;lpwo)cn^_~2`? z+Rjq^(4Y9=U$rfN#gl!$9K1M8`i6rE#&oL`*aRT;66 z{K76Q<*oE^oVH~&b+lql7T|zyb7mg+Z#H(Hreckg8tHMcp_7y?O2IvhYuFrh3`N_y z8do3c(|_oH2_Jqc@CzVU=inxFe>hy0J=N^|kLUBJkqgF^1TD>C@PuU_##=lc`?k6J zIS(+#1Cz*gniEIS;Z=E@Qr`I3R?juTI7?Z=Yo3)c|7wqtTLtfDtZ^2%&z{Q=mkAiA zd%eQxG;RLMP*fy9+gP-uw})$^aI|L2a69CRAH=$b4BH|1?y5}u*IxdxYOIaZl}nFP z4H_(xFxE}XO&&qU`8{r>-hnsh@BbY!01zj|1rjnAG=dkGcw|FZ;^e1!mS&3rfW#&B z2?6Sr%Sn)myF^l>-5mzRUb-UDIFG}nMMaQE{7NMI{A&<%ia?Py?=u7e%*oZaH!)wL zwO~nUE5cFN#d0K!zPl9hj+n6?BQ3%sNOo7AR{C(M&!8wLrie}vk3AI?mg;|do^iI` zEXne`c6nHK(yc4j-zXBHF?gd^Uvu`2!%}Cptg%wEpgXw)yrZ7yJroLkzmci zBjk@3QlgS9HC|`6$*K60UsYE9-OPUu8TSDtrcXiET`q&&qPZqpmkeR6Sy}b6A|k`* z0{%X&s|;I_G=Q_M)p3!;Y2Nw&VOAl(7#N{VYnrl5K2vNg(n|Rfrw4l}!CanC>5IZy zNbzz#-!Qo`YB`|9iEK-l<4NHJch)<>%gR3|rGJcGgoC)c4eq4K{{qO=!0aT6&s)n~ zufxzuu5Nz;3N#x|CPKwu@!1nRdv6)H@R*FHj#%(wA8d0`Iq+|{^xL2TTi8PFBKzg& zpHtCCZPGg7Shquh#JN`c%af}>WPPL>SVTARqYvYf#-{%mvVTmt`!|~IVSDWQPhlkf!Xhul^R+rypFq#>#Bd>$Aj#A8)^Wzs}enr#fyxTvEFv}-*LoXub zf!gu+Ip;+0eFYuUS%Xf9t*`+I!HUE0#=8G*>)&VE;i25IYGXq~V;V!_9DVOkHY5_^ zC1ZkWLKh;pS{+}xaDbkuO}&FM_Ki0(R7?GSyBR+>>peRDnDhUdc|}l1Xt`eT$lvbB zz0FcrKyZTr52K+~|8Ulnvr=ytGGMr4*L&lyk4jWt$uJPZNdFnSQ%BZV{4uS5>c@hz_bl(h z0Pl87GpMerV>jowe+3vrNhj2eegXb!xqW^6;D@+k^}`Kv@HfWFpuZaK3lIq^ zQS|#W#;a3YIS)cz@qf1b^IrUY-u=hBORte8uOJhOE_|-xCqk zECV+Pu%8yhqx3e*j(^Z?SxCFMAJuJiMLDg&QYqUvu->htXvMU`s1hvpWh_VMuezwk za%DuA!t~@pdxG$qd77#~?BhLCdK&yMNSSktCzGS8Lc{ZSSD^aC^~jA2M>KkT;b5%L zoLWXB$kHa1MNtM3vExT@puM}n?BL-vlCwzTf#7L-qEK~C2^KVM{{(Gsnc&8OxOLIc z=A5(ipKp-Zw8V_1pi<0R1DOXR29H=K-iikim#iO({d~+$7wv;Tdjufh7|>bl56tj$ z)!DA8%i<)T`#cx|L-pw*z3e*HI`(L3DU0XA@m8yjw|zwBb2xk-==1TRj=(kW?;jn!x)gthVbB#C#!qA&PC1A@!KvM7j4{$kO@zo6-|p0pw`k+IkyZ2V2F`18g%D=2_c%KRbjiBUIJ+EBGfp6+j9j&d&Gt5oVps*q1b{M&_t@Usq8IiY z)Vnj>yC)Q$+{q~Jlz7N>B-Q)21r2v{#73|iViL;67BSRBeEN8>>jU+Y{CtJ*V0f@d z@sEJbZF2sq`NUCxZ~VKrsvE)!&osGNkLF+mOA9~|2FvLDQu*gdG)d;4BN-}Hd7Zmc zAge!ChSwa%%aqx}r@nlRw^W*v@hX-j`AMKaa=AO9Gxe4I`8wN%{$gYTHFjW81*^`4 zggYUAzwd1nx{2zp1W~>3(P3RvLACNV3+&C!OQX9^st;VdU__3!)W`2yt2Nraq;I?C z^W%pov%%vo$oWs5Q9`6qADCwHZ~*1>t^6(5+|~AGV;dQgM73}PYL_;v7B*yp+)c7l zTB)rytn*6K4i5V%ILnKk8r#cZWGsJ``dK~SICTgG{A$KSEDzyI^4kbJZ7RA$F>Sns z`iPaC5K7t{DIup>6$v$go2ux!1gVY*?bLkFPu{!hDsrKyNEVwGB+F!ZDn@E-Cjn7` zi;X5{f**U=Jwhv04m6}WaXihq5>(a^d59oM#&}#?j2F>2RYEsZ+~_U^tHCjhZ?d$D zJxmsKoK2qa#Si51(uWe1CRF;z=KkZwz^|X!*zse}|aaI~0!rxzJ$}o0IQw^2LgzWARPYH`Ohs zSUbmSEEfbMZI7dd5HsPhQu`DJmeig!b#zQiQuN`Y{l&DvNvsvvtZmalh;{ z=;Qntaq2`8YxM;f>4Czy2C!9v79Ao%-eUCqYK3sdKo!5T2zP+WwkhpteY8uHtopbb zp2*KPpAYK`5ZB_+mF(icWNzW+d6}Q>nEr-sB2urH z%LZUscz(9VPls(m0;8mFnYrV{_A6|m4+XBT`?jIjUndHBvYa!Gada>T0~7pp%i8J) zSRLp%oBO4ytx%XKFfQ^P488CpVNo}}Uq4W>`v_d*Ul!Dmu%+~RgUoxywrN}%b_F{(bN}Y zpBTD=qQ}d6VVe>yy^L;^WjVMq_!QBLw znp?JxjOl=^SG?6J3;g6ByYQ$Aww$Ex=oQAT#N%da)b^q_fbVq=j>^$Se|P_tyM4n? z$Cp!I82fsyKP_k_}C#>BY*%&WU0(Of2{vc;#jAaHw=n)5`(S^tt(TRx~{hXJf zj(FQvEhq9>%3OX}w2iqkslf2tmG#Qf;P#fCRkCJEvYeo`#ZvYlHc~Jk2Wv)M_CLa^ z{|>SK?RO%R+Xu;O@ynqONZH?h&v9^vhE^7625a{gdO&e=n_utzR%l1d-1ext-t@;U z)e+vlA#ZFc;!TN&jJX6E%*FS*&n)EF%?;f1;r*txm1YYQ|2`2kFZ1iGK73e zl_~Z|q>KroOt^SgqM-~{Z}=>%hoJVm)%*tdEmi%G137h;?0I?5{pvl67WJuO8P$&r z#1a;f{hW}MD~djR*B$5UpXwC27cJVI8XB>cnVQPm z&ntauO)?v8MDACa$P*+*2m%__i@gNl#LhtImaAtQ8s^~cVc!D2&+hnX_ZPQQ&8*7K z7#xnZaipz3M@_R01>YD3#{^1Ul5~pAF8R2t$LO5g7aZ_EU80MYPj!ig5|nElVLsY= zB2SJ$;K&sa_3l4#r}P9eCr|Bgi)JJ&DgiogMPv)~w;4#8Ti!6-@9k3cQPQ_ZPYvU! z*m?qaT3DibpLEq1eynz7f~ZUoVuJ<(iyj4wT1RhOD(h-Y-Dv{a+MA5&^I@>}6*Ab^ zl~RY9`ft0MqPpS%$YU%?tMQarXPd~F)DKVNUA5?6 z@ZP>1E~jKdo6{b)aFzz2XBz;fa;u?x$KybQP0sZGu;B$x*v^2cd!Hi#5l|E`l!Kbv zv^C5A_(T2_7w?A@{8SlV6bm~ffA`5Yv3gb*EIEe5Kja06Jhin1idDLuh!=8JfY1cm z_6svk=x_c$N3_2rnpNibic&Uu@e2W}5mdY89Wq78CVE=(jv%&>U%|e@s2j$I(#*UO zr;mU@52RXIbKd&}J<#2NwcR#&I0X4gQGDCk*1fPtNwTpNLZj3-9FNW|^>t~ru)YLs zF*^E^n=P_QbQ}_M7fEJP0VXmR_)&lZ_eew2Y!&gX1xbUi;)`{)V&QjpSR%J-Xl+De zT(GNND4~`rjQKu?b4lE*Q7OpdVt=(j|2=K(EKz}ijopqp`QB=)l$#gi8=5=oiHp}P1I#BGe@Zh%-JzyI(Df#KwjsH|Q_H|D-SDKm6v`wSWotJsent5jpnS^X^T z|IZ|a&$~a_hLjP0EFWejVG@aMYKX1tEZS1v(jKh?Azlu$l(I&Ph&s~$_9o3aa_!8z zH^C$&(HgCqr?S*6brLhyn6PNR=||ZRd5A9GWJ5614WTcQJBZBozpW`36Naz=66Qd3 zk{pw(qQZZa5&4;Kaz4{`#NB*e?-$kINMCmxYhgTDZ= zQbTF)b4O7G3iS}Hgt+SmpROCK)4T@);nkZ>^sS)&(i_4o`Ff8 z4}yo({}({S>@NPttST)$o)#XwCEM{s;W%g)Oa=J`c#a?rZ9@bPI0EJG?290W@{mIb zH=5NuI`5A2e!SmKi{NYhvo~JN!K=K%s~kam=YO6S!43RZXC+(y>RbM5R_;)71Z_8X_?pI1Klx8Afx;_VS1WC-zZ zw9kKDy6xY(D@9m=^8Xlu>h%8@;va+LzsnFT`ze~;r#$MYp3Dm99h=%B(QbXw$dJbb z8x@R&%p3WSLf`$i0Nc$0kGMn$r+u>{)&55Gx24zDfAXo<1=3~}_96d{1f2T?xSR~i zWcr)>=^rfW4#15ZOlLZOj_>0Qc#3!HSXkP)g0i27U!%zzce3<=GYP$z+6m>6(5$F`FT~)7h2acOS&V;rte`O+60Dr7m=qWN z8j+Dk1r2~uSVsvt^Y~Yoa}a_W6|FStM6K%YGg@2Ja1Q*EFBxVg(2;{SL^Wj-)#_pl zl$BB2@?-p2)qeqU>kt~u&aBKynBKv@H!46P6;XptVB@Jsa&u=hK4qvFAU4Hf=e{FN&Rl%6wk^?BBwMH_>2!6fw+5DvWYP11AS;kpv|E{oA~pKV zBj(T_7D|YDE7bUnqODa%7dAe6ghH^KQRq8TjkjZ~%Y;agh#lJO<+28us5&e05Sxjx z7fcTOC_U3SioqGhH7s$yUMsQJpa`DdZmcdXU&;QFC!URCGF8UEHum-^k=-5aHNd;f zwP;9MsV9qXhFz2Jj2gE*;9Z3zMPVxMtv$XW9O%>0;*ccEg)8>msZCf7U6`$HG^N*= zv#`y7wz0O&L%(7j2cYu~Gp?A7icgS1fV4aw%O|n7`2#V$Zc)( zW}oY$kCcuxUub)c8+|6_K^ptvd3@P{C+oAq=*#}9Lc`He7H!eZML|L<1Fz-$=nlv7 zhsMse@((FqMo+t{qc@Ypwq_6u8gvC9oUfPx68|yr?B1cNVVae7H`(8i%=YES9etYj z+S&ITXsVmC?WyLr=WX?|b!;x~oN@G;FLsa3+-EK?b1+vZY8s~Rgw368y=7kLoO36> zFo$kkvjXX^U)JqK#uv!uejhS-W~~C@i@hF{JWCXz7GE|A6BOti{S)sbXC=daK%`{} zbpaHME!$Ej2h~e%%e3Stej!t{5M=n=H9i&0ir6I~4ehO-k!C+2{{rMhzqu&-mv5B| zZy9%!cjh7RSuD{LbY6TBH6di}ZsvSG}x z3*uW5=*FO_k4*ue@#6|~4yZ7YdmZ1msWc82~pnuirGNj7qsmd%v zTT^4}t!Vvdv$PP;lg2n+6g;*}V0@^b3mz?6Y>@VE@t4S5m3Twr=UB!ZCgp_!i(_w& zFc1ZaPpAh;#09o=X*ZnlR~-Uz=5QWzcp#K9Aa4EAmo`wJ5hD67VfVA(MnUv!2|P7p zG9|@E1k$?pFm}nF$muyfS1=m-n4F*gbbkth)nxBc|AGl5%yp|O6>M>AAKabf8rYQ-Pu?YAcKwy;Ix%*vMcD;}}=yG3kl77%t`uTj9U)U2Jhi zumw$^jAAPbsZ72|A}m$Va#C|mz%z5QzsL+aUe5^a(_1(OJpx9Q7->3ZPMs!?*6>5xQ|fG0u9V9*JCavDJm6* zap${l?4bVn1QzMY>Mi5!amlGkROr+#TRIgW3&}iAfYBV*({H4UE-Q9 z7?rnzEz6YmaNUjz_bfOgE@NKUEBAQ7k2mv6mLu`pQxn-15~$f^;e)_%8B8V1I)J4( z2hA7p1{>#Hhj|?DUlvWu0;C!mzGpU^vad;YBV}CPoobM5(m`XOEvgi4VjuPxksv*U zOf`&*8)$#u` zg!3%uz1K`z@b8Da8%IWpTXIJm^f&29NFsM-(?81mKHajuJTd-o{{!4h#Sk&3FrbnX zs5M>Nm^!?;M_^*`Erxbzx67Tkb#W$-Q;)uj*+ounuImy}yK%$%7Qbkj`91wDeRJiR zi+*S^!y;Rv0jNN)e!YbQPo({9F`n&np=(CK6e4wBntg?3daz#fQ45aIE5!}v3kepr z&qydA^R0Syaj&c8pPnxF(GXtlMC+2xJt(%u+Sh!7CqThW$_r9CV0~m6t>B44dyZ2( z(58nxOgy-|3i)mr)&n;K$|th$v|tTR501Si&^lsEkW<0n={Sl1-mX>s(NfHzJnq5& zn6-_}>tM@oC!0s=%c#)(!t4uKTdM}>!6H6I>qm7c(m4>*kTwCmc$~C^_uF=g!IbV> zIn;A2&gQ4d+9;GHf#j~oy7Lm#I-Vb-Zh&vMG`T;>j{&vE(CIEHMvaoE9`D5I(=MGx zVz~KWbJ)4Fd?U6-O)e=n87Pu785TUQ7+naWkM0j<7>WG_;3nA20ApVk4pi*@U+leQ zRGiDUF5Ec5J-AzNcPDtz;1=8)cL^>bIKeHrd*cw?Ex0tU!QBb@I%}`B_TJ~*^W8JX z{dMm+KNwWeMR(PF>z!TioX>oolmzCEW>A=VqpAG`9+!hS;_7XUoQYmKOWk zSH9aUC(2Q@gW9z>jCOzknajW#)aLyKkLijPUFpXarzJU|rcvkml?gLJx{8E7dbQ5ywTTRUUQxJd9}mnY@S{+$UTt`*JKg&$MIKa{#+WA+US$Bs0$xuz zYq^Wc`wnTcGu<%>C>4=PdbxQ|O(-OGC_R^wur=dfgKx^0$8W~Nnj8d1P(hlnIfv0^ zQkH6NCO9V%h+P(htflf7yS;9kHz^IqHVE*`>ixFLl)fwTlY59XFi+&zdI)h&;vG<+ zV4PBg4Qz<%ixyvPRmJ9 z1|~2jD8J>q>r?qK%WjOZPymAGDfPR+nNTBoMZ@jdP;ZjThAi>jxV6O<2C?plWn3yg znBp<*{RKDOHc<3ULClEU?kwDhqR^h?XV1D&h-D#_NtX1Uj(5Qe?sxe@N z)@ic-X2h{9HzCBP3QaAtdpd?6_#;<_pnUgfJWQ{P$3jiHnW+Sh-xyP0u1E^X`M6GF zLC5*K`d|WW{ZW_n zSHQPQx$n+d@ndrG-u@UU{6t|#oH{K-v`upn4FNUtG4hB3ztT9!dM}!t`j}99m?i>% z{vD~LP4H8BW^!o>@`nA%YKV*V`B1(vC)-aSEvYg?eR1Zb%Hl8e1|uqR49V(H3fKYC z%J?u;l`s-B8U|7&NE<=CjcgzMtI2q|EiMPvUT9)AUm>@oZ@4MA5CXiX0pEp=36^%cGptesK57;EuEoE)zy`<%a$iz z`F?8;Zpv=iHuCgQ)Sgl!prSuMJP?X}u(N*%Hrt00x>6#9Po)f_u#MQf;-Pm3An-eS z?T|_Ki&%xGQ1XuM$sT&4E(9mWDl2(Za_JE7K|8@X3DQNQDiZ9tmlBFY_!q6uoO%Td&n>ACA!}A;dMQ{hzIaLw4C&7rt}eJ z#v2$c7nXs$fEPF={VTF1j@ra5yf@vIZR3OkFQ&|W?+j8*kCWnLJ6DYg;EA)*yk}#J zbY$zx#ds!$rUA0U`oKq1tAhinEj)8Sj?+`Uwj+gs(49utzIY0;?iAryLNPw`rn=J0 zJ94Jfm#c%0=It%kXqafqUwnvz0Q!#b%AJxVAa-T0AE^m;UeSqHMn)f#q{+|^=I=zU z!uS&;DZXs&DHeVjrtvVkI+3t)XS+V%LPh#%9%ryVGU^N-ULoW)?cUyHU_XdbcY(2l zJ6Vgl;&OL?|Eel$6!}xo#X=Z^I8AsJg0HL7k96Pn?Ze3MM)#z)CyMJnHv34*CH)+e zVl#GZ%$xBl6k^drF&VN0JoT85@Ao8Xl?GP1PCl+J&t>)sCl1qFGPfkf!SCEqWYB#R zE@f>1O7P5CHa@X6&4|v+FeJmN7J~-%g_r?m0Cf zR6j93sB00@X1Lu>3QUomWQa^9_;O)ucE$hfq z<1LRNVn#s?(+Q>5inNsEA96}Ju3I#)OGk`=(+b{FF=qmhuLh~68(6M|MQ*LBvtOGs zz$R&GX~dyre8E8DTv1D}Dz`UMR&^?YGABCEvb0JW#8NF5?&3 zmC?0iMDj#-{!9|R+kT$BSN*G@q9ye1XI^w3NVKl_<=H>&7Xay>EtquLk5kf9x@1f5 ze*r#MS*xbzpGlWfnwC-(1Fz}}}EkSa(s{8s%JU_JIqbW`T< zmYB=i$z}RwM#(1z|Fgk|ob&v@8ejSIEKhKNxVeAL?w`{np5nZPEa3agNY2y6i{&%@ z-wiaYv?nPdDJy?-<)8PEW5f&2KU)Y~|FgLNxpMzuTdJLuLnw*({*=+gVB=BD%n_s3O zser*72yNGgeFL?{etS1ARVhajioY~~3~j#}*re-OU(q}yk397eCgs*%s?dMnI27xy zZFB$e(;e7w?|YtIr`>5JDhFkmuA>9qdz&Kf6x zel^S@5#a2u<${)03?3Kyz6h3K9(>%F{lb-VhQa@A{e4jfEFgpz_jVW~lz<9JlYI(a zfd2YGjCel7$qOR6qCR~y$`d*7|s zW7_OY~E^qS_PY<6Z+>>$+t+$=Dj(LciUUou}83V28y6puK{PdL(3ZtrUFEp7DgNS5NBh zbzYI-au5&s=%Mx*Ur8S;iL>Fd#l_7Z~jK^<>+6jecq8$rOJ-Z|AdieELW2CaYI^+ zW&lk|KK&a1YZwegBz4r#?z%}}ORL#qHiF2U;ey>(^Eor2-pkY zTYt3#)>&D-*9D3yUcqM&WM3)UJ(M}8CI)CsAfqWcL&6m^Li|P6YzE>$G?P}udg^dT zqj)AUqua$u^ldFW&=E5aHg0rQN5$Kn0!#*IKSwx`!`IEAbo~8<)g6g)F3(j0DxxrP z0@AGjHea-}UjW2yz1P;1_3bSn2$dQ$&U*nm;q$WBqQ?tz8m=cIVELKKW2WsT2WVKS z2_<2lxJCt4=bHS0ev>yAJsP`I!W+BqZ6$ zAKv9C!8+-$A3t@jza*9a8LRwM30xCp-uRTrzpR3pqpzlD4Rito*?Ja)V7#>tC?Ve= zCX;|uloFS+?cfpkS)<4O^*$nj!JKO9*0lECjB#j1xB)}nUh z7t$!Bhprq*Gxl&n9Uo5uEnsB!GsQD<`Msz~%WNupR#LU<70YUF&PvdbBN%6fPU>4` zs^5&<@A2<7O;I&w>WwY8LzypRNJWM9z|V3p+d6_TLXhYRb{g}4Q{9?cdsRKE|8^xF*bad#IM35!#&zp zsA~;^@=TG?NftNd$~R7Wu1!rLrk!Zd?l|Hcc$}NN{j|ksTd86_rF-x_-ljIMVRWP* z^C6&yfJjUqLwxuea7}QXm+lQI0YNS(CxG9hg5v{3 zZCAR>#7R4yiQ()_b1m6n^(YEV9nYMCK@6rJ^0uL~oQkv!mI|}Rqe1hf(KW+Zbu4`N zYHaL=UU`tSjLK~B6jv!iiU)OVzwrDV*mn}B1#mK7W_8yZ^BFyY z83?*Jp3Ph)510B>+|Ve88=hIl&wANH1;ZERjCKgPNoNec_bJg{^mNeDvMu)!L_SKG z`H){kh2~}Pu>fi^_eeMWw34YcEY#ezj5)fhsBZZ7K8W9_gg^s1s0G-!6Wl}W{T)G7 z5mKVdoxrOV-DPgh4@20=3O=&un6RkbnH*G$1ltqeQ7`uu%f1bQRzd zAB7a#s~f$N&Kt^Qb>n{=A-YK#bTpwJ)jl3-@O20&AU|>7qw&yJ^mln>awSW8`b$fb zUP!A3VR#qy0W&}XVE#7*I^3SMqt>c%yt*E6HcAcYlG-v$TpNmQPdnl$bZ! zTqZud8cIGlU(q64sxkbBltdqQMANF0|D{uD?fX{Q1Y`iG_cz$4OzOKvo5MfkYK}^e zO=tfbdNq;s6(fICpzgUT6fsvRjZ`sh#o%w7xIcFZk|GN2%6!+j6LU+7q3FIto3`d=+wC%Lp4NLm1Oq3z~30;Yo znLi-e!Vg@S!dW41uAW4mOz+-M`uDnjy%I&(Q~ z;7(3E;$XALqTLJmoJsl~SECMfojQbfd|n2tMalxIy9Yf3hX1Z|Z<>h|k;2xTy_+`& zD?w?AL+NZR-F*IY@tQu@RZss46uFL$rkH?(isO0&mkZ=!_EVYRqCzOEGC>R|KmK7x?Z^&-MJU< zs}>k|e3L?LSMn$fzkVsSL{sR#(pO_H1aoOisYB{jTXLhsSp4JE(?n8=E-K&Ze4K0o z-gZaT`(+g`MH$(tp`k%|l}T^Xxova(+`L(tQDEK$X}s?oMH$;Bzd00bOT3Ihtb7{h zng6-%H7`zW6zW;%xd;;#Rf>QB6KfNs2&0gsp$kT;iGZ$%$~WSN0o)1&&ys$jt)NZ& z47tN*u#UpzCBT}9tSuiN1Yh_Lc%b;q*wT~&VcE75wR4U$q^i%1Ywno z`o7Z&mlj-Nd*|LX{lJV8C*?%UOb8xYVOQ}`h$~y?UF|+OQdTroy9znG{ZI#iI5ear zQvr7z7$DJl%VPYSN&*lGk5@s0tbQ6bR-M-bM)Tk}h}bf4g2De7ESw<}-!%Co%}aKZ z(PV#gG+FpMLMIj3$KT&2*Kp{Zxv(FM^9eV&N6rAAQv9*`KF&}M3pdG?c#-Jq8x+{g2-2Z5X0>&kMQ*092_ zhm$r&LHJDFZf+*WhUELU&^#M#g9y~_RSCi@BN%Tr4+FZGht$L_2OK-^FP;xO2E##Z z1xw6rc9$Vc<%}h+FaS1K`$4;XoY{QGdPH^y3f@2_Wby zRJ(Z}>(9bBvv+jP!&qIBQR-!A%2cpe%Jw6S5{3UcUi(jwu6=5jySs*yxqZA05q8ra z(c-!IK7#<{9YuhqYH}=92umM_fxgK_qRdULwc>5=$I!fwq)CN5NyK+etmImaZ6Pv3 zX7LHdLF~t}qT=s)GAu4ei6oE|*s=z;M}rWB{WSa;+Vbqi3TL^IM|JFbmuWKOxF~5+ zsG$^CPL{fFP78d3S>1>^0z3r1;yHXizr+gKz`N#tth!fB)@EGA+5zFzV@tD?dn;GrdRr+F(uf z)-rHp2%p8@`xVj>HJtH;z?F)g?WCy{bvpk1()4crt2SEgU5Au}kLNo+!N>5UR_Q*g zeqVdQpBa;|nx)ML=HFx$M4`zZ3%@xj&=$hznje|!b|L>)HC<8n`Y;7S{~ZcK|(OXCnn#A;jipad-SoY#9C5O_7Lcarl)z&FeV3xIxmij zaZ+&0JGiR{&GhtV{^^MjRKy(m2x~no9sCdy9W&xEdQsF%Nf4=E$(*F-gX{UDd4|9A894J^oN{<|LuiZV&(7@9S9FUg9S3xz+LYeo4-OR&6K6|1 z#d5%t)gsKSJ;Vtikj}a= z!sA5&?U^vLSc>8 z?Bef7Ir=Lx+Wf;4E&TRG<#o{kJ18&{=;Sx>A>TnemJT$lE4dyCmhQml7H2u?jbDJw z_j?gKX*H511~$4Kg}XcUa3FEtk|iGg6-gHDZCK08>wRUJww770Cxs>7TH@QOA)GfW z#8FNB1N`{g^T}85QG) z@ekH3X=fy5Yo&11QU0R#|NUFzY5PcvA^i_U*M;z>-prx!j$(cqH(b6hM4lKBi}nh@<_AzU1Yvj$a=7!wK2XYx){2Rn?z= zb)MdmT`h|~{ZKWc&ddM1a~mPL5AwhJp4TNOCG%G&|L@64{m<0@pS5~XMSSO~n?vrn zK-_QUK{!*J&Wl^o6|(t8TOthzJiPX0(TjWi@ZGv>?~R%m zhib6tfq}9qj+?rc$O}c5w_fp{4b_%jAcVwj>cyf(_)GM|)by18zJ=dfEfG*{PIEeNpjmGI#6lgvZ9=)VlB zi*v~@%4^BFmpc{RUltvM++cqV6aEsMMZ%2>iPHBdJN*KPKxPs9_psvv;vWYkRhBPU zKCutCu~!h#^xuQb$@gUxrCdbM6Ic8vLH=nFy!D@heBK}k2-q+}vI2iio$T*HOS1e^ zGpheb>*M~%`W!E4x_<#u{xAb%eRQz@q5v> z|8dd9OR`n}J2Mx_;W(dMQE4r9r#Mbb3&wiy|Lc|bw+m*kZ{7IXQ4X#@3G{vFX^o#! zQ`12Wv0LQ&7ob+xgyL4)3F*t{JJTICHnD4102?FB-zNSK*FgqHwHrwi#hO?ax{NXa z2dxV&32q?6*h65_#T>{r5{f5BscBSP0vi)PT>}A!{}=lQ*qBtz^OYOErhzxVa5&U= zBK2u;QhuCJx{(&eSD9aNY3Y{F+uIUX@4uLa^tN@QU54f|{m^>Dwpc~z%Y~W%cD8Jl zi_|%4Osto+;{yElvHJ-SDZ?{=gbnZziu0NerkX^F6B1Sb=f3*4&9~eDp_;^}37Sy` zHHCe+iSI|s>x4)<&m4oEm<6y={|MxrzJWEy${_yuM`+I_aY(*GryYC7xdVR(j z9%%&P4R?OWF%M@)@0w>S9GLN@_a@eS%m*lvY0U5sF5_buk^lbZe>bx9ajRR@5%jpZ z10^`_d-x~Wh?r1kzUMb7gi0A64!9i?Io4bl+oLhE8cFP3T~sjV6(9ay%73`ske9*3 zTpaC~Tyd5o&H<^HZ0^}h#7_E^X8}yr4T7Ibt9ID+4GLExj3b)ezyWnhYvq@$fE{Nj z-2duEl!ipBU2I@fY?0r43+Ur(jS>IG@x3vgu>+nKwT6vAbS66I$KvU|MAyj8Ubw^m zIiG*so4>t;^US-Nndiw`ZLpqKF^3mBgM-LD@eIW@*Ql)*^TtdiWso%5MFCRT82>Q9 zC>zf7Uu^XEYJYnTaQn>k-r9jle5GYe?5J%j^e3@>SS)lZI)a0Azv$@33}d+HObJF? z&pxNGFSsz!HUI9_7XIP<{_tvZy5m)Nl{E$Dkzq%_TPB39O}in&ZsJ?+0=7EOuFo zS@NkSs!*9}e14x6`_vFZHBOzD@2-W>&w3}S3$Y5sifvM^KMiROr@r|9VI-L(-aGU? zh8r9|Ly8junLn(F4Q)c?Ds?2}IGy^(LFA9>(|^z*|Ix=ytGo}FKi>s;5H@Tr_fj28 zo0}#NICTjJR%CqmM2gJterQVGR%p{p&5P?hE|bOw$nK!4iV;GR zl>i(KW-ZOyDXs(78^+M4hNyr`Hfo5lZLqLxInN!Y0a(ffCOk|Xiwb>9jRSpG4C^1Y zvg)%pg2F^fDJrvc7`nZ&9MZ6%!vNR)QXDAz$fDN9?0LMVb=yzCsva*aK_HgHKo{rA zj*(kQ@d9WR=nR%hKhaWck)&qB<#!{hbBXdt_Bi=-Hos@|-}u3G2CF1v!6Y2~@Qy5& zqD@>uOMatf)tft#?q_n40?+@P_fhL7Do~R{(=7CVdHi$zNXcNQTEsRklE(EJ1xvmw zNs7s70btQOhM3MYD~(jR*@?C2ng7;6b4US)C{4#6peZ5jDEmYF=zF9P_ZA~7t~Ib^ zQAR+F#;R5z)I^%Ui!R360Hx)R%E1O1joFiHAMH~XENMBqNB|WeYno5Yiz+HU9pLe2 zE#GG-<)Qqc@(e4mRKQ=wg?U}?FX=BC_Lh03jQ%e5tHF7W6pFMtbU&+B$PY<8!35Jx z*`a3Wq=`+qWage6Xc1RfO$;J^P4Fv$u7OKtF$73165OllEiFL4rE7Aa%LLjiQZ|84fwm|8L9%;(0WByMPH;7Os%J%tJ~eB1dIJ)-`{ng+JBdO zGQo*b#!^-R^MQDOS6cq}FBmQ?H-k)M29Sjw3Q!6;@^0_3-VI)r9R6?h-2Y@%Qpry0 z*4nnXSPY#U6KN>bQHH2uH`v;Vj75eiu0EeoMqn_#aU*7>@Wt{#31FYebSqae7;goj!DBmQuJd$$Q&_L5t`ynCfi+OpAbb#V8jXAER507(D@7!|0Xe5 zI(TG2YhE_m)WQ>Iqs%$Xpf?`6;=H&RPL?K`Wf-$$n(p;B0h@<|pzbvc8U2aIjKU|) zO;Vjgg{X(fV>TtgB)}zr9tu8}fop0-oLH^kqt)0tg+3(jHz0VS(qHk(#P@A9$4D*1 zAj_^vFV$Yx-6TnwOQFQYNZl}3CAkKNpl{ckp+Qne8HIrIaf$pl^VFf!Is)Y9?vz|& z)AIp(=(@F4nye#{DbZz!)Yqv7GYGAh9B)S;EtC)2wt}K?QvMD9lNO-z6+6D|(e(QJg~=)Mz|BEAfkuu{JbK zI)z~pmv25b0V)GlOyk!AX#d@T{$1YxSD8Pcj4-#49RiK|$?((iaQ)Eq7r_5fUE`iW zb)}qOt8$6#It)UG_!wDCCf@{kjg0x_>;`vGd0RSRvB||Txv_3VE-Sv&G^SnC;Wp&l zU{!d<2TRb^?Gu^1oCRrcV%JMUvCtbQ*tij183+^1A*zM}!#cKCEYpBhpZ&Cm91P#1 z+lj#*&1a9S*^CG^+G9LL2*ZH7ZuVx;ih%5o10sPsV&VYni{<0ME!$5!YwRuHJz{rq zB)eornxLh$BE;9mz$FX(LF(_Y4qlYVRwfY&LbWNqBHsZp)S09^?q@2a6?XNJn`*es z?Hr1H5=faCeEd6vHOtN%5<(AsaY0xry^aVa2T_iK?hs15b<7Yi)5S0j)dzDrI=Yps zY!H<0oqDpO$Y#aXL-Nd|r6srqqRH1iye&Fk$yKLVB5q%$fh8*Nj#Mub>iSUm`+I7L zW=!npI!A7Z#7K4Yp@8KTH&^lmD|t&uR9Whdrl=RXXsYPgceqvtZ2O{P{j)?z#SC8G z4wp>tr|*r2P0CK+Bqnm@lOLpo5h@{yf0DSZ{X)~>@lgd>6-&s0F$gy4|G!M9Y{4HP z1xah76IC+PCDo4)qQ9Gi)9nZLu9W8g5*nLlV{&=om5Wx)zLTXrB;j%biH@qsW^*He|P9&6)m#^XT9)LQvm23<522GvXr;ZnuVM!A@; z_SzCQm1^$*%}-B{SAA^-#agCoISjF}PaL}*3Qlc_hA$VG_M8;-+m?yvOYO>8dVQU`o#4#AKU{9d>0ov7ZVSZ^#aEaK43h{ zRZy-@fG(#$jm7NV>C;0ac|K+Az7)4|~GordV`fHLQGwn?%+^eQRwub!NJJi3FCEY(}JWXtpK z;(sQcf1)tNfEj|}n{4V}wuk5L{Os%JzX5-tuGQ+wri-^u5X|Zbvyq*u!U2zB_x)au0?}m->Ogh5 zq=y{G{R5r!!2q`hS60La44pN_d|Gg{$d>Y|yyw~X>p?CXxN4aD1&z*&hK2Cz8$A+} zRp8i^;qm~6nJHBa;?9eKZSr|`e#MVp0A%hH<+n*#TML@j)TsE5Q^#T}WbcBn;aktp zJ>U0fc49(|8(Ng*0Ax-J>9zO;U?Tk-h4OPML9jvhoGi5Hd_AT83n;d6cT2+@C=Rb%jhq`RBPU*@uhM8X?2>R-^Ts-3prIV`>Hpze*wVq z2ki=9J&TTX+dQVP9WbpE(XXw}a48cO7TEytlcjVpg@g@`1Y68Ln##dfHt9hJyjr>W zM7hXL4&)s42ndWhlkw?=TmHm{=|Up-wN0&!)4;O|Eb9h@1`bM+{9Sbz0Ggmvvn!6| zIanKXuUz8kM;)XneR*In)Zk1Ni;_c6x8y&myEuQ1juXxMHgbTOSGB$o6hqX(aLdI= z6*{o%%=s7-TxER-cLsI%I8aKmG0UyUsL>c59`9kc!9Qs!e&;W_P)UaA_Vazuqj^}R z(b28;878YKd)O-GwQ#HH3!KhJz`1m8o@b)kf&&o9HQO2?3DH%ia1~FY`2@McZeK1A z3_K!1##>~_HoB?E%`Hqs`~4>$vi=Mp-s@$eme`9DfO@&GswqhLC|_LTAu84$_tfS?30Ks1vHy~hC6oCGwg!+mgc;4 zsAD>;>bHFIC2aXkO`#im@9mnKeziq3R_XiCCHM7e)2=626MRLNW36o~(*3{yK8E(F z1Q{~bvuIIVTT;pyH-eAj75An}^@u31N=7twl=vrP0hZ(@wb!u#TT--nt`6n5&99@G zJ{s2IOm9z6@3ihWQ*ZweR^)i<&ZdYRcpNBnD6)FY6o^nooyha>ON1eF49@iNMw6jT zZiMcAmY$`(mwjPuYG{gi;m4zm4z#-3gQG?Xjv~I9;R5A3O%R7ig zWjYI>*Mgb+(4WmQ=c`q1rAtzk7R}%N`ZYz^Tz5|#I@4h81`6R~{erJT$vqzG+hv^t zfyT=w79Cyj#r#FOzqN(!B7y}5|!X(S8X__EO{Q&gdSVrE9q5q zAOI|Iuf}YwB}Z)Ln}l(q&X%#$;@|4PLlS1{0_$k2$v8xkinOOOFr8T7pH*6xRvmx^pb{fqOYN{Zj%%g-g@IFvI#eVGZd7s)zym ziAe(gvy2BMY^smX{7pp`D-KNyCUlS*UH*oqDbTmFWOl~k^;k5vU-_P4ez!IPU4!J6JbXWJ-jO=K2duWW*g1wN#?o;0a{J{G_Pe zHRHN!q=3*875LJ%0blp|2dk`h$k@m4$;)U99efh2`yHk8sWQ{#_IN+Yj%-^Iqsi#c z3-5TN&UbEET&UMnH13KF-+tmrt$Z$|*c{$CTTf`;G3kJn!blyX5`ROaaIzEo?or)E zS9fKYI1${-IN%)h1Hbf^u27358TUr$sQ1x&0m88*w}o%$Q{sxOGxARM=UE0VN%-Bj zZ$+)>g!sB=;=!tpty~LHO&MSt_RGbAD*x6G#H6qK-9+?1TBn#igu15;@?B3BV)f%( zaJ`pKknTl^;Xu5Dk1=3+J$t2agbICmr!ak8PjwKk_l8S$e+c1tLWhd#-oHIRDuF?| z_I%mKkfQ10RS;QCg|$(l9H`rd(x4JeNqn_4X`c%B$0u7Lj*|>ACgocf!ZHBH6WXfs zz8rU9WncWTa63o4CH+KTWR8iq+76oOnCoWo`cbRNeg7tBQ#kVu`#PVx}yf zq)=4)aXTvYw5Rl3p%Sj*F5$bUYGutIZOH}x6iVmC+kygEBTQHMV7CJGsb-(P#~i?^&Nm% zwZsMZ%M^1+)zqT!^{bDE{Z96Jd&4BJ1;s@|=Fex2C5N^qR)h)&@ZYvLqlpW-OmYh{ zv?du5$ace;VM@dVEbXQwS_z0$Zdr51XV71JYRVqdB4Va%;cF5gvUMd0m}P^;Mdqi3 z43jbSeoXbd73w2@HsH`qA|pb22nir)-PjiPvz?`^s}Dqyw1u_OGFFd*YMO~z$(e|- zvpWewWHySRpn2;Qx~!~kT4e2vz`jf9^5e?WqsQA!v(;|*n9vLEWFf_R4-j})g5di# zI~;#!hUx9=Y=Wjnprn$v$^0qSGIF7NZI(m=6`c?(O(ON}#zKWihB37XZ(XBR?-|n6 z8=?-Um~Yn*XhrfH%7o?DF_JA)`LOIaRGs1vszV~C!@I3l*Fb>*K_3MJW^OOw>AXTy z!tkO@3&0WT@-yk{qRn7Ae@iQ0&ZkLBz)dW?HEr^VV^jW{ZlP(XMHWbK726u)KK$K`yY7MNp}U0D9fuSO2W1+ER+5BM z8_B22bp(mzZ*<>+CbkvyKGabuSQfpuQoSRoPT(oW%nWdLL=#+a*y&9}Pfklb%gW!| zCVsq7Y;0((j~#6YqQ~Y3W5E$TBH_3-yI6VMMN;u*3nYNmUPf%WYGgpM8u7`L9=xfe zG03qg!d(N5A|Br$=~`2jbeBbL30 zNES``8FYp0^z(S0C82KEviQTeSE1ffwQo5Sc-MA@H7)#89JKp3mBT9XWQPw_eFHfR z@D;Q&8)|U)%iY3SprsYd`1i&T{rF(sWZvx=;Xn3xYjTps%j=6U&{XnlB{ySAV>6S7 zn8ugr@O!Tbrv;$}#XJal+xOiC72?TuhbM4V0KuFAJah`thu<`CYHG6DiRvYgg9w31 z<7XGdTo>WL+j7Ao2s*(65(Pk9A2*3)>Q;j+=<_6rA^1KMoFRK`;)8g)!X%S&rEM9# z{c2K$T|1pSiZG^d&d1zh#cFqgp{#Y@JmQG+D$w6L`7Z5BRe#M@E91`Yik9V^m|six_i!*UpG&(Tbd zlCNm#^CiS!%L3yB(0g)NnsBdc_j#v0ZTxZX+mB|%XhMxWLoR{ru@uf1QRhp8R1h5k zywt|!uv{Qn%sZMC>4t=5!TsgX2nYUi&nu{<#91vThp)O)6^Ddh$`U(P;)lwqjUJ>h z=tlfe=A-)yE0@bwz`Y!$D|zZDP&WjHBZAqMd17up(eTqZg~`j?%CnoFcIkY;X{Or4 zbU7_e2tTanwRFE#1DRw=8pu!&nlN_IZ&LcWta@jU71wWPwF(sx<6p&^xrCU<7zBKo zEZ*U}typE0Aau|0fK5r5bn~Jbb#ebHeGFl58eHuHpua=X&vglEASG0M-(8(MiM&3_ zZW~D4maV)Zmziq$|PG=y)cE~FUqzEb5@tRDWxW@eBWSHh9IzvmGZy(X#3GT*>v;((KZ zxyhoZGc!9&Ui~cVc2nliFnVgCk)_F)>Bx?=+)f{5iGAr$cy+Y+uUw2EZl zW!p3D&7Hxe%O2v%YN4HWxr8kdZ9K-=>OH}a_;5AN&f0+STRWarhr~q!=wZ%r-)o;= zD&~W`^ZOkvos0!mml(dvhJ4N}MdH&$Ih5VsqYWV1LM7Ac)J~C~C+(uCm?Evvm0qeH zK5Me|U)Rv;ztgSC1fGIU)C(>ZaCnxj0`c`*{8QP<8+mqiNjs<6C)ooj) zfB5}SH(}h3E!pf!J%2kdv&#K9D*}Y2bO%I6WTP8XnXg|t)}*PM)EB4!mU+quL_@Ou z_9H<7l!(1HN=~aIW7U*vmdU_FVdvcPCE1(r(lQqWS#zDRX3F=dG~pp!C!#8mhi~lD z1-+<~m*?eNP}_#H#y&?~wS-e|u*2ZCyMRsrnU1ge0n94%C#y*2}p^QF~M0!Z2O zbG`jQnWwFM$8^Q%%et#Y_UWZwUXXM=IhxCEJ}(U<@cX{2vJB9}Lzwg}#hY#+y2ZBC zUSw{!9N&mZn|wWqNE%+2D!AX-VQW>9s}c=)T*B64mb%UAbhAOTxh0IZPeflzcAc`7 zcU1_c>ty*pSiJFqG@XfMuQ`&8-AqI>biFB#h&-sJyPG6bBcrEgMGwrBo?Nh?SqF+U z^eW$%X>qR$Zi16N7XJwcaGA38?l=qh1!!>Kwwz`;&fY@~NS+#+Q6vuI!WRsa)5kSb z;DI7J^^ANn1f3T)mT;5R9OjsdrTe-ckhioOvA-Tq5O+w~?S`Qf(m0BRwVn|#lER() zS$8mYDPfXoHnfu*-vpCFUAwxl=b-DiA42}Eqpl-SV-9a`0vkn*X*64TY>`2F`+Iy0 zJU!HMa#E$CjpNGl;lMPY@#$nCPJWD3`RRb;OM3L&MB3H~O~~o3Zmth+XpAPm2C2H+ z^+DX||CK}<P3{fKEXMc8iiA#11*O48PzHB+-Y=(y7;EEF?Pr}=mE#7v8t$NvF zY^ETCUL=8<9e)NuZ}^rjL_&Df#jA*>AOjeMcxP&wmi?9}#|jZDbi(nYKC#&ep{{=n z@btQ+YNtMa4LRSIyw=Hy0GQiL_1yL)jCg~1$277Qa?gvzF^|dHtF|{|(&#u|q#DXZ zf7uR0W%R-9#;=6?4y3KaYt4C9Owu1BQiWrh85$xi08I*@F~`!N$BKL42(~HOFx1C*bco9^ zZAA2t)guZ-ZAx%dfQsCA?Ig0%Y8}XMA{lGqN%1(dcCl~#LEfOIFP--e-uj~k6Gt!X z4lBXY`yNNB>J?i|8{ID+ud%2`f;8y6z3CXUvOE!5Zt`Y&2;H&^NmiU!xl|@;#_(cU zjKZ{5TjrcHv(CDyQ3nAk&<6rawU%YWPCn%gbG6u^Au$<6tOSsd>R`w-qjzNA`C5`+ zkyMFXiT*?V+M4jfmpafVdwlxKsSH@i698b87)lKH*~za=+tX?|Z=6^lB*lIHygh8Kq$LrWm4V zFjY2V=(5w*AmvbJ*e-753*6Lr|Ie`x)=k-$2dpS@qro^Mh-jHcmM&(*!Oa)r0Cf?r z_ABFB16(~#pg8D(`DGr~Y71RRG_8pS$!On7DEk#Y8zf=DSK+!f-&pg(pkstf%Oj88 zl;_gR`Qx4S#w1~T zO7v^4R@$>NvoP;9Co@J+6Ak82BuEJcsuf1?k#ECH2XHo!VaPdc3JdnnvvOhf3^UzU zcqY&qB`~_vc$pio`n{@3c$8z_ZEV&O6zY$Bdn}0o^%xx6%binU)g+J=9h0a84pJ8K=ZEG)FNvN%NK8CL?V5q6~0IfF@!twXI$g~1a~du*C> zt7LOd9uor)|a>E?l&m3jA`ha!k4K?2^fjKlL$9j`U6Jz;&MF=r@yQJ-lC5X{$; z?b3!IEa>(X8Kh>0B}PS6NdOxkA3qNP{l9@i;XtICRe_$8g?zA#@EgccwuGk=`lwfy zVawFUzUiXg%CY$pxr3_0=#dWQCmGF2xxAOSrg9Z_uwN7 z1?cBRhV30PD)65cI;=`WQ^ij}N9F8_VXNva)0R9Aq~QL4?7an0TTJ&6%l9=lkcCjDrevICS-`P%)7JzhAvUOf#~0tixolkdBuV491q6KDi%1 z71g#jx#11!)R67DA##0*0TdJ-J$Z$jH1k^)dM%F1A?A#n$O%GF1$S5~Q2!Ki_<#5d zV|LeHdJDI;I_XL=>EH%nSZrx2^mAf%=sSC|NarGR_KxL zKn6XJ+oEbH9g9yDF-3_jtN*Mqf-0af+L&28k4Z{!rM@~`JFa^TzvfXW*;D8-T)E32 zke}Jk$X(*lrQ3a(>3rYx*W`1FZBN-ntv+jQY)HCMs{j9H7XH6^oeMOYgzzD=JMt_f zWHMy;XYinjwQHG9*KxTbU?aQkRMJ-B+xY#(3RL>?`)OhHr0;C}1nys}8K=d6t#Pys zg@&c%iz?cO3XD2dx^@^*^;Y2ZR6|oq3KQthv1_N82y)U=%`IKG1F?lpI+LmNh{vN{ z#@|ELiM{SwN-I+bN0jcFK=&_!ck8S<_K4x+7iM0H9MIA4)*CvhL*(D*LrJ&Gb_qNc01FzF?uC!K2lrdd*~+w zt@SvZL%zyKsi>JE0`HYK$h`p3a^!W8`(k)~F79G7;Kry1yAhqEk#z_^X@8WKFtKb% zPpYw5ioW@ZK9ycn6dq0Ww%{u&)a^G9L+!r$qvlhTyY=|d;nRm|B5{1h$lnPUZuTTg zZy4Q_Kc_6IXbk+1gjbsy1cc`fd|z+6pVyyUjp~05Zb9dvBdYN-BqyNR9XfudVc*al z3#^<9K=}%CU>m_?;0UL$qD(LdQZ5n9o-v`g+Mj6jvkY$<#~4r7E<@Lk9_N1SI5-qQOr8^_ZX7Z$#bH>?o*TvXWyy7mRb#&+8ak@+(NTXZXJQTE`5;K{QjC-0}~z zKp9w7^Reg;?{N23c!?2abDCPG*sij@7^Kx%$^?b_?s_;+~1LCzK4%0jPns>$B_vTKYd4K2`CQ(?g@gIh`0y*x^g50>%$RTo@LNCKqMORif>M6!FA;~nw-;>qpw|w+;t<(H4$`8=#8$t zJ(%9LZ}V8KVn#z3t*Ga?xAzc`jEZ~l^*s%3w1~NDX^D^(jEvxp#Je61X}ujFwGJJ= z7X0>-Z9WF(-aVd8P~n>*;!}QSMdMM=(ZjrfDW-2u@fe0N$$iLsp1I$|AcO*8At^yK z@No{#&;XwZO{xIN^51lVuP?U~tKseE?t?m9xOeeNcbZy4KE}{eBydlvq66HQVjQ6& zzw5=4Lg~pAtqvg<|FnpwRLL7PpL9LZ&)lxu{dJhURpzkqp-Rplql11WXVcsl4DB+=;+VIgBi@LTxdNGRWaRcfyTH{Q~Gq{b(SvOiqA*Abi=*G8SdH*#0= z>6y6tk@Rf(G9}WP(elTS#`~7wm5lB9j3sl-Jm8N=a5`-w*RQSG(80|3k{7xBx&Y$L z17gNxKwppoNq9)z2*5?kk2$+Jx>%1XVyX^NMgP&7VgH=aC4eo)PfuSmn6f1ri%ta_ z03aziRx2;3)l?}j06+!7aaOX~5RZBUfx_6|}a*@=v&T)>E=>>9m4%UFo}(BHLNLsmhknlV5NWxj*iAafEtctn|0>*13!z1S#a}uOw#8&V^ z0r5{G$f>Lv>TgKS>MH{CI|g-8MJ>%Lg}(%!0Pwbl8XiX$KU1IaeKa^84Z9L#+ zB!+dj6cs@@iAaL)E1oN0eoK#?zH{LKF<25?(W zAICosK3L~jI3Rp_Dm$y2zuaf{!z+6ifmaB{H!{3as?6^E{y9MRg5~=hvlVbq;b?7b zEgSD%w8V`*z9$48Ni{^;24=~ohI+2))#b=9{4TA@(QB+2i8`7ke7#@Y$zW{X*M5In zG2>H)Cf=!5;Q|ftwX#tJW8K)6eZnl>uc5M-k19D$QjTptPfbQZ4k2zDp;QAmJq;X? z-Qt~&vAQbWQJALnu3SitYv6^id)}EM)%gzT^1Y7k+QqjJ%|cB0K?E4Y?y?glqu(h3 zj;33NpKP1BYBL=7?bWPxURJ^!Gyx)kG{60Kg7|wR0AjEtV#LSx6P-%c2F8ZV6Z!UM z&5q9RE1T5{3OWo2-VKOJb@kDIjq1{QeW5GQGhFft$2pNa>c_ z5dH+fZykGIFQTigs%mASDK?%(H&me|OSwKO>{+Q#Cau@}p>XUP`w-xPKjAFqTygq^ zVo~MP+Y80wz8KFGY%a}K&}zEybI&B(O<50cE*Uk;X*%D&RUbM$r5NMs!p*vT{09K@ z&gPOUD~g#J2dtp;v19cfM1)@ccR0+?1c>o@mTdOkSq%fng@tM4owQy7`@5O?(IQZ+-c{ zV=f+K(hf#B{8B2549D|zW)0j^b*2Ieui_RpOQM>NI=So_+Y4ulrtUpjyg4Q*(5a?7 zL@uFonG_i9vt3)WL)=A42Cu0^@rhM1v2b`tXaE6nCE95x3POh=jx|jvj#kZ0izc3J z_1!ngOm8zJiah<7h`VDzx)9(`1P$Z%y^%UbDydy9cMlgjK6{ z@5cz%>4I^(9BcHIsMwU9xjKspW9`4LQZWN~O^}EY$wI^ss_dK%U|LOX`3g+%|2$qz zRTlB3P9}|RD+CG_-9H8i5F|TtBxC~x(kU3jy6!WQQXXD! zp%ikJ-iQ40;)uJE{j1mN*Bo(icLQ(`&rK-Efc_qy=%6e4?LWr*_nGQ*clw5(6y}?)K2Gj|$5MwTv2}LmKlgsa`CKQDnJcR+p2o<^pFInJ z4xy6+SY)H=P1Y@vK|s#x%F$GqwzD!smCN9PU0wK%06fo2#rFerkghV8i&8;DOrtAr z7s*ok)sOP|glAAHy%qORiVSl^i4OTBR%e5lA0e}@-UmO4$ji%HKn7%I=Z_KP7{l^4 z%T0zn5KxtHBSc6FDzza56%`dt9$=kDdp#iI>rI@RIx)AOw!xje;J1Yg zk&U#ts(w_r?|YAAuc+s)aN`{%oYWC1`?n6?5+{NW*k*}rBATU%qH ziZd3<*W-LIgE9(>FxuKB_Nlg_=pWO095ltKx*yZK_}SDvU%5=m6|OHwcvN5o+4PHr zw*|l=!7ZX{)BcxjtS9w(*Ep+~AS+c7ly0V4c?YX|&^Xj2AABsEA%W39BMtxp-8M$Z z{NrL$1xd@O{_A3jBL5j0mHLS|$I~*DWK;gQhBW2lXAx=K-1_gR=UBu@hHIRUtg(*W)L`5!KHq|EfOU;W7avlX!==eA|VWmt-!dkEq zs;%Br%6c<%EY8%Oz>R&p(m4L%8DDF@pOBBu^s7y4yd8ZZ4iem6I)$!o;iP6%&j>;S z$Li9Pmc|1lrD0T6sW7mti)`C<|48eGm7J1|3h`>$h1iB>y{&fSY=x{{n7@~!N?zr# z48CMcTg;8+Gy%;d_4F#d$t|A-BJ)v!}z+ zQKz}7Vc{2Knf+t6&DGb$zLsQtzC;UlRtJwsohQFcW{yuW2USHK&GjG#?xqL^MC{Q= zUQEo6DT(WZmAjFOC(r%$*Sj&?cqTJi9HCscb%Jghp;$jv7p~GuL!%icM;7P4#yHi) z^pEe{YrXK_pf=m?``zq#P9AWfWj{wbt8I>6PwMKXx_1bYX3aI>!uZ7;?6-Ava^os9 zioI>qcQ-NTR2pvxRaRl3uVb>B_QaW_k~iFV945ThN%b5ZRXXCT zx6YU3Sh5B?J~yvGd?N^H4oS~khkgXmrSs>k|0o?H*EyFgjd@e`FmcTpN7bzN86HyB zV}{?yo&?+TEa9S$?X|4z=j5;Ko6zqCc^{?>QIh;f{WPTBslcGGuj#z5TUZn(Y_&5G zgpeG3J$nFConMn22VK0cN*tFECf1 zhnsE`1$K2@Tl) z`KV8WM}`V&G(Z_ef+?$Z8`J&K4&2ZR?~IMcfA^>_&caK6GqRhUa5x(ecr9|ENOF?p zT-KpG(!EA{Ng<8S-{I$HK>Jh|am@*IoS@F0f^|G8qZ!K`{vxxOQ4QT*6*q|~n-^pI zQmnLM0oe0@BAGX^dc~exv&#&R&&$*$`!-?Cc!bFg>rmF}&1bav*1n+Nd*Lw@ap7WQ zLDosvVndlX35iSQ8C{h7Z#B|6*8Ihk{t-Zn4LNIG6?k6Q+}R4b@=c+pyEQd%)R_v7 z6kJ%z80xaaFH{%a{4EbGE#%Ci%M|x7@tCWtlN8tZs0a9foRzJcAresIOz8>pdn6N8 zrcj~~T&{L92xVgFU9mX(I=I6IDOsKsn5dJ0*=P2nsB@gNs!$SaMZ-vfpCHggZ zR@gzrL8RW{3lGL-izPE)BfEKjE zs9KkU2-NCy)*NIIGiAA*k(rxPA{^kq^3S;DP-BnERT%VdIs*aBI1}ksyiA9%2>8dF z`1cheU~riJw#VaozHCt%@i*qxPui}-#4?mZvqGwK_ZE>q=gh*qmGEA>r5Qt?vBbkN6vqkx1p=&2Eyl({lyxlZ`NJ1-2KG>p>EOP4 z8|e?~)X|S?^Wl-Q82ha>*S07$cS}*|iqVu@!A`veH5zRh=Eu+l{Yg0}fRGB>g6FlB zbv2GY{>Ofia|KuUf0}kPLDKwBkYHfBDdJ4~?>bCN8qI9_IZ9aC={D{AgEhMzEh+jw zg`w&lx>XxboFByFPO)qO0)5DIRKZC|BU3~OawnYT*n1l!OD|GhD6=Oxz!S6CeF&a{ zvAsV`r$*7uNA(h)=HnpW}+x9cBT$ImDRm*l56V^q2jwHoNvQ*F< zn1#VXlLYLed!B^KRT>MQ#Y%N-K_ux~UG3qX%1wq71O`P7(x?QqFGFO2;2wtT><^QO zWF$p81i4|AO#)*b5$!)h&+8a@Tz_&e9X zh1A(2lV7a2_S``w6hT7H0PBQC0D}Crp@8ucYGkRguD#ZhL%Ru32l(4?O^SPe{D*eh z4v_=bHEm#5zF4j0w~CYL$50)1$> z3d*Pl9)jLh{Hqx67uyFq3h#N|{EAvz!|H&d1+L23dkFwo2&ekQQ~8;D&7MZ%@yD#= zK(}$a)%m)Rn|*3W$~ibP;*7Hf-C(46ooOZQ0K9Ku+v~VHepp~(;l?V1t!>=B_(GN@ zaXx1!eZSCnL?8+C%EK2NMY;hOO7aYqhq`ldy*NVYUj`(nip9hc@m=25@B=F8RLjc} zun_!q8@=~#)eqp_-T@lbuhB)n&%JNvctc+3-~|2wu#-+{kZ~OPs`BgdXsD(3D8OF@ zKAf8mQ)815-XJ+C02Rd|Ha~MS?E%{4+EFsb$^6le-OuxIO@kmX14fq>B;KIR&S~+I zhq7nlx2FRC2JfC=(?g7)cVIeS1e7DdUo{6Se4OLHm&O~E7FHkG{Rm=V#@VoCyx2I! z=~%DFqsky=<>g&-J)?!m1IlhijZo54+|dNlCdeK{Tf~PJAXmU7Nm(8*nd0o|);NL} z#7*OeQ%w0L^xO+k>67){DeAkjp=eknR!mJ{t^D;V!x9vo>kRgO5211Qhk`>?hyQ}KIKKXRW4xDNmvRdsHr+%l7EF-0!-bj<@V28DmW zERp_V)7rIEzNxZcEPuxTq#0A&2Y}c+CWC`mR1G%coeyhgtMHlk>YLK8d25_02OYQh z#w`B(bn~|QEDXd~2Nsw+T%q>{Q`nnWn zmT!^n!t4)#EI2EA7w*$`50y*6)cm4{nt1!`N!LIoUk>*WeJJIZbK&DPk>os-)Jz(; zSPBl*ay(4h50bNZiyG{Z|1G{?$C_JH_a7K0X)eq1&$fyq@@6{GK^YMeW0 zX=lQU;G-DIz#o*p=HGkR3F29_0ZGSOh@UPiAg>)OIwtG~o;&!`5oPo>@2! zJD1Va6zOuvz2@-MrRkR;S9T<@M|z=h1bN-(p}=s;#~nz%D?uKJfO^QAK#!eZCrf;O zju7GRpCh1Ua^CS7XjBkE@ocR2sxi(=0r@?gDutZPPRRomQX{0E6?~O~Fr@|e-Fp*c zr|R~m;qPyey4}tOy{+Rk+zeNH%V(7cD(FlUTtx9d)*?nbHtyki70=@<4EKE&SKLF* zb4?ozU{C|&H8Jtywga)~hV zP>Uhssubh+$>Pe@AGN~v-oSHAzI6A<+!pG7!ebur=ej|gYX|KS2?9Pbh`fQ|0{;C` z6-!j~nO1Dxf0qrDZGYF|2*Fpe1^y7p@f3>l%OS>6H9(O4oAU@|6nK{#F7@xIfLd*k ztzK14r1Oh%!HHkak7S74>IWmRp^=FUqZ=G}R43@!wXr%dq6_tVTXS<;tm8cT7#?4L zDIVHnkpK!v#=ms^ccgT8bH}{d{OwiM@pNCQ-sI^rXpA!u?HPhL9*l7g--hyg6I(z)wvuE883lIKvgLsyDnX z249@GNA`Q&<%BkGrLS>h6ISiKAfT;u?2HUz z5CCCX-J-EM%_CF62c+|7adzxhs9fXh0%!Uf#d-9{6;0Is5~)v7dq>*}EdQ5K=wrHU8incK&{uh+^*o4U3IJxl4{_HbD%c5F-o zW9M*j!#x~4SLlT|M|Pisn9yUs-UI}Zq-8atdU+D!aie%`x6qByiF!2x`?eOzC|qPh{1TzR8uK4q%z&e_JL z`3na#UCxDHUG3$z*HJ%FTQp$CV!>9^HLx=_m3kaO;hzV$ zI6NtXTzpR$RxlSgxK@cUXjdEF1%2*vz1@5BSU#&Fmy)eAl{7flj012ik?QgFeLdvd ze5A(7y@yeLMQ2E!Ze-#yTI3w|RFJ*cRz}oGjZyu&rYF`x%+B}ogx%Gsl2QyWjv@@k zY0WiJQH_y}9F9_oTpSt;?W+f=vYotE>zOZ2BfO zJnn0HT@7^{95bljfz-WCx^T8V%fw8xXyVxGm8E8B+Y`w58_X(8?*=b=TjP!{UO^C* z#P~Vy9cx$XEksRQbe6R#h_jG0OqzXVs0Jy39=f~H(e3#lU5=7d<^jBg_AE$R&%LBfi6uk`RUH?RL4q^4a*!Nu@ zD)Is+h8)cAw?!Npwx=kxWsRpT>`Dg|h2n*k9ANAPZ1e?*R%iY*G6^`oT8qm68JX^d z%<98jSw2?&a!mrS1aDWIvVx}pz9eh)Fa2XMAT@~>0}FJHm|MI{r;9hjfCj~u){tu@ z+7Wx?N+Zpb(MKqnG`=C4DraR|M>mLu(Ey#q*Bf;1xf71r;xe~3ITG#SsjV&^g*oTj zDRx8sXKq`ox5>{_wCHI`lQIG=sPElK@hYoODji>Rw^k}Yh&TJExl$a@an7}KqKdYr z#1D~q;!|Ow!FK@#?0qbOI(gAlN&D+6(3|QTS;X;$lo)zFp^!9x8mq9gaZQ)7s~d=; zpgQCS>TJoZ(R!!;YjHFoC5(G+K6gVB91#VB|C4*(kYcc5a(ETjCd2vq?xR0%!@>$#7O@tw%CY!U3U%uCeL zI)0?8N>eKPg6V^LIiNDnUAe~7)@sVczWb{TQ$rLOP8(v8a zpSh5l6f)YDt@|>cGI_nebDhn!op*DK5f1;v=#QSA8CTMag0a`5u&k>QgLC4FlHXr4 zHT*7PwIyvPK`f>ik2GwFYt>sh&7nzbJ%b3?t4*Cu2uU;2rh}V|rK^dG9s?L+$S@d^ z5K;z)=n<=k{3mbG3`o?s^&b+aEn}~NI%CJOf6EXqb z0nEAH-p-uof}js`B(`Q$Bl{V-6DBfz%LWHs0q0DhZEJs@OiMz^a3~WYj>YUGDGYet zWJHxvyWs1D&|~FnK4s>vMA7C&io~CTET{E7kkwu^l{qo*&LkhD5L&^8t(3PbwYv*eM@ zjBUS|@;R6G(PxMxTXmACB%0=hvZU{g(3gbtw}QS-E-2r6WS|h46)d zw*M8^g?jIFRE~L*{}ImP9wWM_{=tTJQU;iiIJ#{2m%)U${r$Pu$^Gc;wv${3#8dVK>N19{BRD0`VG+p-?)x&)et<{N7WpFz9`2%!9~7(xYmA4S&H z0U{YbNl~oB8t5p8bH@c7LK}0WobTu;ZKrV_6GYpizMlENxtTBrm8esjd~eHM78_kG z0@r{Chi#ADUBeFVQe6T?3aj?*qKDr$6Q zDC(4l7`RtjioSmhuzge1iSxCMdOI8yHewqp!E%qJ8+efl0dI3!w071-;wqX)YD9JA z^f@>KjYJX33?7J4++0ue95zVj8e3*7F+Wr)zpHEyC1%S&ZzGR+nF01Q`V4LbFf1K} zFhph2?Z}xsm7d^{_RK*5E()d&_tT9q0LkHZaUQ!x)C>$TGLfD94jY75g6zfVA9Jj$ zmgPeh*z6IlqAl1WXx@pr$Zox?#~Ur|*0(NwmU=A^iQElA+c|V@ zb)lZK=BHivnu${RL(<<+ympzj#{&VJd9PLfEiP@kJLRp$!{qGAPt#v?U(nz9S#0*M zz+1q;Ah4Il{6O(Ywa}Scu}*!MrVPHyr+smB#~E)V5E)YfzOG1sbM#$)YPinLv?C*0 z9oMNcEISShmV>TW^F9LQ^s~yec(3RTIcC61>0^icsRAV03I6J;prj}MiwG9F4Cyk# zOY>3H=OWtsj!%H~j<1K_B&kQ;#|YaM0o7iut#2d37L(xw(${TfB}vL^C{A5DmBp0Q zjm)Nh0JPg#y#j>jSkAQ|9#(A)R`d=1GdRSeCend=pR{XruDf7uFr>DkJ2|I?YsigT z@bEZi#r9fq4tG-`SL4?S0a}dSUYOmHT=u+;vHG#`R_iTDU`M&sV(^>$RinvPXc9X@ zk-uu@p#yv!Q?3==G=pcdrabYV)^ltVWU@?8U&Do;{5Jh znPWUv0geb-JSf1`(6_n87C9c)N?eRl#Ugnw*Df$EMB?MXhV<<6AxTEzp{I%L^;DGZ z+w_$e5=8qMalaYyL}d13MSEf-XAZ-_^zyRpm5g&>zR~+q@jU}9o91+cOP+CG!5gEf zUlQbISJDIN_D(8pLTF9AgAronQp|Zl%X=_OnFibDLxTJu;^UqN`ZCtD>gpLDiz5tsfNqxu(dVA?;34dJuGE5_ zX|779_r!x7c=XEE^p=`Z@ABw_g8)6y(ehiV;rTK2@38+uEf)W!-Ril0Z>0%c+r;ww zE#=pnc#HjeHAsQzSOjg4p&GPDQG}(ck1QjVj881VfQ(H^mMCB1Sh&10b|B_d{_GVZ z=U6ap@k=;WU1Q$Kbe_DSSqm=TR(Fw<^_M6BInhXY7UU_n`)k*ZUA%(7U+23B^F?lY zU=c$eqg0{ouS+DmZx(E+4z=(6N)dvD!sYBsz$-Z1SB_Qy97dfaeq_+bg4(SA`~rCN zz{kqCAzFgUAJyA@{!0C%VQqEgvPoq&U2_nMcZwG_F57E@0Lse7yfu6~)mwTOS;!8l zag1*)gB&jBrMD-^+wpyp3}tWv1Kz2(B2(24^?`Si?3lyq^vAi7usEJ^ft|b*t+p7c zA88ne1J=ANaqtuxDy|>_OpYPW>M&>FWK$~De#d!6(J-)l5}J|Nl+de9u6$D_SO>_4 z;gd!-EPSLUZ{q(UbCmzbcL0!&a*XF|-3X(wj+Qr~TStQEWT1S&sxi|A#J{n(-w5FK z5w9$I63=@1EwTw$)K123@VDXksG%tat!nSye&UY!o2Ho#&;~?@#m=8m| zP;jag4?B_8@PPFu8LWgL5ak&1;cRs{lGlO|uA$p1a>VZ)g?%h?@>{ino4eZLd)NW~ zNn@a`r%*lcLT5TRn?7Tb6VRp)N#Di=b%*yf+v9cu7y9LwjQjgOc7*srd}Y756@7Mo zyss6*)TSp}A!r8(`x>`ubwvw#Eit$)S@w^7%l+lGltR3eiFW0g^ZVR<;ndAB+8x%g z^r-^cC1h-m9lvN>CrHn-NSgvm&Q1>^Z=gncoG;Cb1CEqF4)SGcw7fg?mgY5Fm&Na) znb(3lDDY-#+Hc&?P^urToW@J=4st=FA$q9dS)kGP&D~qe4ADmw32P7iy&cc+oq$bO zU-YU|rJU3CQC>TarerqbW4a~N64(e}NyWy;OXUIiF@bn5QmpF!0PWCyPJ0$iN5Pq5 zfYM+yxSWE}=@M1dkNr~DmZDLP5wE4kDX*hoLxgujB#=pKBt9LzKP_6JUIJ^aOLoK_ z(m*9@!Q=`5p5Rs@RZipGwYOX)a4=vaWqJ67M!CG`)lj~!Cp6RKDPk2lPD{oDSzfG? z(e|8A`Ngx)5LC_MoF$KVdc}4PZUyOA5TUaB6nVgRf;Hp&W3Khd>A#EO1A?c~dCB|; zT;{59wkFA;C1|p;bGt&?h7`Awb>&J=FxQ97>U-*=_redZM_}n0pm8DfNd!=%o*mD_ z6iU4UvRZg*VbY)un;fz^jG#5X;FGEce4L+fE>b|+D9?9scG-7+K>{%6G4;*Ug=O@E zF!{cA0k{bwV!AB8uHIXZ)nnzK7OVFQjt~)wRBxQm3n3r+)>!U)Rr#MgG~*C?(;Ao< zW&~-+q>b3hoE;7l0h6S_A|->hr~NC?@%gqnaRdF?bv^067N0|Ww|@ddVez+Kh-@>A zA&#VlCvqI?sI(YKs@ketT|@eK$Qr{r7>T-82woZHSl1goaO>St=7>RO+vs@Rf`p_p z#FX4m5>0X#58_cjQ7RQQl^xIZ*M+YV(mbNgE&tVxj{Z?stlPDlf0 z+W%eJl5$f$qvH;EDrj7%j?4pJa2K36d*Nhlzg%s^WAC3xr^f|GT&@}(IXvJZsqyx8 zrh4yCVh+#FE_Kkpi}&b;M3kKj+GwyVYn@1^H5n2N40W8;uva@lB zk%*DD+!GdJKeqV_VAx>ydyUZ9Y%y1lBVCAT?w&5qeghZv;-02E_(`z z`%*NJn`Rt%-W0}`(?>kFZMO{>_0OGKbOd~1W144&Yj!zB7m!0sG(uB+GKt}YQ5&4s z1yez_u#O3sU$XI;{{||_{*s~^VaQ_+3a6>%ow~-3tvqSb0vZOSN&OE|T6KWC80xWJ zCheO0s zKuCl{nNnf(3|-}x;??ltN&L_V2*eG#BCF{s`g$V-j`nTY^p*Y_RI0<2lq|_pB|@&M zS;yRYo|4ekFahr8qO${PUcjm&@w@AXvhRJn99Nr2&)u3qYXy93rV^o4Z zAtb=eutFX!IhaBK!3+4$wZoVZGmioUJ3I|!Ehps!M$ehU*(BAmt6e*XLG5M&P$I6%GAW~gSx3G z=DlW_7_K!%R&Mk<-7Ma!%XU%oAiU}jesmeKc?8)y8%)xMpwfY_5#+%_pSIjnHZ#W~)#j_ITAClpllM~z zWMYBkd7&+WB(94!%NqvD1wwsM6TSHA&*cO7>B2qdB_4yHI9JK>;`C)2Mp@5?2+^p_ zzGW)KNHB`8%PRyukC-hVkMQlb6q(0cy$R@s#l?ESN||Rue_Pv@8>V8vnI{0=X#IdB zdb#NY2ey&urKW|?_T5JaQTd3aAh-6|!sUF5D1;H3OhVW7ZoEnNlp#`iInDrKon7f> z!@4r5pOpI$OGL<>*FmrNJ5{umh74mmiQWX2&Z8IZbH?+Uc};hd=E4Q@mofhj3Qlv> zVf)3=pIq>eJ-4>51qhgfj^g9XA_dP+@3cp>BV2ws4QQTQXHxs!7rfnMgCd zqbsa!3lLruu+cqCz@Qjtk~U}lH0}3G^l5P%{f=CLjRZ#CoUG6%C3 z{>($iPW%#mgPtA#rIwa9`%)$nhXJg@I!1VQUoT#&;LM{^2qKzo(hgKsyn0F?b&%mf zc=nLgiFw8=po8ub;9|Fr1z{b>@FTyY%|l){muz05hKPY^yvOg-;zhyc2L@){=n{y%uC zFx1gj;X4lJp7gFqwyLnMr(AQ)w8;E)hZ5|?JV}d|>EA@sJq1%vvHeb&z zHuSLzV{pr;io7KT&ZER3-`$D(V$DBX4KSuK*Yro;bCeGk$;FSd!kR}Agl$L65Jb0To3^AE zUvzsi#Z>$UfC@)I^BaV%>oh1T6bfNo|1E9!zr22s<};+VF)|AJFJoadF55l74Tj+ z_e@1{Nf;~SW&)4_@FhB<;ic!los_HYz4;kv)4fpCvgpjjRBkaqwoKX+&&9$ zC+m|GY`}NRM@j^ca^^dTYB!N)B9U+bTAz}cWNQq=-rRa&GIsMG%~ZnytoP56A!57Y zs)FXOD_r^PWkV_FLdp@Yxu80O4|N4nhfGa_XImyUT^>(AGEkcejE=pLPf^y;VCsoj zkrAo^vJ&X4qACPj3qQ@WZ<4qatf&5WO&yDc| zdu7`{DC8F|;YiSye?&*3To&7w-o zI(Qub@LxWE!EOf&D(&dpb#uM!HTa8>63RDJ9G<3Pt%_N>9PTjf49m7Th(nTUb?{Bb(tL-5^k-54ke}d&vfK_prCz-rddB&RUBcONEl@yHxuc(gn zX76Q!V|!+F!4N$83b<$?=PoG2yj??@8QA6}qsBUxZQe@e{-=c!HM>qvt^HG`@qo+2 z$EzHJ;Yrq?1(a6f9xCt<1#Y)X^!fceCTrbA2SEmR>k3;`i^Z3;%o!uu;(j+QYVVLv zm^LIDfltz`Y7aY7*Ar!sb#_KeZ5t4F4m@iOI+9-1iH2Ca%L+)(t}hEAu}HV zJn#x`Qs;ilBKwdJx`rjZNni@c2V^mS**GRJ*b?VUANDB( zPK#a#vl3N$`xS~Hodk^*Q;1ThbB-xIU$?8%4mfBoD!#=lpbs0D(Dk!m9Yx8WuIYW5 zBeU+=mp-O%-WY6NHwlRJe*ip&;_gSb7UL~#{RoauEuF2s`H%0@%oYWtf2xV$xw+;n zLU@wp{|Qf$HoqBP@1z&}UTERJzL z!bP6#+O@ezp#Mx%@8FyIVS8G5vOnBp2Nl392`MprCuBRu!H{B9a44cy zsp(7`CWcow1&7Z%XtCi1naEO)nCbiAA$YmZB^If70{v}fPbN8joG%oEzb^xFd)x3K zn!~zHZJ--z4lyccH_hIbG!19iIBdZL846I4fe!^xXU_v}ce3v|IaF1u1S~;FAsS3T z9af{F>K^{&7RX;wZ0A@uZp8H-T0Fd-50i&F5Grx9bi*E3WxYfE>sH!cR3RQH358ySRER>DQy@OPMhBpXBLzWOm()Lgp zbw~IIfQ`nNoR#Yt{3ygI*k>P}7 zRP^=wY;jXzO>wCc-5_6kf+8?yNW)@U(^{N;}CYu zz1-h=E`MuQNgvoN@P;`=*jgr_pLNqRimu1KWfZd-5-J-mx4_mrn2qbC4)aoF?*_|1 zDaECK+&-TlHME%g#Di>FHz$Z{k5A&mWefC*@#d*zKL6Ik266?V2rvEt(eQkR;=ws$ z!I(l%**`y$nV7USL|igt9&vh`Z9bu1Gv?!6^K0g}-o!6zo;|Djcw-VoxfMJ@E_Q-N z0(jD*u=jo-kyI_FdKSr|9D+*XaIz$_F!@~g#(BTsNw?jaNr_BqU7xUDV6mA-9&K<~ zGA1=m@Ias$zg9zazr8Zgu$RqB%+3+|dv>H)cQhdpI_a$X(%kUJNakn4^iRmtFYpGg>^voAu5P5uY2LCX!bF-r8O?B;wjryDEM znc&S-@MPvbOo!SE0T=cQSL`@D~&x87nalYUEY1@o|MaatJxP zFR|gv0Ao#X@Fr#6o}cCuoeuH@k4(%cjxIC@MC#4v5lT}FqVVcI?S0~Ax4sbHbkQg1 zA7gC~PcQ9v%~ye?YC=0!aC}4004| z$2-?5&F@ir8>xF{4L#d631l@**xKA!&pzDb{C#^WYlIsECmk$5s7(pDdwROjDhHK# zET&0IddM)A7A1941@|V%-C_X@Y_)jY8dFvytyOA(q9*(n)(BT6RpI5sMB*Kx(NH7L zKfPce;s{qd(G_;c6Hf0K^UulA7zI8pim%Y484ngGs&)gnbbRZOe#}rw2eO2e$835F zeKFy`hh!#=I%Z9O%u}@G9?0(4^g@e`kR6geL5mP%G_HOGqqLi9qSJ0Us;A=J!j~OU zsF$oE5i54vW#gz0ejeOIJ64?W6o{AhomEG~+3ax4y`HK6;`%m44u)1w`H zRm%0%-9c;~=VX?ZNg7&1NjhVMwt=Ucd&g;}^}SN!N__XqP@3^L#Bt>E(rMEDGPBSB z^ch&&Sj-*qMzuEM$XMACu|M^=Q_{TXT+)`H`aU4%LQz1l5>92ny;~5(E-fU}g)8dN zR|3|5G}53irWjD>gKN)zpxG!)0ORG^sBvMinT^d8_oqAp?MapAovqu@4cZ47>l!G& zp{~W56og2`k*e4`!oaGW{VBj1q&%C6pW&LPJ7P)Q$yWKrRCJ*J4*-3wV zwo+?DR(P_Q57Bx}jw&IN2E;a0WRM?AMi*$Y{QB+Zy=!gvw=d=$-VZI-d=C?}vz3z4 z8HegYjv~W!aqF`PXnKhx+lN>+xFK`ZF#`SlXLyF!)ZHv7yoiG z$6%pc>0|4EWA81a;_BKo;lkY|xK-g2Tni2E?hsr9L4rF3hXRV=8r0fM_j0)<=9 zAOS*I%fpD~Wt?jSHTQ4km~S?lVw z*IzY?;_iG@8VS@yeeVHktVwCGaealNR0whgnDpL{rZVe$LZkS9tqpO66wYcG3XX z4M&^A#6_DudH177VX#%G5KBc}tcg z|KmRAF97x|N}Nrq0_hCTl2C&j%pK~F@WZI9J&4JdZ7-x?rVb#j0VSiwJ0Sr%=}U}> zt{>l*#LyeruXS=`d^rE9cnp8+42a~yu(gj&~a@&M! zl(^O{I|L1yV8fiMkcaj+Zt@cE6JY_GXfleLN^}TgOdqHc;=XidI%yqOBL4uXxR!#; zwi@wrt?PUrD-R5#AQUUws;*`;9FN}Pu7{2cp#uBq!VHeKUW$u0%~S{)HCPj)IRn~i zx6zTI?&N+=W>LOqzoJVGdp<>}#31y1a|F3gfL=Z~_5)ae^}%Sbzk~`{r!XeMLBK$O zUf5Qgm&Y8;lril+!;YBm^OuAq(6i>t&Af8q-6a2`k6acBqq?8xI_hQIr^PD}e(EEy zXxyHZs;oT7n8`P+p;A?16l7FVwTm|@XLtzAhm?wR3(3)}fzj^>@;>Ld`Dmg}3Tdie zfwYTT>~V>&$~UhBIBnL}ji+ii1~ar?_!estN_PW;-OnYJ>v8ti21M*FZUxVqsDkeO z`bUCaNM5i3Hi|kcxb~f$b7EZA+lI*4>(;x|Uu)0bDsfJ>9XF%m zMA;7j)_;v0N>QWGTXc*eF~ikUL~@k^uoSO2_|9YwnNdg@3n~S0I|*BR2z#33Q)bAm z>RS*xYzoTjodAnPjrJXpcHu&c6k>c>ysC_U0Sxbtx<8}AXQN1mqboJ8Jl8uwtTDF< z5KU5j5kVvc^|b4;Hz2>j*vn(J!GYGG%k+cgo-*3UMDK!rxX*PMMXOKnL{-%gJCJkI zotGb$tQ_%vMBP7iIaEkCM*dYL`Pxcvsc5IeE0ZZ#ZdC)Jnt`VbBQn}a>*src#o7N* zWX*IXM)H5sdskHcY2#nGzQ$bPwQC_Gldpdy9qKWBXIpQ73>AH16oT{{78|j+mfQLfoxQec3+EORtH<_m-}KQ5C2gz5c;{ydTZ?v3^(k;^ z&E?v{vPo75RtXnJuhc&l6z`FZ(Fa<4-Zs16db$d_VrzDYtS6S>c2et7S3B-Z#s7l^Z#n~3yRo8-=1-%!XVGNloFpYW{U5uo#!q-$cO0oFiF&*X&$*$++TXyw7F+f3+pW$56=8izxqG}8Wz2==$JAH!^)+Tw#i zcOFw-O~>L=rUN1|B<_1`w5j4EmMdVda))^B#ov#t65EH!F)%c#IG=kdqG{jTCr8O` zkf}_sMF){;ly2;XfW&JuD-{cSVI+~0A#PuL9^Ntk=^wY@Gy7qw-2J=aRVJHwwS(yk z3LFd$mRA`0Fy)$_5DXwP!HW2dvH0vFB!p0bGiR)P&D6A^_2CsS3XP@+5?xAank%Y5 z?HjnAiB5O>Mfm0ytaX7d!L1kjc1WBNBf%e{2j=C-O~kGdpfHnZRkpRhX)=V++H0YT z!&0*XWuu?;rS~N)vA}D=>#N<|VB$}+i)CFGt&xu>0i9NDY#9csz`-|C%ya~~HZ~P~ zch%{lc#MsBcV_R4KR&EHG-?pf* zF9LQ+K3dywq;H|UJXF8-;7J@2{&@A(|AR&h&}biZde3CYjkBaf%)1&2*cBO8Jfwc^ zu=ziV7myR3D|%SYMxj`KEkNNA?YuE4H`xYx2h=4%t zxXH}fs`PXKPIz-4Z*+{JtT69?x zgvhoc^H4GS1yu3X5AC6_VR0n;7&`L9(z3EGC3BPZe2_9 z!*-Wuk9|4R6L(x?jf%iDHzV#@U`O-ejP`XpoUPo`mPwgq6DJl<3~Wjl?h~P6J*2K0 z>>_uL>VUv;c)!fCN0QNFK93s^#e{!x_+n}VU@_5^25LALaOcb>~ zV@Ds{YWxz9!$bC951NGZG-*E}^ZCZ|~_&r5ejI$3l+!1VnHq2$f0Wr6VLN_v+8k5rvHe-+*W+)9GC(k5SIf z^kpNF>F{exgRS!wXIjN%1*lziSoVB5=P}MfJhE5ZU*QDOh-Pn%%09Ya5{eTa+Eq;x zd8i6;Dop-dPueV+jOr)NC$$$eFCJv|FFvid@PHJB)*={)0d_(&W4QrKVTB($;vCM~ znoQMrP{H9%GzRr2zqQVzN(7%7a4fM?*38UFS|B@q1X{{brU{7y2XsV{GgswPI}Sj; z9`6hyleL&tqXpL`1I@mKWDT^2GP&)sT9%>k8xu9(sdq ziy=(qvnr?3E?xcM=U)JPlTFwFUMh&mcQQG{Mr_f#397pl5s@^zP=7Pnl)gUD+$ziaVVJ(+;bf?r2VxCkT0A;BN;X>^zsipBv1OSScex3&u2&A{MBF#Wfx2z z+Q!JTxb@ul*sbU(-(le|TNfHS5pgEs*v1>u?^bX?V~6Bq6*Sh0x~ZqAeSm6;$b=Pb zY4O@!6Aj!g-1YB$yYv}AaWpqM3RC~A^scM^@>s_1BrRT3KfyCxg3bMDDk>BpPJS)| zY^Urs;VYI{`j(FL9VK&GMoe2X1NX7@b~3s{$!)E%?G#b|=5>ztDdF7lS;Q=i=C>II z-R+wqhXPhJGNkissteU%UL|^hFFr-Ei97 z?uI7CW9)6U@AAQ{!>?UXk!;s}?bj}GscUHL_oBFzxW6HvI<9`B+H1 z39;vvbOnEexX~8augZhs1}~yQSTEOVTkBJuV#zeU5rxqfe_t<`uFD@==q|jGi>ypY za|ET7rlom@PkOkCiv<&Z8GIKyh}+j1cyOiG}1Bmh){sRS~<`jXj3`Lh%QI0RkX zDQMBP5YxkvlyHPXpks}U-n$VQ(L-MPdl1{}hSkUvS#(mvL3KbM-0(a|L^4mKD{;{f zGqL1r%Z{~c_wd5vR@!&P?}~kUzqOT0zK+Kwx^gE>mKug*QMJ<@Lj6cQy(lXU(Fw;_ zB|9-+p1&8?3Tv=OIvT4mQCL_{No;zIt3p*rf8B*XjVpfrCkmO^5VCDvvK7Y^IOL;` z{0T)#Dccmjj%0FJm%@@`Iu?RUmMg)p0nnlij{<;`IHv;shnDHz6|;A^?;{d+kv-6X zA1j6a^j@y?o6K5qyXM+p))=S(8BxP?tX!q++jL!%6z)BEUYhR5FfaNZ6}G!R{M7LX z*rfWm%fqYa@7;1As}xU`q3rAqG(vGC@(@W@%**#bf9pQL&qiXmz;W35g0QcarJtD@ zjha`K%6kf<^qYd$ZCbk)*G}Kw7SFNNcZ{2AlZth@&~lnoOYB;Z_c&P;wK_oDO%(e< z=V0N@VH8=(d*b(2U_#6UYXHPwFK^Mc)PfC%ZODp%vk4hvKav{p&|6Z%@%rdCgGoLc ze~M0X1VsA*+5^c1{k*BBtXNm?)20v)jol^Pb#Cy!hp6C2i%bU{!3z&2y)E%xgy%1F zb6v$vsQG>*EY+%i=a1lj-7F_(nv=6RoUQ_js2y=KTWcM_rL$dbG@;?%u zj&r{fj^Akhv*^QoHryNO8J_m6i+?<+G9jy-F8i2$jO9nCRHnGGqrpZug0ku8QoOir z_=HedJ-)rTpna(jy?Ub4kzt_beZ<_b`Os~+%$}xNNTzLIiG1}9Wb zv0F`W=;G3T#_Fg2AJKtgK1FMvhJK1pjv=m_-5;^n*3PiLd~DHpHSC@MDN@rg%X$aL zOK{dTO_iI*NB9K<`M=3I!)hn!38Ner;SJ2B^(5a#Mq02*VMr`V2`@sN94SPqEbHet z5WJj0jt6O1yz>&lF0mmvi@U~A(C>VnX>D5Rk*cQrex-u_I}q-t^ig7`kUbJr=7r^I zp-rc7AK4Eu>t6td;Ml(a=J+AsTMKdg+0&M|z}nfnr3pl>QE-heN7so2oJ~H{YOk4# zAil*LUBGe*L5fi*T)Ra>C*1cDq1-?WdnZV_zof&v^964#!wf+v6u`5V;B_)(w7?br z`cI0{v|1>Ocx@INg83H!;qWIvLbII5@6DLy-7nc^%VAb#-zR^rGnv- zn&fpGYsRxbldxv065nhb>F+gfkS9)y01_vlUzs;T1y_*+G7m*IDVplRmW>5Z$hxhE z&jOzpQJbC8DQcHWUfYLD(bJ*YcgKN~6(rf)X?EuuS~Un^Lxm=5g2nD6KfUifOBhwD|{4l!2US1Iu zzuBm2Qs7MN{+#6;$a$WS;9;ZK_^{=H3QKSio_5H8K3Ev}U8Ui6>(9}ZfVguEjJgh< z!ONg5*h++W3{-x(%$E|!HCc0TJv#$sk?!oe$MFW+WAYB-NsmbEE|=HYs|@zy3)~iD zZMU^5B0Q6kiGYGk1w+;OBsw+C=vEx|2# z+cMmrAVrEMR?f{|zO%+RNp&$$x{PytggN!Cz4LxC2@4;ei%)`m054Soy!t%^2XTG! zQM=e9a{A%vd~mGwiXIE6xdlYnCpyJY=e_H0`*W9W7OiN`T;0n|W@X^FPF*L}vQSsz zSVwn;{+BPAOk~FUdS9rlg^vfw4|C$HU>wHcy)l6rBy9zZwS(~$UpEk_2jf`?Dhr&> zZ4-V1T1F1pq&B#wVNHYwbFOf(UsauM+SNWJM_02}b{*D+kJ zSW&aCbpmu(s1KkMG8Q+qa}z(yW(dOWK3p`#qCI?MvM1Zobc6s^3D>)D?*gsWsJ$cV zc!NFOPutK|^Kvwr4Gxz_>!{Nhs10!5@o6m3?*0_#!iLrMpn^JBx^7qpSVc#IQ-YlP zr4($d`d>Wy4OGEKoY16ln)(9JcoB{gG||-l6Z?*TmwEoDB=i4?jPuwI*~T98*E)Tq zcgy*HYweTBkgRD8YWIycXBLMzI>Vmpq064>js1zUC->|$*J5jiOJq-ejy4~}jqNPX zLox61K0;K!lw2x8v~88r<9SnGJ8}JU!o)rDJOipwz7fgkESJI_9A~19ERLGJYr2RD zgs^@9pM5f~d*4~^;%%37`z)oBbK=oHKP%HDeu;&RJ(Tsc<|15n7=z*LeNgz88ZWsv zv+rW&F!x}3-?ST=d5=!-He-7_BXoXw=SUWDM}vcf=Of4y${Vb|Wq)4qCL^ELB)Tl#M**!c9wf|7bRl6GMd4|dhOm34(QXL_>)VOd}o}l z5W|_6w!V_aHDO}g=^h5Hi43K>>8r3^N#8hdNTLSMJV&(gUVo{ti>j`Op8@ZgVdZrP+|%ew*1}VYvS2lr z+XBwQG%KQbpASuzHq2yjNFvW#uC)YgQTt?m%Bwuy#1Xn2(2TcA#%e^&euyMORgZ=w zT#kps@VaM=U*<)}!zWMQrTXEEA&$zR7S0krdW)%qeBPks{3hm*P3hJsBp8T5s0#Ymln(T1F~Z_O?DD?YZApmZmwfbE4A$%;TG;~{SQe&Aw-v=5DMelmoP!P& zf-tf&^&JhJ{QM7G=EaMhUiGhBz{yyS2WhT^FgBF?yj0&|#a)_K>fV@izaoW(u1NE( z)t01Y43?RR0UL-YM1)wbu%qii^{&h)aURx4;6iRV?t%oyruF~eP=%NTerzAUKEDIy z_1445LQeY8gFCyY&*~Yzn@;+IhdhZ7!FQMLzivk6OWqRDH%6UPQf2{t6B3kUF(SfC zngJ$s7Z^v{s}9u;{IkrQx5QqwTterxrCJiifG_$-xTD5I2W!!6SbVOkx$J>oXT;Hn zpJo^HdQ^y91($_|V?MH0IzQI5Q1 zCZA^x^4+I_G}Jy>~Sg!mx+Jq{s;Q?*+7&xrnreF3a1f)|khF&uKK#JR~EK@kRN0yq4rPos2BQ^wy@|Fp=}sIWpHNe7rd8pAPuF=r7TB=Yj5Oonn_B5E=(h z)+dGzSlsNrn|gVRT=cpP>&LIm`(YiC_&rIcscybIH?+XR+y(24#|uB+p^Co%r3xJL zWhoFYE*@q-P#Rf$!fR z@QfM>{hSH@kippxYj|d8BW={6;_aGYg{-B$76U;0pxhm3sR_5oe4grXy_ec-!=>cn z%su&jwthqCPG0S|MHsN+5y~=O9X5A_wrKDNjJsMyfJkuCgl~{8;Mn#r8ko|c zg>Pmu;dAxC&6X^nsXdu>#Pv%wYNt=_@84r#=2SFdYs-SWEg}c%=BGVWgaR)&Ax`Uu z(a@`J^gIc*&(z2J z2RZ!hV*&Qwoae~$q?|7@YdS}Ffyw?`B7{ zZPUdJV{I60UDcM&zU6_H(jU8V=2={!+ikHDJ~47&Ht{cK@E{PX;aikU)~p#gpoJ?T zp;60m<1{(~mHDE5@6Iy+WoG zh@+*Uep6HXKXN<|WY(aZaiq)}#Mt>Ixw&)a@!FJ0_|w>0LzZmnFSR#(b3d|?`(WdL z6+sSeam;?4N1s1mb!pmAdy_%OBBQtI&?oXxov>R`{lb_$d&fEEyrx}Pj?6&Gty_jQ)kj3^elcCaZ&@w!a?>ffL$T?6nw~- zCj676z2a-oXRc8l0;2H10EG1j3dC}`{1mLmKikI_~Ij<^zC!1-lSwB#>s~Cwy>E2~@y@q1SP8 zS>O?kN8X;(qpIstkGqe;gP8Iw9d%*v6Li%kQV$b3TmkGKR|6K^v`q_M!c?ChVgn0~$3zKYOK5s{`0qXI{slNibunf?s!MsyQmv3h zu-zkQ7|Ykm&pZ`@4~Qz+YKs}%YE)-k0gl-ilw|9WSHWz$Pin7g&S+K@Ej#jUSvi*m z)HQE3-H2&}sygqc92Uig!{b;=xZ$*`e^pvXF!J>a*N#4yBKd{X_`_VqYAw-Gl85oR{4O~9MBsmMYyZ00y@a3haH24TK{;3V^H~o<7 zQMgB)!lUDA;`fD?`+A&DQ+=^Me)@A+pb%TF#UFH<4aMC~833$YbWD@@cXsY=OaF_xQS<ykEp@RWRm~XaA}<7 zz(VJFy!kIczQXfw$z6qi4KKW5rySKJX2`#V&Ea{XX`W!%| z^JNiK-H73!s`vHJX+jTYkH@#k-xiC1)9#yyN4*KLT@9Jxg9qcE&YoymkvM2|=F)jh zPCinG0Qd{=U8eECitazTnl>`3tq4d*DF`984}OFGlUwUFnl6|$X@ozLaE{G$zwF&v zCs%k>_U!DOb(WDu0b{*dc^Kgk{%tci+1L0_LHf67@~^+vi;!;+(y4(Go=|BHBKX-@ zN6Mj5L4zYI`;eEm7s~!y5&O5u8nFBqSG`Z{$qmnf{U^_iY0>}f561t2ZWo&K>U9XF*(gXfDhg#gS`Dg8EAO|o#gAJ>`9^`Lj@AiIg{6)Opw<_yov^wb zDuH=drw;r1@mhC-YC5TNja@P zB!GS-u_lVZg3F3gkf!T{%35vb;r`c}N#UCb_%J{W(09!%GCpe0@QJ&g@p&he9uO&|J&xV3O;f#2a}Mv>HYfFd`bA2Krrel9_JtfW2z{ z*#NuX0EFewv@n@f;yZ(O{o31r5PI7Hzihs64LQ18SCSX;XHlKjW#)Bx{tL3U4z+ds z`<)5-F9-(}heIO6$jA?~&shrbUS6U`R~{R96CP7&mzHFM-GcHr#$1=jcR{20u9Uc# zfL%$?iXW}jV?NGRzFMdSRj+YGCNo~JqMr4wDbZ+126$p8`Jcg!bZp6tA5{v@sIbns zKrPm@nI*~N#hZlkL4CJ%*&p+<{8Y6~<%lw0PbQXVdJCeD&b$hj4?2ca*2$Hue;p97 zT`1j~K%Akdl=T+Y{AS^IiyA7xQq}&VX!U9ywgww@HTs}LYzyUJebf1j?;dt8Rn7RX z1}zRHFd>)GX}-9k?AD%aSeLFda0;8jF;D6n>29RH$ioP7s2DaJ(1`d%l2rPuTydG} zdBWE12Ytc>xi2WuPD<(}C2tr*xB{Evo`2j|IA^@F^4L3DqJZ({y31r(&#*)dXq(9v zT7^ZZgaq)Z^6-sYdM9$@)wW2nfor4C3?-LIaJ7JXTINs++!&G zlpJplL7#?bo?*pIQ&nOH;W-U7w%FziJs>BeH2dzm0qi{tz!7lNrE_pxpS*q&cKVLt z^E4rTb>pOrP;d{v=q|+f#K^xsYexf7>FHzXv7ol8%S%U!l4g1%1ai|fTQO<&Y29S! z%A!#4@QLDb-6LoD;HoV5+L}|8Qf=+2wML6{WBBOue%qm+wt?gqO7gyH1Anurf&rCJ zY~vGJUC^_A?txp|__2xewPtSu(XD{ndf5^X0#xevEE?+$Wx>ySY^OW?37iiUVb{yH z{qM(!Q-IvxQSY?!*RIWM<-pu#uNYeg-KU7-^c71q$H#r?A{Hr<(_}%(RmD(WxX=8Y zk?}@1YILb!Dd(r5sVba#fwtkVy&EEyDL==mjiU;)1GU7Y!3RoEx@4vU%1oTWkazZ= zTVe#)IC(GWbcdMYmSDLIs|yDWf*di?wDA{_{dse zDm&l8k^VdIN{O=0hkdxh2z|s&(l|2ErC7=KtrzWDo+}d+eOCozvUGR)!WyH)8cl?{ z0;#B|s6Efsr6`Y7&WM=X2*}b$t7m~OF_N7QpYLk20_DorP6&=tpQ>2*L)b(>Eoqi1 zx96)(Bwy7Letd-#6C>2hS@rLHV%XI+lMpH6?U{K?6}2Hv3@tnR4sT@W;)?K01c$l? zDwLaE~7Gn{5VV8o!m|3Tvz19 z5_xx+92P^BqofS?!or{dm>#~piq2Rk!_Wi9#x*!Wqa}?|ZPVw@_7&A_pU5K%l6M?j zPWgR?x*F#VdHU4`$p;zI01@jH*e(Ll7JCp1=o2}dU7#H@c9<<&20qB9w_W-9(8=hl z1maV536i;>F(>RM;{aUd6l>}}rbV!0hvP~UxBL-mroBg(g9|nP1*oh`@2n>X8I^hY z`w;%&CxlpI#%4KR7++X9q73^u@W0Cng6U?x-EG=E;qFAmbiYakPIxj3Et zeR4NNQ<_^K{aaI8-s@qUj8LK|%bQWqGbUvD-lh5J)ZIuEW>}WU(&PX=V3!ARA;(A^ ziucdd!h2?TT{=}8*6|{h+np@A0lAPvNciWy65O+0-=ry)CF<#UlsQ&^t3!C&Cv+qn zjW!SPPFD|yO&%(ipu8Z}On{x>+36;BM-JmVQ(de>Hqf7&a+@^~{TGgtU|k5Io9Ip` zKk$FjP&~+JaJaU}Sb@yRe9N6lt$xOGA5?&FU0Qp4eLk{&k?+F)#&YucZ?T+ee*>iY zpa{qixL_5hl~IZQ?@y<82O8i}3gP_MHxU!&?wiRWY_56lu3$!IU4ErdUe{gg6!MYs zi|gS(k4bf<3csk!VEQf}hfO{CK~qsjNaK3vBK|OesvY^`p%HITw7JCVTEJUA6zxeV zq)Mjr&To?|*^HN+e)>lkyfTSlG-v>XMeE$V%DQt|@8wOK9zqjvi4vcw1ztv|DH0@P zh999*(GU@Vj%e1t*6P)5d@P40^k);;lPAEBmkQOaR}fVZtzt-OPz5)|&&La7`5`)W z@@wIlu*i3eLR?7Pn*dek)^;vbN|DiFK4#bIN=R~IzO@2S)?MQ;y@#R&uS=V* z=bcR$z2dB0TZd5!`Dr#-)DgREy%X7`zOP<3aIc-?*(5)cr96NtAOrWT^S_ag!cVgl z<#5hG_~rP!XOvD#e}DNhN$E&lk?OKe@f7yFN}CWGaLTTK#Ey14NBl9-`gV>Z94kJ# z0?9=}lWZ$Lt6$`f7@=3rb531QQ2(JF!2fgbA2N{N%@LNmBu zg3YpAd472n;*_P3%#nxMk7{a|daBoX&%3)YBUCbgb?s zYHrJV5k7fV%UKTzj4cl5+k~tuFTj&2{x7%5{{r~#esBF4d`6S^?)#9Dl?VE%n*-ty z$@GwyI9$3r;{_1|N0z3CIss);U}I@(ce-?7tba^6$~!pUQjaVh`JB!lR|GLsXyb2Q zCMhO{xRNl^bxFG6w%V1j+$gy?uR4lF>sXGeg#I5orW8{M581hS($Oz>gVSuWJYFp? zpcwU+ZJS-o5q3zQ_buFHVZNx!(FJ%Un8?HQ3NxL#a`%%7eEx>35&ijf*5ikas!RUf zlg#=vwEttH)-h04!>Yv$#iR|+OwH`wq@Dg>vseWFiwM7l@>ca_v+t?w;Q_7jLQVO@ zz=?5d8*L>CV%%_dcSqLZNFyEON;r0GW5jKyO-ygXIVZkyJMs{!vKvjRs(#*xx?pSF1Y z25JILYCk6gO8b=>CsMndGo_AZW?2)U(#HrR3(j-%%1blX0mPixBR^k4nv1@{lq$b| zZK*Apsx-m!BMaMqvrf6^`P}FR2bF{wwEiG9J2LRE{nYwF=TojA9+@BJeWJVAptP{? z@T@1~C&IFpp+9bIh)+o6QCF_un3vVu39$r$e&8#2#(b%h6Pd?Lh`^`8tmdLaf z;De=+J=FF-rIq$8G5P4F>~+F)dBi+heaWaW0mlw?6aynKgea=lcuh>##;o0a-nzfV z9*rJn?eo!WLZk{D^@SHa-r^ju-d43`7)Rwf;w0{8!-g+Zjf$+>(YJO%QPWa=s|sL3 zgt<8*bGoIHxu1*KWctprbqsA`)NPee1#~PbDumpEQjcZh<#Q0_QT33EY0mW03sOfap3UCWp0N@BPDOELqaUS6Krm8)LLL;iV$$Kd zmXd2vlKt|H-6hbNu{SHg#idInGy=c6;S1?8mdkqBHxKt=Dd78twz^!tkhZD6{!^+^Qhm3XB#A~pVAjN`rmis3RP0!d;Y># zaI(wUNu)LI5={5O1Gd;HK8{k8I`jit-i#y1Lq)U|fQ$Mq+(U0M2auJB6}c*%|q&K-Q@hd8be_RWlxcXPuL9@+V%;E+OoHSKG&^aF7T)_fD|;Bh+@ zxA+2XNH4bs!dO4x+N3ML+_CuR>h)`}(jHsAIN5nYqwosM!bVUsH=6%U_wu`wBs^0m z9Yb>sdn7a{>#0rR@G?%10$VkHZvb5g9yyR0A+g*^+gE5?E%EH8B#E&!*Jc$Ji*A~S zY~~W+p7aLuw0}GzoL7`$VyRV4bVemYw=2Qa2oy_~rz`pe5An4v`F^yN=Yl}>^4}>? z;n@FA{-GKEz9fzHo)m|O&dtGHiWzC)2`jesN1&(^OHl*eZjVX{D5JTjVV4Y_r0W6GfAe9vy}2PfAg}jIQ;1P4vs@ zWDqA_O+nuw4nb^~kSuBzN!~MjSbBTE6Ls)zx^?qO?j`nGZ$gD6O#)V@MoB+Yoe)a^IFogL)G*50SHoYhzu zF5(mv6rE zi?g<}x}0;lXu77&REZ9}uSczAV61 z(_j~Ge}0!39VzzHc$pai^NacVQ(~zu-RGpLb;1d98(mrs4Sh+wG5o_Ha{GJK+1R^) zv3K<)ZQ$iLQc#|rA}?X^_xm=uqocn)PV*&GYK60bs9fpJRKT0kGFg4L4wn*FxY6rY zs0W@g;E;4pv*$8w@$|G6iNYB6is3j+mac8S8vkLylLZD>xV?_s|JjRi^0wqyP}9VT z=hJYQ09+Tsp((OGclV9{9?zfx2c9NkIWx%-#oO4HGh;`>rMH3#y?kT#9Bz4ut&Kny zol$W-YmNCpr4r{9v#D}(jyYqAv659i=~YU)WCTh>DhRj#Eb5Es-j=iadRP4wLp2_b z5AiNCTA*4tEJEM}6;21WOlpEpwWv$pbn3QId2ig=jo6)#P_HFrsSz!b(*r}KR1m&? zT6=8E!~|u|SDXdozK;oI(-uugxnx(pJx7n^$KrAdwDIr!_)p?@!E>c86UdD!E)1$o zf`a8A4E(G+-2jL1w=TNGzHBSCG zQ8`6Nf{xBLrs6TS;btP^<5YFzndeBHP<7uiky@Ueb*iH;Kh*>m+;3k4Pv`xfz4_pN zBR=ON!f?F-Ph(NLa>wegrTZVs6b+m9KFaHBzhY+x=!T=yg-R16gk~>)+}_0g{(8+_ zV58ELpEQ-jHRn#uW&JCUX~Nl^ID1vaDtf-dfp1D_5zT;cmFI-fbAeZb2-F&N*WDUS#&sdcK^glxOc*zNpd!Tk-M=@6LBl zIc;2PU7Ba!daq_Vv$y#0MUq;9@y`+VJ}=WN?!g%$E>k-y=w7L%Z$70TDWhGzDNP{? zS%)2$8eD>P+x>5ItZNM&U%U`|`{V8@pRVM5wG^UAW=^~-JdG==iH274-M>YGuN@BU z+4EXFFUu##dhA{`86OYXQ>_)A-1DhWvF_Tz zirIB+TQ?p9_%4P{moB0LQgZ#1w4{hze7TA;3@=eXI&*h*dL*w^;4qlTrBEO|9WWM< zAOAWt^rm<&|cd`sZGhpj5SXf~0+pmFFpHyNA&vqbK zA+3BcX%KxOyr5lWCck$_PAx8{V!LBPt&BC?2st*1HyliKxIM$3L}zqsLfLMR66Zyq zK@An2SZ5z>H`eS3Yh{dD(1#pMixqI047Fb+c;I97)8}QqBJ@23W6sak(qC*wgB2>B z@J*#{y~Iq>x%RG^ysn_&UgnoTR^gLtgoH~omX%uDiyyb^C7x!hnLS&{Wj5?E7vcM8 z5%W%DYp`C$>V#fXd#5zcQcDr^{E;!SZ*q zzhtQ^OH|d>G9xfU{+#k|Qrd{$0tLwrx7EyF89+%TpXDI76&qZC>| zmsE@@?^|q9eKJi}E(yhcM$WT-j8tD69{`8!PK-&PGk&2#^(F*|ID@w7OPJ!c9SQ72 z0(1OCJ)O4ZhQYOJ91|CzNhJxB9H(iyUX2)J-ln?7qK*)!XSkCZYn(E^VP=s85+S9S%`rOrcB7{QN#3>`7Aa-5nmd+R5AK7PrymM0?6@PK1(d?L|rxG#2rKf61R5PnnZa@^k z`#Ucarb9En*-u{_sn-oANCXL#FE4yyhCf+Wbg{Qf=-QLaAa4q^EaKKRmZOyN&;;WZ z&LhX#cHQ85m5m=oNW-@i1lr6kX51bCs&4P;0*Ni&Lrts*A}v@Nnp-Fw8fPJ{r?qNb zizo|11vKg-_wNoJJ+d;NXK|iBCaRp`CS#m@^ytOuv$jIn#Kh##4j~Zt8XCKJdZ+lYbE*I`hhxcfrUpyetT281Kp9(B81fyBa z1OOhaG?Um~G0Y=u-T3UG{jVYHL_O$wIPJ5LVpPc?l8w2g^LpfS6>|a-|EZN|8#ev* z`}ZPfK<*H4pOiWzwFx0@onc3TWDf{jcFoh{(jQ%dbhAo=p42j`4P{yRbSjB$p%1aL zx+-j#T(OMx^VHkL3is3w)!}8>WmAFKnRVzz4k`#b(0>1PohyhE+ck01GJ#8Fd~!^lKw!f6nFlpYaL_*(ErcA%6T1@R z9$S|eJEXp&Q(Hc&KgdE`HZUKf+U?vSLmyrcI?F%~0;jZs+^1Ch)Caen&YU`X5sDMnNtDIQfH5*yy89(_LaE}-SHG)PmARxtHGC4VWa%y$qb7LAoXu?{5$BD5LyYFmQ8fa`TUIsV0It<04pf593 zZj>8N8oU9NO@YiNQ!F*MD|b!oigr>`@se}c3Mc}WA=N={COHGy>Y>7W2Suy~xDvEC zCv$R8BfpIbEF^2O2$Ks>OHa>iDVO3^ot6hW=kdCUmvwHY*-KS{oGPD0Yq|Q$)mv%kyr#Hzr6OdQ8UT2__VCU2>UBqinlKmjdAyoAR1!{iP$GKa9;;#`|Xta(y}Dsu7pX(nfn+A2IZD|VAh)b2uZgfaDm)+H*_j>PMaGtSUQJMv4vx&TeNO*F30?tkh&B*7iu9Dt+UNM-Q1j z6et&XhJ6~C`%V5u&T;ret7doxjrg*n$oQx2v;00(wJRgn|L%Y-v^5ol%ZlCHpSEgI zzccGUge72R$@fOQ=1i9~a)5>RDE6a2trr$240rKysmV#4K(tyG4Uc zN7SjaAmXo)UN75WP!+(C6OSE8*Zy`QoR^4FUWk#XvH|k4nK^_O2~sY*O$t;z#OnbO z7raqKX3v7%%nZpWjR1XKG`7T)0xud=A<9(Z)Vo=`lsX&G5KQuukM{uXX9%)uu`~J! zFmCq396Rbj>e|z~orv4g_wuL43kiJ;;aSH^Vl+u%7ZMuj-qf{K0jrXg*jL8yGzb$n~>vTF78X_$DDyH5+U z*860+k;85lFV)6EtaHADq@S2r1>AU%Nvvm&0B z9O01U7k!nxn#l&$RK5CyXTuC!rlXxGMM#J%_gdk+LV5FXN%^ef;hognZ@1**q#LUT zTT3UO3YEZEY3mcD!4P9ykWQJNUF{7_Ng(2p+zKBds9qJug^_T(szGpon3dveUEPHj zguCLv-i#RCjELXK<7oMpoR62DxhX1~giq^BQ`Hn(6aYSOOpH4%2~L-3H8tYvPo794$+_K+ zet(m3T74@X3Gu`|9dG!4UOw>YdmZ%CrzblIlT@?delanj(+evReK<;dj8hP2h3 zF6*FH^poJNwqvD05;@7a{?X-6Z%i0fZ7eaKJ2c;^k^t#xe7i3;n5oIKWi%NfROw|j zRDN^L(wGaQmGL~oC&VDA-@~ksE7!`=Rqx^xSV>AS1pjUbLIm0>Bj79%aOQbjVZ##E zWTtO9i9%^q8|~_(*Chtag3wU;MTll&A+Iu--7DAX3clAh1;qK&$MxK*`Eu~U`nHd+ zLyx>ewDzGQDovp)BkB8_-#io`R+R;HjT6g}i&4rh1-LJx4{)1YQ?Phx>pLgpbCc913;@ugs^L!0= z^ssNx){h_jPF2esi_uf@9)x{<3GW8H#pHzdFU*&0UX4fo5B$>sk1_s);nMDU%5zVd z%ax-d*r)&CAr=7?gS##=vC?Q_#FLSB5j!xv^dSoHmu4jMj8(L+z#F5Zo7p}o^nCDe zII%@ZGhHY<XeOCdN1!F@@RRL=Au3-N(v$ z_!UdFanz_TTp`FLc5J4+veO#JVHsz18B>&xmx{t7H#euocl|!&JkfHveRU^3TUUhG z$L*U)9zmdmP49j$Yzc}2Jl3zA-4@2gHu1I*3k@&2-X6`@TVcZHdt1(|iYBb<0!HYU zELT{u5zqY8vuk`8x)0ukDX_E#z88bFWMCW$AF+V0lKtD^eh0NL&K&hOC7S~A4Ewm@ ztI!yeE|Ec6AYVo3`Nl&j$~}&&XV#ZE$=vu#8aJ;>nlN#;Dz@9rO?)#NOjOdOHiX~B z#>m_S(dF>2)I1%|kpQ_}2F^{nlM^bHzK=}QcGGv`RXzyf(3LxznE>?{u;Bd8r8hbY|mCJT$Ow*^hBe2Zz@Sk?kIW#C$=JLNezg< zNN*Z;`AI@b(ZP;~)Yiud9r_Noj@)k~n zGHEpSecj}Nz0?|5x1E`W^hsi*Qir_8dF!{7%@eS$iWg<~G!a-yk@LH-stVQ;1A;so zJoA7ja@{ODQ6uxTam!%0kmkK1CR+}6?F7q}W-qi4Lv({H7e{cp+rd2~p5>7Ivpb9xVP*H>FM_}r-c zawMel`GH}W&_+qRH*d0Y&(_gLhj$a^yXF<|>!NJutT4+W#NTP%IfNwuVz)ycyK_d zxQ(W6u;=C$RS20&VfR-`#ZjkZNg0R}uYUa?>*iv#3JjMv;|CQZwN$IO1WObPc?mUG4x9391PgKp-+?iv8sX$V(* zEfn>T85T_+WroPfj-Hu+Hw1)&N>`#XsX`mT^cL*v8l_>V98Q{RWJH4m)Cv$KYkz4? zUAg;tJ5S&>*J%msPP&<}VkuY1#|s}yk^cN%pQ7xlUE@TuJucaf0cLqN11V=iWdZEM z4DvGVXl*e&)JQJ&PPF#zDY2)RQmB1C%0Cj(0;fvoFDu-+xXAqTYm;0)sgFFXZw@&l zQN3!*|GE*5rTxs2@~9u0>?C7wr*23|;!d+;#FVJDm7%vBGjf|?2L$uOY=m3k206M6 zQ)R7{g<9RM)G3|Eg|BGHyfaQm-d}qpAjt2?)x2*U+|Hg_*7!2m;^$M_wwH7kT0E15 zw^n^Zl9infZ-lEx@$R~|Y)t_{N1!|QKk6)9ZA`!W<>ahYpP5evdyduCdc2x+IH>Dv z2hN{WBp-+GXj&&=QIBi*+(nF;TihGf z2yg~LEVAwwMHX|jAV12C9jGiI^TW`O$*}a`h>)5!2kL2&%E)-!;sqioAy%_uWJ+nx z5{Mx5gdqAGiEGt6g`e}uXUu}8wnkIq49d$EM(HbQ6Ri`zz|y9UQ0?$1Tcl{ zugbmLsIWYg(FUxasxyKoafl}If{Bf_2dONm5;`eUuARJhvLryyCFbf3;=#gHBdHcB zu!DEE!`c_uv@3s<;6nacMoYztWBAB%Xi9V2;AIQ3@jU}&`w1ygc@za5`k!E6N*D$H zo}{C^u&4Y2qujnw{`<^^;250mAhV}HBpP%lQtCKn=VbKq^bTF*mK1PR#@Q}Cg2J9O z%{+rIDqUoGTpR)6bQ02w9!kKqb#IbSiSmyw_|Tt3l-($XfXhSL1yfb(&pwoYP*F{h zs-gg{0o|5=NB&pZ|8F2{X~+g`Dem$Ve}@K^M4bVXskMKXm}2&zvzyYqil{le&^1q0 z|6M~UmcoZ7tva=LI-Mz>3P+!kd(?2|9{n}+MotNRnqadF>##WABO*vOR7S#*gBn~K zrSB~6k@s2rq-K16-3Goo>|2-nMQ@-qs12_89h6$vv{NB9QH6C1PrQV4i$n?0?@MJ7 zh>9I=J-qZ<1~l?3rS*`=%C-%BbLbLsn9)h`MiT+`X9q6NhMz=KPH6_h!R|_iNPl+d z|3yS$G5oP$T%2=x0zP+_ zP`dA+rhr1XKLm{ot$qfCJuiL-o$U4>cYg<+o~K@3l9c@v^}pl)pOrrnlN*p6UBuyP zedVW^zmS+wNFov&B)pN>Qc(R&021?24iaTB3%O3AI0P|_$9BvC+G2#zEAt$K0=M12bBoyKv{-g8`){Y-)c)d(N)NuPs4R!Op#gOoP z!tBk0!OZZ74u|ts)3~_Ov%gJaFecq-D!HJYndfM2BnipVmfRugy~i0KmT}5!#lE6w z`WK1vJtb%(-N8nYQkFE+OjqQaO1kK8$a(rki`tpC)I~GQUJT|Kec8Z0-|kt?NZ(#5Vw)bZzmmP z#2}9o{&vzb)tu)RqvRfE$~Z(3k_ulW?Y1N$bR&N+lA$bY>|6A`hzX`_Z!o~RnTSdK zhJ*&f*BbZVi&|kyFRB@2E4}bRBnuZQ@Il?DMT`sAe_R@~*;dUsTV#kz3LjTU-#fh? zVWsFTiF85(uisLaz^PTfd0LP7k&7x!+?maij0~FU&g{Dx{#!w(Wb-JGQ%mi!HRmw4 zZFUvjx~~L~{=KNVvLTZ8+F=t4P2WhISIfOD_3)y2n>Vhe@_R-VvGaLcy;5V%^7XC5 z-rMqR0P+j@3oQPsXoOw1==DjVEkIp%9FjJjQW%Oo-+VAt1I<6Q7jLr*c$SzWWO}X4 zBmayK#(d6lhHh50zk`lcg6>A#0x5_pXz?lPM*IU%8VuT8iaEpHU(iobitz7^ zCID?C{{sDA>OP)_8f!R)sW2E8zj2s$v%B7}3#1JLWI|FCH*)}NA;)GDS@A88H#bo@ z+69$>yqzcxNqW9Q-$6z4c=!Xl2-S=j=w&v5e=|GugGlltWChc#?Ri9a^QF$92b#4b zmy(1nIrSed)i^lCS?tBwp@ zBiWhFX+L6Y>Ej+y4^yzaxXnt*aH|sX0*qq^@JlAlliSM?fef zDO|-NiI?+CYXskjrNaS2BNrTYv397W8wf58(XLe!k{s*BTq1T{^a!exP2wcA30XxTZ(>bW2jN#XQ6hA(( zqle53&%}imY5%SdwYf5!@=GC@So!gi*LzR*0hZUw8E&vloOVHjrgM}MHxB4eB~AUI zq`-^&jbXVat=TPv+7)WA=WYCF%2^Sbh&COl)OLWOilku?_*lDE(LXyAh~=eNqNnHc z3P5ORCQU?Q9GR^Y>5RVK8BBjo5jdv2yBE!Ftckgh+_bsqmpYKd z9HY%RrYtEb#7A_VzY|S&y*u>(IU4X{R)qOrDCNzwhqoX>wftG`;pXW#hzSYxKvVZA z!@_^pLv+jta=^0!FYxIL*>yhvQ@&aYC+Sy5L=6=9hI zL@wl=v5D9{(MSOebQr@3Nx`}U{n_jn8!Xrj48gjEnZnLd%{ZNOLxZuC7fIq@Kl!7J zu7yCA)Ej@UUihD5iJ2>UV6jCFB!5j>pwfdI#}qzIl;Gi;9w#=i?n>uv)*DM%cV`c!fng?C}FiSokyMXUu) z9P30N5#K6%h{2vnvd%9a{(>-W`e2$mjqWH?K17Nnr|%%+=uO0~F)&SJ2YK~Y!KLgU z7?JupN#HX;bnHeZ8l?BB$nR=_&yQ0j(q3&QI%X3T|X|8_l*EY06B z0KlC`ddXj<%FlWdg>$r6xUv*>QwMt-F|ECn9Ex7|Cz$>UU75}BP7dG273XPp9H89w zEy36sYNobH44g1OzJd>bl~(@z zHoCFb<{VW(Pki($h9Qb`e}UpXw1?5b0%xk_#gv!BCAY~>Z2V%0*-tsG z3jOp+Gpa3;2`VWxhEdzidW$qd!yv^Y`oetlC-(jv?C|zuy9o}W6JD!AooY7eHoo)q z9)>@$DlelsIj0Zp#@D49j#Mv>vs5J>b0T`Smsvfq`>+Q{>!b(O8bm}y%zCb#Zr(9~ gz5XePzSuz#i1xdR`1|+wuMGah!0$1D_O#3tt;2$GT1lItzIwvA&sR13ZGBBI&l?WTi1+g#R?_*)`#lgVod1AW$;R3G{8Rb< z;nQW^Z7e-q++i%=&LD~q7sS&Jasy(+ifEUE=M9_RUe|=kL1ZDch{Yaa1F?j7BEHxC z?Ry)DC+k0KnExL({FiQ>ci_Ak?l#Wn4gIZA>NeI6mNG8Bzx4zTWaJb=V;|}9exMAsOBWGjfVvT67nvJuar#(#Q zw+}(Q&%+b3lbm3_SsOu)@r~-z;ui!)z^}5b+SS@KvmMO>g)~7oI?q?S+%0%0j&MXM@Y(d<%-qY_NwJwg8j+*crD4Ikv;BbJw*s@LE`0Y7ziQ5!y+1w>INR=B zqPpp?UPaltL5cHMwyL7juqb1Lcvy&QS5x)a5$D^qjHhJun4 z1Foqut0QY*nAo`M+br%HhnF>|X%!R0#&w$h1*JvJ6Bvy3PiqDtLpi)R%r9_U@8RRO zi*)A?WRE?7Q_(}v?#R%4z-15BYIFHcm^otEa3`Z>Czhm~BFc8O4Xwhtl0Bw|<~|S; zt|$wPqD&Q{D(Bkv=q#)TQMD!ozRth1GWLSbG}dLiT#G;Oa{UZB)~P5nWncl#Sa03M zw%Mq&YQOzX$(v^{cjM;ZL5G@77r)7pP1i>T$1Nk*KD^?*3%A&7KdV0S-dFK56~FZ$ zX|1Wjj9QOu`?$y-q67AHdUlCKRa0= zLk1B>K2$#p2!G**`|iTtoRx1d22^3r#E%MKP)JFPc6p z6slUBAS^UBQAu@tT_n-p3`6miw)fBrLcgi`lvgH$SBgTeK`}&4u-FHq27grpgMxq~ zo|mB!Y4-UG@}3AN1ChVAFI(Z7^|M+OqQwcB5&fqe{1%rsygNA>(9h`5ebH+_r_E#w znjB#_-Msj`XLtM9zg->wF7`@D!aPAW>Am?!{@%#S&XLSl`eYA;k>ifg`YfWa1?8LG za;JJlaVf=4Gmadu!B%!IyWb z#wz7vKbPalp_0$So{cJ=ucCSewQBGMrqBNS#D$(1^V~Mf<_1wiRX&GpEN>KtO?GbS$Q8`;(F%zj&F-nGnMP}K2{e8{r^<8eQZGPV%yf0r9W~Lh^Zf@mY$3iAa zRU;YOYQ2kenKizayPCzOlfk*VwgTEv!&H-Nt?^-^omEFvh!Pc=0}ZaJ>_g{vX5M#v~ zoynab$`<5+#L`IHyX_Xan5eR5&YG1qp;hhiV@c+y*>e~bViQS}&6bgjm}!}NF_V?| z8SvLgdSG9x89F+oB5~9m@w$aPkt1#*2YRH6(01*#_5(lNLy(lCyqN# zG`#(VL0=y2Y$EEL-bz{%Ez}+Z5{9e#34!|3pY;(xykVN9{y@eRbzpqIJ)jhQd7q8p zpc!hnjfA)Wh!~w;k6Y%h4EqwZgVuBt@&L^b-koIUIqjR-ziFTUk~YU3`$4ueR{RCt zQvx%Y2vPcmWK(Xk$;YA}tb7Ru<}JzIA3=$YQoYgFVivLavMnE!O6#Hp>8UkQtvW`f z5G^kUP7mAVJhX8xJSZA3PO8S!?xmc0*Q_dvU#>9-o4*pt#JPgS{QjH#K9!So6S_bD zG?e<@HGC0nOyz!uq?dZlhNt?qFFI1&;nOB}J;-|(ve`n7UyHZRC|Y*57f62;WBe%g z=)$fKWj-FxR31rdf#ZGloMf_(PhGXe8&fgaguc~Ond8|t%cv4A_KW59X8Uy}i|)p< z`79Y-+3c$dTW*UzpS_>)ukr=bz3En? zoUX8oKWjRE?vG`=M__(M|0U{};`SzI;5Y4kw!`%(woZD_QsJ5I*to~9#0DCj-E(xm zth_CL(9-0rUp^o<;2xC9X=GWpt#?4wbkdGBdN`i>4!711YJNs+dB^CWKU0#=)u@b# z>5w@1$`sopToeY$i7znS<}frI{1f~3D(w9nsFg~S3hzjBD#1tqz2i{jWx;rTOrZM33*YJpY(|K)T1usTdG&8Qc(3&q|Kn_lK|Rp7Or=B)Gl1bKN`Kjmngc zHEzGTbC#C7_t||r9-agp^&6&bJ({txNW~1?3X~i3UQ4pC#z_{*-`r+Zap1sYZ0SwY z%2}*%LK~u#%YfX8ugx9T!GaR@Z zY)Px>t&o<*Jsx3A*1|Cfp)N87AF3$WFP$P+k7^80P?plor1I&CBw(W)3t-If>fgkI zFAAhH$6qL>&mg#JkG*`!9G&Ctyb33IKi3y4RWIz785IfgkLC?fhaR4fjzv=YrQ^XS z%{u98T*c+BDotk_Ds7ntPs`gU%dc=<3Dy?RG?OGXL-zDbw6#{_jdf2N%X#$%d(xzy zPxgQ`ZQk48pX5c|hc)vZvyrxT?T@z(zv=AbPB%`KZ>n4Pz$a(JW)(e?T*EEM>}~MK z1P;DTisBD1Koi=(k{!GnXDrDbtca$6)QL&WZTG5I^Xb+*OAi`_ z4D|FmiSn*JAD7QjU8PGZ>+;_q&TAo=U$h4Ogm#Wu(nEw*3cC#E87i$lr44pO@n-GO`x^CiGxL`oT| z{W6w=Bwg@@^sPi|+x7>B^Xv9Wlqn6!kP4%P@!Q&2Y~n6KNmp66v@*DBGNp-QLQU3$ zm($D1W?zG78H-o(-5yx12hP1V1B0IKba_64y=zoe%X!CT%-Ke!E%pf`d8xxl<4}#c z-Lkrr;u{@vTaJ4iRI?A-@s3TzN4Lwk1f?nBpG@zJq#kWt=6fR&KQEQ!%(pl_bt`M< z%2E&s4_T9*gJI}gI%yU86kSyL_K}a0U_jIyv$dr#^Bq+THjJhkHz(XD+Aor(Mz^P{ z?%Y?sY~YxeP0=@BxrCA_L)Ry*b@?Pe>nq^{&#T7o?q_XOvN25}+nA>sbBX58x)*11q32c^yRd4QX5nj)U=P5hPwhQ@q50hFtz%HT*$ud}hCE$Ja$@62s5CwPyG#s>2xb zq?vc0+TCcg;WiDwz#HDra}uF8!o44p1R#6S3`LU;>YMk9JSBzn&q#JUMc=gw*PEDY z$#(T@iCwh32FT~ns0`(G}Se~SpPcu0Z%Eooe> zJR=mn{ekim_}v}jq-iBs483NB6IPO`Ujeklj}OZ69DM|r53{N@#H%y*D6ii0RNU2( z;gsOy=HN-=j1`@*eTv{c<98y>J46Prcf};T%E=%IlroiLj8`$;P$jG}66ZSIAU0S-`h?o!g)SI7^>NS(^Zx;ABAo4eX^ zj!*9{X)L-C<@tn!S!sD6EPe=uTG89rF>Ur@d_6L;&tX+D$3 zQ#5kjkc%I822h<%a=kJZh9%k zE5CO3LjlpM=Qz=UXe#~UG3=BF@uxQXnQOdzs>< zP3jMIxCO7dI(K@A2=PZ5e3~X7ki0t>8i!*YM?~NGlod^8-LM_KxGTHXy4p`g@F;KX z631}(aJHX0+gru28O(8PS~rQd25EUvW>6DxK6CcQkH%3f8mJ|p2duE$a?YC?eo&`r zsfc707jCVk!1kD5?QYDlGZ_n9LS0MvLWxUV?il4^^0tY={DGx;EmwkH+?V<+Yzxr> z9zvq3-Df>0IxiI{j9mnt*xrjW3lFCl8Z^)IEcztxAw(qJj$OLYZn(vb!a&DRc#mqs zqVR!abjvj|5YVU|=BxmZU^ zr^yR!#Cal1n9~rd{&xA@k@02frDPTp7K=}`M=L=>F-OLp|r6 z)3~TNGn_2xpZ#Akezt>$1ibP3^pzMybio?9-`QnhO3rQDKJu=s?D1;+% z<4*|?uU{z6uPM7n`!@9biik!s2i)V)#~HW1M{>+6@uQq8lq)2p!w>DL6Z$zsglplR zS3mX`b%~(&L3jvD(*Vt00T_*k*@J-tZ%= z@X}N_v%XmD#-b|5?1d9mSe;Cz#arJLK}pMkP5+U0EuG$a!H*cWzgHDdOYEVcCwn)Z z*(iU0{n=dMWvf0{meeEFb){$=l{5k5C)mmG&C{%kgXwX)sKw#_p^kQ|&y-CMI7SKd z^V%buG+v~fQaait3vnMS(N4yw-V_h|bj9k5Ne29aZmYKKY*?8xs(2DiglT8|uJVv` za@w~QZ_qoU*r!D;_%kCUAiOTEgaU% zHo`X!$H^*b_+hZE_24tSA}BG6SmiZMM%!2WO+>> za>Kom%KB;$tKXF_`=`Y`J!SM~UhdP9^bhyN0?KTf73-}y#h%AjQI00lk@wrgVXe}Z z%onXK)_A(K4HQ+WgL;qkidYOeWo?>w2Ce6rh;*+>sL$e&PJKC5L=k=* zwv1LHd>)R$m~q5KF!aip5u7G}BGVhYWpTd5~u_!Po2x%KA5YdrKUg za3^nb(#JKKH{-nbyc44qY<{Y`@3vp@>}j4Rq);5I=kCS5++vP(o!7?PE}V+H1=KCL z#`uiPBNnKYT_=5EM&Xww8(xS6_FCV@>maF$#KhH6d7h}pQY_+5{F$Ac(PXX`Rb{in z9|LJWY`U ziHxPJ9pPkHHWZsk;I-g$rR*(CUqY%Q3tPc^=iJ#<+~ghtdu}B|(oY+QC7AKe*I240 zvI(zbJ)ulUvgF$+OIvArG1Ee+z^7eipLAuRiU?%1p|`lO%vXt zo8=3VNuRES?2wy;C(n22P4(&;KjL*SJi$C-oXQtH)=lihn?EJ!=uvq2D!%wZ*(GfQ z4;%gT8&cU9s2->LsLiuo;-~8MET%7otRmmS@UL`q#;Xoz+({#FMtSZOnihz>`#40c zKcDRIvTY^g)?7qeH3=)~E$@r3!+Y7Gx4sw>JsNRB!{*Ck_1Ac)Y~+AN%aM%UM6m7E zXT0@{8^59Y5p!u<2sOTYO&vEBjnqYFsyf&EMAMko{~`Bh6BlV*M-xmW{Dg#x6K2v5 z43g!AL;5*o(bpIgwyFe{4)r>d9QhP2Cytc;6`UpErMzWm%6B*PY?c@64$(+RgOhET zR<6FY9=Rrzy}i3k2^|Y~Q*1$er`$#0XeFmuW+W~1&KoXT$&s0a(*g_3O7{`6s~~Dber%u?@Hj+C-*rrJz{JYfeNjBb<2)$0iFCBs8OIi& z(YJgXtADHMFkOahk1Qf~Ma=CRmoIfux$*zmcyD*3ps#8|duAYIHryQ3ZZ6vsg;?l(ncxm(L(~48%OEo^m(f3-Or7C@BlpfckVq^Hs*Z?urS#=@XU15!P~&ZKhFa{SD=l&%%XFhIflr=e?pYb9dZ>1kTbIx?617 z(@?opG-B&ErV%v1Z}16VR=;7b%kLqX7NS}T?|d2GcuJVwSn-7|qGp4r>m#{GljK#= zY9HTKmB%cxIbJOb8P<{YcbC{>oZ6Nz8;99YW-MIPXO6sn`@njtX5_k}O!>UR+_XYO zZp_lV*)}G8LbR%P>=;u7XqPFE-*MabJ}hIRzO0(u>;nCOx_3vKN=X|{yhT96!ibH_ znDI3$v^iQ$T-EArHE%1UNbR_(IMLKEWG>mTX^5er&zC#qhM0Fmdx^ZI$i{imT zTplgnyYj{vbkr9p=i#s_UaWZyI}ruTL0`h2hrH7i!YLB{7?+r$gYYmGC4@!xs-N?} z@T^4hn#NJAY)i#0r)?S_Wc#=+!>Qd+ey1Qsq}v@`zV7Z^bpZYAS3dDnLKAr>1kIAG zHkL5QK!sU{T|$oc&fHp)NVT}PUdYgMq_+3H(Xd3jCj8CrvFU1slKv%h2JZSvj?Jz> zBRloN4srokDNZi#*7gNk8kcw6L=%Q9>|q@~EH{JT4J5oNs8v*J?nbLx`)T9%v#otD zrO-5dBl#RN4>f4IPqVRmI|?`pmhY zLudSMO&#x-26J1dy;p+cC=BP$TpP^!mR-9u0&25L;L{26QEl@p1|<))09Eer4gqdR6pAM3xv)L zb-8Y~4*0J~k`4+2aNiA6`M-IX9&JgbhdXHt=iv76TE5E=&he7|9-5rOstu;5xISKx zF4om_yklS3jgx8-6s9xF`?`3lu@VJTs4RiOhMks=XAhcQ@%FO~DKfrEYl*?mEAs5f zH_9-2Z`)WDF(2?EN(FP|k^0wlm|h|w(OXv&!jzEQgS#r>5t;ONT%5xrI-ysoxrt-@#czMLAu$b3%~A1wVz8+-)$}vQDh5fUHhB*1 zZAOI?wH7J|5~(0aZM zOPJK9suF(Oq2iMFaoT%_@osjdxgfozsoD5`9~y-$C20!>9s%x#p$Zy@+J( zPx*FUwK&$x!n~fFHOoihHr8L9Ww6_oeV182AoXA{Vb*jx?U9tB9lZZG{zLx$Xi_f6 zYKA(wM@{}f&vcFEL*C;m}{vlz)l3~j?F zcS89R6#8JflCNb|T-Ui+=~2SOIHPx0?sbQ#-l1Udn56E_rV4hkb#zoIYbh%Wy|HpW+LShF)U^sGafUZ^(mB>c>Mc%C+ae6Q8Xg7peSM= zms!_y#Y=ki^2H?P1tX%(nE98*l{&dD^&bjc24fZMmQKy`Eitd1@%WQlI#@QD1=rn6 zXxM2#6Afb4!-XllbZOfPNTy)cBbQ0PY&@qq_vFr+Ma5Y9g4$)J`()fXyo`}d(y*d$ zvh_BZoMIg?(i}q6JA6;Z*X^VGg?HH+RV^OX@2==*E7BI3IVq1plHIN= zF`Mm2tQno{FzvNUi*!!?3boX9)u6`L8uJ8_RqOBf#sY(Hkf;hXhux9c{^$?yjkEGv z@Ljx>>K30Fc?Yo9 zd0AKdX?txyMKGH%Ip_`vheD&-3|xOvI=JAbc6yz%d>SXbn&~c109`?4aeP>k5HFT?p?epi%)KnUvN?D)jH~|KINz6vQqAa zR+nPU(aPyQAM5JIZXYX^Z0Lt0bYg+q2IZF|d!_O3jNmGSkaov;eoiDbW2;);!+f0l zTy$`6CjB*;?lxQH{fnQ~l`tGnSiDo-TwYSSbi=Bp)%_FQtJm(Fp6G}R)kI;TB}$7x zF?3GP0*kxFII9~OyFnk1y7ImIRQX*Ed|le^Pb}$nFc8!Y#Nk~(KJ^z1N| zy1KY#>6)YjX)o5Y+?2y~7SW2@Z(;Zb-iErCWcY(93o2OC)|;0x1fLK ztlL%+zQjpBeG#rY)XqPidM&K_ThWLx*3%bN7HK`ymFQv-KGylUe6G#)C2x0oIULuF za20lf=d^+{vk34UsA%>T_JR#c7(^(9trY!n`V;grJZn%wswyWRN2vQzN1&BHYoQ~TNE6z#C)(K z-B=o!t&>!Ua~zoLR4NtR1Akq~)9zNH7~EedbVw=aJHY%cm{EO?v$Qt>(s?5?$nz|L zZ&Gk&SG4C^T&=>%NUPG!+ejjeQAoz^dA<>CXsUBEmAT8vqK0soJy(%}sF^GwuBxeD zi2%QSJ!6be!RzP`QPvgDWs$J)c>A~g_WAASFN!^z;n2lZG*-Im)ptZ$aMjTHiWTMy zym!Lm4)I2rgov@^8SVX|nh(}zaR_-xt#`@*J^t!=AdQXwr|#31NXDUF3u!oe*9U$H zn|Bn1dD7NQ0fIey3R(IAgFZ#2k#)^!x*YFc@Ap|JIAJ|Ak2tPheS*?6V&%?ERoF4o z^5&a@MUEP~;z27~l4$_D_6Twv2mVsjxC?5_;1C7-Coh_~F6x%R7F;tM2lV9R`zn<< zG#OJNw@K!UIG}hC;XX@TvM~p$ntj5DBH_-wEEXuE5QX7-r%O@&21O*tr#zmcgyc3I z-pk$u-c8tgM31|0c&9IPENAE)&$oS|r6wHBXrI5NtTkUH>8OML5~uuXkvV<+CrS{0nZ14muCxQ*PIet%xOvEW{-#VN+mJV`?%aB($i9g3u_+vM$QK?+t&{#qqL zvkPBbk|;htZ_s$Aw9>rqC8n20kY8ua-)>rMdVywCU2CN(%e%;jD2Ncgx3QJFCL9RYqm?c#fXN!`;scj>h+wd-gZd znrg#OF_Lo39-G#zT=WTWfB4~ZY{{#V#d>TJ(;S=V!P>E_DW=I6%nolzOY3;2Wk2Ta zBF(^~s-8u8V@w`p%Q44J)=xlWBqAh4d|!TPB4C!J_r>s}RW;>Iln+@bL)&GwY&wL$Yv8zVOju+wjVALxxkc%WYk5+__sG!rwi$iB z9;mIo9eTOaM8#g_Hl6c$N}mfKwK*XX_1gPMETT`s)^ICFAr~92@Vh1J#&ncs?_Og( zsiNuB=6{0M5GU}iKg$s7jjKIzMqNc%f)EtRmeEV?5wVL_?E@>G&Z`;< z?M{mHd0m5z7hpq?@`>3U_79J)v1nhIvXD)@dBc(_qfRS2cjBVrXo05IwAW=tIoUy@ zhFhu9iX*NZtFfHZ1^G$kO&+K8EJ&ifA28FzC15vb@RCd~+45|V>)T)HGjV9s$11sm zj`}Fli8rl9XMO%@LC|wl&iXoj+8JLxKB>u~6nukdf07G=boa6PVi+t^-q}s1Q9QKp}d;8nUJtw(EB1bho8ZR+Ond>h$wVhZJ&m1$l{cO z#Hq{RR%Nw>>QTG7L))se*2gj7lLETUIQ#<~p)$^*tQ`XJAUSz7a_w16`8yrYn$LJJ z+tP{-UvGcqUuz0pJ4HV{g7NL7ZhuW#n;n8QM8wV>@&q399|jTZ#@7hOc#Ikt;Roeg zdsn>J-NZ?MTRCMhF5tV6w#aX1yr3V^y0(zM8Q8(T8Is3Nd@(gqj7y@WpWIA`2_X+4 zg*lS+8oyHHeK+MOd;7M1He|Cs{T0kCk<4{AT$+_Uc`Y6Jz$nhS`gL75k1yWJ0{y#8 znnVNB?F0E~vG-1aY12|1UWUCcEQ+_DO{h$f=2C1}sP`Q(qS{Y<-handM2hQmOn|sz zR(VUIn2OOV)@5%Dg}-?c8#xCPd~-y)hZ0m6H%d;C8Df~M!K044MyTcgu6@V*q6Tdt zqpSBSjxCNtbjP-{bZM(Jk;TPur)`rLtU`Geo%HT!*u;;w^HDGNOLw;;IeFxAt3^if zn=cDKo052^oh5^ia+xv_Qm!^3oWpEV9xX;Ton}8yO&SwDkib~W5aXu9BP$_=P~O}_ zQH-8%O!W~FuS!;U+=)ND=UlN6$tAuYFS^>J7%)rW&S`m&J!0i>p9Y`N>T`~&7&a%? zQD?-UO%*d#HzeF_^3+ySz%!plP&jci`4gGvNGEMOz7qM{`GSWPE2ppFUl#BfsiT$y3JbYR!S^f_pE(SNaQ5C-n^5iPDVpg!NDuLhQ=0OLpV#a zOBuF_s(4|CT3v>0Nt@tqWKl0E;!0tKe2E?|kpo>G)p^hI{Mr|VrjvV&VFHCz!*@`e zVg+(+pqCQ`-5xg{t>4NUZBJA5Ra1-NS!Ly38hz5V!nJb$xN5Cy7|*~n7Mrr7J9QsR zQ$vluY~D=*k7>DzX_MiNU8$(hGUxl((Q?$i941j@6QPCNS>k!CY)omy8lvU?d>qo7 zk02Xag+vKi;`$7GOFL9Tsf8SzNi{F{wBz+(bI1>c*FGSnwP!bev_t7;u(7EBQOwA% zUh}Qw)0_IIwZ~rj7Y^?b7%Jfiuk-er)rO65FVuIbB-yYrE>}wkk4roYMjkpsVY*c? z1)Zn=sC#%|fMV2v65(0n`t&xYkv_BEU^NSzJr37$BO;OC{G!D}&rcp>0^!0OrMQUv z6Rb=Z!}oCEhs~2MIbp{w>X)Aw2+_8Pyp0mZVN<*eU5fO7p^P1TMO;^0HT~hWo|Fgo z9=RpCr%>_D-iBc?F&_z2?tMv$y{Bu5CZUtY&UP~+o`s<__N0gOb@ zwy)k%wVfmW!K^maRO7bPPG*TZ_XA!_r>Lb5L=Za?M{d36SZ{ytQ>WF32|733X6oUd-e>)vFXo%+tkQ*c17Z9fQYl!>uwmJP4w;Oq$F(y_7fM6AXQ7Z&D(3&8nd zLOk4@0&qAlVuOM_oZLd(JTM_%I42wq=ixg~AD3})w?<@hpJ&LQKM}7;|Ko>Pc{;c_ z|CTtfucHj(I{(6A@9F6(!o}s|4d2S^nhmFmyWMY@?TGa6zbEPcpqV^E zoIFB2LNFn2UQQlFn*{!`oxeBz=bPz)Xy@ zou{#Tcyj&Wrt=AM3i1fTgy4LfLWq|CAA9(3z4_Og?qY4lVQ1s=<3^kPP73$=LHs>a zUsf8%%?XEbSy}$}|KHR3x&N`4;%@{gJN;I9pp2*|aCdb%-z_jtcP~V8zkvf{mm!#f zBetbv<6vj+3FAQ=DMabP4HtJ^S4%6zI)5)5;F59h^w6?#mvwP+b#X?NCLpQ;xOCMm zJse?tf2j}P`n`lePFC;#Dj?wdOPRvoD+tcU&gLNaDhkR95F}&>1PSqkoJ}I?D$r0+ z(NIy)&`{CP(a|tq_%IkICX5&t4;!D1n4Fx9n3R-~nvsU`0zDNeDJ?rKJrgtQWmXCr z4mbx3oRNi<<$NPZ=;-J$3>Xm%M#Mr%O3CtvA7?EP0u0Cp=n52x4uVX81SLQ^dj+9E zv=bHSw+G~}4pfjIzI0g5(N5} zZXv$@rDOkBzX%ZhLPkM>qM)Dm3klf=u|Nq>P%m+#5lU;LTe=a^@q}U!%iMeTtQC`< zS7)2#n)?8Zl!0%aap$~izxC`t*Riml>e=5q_V<2GK(L`mh=T_ufJj1)bYfU?k--A? z0bl?y02lxa00sa9fC0b&U;r=x7yu0X0u11tjsO3JMecuen)oTrhZ`YC{&CHR|6Gwm z_-g#enh*cq%r^cih5ibfPa4pC{;FjG%?Id3Kr;gj00sa9fC0b&U;r@i=V0LbanL_{ z+5LCML4WlZ{3mhH`K6O{Q7|0#_uB;XoLd(EzQ%u6+z=E%+$HAsy9EE7xbZ`$ zWPxAMDH#a|`51^BK->U>*iQ~(|JC{~*glv;01N;I00V#lzyM$XFaQ|%cV*!Fg1~uH z{YS%nAw&kmzZmZS&=6eU7c>M%)3}0l)xY z05AX;01N;I00aM=f$xX==Sc;BG~DNg!~Y@O;iv9@F7QJ~VMNgcxc|AfB|4oOF;D2d zhZyuS&ss@z2DTY^uepa|!br);DJU74m|0ja^YIG^3JHtI%E>DzDk-bz>giuGFf=l@ zvbM3cvv;`e;pye=(g3usAph&+zAiq73 zkfA83Xy_o`2jkE`ABVvH0s99q02lxa00sa9fC0b&U;r=x82GPZ;QPAAdA`9P)jb6H z1pij$!OJi36ONUFKXj}V{1qK5t&u;0Di2WQ0kXM3ss~^IFaQ_;3;+fI1Aqa*0AK(x z02lxa00sa9e;Nk9FA1I(82wR6P>7fBUzG%Z=<_Q0EBd@zLuG&@2qeKj&7b^#)$9V; zE||Fj3;+fI1Aqa*0AK(x02lxa00sa9fC0b&U;r@i@5R9PrQ`Ez);}s8{~uMf-2909 zg~__yaB%-(_h7+a z&^;K$LBLH2jCp^eF%M{Y|6W=iIG*5m0tNsBfC0e3&oc1cIEeS$=Hp+AgLr<*RaNkZ zuBw8-RTYg0xT^jq>|(=y$}jeBcCmkVi=BXALy;iJ1W*EqB;-g3T+f4;4ln>101N;I z00V#lzyM$XFaQ_;4E$0Id|&rCH!uB{bPsNXK=H@(V1<5Yt19#h+NwgTKprf(dn_0i zf2nctXLV%oJ}?IX7yt|a1^@$q0l)xY;Lpv#_v7DlFTXz;{|XBI{jMTEH|;Oqe$@_CFdEoef;{zA~3;+fI1Aqa*z@M3c z?}wG=@!h`^R`Ln_Ow#QSO_vZ8n{b#8OhN+Y;puK;=>$6)JDY>xt0*WdK#-6j5G2GC zayAK(g`lCJqM@Rop`oIoqoZNK@L@1aOc*gP9yUH1F*!LIF)1k}H6sn>1$rt{Qd)Le zdM0Mp%d8YM9B>X6I3o)y%lSr-(9zLh7%(CjjEIGjl#=BSKh9bp1Q?JnAglyQw?JV7 zVKra?FaQ_;3;+fI1Aqa*0AK(x@ZZe9_rv}3$=W{}?h7JHWdAmc{Bz;{552{NenoGw zFHnR#6&=E!3JFSpboL5DgODXqk$!tX{`x>dh9cyNzj+bkLy(c6P-GM+Dk=&JV(lQr z?;$7zsDzierGdAYfFSS|`!g$uplv|m1`GfO00V#lzyM$XFaQ_;4E%+G?}yvx)7*bF z-2Q)XZ&%?TdRhtpik?>B-mVy6di}pyMg!v;IL84700V#lzyM$XFaQ|%=L~#54mzLz z`1xqqgrUigP*ZNk5xSsRiVayLk`2WD;mZGX~0;{WFo zKiEHD{{RL61Aqa*0AK(x02lxa00sa9Kf}OxP#c~o4oE;n4)ZgbDB< zzW>8>d5Gyi#0TrZxD*_2c-pulW&maNm1!PYQ2&fhS z)dFBP3(RN&1^@$q0l)xY05AX;_+QV!rqfMQ-@@hgs_SiCuRnj5&uYBCBqfrf7UYVF zvJm3V+o6eFyW4~F%7u8~nPS}gVm4?PhB$`!(*jIIPnE;~f&qCFMpR;LDX{!6+6N@1 z^IN!U-{=|yjyWPm-MMC2;I~`pJ*X}(J9H}b3zhqC+qGsht9&l=I+Cp6Lc7r0YKVXG z__0ANO5hZ}pYcQr=KHaJFDsn4m$tdu-#B&*mfYVtP12aF`M4%S(1>n#(B1ATw15&D zbBx~d0FTcFLxCYovSy7LwVYX=^RY8V{MQ)wDeN3q<(9Jkm^}x}c}=tNE|I|pxdws@ zT&OEEoHYb%wO{Sb9&*kj*-BxG%HrQN*F>k!+HWmOjG58axniqay%~Q$&}X~Oc)Qxk z3;jfGZy<4~l>&zxE74lR`|gFTdtD6Hu-)UY8BL!$&mfq4nuKrpH>xDkpY(Rj*x&ki z?=}So1$`E@H>KyAhU2X=yi&>MYmcK>c^B8%a(AwV@4v*-&^4&^jHrEIubDw1EYyz6 zBa>7;|Li?%`BUg)GFvaEoFbM-F|sh0Mox4Nu6+jkX_$?`gtXh?FYj(#l5bgL52& zC|+L-NkO-7Th1kUH~LT!ll+sohDzG4z1zAfLmPbAA~Wq8p&T0=qtTay7kC&G4mtHoJ!=b zTzf7g@7Z(<(bF!(JiKb}8`a*R7`-9?qQ(+u%JBdzj9M6F!}6Bt21P4HCyuY;-J3Yq zjjejT<{urWgq5kWw{KvEsJcRMg~+?!>{>Z=YCbT)WO1a{9@l8H$$R(tI?JPaoVNe< z=lFj|MVd{wJp=dg2X!Sw``S_uPwo84dM#A#L{)Bxzq=m;QV()O`6HazhBYyYIVYEn z^^F3lJB}A_B(C|4Qwm^qxoc`R;PUW>c$l}(X@(0Jn}%(@+QKbxhwF?({kRjxdzjJG zLWF2>4e_Ta_tP%L%wWvb#nIj3$GG<>QeO)Wf#yPMd~{mtO8Sk{BC2n<`_jzcikgqS z>zAjls(WRM^RWj0M}lWOGWV+Sr)oX=aoL(5#cajuc`7X?XJwCR%ah1O{s-4gR8es+ zXjXFA!|dv7RfCk1BVOEgov(nfqd{X?$eqVqsU4TMs7?e%P8br+G;aG86hk=Y#5k3) znl&%`UM#*3bz^!_@u+@neqPrsy@XBWVpKby!QeO1L)m;sH;O%maOL-ChSBeicutE*m+#=pB3m=bUS^uF(z`A!^ ziQTg}dEzTfq4LC9NMQN(_koTyb{$a{PLm7!AY3kvxQdr31VXJWUfjIR)rPn*Be?VA z?HAiDw_1mVYoEjFaVXvDZ*DP!zNKg-*~1GDq{#DC80{S_J7OBRhu|E@*ITRBK1|G~@k7(mD(;lWzBO{le>S7tu5@ zX;{lW2CCyE<({dEGxc9kwdv|1TySf-khX8zLcz?ikYun@u8>VWWWFY}tfT8=iJ&n5 zKNWLG2iZ9VU;i>XvOP5^A=tr#uF%@uEp|~#GE!3>;%gWC4I{X;VLN-YIOx{m%NZeM z$*mNm=AP?2qMt-Fu^!jHsLPmGQ!m$h_bUdn`F3y|=ohZD4W0OGM;W~PC`d{8rr2SbnV``2 z&Qgm`c6EH%GuIfI~bT)V~glC*oMzZY-JGN>pWJ<9Dh!My_| z$lVe)V^2HnuSK`t#|nzYx81}YInftVr+YJlKWVt$C48n(VgRuZ_CKZTJt&dkIeh^h`A~#m3_gmstXgc`2D=4_t*25vUv(SAM4t_ z9gfOiQ3>Mes4SfJQ49;@k1l1*U6MG}nxsvsy7Uv*V{@^d``032HT%Oq`U?=_w7@os zo+Rs~+N8!(l3j!nY@yq9&Qr#!2?4!4d;hCB2s%U3MDx{_muNNA>WQCTb*hVWq|%{S zeu?T0dyIk~p5w6_m~Uan|LGg|xjRwo24|zt^(DrV+#Tj3MP-|MWiM zbft^0El&*jj~BWM%M{w!g&2tO4-^3wM`MKtrDHWYrFJ;ejdmYXJgVp7di{$nvIxR> zTg&lx>RMHwKhD@{b1&#<f-r&ILw>RQS>>(i_^NV=vgZy>EXgf?fZjw2L~C%6*cgI7)8$9_Q7D zu<2*%@*Kojt;%S-$#xBT`uH*SW(LNXm=vd~Qr5=R=57YJ9tz~9wdW4H!`JWjdM$`w zjNM&BL}PJ?zdE2g|C#qeT_EGU`qd7fH%@sbemUktd}Xaxx0O2$AH15gPQXQKExEjP zlO|AoIFJT?tSSHH-~k!wXW=Wc2_DbtCopLfq^YPZ60ffo)dzefkZ=rW%xRx1kH|o~ zV~5hvTzNgC?46Cn-4%itHzML8B<(Cm?NqszbwcGzWj<{;xd+7bAwhIIl&+0A6Pov} zSF_4p1(BZHc+50h{=%w25MTdnxnIX+1|dmQ(;~D9$bYz$JRtDBqqMzP^n9dzzDD6| zOc&=#h+yHEL<8%&te1J%KC+X$nJj&8Zz1)a^QE@tZ`M zirs3FiYC^pBP!b>l+fn#A7m!=^ej`u(H~sd3OR#(^xfV%jc_`H>|GyyASU;;hvVKj zC3l$JIREG2(K!D9kG=N{i)!1pMN1Ju6c7a^qk@7UN{}2%KtM!5Boq==1Oz1KB9x+l z`F&dhm@hiy^r>XrdIl@uLy zQG!*EZV+x-*RSXQsrKynJ(IScgVM{A)Y=TamRS+Zjx&li=#OYtvQHBa$QEc>vJJf{ z=btE;e81?O{rE{6h1`t4-xFR&vNDZ@gbW|$uwS5Vvg<3gHuD2aYV_|(eQ*aK&%j5Gf}(Otu&@0Ha@mO$Maz&^*kxG=&Dkn)f-$Uw z=`@Z-Xr^Q%0{HLHsb+Ge%gfO1eq+`_@qD5y6TVe)h3lTyUBUhXlPw2Rpc@3ncS z1N(v*DR?V+Gc*gCr>HvdGz9ro8wyfTJn;NjA#A+ButwX^wEuqKA%q-41c4AS$CJV? z+VG1OIN%*Y=l|vQ-rf*Ykf`c>{3b1pNaUR5jUA-%8eW~V zV95jW%^lxuFa6yQ>nVp4upysxO)7DH*|g-6TK6smWxb(Zp5S7aG#lmXmxqU>A_A%> zer1}PO45kXxu_t$OK(r6YgLD|TEwdFIqCkoCmS6bB+^DsSNrzW2U+|^q~n_8&ylNY z&g5L73QxQ9KVbETaPlwQR*(8>_A9F!qLvPSm?atU4dH*TP{vnaC9)WaNET`4Qp+gI$u$2udlLsqm7Wo`4jvwa1Y@0$V$6?|zyCVJW zRtj10FY-l#M-UR4^2joA%YEHIy@UkT*Jsv0(VMq+RZMi4!4^Aebj*Cq)L^@aDn1tN z@UlTTeOsxK31LFK5n-tzwY2dMrXz@5YGlUuNj!|}>mvx#>Il*iRus0up3J=QWQ(nB zE9RJKZ89d67l-`!=YPL3c)zQL6@737QTN>>>w&RMnC#(K9GXL0U`m?7*LU@Z=%)bE zE51ZP=^A7Lzd+v{l7Xg(km zEzygsf}oA=Ng0q|=kgJe6nqp-k!Nl`8fNRC7q?&i;KZ^(d+UHTI0lh}cIQs0qbm+- zbDFa78aM6ddv!*;)mAe$TAc5}`^OIshaD0zEa4@2!JP`!^NQO-z8!rWLFxi4KAsBc zD8~wMhxBn1_x&-KQQkZLXjWFmq}<47q~o~#(^r1HawyHl`A1MT`&QKz!;;|kb{0JxhGqF* zFoy9kctfAX74n|Y$d$fY$N08T4zK4`O|9oZ4dsn{Eb>y3(ytrNctG`9ZGo!my zlTHN_Ha-{P9Hpg|m{}PEae2>J3txE7Caejgm|~=#_r0)N|9D;T%$8@jo%4#l-kz@S z$M9s-`Ej008StxGG>XBS(k6lyLk;UPRCrP(9@&|E=SVs}jTY;_(UFr(VNVw2?0MH- zn1sY%hC@)Mc9EHYchY^5!4pEeo6CuTlEDzAE(RQ_f8jlPFt#QLp{X(RFJNw2Yn_x z-SNS4Y8m){m%Aj%{r$Z;?L^aF=h?zaoLhod?9N4=!L=is$*i$U)e^2^ZS9V0SZWP^ zBP-2GXVJaiNApaxG`DBq7r{04u3t0=FD#?&r`pl?w21zPa}5WqSu91e6K+oFLGMYb z!*Oz9ta0-@VX1UEH%O9ep3D&tLA7eSVM>7dXCjwQ%5IyAGmQ8!SF{Qy7OC;Z?EgD3~%N~sdKuVOOgtU@PxIIvbL4WT}aj87>{lJ zP8X()s+Y9y=kAs?4uit3<5N5K2BJZiZhuGX@Iq?nKZW_ddo~BPhjKrvk01m$LF&9) z_|K{HU-2#f{dfP1#_LSZiO8;wbg7uWbp$~-96{JMl;A2_6XCqRUSPuK*?vb3A6xB6 zdg=xm;^ziWnJ8LOlsHU!*>7r~s3GLIAtg^_+X91oob%*0qO`cwSR6kQ6h7Bz_jT^e zT3>xE8yU~I;aaHYu_Z4OQvA9krut+-ka#^|Q^u7AdR651VKI|Z@fTk~2=VB-iS`kS z1|g|o!;viwExF#M7n@Qgt}6pso3#UjrbN9oN|_QORTR^(wqxlu^_|JK_XXXaB_gQ{ zbgMCYnWRa4PWXFYt>3%gLexaI%eQc9swt4F>v!OPOHQElorjRFmrj4y=U_B)}CqV zYtqCfMLcG5Q<|K{H~jk$$n<=g!6c)CjDH+cod6188jJ6z)E?tqVt|f`jv)OKV*nnf zzy2%j&?7b}8r*Vlr<{9z4g_d05t-igSZ{-WzOq_}VVbTnZeeDMDV@qZ$+WQORby}W zIIt(h&-z=gU#^P#5}^I<(%RxL*9$uo4L3Gbx)o#+E$;;Wiju(y?IPoMy}*!9D5 z_$8^|X8DW<@iF&TZ?Nb2IO#yL7-@aY9xAU%3DvrjvM1a4_cNz_43H9p;K6sTGo+Kp zS`&Z1=34F!X6K$?4pU~2p7SfJ6pU)=d(ZAIISSJg%xo+pwp%Z#OO@fbOT%!!5815y zDbt;}LvhZrgGk+RB0Q<#oH5yiYJPIE;l6uO_ex?}iQh7Aqjn>sflm#?gaoAhZ1EV5 z16wcV`Fi_9lZ1u$@h-#b(-jbJcRv+s-|sGUb`4eY^lOJc9}zYtx@oP{$m!L zw`q#cbmqporFw;wE_tDp5?xhUa=d<((z8~l6?^8A`1?~H9zp7b1mIY*!GS>$J$#Q| z1`qZDNzDP_M4vn%vX$3sU)L6gRd)o7^X8NeTEHMLu>al!AcX>S{1;riP<}EVA_M9X zrE=Z-mmaLefQ4ZG{d6U$xE-7``s`Mo`;6bVd{@5QkGb@R->pTBSYP5wpS!9q+-w^c zv>uTZ?*k=Reu%^qBZqazaa3S7L`8kd@|En&wszWNExXz?peNnARX zyi=(3rFJT-x7Ly`(h|>+I1_T0!8e))H-ti-OEQmtEHamHBsQroV%4k0Rf_K%h>Akr zmw<1ZG=JLbi_&6AmeC{*bz;=GXL)Ctm5ATN;g{4USH4)=Y0Z?Wb55j98-w{?E5~`} zH;I@ZX0-JsBN%qii-Y`eEeVP@5usy1w>e>R1aVTsu=L`x+Jil{hSjx$4su`>8Y{B% zi1*e?jvy7RSklwCzKcDerE946XiAU^tOe2 zYD=Aw)@V^XjHGcm@zc5VuWig*Bdy7^B*B5oyadcY(Lq05s+?EoslO3Ai!=yTQBxu{ zrDgB8tjb7ffH?6)=i9vpj9yZ>>9t9=!~Vf^-^dtGJ|TD?JVZm0{t*NRR8oWasYCHW zqVc@#p{PK?rf++q#@ziICMVy5Za~!yv#W6g84CtU7#+qUw`7apr2lu`{SLON8#-6L zip3P{${z+LA=+KW4);vfkS}9=kAE2PG6mx9^w_CigfuhZ%K2RA@g&^K#fsT%vDKc? zEQ$m%OLqh@f^MtM{nW{dmg;`dKj_M>r2C_Hjsls)ObCZ&1T#Dkh#k@Hl*U&B}R(!}L6#!LAQwLvTG z*EHefn2tFW|RlG}pNP`7Qbim#{FkC>Uh#rrcw1z|Tv1i`Dc zl{tApkKfdgJwzpvQylIS`(vSRk7Fq1iMc~VxpE^)E_r{pNJy*NMmat%`_3$_EFMvX z8=__-gK|&D;OU~$v}I>qS90vH@E+2*%TI1N^WNx$MRv)yEP+ArJDDjd!?@jTQZG>U z->M70?&o)5iySY^I45>^f%wYsxBe#=l)a|Sy3O&DFpC|fLW$ly(jGMNIjBgmnH)Da|_a-#l~wyTH!=$!I((napDyT?>J%`w%E<4-B6D1L9rl=M=7 zBd+C(U8ww%(3&AW`G}Ny#{Rbor5AMOP`>7J&Bmn=c+Okm8j3@f4wnAPnYDDe$V3fa z0W!}ZzKzQDFc!m~VP;)ueS%NZG6%K1<9bO8{o7l&vJ9J3Wjeoqnzs=(FnggP>cN!O z{AWr%_4V%BPlgTr3GZpYEkz2(wTu#pwU4i|QE9L!G`=L#nznQe%0KE#8E^BBo;fdC zO7qz?z4wgOlV1`2}g!D`I#AYaB_=Xude<8wKe|R!6(~tn8!f!GvU5{XJV*COTH;F3Zb_L1(Jo zP2J;M_z44cY8#AaaMT*~kT4aA>#7pwLpUTIz8lu&#TQUi`7gqdX5w)i3jdwL>)xdn z_hDQC*}OO@cZlD^ycSRU@*zCSM#@Fn#DuGPslY=@{nE1dJ+oa!rGAWn5{Fin((qYG z|1|M;dgup?i=k`mGAhMpxUEc12al^`_6YJXFac8g7!#q=K!`i`;4!SjUDK2T*HUvsJ?1TByr?5oEs;MG@H=X>S$)&=MYXl;&Y! z#yyFbF9K$Ll2EbUE|N`KdORRChFKp+#%rug85z&Z%5pc7c<=BwLl6WZC$~RH@}DZE z)d8~m2-2eNAK1WhiifpRFI0h!sqrVP@4rapxyJ#91NuKXUM1aUHD=8}Ot|oRq^m`= zXkWL?xya-Tdvq7;H2vJm{7>@);YX00Dw7-yFA`?sv(l+8lDV!G@G^r6NBh^Q;P4+f zFizgf?Z66t==CE=hUA_HDm7@W0P|T3TSPa@l7f`Jh~ac2SqI2m=iYd3A-j=Cf>z(Y!Z#f z{Kc;IS9&dt`7Z=qlEaf115t1L>PA6b;XhTYwM|lZ1o@S&_0N|ycE?<(zZngL#Ai8(VvX^k=pI9YKeU_nW9vm;qH} zSSBeo+wuC-DCH6oQSO*chR>#-Fpjuz<&Z=*{a~z2%H_BjcfNaOUQtaE3H?=xs<+Fs zm&KlOXKEqsOvuU|DlP86Ib2 z2_w;;dMSZB6T__*?Cvq{NMw#6QL`3i?7H8eaQ9>5jSH@$18s2P+O9;OXa#-8eF9t6 z%+WhDq+j!TEkfbFo`$KC2R#G4>I|tpPUW#f!&^w6?)Q3KE2E<`+{GLC^EKV}unc9b zFAw8-(kLJJ>uJ8RrnB64B$n&TvnyO8$S9gvchb|nPP!oc(rAxXO05mcgW;y=HB`G)eLo|}J^7Od?Gw{RlH zc-rzeFJUFM#9>)w1r;A;^qB$#r|7-tM2T;u!QKFfryY?H5gL)M|J2Hje~mb? zV9$(v%$RzhyRCN>tn+r-s)a~EoA{e|VSx}CooA%{;6%7HQd8O{*BY<+J z0bDd#+jro;nDygYSM3PmkzD&Gd1AS}+cHTuZx2rU@t^OA#aVyTl+@Bd4cvNuFH$olp7S+N%LGc~^JXtF4+2fR2|85C@WhoJurPki1enej_V*hfAk`0gt{94@(9VEfS5)^AuqVFIs3LT?_ zT;X@K!hjW_q}$)6+rJ1q1JYD2@g9!Y5kw2rup$QIEVCAJJ8tv7CO8UT*8JH?N$PV3T_z#PTayUcoawUy}O9+->_ z+1JHh^*845F!p`OUY;*oDrmzuiswp14hrhEw&e;ouui%HXDA!n!gea4yNuS?uPv0n zT;8MA0lR;cBJ!@p0EtKD7JWKLxl!u7_P2ksBrJ%kFqO#6~Z9vb$&Ml(sEL3Uqr&<+YyMN`~5| z%2HaVO|pNveK`Hfk^;qVEmX#?(ld|c?D{zy(8V0cm3hCAch7cY{}PsLj%=fFpSCV1 zzg^M3|7m$=Sou77ph|v$;B<=|?xQd`Hf=sR-;_qGJ-k)?FE?D-A3KiL=8rkr$8$$=(WqX08B?G#Jrdwi zxfZu0+|v(Jy7ZW(K8%W7DV|v_SmbXDghkHB-pV^79{=$Nf3K_kzE3;j#bxiC50K#V zHHuqtGa;czkk9R4RYY<}?KC6S6;JQt32b-G4#KPlO_}%u7HFuoK~Vuz6?fp3?l_T;N`}2A0S8JPbLw-#F)5MnEebiXFD5W+^n0Y;{ zy42&D@>Gi;wK0|?3JG=5m-wE_h1=~Bs5m*jPgDN!&BH_HQNoh9L&dV8qwo#>eMd+- zB@;61GwH3Ee95*ZS($tb?8C0|fp`i(r|qHpLVde#^TZRYniiU^no7up=(#>-=pXT#c*QWaWQ=ILuM$O74^JC+Fo&Zxsfr( z_k|-f{u{dlA12oY8s{W5<44WtANukO$}Cr0IN8p_=xWEt1rX@P0^HBGp@!MRv$d0C z8-;d`XwHhSl-{v$=0QBiPfAsdAhRnWBTz_MCbNi*Da2ck*wfCw_`GPG z?9C(00`$))y#}JR*%8Fx^+849{JytB2UCWa%zF5HGAnWd?nj%qX5i=>8|?Mzmc2Z^ zed~6?fefi*c>ZV_$tev*vxkc;Ro@h&`4Ly{^~gxe6?7YgU5pz}jHXfC|D?RgqtO%7 zX(ZTJ(=;qeYpT|_k(x68V??Gn5SNi5x@dMVJy3t}b8p5(%Ug#UFJ}&@m=d%7Q0RmE zGlk)^QdDMJv^D*fNi0(+^G#uyVmfiEjSQo<_K7|jG3>w})f&axVgleL-ycD?4VD%c zvd4DbVU8f&w%^*Pq-XYUL23gr^is_cL~{`YTrT)OU6Ihot{lp%&`pN7f*s9a|N7WN zcZ$sj?Mtu~z=}7^?b2agdr&iUbxu(!yqv@nNUcroq!^EV`g*~?PzLcZE9Bm#G2rK_ zjoK$kQChch;z2pyhPvQdYY}15N03hFVOZhdQaQ~bc&s?Y0hiUx*ed%T_E`-Dh614> z+X)gvvfz!FA_S?e^WxKPdgoDI3d$SP4RPVqKDBUV=;?%=K^f{oX>J-kk8&l7n~d{) z6jO-19=*($C2fwEj(pzm?c_!q2kS6){m~@%@)q6}=23=bnbY>5K(mz5EQOCavtwC(;i9If z1QD_7UTOQKr0y3!E!&w&5l_sUEpLOQ{CW_scN%nf{(~PXb0LKh zC)ws>2eGr~Ji6mvWS>>a`S4_JHeT!MC4N~)(I5&XYxiM!O~hXk5P%FPGtl+(L_iEa^a{*y-i^Xq@fx3+rH$#&Ye zb}~X9|Cy|09J)vAAaLxHFgc{mb5Sb?_{M*(red2U9~5$_O2qC2YMe`9Ya@68pKQrB4RTt{5AbT~Nf$ z%c!?H1PdHaIwf|zMz~xCL-F*Ge^dq+4{GpJVIS|-Wh z@?b132eq-#2MWdwvPC9OO6FTG;^YM9A+*kAUI}gwBb4)px;JwO@}GvG_&u2UW}e&# z(3kC>deLImK_p|}I;3nT@D-h|On9fO_1lW|ByZJB`z}0eGeS+dq=#X|g0X@9OUO;U zav?jxE-UY+8ZXcpch1-jdO%$)Dt8uplloew%sP>bny zQ}Dyqc}m*SyD_+(k$p#fN8ym)YQHa9MRRCu$bI?k6$x1(eBsa5$Xn^iONOlRqqsrs z{nNbuX5oljXuDi>*|YP)ieZWJ2NfY095*r-1zFRvO(6~tg<=J@J=RBq$wK5m(|!B= zJ;1KM$+1{_BZI#TjvX+$mzW<&^;Wcqo>$06$d_x{*qbVwKgf!gSpr`;Xfre(S2iYW zP%Ct4++edNXp0JzC&4zf&C*iRTE-4l5tDR#XT}hHcaY3TrFCM=ht-P3Kv)p$W4MFa zmeBi>2|qx!PDQ#$$Dnc+V4^Bg@7e?#+$+tng5i{CrrS~zJ}N%*)6bjCK;~enH|({P z+rv>o^pzs^@sTD>$&9UdSu?YjJ#u?z1{2fNzNg<`A$zp4&ZZ`(zJf0kMywv+XLXDP)I47)g<9~T+qF#So@_=-$Q4jQ=u0HnTz`O>wF2E;0 z@~pHgM&{;UyI}9hg3<>WApGGt&JJ{-57OBKgoc)R9DezSpS5F|haXsIy1%wJK^QaU z`AJdmHca7l`1DLG6-~%!Y%OkiNdAm#b;FB^{8(0^H?)RSIO79^d<&;V40N2Dx0E@| z!siGQx}Yd8?np634fEN`Oe^+cc6xKgI5D5}z_NZu-O3(!CfCL~d#Eis*56bTz2E2G zezi)_uZf-Ey}YZ1GZCS~ioToTY>K456JkwH4+j=W?Hf%4GNb(PaSVKKt6XJ8_=X|y zJX3wxIEYv0%vKxeTn)QYqt-MdwuP8vr*;Z>EtV>(`48sSw=nYVsca#7PsM{MxdtXjR+rU09g9rMejw&TJ4CY1TNWs747Rz3UR?^@(M@)?p7 zaMWJb#~k&U)Gn6!Y~S$;tzWdluQB2a*w^aqf7YlMk7vlTr{y;r{t${@s{lo;vcTb} zlbbNrZk8Yx{F1;wX6$&@Sh^f;>)zYq<)xaXHN0#QzIDj14s*%fOfvX|=T%m!=ir7z zZHXtG=;a6oWw?pS;@9FboIxMuiS6saUfRVVPuMAdE02;pK7|1%YmF$JTN?53Rrr$27e2aC`Ynp> zTP4h{o#*oMJn%QOAL3s?JtCh?KxWp+E8a~4u|mm8cbbfFq*gh2L(X*KmPmk%jHcBT zt31-`T7i4}zz+da;!B6^TNoh}yKRtbVk-#R@cX%Ya59WYMte$2Q+!M=^}R>v9TNpi zg@U&@*+w4o)3or}m3H`Luj}`efe@ugO7^B{W3tYSD=wY-gEoEP7JV5kCd~N$qS?c+ z9D#2R`X<|t^zuWNZ`xh({(LCaK=-k}%xvF#Y2RgNSUrEvxF*I|WLYUG!=t}AuhVPv zOH(jy#GT>iAo{kvw?+^Z8_;b7Qb#wVt+3rqgNyh;?+B6u)6-G_BtVJe++I#~9fDHp zK?BPvuCwFRw<(-*J2of}fUihr-^Fo>;eA9!c#Ez15;H(2oML7cqTi!2_o~i2<3JZ5JOf1>EHakeH|8b#E*Qc|#x~g>u zLfb$p5*QckeA_p4$=k=9_fghg!9JIophf%zCRmQ0LJ^JH6{CzO$Me^B>}4Y|ED!XDAse+Bj}`*XOr|Nx+~Ib6nghV{0s#w7zY9gG)plkD zbNO8B)8V!%O&RdmZ@^=h{028@I~r{Xn^#G(x7{modP|*AxO< z?)OzpgPe4QPmC<_v2j;ZwcPzT}>*aQ@FKDzn>Yh)`jlRBd;EO{>^fVpU z&evm^FrmbpZRobJK>&PZ8c0$ua4S&T`+`11fb&g{UA0kM9+nRVzQ1DVQKLRC<+<${ zvpYLq^bJL^>DjtrAK8qd8&jK7`>d!;@>SYRD)ho!Y_xgX^UR~xKGW^vBc|!FO{HGV z7nMsV7gC2hw%C*wIz1Jvc?VgtL79021Q)R*2>7zC#9HAT4ZAbDA^FUd{*fD0A#77w z%^jh)E@@czYjrQY_fvdh%GkZ9;06w2*zF;H?_bp@O2BNi(8ce#KZ?^0*dm~%!r<2F{ftnH_+k8vujU*XrKygD z@)_-u4=@1zEhl1|)pcxDisfTfpZheRP0=(@4PG4AX|voyELBk(_lF!o{1W9VQgH$v z7Jus@6o_sl=}jjg`Zdiomg`S?V~ZGWLwb_O5D> zr>58VRgQlu+*IFPdX_nyt)`{$o|Q~3>>k>K1*dv&zUL;px{+E^Wjp=Or*7goHFTeB zoWW07qWQVx3ko6k3OFz#@psbSXzwXo?!{R2137AMK~vAh^P=oW`ZK-Lh1qK6UltQsn-J9hizuMwVCKY*v2_Wkjv#w;XOy<=n_qb5b~>M!EG zW^PV&-f{iYZf~CX7;&6Jf_fcsTaGb(2N^k5qBv|Hb1!_pPA+S+|I0>CfQY8f1 zW%UVemSwZLGYu5bNW(5ew_P`a^q%nXh!wBT*42KJAiae=bmO|XZHVz+yOO77!v_@E z@^{*Q~P{^5lqlv>|TR}SIMISlDBj%ltd2~5id2wz#FghE?4^tRP zI(3-098Ig2Sw-;{wp=g%9&6@Keo4fkq@a`_hd9#+wTF_cnV$rql(;>imV{jityIb& z70uJh{!d?qT^i2?p@?bwQ-X$ev6T7s?a6(N`a13G$Tzi~ajRVVx>&!G=|ac; zB_Sw%pqH zS2~stj2pjxRS2B7j>ONpR{SX9{$hUtA5 zv*P=uch-eE8^){5tsf76;GQ#4sFOfkRdsq;&J7ok_{9on zpr>v##O3Q9`|PGLj)?+ks-GRc^fmkG{b)bu#NgXJS{Q~t7<#wLdb{A8E+1*~_73e- zx*gX{Zm`XZp%zg5DIOg7YWJ4gb452Q46;Ve!gmQuJEk4+-^F-r_^kbHw?Wb?@CGlI zSzoCn{Y>$M!6$-!*Iy;5%K>`qBN{`mjO}0PlOy|DWYVT8#u0lIPFDbGi38lfKynNk`P0HmM-Dn4=*?zghE>*uW{~n?m3BQo<_!+ zAOCVeJ`7P(p{R_vo5%?=e@i6jR3wGKPLk%cR;LpZyhw6ADiP&#sb<%(57U|g_UM=O z_B*{5)0_w(cV0ovOC&3e7-Gdh&OomN&}tCmprPfxA0;I>A)*mdQ&EQ40=Zu|gS+H` z8v@h@3v}344Lh5I7AHg?TOH!OR-ZyHh&?Dd{0WSij+I5~kFK)L4)$L zKCq|ZMR6<4-%x?ds<@;uu1VsnHE$`YuH>lCokJ^vjJJFQL1pacy?!d#Wi83dZ=SUG z=8J<#8vZMO#}b@-u{&{BziRSg5Fy5fpA!XgecF~@_D^T=G3J_y>}$=E)u3pvRuokfMcmH6a4&8A+WYYUN0!2vV0yw! zqE8*q2I;@BasLk!wgp0G0)yQ|AeB}C?>(voFL5L69ocac#e$NTQXh zsc0i9Q&VN0s)6EO-R;o{=m_~Y{`~KC=NucO9OxFOfKHC!*ZDi84sjIGx{EPBf&@gR zBZlY>NoX8j{^i1JZ!9I6r^Z3@BAAqKHh-t(|LkA?YwL|Tm0`>zthou zqv4AiJ1PhQz3>BRgsMcjOgk4Z)Z)Zs>@{iX1->(-%bljOUbfPoI?)QNp6>Hs+Las)K;m z3*@=qJG!jxFBi&VFO&!hN;JfSQPgibSyF*@c1pIh;OhE~F#cw;gmG%!1|wh_mApl+ zHSrjPx0&Br?KrauAl)(&>QHP>R(|ZXwPqM^tDtHJ;n0xh#UIXOO^(VrBm?l%;ca7u zerdV4w!YD^X68SWR$V$)*OBXVBXo#7NU4wZ+YqL^FAhewe!jz<)r9m4{)Wo*D;v54 zo|%`kezQNu^vu^dW>QgR-SXlNjmswoheobuJ{)Dk@$n$fO`mUG`Yd$os>ZT~CQj~# zWk@0Ki0QO5@fs2b>VsM^L}l+gwG-0`Ic-+`Y!6G_AOMmTfNvCT*s?wVZVk2wHW8HS z>2)Qy67XFfzr^Dcl0!Ygu-xm37ai68c5X!BJV*;+La_t-`gaYN+rJ&Z{_hVni{}?3 zKcU)RwV%fydXegNh0R*|Ua!HwkiZntFe`bEd9#c9>R4BSYF+*< zoi^xgr;h4Gp$`W>U8^Sqe)=9+4 zE~9M+^e|`afpDVLp@Z+Un9_Fa6-O%0~24a}7eX#>rx>>pb zv;cB(`aK4CalRoAf$M3&&?^=08f%kOEHqvB;?u=Sh!c)SvgZB!+38B%KJ6G^X=0hq zgdf%5d^rp4e}~a8{fjV+4%;v~k=6{`+Jy1D?;Yfulae?7QOX}*D!ugMt_c`=94B;Wk124Y&6=?@fv-RhoYWP+;d4S2TQ11 zS9{&7vbZ1>AV`M-Dl0b)aDO23Beh2f2J^!cQ30n8|Fs$H{+ z&g+Q2hi~Wp)!CviaUR#GukY1Fqv*wkJcf*dQ8eQk1rp}W;7mUG#mI+?AeOI^nqWo{FX5{@n&LvshkD9&@1kj}Hr+>bMtON4 zOZ3u$McK?A?R|cv;GS{=%Y)i5w{7FVdktos4*u&H$%=6f1VJ}aaJ@WwJ6&O^>1uO+ zldD2_77jF+-B(*}D)oTizO+pYM|J0R-W7Z58DR>|3$elU`Hx>Uf#Yz^?|y|fF&fFQ zuE1wQZNpqX^T>PM^9+a6es;pUl@22rrxAOb-Ou|R8Tx$gTmi(_={y2Qqs-CzN<~3j zVjnVjvuDBnZlRy%kffbHYiSL8Ly0WEuh|NJU!bO1qrMeuY3z=FmV%EPO?{p{t|?Zy ztCugP+*@u{wo%r$f$jm_BLwqQ>pP3CZBMH`8eW3ZoM}f{39ytRgh1`c^gmyUU@2Q8 z3kgbWwnmAy3@NVfNG2xoVO-3@jJ}VJRT)y$Jn3)$fsz~NFzkIJ?(q;z*O#+NG!U}I znTJ&0Q}*>iV5!8kL)lW_r5GmdGo`hYj2%Jr^slxgUh&TsHru3~T4=z=^8w!M#T%=de7`PPf`{8oojX-}DuKB@9O-@l_A$#|K>#Vz@dAP}u{VfGYM z04&*&^P+$XM2_*cMo;zTaOJfq{J@&z*0BJa)HQJy_VQRsr=vJ2?C5Ox#9JFr$ka^i#@a4n_v19BU%J} zU#lQaCOek4)X>g&o(qgt3s^lJL9(a*FJuhhI%Ko%linH{D#LD&>11zHN1yYCCQ|EP z0%XF%^{<3as#H~OvoxaT7V6=5XD#n6ZJ{rW8~oHlh-IdwtBGcHw=GQF<-wqRU7=c< z5sNZ3Z^bQNHPqbv*g)T{kXmf&Ew0ZJ8_e;QW^$?hZm(t0Rw|O&?5nZ19}zXeHz{o& z-TJhSP~sPmqs@74_@p?Bpph&Uxge>j=~rHnFf}zNHbnqb;Y5tunDwm&Y{X#oq+@o@ z2pM1%HrsOkLufJf2ee2B(4qpgAeU|`tY1q#A8$hnQ`44;P_%nzigQ^&ankI04a00? znMbUs(sy|wqSF|J6j3dg3cSc;SW&sP4CpdkC~5+$C%Nq?ySTsi9{>{hL(n;efwn+% zP&Qh94GT^;NDloW?ZUu_+o4)WP?ChQ2@46?9w=>pAmH?zJzVd{Hrdls2OZl<=AxhP z;JgO1U-^KY{IqB9ypYh!2)aj9Ry^YH_y7oO0XtixRHwM)!EY1yl{) z0|`9(8sw^R-o;>0Wk;eWGAUoi-Jiq3Vc6_UNnpM#%XS3$!1VlUUg#3xdehQw{OxP_ zg2HJHuW@#k+{n!b>L#2;y)LnzizJ7ye#X3xu}y*Oi=zaqY=m+!Ig*uaiW`saou3G5 z7d4zpiLBe0tzNkQ-uO;_(X*xC>r9LOpPc!quRtkh6FtMV%93+4*9~QebGg##!5I*$ z?aDde42YbKr$vLN5;Hp*6ytqgY=_3CBKxl z_vOZ!X3D6_G0IUcpRS+lxkc|U?p{87hJLjU9C{TA3x{wAw%Y3kB@G%3T9skH^Q)`M zmQ4BclN{pnoud^wM{Ag~qvA5R8;(7^<{&S~G1LwYKrU5~_({w20-RQce`D*$cGBXK z*0o)M$LmVvt(vWruS#CbLWAG!gLCRGUd^4cXk(c}P-5=)IXp1#vy~C8fxo+XtR_xZ{e>|!^fd)+XkLPk zrp^>WfaYHh+giPdVoB1M*V&P~e1VCPkvDI?*JQ+#X7t^Vr;6tOm@@a{=~DXyKj(exkr@fx5Reket+|C8la!8yQR2 zh*aD)Ff997&F-}+?AgN==cLZD|GYdzZ9pbr@!>qKlsFmsg4$zSvA0$p+yN2<9v9ge zA!js2tT@$&TLnJQ!tMk0+~TK=CV#hhne#y1SXtprrX4<%cFwo0kYJ6|ph-coPpq$b zVdG~j8WfzRpE~~R<#Bc`UcKgRyPiXz_SjXyna~coodLK9=ni6MucEGuz81QHQx1CD zWHgmDuNm=4nB~mFLmd>nGCV5=i`{^0cm%Lmvqun0Xr2}E96pw zk(5Xs@L}OQpo4a-Haqn-|9&B{F7VU-{@*D5TWZ&DT^|6@->zfiOXMj+wRCA}+&6h3 z#Nowp2lF^>mdUg7@3N)Rp4K%F&RM@6rZYejay|$CZDm8fOZa;sg3(*IN&|8qYA8E0 zFG&-(xVh6ay3tL|8~&=CD$m%vy;e~<{quEMcJT;px;QMe#AClxz|-Q}uwLYkeL8rS z*3$^IphTE8@KY<0{GksWn}VQQ*D}t6kv%0-waiAX691wd#oIk*Iseh9M>$FgD0}Dj zL%`Af2tyNW00Q7-ql+W0JO&Tc_YAfJZtJi_RCUp@{DtR+E%80XO-;JP3g*6EabdJ)6It>e!}#r9~D(n>P*1IZW8F+g{#$t2Y!r#mJj6RnPBNkg3Ky zw3BnVL@P>MUo^E4HqJ7)`S*2he!R|ud(RgYdEl>?OX72OF-(Pd6tAAqua=sr@Vh!g zMflj|``^4VL|hyNhTqBjwE)wcqaCEIyy ztjXGBCa5S90$v*5AC!F4V)KAu1L0cC&%&Sk8YYz*L=cd;Rzls5xfy1I*#^^99`Xm- z+bV#y*8tl@`k3sM5!XReLpSK<(v`5Vd5V8dR&cyND z#d6gBtQiR*Bp#g&634{LD}v$zFo$p783B*Jgut;! z|3lA0(5w@nC;feXcfkGNg6lY5lgC_N1Wx@H=$A6+|6uPsz?w?8wLzMqfJpBODAK!h z5a}QwHFQKodhZ}aRC*H-krs+bCqbo!svy001VZl}LT~;%&N!oU=G=47-1Fc2-}A#f zV?we?_V?|zzi+*3z3W|VtnvGPKr-I~ygUg83^DJB8%Ie|2S+|QQ+nh-L77(sVH2ph-+#HZR&YBX8kPV1VZ zMeu&YaL)>n@&|p3hf&k&iU-fH@OB7dRD*@#GKQl3EUF2FA8xp2@mq=jC{90~)_dAV zaKy^kDuU-~(m5UUCnt^5VMjBA0!BGgtQgsgL?53}IF^h0?F9lrDGB* zQFEaMiS>L?=3vjPt5CMnF2E5bqQTcIT_b5cmI`ju*1$R${*WfLpmP3HwgUHUzUAhU z+ZBC9x3yVg3YI!xR}%|{<{L8Ug{780;$vZPC61rz(=6puvKFax2oOT;?%8=WdX`^O z?j-L3YpOaLOl#w$^b4Q9VBthi9hYxvDWmVh!HbhV6vav(d(w`m&4KGX>V5OHdfea3 z&>Jp;9g};w5Xs4vo1BxH9+H=9a1yBkFKY>i;AyQ$vvH6h@-u47NKZ6Rh($!rmM7(i zzjbGmb8))6l11LcCZAiRW=z7=He%lM*6cpCb#i>H=1>kr8l?I$c{f^dGAAL?oHX=T zX?TDdjB*F^Bk04Z-l%@tNDp4cg(dV(Iu$`^E}S5+Q2xc(-4jRf zd{qK6pkj3bg3W$i&mhf!^YhJfV|oS{Z9(_Kz%KI=`+JLq{XYuuBsCl3amLMQUpPGg z8{(q~@vp>s|2Z7W`ZL|`!yl-B$PbZE#j_q6Zpx$bAWyu0M^k@Yuo}DbCuKUPYEmEo z9tt=H-{hqiO~3&TQ{t#lO6o_z{J*jNm^Z+%$dk$A#h3dMgN>&fr!MF_SdX(dD^3-C zfoz`*cw5?a$LtR;1PJU~_l@IiQUgI9Ba?5;W)kopo5m!+xoT$$NOC?*LWd}OG5J*! zSd|aUxkE)3wPhmLAiTSQXko`$Yv%;+CqdAMC9$jhHckUG_q|m}h56I3aPYsqE1(;*#5sUpLvz#5}uk zoBA>iBdX_FHmc{@GxNy$Tn-B0o3HF&i@r)+){%0c=xQ8;?QMz^vQU2Y-3goU=uSzgqb&d-Ee1+g)mJ)t`snAPvT?8a=dObKn z?s@2N_K|OHg|j8m>Un~_aZFB{LRA$sAy{%HXd2JHUL&r5-Ea~Q&K@%pAg;poCI>xe z)M;o**|`sdWgxv||85W$rJPLSmyODNYL^C9%H-`kEsR=}BK73QA75*cz{G2EDhO|+ zz@eCWZ;_d(vY>1%>%+gXH`S0lrEsmVU(IDzPgZBzAclBFZX@Y&M$)8e?h~Da?UwyV z6)?2Niv7p*Iab_08BZHmqHiHGkS`x!sYFJ|F@+@!`cRuIrg*k$5Dd^cE?0l+v=)^Z ze!-;26+QY6)0)|V19A4G5XPL^Xf!!Q6sKOC5+$Bjl?k)jyj*Yk@*E(Z=M>%@aDI>C z6WXS+riVa70%vH!<93c|+mE9_8vxRWXnxVDNic?}s1D&7?bV&+;*-1)V|^)SyL|+4 z6Acv(!_^H>w1HTzja+zI8esv8U61&+^s3f|8cc`l6&taiL|+}@)qyJ=qQT==vG=a$ z)pO>r*>FIH567&nUpDM#-id%yP~b#;!md1g`BoWdL-l-GEC?|2w!&Y8C6M2ATfqzc zzv@be13r3Tcqt(4`dwEdz&d4%vJa?=0JKyF;e$W@+GiqQ9vJim#gttYy7N7Vg7W+& zP#(p#*WhIYc>4zQQMvt(nY=mcnRnysL_6ZoE*{?mwq1kF$?q`}JqTUDu|oBg<93Y4 z?^DI(V0~anah!~yaCFvn!L{Hd&EBvQu?XfXIz943Jc5@pT(0&mcT{ptVfPy7Bd!eC z_-67hiB-u>iJjmA2Mx%_8A}CC7?!_Fr@6y^YfvrdZJ%Nz zIliFTAthYKrG-VY-H<$UYAu2m`N=u3J-N^BIpjK)JPn+pb5WN}oVwq(|4RWyzX~rE zcBj~F>eJZ8ZBVjk?(>pz5r)V-njO)ekvhu!SzbGOSqcP$UxY|R&08DGu}k#Q-bems zcrnzskLymRft~$Jw+ck}4rMV|O_fGlkNrH`>ecs8WUQ~$ldrr1_>Wm*fOEQiMM0N3 zyfhEDAbYGXYgobAK|UNuvYDxn*mz9J#0ojerK#4-z~+Nf<=GkJ3U{>*!)vw*+yhEO zG}Zw!-3?wcYCdq~t}xfWyPlB@jpF9|dS-cCK-7mw$u5GA_qD8U!eU=0CB`5iOA07` zU*=1@dU}d_N6vGX)-MD3NBpBxIKQVB5QYZN;P?Pq&Iy&dm6xWcBh!}6#xFeu)_&^k zpRV~oh4z?!(7*z8#B?8QIDQ^p8{n&(qPh6RX#OY0$saVw48SP0Dwl5pd;ftl%|8;E z3qXm?MJXnEA&u&OR9}E^>e^VSyAqe&N(+zG&E|(U0L#w|t>2>2Pu$A74fEsBl5r3! zeQpODG66p$kU5|eG2VZ_WAbfJ`<;dF)&2|TJP}}J{t{!EH>bn&QE97~ic$pT5p4&U z%~ZTd{AKk;0qWURgVQaEC9$t)DO=l#mQ!%Q8-8`4r&;Q%Ja+6oK>DXGWft?z#V^B$ znuoxZI6#(s8I%_{T@|zIN>_0J6zp&o8EgRMYV;vCcJJvcZ79eeatUF2_2w-^lF$A3 zNGj+UaTUV*_=eIyM7Bjex+FUxI4tM9#_ATb&C;uNf08}ES;a2tWt! zjy!9>8{lC!zyMjrlmilphAN&AWX=1Vd*HwWnv`J@Z_ed8&-$9@I|@n{7(y<&N8fe^ zDp}`>w}F~5`u9~Q4PVi8rpPHe_af~-f=pzrRoUw5T)`Sb(^jnoLG0}JQTkn1WPtk% zM&7(5>YB5VUeN>GUb8;Y<^i9(tq zP{Oia{$WA)E9YXeplO`vbkj@LfmGoUw72w{7VAus&E4M_ov!IGH`|$CZNAq0@gvQX zBM{&j0+ZBOiTw6@e6*=QO0z7OwkQ(YN%VLClFaVot)0~UV1j%_qu^5bR!= zcBElTMx*_nlOPAU4mLMP-rY31;mcP2NwWnRoM|DQM~MC z9C;A9>m$C7N(20SK;*u{cH}n>gX$^CoC}Z!(0_jn2GRhFd*H#Q-FuQ~&t7=<3`$H? z$*m^b^Xpu0(jC>6 z8cj27!q{PtVEmiDDZ4a=2qp40-kXdrj7ecfU^OEZ+dGTeMq%4Lhvnrqb%0Y>D<<`S zwS<5~0SzU`$cmDd1>lW4l@s8AUmf zqA_~P#J@P}BKFqEy$H-3N&||f&U4dC4~g(pKmYKRMu}kG2WoS)O9_X!R^d}G?Tf3# zYr!{`CK$^wv$bt}?*RFjTGb3NDklTpfQi&pygFRdk8k3UcXHsuNu~1#!OnSBshBs# zR)gofHd`mn+JI{c`$yL_Wz`E2H+^dQ*GvFFrLVl=;&+*WWDZreI(O1^_Ab9$WnJyC zU+<1bXp{N{{AOWOoHU_rL80w-FCVWmYS4fsQmp$^aN3HwSs^IVqv2EjPVjYHOaYv> zJNTZboP#N!K^X&dq1#AshEN30-sR*4;!7!N2hM(@xV*Tu!=WY1n&gVr9qHAbQtcK1 z(N+W1Kk8eokbTj;XK}FGpjJimxCLK;)4{iR$&Jj>ShwC8{e}>>tnB$6oK4uZ0$|B? zS`;7`@&-v%Mjl3j=MPMKS~nrJ1~L1_6OL0GWBtx@E?%xAcyHST9M}eW*rK?aOf*0} zHxkN4wF}-bB;1@2Yvi+JwCy)|NLLHDmHSlKq%?HbL{Opv9Pc~=d9wp8327;zO*XR% zne8nQR@pF59QDx93VtI0=C!HFn-^#R?$scwJ**IZWEL3qjFsbm zS4Od1!QaFq@s6SP1%|iN!LRBLJk8n9R zNCQLSFVU=k(yD60xLs!#ZqlqC+(8)()NDhX@b%-VEcg?H)4X_%JIClLlugy z2qIW#O(i2#gdtuoodcECmPVc#uQbYmfTCkqw3Bs9JGbKHT{W+&6N!oBeM;({-N=V1 z2n`W)_`5-Ym0yDbWj_W5s3juUpiox}$k?#wz1!$0^+b5t6nuT9K<)<_Vv^Z`Rt9Z5 zJN@Lo&7Le0;u6usBUWH3|5b@rpHlAm8Z@M8@fN`O>u)-AI|j3CQ!;C`Sjvnkzw4w^ zSM1jQWg@PY-j2Fjo1J5kUL_60OE0?j`#Xq>R}fh_>N8{Bju)j4uyZ`XI$kaua3wFQ zAS-)m$MF2NgKh=xR%`qWJb5m;wPl{I5e{Rz1$~31UC)&cSI1~oxx9xzv`h*h_eOsc>s>nt z0!iE*gblK9V)F?bR{G2CBwC0+$?4ppi!}{uWl&5^c-2tk*pbnvH#z}8rOikbNAoWg z75+CD62caSWz!1A* J{D{{3E5Xi15AoW(SZ2avp71uqtT&iG=`e&DdVH!}RKfis z$$UN)MlVZ#-A{~o!glH7ghTHemPmx0!ICT$#Bw4f@Nhh2-fy)aqqJA|iT5JtJQ?Ga z)M-yF`P#jYjIOZM#}_xor8i<4UC)V)W#>v(B<9YbjSxG$dA2n4K!DINwvt=_6E*2( z#=VBLD}48IbFtCTT+rO0RE-kEe5w~SUP#hg>7{dNSYp1u(2Q60@tXXn15cajC-`z{ zxO@v7mUfOU8A}xmaEL|0M6`AAm7AvO>kNfjS{RJcUT7kN5gy2Cnd0}#W|RC!G99>7 zi)4F_5Fr_f$2&6<4+$#qJSD=89SioWx~x8=4>ybeZ9Ck61iCqOwAa?>X(n^P2t+rW2As3G-(-WLYppMuf`Pz59`@XdvDm;6s+^pKwrbL^kNJ?A5F&GL(;aAVO{!B+^3J}pF^0Iwt(lMkGv~NW z2nXZp1gKdU8=m9gt){<9xw>li^yO&kWz8MT%aSzs=a)I0^}3*S3tg&tL(13W z{VVSxnH*?{ePkyasaKmw=KA+;UVC!&DZy31-48Op=mI0kD0Rb4@P!qJG*kMAm@~PV zp*bmZwh7HMxuInjJX=;>V}USabjJPCNbGxh+e($xLqvb0;FGdZd(V{-Ad#LxAv}$@ zS?3IF{2q0NnzO>Qdse_gHz! zNe`^K#EEM&`Ui0mxrk490m|a}xh#|FnzQXVkM2%VKy}#()Oz?^*_E{(ykMMhzPN!n z^x9rM)GqL7x3(O++Gj&@g?aCD6~hZV@&@iDoI?JU?)HIx(>-w1Tz~6fuX(nbfmls= zre=M8lHI{l#68mm$-J)pp0rVPSifz!GKNc?tC&9uvcWA>nPXt((5xrXs83-8ZMQp zrmUrQ^dI6QSYnAmkwPs_PB9f?v71i);`Mq0fo<(nQYI&1)*$?&b1ZEEBmcc1n4F)LjzzS$ByF5SJ_f7jvD09aFvu(xD1U zev_U=z5BbcBxg}J@2*17ehqK8B!4 z{9SBr9}u8?UmR{Uul-7(eCXUcl6&_r?VQH8hBbB<$li-sKW^U`Qhd3@tYiwbu7X|K zAyE7&;t{uSxHA@WDM~+Im8xV#K4W5g8q?cX3rNQNri%E>tZ*H~lBPg2QPtq9C$tD@wey8IYak*QU%<3?%r0vIGRe%5XfuW%!-&h#yxgygu}h zG*0<6Y<25{03oeUIZ>p^;6T;6N~`in{QcA+$zAK6XdO-8)flX}6Nr4%sd4L2>*mtM zFIrwM=Ed}{ zD*gf&_TcjmHhS~;Z|?plECmV_0^gNL8Ff05%~>G+R;B!1C3F%{3H?dGj&ArhSfWh) zj{>A9X*&>nQ#+5>?7F-xcL^W)Au>i`uIS409^QSH>c(_cKJCqTrYHu<{^Tcg@laEp z&Bt8F30C>*5f5$rjeLY5U(vGkrfwuC%M7`tm$9y|MD&$Ksb!VBLEsG2cSvaR?~#aJW>73RsXs$EMloEU#CS{GPT2GL23E4FiAGPDP!%4>d0vAN?DuFP^k~g{~Q+@#FH# zl)M#KGMKNJ&Ag);^fvq(|T!?Boqc2CuId=pl}?qxMZNUyWLfRwW5E zpa>A=?|FVpc;mIX;?^r5_6*1{0c*~;%Sc5_Qe!Bg6F&1XL~>S0qV@vykrU2j!uHZ; z!h-E%{p?S6QG}Qk*aA%%?+#zcczy8|=A)m~unWZIyvS0ZNCgSi@#X9t*prV|!7Q0* z$v6ZUO07HR;5EOCub~!!dt>|1)3qLbHzgNc;yhxFB-$zkX!(~2+P4B^K5Ff%IC@0t zLWe*dpvNvA?B`n4O0R(_ef{SU#NRV4MZmDuojdC%v$U<$R=lvBB>>?LlyP$c8bUzU z>*w23KzCqm$Aoh9rB3gQsHfOIDhhL@^^f?h)L$}G@T5Jc8a*=ty|E?}mTDQOy>$hR!q7=@!Q;~!Sh(*8HM%9%$lFkj#=$vO!Tc9tZSE4H{x}h0o#0(-)}K~*ZPGMv@idoSg!lmg z?ME;4MXk`boQ@cOv7y3!<_0T`FC>6Vz0rB^yGza5d(8USzDP%R#O2-T>fsKJ@Q&ao z7+2n~N{;he0}5N6$90#&TS_zKirpq0jX^n{HhV(vG{uV8O6Br@*3?5SUq5QQS z5{5iJoo)w&A$dRRfp_il*jTs;4**Jy%kHrdo&O`PDwR_o#Z@SW{Mcqx8xw)d9{GBC~>4Ht^_GF1p)Z zuB-a5#`xiet1;^U&9E-s^Om`FqOPTutXQDAY^I?5elEuN&nt|y8 zTE4B6ai4u`k8X=J!Zgel<%Gz+;}5^v^BhzG+nKYzgr#erhFGRtg}jb{PANSR^j8(y z+g={ufnwO3(VDw#98Sy2s2yXE!?ZEji@LDR{EeNR6vJ+ zo5pEcKI&=YEkyOUOd;Bho3)%55$? z)gf8YXe&f6+iP^Z!Y%SLSh)$+Q>D~-enZ6j)nf^R4T$@MI`Pa>NRmKYW$FGc7E_Lh2mX*)kr_QC@jod!fYFP5xP0P+>?0N2AGn-^; z1q}FD*$jluaI3tS1Zi$aT|L|frnjyL=2&|Oabsr;FH2e6&bCOe)E;p+&xnl$HX7mp zk}r>ij|RV@$)mJAr>)jva!@j8=_4i&-pV_ElR?8>tku%AmUa3}u|wOl~l0#zXUtAzEpw|-vOybfyvMAFN+h+rQ1>1acz`>933n zmL$sHxmFv zPFTPUa%=k&c9mC1PA^a!fzRtW)V#45=n{!PoajSZOC>+DPE6rG69wjSFV z=UMrY^80?9Kn;-YxcOwKh;na7t$9al978lu!E=LYzvhua%mPoqQ%FG(_ZV_7-_r&h z8BNlEG{tU4So?tm9(N>ZDpRa`BQSJ3+SFuMV(26os7o0loFqTO(nYUrV$_|oeD?Ag zkM&RyZ*~rv74y3EVw72eS&+ujn7;3Y^7YEicFaiP?m`!r?~SepWvK7wES95snYxP) zSAQs}DfYHr_OP=}p_+=vcyoW|AuYjGy4uRxqe-mTtl(`Q{W|rN1QF{YbI5@EBg3E; z)mKC8DK9rZKdZ=YAfTBr4%;h#_@K9*usugV3=Aoq77cdDw5x=~LlerI?(r?jzVAYpJEAVlY2-H|7&St@c(op%U` zO=#cn`yob%{x{D_#KM!?PkQuqM%ta74!#iIG#tTTLUZ!K?7}1gy0hY0RWtRe_sSQ0 zD%^j6S8^*2N3_CpmK7hE=5^WWqD+I%f?V@+|YSxKJ=$}Y9JyRx zsg(#0nPX{)c~ZX<&1N83aB6zd(4)8%mEFn)=Jx6GtLsDz> znQLOg)L~)z372OhT10U(2pdv(6p`Q&P!zSLOftAXGIB?9QgB_f6ejgHd2HsA3_?DQVEi zBe(P9d~!;(bb6yye`N5qwKwDTrV_oqr{97W4;Ocb3hxV9In~t4>yye1wXfK1-{^Zj zQqI&(T8mc4=4nId6F)E%krdvF$ZsIKY zVg%u8+_M9Ju^X2<91QAK7D3-W{u9@uk8rpW)=-1Z*REOI7%;}pu`_BHp>6-+vd)?y z+I!d2%d*61Z-nwWFM2Gici)oY7I-BU$s&930{k9PofP@jYdx@1E^~Qfy)cOat@X~a ze2v=TnqWGrXr2Cz0FKM!z$rWe^^M(h8`ng+;&NO%>&sHi3sUDKY2TPC~`L<<}NZMpN zH1w@4g-PqqJpY&c5!uUGhDr(+oyu9EoquLQdK{?S|Zrxq`HmvFxms{ ziuLGYf(?~1V*M1dCXVa(^Gn=%UWDK}G1{EdA&PuC&D!q=Q{IU4cS*t3;fIFX&K@M* z?WrJ**2X}4cie%E#yD{)p(b~!#7mRbcDt2a!qw}84^U5ibE3!nRsM8Nki9J-e|U09 ziq)%uTN@+#H{4`!F=d*YKT_i>NQ6JwfA-DQc?q~WjeBEg0OS^nsTRam9^K->4zJ3vw$;XqH@`nrW2lDeiI6C2<1d< zQL6Zv09p69BHZ8hxAb_vcXd<>y!nnWZPONXYZ$6}h0zG!toHlI z{^;3)^Up3g|H|Vr2TC>cs+T<}2moCowXt#~JTHpEIEH55#PXc4XavZ1ry}j5WkpDX za~8!Kz?iDHJdCZ6K?~DkLsHR75GXDdt!j_$;byhQ9fglCE(6^G8oXSEm}Kt_ruhOs zb=aS6eCOVllqC$fBn*Q>G=)I2ERu&IKwEjBko&RH!wdFPSZq~Teb$}F7vzkykMnx- z*HWuW6;^LVSQwZYQe7QvbInOz9uDw#o?9He^dtqs+al2iHY!%U?e!Hc{1gy8U<&}j zOZ9E#bG~@vPS)K=GXRqx-2SxskwFLsIdAS4CH7u>4eL7D+(&}OrbdQ{ zqzR&Dk*T8W(0?bA-I3nApttIYnqt!Zb9Rw@)Ml82n;79K)j82fv78 z0)>@yK(7{jARXMTWI*o4lAqs^+fPjzC^2ClK*=q%j>T4eL&wT^c{Y`DFTuqOR9A3BblMEXs_gb7r&xe?4G*L4bN5i zn>EOk7u#o9`q)O$H zW{2a`xtRAXfL4WU_oaw;=HGmOiCZvp1 zKtNtTNG~7;LTd80f8t>Me5OAu-kiPnKQkYA;UWeJqnKsb7RbJo*S4FqWR#5j(7fC~R|4P>!3qOF#3=Vw3e(ptpf6PT{31b zWfYxiObu$4WppZl7;hKk??`U)BYtm7(AR~Ld$F~QZITC}7pHH3kMnL0quW3$u4$drI!7MStcLrtBks%5Q(Z&iy-Z-m=L44zJ4m9bu` z&N$B6T-_h2@|?98vKqYg+HmZl)IMF|u2@a1n?}7$e7mTGPI4@XVxxLV)fCg`lu_UK84rnlK6B;T1d?To+Znk#ya_>Jd z1h$BA(HH4|^fR-U5(+ZoX_s?uKM>b6(ye}WxDBP#-4vT$u5@d&UH$NcEYM^PTsK(d z^uY1u#-WmLqYO3Q(rT%5-hil_Sr)d8iK7F)Xpxxkju?(gvQM6We{pELR^YI~`A;nk z&v5Yp|M&33S~z5bTkx`L;ZBsDp{=YkZ*x?@gMt*tWLq<*TCL;8hQ`g+YwoducOf`kE;J%qRvYhx zGM2oHii{(VG5X*5h4DjR8+2^Z%GaVnmNexbsAKkf0bQTW^*oW???X9z{C^|S^{==N z>^kF}4-foLjTT08j#)E%i(hjz$({7oAWT0UQcf=v_x${zr$PjrH#AX zf*?JQD!m-}QilW1v<_ush%H-xbo;BhX=>Y9;v<0e8n|enos~OgrJ4wje@XqEnq_A} zg0{-Kg6MV6o3%!5r|wT3nJ{UW!@2020j3(Lmkv7r=izTO)t- z&;Cu;#k2AJx04CZe2cSt=gbDqY~aiW&TQb!2F`5Y%m&VE;QxjV(0%=aYi9S**~Q7s z#1{3;{(%)PA1^K!F6xP>C@!al>jM{02Qyqw1zQty)PtRinUjOPjfsmHu8Wf^@CwNC zp^F7BHy0NdE~mVirMZO*F83`VTuv!_8+#`W2NP4^7-=&%OH(s-xjVR=cPw3;@0mGC z+1onU+nL$9;0gj?qHN-94SWO-@Wtx5oU)cSz?T4jC8a5CW@`Ts_<*}+cIMx{1$A!f zxZ>itzn=5gp|2BYmlb6dWYN&ip`oDz|Dk;yL6btm#>B$L!oN9oXgL@Q80hq9=Lpd;2+_ZOMxz7H6AS&@574ys+YkCV z4B&im&R@X22pmv+8SNZ82F5u|3@j{6OyFoA;C(bqLaZy-xFxZP)J$;b9f^7TqSDSY z+2)iZr) z_Q>4A(#qMz)y>_*)9cxD|A4@t;E?E;m$9$nUdJb-zsY!;nU$TB`~E{waY<xfK=kYsFd?zaH-KPr-yy_by51hJjNg4Sj zuWzD;_HAVUxq&_XOC$SjV84%R80``UI&kqY2+<_a4%E*AV*mdH#Llku*|k2ifioL8 zvw<@kIJ1E>8~8W10fIk8fqZ`f1>OR%<986q%YExFfk4#v{sR!m|Gy6eqKlq;b_N2! zf!i|>_;1Z3_%~f7&#u}3Od-eFyr0ecnGKxTz?lu4*}$0%oY}yc4gBkE;Lig9D3txr zrh$cc0nq%#sC0D_E`YnEC6s8MLi1;oY}yc4V>A)nGKxTz?lu4 z*}$0%oY}yc4V>A)nGKxTz?lu4*}$0%{EZF#d4LubDE(&xv_IGB@bmm7ik9F%j?fDI z?~Bl)y*{hc`L+J&jG}dxX8PBsna<|)Y);Q?;LHZjY~aiW&TQb!2L6f-{5cXr@j?D; zNQe(mFZkyb3%I%dArj)|{$GfM!p@M;84@~+!~9Jg=C7<+XMc0H)1BGCnGKxTz?lu4 z*}%Wn2L2okpwbh64-Nc@S{V<|pBAdiIhlC=xm?+IF*5Ey=#|N$RMCD_Ec;!d%p+-a zIM|6;j`X(L#|JM&pWH6dVdOji`9UGN_1xF5wp+Gv%~A)DyIH9G+6g+POp*sD?{WtX z{eSS29)Y-?E{0>Do^msc=!AN8WM7PaY2Y-UA#tHGJJIDi(aC*w)sq`DHJ7`MDuNHA zTL@LQ8Hyqf@3ak)OI%H_Drq>p(v{URh+Cs7ZK$8;!@bCk9N4%0+8ONIptl@)+gNMt z%2zVN)pw*vx!cL{WU~buIMPLNdaFc>BT}%}=Ck!4r-KB++1^BJl445Zq=(Nv3o3Vf zq@|Ost5o0G6I3C-70 z+*LX{OoY^iUyB1De?ANk5}s|Mu28qnxVUY5Yp+>w&~gp<6H0=y8X~p%bCr7Ti})v! z7(SWKRP*dezBVa1WgPfxpSP?u4Xz2)FuP)#uZOA2G_{j@{?Nhb^)eS|HbfNZt0GO) zIuTy3SQmRJKX9?r z_vTWq6SZB*91R*-7h*_PAwfPce4DjuDC>25u)Rt=t<%ZKcGc?^XwLFA)9h2xCn-c~ zgHJ2eReQ`bSY3qP#I^fGARpQ86;wTd1i)DZreH-E>6fA}@UC0~J^~AIYtJ+EXi0Fe zKBD&bEy?6|tRl&hTH!}$ST&6)(*7(M71!KDZMZ*BY z>`)h>OjZ{kBYlA`0mBg_dlFZvqg`8X2ehTG>@-0@x-SO}HlS6$x`u676DK5`Z#Q*I zZ>`{cp4 zUM?_0li@xN{n9IIvL$<$46=m+Y33V5Y1*{l*S^*rNlRZOegD)enxoOhzgoB}5#x}0-qY5;!SfMq(=^EE zMmknrCF{haa1BhTmX%bl zxYsY1(q-m2-ry;kN7L8gV+(8aEL+bCUiu#4xYd2B^*~^7{9D~kQgL9(}PuR zv*vVXk81BRVHVk%oSZXl7gNWyZaZ{x#bYeY*%bJ+i|LLR7y56PVt5&R)*`U35R6=Q zJDTH9BAeT_h6Y8f#amvrwwL1GLl14Z+cY zTr^0kOUgFKHpD4U?mD4Y;T+vDvBjC6buOD8h8xwz79$$qUm;(wbU!6o;OT0evLkb> zKiFI$Fs@l1g^rloc1}~zO8evjR$5vw#xY9fe!B8#{wCAXE2*01gAW`Xoci;gN#nb8 z&n)^{ZrdjrbF3V1e)g=kgAMMEoa$c=*Q+WUB(N?djSdP+==QB2c3zdt@zi4Km__94Q|I(Cl6m9z$h=^r+(i*ELbx+j zq${}G#js$t7&&N|lrpw5n#z!Du?dAO7yE=Gd+|GrbCB+9qojN1XcnbI-3+-0ixJ5+ z8uPGM4!$pvC7{X1H|E#-J^8yZgbCTl#`^_9^XoCl*5QNcmE-$N^Gr1<;@+EE$Nm07 zofs#11TLvWu1ofG^TtR)fq61mhjF!s+u@M{*?fOp0)dM^zH9m{um1Ssq`^+BRuKue z`CUl^L0D>q3%y>oJqhyg;J80tL}KjAL6peXRZa27I~Vud>BMJ<%BAFl)#9qLkb=qJ z4ePQ{=Yy4&4zNAbdSKJ})qPy`Z)xtfdrI;<2HsmI7chtcPRx1Se2u4*?mSvKB53Dc@1>9rp z`hv5ij5XnQtK1zs9=@kMkj`o3ZgNs>L+n>8^+spS*i*VNyimue{&Gw!m;0Qf z@E)jm(e)^xW7?#hVBzrXU9m1A7hQx_xdv0Z9kY`fhZ3;FdM34kc=}zBWIL^9P1-r8 zv~kKM<9N#z$(9}@2MO;OMTI&EC7RF0IF1?AwLAEB2}|UpjmQJIcxcv6&dSjMAIqY1|@Us5BJ6}WGatvN#ht$79re`53VAe5pHu$5NKN-ajOY9 zlAC7@tsvOGWT@>P3(2l}E4;MR1d(;9}@E%!hugJHyCaJh-Vnsgrn?~P$d>nDRi zI4`HZceQ`wY$fGF30!1*nn~3)BhTd>bf_v0xGW^l`3Ix1Sth63&5kBe{iD(1Do3MR z?$3bF0_>=cV>mclzcZ}t8fez5*RBmYAl~cAwq}-!1Nb| zZHf}pn!W&MCYTGvLke|dVIXdJF(7iNfXk8Wq%l+AWLDiS=sv%kRDVo1r04WwvF#Ujef{MoOzC3VU13yP_o|7I z@8MK{t1MG~53qwiF`_-|w}2a6_xKf(r^|YQ!!Vm_cO^N~AXQnnJf|UYB;UF3MI7s7QO?oT0h|RMOY-q?349mG=0|0H z{t1A|{pU2h{{+DNMeX`GLF~T^FcG0{{S72fOoEgjVASI=X1XeyCVa+=GaCOoDSUK` zOo54d(SmXC`0K^1l>?=_5TW;-F-+3`(T}0GqBK>2T{?_8h{)&q>hohNdcAk;Ii?8< zl6_IawZXKSG#PZad}XB*U55@6S~iupFDIl|lOma?=cS8uH3mR_;+nqX`@Hl^T_$UJ z3ih!CVe2PfnGO#rb?o1!G*oH>2=i6Yt-N~iYbOuqununTyGs{IVyyZf26qLv@ort) z007MJ3-3%{p6w6-U0&|&bWo5sq|I%DMaX6fVB<)%!?<)bD5)Z zp5&2o9+J|@d7ngF>6UKzT-&w;UJgIPsANoE(;5r+f-Gj;rsD?9Qg~vY;?NIgv{j`; zdvLl`O`#tQViO%tvW&G13AQPRl3 zTCV|snE5$WH_TT9g32G(8dTg9Sj0szmD{ZW>({Jk$*zl_z?`LdTsYz-tD|Q`&1JR0 z(TaPZMN98N1ON;O0i5GJ=r%q)n~4ArEac@yCnn$JDu(pfi-r+~IZTT@Jv4`*Wbfde z`lF501_bk;03-*3e%>)T1#my^<{d;vt9)mdk8_GpS5^ZYQN-#@F{c2(>OLtU16=F} z@vp2wOA5fY;|DEW)4=dK9fmY|nm``GZ-ST~)OA#QNW6sK%=>#tmKR4$ckk0S ze3b;E7|SFs6boK=_UfzLe0`IU`#J^H0mu9f|lY@H+P5%>$cnQcz?sknxossZ^tWNRPy;I%GVq=dpN0!zw5cZ?vMit1o z)465(d9t`TM}*y=!~bFLy`!4i+J4ckZgmR^8<8%cAfl8|q)4~WBp{-aKuCfJNbd=~ zs7MV(Pzb$*Dv%%$AcUfHr6-{m=_Mc_T|nS0_WOPBx%b@fyzd=njC;p9W9<9|nQP7R ztoh9O{GQ)0YtlW_2HaJW8QuKi16U10QpmcSzpF^BxZkP7+^%*0R8x~MU*oNp>syrD zwd{F3h3a;?UoWgd?nb|IJiFPlyV0&7(Wa{qy|lgP37S^ERhhTT%z}r<^YxT%q)!PD zcAq)O&RaU@cQJ0%2Jopzmoa#|fX;3*bxo7UiYI@e9FO6k$}Iq|FTX*8rZyY=gyx~; zZdJjB{q8*ThssAnI5q&hWni1fKvUrxg|l7hdbcZ=w%r}NJ9Z8e|5T|HS=gH5<5&@# zZ>^i~|FXL^2t2I6@MLte-@-34W}~{_i*MfNNHUbq&N4&3YZc;tXgi^7S~+01|4oe% z*9t2m1r(^=k?1nTPyO0jRR~cq=q_mXqwX#lrZOW#0AP5X7v*n)bZ%9k9C_Rp_Wm`Q zvanW(cUzeZ-L@|CxvdHn94?Ghg-)w*;dn08@2{5p@cE#w zR9U~8v+i*{luw=KAobj#<0Xot!>&ixPo=l&KE&DaS2L@Mtjo~6PfHy;GJmQhkp1*e zjaRF0zFa)01^Y$|P`+&)@oCg?Bw8O;>>@4v3|c^=TQ40kK6jYu*21vrw_Ceq;>!)n z{q72B`)D73y*bZ$mq~wQ?N1T@qFs#?@fEfGIYN(ycgaI_yWASp(;J-g91oaWE`jOF;K+yJT?!uobvnkZ;^|b_sZL7g(5I zkRN*hVZQrcVLq`8+A)3^J_2kpZ-9sv2%VQWfQ?!gobmyH0K$psGrk{5!t+dHnq-3j zCm>YS57&J=0>G3&mldbRP8U_9(d1Wz9-1~b(XlsY=Qt*R@nw80*No7S?4pVs`Ly6m zB<*L5?crhH|qopp&K~^8uy8+bjTvfI!+F2Daj$3yRI@|P< z;o@^d39G>;$6nGE4gjZE&G`?h`lLX+{4)0EKs~b&$D;(Q8LMTb>?KN;Z1)4PJ;=(< z%F(1ca-*uN@Reh7^<~NhZ*Bd5bjJTC(GG}eBdhqk(01w770|AYxin$&%8G!BfydIo zBmlQ&zVwppzvD(7hXcLrm&;P3&yx8lY{13@fE~;+7D?T|8!0@K3t$cD9lYQ6ZT@2{ z`2Y`CuiI)L;%e(umZsDVG{LlP}8_GRAK;-{K{yT*Y zSgpi$zusKO)g0ps{rdwkj?^Hs5ikvURZj2Ri%E9nofDRlNcIVKq%xw*k0FFz*Rog8 z=zHeGZdd=`$Y72|+uC&?XlHQ129L(2cliD`mb0%5U9>$YJ)^Jg*ER^cT-EIgtxKs= zD$Wc+<*W?y0D*r_MCW%ullROC-R{WyPT8VcpTE5n#(~%za#x2Q?LMC~Ul19~uigp< zFr(-@pZJ)<^(TpyJ$huouyH^nk0dEce9_x|&~a;1ll(+tFwDB}-uTd)^_&hy6qc{I zY>WEsC;-Lz`s6$ZojbP!>J zY1XY3y?XyD+%>g4I(26$@Adx^0LUK8w~k}|TT(Lr9RM5?KmWe~fdAoT$bSQXzX8DC z0N`%`@HYVX8vy(b0R9F5>wg1)zX8DC0O0>q0PvruVgBC$z`r^i<-d}c0Y4P^FMeL~ z|AGPd$5AK$o&gvRd;IhP`%5#MlCz_ncOS86j+!*HzxZ}EcD*~c9Z@g~v4n8Zk3PMG z3e?>eE?fVm@~`+1T<$Uph5f!fW%W{1nEyNXH@}z#3I_X+)Bo7~t7t%A_E`39-SAI8 zfniu7e&A`86V5bKVX-`+n`>MchnKagW!NlP@B7UEr$HdZt@yfMzORMv008#Ji(|Gj zH#-xsZ6QrZ*MKXVR0aLa=eTyYe|`$JW{f0SE%Myszoq}B^)qv5zK!`x*j`Hy<6O?V zuCia_RTf!?-&G$=jU0gs8+Dpr94_=%1xB{ViIQ^G^Fbu{F!tGC?*Y>njpK>(CzTFu{|Q|!<DN-RcD&KD8nFmnQ9KFLCR2DEI7_^T+9Vvuqv|Vr+nO;hoD$heJZ) zx;0#FT8Kqf+#9-d9Z2-tH?_)OZmDuuR_zR`5`|8$pDrc>t;U{nTLx0{qNwx*auxOz zKvs~-LmI23n3bySzvsfk+C6Hn<8rG!G6~eiZ9;YRy-LBoXBiTR5kEU@vbz;uu-7Bd zm(-s@9uL&xFl0bF#|&h!l{U4ar{?PHUDBi6R8q9&%mB(kh`_(WFJKF~B+;u!j@bNt zOnxWml%HeDh+efc){GCJ5jYO7yKAVkfr;?rTF}{ys9JP7&qDjawoKQp4as(ms*9D( z0Og^lGZW07}G~c*uEPqM6yKp~-c{2yIZ;`^B z!sC`W8w{_T^80>@#ZZj$9|V9zs{Wb1=ksnIjQ|1gU`I>9_R+;vpke*Y;#J?83J&0V zW`F?jZ5I}>eQ(FHc=x#9ex)P{l>d0@k~o}i{xr}nwkqE*Rsy!i1b|tt)q>Yw1i+^N ztN9JP>S#AJKKE_O4xo}&An7eu$(P@*jU78!fBqtanSG}`W#qQLNwp+1z%sRa_+G`m zNJPhg1P_)ChesVUoW76pe;phq0Lf{r8K`YmsqUgdSqE7g8^f2mbSy$rjWceo;_voz z(9hdY?nD6nkOw;Y1fXhgd92=5Bn}Q2JB4Vw>PWZaJh=uT-BnLrFT6hx6~x!IV3V;l zRTs8#Km{aX-ikYT73t^}K;#&R+d2s(_Ej?Xr;v){pOQCzmchI(%vUa#R*c-nijuz6 zNFK_w7#4lx%M5r7=%oByu&4HZ3arel6?NY3ZUs~o(*S)$@Er(X#`$BYyyOcglhNrD zwXcE9c-gcU9GExJ9a|>;z!H!`zF*m?0y<9FW2Kjr2B5{!V^zG})5DG(pAh?E<6Gme z^T+?m0GOs!ZL}V)qs8aXhJ>t<#|V=RYsz>){m3!5CoZqvdnFIVs&YpkQ_b76d@Sx_ zW@e68kVyMFlit@0)JnVE-QjUid1bNkr1#|o_dEQz13XDHnH?z`o@2<%-IjohZp&7x zKdri^a!6%7Yn>Oy!M_Uxy!eB3Y&>17= zR7{)bK|VtBS~%2 zUN@~A8D1IRP!lLH3I<_($j;ah>D$7K*HT!w06p0rpeH*3^kj#P3I<1$Dfvgv4|@ZE zVnA;8e|WJ|NZ0u3`E)k&?W4>tL6ktjH@qV7SJ3YK#4p6Hd5Lb*=@jcFJnC@om!;u+ z_u*HCo-l6rCA)o{`R;<-0n)CAstQL@Zn%ES`SEufJA(82B1!?bP-*NQ8LYDO3ZoJ5 zyc0k~q$=`@t!N7uj4v-gb4XWI8|&FUjM!jAA6`|!Hme-#ulgJr=g1Y+8_bKpp115C z?scWdcNM?hh+b5dSUIQ2Vz@eYy?A-?m$|dQp<)2+>+aeHtqfa?G=(Q^)K3w5VjlKp zC-d;nHxCoKBqWfZyuIA_ev!g9rb1--7tR3cv%cz^2^?MHwGI=%7KYZo(r50%PmJZT z8XWjv>JaXpe5fCl&WoS*Eq3<5?YzRV;*!DfSDpN)H)<+I&_48EAD!zeJF3p3T=@7& zd6?V$3$AP&8A@mY+qZx0J7S%i*8m!iy0I-BZJAO`A7%IOaO7B_zvPqOu?vhs;jSMz zbQDkb1KzYuMx>4m*M+$SOssIY?5+2m@qWDmNU!b+bR_1LBckTrFF;kD#=lf4zb|3* zP{|mX;>*+mxS#^Wd$gYexLOiev@UlakX2OPRaI|zPS%+!`^NG2|2o!j?bKC?qGHnU ztOlL$2P(fPN4X(J4!8+FjrCH-XUTrsud0!xc7UJp08~Y&m=%D3@n-mLR}YXpfcR8d zA^=jow6V?BHGN{tanv${XY&doKN%Qd4^-oDR>Is75EqZo)_hs#_fC0}Lut(_O#ht= zDyM^=9@4X%mQ}kP9CqwTLfsa@U1!gt5To6S`9Hq% zPcrDK(uU9%4X?o=#yMfpf$iBYtIQ>BDBX5!@6Pcq+mtB645)y1HM_JhKF$Ceo6WOT znG1{{`^)cD7u>8=x#thL9m6&#Q>vT`3@U*9ny!gB-hQRcf`6?GoOiQS<(#LLALMlj zAIrc_03pgeqf)|>Du7fB_b{DXHCs6%p z2B$2s+Ox-aq$WaZCYbXZ;QBtoEwJU2~vV(EXZTc5em(>|SDtwL{wOPgi zQ~^lVYzL~iIe;q8Z#0ccjBrG6G*mI8#z#++Qz(GYYx54Gq}oHEApIPxodg`{h^w5( zTt)fFZPUdXPCyXW&N7yvMSn#$k_XrlP>}oeFNi9sokl>a2p-sO@ERVasRMS*KD+36 zAm%NMJEI^i3*xE1QJK8`McHu~lcN7mGKlXVWKg6Jq2&}H25W>T?)TSu0vC9{2Erws z{~&e>JBCUGIcCXa(TNf$RmOwQ|6q4&|B457Yi_zeaqM80d!~v?%M=F8T%_b7V4G;H zD+MH8V^>W2Fr$`KfF}rsr%=87j|l&|C}HxgICPKAADz zeY;!gqRNcWB0o4529~oLY+M=@)NpJ=Cpa}JI{`L)(B0eTJ@Zgq&&`pUaVFQd!#8Gw z4QSvIbSVgE7Sii({ROex(xC->*Nw^n&}|b+TBXO2L%LkcxDo-LG{I|v*)Tr z#^3M$Nq!)|=vdqHP`wkgvVF_-Ngd0^?|xUtK3IKMC*pB+`?uN#EYkqto3@H^_wwG6 zey`ONyPdON-mPyXb^{#VJiwI|>i7ioK1JTsr-t;0Syx+z`IILxXLIzfJtWR*{@S(` z;eU{B1i%jYE5=&8>Waf*?tO2btITfnmgx5q(~}D$egSGwYZKYKOzt?-oZKcBePUz9 zhvd+I@$qjzWDaFH@&3QygZ}eW3>hg|*?$Vc{?(h<{s9N&rT;G{V*+>nuc5*J;b@Hi zDIGM5sko$3`qGT;>F+F69UWX0E|wU~bm)A|QFSUI0<4KFa4)MS7kcy`shu$N6@^$p z8m-b<{>|sYz`VMHN!c%Ag}(`EemLp7y~!s|^0dPRlKoQU-9VzOh0Rjsoj|nk?SQ-2 z5DIpG(rgeMwl`?Dh(KEz+OR|To^{tws5ft?H&4%<+!2qgMNFXPt<-O&iPOgbu`<`^J)0wgT`vDK$SJ!V@8hB`QJAd12rtn$^Z_VmTyhb+tVv8aE z;lr|JsXmZqrknn#8y&fql52HWK5a>DcR;<$crb2}eXB|Xo59m*^657Dlq{h-zuUrF z6E)!W>%Q6*+odYd)AVtaIQcZfg^)hUgLA4&%tGWl8l*RF)-3!+?yuR}&nhIiBeGVK zkO&l9QF>vbs{Ys8eEcg!VOM_Pthps6eMjA@Cur{HZ_%MeY2S;64F+95QDwFKwQk_wL!Q8@W6pi0Z}AJ>OgtN(HJnuvr<*@>&=$}ORc_e*x9WjdYm?g z4~iWxs2IqU)ad4`FVi$TlOI_#alPyTZ`!$H-mbJ8H~qKe<}gj}Cu0WaFz zyIIQb{lfWSWshrWS$+d0yib)D1$B$X_x%6l)kMu!P5)F_u8LaGM055!k}Dq;t7S7% zD(*wmsv}H$RF~EXPe&e(N!1F_Ne?8#9ttY@FeSAM@AE8|@j0903#K#DWc~K!5Etl# z1!Y{>d`6WQ`4g=!X>GYk1AV#IkG2#+PrQl{ZOqqOT;V zHogU^FLlM-DV%@lwIr_tg}huYV6`1nWuVzb(}%96I7=AS%jw=d$Dx z##-dFd!Gu8>@8!i$p7bn;PQP%jK2sD+|y>9J~OWVVETwa4-oNu%_r7oI@K=soE?R4r9#X8^sH@vMP8!8eWT==6&aUa_fioO=X)AC135O;j6b`>UrmuQ!rXS z`uhbz;svzqhq;yyTl?;}5S{p}u8QY1o(&@|^U_j@=jk@fKk0clVAqLm-}cI>hBHN$ zkF{uO2ib){#k4V^1K+rAvev4vr!>7d+z`eTLMIDe0B*XW-u{v)MXjffvB>f%G?%s* z(}E&zYM*WLPEgQ*uP1Wk#T0w+I_EkS4rPzgW zbz|E;#;wtG6KzrM3gBy@%3#cb0i8=uGZ*9tPxm9yAn;=j7~ zIVyJ2ZFO5a8?p4R-5)5bzv6kQr~B;`f)e%ZL)>_{^(lP!#@Xen4A`Ugd*ZpYgoju@ zz_0_J?yAaho7A9?xBZ60+fmBd4w@VpmHwHi(`@;E4_l>{lS4qXn9UFKjh>43+U@in zh|QQ%?o!l^JwNv4?oHESs~}scgF4Ankc#idM@z+2W;<%73jg*4Xx@F(9Lv>&U=%ES zBf`DMp(8iU4;{wgS-3S(;)ldgDtw3atuHMY)KN#m29S*{uH1Bme2py&1D@{AyTt-T@}Z zygy9+w0;AW>!e3M%F%cb!Wiv2o|%l|riyTP&lHRiBj_K!eoFHeE5FblpL^~dw&^jx zOP752uyB*;F>{qy!|C82ouNQ)B;jFlYauj|n9kfN{*6n!y?iq(mUzI>4Y3(gV%Ip* z*tZ=5=39mCQt_GqU2Dl#wdOQ1quZ_`Lci@TQ#Jc?rxSpgaVC2?FXu2TXR0CVN@`c| zKl-qVy7&Dp)BOE|$=IWs&DYsmXZg(H;;$mQ7VxGw9pEZ?w1lX3;!($Rk~X#wb(w1N zNuT_BQ3?T~K{XXqnEbRw>9EZsYTaT2)5s>#>*;(WW6WW}HH4py3)N}7-$(1J_fjR8 zr%!e{dwlyrd-G<)Cslaj=0?({oi+`w_0}Kp&fi}sJK0bXbL>XBmb6sMJp`A=Oj{-eL1d9wm;{_C7g8(a2KZ|8J@_NUvxqWo>o zkIMEmnyx{Wkh?PWtLYS#I2e~vEO`Z#lTq0|L>?G98W`zYaI2=vb0Gw z?Yz!Yv>8L(GSlkb0}c+FCQL8OyOQf=G_cda_H?w>3LhMuv`Jd}gZimAJ3TdV6Dvgj zi5H5EN@w=THLLy9Fz$DieCRp&)_>3M!CpHimdUhf$&xIk=hLY!Ur6+0{&bJ(swyVW zEPG+Gc88*Y_0b|VR&!JRrIy<_H%oH3X$jxF_|^LFEBaYe42O$zBy{&)#is4YXkFp0 zw9$%OtleHSSIKf%&3^z@TYE%huDCh|qOsDcGDSPkpVjh|SDV#yC{G2dsSuSJOr31Y zGRnp3(vVA`Oob@Yj~frvbhAGx3e#8g*Ba)onvpVIR|8&xzrc1;B%pusGevO4y+tHL7f%4510x4!Z=* zu%xOlT~FO%GRcw9CX<^*u{PBkJ$?7L=@Hz6N?oUKED+IO-hx(?78#>`Ln;O65{lXF z8)iQfwaoVJ%ke&?`Li;73gL3VH8Lx|04Xbe5j_M5idkI+HlO_d$k>P=grJOak^^$l4dt7oyN@0W09Le-JH|ktthFyQ;V-FCnVa+0r zAtF3}9_dV1QkP;K3Cg~SQRXlxb1IF-W$?(VuO(@F)TzGgNGUOIkv1v(F3lu&dM>6FCAS>teJ}N?=u(p0a=z|Fxwq=e3Km3Y*L`pI zRnn|T?VVwHW-c0nCjH@^tr_A=F7J!;{x02Tdv4(loCn90`mc@+DWj6(`vmbcZ_SQ; z;l2jNFS)5N{zQgZox%$@61CfhCzDZDt46OjsR=7dO!)}(OFgBB;`vr*$JqR)-y#!< zk@Sz=3o9;?4*BmEn-tq~Rr1&m#nUbjLl0Z8k>N@x?P4u&_l+JY2jM1*y`Y5cyDFJ! zPZ{k(fM+B73#`n4XPyOrOEIjX%}p9=Rg zC2QZQK>_3CrI6ia?(g;1X6Y*?`Hv>K_X7676T_|C zAcN$^rr~F-wU;^?jFmYu$`)+3i(SVlZ&xDeEyS$j?xXeI@8!9qDFbR|O6^rYMY^kQ zcY<86L3!hKoevB<_8C^{1NZl2sAI>yF^8Qk$E5-NF_xpeFZfyQ$=;)g}p*>Oj<1@Mc2-NN@hG_>$J+R3oMDqy0|rL2$u4 z&uW~zPr|fNH@%my*2?5&(Pi=l7XgdN4Q9@rv-!?_;^cIdH&X^Jl%QU$oE?d}mCH!; zdZeGPSN;()0NFqhqjk=7re)9;Y^Xl1z$kE)N&Qp-W?7CGA;QRc-Y z=>1@mexD2`OYxpTe;79N@S@d)UwW$b0cz5>iI~NdD^E4TJuuwZZ6&*e&C@O0?ePTf;jZmW%NppvcpIz1OPkT`;n3@;O)2UaUz22 zJt_kke(J>%-W1d;*i%tEk4Pt-zAJoZy1u%d*ek*nq7{?3@1{P|>RTaWlHK8N%#23e zPA&@q^H7M|mq5@x+ly1I23fM&aD|5Nl7AlyMS5um^rzEKpr1Mv3EJbXd#t0E6<(^+ zBBHWQXe{MrU4%)!s-)(D7`GE-32zN*B%R6j$#RhoF^tcm?Nyog%1hPg1!Gz`8G;~R zUyQH1l*YE1sNtat&DTStA{}iDyhzFWh@N>?UcG^QlRn--b;B6&Y}F}mKMH~tHEf6l z>bK z{4&at{0LEUMqa$PalJWW{uYJ+*bpcjdz7=VfeCjs7$|VjOMjG=H;4A`QR&6oI@2Dz zJ>xdUo*2_m=}ub6SIh;S^*$p@pu2BLA=o!x+gj5~#QzxnyrGwW;$dpkr82e!{pWfp z_=syog+HT=w#j|a$eu$Fuan*9GKYib3ti>1L;7}0l^W2YezCBBk-a=F>gqW8SEHBMiCI6H( z>wNx)i=>jA)I&heB8?Y$kdw2|xT^LVHH(|**yvB-ZEXY;7D$`7{= z$nFgYkR7v@i{$n~t`6r>V%s#2xYhUNVDDx2pjX!E0`P+{>(c8xLD%;*lfWFg3nO-C zkRNeQ6`ABHcl0~!#i(srO?KP@&w>up3VT{~FL-zk4S_*R$Qm+e=m{l30-I4N4(!x7 z;N^@EFWiYfOtWqv{N|2CAYh^d%_C>^*2<|RUy_`qLjhS&#yGh_4xJhTxxlE83fp?w zM^<*hx7j}HJ6cy#XRQz^n(A9z&oykbHHti(42>#2(cd@w#x4lL5r3dHE|f&#`}i8G zMo5B)7P=a$HKvDMx9v2lFYKFrWtSQsV_3$0u+PH#G>kfJ{4S; z-J8CeQi821=@y&+JE7KQOq$ryQ}7IZM@1+pgQ+YS%Y`@xUHwviG$>-->;?(8d}Y&1O%5W`eb_&3(LZ9~v1!p)Nmr zoAWWEfb!{vftsM$8Uq<+ZZrWKVOvqHXqc_eVGD-5wR}bw{1s^35b?3YjuNK{Z9+qx zmu>83i8)FQvGFTs0*e-O(kHcW=WaDML3_5;QGKK~#3Wsq@NG~>sUaQ`&t~@Zma=ft zM=@P5A)sGoP}0DJh{F)?BsczsC^!H%!e-?&AQ0U80L=?k%6g|rBs(1mBB;}=`RGr3H7 zVHhzkV-|>S+JO2Ki=dmuk~K_ZZ_@gNPI5!Cup>k9WfOO;8$1mhI$+?zt;Kx%Q}5AT zT~GRVhAePJP#?kB&3V}x+!DX?$3F355ksE|F4M5-VzDfot~f18axXLFuUr(Ld~U~O z(jL{*bJ6&SskBsdAGI19+{^t0saVZK79&_fN#_X`g`=0!oG02U`YYbNu;*nmiHE$8 zGmbno4zuSn>bG$hSlZnnii({bsH;et zj<-LzbzLw5KO5yd#KIuX1z6#A4)f+K>(@mRlWeFUN)9O=G3g5BJcIrAB0+ER!&$=C zG-UK*Zb^>Py+qhO(T<{-0#9PJ`hDA=bH$R?CqapDcj%2kQ3r-q6f?Yfkj!J>EIzsJ z2yW>w74+SuQezY2;D2@XE4BlKS(*ipu?5$c*Gz{eH>gP@-{UZ;#7xJJTD$=_NLIx2 znq}wQ7kwz^m!c1t-1qB`SW2985Wmb9EB|KH9v?K7~ zo=pBQGzrnZ!0-+n4R?2rlMmV-E-02}H+cc(1f8P|mJ%dvrc(9%ZXnG!-@(UI10!)ICe+vTUJOPz{5t zUPa4uwF{YXnoM#}*Y<>$ZhEP*>b;!=D(+!(WVCb+D!fI#LT4+%e#z-OSN zmvT=HH2NmGsG4|zIa~Ve6dZW%g5kKty{UsYKiRi-exMJy8@!GH2 zA;4Jbs7Q+dTqv&-PcTpHxAs49&Em&ryJAoxzj>Y4fajefczhIB@ZK#uxK zhNTDIY{&ORiRYXaOS+LII#C+fK6(kl5hPkc*mzy&SBtK$dacjTnmgTJD%4@#PUSub zya>Fl&c!)MZ?X_N1R-}wVt-VQaKQ71V+Txjz$R-vKRrid#V4=h$HRh&k)n=(xxk-- zDl1lsYn9zA4IR&A;a~SJm|_nX`bgg=fa!9gV#+ijTLO)-YiNLwzC40`#)V0fPZDMo zv&`G!FBS&KO9Az>R$#U3x~=%z6>T46_k@LF7r$D{1-VY>q+jZ?SVDBOms*!D)*JGX zu9sO7q?t?_rig~LJqgNNMN&tY-Owcnmdo7UusG@+ey0Tz@{jh>E|N6&x*LSpg7SPDG(cqRWYj1{X${j2RnY_OomVepA8BypZ&| zmA!K~7hmbrZ?9r~_|onro+R|^f{Bb-5Pp39l^vf+cT`E7b>yK+nCsTWFmcQNC4SEB zo;}49m5rYY1jdbgh5_NH?Q0XhLe`AWR9N@#Rj4LKr-2$FdP@Q7_ZKG`*eOhAm@mTi zEW2;S1=2ri%&=C3`Zln~=no34pEFTc{k!o0C?n8E%K9b*xZ&C8yA|K~?EOBocOu98 zLjv)Yw}!l<#L2{{vMt{f{B6=l4UYbpiZ}hgndnEC#JOZ*uG2lhOSUHZR;A?=dvIou zJ>}W~G8*5_h(Bk~>x>_Sff!0OXq7fgolCMNYNP2*kiko@F>{4Dw#qjfz>46kVG_=W zlp&KYev+w(A24}0=>2$PkIO{HPit_ms$y%Vrs8|Mxm~+|0;MrXf;-;$&1w8zSwu_! zw3eVlVszT#DQF*L7i-XNAKdGKTc&jPFF#vC+O)$jC7l|O1L5k?)``>cRoBHF#uzIcR~?po%nSFg z`j*`+a<0eBX=Af84V}(G)dHa=TH^6jILUxSv$N19HgrAa%uaHQ5V2#h&D0Ww z1i`>zg*dG-#p!aSP`!#9TS~KwbXQ(0G3TY zI3ox}c`9Vgh*z$dikIZA1oj`_^{0nnUmsRDR#bFoWU4qf*O$ZD=DxC_Co!5F(5d6a7)&#?)5kYam_epu)oKF@_@ zTdH1%S6%;Jqj;!elF9*(!+TChC*R|Ow}**NNs}iuo^0A(1=|GCAmp!a#m!ve%=`;I+D7hC*t}TuC=$l;5Am+{ajkeyJyLh!^l(4ky0E6>WAZ&-xDX)& z?0Gk$1sgvl9A{}mA1pO#*b|v{^g12z8o$CwKqZ=~-Ruaos}k)6MiA%4a$)sdjF6Rh zRe(fmyn>@_fSGH<6283se7T_$!}NrRi#YkD#MWtP?j+-2em2N$TP~Q3eV<7K{?oVN zuMoe9wn{EV(#)4FO&3cR~s_xqhM1TR|d&8i~^QqVyifZNptQa$I0o5ScF*%n>44%m)a&4r(%cLJj04#b5clC-oF&;6~DkT;~b^QCJlb}Xdw8R0_<|z zl^)b`0=o$F43oGYVQufCQFqZu0Bo&dYW?Pc>kZJNkQ`JImYMbEok`dTb=YbOX&CD>b1 zo{sN&RmcfnQxu%=zW&!3#A}+M7d2%x$YMuAz!=ths2|v@n=$8=D%@{78k?8|76yV} zK&Rq@SoC5+9ltMK0n(&y_R9`HE#XUH69p*on%Bj;5!SINv%@FP*h=5@v)&ht#``%W zCN}`~Bz$Z}_po`LHhsWJk@?!@8}p3U0&NX|B`y=7=7j^Qp7Tts70oN|{uk7t9q+owJ& zr57vd}?&UtaLB zw8-2#)w+-iFly0K-6BJcz(*Jt<@dzqn}u7i+Q?3pU{E1EJ;o0!9AW*Wwd#D1Gpd*GRz8^A{t_8fax zFel|rGg_o@8!048B^&k(JJ_RL`m#jn7mozuh97EV>sjieLN^=aNHVLm4B_89})yKfg0X7RuE#lu~;ZDZnBoOA>)=Vt=%K z)_Ja876&Z=jVa{-Gpoydsrqn6nk-hl(9R&3R7rfc+WRp=EboJ*LpiC4Fe7@ZkxS30 zR&~_~8dRi;=XH=JDUt<>X9}$%taHD^Y7w6ozpQ7LhlA05H9F7K-LL}=C%~2pv_-%b zG?46^9oF9#(Mjw-@B}e67&{bSINQgfDP!k82KlI1x}De89ETD@3Q49=veEH2$VPcP z<1QitoEH%cHRYsavw?T!a>e*zq9n0mDo2O)l(*g1i?jX4*1S1(r#UI3adtnymx{no z7q@Y=EnHm053AunyXG2Bi=gno36kFz%=%6j5n%{fiI)_iez9rq)cy8Fx~*4c1PE6i zHzKyS@nvxlwxfYEMziniTHqH>aw0qr4E>PCJT;P<90M(n`F>jxL18dU68pbVlKxdZ zc6~UjmicgR`!F{Y?i09BzO!?$*TLyBq4v{x9NTiIh0W^f%LOOY3Z(AJFkyr36pP|g z*Msv97iiW2l8-X=3lPmk?_EQzqajHOtYSU^ChD2Desk+>`!>#O&}SfJVyp0Q_O*3- z)VrZB@L9%(3+I<&hg#a>jJS_xs?|r(mpUt+B^eDvwh#Lk%2q|B%4&(k-lqit+bTG` z;_KX#w+6ucU<^soiKDb0=EwnK5Q`7r9Z(Y(E`aH=PuF)OOK1_Vi4EyI`3hBc zvm>uPznBn(eE64&vfsi44ltmCD>~w3D~$+v8xr zgG;9}fs|sqGPkJa=Z>u@(j}O!n6b{@&e1#TLIzop+>=kdxSDdPu6ez&CD%lQ-nFWm zJk2k8F3mYV+x1KWKS}MmSaMAyC)O$S1(f{2;<)`axHpX3NSbZKAnngi{F^KbTS=|j zh<;ZJaA_#p{bv7^JoU1Iv1`%>y1zZ~vcXGChk$!<98$dEESSe1B9#S`48^s+-r(ew z^u7v4o^h?x0rQEU!@xd7`hsQhU{Ng7pWmFg)ef%DsR;{OMy+)yBaRcCj zex2Yc)geaUg6}uD*xsGd-4xhATv(&>g5uZ{#aJ+pZ2s6yvqoJ|x~Ez1%UUl}Bh%VG zI2lWLjs&;hAj9g&i;)F-!kDkmbR=fc-#6E&+#tG0KNT0Kkx%rh5#TmVj($sFq3v8G zm}j+M=BJ9E$HnIvV9KbQYo{prN!}?53$r!@7#@jJBvc57g3Hn?ymOo6huOZ7q zIic~T6f!CrD+ON!>>c~j;)YK^-e%%^3a4Z_w#AdHrCEHBJTFh=6r+T{T9?4>a>c#a;!k5r1dcVQRa`O;I!@+py871WPF$43 zX3u^to#iLiNWySzjI$iLL}M9)h_&_SYp#Pk=@t?;K3r`%zVz4-a80s7|o^|oLjDgrWSrB)0UiASM-OK=hE=_LL(|JOX%6x$D>;N%#?g?Yk|by zG|%X6m%>{uYMxe>(BiQjIUXFe$&1CngX0uS1iVzl?~J9iS<@wC2il%XVr*p48r1A; zOn8pdaD09S9KRlK{bDHMwE2oT20N=b*V=9b>RX&*Kae?Yt@ol9_%qI2L$$kDy-fOr zMN1RUklAN-iAlxy77Sv0mDw1X1Rq6oFk>HM%41dglyDTUTQ*Hkk)xg^l_lrk9l7Z% z&)b!-x8caT^BBx%)08!f*zHU`9Sea3%g+mb_Qs~jy56<*nFn=|enTT$`g5+`#UL{Z zb96J+*+9HFi#=6z-Q^o=XWQE_!IlneGnh|ea?;3XyM5O3Bz6%Le{Ay8;!e1D3n9OP z@lehAPIalZd+aq|&>c}60X#hIIpuVT)f=;*iW;}CjT$d7$i~vRT0lWLX({C`MB9Dd z0`}aw=w#lW+@^&`9Q_X3%022S0=aT9MmIM&E5Mt9Wk!I1!2_qjCCvgn@{MjqWJ9A= zRb-Xc*GR;X49EivazV#bC-8zwE1M{>Mc)tnsF4z3F((A~!BWT>xj8Jhg%F^KCDKRw zG}sSjON=-{cQGC8sbcG1hgXu`v9Jp3rbtl0oWQse#P(l!Q4mK@L9Kid8lW{^OW5Xy zbC`)gxA>z>@zQ5FtS2vcS|i@i?+KlhLS7ot8&~1Z~x?3=Y8zZL)G0Bc>2y_MivttKpWb#qS(_8RmAr3pq-7^R%~P zYVvy#0k?s39CGF^jvFlERzonfbz#Z^Hs4t_pTl2bc@6H{Y}9`RqRrYC*8%%B291}`@YT67%)}|Z2M4^9z0(=vvx5pk_+TD zFkl+lIqCZ8tB}(knVdaIrL1#M@}qoWMvP#{?}SxS$}iXvH+&N`7lLUl-Khl2OP;XyJ%W-dDrfs9-bBtNA6TT3(AeJC05mds+3h& z0S7(Fk%SBZ!;z||L6bR*FzaLniS|P5#>C?Tx{E(}_Y8=ruT@o3_1$HcycdW?keaF! zOgDE9HR)%dp#4cXcL+S3|JqK~%b+=E6xJL*svj#qeX;y=P*(;Tj;!L3tPfJ9son)O zgAcx(7AI;f3b~aOZ>brBjs&^c;GvMS9Js#vAZxVqRT%I8UjL5yg4A#AZIWsfoOidz z_IIKfk=B7vF_Y_u2i2y`@Nb`kzIYtMn;7BNNBu5gu zvxfA`ed2Kh2MPE}B&40cJvZPCoC&E2kJmDH&Qwusn_Wjow?m`5-hfGwlR6azdxB<gu`#SR2F--q4~hNqb5%V-r%{0ymJ4RPM)X@rP`=| zHlz8#LYJgG7y_=XS*UROd?)zKi>|)bXq)b02$q*LYUlbjx@C0TwS$rJlN;8t7|S0a zTQ!>8oOASU#+Qe-HdXUm@Z1)~?m?r*>hlBsYs@pQ`;zAjQd!9#Pd`xYR2xqmXo6uDk%>T@;eJ>@yOUp0A?v;aO>gU6fxXf>_xt*)z$ zOk5Lq_UHYLy$qvY&No+|d(omly;}P;&fhfqu(Mg2n>MTAZ<$zu4aA;2Z()7Un~lDIy{Y(iH@bf&>U1Au3IziKs}8w9o{kMp|M8IWz$!gcf>HiUp2So2i|@p32y@eGl#6Ij*2sMo#!}EQ*ly z^nBt^2TWA0F~q0#LSr6vT4Ig1JNifj)M|JnvHcA3ibw@q!RiYy#D-wlxojF|`fRO# zeCJJ+C7@$b(byIg=*zmkoY-+i#TIWN~`y3(+aZ$B{&L{aS?N+1iKt4QxK@J5tQP2!f_1rrN|V2+NP2 z>PwqV)0I99xC<|nd}|aR^6fo-B7_PR1kdO)p}C7KFHeB=h-Zi^F@z7%67E!ax4xg# z>khAK@7Li(L7SAkUuV6X#3U2?c9{>Cj?bA4)km~7pA z2CUCol8?psavi6IvZk8a#l`@O z`DM^?f|%qx1J}1OUas-DzB}I?DqpwEEk3QC60H-ON%)GAv%3m9DH#b+Ke4ztQC!*UH}> z)JRnwfmETZ1OL8$Td37+Po z3V%vkAZ}%QScsH9o6XRdw*T_FufOl!Y1BZCVw)j0_JQ50T2-s(^&^5 z>`}ltes57e5Mc&Mg;}d_%AAy7$N>fp`u3jovW95*--@D=Vw;(|mLN7eKzk zhIZedNAl>k0PW4!g$rkM*YfakO@ckvXEuV|VZvx$x7TL(t>adD?+BhCm{ zoB8*e=2uSx`>PL0>5J*J0I_)NFKDZk7;WfadIIreU&S~E~ z)Mxy;#|g==^Aofc75%zrXCu$Y>Fy;NSXtT7!w9D?9RA(u6UJ8}T%YV_w&d(RD%O#K zRi0BR&$3aU$=eird_P*ky!KMcJ2lV9KOY(78c^2r|EV zO{T3XdcGf+U0(v`mvUv|3{g7026_2v)cJ4y0}2U-e)Z2l$BlT!or(prNAt}0s({!1 z>{?C)u<1-~4*-akxN^$RmRDAtn!Oj~_MBTj(&!auAAR+3 zy-SJWXF^~9&h)p!3*Ojnyn~+rEEj^$6zb7Gm&N27Me5v7$|06gYh|FkOE^ zPFTJ%Yt19AG3k7ib5fGhV|CZJUwcHmh~?+(WReq3xx3;&RxFwy$qb1WId9FD~ZQ_Lp6A*Bv|1xMeA%=)FVu!)sOJ z-As<`3T(rEpULpD^3RCtwhe1*IT>Hw&FSDiq=COZnCd+(vQdf9-|${VtdJwL*1lFk zx^YyODRFF#8)G;JGA*)M>6Jq((`FSObVI0GeU`>Fd?};!K+Y3`TM z<}qQ~{(3x2!Go24Q8?Kgzq77~G0|kq?3NvZU`4^K=ZnRy^u4t*J^TyN36^1;Ir|nbddXgOM?rPISA#py}L&K3EB-2?D_s{B@$Y@w>*hwa`*99aozYPx>lyL z!$t?Oh5A89;NOtOiggt6GaIQ4_fhD5gvQ2t(Ec*+v*-eovrlJ+>t=J=Zc}T;K5ig_ z><1-@rzibEv!H7pNcTcc0>!uvT$$@SF1(Box&zXdMod`$QBC0pKG;JV`s)p{W$p_e za5CP^ibg;uqz_W)j@dk)L=Q#aN1z`Nric-L1^VjL$nX!$ne@Aie>|g);tG(7OCV|F zQJgV7_8@zTafQrHEJYl@kMw{TF?5&WR@Ig0t9BzAJIo#+Fs8RA{+I@4I^na(<8a=j zvR?rM25jVsPqd^1^K786FP7oE10%yRF8*XoZUq1SyxnN^1cqCv4lKh>Zu$}C>c;R(n%Jh;Bv zH4?Wy(~IAZ);PjyJzaeg!Wr6wOXfn3LLw!XuOI&i-54L~*`7-tU)7LqSJ{UGM=aMX zLd?kDm~4GI>1{W*KQW|65#SvjM$2s66KWtf=2uIDBYBu9ZdSlFKl3Y!ahq&X%i%@S z*F$hXC!=V%Z9biJn(50$(C)!Dp9swiCrag{uQHc@OqH+SLmH5Z4lW+0f7udR-CK&t zLPpY=?bRXaya=DN<&YsLcfhhH`$zJ4*;2$ox^p-}94|~2-k--MlW8h>bru`ztJT{Y z+DLio&(Kfe~EYZ^6kNWFUZ&r~L4nRK)NV9(*I$>K1tel%05$ z$%!8*-}WS5(Z+}5;a`VZ2Yv_C4iTU? z+OYtXC3us`iSJ|e_3ddRLnqbD5g`@kG4%S`x=6r=L#T;UXM~9dd~|oRahT=KOz%3G zVk5VSe_B@VKN=|xAukL4SZOq+aL|4gG)OFyHZVI<2MW7j#zN0ZmgsPL7ey%!q@lhU zVJp?xvt>7ulWtPFX!;8IS#*i(AoesPZ#cauHQj!+FCJ7ETcX|sSlcm%MmG99vzuwZ zI{rQ9H&H#M_%aJ&xfLP~v323y?`Sw3LJdJkq{PpF5+P;R^^mG_yAjM+?vs{xao04O zsLHW)0i26B59_6t z)ir`lUFyP#YeR?AD;G_LeLyHtu9~l_v+=AudIh)M1Cz!$=lQ~3tUx(;9I|Jl{}%A?L75h6W#H5m_p=ZEW*aO=c1)S6{~v4_%2~& zqi1QL4z8GrU2SiV`;~qWr=uUWtI1H<4w&rIWvvsr_qw*?YGyX^yB-KcjKqSqAeCEd zbt#vC%UKV{rTOeQKeXH~lZJW+_EQ6E`rZ$eeB+|9E~XTI1#NF#QJH+w$v=FJ=Fy2C zj%PK`cBOdpX<%1KlxpUKC9nJA)nWQ&@ab&O`j@LzKAXk3cDRK`VB???6PXzl1yEd3D6G&@p#dnL=eTEmt)_=Y*U zc>wxyAHlq3IZVey(FL@Y-4HE^Oz+KrN$NM;eGwG0`wb!_9cf;=H8+0jz_o3b%e%a@ zeeYK_Q*()OZfU%?r&_}VC+ve~e6?95SMt$Noygt~7zokS12?&9)WnJ^98e5H)+Ns3 z|InYfh~K*E5k?*Sk~qh{XU)R;*KzEA%39}TtaMd-zZVUmZuZ5tqw46DbL{)ptdIUG z9~Ze&ne6;DU7W`B@*CzpA1$gAd3LrdM853)4)Pz=icv8FE<8gv}ah)85oidb@73YtDpj5)?uuro@G+=W$ba=DOEDU}U#m zm`YjniI#4AN7B9Z=I}SQY$tN*Dmk0@Q5&4~)(kap1^S+%x5GaSrA(Zq_P{#ZG}G^E zZWqi##&r;k(bJo5k@Fo~qLe3MEQw9GK3E4b=+i8It%F@FJ-Od|7T++i$Y^t@rRCx{|%_5d0u#QkGT`OksBlmEp!)6B(h>4h=-eRmJ zBPv>J*X<#apRTrvnG&JmaV)uBR)Ywwy}*+3AdXIE-xr-?VQcAS`g3@_zmzx(C=qvU za{$X0re1XtC#z8nMLgP$H!V;WLq6K_jtqroVde@fEW@a`MCpp@>Quk`Oc1Md7Rj3Y zFb7%cibH8!3)>#J9sBkb8TZ|D7Bcz}K@H8a+ZLqhBwnGzh^RO|YJ&b(tnM{b|P!y>9D?G?b3=k#PY&h`bLDX_lgpdgQ{Xno%3zGMjP0Ulw4_q zdmw7ZmOAm4R?M(?2(uhkM>$Fx0rbHAQ>CkWzaT}7#XPbJ!Mqohh10Xiy?h6Y1xuuk zw&OVHR?}d2F1-ho}F6tlXwB6e~2`yFh9PLwLjcrDF5NR57PwN~HV*7h`znOw*lM^BPZnLP=beiDY zl=MW@931N0BJNc?*_XAx7;>QFcW>`7E|y+1&rW6VSxR-T+blu~OO=|k*Tb1l+aJ(c zv}kT7FLft4=-a$qdWc6`z|z2uvRg34b#6xCpzzgTeUn<{T~0_}^cVE3ok?|I`3x%E zod})ReRz#!ca039%|)k{!Z$x+@)2l2PnL|f5iW}JwA8-BCWB}~4gA)HuYOLayupRl z&Oj!wq~c0x3cHD61E&6!GV=|mLTxv@lGY((8_+cw-*iY|^LZ-8_MzI$G7j8}a`gHN z+TSqMSNLPSr9sH+JT5l08c-C37Qno~%ZX+)DawC_Ez@d_WhvY@_6sA*b=IACsz~>N9Z@kXG2Cn`@+cV$k$PyP1*1- zy{&Q9ffFhC&-9wbE-X(tA``Q=@&iPVjFog!IGCMkD$~b71A|d1T^;LOVYWM4rO?%a zjxu^AY)3D@q1IFbCrfn{NBU5S*`R6-h3&a_D@!g1c)Hkr1;TR$`oYWPaEFIIhA-Xz z2m5|-d7?f7H|jP_2hSlNcED9=L6cXsA?q5>Kh=6swU})V)*_~GeB~wL!ro|DIP2bz zu2OMvC@Otka|m7bbfH+o8PFGG0KUt?oW{5+?1-%r!@@Brdhjf$c6EF9AfPQAm&1D1 z2N~JhPe*1up5S*xJ}vu&eEMqzP(-)FroI=P-n+LQ(PiunSpjh!>g#)LG;KanutGQj ziKTC(rt8ji^}@F?YBaZ1u3dJzC7vpk5}!%6fB}3`V;hmYKgQdk4-i5$?D{_#{!NK% zA@mkVU-9dha#PCOL;W8sr965}h0W9uBC|Jc+xagh2EUnD25w!gfYc1X5dCQyORpHF zn^Bdw2QI0sX~ZKk4#LTF9-{G&3kR)Q3!&&GO!MUQT7age8YQ7Bfa~hE#CKqcrwxB)8ZQO%esEGd5szJO+^dTf zq@p_4(|rI}b4lZXjadxcw+2P3KQKbv-(fqL#=U^X(Yt!8bF}fMvF#Da)QMg56|XqqjcOc{7fa@h5I< zZ~p@e0;Cp*2R-gUL^oi|ew4{?9&B6uB4KYs668-I}nV8{lay{RR1pA5T8|f+@&gdkyMKNyuODr5?6x4jW42RSv`ZHSh^ZZa zig1MZm9gQi0*2l}xftQN5evE**_$(ugO zcEkVH@jpV&?shXef923q5N(L>Wq)0EaQ`DJm=r+%4mD!>(A@Dq_!xlUsTJkuXEb1; zg3JqyQpRiOHXkdm1ZjYsta2drTXf=95W@TE40qE^1`WqDe z4`9QR16|dix8lQ&zMY@@$BC;4prsNBhRY{loVxVjJMlCg@#+Hr^5XE0x*J zbHo4MRDX;M*$01`Zbs|FO~=szgyy5k^%^KYA_7tAy&r|hIE>9b+0SUl`Qkh4jhmq9rd6-%tvYe1+D_} z5}=X&_^POFst(G*6gm)RM#^42S%*FN6ITsgSvZ`j>;CANKL*N)&;?SOEK6B^2DOfs zon|G6(FX*oI!h@yNBk>{)N@|9*AK7#Qm1HaOYQSqv+9`|{1Nw4A5vn)H~C;RZX95A zUZ6TKY)}8!*16s{#KtP_gVFuShpy(k{l5c7B_%BpUzzf*qMmD^ze7qi|M9V1`d4P& zBBi>@{yz|P|GN*g*EHm$a`)nY5wH7?9R8|m^8bG1S^1ym@K;s%pGThmuaR~DkAF2@ z_iso$-DG=Ri@RrZExm1yatG`&$ewiy;$q^UL9kM~<>)CE>!=ZiZZeylXI^}bPA<;U(9;kwX`;xt*L zYe*?S&f+Shy8pT*YG*!bli1~=Gk}#$yW;Tq*22VxCz4TU#=)7Y)FPO$(N0gjpszwzDSM~CD?=dPsZv)rfrrjYv zQe1JcvV*a_bkG?I#@fWwPQLOURUVO7k-i1S8B=1|AY?ioY--?Z*-*M^qgV#rIM|V(ZZA`$e}R!kmSJG#QMjt=aah3 zl&>kV_1YNdHD5@!>B2__f>1M~Z&S)$KQ={+n@1Vgtdd#h`%2IIzcyiRiMEyxsB~L|RqK&TPYS;I7^71G1!#7{MK2{QK(;Iwk#$N}=^U>#0e+>I^9uR{Ez$VHzZ~-~^Mpt9&D1y{hs?7tDQy=D7U=02 z$4ZJS7EB!}epg%A{`%5Pqw2L$=&o?zJAvotV!xD|C1w=`y08%{JKCe=e) z#bvAUcQpp?W3K94DHGA6;PdD|<~PJzU$}`;;|o2EZ}AI`W(kuj`DL#N0GT7quB(-1%!X)oy05QTsvIB`o3Xs>#I+mxV1!GOGAz>QxFk~;!o7As z!STr*Mf;GXweDcF+r**8Jj)BLsa}KdtR*D!jJ6kF>3c06!$OG42d%ch23S112ApQ?>56>nB`NF)cPEmLqA4cnl_WVW zJ#aj9XUM3H@CDq}-pnul_~|W;q>0z|6@?vrGId~K#Re6ZHZNjQ2|AX46g}GdgkM2Y z(kIARvHoaVky^CCc*YXNh~0Q1jZ?u7B<#{DU$IK~F5xG})4cdR9=`z3&QeoV@SHwQ z?3LL&WDkGHnbPIpH`EvHNlX%a9!hxcbp67?r>QfXS0Wsn@^uoqQs#JXsJMs|Yf45p zmBk$;MPT18=WhTe&dtU<2J+(>l*y6IR7-Q4y)FI<7L?PW?4nJu$(;fCea}P3pmhMs zbh*uidI^CPKb{CVi_Bxucx={Wxx%+ZFtl;beV6|Ao)w;=g3()GJ;BM85kdBR)qi`u8t ziY?AtZm^Hi&!45mmskpceOx4+rr*iSRUl1istU^L?qUL?=^dHzM9b^FLUl(~Cyfxt z9$Qza!ZNy&PpH5Yoc&wd$y-u=jY6ra9@DvzfKGd_!(X)uyKbNTaUc9?7`Ce}&Xp3( zcCi5cESYNCo(#w>6($s<|k_c}C@yy0|6Tf^C1NI>N` zKaA4A3m6m}?7{1GC@Ew3KIDe~-}?Hew915~D~Y@VK;k z5$Va?3eU1M@3?=NM5C#5E-v#OK46ug%p#17Z2wktsG6f_DjGef2~LMyNB`E=sX5nG z9UkxQH2%7y`^!M)az8UjZ<_o37q?d1!@1TKIZn(=%VR!@%=zN_qwNGm5v2%ppY(HI z4_=}ZATQS6x{Gw~_dB6WG-|ntYcmLM!8(ZC+DNgf2uz7n1lkqIv2!I{;$Rnp-v*6<5p}j;4R?7N;>hqX$o_! zSds050QA;?q9dQnT+X~dc+lrKxYAS_))`gdrz9Q+ewQUI-P>14NE8%(g?wt-2%r4^tnoWSvYw0fxhA`y@qB;Niq6v;CSf)n0-qn6}(AFOM7=}P@ z?Rv_Yp91dwU|wk>ILxn&tKdylwpu=-`I56Dg_X7(1_^u%_Ik=LnOTgh?TbUJbfObG zK#wR%wu}Y4IJ81~kAi=iRe0+4HiqlvNw2Oy0OJ`cscP>=M+rG!(e&wJy;p+E8P+zj zMhyNN&hvdX;H4S#<_+_#O9Jl`iekZ=ON7f{u2{62?UBdfyY7R8E=QQis$)Lmn`NNS z!_1qUoRswhqm?lk*2I%~^ntr1>vjcBHSuO*&NoiZuB7DnK6!Jy)H#gnNA$r#N_p4( z@{OF~LnfSFg>Ww)#tzE%(EB#@u2hO6(MFM&8wG}Es>O!8-sM!Fs3w*YQ(&^eskKtD zA&BZH6&HR15}=9Bj{DXXJ3Yi^Lc?j8mZn!LG>yte{G!&?`&MA0x6uDhLr=%+5=-^& zeUFM6fM7J*Hm!;{TbAP0d*Ah~-h6wF{0vlan6T)Rk4Mkvju)R9(d_PA}K{*zXC+pTS*T#BEjc>pk}|hohBq z*agPZ9=Q4{>CD`dKL)J1OFl4faF?^+!42{1Ltiz_70o!GxVj=!E|^M2qy0S|inv%| z4yQfIrxx-P7hgzDrU7$}YrQ~rdC>)l#29pq(!h)nET55E+h;WMV*X3blkU@sUcEBI zht%B?fG&FAbT`5fgnTBPk(F&Ki6cK(f3m1EM;EkM|){!%u*Gj)$ zlr-X>L@#kZe?t6o#t0)t>?ch2M?-pZt)D*);O%>DkO%|1^%Qv_SvT4UeM7G`8n0vs z?S2M^Yb3jA^^WklFcT~Mv*X2MVpB3(C(dxnlvb&zJIw;gYr#xMFssTEwScZNJS0DJ zT5*Z@2Hb^1U=njo>HBdtMHy=cZylov6hFT(*v$jhh8|=E=O^;RPrOrhLG-rA1FoaP z%#Smi2E0~BbDX~0=L>)X(|jxru@MwcD^e`McIo^npCJRnR=$9DTQhbf`7>zCX}b;` z>Jd15GBcHb@0KG=^ZDtN&l*wV6{_yjpSe|fv+PIDhA_sOn`cD%Lb_avhuPq^@F5&)M^4TeoE zI<4sIC;3VbsGVhee{0e{-kCJaahRvtx$9j=J3uQ0-WV#@>YnH&NWB3% z!lUBi*4sR3IhG;Z+g^Oa>!^urM4=z(EXuiAM?RhqcS|o2rT_y>r@?jV7x?+glkWb^ zNd1(ztwjb;5`=(L!*_V(Bqb9fBhbEt?TM5s!afnrDG)IqqV0EgW>i&8XC|Qq_V~2J zd%|9CsGO}KcDcrAFF(zk~jVkhnr3b*)`G! znw8p;OW4o=tH4Z@2C&EeUld{CvU(yZ&ti)CCNN_Dfl7rD(hDEz*UP!HgHq% z3+F3t<%@s|(6#Qp=Unf{xa2&=c^q6?HvdYP?gYzS&A$kCAXE~IGvmz?NjfT57u?v+ zm{osq=C!bU^!ln^IN>Ck3A?I@I-ervK3&YMd_;dXbo`JGspJV-(@gdR;il0A1qpQ5 zc?A{aiooom-o-)#_vVk}b#5?VPW_qRrk zOgbW5Zy>5#cJ}TzCMTa)5GTBU)tc@$%`_tYA-JCZU0d;L^(-Okxx9RQbh@*ZaD%Ak(&vSfjFrK20?l@)>^VZZ2*ZP#` zm{)9~W7Q5zOMm)5u1`)d(g&YU2^X+;^b!bs8`xR)or5d?5+Kt9#aaR9#HLFrAFj2B zaLT{xQ0$#`k2dywC6Z!Zesp~fXlSOrviF>OTI!wCeXl28R_a>4>6QuW6 zmX^y%y{J_d%&u_1df|5v%6}4O-I2m@{3|?F1!KwT1XMh*!H)tsGXl|$q4O{NiMp}a zV>{$F{);H!|19024E~QO;J;A1<^Kh0PvJLY@Nd-K->AL6QG0)*_Wnlg{f*lD8@2a0 zYVU8<-ruObzfpUCqxSwr?fs3~`x~|QH)`*1)ZX8yy}wa={}Gz|_d+G$|BTxE=Y>k- z<>dYoY)>BetnK~8+t>W5y`%F_J&5?-2z}Ivxv5zA|MUE+psKQGD zuXz~vl-}XS_QS-+tUi?t-;edqhGwZPS0(rGlYtW0N-hm2x63yN8%58Uy?W?ZCK*;3 z>J&Q6LS~c8;&-yPiOHzWh-TJhfU;RjJj`*PpL%;A|JIcwrqX3u@Rlgpw7sp7>usT% zBPEj7+RHJYw`p`etmzWBlIKyad!W3s6Jg&cK1fY335uLG8wmfAjx`j$MSfj-WzS3` zrp@%3j!um9H;n+%Wyvt(#Vvwyt>7NlGb0_MbiLq?;(*EvE&I|=p3S556WpG8%?+8Q z{rbHsj-?egA5wi71sGmoZnL>N_GWeWESA&+0i|)n7hp`g=9>-zFn0jHE&ic2LI7k`&XrPLajysDkBR7*J_)H_=|ei zPt5(1@;d1OHg3Br%Vj}(UY(%(`-uV_()9%j=@n4cUVJesEv_xdW{ho14iH{|i$z24 zynHJ#*M6$*uEmmluAsl|XL>~&lGx^h|s|{pEf?MfYu*Hhxq#jlq>XOOTTI;!{ zMSN2$Z85WXtgI4fHQ|Kc+O5f4X@ATjwid+}7QanVnw? z&noS2+679<->Md;JT(=n393I zI_C{5ZGLkJ$mQ^F2*3y~b`9?;c7wO%x^!#LR&xa#>KNadeG~jn1QvWk^Yr$cr~|^7 zogCx=(1`vlO=A6Rp=I}E;mYlvPY3Z_+j&t3PH9|ef_~_^a2woo*eV`+r!r@-r!VGy_v|{*uxVD=dT9MuKHxf6CuePFmz0?Mdk?QxvQ}Z<^M*cd(ka(Wf~W`#ptgsTMJg- zE74SlYsK@Jy~^G>f<5K0nRPI2r%uMFZtYiC1#vD^uYLJCu8NYbX|I=Ks&GFj{w$J( zytk;(8|H&zqwR}P79hn$)t)v#%kddimHE=pVI)^JBC1~)%js`DF>|=#rC)7$#Y~V5 zU^j7u9cu(I1*BoMtBIPjl)xL+D1{Y`VT-c3$y(u_nK3T{-u6I-yhDCjNDj>(U#754 zdQU*pYPZ^$n*rAr*drn~u#=@fzi(VGSqoO?o2Ok<(IH(dkmb?5VH#B@+`A@b0-E6B z;!08jWpvD921x+d0o5J?oKbyox%x=3&|=^XQB~x#n%OKu+Sap4JE0zDV2JK2RqNcZ zy{vi5w42-%^Kc7Kj#B(KxFZ;_zK zwtao#_$WLJ@US1aPU-1jNhU&sZ?Al-LbAIoyn8^Eyd(OmaCj4t82@pVzSJIs z^#Tp=+On>_4fgz8gOoY|!ZcjVXpI}3c&7a;)) zn?{n*>hUoOc?%-;@lm*JR0#GdDndb3YWZ>+&yHw!B#mo9hY{hI&1_OLWBp7kWIZ(@ zA%WYF|L<>mM|6{qEnv=LRasfx;{IYEr%Kd;Pj9FTKj^mL1yB!kCe3xF#Ggr{J3mPV{&>c-YU1**&rYLl|{ZMw8K-h`B! zDk!?pGFI9R0@>k%g92&vn#ir`2qn?Q~BsB3e@r zr7F7Ud!8>-BS6gk-9HYzP)}PnY)XIjZ|zFOIj5B^P4L3uUVN0L=OQY59-HW8*zOuo23$Kw}HWz8z@M zJncU@F&FLC|3RI*YRT|Eb1BDrO{?3}bI+qjqQ@z`o1NX?cA^LDUmI<{1WSKAcDYq2 zC}SR&QL8jXdsG4`DwHF(=^XxoOEzs@JU_74nqQ>zOOKQ=Jd=Ah-Q!EvKK0mcRm2z$$PS$y!$<9Y+^tM&|@z9vKK=pSStItnu3z7VBH_psk`mhz1E`A zOxdv_edn>U{vSjNqXNE}=){DSwzBz}mp0j;4~B5h0D|C`fV*@@W!Yh}mdET9VYFPF zZw^}#hX(m;aWEP)>wdq&hQItlqxvhp=3WFY<;Pn78Cg<*nu|5yV(w^54c}R_pl_j` z3x2(}STmK?4h`pv6sEK!oR?4#8Dqux$Y$=n9o{L-%-E#XmQ!< zV-5I#wySu|w$4q6>w0wtBzres_Xc!m&_fywo;A@i_VeQ6n9UC*gvM{32O79*vGN9J z_8}?IwcVE5O3(+c_4wzN4sjX&(jn!)UTcg$kb%#wfZ+=g+ke;=Y3v?;Cgy(cJ8YLp z=+r0hX@Ii0a;10Ph8@ayoASO2jbnG5^8jORSuE*ImT=YZnAM%FWj|4SmHeK125X?Q z?A6d~qC2$$)@UqKl8d>=9(#M@ z<_@~&wkc`hw@UAXAW8S_ZkhH+3`M%uIY)+6C3EHSkv2>!=k(~i?pH+(Fpq$3hss4u zeX5T7SY+VfYX{A_EmFBDsS=a=DVknNigdMBx_LS<8=9Bj%xzlP^tCAU|8!~z(Z%F? z%YFF$e*xe4_jKus@<8=TXD=sj2ltaI|2$o~ilWMYBwYe4DgR&Y27jL*`&V{@6Hy5~ z`e*g*8_a%vNorD-Qm-Qx>`4NkJH98LG>js46Tts#ZZ-(RN-uDcdEu$ zx03EV8m(?S-G?C8%29yX?I;qot3tbaSd1m#`DN zo$tqr!9Vq%wW20Xs)%Q!o_!%1(?-JIE2VJb`PTs>Id! zL+s9Oj|I`-kc(sZlc7bL_8>jN_kQo{T&YF0FY7Q)V(fuoXun7~YKRZFBD}Pb%h#>Z zD0;SPITKXr6478ECb2YXpt2aNhFxm(T{o+16X80T9y;hZT#O8&qkvTFc$-j?-D7B1 zuoj8&%xp;{*g=G~L*`zyxHMI%vC90czY;~B=AyT zf1+PR{r;n#$WY_mR@$J1iOWW;^PSN#$T)%KHV(>aqB;V-R1ywXdT&J5k{~ zGkiuqq=Wt@1HQ%hApJf3+kAD3IJ_z2bjUbO!KJ~E`ftbp|! zBs75sH~A4_W41@i#>97%cHZ;^n@o!CWca`GBFt&mUmp9gB|CQhAZ=&0SX4V~*bLqe zUR=I@iG_BxI1x&slxwU=r|l^3tQN8_F>|H2+ikR3rL(DN@Smk4llELgT@|>?JF8iW z!U5?ZX8wu+S8q%$;vMwuyzh?(2d`~U*Wp*x?LSsfM2z@yGI~%p*oacXP9pt|B8mqz zCcHZq95%$4W2f~#oO(7q_?cKk0`t2;Puhw;mCFpmuH_CNf*K7qRk$Uu*njsIHR4J_ z)FDZpg{l6pmTCYrGU`eM>Xa5Fch}F`^I)v`apYb-*YQOcnNQ; z<@D>lCK{ltg{ImK?`3-vH%ssj)mIYWO?Fn8d2ISl9-k2^cntKja3wTes$Y6(;C389 z6sOaWu=hGFWz~VPJW7%@Qe6Raz|5B2@Xa}Pw=pKc&qdN#*l96^-F|@FA*!pjz#FbR zRv{OlGFZS!5Z)0*{9{XZDC#yBpQb{O)|gawkwCpG7fSOHiW6*{ycFXYIOP1z=A2{L z&E>rchC+k6sXk*+8e}UQ?1bAHzT*NU1V>F;BX>G6J4=(nUmrirLV%2SYM|d&P)|t( zREPfazzAfhqI61x;A(3xHLWm`(P=xsfTE$vibEEelqq~W;zSwryBR7=#0zT>*E`96 zkbe7WZCjU?B^TUw0sEU+kC%`W`b|c+A@nMzIm2fRP&W`Msw?z+JrH|#@3}2+-6K#p zRwlWP{DCw`<0eJ%$?eVD=OP8*jn+wXg-{~u^T31P*CLPsQ5~F3-DAEL`!YPj(?zr1 zdiWSm;X%1w{Y$mjVCwyk=C*rLLIG&dO`84(k#ujyhki@Gs!uy{4z^O!83ERE_QgG# zC=pXl$TMqeL)S?G5xf&^nPl{EFL}@l-`C1LngGQ21!I@EX+z{I={_QLlL7Fd zoo|&QE7bAU4@GTzpr2X(!F?BEv56TsS*h(74sk8%tSC%N+ zMO9?*MhmXcY~0Hp^A=<@JXBd;D!Na`FCuEymr=n$bSx~tM+bMR&JuXz#Q~Obw9}Fy zvc}$m6?`k~=H9HAq+AhzCF&XJfqHilrb(pu02l{GLMtJ6A+s&P3@c5uY6wR!mbFO9q8)ke^}w1tPmqT8SV1 z)65|efayz_+sX+qn=V6UY-6yrWJb^xfN`^Y&uSY zs4sH@9jfz(DV69ii;j~p8OgyL90@p&*Xp(fnuYjlh2!403v!v+nO>mBveL_{1zsJ& zE47LZFU^)g-O*T+fZPGf$rXj6o|m*7SpXMau%cqf2t(0wh(&cpOcjzFoPP&jQI1F~ z)CU^E0Ge}Q&Vi}@xcMG(~BWR!K>kVJ?nTMDCZ-gz2dYuEL zX1xWZ&N6{I+%35IQl0ZG$?aXH$*ZFQ0+$O1F5XN(eqvei_TRVu8)^e8veT(yVu}T4wlq`u6xdNi?ts|4Q8!Z@IjVC5dLR1FPesg7owKp7^x$I}68lmRe|S zPl^Qts$v7LDqO%wFAvBg==yqjlKejWXiQzWIk^*x5?e?E~#2uRH|%@}70lw_fAGJhcotd?Hpgz512f zm8D%1eU-4W%*&Z5ga`MUqXJkMZ&vea;@GXi@6{yj0PpMo{vx~F&_Wq_PPU-_)tdbu zvje8!vl!<_p1&44HDmqmTqSflN^S8`#R z-goTI8QLy?Qv7zWoGRo=?o;e4Th5YFD9jp(SYGHO(zGQ36R7u4zn$F5gCa=e#zNj& z;j&rH(QWgFhh_Fh*RfLD@wuum&mu3Yio!%YgOQM>Xb>m(XNil z3CSyyqwYroT;fCGaWx5wL--SN0p}2elN%5=(-7G;gnF9BYp?* zhuA2{#L}$orN;0!bHTFN?)uHXJo1N20c_}r;I}*{G;QLURYOkYl*`mXea064i|7*K z4@~b6Z*hc}pCj+q4F+Y$d}%qeLc zWljAg32WzAl;x|mw0896(0oy9p#ORjZCf|iFw@cQT#)=~=aJk%>P7PnGX})^Lr*ZaLMkF|SCxjLUy{ERO! z18oAed%3K%JzX4o^iF=;wRAb6x3U~u_@*4FYbz1q9mvu&TL!Br7+Yowe3iSNAv4HW z5L$wsu$Dj!(JnIt;mXGiHsR~wgqkH~SkKF%7Za1>vB03}iAbrmw`Po0G4$^h8<*aI zOU`TvD>g=`<`83u4+k>F#mW6n?y9IQOF1-g>4aQKZ&r24tD+5n=8Aj?|8Q>MtX;v0 zj9x~**!lq8tY`AIxO&K1X*=~ynbL|`J*kPsOYMQcs9b}jEvI&~o0-@gKRJgd+u`^s z$~8S@vmDMnSw%)%j<4L!L$jN4h&!eAl>NR#seS?9pew2VQ#_x*@2?6qBDXFQ77KzF z7mJHx`^XFT%a;7^_zg9u0=WX#VE}8pQ>^j`>RM!s0glr{To-rv80FkIVRrL5)&@xR z=?)519+*s{Mqj1owbsQ>BZ?v$~-JRs1qC7enY#1R(X*09fhDy%UDuYui1d~ z^_zO0-xerB#3Cde-qm}=9?{g#luw}UA{H}s2zvYA6d=KAEv}jPl;(^tUHE1c^xfk- z>MpaWn)U*RuegfRQv8Rok(pAtQk~OooSu2mn`zQbzh6q6OT-r0=WZAJ#j3ahmEJYK z3|tUT{feZ0uYrYbgp(a({VkL6)Q6Zli+Bku0-|rMk4<^WeJQjFc;tLT>t6x%^|?ud z-~tIke$g14Cr*2H{YE3x@>l+Gt$vg*f%`HorE;@2zmLNhp$*9&30F-9Y=A-Kaq(^w4{(vUJuLGQ z+r$<&E=lEO(*hZi-QsaEFRVd%dCyjEdF_qPYLi_^^SH?nbmiF&*~hCUEL*w*2|hd~ z*ajzy#j+QnEZZTa&=vpD$Q?q1jETi;0aecWG)&xN-?gtEEL>UsZS}Nq_891EpKBkb z=lyZ42%(VPy*{PoZiM1Wj9p#HZj-WEeal>mWD>4lFE8~YI}mV{75|Y`|G8Wg^!erI zf!7uD(93%32EfD^I`m?@{xc#*#l>pPUb%M-ljZrfF4m)O{cazd(o*Hn#TATQZO{h% zz{!^f4s0m0zN{&22cYNx8s5l(!#Bw(r_@RN(=VRJWvwECTAa zGP@>A4kckvPrN`J-XOmm?H6lT1zoRuh~XBJ7hQzJ+Uwo?Sv=`F=@;9OuR>~46+mse zI1anHSQxbQ-Y*GX8}#E%QBJz;gGaxje9viwpP z`|&BU;Z48?uw6;^z~_9nA?a1}09-%GdnWv%JW%gFmF9^JW8YiRl>Z<6b*Y>M>-d^{CD!z_+-w#{A)vk2ehHKqGaczfnRQ{CuKX*mt{zB0!$o_@4lKb~4dU52Ic5DXpS7u-CZ1 zFnJTO{$%j)`EC;yni)erS%foo*1hM|YV+OtqmeNOB#d_T-2$961c-6~k^!gnn1b1Q zDv}G!Qzpu(Nw}@n5JpS_fLP(Z*EQ|bFCy-qJb?RMjB5ivQMpi>Qx7*+Tqcy}ONP6u z7U$0l-Lc>rO~t*=i!nJ8Z`6?~v(#b54B;li_0{NeN7T-n*)?- ziHOHE@B{E`lJ}vaD%;YB=`Ii5p3uO{@>QtHNTotul`F&4Ft%TduM7xPDe%=tehH<^ zYwIS(EXpRj!beQsr;f4X%6QN#MStAk{m$Q8fJm0YI2H?6ZLnCAq*0i*I=6l`fs@+2 zwcr+QpUGbUz<8bwu(3Zoay+d!O+LKKgGk~G7WuZ8WtS-N_7d;>9OeU zGw(vBa)}iu;+R*K$q>k$Vjj&gp6G6pK9Yh^=Dg?KcQAT`$(JCq1$SK1aoQ1a(xwyz zq#_5B54(Jn`JbC=UJ&h!*<`<}QRAke^ca2CbQ4@;(sqYu?n3VmP6JR11JF?ZswpNc z3wi~*x%fw*w#*v_p-2%9ELnaT30^Y1>&)w)zTcFgoYyGLBRd}F!;1r0MnlP1!>9M4 zTax~-+YvAsba`R1E~s@2=vz$0$_2uCv_$wOQSxCY^nj~39FdEG7%#(@gn$f&PbyOe&z9tC=vF)GFzC7J8#V%70vLsbEfR-pOU6tB>hl> z_e&*8VOmW&HB)dQ{8cZ}>E_96Q~K)IFxpTI4*=yv6*}>uFt-TL*k4npAIP8R70X5R z@syLN9l=#M>BLcm>D(89;FyT4%v5t~yyUBowIO9Wf~#yYRUn0(*r_41zKN!iQOXfw zJTPk0DV;q>C4h8o;h)BILlcD)(<~M*W{1(E3DBjK_J>9?Le08NukxRpszg<@Sd0tI zf(n9a8PF~5-}a!rQ9w_Mb#!;VzA{M#P-ju=3y6>+M1kS@S|@Lo4MWYL8_~f@6P3%U zvC1@RXd0BnwEI9sot!r$Eeu58Bd?FdMop`(b=ftgli;zp$f)`7pHO*LpG*c`FmA=T zJQzyh>r?QRDo|43_YI}8+vD>!HvJHrs+nwvRt}HIE7r-Ix59h^D$%81WPfr&P$&N( zZ%rgR7|bT;b(hTV%;iwWL%8Zj-~=;$m*Q2p15*y&Hq{(|TD4bA&R8w2PBOmv^SQP3 zv&?E{Ccy;(@McjyYDkWz1Knrs^3nDjZVfJ)C#T(2MYj}T05p+0YeAGtf+^Neo<9F8 z*w0j{C4*2cYyl@4#|X^hA#ZCAxpVz}LeE&#aq^MbkwyPFB{s|+Q6lrWrmRm0SD+{J~?TRmm?P{cV*3fm#+$i zK7g5yw_IM2a3s4NR6AU8neUmJ-k(j?OX=b_AZ)wgfx@dkfaAt$8^y+I?a1(#6Jg#- zOk&ebRMtBWK*AOPC@50e8_@ZF^Elq^Tdr0ys^KnTMsF58pBZvN)q3eD|cl7i@sk*BE4aH&*M* zv}7yvll)~onNJ4yI_PP9twcnVP?HV`%MoEKScvl}OfY6vg2iLjkt%vwzQI6ZcVt8K z(?!C#Wj0mc9Uz1wptLhiYL#K8*g)2pX%^8K)6up?SO?9fz0kM#DD7lh!InG89@9_b zHbB#MXg<`v))aYuLAY48&kQ?Dh@MC0Pn-C7#T6s<<%ddazu z$e2Q1j6a)ZYd|#=qkOqC7&Qhho8}|phGAuVfjOL4ycSt3&cyW)6I|4z^L0h?q8z!{ zZob|TpNV7BusSyomj61U%QCwGRVkoL@+mgPnT}k;pX%9I?UIy-7S7V1>U~Az+Lerv z#TT`{bk2NAQ;IC)r)8_W{~r(9%?m>7!4<%OI2&~*aMEcH$8j6zsZ538HXQ;;AF zEkV`{8tqJJs_|}QtQjLzn7SNR;xYa?TIVY-l@Sw*oL3kha1_icHml%{Vm_o*yC&-7KA7Uep1P}SBkPU_( zC%LpMMlr#C7n$I*)G&DN@BjS&Gz7<6WU^7n7#0#c0ob}pgeouq%x>a}Qqj9Q%tYot zN5Gsi%!`U2$uJ^BPK~CH$i1m;DjdbnIhuD4*9weBJ)8!?O$8VYn!rm8*iANdN+w@O z6~v|Pu_hCDL1n~5gB_UI-yBIrGbT^fTPmvV1r7*?wbJH`=u$sIwy_M4)o9~36$yM1 zxq}g*TEIC5RQvDG5$~7s9q13zyyF!jQspE4yDz;zYkH)oAplG`&my{3N zON5U!)&~;ZRPUhqj#NM?o|8+|EIOu_i}x=XljHLQidCpCN9b#aU~;4lE}xZ(P?cq) zo*~22NARRYbU9@!P~|{@vl#e%=%lQ?UZN>sID8rTkqeohTDM2F7b`zgZ-;L$hx)d# z$9DgHR^kjrghvk8!nSR$f_a50E(!x2kbEo=N|ww4+l!CtrXBo1N|lRuoR8|{ zPoomjgumDiAQ4Fds;~yLB*DgLt&cyzRG0Ovs9RyW)n0*Amx11b^+V682a>fK*(y>1 z%g$bL(cw%?0vX%{eZj+2h|OA7-=F~4 z2A^Y%*M?w0l>TXj&nZpM6cnL9pyJ!nk0M?#uWa(Mc+U9pLY?#PQ|ZXOuKGS;@~!DJ zm8i$`Qb}#_vxa>1e!6Q=`YqLji&P<>qv#+Q*yqlJuZC!(gm_Mg#+@mpKstuPZ7H><+el#mwe;zi^K5*zBIpc>LwTz&7 zUE}3Nl^%6YlO#nnf|F!l2vcVOwPMt)2+^3@Ni!OP z!S_2Ah0bavpO09f>I9~B_NsXFCLgeF+@o^Rn9?>j*8F686)q!jBZzm7jA-Aj4l-4U zm=^BliS!3q+@G0MRbt>DX?M~9o*enEYUTX_Kd`z~og@`HeMUcg`eetE1J-I+J<{TS z2^bT^1k?Qv1TNRykI?I2~_1z zx9E*M9n3lCzm!dIYrKKIa|eSsI?E$#xvl?nZ+$tyEWX1Wz%DY+{@szV;anJ%U>2cA!&Lo=Xhb#QGF z(@ata^7xJ%DlJVCGt)GF5WPXhgm`1i2KbN)-6$U3sgj+DUM2Hm&}e0f%bXhNPRi)r+}2=zT9 zdm5cEvND>!5L=kYV}40TkdqGFyw53fk4(+s3~Tr%kB25~8MqD8+LKr2&SKCa3Y}k% zwHDRt?|%BM7@YDG$;+)Sgf7#;E$J0QSlW_hS(wPRdQ)BnYGCNy&SjT2FLbv5q$Vvs zTeWyWgm!6lMk-%9=A0%dMdYgX+xbPLf3dA!Pb$wGSnh$tnX)tQ1UmSOc{EC*rrF~n zby+XG7@)bUIT^8F)Wr~Vbap3VD7D& zc1?S~hu#E};4hqUzY>9sT9QlyW;n|BGMXQqZ0e* z-O}796JB7?gHwZB6J&OEAQdj7S`S%27pxe%LsF%$OFxLz*M{qeRCGrq?T>Jn#+o{b zK;8k9MrDcB9qYmXZ0HWDBlTd6HJn=q?o(M(Io-cese%>*<`#ZYj(k+%(2d7|FDJ^6 zR_KcP1|%Jswz)w@W!KR)J^Le+I2!)=Vhb|i=k}gj6Kg*>&JwQ1Tw@1u=X<|o*fj@< zGFisGfw#Y^xpMNZ$=O+5Hopl+BY?SMd3n@EljMDjD_jCIGjwSPA-ld_`J@mV;gZ_q zOC%xr>mWGm=pvKd%Mp*xyCmQ-30CAVskW#bd!;z^NJzwUveYiHkeDV}|rKDAr|HyHkd{6yn0K5uD zx*ENY!o_w;Z}R`3g)EIG2Ni}&5#s{OJ}~w4JkW#NafLH3y(dQFbh_*Ug>&NtdAHY^$I~uu z-aep&EM1rU)1BCgi{E-$$KYdD-ogDd2|l0#xD!(W=|3rUnd@)jPg`zwDlKMgl(VqE}#IBa_+i z_^JQ#UJxMI&mRLda1eZk{U=A~3h~d19C86)AvAQkK>(9ZYnN#E3c4uO$GsoV_O`Ca zX1smImT}>eGQE#gdCRuLu|6D6vjKP^zv%YhLVWTRa7Ar(_^OLO9P@g0d;wExTENA= z{p_im)sh|{{C`wlvt~{jay**b9_WyJw@$=rdPjX7Rc!F=gQ-bhzhOmwyLw(-tx4%B z>lSZ15R^x@d9$AIfy)aX1AYY=gGZf|`{S)4GTmGYZMJvo_%n)rD(puiuU+c@n>(>0 zCy8SJ{(^Z|Kj2Qhv0DKj$iFo4a#@#G^X6J`ZM7Bf?Tym*&WW;}2JFc|(kfYO(nP+i+zB4v<$urWTo6RarSKc4<2G8lp}R`5&Dg(6h* zhvS3%Ks6nKrK?xEx9^K}vDT?}IzyJOUN`AuMJT>lcvK#LaG|@$ZQ*0}^5S^S&Mls{ z6BwMEW|`HOgMiT3ZEf%m4b9&AdA;mo%+{FyjWPG^FRZo_6^~C+k=IJ!@!-~30T<)V z@wsr-56FW}N;h!#mkryY9rlH`@$~hhEk4ADXx~^5QvA7Hd342hz}K|$kjJgwbM=#L z-W=f6<}R3`p1Cr;YeZ)F_M zFGCjCGBUjI9>Wh7ALb7GpV}@%o`!+VVzaS^fPhu})aw{8-|(RE!-vd;4+x{e;L^hG z)rY4;=;Llr1YzSEEa{O4C9!R9>x+VTr=dd9^3KvjF~9*@Q?$njDzkmcgR9)in}(F{ z-Ex;*aB?^4rrl}?IH8b?6Ih|IWcxqzQ_+)hK(hX<}1|(1O)_ zz@1p~%blnUxD#0~;+)VKt$4BNxUG`8?mVMYccQ<0PuK48GbWXfqJ-^f9=0`sh4zo% zLxC5D-++rH0$qBlnbaCWIK;+p)ei;oECiiMAbH%J&~n}D*ZhxNzboIY8Fo9EP@~1P zTUXYGy;f5f@4i_!eYm}@5UXI;^SU*pZ*SQ$htWQ^S)o^$+c(t4{z1D!?@53>6md9} zP1vX@^I4lh?zmJ2$*WokjFu#*TFt0%V7SK!DjX9*LZcP#lR>WeQ+Qi1U!#(1I)ENOwnst5h_Fm=X)h6X3 ze}&uwzo1Z~`G*T$Q=#?^KSg;<_q4Jvuc;l(m*cb$Z;jz@g&$POx7Zi3)d#Y++iw9^ z;5;^DlRTi|K?W)I)Z=uK0Ku1mdzUV}Nn!d$!0nzCci8ON1~}IysuyPVE_82`5`#fS@j-O|u%vA-YKkr_`j%N=$ zS}%MB>~FhSXYIshEKWUVswhb_`@*{1?htqJ_ATJPO{iD5S~!c>Mm+HVtY#*+S0-ZW zB&-(X6uY;pLtgnD-MY++TQ%a~CJamBoG@Q><(6D?ko&%#9Zs2FWRko>-DhkkpIm6) zno+&xv)~1ilf;q!-I1pA%aK-*2`mLZ^m5I654^(6VShQ&s{H2-lH)h4Dy_OE&)bdx zOH5R`fZfXyzhyRHp*A$gezhsn1$~w`8~mmgy1)Rq*3H5^M}a2B877ZO1k6Wzt$8+o z^-{e)p7p34*RPxL%wyo?E_^@;e79o~U+7TYmF1;sKh{VHuBtsy_e3x2n)HGFT0QWf z-Z&@g>6IK&ljaW&Ycs)XqsNY}Ub~O%uLY!(542+M0a3c}&PGeD^Glz7!qzjlgVc>_ zG17(z51@I_r8v7F9r#5yEPT{R1SsLQs%&@C163e6K0P?Uf}4$&OSkRbg3TH|25R}( z=I&)W&xekv24!1yhoBGU#c*?vPkRXzgKq%$?mv@wyEBwt_P3(%A(BqX~h#Ih^9r z7md9R7!wCn$aghTvb?$Hp_1ZRK`|bEw0`e-#!tcNar`P^L0JmRv)wLlq&LLeP6D9;{uFoFCTwh z39(6I%E*YpGoX@5+FLos5k?uF|krhB*N?+`@s#r4}UN}%nFsXY< zKw#7TAxrkPO5z<-t{Z0UWCjh?_fZqwg?*!m?0`NNl_?-a! zLQ?!r0DdO`zY~D}7YV>$c*1{90RErh2~TVP?<4@f@r1wegun5Gzwv~>@r1wegun5G zzwv~>@r1wegun5Gzwv~>@r3`octW{Jw1z@`;ZMrCjHUVeaQct`(00dC9`6kkGa~|Vr-$5Y1iXR>W z{f3hi`^Y*`lMe8Yc6e1P^KJxIpI3b{v98JLBrcpfo2 zY5)kC_{CjLg)|p&I!k&8@{o!7kEmY+`dxFNwqLw@8KPDj0Qp_Y3Rn`w>+Ihqj0AoK-ZG0=%`zN5Mq(Gna}dep0ssvE>DH8dl0ncd zo*1BtnW@H54;3gN0g`GK8vvjoixhTNyX2&R0xAc<&06v#EIWVT!<1{SyW$JRbOQ(m z6bRXFcemYpVqJE1oEhQ|Uo}85V$URo#Q;Q>#xANnD@Xz7tdL|AB%jT-^``Pmn@4AQ zZE(9AWa)Vj+mfit{dH{pMPDV16=}%Y=$cLac$E@B&X4qlt+j}MFaYrU19RWf5y@f# z_uvAW1B_gU->F`zXeI2%CJ1Q8#IBuexf5WLVm zw&T#Dort|>KqbnMVe!3GBzA5*0pQwx!15lb=hfFHwFS=S>9Q*V z3q|F9{csZ~fI=4+n?CoSarJip0au@}71{UCxO!%@*nh^=tJUWGYh1nTFI>IF-*NRn zAZzVvrAGgZtDgjL_1ouOD*1AqO4mpcqzm5k&h1;Z)dxd7&z5U~1uzT>ez6`|LAK+o z;U3~nPk-U+$N!G2w*_$ZyOR+v4SR+)gLojMwQ6bs3rUA_fFdkbN9SMQ>iPc@u0AP| zVc|dF>URNLy|pJfQ69G%Zm$sbKxQGoEA5|g^^>S#FFiz5ZgwMz&1;a+?d#ug^}9#A zeAdq7#1&fx0(W-%vVUXDR)1ZBPa1f;3k`fZ0F3yE?5Ew;?oKiQx@mQ)!8-~%Hh|NA z0R&+Sr-HC|TQhUr>n5jyFoe5F*Y1%VzxCg7^#Thu0IvS?-rl!AarKu^arOCITVmZ6 zt_%0F*AJ3lRYRNV71=js{f`~LE8Ep%xE*{vIR|$uvmS3uQ$ss1m;l14J?Mf2bm_+1 zS`$UJdrQGT&ND2)mF~URia-3Lq0T2zIJII7fNZbt@q%P8X#xOsyV@_B9NFM909RiI zFzNrF{B2ZGT$nTv8ogJ4w0m`Lw+FS;+kJerRdh0Yo&e`QSaUnjUNFg$4^w_{ET(W0 z=YyGWTDY0I6+pU>#HAEwu4EFgghOG84ZjfaSa~%rrHXR_1>pDV2fMEByh>_geS4px zP7(2{e@Ddke`M?Oa}QWpAKYt=+KL2Jd16z*_Hp%4?_YSRj5Ne^YOS>x%K)FxLtceJ;A6vyX_=Z}@H!Ah~ z3q<@9fQWbf2O_>b$uV2p*CW1OsfKpfS3N1~;5`f=;Ab3PnIHCY69)+J_;LWEKG{~M z_Fy+&K4j08R0S5W1PCud^IiR;V(mWlF&Fb{Jm+kAI=@BN^WfaQl5j74tHAHYI=mEd;*4=5Hk!ad{w z$sRqhL=1dY1o;svAt%HP$(mDe`{Kio2|h<8Dt0Z4(>#=bIN4iUZc!gl$q_Gzp8Wju z4?Ui<)cdU^pss@wx_{Q={pM|>KXtfNkk;sWGM zDOBKDDFCA0>}7A){PlsV?YIdo0bBnCkWrQAAg!H0lGTYYDg}Ky^qm=1o!k=yq}-x* zWj24VJM!jp4w)uWlbw6FycoN=u?fB}j)$tiW_c{n_-Y*1x+rJHZ~m#rD*^O)&423g zYW~#YiHkQ$Sj`X0^H!{?`2w=!5VRGbm7@fh>$hvlk*BMge7yxwyj537c8}fIy5Z$D zRovE+SYP^|P_SCdOU|r=%0E#w*R2Vqx_}EGdnQMydJa$6q8X| zCtXv9Z`-q7XjQNIYL`f$P|;`nOONOC&w4zz?*92);a_?@(}3lLUL}xUN6IU&k#Aan zVq9n5%Pns%SQ)pCEC;%L+Tc=J;8KiB%km_js@^&QLG#EAZ~)jBkGI5{u`PE4`Z1?& zZ1++jpfW9(_W-TAk z`zdzS+|HHTCBqjuFS)9CLowwFHI(Gu=72)*%N_+m3=r~fHvwI}=&B?&TORB-Pf8HO zJ7cd}E$aw9ge;ji`4c)qp4M;9b>Rc4RrIZv5*yB%!0q(Ebxo|_*bCpg4`813u~(D6 zT|AZPm3}4Sc|~t_1=HC99j&qVf?`62ym!IgmLD%~asCB}$H%WRNCAL&)-AGzCu;-J z{*X~iv&jbPIfG};1TmGXCPO|4$0ty0=R0n@qO*E?-t*u-@bBCz{!c}o{NGVX(pUb0LX!XY zC?q-ge*og8{>PsSJ5B%oTM1bF(}63@;Zsc4W7KQuSA>|JFr5_)5;!;(uW`3bw-EZQ z;%noPHFc5~zV>20t&`{O$Gw=p>`4FD|8Zf%^K(uPweMF|A6FAf+?EB@eo?08-0AtM zw`*W&qnr^{+coMe`w;nH1DV2{$}=SHV;@b`ANlw5SfC&63>Au~W;NSkWcTl%8w)>k z)_piZRE*&)eN{-irL9Ga5$77Htk+0P%;k0)DU-{KyEkmxTE6ljP6&-m3Ck308Ct#M zlFbgw;)x$4?q2sbvo(abYcAx4=D8T^I8=!Y$@YTGg2GAc-sHw}wmuiW3zOkl3fp7t zt-b4DA!^Oy7LVlPq_+w4BC-zD5-`_4w>(CcQ=TM+s>^)LjYr=O0l!C{gSX}%(@lad zdnRg|_F}F#w|t%@iXlZGiWw9$m@4{z6W`UWHeyH?5x%3}dr5-Ll}k!eCxr%AT=iQ*MefHoowexWt)0~tpaEBl`R zz^B-ln5eJC&N9+MC@~My6$JU;dj~R8mZuwRN9g$)+wrt%Sf;@oGK%is=A&-Zvw)O; zK*~TmgCB5CLk}+YDr|?d)8?>-n#%Yz8D5$ce$C&@YNid}_%0)px8?~il9>^xd}C%t zp*7i(DJdx}h@s*JaYT7#f_?;G%%4l%zI2v`n`HtXVEFi)S0i(NKp~liiaec7*bCn; z-V*QPqBJ~BKDCSw-V;QfS{9($<0W=9G3DQgB zmlSVOXQ$j_sz7Pa*&=DQyt?4*U<2K8Wx;pIGa4!^bZX2Aum3o`j`we;OZGxRF>rxj zXRv!B+h$MCFv}`#Z_#CTs1wuFs4%jRN<^-Fa`=gIfiq}dbBJup!j+gy3y(*rJcjcy zYqj`*r(hzKM}lhLgpSWltPIha57!K+7diu7sl3nwO_Dl<@b1jmy*q%n_?r-u~n zrMuiSjwv=}X^rYat!1)nW0XDL1*OtcvwCrB8S*)*XHp(0uOpw%Iuysrh%$&opxmS# z?m#B9*Tg<3;N=Am-!Hz=IS>DE4Rp3i{+rHz0^u-%&MZF_LLV22YF0I~%SlW`JrZn6 zCd7B}h|;d3x?ep>vFv*GSIIEAX7q3~^vaocwR2kPwN(T9G8ezt>`jT;O zsrQFOA(0}Ss?3+mYDGliP|b<%Rz)(x^Pa}>x-up3I=PYL%-kzR!@?kb0m!2c9wOKEpkc??#n<(Qbo?89<5`6P%yXl}`2;q8FvREEzS~P1v%CDMO<51O3T<;{ z@$g!OGL7lb>q4^=X*_Z-`6cbL45JdMvKaThj`gYp+cmVK>EiA~>~i`tbs2N|zP_>- zzQ#9|EBCqKf;;m)-3~~~-IgHM6j&|ejyR_O^^I9QN%}Y$sBccPWJ)!4oq|fDT#=dJ z(yC$^v&ol+3v|@t8Y+B`C@g_-J^_~%o}DKW^^73O-iqI8Dw+|IKGlt#vIG}JlCqH-S~#FvSbAccub1$UY}jJ=)_R))Xm&8}KsLp|_(e%jNNE6YicCUwHX9%JvG z0&9fExj5puUK<=iVDT{CxeHkxHqh%*eSok?#Ra8$0DW}?k+%%p6v0QFrO5(c5$?N^ z;%S`N@&cBWedv!*POf3D*8kR^7aLVC`wV&b?AXB97#5xRQy?>0(m@VtBfR2d$LF;c ziif7uEW4&B2ldMc_B9tu2}mK!8qKlffh<>`eaqAwV1FOir>LJS{!Ywu6Ec^F7fDL# zP?s+K6U?=jAvzL4FlVK^t)L}k- zHGXqWc3QVT#iKZw*B_<}eUES=h+nA8cX1dB|%=?j39_c@J3ok`b!vwq|HH@ON`p0Ub zqgIDES%!3F#D&|EAF}ABQ5wI~?`XJSoApEeg%o&g$?-H|O#;x9+qcP?SF;owQBB1#42KZ5nlEB8kWgdKCdwXB7Nwin!?RJg9O_wdfL#ekUs2`0&&z;{qf@9@I zgdb5_+e-3}wfOohCI6I6b~$Npu@=lFXZ93j4op`kOSqW%;0v5@Lpb@NKa9DDlzLC{ z+uIj<`T)1Y=Wn$)7dR#eDAH-h*!sF`Jo5m^rowllON2A^vaoqc>F$U+A3 z5N6wK7x>MUR@ChJR|!$fJJkgc1W2Tyny2fZrJ(77EkE{SiSR63<_t3gqXHhf52NTHa=5_GDC)GRTsiK^^7dqrFYu2FBbmu1=8`EC(SW^t zLrIbFxM^`ugtSQ;e$L?}&x4bBZ^ELCIa06IscFLSn@RJiCe#M{gqnjzFZxaKEvB5O zxfsT2n06F0S*wXhZp7r%(;DWl{tZa@R>y^Lpb>;Yy@i&lJG60_rt3^OlavcE^@6XX zaKih~j40Q1D#q-#6p@~6U&Az@+h(SMtn(2rX5N{h7x}WO0^GedH751xgnKUmH%Fgj zK9|2liWzXoS#DqL;~yPf2rKo6Ab?Vw?6PLGE$StOs&i_Z%N zbOEBV%6au~NGeEYa%O{EPM!XFeqh3o62=NUUKM0*f^_gHDky0;-esg@l7?(16DG3S zl3jpX$gpGZLKzr)hu2Uf4(RR!DP(siL!ZqCA0ND>L@!F&{Ye0`hi*$y{~6}?Huy)y zKrnT*VO(j`Cq6|b6@w*yBPtG%J^bSpyo4Y{s_q?#eNG&;W{QR2IW3Cby~P{*d(K=; z)KOqig^{9QTMFS47%yoyAH~H2KCP`o0{OI{Xc4;c;0*9Y^bB;wFL^8dhhSm?Dv|mb z%MGnZu&PTcCL_%xaFdo^e#z%iT;P$G_s^(AgjrEqDZhTH7Dvgbp=GNCa?*V)G?lB+ zDw%JRlTFRdLdjAHd3#!cFy)fp&Vw_+YKMFXfjsAb`yf4&f67SN!^n{(mBt}o3`s#8 zl{e&}g9}W1F-bm#uRk+a5$=M+9YmRFBVjUp3Rl$}8A;$EGkO$%PnN2ip^UK9Y3ohd z;F|lNPS0#H1I@%lK|$>;kEvrJB|xn)1x5NQEI_E2_}@Nmc`MnQiG%Tr(8NO`UEqk< zQIQciP%n~QMQ~rRbMQi@Zls3kLsoK%-b|t`D90*EBq=TY4n^785(Oz1k>KQpLl+oRbHx|0nIa+)aVwZ=nD5;+ z_-Bw0&mmG?>^48DuEbm-U*x_8F!(WTFyGKM!_WLdwiHnjE^s;?<|D}|!zvM=RmJz2 zE|;7yX>ZFXoz6r$2x5hOP1)lx8aK!|;F_Jz%R%d()CtH;wzrw^^T23nxD7$;igrb( z!eqph?7hG7T;nrrY2ds4Et&AVm{LodUo;IEW-{_hw;3yAIpI8@8LjiOAX6lbQ;Oa9 z$QTRW;@CvZO(ykHQH(V-4y7!@Oj7}=CrkswKkUuQ8z@DsX!!3R7$eXM>kmB-;Ey^IbdFD`}Q5 z!8$>%=<;xTqjO=+o~;D|`GDfoD#4IOx;!|pwd7IcYl|m5?+|$xM>xwPQ{BBNsLkfA(_v6kZ_2F{!7Nc?Kx556JhjT<6&ZJElhDkIn^ z&7M(-ik|z9W?km4_H~R zxm^kP{ya61gUOC%>k?Az>Vly?Lkk^^E$+%T(>`wn*=Q%*kD#gTSOF@l6${OpSAAF$YEnJ1NJ>loTKG19)=snX1gKmH)+ zeo_8pi>YoxXPNhi5p&P3dG>?$b5%vfo#Sc3Ov#Iad$ae|hZL<(hyYGWk>1J4Q7nZb zd6ZJrrO{g7RX`f$8&djk;AE2)bI&iwLU%~}F6o7k(g!QRSMJ(}mfMm!t5 zN0}~&%fjSO_Ta93ZoKJ<`_jSsG9Pl&&VC;4po4_)ebRBiv%a3BTIdRWpCX(xr9Msn zC8RX)XX62NFY%hOtDr{#_8vWjcO!XA2#fqQ<PQ>6wIvE8nzckxtO9)Euy^{n8pi)#MG(k{6r1xF}K`BX8P^1Nes30I! z5JZa7q$(gq5TqlZq4)2{^S)=l@43!C-`?N({(bm|T$#ztnl)?6x@TtHFm`uV{D7Vy z1MUJXQr5aNi8bBPdENWrT2OOx8oP4Ab!iA0iQQa7i(qy$1Y=2*H-ENin0UJf#@Av_+WK0$z(X~Z9X59nHCZFegOwX?DcMm0xtf8;-N}v|Tu+2@YLI^#_cE3Yo*4d8zlDHgL^bRZqk0c2~MKB!U_35BS=Z_2pRIoic zfqnfbSeCg@0g6jR!ElCSAbj`^Ho-pfF^-&e4e0x;Nl}D6?0JrSdV-_NB^PaEBE!p_ z&46|jQA+x4A|CS3^6-=ze612lrSrq!G`-R(!Yyw^Pw~`WdDS@F9KmUf2ms)QYi{JlslekfKvdg0G34* zJSN-{aY3KjcB}8Nqv}EX&rcrXvpOp7eFO1E_AE30F}31@gtZeyj~F{D05ebfDLwEb zJNX=X7!2{NE*;mFWWk>uDIvlS36G3WG%mctoksISp~c_=Cj-$q7_YH231-QR_vzu2 zf|WqJPZcp0^>oeJ?<2dgdz6d7N27yyH-yK9I9LxJiIlPF*7gXUwPgm^-L}7aze5j_ ze&2MMEYoJxF?Wj^x*@Xi?ZBh(owXIzAI4tSkb-atyDwb=we=capI$*82SwVu zh1YvQyj_g13$Hn^%RXFlz<}SmNf)Cgkpb67G86YwJ8(&8UyF5y0p&Isv<95aI-^H+ z-IpDRVT8e9>rf}`H|1_F{BzuR>9QxJ`&mifIS=fwrOQ0^Q5J?NVG9j0P{k-e@cxGZG0)IgA1x8L_EQCq`T~i zc4vTug!}WSX@J2?mS8VK%dTeKT4$DooO8eiBqSK2LoI5Rk+P?PQODBth@X2S4n?$; zp`E}3*S-thX5qGMV3miwf3fmFs?h}lzNz<1@#vCLehU`-=Mu6My{}YkJ#^&j{Rcif z3?GwaehMGtE66O-f}043N52lpe3je=A(Wy+!F92?@dgmOiPaZkb`5(x8*Yu55tXEJ zit0~j;jw^zR9+d9z5EFG$#iQEeovVs?y~Nkxn>v%@dc0S zcND3D2)QmxqTMy1kp9JN>|Ll|51|M>!svb?^a>Bp<%Y{hLZWFQ6hw7SWS2o3#*Pb~ zn`hCHi2+3l5Cm^wloP=44I4HvH+kjh5EK5;GV%gMH#ngILErOo5;+WyIyO%zFZ_~l z>|kFBg3o@3Kqe~O;6mS|?_bL4ooH4K%-tUB6*zF0^=5WoNZb2_%hMnt0>0&GOkd!3 z-o=(GMeR4=IX#W!Hp^C7E zkI}xT-l3tohIP_@s^6iNrN-qyQUY_e1oG3@OIU$FsbPW~ay7;dCdnFe%=nFgS6}5y?zR%-hbHT9IF1@=638opZ zA>U7Ql~E|bHnKRAu%%Fwwp0!itV&V4Nu(IADxt&><$!1@(9uWLVH1kbD#&as?;CZ( zBM{5Q-eKED$4=Iprv+U=7jbXN!y~|*BU0y}etmw>id2$*fk_k{lV_RQMekb?Bo^42 zH$t5@hQR!E&7;E+;sj0}DN#e-P5; zB%FYI}On{^Y7;EXeR&_mca;i^Xm405d0orryZ)&i71Z&oTx0K z%QT}5Qp@32#y(hodkORsOD)2XEb+1s^j$^QqU=%9fmrM={nCvJuN{-4^CQ4Ks*G1M z9MpiKkj9i4H;tzW^yCC5ySv?m9d@!(RKJ-S=(yJ>Y$=Z_a42BAX9|>+(!>S${ZCjk_3@-671oWkX zY|-vchKVG?#C^q(t%J|DuFT^jsD-D~IE^~QAgKnhU&}hkY!r{keLPLFSm$Ld?enA+ zqiNr{Ryq{*j7ZzgCZ$J5G_%)6e6!=ts}beg&by1M*NqyJQ3;3n!>BK-39{sfuOhhj>E% zUBN9mCnuY96rYXmah2s)-Z;z9rNS@uSwVydm`X_Qu6x{{uRFrpU3 zJy`F04f*R;HIFe0nXK@}3V=Ge9u+MYmpD&DbneCk#I}Je^ikT#u4`@B1bG4;%YKV+ zkmaT~^d<}+D3&g#$Bgn|7}bGi!L5*OC_WM1WHI>*m!y&B?p-it8~0yuI*0wW$cf{H zFGC2jHt9JXH%WRkm#u&xUR-?{g0&w+*g)Z*i}h?oTpr+_f_z0(Q0#=yq~~y@=RO~1 z+q8RrJ`)uF5)-hFrU5J8j7L5cdoad0P~HL#zoT(C;zc?qju>1r31UD-q&x;+E4U~j zfkbRv*~Tn&=tRV1pHaAodVoy>KNU$sKH@OP-hP-qwxFE;F^LtYgX@8JK?uoO0Z}p41Z!owqV6Eb_hd;{u-xo zZBHJWW9MfVF0uVESaPOu`AuIpk_&A{Q5jDsxCKS6-4SeVCo!;NN|KQx&tV$+Og*o3 zZUyac%FzmW3R7W9uu3UBgc6b{#x3Gco3=!VQp9JK|4_v7>WM4v%Md)X&QgTVkLz8~EzekRW?)~cY zPQ5X}kYA9fFmAU$Qf~V1{kW^g2neTVDM86}sVs0t$Ndjh=Qd>>9?uHYb~U%H^56xQ z10^A9P23pL2xrsKI#2a7nZr=yBDU8yP;5PqvttaEN`1cSMscfk-teA|jo~n=9PU~Y zd~8LxrKOW)xNm~pe&LJkTX^P7eLes?=DN|Kvu_v!e-}7|%AINtL{T6%-c3_ZB)1=k zL&gdt1FgfOnUJrygDh%xYV|qB#}TIv`Kf3A;h=h_)<6gmp6B7gVy81a9xZXEnzWi@#*!UrdcEWb_`hVr$+72(n?H2i2j>^)rV>A+fafcNfm- z)7gg=LF+`*WU;Kv85b}AiDjoXzdM) zP0}EPJ8wLHf(O3V?O}0xP;uBPW zUiH}|63^22Dq5%Z{JB^x+fT43 zzyjtskZc4soM(5>0MU`QEPTF2pJXD>7Uzo*KA{jUINZ}%4xz4R&;I~mi!uiw)e{ra z1=vKn0`6C9$XB`CvzJ~xpTWC`44qzjq3%m)mJYX8x|M_qRIVHR_(B8Teo$^{S0`=`63< z?0Mm(#7DH)J##_n`v8`5=ZzZBo>Y9mmn;Jk`%H{h5a8U(3%z>g^hbH1obU8!czGyN z&*m6@0T}{ zYT3u){JpkMDB;10%JcL~RQNq7oqMUjJKsb2%#-emS%~yi;&t#&aH|^7_@D^r&veM< zwTAO!7B@`wfHf05K1}BYRZYQhVfR4-k>Dxz)fcjc4>?34q47y5ju|6hDLn{zv+JW( z4|^{%X2E??4;uX114wql{sIpQLALpeX)GgNq~#{7A~lyoEkq|#mtb0be2Pdu$SS?D zUM`(*XVUTq>YmqXoq(%vG#h$;kQdsTkf3dEggV7Pej)-o+INKt#MLkG8uP(!k`N?5 zsMmENv9_rkb=>vYdJHyMEj>8VE>{KD_ z`Ppn2n1azAs5|Eyh#zP2#9r1A=Zq0{@(l-;754$5)0K{F^}o!VoC+45SwkN|6!fua zAS!YTrSXQy#&4D5%SvkNz}CZ`1PZmlwm}rkQAY}}6kJ5RpB@?Lt+Y>O$asJAIQ)wI z4ECFLF*ANm5)X#|B5)dFDMLN=&afS6>7HfztSJvOoUIm_N$S{FUP*(GT|f`fcba0K z0)ylaE`2k@&3ZCf*h0kXUWWvG66(Z#I1Nnx&?xiO)QTUnCqAqU5&#f}X0XnfW`RIp zwu@*dHU)oicBnWE+vYL+exXtIMnwRC0hCa{`q-vp8MCY`2(+CyVeA0zp@B5KQCXAg zBAqEE0P$0)W;A9uhyuS<(m++6|iA!dC0kg`WU=$(snco;uK8PwORGbD?ox_9v|?kjQz zgboU9yxqewBZKaK2jYc^ zQzv{$KO+@)1~B6sK{9YVLrJa(#zxRGOhKK37#R+gEdYptwMDojL`d6WC*BNln#S<3 z9b7$u@|n93@Gj7$0@0n4hU5rO1jzwu$nbKC`?+$PeMJjBSU9XnLaP^W+K;c{7VW#S zR}mz;$^sCi0)g5p-!ZmB%o3^_m^wvf_tPF_1W*>}%u+=P^JQEiq`E-e?N9y~2|$-} zuy&QeNDZ5KXNIbLv7n5UL5C*HYGzP$QniI-A4LvEe#R~GbRuVS0sfFv*Lu`uXoo5J ze!YDKnLbnm04_cf7z#E3jW#7G&f#X&2}P*M;V+=jpNUvoWWcp4NtZ2)j7Apx2&TTM zn~RW%i>3!{_-^kQ@5p<@%@SbVqt7zYyYmxRHd53(28{A#FYlc|5Gi+G|USH${2ybtHgD=Bi z)*7-8lU!ZuelW?0`u#_8Am)w;9u=1TC^)bd`eu zin@00O?ZY*uL*oJOH{Y#73Nn6qypNe-F0Ue7tm>9i>2Jl2Qq3b={z^o;edvom4|FW z3)m_XnHb6tHV`tbI0YA-0cMXmOmfo$tS|RkB32uK-d;LjC(x)943QypDdF1#DP1@< zy@p%FN7P%_Cpe*KAGm`TQ(ZR`emk(^TBrcDN&St`ttYrSPp4717Ot|%tUlTRT9Z6m zguaY@y?i4I?SQqbmPKwehE8zKaBY!c9%nnek(y)YXYy_X!v{1NdQ0sRR%UriRFa7S zkT+;4iX-JJqD>kabxqe0d$fIo29ZB8y#bSnXZ*HnO?Ha!blfm8Ll@P3Ll3T_DP|!C@ zauYB=g7&PjQuhJpJtPZPwhx~)f+zzmv9A{j#HvBo58&@LtWifT{kmPRFdyO#uVVu+ z-�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 literal 0 HcmV?d00001 diff --git a/docs/cheatsheets/README.rst b/docs/cheatsheets/README.rst new file mode 100644 index 000000000..173dcc956 --- /dev/null +++ b/docs/cheatsheets/README.rst @@ -0,0 +1,9 @@ + +Open Data Cube Cheatsheets +========================== + +This directory contains useful "cheatsheets" designed to make it easier to get started with Open Data Cube, including demonstrating how to perform common analysis tasks including loading data, data preparation, plotting and exporting, and geospatial manipulation. + +Can be modified via `Google Docs`_. + +.. _`Google Docs`: https://docs.google.com/presentation/d/1aSk2JSK1uGuGdZQQDYhqN0b5bY7wSgIIApLG6k229QA/edit?usp=sharing diff --git a/docs/data-access-analysis/index.rst b/docs/data-access-analysis/index.rst index ca0070f6e..6ffce76d0 100644 --- a/docs/data-access-analysis/index.rst +++ b/docs/data-access-analysis/index.rst @@ -13,6 +13,16 @@ The Sandbox prepares a JupyterLab environment for you. All necessary software is Alternatively, you can also use the `Cube In a Box`_ to setup an Open Data Cube instance locally using Docker. This instance can be configured to index a selection Sentinel-2 level 2 data. +Open Data Cube Cheatsheet +######################### + +To make it easier to get started with Open Data Cube, the following reference poster demonstrates how to perform common analysis tasks including loading data, data preparation, plotting and exporting, and geospatial manipulation: + +.. image:: ../cheatsheets/ODC_Cheatsheet.jpg + :target: https://raw.githubusercontent.com/opendatacube/datacube-core/develop/docs/cheatsheets/ODC_Cheatsheet.pdf + :alt: ODC Cheatsheet + :width: 400px + .. _`Digital Earth Australia Sandbox`: https://www.dea.ga.gov.au/developers/sandbox .. _`Digital Earth Africa Sandbox`: https://sandbox.digitalearth.africa/ diff --git a/wordlist.txt b/wordlist.txt index 696310899..12e238e1a 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -25,6 +25,7 @@ ApplyMask ARD ard arg +aSk au australia autobuild @@ -52,10 +53,14 @@ boto BoundingBox brazil Bugfixes +bY carrotandcompany cbk cd CEOS +cheatsheet +Cheatsheet +cheatsheets CircleCI CLI cli @@ -231,6 +236,7 @@ jupyter JupyterLab Juypter juypter +JSK KeyboardInterrupt Kirill Kubernetes @@ -336,6 +342,7 @@ pandoc param params pc +pdf petewa pgadmin pgintegration @@ -364,6 +371,7 @@ provence psql pts pwd +px py pydata pyenv @@ -458,6 +466,7 @@ ToFloat txt typechecking ubuntu +uGuGdZQQDYhqN UI ui uk @@ -481,6 +490,7 @@ utils UUID uuid UUIDs +usp ValueError VDI vdi @@ -500,6 +510,7 @@ WGS WKT WMS WPS +wSgIIApLG www XArray xarray From ca4a80c7e9a079ab88b53fdb2ef38389e1b4c95f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:56:42 +1100 Subject: [PATCH 107/153] Bump codecov/codecov-action from 3 to 4 (#1542) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 483719ea0..33dc068dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -175,7 +175,7 @@ jobs: TWINE_PASSWORD: ${{ secrets.PyPiToken }} - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.xml fail_ci_if_error: false From a42ff5691ee909ca87168adfdd1772e6aa07551a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:01:44 +0000 Subject: [PATCH 108/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adrienverge/yamllint.git: v1.33.0 → v1.34.0](https://github.com/adrienverge/yamllint.git/compare/v1.33.0...v1.34.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f102e6d3..19a5c02d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/adrienverge/yamllint.git - rev: v1.33.0 + rev: v1.34.0 hooks: - id: yamllint - repo: https://github.com/pre-commit/pre-commit-hooks From 2be67df989aa39c5c09fe0a44a42322ba7d0ac22 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:07:34 +0000 Subject: [PATCH 109/153] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adrienverge/yamllint.git: v1.34.0 → v1.35.1](https://github.com/adrienverge/yamllint.git/compare/v1.34.0...v1.35.1) - [github.com/Lucas-C/pre-commit-hooks: v1.5.4 → v1.5.5](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.5.4...v1.5.5) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19a5c02d4..78344a62c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/adrienverge/yamllint.git - rev: v1.34.0 + rev: v1.35.1 hooks: - id: yamllint - repo: https://github.com/pre-commit/pre-commit-hooks @@ -23,7 +23,7 @@ repos: hooks: - id: pylint - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 + rev: v1.5.5 hooks: - id: forbid-crlf - id: remove-crlf From bfb84171559e75da792b53074e79e38dcdc3107b Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 21 Feb 2024 14:38:15 +1100 Subject: [PATCH 110/153] Fix codecov action and update whats_new.rst --- .github/workflows/main.yml | 1 + docs/about/whats_new.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 33dc068dc..69235df1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -179,3 +179,4 @@ jobs: with: file: ./coverage.xml fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 58550dca5..f8ecdc76a 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - Update github-Dockerhub credential-passing mechanism. (:pull:`1528`) - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) +- Fix broken codecov github action. (:pull:`1554`) v1.8.17 (8th November 2023) =========================== From 4ac020f6bb62136dec692b195c33d988e8216726 Mon Sep 17 00:00:00 2001 From: Benjamin Glitsos <144193171+benji-glitsos-ga@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:07:18 +1100 Subject: [PATCH 111/153] Updating all Knowledge Hub links in entire repo (#1559) Co-authored-by: Benjamin Glitsos --- docs/about-core-concepts/existing-deployments.rst | 8 ++++---- .../advanced-topics/virtual-products.rst | 2 +- .../tools-exploring-data/using-juypter.rst | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/about-core-concepts/existing-deployments.rst b/docs/about-core-concepts/existing-deployments.rst index 42df01a6a..be004917b 100644 --- a/docs/about-core-concepts/existing-deployments.rst +++ b/docs/about-core-concepts/existing-deployments.rst @@ -7,10 +7,10 @@ If you are using `Digital Earth Australia`_, see the `Digital Earth Australia User Guide`_ for instructions on accessing data on the `NCI`_, `AWS`_ and the `DEA Sandbox`_. .. _`Digital Earth Australia`: https://www.ga.gov.au/dea -.. _`Digital Earth Australia User Guide`: https://docs.dea.ga.gov.au/ -.. _`NCI`: https://docs.dea.ga.gov.au/setup/NCI/README.html -.. _`AWS`: https://docs.dea.ga.gov.au/setup/AWS/data_and_metadata.html -.. _`DEA Sandbox`: https://docs.dea.ga.gov.au/setup/sandbox.html +.. _`Digital Earth Australia User Guide`: https://knowledge.dea.ga.gov.au/ +.. _`NCI`: https://knowledge.dea.ga.gov.au/setup/NCI/README.html +.. _`AWS`: https://knowledge.dea.ga.gov.au/setup/AWS/data_and_metadata.html +.. _`DEA Sandbox`: https://knowledge.dea.ga.gov.au/setup/sandbox.html Digital Earth Africa diff --git a/docs/data-access-analysis/advanced-topics/virtual-products.rst b/docs/data-access-analysis/advanced-topics/virtual-products.rst index a5439f99a..cf9f2b0fb 100644 --- a/docs/data-access-analysis/advanced-topics/virtual-products.rst +++ b/docs/data-access-analysis/advanced-topics/virtual-products.rst @@ -21,7 +21,7 @@ pixel-by-pixel. The source code for virtual products is in the :mod:`datacube.virtual` module. An example notebook using virtual products can be found in the `DEA`_ notebooks collection. -.. _DEA: https://docs.dea.ga.gov.au/notebooks/Frequently_used_code/Virtual_products.html +.. _DEA: https://knowledge.dea.ga.gov.au/notebooks/Frequently_used_code/Virtual_products.html Using virtual products diff --git a/docs/data-access-analysis/tools-exploring-data/using-juypter.rst b/docs/data-access-analysis/tools-exploring-data/using-juypter.rst index 64a9d9dde..b5192115c 100644 --- a/docs/data-access-analysis/tools-exploring-data/using-juypter.rst +++ b/docs/data-access-analysis/tools-exploring-data/using-juypter.rst @@ -23,9 +23,9 @@ Digital Earth Australia Notebooks * `Real world examples`_: More complex workflows demonstrating how Open Data Cube can be used to address real-world challenges .. _`Digital Earth Australia Notebooks`: https://github.com/GeoscienceAustralia/dea-notebooks/ -.. _`Beginners guide`: https://docs.dea.ga.gov.au/notebooks/Beginners_guide/README.html -.. _`Frequently used code`: https://docs.dea.ga.gov.au/notebooks/Frequently_used_code/README.html -.. _`Real world examples`: https://docs.dea.ga.gov.au/notebooks/Real_world_examples/README.html +.. _`Beginners guide`: https://knowledge.dea.ga.gov.au/notebooks/Beginners_guide/README.html +.. _`Frequently used code`: https://knowledge.dea.ga.gov.au/notebooks/Frequently_used_code/README.html +.. _`Real world examples`: https://knowledge.dea.ga.gov.au/notebooks/Real_world_examples/README.html .. _Jupyter Notebooks: https://jupyter.org/ From c9c92f829691c35958f5346315bbb59d7738fbe2 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 7 Mar 2024 03:12:06 +0000 Subject: [PATCH 112/153] refine _time_to_search_dims logic so that behaviour when time is passed as an int/float is the same as when it is a string --- datacube/api/query.py | 35 ++++++----------------------------- tests/api/test_query.py | 4 +++- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index a81701ab6..115c592f6 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -14,7 +14,6 @@ from typing import Optional, Union import pandas -from dateutil import tz from pandas import to_datetime as pandas_to_datetime import numpy as np @@ -126,11 +125,7 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc if 'time' not in self.search: time_coord = like.coords.get('time') if time_coord is not None: - self.search['time'] = _time_to_search_dims( - (pandas_to_datetime(time_coord.values[0]).to_pydatetime(), - pandas_to_datetime(time_coord.values[-1]).to_pydatetime() - + datetime.timedelta(milliseconds=1)) # TODO: inclusive time searches - ) + self.search['time'] = _time_to_search_dims((time_coord.values[0], time_coord.values[-1])) @property def search_terms(self): @@ -304,23 +299,6 @@ def _values_to_search(**kwargs): return search -def _to_datetime(t): - if isinstance(t, (float, int)): - t = datetime.datetime.fromtimestamp(t, tz=tz.tzutc()) - - if isinstance(t, tuple): - t = datetime.datetime(*t, tzinfo=tz.tzutc()) - elif isinstance(t, str): - try: - t = datetime.datetime.strptime(t, "%Y-%m-%dT%H:%M:%S.%fZ") - except ValueError: - pass - elif isinstance(t, datetime.datetime): - return tz_aware(t) - - return pandas_to_datetime(t, utc=True, infer_datetime_format=True).to_pydatetime() - - def _time_to_search_dims(time_range): with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) @@ -343,15 +321,14 @@ def _time_to_search_dims(time_range): tr_end = tr_end.isoformat() if tr_start is None: - tr_start = datetime.datetime.fromtimestamp(0) - start = _to_datetime(tr_start) + start = datetime.datetime.fromtimestamp(0) + else: + start = pandas_to_datetime(str(tr_start)).to_pydatetime() if tr_end is None: tr_end = datetime.datetime.now().strftime("%Y-%m-%d") - end = _to_datetime(pandas.Period(tr_end) - .end_time - .to_pydatetime()) + end = pandas.Period(tr_end).end_time.to_pydatetime() - tr = Range(start, end) + tr = Range(tz_aware(start), tz_aware(end)) if start == end: return tr[0] diff --git a/tests/api/test_query.py b/tests/api/test_query.py index a2ac72276..42cfc431f 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -144,7 +144,9 @@ def format_test(start_out, end_out): ((datetime.date(2008, 1, 1), None), format_test('2008-01-01T00:00:00', datetime.datetime.now().strftime("%Y-%m-%dT23:59:59.999999"))), ((None, '2008'), - format_test(datetime.datetime.fromtimestamp(0).strftime("%Y-%m-%dT%H:%M:%S"), '2008-12-31T23:59:59.999999')) + format_test(datetime.datetime.fromtimestamp(0).strftime("%Y-%m-%dT%H:%M:%S"), '2008-12-31T23:59:59.999999')), + ((2008), + format_test('2008-01-01T00:00:00', '2008-12-31T23:59:59.999999')), ] From 57dccd2e41bbed110f41c914811efe68419220b9 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 7 Mar 2024 03:29:29 +0000 Subject: [PATCH 113/153] update expected tzinfo in query docstring --- datacube/api/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index 115c592f6..6c53117dd 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -68,7 +68,7 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc Use by accessing :attr:`search_terms`: >>> query.search_terms['time'] # doctest: +NORMALIZE_WHITESPACE - Range(begin=datetime.datetime(2001, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), \ + Range(begin=datetime.datetime(2001, 1, 1, 0, 0, tzinfo=tzutc()), \ end=datetime.datetime(2002, 1, 1, 23, 59, 59, 999999, tzinfo=tzutc())) By passing in an ``index``, the search parameters will be validated as existing on the ``product``. From 91cd8f2d7dcfb3ec91fe9251f12258c4dda470e7 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 7 Mar 2024 04:32:02 +0000 Subject: [PATCH 114/153] convert np.datetime64 to datetime.datetime --- datacube/api/query.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index 6c53117dd..b069fd90e 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -125,7 +125,11 @@ def __init__(self, index=None, product=None, geopolygon=None, like=None, **searc if 'time' not in self.search: time_coord = like.coords.get('time') if time_coord is not None: - self.search['time'] = _time_to_search_dims((time_coord.values[0], time_coord.values[-1])) + self.search['time'] = _time_to_search_dims( + # convert from np.datetime64 to datetime.datetime + (pandas_to_datetime(time_coord.values[0]).to_pydatetime(), + pandas_to_datetime(time_coord.values[-1]).to_pydatetime()) + ) @property def search_terms(self): @@ -302,7 +306,6 @@ def _values_to_search(**kwargs): def _time_to_search_dims(time_range): with warnings.catch_warnings(): warnings.simplefilter("ignore", UserWarning) - tr_start, tr_end = time_range, time_range if hasattr(time_range, '__iter__') and not isinstance(time_range, str): From 7c221c38f782156c43d986d7b0a6721ca7302f90 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 7 Mar 2024 04:34:05 +0000 Subject: [PATCH 115/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index f8ecdc76a..243ece996 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -16,6 +16,7 @@ v1.8.next - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) - Fix broken codecov github action. (:pull:`1554`) +- Fix handling of date value as int in Query construction (:pull:`1561`) v1.8.17 (8th November 2023) =========================== From fd5e1163305ed9ad4b650100d13ba2250cb464c6 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 12 Mar 2024 00:42:03 +0000 Subject: [PATCH 116/153] error on int/float, less back and forth dt conversion --- datacube/api/query.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index b069fd90e..d290b8501 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -57,7 +57,7 @@ def __init__(self, group_by_func, dimension, units, sort_key=None, group_key=Non 'source_filter') -class Query(object): +class Query(): def __init__(self, index=None, product=None, geopolygon=None, like=None, **search_terms): """Parses search terms in preparation for querying the Data Cube Index. @@ -315,21 +315,30 @@ def _time_to_search_dims(time_range): tr_start, tr_end = tmp[0], tmp[-1] - # Attempt conversion to isoformat - # allows pandas.Period to handle - # date and datetime objects - if hasattr(tr_start, 'isoformat'): - tr_start = tr_start.isoformat() - if hasattr(tr_end, 'isoformat'): - tr_end = tr_end.isoformat() + if isinstance(tr_start, (int, float)) or isinstance(tr_end, (int, float)): + raise TypeError("Time dimension must be provided as a datetime or a string") if tr_start is None: start = datetime.datetime.fromtimestamp(0) + elif not isinstance(tr_start, datetime.datetime): + # ensure consistency between different datetime types + if hasattr(tr_start, 'isoformat'): + tr_start = tr_start.isoformat() + start = pandas_to_datetime(tr_start).to_pydatetime() else: - start = pandas_to_datetime(str(tr_start)).to_pydatetime() + start = tr_start + if tr_end is None: tr_end = datetime.datetime.now().strftime("%Y-%m-%d") - end = pandas.Period(tr_end).end_time.to_pydatetime() + if not isinstance(tr_end, datetime.datetime): + # Attempt conversion to isoformat + # allows pandas.Period to handle date objects + if hasattr(tr_end, 'isoformat'): + tr_end = tr_end.isoformat() + end = pandas.Period(tr_end).end_time.to_pydatetime() + else: + # if it's already a datetime, no need to extrapolate the period end + end = tr_end tr = Range(tz_aware(start), tz_aware(end)) if start == end: From c03d2419a065cc347282f5a19a45b1bb83ee28ad Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 12 Mar 2024 01:16:48 +0000 Subject: [PATCH 117/153] fix end datetime handling --- datacube/api/query.py | 17 +++++++---------- tests/api/test_query.py | 7 +++++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index d290b8501..b07aa98f1 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -321,7 +321,7 @@ def _time_to_search_dims(time_range): if tr_start is None: start = datetime.datetime.fromtimestamp(0) elif not isinstance(tr_start, datetime.datetime): - # ensure consistency between different datetime types + # convert to datetime.datetime if hasattr(tr_start, 'isoformat'): tr_start = tr_start.isoformat() start = pandas_to_datetime(tr_start).to_pydatetime() @@ -330,15 +330,12 @@ def _time_to_search_dims(time_range): if tr_end is None: tr_end = datetime.datetime.now().strftime("%Y-%m-%d") - if not isinstance(tr_end, datetime.datetime): - # Attempt conversion to isoformat - # allows pandas.Period to handle date objects - if hasattr(tr_end, 'isoformat'): - tr_end = tr_end.isoformat() - end = pandas.Period(tr_end).end_time.to_pydatetime() - else: - # if it's already a datetime, no need to extrapolate the period end - end = tr_end + # Attempt conversion to isoformat + # allows pandas.Period to handle datetime objects + if hasattr(tr_end, 'isoformat'): + tr_end = tr_end.isoformat() + # get end of period to ensure range is inclusive + end = pandas.Period(tr_end).end_time.to_pydatetime() tr = Range(tz_aware(start), tz_aware(end)) if start == end: diff --git a/tests/api/test_query.py b/tests/api/test_query.py index 42cfc431f..7b6c1e406 100644 --- a/tests/api/test_query.py +++ b/tests/api/test_query.py @@ -145,8 +145,6 @@ def format_test(start_out, end_out): format_test('2008-01-01T00:00:00', datetime.datetime.now().strftime("%Y-%m-%dT23:59:59.999999"))), ((None, '2008'), format_test(datetime.datetime.fromtimestamp(0).strftime("%Y-%m-%dT%H:%M:%S"), '2008-12-31T23:59:59.999999')), - ((2008), - format_test('2008-01-01T00:00:00', '2008-12-31T23:59:59.999999')), ] @@ -157,6 +155,11 @@ def test_time_handling(time_param, expected): assert query.search_terms['time'] == expected +def test_time_handling_int(): + with pytest.raises(TypeError): + Query(time=2008) + + def test_solar_day(): _s = SimpleNamespace ds = _s(center_time=parse_time('1987-05-22 23:07:44.2270250Z'), From 811283e09092f46e0ac2f351a75d45941f604a8b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Tue, 12 Mar 2024 04:50:24 +0000 Subject: [PATCH 118/153] more informative description in whats_new --- docs/about/whats_new.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 243ece996..b083c38a3 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -16,7 +16,8 @@ v1.8.next - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) - Fix broken codecov github action. (:pull:`1554`) -- Fix handling of date value as int in Query construction (:pull:`1561`) +- Throw error if ``time`` dimension is provided as an int or float to Query construction + instead of assuming it to be seconds since epoch (:pull:`1561`) v1.8.17 (8th November 2023) =========================== From 4bf8cb8dc4fcca681665e1e688e810b407034d4d Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Tue, 12 Mar 2024 15:51:38 +1100 Subject: [PATCH 119/153] Update Query constructor syntax Co-authored-by: Damien Ayers --- datacube/api/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacube/api/query.py b/datacube/api/query.py index b07aa98f1..62d9804a4 100644 --- a/datacube/api/query.py +++ b/datacube/api/query.py @@ -57,7 +57,7 @@ def __init__(self, group_by_func, dimension, units, sort_key=None, group_key=Non 'source_filter') -class Query(): +class Query: def __init__(self, index=None, product=None, geopolygon=None, like=None, **search_terms): """Parses search terms in preparation for querying the Data Cube Index. From ba2b37df016ac0ebfcf177e25f00adf24244eb1c Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 13 Mar 2024 05:18:49 +0000 Subject: [PATCH 120/153] add NotType and NotExpression --- datacube/index/abstract.py | 12 ++++++------ datacube/index/fields.py | 14 +++++++++++++- datacube/model/__init__.py | 2 +- datacube/model/_base.py | 3 +++ integration_tests/index/test_config_docs.py | 12 +++++++++--- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 87b671cfe..3654eae0c 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -21,7 +21,7 @@ from datacube.config import LocalConfig from datacube.index.exceptions import TransactionException from datacube.index.fields import Field -from datacube.model import Dataset, MetadataType, Range +from datacube.model import Dataset, MetadataType, Range, NotType from datacube.model import Product from datacube.utils import cached_property, jsonify_document, read_documents, InvalidDocException from datacube.utils.changes import AllowPolicy, Change, Offset, DocumentMismatchError, check_doc_unchanged @@ -374,7 +374,7 @@ def get_all_docs(self) -> Iterable[Mapping[str, Any]]: yield mdt.definition -QueryField = Union[str, float, int, Range, datetime.datetime] +QueryField = Union[str, float, int, Range, datetime.datetime, NotType] QueryDict = Mapping[str, QueryField] @@ -688,7 +688,7 @@ def search(self, **query: QueryField) -> Iterator[Product]: @abstractmethod def search_robust(self, **query: QueryField - ) -> Iterable[Tuple[Product, Mapping[str, QueryField]]]: + ) -> Iterable[Tuple[Product, QueryDict]]: """ Return dataset types that match match-able fields and dict of remaining un-matchable fields. @@ -698,7 +698,7 @@ def search_robust(self, @abstractmethod def search_by_metadata(self, - metadata: Mapping[str, QueryField] + metadata: QueryDict ) -> Iterable[Dataset]: """ Perform a search using arbitrary metadata, returning results as Product objects. @@ -1115,7 +1115,7 @@ def restore_location(self, @abstractmethod def search_by_metadata(self, - metadata: Mapping[str, QueryField] + metadata: QueryDict ) -> Iterable[Dataset]: """ Perform a search using arbitrary metadata, returning results as Dataset objects. @@ -1129,7 +1129,7 @@ def search_by_metadata(self, @abstractmethod def search(self, limit: Optional[int] = None, - source_filter: Optional[Mapping[str, QueryField]] = None, + source_filter: Optional[QueryDict] = None, **query: QueryField) -> Iterable[Dataset]: """ Perform a search, returning results as Dataset objects. diff --git a/datacube/index/fields.py b/datacube/index/fields.py index c67a1115d..557edf4e9 100644 --- a/datacube/index/fields.py +++ b/datacube/index/fields.py @@ -10,7 +10,7 @@ from dateutil.tz import tz from typing import List -from datacube.model import Range +from datacube.model import Range, NotType from datacube.model.fields import Expression, Field __all__ = ['Field', @@ -36,6 +36,16 @@ def evaluate(self, ctx): return any(expr.evaluate(ctx) for expr in self.exprs) +class NotExpression(Expression): + def __init__(self, expr): + super(NotExpression, self).__init__() + self.expr = expr + self.field = expr.field + + def evaluate(self, ctx): + return not self.expr.evaluate(ctx) + + def as_expression(field: Field, value) -> Expression: """ Convert a single field/value to expression, following the "simple" conventions. @@ -44,6 +54,8 @@ def as_expression(field: Field, value) -> Expression: return field.between(value.begin, value.end) elif isinstance(value, list): return OrExpression(*(as_expression(field, val) for val in value)) + elif isinstance(value, NotType): + return NotExpression(as_expression(field, value.value)) # Treat a date (day) as a time range. elif isinstance(value, date) and not isinstance(value, datetime): return as_expression( diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 1880b3345..14a346a47 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -21,7 +21,7 @@ schema_validated, DocReader from datacube.index.eo3 import is_doc_eo3 from .fields import Field, get_dataset_fields -from ._base import Range, ranges_overlap # noqa: F401 +from ._base import Range, ranges_overlap, NotType # noqa: F401 from .eo3 import validate_eo3_compatible_type from deprecat import deprecat diff --git a/datacube/model/_base.py b/datacube/model/_base.py index 103765866..9f5ca5f48 100644 --- a/datacube/model/_base.py +++ b/datacube/model/_base.py @@ -18,3 +18,6 @@ def ranges_overlap(ra: Range, rb: Range) -> bool: if ra.begin <= rb.begin: return ra.end > rb.begin return rb.end > ra.begin + + +NotType = namedtuple('NotType', 'value') diff --git a/integration_tests/index/test_config_docs.py b/integration_tests/index/test_config_docs.py index a5483dc1c..a4913e461 100644 --- a/integration_tests/index/test_config_docs.py +++ b/integration_tests/index/test_config_docs.py @@ -15,7 +15,7 @@ from datacube.index import Index from datacube.index.abstract import default_metadata_type_docs from datacube.model import MetadataType, DatasetType -from datacube.model import Range, Dataset +from datacube.model import Range, NotType, Dataset from datacube.utils import changes from datacube.utils.documents import documents_equal from datacube.testutils import sanitise_doc @@ -447,7 +447,7 @@ def test_filter_types_by_fields(index, wo_eo3_product): assert len(res) == 0 -def test_filter_types_by_search(index, wo_eo3_product): +def test_filter_types_by_search(index, wo_eo3_product, ls8_eo3_product): """ :type ls5_telem_type: datacube.model.DatasetType :type index: datacube.index.Index @@ -456,7 +456,7 @@ def test_filter_types_by_search(index, wo_eo3_product): # No arguments, return all. res = list(index.products.search()) - assert res == [wo_eo3_product] + assert res == [ls8_eo3_product, wo_eo3_product] # Matching fields res = list(index.products.search( @@ -491,6 +491,12 @@ def test_filter_types_by_search(index, wo_eo3_product): )) assert res == [wo_eo3_product] + # Not expression test + res = list(index.products.search( + product_family=NotType("wo"), + )) + assert res == [ls8_eo3_product] + # Mismatching fields res = list(index.products.search( product_family='spam', From b70ee5c90d2892a287363ca39c9ad987496e73e9 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 13 Mar 2024 23:59:52 +0000 Subject: [PATCH 121/153] update whats_new --- docs/about/whats_new.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index f8ecdc76a..ed57ea129 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -16,6 +16,7 @@ v1.8.next - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) - Fix broken codecov github action. (:pull:`1554`) +- Add generic NOT operator and for ODC queries and ``NotType`` wrapper (:pull:`1563`) v1.8.17 (8th November 2023) =========================== From ae4f2f0ad17aa8bb14860130545fa5fee96caf4b Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 14 Mar 2024 00:14:21 +0000 Subject: [PATCH 122/153] update wordlist --- wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wordlist.txt b/wordlist.txt index 12e238e1a..d8abe43b5 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -314,6 +314,7 @@ nodata NodeJS nosignatures NotImplementedError +NotType np NPM npm From 0b550ad6b4aa6bba357e0d8dcc8fb66b5f907702 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 14 Mar 2024 02:08:44 +0000 Subject: [PATCH 123/153] rename NotType to Not --- datacube/index/abstract.py | 4 ++-- datacube/index/fields.py | 4 ++-- datacube/model/__init__.py | 2 +- datacube/model/_base.py | 2 +- docs/about/whats_new.rst | 2 +- integration_tests/index/test_config_docs.py | 4 ++-- wordlist.txt | 1 - 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/datacube/index/abstract.py b/datacube/index/abstract.py index 3654eae0c..f7cc2c65d 100644 --- a/datacube/index/abstract.py +++ b/datacube/index/abstract.py @@ -21,7 +21,7 @@ from datacube.config import LocalConfig from datacube.index.exceptions import TransactionException from datacube.index.fields import Field -from datacube.model import Dataset, MetadataType, Range, NotType +from datacube.model import Dataset, MetadataType, Range, Not from datacube.model import Product from datacube.utils import cached_property, jsonify_document, read_documents, InvalidDocException from datacube.utils.changes import AllowPolicy, Change, Offset, DocumentMismatchError, check_doc_unchanged @@ -374,7 +374,7 @@ def get_all_docs(self) -> Iterable[Mapping[str, Any]]: yield mdt.definition -QueryField = Union[str, float, int, Range, datetime.datetime, NotType] +QueryField = Union[str, float, int, Range, datetime.datetime, Not] QueryDict = Mapping[str, QueryField] diff --git a/datacube/index/fields.py b/datacube/index/fields.py index 557edf4e9..2ee64acec 100644 --- a/datacube/index/fields.py +++ b/datacube/index/fields.py @@ -10,7 +10,7 @@ from dateutil.tz import tz from typing import List -from datacube.model import Range, NotType +from datacube.model import Range, Not from datacube.model.fields import Expression, Field __all__ = ['Field', @@ -54,7 +54,7 @@ def as_expression(field: Field, value) -> Expression: return field.between(value.begin, value.end) elif isinstance(value, list): return OrExpression(*(as_expression(field, val) for val in value)) - elif isinstance(value, NotType): + elif isinstance(value, Not): return NotExpression(as_expression(field, value.value)) # Treat a date (day) as a time range. elif isinstance(value, date) and not isinstance(value, datetime): diff --git a/datacube/model/__init__.py b/datacube/model/__init__.py index 14a346a47..3c9b4ff65 100644 --- a/datacube/model/__init__.py +++ b/datacube/model/__init__.py @@ -21,7 +21,7 @@ schema_validated, DocReader from datacube.index.eo3 import is_doc_eo3 from .fields import Field, get_dataset_fields -from ._base import Range, ranges_overlap, NotType # noqa: F401 +from ._base import Range, ranges_overlap, Not # noqa: F401 from .eo3 import validate_eo3_compatible_type from deprecat import deprecat diff --git a/datacube/model/_base.py b/datacube/model/_base.py index 9f5ca5f48..d1681e8fe 100644 --- a/datacube/model/_base.py +++ b/datacube/model/_base.py @@ -20,4 +20,4 @@ def ranges_overlap(ra: Range, rb: Range) -> bool: return rb.end > ra.begin -NotType = namedtuple('NotType', 'value') +Not = namedtuple('Not', 'value') diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index ed57ea129..0242881c2 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -16,7 +16,7 @@ v1.8.next - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) - Fix broken codecov github action. (:pull:`1554`) -- Add generic NOT operator and for ODC queries and ``NotType`` wrapper (:pull:`1563`) +- Add generic NOT operator and for ODC queries and ``Not`` type wrapper (:pull:`1563`) v1.8.17 (8th November 2023) =========================== diff --git a/integration_tests/index/test_config_docs.py b/integration_tests/index/test_config_docs.py index a4913e461..918c5623d 100644 --- a/integration_tests/index/test_config_docs.py +++ b/integration_tests/index/test_config_docs.py @@ -15,7 +15,7 @@ from datacube.index import Index from datacube.index.abstract import default_metadata_type_docs from datacube.model import MetadataType, DatasetType -from datacube.model import Range, NotType, Dataset +from datacube.model import Range, Not, Dataset from datacube.utils import changes from datacube.utils.documents import documents_equal from datacube.testutils import sanitise_doc @@ -493,7 +493,7 @@ def test_filter_types_by_search(index, wo_eo3_product, ls8_eo3_product): # Not expression test res = list(index.products.search( - product_family=NotType("wo"), + product_family=Not("wo"), )) assert res == [ls8_eo3_product] diff --git a/wordlist.txt b/wordlist.txt index d8abe43b5..12e238e1a 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -314,7 +314,6 @@ nodata NodeJS nosignatures NotImplementedError -NotType np NPM npm From fa58d90c82c5e870a9fb950efbe150f64758607f Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Wed, 27 Mar 2024 10:30:07 +1100 Subject: [PATCH 124/153] Compatibility fix for passing `odc-geo` GeoBoxes to `dc.load(like=...)` (#1551) * Compatibility fix for odc-geo GeoBoxes * Linting, whats new * Update wordlist.txt * Use nicer approach of checking for compat attribute * Formatting * Update test_end_to_end.py * Formatting and missing packages * Actually include new test * Remove incompatible resolution and crs params * Update geobox to match data * Add type check to compat for odc-geo compatibility. Co-authored-by: Kirill Kouzoubov --------- Co-authored-by: Paul Haesler Co-authored-by: Kirill Kouzoubov --- datacube/api/core.py | 7 +++++ docs/about/whats_new.rst | 1 + integration_tests/test_end_to_end.py | 45 ++++++++++++++++++++++++++++ wordlist.txt | 1 + 4 files changed, 54 insertions(+) diff --git a/datacube/api/core.py b/datacube/api/core.py index f638347f3..eedcb1836 100644 --- a/datacube/api/core.py +++ b/datacube/api/core.py @@ -875,9 +875,16 @@ def output_geobox(like=None, output_crs=None, resolution=None, align=None, assert output_crs is None, "'like' and 'output_crs' are not supported together" assert resolution is None, "'like' and 'resolution' are not supported together" assert align is None, "'like' and 'align' are not supported together" + + # If user passes a GeoBox, return as-is if isinstance(like, GeoBox): return like + # For `odc-geo` GeoBox compatibility: use "compat" attribute if + # it exists to convert to a Datacube-style GeoBox + if isinstance(compat := getattr(like, "compat", None), GeoBox): + return compat + return like.geobox if load_hints: diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 320d5ace0..081686d4c 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -15,6 +15,7 @@ v1.8.next - Update github-Dockerhub credential-passing mechanism. (:pull:`1528`) - Tweak ``list_products`` logic for getting crs and resolution values (:pull:`1535`) - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) +- Compatibility fix to allow users to supply ``odc.geo``-style GeoBoxes to ``dc.load(like=...)`` (:pull:`1551`) - Fix broken codecov github action. (:pull:`1554`) - Throw error if ``time`` dimension is provided as an int or float to Query construction instead of assuming it to be seconds since epoch (:pull:`1561`) diff --git a/integration_tests/test_end_to_end.py b/integration_tests/test_end_to_end.py index 79157125c..834a16b42 100644 --- a/integration_tests/test_end_to_end.py +++ b/integration_tests/test_end_to_end.py @@ -7,9 +7,11 @@ import numpy import pytest import rasterio +from affine import Affine from datacube.api.query import query_group_by from datacube.api.core import Datacube +from datacube.utils.geometry import GeoBox from integration_tests.utils import prepare_test_ingestion_configuration @@ -116,6 +118,7 @@ def test_end_to_end(clirunner, index, testdata_dir, ingest_configs, datacube_env check_open_with_dc(index) check_open_with_grid_workflow(index) check_load_via_dss(index) + check_odcgeo_geobox_load(index) def check_open_with_dc(index): @@ -317,3 +320,45 @@ def check_legacy_open(index): xx_lazy = dc.load_data(sources, gbox, mm, dask_chunks={'time': 1}) assert xx_lazy['blue'].data.dask assert xx_lazy.blue[0, :, :].equals(xx.blue[0, :, :]) + + +def check_odcgeo_geobox_load(index): + """ + Test that users can use `dc.load(like=...)` by passing an + `odc-geo`-style GeoBox. + """ + dc = Datacube(index=index) + + # Create mock odc-geo GeoBox + class ODC_geo_geobox: + compat = GeoBox( + 500, 500, Affine(0.002, 0.0, 149.0, 0.0, -0.002, -35.0), "EPSG:4326" + ) + coords = compat.coords + + odc_geo_geobox = ODC_geo_geobox() + + # Load data using .compat method + ds_compat = dc.load( + product="ls5_nbar_albers", + measurements=["blue"], + like=odc_geo_geobox.compat, + ) + assert "blue" in ds_compat.data_vars + + # Load data using odc-geo GeoBox directly + ds_odcgeo = dc.load( + product="ls5_nbar_albers", + measurements=["blue"], + like=odc_geo_geobox, + ) + assert "blue" in ds_odcgeo.data_vars + + # Like behaves differently when time is specified; make sure this works too + ds_odcgeo_time = dc.load( + product="ls5_nbar_albers", + measurements=["blue"], + like=odc_geo_geobox, + time="1992-03-23T23:14:25.500000", + ) + assert "blue" in ds_odcgeo_time.data_vars diff --git a/wordlist.txt b/wordlist.txt index 12e238e1a..013bf0cab 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -170,6 +170,7 @@ geo geobase GeoBox geobox +GeoBoxes GeoboxTiles GEOGCS GeoJSON From ecbab26ef501ceaff4c2d093d7e60b49bc4d764f Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 27 Mar 2024 11:58:38 +1100 Subject: [PATCH 125/153] Update whats_new.rst for 1.8.18 release. (#1568) * Update whats_new.rst for 1.8.18 release. * Update PR number * Add missing PR. --- docs/about/whats_new.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 081686d4c..0d273f264 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,6 +7,10 @@ What's New v1.8.next ========= + +v1.8.18 (27th March 2024) +========================= + - Add dataset cli tool ``find-duplicates`` to identify duplicate indexed datasets (:pull:`1517`) - Make solar_day() timezone aware (:pull:`1521`) - Warn if non-eo3 dataset has eo3 metadata type (:pull:`1523`) @@ -17,9 +21,11 @@ v1.8.next - Add new ODC Cheatsheet reference doc to Data Access & Analysis documentation page (:pull:`1543`) - Compatibility fix to allow users to supply ``odc.geo``-style GeoBoxes to ``dc.load(like=...)`` (:pull:`1551`) - Fix broken codecov github action. (:pull:`1554`) +- Update documentation links to DEA Knowledge Hub (:pull:`1559`) - Throw error if ``time`` dimension is provided as an int or float to Query construction instead of assuming it to be seconds since epoch (:pull:`1561`) - Add generic NOT operator and for ODC queries and ``Not`` type wrapper (:pull:`1563`) +- Update whats_new.rst for release (:pull:`1568`) v1.8.17 (8th November 2023) =========================== From e8f0c8d5c2b7f36fd6ffe04eae45513d07baefc3 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 17 Apr 2024 07:34:47 +0000 Subject: [PATCH 126/153] fix dark styling --- docs/_static/custom.css | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index c759ee20f..581a39cbb 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -105,6 +105,10 @@ padding: 40px; } +.bd-container { + padding-top: var(--pst-header-height); +} + .section p { line-height: 1.8rem; } @@ -185,3 +189,24 @@ dt:target, span.highlighted { max-width: 100%; font-size: 0.9rem; } + +html[data-theme="dark"] { + --pst-color-text-base: var(--pst-color-text-muted); + --pst-color-sidebar-link-active: var(--pst-color-link); +} + +html[data-theme="dark"] .container-xl { + background-color: #3B3B3B; +} + +html[data-theme="dark"] .editthispage a { + color: var(--pst-color-link); +} + +html[data-theme="dark"] .bd-sidebar { + border-right: 3px solid #ffffff1a; +} + +html[data-theme="dark"] .navbar-light>.container-xl { + background-color: var(--pst-color-surface); +} From 41c9668e929c1332e61bf7451d36fc1f9dc908b3 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Wed, 17 Apr 2024 07:39:38 +0000 Subject: [PATCH 127/153] update whats_new --- docs/about/whats_new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 0d273f264..0881b51dc 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,8 @@ What's New v1.8.next ========= +- Update readthedocs stylesheet for dark theme (:pull:`1579`) + v1.8.18 (27th March 2024) ========================= From 807f5e99f6704b3ebe727c1d532d2ae814339bc7 Mon Sep 17 00:00:00 2001 From: Ariana Barzinpour Date: Thu, 18 Apr 2024 01:25:43 +0000 Subject: [PATCH 128/153] update spellcheck --- wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wordlist.txt b/wordlist.txt index 013bf0cab..143661f65 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -445,6 +445,7 @@ stacker stacspec stdlib str +stylesheet subcommands sudo sv From 143859398a8d7a44ce731291de45f589ed311c55 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 28 May 2024 14:12:58 +1000 Subject: [PATCH 129/153] Attempt to get docker image building on a GDAL 3.9 base. --- docker/Dockerfile | 32 ++++++++++++++++-------- docker/constraints.in | 28 ++++++++++++++++----- docker/constraints.txt | 57 ++++++++++++++++++++++++++---------------- docker/nobinary.txt | 6 ++++- 4 files changed, 84 insertions(+), 39 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4428fb135..99e9d640f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,13 @@ ## ## This file is part of the Open Data Cube, see https://opendatacube.org for more information ## -## Copyright (c) 2015-2020 ODC Contributors +## Copyright (c) 2015-2024 ODC Contributors ## SPDX-License-Identifier: Apache-2.0 ## -FROM ghcr.io/osgeo/gdal:ubuntu-small-latest -ARG V_PG=14 -ARG V_PGIS=14-postgis-3 +FROM ghcr.io/osgeo/gdal:ubuntu-small-3.9.0 as builder +FROM ghcr.io/osgeo/gdal:ubuntu-small-3.9.0 +ARG V_PG=16 +ARG V_PGIS=16-postgis-3 # Update and install Ubuntu packages @@ -17,9 +18,11 @@ RUN apt-get update -y \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-change-held-packages --fix-missing --no-install-recommends \ git \ libpq-dev libudunits2-dev libproj-dev \ - python3-dev python3-distutils python3-pip \ + libhdf5-dev libnetcdf-dev libgeos-dev libudunits2-dev \ + python3-dev virtualenv \ build-essential \ postgresql \ + python3-pip \ redis-server \ postgresql-client-${V_PG} \ postgresql-${V_PG} \ @@ -30,6 +33,12 @@ RUN apt-get update -y \ # Build constrained python environment +RUN virtualenv /virtualenv +ENV VIRTUAL_ENV /virtualenv +ENV PATH /virtualenv/bin:$PATH +# Needed to build cf-units wheels. +ENV UDUNITS2_XML_PATH /usr/share/xml/udunits/udunits2-common.xml + # Set the locale, this is required for some of the Python packages ENV LC_ALL C.UTF-8 @@ -37,9 +46,10 @@ COPY docker/constraints.in /conf/requirements.txt COPY docker/constraints.txt docker/nobinary.txt /conf/ -RUN python3 -m pip install \ - -r /conf/requirements.txt \ - -c /conf/constraints.txt +RUN python3 -m pip install --upgrade pip setuptools +RUN python3 -m pip install -r /conf/requirements.txt \ + -c /conf/constraints.txt \ + -c /conf/nobinary.txt # Copy datacube-core source code into container and install from source (with addons for tests). COPY . /code @@ -56,9 +66,9 @@ RUN install --owner postgres --group postgres -D -d /var/run/postgresql /srv/po && sudo -u postgres "$(find /usr/lib/postgresql/ -type f -name initdb)" -D "/srv/postgresql" --auth-host=md5 --encoding=UTF8 # users and groups. -RUN groupadd --gid 1000 odc \ - && useradd --gid 1000 \ - --uid 1000 \ +RUN groupadd --gid 2000 odc \ + && useradd --gid 2000 \ + --uid 2000 \ --create-home \ --shell /bin/bash -N odc \ && adduser odc users \ diff --git a/docker/constraints.in b/docker/constraints.in index b25b22227..fe2b71c2b 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -6,33 +6,49 @@ attrs>=18.1 boto3>1.17.0 bottleneck cachetools +# For Python 3.12 support +cf-units>3.1.1 +# For Python 3.12 support +cffi>=1.16.0 ciso8601 click>=8.0 cloudpickle>=0.4 -compliance-checker>=4.0.0,<5 +# For Python 3.12 support +compliance-checker>=5.1.0 dask>=2021.10.1 distributed>=2021.10.0 fiona geoalchemy2 +# For Python 3.12 support +greenlet>=3.0.3 # New reference resolution API jsonschema>=4.18 lark +# For Python 3.12 support +lxml>=5.0.0 matplotlib moto netcdf4>=1.5.8 -numpy>=1.22.2 +# 1.26.0 is the first version to support Python 3.12 +numpy>=1.26.0 pandas>=2.0 +# For Python 3.12 support +pendulum>=3.0 psycopg2 pycodestyle pylint -pyproj>=2.5 +# For Python 3.12 support +pyproj>=3.5 python-dateutil -pyyaml -rasterio>=1.3.2 +# For Python 3.12 support +pyyaml>=6.0.1 +# For Python 3.12 support +rasterio>=1.3.9 recommonmark redis ruamel.yaml -shapely>=2.0 +# cython3 support +shapely>=2.0.2 sphinx-click sphinx_autodoc_typehints sphinx_rtd_theme diff --git a/docker/constraints.txt b/docker/constraints.txt index 39f1261d0..db97b370d 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --strip-extras constraints.in +# pip-compile --output-file=constraints.txt --strip-extras constraints.in # affine==2.4.0 # via @@ -48,10 +48,14 @@ certifi==2022.12.7 # pyproj # rasterio # requests -cf-units==3.1.1 - # via compliance-checker -cffi==1.15.1 - # via cryptography +cf-units==3.2.0 + # via + # -r constraints.in + # compliance-checker +cffi==1.16.0 + # via + # -r constraints.in + # cryptography cftime==1.6.2 # via # cf-units @@ -86,7 +90,7 @@ cloudpickle==2.2.1 # distributed commonmark==0.9.1 # via recommonmark -compliance-checker==4.3.4 +compliance-checker==5.1.0 # via -r constraints.in contourpy==1.0.7 # via matplotlib @@ -123,8 +127,10 @@ fsspec==2023.1.0 # via dask geoalchemy2==0.13.1 # via -r constraints.in -greenlet==2.0.2 - # via sqlalchemy +greenlet==3.0.3 + # via + # -r constraints.in + # sqlalchemy heapdict==1.0.1 # via zict hypothesis==6.68.1 @@ -176,8 +182,10 @@ locket==1.0.0 # via # distributed # partd -lxml==4.9.2 - # via compliance-checker +lxml==5.0.2 + # via + # -r constraints.in + # compliance-checker markdown-it-py==2.1.0 # via rich markupsafe==2.1.2 @@ -202,7 +210,7 @@ netcdf4==1.6.2 # via # -r constraints.in # compliance-checker -numpy==1.24.2 +numpy==1.26.4 # via # -r constraints.in # bottleneck @@ -234,8 +242,10 @@ pandas==2.1.4 # xarray partd==1.3.0 # via dask -pendulum==2.1.2 - # via compliance-checker +pendulum==3.0.0 + # via + # -r constraints.in + # compliance-checker pillow==9.4.0 # via matplotlib pkginfo==1.9.6 @@ -263,7 +273,7 @@ pyparsing==3.0.9 # via # matplotlib # snuggs -pyproj==3.4.1 +pyproj==3.6.1 # via # -r constraints.in # compliance-checker @@ -287,20 +297,19 @@ python-dateutil==2.8.2 # owslib # pandas # pendulum + # time-machine pytz==2022.7.1 # via # babel # owslib # pandas -pytzdata==2020.1 - # via pendulum -pyyaml==6.0 +pyyaml==6.0.1 # via # -r constraints.in # dask # distributed # owslib -rasterio==1.3.6 +rasterio==1.3.10 # via -r constraints.in readme-renderer==37.3 # via twine @@ -343,8 +352,10 @@ secretstorage==3.3.3 # via keyring setuptools-scm==7.1.0 # via -r constraints.in -shapely==2.0.1 - # via -r constraints.in +shapely==2.0.4 + # via + # -r constraints.in + # compliance-checker six==1.16.0 # via # astroid @@ -393,6 +404,8 @@ sqlalchemy==1.4.46 # geoalchemy2 tblib==1.7.0 # via distributed +time-machine==2.14.1 + # via pendulum toml==0.10.2 # via # -r constraints.in @@ -414,7 +427,9 @@ typing-extensions==4.4.0 # pygeoif # setuptools-scm tzdata==2023.4 - # via pandas + # via + # pandas + # pendulum urllib3==1.26.14 # via # botocore diff --git a/docker/nobinary.txt b/docker/nobinary.txt index fd69f0b5d..91180cabe 100644 --- a/docker/nobinary.txt +++ b/docker/nobinary.txt @@ -1 +1,5 @@ -# Empty for now +# libraries to compile with local gdal +--no-binary rasterio +--no-binary fiona +--no-binary shapely +--no-binary cf-units \ No newline at end of file From 79e863811d7c5ab6bf11953e9daf1625c2173e47 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 04:17:21 +0000 Subject: [PATCH 130/153] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docker/nobinary.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/nobinary.txt b/docker/nobinary.txt index 91180cabe..57b77ce03 100644 --- a/docker/nobinary.txt +++ b/docker/nobinary.txt @@ -2,4 +2,4 @@ --no-binary rasterio --no-binary fiona --no-binary shapely ---no-binary cf-units \ No newline at end of file +--no-binary cf-units From 16ebe4201ff03604afd6b97f62793599d7b15144 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 28 May 2024 14:36:42 +1000 Subject: [PATCH 131/153] Add twine as a test dependency. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 8464f799a..a7a55a212 100755 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ 'beautifulsoup4', 'nbsphinx', 'pydata-sphinx-theme==0.9.0', + 'twine' ] extras_require = { From e054b5399ed24c7091d470eddeaa66cf40ac6606 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Wed, 29 May 2024 09:20:39 +1000 Subject: [PATCH 132/153] Don't just build a virtualenv, make sure it gets activated! --- docker/Dockerfile | 12 ++++++------ docker/assets/with_bootstrap | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 99e9d640f..92238296b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,10 +34,10 @@ RUN apt-get update -y \ # Build constrained python environment RUN virtualenv /virtualenv -ENV VIRTUAL_ENV /virtualenv -ENV PATH /virtualenv/bin:$PATH +ENV PYENV /virtualenv + # Needed to build cf-units wheels. -ENV UDUNITS2_XML_PATH /usr/share/xml/udunits/udunits2-common.xml +ARG UDUNITS2_XML_PATH=/usr/share/xml/udunits/udunits2-common.xml # Set the locale, this is required for some of the Python packages ENV LC_ALL C.UTF-8 @@ -46,15 +46,15 @@ COPY docker/constraints.in /conf/requirements.txt COPY docker/constraints.txt docker/nobinary.txt /conf/ -RUN python3 -m pip install --upgrade pip setuptools -RUN python3 -m pip install -r /conf/requirements.txt \ +RUN . /virtualenv/bin/activate && python3 -m pip install --upgrade pip setuptools +RUN . /virtualenv/bin/activate && python3 -m pip install -r /conf/requirements.txt \ -c /conf/constraints.txt \ -c /conf/nobinary.txt # Copy datacube-core source code into container and install from source (with addons for tests). COPY . /code -RUN python3 -m pip install '/code/[all]' \ +RUN . /virtualenv/bin/activate && python3 -m pip install '/code/[all]' \ && python3 -m pip install /code/examples/io_plugin \ && python3 -m pip install /code/tests/drivers/fail_drivers diff --git a/docker/assets/with_bootstrap b/docker/assets/with_bootstrap index 02bdc7791..52dac006b 100755 --- a/docker/assets/with_bootstrap +++ b/docker/assets/with_bootstrap @@ -6,7 +6,7 @@ launch_db () { local bin=$(find /usr/lib/postgresql/ -type d -name bin) [ -e "${pgdata}/PG_VERSION" ] || { - sudo -u postgres "${bin}/initdb" -D "${pgdata}" --auth-host=md5 --encoding=UTF8 + sudo -u postgres "${bin}/initdb" -A -D "${pgdata}" --auth-host=md5 --encoding=UTF8 } sudo -u postgres "${bin}/pg_ctl" -D "${pgdata}" -l "${pgdata}/pg.log" start @@ -35,6 +35,8 @@ launch_db () { target_uid=$(stat -c '%u' .) target_gid=$(stat -c '%g' .) + echo '$target_uid :: $target_gid' + [[ $target_uid -eq 0 ]] || { # unless gid already matches update gid @@ -79,6 +81,7 @@ env="${PYENV:-/env}" if [ -e "${env}/bin/activate" ]; then [ -n "${VIRTUAL_ENV:-}" ] || { + echo "Activating virtual environment" source "${env}/bin/activate" # if there is no .egg-info then we need to install in From bd525815499ccb67862a039f9d88b5228d855b70 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 30 May 2024 09:16:32 +1000 Subject: [PATCH 133/153] Actually seems to finally be working? --- docker/Dockerfile | 20 ++++++++------------ docker/assets/with_bootstrap | 4 +--- docker/constraints.in | 10 ++++++---- docker/constraints.txt | 20 ++++++++++++-------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 92238296b..1240eb5bf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,8 +33,8 @@ RUN apt-get update -y \ # Build constrained python environment -RUN virtualenv /virtualenv -ENV PYENV /virtualenv +RUN virtualenv /env +ENV PYENV /env # Needed to build cf-units wheels. ARG UDUNITS2_XML_PATH=/usr/share/xml/udunits/udunits2-common.xml @@ -46,15 +46,15 @@ COPY docker/constraints.in /conf/requirements.txt COPY docker/constraints.txt docker/nobinary.txt /conf/ -RUN . /virtualenv/bin/activate && python3 -m pip install --upgrade pip setuptools -RUN . /virtualenv/bin/activate && python3 -m pip install -r /conf/requirements.txt \ +RUN . /env/bin/activate && python3 -m pip install --upgrade pip setuptools +RUN . /env/bin/activate && python3 -m pip install -r /conf/requirements.txt \ -c /conf/constraints.txt \ -c /conf/nobinary.txt # Copy datacube-core source code into container and install from source (with addons for tests). COPY . /code -RUN . /virtualenv/bin/activate && python3 -m pip install '/code/[all]' \ +RUN . /env/bin/activate && python3 -m pip install '/code/[all]' \ && python3 -m pip install /code/examples/io_plugin \ && python3 -m pip install /code/tests/drivers/fail_drivers @@ -66,16 +66,12 @@ RUN install --owner postgres --group postgres -D -d /var/run/postgresql /srv/po && sudo -u postgres "$(find /usr/lib/postgresql/ -type f -name initdb)" -D "/srv/postgresql" --auth-host=md5 --encoding=UTF8 # users and groups. -RUN groupadd --gid 2000 odc \ - && useradd --gid 2000 \ - --uid 2000 \ - --create-home \ - --shell /bin/bash -N odc \ +RUN groupmod ubuntu -n odc \ + && usermod ubuntu -l odc \ && adduser odc users \ && adduser odc sudo \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ - && install -d -o odc -g odc /env \ - && install -d -o odc -g odc /code \ + && chown -R odc:odc /env /code \ && true USER root diff --git a/docker/assets/with_bootstrap b/docker/assets/with_bootstrap index 52dac006b..e2a3a1a51 100755 --- a/docker/assets/with_bootstrap +++ b/docker/assets/with_bootstrap @@ -35,8 +35,6 @@ launch_db () { target_uid=$(stat -c '%u' .) target_gid=$(stat -c '%g' .) - echo '$target_uid :: $target_gid' - [[ $target_uid -eq 0 ]] || { # unless gid already matches update gid @@ -81,7 +79,7 @@ env="${PYENV:-/env}" if [ -e "${env}/bin/activate" ]; then [ -n "${VIRTUAL_ENV:-}" ] || { - echo "Activating virtual environment" + echo "Activating virtual environment" source "${env}/bin/activate" # if there is no .egg-info then we need to install in diff --git a/docker/constraints.in b/docker/constraints.in index fe2b71c2b..1bb415e4c 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -36,7 +36,6 @@ pandas>=2.0 pendulum>=3.0 psycopg2 pycodestyle -pylint # For Python 3.12 support pyproj>=3.5 python-dateutil @@ -44,6 +43,8 @@ python-dateutil pyyaml>=6.0.1 # For Python 3.12 support rasterio>=1.3.9 +# For Python 3.12 support +typing-extensions>=4.7 recommonmark redis ruamel.yaml @@ -54,6 +55,8 @@ sphinx_autodoc_typehints sphinx_rtd_theme sqlalchemy<2.0 toolz +# For Python 3.12 support +wrapt>=1.16.0 xarray>=0.9 # Previous pins were to very old versions @@ -64,9 +67,8 @@ hypothesis>=6 pytest-httpserver pytest-timeout -# every new version finds new errors, so we pin it (so old though!) -pylint==2.4.4 -pycodestyle==2.5.0 +pylint>=3 +pycodestyle # for packaging setuptools>=42 diff --git a/docker/constraints.txt b/docker/constraints.txt index db97b370d..712563e75 100644 --- a/docker/constraints.txt +++ b/docker/constraints.txt @@ -12,7 +12,7 @@ alabaster==0.7.13 # via sphinx antlr4-python3-runtime==4.7.2 # via cf-units -astroid==2.3.3 +astroid==3.2.2 # via pylint async-timeout==4.0.2 # via redis @@ -110,6 +110,8 @@ decorator==5.1.1 # via validators deprecat==2.1.1 # via -r constraints.in +dill==0.3.8 + # via pylint distributed==2023.2.0 # via -r constraints.in docutils==0.18.1 @@ -176,8 +178,6 @@ kiwisolver==1.4.4 # via matplotlib lark==1.1.5 # via -r constraints.in -lazy-object-proxy==1.4.3 - # via astroid locket==1.0.0 # via # distributed @@ -250,6 +250,8 @@ pillow==9.4.0 # via matplotlib pkginfo==1.9.6 # via twine +platformdirs==4.2.2 + # via pylint pluggy==1.0.0 # via pytest psutil==5.9.4 @@ -267,7 +269,7 @@ pygments==2.14.0 # readme-renderer # rich # sphinx -pylint==2.4.4 +pylint==3.2.2 # via -r constraints.in pyparsing==3.0.9 # via @@ -358,7 +360,6 @@ shapely==2.0.4 # compliance-checker six==1.16.0 # via - # astroid # bleach # isodate # munch @@ -410,6 +411,8 @@ toml==0.10.2 # via # -r constraints.in # responses +tomlkit==0.12.5 + # via pylint toolz==0.12.0 # via # -r constraints.in @@ -422,8 +425,9 @@ twine==4.0.2 # via -r constraints.in types-toml==0.10.8.3 # via responses -typing-extensions==4.4.0 +typing-extensions==4.12.0 # via + # -r constraints.in # pygeoif # setuptools-scm tzdata==2023.4 @@ -447,9 +451,9 @@ werkzeug==2.2.2 # pytest-httpserver wheel==0.38.4 # via -r constraints.in -wrapt==1.11.2 +wrapt==1.16.0 # via - # astroid + # -r constraints.in # deprecat xarray==2023.12.0 # via -r constraints.in From 6d3f58fbdd35cc996217eb52ae6a0efcc3286beb Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 30 May 2024 16:45:27 +1000 Subject: [PATCH 134/153] Use ubuntu-full and don't use US/Pacific for some reason? --- docker/Dockerfile | 8 +++++--- integration_tests/conftest.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 1240eb5bf..a7385d070 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,8 +4,9 @@ ## Copyright (c) 2015-2024 ODC Contributors ## SPDX-License-Identifier: Apache-2.0 ## -FROM ghcr.io/osgeo/gdal:ubuntu-small-3.9.0 as builder -FROM ghcr.io/osgeo/gdal:ubuntu-small-3.9.0 +# gdal:ubuntu-small no longer comes with netcdf support compiled into gdal +FROM ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 as builder +FROM ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 ARG V_PG=16 ARG V_PGIS=16-postgis-3 @@ -35,6 +36,7 @@ RUN apt-get update -y \ RUN virtualenv /env ENV PYENV /env +ENV GDAL_CONFIG /usr/bin/gdal-config # Needed to build cf-units wheels. ARG UDUNITS2_XML_PATH=/usr/share/xml/udunits/udunits2-common.xml @@ -71,7 +73,7 @@ RUN groupmod ubuntu -n odc \ && adduser odc users \ && adduser odc sudo \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ - && chown -R odc:odc /env /code \ + && chown -R odc:odc /env \ && true USER root diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 3c0e7e0a7..dbbc309b7 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -525,7 +525,7 @@ def cleanup_db(local_cfg, db): db.close() -@pytest.fixture(params=["US/Pacific", "UTC", ]) +@pytest.fixture(params=["America/Los_Angeles", "UTC", ]) def uninitialised_postgres_db(local_config, request): """ Return a connection to an empty PostgreSQL or PostGIS database From cb80996bf4c3528d30b5e7e9779ab04a8f0a3de7 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 30 May 2024 17:21:05 +1000 Subject: [PATCH 135/153] Remove test that can't work across shapely versions. constrain moto<5 --- datacube/utils/geometry/_base.py | 3 --- docker/constraints.in | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/datacube/utils/geometry/_base.py b/datacube/utils/geometry/_base.py index b115b2889..0753dd466 100644 --- a/datacube/utils/geometry/_base.py +++ b/datacube/utils/geometry/_base.py @@ -853,9 +853,6 @@ def multipoint(coords: CoordList, crs: MaybeCRS) -> Geometry: """ Create a 2D MultiPoint Geometry - >>> multipoint([(10, 10), (20, 20)], None) - Geometry(MULTIPOINT (10 10, 20 20), None) - :param coords: list of x,y coordinate tuples """ return Geometry({'type': 'MultiPoint', 'coordinates': coords}, crs=crs) diff --git a/docker/constraints.in b/docker/constraints.in index 1bb415e4c..4d271e724 100644 --- a/docker/constraints.in +++ b/docker/constraints.in @@ -27,7 +27,7 @@ lark # For Python 3.12 support lxml>=5.0.0 matplotlib -moto +moto<5 netcdf4>=1.5.8 # 1.26.0 is the first version to support Python 3.12 numpy>=1.26.0 From 03e920c5c4fb4aa0937f05142ec6a9f9a95528c6 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Fri, 31 May 2024 09:59:15 +1000 Subject: [PATCH 136/153] Cleanup - revert un-needed changes. --- docker/Dockerfile | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a7385d070..81a81447f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -23,7 +23,6 @@ RUN apt-get update -y \ python3-dev virtualenv \ build-essential \ postgresql \ - python3-pip \ redis-server \ postgresql-client-${V_PG} \ postgresql-${V_PG} \ diff --git a/setup.py b/setup.py index a7a55a212..8464f799a 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,6 @@ 'beautifulsoup4', 'nbsphinx', 'pydata-sphinx-theme==0.9.0', - 'twine' ] extras_require = { From 8b46c2284e13c74739ca16faba67669af0b9cf74 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 4 Jun 2024 12:12:10 +1000 Subject: [PATCH 137/153] Deprecate non-1.9 compatible configuration environment names (#1592) * Deprecate non-1.9 compatible configuration environment names (in warnings and docs) * add localmemory to spellcheck list. * add lintage... * Oops - environment name pattern doesn't quite match that applied in 1.9. --- datacube/config.py | 7 +++++++ docs/about/whats_new.rst | 2 ++ docs/installation/database/setup.rst | 13 +++++-------- integration_tests/conftest.py | 4 ++-- integration_tests/integration.conf | 6 +++--- integration_tests/test_environments.py | 8 ++++---- wordlist.txt | 1 + 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/datacube/config.py b/datacube/config.py index f2b08aba4..48abf61c0 100755 --- a/datacube/config.py +++ b/datacube/config.py @@ -6,6 +6,8 @@ User configuration. """ +import warnings +import re import os from pathlib import Path import configparser @@ -92,6 +94,11 @@ def __init__(self, config: configparser.ConfigParser, if env: if config.has_section(env): self._env = env + if not re.fullmatch(r'^[a-z][a-z0-9]*$', self._env): + warnings.warn(f"Configuration environment names like '{self._env}' are deprecated. " + "From datacube 1.9, environment names must start with a lowercase letter and " + "consist of only lowercase letters and digits.", + category=DeprecationWarning) # All is good return else: diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 0881b51dc..9edafd9f9 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,8 @@ What's New v1.8.next ========= +- Add deprecation warning for config environment names that will not be supported in 1.9 (:pull:`1592`) +- Update docker image to GDAL 3.9/Python 3.12/Ubuntu 24.04 (:pull:`1587`) - Update readthedocs stylesheet for dark theme (:pull:`1579`) v1.8.18 (27th March 2024) diff --git a/docs/installation/database/setup.rst b/docs/installation/database/setup.rst index 4a758fa26..0fefa33f8 100644 --- a/docs/installation/database/setup.rst +++ b/docs/installation/database/setup.rst @@ -48,7 +48,7 @@ Datacube looks for a configuration file in ~/.datacube.conf or in the location s # A blank host will use a local socket. Specify a hostname (such as localhost) to use TCP. db_hostname: - + # Port is optional. The default port is 5432. # db_port: @@ -66,13 +66,14 @@ Datacube looks for a configuration file in ~/.datacube.conf or in the location s # A "null" environment for working with no index. index_driver: null - [local_memory] + [localmemory] # A local non-persistent in-memory index. # Compatible with the default index driver, but resides purely in memory with no persistent database. # Note that each new invocation will receive a new, empty index. index_driver: memory -Uncomment and fill in lines as required. +Uncomment and fill in lines as required. Environment names should start with a lower case letter and contain +only lower case letters and numbers. This will be strictly enforced from 1.9.0. Alternately, you can configure the ODC connection to Postgres using environment variables:: @@ -82,17 +83,13 @@ Alternately, you can configure the ODC connection to Postgres using environment DB_DATABASE To configure a database as a single connection url instead of individual environment variables:: - + export DATACUBE_DB_URL=postgresql://[username]:[password]@[hostname]:[port]/[database] Alternatively, for password-less access to a database on localhost:: export DATACUBE_DB_URL=postgresql:///[database] -Further information on database configuration can be found `here `__. -Although the enhancement proposal details incoming changes in v1.9 and beyond, it should largely be compatible with the current behaviour, barring a few -obscure corner cases. - The desired environment can be specified: 1. in code, with the ``env`` argument to the ``datacube.Datacube`` constructor; diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index dbbc309b7..e527b3c00 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -465,14 +465,14 @@ def local_config_pair(datacube_env_name_pair): def null_config(): """Provides a :class:`LocalConfig` configured with null index driver """ - return LocalConfig.find(CONFIG_FILE_PATHS, env="null_driver") + return LocalConfig.find(CONFIG_FILE_PATHS, env="nulldriver") @pytest.fixture def in_memory_config(): """Provides a :class:`LocalConfig` configured with memory index driver """ - return LocalConfig.find(CONFIG_FILE_PATHS, env="local_memory") + return LocalConfig.find(CONFIG_FILE_PATHS, env="localmemory") @pytest.fixture diff --git a/integration_tests/integration.conf b/integration_tests/integration.conf index 10d0599d1..36c09ebbd 100644 --- a/integration_tests/integration.conf +++ b/integration_tests/integration.conf @@ -8,11 +8,11 @@ db_hostname: db_database: pgisintegration index_driver: postgis -[no_such_driver_env] +[nosuchdriverenv] index_driver: no_such_driver -[null_driver] +[nulldriver] index_driver: null -[local_memory] +[localmemory] index_driver: memory diff --git a/integration_tests/test_environments.py b/integration_tests/test_environments.py index 42e957300..6d609dceb 100644 --- a/integration_tests/test_environments.py +++ b/integration_tests/test_environments.py @@ -19,7 +19,7 @@ def test_multiple_environment_config(tmpdir): [default] db_hostname: db.opendatacube.test -[test_alt] +[testalt] db_hostname: alt-db.opendatacube.test """) @@ -27,7 +27,7 @@ def test_multiple_environment_config(tmpdir): config = LocalConfig.find([config_path]) assert config['db_hostname'] == 'db.opendatacube.test' - alt_config = LocalConfig.find([config_path], env='test_alt') + alt_config = LocalConfig.find([config_path], env='testalt') assert alt_config['db_hostname'] == 'alt-db.opendatacube.test' # Make sure the correct config is passed through the API @@ -42,12 +42,12 @@ def test_multiple_environment_config(tmpdir): with Datacube(config=str(config_path), validate_connection=False) as dc: assert str(dc.index.url) == db_url # When specific environment is loaded - with Datacube(config=config_path, env='test_alt', validate_connection=False) as dc: + with Datacube(config=config_path, env='testalt', validate_connection=False) as dc: assert str(dc.index.url) == alt_db_url # An environment that isn't in any config files with pytest.raises(ValueError): - with Datacube(config=config_path, env='undefined-env', validate_connection=False) as dc: + with Datacube(config=config_path, env='undefinedenv', validate_connection=False) as dc: pass diff --git a/wordlist.txt b/wordlist.txt index 143661f65..aeeea4c7b 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -261,6 +261,7 @@ libyaml linux literalinclude localhost +localmemory lon lonlat lr From 238c381d15efa6e65aab498f3baa57072abe64e9 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Thu, 20 Jun 2024 16:00:31 +1000 Subject: [PATCH 138/153] Default floating point bands to nodata=nan when writing COGs. (#1602) * Update test_extra_dimensions to work with numpy 1.x and numpy 2.x * Behaviour of num2numpy function changes between numpy 1.x and numpy 2.x * Pin moto<5 to delay rewriting of tests. * Default float bands to nodata=nan when writing cogs. * Test float bands default to nodata=nan when writing cogs. * Update whats_new.rst * Lintage and incorporate Dependabot's #1600. * Add ESRI to wordlist.txt (roll eyes). --- .github/workflows/main.yml | 4 ++-- datacube/testutils/__init__.py | 11 ++++++----- datacube/utils/cog.py | 4 ++++ datacube/utils/math.py | 1 + docs/about/whats_new.rst | 1 + setup.py | 2 +- tests/test_3d.py | 19 +++++++++++++++---- tests/test_utils_cog.py | 19 +++++++++++++++++-- tests/test_utils_other.py | 7 ++++++- wordlist.txt | 1 + 10 files changed, 54 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 69235df1c..37a245a09 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,7 +76,7 @@ jobs: password: ${{ secrets.DOCKERHUBPASSWD }} - name: Build Docker - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: docker/Dockerfile context: . @@ -108,7 +108,7 @@ jobs: github.event_name == 'push' && github.ref == 'refs/heads/develop' && steps.changes.outputs.docker == 'true' - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: file: docker/Dockerfile context: . diff --git a/datacube/testutils/__init__.py b/datacube/testutils/__init__.py index 29637521b..91755a883 100644 --- a/datacube/testutils/__init__.py +++ b/datacube/testutils/__init__.py @@ -415,11 +415,12 @@ def gen_tiff_dataset(bands, **kwargs) gbox = meta.gbox - - mm.append(dict(name=name, - path=fname, - layer=1, - dtype=meta.dtype)) + bdict = dict(name=name, + path=fname, + layer=1, + nodata=band.nodata, + dtype=meta.dtype) + mm.append(bdict) uri = Path(base_folder_of_record/'metadata.yaml').absolute().as_uri() ds = mk_sample_dataset(mm, diff --git a/datacube/utils/cog.py b/datacube/utils/cog.py index c3611eff2..5e1b66154 100644 --- a/datacube/utils/cog.py +++ b/datacube/utils/cog.py @@ -133,6 +133,10 @@ def _write_cog( compress="DEFLATE", ) + # If nodata is not set, but the array is of floating point type, force nodata=nan + if nodata is None and np.issubdtype(pix.dtype, np.floating): + nodata = np.nan + if nodata is not None: rio_opts.update(nodata=nodata) diff --git a/datacube/utils/math.py b/datacube/utils/math.py index 8181bd2c3..1d62f2295 100644 --- a/datacube/utils/math.py +++ b/datacube/utils/math.py @@ -178,6 +178,7 @@ def num2numpy(x, dtype, ignore_range=None): :param x int|float: Numerical value to convert to numpy.type :param dtype str|numpy.dtype|numpy.type: Destination dtype :param ignore_range: If set to True skip range check and cast anyway (for example: -1 -> 255) + (Not supported in numpy 2.0+) :returns: None if x is None :returns: None if x is outside the valid range of dtype and ignore_range is not set diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index 9edafd9f9..a6366449d 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,7 @@ What's New v1.8.next ========= +- Always write floating point bands to cogs with nodata=nan for ESRI and GDAL compatibility (:pull:`1602`) - Add deprecation warning for config environment names that will not be supported in 1.9 (:pull:`1592`) - Update docker image to GDAL 3.9/Python 3.12/Ubuntu 24.04 (:pull:`1587`) - Update readthedocs stylesheet for dark theme (:pull:`1579`) diff --git a/setup.py b/setup.py index 8464f799a..43ee11fbd 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ 'pytest-cov', 'pytest-timeout', 'pytest-httpserver', - 'moto', + 'moto<5.0', # 5.0 will require changes to some tests. ] doc_require = [ 'Sphinx', diff --git a/tests/test_3d.py b/tests/test_3d.py index 072aa8d9d..90b9e9799 100644 --- a/tests/test_3d.py +++ b/tests/test_3d.py @@ -68,8 +68,8 @@ def test_extra_dimensions(eo3_metadata, cover_z_dataset_type): # Check chunk size assert dt.extra_dimensions.chunk_size() == (("z",), (30,)) - # String representation - readable = ( + # String representation (numpy 1.x and numpy 2.x formats - numpy 2.x reports total size in bytes) + readable_1 = ( "ExtraDimensions(extra_dim={'z': {'name': 'z', 'values': [5, 10, 15, " "20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, " "105, 110, 115, 120, 125, 130, 135, 140, 145, 150], 'dtype': " @@ -80,8 +80,19 @@ def test_extra_dimensions(eo3_metadata, cover_z_dataset_type): "140., 145., 150.])\nCoordinates:\n * z (z) int64 5 10 15 20 " "25 30 35 40 ... 120 125 130 135 140 145 150} )" ) - assert str(dt.extra_dimensions) == readable - assert f"{dt.extra_dimensions!r}" == readable + readable_2 = ( + "ExtraDimensions(extra_dim={'z': {'name': 'z', 'values': [5, 10, 15, " + "20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, " + "105, 110, 115, 120, 125, 130, 135, 140, 145, 150], 'dtype': " + "'float64'}}, dim_slice={'z': (0, 30)} coords={'z': Size: 240B\narray([ 5., 10., 15., 20., 25., 30., 35., " + "40., 45., 50., 55.,\n 60., 65., 70., 75., 80., 85., " + "90., 95., 100., 105., 110.,\n 115., 120., 125., 130., 135., " + "140., 145., 150.])\nCoordinates:\n * z (z) int64 240B 5 10 15 20 " + "25 30 35 ... 120 125 130 135 140 145 150} )" + ) + assert str(dt.extra_dimensions) in (readable_1, readable_2) + assert f"{dt.extra_dimensions!r}" in (readable_1, readable_2) def test_extra_dimensions_exceptions(eo3_metadata, cover_z_dataset_type): diff --git a/tests/test_utils_cog.py b/tests/test_utils_cog.py index d73e33fb6..5b2ab81f5 100644 --- a/tests/test_utils_cog.py +++ b/tests/test_utils_cog.py @@ -3,6 +3,7 @@ # Copyright (c) 2015-2024 ODC Contributors # SPDX-License-Identifier: Apache-2.0 import pytest +import math from pathlib import Path import numpy as np import xarray as xr @@ -19,8 +20,8 @@ from datacube.utils.cog import write_cog, to_cog, _write_cog -def gen_test_data(prefix, dask=False, shape=None): - w, h, dtype, nodata, ndw = 96, 64, "int16", -999, 7 +def gen_test_data(prefix, dask=False, shape=None, dtype="int16", nodata=-999): + w, h, ndw = 96, 64, 7 if shape is not None: h, w = shape @@ -109,6 +110,20 @@ def test_cog_file(tmpdir, opts): with pytest.warns(UserWarning): write_cog(xx, pp / "cog_badblocksize.tif", blocksize=50) + # check writing floating point COG with no explicit nodata + zz, ds = gen_test_data(pp, dtype="float32", nodata=None) + # write to file + ff = write_cog( + zz, + pp / "cog_float.tif", + **opts + ) + assert isinstance(ff, Path) + assert ff == pp / "cog_float.tif" + assert ff.exists() + aa = rio_slurp_xarray(pp / "cog_float.tif") + assert aa.attrs["nodata"] == "nan" or math.isnan(aa.attrs["nodata"]) + def test_cog_file_dask(tmpdir): pp = Path(str(tmpdir)) diff --git a/tests/test_utils_other.py b/tests/test_utils_other.py index 60ca28fae..462a7553e 100644 --- a/tests/test_utils_other.py +++ b/tests/test_utils_other.py @@ -588,7 +588,12 @@ def test_num2numpy(): assert num2numpy(256, 'uint8') is None assert num2numpy(-1, 'uint16') is None assert num2numpy(-1, 'uint32') is None - assert num2numpy(-1, 'uint8', ignore_range=True) == np.uint8(255) + try: + # Numpy 1.x supports wrapping of unsisinged types + assert num2numpy(-1, 'uint8', ignore_range=True) == np.uint8(255) + except OverflowError: + # Numpy 2.0 will throw Overflow error rather than wrapping + pass assert num2numpy(0, 'uint8') == 0 assert num2numpy(255, 'uint8') == 255 diff --git a/wordlist.txt b/wordlist.txt index aeeea4c7b..afa81ed60 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -150,6 +150,7 @@ EP EPSG epsg ESPA +ESRI evironments f'file fd From 3b30cb3fc1945806162931bcb5c3baebbf1c5364 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 2 Jul 2024 16:45:51 +1000 Subject: [PATCH 139/153] Update whats_new.rst for 1.8.19 release --- docs/about/whats_new.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index a6366449d..c0ce58023 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -8,6 +8,10 @@ What's New v1.8.next ========= +v1.8.19 (2nd July 2024) +======================= + +- Update whats_new.rst for 1.8.19 release (:pull:`1612`) - Always write floating point bands to cogs with nodata=nan for ESRI and GDAL compatibility (:pull:`1602`) - Add deprecation warning for config environment names that will not be supported in 1.9 (:pull:`1592`) - Update docker image to GDAL 3.9/Python 3.12/Ubuntu 24.04 (:pull:`1587`) From ff0d0b7e6fa5b966b024c2414d4ec968c09e0fc1 Mon Sep 17 00:00:00 2001 From: Paul Haesler Date: Tue, 2 Jul 2024 16:56:38 +1000 Subject: [PATCH 140/153] nd in 2nd needs to be a word apparently. --- wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wordlist.txt b/wordlist.txt index afa81ed60..9df34aa2a 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -303,6 +303,7 @@ nc NCI nci ncml +nd ndarray ndexpr NDVI From 8e7ad53b26e3346fff8b9b2df4ea587aee85f4ce Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Tue, 6 Aug 2024 10:46:21 +1000 Subject: [PATCH 141/153] Update Slack to Discord --- .github/ISSUE_TEMPLATE.md | 2 +- CONTRIBUTING.md | 4 ++-- README.rst | 2 +- docs/about/release_process.rst | 2 +- docs/conf.py | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 96b2fdb64..0f90cbb21 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -19,4 +19,4 @@ > **Note:** Stale issues will be automatically closed after a period of six months with no activity. > To ensure critical issues are not closed, tag them with the Github `pinned` tag. > If you are a community member and not a maintainer please escalate this issue to maintainers via -> [GIS StackExchange](https://gis.stackexchange.com/questions/tagged/open-data-cube) or [Slack](http://slack.opendatacube.org). +> [GIS StackExchange](https://gis.stackexchange.com/questions/tagged/open-data-cube) or [Discord](https://discord.com/invite/4hhBQVas5U). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d63444311..f5cb28b8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Firstly, thank you for contributing! We are very grateful for your assistance in improving the Open Data Cube. When contributing to this repository, please first discuss the change you wish to make via an issue, -Slack, or any other method with the owners of this repository before proposing a change. +Discord, or any other method with the owners of this repository before proposing a change. We have a [code of conduct](code-of-conduct.md), so please follow it in all your interactions with the project. @@ -40,5 +40,5 @@ and consider major enhancements, and there are a number of [current and past enh In case you haven't found them yet, please checkout the following resources: * [Documentation](https://datacube-core.readthedocs.io/en/latest/) -* [Slack](http://slack.opendatacube.org) +* [Discord](https://discord.com/invite/4hhBQVas5U) * [GIS Stack Exchange](https://gis.stackexchange.com/questions/tagged/open-data-cube). diff --git a/README.rst b/README.rst index 1e8c7e7d1..67466ef27 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Documentation See the `user guide `__ for installation and usage of the datacube, and for documentation of the API. -`Join our Slack `__ if you need help +`Join our Discord `__ if you need help setting up or using the Open Data Cube. Please help us to keep the Open Data Cube community open and inclusive by diff --git a/docs/about/release_process.rst b/docs/about/release_process.rst index 42df2085e..6d135e3f6 100644 --- a/docs/about/release_process.rst +++ b/docs/about/release_process.rst @@ -1,7 +1,7 @@ Release Process *************** -#. Decide to do a release, and check with regular contributors on Slack that +#. Decide to do a release, and check with regular contributors on `Discord ` that they don't have anything pending. #. Ensure version pins in setup.py and conda-environment.yml are in sync and up to date. diff --git a/docs/conf.py b/docs/conf.py index 04e8f71e3..e3f4bd8d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -130,9 +130,9 @@ "icon": "fab fa-github", }, { - "name": "Slack", - "url": "http://slack.opendatacube.org/", - "icon": "fab fa-slack", + "name": "Discord", + "url": "https://discord.com/invite/4hhBQVas5U", + "icon": "fab fa-discord", }, ], } From dc72b4b3d7357a2fe0e75cbfc71a59f0c68d8d08 Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Tue, 6 Aug 2024 10:47:59 +1000 Subject: [PATCH 142/153] Update release_process.rst --- docs/about/release_process.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about/release_process.rst b/docs/about/release_process.rst index 6d135e3f6..c4a652fe0 100644 --- a/docs/about/release_process.rst +++ b/docs/about/release_process.rst @@ -1,7 +1,7 @@ Release Process *************** -#. Decide to do a release, and check with regular contributors on `Discord ` that +#. Decide to do a release, and check with regular contributors on `Discord `_ that they don't have anything pending. #. Ensure version pins in setup.py and conda-environment.yml are in sync and up to date. From 82f7910d78053930179431f5bc99f9165ca1b6fe Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Tue, 6 Aug 2024 11:00:49 +1000 Subject: [PATCH 143/153] Add Discord badge --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 67466ef27..c15460c15 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,10 @@ Open Data Cube Core :alt: Documentation Status :target: http://datacube-core.readthedocs.org/en/latest/ +.. image:: https://img.shields.io/badge/Discord-7289DA?style=flat&logo=discord&logoColor=white + :alt: Discord + :target: https://discord.com/invite/4hhBQVas5U + Overview ======== From 4b44846e8845df78bdb788abe2263737fd1dc9ca Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Tue, 6 Aug 2024 11:16:54 +1000 Subject: [PATCH 144/153] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c15460c15..3f7344915 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Open Data Cube Core :alt: Documentation Status :target: http://datacube-core.readthedocs.org/en/latest/ -.. image:: https://img.shields.io/badge/Discord-7289DA?style=flat&logo=discord&logoColor=white +.. image:: https://img.shields.io/discord/1212501566326571070?label=Discord&logo=discord&logoColor=white&color=7289DA)](https://discord.gg/4hhBQVas5U :alt: Discord :target: https://discord.com/invite/4hhBQVas5U From 81b4a31b5e6500d7b80486a2331917ce3b12767c Mon Sep 17 00:00:00 2001 From: Robbi Bishop-Taylor Date: Tue, 6 Aug 2024 12:03:44 +1000 Subject: [PATCH 145/153] Update wordlist.txt --- wordlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wordlist.txt b/wordlist.txt index 9df34aa2a..fb665e107 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -188,6 +188,7 @@ GeoTiff geotiff GeoTIFFs GeoTiffs +gg gh GIS Github @@ -203,6 +204,7 @@ gridWorkflow GroupBy HDF hdf +hhBQVas hl hoc hostname @@ -263,6 +265,7 @@ linux literalinclude localhost localmemory +logoColor lon lonlat lr From cb1b18cb46f54a7fb7a777225f67b6aec2b82f6b Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Fri, 30 Aug 2024 15:28:33 +0200 Subject: [PATCH 146/153] Dockerfile: fix casing warning This fixes the warning: FromAsCasing: 'as' and 'FROM' keywords' casing do not match emitted by recent Docker releases. --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 81a81447f..8566682c6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ ## SPDX-License-Identifier: Apache-2.0 ## # gdal:ubuntu-small no longer comes with netcdf support compiled into gdal -FROM ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 as builder +FROM ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 AS builder FROM ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 ARG V_PG=16 ARG V_PGIS=16-postgis-3 From 3de187d3b59322cba56756d28d48c66c69a50c0c Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Fri, 30 Aug 2024 15:30:36 +0200 Subject: [PATCH 147/153] Dockerfile: fix key value warning This fixes the warning: LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format emitted by recent Docker releases. And while changing this, bring all the ENV lines into a single layer, which saves some space in the resulting image. --- docker/Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8566682c6..84e9b82e7 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -34,15 +34,14 @@ RUN apt-get update -y \ # Build constrained python environment RUN virtualenv /env -ENV PYENV /env -ENV GDAL_CONFIG /usr/bin/gdal-config +# Set the locale, this is required for some of the Python packages +ENV PYENV=/env \ + GDAL_CONFIG=/usr/bin/gdal-config \ + LC_ALL=C.UTF-8 # Needed to build cf-units wheels. ARG UDUNITS2_XML_PATH=/usr/share/xml/udunits/udunits2-common.xml -# Set the locale, this is required for some of the Python packages -ENV LC_ALL C.UTF-8 - COPY docker/constraints.in /conf/requirements.txt COPY docker/constraints.txt docker/nobinary.txt /conf/ From a3bce448d243aea18fcf84db495408406345b871 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Fri, 30 Aug 2024 15:39:20 +0200 Subject: [PATCH 148/153] Configure Dependabot for Docker image This will cause Dependabot to make pull requests for the Dockerfile when there are new releases of GDAL. --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 90e05c40d..e115726a7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: directory: "/" # Location of package manifests schedule: interval: "weekly" + - package-ecosystem: docker + directory: "/docker" + schedule: + interval: "daily" From 9caa8e9507ab82fe6806bcfcc523f22383f2aa6d Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Fri, 30 Aug 2024 15:49:30 +0200 Subject: [PATCH 149/153] CI: set permissions for docpreview This is part of the documentation for the action: https://github.com/readthedocs/actions/tree/v1/preview --- .github/workflows/doc-qa.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/doc-qa.yaml b/.github/workflows/doc-qa.yaml index e850ea93f..b65e55227 100644 --- a/.github/workflows/doc-qa.yaml +++ b/.github/workflows/doc-qa.yaml @@ -8,6 +8,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + pull-requests: write + jobs: pyspell: runs-on: ubuntu-latest From 6fd8e8a2cf84502a1257e61928f4ad92e4e28501 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Fri, 30 Aug 2024 15:54:38 +0200 Subject: [PATCH 150/153] CI: only doc preview pull requests The documentation at https://github.com/readthedocs/actions/tree/v1/preview uses this. According to https://stackoverflow.com/questions/74957218/what-is-the-difference-between-pull-request-and-pull-request-target-event-in-git there is something with permissions of tokens that differ between pull_request and pull_request_target, so switch to what the action documentation suggests. --- .github/workflows/doc-qa.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/doc-qa.yaml b/.github/workflows/doc-qa.yaml index b65e55227..233c7a988 100644 --- a/.github/workflows/doc-qa.yaml +++ b/.github/workflows/doc-qa.yaml @@ -1,12 +1,9 @@ name: Doc QA on: # Triggers the workflow on pull request events for the main branch - pull_request: - branches: - - 'develop' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: + pull_request_target: + types: + - opened permissions: pull-requests: write From 091bc01c39cc013206d0de7dc11412404c4e8059 Mon Sep 17 00:00:00 2001 From: Ariana-B <40238244+Ariana-B@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:19:33 +1000 Subject: [PATCH 151/153] Don't error in dataset add if `product.id` is None (#1630) * don't assume product.id exists when adding a dataset * don't assume assume product.id exists when adding dataset * update whats_new --------- Co-authored-by: Ariana Barzinpour --- datacube/index/postgis/_datasets.py | 7 ++++++- datacube/index/postgres/_datasets.py | 7 ++++++- docs/about/whats_new.rst | 1 + integration_tests/index/test_index_data.py | 11 ++++++++++- tests/index/test_api_index_dataset.py | 3 +++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/datacube/index/postgis/_datasets.py b/datacube/index/postgis/_datasets.py index 502146601..523964b95 100755 --- a/datacube/index/postgis/_datasets.py +++ b/datacube/index/postgis/_datasets.py @@ -165,7 +165,12 @@ def add(self, dataset: Dataset, return dataset with self._db_connection(transaction=True) as transaction: # 1a. insert (if not already exists) - is_new = transaction.insert_dataset(dataset.metadata_doc_without_lineage(), dataset.id, dataset.product.id) + product_id = dataset.product.id + if product_id is None: + # don't assume the product has an id value since it's optional + # but we should error if the product doesn't exist in the db + product_id = self.products.get_by_name_unsafe(dataset.product.name).id + is_new = transaction.insert_dataset(dataset.metadata_doc_without_lineage(), dataset.id, product_id) if is_new: # 1b. Prepare spatial index extents transaction.update_spindex(dsids=[dataset.id]) diff --git a/datacube/index/postgres/_datasets.py b/datacube/index/postgres/_datasets.py index 4ad2b0f76..51a2d606f 100755 --- a/datacube/index/postgres/_datasets.py +++ b/datacube/index/postgres/_datasets.py @@ -155,7 +155,12 @@ def process_bunch(dss, main_ds, transaction): # First insert all new datasets for ds in dss: - is_new = transaction.insert_dataset(ds.metadata_doc_without_lineage(), ds.id, ds.product.id) + product_id = ds.product.id + if product_id is None: + # don't assume the product has an id value since it's optional + # but we should error if the product doesn't exist in the db + product_id = self.products.get_by_name_unsafe(ds.product.name).id + is_new = transaction.insert_dataset(ds.metadata_doc_without_lineage(), ds.id, product_id) sources = ds.sources if is_new and sources is not None: edges.extend((name, ds.id, src.id) diff --git a/docs/about/whats_new.rst b/docs/about/whats_new.rst index c0ce58023..2a35acff4 100644 --- a/docs/about/whats_new.rst +++ b/docs/about/whats_new.rst @@ -7,6 +7,7 @@ What's New v1.8.next ========= +- Don't error when adding a dataset whose product doesn't have an id value (:pull:`1630`) v1.8.19 (2nd July 2024) ======================= diff --git a/integration_tests/index/test_index_data.py b/integration_tests/index/test_index_data.py index 40ed4fe05..a8ff47456 100755 --- a/integration_tests/index/test_index_data.py +++ b/integration_tests/index/test_index_data.py @@ -18,7 +18,8 @@ from datacube.index.exceptions import MissingRecordError from datacube.index import Index -from datacube.model import Dataset, MetadataType +from datacube.model import Dataset, Product, MetadataType +from datacube.index.eo3 import prep_eo3 _telemetry_uuid = UUID('4ec8fe97-e8b9-11e4-87ff-1040f381a756') _telemetry_dataset = { @@ -258,6 +259,14 @@ def test_get_dataset(index: Index, ls8_eo3_dataset: Dataset) -> None: 'f226a278-e422-11e6-b501-185e0f80a5c1']) == [] +def test_add_dataset_no_product_id(index: Index, extended_eo3_metadata_type, ls8_eo3_product, eo3_ls8_dataset_doc): + product_no_id = Product(extended_eo3_metadata_type, ls8_eo3_product.definition) + assert product_no_id.id is None + dataset_doc, _ = eo3_ls8_dataset_doc + dataset = Dataset(product_no_id, prep_eo3(dataset_doc)) + assert index.datasets.add(dataset, with_lineage=False) + + def test_transactions_api_ctx_mgr(index, extended_eo3_metadata_type_doc, ls8_eo3_product, diff --git a/tests/index/test_api_index_dataset.py b/tests/index/test_api_index_dataset.py index 63f68405c..7824b86e7 100644 --- a/tests/index/test_api_index_dataset.py +++ b/tests/index/test_api_index_dataset.py @@ -208,6 +208,9 @@ def get(self, *args, **kwargs): def get_by_name(self, *args, **kwargs): return self.type + def get_by_name_unsafe(self, *args, **kwargs): + return self.type + @contextmanager def _db_connection(self, transaction=False): yield MockDb() From 2cb2edf09a285a45cc34d7ff2399325bb87205ff Mon Sep 17 00:00:00 2001 From: Caitlin Adams Date: Wed, 4 Sep 2024 10:28:34 +1000 Subject: [PATCH 152/153] Update docs README to give suitable instructions for local docs build (#1631) --- docs/README.rst | 52 +++++++++++++++++++++++++++++++++++++------ docs/requirements.txt | 2 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/README.rst b/docs/README.rst index 31a0a05bb..7e4725eb8 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -8,22 +8,60 @@ Developing Locally Requires a Unix like system that includes ``make``. -#. Install NodeJS + NPM -#. Install Browser Sync +#. Clone the datacube-core repository. If you don't have permissions to push to the datacube-core library, you will need to fork the repo and clone your fork. .. code-block:: bash - npm install -g browser-sync + git clone https://github.com/opendatacube/datacube-core.git -#. Install Python dependencies +#. Check out a new branch for the documentation feature you're working on .. code-block:: bash - pip install -r requirements.txt - pip install git+https://github.com/carrotandcompany/sphinx-autobuild.git@feature_event_delay + git switch -c docs- -#. Start the auto-building development server. +#. Change directory to the docs folder + +.. code-block:: bash + + cd docs + +#. Create a conda environment for python 3.11, with conda-forge as the channel + +.. code-block:: bash + + conda create --name datacubecoredocs -c conda-forge python=3.11 + +#. Activate the conda environment + +.. code-block:: bash + + conda activate datacubecoredocs + +#. Install pandoc + +.. code-block:: bash + + conda install pandoc + +#. Install requirements with pip + +.. code-block:: bash + + pip install -r requirements.txt + +#. Run the autobuild. .. code-block:: bash sphinx-autobuild . _build/html + +#. Open a browser and navigate to the URL provided by the autobuild + +#. Make changes to the docs. The terminal with the autobuild will continue to update the docs view in the browser. + +#. When finished, quit the autobuild process using ``ctrl-c`` in the terminal. + +#. Stage and commit your changes. + +#. When ready for review, push your changes and create a pull request. diff --git a/docs/requirements.txt b/docs/requirements.txt index 49ca72d6d..417d3cfd0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -179,7 +179,7 @@ platformdirs==3.0.0 # via jupyter-core psutil==5.9.4 # via distributed -psycopg2==2.9.5 +#psycopg2==2.9.5 # via datacube (setup.py) pydata-sphinx-theme==0.9.0 # via datacube (setup.py) From 6d69dea6bea2db5e69ceafa2198b04bd3211906d Mon Sep 17 00:00:00 2001 From: Caitlin Adams Date: Wed, 18 Sep 2024 09:14:01 +1000 Subject: [PATCH 153/153] add explicit reference to ubuntu set up page on install page --- docs/installation/index.rst | 9 ++++++++- docs/installation/setup/ubuntu.rst | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index 7a811cbaf..b5e7c634b 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -3,7 +3,7 @@ Installing and managing the Open Data Cube ****************************************** This section contains information on setting up and managing the Open Data Cube. - + .. admonition:: Note :class: info @@ -11,6 +11,13 @@ This section contains information on setting up and managing the Open Data Cube. For people using an existing Open Data Cube, such as Digital Earth Australia, you are not required to know most of the information contained here. This material is designed for people wanting to set up a new Open Data Cube instance. +Set Up +------ + +To learn more about setting up the Open Data Cube, see the relevant page: + +* :ref:`Ubuntu Setup ` + .. toctree:: :caption: Setting up an Open Data Cube :maxdepth: 5 diff --git a/docs/installation/setup/ubuntu.rst b/docs/installation/setup/ubuntu.rst index 2f999ae6e..3d4d94a5c 100644 --- a/docs/installation/setup/ubuntu.rst +++ b/docs/installation/setup/ubuntu.rst @@ -1,3 +1,4 @@ +.. _setup-ubuntu: Ubuntu Developer Setup **********************