diff --git a/README.md b/README.md index b5a6516..f773ccf 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This module performs geospatial analyses to determine the available land area fo

## About + This is a modular `snakemake` workflow created as part of the [Modelblocks project](https://www.modelblocks.org/). It can be imported directly into any `snakemake` workflow. @@ -17,6 +18,7 @@ the [integration example](./tests/integration/Snakefile), and the `snakemake` [documentation](https://snakemake.readthedocs.io/en/stable/snakefiles/modularization.html). ## Overview + Data processing steps: @@ -25,77 +27,84 @@ Data processing steps:

- -* Geospatial input data (vector and raster) are acquired. This is automatic for most data, but the following data need to be manually supplied: - * Geographic boundaries in the parquet format - * WDPA protected areas database from https://www.protectedplanet.net/ in GeoDB format (choose 'File Geodatabase' when downloading). -* For the extent of the provided boundaries, the input data are reprojected and rasterised to the resolution of the land cover data (GlobCover), and merged into a single dataset for further processing. -* Based on the supplied configuration, the land-use analysis is done for each defined technology. -* Results can be reported on a per-geography and per-technology basis, as pixel surface area values (TIFF files), images for reporting purposes (PNG files), and also as a summary report with per-region capacities (CSV and HTML files) +- Geospatial input data (vector and raster) are acquired. This is automatic for most data, but the following data need to be manually supplied: + - Geographic boundaries in the parquet format + - WDPA protected areas database from https://www.protectedplanet.net/ in GeoDB format (choose 'File Geodatabase' when downloading). +- For the extent of the provided boundaries, the input data are reprojected and rasterised to the resolution of the land cover data (GlobCover), and merged into a single dataset for further processing. +- Based on the supplied configuration, the land-use analysis is done for each defined technology. +- Results can be reported on a per-geography and per-technology basis, as pixel surface area values (TIFF files), images for reporting purposes (PNG files), and also as a summary report with per-region capacities (CSV and HTML files) See below for the [data sources](#references). ## Configuration + Please consult the configuration [README](./config/README.md) and the [configuration example](./config/config.yaml) for a general overview on the configuration options of this module. -In the configuration, you can define any number of `techs`, and for each of them, specify the `initial_area`, `continuous_layers`, and `binary_layers`. +The configuration is based on an arbitrary number of `scenarios`. Each scenario can define any number of `techs`, and for each tech, the conditions for its land use, via `initial_area`, `continuous_layers`, and `binary_layers`. Results are delivered by scenario and tech, e.g. `/{shape}/{scenario}/area_potential_{tech}.tif`. Defining only a single scenario (e.g. `base`) works fine if no scenarios are needed. + +Per-subunit `overrides` inside a scenario can be used to override the specific settings of a tech in just one subunit (the keys of the `split_by` column, e.g. country codes). An example use of this is to provide different offshore wind distance-to-shore numbers for different countries. By example, here is a `pv_rooftop` tech. We use the `settlement_area`, which is the settlement area in m² in each pixel, as the initial area from which the further analysis proceeds. In `continuous_layers`, we use the `settlement_share`, which is the share (0-1) of area covered by settlement, and exclude pixels with less than 0.01 settlement share while assuming that of those pixels not excluded by that, 0.8 (80%) of the settled area can be used for rooftop PV. Finally, in the `binary_layers`, we include all land use types except `NOT_SUITABLE` (since the main selection is done via the settlement_share). This means that, for example, `FOREST` pixels with a `settlement_area` > 0 can be included. ```yaml pv_rooftop: - initial_area: settlement_area - continuous_layers: - settlement_share: - min: 0.01 - max: 1 - share: 0.8 - binary_layers: - regions_maritime: 0 - regions_land: 1 - protected: 0 - landcover_FARM: 1 - landcover_FOREST: 1 - landcover_URBAN: 1 - landcover_OTHER: 1 - landcover_NOT_SUITABLE: 0 - landcover_WATER: 0 + initial_area: settlement_area + continuous_layers: + settlement_share: + min: 0.01 + max: 1 + share: 0.8 + binary_layers: + regions_maritime: 0 + regions_land: 1 + protected: 0 + landcover_FARM: 1 + landcover_FOREST: 1 + landcover_URBAN: 1 + landcover_OTHER: 1 + landcover_NOT_SUITABLE: 0 + landcover_WATER: 0 ``` -Here is a `wind_offshore` example. We start with the `pixel_area`, the total surface area in m² for each pixel. We include pixels with a slope up to and including 20 degrees, and exclude pixels with a settlement share above 0.01. Furthermore, we include only land areas (`regions_land: 1` and `regions_maritime: 0`) and completely exclude some areas like protected areas or urban areas (`protected: 0`, `landcover_URBAN: 0`), while including only a fraction of other areas (e.g. if a pixel is considered farmland, only 20% of its surface is available: `landcover_FARM: 0.2`). +Here is a `wind_onshore` example. We start with the `pixel_area`, the total surface area in m² for each pixel. We include pixels with a slope up to and including 20 degrees, and exclude pixels with a settlement share above 0.01. Furthermore, we include only land areas (`regions_land: 1` and `regions_maritime: 0`) and completely exclude some areas like protected areas or urban areas (`protected: 0`, `landcover_URBAN: 0`), while including only a fraction of other areas (e.g. if a pixel is considered farmland, only 20% of its surface is available: `landcover_FARM: 0.2`). ```yaml wind_onshore: - initial_area: pixel_area - continuous_layers: - slope_deg: - min: 0 - max: 20 - settlement_share: - min: 0 - max: 0.01 - binary_layers: - regions_maritime: 0 - regions_land: 1 - protected: 0 - landcover_FARM: 0.2 - landcover_FOREST: 0.05 - landcover_URBAN: 0 - landcover_OTHER: 0.3 - landcover_NOT_SUITABLE: 0 - landcover_WATER: 0 - + initial_area: pixel_area + continuous_layers: + slope_deg: + min: 0 + max: 20 + settlement_share: + min: 0 + max: 0.01 + binary_layers: + regions_maritime: 0 + regions_land: 1 + protected: 0 + landcover_FARM: 0.2 + landcover_FOREST: 0.05 + landcover_URBAN: 0 + landcover_OTHER: 0.3 + landcover_NOT_SUITABLE: 0 + landcover_WATER: 0 ``` +Some of the processing in this workflow is memory-intensive. +The memory-heavy rules all specify their expected peak memory with `resources: mem_mb`. This number was hardcoded based on an estimate of country-sized subunits, with up to about 150 million land-cover pixels. +Snakemake only uses these declarations to control how many memory-heavy jobs are scheduled in parallel, when you tell it how much memory is available overall (e.g., `snakemake --use-conda --cores 4 --resources mem_mb=12000`). +To help diagnose memory usage, each heavy job also writes a `*.benchmark.tsv` next to its log file, containing its runtime, and on Linux only, peak memory. ## Input / output structure + Please consult the [interface file](./INTERFACE.yaml) for more information. ## Development + We use [`pixi`](https://pixi.sh/) as our package manager for development. @@ -108,18 +117,19 @@ pixi install --all ``` Please be aware that this is a multi-environment project (see [pixi.toml](./pixi.toml) for details). + - `default`: used for development and integration testing. -Because it contains `Snakemake`, `conda` and `pytest` as dependencies it **should not be used** in `Snakemake` rules. + Because it contains `Snakemake`, `conda` and `pytest` as dependencies it **should not be used** in `Snakemake` rules. - `module`: contains minimal dependencies used in `Snakemake` rules. -If modified, be sure to export it to `Snakemake` so it can be recreated by module users: + If modified, be sure to export it to `Snakemake` so it can be recreated by module users: ```shell # create module.yaml and conda-spec pin files in workflow/envs/ pixi run export-snakemake-env module ``` - ## Testing + For testing, simply run: @@ -136,23 +146,36 @@ cd tests/integration/ # navigate to the integration example snakemake --use-conda --cores 2 # run the workflow! ``` +### Module-specific unit tests + +Besides the integration tests, this module also supplies unit tests: + +```shell +pixi run test-unit +``` + +The unit tests are run in an isolated `test-unit` Pixi environment that combines `pytest` with the `module` feature. They run through every workflow script on a small synthetic dataset, then compare the outputs with committed reference data in `unit_tests/reference/`. + +To regenerate the reference data when results change deliberately, use `pixi run update-reference` (or `update-reference-unit` / `update-reference-integration` separately) and review the resulting diff as part of the change. + ## References + This module is based on the following research and datasets: -* [GEDTM30](https://github.com/openlandmap/GEDTM30) for slope - * License: Creative Commons Attribution 4.0 International -* [GlobCover land cover data](https://due.esrin.esa.int/page_globcover.php) - * License: "You may use the GlobCover land cover map for educational and/or scientific purposes, without any fee on the condition that you credit ESA and the Université Catholique de Louvain as the source of the GlobCover products." -* [GEBCO (General Bathymetric Chart of the Oceans)](https://www.gebco.net/data-products/gridded-bathymetry-data) 15 arc-second data - * License: "The GEBCO Grid is placed in the public domain and may be used free of charge. [...] Users must: Acknowledge the source of The GEBCO Grid. A suitable form of attribution is given in the documentation that accompanies The GEBCO Grid." -* [GHSL (Global Human Settlement Layer)](https://human-settlement.emergency.copernicus.eu/download.php) built-up surface data (R2023, GHS-BUILT-S, 100m resolution) - * License: "The GHSL has been produced by the EC JRC as open and free data. Reuse is authorised, provided the source is acknowledged." -* [World Bank Global Shipping Traffic Density](https://datacatalog.worldbank.org/search/dataset/0037580/global-shipping-traffic-density), the number of AIS-recorded vessel positions per square kilometre accumulated over 2015–2021 - * License: Creative Commons Attribution 4.0 International (CC BY 4.0) -* [WDPA (World Database on Protected Areas)](https://www.protectedplanet.net/) - * License: Non-commercial allowed. Citation: "UNEP-WCMC and IUCN (2025), Protected Planet: The World Database on Protected Areas (WDPA) and World Database on Other Effective Area-based Conservation Measures (WD-OECM) [Online], June 2025, Cambridge, UK: UNEP-WCMC and IUCN. Available at: www.protectedplanet.net." +- [GEDTM30](https://github.com/openlandmap/GEDTM30) for slope + - License: Creative Commons Attribution 4.0 International +- [GlobCover land cover data](https://due.esrin.esa.int/page_globcover.php) + - License: "You may use the GlobCover land cover map for educational and/or scientific purposes, without any fee on the condition that you credit ESA and the Université Catholique de Louvain as the source of the GlobCover products." +- [GEBCO (General Bathymetric Chart of the Oceans)](https://www.gebco.net/data-products/gridded-bathymetry-data) 15 arc-second data + - License: "The GEBCO Grid is placed in the public domain and may be used free of charge. [...] Users must: Acknowledge the source of The GEBCO Grid. A suitable form of attribution is given in the documentation that accompanies The GEBCO Grid." +- [GHSL (Global Human Settlement Layer)](https://human-settlement.emergency.copernicus.eu/download.php) built-up surface data (R2023, GHS-BUILT-S, 100m resolution) + - License: "The GHSL has been produced by the EC JRC as open and free data. Reuse is authorised, provided the source is acknowledged." +- [World Bank Global Shipping Traffic Density](https://datacatalog.worldbank.org/search/dataset/0037580/global-shipping-traffic-density), the number of AIS-recorded vessel positions per square kilometre accumulated over 2015–2021 + - License: Creative Commons Attribution 4.0 International (CC BY 4.0) +- [WDPA (World Database on Protected Areas)](https://www.protectedplanet.net/) + - License: Non-commercial allowed. Citation: "UNEP-WCMC and IUCN (2025), Protected Planet: The World Database on Protected Areas (WDPA) and World Database on Other Effective Area-based Conservation Measures (WD-OECM) [Online], June 2025, Cambridge, UK: UNEP-WCMC and IUCN. Available at: www.protectedplanet.net." ## Contributors ✨ diff --git a/WAIVER.md b/WAIVER.md new file mode 100644 index 0000000..443d195 --- /dev/null +++ b/WAIVER.md @@ -0,0 +1,3 @@ +Technische Universiteit Delft hereby disclaims all copyright interest in the program "module_area_potentials" (geospatial analysis to determine available land area for energy infrastructure) written by the Author(s). + +Cokky Hilhorst, Dean of the Faculty of Technology, Policy and Management (TPM) diff --git a/figures/rulegraph.png b/figures/rulegraph.png index 94174a0..d384707 100644 Binary files a/figures/rulegraph.png and b/figures/rulegraph.png differ diff --git a/pixi.lock b/pixi.lock index 751b900..0c9fd5b 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,8 +1,20 @@ version: 7 platforms: - name: linux-64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 - name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __osx=13.0 + - __archspec=0=m1 - name: win-64 + virtual-packages: + - __win=10.0 + - __archspec=0=x86_64 environments: default: channels: @@ -749,6 +761,772 @@ environments: module: channels: - url: https://conda.anaconda.org/conda-forge/ + - url: https://conda.anaconda.org/bioconda/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.16.1-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.9.6-hb9c0fe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.9.13-h2c9d079_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.12.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.3.2-h8b1a151_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.5.9-h841be55_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.10.10-hf621c6d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.26.1-hc87160b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.14.0-ha25ca29_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.11.5-h9b5df67_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.2.4-h8b1a151_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.2.10-h8b1a151_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.37.2-hb153662_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.606-hc9b1074_13.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.16.2-h206d751_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.13.3-hed0cdb0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py313h8f72f4d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hebe6cf0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.5-py313h29aa505_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313hc8edb43_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/curl-8.18.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hd9c7081_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cytoolz-1.1.0-py313h07c4f96_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.3.1-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fiona-1.10.1-py313h7033f15_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.3-h4db4eae_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.63.0-py313h3dea7bd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.10.3-py313h60f829d_27.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.0-h480dda7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/geotiff-1.7.4-h239500f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.15-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h2a13503_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.14.6-nompi_h19486de_106.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.1-py313h2551ef2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-19.0.1-h3c191e6_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-19.0.1-h635bf11_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-19.0.1-h635bf11_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-19.0.1-hb4dd7c2_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libattr-2.5.2-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-8_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-8_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp21.1-21.1.8-default_h99862b1_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.8-default_h746c552_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.2.0-h69a702a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-arrow-parquet-3.10.3-hef02800_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.10.3-h95ec890_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-hdf5-3.10.3-h966a9c2_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.2.0-h69a702a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.2.0-h6b99dfc_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.39.0-h9d11ab5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.39.0-hdbdcf42_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1023.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.8-hf7376ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.8-hf7376ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.9.3-nompi_hbf2fc22_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-1.21.0-h9692893_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopentelemetry-cpp-headers-1.21.0-ha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libparquet-19.0.1-h7376487_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.1-h5c52fec_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-6.33.5-h6eeba95_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h96cd706_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-h2eee824_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0737f62_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.2.0-hdf11a46_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-hcc2c06a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.3-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h0e34353_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-hca5e8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.1-h26afc86_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzip-1.11.2-h6991a6a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-4.4.5-py313h28739b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.3-py313h78bf25f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.3-py313h129903b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.2.2-hc2c9f91_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.2-py313h2551ef2_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.2-nompi_py313hfae5b86_104.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.2.2-hbb90d81_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.0-py313ha87cce1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py313h3b26573_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.6.2-h18fbb6c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py313h54dd161_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-19.0.1-py313h78bf25f_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-19.0.1-py313he109ebe_2_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.5-py313ha296275_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyogrio-0.11.0-py313hab4ff3b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.1-py313hcd509b5_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.10.1-py313h85046ba_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.14-h6add32d_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.10.1-hca0d9c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rasterio-1.4.3-py313h3209d2e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2025.11.05-h5301d42_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.7.1-h1cbb8d7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.9.0-np2py313h16d504d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py313h4b8bb8b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py313hfc84eb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.4-h6e79ffa_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.8-py313h995f894_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-hd6090a7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.2.5-h988505b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.48-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.3-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-auth-0.9.6-ha02d361_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-cal-0.9.13-h6ee9776_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-common-0.12.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-compression-0.3.2-h3e7f9b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-event-stream-0.5.9-hd533cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-http-0.10.10-ha1850f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-io-0.26.1-h4137820_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-mqtt-0.14.0-h5721393_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-s3-0.11.5-h7d214dc_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-c-sdkutils-0.2.4-h16f91aa_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-checksums-0.2.10-h3e7f9b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-crt-cpp-0.37.2-hcfbc53e_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/aws-sdk-cpp-1.11.606-h71a6bcd_13.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-core-cpp-1.16.2-he5ae378_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-identity-cpp-1.13.3-h810541e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py313h8c7ad59_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.5-py313hf5115c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py313h2af2deb_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/curl-8.21.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cytoolz-1.1.0-py313h0997733_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fiona-1.10.1-py313h0e822ff_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.63.0-py313h65a2061_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freexl-2.0.0-h3ab3353_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gdal-3.10.3-py313h976e3e5_27.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/geos-3.14.0-h4bcf65f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/geotiff-1.7.4-h1d7e6e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gflags-2.2.2-hf9b8971_1005.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-5.2.2-h93a5062_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/glog-0.7.1-heb240a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf4-4.2.15-h2ee6834_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_had3affe_110.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/json-c-0.18-he4178ee_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.1-py313h738389e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h6fbacd7_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-19.0.1-h96c63c9_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-19.0.1-h0f28eaf_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-19.0.1-h0f28eaf_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-substrait-19.0.1-h8746646_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-8_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hd5a2499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libevent-2.1.12-h2757513_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-arrow-parquet-3.10.3-h2616c01_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-core-3.10.3-h9f236a5_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-hdf5-3.10.3-hdacffec_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-2.39.0-h2f60c08_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-2.39.0-ha114238_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libkml-1.3.0-hb833057_1023.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnetcdf-4.9.3-nompi_h7a8d41e_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.33-openmp_he657e61_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-1.21.0-h08d5cc3_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopentelemetry-cpp-headers-1.21.0-hce30654_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libparquet-19.0.1-hdcf9336_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-6.33.5-h2d4b707_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librttopo-1.1.0-hf7cb3ef_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libspatialite-5.1.0-h823fe50_16.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h34a968b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-hf67920b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.1-h0ff4647_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.1-h9329255_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-devel-2.15.1-h9329255_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.0-hdb3d66b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-4.4.5-py313hd065f0a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.3-py313h39782a4_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.3-py313haaf02c0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.2.2-h56c1371_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py313hcb059c8_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.2-nompi_py313hb28a5cb_104.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py313h16eae64_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.2.2-h578b684_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.0-py313h668b085_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py313h4ede67f_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/proj-9.6.2-hdbeaa80_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-19.0.1-py313h39782a4_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-core-19.0.1-py313hcc89289_2_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py313h680bcb3_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyogrio-0.11.0-py313h4ad91d6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyproj-3.7.1-py313h84bd6f1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.14-h448ec07_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rasterio-1.4.3-py313h9a45b90_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/re2-2025.11.05-ha480c28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py313h3b23316_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py313h52f5312_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/shapely-2.1.2-py313hfb5a6ed_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.4-hdb6324b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.8-py313hf5449f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uriparser-0.9.8-h00cdb27_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xerces-c-3.2.5-h92fc2f4_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh7428d3b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-auth-0.9.6-hdf23a24_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-cal-0.9.13-h46f3b43_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-common-0.12.6-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-compression-0.3.2-hcb3a2da_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-event-stream-0.5.9-h972bbec_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-http-0.10.10-hb410799_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-io-0.26.1-h0d5b9f9_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-mqtt-0.14.0-h833cf40_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-s3-0.11.5-h3ec5e31_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-c-sdkutils-0.2.4-hcb3a2da_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.2-h5e571c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.606-ha60a6cd_13.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py313h2a31948_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-1.2.0-h2d644bc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.34.8-h6a83c73_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h5782bbf_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cftime-1.6.5-py313h0591002_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py313h1a38498_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/curl-8.18.0-h43ecb02_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cytoolz-1.1.0-py313h5ea7bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.3.1-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fiona-1.10.1-py313hfe59770_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.3-hd47e2ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fonttools-4.63.0-py313hd650c13_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.10.3-py313hf168f70_27.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.0-hdade9fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/geotiff-1.7.4-h86c3423_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.15-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-12.2.0-h5f2951f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf4-4.2.15-h5557f11_7.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.6-nompi_hae35d4c_106.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.1-py313h1a38498_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_he24518a_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-19.0.1-hd086c85_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-19.0.1-h7d8d6a5_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-19.0.1-h7d8d6a5_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-substrait-19.0.1-h524e9bd_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-8_h8455456_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-8_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.8-default_ha2db4b5_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcrc32c-1.1.2-h0e60522_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.18.0-h43ecb02_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libevent-2.1.12-h3671451_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-16.2.0-h110b43a_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-arrow-parquet-3.10.3-h4b7837c_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.10.3-h1dbae3e_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-hdf5-3.10.3-h0f01001_18.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.86.2-hd9c3897_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-16.2.0-h8ee18e1_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-2.39.0-h01c467a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-2.39.0-he04ea4c_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1023.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libnetcdf-4.9.3-nompi_h3948bcf_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libparquet-19.0.1-h7051d1f_45_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libprotobuf-6.33.5-h6cf2d3c_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-h5ff11c1_19.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-h9d118f5_16.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.357.0-h477610d_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxcb-1.17.0-h0e4246c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.1-h06f855e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.1-ha29bfb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.1-ha29bfb0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.43-h0fbe4c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzip-1.11.2-h3135430_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-23.1.0-h49e36cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-4.4.5-py313h4bbca4b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.3-py313hfa70ccb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.3-py313h81b4f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.2.2-h8fa244a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.1.0-hac47afa_235.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.2-py313h1a38498_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/netcdf4-1.7.2-nompi_py313hbe59507_104.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.5-py313hce7ae62_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.2.2-h0a1ad0e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-2.3.0-py313hf91d08e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.46-h3402e2f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py313h38f99e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.6.2-h7990399_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-h0e40799_1002.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-19.0.1-py313hfa70ccb_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-core-19.0.1-py313h5921983_2_cpu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.5-py313hfbe8231_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyogrio-0.11.0-py313h75b81ac_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyproj-3.7.1-py313hc3e4018_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.10.1-py313h475ba69_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.13.14-h09917c8_100_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py313hd650c13_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.10.1-h7502b6c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/rasterio-1.4.3-py313hb64d538_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/re2-2025.11.05-ha104f34_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/scikit-learn-1.9.0-np2py313h4ce4a18_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.0-py313he51e9a2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/shapely-2.1.2-py313hae85795_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.4-hdb435a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.8-py313h5ea7bf4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/uriparser-0.9.8-h5a68840_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_39.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36247-h633cb9f_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.2.5-he0c23c2_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.12-hba3369d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.5-hba3369d_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + test-unit: + channels: + - url: https://conda.anaconda.org/conda-forge/ + - url: https://conda.anaconda.org/bioconda/ packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda @@ -772,13 +1550,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.16.0-hf824e48_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.13.0-ha7a2c86_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.14.0-h539c000_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py313h8f72f4d_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313hf159716_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hebe6cf0_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.5-py313h29aa505_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py313hc8edb43_4.conda @@ -788,7 +1566,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.3.1-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fiona-1.10.1-py313h7033f15_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.1-h27c8c51_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.3-h4db4eae_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.63.0-py313h3dea7bd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda @@ -805,14 +1583,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py313hc8edb43_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.1-py313h2551ef2_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libaec-1.1.5-h088129d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.8-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_hc2c16d8_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-19.0.1-h3c191e6_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-19.0.1-h635bf11_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-19.0.1-h635bf11_45_cpu.conda @@ -829,7 +1607,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hb8b1518_5.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.18.0-h4e3cde8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda @@ -838,23 +1616,23 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.2.0-h69a702a_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-arrow-parquet-3.10.3-hef02800_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.10.3-h95ec890_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-hdf5-3.10.3-h966a9c2_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.2.0-h69a702a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.2.0-h6b99dfc_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.86.2-h32235b2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.39.0-h9d11ab5_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.39.0-hdbdcf42_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.78.1-h1d1128b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1023.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-8_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm21-21.1.8-hf7376ad_0.conda @@ -877,18 +1655,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2025.11.05-h0dc7533_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h96cd706_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-h2eee824_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0737f62_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.2.0-hdf11a46_4.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-hcc2c06a_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.341.0-h5279c79_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.3-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h0e34353_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-hca5e8e5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda @@ -902,19 +1680,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.10.3-py313h78bf25f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.3-py313h129903b_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.2.1-hb71707f_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.1-py313h7037e92_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.2.2-hc2c9f91_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.2-py313h2551ef2_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.7.2-nompi_py313hfae5b86_104.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nlohmann_json-3.12.0-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.5-py313hf6604e3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.10-he970967_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.2.2-hbb90d81_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.0-py313ha87cce1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.46-h1321c63_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py313h80991f8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py313h3b26573_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.6.2-h18fbb6c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/prometheus-cpp-1.3.0-ha5d0236_0.conda @@ -922,7 +1700,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-19.0.1-py313h78bf25f_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-19.0.1-py313he109ebe_2_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.5-py313ha296275_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyogrio-0.11.0-py313hab4ff3b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.1-py313hcd509b5_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.10.1-py313h85046ba_0.conda @@ -938,11 +1716,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py313h4b8bb8b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py313hfc84eb1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.2-hbc0de68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.4-h6e79ffa_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.7-py313h07c4f96_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.8-py313h995f894_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-hd6090a7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda @@ -970,23 +1748,25 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/affine-2.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 @@ -995,124 +1775,136 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda osx-arm64: - - conda: https://conda.anaconda.org/conda-forge/noarch/affine-2.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.2-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda @@ -1134,13 +1926,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-blobs-cpp-12.16.0-h5446563_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-common-cpp-12.13.0-he467506_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/azure-storage-files-datalake-cpp-12.14.0-hdc9d693_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.6.0-py313h7208f8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py313h8c7ad59_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-h7d5ae5b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313hde1f3bb_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cftime-1.6.5-py313hf5115c3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py313h2af2deb_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/curl-8.21.0-hd5a2499_0.conda @@ -1159,13 +1951,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/hdf5-1.14.6-nompi_had3affe_110.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/json-c-0.18-he4178ee_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py313h2af2deb_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.1-py313h738389e_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-hfd3d5f3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libaec-1.1.5-h8664d51_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.8-gpl_h6fbacd7_100.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h6fbacd7_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-19.0.1-h96c63c9_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-acero-19.0.1-h0f28eaf_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-dataset-19.0.1-h0f28eaf_45_cpu.conda @@ -1177,7 +1969,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-8_hb0561ab_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcrc32c-1.1.2-hbdafb3b_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.21.0-hd5a2499_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda @@ -1186,17 +1978,17 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.5.2-hcf2aa1b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-arrow-parquet-3.10.3-h2616c01_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-core-3.10.3-h9f236a5_18.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-hdf5-3.10.3-hdacffec_18.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-2.39.0-h2f60c08_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgoogle-cloud-storage-2.39.0-ha114238_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgrpc-1.78.1-h3e3f78d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.4.1-h84a0fba_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libkml-1.3.0-hb833057_1023.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-8_hd9741b5_openblas.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda @@ -1212,10 +2004,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libre2-11-2025.11.05-h4c27e2a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librttopo-1.1.0-hf7cb3ef_19.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libspatialite-5.1.0-h823fe50_16.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1b79a29_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h34a968b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libthrift-0.22.0-h1fb9c8a_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-hf67920b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda @@ -1224,32 +2016,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-devel-2.15.1-h9329255_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzip-1.11.2-h1336266_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc7d1edf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.0-hdb3d66b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-4.4.5-py313hd065f0a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py313h65a2061_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.10.3-py313h39782a4_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.10.3-py313haaf02c0_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.2.1-hdb7fadc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.1-py313h5c29297_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.2.2-h56c1371_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py313hcb059c8_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/netcdf4-1.7.2-nompi_py313hb28a5cb_104.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/nlohmann_json-3.12.0-h784d473_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.5-py313h16eae64_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.2.2-h578b684_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.0-py313h668b085_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.46-h7125dd6_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.2.0-py313h45e5a15_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py313h4ede67f_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/proj-9.6.2-hdbeaa80_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/prometheus-cpp-1.3.0-h0967b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py313h6688731_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-19.0.1-py313h39782a4_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-core-19.0.1-py313hcc89289_2_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py313h680bcb3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyogrio-0.11.0-py313h4ad91d6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyproj-3.7.1-py313h84bd6f1_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.14-h448ec07_100_cp313.conda @@ -1262,9 +2054,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py313h52f5312_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/shapely-2.1.2-py313hfb5a6ed_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.2-h85ec8f2_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.4-hdb6324b_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.7-py313h0997733_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.8-py313hf5449f0_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uriparser-0.9.8-h00cdb27_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xerces-c-3.2.5-h92fc2f4_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda @@ -1274,15 +2066,15 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda win-64: - - conda: https://conda.anaconda.org/conda-forge/noarch/affine-2.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-h4c7d964_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh7428d3b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda @@ -1292,6 +2084,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/dask-2025.7.0-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/dask-core-2025.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/distributed-2025.7.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/face-26.0.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 @@ -1300,55 +2093,60 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.0-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/glom-24.11.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.1.0-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-base-0.24.0-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-geopandas-0.24.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/partd-1.4.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rioxarray-0.19.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/snuggs-1.4.7-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tblib-3.2.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/utm-0.7.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda - conda: https://conda.anaconda.org/conda-forge/noarch/xarray-2025.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/_openmp_mutex-4.5-20_gnu.conda @@ -1365,13 +2163,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/aws-checksums-0.2.10-hcb3a2da_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-crt-cpp-0.37.2-h5e571c5_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/aws-sdk-cpp-1.11.606-ha60a6cd_13.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.6.0-py313h2a31948_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py313h2a31948_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-1.2.0-h2d644bc_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py313h3ebfc14_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.34.6-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.34.8-h6a83c73_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h5782bbf_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/cftime-1.6.5-py313h0591002_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py313h1a38498_4.conda @@ -1379,7 +2177,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/cytoolz-1.1.0-py313h5ea7bf4_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.3.1-he0c23c2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/fiona-1.10.1-py313hfe59770_4.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.1-hd47e2ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.3-hd47e2ca_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/fonttools-4.63.0-py313hd650c13_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda @@ -1391,13 +2189,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/hdf4-4.2.15-h5557f11_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/hdf5-1.14.6-nompi_hae35d4c_106.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.0-py313h1a38498_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.1-py313h1a38498_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libaec-1.1.5-haf901d7_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.8-gpl_he24518a_100.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_he24518a_100.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-19.0.1-hd086c85_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-acero-19.0.1-h7d8d6a5_45_cpu.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-dataset-19.0.1-h7d8d6a5_45_cpu.conda @@ -1416,19 +2214,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_19.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-16.2.0-h110b43a_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-arrow-parquet-3.10.3-h4b7837c_18.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.10.3-h1dbae3e_18.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-hdf5-3.10.3-h0f01001_18.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.86.2-hd9c3897_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_19.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-16.2.0-h8ee18e1_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-2.39.0-h01c467a_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-storage-2.39.0-he04ea4c_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libgrpc-1.78.1-h9ff2b3e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.4.1-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1023.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-8_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda @@ -1440,12 +2238,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libre2-11-2025.11.05-h04e5de1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-h5ff11c1_19.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-h9d118f5_16.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.2-hf5d6505_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.22.0-h2e43b2f_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.1-h8f73337_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.341.0-h477610d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.357.0-h477610d_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxcb-1.17.0-h0e4246c_0.conda @@ -1455,32 +2253,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.43-h0fbe4c1_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzip-1.11.2-h3135430_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-23.1.0-h49e36cd_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-4.4.5-py313h4bbca4b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py313hd650c13_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.10.3-py313hfa70ccb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.10.3-py313h81b4f16_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.2.1-h0ffbb96_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_908.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.1-py313hf069bd2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.2.2-h8fa244a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.1.0-hac47afa_235.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.2-py313h1a38498_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/netcdf4-1.7.2-nompi_py313hbe59507_104.conda - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.3.5-py313hce7ae62_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.2.2-h0a1ad0e_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-2.3.0-py313hf91d08e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.46-h3402e2f_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.2.0-py313h38f99e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py313h38f99e1_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.6.2-h7990399_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py313h5fd188c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-h0e40799_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-19.0.1-py313hfa70ccb_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-core-19.0.1-py313h5921983_2_cpu.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.5-py313hfbe8231_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyogrio-0.11.0-py313h75b81ac_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyproj-3.7.1-py313hc3e4018_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.10.1-py313h475ba69_0.conda @@ -1494,16 +2291,16 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.0-py313he51e9a2_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/shapely-2.1.2-py313hae85795_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.2-hdb435a2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.4-hdb435a2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.7-py313h5ea7bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.8-py313h5ea7bf4_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/uriparser-0.9.8-h5a68840_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_39.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_39.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_39.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_39.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36247-h633cb9f_41.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.2.5-he0c23c2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.12-hba3369d_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.5-hba3369d_1.conda @@ -2041,19 +2838,19 @@ packages: - azure-storage-files-datalake-cpp >=12.14.0,<12.14.1.0a0 size: 302771 timestamp: 1778763856084 -- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.6.0-py313h18e8e13_0.conda - sha256: d7aca2b34335035ef80eaaebff4e5a0ae524b6f3792a82a144d38c4a81f42916 - md5: fbc7d3707f09cae6648e6eb1b6203a5c +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py313h8f72f4d_1.conda + sha256: b8e6cbfade3ff6b344f9acc5c69193bd1aa7a2dc792e60eb8e05470fae122a1b + md5: 7ce840e6cf17fdba00e3d662f1d89e36 depends: - python - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.13.* *_cp313 + - libgcc >=15 - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.13.* *_cp313 license: BSD-3-Clause AND MIT AND EPL-2.0 run_exports: {} - size: 242845 - timestamp: 1781450812380 + size: 243747 + timestamp: 1788491297080 - conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d md5: 2c2fae981fd2afd00812c92ac47d023d @@ -2171,19 +2968,19 @@ packages: purls: [] size: 206884 timestamp: 1744127994291 -- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - sha256: cc9accf72fa028d31c2a038460787751127317dcfa991f8d1f1babf216bb454e - md5: 920bb03579f15389b9e512095ad995b7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.8-hebe6cf0_2.conda + sha256: 9c37cc233238adf366ea75b0e845662a5be07ceadf62e458183516369340274b + md5: 3ad2b403be8fc5612b9159e2ce621f69 depends: + - libgcc >=15 - __glibc >=2.17,<3.0.a0 - - libgcc >=14 license: MIT license_family: MIT run_exports: weak: - - c-ares >=1.34.6,<2.0a0 - size: 207882 - timestamp: 1765214722852 + - c-ares >=1.34.8,<2.0a0 + size: 228700 + timestamp: 1787169971173 - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 md5: 09262e66b19567aff4f592fb53b28760 @@ -2593,25 +3390,25 @@ packages: purls: [] size: 265599 timestamp: 1730283881107 -- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.1-h27c8c51_0.conda - sha256: 2e50bdcebdf70a865b81f2456bbc586386451ec601c60f2b6cd22b8c40a2d384 - md5: e0e050cfa9fa85fe39632ab11cb7f3e0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.3-h4db4eae_1.conda + sha256: 5a3eb10b18a97223ab06b3a7f0d7f56658db2f2800e2f2af95836fe3bf55ba63 + md5: 922776b528a470ab5afa81fd42abfa1d depends: - __glibc >=2.17,<3.0.a0 - libexpat >=2.8.1,<3.0a0 - libfreetype >=2.14.3 - libfreetype6 >=2.14.3 - - libgcc >=14 - - libuuid >=2.42.1,<3.0a0 + - libgcc >=15 + - libuuid >=2.42.2,<3.0a0 - libzlib >=1.3.2,<2.0a0 license: MIT license_family: MIT run_exports: weak: - - fontconfig >=2.18.1,<3.0a0 + - fontconfig >=2.18.3,<3.0a0 - fonts-conda-ecosystem - size: 281880 - timestamp: 1780450077431 + size: 296288 + timestamp: 1786667377340 - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.63.0-py313h3dea7bd_0.conda sha256: e0029a390d7aef29bd6e7c12a3759f5e0b989930b5781e544ca9bac0abcd8442 md5: ae83c999b4cfc4c171ce88b99c8b43cc @@ -3080,20 +3877,20 @@ packages: - keyutils >=1.6.3,<2.0a0 size: 134088 timestamp: 1754905959823 -- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py313hc8edb43_0.conda - sha256: 0447d2901639f295989c5ccba7b1c367ed78b216e0d2705327a8c8a87a31177e - md5: b81883b9dbf5069821c2fb09a8ba1407 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.1-py313h2551ef2_1.conda + sha256: 773f5eb66391d92a39c6cf6651111594c4a3f9941540934af3b813529c083ce1 + md5: 6e7bc60a75f1668be8cb9b27191abe54 depends: - python - - libstdcxx >=14 - - libgcc >=14 + - libstdcxx >=15 + - libgcc >=15 - __glibc >=2.17,<3.0.a0 - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 76911 - timestamp: 1773067054809 + size: 75129 + timestamp: 1788505657484 - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 md5: 3f43953b7d3fb3aaa1d0d0723d91e368 @@ -3139,19 +3936,19 @@ packages: purls: [] size: 676044 timestamp: 1752032747103 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c - md5: 18335a698559cdbcd86150a48bf54ba6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 449500f2c089da11c40f5c21312e3e07 depends: - __glibc >=2.17,<3.0.a0 - zstd >=1.5.7,<1.6.0a0 constrains: - - binutils_impl_linux-64 2.45.1 + - binutils_impl_linux-64 2.46.1 license: GPL-3.0-only license_family: GPL run_exports: {} - size: 728002 - timestamp: 1774197446916 + size: 745303 + timestamp: 1784214507189 - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h0aef613_1.conda sha256: 412381a43d5ff9bbed82cd52a0bbca5b90623f62e41007c9c42d3870c60945ff md5: 9344155d33912347b37f0ae6c410a835 @@ -3164,9 +3961,9 @@ packages: purls: [] size: 264243 timestamp: 1745264221534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 - md5: a752488c68f2e7c456bcbd8f16eec275 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + sha256: bf9fdebf55d8bc99d83531cdffda00703f4dc5f93a1a956768c147362c72feda + md5: fb9d356b1a57d6d54768be7ebd5fce09 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -3175,9 +3972,9 @@ packages: license_family: Apache run_exports: weak: - - lerc >=4.1.0,<5.0a0 - size: 261513 - timestamp: 1773113328888 + - lerc >=4.2.0,<5.0a0 + size: 271158 + timestamp: 1785036167977 - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda sha256: a7a4481a4d217a3eadea0ec489826a69070fcc3153f00443aa491ed21527d239 md5: 6f7b4302263347698fd24565fbf11310 @@ -3229,9 +4026,9 @@ packages: purls: [] size: 883383 timestamp: 1749385818314 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.8-gpl_hc2c16d8_100.conda - sha256: f916b51f55f51a9bb2d902e0a5f029490f7b745aee549c349751c1787ddc26b8 - md5: 44652e646cb623f486ea72e7e7479222 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.9-gpl_hc2c16d8_100.conda + sha256: 000b4baa9ce849b5fb259af9256331d0c7872361ac63a2bba0d0c48eb35663d0 + md5: 3988040b14a8083b1a5382d99f584e5f depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 @@ -3248,9 +4045,9 @@ packages: license_family: BSD run_exports: weak: - - libarchive >=3.8.8,<3.9.0a0 - size: 867280 - timestamp: 1782289011634 + - libarchive >=3.8.9,<3.9.0a0 + size: 876197 + timestamp: 1785250447640 - conda: https://conda.anaconda.org/conda-forge/linux-64/libarrow-19.0.1-h3c191e6_45_cpu.conda build_number: 45 sha256: 26a010858a803bca78d1033c4fd081ea19794b07e0a9dcd8bd41487b09a9a381 @@ -3593,20 +4390,20 @@ packages: - libdeflate >=1.25,<1.26.0a0 size: 73490 timestamp: 1761979956660 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.127-hb03c661_0.conda - sha256: 7d3187c11b7ae66c5595a8afd5a7ce352a490527fdf6614cab129bc7f2c16ba3 - md5: d8d16b9b32a3c5df7e5b3350e2cbe058 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda + sha256: ea46b0ca0fa16af1f8b329b740e6cd8b4577c5378fa8717b28a885f70668633d + md5: 64cc91512b6278c315349dc13c53f680 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - libgcc >=15 - libpciaccess >=0.19,<0.20.0a0 license: MIT license_family: MIT run_exports: weak: - - libdrm >=2.4.127,<2.5.0a0 - size: 311505 - timestamp: 1778975798004 + - libdrm >=2.4.129,<2.5.0a0 + size: 313461 + timestamp: 1786684701973 - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 md5: c277e0a4d549b03ac1e9d6cbbe3d017b @@ -3769,20 +4566,20 @@ packages: purls: [] size: 824921 timestamp: 1750808216066 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 - md5: 57736f29cc2b0ec0b6c2952d3f101b6a +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + sha256: 24090e675d34403b4ee1cd4372d8f6c0937da7ecfd66a19a57cac2ed0f4ea793 + md5: cba14d01083fc62ffd32c24d7d390633 depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgcc-ng ==15.2.0=*_19 - - libgomp 15.2.0 he0feb66_19 + - libgcc-ng ==16.2.0=*_4 + - libgomp 16.2.0 he0feb66_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 1041084 - timestamp: 1778269013026 + size: 1058083 + timestamp: 1787618680111 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_3.conda sha256: b0b0a5ee6ce645a09578fc1cb70c180723346f8a45fdb6d23b3520591c6d6996 md5: e66f2b8ad787e7beb0f846e4bd7e8493 @@ -3793,18 +4590,18 @@ packages: purls: [] size: 29033 timestamp: 1750808224854 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda - sha256: 9dcf54adfaa5e861123c2da4f2f0451a685464ea7e5a41ad91cf67b31d658d98 - md5: 331ee9b72b9dff570d56b1302c5ab37d +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.2.0-h69a702a_4.conda + sha256: d8e66c14e23f2b3c70410cff5979d9d357e6edfb990b28d8e630852f4d395629 + md5: b3e52878163a841f6fb951989cc0b217 depends: - - libgcc 15.2.0 he0feb66_19 + - libgcc 16.2.0 ha9f2e26_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: strong: - libgcc - size: 27694 - timestamp: 1778269016987 + size: 28403 + timestamp: 1787618684957 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h6f5c62b_11.conda sha256: 19e5be91445db119152217e8e8eec4fd0499d854acc7d8062044fb55a70971cd md5: 68fc66282364981589ef36868b1a7c78 @@ -3912,18 +4709,18 @@ packages: purls: [] size: 29057 timestamp: 1750808257258 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda - sha256: 561a42758ef25b9ce308c4e2cf56daee4f06138385a17e29a492cd928e00be6f - md5: 42bf7eca1a951735fa06c0e3c0d5c8e6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.2.0-h69a702a_4.conda + sha256: 7653be4d88a4d74676c38dd892914d027dcecc0c65c406be772d31cb641f8cfd + md5: 5e92b8413fd1c8f8f3006f4661b12def depends: - - libgfortran5 15.2.0 h68bc16d_19 + - libgfortran5 16.2.0 h6b99dfc_4 constrains: - - libgfortran-ng ==15.2.0=*_19 + - libgfortran-ng ==16.2.0=*_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 27655 - timestamp: 1778269042954 + size: 28377 + timestamp: 1787618711732 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_3.conda sha256: eea6c3cf22ad739c279b4d665e6cf20f8081f483b26a96ddd67d4df3c88dfa0a md5: 530566b68c3b8ce7eec4cd047eae19fe @@ -3937,19 +4734,19 @@ packages: purls: [] size: 1565627 timestamp: 1750808236464 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda - sha256: 057978bb69fea29ed715a9b98adf71015c31baecc4aeb2bfc20d4fd5d83579d4 - md5: 85072b0ad177c966294f129b7c04a2d5 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.2.0-h6b99dfc_4.conda + sha256: a5510cbcea3b9e9ab24b336429de997fa727af1dba323031500a962a20e38600 + md5: c22348a769bb072b6184eb6f7f05e4f2 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=15.2.0 + - libgcc >=16.2.0 constrains: - - libgfortran 15.2.0 + - libgfortran 16.2.0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 2483673 - timestamp: 1778269025089 + size: 2526008 + timestamp: 1787618692926 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_3.conda sha256: ec353b3076ed8e357ed961d0e9ff6997491cade0e603de5bd18a2e301ac78ebd md5: f25206d7322c0e9648e8b83694d143ab @@ -4025,9 +4822,9 @@ packages: purls: [] size: 447068 timestamp: 1750808138400 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda - sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b - md5: faac990cb7aedc7f3a2224f2c9b0c26c +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + sha256: 0fe5cb8e0752241ab55e11656ed1b9726248b522d23b929fe7c95b83eb55b9bb + md5: 89d2c1231f47bd818f5d624b9411459d depends: - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 @@ -4035,8 +4832,8 @@ packages: run_exports: strong: - _openmp_mutex >=4.5 - size: 603817 - timestamp: 1778268942614 + size: 639968 + timestamp: 1787618616266 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.39.0-h9d11ab5_1.conda sha256: 44f8e354431d2336475465ec8d71df7f3dea1397e70df0718c2ac75137976c63 md5: cd398eb8374fb626a710b7a35b7ffa98 @@ -4137,9 +4934,9 @@ packages: purls: [] size: 628947 timestamp: 1745268527144 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda - sha256: 10056646c28115b174de81a44e23e3a0a3b95b5347d2e6c45cc6d49d35294256 - md5: 6178c6f2fb254558238ef4e6c56fb782 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda + sha256: bba8538e6538ed58a8479b332337b96986561f975d06cfa2039a016c2d246ee4 + md5: 898d1c9793eaa52efc4727bd84d2e39a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -4148,9 +4945,9 @@ packages: license: IJG AND BSD-3-Clause AND Zlib run_exports: weak: - - libjpeg-turbo >=3.1.4.1,<4.0a0 - size: 633831 - timestamp: 1775962768273 + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 650434 + timestamp: 1785896381946 - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1023.conda sha256: f6348ac9cebbc03f765ea6d2da88d57d19531585c8f3a963b98635b208e8bef1 md5: 953b7cca897e21215302dbfe2af5cd0c @@ -4694,19 +5491,19 @@ packages: purls: [] size: 935828 timestamp: 1752072043000 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.2-h0c1763c_0.conda - sha256: 1ab603b6ec93933e76027e1f23b21b22b858ba1b56f1e1695ef6fe5e80cb7358 - md5: 062b0ac602fb0adf250e3dfa86f221c4 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h0737f62_1.conda + sha256: 35c05bd0adfaf6fcac01fc3a42214e4e2a44e32f7ce2c77ddae419522c3c328d + md5: 17b328272d5caa0994678529be5b86ab depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - libgcc >=15 - libzlib >=1.3.2,<2.0a0 license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 957849 - timestamp: 1780574429573 + - libsqlite >=3.53.4,<4.0a0 + size: 968893 + timestamp: 1787051134858 - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda sha256: fa39bfd69228a13e553bd24601332b7cfeb30ca11a3ca50bb028108fe90a7661 md5: eecce068c7e4eddeb169591baac20ac4 @@ -4734,19 +5531,19 @@ packages: purls: [] size: 3896407 timestamp: 1750808251302 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - sha256: dff1058c76ec6b8759e41cefa2508162d00e4a5e6721aa68ec3fd10094e702dc - md5: 5794b3bdc38177caf969dabd3af08549 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + sha256: 40b792b0186c1e8859280a1f6f19a54fc50a11b32724fc7b637009c1a9bd302b + md5: 2f2ef0d96de5bdd8c1270ff22fdf9352 depends: - __glibc >=2.17,<3.0.a0 - - libgcc 15.2.0 he0feb66_19 + - libgcc 16.2.0 ha9f2e26_4 constrains: - - libstdcxx-ng ==15.2.0=*_19 + - libstdcxx-ng ==16.2.0=*_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 5852044 - timestamp: 1778269036376 + size: 6613148 + timestamp: 1787618704262 - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_3.conda sha256: bbaea1ecf973a7836f92b8ebecc94d3c758414f4de39d2cc6818a3d10cb3216b md5: 57541755b5a51691955012b8e197c06c @@ -4757,18 +5554,18 @@ packages: purls: [] size: 29093 timestamp: 1750808292700 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_19.conda - sha256: 0672b6b6e1791c92e8eccad58081a99d614fcf82bca5841f9dfa3c3e658f83b9 - md5: e5ce228e579726c07255dbf90dc62101 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.2.0-hdf11a46_4.conda + sha256: 4cdebd87b76cf53a58a08ebd6d15336daa79c5f0aa34d83a895ac503c0203632 + md5: de0dceacf3e33c5fc88e167885dc8274 depends: - - libstdcxx 15.2.0 h934c35e_19 + - libstdcxx 16.2.0 h934c35e_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: strong: - libstdcxx - size: 27776 - timestamp: 1778269074600 + size: 28459 + timestamp: 1787618737021 - conda: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.22.0-h7d032f7_2.conda sha256: af6025aa4a4fc3f4e71334000d2739d927e2f678607b109ec630cc17d716918a md5: b6e326fbe1e3948da50ec29cee0380db @@ -4804,26 +5601,26 @@ packages: purls: [] size: 429575 timestamp: 1747067001268 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda - sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 - md5: cd5a90476766d53e901500df9215e927 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-hcc2c06a_1.conda + sha256: 10e125da82ca93e09191e66e5a94d566b2cf8d4024e2a0db3a9114297447e0d9 + md5: 0907d876f460ebc941ae590338b6102f depends: - __glibc >=2.17,<3.0.a0 - - lerc >=4.0.0,<5.0a0 + - lerc >=4.2.0,<5.0a0 - libdeflate >=1.25,<1.26.0a0 - - libgcc >=14 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 - - libstdcxx >=14 + - libgcc >=15 + - libjpeg-turbo >=3.2.0,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libstdcxx >=15 - libwebp-base >=1.6.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 - zstd >=1.5.7,<1.6.0a0 license: HPND run_exports: weak: - - libtiff >=4.7.1,<4.8.0a0 - size: 435273 - timestamp: 1762022005702 + - libtiff >=4.7.2,<4.8.0a0 + size: 459753 + timestamp: 1787755584172 - conda: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.11.3-hfe17d71_0.conda sha256: ecbf4b7520296ed580498dc66a72508b8a79da5126e1d6dc650a7087171288f9 md5: 1247168fe4a0b8912e3336bccdbf98a5 @@ -4847,37 +5644,37 @@ packages: purls: [] size: 33601 timestamp: 1680112270483 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda - sha256: 9b1bdce27a7e31f7d241aeecff67a1f3101d52a2b1e33ccc2cdf2613072bf81f - md5: 01bb81d12c957de066ea7362007df642 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.3-hcfc3c73_0.conda + sha256: aa58bbba56644ffd062a4a9b358782c5eb7560ccead2ab8f7c5c6ede0a7d33a6 + md5: 74a0a409d9f4561265d36b789d3f398a depends: - - libgcc >=14 - __glibc >=2.17,<3.0.a0 + - libgcc >=15 license: BSD-3-Clause license_family: BSD run_exports: weak: - - libuuid >=2.42.2,<3.0a0 - size: 40017 - timestamp: 1781625522462 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.341.0-h5279c79_0.conda - sha256: a68280d57dfd29e3d53400409a39d67c4b9515097eba733aa6fe00c880620e2b - md5: 31ad065eda3c2d88f8215b1289df9c89 + - libuuid >=2.42.3,<3.0a0 + size: 39998 + timestamp: 1788347719520 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h0e34353_2.conda + sha256: a70c25b66321ee77f9892b205e3247263c400803f543dc8aca53d569a59c5a77 + md5: c5f5f159b66332152197893427071695 depends: - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - libgcc >=14 - - xorg-libx11 >=1.8.12,<2.0a0 + - libstdcxx >=15 + - libgcc >=15 - xorg-libxrandr >=1.5.5,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 constrains: - - libvulkan-headers 1.4.341.0.* + - libvulkan-headers 1.4.357.0.* license: Apache-2.0 license_family: APACHE run_exports: weak: - - libvulkan-loader >=1.4.341.0,<2.0a0 - size: 199795 - timestamp: 1770077125520 + - libvulkan-loader >=1.4.357.0,<2.0a0 + size: 206957 + timestamp: 1787491938583 - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b md5: aea31d2e5b1091feca96fcfe945c3cf9 @@ -4922,6 +5719,18 @@ packages: - libxcrypt >=4.4.36 size: 100393 timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + sha256: f7e9292dd219a6435bbb1223da9586c3e70d66d169c5a92f08db3f2127df04e9 + md5: f7a7ff5a6ab331e037abd34f379a631d + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: LGPL-2.1-or-later + run_exports: + weak: + - libxcrypt >=4.4.38 + size: 101957 + timestamp: 1785887123445 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.10.0-h65c71a3_0.conda sha256: a8043a46157511b3ceb6573a99952b5c0232313283f2d6a066cec7c8dcaed7d0 md5: fedf6bfe5d21d21d2b1785ec00a8889a @@ -5224,40 +6033,40 @@ packages: - pkg:pypi/menuinst?source=hash-mapping size: 172740 timestamp: 1750792417618 -- conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.2.1-hb71707f_0.conda - sha256: 41558b95a387ce2374260aa034a99c3a88cbb98e1a56510f4fa6839063de867b - md5: fe7b4ff5792fee512aad843294ea809a +- conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.2.2-hc2c9f91_1.conda + sha256: 83837ffc5db1abfc14de88f5c9f07d1a666f149cd95249777f0ed09073ae7c22 + md5: ade84857af069283ff1e3b7aacae1232 depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - - libgcc >=14 + - libgcc >=15 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.3,<6.0a0 - - libstdcxx >=14 + - libstdcxx >=15 - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.6,<4.0a0 + - openssl >=3.5.8,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: Zlib license_family: Other run_exports: weak: - - minizip >=4.2.1,<5.0a0 - size: 520570 - timestamp: 1778002506337 -- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.1-py313h7037e92_0.conda - sha256: 87a569daa29cb526cff5e9e5cca3fc92c479a92f13c0bac6a4ad6b38ef068f5b - md5: 4f7f0bbf165e65d6f620251860c098c2 + - minizip >=4.2.2,<5.0a0 + size: 526042 + timestamp: 1788051267376 +- conda: https://conda.anaconda.org/conda-forge/linux-64/msgpack-python-1.2.2-py313h2551ef2_2.conda + sha256: 902cc244b567552538ca7817673f302c8000b75cb72a46c62f70ad4c05c32462 + md5: 10eabac2cb17d77ce66a2d46890127bf depends: + - python - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - python >=3.13,<3.14.0a0 + - libstdcxx >=15 + - libgcc >=15 - python_abi 3.13.* *_cp313 license: Apache-2.0 - license_family: Apache + license_family: APACHE run_exports: {} - size: 102737 - timestamp: 1782070377224 + size: 114896 + timestamp: 1788525095321 - conda: https://conda.anaconda.org/conda-forge/linux-64/mypy-1.16.1-py312h66e93f0_0.conda sha256: 2d1dca2a580374470e8a108565356e13aec8598c83eec17d888a4cc0b014cddd md5: d52e9cc0c93e47a87e1024158ed2bcd3 @@ -5426,20 +6235,20 @@ packages: purls: [] size: 3131002 timestamp: 1751390382076 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - sha256: d48f5c22b9897c01e4dff3680f1f57ceb02711ab9c62f74339b080419dfad34b - md5: 79dd2074b5cd5c5c6b2930514a11e22d +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda + sha256: 4747b2d6a8336f52343bceb8a1ebf41a9e6e665b9d2e3f989972de53a310599e + md5: 16e3034a330cc625f2c685edec60cf4e depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - - libgcc >=14 + - libgcc >=15 license: Apache-2.0 license_family: Apache run_exports: weak: - - openssl >=3.6.3,<4.0a0 - size: 3159683 - timestamp: 1781069855778 + - openssl >=3.6.4,<4.0a0 + size: 3202955 + timestamp: 1787698780103 - conda: https://conda.anaconda.org/conda-forge/linux-64/orc-2.2.2-hbb90d81_1.conda sha256: c59d22c4e555c09259c52da96f1576797fcb4fba5665073e9c1907393309172d md5: 9269175175f18091b8844c8e9f213205 @@ -5612,28 +6421,28 @@ packages: - pcre2 >=10.46,<10.47.0a0 size: 1209177 timestamp: 1756742976157 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py313h80991f8_0.conda - sha256: 55a76548bb003ff6deac9bf209b279d428030f230632fb70f15ae153aed05158 - md5: 7245f1bbf52ed5e3818d742f51b44a7d +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py313h3b26573_2.conda + sha256: 400597a5bda2bf748536c2b69c812078901bd9bc2ee8187fd84ac29f3ad578b3 + md5: 56e4487df0f02a73655d1a24c9a68906 depends: - python - - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libtiff >=4.7.1,<4.8.0a0 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libxcb >=1.17.0,<2.0a0 - - libwebp-base >=1.6.0,<2.0a0 - - tk >=8.6.13,<8.7.0a0 - - libfreetype >=2.14.3 - - libfreetype6 >=2.14.3 + - libgcc >=15 - python_abi 3.13.* *_cp313 - zlib-ng >=2.3.3,<2.4.0a0 + - libtiff >=4.7.2,<4.8.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 - openjpeg >=2.5.4,<3.0a0 - - lcms2 >=2.18,<3.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - libxcb >=1.17.0,<2.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - lcms2 >=2.19.1,<3.0a0 + - tk >=8.6.13,<8.7.0a0 license: HPND run_exports: {} - size: 1052168 - timestamp: 1775060059882 + size: 1072892 + timestamp: 1788610327434 - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.2-h29eaf8c_0.conda sha256: 6cb261595b5f0ae7306599f2bb55ef6863534b6d4d1bc0dcfdfa5825b0e4e53d md5: 39b4228a867772d610c02e06f939a5b8 @@ -5818,22 +6627,22 @@ packages: - pkg:pypi/pydantic-core?source=hash-mapping size: 1890081 timestamp: 1746625309715 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py313h843e2db_0.conda - sha256: d6705962afa2655cc22edcd075ce1f7e67c4407c005137d6d63c0dc5eaa76f47 - md5: ef0f9efec6ec28b17e22f787c7672c67 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.5-py313ha296275_1.conda + sha256: ba744cf30856ccda82fa4dca2058294fd3574c0f36e737b16411b49eaa5f24cd + md5: 41286094042803a6cd590db9d13f5aa1 depends: - python - typing-extensions >=4.6.0,!=4.7.0 + - libgcc >=15 - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - python_abi 3.13.* *_cp313 constrains: - __glibc >=2.17 license: MIT license_family: MIT run_exports: {} - size: 1893749 - timestamp: 1778084222642 + size: 1877111 + timestamp: 1787928978287 - conda: https://conda.anaconda.org/conda-forge/linux-64/pydot-4.0.1-py312h7900ff3_0.conda sha256: be87ec5ee93853c3deb8115099c74f002e0af711410e23b1ae14cc30c12c0041 md5: 6ff35cf1336b0aa49338b8171aa74c94 @@ -6360,22 +7169,22 @@ packages: license_family: MIT size: 3707065 timestamp: 1775241332871 -- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.2-hbc0de68_0.conda - sha256: b7c3217b437f8aa531b4a18c89dc137b6066757f1e93146dc0d8d999ef55da09 - md5: 38d9bf35a4cc83094a327811e548b660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.4-h6e79ffa_1.conda + sha256: 45babc48aef7a468175a609848d56242e43c043a5c2a4d595c220cfc31488028 + md5: 237875bda674b2da7ca68765ffb22645 depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libsqlite 3.53.2 h0c1763c_0 + - libgcc >=15 + - libsqlite 3.53.4 h0737f62_1 - libzlib >=1.3.2,<2.0a0 - ncurses >=6.6,<7.0a0 - readline >=8.3,<9.0a0 license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 205545 - timestamp: 1780574435288 + - libsqlite >=3.53.4,<4.0a0 + size: 205639 + timestamp: 1787051140325 - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac md5: cffd3bdd58090148f4cfcd831f4b26ab @@ -6418,19 +7227,19 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 850902 timestamp: 1748003427956 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.7-py313h07c4f96_0.conda - sha256: ed2f17b2bc92389187b47e7444aa7d6ab6c70c3f3ba6bf2ced508027e60e82cb - md5: 9665531f18d2bcbc946e3ba0cf53ea91 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.8-py313h995f894_1.conda + sha256: 0816baa2de2bae5bcb590ef8b649577b2eb9e823525632dc66d24a4918a73798 + md5: 4d57957c3ef7171f5c4f654c1e8fb0ac depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=14 + - libgcc >=15 - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 license: Apache-2.0 license_family: Apache run_exports: {} - size: 885824 - timestamp: 1781006802459 + size: 892774 + timestamp: 1788524837756 - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda sha256: 2aad2aeff7c69a2d7eecd7b662eef756b27d6a6b96f3e2c2a7071340ce14543e md5: d71d3a66528853c0a1ac2c02d79a0284 @@ -6458,12 +7267,12 @@ packages: purls: [] size: 330474 timestamp: 1751817998141 -- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda - sha256: ea374d57a8fcda281a0a89af0ee49a2c2e99cc4ac97cf2e2db7064e74e764bdb - md5: 996583ea9c796e5b915f7d7580b51ea6 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-hd6090a7_0.conda + sha256: 6b9e182021ef3a64ab3bf788ebdab6de6775035612237c639ccafc941639eb13 + md5: b34c5559f45d8996e3bc0b6250a6cc84 depends: - __glibc >=2.17,<3.0.a0 - - libexpat >=2.7.4,<3.0a0 + - libexpat >=2.8.1,<3.0a0 - libffi >=3.5.2,<3.6.0a0 - libgcc >=14 - libstdcxx >=14 @@ -6471,9 +7280,9 @@ packages: license_family: MIT run_exports: weak: - - wayland >=1.25.0,<2.0a0 - size: 334139 - timestamp: 1773959575393 + - wayland >=1.26.0,<2.0a0 + size: 340543 + timestamp: 1784249169392 - conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-1.17.2-py312h66e93f0_0.conda sha256: ed3a1700ecc5d38c7e7dc7d2802df1bc1da6ba3d6f6017448b8ded0affb4ae00 md5: 669e63af87710f8d52fdec9d4d63b404 @@ -7081,16 +7890,17 @@ packages: purls: [] size: 619606 timestamp: 1750236493212 -- conda: https://conda.anaconda.org/conda-forge/noarch/affine-2.4.0-pyhd8ed1ab_1.conda - sha256: 0deeaf0c001d5543719db9b2686bc1920c86c7e142f9bec74f35e1ce611b1fc2 - md5: 8c4061f499edec6b8ac7000f6d586829 +- conda: https://conda.anaconda.org/conda-forge/noarch/affine-3.0.1-pyhecae5ae_0.conda + sha256: 8801cd5b4e0f0203b7a18e8def15929f3413e93e2da88d5b918b735454106f85 + md5: aeb404cab9e719a49264ab40928f80c8 depends: - - python >=3.9 + - attrs >=21.3.0 + - python >=3.10 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 19164 - timestamp: 1733762153202 + size: 22619 + timestamp: 1788134268940 - conda: https://conda.anaconda.org/conda-forge/noarch/amply-0.1.6-pyhd8ed1ab_1.conda sha256: e8d87cb66bcc62bc8d8168037b776de962ebf659e45acb1a813debde558f7339 md5: 5a81866192811f3a0827f5f93e589f02 @@ -7116,6 +7926,17 @@ packages: run_exports: {} size: 18074 timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.8.0-pyhd8ed1ab_0.conda + sha256: b8fcb994134d3918d1c64a9d62f4168ff85d5bfb89e698102fe4ed679fe0df24 + md5: 108c928d2a8551832dbc762b535e90bb + depends: + - python >=3.10 + - typing-extensions >=4.0.0 + license: MIT + license_family: MIT + run_exports: {} + size: 19461 + timestamp: 1784935220549 - conda: https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyhd8ed1ab_1.conda sha256: 5b9ef6d338525b332e17c3ed089ca2f53a5d74b7a7b432747d29c6466e39346d md5: f4e90937bbfc3a4a92539545a37bb448 @@ -7209,9 +8030,9 @@ packages: license_family: MIT size: 171751 timestamp: 1773315364851 -- conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.9.1-pyhd8ed1ab_0.conda - sha256: b3d3b93a17fa678e81cd5ff5309fe64c7feb65c71cb134f14e4fe2113dc0c8d5 - md5: 123fcfe0df4b6fc21804538e59cdaafe +- conda: https://conda.anaconda.org/conda-forge/noarch/bokeh-3.10.0-pyhd8ed1ab_0.conda + sha256: 34bb5cc4f6ccff1e410dea2d39880190f88a8be7ad5cfe9ff0da2cf3d2ad7ea1 + md5: 67d0ed593951a4933024453b76ef1355 depends: - contourpy >=1.2 - jinja2 >=2.9 @@ -7219,7 +8040,7 @@ packages: - numpy >=1.16 - packaging >=16.8 - pillow >=7.1.0 - - python >=3.10 + - python >=3.12 - pyyaml >=3.10 - tornado >=6.2 - xyzservices >=2021.09.1 @@ -7228,8 +8049,8 @@ packages: license: BSD-3-Clause license_family: BSD run_exports: {} - size: 4526315 - timestamp: 1781002115296 + size: 4454345 + timestamp: 1787144786949 - conda: https://conda.anaconda.org/conda-forge/noarch/boltons-25.0.0-pyhd8ed1ab_0.conda sha256: ea5f4c876eff2ed469551b57f1cc889a3c01128bf3e2e10b1fea11c3ef39eac2 md5: c7eb87af73750d6fd97eff8bbee8cb9c @@ -7241,16 +8062,15 @@ packages: - pkg:pypi/boltons?source=hash-mapping size: 302296 timestamp: 1749686302834 -- conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.0.0-pyhd8ed1ab_0.conda - sha256: d3c8be0524a5223bfed89c4a5be90598bfca28e0c702014f7799b978027eb5e1 - md5: 06c2cbdcfd20af2f72a95e09f8747c52 +- conda: https://conda.anaconda.org/conda-forge/noarch/boltons-26.2.0-pyhd8ed1ab_0.conda + sha256: 8fd76dc72d31a8688159039cfbb17eabe21c65792a96b5245be04e5887c2291c + md5: 52f3c30dbe025efb8050021f35648949 depends: - - python >=3.10 + - python >=3.11 license: BSD-3-Clause - license_family: BSD run_exports: {} - size: 307488 - timestamp: 1781879511321 + size: 321343 + timestamp: 1788865918329 - conda: https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda sha256: 1acf87c77d920edd098ddc91fa785efc10de871465dee0f463815b176e019e8b md5: 1fcdf88e7a8c296d3df8409bf0690db4 @@ -7280,24 +8100,24 @@ packages: purls: [] size: 152448 timestamp: 1752037382564 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-h4c7d964_0.conda - sha256: 7f458e4a82514d7bebbfef23d92817794a16aaf1c748a15f04870d4fb49aeab2 - md5: b9696b2cf00dfeec138c70cee38ed192 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + sha256: 95e8e74062a5fe5f870ac8c90302b6e89945165fdaed7810606e84ddee6aac12 + md5: e27d2ac27b096dc51fedfcf775a53f9b depends: - __win license: ISC run_exports: {} - size: 129352 - timestamp: 1781709016515 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda - sha256: f8e3c730fa14ee3f170493779f06522c4acf89169f43db4f039727709b6419cf - md5: a9965dd99f683c5f444428f896635716 + size: 132136 + timestamp: 1784754918886 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 + md5: 0f51e2391ade309db462a55611263e9c depends: - __unix license: ISC run_exports: {} - size: 128866 - timestamp: 1781708962055 + size: 131780 + timestamp: 1784754889428 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.7.9-pyhd8ed1ab_0.conda sha256: d5bcebb3748005b50479055b69bd6a19753219effcf921b9158ef3ff588c752b md5: fac657ab965a05f69ba777a7b934255a @@ -7308,15 +8128,15 @@ packages: - pkg:pypi/certifi?source=compressed-mapping size: 156733 timestamp: 1752115379962 -- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.6.17-pyhd8ed1ab_0.conda - sha256: 6c13620e458ba43278379d0cdacc30c497336bddfda81681fd50d114a65c702f - md5: c13824fedced67005d3832c152fe9c2f +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: 37e13edbe3b48f1095a9d085ef9cd83b depends: - python >=3.10 license: ISC run_exports: {} - size: 133877 - timestamp: 1781719949728 + size: 137015 + timestamp: 1784717699092 - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda sha256: 535ae5dcda8022e31c6dc063eb344c80804c537a5a04afba43a845fa6fa130f5 md5: 40fe4284b8b5835a9073a645139f35af @@ -7328,16 +8148,16 @@ packages: - pkg:pypi/charset-normalizer?source=hash-mapping size: 50481 timestamp: 1746214981991 -- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda - sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 - md5: a9167b9571f3baa9d448faa2139d1089 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + sha256: cb60ef3e0631c8bacb4f7057196dee4496091a22baa3bb4b9bccb12c7e1c921b + md5: e0ac3accc64e23e40969d660e5f58ac8 depends: - python >=3.10 license: MIT license_family: MIT run_exports: {} - size: 58872 - timestamp: 1775127203018 + size: 64487 + timestamp: 1786835648298 - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.2.1-pyh707e725_0.conda sha256: 8aee789c82d8fdd997840c952a586db63c6890b00e88c4fb6e80a38edd5f51c0 md5: 94b550b8d3a614dbd326af798c7dfb40 @@ -7660,6 +8480,16 @@ packages: - pkg:pypi/exceptiongroup?source=compressed-mapping size: 21284 timestamp: 1746947398083 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + run_exports: {} + size: 21333 + timestamp: 1763918099466 - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.0-pyhd8ed1ab_0.conda sha256: 7510dd93b9848c6257c43fdf9ad22adf62e7aa6da5f12a6a757aed83bcfedf05 md5: 81d30c08f9a3e556e8ca9e124b044d14 @@ -7771,16 +8601,16 @@ packages: run_exports: {} size: 4059 timestamp: 1762351264405 -- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.6.0-pyhd8ed1ab_0.conda - sha256: fe0156e6d658be3531aad2a99e42e8ad1ee23c69837d469c44c1b6010373913d - md5: 7d7e6c826ba0743fc491ebee0e7b899c +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.7.0-pyhd8ed1ab_0.conda + sha256: 3cd1c985695d8114bdba2a4a38c87e86d633fadd7cfe7a6733ebb3fe807fdc86 + md5: b9176565976c773a0739bd83deaf06cc depends: - python >=3.10 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 149709 - timestamp: 1781615868173 + size: 151868 + timestamp: 1785325238671 - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.0-pyhd8ed1ab_0.conda sha256: 6f5658fc545065c71a61523a9b35c888a44d008d908963b7686e7e8a302bb61e md5: 6172cf7532433bf6b19ba5012444ee06 @@ -7863,19 +8693,19 @@ packages: - pkg:pypi/h2?source=hash-mapping size: 53888 timestamp: 1738578623567 -- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda - sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 - md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + sha256: 307dd6ec90140c3cf4171071b0e5e870abec314f4565c1edd5bc433e942cdcc0 + md5: e652ac7756069c456d0da2a922cd7df5 depends: - python >=3.10 - hyperframe >=6.1,<7 - - hpack >=4.1,<5 + - hpack >=4.2,<5 - python license: MIT license_family: MIT run_exports: {} - size: 95967 - timestamp: 1756364871835 + size: 100789 + timestamp: 1785796355216 - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba md5: 0a802cb9888dd14eeefc611f05c40b6e @@ -7945,17 +8775,17 @@ packages: - pkg:pypi/idna?source=hash-mapping size: 49765 timestamp: 1733211921194 -- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.18-pyhcf101f3_0.conda - sha256: c75632ea624aa450a394f570749420c5a2e0997d0216bc29d5d45b0f39df0426 - md5: 577b04680ae422adb86fc60d7b940659 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + sha256: 1c35a59c1545ad0fdaddf1fbde7bcfa6ef41a8d68c3a2b0b4a291be00676163e + md5: a39ae05027e9b707742e41b30d296b75 depends: - python >=3.10 - python license: BSD-3-Clause license_family: BSD run_exports: {} - size: 163869 - timestamp: 1781620148226 + size: 177433 + timestamp: 1787059857580 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 md5: 63ccfdc3a3ce25b027b8767eb722fca8 @@ -7969,9 +8799,9 @@ packages: - pkg:pypi/importlib-metadata?source=hash-mapping size: 34641 timestamp: 1747934053147 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda - sha256: 43e2a5497cad1598ff88a3e69f69bc88b7b8f141fa63c60eab5db296317318b8 - md5: ffc17e785d64e12fc311af9184221839 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.1-pyhcf101f3_0.conda + sha256: 95a074a7d3f58b3494c068d4789c364dcb591c56b70ca6b12f149a540f9aeea4 + md5: 77ec68e0aa61c3fff9ecbf840f93d64e depends: - python >=3.10 - zipp >=3.20 @@ -7979,8 +8809,8 @@ packages: license: Apache-2.0 license_family: APACHE run_exports: {} - size: 34766 - timestamp: 1779714582554 + size: 34920 + timestamp: 1787943971257 - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 md5: c85c76dc67d75619a92f51dfbce06992 @@ -8006,6 +8836,16 @@ packages: - pkg:pypi/iniconfig?source=hash-mapping size: 11474 timestamp: 1733223232820 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 13387 + timestamp: 1760831448842 - conda: https://conda.anaconda.org/conda-forge/noarch/ipdb-0.13.13-pyhd8ed1ab_1.conda sha256: 33275d537122e67df200203d541170db8b55886667d30cc7262cc1e463b04406 md5: 044c5249ad8ea18a414d07baa1f369ea @@ -8190,17 +9030,18 @@ packages: - pkg:pypi/jinja2?source=compressed-mapping size: 112714 timestamp: 1741263433881 -- conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - sha256: 301539229d7be6420c084490b8145583291123f0ce6b92f56be5948a2c83a379 - md5: 615de2a4d97af50c350e5cf160149e77 +- conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.6.0-pyhcf101f3_0.conda + sha256: 96b76ace583404ce4fbc234baa6cdb3de485887913104d4ac51994f9b6656696 + md5: 4ebc70ef1af2e21cdc8edb1bfa1ec28d depends: - python >=3.10 - - setuptools + - cloudpickle >=3.0 + - python license: BSD-3-Clause license_family: BSD run_exports: {} - size: 226448 - timestamp: 1765794135253 + size: 228858 + timestamp: 1788177170105 - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpatch-1.33-pyhd8ed1ab_1.conda sha256: 304955757d1fedbe344af43b12b5467cca072f83cce6109361ba942e186b3993 md5: cb60ae9cf02b9fcb8004dec4089e5691 @@ -8300,21 +9141,21 @@ packages: run_exports: {} size: 8250 timestamp: 1650660473123 -- conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda - sha256: 967841d300598b17f76ba812e7dae642176692ed2a6735467b93c2b2debe35c1 - md5: cc293b4cad9909bf66ca117ea90d4631 +- conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.11.0-pyhd8ed1ab_0.conda + sha256: 791137f3e107c73315c74bb0c7be34e5f7c36da1828efbc4584544311121484c + md5: 0323c95182d828bb1bcf708c4077db3b depends: - - networkx >=3.2 - - numpy >=1.26 - - pandas >=2.1 - - python >=3.11 - - scikit-learn >=1.4 - - scipy >=1.12 + - networkx >=3.4 + - numpy >=2.0 + - pandas >=2.3 + - python >=3.12 + - scikit-learn >=1.5 + - scipy >=1.14 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 810830 - timestamp: 1752271625200 + size: 448635 + timestamp: 1786480374013 - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.1.7-pyhd8ed1ab_1.conda sha256: 69b7dc7131703d3d60da9b0faa6dd8acbf6f6c396224cf6aef3e855b8c0c41c6 md5: af6ab708897df59bd6e7283ceab1b56b @@ -8349,17 +9190,17 @@ packages: run_exports: {} size: 11766 timestamp: 1745776666688 -- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda - sha256: dd2744a501f2db0aef084566bf3d0c2b312661dc91beb5a4cc97d27cdda0a959 - md5: 9450fb40fb1e147d0bcbdf07cd02ca96 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.25.0-pyhcf101f3_0.conda + sha256: e48d972fae8e32ca43eefd1a9649afa3a82b4dda5fe12caa441f0bc9e8d1938d + md5: 0117bf65e45c951a9b0004172cfaeef6 depends: - python >=3.10 - python license: MIT license_family: MIT run_exports: {} - size: 285532 - timestamp: 1780672242196 + size: 293989 + timestamp: 1787261063562 - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_1.conda sha256: 7a5bd30a2e7ddd7b85031a5e2e14f290898098dc85bea5b3a5bf147c25122838 md5: bbe1963f1e47f594070ffe87cdf612ea @@ -8431,17 +9272,17 @@ packages: - pkg:pypi/packaging?source=compressed-mapping size: 62477 timestamp: 1745345660407 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda - sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 - md5: 4c06a92e74452cfa53623a81592e8934 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + sha256: c432626b16768b8dab228bfb706f7060c2d462a21c516d240f68f2f902b5a044 + md5: 936687ed80f295a1f5dbcf8bd34c252c depends: - - python >=3.8 + - python >=3.9 - python license: Apache-2.0 license_family: APACHE run_exports: {} - size: 91574 - timestamp: 1777103621679 + size: 116363 + timestamp: 1785888127370 - conda: https://conda.anaconda.org/conda-forge/noarch/pandera-0.24.0-hd8ed1ab_2.conda sha256: 857657552f1a8441e4c9e723637b8270f46b6262403dc18a76559dd5a5ba782e md5: a12a21a89519f0cc224e44da86f5be2c @@ -8607,6 +9448,17 @@ packages: - pkg:pypi/pluggy?source=hash-mapping size: 24246 timestamp: 1747339794916 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 25877 + timestamp: 1764896838868 - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b md5: d17ae9db4dc594267181bd199bf9a551 @@ -8678,21 +9530,21 @@ packages: - pkg:pypi/pydantic?source=compressed-mapping size: 307333 timestamp: 1749927245525 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda - sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f - md5: 729843edafc0899b3348bd3f19525b9d +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.5-pyhcf101f3_0.conda + sha256: 701ed8122021f62e1c9a46bdf4932ada911c10afa56ba0459f56da940bcbe5a7 + md5: 2d0f9304fc1cb1e3e94e7b37c14cd70a depends: - typing-inspection >=0.4.2 - typing_extensions >=4.14.1 - python >=3.10 - annotated-types >=0.6.0 - - pydantic-core ==2.46.4 + - pydantic-core ==2.46.5 - python license: MIT license_family: MIT run_exports: {} - size: 346511 - timestamp: 1778103405862 + size: 346633 + timestamp: 1787941273167 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a md5: 6b6ece66ebcae2d5f326c77ef2c5a066 @@ -8704,6 +9556,17 @@ packages: - pkg:pypi/pygments?source=compressed-mapping size: 889287 timestamp: 1750615908735 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + sha256: f5f015ff1bc3e1b7fc08eee096b1865234189ae0b82bebdb86a5369116e42fa0 + md5: 2882dee445dfa45b0c5afce3ffb7730a + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 959376 + timestamp: 1786995678795 - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.2.3-pyhd8ed1ab_1.conda sha256: b92afb79b52fcf395fd220b29e0dd3297610f2059afac45298d44e00fcbf23b6 md5: 513d3c262ee49b54a8fec85c5bc99764 @@ -8773,6 +9636,26 @@ packages: - pkg:pypi/pytest?source=hash-mapping size: 276562 timestamp: 1750239526127 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + sha256: 430051d80765207a7d782b2b188230ba1489d35c6e75fd9903f76cb9fda4af16 + md5: 64c98a12c4e23eb238bf66bbecafdf3c + depends: + - colorama + - pygments >=2.7.2 + - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 + - tomli >=1 + - exceptiongroup >=1 + - python + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + run_exports: {} + size: 306724 + timestamp: 1782127176429 - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 md5: 5b8d21249ff20967101ffa321cab24e8 @@ -8809,16 +9692,16 @@ packages: - pkg:pypi/tzdata?source=compressed-mapping size: 144160 timestamp: 1742745254292 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - sha256: e943f9c15a6bdba2e1b9f423ab913b3f6b02197b0ef9f8e6b7464d78b59965b9 - md5: f6ad7450fc21e00ecc23812baed6d2e4 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + sha256: 3f05db78cf8be33cf6dbc469664b8e3a01f3980d61d6d6bef48669b171896d8a + md5: eefc8d916bd2e708d76d40398ef9a1ee depends: - python >=3.10 license: Apache-2.0 license_family: APACHE run_exports: {} - size: 146639 - timestamp: 1777068997932 + size: 146862 + timestamp: 1783704822814 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda build_number: 7 sha256: a1bbced35e0df66cc713105344263570e835625c28d1bdee8f748f482b2d7793 @@ -8851,17 +9734,17 @@ packages: license_family: MIT size: 201725 timestamp: 1773679724369 -- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda - sha256: 5020863d629f584b5c057333a67a7aed43e3ed013ba15dd70f353501ccb5aff6 - md5: 03cb60f505ad3ada0a95277af5faeb1a +- conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.3.post1-pyhcf101f3_0.conda + sha256: ad774abda71c759baef515983bfedbba34d56d6227974c23715c04612f812a90 + md5: eb2fb9070c0232e96ae5515d8b28e4f9 depends: - python >=3.10 - python license: MIT license_family: MIT run_exports: {} - size: 201747 - timestamp: 1777892201250 + size: 201126 + timestamp: 1785077087428 - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.36.2-pyh29332c3_0.conda sha256: e20909f474a6cece176dfc0dc1addac265deb5fa92ea90e975fbca48085b20c3 md5: 9140f1c09dd5489549c6a33931b943c7 @@ -8938,16 +9821,16 @@ packages: - pkg:pypi/setuptools?source=hash-mapping size: 748788 timestamp: 1748804951958 -- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda - sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1 - md5: 8e194e7b992f99a5015edbd4ebd38efd +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + sha256: 9e200ee5f9ff19a4d94e4b51c4856d53dec849f91032f345cf0c6bc3d51a7183 + md5: 62ac906f1cd582c6c264c95625cb9d6f depends: - python >=3.10 license: MIT license_family: MIT run_exports: {} - size: 639697 - timestamp: 1773074868565 + size: 524488 + timestamp: 1786282924579 - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda sha256: 41db0180680cc67c3fa76544ffd48d6a5679d96f4b71d7498a759e94edc9a2db md5: a451d576819089b0d672f18768be0f65 @@ -9116,6 +9999,17 @@ packages: - pkg:pypi/tomli?source=hash-mapping size: 19167 timestamp: 1733256819729 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 21561 + timestamp: 1774492402955 - conda: https://conda.anaconda.org/conda-forge/noarch/toolz-1.1.0-pyhd8ed1ab_1.conda sha256: 4e379e1c18befb134247f56021fdf18e112fb35e64dd1691858b0a0f3bea9a45 md5: c07a6153f8306e45794774cf9b13bd32 @@ -9176,21 +10070,20 @@ packages: - pkg:pypi/typeguard?source=hash-mapping size: 35158 timestamp: 1750249264892 -- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda - sha256: 59d7851d32fddb5b510272e6557aa982edeb927d349648dac27f5bf01d18bb26 - md5: 4460f039b7dedf15f7df086446ca75ae +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.6.0-pyhcf101f3_0.conda + sha256: 82cb76b4dd554d1da90a9e1ea5f99e24aaa4997bcd19ceef8233fc17d9da432e + md5: a61716f1640d9986d5d6de1c4d140b42 depends: - typing_extensions >=4.14.0 - python >=3.10 - - importlib-metadata >=3.6 - python constrains: - pytest >=7 license: MIT license_family: MIT run_exports: {} - size: 38297 - timestamp: 1778779291237 + size: 38525 + timestamp: 1785075519409 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.14.1-h4440ef1_0.conda sha256: 349951278fa8d0860ec6b61fcdc1e6f604e6fce74fabf73af2e39a37979d0223 md5: 75be1a943e0a7f99fcf118309092c635 @@ -9201,16 +10094,16 @@ packages: purls: [] size: 90486 timestamp: 1751643513473 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 + - typing_extensions ==4.16.0 pyhcf101f3_0 license: PSF-2.0 license_family: PSF run_exports: {} - size: 91383 - timestamp: 1756220668932 + size: 94080 + timestamp: 1783002732887 - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.1-pyhd8ed1ab_0.conda sha256: 4259a7502aea516c762ca8f3b8291b0d4114e094bdb3baae3171ccc0900e722f md5: e0c3cd765dc15751ee2f0b03cd015712 @@ -9223,18 +10116,18 @@ packages: - pkg:pypi/typing-inspection?source=compressed-mapping size: 18809 timestamp: 1747870776989 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda - sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a - md5: 53f5409c5cfd6c5a66417d68e3f0a864 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.4-pyhcf101f3_0.conda + sha256: 293c66b208468d186a94ff486c2ffbf67859a2bde8c88e965fc9d06c517191b2 + md5: 880e91eac5d926568ef278e20554148e depends: - python >=3.10 - - typing_extensions >=4.12.0 + - typing_extensions >=4.15.0 - python license: MIT license_family: MIT run_exports: {} - size: 20935 - timestamp: 1777105465795 + size: 21070 + timestamp: 1786818918217 - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda sha256: 4f52390e331ea8b9019b87effaebc4f80c6466d09f68453f52d5cdc2a3e1194f md5: e523f4f1e980ed7a4240d7e27e9ec81f @@ -9247,17 +10140,17 @@ packages: - pkg:pypi/typing-extensions?source=hash-mapping size: 51065 timestamp: 1751643513473 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c depends: - python >=3.10 - python license: PSF-2.0 license_family: PSF run_exports: {} - size: 51692 - timestamp: 1756220668932 + size: 52631 + timestamp: 1783002732887 - conda: https://conda.anaconda.org/conda-forge/noarch/typing_inspect-0.9.0-pyhd8ed1ab_1.conda sha256: a3fbdd31b509ff16c7314e8d01c41d9146504df632a360ab30dbc1d3ca79b7c0 md5: fa31df4d4193aabccaf09ce78a187faf @@ -9279,13 +10172,13 @@ packages: purls: [] size: 122968 timestamp: 1742727099393 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: fcb489df604d100968b737f2cb6076c6 license: LicenseRef-Public-Domain run_exports: {} - size: 119135 - timestamp: 1767016325805 + size: 118849 + timestamp: 1784250406640 - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.5.0-pyhd8ed1ab_0.conda sha256: 4fb9789154bd666ca74e428d973df81087a697dbb987775bc3198d2215f240f8 md5: 436c165519e140cb08d246a4472a9d6a @@ -9382,16 +10275,16 @@ packages: run_exports: {} size: 879913 timestamp: 1749743321359 -- conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda - sha256: 663ea9b00d68c2da309114923924686ab6d3f59ef1b196c5029ba16799e7bb07 - md5: 4487b9c371d0161d54b5c7bbd890c0fc +- conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.9.1-pyhd8ed1ab_0.conda + sha256: b9439f1716ff580ce56e9b4a4df7ea92e5ed2bdc9567d25cdff223d62c3183a8 + md5: 5d7c149ba9c728d4951f3fe6d3ce0f84 depends: - python >=3.9 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 51732 - timestamp: 1774900074457 + size: 53963 + timestamp: 1788530754725 - conda: https://conda.anaconda.org/conda-forge/noarch/yte-1.8.1-pyha770c72_0.conda sha256: 439ebef131ef2e4711f286375240f8d779fce2fe54b4ec786fb58c6c9141b17b md5: 55a52c71e7919a4951cfc6cccf4fa16f @@ -9744,9 +10637,9 @@ packages: - azure-storage-files-datalake-cpp >=12.14.0,<12.14.1.0a0 size: 198818 timestamp: 1778764243281 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.6.0-py313h7208f8c_0.conda - sha256: 4d39bf744249f60212a728369dbc6cd6ec4d5aef6668a14321f747d7eb4bac2d - md5: 6ab3d07883ad437c12a8f5fd90c1df5b +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py313h8c7ad59_1.conda + sha256: 824139728f10836e49aabc4d14cf8f594a430ecaf5815d471d50f10b66c8073f + md5: b50a4cca2d4af559fab07843625a598c depends: - python - __osx >=11.0 @@ -9754,8 +10647,8 @@ packages: - python_abi 3.13.* *_cp313 license: BSD-3-Clause AND MIT AND EPL-2.0 run_exports: {} - size: 243873 - timestamp: 1781450811773 + size: 241808 + timestamp: 1788491298513 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda sha256: c3fe902114b9a3ac837e1a32408cc2142c147ec054c1038d37aec6814343f48a md5: 925acfb50a750aa178f7a0aced77f351 @@ -9867,18 +10760,18 @@ packages: purls: [] size: 179696 timestamp: 1744128058734 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda - sha256: 2995f2aed4e53725e5efbc28199b46bf311c3cab2648fc4f10c2227d6d5fa196 - md5: bcb3cba70cf1eec964a03b4ba7775f01 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.8-h74c22ad_2.conda + sha256: 82bdd5a3fcada6afef6255a9bf41172f07b362f36e04a354dc2abc0a29bfb756 + md5: 4cc01a374215de38387d45e19c8c5c9c depends: - __osx >=11.0 license: MIT license_family: MIT run_exports: weak: - - c-ares >=1.34.6,<2.0a0 - size: 180327 - timestamp: 1765215064054 + - c-ares >=1.34.8,<2.0a0 + size: 197819 + timestamp: 1787169996553 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda sha256: 00439d69bdd94eaf51656fdf479e0c853278439d22ae151cabf40eb17399d95f md5: 38f6df8bc8c668417b904369a01ba2e2 @@ -10582,20 +11475,20 @@ packages: - pkg:pypi/jsonpointer?source=hash-mapping size: 17865 timestamp: 1725303130815 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py313h2af2deb_0.conda - sha256: b0ac975a7eb40638b1405c8092835c47222ce758eb26114afee50a8d1ce98569 - md5: bd1e04d017f340e42431706402db8b02 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.1-py313h738389e_1.conda + sha256: a94fbdf1974a51b8e4a32d1952f54b95095f7ff058988a94166119e99c504232 + md5: 24ee9d68fcbe92d1a6b8ac67b5e9dd80 depends: - python - - python 3.13.* *_cp313 - - libcxx >=19 - __osx >=11.0 + - python 3.13.* *_cp313 + - libcxx >=21 - python_abi 3.13.* *_cp313 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 69457 - timestamp: 1773067363162 + size: 66486 + timestamp: 1788505929375 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda sha256: 4442f957c3c77d69d9da3521268cad5d54c9033f1a73f99cde0a3658937b159b md5: c6dc8a0fdec13a0565936655c33069a1 @@ -10651,9 +11544,9 @@ packages: purls: [] size: 188306 timestamp: 1745264362794 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.1.0-h1eee2c3_0.conda - sha256: 66e5ffd301a44da696f3efc2f25d6d94f42a9adc0db06c44ad753ab844148c51 - md5: 095e5749868adab9cae42d4b460e5443 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda + sha256: c97aa17d16d2ac332ba9f184e82ce8f72dcb10e9a10c5f299030be2d44e191b9 + md5: e429aec4037d5cb8fd34ded9f5dadd39 depends: - __osx >=11.0 - libcxx >=19 @@ -10661,9 +11554,9 @@ packages: license_family: Apache run_exports: weak: - - lerc >=4.1.0,<5.0a0 - size: 164222 - timestamp: 1773114244984 + - lerc >=4.2.0,<5.0a0 + size: 166477 + timestamp: 1785036480092 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260107.1-cxx17_h2062a1b_0.conda sha256: 756611fbb8d2957a5b4635d9772bd8432cb6ddac05580a6284cca6fdc9b07fca md5: bb65152e0d7c7178c0f1ee25692c9fd1 @@ -10713,9 +11606,9 @@ packages: purls: [] size: 788465 timestamp: 1749385999215 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.8-gpl_h6fbacd7_100.conda - sha256: 05c370fae4f2a5fd7baf59c15d75caec718d785ec47e813dbf7bff68355e4bb7 - md5: cfa10f3c4b14c13f676dc08e2ea29023 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h6fbacd7_100.conda + sha256: b58cfffd0774142d308a25f1bdc610c46d71cb67489f1b1eec0891e78ede5b11 + md5: 4963589c8bed67dd0540d890ba690f53 depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 @@ -10732,9 +11625,9 @@ packages: license_family: BSD run_exports: weak: - - libarchive >=3.8.8,<3.9.0a0 - size: 796153 - timestamp: 1782289667690 + - libarchive >=3.8.9,<3.9.0a0 + size: 800281 + timestamp: 1785251408018 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarrow-19.0.1-h96c63c9_45_cpu.conda build_number: 45 sha256: 7907d58e8fbb1b54fb048820e489afdb4d3427dd276cefd2d9287865e2304065 @@ -11001,16 +11894,16 @@ packages: purls: [] size: 567309 timestamp: 1752050056857 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda - sha256: a2e7abab5add9750fab064c024394de48e49f97631c605ad5db5c8ac3fc769ef - md5: 89f76a2a21a3ec3ec983b5eb237c4113 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.1-h55c6f16_0.conda + sha256: 3f902803f0fc2643a4f82ff15a06811d974ff5fb6fa8f957720a7ef84e13ad98 + md5: b61106a0b5ac7cfe874f8b2e4f067dfa depends: - __osx >=11.0 license: Apache-2.0 WITH LLVM-exception license_family: Apache run_exports: {} - size: 569349 - timestamp: 1781670209146 + size: 578565 + timestamp: 1788869180613 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.24-h5773f1b_0.conda sha256: 417d52b19c679e1881cce3f01cad3a2d542098fa2d6df5485aac40f01aede4d1 md5: 3baf58a5a87e7c2f4d243ce2f8f2fe5c @@ -11161,19 +12054,19 @@ packages: run_exports: {} size: 338442 timestamp: 1780933611662 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_19.conda - sha256: 06644fa4d34d57c9e48f4d84b1256f9e5f654fdb37f43acc8a58a396952d42b7 - md5: 644058123986582db33aebd4ae2ca184 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_4.conda + sha256: 00b8d07ea98344c72c6e332a09b8060f4176849d5fd0eb78dd5b30176ad97ca4 + md5: 408af8d9d5226ff45ff04756ff1aa91e depends: - _openmp_mutex constrains: - - libgcc-ng ==15.2.0=*_19 - - libgomp 15.2.0 19 + - libgcc-ng ==16.2.0=*_4 + - libgomp 16.2.0 4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 404080 - timestamp: 1778273064154 + size: 365758 + timestamp: 1787617459249 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgd-2.3.3-hb2c3a21_11.conda sha256: be038eb8dfe296509aee2df21184c72cb76285b0340448525664bc396aa6146d md5: 4581aa3cfcd1a90967ed02d4a9f3db4b @@ -11266,18 +12159,18 @@ packages: run_exports: {} size: 598531 timestamp: 1758004553771 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_19.conda - sha256: d4837b3b9b30af3132d260225e91ab9dde83be04c59513f500cc81050fb37486 - md5: 1ea03f87cdb1078fbc0e2b2deb63752c +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_4.conda + sha256: 7582efbbffc6964093afd80e01c2ac60d89aa4e404cac664544675b9d4d1c5e7 + md5: aa6c627acd0f5709d9c79bf708a7b81b depends: - - libgfortran5 15.2.0 hdae7583_19 + - libgfortran5 16.2.0 hdb7a957_4 constrains: - - libgfortran-ng ==15.2.0=*_19 + - libgfortran-ng ==16.2.0=*_4 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 139675 - timestamp: 1778273280875 + size: 99624 + timestamp: 1787617544212 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-5.0.0-14_2_0_h6c33f7e_103.conda sha256: 8628746a8ecd311f1c0d14bb4f527c18686251538f7164982ccbe3b772de58b5 md5: 044a210bc1d5b8367857755665157413 @@ -11300,18 +12193,18 @@ packages: purls: [] size: 806566 timestamp: 1743863491726 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_19.conda - sha256: d0a68b7a121d115b80c169e24d1265dcc25a3fe58d107df1bbc430797e226d88 - md5: ba36d8c606a6a53fe0b8c12d47267b3d +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_4.conda + sha256: 902719c38c7a390d305435a362dc83893754c3f933569a38347c434cd1c12540 + md5: d54390644d893d50e739b19145aae45f depends: - - libgcc >=15.2.0 + - libgcc >=16.2.0 constrains: - - libgfortran 15.2.0 + - libgfortran 16.2.0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 599691 - timestamp: 1778273075448 + size: 554806 + timestamp: 1787617465010 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.84.2-hbec27ea_0.conda sha256: 5fcc5e948706cc64e45e2454267f664ed5a1e84f15345aae04a41d852a879c0e md5: 7bbb8961dca1b4b9f2b01b6e722111a7 @@ -11432,9 +12325,9 @@ packages: purls: [] size: 553624 timestamp: 1745268405713 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.1.4.1-h84a0fba_0.conda - sha256: 17e035ae6a520ff6a6bb5dd93a4a7c3895891f4f9743bcb8c6ef607445a31cd0 - md5: b8a7544c83a67258b0e8592ec6a5d322 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda + sha256: 05006418f9392c9b723e8428808db106de0350a497832f95f921b85ff1072310 + md5: b2f8c8e5a7651a1d7c05404c3a159517 depends: - __osx >=11.0 constrains: @@ -11442,9 +12335,9 @@ packages: license: IJG AND BSD-3-Clause AND Zlib run_exports: weak: - - libjpeg-turbo >=3.1.4.1,<4.0a0 - size: 555681 - timestamp: 1775962975624 + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 558459 + timestamp: 1785896382474 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libkml-1.3.0-hb833057_1023.conda sha256: bfc6c0b1f30de524e3270eed2171d87e6b96552ff90cf2a5eb34c00d6bcb3dfa md5: 3d8eeedb8e541d1cb593e8204fcc397d @@ -11869,18 +12762,18 @@ packages: purls: [] size: 899248 timestamp: 1752072259470 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.2-h1b79a29_0.conda - sha256: f06b6d9d50d5ad1bed09daada386eb1aa8ed7a9ca4618facd3aead75b82db9ff - md5: 530ef68b7f9f7bee04f67db8d435f872 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-h34a968b_1.conda + sha256: 3fbd885062e6e8de5118515a8b801ecaf3c49ff30a940fe80c2f71c2b82f0db7 + md5: d583309355fba01165ca2508c71a0970 depends: - __osx >=11.0 - libzlib >=1.3.2,<2.0a0 license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 923664 - timestamp: 1780574869893 + - libsqlite >=3.53.4,<4.0a0 + size: 938877 + timestamp: 1787051200294 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda sha256: 8bfe837221390ffc6f111ecca24fa12d4a6325da0c8d131333d63d6c37f27e0a md5: b68e8f66b94b44aaa8de4583d3d4cc40 @@ -11928,25 +12821,25 @@ packages: purls: [] size: 370943 timestamp: 1747067160710 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.1-h4030677_1.conda - sha256: e9248077b3fa63db94caca42c8dbc6949c6f32f94d1cafad127f9005d9b1507f - md5: e2a72ab2fa54ecb6abab2b26cde93500 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-hf67920b_1.conda + sha256: 847d22a86ae28f66d3888702698254d25bb4eac3a0f64c1fabfb1842ac00be4c + md5: b6b191267455bf202af79f973690ed5d depends: - __osx >=11.0 - - lerc >=4.0.0,<5.0a0 - - libcxx >=19 + - lerc >=4.2.0,<5.0a0 + - libcxx >=21 - libdeflate >=1.25,<1.26.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - liblzma >=5.8.3,<6.0a0 - libwebp-base >=1.6.0,<2.0a0 - - libzlib >=1.3.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 - zstd >=1.5.7,<1.6.0a0 license: HPND run_exports: weak: - - libtiff >=4.7.1,<4.8.0a0 - size: 373892 - timestamp: 1762022345545 + - libtiff >=4.7.2,<4.8.0a0 + size: 380645 + timestamp: 1787756067271 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libutf8proc-2.11.3-h2431656_0.conda sha256: ae1a82e62cd4e3c18e005ae7ff4358ed72b2bfbfe990d5a6a5587f81e9a100dc md5: 2255add2f6ae77d0a96624a5cbde6d45 @@ -12108,21 +13001,21 @@ packages: purls: [] size: 281996 timestamp: 1749892286735 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc7d1edf_0.conda - sha256: ccbaad6bbc88f135ab849bc36af5fa6eda36a9ed18ce6f58e3dde3d11784c156 - md5: a9c118f6343fb6301b6f3b4e94c4c562 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.0-hdb3d66b_0.conda + sha256: 996d3a9de61c8ccc3fcb265938f9845f11868251f38e6db4e7190de3f9a971a2 + md5: 0affff5130a421d11d4d77ffbb00dad7 depends: - __osx >=11.0 constrains: - intel-openmp <0.0a0 - - openmp 22.1.8|22.1.8.* + - openmp 23.1.0|23.1.0.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE run_exports: strong: - - llvm-openmp >=22.1.8 - size: 286313 - timestamp: 1781736516782 + - llvm-openmp >=23.1.0 + size: 287808 + timestamp: 1787723323710 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-4.4.5-py313hd065f0a_1.conda sha256: 71dfac3971dcd134c8a31b3f670d00b8d551e275fb386568ec11ab68d95fe540 md5: ece4dab2afb98b065b69ce769a5c6c42 @@ -12256,39 +13149,38 @@ packages: - pkg:pypi/menuinst?source=hash-mapping size: 174339 timestamp: 1750792742701 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.2.1-hdb7fadc_0.conda - sha256: 4b1d4e4b17d27bbec50b1ad144e000c3b077cc4e0ef487ef11011c52ef8c86ab - md5: fcd860469a8fa003e25d472b40b0ff81 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.2.2-h56c1371_1.conda + sha256: 0a737bebd276d48f0436842ca47b38b823b4f8e3c3065848c9d1f421f334a4aa + md5: b7819f4118a456301e220e995afd53ac depends: - __osx >=11.0 - bzip2 >=1.0.8,<2.0a0 - - libcxx >=19 + - libcxx >=21 - libiconv >=1.18,<2.0a0 - liblzma >=5.8.3,<6.0a0 - libzlib >=1.3.2,<2.0a0 - - openssl >=3.5.6,<4.0a0 + - openssl >=3.5.8,<4.0a0 - zstd >=1.5.7,<1.6.0a0 license: Zlib license_family: Other run_exports: weak: - - minizip >=4.2.1,<5.0a0 - size: 459816 - timestamp: 1778003052237 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.1-py313h5c29297_0.conda - sha256: 12f2c785c34a465f399e7af89e6b88a86684bde704e25460789e83efc0d8fd87 - md5: b929e2af3d4059fedfb0a5da0007556f + - minizip >=4.2.2,<5.0a0 + size: 462531 + timestamp: 1788051612824 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/msgpack-python-1.2.2-py313hcb059c8_2.conda + sha256: 7846717fdd4ab12617c308ece9c5f8f862b7aecd529abee1a70eb5a99cee1a6f + md5: 4e90283eebfa97c70d4fe6d46f6e273c depends: + - python - __osx >=11.0 - - libcxx >=19 - - python >=3.13,<3.14.0a0 - - python >=3.13,<3.14.0a0 *_cp313 + - libcxx >=21 - python_abi 3.13.* *_cp313 license: Apache-2.0 - license_family: Apache + license_family: APACHE run_exports: {} - size: 91552 - timestamp: 1782070886759 + size: 101084 + timestamp: 1788525103407 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mypy-1.16.1-py312hea69d52_0.conda sha256: 32920a7074b450a15f0f7b1ba48e5246484c44c167d38311db85ca9d7b100eac md5: ab4b5c37bfdd0002491a92239a580af7 @@ -12435,9 +13327,9 @@ packages: purls: [] size: 3071649 timestamp: 1751390309393 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda - sha256: b3e3ca895c336d4eb91c5d2f244a312bdb59a0de8cfa0cc4c179225ab2f6bbfb - md5: 8187a86242741725bfa74785fe812979 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda + sha256: f23239eacd75c4c50705e68fae1aa3292da473e6a3a4abe2330f1e6afa680704 + md5: ae71ab40048c19a389a7dcccb86c2481 depends: - __osx >=11.0 - ca-certificates @@ -12445,9 +13337,9 @@ packages: license_family: Apache run_exports: weak: - - openssl >=3.6.3,<4.0a0 - size: 3102584 - timestamp: 1781069820667 + - openssl >=3.6.4,<4.0a0 + size: 3110142 + timestamp: 1787698648639 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/orc-2.2.2-h578b684_1.conda sha256: a25faa4aa71832f908dec90ff3f66490ab06c47304d3c1e474c9f6306ae78452 md5: 5ed1fedefe1098670f8d8e8189dcda7c @@ -12616,28 +13508,27 @@ packages: - pcre2 >=10.46,<10.47.0a0 size: 835080 timestamp: 1756743041908 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.2.0-py313h45e5a15_0.conda - sha256: 90333643a7868b10724999633bb393d005bc5f539d05666f80c41fb67e5f0f3f - md5: 6186601fd72a394a6f7c7b7096f6a063 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py313h4ede67f_2.conda + sha256: 7d49dd4fbe81e0b6cd0cbcad50c233416308acbd3c2eaa834bc94262a08fe918 + md5: 0815276151429fe3f361115c834aab72 depends: - python - - python 3.13.* *_cp313 - __osx >=11.0 - - openjpeg >=2.5.4,<3.0a0 - libxcb >=1.17.0,<2.0a0 - - libjpeg-turbo >=3.1.2,<4.0a0 - - libtiff >=4.7.1,<4.8.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - libtiff >=4.7.2,<4.8.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 - libfreetype >=2.14.3 - libfreetype6 >=2.14.3 - - libwebp-base >=1.6.0,<2.0a0 - - lcms2 >=2.18,<3.0a0 - - tk >=8.6.13,<8.7.0a0 - python_abi 3.13.* *_cp313 - - zlib-ng >=2.3.3,<2.4.0a0 + - lcms2 >=2.19.1,<3.0a0 + - tk >=8.6.13,<8.7.0a0 license: HPND run_exports: {} - size: 977319 - timestamp: 1775060469004 + size: 996699 + timestamp: 1788610377472 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.2-h2f9eb0b_0.conda sha256: 68d1eef12946d779ce4b4b9de88bc295d07adce5dd825a0baf0e1d7cf69bc5a6 md5: 0587a57e200568a71982173c07684423 @@ -12803,13 +13694,12 @@ packages: - pkg:pypi/pydantic-core?source=hash-mapping size: 1715338 timestamp: 1746625327204 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.4-py313h212e517_0.conda - sha256: 5bcc20f2c21d8a20d8f2821caf31d8c0ef2fa3bcdaf7a9bdce62e37be819f1d7 - md5: 32bb0d4dbb1be2064c06248a1190aa96 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydantic-core-2.46.5-py313h680bcb3_1.conda + sha256: 475c4d1896024dbb856ff9d58b8725cb5082db32e84dbe9f2851b891ddf69c95 + md5: 1faebbbbd03a8e59b1d9e574469cb81e depends: - python - typing-extensions >=4.6.0,!=4.7.0 - - python 3.13.* *_cp313 - __osx >=11.0 - python_abi 3.13.* *_cp313 constrains: @@ -12817,8 +13707,8 @@ packages: license: MIT license_family: MIT run_exports: {} - size: 1720475 - timestamp: 1778084300413 + size: 1689376 + timestamp: 1787928984245 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pydot-4.0.1-py312h81bd7bf_0.conda sha256: 1d9fc2c0f982ff9b1ee9d7bd27bdbed6cbcb53bedd8200b6249f12710c48e359 md5: 2e6a2e3c32f6a48b6cacfec0e1f3da90 @@ -13224,21 +14114,21 @@ packages: license_family: MIT size: 3697021 timestamp: 1775241400922 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.2-h85ec8f2_0.conda - sha256: 5d0a6544dcbbec6322c06a8ee994d6823916af35bbd5a0f8c35387869f1b5d76 - md5: 1966e1b7f598b30cd746309ba483e541 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.4-hdb6324b_1.conda + sha256: 7ea31b7063fe292f5c7e543bc7d2c6077c33f608eabcdadf963e2335e50ab3e1 + md5: a70594a09c2167c484156a77455fa1de depends: - __osx >=11.0 - - libsqlite 3.53.2 h1b79a29_0 + - libsqlite 3.53.4 h34a968b_1 - libzlib >=1.3.2,<2.0a0 - ncurses >=6.6,<7.0a0 - readline >=8.3,<9.0a0 license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 182161 - timestamp: 1780574896374 + - libsqlite >=3.53.4,<4.0a0 + size: 179881 + timestamp: 1787051206584 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 md5: a9d86bc62f39b94c4661716624eb21b0 @@ -13277,9 +14167,9 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 851614 timestamp: 1748003575892 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.7-py313h0997733_0.conda - sha256: 02c7b05be4d74da0d7329f92445646e3489634f0c3e59b26efefd846662ac20a - md5: dbc95e65c936251c3d32111c867d84e1 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.8-py313hf5449f0_1.conda + sha256: 7f7895de1dce43905cc1642ea0b2811bcf242a3b789ce071a0cf4284caaaee7a + md5: 90cf89c127d4be14a3f1acdbbb0ba37e depends: - __osx >=11.0 - python >=3.13,<3.14.0a0 @@ -13288,8 +14178,8 @@ packages: license: Apache-2.0 license_family: Apache run_exports: {} - size: 889689 - timestamp: 1781007967544 + size: 894250 + timestamp: 1788525258397 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/uriparser-0.9.8-h00cdb27_0.conda sha256: fa0bcbfb20a508ca9bf482236fe799581cbd0eab016e47a865e9fa44dbe3c512 md5: e8ff9e11babbc8cd77af5a4258dc2802 @@ -13718,20 +14608,20 @@ packages: - aws-sdk-cpp >=1.11.606,<1.11.607.0a0 size: 3438987 timestamp: 1771598251928 -- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.6.0-py313h2a31948_0.conda - sha256: 65eb354dbaba5925f536613c8d645a6254226eb6c6f16cc6e57033eb97cc0159 - md5: 144ae232f6f920307f4aadc088137589 +- conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py313h2a31948_1.conda + sha256: c9421fb36888e70029a3f564a4e870019eb070e24c440a9d9071c57c8e82c794 + md5: d742785ccf2834a29b834892ddb23672 depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - zstd >=1.5.7,<1.6.0a0 - python_abi 3.13.* *_cp313 + - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause AND MIT AND EPL-2.0 run_exports: {} - size: 241936 - timestamp: 1781450845361 + size: 242878 + timestamp: 1788491289149 - conda: https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda sha256: 9303a7a0e03cf118eab3691013f6d6cbd1cbac66efbc70d89b20f5d0145257c0 md5: 357d7be4146d5fec543bfaa96a8a40de @@ -13842,20 +14732,20 @@ packages: purls: [] size: 54927 timestamp: 1720974860185 -- conda: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.34.6-hfd05255_0.conda - sha256: 5e1e2e24ce279f77e421fcc0e5846c944a8a75f7cf6158427c7302b02984291a - md5: 7c6da34e5b6e60b414592c74582e28bf +- conda: https://conda.anaconda.org/conda-forge/win-64/c-ares-1.34.8-h6a83c73_2.conda + sha256: b474e4b7f3136576c321533e340b34368a5ce671949bc1a77ef446eb94870136 + md5: b5a0be28d048d8bbe81df51e4b750500 depends: - - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 license: MIT license_family: MIT run_exports: weak: - - c-ares >=1.34.6,<2.0a0 - size: 193550 - timestamp: 1765215100218 + - c-ares >=1.34.8,<2.0a0 + size: 210803 + timestamp: 1787169972884 - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h5782bbf_0.conda sha256: b9f577bddb033dba4533e851853924bfe7b7c1623d0697df382eef177308a917 md5: 20e32ced54300292aff690a69c5e7b97 @@ -14184,9 +15074,9 @@ packages: purls: [] size: 192355 timestamp: 1730284147944 -- conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.1-hd47e2ca_0.conda - sha256: 9217184c4a8e82101b0e512b059ae3ff67e3913133b9031edad89ab5341284e4 - md5: abd79bad98c99c1a116154d6de74ea89 +- conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.3-hd47e2ca_1.conda + sha256: f26139e3c774a6434c8a73381c08e3d2a4c2f9d9ef9587c66aab8836211ee2ec + md5: ed95670fed91b091fe34ba6cae6677d7 depends: - libexpat >=2.8.1,<3.0a0 - libfreetype >=2.14.3 @@ -14201,10 +15091,10 @@ packages: license_family: MIT run_exports: weak: - - fontconfig >=2.18.1,<3.0a0 + - fontconfig >=2.18.3,<3.0a0 - fonts-conda-ecosystem - size: 202630 - timestamp: 1780450217840 + size: 217988 + timestamp: 1786667461139 - conda: https://conda.anaconda.org/conda-forge/win-64/fonttools-4.63.0-py313hd650c13_0.conda sha256: 10cd3c3606219bc8e1a387757b069175b8202c54f02244b1557c283bd6c252d1 md5: 2b7be2be35fc3b035f1365a015af9706 @@ -14550,9 +15440,9 @@ packages: - pkg:pypi/jsonpointer?source=hash-mapping size: 42235 timestamp: 1725303419414 -- conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.0-py313h1a38498_0.conda - sha256: 58c7b7d85ea3c0fac593fde238b994ee2d4fa8467decfe369dabfb5516b7ded4 - md5: 7e40c4c1af80d907eb2973ab73418095 +- conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.1-py313h1a38498_1.conda + sha256: 09f049337936bbc764098e67bb4b14a7194d7f1ad57536cf63ed628ccb2d36a5 + md5: e4cb42764087bafbdf394d394ec3a0c1 depends: - python - vc >=14.3,<15 @@ -14562,8 +15452,8 @@ packages: license: BSD-3-Clause license_family: BSD run_exports: {} - size: 73548 - timestamp: 1773067061126 + size: 73459 + timestamp: 1788505726845 - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.21.3-hdf4eb48_0.conda sha256: 18e8b3430d7d232dad132f574268f56b3eb1a19431d6d5de8c53c29e6c18fa81 md5: 31aec030344e962fbd7dbbbbd68e60a9 @@ -14608,9 +15498,9 @@ packages: purls: [] size: 164701 timestamp: 1745264384716 -- conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - sha256: 45df58fca800b552b17c3914cc9ab0d55a82c5172d72b5c44a59c710c06c5473 - md5: 54b231d595bc1ff9bff668dd443ee012 +- conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda + sha256: 93d666f63f284ef77b87b0b1f77b70f7d36d315a132f9afa64bc0012d937ba39 + md5: add59e2b60ac9d4299d17c938185c75a depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -14619,9 +15509,9 @@ packages: license_family: Apache run_exports: weak: - - lerc >=4.1.0,<5.0a0 - size: 172395 - timestamp: 1773113455582 + - lerc >=4.2.0,<5.0a0 + size: 175297 + timestamp: 1785036247761 - conda: https://conda.anaconda.org/conda-forge/win-64/libabseil-20260107.1-cxx17_h0eb2380_0.conda sha256: 7e7f3754f8afaabd946dc11d7c00fd1dc93f0388a2d226a7abf1bf07deab0e2b md5: 60da39dd5fd93b2a4a0f986f3acc2520 @@ -14674,9 +15564,9 @@ packages: purls: [] size: 1098688 timestamp: 1749386269743 -- conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.8-gpl_he24518a_100.conda - sha256: abeec1902a9933f71a70bb54a436897d54ea08b290f084c0ce511e4d7eb24548 - md5: e61cb5e50e73c4563c2427de40435171 +- conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_he24518a_100.conda + sha256: d27338b30444c82b7958c833ecd6db905d45ee4d88df6e29faa7c65140b1a601 + md5: 3267f5cd28472841f357865668e17c10 depends: - bzip2 >=1.0.8,<2.0a0 - liblzma >=5.8.3,<6.0a0 @@ -14694,9 +15584,9 @@ packages: license_family: BSD run_exports: weak: - - libarchive >=3.8.8,<3.9.0a0 - size: 1117061 - timestamp: 1782289351052 + - libarchive >=3.8.9,<3.9.0a0 + size: 1149153 + timestamp: 1785250768915 - conda: https://conda.anaconda.org/conda-forge/win-64/libarrow-19.0.1-hd086c85_45_cpu.conda build_number: 45 sha256: 634bac7300c6de1f507e5f9a6a3dd5123a3232f4458db45a14223eb1a4d65e1d @@ -15120,21 +16010,21 @@ packages: purls: [] size: 669602 timestamp: 1750808309041 -- conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-15.2.0-h8ee18e1_19.conda - sha256: 80e80ef5e31b00b12539db3c5aaecde60dab91381abfc1060e323d5c3b016dce - md5: cc5d690fc1c629038f13c68e88e65f44 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-16.2.0-h110b43a_4.conda + sha256: 32c0d1adcc96835d5f681c7e72b7f5e2e30088149239e002b8a6f581a072e1d2 + md5: 02793cf48b68687fee158a48c92850a6 depends: - _openmp_mutex >=4.5 - libwinpthread >=12.0.0.r4.gg4f2fc60ca constrains: + - libgcc-ng ==16.2.0=*_4 + - libgomp 16.2.0 h8ee18e1_4 - msys2-conda-epoch <0.0a0 - - libgcc-ng ==15.2.0=*_19 - - libgomp 15.2.0 h8ee18e1_19 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL run_exports: {} - size: 821854 - timestamp: 1778273037795 + size: 826694 + timestamp: 1787625780171 - conda: https://conda.anaconda.org/conda-forge/win-64/libgd-2.3.3-h7208af6_11.conda sha256: 485a30af9e710feeda8d5b537b2db1e32e41f29ef24683bbe7deb6f7fd915825 md5: 2070a706123b2d5e060b226a00e96488 @@ -15280,9 +16170,9 @@ packages: purls: [] size: 535456 timestamp: 1750808243424 -- conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-15.2.0-h8ee18e1_19.conda - sha256: 4dc958ced2fc7f42bc675b07e2c9abe3e150875ffdf62ca551d94fc6facf1fd7 - md5: f1147651e3fdd585e2f442c0c2fc8f2d +- conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-16.2.0-h8ee18e1_4.conda + sha256: 9ef1a22fb50b31fbf9c01cf709e8f55b60fd3c02401d150be4a55943f9e3832f + md5: 91189f0bc9ff21f385fcda6232346612 depends: - libwinpthread >=12.0.0.r4.gg4f2fc60ca constrains: @@ -15292,9 +16182,9 @@ packages: run_exports: strong: - _openmp_mutex >=4.5 - - libgomp >=15.2.0 - size: 664640 - timestamp: 1778272979661 + - libgomp >=16.2.0 + size: 688168 + timestamp: 1787625720312 - conda: https://conda.anaconda.org/conda-forge/win-64/libgoogle-cloud-2.39.0-h01c467a_1.conda sha256: 098ac4abc51752a1c56c1c05ed4220e88daa7d0e18922b0d355056d5b305f167 md5: 453d3a0347fe049b922a2a851c1c0110 @@ -15439,9 +16329,9 @@ packages: purls: [] size: 838154 timestamp: 1745268437136 -- conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.4.1-hfd05255_0.conda - sha256: 698d57b5b90120270eaa401298319fcb25ea186ae95b340c2f4813ed9171083d - md5: 25a127bad5470852b30b239f030ec95b +- conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda + sha256: df78ab4c0eecb3dd9331898f96baeed8e5ca1c363346332517abeb0b614b9a53 + md5: fc2c23bacefd1e733f1e1d6cd3b2aaeb depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -15451,9 +16341,9 @@ packages: license: IJG AND BSD-3-Clause AND Zlib run_exports: weak: - - libjpeg-turbo >=3.1.4.1,<4.0a0 - size: 842806 - timestamp: 1775962811457 + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 990125 + timestamp: 1785896494014 - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1023.conda sha256: 6b2c6506dc7d7bb3bc4128d575e4ae6c2cf18d3f9828a4be331e0b5e49edf108 md5: b44e0d9eb84c6c086d809787aab0a1da @@ -15773,9 +16663,9 @@ packages: purls: [] size: 1288312 timestamp: 1752072137328 -- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.2-hf5d6505_0.conda - sha256: 4cd81319dcc58fb758da20a6d5595950c021adc2c18d7cffeadcfb590529629f - md5: df294e7f9f24a6063f0e226f4d028fda +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + sha256: 0a45d7c0f20146fff787a106f8fa187872e309c70975ff8f0936e188914c26ad + md5: a72d495965b144bb7da033642d389047 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -15783,9 +16673,9 @@ packages: license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 1313306 - timestamp: 1780574491977 + - libsqlite >=3.53.4,<4.0a0 + size: 1314919 + timestamp: 1787051140039 - conda: https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda sha256: cbdf93898f2e27cefca5f3fe46519335d1fab25c4ea2a11b11502ff63e602c09 md5: 9dce2f112bfd3400f4f432b3d0ac07b2 @@ -15837,15 +16727,15 @@ packages: purls: [] size: 979074 timestamp: 1747067408877 -- conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.1-h8f73337_1.conda - sha256: f1b8cccaaeea38a28b9cd496694b2e3d372bb5be0e9377c9e3d14b330d1cba8a - md5: 549845d5133100142452812feb9ba2e8 +- conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_1.conda + sha256: 3575a092e3e52625a1767804a4ebd321becaadf61b63dcd6735ebb88c0b3c359 + md5: 970dbe48f843e7fbd179a9b6c22f865a depends: - - lerc >=4.0.0,<5.0a0 + - lerc >=4.2.0,<5.0a0 - libdeflate >=1.25,<1.26.0a0 - - libjpeg-turbo >=3.1.0,<4.0a0 - - liblzma >=5.8.1,<6.0a0 - - libzlib >=1.3.1,<2.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 @@ -15853,9 +16743,9 @@ packages: license: HPND run_exports: weak: - - libtiff >=4.7.1,<4.8.0a0 - size: 993166 - timestamp: 1762022118895 + - libtiff >=4.7.2,<4.8.0a0 + size: 1014211 + timestamp: 1787755773611 - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.11.3-hb980946_0.conda sha256: 5d82af0779eab283416240da792a0d2fe4f8213c447e9f04aeaab1801468a90c md5: 5f34fcb6578ea9bdbfd53cc2cfb88200 @@ -15870,22 +16760,22 @@ packages: - libutf8proc >=2.11.3,<2.12.0a0 size: 89061 timestamp: 1768735187639 -- conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.341.0-h477610d_0.conda - sha256: 0f0965edca8b255187604fc7712c53fe9064b31a1845a7dfb2b63bf660de84a7 - md5: 804880b2674119b84277d6c16b01677d +- conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.357.0-h477610d_2.conda + sha256: 4b094443126c5fc38d200eb0ea335b67e64d966d7aa737bb3141217a787b0c73 + md5: aaeed7feddbfd7454f8853a5eae8d902 depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 constrains: - - libvulkan-headers 1.4.341.0.* + - libvulkan-headers 1.4.357.0.* license: Apache-2.0 license_family: APACHE run_exports: weak: - - libvulkan-loader >=1.4.341.0,<2.0a0 - size: 282251 - timestamp: 1770077165680 + - libvulkan-loader >=1.4.357.0,<2.0a0 + size: 288787 + timestamp: 1787491929908 - conda: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_0.conda sha256: 7b6316abfea1007e100922760e9b8c820d6fc19df3f42fb5aca684cfacb31843 md5: f9bbae5e2537e3b06e0f7310ba76c893 @@ -16080,23 +16970,23 @@ packages: - libzlib >=1.3.2,<2.0a0 size: 58347 timestamp: 1774072851498 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_0.conda - sha256: 50c02902bb516eeb56680358f052be38b5bf74b40e78ea4b2a675e84957e7307 - md5: de3551bf6508d45ca46b714639e52823 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-23.1.0-h49e36cd_0.conda + sha256: f9658ec83a6744f38e2b8188c76e1c2dea2614f5aa0e14d9d274b19f60646b8c + md5: 79055ec79e1b43749763122cead30976 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - openmp 22.1.8|22.1.8.* - intel-openmp <0.0a0 + - openmp 23.1.0|23.1.0.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE run_exports: strong: - - llvm-openmp >=22.1.8 - size: 348002 - timestamp: 1781737042070 + - llvm-openmp >=23.1.0 + size: 342468 + timestamp: 1787722783678 - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-4.4.5-py313h4bbca4b_1.conda sha256: c00d35e5228ac375658495e86d33e1daa819ed11161034a5799dcb07bfa328c9 md5: e8df314d3f3fa27e935b6cb449d754f3 @@ -16246,9 +17136,9 @@ packages: - pkg:pypi/menuinst?source=hash-mapping size: 140384 timestamp: 1750792754303 -- conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.2.1-h0ffbb96_0.conda - sha256: 208b235b20232891d1dc9f468d08b00c53e7ca65a18b9bc0ff4c4867680b9a75 - md5: 5d55038c4d640e5df06de1cfb4e8d741 +- conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.2.2-h8fa244a_1.conda + sha256: ee989609be75355a6b9125e28e0ac7e36c29ce77dd65890b5bc469a4a6d14e86 + md5: 124e35907d5fb36b1e0cb3b74c20bc9d depends: - bzip2 >=1.0.8,<2.0a0 - liblzma >=5.8.3,<6.0a0 @@ -16261,9 +17151,9 @@ packages: license_family: Other run_exports: weak: - - minizip >=4.2.1,<5.0a0 - size: 106290 - timestamp: 1778002682910 + - minizip >=4.2.2,<5.0a0 + size: 116615 + timestamp: 1788051329472 - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2024.2.2-h66d3029_15.conda sha256: 20e52b0389586d0b914a49cd286c5ccc9c47949bed60ca6df004d1d295f2edbd md5: 302dff2807f2927b3e9e0d19d60121de @@ -16275,12 +17165,11 @@ packages: purls: [] size: 103106385 timestamp: 1730232843711 -- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.0.0-hac47afa_908.conda - sha256: f997bfc9bc4d4e14261cdcd1ad195d64a72ee44dca3145d24c1349f8d1311aa5 - md5: 36ea6e1292e9d5e89374201da79646ef +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.1.0-hac47afa_235.conda + sha256: bb8abe071f73765446df62924031e6599577e8ac9003264dc4569e78c0fea16d + md5: ace316c48c178d7faa403dee51e30c7b depends: - - llvm-openmp >=22.1.5 - - onemkl-license 2026.0.0 h57928b3_908 + - llvm-openmp >=22.1.8 - tbb >=2023.0.0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -16288,8 +17177,8 @@ packages: license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary run_exports: {} - size: 114354729 - timestamp: 1779293121860 + size: 114686732 + timestamp: 1787725739779 - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-include-2024.2.2-h66d3029_15.conda sha256: 87b53fd205282de67ff0627ea43d1d4293b7f5d6c5d5a62902c07b71bee7eb58 md5: e2f516189b44b6e042199d13e7015361 @@ -16310,20 +17199,20 @@ packages: purls: [] size: 108659788 timestamp: 1730233129079 -- conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.1-py313hf069bd2_0.conda - sha256: 08cfc45c7160779556abc25ea2ac1352fc68c05a7db5c8f7eaa051c928ce93b8 - md5: 83ede6d6d5227244621c126264ccaea9 +- conda: https://conda.anaconda.org/conda-forge/win-64/msgpack-python-1.2.2-py313h1a38498_2.conda + sha256: adc02df0943c297b71390b70ca31132082004ec9ab22bbc6ca902e45aa5a92b6 + md5: 2747a4f5c12412e25f1da5c6313474ef depends: - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - ucrt >=10.0.20348.0 + - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.13.* *_cp313 license: Apache-2.0 - license_family: Apache + license_family: APACHE run_exports: {} - size: 89970 - timestamp: 1782070487087 + size: 90195 + timestamp: 1788525100970 - conda: https://conda.anaconda.org/conda-forge/win-64/mypy-1.16.1-py312h4389bb4_0.conda sha256: 5bb00863b7a736e5bfdabc576af1ff93bb95ab78b3f35e5d800feda57148ba1d md5: 749b32d06a926c492bc48de440fc6919 @@ -16416,14 +17305,6 @@ packages: - numpy >=1.23,<3 size: 7524105 timestamp: 1766383318405 -- conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - sha256: 42ad15cbb3bf31830efa04d4b86dd2d5c0dd590c86f98adcd3c8c1f75acf5dd5 - md5: 9c9303e08b50e09f5c23e1dac99d0936 - license: LicenseRef-IntelSimplifiedSoftwareOct2022 - license_family: Proprietary - run_exports: {} - size: 41580 - timestamp: 1779292867015 - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda sha256: 24342dee891a49a9ba92e2018ec0bde56cc07fdaec95275f7a55b96f03ea4252 md5: e723ab7cc2794c954e1b22fde51c16e4 @@ -16454,9 +17335,9 @@ packages: purls: [] size: 9327033 timestamp: 1751392489008 -- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda - sha256: cb6e7ba0d010ee0d3249ce9886de3d7613d26d9965d4c95666fa66b9c4c31001 - md5: e99f95734a326c0fd4d02bbd995150d4 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda + sha256: 9dddb559ba49744d5d94092d8d13cb0567f5c3b3f439f3acf28433d1f4256acc + md5: 73cb1783f47c452be4c309430fbef89f depends: - ca-certificates - ucrt >=10.0.20348.0 @@ -16466,9 +17347,9 @@ packages: license_family: Apache run_exports: weak: - - openssl >=3.6.3,<4.0a0 - size: 9414790 - timestamp: 1781071745579 + - openssl >=3.6.4,<4.0a0 + size: 9474879 + timestamp: 1787699876495 - conda: https://conda.anaconda.org/conda-forge/win-64/orc-2.2.2-h0a1ad0e_1.conda sha256: dcfca3c3c117e9102fcfca116ec9e4f0bbcd0f13b3fce06ff111ae9f107d04b7 md5: aa6701a960f0e94478229af1e061c237 @@ -16644,29 +17525,29 @@ packages: - pcre2 >=10.46,<10.47.0a0 size: 1034703 timestamp: 1756743085974 -- conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.2.0-py313h38f99e1_0.conda - sha256: 54df76a56eff31deab5e72350ca906c79dfb71f0ac9d84bf2f7420ab2ee00151 - md5: 72666a34e563494859af5c5fc10364a0 +- conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py313h38f99e1_2.conda + sha256: c9f95ee0a4c06bcf6e5e57377eb311eb3073e3dfd4babd7e1c3cc03f40b74758 + md5: 0290df58ee4827dc40a26e7d1f9b5d3f depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - libwebp-base >=1.6.0,<2.0a0 - - openjpeg >=2.5.4,<3.0a0 - - libtiff >=4.7.1,<4.8.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - python_abi 3.13.* *_cp313 + - lcms2 >=2.19.1,<3.0a0 - tk >=8.6.13,<8.7.0a0 - - lcms2 >=2.18,<3.0a0 - - libjpeg-turbo >=3.1.2,<4.0a0 - libfreetype >=2.14.3 - libfreetype6 >=2.14.3 - - python_abi 3.13.* *_cp313 - libxcb >=1.17.0,<2.0a0 - - zlib-ng >=2.3.3,<2.4.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libtiff >=4.7.2,<4.8.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 license: HPND run_exports: {} - size: 957015 - timestamp: 1775060119774 + size: 962638 + timestamp: 1788610331353 - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.2-had0cd8c_0.conda sha256: d7d1f1052f15601406883f17ec149abf5e99262782ef536a415a41add060596e md5: 2566a45fb15e2f540eff14261f1242af @@ -16842,9 +17723,9 @@ packages: - pkg:pypi/pydantic-core?source=hash-mapping size: 1900306 timestamp: 1746625389678 -- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.4-py313hfbe8231_0.conda - sha256: 6466b7e441adb71e29308de2a87c9787d5d0100601ede2d02ed4f85c251699d6 - md5: 9981bc46be6341306a5473b290b2f366 +- conda: https://conda.anaconda.org/conda-forge/win-64/pydantic-core-2.46.5-py313hfbe8231_1.conda + sha256: 96927a9eaccfa212b43a79e44e2d3db5ec2a12c58d4674d5c793dfcc1efd4877 + md5: b17d876e4ba18b462a899addacb15a6b depends: - python - typing-extensions >=4.6.0,!=4.7.0 @@ -16855,8 +17736,8 @@ packages: license: MIT license_family: MIT run_exports: {} - size: 1888029 - timestamp: 1778084253466 + size: 1865505 + timestamp: 1787928988442 - conda: https://conda.anaconda.org/conda-forge/win-64/pydot-4.0.1-py312h2e8e312_0.conda sha256: 2aed920a88a9bc003e56da4b462c181b91776817ba4ebc12949a400f8d1d3d97 md5: c81f64a8ee9e03f87155db2c7876d965 @@ -17335,20 +18216,20 @@ packages: license_family: MIT size: 3665426 timestamp: 1775241406935 -- conda: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.2-hdb435a2_0.conda - sha256: 0640ffa3360669c8826f05a8295f38c3bc57dc9cac0c9732940897ff5256f996 - md5: 58e6f936ab416fddc633d4105f43aea2 +- conda: https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.4-hdb435a2_1.conda + sha256: 7616fec2d6ded67e111a47d4842738d0b3730c0acf3eaebe928cf6d3e15a6822 + md5: df9d7c5d34602e0f2655a524d31fce87 depends: - - libsqlite 3.53.2 hf5d6505_0 + - libsqlite 3.53.4 hf5d6505_1 - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 license: blessing run_exports: weak: - - libsqlite >=3.53.2,<4.0a0 - size: 425926 - timestamp: 1780574498030 + - libsqlite >=3.53.4,<4.0a0 + size: 426888 + timestamp: 1787051146089 - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda sha256: 03cc5442046485b03dd1120d0f49d35a7e522930a2ab82f275e938e17b07b302 md5: 9190dd0a23d925f7602f9628b3aed511 @@ -17416,9 +18297,9 @@ packages: - pkg:pypi/tornado?source=hash-mapping size: 853357 timestamp: 1748003925528 -- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.7-py313h5ea7bf4_0.conda - sha256: 973322f987fcbcc25bf67cd65a7f17b2cb57606e688ec7bb6e203d7dedf83d4a - md5: e80e3b9589e828c0b0b83f149438c29c +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.8-py313h5ea7bf4_1.conda + sha256: 1fd6385331280c32b560edda948a7adb07d297c4e1f5de1799711a389d6c3c08 + md5: 5b7993a5cf3b70c41f49998c38565a9d depends: - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 @@ -17428,8 +18309,8 @@ packages: license: Apache-2.0 license_family: Apache run_exports: {} - size: 888693 - timestamp: 1781006912014 + size: 894638 + timestamp: 1788524943705 - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda sha256: db8dead3dd30fb1a032737554ce91e2819b43496a0db09927edf01c32b577450 md5: 6797b005cd0f439c4c5c9ac565783700 @@ -17499,33 +18380,33 @@ packages: purls: [] size: 756109 timestamp: 1750371459116 -- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_39.conda - sha256: 8153ed849c92e891eacac0f2f8d7ecb79f9b5fd7f7917fbb896f252a60a40390 - md5: 06a5bf5a1ca16cce0df6eaa91fc42bc2 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + sha256: 4e4cb599cdc41bf2109d1464c127b5bcbddf548ce3e322e612afb691338b48f8 + md5: ac5333bb3d429361f23adf704cc49a78 depends: - ucrt >=10.0.20348.0 - - vcomp14 14.51.36231 h1b9f54f_39 + - vcomp14 14.51.36247 habf1de7_41 constrains: - - vs2015_runtime 14.51.36231.* *_39 + - vs2015_runtime 14.51.36247.* *_41 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary run_exports: {} - size: 737434 - timestamp: 1781320964561 -- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_39.conda - sha256: 07fb14713c4bc62e2533a2e23a363abfb0e65650681fba0ae4c840e2219350f3 - md5: 8b53a83fda40ec679e4d63fa32fae989 + size: 767955 + timestamp: 1785359364369 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + sha256: 731e043390c9457299484d39e427221fc868a9249540a498a5a4f6456c7744d1 + md5: 350bb67a5c8e5f1c53347ac544ab6600 depends: - ucrt >=10.0.20348.0 constrains: - - vs2015_runtime 14.51.36231.* *_39 + - vs2015_runtime 14.51.36247.* *_41 license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime license_family: Proprietary run_exports: strong: - - vcomp14 >=14.51.36231 - size: 120684 - timestamp: 1781320948530 + - vcomp14 >=14.51.36247 + size: 155910 + timestamp: 1785359349999 - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_26.conda sha256: d18d77c8edfbad37fa0e0bb0f543ad80feb85e8fe5ced0f686b8be463742ec0b md5: 312f3a0a6b3c5908e79ce24002411e32 @@ -17536,16 +18417,16 @@ packages: purls: [] size: 17888 timestamp: 1750371463202 -- conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36231-h84cd919_39.conda - sha256: 6de6c2cf008fc2dce61060b583f2d8494c83883106952b201381b6b0505f03d7 - md5: 2ccc63d7b7d066a814ed9f99072832d7 +- conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.51.36247-h633cb9f_41.conda + sha256: ee0ae67c96b80b9fdb50c3f617c6329dbcdf1562d0267604f6c0e24f494431d6 + md5: 21504569fa34b5d3a67760219e3ff1cc depends: - - vc14_runtime >=14.51.36231 + - vc14_runtime >=14.51.36247 license: BSD-3-Clause license_family: BSD run_exports: {} - size: 20355 - timestamp: 1781320968804 + size: 21386 + timestamp: 1785359368990 - conda: https://conda.anaconda.org/conda-forge/win-64/wrapt-1.17.2-py312h4389bb4_0.conda sha256: a1b86d727cc5f9d016a6fc9d8ac8b3e17c8e137764e018555ecadef05979ce93 md5: b9a81b36e0d35c9a172587ead532273b diff --git a/pixi.toml b/pixi.toml index cce4095..f3db0e0 100644 --- a/pixi.toml +++ b/pixi.toml @@ -7,6 +7,7 @@ readme = "README.md" channels = ["conda-forge", "bioconda"] platforms = ["win-64", "linux-64", "osx-arm64"] homepage = "https://www.modelblocks.org/" +requires-pixi = ">=0.79.0" [dependencies] clio-tools = ">=2026.06.23" @@ -49,11 +50,40 @@ glom = ">=24.11.0" dask = ">=2025.7.0" numpy = ">=2.3.5" +[feature.test-unit.dependencies] +pytest = ">=8.3.5" + +[feature.test-unit.tasks] +test-unit = "pytest unit_tests" +update-reference-unit = "pytest unit_tests --update-reference" + [environments] -module = { features = ["module"], no-default-feature = true } +module = { features = ["module"], no-default-feature = true, solve-group = "module" } +# Unit tests exercise workflow scripts directly, so they combine pytest with the +# module runtime dependencies while remaining isolated from the default environment. +test-unit = { features = ["test-unit", "module"], no-default-feature = true, solve-group = "module" } -[tasks] -test-integration = {cmd = "pytest tests/integration_test.py"} +[tasks.test-integration] +cmd = "pytest tests/integration_test.py" +depends-on = [ + { task = "test-unit", environment = "test-unit" }, +] + +# Regenerate the integration reference data: run the integration workflow, then +# freeze its report CSV. Review the resulting diff deliberately before committing. +[tasks.update-reference-integration] +cwd = "tests/integration" +cmd = """ +set -e +snakemake --use-conda --cores 2 +python -c 'import shutil; shutil.copy("resources/module/results/NLD/base/area_potential_report.csv", "../reference/NLD_area_potential_report.csv")' +""" + +[tasks.update-reference] +depends-on = [ + { task = "update-reference-unit", environment = "test-unit" }, + { task = "update-reference-integration", environment = "default" }, +] [tasks.export-snakemake-env] diff --git a/tests/integration/test_config.yaml b/tests/integration/test_config.yaml index 622dc74..21880a8 100644 --- a/tests/integration/test_config.yaml +++ b/tests/integration/test_config.yaml @@ -74,3 +74,9 @@ module_area_potentials: protected: 0 shapes_buffer: land: 10000 # meters + # Exercise the per-subunit override (glom merge) code path end-to-end + overrides: + NLD: + wind_offshore: + shapes_buffer: + land: 2000 # meters diff --git a/tests/integration_test.py b/tests/integration_test.py index c56b086..bd2a42a 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -4,13 +4,19 @@ Contents may be updated in future template updates. """ +import copy +import csv +import math import subprocess import tomllib from pathlib import Path import pytest +import yaml from clio_tools.data_module import ModuleInterface +INTEGRATION_TECHS = ["pv_rooftop", "pv_open_field", "wind_onshore", "wind_offshore"] + @pytest.fixture(scope="module") def pixi_platforms(module_path) -> list[str]: @@ -82,3 +88,194 @@ def test_snakemake_integration_testing(module_path): check=True, cwd=module_path / "tests/integration", ) + + +## +# Module-specific tests: compare the integration workflow outputs against +# committed reference data (regenerate deliberately via `pixi run update-reference`). +# These use only the standard library because the dev environment has no pandas. +## + + +def _report_csv_path(module_path): + """Path of the report CSV produced by the integration workflow run.""" + path = ( + module_path + / "tests/integration/resources/module/results/NLD/base/area_potential_report.csv" + ) + assert path.exists(), ( + "Integration workflow outputs not found; " + "test_snakemake_integration_testing must run (and pass) first." + ) + return path + + +def _read_csv_rows(path): + """Read a CSV file into a list of rows (lists of strings).""" + with open(path, newline="") as f: + return list(csv.reader(f)) + + +def _cells_equal(actual, expected, rel_tol, abs_tol): + """Compare two CSV cells, numerically where possible.""" + if actual == expected: + return True + try: + actual_num, expected_num = float(actual), float(expected) + except ValueError: + return False + if math.isnan(actual_num) and math.isnan(expected_num): + return True + return math.isclose(actual_num, expected_num, rel_tol=rel_tol, abs_tol=abs_tol) + + +def test_integration_output_files_exist(module_path): + """The aggregated area potential rasters exist for every configured tech.""" + _report_csv_path(module_path) + for tech in INTEGRATION_TECHS: + tif = ( + module_path + / f"tests/integration/results/outputs/NLD/base/area_potential_{tech}.tif" + ) + assert tif.exists(), f"Missing output raster: {tif}" + + +def test_integration_output_values(module_path): + """The report CSV matches the committed reference data cell by cell. + + Tolerances are deliberately tight: the workflow is expected to be + reproducible from pinned inputs and pinned dependencies. Performance + rewrites that change floating-point precision must relax these tolerances + consciously, so the change is visible in review. + """ + rel_tol, abs_tol = 1e-5, 1e-6 + actual_rows = _read_csv_rows(_report_csv_path(module_path)) + reference_csv = module_path / "tests/reference/NLD_area_potential_report.csv" + assert reference_csv.exists(), ( + f"Missing reference data {reference_csv}; " + "generate it with `pixi run update-reference-integration`." + ) + expected_rows = _read_csv_rows(reference_csv) + # Tech columns are named after the tif paths, which depend on where the + # workflow places its outputs (e.g. the scenario segment); compare basenames. + assert [Path(col).name for col in actual_rows[0]] == [ + Path(col).name for col in expected_rows[0] + ], "Report CSV header changed" + assert len(actual_rows) == len(expected_rows), "Report CSV row count changed" + mismatches = [ + f"row {i} column '{actual_rows[0][j]}': {actual!r} != reference {expected!r}" + for i, (actual_row, expected_row) in enumerate( + zip(actual_rows[1:], expected_rows[1:]) + ) + for j, (actual, expected) in enumerate(zip(actual_row, expected_row)) + if not _cells_equal(actual, expected, rel_tol, abs_tol) + ] + assert not mismatches, "Report differs from reference data:\n" + "\n".join( + mismatches + ) + + +def test_integration_output_sanity(module_path): + """Basic physical sanity of the report, independent of reference data.""" + rows = _read_csv_rows(_report_csv_path(module_path)) + header = rows[0] + tech_columns = { + tech: header.index( + next(col for col in header if f"area_potential_{tech}" in col) + ) + for tech in INTEGRATION_TECHS + } + class_column = header.index("shape_class") + + def values(tech, shape_class=None): + return [ + float(row[tech_columns[tech]]) + for row in rows[1:] + if row[tech_columns[tech]] not in ("", "nan") + and (shape_class is None or row[class_column] == shape_class) + ] + + for tech in INTEGRATION_TECHS: + assert sum(values(tech)) > 0, f"No area potential at all for {tech}" + # Offshore wind exists only in maritime regions; land techs only on land. + assert sum(values("wind_offshore", "land")) == pytest.approx(0, abs=1e-6) + for tech in ["pv_rooftop", "pv_open_field", "wind_onshore"]: + assert sum(values(tech, "maritime")) == pytest.approx(0, abs=1e-6) + + +## +# Rule-level regression tests: dry-run the integration workflow (after it has +# run, so the breakup_shape checkpoint is resolved) and inspect the DAG and the +# resolved shell commands. These guard config plumbing in the rules that no +# script-level test can see. +## + + +def _dry_run(module_path, *args): + """Dry-run the integration workflow and return its combined output. + + Only modification times decide what would rerun, so provenance changes + (e.g. a re-locked environment) cannot re-trigger the checkpoint and hide + every job downstream of it. + """ + integration = module_path / "tests/integration" + assert (integration / "resources/module/resources/automatic/shapes/NLD").exists(), ( + "Integration workflow outputs not found; " + "test_snakemake_integration_testing must run (and pass) first." + ) + process = subprocess.run( + ["snakemake", "--dry-run", "--rerun-triggers", "mtime", *args], + cwd=integration, + capture_output=True, + text=True, + ) + assert process.returncode == 0, process.stdout + process.stderr + return process.stdout + process.stderr + + +def test_subunit_overrides_reach_area_potential(module_path): + """Per-subunit overrides configured inside a scenario are passed to the script. + + Guards the override lookup in the area_potential rule: the test config + overrides the NLD wind_offshore land buffer, which must show up in the + resolved --override_config argument (and nothing must be passed for techs + without an override). + """ + output = _dry_run( + module_path, + "--printshellcmds", + "--forcerun", + "module_area_potentials_area_potential", + ) + commands = [line for line in output.splitlines() if "area_potential.py" in line] + assert len(commands) == len(INTEGRATION_TECHS), output + for command in commands: + # The mapping is passed through shell quoting; undo it before inspecting. + override = command.split("--override_config=", 1)[1].replace("'\"'\"'", "'") + if "area_potential_wind_offshore.tif" in command: + assert "'land': 2000" in override, command + else: + assert override.startswith("'{}'"), command + + +@pytest.mark.parametrize("where", ["tech", "override", "nowhere"]) +def test_ship_travel_layer_activates_its_rules(module_path, tmp_path, where): + """A ship_travel layer in any scenario tech or override pulls in the ship-travel rules. + + Guards uses_ship_travel(): the download/clip rules for the ship traffic + density raster are only part of the DAG when some tech uses the layer. + """ + config_path = module_path / "tests/integration/test_config.yaml" + config = yaml.safe_load(config_path.read_text()) + scenario = config["module_area_potentials"]["scenarios"]["base"] + layer = {"ship_travel": {"min": 0, "max": 21024000}} + if where == "tech": + scenario["techs"]["wind_offshore"]["continuous_layers"].update(layer) + elif where == "override": + scenario["overrides"]["NLD"]["wind_offshore"]["continuous_layers"] = layer + modified = tmp_path / "test_config.yaml" + modified.write_text(yaml.safe_dump(copy.deepcopy(config))) + + output = _dry_run(module_path, "--configfile", str(modified)) + expected = where != "nowhere" + assert ("module_area_potentials_clip_ship_travel" in output) == expected, output diff --git a/tests/reference/NLD_area_potential_report.csv b/tests/reference/NLD_area_potential_report.csv new file mode 100644 index 0000000..bd260a2 --- /dev/null +++ b/tests/reference/NLD_area_potential_report.csv @@ -0,0 +1,14 @@ +group,shape_id,country_id,shape_class,parent_name,results/outputs/NLD/base/area_potential_pv_rooftop.tif,results/outputs/NLD/base/area_potential_pv_open_field.tif,results/outputs/NLD/base/area_potential_wind_onshore.tif,results/outputs/NLD/base/area_potential_wind_offshore.tif +0.0,NLD_overture_0850d5ed3fffffff01410bf814c95115,NLD,land,Zeeland,45614939.491241455,70568063.23730469,138924692.73242188,0.0 +1.0,NLD_overture_08500891bfffffff01caffd8afbb104e,NLD,land,Zuid-Holland,239411790.89245605,55707915.19970703,98879005.13964844,0.0 +2.0,NLD_overture_08506b42ffffffff01dad4872a9ad536,NLD,land,Noord-Brabant,261438655.82162476,58555851.94970703,114348744.1159668,0.0 +3.0,NLD_overture_0850503cffffffff01a6ec42c4a7a1fa,NLD,land,Utrecht,83359157.83892822,32028171.57373047,51261075.75830078,0.0 +4.0,NLD_overture_08524a277fffffff015d1e37b5c6abb2,NLD,land,Noord-Holland,170660031.9779663,50628361.48730469,92422923.20507812,0.0 +5.0,NLD_overture_085141db7fffffff010809a6ddfd85ce,NLD,land,Flevoland,36199708.75198364,56640749.30078125,111569143.37866211,0.0 +6.0,NLD_overture_0854e2ea3fffffff0161e67fa4077784,NLD,land,Fryslân,70711468.42324829,161790043.7783203,263544557.79711914,0.0 +7.0,NLD_overture_08518a6b7fffffff013c19db89489cbe,NLD,land,Limburg,123171626.34689331,21578642.947753906,44680078.654052734,0.0 +8.0,NLD_overture_08504091bfffffff01cc3ea32562a467,NLD,land,Gelderland,194579421.5802002,68436695.53027344,117513411.03979492,0.0 +9.0,NLD_overture_08522d863fffffff012b3bf2187a9c6c,NLD,land,Overijssel,113906781.70489502,73087938.67871094,124642033.91137695,0.0 +10.0,NLD_overture_085474bb3fffffff012829670273578e,NLD,land,Drenthe,54898098.48864746,70948774.9921875,134381847.76489258,0.0 +11.0,NLD_overture_0855cd063fffffff013415aa0479994d,NLD,land,Groningen,53845676.44485474,106020352.90917969,198487599.1689453,0.0 +12.0,NLD_marineregions_5668,NLD,maritime,Dutch Exclusive Economic Zone,0.0,0.0,0.0,37547919565.48047 diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py new file mode 100644 index 0000000..1c879c0 --- /dev/null +++ b/unit_tests/conftest.py @@ -0,0 +1,143 @@ +"""Pytest configuration for the unit test suite. + +Makes the workflow scripts importable (they are plain scripts, not a package) +and provides the reference-data machinery: tests compare computed outputs +against files committed under ``unit_tests/reference/``, which are regenerated +with ``pytest unit_tests --update-reference`` (``pixi run update-reference-unit``). +""" + +import os +import sys +from pathlib import Path + +# Scripts import matplotlib.pyplot at import time; force a headless backend +# before any of them is imported. +os.environ.setdefault("MPLBACKEND", "Agg") + +REPO_ROOT = Path(__file__).parent.parent +sys.path.insert(0, str(REPO_ROOT / "workflow" / "scripts")) + +import fixtures # noqa: E402 +import numpy as np # noqa: E402 +import pandas as pd # noqa: E402 +import pytest # noqa: E402 +import yaml # noqa: E402 + +REFERENCE_DIR = Path(__file__).parent / "reference" + + +def pytest_addoption(parser): + """Add the --update-reference flag.""" + parser.addoption( + "--update-reference", + action="store_true", + default=False, + help="Write reference data from current outputs instead of comparing against it.", + ) + + +class ReferenceComparer: + """Compares computed outputs against committed reference files.""" + + def __init__(self, update): + """If ``update`` is True, checks write reference files instead of comparing.""" + self.update = update + + def check_arrays(self, name, arrays, rtol=1e-6, atol=0.0): + """Compare a dict of named arrays against ``.npz`` reference data. + + Integer arrays must match exactly. Floating-point arrays are compared + with a small relative tolerance because the reference data is generated + on one platform but compared on all CI platforms, whose libm/SIMD + implementations of log/sin and GDAL warps differ in the last ulp. + """ + path = REFERENCE_DIR / f"{name}.npz" + arrays = {key: np.asarray(value) for key, value in arrays.items()} + if self.update: + path.parent.mkdir(parents=True, exist_ok=True) + np.savez_compressed(path, **arrays) + return + assert path.exists(), ( + f"Missing reference file {path}. Generate it with: pixi run update-reference-unit" + ) + stored = np.load(path) + assert set(stored.files) == set(arrays), ( + f"{name}: array names differ from reference: " + f"{sorted(arrays)} vs {sorted(stored.files)}" + ) + for key, actual in arrays.items(): + is_float = np.issubdtype(actual.dtype, np.floating) + np.testing.assert_allclose( + actual, + stored[key], + rtol=rtol if is_float else 0.0, + atol=atol if is_float else 0.0, + equal_nan=True, + err_msg=f"{name}:{key} differs from reference data", + ) + + def check_dataframe(self, name, csv_path, rtol=0.0): + """Compare a CSV file produced by a test against ``.csv`` reference data.""" + path = REFERENCE_DIR / f"{name}.csv" + if self.update: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(Path(csv_path).read_text()) + return + assert path.exists(), ( + f"Missing reference file {path}. Generate it with: pixi run update-reference-unit" + ) + actual = pd.read_csv(csv_path, index_col=0) + stored = pd.read_csv(path, index_col=0) + pd.testing.assert_frame_equal( + actual, stored, check_exact=rtol == 0.0, rtol=rtol or 1e-15 + ) + + +@pytest.fixture(scope="session") +def reference(request): + """Reference-data comparer honouring the --update-reference flag.""" + return ReferenceComparer(update=request.config.getoption("--update-reference")) + + +@pytest.fixture(scope="session") +def world(tmp_path_factory): + """Paths to the deterministic synthetic input dataset.""" + return fixtures.write_world(tmp_path_factory.mktemp("world")) + + +@pytest.fixture(scope="session") +def wdpa_raster_path(world, tmp_path_factory): + """Protected-areas raster produced by the clip_and_rasterise_polys CLI.""" + from clip_and_rasterise_polys import clip_and_rasterise_polys + + path = tmp_path_factory.mktemp("wdpa") / "wdpa.tif" + fixtures.run_cli( + clip_and_rasterise_polys, + [world["shapes"], world["landcover"], world["protected"], path], + ) + return path + + +@pytest.fixture(scope="session") +def resampled_path(world, wdpa_raster_path, tmp_path_factory): + """Resampled input NetCDF produced by the resample CLI.""" + from resample import resample_inputs + + directory = tmp_path_factory.mktemp("resampled") + path = directory / "resampled.nc" + fixtures.run_cli( + resample_inputs, + [ + world["shapes"], + world["landcover"], + world["slope"], + world["settlement"], + world["bathymetry"], + wdpa_raster_path, + yaml.dump(fixtures.LAND_COVER_TYPES), + path, + "--ship-travel-path", + world["ship_travel"], + ], + ) + return path diff --git a/unit_tests/fixtures.py b/unit_tests/fixtures.py new file mode 100644 index 0000000..e60fade --- /dev/null +++ b/unit_tests/fixtures.py @@ -0,0 +1,179 @@ +"""Deterministic synthetic geodata used by the unit tests. + +All builders are seeded so that repeated test runs (and the committed reference +data) are reproducible. The geography is a small area near the Dutch coast: +two abutting land regions and one maritime region, with input rasters at +resolutions mimicking the real datasets (land cover 10 arcsec as the reference +grid; slope finer; settlement and bathymetry coarser). +""" + +from pathlib import Path + +import geopandas as gpd +import numpy as np +import rioxarray as rxr +import xarray as xr +import yaml +from click.testing import CliRunner +from resample import GLOBCOVER_TYPES +from shapely.geometry import box + +REPO_ROOT = Path(__file__).parent.parent +LAND_COVER_TYPES = yaml.safe_load( + (REPO_ROOT / "workflow" / "internal" / "settings.yaml").read_text() +)["land_cover_types"] + +# Three abutting regions of 0.05 x 0.1 degrees: two land, one maritime. +SHAPES_BOUNDS = (5.0, 52.0, 5.15, 52.1) +LANDCOVER_RESOLUTION = 1 / 360 # 10 arcsec, like GlobCover +SLOPE_RESOLUTION = 1 / 450 +SETTLEMENT_RESOLUTION = 1 / 120 # 30 arcsec, like GHSL +BATHYMETRY_RESOLUTION = 1 / 240 # 15 arcsec, like GEBCO +SHIP_TRAVEL_RESOLUTION = 1 / 200 +SLOPE_NODATA = -32768 +SHIP_TRAVEL_NODATA = -1.0 + + +def run_cli(command, args): + """Invoke a click command in-process and assert it succeeded.""" + result = CliRunner().invoke( + command, [str(arg) for arg in args], catch_exceptions=False + ) + assert result.exit_code == 0, result.output + return result + + +def load_raster(path): + """Load a raster into memory and close its backing file handle.""" + with rxr.open_rasterio(path) as raster: + return raster.load() + + +def make_shapes(include_subpixel_region=False): + """Return the region shapes as a GeoDataFrame matching ShapesSchema.""" + records = [ + ("region_a", "AAA", "land", box(5.0, 52.0, 5.05, 52.1)), + ("region_b", "AAA", "land", box(5.05, 52.0, 5.1, 52.1)), + ("region_sea", "AAA", "maritime", box(5.1, 52.0, 5.15, 52.1)), + ] + if include_subpixel_region: + # Far smaller than one land-cover pixel and placed away from any pixel + # center, so rasterisation burns no pixels for it. + records.append( + ("region_tiny", "AAA", "land", box(5.12, 52.05, 5.1201, 52.0501)) + ) + shape_ids, country_ids, shape_classes, geometries = zip(*records) + return gpd.GeoDataFrame( + { + "shape_id": list(shape_ids), + "country_id": list(country_ids), + "shape_class": list(shape_classes), + "parent_name": [f"Parent of {shape_id}" for shape_id in shape_ids], + "geometry": list(geometries), + }, + crs="EPSG:4326", + ) + + +def grid_coords(resolution, pad_pixels=0, bounds=SHAPES_BOUNDS): + """Return (x, y) pixel-center coordinates covering ``bounds`` plus padding.""" + xmin, ymin, xmax, ymax = bounds + pad = pad_pixels * resolution + x = np.arange(xmin - pad + resolution / 2, xmax + pad, resolution) + y = np.arange(ymax + pad - resolution / 2, ymin - pad, -resolution) + return x, y + + +def make_raster(values, x, y, nodata=None): + """Wrap a 2D array into a (band, y, x) DataArray with CRS EPSG:4326.""" + da = xr.DataArray( + np.asarray(values)[np.newaxis, ...], + coords={"band": [1], "y": y, "x": x}, + dims=("band", "y", "x"), + ) + da.rio.write_crs("EPSG:4326", inplace=True) + if nodata is not None: + da.rio.write_nodata(nodata, inplace=True) + return da + + +def make_landcover(rng): + """Random land cover raster guaranteed to contain every GlobCover code. + + Also contains the code 13, which is not a valid GlobCover code, to pin down + how unmapped codes are treated (they belong to no category). + """ + codes = np.array(sorted(GLOBCOVER_TYPES) + [13], dtype=np.uint8) + x, y = grid_coords(LANDCOVER_RESOLUTION, pad_pixels=3) + values = rng.choice(codes, size=(len(y), len(x))).astype(np.uint8) + values[0, : len(codes)] = codes + return make_raster(values, x, y) + + +def make_slope(rng): + """Random slope raster: int16 slope * 100, like GEDTM30, with nodata holes.""" + x, y = grid_coords(SLOPE_RESOLUTION, pad_pixels=3) + values = rng.integers(0, 3000, size=(len(y), len(x))).astype(np.int16) + values[rng.random(values.shape) < 0.05] = SLOPE_NODATA + return make_raster(values, x, y, nodata=SLOPE_NODATA) + + +def make_settlement(rng): + """Random settlement raster: built-up surface in m2 per pixel, like GHSL.""" + x, y = grid_coords(SETTLEMENT_RESOLUTION, pad_pixels=3) + values = rng.uniform(0, 500_000, size=(len(y), len(x))).astype(np.float32) + values[rng.random(values.shape) < 0.6] = 0.0 + return make_raster(values, x, y) + + +def make_bathymetry(rng): + """Random bathymetry raster: int16 elevation in m, like GEBCO.""" + x, y = grid_coords(BATHYMETRY_RESOLUTION, pad_pixels=3) + values = rng.integers(-80, 40, size=(len(y), len(x))).astype(np.int16) + return make_raster(values, x, y) + + +def make_ship_travel(rng): + """Random ship traffic density raster (AIS position counts) with nodata.""" + x, y = grid_coords(SHIP_TRAVEL_RESOLUTION, pad_pixels=3) + values = rng.integers(0, 50_000, size=(len(y), len(x))).astype(np.float32) + values[rng.random(values.shape) < 0.3] = 0.0 + values[rng.random(values.shape) < 0.05] = SHIP_TRAVEL_NODATA + return make_raster(values, x, y, nodata=SHIP_TRAVEL_NODATA) + + +def make_protected_areas(): + """Two protected-area polygons inside region_a.""" + return gpd.GeoDataFrame( + { + "site_name": ["site_1", "site_2"], + "geometry": [ + box(5.005, 52.010, 5.020, 52.040), + box(5.025, 52.050, 5.040, 52.090), + ], + }, + crs="EPSG:4326", + ) + + +def write_world(directory): + """Write the full synthetic input dataset and return a dict of paths.""" + directory = Path(directory) + rng = np.random.default_rng(20260707) + paths = { + "shapes": directory / "shapes.parquet", + "landcover": directory / "landcover.tif", + "slope": directory / "slope.tif", + "settlement": directory / "settlement.tif", + "bathymetry": directory / "bathymetry.tif", + "ship_travel": directory / "ship_travel.tif", + "protected": directory / "protected.gpkg", + } + make_shapes().to_parquet(paths["shapes"]) + make_landcover(rng).rio.to_raster(paths["landcover"]) + make_slope(rng).rio.to_raster(paths["slope"]) + make_settlement(rng).rio.to_raster(paths["settlement"]) + make_bathymetry(rng).rio.to_raster(paths["bathymetry"]) + make_ship_travel(rng).rio.to_raster(paths["ship_travel"]) + make_protected_areas().to_file(paths["protected"], driver="GPKG") + return paths diff --git a/unit_tests/reference/area_potential_binary_overlapping.npz b/unit_tests/reference/area_potential_binary_overlapping.npz new file mode 100644 index 0000000..29e9e4e Binary files /dev/null and b/unit_tests/reference/area_potential_binary_overlapping.npz differ diff --git a/unit_tests/reference/area_potential_continuous_no_share.npz b/unit_tests/reference/area_potential_continuous_no_share.npz new file mode 100644 index 0000000..3e4c159 Binary files /dev/null and b/unit_tests/reference/area_potential_continuous_no_share.npz differ diff --git a/unit_tests/reference/area_potential_continuous_with_share.npz b/unit_tests/reference/area_potential_continuous_with_share.npz new file mode 100644 index 0000000..c4588c4 Binary files /dev/null and b/unit_tests/reference/area_potential_continuous_with_share.npz differ diff --git a/unit_tests/reference/area_potential_missing_layers_warn.npz b/unit_tests/reference/area_potential_missing_layers_warn.npz new file mode 100644 index 0000000..b7622ff Binary files /dev/null and b/unit_tests/reference/area_potential_missing_layers_warn.npz differ diff --git a/unit_tests/reference/area_potential_offshore_buffer_epsg.npz b/unit_tests/reference/area_potential_offshore_buffer_epsg.npz new file mode 100644 index 0000000..458791e Binary files /dev/null and b/unit_tests/reference/area_potential_offshore_buffer_epsg.npz differ diff --git a/unit_tests/reference/area_potential_offshore_buffer_utm.npz b/unit_tests/reference/area_potential_offshore_buffer_utm.npz new file mode 100644 index 0000000..8ccefd4 Binary files /dev/null and b/unit_tests/reference/area_potential_offshore_buffer_utm.npz differ diff --git a/unit_tests/reference/area_potential_offshore_ship_travel.npz b/unit_tests/reference/area_potential_offshore_ship_travel.npz new file mode 100644 index 0000000..b391145 Binary files /dev/null and b/unit_tests/reference/area_potential_offshore_ship_travel.npz differ diff --git a/unit_tests/reference/area_potential_override_merge.npz b/unit_tests/reference/area_potential_override_merge.npz new file mode 100644 index 0000000..499f08a Binary files /dev/null and b/unit_tests/reference/area_potential_override_merge.npz differ diff --git a/unit_tests/reference/report.csv b/unit_tests/reference/report.csv new file mode 100644 index 0000000..50bc12f --- /dev/null +++ b/unit_tests/reference/report.csv @@ -0,0 +1,4 @@ +group,shape_id,country_id,shape_class,parent_name,area_potential_tech_a.tif,area_potential_tech_b.tif +0.0,region_a,AAA,land,Parent of region_a,2437283.5775748244,2437438.2973064715 +1.0,region_b,AAA,land,Parent of region_b,2274233.103636447,2214124.083677837 +2.0,region_sea,AAA,maritime,Parent of region_sea,2264137.349540678,2239610.5665923944 diff --git a/unit_tests/reference/resample_output.npz b/unit_tests/reference/resample_output.npz new file mode 100644 index 0000000..8ab5f3e Binary files /dev/null and b/unit_tests/reference/resample_output.npz differ diff --git a/unit_tests/reference/wdpa_raster.npz b/unit_tests/reference/wdpa_raster.npz new file mode 100644 index 0000000..b339b34 Binary files /dev/null and b/unit_tests/reference/wdpa_raster.npz differ diff --git a/unit_tests/test_cli_area_potential.py b/unit_tests/test_cli_area_potential.py new file mode 100644 index 0000000..a7de08a --- /dev/null +++ b/unit_tests/test_cli_area_potential.py @@ -0,0 +1,337 @@ +"""Reference and equivalence tests for the area_potential.py CLI. + +Two safety nets for the planned mask/factor rewrite: + +- reference tests pin the output rasters for configs covering every code path + (both initial areas, continuous min/max with and without share, binary layers + with value 0 / 1 / fractional, overlapping binary layers, buffering with a + fixed CRS and with UTM, config overrides, missing layers); +- an oracle re-implementing the current *sequential* semantics is compared + bit-exactly against the CLI output, including on randomized configs. +""" + +import fixtures +import glom +import numpy as np +import pytest +import xarray as xr +import yaml +from area_potential import get_area_potential + +BINARY_OVERLAPPING = { + "initial_area": "pixel_area", + "binary_layers": { + "regions_maritime": 0, + "regions_land": 1, + "protected": 0, + "landcover_FARM": 0.1, + "landcover_FOREST": 0, + "landcover_URBAN": 0, + "landcover_OTHER": 0.2, + "landcover_NOT_SUITABLE": 0, + "landcover_WATER": 0, + }, +} +CONTINUOUS_WITH_SHARE = { + "initial_area": "settlement_area", + "continuous_layers": {"settlement_share": {"min": 0.001, "max": 1, "share": 0.8}}, + "binary_layers": {"regions_maritime": 0, "regions_land": 1, "protected": 0}, +} +CONTINUOUS_NO_SHARE = { + "initial_area": "pixel_area", + "continuous_layers": {"slope_deg": {"min": 0, "max": 10}}, + "binary_layers": {"regions_land": 1, "protected": 0}, +} +OFFSHORE_BUFFERED = { + "initial_area": "pixel_area", + "continuous_layers": {"bathymetry": {"min": -60, "max": 0, "share": 0.9}}, + "binary_layers": {"regions_land": 0, "regions_maritime": 1}, + "shapes_buffer": {"land": 1500}, +} +OFFSHORE_SHIP_TRAVEL = { + "initial_area": "pixel_area", + "continuous_layers": { + "bathymetry": {"min": -80, "max": 0}, + # Below the fixture's maximum traffic density, so busy pixels drop out + "ship_travel": {"min": 0, "max": 20000}, + }, + "binary_layers": {"regions_land": 0, "regions_maritime": 1}, +} +MISSING_LAYERS = { + "initial_area": "pixel_area", + "continuous_layers": {"not_a_continuous_layer": {"min": 0, "max": 1}}, + "binary_layers": { + "not_a_binary_layer": 0, + "also_missing": 1, + "regions_land": 1, + "protected": 0, + }, +} + +# case id -> (config, buffer_crs, override config or None) +CASES = { + "binary_overlapping": (BINARY_OVERLAPPING, "epsg:8857", None), + "continuous_with_share": (CONTINUOUS_WITH_SHARE, "epsg:8857", None), + "continuous_no_share": (CONTINUOUS_NO_SHARE, "epsg:8857", None), + "offshore_buffer_epsg": (OFFSHORE_BUFFERED, "epsg:8857", None), + "offshore_buffer_utm": (OFFSHORE_BUFFERED, "utm", None), + "offshore_ship_travel": (OFFSHORE_SHIP_TRAVEL, "epsg:8857", None), + "override_merge": ( + BINARY_OVERLAPPING, + "epsg:8857", + {"binary_layers": {"landcover_FARM": 0.5}}, + ), + "missing_layers_warn": (MISSING_LAYERS, "epsg:8857", None), +} +UNBUFFERED_CASES = [case for case in CASES if "buffer" not in case] + + +def _run_area_potential(world, resampled_path, tmp_path, config, buffer_crs, override): + """Run the CLI, returning (output DataArray, CLI output text).""" + tif_path = tmp_path / "area_potential.tif" + args = [world["shapes"], resampled_path, yaml.dump(config), buffer_crs, tif_path] + if override is not None: + args.append(f"--override_config={yaml.dump(override)}") + result = fixtures.run_cli(get_area_potential, args) + return fixtures.load_raster(tif_path), result.output + + +def _parsed_config(config, override): + """The config exactly as the CLI sees it (YAML round-trip plus glom merge).""" + config = yaml.safe_load(yaml.dump(config)) + if override is not None: + config = glom.merge([config, yaml.safe_load(yaml.dump(override))]) + return config + + +def sequential_area_potential(ds, config): + """Oracle mirroring the current sequential where-chain (without buffering). + + The CLI writes float32 rasters, so oracle expectations are cast to float32 + before comparison — bit-exactness is required at the output granularity. + """ + potential = ds[config["initial_area"]].squeeze(drop=True) + binary_layers = config.get("binary_layers", {}) + for layer, value in binary_layers.items(): + if value == 0 and layer in ds: + potential = potential.where(~(ds[layer] > 0), other=0) + for layer, layer_config in config.get("continuous_layers", {}).items(): + if layer in ds: + potential = potential.where( + (ds[layer] <= layer_config["max"]) & (ds[layer] >= layer_config["min"]), + other=0, + ) + if "share" in layer_config: + potential = potential * layer_config["share"] + for layer, value in binary_layers.items(): + if layer in ds and value != 0: + potential = xr.where( + ds[layer] != 0, potential * ds[layer] * value, potential + ) + return potential + + +@pytest.mark.parametrize("case_id", sorted(CASES)) +def test_area_potential_reference(case_id, world, resampled_path, tmp_path, reference): + """The output raster matches the committed reference data for each config.""" + config, buffer_crs, override = CASES[case_id] + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, override + ) + reference.check_arrays(f"area_potential_{case_id}", {"values": da.values}) + + +@pytest.mark.parametrize("case_id", sorted(CASES)) +def test_area_potential_invariants(case_id, world, resampled_path, tmp_path): + """Output rasters are (band, y, x), NaN-free, non-negative, with nodata -1.""" + config, buffer_crs, override = CASES[case_id] + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, override + ) + assert da.dims == ("band", "y", "x") + assert da.rio.nodata == -1 + values = da.values + assert not np.isnan(values).any() + assert ((values >= 0) | (values == -1)).all() + + +def test_area_potential_warns_on_missing_layers(world, resampled_path, tmp_path): + """Layers absent from the dataset are skipped with a warning.""" + config, buffer_crs, override = CASES["missing_layers_warn"] + _, output = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, override + ) + assert "Warning: Layer 'not_a_binary_layer' not found" in output + assert "Warning: Layer 'not_a_continuous_layer' not found" in output + + +def test_area_potential_buffer_removes_nearshore_pixels( + world, resampled_path, tmp_path +): + """Buffering land shapes clips pixels out of the offshore potential.""" + config, buffer_crs, _ = CASES["offshore_buffer_epsg"] + buffered, _ = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, None + ) + unbuffered_config = {k: v for k, v in config.items() if k != "shapes_buffer"} + unbuffered, _ = _run_area_potential( + world, resampled_path, tmp_path, unbuffered_config, buffer_crs, None + ) + n_valid_buffered = int((buffered.values != -1).sum()) + n_valid_unbuffered = int((unbuffered.values != -1).sum()) + assert 0 < n_valid_buffered < n_valid_unbuffered + + +@pytest.mark.parametrize("case_id", UNBUFFERED_CASES) +def test_area_potential_matches_sequential_oracle( + case_id, world, resampled_path, tmp_path +): + """CLI output equals the sequential oracle bit-for-bit (no buffering).""" + config, buffer_crs, override = CASES[case_id] + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, override + ) + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + expected = sequential_area_potential( + ds.load(), _parsed_config(config, override) + ) + expected = expected.transpose("band", "y", "x").fillna(-1.0) + np.testing.assert_array_equal(da.values, expected.values.astype(np.float32)) + + +def test_area_potential_inclusive_min_max_boundaries(world, resampled_path, tmp_path): + """Continuous min/max bounds are inclusive. + + The bounds are taken from exact data values, so that an accidental switch + to strict comparisons in a rewrite changes the output and fails this test. + """ + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + ds = ds.load() + slope = np.squeeze(ds["slope_deg"].values) + finite = np.unique(slope[np.isfinite(slope)]) + config = { + "initial_area": "pixel_area", + "continuous_layers": { + "slope_deg": {"min": float(finite[1]), "max": float(finite[-2])} + }, + "binary_layers": {"protected": 0}, + } + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, "epsg:8857", None + ) + expected = sequential_area_potential(ds, _parsed_config(config, None)) + expected = expected.transpose("band", "y", "x").fillna(-1.0) + np.testing.assert_array_equal(da.values, expected.values.astype(np.float32)) + # Pixels lying exactly on the bounds must survive the min/max filter. + values = np.squeeze(da.values) + on_boundary = (slope == finite[1]) | (slope == finite[-2]) + assert (values[on_boundary] > 0).any() + + +def _random_config(rng, ds): + """A random but valid tech config referencing existing layers.""" + binary_pool = sorted( + name + for name in ds.data_vars + if name.startswith("landcover_") + or name in ["regions_land", "regions_maritime", "protected"] + ) + continuous_pool = ["slope_deg", "settlement_share", "bathymetry", "ship_travel"] + config = { + "initial_area": str(rng.choice(["pixel_area", "settlement_area"])), + # Always include one banded layer: the CLI requires the band dimension + # to be broadcast back into the potential. + "binary_layers": {"landcover_FARM": float(rng.choice([0.0, 1.0, 0.3]))}, + } + for layer in rng.choice(binary_pool, size=3, replace=False): + config["binary_layers"][str(layer)] = float(rng.choice([0.0, 1.0, 0.3, 2.0])) + config["continuous_layers"] = {} + for layer in rng.choice( + continuous_pool, size=int(rng.integers(1, 3)), replace=False + ): + low, high = np.nanquantile(ds[str(layer)].values, [0.2, 0.8]) + layer_config = {"min": float(low), "max": float(high)} + if rng.random() < 0.5: + layer_config["share"] = float(rng.uniform(0.1, 0.9)) + config["continuous_layers"][str(layer)] = layer_config + return config + + +@pytest.mark.parametrize("seed", range(4)) +def test_area_potential_matches_sequential_oracle_randomized( + seed, world, resampled_path, tmp_path +): + """Randomized configs also agree with the sequential oracle bit-for-bit.""" + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + ds = ds.load() + config = _random_config(np.random.default_rng(seed), ds) + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, "epsg:8857", None + ) + expected = sequential_area_potential(ds, _parsed_config(config, None)) + expected = expected.transpose("band", "y", "x").fillna(-1.0) + np.testing.assert_array_equal(da.values, expected.values.astype(np.float32)) + + +def test_area_potential_all_excluded(world, resampled_path, tmp_path): + """A config whose criteria exclude every pixel yields an all-zero raster.""" + config = { + "initial_area": "pixel_area", + # No slope value lies in this range, so every pixel is zeroed out + # (pixels with NaN slope fail the comparison as well). + "continuous_layers": {"slope_deg": {"min": 1e9, "max": 2e9}}, + "binary_layers": {"landcover_FARM": 1}, + } + da, _ = _run_area_potential( + world, resampled_path, tmp_path, config, "epsg:8857", None + ) + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + expected = sequential_area_potential(ds.load(), _parsed_config(config, None)) + expected = expected.transpose("band", "y", "x").fillna(-1.0) + np.testing.assert_array_equal(da.values, expected.values.astype(np.float32)) + assert (da.values == 0).all() + + +def test_area_potential_tiny_grid(world, resampled_path, tmp_path): + """The CLI works on a 2x2-pixel dataset and agrees with the oracle.""" + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + ds = ds.load() + # Anchor on a land pixel so that the potential is non-trivial. + y_idx, x_idx = np.argwhere(np.squeeze(ds["regions_land"].values) == 1)[0] + tiny = ds.isel(y=slice(y_idx, y_idx + 2), x=slice(x_idx, x_idx + 2)) + tiny_path = tmp_path / "tiny.nc" + tiny.to_netcdf(tiny_path) + config = { + "initial_area": "pixel_area", + "continuous_layers": {"slope_deg": {"min": 0, "max": 90, "share": 0.5}}, + "binary_layers": {"regions_land": 1, "landcover_FARM": 0.3}, + } + da, _ = _run_area_potential(world, tiny_path, tmp_path, config, "epsg:8857", None) + assert da.shape == (1, 2, 2) + expected = sequential_area_potential(tiny, _parsed_config(config, None)) + expected = expected.transpose("band", "y", "x").fillna(-1.0) + np.testing.assert_array_equal(da.values, expected.values.astype(np.float32)) + + +def test_area_potential_ship_travel_excludes_busy_pixels( + world, resampled_path, tmp_path +): + """A ship_travel criterion removes maritime pixels with dense traffic.""" + config, buffer_crs, _ = CASES["offshore_ship_travel"] + with_layer, _ = _run_area_potential( + world, resampled_path, tmp_path, config, buffer_crs, None + ) + without_config = { + **config, + "continuous_layers": { + k: v for k, v in config["continuous_layers"].items() if k != "ship_travel" + }, + } + without_layer, _ = _run_area_potential( + world, resampled_path, tmp_path, without_config, buffer_crs, None + ) + with_values, without_values = with_layer.values, without_layer.values + # The criterion can only take potential away, never add it... + assert (with_values <= without_values).all() + # ...and in the fixture it does take some away. + assert (with_values > 0).sum() < (without_values > 0).sum() diff --git a/unit_tests/test_cli_breakup.py b/unit_tests/test_cli_breakup.py new file mode 100644 index 0000000..2c69941 --- /dev/null +++ b/unit_tests/test_cli_breakup.py @@ -0,0 +1,49 @@ +"""Tests for the breakup_shape.py CLI.""" + +import fixtures +import geopandas as gpd +import pandas as pd +import pytest +from breakup_shape import breakup_shape +from shapely.geometry import box + + +@pytest.fixture +def two_country_shapes(): + """Four regions across two countries.""" + shapes = fixtures.make_shapes() + extra = gpd.GeoDataFrame( + { + "shape_id": ["region_c"], + "country_id": ["BBB"], + "shape_class": ["land"], + "parent_name": ["Parent of region_c"], + "geometry": [box(6.0, 52.0, 6.1, 52.1)], + }, + crs="EPSG:4326", + ) + return pd.concat([shapes, extra], ignore_index=True) + + +def test_breakup_shape_splits_by_column(two_country_shapes, tmp_path): + """Splitting by country_id writes one parquet file per country.""" + shapes_path = tmp_path / "shapes.parquet" + two_country_shapes.to_parquet(shapes_path) + output_path = tmp_path / "subunits" + fixtures.run_cli(breakup_shape, [shapes_path, "country_id", output_path]) + assert sorted(path.name for path in output_path.iterdir()) == [ + "AAA.parquet", + "BBB.parquet", + ] + assert len(gpd.read_parquet(output_path / "AAA.parquet")) == 3 + assert len(gpd.read_parquet(output_path / "BBB.parquet")) == 1 + + +def test_breakup_shape_split_by_none(two_country_shapes, tmp_path): + """split_by 'none' writes everything to a single all.parquet.""" + shapes_path = tmp_path / "shapes.parquet" + two_country_shapes.to_parquet(shapes_path) + output_path = tmp_path / "subunits" + fixtures.run_cli(breakup_shape, [shapes_path, "none", output_path]) + assert [path.name for path in output_path.iterdir()] == ["all.parquet"] + assert len(gpd.read_parquet(output_path / "all.parquet")) == 4 diff --git a/unit_tests/test_cli_clip_rasterise.py b/unit_tests/test_cli_clip_rasterise.py new file mode 100644 index 0000000..cfb4ce5 --- /dev/null +++ b/unit_tests/test_cli_clip_rasterise.py @@ -0,0 +1,47 @@ +"""Reference tests for the clip_and_rasterise_polys CLI. + +Guards the planned switch to bbox-filtered vector reading: the output raster +for the synthetic protected areas must stay identical. +""" + +import fixtures +import geopandas as gpd +import numpy as np +from clip_and_rasterise_polys import clip_and_rasterise_polys + + +def test_wdpa_raster_reference(wdpa_raster_path, reference): + """The rasterised protected areas match the committed reference data.""" + da = fixtures.load_raster(wdpa_raster_path) + reference.check_arrays( + "wdpa_raster", {"values": da.values, "x": da.x.values, "y": da.y.values} + ) + + +def test_wdpa_raster_marks_protected_pixels(wdpa_raster_path, world): + """Pixels inside the protected polygons carry values from the reference raster.""" + da = fixtures.load_raster(wdpa_raster_path).squeeze(drop=True) + # Pixel centers inside the first protected polygon (5.005..5.020, 52.010..52.040) + inside = da.sel(x=slice(5.006, 5.019), y=slice(52.039, 52.011)) + assert inside.size > 0 + assert np.count_nonzero(inside.values) == inside.size + + +def test_wdpa_raster_reprojected_source(world, tmp_path): + """Polygons in another CRS give the same raster (boxes stay boxes in Mercator).""" + reprojected = tmp_path / "protected_3857.gpkg" + gpd.read_file(world["protected"]).to_crs("EPSG:3857").to_file( + reprojected, driver="GPKG" + ) + fixtures.run_cli( + clip_and_rasterise_polys, + [world["shapes"], world["landcover"], reprojected, tmp_path / "wdpa.tif"], + ) + fixtures.run_cli( + clip_and_rasterise_polys, + [world["shapes"], world["landcover"], world["protected"], tmp_path / "ref.tif"], + ) + np.testing.assert_array_equal( + fixtures.load_raster(tmp_path / "wdpa.tif").values, + fixtures.load_raster(tmp_path / "ref.tif").values, + ) diff --git a/unit_tests/test_cli_report.py b/unit_tests/test_cli_report.py new file mode 100644 index 0000000..f77406b --- /dev/null +++ b/unit_tests/test_cli_report.py @@ -0,0 +1,95 @@ +"""Reference and oracle tests for report.py. + +Guards the planned groupby-to-bincount rewrite of the per-region aggregation, +including the edge cases that rewrite must preserve: nodata pixels (NaN after +masking), pixels outside all regions, and a region too small to own any pixel. +""" + +import fixtures +import numpy as np +import pandas as pd +import pytest +from rasterio.features import rasterize +from report import report + +TECH_FILES = ["area_potential_tech_a.tif", "area_potential_tech_b.tif"] +NODATA = -1.0 + + +def _write_potential_tif(path, rng): + """A synthetic area-potential raster with zeros and nodata patches.""" + x, y = fixtures.grid_coords(fixtures.LANDCOVER_RESOLUTION) + values = rng.uniform(0, 1e4, size=(len(y), len(x))) + values[rng.random(values.shape) < 0.2] = 0.0 + values[rng.random(values.shape) < 0.1] = NODATA + da = fixtures.make_raster(values, x, y, nodata=NODATA) + da.rio.to_raster(path) + + +@pytest.fixture +def report_outputs(tmp_path, monkeypatch): + """Run report() on synthetic tifs; returns (shapes, csv path, html path). + + Runs in tmp_path with relative tif paths so that the tif-derived column + names in the CSV are stable for reference comparison. + """ + monkeypatch.chdir(tmp_path) + shapes = fixtures.make_shapes(include_subpixel_region=True) + shapes.to_parquet("shapes.parquet") + rng = np.random.default_rng(42) + for tech_file in TECH_FILES: + _write_potential_tif(tech_file, rng) + report("shapes.parquet", TECH_FILES, "report.csv", "report.html") + return shapes, tmp_path / "report.csv", tmp_path / "report.html" + + +def test_report_reference(report_outputs, reference): + """The report CSV matches the committed reference data.""" + _, csv_path, _ = report_outputs + reference.check_dataframe("report", csv_path) + + +def test_report_matches_naive_region_sums(report_outputs): + """Per-region sums equal an independent mask-based aggregation.""" + shapes, csv_path, _ = report_outputs + df = pd.read_csv(csv_path, index_col=0) + for tech_file in TECH_FILES: + da = fixtures.load_raster(tech_file).squeeze(drop=True) + values = np.where(da.values == NODATA, np.nan, da.values) + regions = rasterize( + zip(shapes.geometry, shapes.index), + out_shape=da.rio.shape, + transform=da.rio.transform(), + fill=-9999, + ) + for region_index in [0, 1, 2]: + expected = np.nansum(values[regions == region_index]) + assert df.loc[region_index, tech_file] == pytest.approx(expected, rel=1e-12) + + +def test_report_drops_pixel_free_regions(report_outputs): + """A region too small to own a pixel does not appear in the report.""" + _, csv_path, _ = report_outputs + df = pd.read_csv(csv_path, index_col=0) + assert set(df.index) == {0.0, 1.0, 2.0} + + +def test_report_metadata_columns(report_outputs): + """Shape metadata columns are aligned with the region raster indices.""" + shapes, csv_path, _ = report_outputs + df = pd.read_csv(csv_path, index_col=0) + assert list(df.columns[:4]) == [ + "shape_id", + "country_id", + "shape_class", + "parent_name", + ] + for region_index in [0, 1, 2]: + assert df.loc[region_index, "shape_id"] == shapes.loc[region_index, "shape_id"] + + +def test_report_writes_html_with_total_row(report_outputs): + """The HTML report exists and contains the appended Total row.""" + _, _, html_path = report_outputs + html = html_path.read_text() + assert "Total" in html diff --git a/unit_tests/test_cli_resample.py b/unit_tests/test_cli_resample.py new file mode 100644 index 0000000..32c99c9 --- /dev/null +++ b/unit_tests/test_cli_resample.py @@ -0,0 +1,107 @@ +"""Reference tests for the resample.py CLI on the synthetic world. + +This is the broadest guard for the resampling rewrite: it pins the variable +set, the reference grid, dtypes and every resampled value. +""" + +import fixtures +import numpy as np +import pytest +import xarray as xr + +EXPECTED_VARIABLES = { + f"landcover_{category}" for category in set(fixtures.LAND_COVER_TYPES.values()) +} | { + "pixel_area", + "regions", + "regions_land", + "regions_maritime", + "slope_deg", + "settlement_share", + "settlement_area", + "bathymetry", + "protected", + "ship_travel", +} + + +@pytest.fixture(scope="module") +def resampled(resampled_path): + """The resampled dataset, loaded into memory.""" + with xr.open_dataset(resampled_path, decode_coords="all") as ds: + return ds.load() + + +def test_resample_variable_set(resampled): + """All expected variables are present.""" + assert set(resampled.data_vars) - {"spatial_ref"} == EXPECTED_VARIABLES + + +def test_resample_grid_is_landcover_grid(resampled): + """The output grid is the land-cover grid clipped to the shapes' bounds.""" + xmin, ymin, xmax, ymax = fixtures.SHAPES_BOUNDS + resolution = fixtures.LANDCOVER_RESOLUTION + x = resampled.x.values + y = resampled.y.values + assert np.allclose(np.diff(x), resolution) + assert np.allclose(np.diff(y), -resolution) + # clip_box keeps pixels that merely touch the bounds, so pixel centers may + # lie up to one resolution step outside them. + assert abs(x.min() - xmin) <= resolution + assert abs(x.max() - xmax) <= resolution + assert abs(y.min() - ymin) <= resolution + assert abs(y.max() - ymax) <= resolution + + +def test_resample_region_masks(resampled): + """Land/maritime masks are 1 inside their regions and NaN elsewhere.""" + regions = resampled["regions"].values + land = resampled["regions_land"].values + maritime = resampled["regions_maritime"].values + # regions 0 and 1 are land, region 2 is maritime (fixture ordering) + assert (land[np.isin(regions, [0.0, 1.0])] == 1).all() + assert np.isnan(land[regions == 2.0]).all() + assert (maritime[regions == 2.0] == 1).all() + assert np.isnan(maritime[np.isin(regions, [0.0, 1.0])]).all() + + +def test_resample_reference_values(resampled, reference): + """Every resampled variable matches the committed reference data.""" + arrays = {name: resampled[name].values for name in sorted(resampled.data_vars)} + arrays["x"] = resampled.x.values + arrays["y"] = resampled.y.values + reference.check_arrays("resample_output", arrays) + + +# On-disk dtypes as written by the current pipeline (queried with +# mask_and_scale=False). Any change here is a deliberate storage change. +ON_DISK_DTYPES = { + "bathymetry": "float32", + "pixel_area": "float32", + "protected": "float32", + "regions": "float32", + "regions_land": "int8", + "regions_maritime": "int8", + "settlement_area": "float32", + "settlement_share": "float32", + "ship_travel": "float32", + "slope_deg": "float32", +} | { + f"landcover_{category}": "int8" + for category in set(fixtures.LAND_COVER_TYPES.values()) +} + + +def test_resample_on_disk_dtypes(resampled_path): + """Variables are stored on disk with the documented dtypes.""" + with xr.open_dataset(resampled_path, mask_and_scale=False) as ds: + actual = {name: str(ds[name].dtype) for name in ds.data_vars} + actual.pop("spatial_ref", None) + assert actual == ON_DISK_DTYPES + + +def test_resample_decoded_dtypes(resampled): + """No variable decodes as float64: masks are int8, everything else float32.""" + for name in set(resampled.data_vars) - {"spatial_ref"}: + expected = np.int8 if name.startswith("landcover_") else np.float32 + assert resampled[name].dtype == expected, name diff --git a/unit_tests/test_geo.py b/unit_tests/test_geo.py new file mode 100644 index 0000000..160b2af --- /dev/null +++ b/unit_tests/test_geo.py @@ -0,0 +1,57 @@ +"""Unit tests for the UTM buffering helpers in _geo.py.""" + +import geopandas as gpd +import numpy as np +import pyproj +import pytest +from _geo import apply_utm_buffer, get_utm_crs_from_lonlat +from shapely.geometry import box + +GEOD = pyproj.Geod(ellps="WGS84") + + +@pytest.mark.parametrize( + ("lon", "lat", "epsg"), + [ + (5.0, 52.0, 32631), + # Norway exception: zone 32 is widened at 56-64N, so 5E is zone 32, not 31 + (5.0, 60.0, 32632), + (5.0, -30.0, 32731), + (-70.0, 45.0, 32619), + ], +) +def test_get_utm_crs_from_lonlat(lon, lat, epsg): + """The UTM zone (including the Norway exception) is selected per centroid.""" + assert get_utm_crs_from_lonlat(lon, lat).to_epsg() == epsg + + +def _geodesic_area(geom): + """Unsigned geodesic area of a lon/lat geometry in m2.""" + area, _ = GEOD.geometry_area_perimeter(geom) + return abs(area) + + +def test_apply_utm_buffer_grows_geometries(): + """Buffered geometries contain the originals and grow by roughly the right area.""" + gdf = gpd.GeoDataFrame( + {"geometry": [box(5.0, 52.0, 5.1, 52.1), box(15.0, 48.0, 15.1, 48.1)]}, + crs="EPSG:4326", + ) + distance = 10_000 + buffered = apply_utm_buffer(gdf, buffer_distance_m=distance) + assert buffered.crs == gdf.crs + for original, result in zip(gdf.geometry, buffered.geometry): + assert result.contains(original) + _, perimeter = GEOD.geometry_area_perimeter(original) + expected_area = ( + _geodesic_area(original) + perimeter * distance + np.pi * distance**2 + ) + assert _geodesic_area(result) == pytest.approx(expected_area, rel=0.15) + + +def test_apply_utm_buffer_out_of_range_returns_none(): + """Geometries whose centroid has no UTM zone produce a warning and None.""" + gdf = gpd.GeoDataFrame({"geometry": [box(5.0, 85.0, 5.1, 85.1)]}, crs="EPSG:4326") + with pytest.warns(UserWarning, match="Failed to buffer"): + buffered = apply_utm_buffer(gdf) + assert buffered.geometry.isna().all() diff --git a/unit_tests/test_resample.py b/unit_tests/test_resample.py new file mode 100644 index 0000000..f7622cc --- /dev/null +++ b/unit_tests/test_resample.py @@ -0,0 +1,165 @@ +"""Unit tests for the pure functions in resample.py. + +These pin down current behaviour so that performance rewrites (land-cover +lookup table, vectorised pixel areas) can be verified to be output-preserving. +""" + +import fixtures +import geopandas as gpd +import numpy as np +import pyproj +import pytest +import xarray as xr +from resample import ( + GLOBCOVER_TYPES, + _rasterize_regions, + aggregate_land_cover_types, + determine_pixel_areas, +) +from shapely.geometry import box + + +def _landcover_da(values): + """Wrap a 2D uint8 array into a (band, y, x) DataArray like open_rasterio.""" + values = np.asarray(values, dtype=np.uint8) + height, width = values.shape + return xr.DataArray( + values[np.newaxis, ...], + coords={ + "band": [1], + "y": np.arange(height, dtype=float), + "x": np.arange(width, dtype=float), + }, + dims=("band", "y", "x"), + ) + + +def _expected_masks(values, mapping): + """Independent oracle: per-category membership masks via np.isin.""" + expected = {} + for category in sorted(set(mapping.values())): + codes = [ + code for code, name in GLOBCOVER_TYPES.items() if mapping[name] == category + ] + expected[category] = np.isin(values, codes).astype(np.int8) + return expected + + +def test_aggregate_land_cover_types_all_codes(): + """Every GlobCover code lands in exactly the category configured for it.""" + codes = np.array(sorted(GLOBCOVER_TYPES), dtype=np.uint8) + values = np.tile(codes, (3, 1)) + result = aggregate_land_cover_types( + _landcover_da(values), fixtures.LAND_COVER_TYPES + ) + expected = _expected_masks(values, fixtures.LAND_COVER_TYPES) + assert set(result.data_vars) == set(expected) + for category, mask in expected.items(): + np.testing.assert_array_equal(np.squeeze(result[category].values), mask) + assert result[category].dtype == np.int8 + + +def test_aggregate_land_cover_types_unmapped_code(): + """Codes not in GLOBCOVER_TYPES (here: 13) belong to no category.""" + values = np.array([[13, 11, 13], [13, 13, 210]], dtype=np.uint8) + result = aggregate_land_cover_types( + _landcover_da(values), fixtures.LAND_COVER_TYPES + ) + total = sum(np.squeeze(result[var].values) for var in result.data_vars) + np.testing.assert_array_equal(total, np.array([[0, 1, 0], [0, 0, 1]])) + + +def test_aggregate_land_cover_types_empty_category(): + """A configured category with no pixels yields an all-zero mask.""" + values = np.full((4, 4), 14, dtype=np.uint8) # RAINFED_CROPLANDS -> FARM + result = aggregate_land_cover_types( + _landcover_da(values), fixtures.LAND_COVER_TYPES + ) + assert np.squeeze(result["FARM"].values).all() + for category in set(fixtures.LAND_COVER_TYPES.values()) - {"FARM"}: + assert not result[category].values.any() + + +@pytest.mark.parametrize("seed", range(5)) +def test_aggregate_land_cover_types_randomized(seed): + """Random rasters (including unmapped codes) match the np.isin oracle.""" + rng = np.random.default_rng(seed) + codes = np.array(sorted(GLOBCOVER_TYPES) + [13, 255], dtype=np.uint8) + values = rng.choice(codes, size=(17, 23)) + result = aggregate_land_cover_types( + _landcover_da(values), fixtures.LAND_COVER_TYPES + ) + for category, mask in _expected_masks(values, fixtures.LAND_COVER_TYPES).items(): + np.testing.assert_array_equal(np.squeeze(result[category].values), mask) + + +def test_aggregate_land_cover_types_custom_mapping(): + """The mapping argument is respected, not just the internal defaults.""" + mapping = { + name: ("LOW" if code < 100 else "HIGH") + for code, name in GLOBCOVER_TYPES.items() + } + rng = np.random.default_rng(7) + values = rng.choice(np.array(sorted(GLOBCOVER_TYPES), dtype=np.uint8), size=(9, 9)) + result = aggregate_land_cover_types(_landcover_da(values), mapping) + assert set(result.data_vars) == {"LOW", "HIGH"} + for category, mask in _expected_masks(values, mapping).items(): + np.testing.assert_array_equal(np.squeeze(result[category].values), mask) + + +def _pixel_area_raster(resolution=0.5): + """A raster spanning 60N..60S used for pixel-area tests.""" + x = np.arange(0, 2, resolution) + resolution / 2 + y = np.arange(60, -60, -resolution) - resolution / 2 + values = np.ones((len(y), len(x)), dtype=np.int8) + return fixtures.make_raster(values, x, y) + + +def test_determine_pixel_areas_matches_geodesic(): + """Pixel areas agree with exact geodesic areas from pyproj at various latitudes.""" + resolution = 0.5 + raster = _pixel_area_raster(resolution) + pixel_area = determine_pixel_areas(raster) + geod = pyproj.Geod(ellps="WGS84") + for lat in [59.75, 45.25, 0.25, -45.25, -59.75]: + # Densify the cell outline: pyproj measures geodesic edges, while the + # pixel is bounded by parallels, which are not geodesics. + cell = box( + 0, lat - resolution / 2, resolution, lat + resolution / 2 + ).segmentize(resolution / 100) + expected_m2, _ = geod.geometry_area_perimeter(cell) + actual_m2 = float(pixel_area.sel(y=lat)) + assert actual_m2 == pytest.approx(abs(expected_m2), rel=1e-6) + + +def test_determine_pixel_areas_north_south_symmetric(): + """Pixel areas are symmetric about the equator.""" + pixel_area = determine_pixel_areas(_pixel_area_raster()) + north = pixel_area.sel(y=45.25) + south = pixel_area.sel(y=-45.25) + assert float(north) == pytest.approx(float(south), rel=1e-12) + + +def test_determine_pixel_areas_requires_wgs84(): + """Non-WGS84 rasters are rejected.""" + raster = _pixel_area_raster() + raster = raster.rio.write_crs("EPSG:3857") + with pytest.raises(AssertionError, match="EPSG:4326"): + determine_pixel_areas(raster) + + +def test_rasterize_regions(): + """Region indices are burned into the raster; uncovered pixels are NaN.""" + resolution = 0.1 + x = np.arange(0, 1, resolution) + resolution / 2 + y = np.arange(1, 0, -resolution) - resolution / 2 + reference = fixtures.make_raster(np.ones((len(y), len(x)), dtype=np.int8), x, y) + shapes = gpd.GeoDataFrame( + {"geometry": [box(0, 0, 0.4, 1), box(0.6, 0, 1, 1)]}, crs="EPSG:4326" + ) + result = _rasterize_regions(shapes, reference) + assert result.dtype == np.float32 + assert result.shape == (10, 10) + np.testing.assert_array_equal(result[:, :4], 0.0) + np.testing.assert_array_equal(result[:, 6:], 1.0) + assert np.isnan(result[:, 4:6]).all() diff --git a/workflow/Snakefile b/workflow/Snakefile index da99a40..ecbb410 100644 --- a/workflow/Snakefile +++ b/workflow/Snakefile @@ -30,7 +30,7 @@ with open(workflow.source_path("internal/settings.yaml"), "r") as f: # Python files that are imported from other scripts and need to be included when accessing the module workflow.source_path("scripts/_geo.py") workflow.source_path("scripts/_schemas.py") -workflow.source_path("scripts/_script_utils.py") +workflow.source_path("scripts/_plots.py") wildcard_constraints: diff --git a/workflow/rules/automatic.smk b/workflow/rules/automatic.smk index c81ce06..479d6a1 100644 --- a/workflow/rules/automatic.smk +++ b/workflow/rules/automatic.smk @@ -179,7 +179,8 @@ rule download_ship_travel: "Download Global Ship Density for all vessel types." shell: """ - curl -sSLo {output:q} {params.url:q} >{log:q} 2>&1 + # The World Bank host resets HTTP/2 streams on large transfers (curl exit 92) + curl --http1.1 --retry 3 -sSLo {output:q} {params.url:q} >{log:q} 2>&1 """ @@ -284,8 +285,12 @@ rule rasterise_clip_wdpa: "/automatic/cutout/{shape}/wdpa.tif", log: "/{shape}/clip_wdpa.log", + benchmark: + "/{shape}/clip_wdpa.benchmark.tsv" conda: "../envs/module.yaml" + resources: + mem_mb=3000, message: "Rasterise and cut WDPA data to the bounds of the input shapefile, using the landcover raster as reference for the rasterisation." shell: diff --git a/workflow/rules/functions.smk b/workflow/rules/functions.smk index d15c26b..f0c9122 100644 --- a/workflow/rules/functions.smk +++ b/workflow/rules/functions.smk @@ -13,10 +13,35 @@ def get_techs(wildcards): def uses_ship_travel(wildcards): - """Return whether a base tech or this subunit's overrides use ship travel.""" - tech_configs = list(config.get("techs", {}).values()) - tech_configs.extend(config.get("overrides", {}).get(wildcards.subunit, {}).values()) + """Return whether any scenario's techs or this subunit's overrides use ship travel.""" + tech_configs = [] + for scenario in config["scenarios"].values(): + tech_configs.extend(scenario["techs"].values()) + tech_configs.extend( + scenario.get("overrides", {}).get(wildcards.subunit, {}).values() + ) return any( "ship_travel" in tech_config.get("continuous_layers", {}) for tech_config in tech_configs ) + + +def get_subunit_input_plots(wildcards): + checkpoint_output = checkpoints.breakup_shape.get(**wildcards).output[0] + return expand( + "/automatic/resampled_inputs/{{shape}}/{subunit}.png", + subunit=glob_wildcards( + os.path.join(checkpoint_output, "{subunit}.parquet") + ).subunit, + ) + + +def get_subunit_potential_plots(wildcards): + checkpoint_output = checkpoints.breakup_shape.get(**wildcards).output[0] + return expand( + "/{{shape}}/{{scenario}}/{subunit}/area_potential_{tech}.png", + subunit=glob_wildcards( + os.path.join(checkpoint_output, "{subunit}.parquet") + ).subunit, + tech=get_techs(wildcards), + ) diff --git a/workflow/rules/process.smk b/workflow/rules/process.smk index 8905622..2820c0c 100644 --- a/workflow/rules/process.smk +++ b/workflow/rules/process.smk @@ -1,3 +1,9 @@ +# Note: memory reservations (mem_mb) are sized for country-sized subunits of ~150 +# million reference pixels (this covers e.g. Norway including its EEZ). +# They only constrain scheduling when snakemake is given a limit: +# `snakemake --resources mem_mb=` +# Override per rule with `--set-resources` if needed. + import shlex @@ -17,14 +23,15 @@ rule prepare_resampled_inputs: ), output: resampled_input="/automatic/resampled_inputs/{shape}/{subunit}.nc", - plot=report( - "/automatic/resampled_inputs/{shape}/{subunit}.png", - category="resampled_input", - ), log: "/{shape}/{subunit}/prepare_resampled_inputs.log", + benchmark: + "/{shape}/{subunit}/prepare_resampled_inputs.benchmark.tsv" conda: "../envs/module.yaml" + threads: 4 + resources: + mem_mb=4500, params: # Use internal defaults if not overridden land_cover_types_yaml_string=internal["land_cover_types"] @@ -42,11 +49,33 @@ rule prepare_resampled_inputs: "{input.shapes}/{wildcards.subunit}.parquet" \ {input.land_cover_path:q} {input.slope_path:q} {input.settlement_path:q} {input.bathymetry_path:q} {input.protected_area_path:q} \ {params.land_cover_types_yaml_string:q} \ - {output.resampled_input:q} {output.plot:q} \ - {params.ship_travel_arg} >{log:q} 2>&1 + {output.resampled_input:q} \ + --num-threads {threads} {params.ship_travel_arg} >{log:q} 2>&1 """ +rule plot_resampled_inputs: + input: + rules.prepare_resampled_inputs.output.resampled_input, + output: + report( + "/automatic/resampled_inputs/{shape}/{subunit}.png", + category="resampled_input", + ), + log: + "/{shape}/{subunit}/plot_resampled_inputs.log", + benchmark: + "/{shape}/{subunit}/plot_resampled_inputs.benchmark.tsv" + conda: + "../envs/module.yaml" + resources: + mem_mb=3000, + message: + "Plot resampled inputs for {wildcards.subunit} in {wildcards.shape}." + script: + "../scripts/nc_to_png.py" + + rule area_potential: input: script=workflow.source_path("../scripts/area_potential.py"), @@ -54,20 +83,22 @@ rule area_potential: resampled_path=rules.prepare_resampled_inputs.output.resampled_input, output: area_potential="/{shape}/{scenario}/{subunit}/area_potential_{tech}.tif", - plot=report( - "/{shape}/{scenario}/{subunit}/area_potential_{tech}.png", - category="area_potential", - ), log: "/{shape}/{scenario}/{subunit}/area_potential_{tech}.log", + benchmark: + "/{shape}/{scenario}/{subunit}/area_potential_{tech}.benchmark.tsv" conda: "../envs/module.yaml" + resources: + mem_mb=4200, params: config=lambda wildcards: config["scenarios"][f"{wildcards.scenario}"]["techs"][ f"{wildcards.tech}" ], - subunit_override_config=lambda wildcards: config.get("overrides", {}) - .get(wildcards.scenario, {}) + subunit_override_config=lambda wildcards: config["scenarios"][ + wildcards.scenario + ] + .get("overrides", {}) .get(wildcards.subunit, {}) .get(wildcards.tech, {}), buffer_crs=lambda wildcards: config["buffer_crs"], @@ -75,10 +106,30 @@ rule area_potential: "Compute area potential for the scenario {wildcards.scenario}, the tech {wildcards.tech} and {wildcards.subunit} in {wildcards.shape}." shell: """ - python {input.script:q} "{input.shapes}/{wildcards.subunit}.parquet" {input.resampled_path:q} {params.config:q} {params.buffer_crs:q} {output.area_potential:q} {output.plot:q} --override_config={params.subunit_override_config:q} >{log:q} 2>&1 + python {input.script:q} "{input.shapes}/{wildcards.subunit}.parquet" {input.resampled_path:q} {params.config:q} {params.buffer_crs:q} {output.area_potential:q} --override_config={params.subunit_override_config:q} >{log:q} 2>&1 """ +rule plot_area_potential: + input: + rules.area_potential.output.area_potential, + output: + report( + "/{shape}/{scenario}/{subunit}/area_potential_{tech}.png", + category="area_potential", + ), + log: + "/{shape}/{scenario}/{subunit}/plot_area_potential_{tech}.log", + conda: + "../envs/module.yaml" + resources: + mem_mb=600, + message: + "Plot area potential for the tech {wildcards.tech} and {wildcards.subunit} in {wildcards.shape}." + script: + "../scripts/tif_to_png.py" + + rule aggregate_area_potential: input: get_subunits, @@ -86,13 +137,19 @@ rule aggregate_area_potential: aggregated_area_potential="", log: "/{shape}/{scenario}/aggregate_area_potential_{tech}.log", + benchmark: + "/{shape}/{scenario}/aggregate_area_potential_{tech}.benchmark.tsv" conda: "../envs/module.yaml" + threads: 4 + resources: + # gdalwarp is allowed up to 3 GB of cache and warp memory below + mem_mb=3500, message: "Aggregate area potential for the scenario {wildcards.scenario} and the tech {wildcards.tech} in {wildcards.shape}." shell: """ - gdalwarp --config GDAL_CACHEMAX 3000 -wm 3000 -of GTiff -co COMPRESS=LZW {input} {output.aggregated_area_potential:q} >{log:q} 2>&1 + gdalwarp --config GDAL_CACHEMAX 3000 -wm 3000 -multi -wo NUM_THREADS={threads} -of GTiff -co COMPRESS=LZW -co PREDICTOR=3 {input} {output.aggregated_area_potential:q} >{log:q} 2>&1 """ @@ -108,6 +165,8 @@ rule plot_aggregated_area_potential: "/{shape}/{scenario}/plot_aggregated_area_potential_{tech}.log", conda: "../envs/module.yaml" + resources: + mem_mb=600, message: "Plot aggregated area potential for the scenario {wildcards.scenario} and the tech {wildcards.tech} in {wildcards.shape}." script: @@ -126,6 +185,10 @@ rule area_potential_report: "/{{shape}}/{{scenario}}/area_potential_{tech}.png", tech=get_techs, ), + # Not used by the report itself: pulls in the per-subunit diagnostic + # plots, which run in parallel jobs off the area_potential critical path + resampled_input_plots=get_subunit_input_plots, + subunit_potential_plots=get_subunit_potential_plots, output: csv="/{shape}/{scenario}/area_potential_report.csv", html=report( @@ -134,8 +197,12 @@ rule area_potential_report: ), log: "/{shape}/{scenario}/area_potential_report.log", + benchmark: + "/{shape}/{scenario}/area_potential_report.benchmark.tsv" conda: "../envs/module.yaml" + resources: + mem_mb=4000, message: "Generate an overview report of the area potential for scenario {wildcards.scenario} for all techs in shapes {wildcards.shape}." script: diff --git a/workflow/scripts/_geo.py b/workflow/scripts/_geo.py index 654d3a1..0750ffe 100644 --- a/workflow/scripts/_geo.py +++ b/workflow/scripts/_geo.py @@ -2,7 +2,6 @@ import warnings -import geopandas as gpd import utm from pyproj import CRS @@ -24,44 +23,13 @@ def get_utm_crs_from_lonlat(lon, lat): return CRS.from_epsg(epsg_code) -def utm_buffer(geom, buffer_distance_m=10000, source_crs="EPSG:4326"): - """Project a geom to UTM, buffer it, then re-project to its source CRS. - - Args: - geom (shapely.geometry): The geometry to buffer, in the given source_crs. - buffer_distance_m (int): The buffer distance in meters (default is 10,000 m). - source_crs (str): The source CRS of the geometry (default is "EPSG:4326"). - - Returns: - shapely.geometry: The buffered geometry in its original CRS. - - """ - try: - centroid = geom.centroid - lon, lat = centroid.x, centroid.y - local_crs = get_utm_crs_from_lonlat(lon, lat) - - # Project to local UTM CRS - gdf_single = gpd.GeoDataFrame(geometry=[geom], crs=source_crs) - gdf_utm = gdf_single.to_crs(local_crs) - - # Buffer in meters - gdf_utm["geometry"] = gdf_utm.buffer(buffer_distance_m) - - # Reproject back to WGS84 - gdf_buffered = gdf_utm.to_crs(source_crs) - return gdf_buffered.iloc[0].geometry - - except Exception as e: - warnings.warn(f"Failed to buffer geometry: {e}") - return None - - def apply_utm_buffer(gdf, buffer_distance_m=10000): """Apply a UTM-based buffer to a GeoDataFrame with an arbitrary CRS. - The buffering will be performed row-by-row using the most appropriate UTM zone for - each geometry's centroid. + The most appropriate UTM zone is chosen per geometry from its centroid. + Geometries are then grouped by UTM zone, and each group is projected, buffered + and re-projected in one vectorised operation. Geometries that cannot be buffered + (e.g. centroid outside the UTM latitude range) produce a warning and None. Args: gdf (geopandas.GeoDataFrame): The GeoDataFrame containing geometries to buffer. @@ -73,7 +41,30 @@ def apply_utm_buffer(gdf, buffer_distance_m=10000): """ source_crs = gdf.crs gdf_buffered = gdf.copy() - gdf_buffered["geometry"] = gdf_buffered["geometry"].apply( - lambda geom: utm_buffer(geom, buffer_distance_m, source_crs) - ) + + zone_indices = {} + for index, geom in gdf_buffered["geometry"].items(): + try: + centroid = geom.centroid + local_crs = get_utm_crs_from_lonlat(centroid.x, centroid.y) + except Exception as e: + warnings.warn(f"Failed to buffer geometry: {e}") + gdf_buffered.loc[index, "geometry"] = None + continue + zone_indices.setdefault(local_crs, []).append(index) + + for local_crs, indices in zone_indices.items(): + try: + buffered = ( + gdf_buffered.loc[indices, "geometry"] + .set_crs(source_crs, allow_override=True) + .to_crs(local_crs) + .buffer(buffer_distance_m) + .to_crs(source_crs) + ) + except Exception as e: + warnings.warn(f"Failed to buffer geometry: {e}") + buffered = None + gdf_buffered.loc[indices, "geometry"] = buffered + return gdf_buffered diff --git a/workflow/scripts/_script_utils.py b/workflow/scripts/_plots.py similarity index 82% rename from workflow/scripts/_script_utils.py rename to workflow/scripts/_plots.py index b05ed67..a13cf89 100644 --- a/workflow/scripts/_script_utils.py +++ b/workflow/scripts/_plots.py @@ -1,4 +1,4 @@ -"""Utility functions.""" +"""Plotting helpers shared by the plotting scripts.""" import math @@ -29,8 +29,9 @@ def random_categorical_cmap(n, base_cmap="tab20", seed=42): def plot_with_zero_separate(ax, da, cmap="viridis", zero_color="#e0e0e0"): """Plot data array with zero values in a separate color.""" - da.where(da != 0).plot(ax=ax, cmap=cmap) - da.where(da == 0).plot( + da = da.squeeze() # imshow needs 2D data, so size-1 dims (band) are squeezed + da.where(da != 0).plot.imshow(ax=ax, cmap=cmap) + da.where(da == 0).plot.imshow( ax=ax, cmap=mcolors.ListedColormap([zero_color]), add_colorbar=False ) return ax @@ -38,8 +39,9 @@ def plot_with_zero_separate(ax, da, cmap="viridis", zero_color="#e0e0e0"): def plot_all_dataset_variables(ds, ncols=2, savefig=None, categorical_vars=[]): """Plot all variables in an xarray dataset on a grid of plots.""" - # If needed, resample `ds` to fit within a maximum of `max_pixels` pixels - max_pixels = 5000000 + # If needed, resample `ds` to fit within a maximum of `max_pixels` pixels. + # 2 million pixels per panel is sufficient for our targeted plot sizes + max_pixels = 2000000 total_pixels = ds.sizes["y"] * ds.sizes["x"] if total_pixels > max_pixels: # Calculate the new resolution to fit within the max_pixels limit @@ -82,9 +84,12 @@ def plot_all_dataset_variables(ds, ncols=2, savefig=None, categorical_vars=[]): for j in range(i + 1, len(axes)): axes[j].set_visible(False) - plt.tight_layout() + fig.tight_layout() if savefig: - plt.savefig(savefig, dpi=300, bbox_inches="tight") + # This call contains two optimisations for speed: + # 1. fig.savefig (rather than plt.savefig) disables pyplot's post-save re-render. + # 2. Omitting bbox_inches="tight" skips a measurement pre-render step. + fig.savefig(savefig, dpi=300) return fig diff --git a/workflow/scripts/area_potential.py b/workflow/scripts/area_potential.py index 31da213..1ff59a6 100644 --- a/workflow/scripts/area_potential.py +++ b/workflow/scripts/area_potential.py @@ -4,10 +4,8 @@ import click import geopandas as gpd import glom -import matplotlib.pyplot as plt import xarray as xr import yaml -from _script_utils import plot_with_zero_separate @click.command() @@ -16,16 +14,9 @@ @click.argument("config", type=str) @click.argument("buffer_crs", type=str) @click.argument("output_path", type=str) -@click.argument("plot_path", type=str) @click.option("--override_config", type=str) def get_area_potential( - shapes_path, - resampled_path, - config, - buffer_crs, - output_path, - plot_path, - override_config, + shapes_path, resampled_path, config, buffer_crs, output_path, override_config ): """Calculate the area potential based on the provided configuration. @@ -35,7 +26,6 @@ def get_area_potential( config (str): Configuration YAML string. buffer_crs (str): Coordinate Reference System for buffering shapes. output_path (str): Path to save the resulting area potential raster. - plot_path (str): Path to save the plot of the area potential. override_config (str): Configuration override YAML string. Returns: @@ -43,7 +33,9 @@ def get_area_potential( """ shapes = gpd.read_parquet(shapes_path) - ds = xr.open_dataset(resampled_path, decode_coords="all") + # cache=False: layers are read when used and released afterwards instead + # of every touched layer staying resident for the whole run. + ds = xr.open_dataset(resampled_path, decode_coords="all", cache=False) # NOTE: this is a workaround for the CRS not being set correctly, ideally this # should not be necessary ds.rio.write_crs(ds.spatial_ref.attrs["crs_wkt"], inplace=True) @@ -56,30 +48,42 @@ def get_area_potential( # Start with the configured pixel area as a base potential_da = ds[config["initial_area"]].squeeze(drop=True) # Drop `band` - # Zero out pixels from binary layers with share 0 from potential_da + # `keep` is a boolean "to keep"-mask used to accumulate all "zero-out" criteria, which + # can then be applied in a single .where() call at the very end + keep = None + + def _all_of(mask, condition): + return condition if mask is None else mask & condition + + # Zero out pixels from binary layers with share 0 binary_layers = config.get("binary_layers", {}) - zero_binary_layers = [layer for layer, value in binary_layers.items() if value == 0] - for layer in zero_binary_layers: + for layer, value in binary_layers.items(): + if value != 0: + continue if layer in ds: - potential_da = potential_da.where(~(ds[layer] > 0), other=0) + keep = _all_of(keep, ~(ds[layer] > 0)) else: print(f"Warning: Layer '{layer}' not found in dataset. Skipping.") - # Apply the continuous_layers criteria to zero out additional pixels + # Zero out pixels outside the min-max criteria of the continuous layers continuous_layers = config.get("continuous_layers", {}) for layer, layer_config in continuous_layers.items(): if layer in ds: - # Apply the min-max criteria - potential_da = potential_da.where( + keep = _all_of( + keep, (ds[layer] <= layer_config["max"]) & (ds[layer] >= layer_config["min"]), - other=0, ) - # If a share is defined, multiply the pixel area by the share - if "share" in layer_config: - potential_da = potential_da * layer_config["share"] else: print(f"Warning: Layer '{layer}' not found in dataset. Skipping.") + if keep is not None: + potential_da = potential_da.where(keep, other=0) + + # If a share is defined for a continuous layer, multiply the pixel area by the share + for layer, layer_config in continuous_layers.items(): + if layer in ds and "share" in layer_config: + potential_da = potential_da * layer_config["share"] + # Multiply pixels by their share from the binary layers for layer, value in binary_layers.items(): if layer in ds: @@ -102,27 +106,26 @@ def get_area_potential( else: buffer = shapes_subset.to_crs(buffer_crs).buffer(buffer_distance) - # Clip the potential area with the buffered shapes + # Clip the potential area with the buffered shapes. drop=False keeps the + # output grid identical across techs - clipped-out pixels become nodata. potential_da.rio.write_crs(ds.rio.crs, inplace=True) buffer_geo = gpd.GeoDataFrame(geometry=buffer).to_crs(ds.rio.crs) potential_da = potential_da.rio.clip( - buffer_geo.geometry, buffer_geo.crs, invert=True + buffer_geo.geometry, buffer_geo.crs, invert=True, drop=False ) potential_da.name = "area_potential" potential_da = potential_da.transpose("band", "y", "x") potential_da.rio.write_crs(ds.rio.crs, inplace=True) - fig, ax = plt.subplots(1, 1) - ax = plot_with_zero_separate(ax=ax, da=potential_da) - plt.savefig(plot_path, bbox_inches="tight") - - # Fill NaN with a nodata value only after plotting + # Fill NaN with a nodata value and coerce to float32, which halves the file size + # while keeping the per-pixel error below 0.01 nodata_value = -1 - potential_da = potential_da.fillna(nodata_value) + potential_da = potential_da.fillna(nodata_value).astype("float32", copy=False) potential_da.rio.write_nodata(nodata_value, inplace=True) + # PREDICTOR=3 improves LZW compression of float data potential_da.rio.to_raster( - output_path, driver="GTiff", compress="LZW", write_nodata=True + output_path, driver="GTiff", compress="LZW", predictor=3, write_nodata=True ) diff --git a/workflow/scripts/breakup_shape.py b/workflow/scripts/breakup_shape.py index 615eb4a..09731a3 100644 --- a/workflow/scripts/breakup_shape.py +++ b/workflow/scripts/breakup_shape.py @@ -18,7 +18,6 @@ def breakup_shape(shapes_path, split_by, output_path): shapes_path (str): Path to the input shapes in the parquet format. split_by (str): Column on which to split the shapes. output_path (str): Path to save the resulting broken-up shapes. - output_list_of_subunits (str): Path to save the list of subunits. Returns: None @@ -28,12 +27,6 @@ def breakup_shape(shapes_path, split_by, output_path): shapes = gpd.read_parquet(shapes_path) shapes = ShapesSchema.validate(shapes) - # Print rows where geometry is empty - if shapes.geometry.is_empty.any(): - print("Warning: The following rows have empty geometries and will be removed:") - print(shapes[shapes.geometry.is_empty]) - shapes = shapes[~shapes.geometry.is_empty] - if split_by == "none": subunits = ["all"] shapes.to_parquet(Path(output_path / "all.parquet")) diff --git a/workflow/scripts/clip_and_rasterise_polys.py b/workflow/scripts/clip_and_rasterise_polys.py index 448516e..b880764 100644 --- a/workflow/scripts/clip_and_rasterise_polys.py +++ b/workflow/scripts/clip_and_rasterise_polys.py @@ -1,8 +1,82 @@ -"""Subset to a bounding box and rasterise polygons.""" +"""Rasterise protected-area polygons onto the grid of a reference raster.""" import click -import geopandas as gpd +import numpy as np +import pyogrio +import pyproj import rioxarray as rxr +import xarray as xr +from osgeo import gdal, gdal_array, ogr, osr +from rasterio.warp import transform_bounds + +gdal.UseExceptions() +ogr.UseExceptions() +osr.UseExceptions() + +# Features per Arrow batch read from the vector source; each batch is burned +# and released before the next one is read. +BATCH_SIZE = 500 + + +def rasterise_polygons(polygons_path, reference_raster, batch_size=BATCH_SIZE): + """Burn the polygons intersecting the reference grid into a boolean mask. + + This function streams the geometry of features intersecting the reference_raster's + bounds in Arrow batches. It turns each batch into OGR geometries and burns these + (via GDAL) into a raster that wraps a NumPy mask. By implementing this directly and + sidestepping rasterio's `rasterize`, memory use and computation time are greatly + reduced. Polygons in a different CRS are reprojected on the fly. + + """ + raster_crs = pyproj.CRS.from_user_input(reference_raster.rio.crs) + layer_crs = pyproj.CRS.from_user_input(pyogrio.read_info(polygons_path)["crs"]) + bbox = transform_bounds( + raster_crs, layer_crs, *reference_raster.rio.bounds(), densify_pts=21 + ) + + mask = np.zeros(reference_raster.rio.shape, dtype=np.uint8) + target = gdal_array.OpenNumPyArray(mask, True) + target.SetGeoTransform(reference_raster.rio.transform().to_gdal()) + target.SetProjection(raster_crs.to_wkt()) + + def srs(crs): + result = osr.SpatialReference() + result.ImportFromWkt(crs.to_wkt()) + result.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + return result + + layer_srs, raster_srs = srs(layer_crs), srs(raster_crs) + reprojection = ( + None + if layer_crs.equals(raster_crs) + else osr.CoordinateTransformation(layer_srs, raster_srs) + ) + + n_features = 0 + with pyogrio.open_arrow( + polygons_path, bbox=bbox, columns=[], use_pyarrow=True, batch_size=batch_size + ) as (meta, reader): + for batch in reader: + wkbs = batch.column(meta["geometry_name"] or "wkb_geometry").to_pylist() + source = ogr.GetDriverByName("Memory").CreateDataSource("batch") + layer = source.CreateLayer("batch", raster_srs, ogr.wkbUnknown) + definition = layer.GetLayerDefn() + for wkb in wkbs: + if wkb is None: + continue + geometry = ogr.CreateGeometryFromWkb(wkb) + if reprojection is not None: + geometry.Transform(reprojection) + feature = ogr.Feature(definition) + feature.SetGeometry(geometry) + layer.CreateFeature(feature) + n_features += 1 + gdal.RasterizeLayer(target, [1], layer, burn_values=[1]) + del layer, source, wkbs + target.FlushCache() + del target + print(f"Protected areas intersecting the reference raster: {n_features}") + return mask.astype(bool) @click.command() @@ -13,21 +87,18 @@ def clip_and_rasterise_polys( shapes_path, reference_raster_path, protected_area_path, output_path ): - """Clip the polygons in SHAPES_PATH to the bounding box of the reference raster, and save the clipped polygons as a raster to OUTPUT_PATH.""" - shapes = gpd.read_parquet(shapes_path) + """Rasterise the polygons in PROTECTED_AREA_PATH onto reference raster grid. + + A 0/1 uint8 raster on the full reference grid (1 = inside a polygon) is saved to + OUTPUT_PATH. SHAPES_PATH is accepted for interface compatibility but not used. + """ reference_raster = rxr.open_rasterio(reference_raster_path) # FIXME: read the right layer(s) and deal with both poly and point layers - xmin, ymin, xmax, ymax = shapes.total_bounds - protected_areas = gpd.read_file(protected_area_path) - print(f"Protected areas: {len(protected_areas)}") - protected_areas = protected_areas.to_crs(shapes.crs) - protected_areas = protected_areas.cx[xmin:xmax, ymin:ymax] - print(f"Protected areas after applying total_bounds: {len(protected_areas)}") - - protected_raster = reference_raster.rio.clip( - protected_areas.geometry, protected_areas.crs - ) + mask = rasterise_polygons(protected_area_path, reference_raster) + protected_raster = xr.zeros_like(reference_raster, dtype=np.uint8) + protected_raster.data[0] = mask + protected_raster.rio.write_nodata(None, inplace=True) protected_raster.rio.to_raster(output_path, driver="GTiff", compress="LZW") diff --git a/workflow/scripts/nc_to_png.py b/workflow/scripts/nc_to_png.py new file mode 100644 index 0000000..897e695 --- /dev/null +++ b/workflow/scripts/nc_to_png.py @@ -0,0 +1,19 @@ +"""This script plots all variables of a NetCDF file to PNG format.""" + +import sys + +import xarray as xr +from _plots import plot_all_dataset_variables + + +def nc_to_png(nc_file_in, png_file_out): + """Plot all variables of a NetCDF file on a grid of panels.""" + # `plot_all_dataset_variables` reads one variable at a time, so setting + # cache=False here ensures that only one layer is in memory at a time. + with xr.open_dataset(nc_file_in, decode_coords="all", cache=False) as ds: + plot_all_dataset_variables(ds, ncols=3, savefig=png_file_out) + + +if __name__ == "__main__": + sys.stderr = open(snakemake.log[0], "w", buffering=1) + nc_to_png(snakemake.input[0], snakemake.output[0]) diff --git a/workflow/scripts/report.py b/workflow/scripts/report.py index f551b3c..2154ecc 100644 --- a/workflow/scripts/report.py +++ b/workflow/scripts/report.py @@ -3,44 +3,85 @@ import sys import geopandas as gpd +import numpy as np import pandas as pd -import rioxarray as rxr -import xarray as xr -from resample import _rasterize_regions +import rasterio +import rasterio.windows +from rasterio.features import rasterize + +# Rows per processing window, which are full-width strips with the number of rows +# defined here. The purpose of this is to bound memory use by iteratively aggregating +# summary data one window at a time. +ROWS_PER_WINDOW = 1024 def report(shapes, area_potentials, csv_path, html_path): """Generate a report summarizing area potentials for different technologies.""" shapes = gpd.read_parquet(shapes) - print("Generating reference raster and rasterizing regions...") - reference_raster = rxr.open_rasterio(area_potentials[0]) - regions = xr.DataArray( - _rasterize_regions(shapes, reference_raster), - dims=("y", "x"), - coords={"y": reference_raster.y, "x": reference_raster.x}, - ) - # regions = xr.DataArray(("y", "x"), _rasterize_regions(shapes, reference_raster)) - del reference_raster + with rasterio.open(area_potentials[0]) as src: + reference_shape = (src.height, src.width) + reference_transform = src.transform + windows = [ + rasterio.windows.Window( + 0, row, src.width, min(ROWS_PER_WINDOW, src.height - row) + ) + for row in range(0, src.height, ROWS_PER_WINDOW) + ] - # Collect the area potentials from the input files - # Group the area potentials by regions, sum them up, and collect the resulting Series - # into a DataFrame, where each column corresponds to a technology's area potential, - # and the index corresponds to the regions. - dataframes = [] - for area_potential_file in area_potentials: - print(f"Processing area potential file: {area_potential_file}") - da_area_potential = ( - rxr.open_rasterio(area_potential_file, mask_and_scale=True) - .squeeze() - .drop_vars(["band", "spatial_ref"]) + # Sums (and pixel counts) per region are accumulated strip by strip: the + # region index is burned per strip and each technology mosaic is read per + # strip, so memory stays bounded by a strip instead of several full-size + # copies of a country-group mosaic. (Rasters small enough for one strip + # give bit-identical sums to a whole-raster aggregation.) + sums = {path: np.zeros(len(shapes)) for path in area_potentials} + pixel_counts = np.zeros(len(shapes), dtype=np.int64) + sources = [rasterio.open(path) for path in area_potentials] + try: + for src in sources: + # All technology mosaics must lie on the same grid, otherwise the + # region index would silently aggregate the wrong pixels. + assert (src.height, src.width) == reference_shape, ( + f"Raster shape of {src.name} does not match {area_potentials[0]}" + ) + assert src.transform == reference_transform, ( + f"Raster transform of {src.name} does not match {area_potentials[0]}" + ) + print( + f"Aggregating {len(area_potentials)} rasters over {len(windows)} windows..." ) - df_ = da_area_potential.groupby(regions).sum().to_pandas() - df_.name = area_potential_file - dataframes.append(df_) - del da_area_potential + for window in windows: + # Burn each shape's row position (-1 = no region) for this window + region_ids = rasterize( + zip(shapes.geometry, range(len(shapes))), + out_shape=(window.height, window.width), + transform=rasterio.windows.transform(window, reference_transform), + fill=-1, + dtype=np.int32, + ).ravel() + valid = region_ids >= 0 + if not valid.any(): + continue + region_ids = region_ids[valid] + pixel_counts += np.bincount(region_ids, minlength=len(shapes)) + for path, src in zip(area_potentials, sources): + values = src.read(1, window=window, masked=True).ravel()[valid] + # nodata/NaN pixels contribute nothing (as the groupby-sum did) + weights = np.nan_to_num(values.filled(np.nan)) + sums[path] += np.bincount( + region_ids, weights=weights, minlength=len(shapes) + ) + finally: + for src in sources: + src.close() + columns = sums - df = pd.concat(dataframes, axis=1) + # Drop shapes that received no pixel at all (smaller than the grid can + # resolve). Bincount reports them as 0.0, which would read as "no + # potential", when in fact nothing is known about them. + df = pd.DataFrame(columns, index=shapes.index.astype(float)) + df = df[pixel_counts > 0].sort_index() + df.index.name = "group" # Add metadata columns from shapes in front of the data columns df.insert(0, "parent_name", shapes["parent_name"]) diff --git a/workflow/scripts/resample.py b/workflow/scripts/resample.py index 3abdec0..7ba9303 100644 --- a/workflow/scripts/resample.py +++ b/workflow/scripts/resample.py @@ -1,16 +1,19 @@ """This script resamples various geospatial datasets to a common shape and resolution.""" +import gc import math -import _script_utils import click import geopandas as gpd import numpy as np +import rasterio import rioxarray as rxr import xarray as xr import yaml from rasterio.enums import Resampling from rasterio.features import rasterize +from rasterio.warp import reproject +from rasterio.windows import Window, from_bounds # LAND COVER # Original classification categories taken from GlobCover 2009 land cover. @@ -44,23 +47,44 @@ } -def aggregate_land_cover_types(ds_land_cover, land_cover_types): - """Convert raw GlobCover data to a dataset with suitable land cover types.""" - suitable_land_cover = xr.Dataset(coords=ds_land_cover.coords) +def land_cover_category_ids(ds_land_cover, land_cover_types): + """Map GlobCover codes to small category ids in one vectorised pass. + + Returns ``(mapped, category_ids)``: + `mapped`: a uint8 array shaped like ds_land_cover, holding the `id` of + each pixel's category + `category_ids`: a dict mapping `category` to `id` - # convert the input value to land cover type of interest - for value in np.unique(ds_land_cover.data): - if value in GLOBCOVER_TYPES: - ds_land_cover = ds_land_cover.where( - ds_land_cover != value, - other=land_cover_types[GLOBCOVER_TYPES[value]], - drop=False, - ) + Codes without a category (not in GLOBCOVER_TYPES) map to the sentinel 0 + and belong to no category. - # check if each pixel is in the list of suitable land cover types - for type_ in sorted(list(set(land_cover_types.values()))): - suitable_land_cover[type_] = (ds_land_cover == type_).astype(np.byte) + """ + data = ds_land_cover.data + categories = sorted(set(land_cover_types.values())) + category_ids = {category: i for i, category in enumerate(categories, start=1)} + # Size the table from the dtype rather than data.max(): the latter would + # load and scan the whole raster just to build a 256-entry table. + lut = np.zeros(np.iinfo(data.dtype).max + 1, dtype=np.uint8) + for code, name in GLOBCOVER_TYPES.items(): + lut[code] = category_ids[land_cover_types[name]] + return lut[data], category_ids + + +def land_cover_mask(ds_land_cover, mapped, category_id): + """Return the 0/1 int8 membership mask of one land cover category.""" + return xr.DataArray( + (mapped == category_id).astype(np.byte), + dims=ds_land_cover.dims, + coords=ds_land_cover.coords, + ) + +def aggregate_land_cover_types(ds_land_cover, land_cover_types): + """Turn a GlobCover class raster into a Dataset of per-category membership masks.""" + mapped, category_ids = land_cover_category_ids(ds_land_cover, land_cover_types) + suitable_land_cover = xr.Dataset(coords=ds_land_cover.coords) + for type_, category_id in category_ids.items(): + suitable_land_cover[type_] = land_cover_mask(ds_land_cover, mapped, category_id) return suitable_land_cover @@ -71,9 +95,10 @@ def _area_of_pixel(pixel_size, center_lat): Parameters: pixel_size (float): length of side of pixel in degrees. - center_lat (float): latitude of the center of the pixel. Note this - value +/- half the `pixel-size` must not exceed 90/-90 degrees - latitude or an invalid area will be calculated. + center_lat (float or np.ndarray): latitude of the center of the pixel + (scalar or array). Note this value +/- half the `pixel-size` must + not exceed 90/-90 degrees latitude or an invalid area will be + calculated. Returns: Area of square pixel of side length `pixel_size` centered at @@ -83,16 +108,17 @@ def _area_of_pixel(pixel_size, center_lat): a = 6378137 # meters b = 6356752.3142 # meters e = math.sqrt(1 - (b / a) ** 2) - area_list = [] - for f in [center_lat + pixel_size / 2, center_lat - pixel_size / 2]: - zm = 1 - e * math.sin(math.radians(f)) - zp = 1 + e * math.sin(math.radians(f)) - area_list.append( - math.pi - * b**2 - * (math.log(zp / zm) / (2 * e) + math.sin(math.radians(f)) / (zp * zm)) - ) - return pixel_size / 360.0 * (area_list[0] - area_list[1]) / 1e6 + + def zone_area(latitude): + sin_lat = np.sin(np.radians(latitude)) + zm = 1 - e * sin_lat + zp = 1 + e * sin_lat + return np.pi * b**2 * (np.log(zp / zm) / (2 * e) + sin_lat / (zp * zm)) + + center_lat = np.asarray(center_lat) + upper = zone_area(center_lat + pixel_size / 2) + lower = zone_area(center_lat - pixel_size / 2) + return pixel_size / 360.0 * (upper - lower) / 1e6 def determine_pixel_areas(raster_input): @@ -111,13 +137,126 @@ def determine_pixel_areas(raster_input): "raster_input does not have the projection EPSG:4326" ) resolution = raster_input.rio.resolution()[0] # resolution in degrees - varea_of_pixel = np.vectorize(lambda lat: _area_of_pixel(resolution, lat)) - pixel_area = varea_of_pixel(raster_input.y) * 1000**2 # convert to m^2 + pixel_area = _area_of_pixel(resolution, np.asarray(raster_input.y)) * 1000**2 # m^2 pixel_area_da = xr.DataArray(pixel_area, coords={"y": raster_input.y}, dims="y") return pixel_area_da +def _warp_from_file( + input_raster_path, reference_raster, prepare, num_threads=1, warp_nodata=None +): + """Warp an input raster from file onto the reference grid with average resampling. + + This function goes through the following procedure: + + - Reads the window of ``input_raster_path`` covering the reference grid (plus a + two-pixel buffer) in its native dtype. + - Lets ``prepare(data, nodata, transform)`` turn it into the float32 array to + warp, and assigns NaN where there is no useful value. + - Warps the prepared array. + + This process reduces the copies held in memory compared to xarray + reproject_match. + + ``num_threads`` is passed on to GDAL's warp. + + ``warp_nodata`` is the nodata value declared to GDAL. Set it to ``None``, ``"file"`` + for the source file's own, or an explicit value. Declared nodata pixels + are excluded from the averages; any other NaN turns every target pixel it + touches into NaN. + + Returns a float32 ``(band, y, x)`` DataArray on the reference grid, with + the reference CRS written and NaN wherever the warp produced no value. + + """ + with rasterio.open(input_raster_path) as src: + xmin, ymin, xmax, ymax = reference_raster.rio.bounds() + pad = 2 * max(abs(res) for res in src.res) + window = from_bounds( + xmin - pad, ymin - pad, xmax + pad, ymax + pad, transform=src.transform + ) + window = window.round_offsets().round_lengths() + window = window.intersection(Window(0, 0, src.width, src.height)) + data = src.read(1, window=window) + src_transform = src.window_transform(window) + src_crs = src.crs + src_nodata = src.nodata + data = prepare(data, src_nodata, src_transform) + assert data.dtype == np.float32 + if isinstance(warp_nodata, str) and warp_nodata == "file": + warp_nodata = src_nodata + destination = np.full(reference_raster.rio.shape, np.nan, dtype=np.float32) + reproject( + source=data, + destination=destination, + src_transform=src_transform, + src_crs=src_crs, + src_nodata=warp_nodata, + dst_transform=reference_raster.rio.transform(), + dst_crs=reference_raster.rio.crs, + dst_nodata=np.nan, + resampling=Resampling.average, + num_threads=num_threads, + ) + del data + warped = xr.DataArray( + destination[np.newaxis], + dims=("band", "y", "x"), + coords={"band": [1], "y": reference_raster.y, "x": reference_raster.x}, + ) + return warped.rio.write_crs(reference_raster.rio.crs, inplace=True) + + +def _masked_float32(data, nodata, _transform, rows_per_chunk=1024): + """Native array to float32 with NaN where the source declares nodata. + + Converted in row chunks so that no full-size temporary (e.g. the boolean + nodata mask) exists next to the input and output arrays. + """ + out = np.empty(data.shape, dtype=np.float32) + for start in range(0, data.shape[0], rows_per_chunk): + rows = slice(start, start + rows_per_chunk) + out[rows] = data[rows] + if nodata is not None: + out[rows][data[rows] == nodata] = np.nan + return out + + +class _NetcdfWriter: + """Write variables to a NetCDF file one at a time. + + Holding every resampled layer in memory before a single ``to_netcdf`` would make + the peak memory of this script proportional to the *sum* of all outputs + (several GB for country-sized subunits). Appending variables as soon as + they are computed bounds it to the largest single layer pipeline instead. + """ + + def __init__(self, path): + self.path = path + self.mode = "w" + + def write(self, name, da, dtype=None, fill_value=None): + encoding = {"zlib": True, "complevel": 1} + if dtype is not None: + encoding["dtype"] = dtype + if fill_value is not None: + encoding["_FillValue"] = fill_value + # Source rasters carry no-op scale_factor/add_offset attributes that + # would be written to the file and make the variable decode as float64 + # downstream, doubling the memory of area_potential.py. + for attr in ["scale_factor", "add_offset"]: + da.attrs.pop(attr, None) + da.encoding.pop(attr, None) + da.to_dataset(name=name).to_netcdf( + self.path, mode=self.mode, encoding={name: encoding} + ) + self.mode = "a" + # rioxarray caches its accessor on the array and the accessor points + # back at it, so large buffers are only released by the cyclic GC. + del da + gc.collect() + + def _rasterize_regions(shapes, reference_raster): regions = [(geom, idx) for idx, geom in zip(shapes.index, shapes.geometry)] return rasterize( @@ -138,8 +277,14 @@ def _rasterize_regions(shapes, reference_raster): @click.argument("protected_area_path", type=str) @click.argument("land_cover_configuration_yaml_string", type=str) @click.argument("output_path", type=str) -@click.argument("plot_path", type=str) @click.option("--ship-travel-path", type=str) +@click.option( + "--num-threads", + type=int, + default=1, + show_default=True, + help="Threads used by the GDAL warps (set from the rule's threads).", +) def resample_inputs( shapes_path, land_cover_path, @@ -149,18 +294,20 @@ def resample_inputs( protected_area_path, land_cover_configuration_yaml_string, output_path, - plot_path, ship_travel_path, + num_threads, ): """Resample various geospatial datasets to a common shape and resolution. - Results are saved to the specified output path in NetCDF format, - and a plot of the resampled data is saved to the specified plot path. + Results are saved to the specified output path in NetCDF format. + (Plotting is a separate workflow step, so that downstream rules do not + wait for it: see nc_to_png.py.) """ shapes = gpd.read_parquet(shapes_path) xmin, ymin, xmax, ymax = shapes.total_bounds - resampled = xr.Dataset() + writer = _NetcdfWriter(output_path) + print("Saving results to output path:", output_path) ## # Land cover @@ -177,132 +324,171 @@ def resample_inputs( reference_raster = xr.ones_like(ds_land_cover, dtype=np.byte) reference_resolution = ds_land_cover.rio.resolution() print(f"Land cover resolution used as reference resolution: {reference_resolution}") - land_cover = aggregate_land_cover_types(ds_land_cover, land_cover_types) - for land_type in sorted(list(set(land_cover_types.values()))): - resampled[f"landcover_{land_type}"] = land_cover[land_type] - del ds_land_cover, land_cover + # Below, we go through the different inputs one by one. + # The warped inputs come first. The slope warp is the largest single memory + # allocation of this script, and running it before anything else is + # resident in memory keeps the peak memory to that one stage. ## - # Pixel area + # Slope: GEDTM30 stores degrees * 100 as uint16 ## - - pixel_area = determine_pixel_areas(resampled) - resampled["pixel_area"] = pixel_area.expand_dims({"x": resampled.x}).transpose( - "y", "x" + def prepare_slope(data, nodata, transform): + out = _masked_float32(data, nodata, transform) + out /= 100 + return out + + writer.write( + "slope_deg", + _warp_from_file(slope_path, reference_raster, prepare_slope, num_threads), + dtype="float32", ) - del pixel_area ## - # Regions + # Settlement in sum of area of built-up surface (m2) ## - shapes_land = shapes[shapes["shape_class"] == "land"].index - shapes_maritime = shapes[shapes["shape_class"] == "maritime"].index - print(f"Number of regions in input data: {len(shapes)}") - print(f"Number of land regions: {len(shapes_land)}") - print(f"Number of maritime regions: {len(shapes_maritime)}") + # One value per row (dims: y); broadcast against x only when written. + pixel_area = determine_pixel_areas(reference_raster).astype(np.float32) - resampled["regions"] = (("y", "x"), _rasterize_regions(shapes, reference_raster)) + def prepare_settlement(data, nodata, transform): + # Divide built-up surface (m2) by the source pixel area (m2) to get the + # built-up share; both operands float32 so the result stays float32. + # Pixel areas depend on latitude only (one value per row). + assert rasterio.crs.CRS.from_user_input(src_crs).to_epsg() == 4326, ( + "settlement raster does not have the projection EPSG:4326" + ) + rows = np.arange(data.shape[0]) + centre_lat = transform.f + transform.e * (rows + 0.5) + pixel_area_m2 = (_area_of_pixel(abs(transform.a), centre_lat) * 1000**2).astype( + np.float32 + ) + return data.astype(np.float32) / pixel_area_m2[:, np.newaxis] - mask_land = xr.DataArray( - np.isin(resampled["regions"], shapes_land), - dims=resampled["regions"].dims, - coords=resampled["regions"].coords, - ) - mask_maritime = xr.DataArray( - np.isin(resampled["regions"], shapes_maritime), - dims=resampled["regions"].dims, - coords=resampled["regions"].coords, + with rasterio.open(settlement_path) as src: + src_crs = src.crs + print(f"Settlement resolution: {src.res}") + settlement_share = _warp_from_file( + settlement_path, reference_raster, prepare_settlement, num_threads ) - resampled["regions_land"] = xr.where(mask_land, np.half(1.0), np.half(np.nan)) - resampled["regions_maritime"] = xr.where( - mask_maritime, np.half(1.0), np.half(np.nan) + writer.write("settlement_share", settlement_share, dtype="float32") + writer.write( + "settlement_area", + (settlement_share * pixel_area).transpose(*settlement_share.dims), + dtype="float32", ) - del mask_land, mask_maritime + del settlement_share ## - # Slope + # Bathymetry: only keep values <= 0, i.e., below sea level ## - da_slope = rxr.open_rasterio(slope_path, masked=True) / 100 - print(f"Slope resolution: {da_slope.rio.resolution()}") - resampled["slope_deg"] = da_slope.rio.reproject_match( - reference_raster, resampling=Resampling.average + def prepare_bathymetry(data, nodata, transform): + out = data.astype(np.float32) + out[out > 0] = np.nan + return out + + writer.write( + "bathymetry", + _warp_from_file( + bathymetry_path, + reference_raster, + prepare_bathymetry, + num_threads, + warp_nodata="file", + ), + dtype="float32", ) - del da_slope ## - # Settlement in sum of area of built-up surface (m2) + # Protected areas (0/1 mask, becomes the protected fraction when averaged) ## - ds_settlement = rxr.open_rasterio(settlement_path) - print(f"Settlement resolution: {ds_settlement.rio.resolution()}") - - ds_settlement_pixel_area = ( - determine_pixel_areas(ds_settlement) - .expand_dims({"x": ds_settlement.x}) - .transpose("y", "x") + def prepare_protected(data, nodata, transform): + return data.astype(np.float32) + + writer.write( + "protected", + _warp_from_file( + protected_area_path, + reference_raster, + prepare_protected, + num_threads, + warp_nodata="file", + ), + dtype="float32", ) - # Divide built-up surface (m2) by pixel area (m2) to get built-up surface density, - # then reproject to match the reference raster - resampled["settlement_share"] = ( - ds_settlement / ds_settlement_pixel_area - ).rio.reproject_match(reference_raster, resampling=Resampling.average) - - resampled["settlement_area"] = ( - resampled["settlement_share"] * resampled["pixel_area"] - ) - del ds_settlement, ds_settlement_pixel_area - ## - # Bathymetry + # Global shipping traffic density (AIS position counts per square kilometre) ## - - ds_bathymetry = rxr.open_rasterio(bathymetry_path) - # Only keep values <= 0, i.e., below sea level - ds_bathymetry = ds_bathymetry.where(ds_bathymetry <= 0, other=np.nan) - print(f"Bathymetry resolution: {ds_bathymetry.rio.resolution()}") - resampled["bathymetry"] = ds_bathymetry.rio.reproject_match( - reference_raster, resampling=Resampling.average - ) - del ds_bathymetry + if ship_travel_path: + writer.write( + "ship_travel", + _warp_from_file( + ship_travel_path, + reference_raster, + _masked_float32, + num_threads, + warp_nodata=np.nan, + ), + dtype="float32", + ) ## - # Protected areas + # Pixel area ## - protected_areas = rxr.open_rasterio(protected_area_path) - resampled["protected"] = protected_areas.rio.reproject_match( - reference_raster, resampling=Resampling.average + + writer.write( + "pixel_area", + pixel_area.expand_dims({"x": reference_raster.x}) + .transpose("y", "x") + .rio.write_crs(reference_raster.rio.crs, inplace=True), + dtype="float32", ) - del protected_areas ## - # Global shipping traffic density (AIS position counts per square kilometre) + # Regions ## - if ship_travel_path: - ship_travel = rxr.open_rasterio(ship_travel_path, masked=True) - print(f"Ship travel resolution: {ship_travel.rio.resolution()}") - resampled["ship_travel"] = ship_travel.rio.reproject_match( - reference_raster, resampling=Resampling.average + + shapes_land = shapes[shapes["shape_class"] == "land"].index + shapes_maritime = shapes[shapes["shape_class"] == "maritime"].index + print(f"Number of regions in input data: {len(shapes)}") + print(f"Number of land regions: {len(shapes_land)}") + print(f"Number of maritime regions: {len(shapes_maritime)}") + + regions = xr.DataArray( + _rasterize_regions(shapes, reference_raster), + dims=("y", "x"), + coords={"y": reference_raster.y, "x": reference_raster.x}, + ).rio.write_crs(reference_raster.rio.crs, inplace=True) + writer.write("regions", regions) + + # int8 with a fill value decodes as float32 (1 / NaN). No scale_factor or + # add_offset: even no-op ones make xarray decode the variable as float64. + for name, index in [ + ("regions_land", shapes_land), + ("regions_maritime", shapes_maritime), + ]: + mask = xr.DataArray( + np.isin(regions, index), dims=regions.dims, coords=regions.coords + ) + writer.write( + name, + xr.where(mask, np.half(1.0), np.half(np.nan)).rio.write_crs( + reference_raster.rio.crs, inplace=True + ), + dtype="int8", + fill_value=-128, + ) + del mask + del regions + + mapped, category_ids = land_cover_category_ids(ds_land_cover, land_cover_types) + for land_type, category_id in category_ids.items(): + writer.write( + f"landcover_{land_type}", + land_cover_mask(ds_land_cover, mapped, category_id), ) - del ship_travel - - netcdf4_encoding = { - var: {"zlib": True, "complevel": 1} - for var in resampled.data_vars - if var not in ["spatial_ref", "band"] - } - for v in ["regions_land", "regions_maritime"]: - netcdf4_encoding[v]["dtype"] = "int8" - netcdf4_encoding[v]["scale_factor"] = 1 - netcdf4_encoding[v]["add_offset"] = 0 - netcdf4_encoding[v]["_FillValue"] = -128 - - print("Saving result to output path:", output_path) - resampled.to_netcdf(output_path, encoding=netcdf4_encoding) - - print("Saving image to plot path:", plot_path) - _script_utils.plot_all_dataset_variables(resampled, ncols=3, savefig=plot_path) + del ds_land_cover, mapped if __name__ == "__main__": diff --git a/workflow/scripts/tif_to_png.py b/workflow/scripts/tif_to_png.py index 645ee9e..3cc3522 100644 --- a/workflow/scripts/tif_to_png.py +++ b/workflow/scripts/tif_to_png.py @@ -1,16 +1,52 @@ """This script plots a TIF file to PNG format.""" +import math import sys -import rioxarray as rxr -from _script_utils import plot_all_dataset_variables +import numpy as np +import rasterio +import rioxarray # noqa: F401 # activates the .rio accessor +import xarray as xr +from _plots import plot_all_dataset_variables +from rasterio.enums import Resampling + +MAX_PIXELS = 2_000_000 # Matches the cap in plot_all_dataset_variables + + +def read_decimated(tif_file, max_pixels=MAX_PIXELS): + """Read a single-band raster with at most ``max_pixels`` pixels in total. + + Reading through GDAL's decimated I/O means that blocks are averaged on the fly. + Therefore, the full-resolution raster is never held in memory, saving GBs of RAM + on large plots. + + Returns a float32 ``(y, x)`` DataArray with pixel-centre coordinates and the + raster's CRS, and NaN where the source has no data. + + """ + # Specify a block cache of 256 MB. GDAL would otherwise keep decoded blocks of the + # full-resolution raster around (default cache is 5% of RAM). + with rasterio.Env(GDAL_CACHEMAX=256), rasterio.open(tif_file) as src: + factor = max(1, math.ceil(math.sqrt(src.width * src.height / max_pixels))) + height, width = math.ceil(src.height / factor), math.ceil(src.width / factor) + data = src.read( + 1, out_shape=(height, width), resampling=Resampling.average, masked=True + ) + transform = src.transform * src.transform.scale( + src.width / width, src.height / height + ) + crs = src.crs + x = transform.c + transform.a * (np.arange(width) + 0.5) + y = transform.f + transform.e * (np.arange(height) + 0.5) + da = xr.DataArray( + data.filled(np.nan).astype(np.float32), dims=("y", "x"), coords={"y": y, "x": x} + ) + return da.rio.write_crs(crs, inplace=True) def tif_to_png(tif_file_in, png_file_out): """Convert a TIF file to PNG format.""" - ds = rxr.open_rasterio(tif_file_in, mask_and_scale=True).to_dataset( - name=tif_file_in - ) + ds = read_decimated(tif_file_in).to_dataset(name=tif_file_in) plot_all_dataset_variables( ds, ncols=2, savefig=png_file_out, categorical_vars=["regions"] )