diff --git a/INTERFACE.yaml b/INTERFACE.yaml index 03794ca..8ffeeba 100644 --- a/INTERFACE.yaml +++ b/INTERFACE.yaml @@ -1,19 +1,42 @@ -# Module Input-Output structure for automated docs generation +convention_version: v1.0.0 pathvars: snakemake_defaults: logs: - default: "" - description: location of snakemake log files. + default: + description: Location of module logs. resources: - default: "" - description: >- - Raw OSM files are written to retrieve/{country}_{feature}.json. - Clean features are written below clean; generic network outputs - below build (buses/lines/transformers as CSV under build/csv - and GeoJSON, including substation polygons, under build/geojson). - An interactive PyDeck map of the network, and the default target - of this workflow, is written to map.html. + default: + description: Location of downloaded PBFs, retrieved OSM features, and cleaning intermediates. results: - default: "" - description: location of module result files. + default: + description: Location of module results. user_resources: {} + results: + buses: + default: /network/csv/buses.csv + description: Network buses. + lines: + default: /network/csv/lines.csv + description: Network lines. + transformers: + default: /network/csv/transformers.csv + description: Network transformers. + buses_geojson: + default: /network/geojson/buses.geojson + description: Network buses geojson. + lines_geojson: + default: /network/geojson/lines.geojson + description: Network lines geojson. + transformers_geojson: + default: /network/geojson/transformers.geojson + description: Network transformers geojson. + stations_polygon: + default: /network/geojson/stations_polygon.geojson + description: Network stations polygon. + buses_polygon: + default: /network/geojson/buses_polygon.geojson + description: Network buses polygon. + map: + default: /map.html + description: Network map. +wildcards: {} diff --git a/README.md b/README.md index 821a651..d1470d6 100644 --- a/README.md +++ b/README.md @@ -12,93 +12,212 @@ A modular Snakemake workflow for retrieving OpenStreetMap power infrastructure. ## About -`grid-builder` is a modular `snakemake` workflow that retrieves OpenStreetMap power infrastructure and builds a generic high-voltage network. It can be imported into another `snakemake` workflow. +`grid-builder` is a modular Snakemake workflow for retrieving OpenStreetMap power +infrastructure and building a generic high-voltage AC network. It follows the +[Modelblocks conventions](https://www.modelblocks.org/convention/) and can be +imported into another Snakemake workflow or run on its own. -The workflow retains AC substations, overhead lines, and cables at configured voltage levels, then creates generic buses, connected line segments, and voltage-pair transformers. The outputs preserve OSM provenance and geometry but contain no PyPSA-specific line types, capacities, or electrical-component assumptions. - -This module follows the Modelblocks conventions (https://www.modelblocks.org). For more information, consult the [integration example](./tests/integration/Snakefile) and the `snakemake` [modularisation documentation](https://snakemake.readthedocs.io/en/stable/snakefiles/modularization.html). +For more information, consult the [Modelblocks documentation](https://modelblocks.readthedocs.io/en/latest/), +the [integration example](./tests/integration/Snakefile), and the +[Snakemake modularisation documentation](https://snakemake.readthedocs.io/en/stable/snakefiles/modularization.html). ## Overview -Currently implemented: +

+ Rule graph: retrieve_osm_pbf → clean → build_network → build_interactive_map → all +

-1. Retrieve OSM substations, lines, cables, and (optionally) circuit relations by country, either from a cached local Geofabrik PBF extract or the live Overpass API. -2. Clean the raw retrieval output, filtering voltage, frequency, construction status, and future assets, and grouping relation member ways into one line per real-world circuit. -3. Merge nearby stations and line endpoints into generic buses, AC lines, and transformers. -4. Build a self-contained interactive map of the resulting network (`map.html`), with layer toggles, voltage/text filtering, and click-through OSM links — this is the workflow's default target. +The rule graph shows the default Geofabrik backend. Selecting Overpass replaces +`retrieve_osm_pbf` with `retrieve_osm_overpass`; the downstream rules are the same. + +Data processing steps: + +1. Retrieve OSM substations, overhead lines, cables, and optional circuit + relations by country, using Geofabrik PBF extracts or the Overpass API. + Downloaded PBF extracts are cached for reuse. +2. Clean tags and geometries, filter by voltage and frequency, and combine + circuit relation member ways when enabled. +3. Apply the configured construction-status and date filters, merge nearby + stations and line endpoints into buses, and construct AC lines and + transformers between voltage levels at the same station. +4. Generate an interactive HTML map with layer controls, voltage and text + filters, and links to the source OSM objects. This is the default target + when running the workflow on its own. + +### Important assumptions + +- The workflow builds AC topology for now, DC lines and converters will be + added at a later stage. +- Connections are inferred from geometry and OSM tags. Transformers are inferred + between every pair of voltage-level buses at the same real station, rather + than taken from an inventory of individual transformers. +- Outputs include geometry and OSM references for buses and lines. +- The map embeds the network data in an HTML file that opens without a local + server. Its JavaScript libraries and basemap require internet access. ## Configuration -Configuration lives in [`config/config.yaml`](./config/config.yaml), validated against a generated JSON schema. See the configuration [README](./config/README.md) for the available controls, including retrieval backends, regional overrides, and personal/local settings. +Consult the [configuration README](./config/README.md) and +[default configuration](./config/config.yaml) for retrieval options, network +settings, regional overrides, and local configuration. + +Configuration is validated with Pydantic. The same models generate the +[JSON Schema in YAML format](./workflow/internal/config.schema.yaml) describing +the available options. ## Input / output structure -Please consult the [interface file](./INTERFACE.yaml) for more information. - -Raw retrieval outputs use `/retrieve/{country}_{feature}.json`, one -file per country and feature (`lines_way`, `cables_way`, `substations_way`, -`substations_node`, `substations_relation`, `routes_relation`). Both retrieval -backends write the same raw-Overpass-JSON shape, so downstream cleaning doesn't -need to know which one ran. Clean features use `/clean/*.geojson`; -generic network components use `/build/csv/{buses,lines,transformers}.csv` -and matching GeoJSON files under `/build/geojson/`, which also -includes `stations_polygon.geojson` (clustered station shapes) and -`buses_polygon.geojson` (substation polygons scoped to the buses in the output). -An interactive map of the network is written to `/map.html`; it is -a standalone HTML file (no server required) and the workflow's default target. -Country logs use `/retrieve_osm_pbf/{country}.log` or -`/retrieve_osm_overpass/{country}.log`, depending on `retrieve.source`. The -integration example sets these roots to `resources/grid-builder` and -`logs/grid-builder`. Downloaded PBF files (used for `retrieve.source: geofabrik`) -are cached in `data/earth-osm` in this checkout. - -DC assets (links, converters, switching stations) are out of scope: this workflow -builds a generic AC topology only, with no PyPSA-specific line types or capacities. +No user-supplied data files are required; the workflow retrieves OSM data for +the configured countries. Each public output has a pathvar documented in the +[interface file](./INTERFACE.yaml). + +Main outputs: + +| Output | Default location | +| --- | --- | +| Network CSVs | `/network/csv/{buses,lines,transformers}.csv` | +| Network GeoJSONs | `/network/geojson/*.geojson` | +| Interactive map (default target) | `/map.html` | + +Network GeoJSONs include the three network components plus +`stations_polygon.geojson` and `buses_polygon.geojson`. + +Both retrieval backends write raw OSM JSON files to +`/automatic/retrieve/{country}_{feature}.json`, where `country` is an +identifier from the configured `countries`. The six feature names are: `lines_way`, +`cables_way`, `substations_way`, `substations_node`, `substations_relation`, and +`routes_relation`. + +Cleaning intermediates use `/automatic/clean/`. Downloaded PBFs are +cached in `/automatic/earth-osm/` inside the consuming workflow's +working directory. Logs use `/`, with a separate retrieval log per country. + +### Importing into another workflow + +The host environment needs Python 3.12, Snakemake >=9.27, `pydantic >=2`, +`ruamel.yaml >=0.18`, `pyyaml >=6,<7`, and `earth-osm >=3.0.2` to load and validate +the module. Rule dependencies are installed separately by `--use-conda`. + +Create `config/modules/grid_builder.yaml` with the module configuration: + +```yaml +grid_builder: + countries: [BE] + retrieve: + source: geofabrik +``` + +Then import the module in your Snakefile: + +```python +configfile: "config/modules/grid_builder.yaml" + +module grid_builder: + snakefile: + github( + "pypsa/grid-builder", + path="workflow/Snakefile", + tag="", + ) + config: + config["grid_builder"] + pathvars: + logs="logs/grid-builder", + resources="resources/grid-builder", + results="results/grid-builder", + # Optional: rewire an individual result for a downstream module. + buses="results/shared/buses.csv", + +use rule * from grid_builder as grid_builder_* + +rule all_grid_builder: + default_target: True + input: + rules.grid_builder_build_interactive_map.output, +``` + +Run `snakemake --use-conda --cores 2 all_grid_builder`. Replace the tag +placeholder with the published release or commit you want to use. For a local +checkout, replace `github(...)` with the path to its `workflow/Snakefile`. + +Use the output pathvars in [INTERFACE.yaml](./INTERFACE.yaml) to rewire network +files and the map. Retrieval intermediates can also be redirected: set +`osm_retrieve` to change their directory, or set an individual pathvar such as +`osm_lines_way="raw/{country}/lines.json"`. The six retrieval pathvars are +`osm_lines_way`, `osm_cables_way`, `osm_substations_way`, `osm_substations_node`, +`osm_substations_relation`, and `osm_routes_relation`. + +Retrieval-only consumers can request `rules.grid_builder_retrieve_osm_all.input` +regardless of where those files are stored. Referencing imported rule outputs +avoids hard-coding their locations. ## Development -We use [`pixi`](https://pixi.sh/) as our package manager for development. -Once installed, run the following to clone this repository and install all dependencies. +We use [Pixi](https://pixi.sh/) to manage dependencies. Clone the repository and +install the development environment: ```shell -git clone git@github.com:PyPSA/grid-builder.git +git clone https://github.com/pypsa/grid-builder.git cd grid-builder pixi install --locked ``` -For testing, simply run: +This is a multi-environment project; see [pixi.toml](./pixi.toml): + +- `default`: development, validation, and testing tools, including the execution + dependencies needed by unit tests. +- `module`: execution dependencies used by Snakemake rules. + +After changing dependencies, update the lock file and export the execution +environment for module users: ```shell -pixi run --locked lint -pixi run --locked test +pixi lock +pixi run --locked export-snakemake-env module ``` -To test a minimal example of a workflow using this module: +This writes `workflow/envs/module.yaml` and package pins from `pixi.lock` for +Linux (`linux-64`), macOS (`osx-arm64`), and Windows (`win-64`). The export command +also accepts an optional output directory after `module`. + +To regenerate the default configuration, schemas, and regional-file index after +changing the Pydantic models or adding or removing regional config files: ```shell -pixi shell # activate this project's environment -cd tests/integration/ # navigate to the integration example -snakemake --use-conda --cores 2 # run the workflow! +pixi run --locked generate-config ``` -The Pixi environment supplies Snakemake and the configuration-validation -dependencies. Snakemake installs the retrieval script's dependencies from -`workflow/envs/retrieve.yaml` when `--use-conda` is enabled. A consuming workflow -must also provide the host dependencies from `pixi.toml`; importing the module -does not activate its Pixi environment automatically. +## Testing + +Run the checks and test suite: + +```shell +pixi run --locked lint +pixi run --locked test +``` -The integration test uses a fresh temporary output directory, runs the retrieval, -cleaning, and generic network Conda environments, and checks the resulting -components and country logging. It needs internet access on the first run to -install dependencies and download Benin's OSM extract; subsequent runs can reuse -those caches. Test logs are retained in `tests/integration/logs`. +The suite covers configuration validation, network processing, local and HTTP +module imports, output rewiring, and environment consistency. Remote-import +tests use synthetic OSM data and a local mock Overpass server. -Each retrieval job runs with one worker (`threads: 1`), so CPU allocation stays -entirely under Snakemake's control. Snakemake can still run multiple country jobs -in parallel using `--cores`. +The live integration test retrieves Benin's OSM extract and runs the workflow +in its exported Conda environment. It requires internet access for the data +and, on the first run, environment installation. Conda environments are cached +between runs. Test logs are retained in `tests/integration/logs`. -If this checkout is moved and commands fail with a `bad interpreter` error, -rebuild the installed environment with `pixi reinstall --locked`. +To run only the integration checks: + +```shell +pixi run --locked test-integration +``` + +To run the example consuming workflow manually: + +```shell +pixi shell +cd tests/integration/ +snakemake --use-conda --cores 2 +``` ## License @@ -109,3 +228,9 @@ rebuild the installed environment with `pixi reinstall --locked`. * Jonas Hörsch et al. 2018. PyPSA-Eur: An open optimisation model of the European transmission system, *Energy Strategy Reviews*, Volume 22. https://doi.org/10.1016/j.esr.2018.08.012 * Maximilian Parzen et al. 2023. PyPSA-Earth: A new global open energy system optimization model demonstrated in Africa, *Applied Energy*, Volume 341. https://doi.org/10.1016/j.apenergy.2023.121096 * Bobby Xiong et al. 2025. Modelling the high-voltage grid using open data for Europe and beyond. *Sci Data* 12, 277. https://doi.org/10.1038/s41597-025-04550-7 + +## Contributors + +See [AUTHORS](./AUTHORS) for copyright attribution and the +[contributor list](https://github.com/pypsa/grid-builder/graphs/contributors) +for contributions to the project. diff --git a/config/README.md b/config/README.md index e5166c9..776743e 100644 --- a/config/README.md +++ b/config/README.md @@ -46,4 +46,13 @@ missing user agent risks being rate-limited or blocked. `retrieve.overpass_api.u also lets you point at your own or a faster mirror instance instead of the shared public endpoint, without touching the checked-in default. -The generated [schema](./config.schema.json) describes every option. +The generated [schema](../workflow/internal/config.schema.yaml) describes every +option using JSON Schema in YAML format. Pydantic models remain the validation +source of truth; `pixi run generate-config` updates the schema, default config, +and the JSON copy at `config/config.schema.json` together. + +After adding or removing a `config/regions/config..yaml` file, run +`pixi run generate-config`. It discovers the regional files and generates the +sorted `config/regions/index.yaml` manifest automatically. Commit the generated +index with the regional files so remote imports can discover them without a +local checkout. Do not edit the index manually; a test checks that it is current. diff --git a/config/config.yaml b/config/config.yaml index d508946..18c40db 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,6 +1,6 @@ %YAML 1.1 --- -# yaml-language-server: $schema=./config.schema.json +# yaml-language-server: $schema=../workflow/internal/config.schema.yaml countries: - BE diff --git a/config/regions/index.yaml b/config/regions/index.yaml new file mode 100644 index 0000000..4ec9cc1 --- /dev/null +++ b/config/regions/index.yaml @@ -0,0 +1,7 @@ +# Generated by pixi run generate-config; do not edit manually. +- BE +- BR +- MX +- NL +- PH +- US diff --git a/figures/rulegraph.png b/figures/rulegraph.png new file mode 100644 index 0000000..ee78925 Binary files /dev/null and b/figures/rulegraph.png differ diff --git a/mypy.ini b/mypy.ini index 89978f2..00bb13d 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ [mypy] plugins = pydantic.mypy explicit_package_bases = True -mypy_path = workflow +mypy_path = workflow/scripts disable_error_code = import-untyped exclude = (^|/)\.(snakemake|pixi)(/|$) exclude_gitignore = True diff --git a/pixi.lock b/pixi.lock index 9a11ef8..771e329 100644 --- a/pixi.lock +++ b/pixi.lock @@ -29,7 +29,7 @@ environments: - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-report-plugins-1.3.0-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-scheduler-plugins-2.0.2-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-storage-plugins-4.4.1-pyh84498cf_0.conda - - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.20.0-pyhdfd78af_0.conda + - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.27.0-pyhdfd78af_0.conda - 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/at-spi2-atk-2.38.0-h0630a04_3.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/at-spi2-core-2.40.3-h0630a04_0.tar.bz2 @@ -249,7 +249,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-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/click-8.3.3-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.03.30-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.06.23-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-inject-1.3.2-pyhd8ed1ab_0.conda @@ -375,7 +375,7 @@ environments: - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-report-plugins-1.3.0-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-scheduler-plugins-2.0.2-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-storage-plugins-4.4.1-pyh84498cf_0.conda - - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.20.0-pyhdfd78af_0.conda + - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.27.0-pyhdfd78af_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/amply-0.1.6-pyhd8ed1ab_1.conda @@ -396,7 +396,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-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/click-8.3.3-pyhc90fa1f_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.03.30-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.06.23-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda @@ -707,7 +707,7 @@ environments: - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-report-plugins-1.3.0-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-scheduler-plugins-2.0.2-pyhd4c3c12_0.conda - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-interface-storage-plugins-4.4.1-pyh84498cf_0.conda - - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.20.0-pyhdfd78af_0.conda + - conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.27.0-pyhdfd78af_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/amply-0.1.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda @@ -726,7 +726,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-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/click-8.3.3-pyh6dadd2b_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.03.30-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.06.23-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/conda-index-0.13.0-pyh5ded981_0.conda @@ -1026,6 +1026,457 @@ environments: - 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/zstandard-0.25.0-py312he5662c2_4.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda + 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/backports.zstd-1.4.0-py312h90b7ffd_0.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-py312hdb49522_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/contourpy-1.3.3-py312h0a2e395_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.1-py312h8a5da7c_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/geos-3.14.1-h480dda7_0.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/icu-78.3-h33c6efd_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-py312h0a2e395_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19-h0c24ade_0.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/libabseil-20260107.1-cxx17_h7b12aa8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.7-gpl_hc2c16d8_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libboost-1.90.0-h1101c1b_3.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-6_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_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/libedit-3.1.20250104-pl5321h7949ede_0.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/libexpat-2.8.4-hd2095e1_0.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-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.4-h08c5dba_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h10be129_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/libjxl-0.11.2-h174a0a3_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-6_h47877c9_openblas.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/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/libopenblas-0.3.32-pthreads_h94d23a6_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/librttopo-1.1.0-h46dd2a8_20.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.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_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.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/libuuid-2.42-h5347b49_0.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/libxml2-16-2.15.3-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.3-h49c6c72_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-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-py312h8a5da7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.9-py312he3d6523_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.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/numpy-2.4.3-py312h33ff503_0.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/openssl-3.6.2-h35e630c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/osmium-tool-1.19.1-h7ac1ec8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py312h50c33e8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.1-he0df7b0_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.33.5-py312ha7b3241_2.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/pyogrio-0.12.1-py312h053e1f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyosmium-4.3.1-py312hc7fdb42_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.2-py312hbc8341d_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_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/readline-8.3-h853b02a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.8.0-np2py312h3226591_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py312h383787d_2.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.0-h04a0ce9_0.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/unicodedata2-17.0.1-py312h4c3975b_0.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/xerces-c-3.3.0-hd9031aa_1.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-libxdmcp-1.1.5-hb03c661_1.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/branca-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-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/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/earth-osm-3.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_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/hpack-4.1.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.13-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.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.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-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_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.12-8_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-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/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + osx-arm64: + - 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.4.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-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/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/earth-osm-3.0.2-pyhd8ed1ab_0.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/geopandas-1.1.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_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/hpack-4.1.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.13-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.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.26.0-pyh5ded981_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-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_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.12-9_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-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/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_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/backports.zstd-1.7.0-py312h1a36842_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-hf00d406_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-he4a93e4_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312ha52686f_4.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/cairo-1.18.4-he0f2337_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.4.0-py312h67ba313_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.65.0-py312hf4454c7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.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/fribidi-1.0.16-hc919400_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/geos-3.14.1-h5afe852_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-6.1.3-h74c22ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.15-h784d473_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_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-py312h67ba313_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hce41798_3.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-20260526.0-cxx17_h4c27e2a_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h76147b0_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.90.0-h30c325d_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-h1dcdb26_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-h5295a6a_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-h2ddc9cb_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.22.0-h6651222_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-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/libexpat-2.8.4-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-core-3.13.3-hc1e97f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.90.0-hccd281b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libharfbuzz-14.4.0-hcda0f7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.4.0-ha332bbd_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/libintl-0.25.1-h493aca8_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/libjxl-0.12.0-hb71b141_2.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-6_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/libnghttp2-1.68.1-h8f3e76b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.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/libpsl-0.23.1-hdb0c161_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.12.14-h4e5ec87_3_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libraqm-0.11.0-h45af499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/librttopo-1.1.0-ha909e78_20.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libspatialite-5.1.0-gpl_h16ba230_122.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_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/libtiff-4.7.2-hf67920b_1.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.4-heb56d2d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.4-h550178d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-devel-2.15.4-h550178d_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.4-hc7d1edf_0.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-py312h04c11ed_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.11.2-py312h11997a8_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.0.10-hff1a8ea_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/muparser-2.3.5-h11e0b38_0.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/numpy-2.5.3-py312hff34920_0.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.2-hd24854e_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/osmium-tool-1.19.1-h693c552_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py312hf9dc30a_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/proj-9.9.0-h51dee2d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/protobuf-7.35.1-py312h5674827_3.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/pyogrio-0.13.0-py312hd1c4548_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyosmium-4.3.1-py312h892b0b2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyproj-3.8.0-py312h16d7083_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.14-hd05a0c4_3_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_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/readline-8.3-h46df422_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.1-np2py312hda66c2e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py312h4519d97_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/shapely-2.1.2-py312h0fc4a54_3.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-ha9f7ffe_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/unicodedata2-18.0.0-py312ha053593_0.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.3.0-h25f632f_1.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/branca-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-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/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/earth-osm-3.0.2-pyhd8ed1ab_0.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/geopandas-1.1.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_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/hpack-4.1.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.13-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.5.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.26.0-pyh5ded981_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-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_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.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-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/threadpoolctl-3.6.0-pyhecae5ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda + - 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/xyzservices-2026.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py312h06d0912_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-hc8c2fe1_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hd477307_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py312ha763cb9_4.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/cairo-1.18.4-h477c42c_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.4.0-py312h78d62e6_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.17.1-hd47e2ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fonttools-4.65.0-py312h05f76fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.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/fribidi-1.0.16-hfd05255_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.15-h5112557_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.1-py312h78d62e6_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_3.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/libarchive-3.8.9-gpl_h8fdc8af_101.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libboost-1.90.0-h9dfe17d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hf02afa3_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-h84f9c24_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-he2a975b_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.22.0-hdb0ef4a_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/libexpat-2.8.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.13.3-h6864167_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.90.0-ha564072_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libharfbuzz-14.4.0-h03b5201_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.4.0-h172a326_0.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/libjxl-0.12.0-h932607e_2.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-6_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/libpng-1.6.58-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpsl-0.23.1-h9b16d47_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpython-3.12.14-hcc31f7b_3_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libraqm-0.11.0-h50d6d30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_hfd1cb16_122.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/libtiff-4.7.2-h8f73337_1.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/libxml2-16-2.15.4-h3cfd58e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.4-he095d88_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.4-he095d88_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.4-h4fa8253_0.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-py312h05f76fc_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.11.2-py312hf9659c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.5.3-py312ha3f287d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.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.2-hf411b9b_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/osmium-tool-1.19.1-h60971b7_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-2.3.3-py312hc128f0a_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py312h7bb5964_4.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.9.0-hd30e2cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/protobuf-7.35.1-py312h87014b1_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyogrio-0.13.0-py312h0e78342_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyosmium-4.3.1-py312h3e3ada1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyproj-3.8.0-py312h5a521b9_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.12.14-hb12b558_3_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py312h05f76fc_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/scikit-learn-1.9.1-np2py312hea30aaf_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.1-py312h8847828_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/shapely-2.1.2-py312h37f46ab_3.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-ha3553a1_1.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/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-18.0.0-py312he06e257_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.3-h41ae7f8_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_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 packages: - conda: https://conda.anaconda.org/bioconda/noarch/snakefmt-1.1.0-pyhdfd78af_0.conda sha256: 11c6ea3fbc6ece521909b776e8697170cab3db5bfb1b7462149ddd582a795cc2 @@ -1104,14 +1555,14 @@ packages: license_family: MIT size: 22783 timestamp: 1773699846635 -- conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.20.0-pyhdfd78af_0.conda - sha256: 17bff0693974075d19ee57235aa7c87ac425c4987bafd84aa7561e23cec0529f - md5: 8fec66fdc146656a4f2f830732856b51 +- conda: https://conda.anaconda.org/bioconda/noarch/snakemake-minimal-9.27.0-pyhdfd78af_0.conda + sha256: 2c80dd4dce15bf6eb1806ccab28f12a47d81e623d50e8543f58f6eac9090498b + md5: ef7be2b45f827b930836cd91db64e769 depends: - conda-inject >=1.3.1,<2.0 - configargparse - connection_pool >=0.0.3 - - docutils + - docutils >=0.20,<0.23 - dpath >=2.1.6,<3.0.0 - gitpython - humanfriendly @@ -1119,11 +1570,11 @@ packages: - jinja2 >=3.0,<4.0 - jsonschema - nbformat - - packaging >=24.0 + - packaging >=24.0,<26 - platformdirs - psutil - pulp >=2.3.1,<3.4 - - python >=3.11,<3.14 + - python >=3.11 - pyyaml - referencing - requests >=2.8.1,<3.0 @@ -1133,7 +1584,7 @@ packages: - snakemake-interface-logger-plugins >=1.1.0,<3.0.0 - snakemake-interface-report-plugins >=1.2.0,<2.0.0 - snakemake-interface-scheduler-plugins >=2.0.0,<3.0.0 - - snakemake-interface-storage-plugins >=4.3.2,<5.0 + - snakemake-interface-storage-plugins >=4.4.1,<5.0 - sqlmodel >=0.0.37,<0.0.38 - tabulate - tenacity >=9.1.4,<10.0 @@ -1142,8 +1593,11 @@ packages: - yte >=1.5.5,<2.0 license: MIT license_family: MIT - size: 883505 - timestamp: 1777722551210 + run_exports: + weak: + - snakemake-minimal >=9.27.0,<10.0a0 + size: 891186 + timestamp: 1789337861924 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda build_number: 20 sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 @@ -4189,9 +4643,9 @@ packages: license_family: BSD size: 100048 timestamp: 1777219902525 -- conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.03.30-pyhd8ed1ab_0.conda - sha256: a8b34e4bb8854ad899e461430fbabad81fa694805e45421bfe3e56da031d44ad - md5: 169bb144044321bb1cec1bfa4ea82a5b +- conda: https://conda.anaconda.org/conda-forge/noarch/clio-tools-2026.06.23-pyhd8ed1ab_0.conda + sha256: 68f97f005c987796455f51bb1a84b5f8879d09a78959e369af3a00945795b568 + md5: de13c74a1b7580a7e4a2323b252f2da2 depends: - networkx >=3.4.2 - numpy >=2.2.3 @@ -4203,8 +4657,9 @@ packages: - pyyaml >=6.0.2 license: MIT license_family: MIT - size: 15754 - timestamp: 1774882664468 + run_exports: {} + size: 23713 + timestamp: 1782229072527 - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 md5: 962b9857ee8e7018c22f2776ffa0b2d7 diff --git a/pixi.toml b/pixi.toml index f095350..b6c0a3a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -9,7 +9,7 @@ platforms = ["win-64", "linux-64", "osx-arm64"] homepage = "https://www.modelblocks.org/" [dependencies] -clio-tools = ">=2026.03.30" +clio-tools = ">=2026.06.23" conda = ">=25.0.0" ipdb = ">=0.13.13" ipykernel = ">=6.29.5" @@ -22,8 +22,14 @@ pyproj = ">=3.7,<4" pyyaml = ">=6,<7" ruff = ">=0.9.9" snakefmt = ">=0.10.2" -snakemake-minimal = ">=9.19.0" +snakemake-minimal = ">=9.27.0" pytz = ">=2026.1.post1" +pre-commit = ">=4.6.2,<5" + +[feature.module.dependencies] +python = ">=3.12,<3.13" +pyproj = ">=3.7,<4" +pyyaml = ">=6,<7" earth-osm = ">=3.0.2" geopandas = ">=1.0,<2" networkx = ">=3,<4" @@ -35,12 +41,20 @@ pyosmium = ">=4,<5" pydeck = ">=0.9,<1" requests = ">=2,<3" shapely = ">=2,<3" -pre-commit = ">=4.6.2,<5" + +[environments] +default = { features = ["module"], solve-group = "default" } +module = { features = ["module"], no-default-feature = true, solve-group = "default" } [tasks] test = {cmd = "pytest tests"} test-integration = {cmd = "pytest tests/integration_test.py"} generate-config = {cmd = "python workflow/scripts/_schema.py"} -check-python = {cmd = "ruff check workflow/scripts tests && ruff format --check workflow/scripts tests && mypy workflow/scripts"} +check-python = {cmd = "ruff check workflow/scripts tests scripts && ruff format --check workflow/scripts tests scripts && mypy workflow/scripts"} check-workflow = {cmd = "snakefmt --check workflow tests/integration/Snakefile && snakemake --lint --snakefile workflow/Snakefile && snakemake --lint --snakefile tests/integration/Snakefile"} lint = {depends-on = ["check-python", "check-workflow"]} + +[tasks.export-snakemake-env] +description = "Export locked execution packages for Snakemake on all supported platforms" +args = [{ arg = "env", default = "module" }, { arg = "outdir", default = "workflow/envs" }] +cmd = "python scripts/export_snakemake_env.py '{{ env }}' '{{ outdir }}'" diff --git a/pytest.ini b/pytest.ini index 61b096f..6654657 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tests -pythonpath = . workflow +pythonpath = . workflow/scripts diff --git a/scripts/export_snakemake_env.py b/scripts/export_snakemake_env.py new file mode 100644 index 0000000..27c37b5 --- /dev/null +++ b/scripts/export_snakemake_env.py @@ -0,0 +1,53 @@ +"""Export Pixi's environment and platform pins using the ModelBlocks convention.""" + +import subprocess +import sys +import tempfile +import tomllib +from pathlib import Path + + +def export(environment: str = "module", output_dir: str = "workflow/envs") -> None: + """Write the environment YAML and explicit platform package specifications.""" + root = Path(__file__).resolve().parents[1] + manifest = tomllib.loads((root / "pixi.toml").read_text()) + if environment not in manifest["environments"]: + raise ValueError(f"Unknown environment: {environment}") + outdir = root / output_dir + outdir.mkdir(parents=True, exist_ok=True) + subprocess.run( + [ + "pixi", + "workspace", + "export", + "conda-environment", + "--environment", + environment, + str(outdir / f"{environment}.yaml"), + ], + cwd=root, + check=True, + ) + with tempfile.TemporaryDirectory() as temporary: + for platform in manifest["workspace"]["platforms"]: + subprocess.run( + [ + "pixi", + "workspace", + "export", + "conda-explicit-spec", + "--environment", + environment, + "--platform", + platform, + temporary, + ], + cwd=root, + check=True, + ) + pin = Path(temporary) / f"{environment}_{platform}_conda_spec.txt" + (outdir / f"{environment}.{platform}.pin.txt").write_bytes(pin.read_bytes()) + + +if __name__ == "__main__": + export(*sys.argv[1:]) diff --git a/tests/integration/Snakefile b/tests/integration/Snakefile index e1f7e10..6a8c383 100644 --- a/tests/integration/Snakefile +++ b/tests/integration/Snakefile @@ -6,6 +6,7 @@ module grid_builder: pathvars: resources="resources/grid-builder", logs="logs/grid-builder", + results="results/grid-builder", snakefile: "../../workflow/Snakefile" config: diff --git a/tests/integration_test.py b/tests/integration_test.py index 3c9af85..76ea39c 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -37,7 +37,7 @@ def test_interface_file(module_path): "LICENSE", "README.md", "config/config.yaml", - "config/config.schema.json", + "workflow/internal/config.schema.yaml", "config/examples/config.BE-NL.yaml", "tests/integration/Snakefile", ], @@ -94,7 +94,7 @@ def test_snakemake_integration_testing(module_path): shutil.copytree(workdir / "logs", log_dir, dirs_exist_ok=True) assert result.returncode == 0, run_log.read_text(encoding="utf-8") - output_dir = workdir / "resources/grid-builder/retrieve" + output_dir = workdir / "resources/grid-builder/automatic/retrieve" for feature in ("substations_way", "lines_way"): payload = json.loads( (output_dir / f"benin_{feature}.json").read_text(encoding="utf-8") @@ -103,7 +103,7 @@ def test_snakemake_integration_testing(module_path): assert elements, f"No {feature} records retrieved" assert all(item["geometry"] for item in elements) - build_dir = workdir / "resources/grid-builder/build" + build_dir = workdir / "results/grid-builder/network" for component in ("buses", "lines", "transformers"): with (build_dir / "csv" / f"{component}.csv").open( encoding="utf-8" diff --git a/tests/test_config.py b/tests/test_config.py index 05be5e2..1aa429b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -46,6 +46,9 @@ def test_generated_config_matches_repository(tmp_path): defaults = generate_config_defaults(str(tmp_path / "config.yaml")) schema = generate_config_schema(str(tmp_path / "config.schema.json")) assert defaults == yaml.safe_load((config_dir / "config.yaml").read_text()) + assert schema == yaml.safe_load( + (config_dir.parent / "workflow/internal/config.schema.yaml").read_text() + ) assert schema == json.loads((config_dir / "config.schema.json").read_text()) validated = validate_config(defaults) assert validated.retrieve.overpass_api.max_tries == 5 diff --git a/tests/test_environments.py b/tests/test_environments.py new file mode 100644 index 0000000..1043a14 --- /dev/null +++ b/tests/test_environments.py @@ -0,0 +1,53 @@ +"""Check that Snakemake executes the packages resolved by Pixi.""" + +import subprocess +import tomllib +from pathlib import Path + +import yaml + + +def test_exported_environments_match_lock(): + """Prevent stale platform pins or manifest dependencies reaching consumers.""" + root = Path(__file__).resolve().parents[1] + manifest = tomllib.loads((root / "pixi.toml").read_text()) + lock = yaml.safe_load((root / "pixi.lock").read_text()) + environment = lock["environments"]["module"] + exported = yaml.safe_load((root / "workflow/envs/module.yaml").read_text()) + dependencies = manifest["feature"]["module"]["dependencies"] + assert set(exported["dependencies"]) == { + f"{name} {version}" for name, version in dependencies.items() + } + assert exported["channels"] == manifest["workspace"]["channels"] + ["nodefaults"] + packages = {package["conda"]: package for package in lock["packages"]} + for platform in manifest["workspace"]["platforms"]: + pin = root / f"workflow/envs/module.{platform}.pin.txt" + entries = { + line + for line in pin.read_text().splitlines() + if line and not line.startswith(("#", "@")) + } + expected = { + f"{item['conda']}#{packages[item['conda']]['md5']}" + for item in environment["packages"][platform] + } + assert entries == expected, ( + f"Re-export the {platform} environment from pixi.lock" + ) + + +def test_export_to_custom_directory(tmp_path): + """Support the same export command used by ModelBlocks integration tests.""" + root = Path(__file__).resolve().parents[1] + outdir = tmp_path / "exported environments" + subprocess.run( + ["pixi", "run", "--locked", "export-snakemake-env", "module", str(outdir)], + cwd=root, + check=True, + capture_output=True, + text=True, + timeout=60, + ) + for source in (root / "workflow/envs").glob("module.*"): + # Git checkouts may use CRLF on Windows; compare normalized text. + assert (outdir / source.name).read_text() == source.read_text() diff --git a/tests/test_module_import.py b/tests/test_module_import.py new file mode 100644 index 0000000..c6b5c79 --- /dev/null +++ b/tests/test_module_import.py @@ -0,0 +1,208 @@ +"""Exercise remote source caching without relying on GitHub or live OSM data.""" + +import functools +import json +import os +import shutil +import subprocess +import sys +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from threading import Thread + +import pytest + +from workflow.scripts._schema import generate_region_index + + +@pytest.fixture +def remote_module(tmp_path): + """Serve only module sources, with a fresh URL and cache for each test.""" + repository = Path(__file__).resolve().parents[1] + source = tmp_path / "source" + for directory in ("workflow", "config"): + shutil.copytree(repository / directory, source / directory) + payloads = {} + + class Handler(SimpleHTTPRequestHandler): + """Serve sources and a minimal deterministic Overpass API.""" + + def do_POST(self): + """Return fixture elements matching the requested feature.""" + query = self.rfile.read(int(self.headers["Content-Length"])).decode() + feature = None + if 'way["power"="line"]' in query: + feature = "lines_way" + elif 'way["power"="substation"]' in query: + feature = "substations_way" + body = json.dumps(payloads.get(feature, {"elements": []})).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + handler = functools.partial(Handler, directory=str(source)) + server = ThreadingHTTPServer(("127.0.0.1", 0), handler) + thread = Thread(target=server.serve_forever, daemon=True) + thread.start() + try: + yield f"http://127.0.0.1:{server.server_port}/workflow/Snakefile", payloads + finally: + server.shutdown() + server.server_close() + thread.join() + + +@pytest.mark.parametrize("remote", [False, True]) +@pytest.mark.parametrize("backend", ["overpass", "geofabrik"]) +def test_module_execution(tmp_path, remote_module, remote, backend): + """Import beside a conflicting scripts package, then clean, build and plot.""" + remote_url, payloads = remote_module + api_url = remote_url.rsplit("/workflow/", 1)[0] + "/overpass" + workdir = tmp_path / "consumer" + workdir.mkdir() + scripts = workdir / "scripts" + scripts.mkdir() + (scripts / "__init__.py").touch() + (scripts / "_schema.py").write_text("parent_marker = True\n") + (scripts / "_helpers.py").write_text("parent_marker = 'parent helper'\n") + (scripts / "probe.py").write_text( + "from pathlib import Path\n" + "from _helpers import parent_marker\n" + "Path(snakemake.output[0]).write_text(parent_marker)\n" + ) + source = ( + remote_url + if remote + else str(Path(__file__).resolve().parents[1] / "workflow/Snakefile") + ) + (workdir / "Snakefile").write_text( + f"""import sys +sys.path.insert(0, {str(workdir)!r}) +import scripts._schema +import scripts._helpers + +module grid: + snakefile: {source!r} + config: {{"countries": ["US"], "retrieve": {{"source": {backend!r}, "overpass_api": {{"url": {api_url!r}}}}}}} + pathvars: + resources="custom/resources", + results="custom/results", + logs="custom/logs", + osm_lines_way="rewired/{{country}}/lines.json", + map="rewired/map.html", + buses="rewired/buses.csv", + +use rule * from grid as grid_* + +assert scripts._schema.parent_marker +assert scripts._helpers.parent_marker == "parent helper" +assert "rewired/US/lines.json" in rules.grid_retrieve_osm_all.input +if {backend!r} == "geofabrik": + assert rules.grid_retrieve_osm_pbf.params.data_dir == "custom/resources/automatic/earth-osm" +assert grid.config["regions"]["US"]["frequency_hz"]["AC"] == 60 + +rule parent_probe: + output: "parent.txt" + script: "scripts/probe.py" + +rule all: + default_target: True + input: rules.grid_build_interactive_map.output, rules.parent_probe.output +""" + ) + raw_dir = workdir / "custom/resources/automatic/retrieve" + raw_dir.mkdir(parents=True) + for feature in ( + "lines_way", + "cables_way", + "substations_way", + "substations_node", + "substations_relation", + "routes_relation", + ): + elements = [] + if feature == "lines_way": + elements = [ + { + "type": "way", + "id": 1, + "tags": { + "power": "line", + "voltage": "345000", + "frequency": "60", + "cables": "3", + }, + "geometry": [ + {"lon": -100.0, "lat": 40.0}, + {"lon": -99.9, "lat": 40.0}, + ], + } + ] + if feature == "substations_way": + elements = [ + { + "type": "way", + "id": i + 10, + "tags": { + "power": "substation", + "voltage": "345000", + "frequency": "60", + "substation": "transmission", + }, + "geometry": [ + {"lon": x, "lat": y} + for x, y in ( + (lon - 0.001, 39.999), + (lon + 0.001, 39.999), + (lon + 0.001, 40.001), + (lon - 0.001, 40.001), + (lon - 0.001, 39.999), + ) + ], + } + for i, lon in enumerate((-100.0, -99.9)) + ] + raw_path = ( + workdir / "rewired/US/lines.json" + if feature == "lines_way" + else raw_dir / f"US_{feature}.json" + ) + payloads[feature] = {"elements": elements} + if backend == "geofabrik": + raw_path.parent.mkdir(parents=True, exist_ok=True) + raw_path.write_text(json.dumps(payloads[feature])) + env = {**os.environ, "XDG_CACHE_HOME": str(tmp_path / "cache")} + env.pop("PYTHONPATH", None) + result = subprocess.run( + [sys.executable, "-m", "snakemake", "--cores", "1", "--show-failed-logs"], + cwd=workdir, + env=env, + capture_output=True, + text=True, + timeout=120, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert (workdir / "parent.txt").read_text() == "parent helper" + assert (workdir / "rewired/map.html").stat().st_size > 0 + lines = json.loads( + (workdir / "custom/results/network/geojson/lines.geojson").read_text() + ) + assert len(lines["features"]) == 1 + assert (workdir / "rewired/buses.csv").stat().st_size > 0 + if backend == "overpass": + assert ( + "Querying Overpass" + in (workdir / "custom/logs/retrieve_osm_overpass/US.log").read_text() + ) + + +def test_region_manifest(tmp_path): + """Every shipped regional default must be available to remote consumers.""" + regions = Path(__file__).resolve().parents[1] / "config/regions" + generated = tmp_path / "index.yaml" + generate_region_index(regions, generated) + assert generated.read_text() == (regions / "index.yaml").read_text(), ( + "Run pixi run generate-config after adding or removing regional files." + ) diff --git a/workflow/Snakefile b/workflow/Snakefile index b8ed000..0858ff9 100644 --- a/workflow/Snakefile +++ b/workflow/Snakefile @@ -1,28 +1,64 @@ +import hashlib +import importlib.util import sys -from pathlib import Path import yaml from snakemake.utils import min_version -sys.path.insert(0, workflow.basedir) -from scripts._schema import load_region_configs, validate_config +min_version("9.27") -min_version("9.19") +# Resolve auxiliary files through Snakemake so remote modules are cached too. +# A path-specific name keeps separate grid-builder versions isolated in one host. +_schema_path = workflow.source_path("scripts/_schema.py") +_schema_name = ( + f"_grid_builder_schema_{hashlib.sha256(str(_schema_path).encode()).hexdigest()}" +) +_schema_spec = importlib.util.spec_from_file_location(_schema_name, str(_schema_path)) +_schema = importlib.util.module_from_spec(_schema_spec) +sys.modules[_schema_name] = _schema +_schema_spec.loader.exec_module(_schema) + +# Snakemake adds the cached script directory to each script's import path. +workflow.source_path("scripts/_helpers.py") +for filename in ("colors.yaml", "tag_corrections.yaml"): + workflow.source_path(f"internal/{filename}") +# Remote directories cannot be enumerated: the manifest lists shipped overrides. +with open(workflow.source_path("../config/regions/index.yaml")) as f: + _region_codes = yaml.safe_load(f) +_region_paths = { + code: workflow.source_path(f"../config/regions/config.{code}.yaml") + for code in _region_codes +} -# Define pathvars to expose OSM retrieval outputs for downstream use. + +# Retrieval paths and public outputs can be rewired by a consuming workflow. pathvars: - # OSM retrieval outputs by country and feature - osm_retrieve="/retrieve", + osm_retrieve="/automatic/retrieve", + osm_lines_way="/{country}_lines_way.json", + osm_cables_way="/{country}_cables_way.json", + osm_substations_way="/{country}_substations_way.json", + osm_substations_node="/{country}_substations_node.json", + osm_substations_relation="/{country}_substations_relation.json", + osm_routes_relation="/{country}_routes_relation.json", + buses="/network/csv/buses.csv", + lines="/network/csv/lines.csv", + transformers="/network/csv/transformers.csv", + buses_geojson="/network/geojson/buses.geojson", + lines_geojson="/network/geojson/lines.geojson", + transformers_geojson="/network/geojson/transformers.geojson", + stations_polygon="/network/geojson/stations_polygon.geojson", + buses_polygon="/network/geojson/buses_polygon.geojson", + map="/map.html", # Default configuration file generated from pydantic schema. configfile: workflow.source_path("../config/config.yaml") -config = validate_config( - load_region_configs(config, Path(workflow.basedir).parent / "config" / "regions") -) +config = _schema.validate_config( + _schema.load_region_configs(config, _region_paths) +).model_dump(mode="json") # Load internal settings separately so users cannot modify them. with open(workflow.source_path("internal/settings.yaml"), "r") as f: diff --git a/workflow/envs/module.linux-64.pin.txt b/workflow/envs/module.linux-64.pin.txt new file mode 100644 index 0000000..ea25fca --- /dev/null +++ b/workflow/envs/module.linux-64.pin.txt @@ -0,0 +1,146 @@ +# Generated by `pixi workspace export` +# platform: linux-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda#239c5e9546c38a1e884d69effcf4c882 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda#a9f577daf3de00bca7c3c76c0ecbd1de +https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda#0aa00f03f9e39fb9876085dee11a85d4 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda#a77f85f77be52ff59391544bfe73390a +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-8_cp312.conda#c3efd25ac4d74b1584d2f7a57195ddf1 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda#d87ff7921124eccd67248aa483c23fec +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda#cffd3bdd58090148f4cfcd831f4b26ab +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda#fc21868a1a5aacc937e7a18747acb8a5 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda#d7d95fc8287ea7bf33e0e7116d2b95ec +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda#e18ad67cf881dcadee8b8d9e2f8e5f73 +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda#da1b85b6a87e141f5140bb9924cecab0 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda#d5e96b1ed75ca01906b3d2469b4ce493 +https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda#38ffe67b78c9d4de527be8315e5ada2c +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda#1b08cd684f34175e4514474793d44bcb +https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda#c80d8a3b84358cb967fa81e7075fbc8a +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.0-hf4e2dac_0.conda#810d83373448da85c3f673fbcb7ad3a3 +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda#d864d34357c3b65a4b731f78c0801dc4 +https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda#b88d90cad08e6bc8ad540cb310a761fb +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda#a360c33a5abe61c07959e449fa1453eb +https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.4-hd2095e1_0.conda#a77b44d6cbaad97f3ad050ab35fad55a +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda#4a13eeac0b5c8e5b8ab496e6c4ddd829 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda#18335a698559cdbcd86150a48bf54ba6 +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda#d2ffd7602c02f2b316fd921d39876885 +https://conda.anaconda.org/conda-forge/linux-64/python-3.12.13-hd63d673_0_cpython.conda#7eccb41177e15cc672e1babe9056018e +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda#15878599a87992e44c059731771591cb +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312hdb49522_1.conda#64088dffd7413a2dd557ce837b4cbbdb +https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.4.0-py312h90b7ffd_0.conda#542da724e75cdeef19e29cca23935c25 +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda#fb7130c190f9b4ec91219840a05ba3ac +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda#a9167b9571f3baa9d448faa2139d1089 +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda#929471569c93acefb30282a22060dcd5 +https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda#9659f587a8ceacc21864260acd02fc67 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda#9de5350a85c4a20c685259b889aa6393 +https://conda.anaconda.org/conda-forge/linux-64/pyosmium-4.3.1-py312hc7fdb42_1.conda#44bba8b7d3d64b1af5dedfcbc906067a +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda#646855f357199a12f02a87382d429b75 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda#9063115da5bc35fdc3e1002e69b9ef6e +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda#89d61bc91d3f39fda0ca10fcd3c68594 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda#6d6d225559bfa6e2f3c90ee9c03d4e2e +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda#881d801569b201c2e753f03c84b85e15 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda#36ae340a916635b97ac8a0655ace2a35 +https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py312h33ff503_0.conda#5930ee8a175a242b4f001b1e9e72024f +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda#93a4752d42b12943a355b682ee43285b +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_0.conda#7c94c948b32d681345f134ae11892fee +https://conda.anaconda.org/conda-forge/linux-64/libboost-1.90.0-h1101c1b_3.conda#bcabb17507a7c82f12e9a0cc67fd57c7 +https://conda.anaconda.org/conda-forge/linux-64/osmium-tool-1.19.1-h7ac1ec8_0.conda#14de95da725f036b4b55d67cdc0b0256 +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda#e5ce43272193b38c2e9037446c1d9206 +https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260107.1-cxx17_h7b12aa8_0.conda#6f7b4302263347698fd24565fbf11310 +https://conda.anaconda.org/conda-forge/linux-64/protobuf-6.33.5-py312ha7b3241_2.conda#0aee4f9ff95fc4bc78264a93e2a74adf +https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda#03cb60f505ad3ada0a95277af5faeb1a +https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda#eefc8d916bd2e708d76d40398ef9a1ee +https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda#3339e3b65d58accf4ca4fb8748ab16b3 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda#5b8d21249ff20967101ffa321cab24e8 +https://conda.anaconda.org/conda-forge/linux-64/pandas-2.3.3-py312hf79963d_1.conda#e597b3e812d9613f659b7d87ad252d18 +https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda#4487b9c371d0161d54b5c7bbd890c0fc +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.0-h04a0ce9_0.conda#dc540e5bd5616d83a1ec46af8315ff98 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda#aea31d2e5b1091feca96fcfe945c3cf9 +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda#6178c6f2fb254558238ef4e6c56fb782 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda#6c77a605a7a689d17d4819c0f8ac9a00 +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda#a752488c68f2e7c456bcbd8f16eec275 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda#cd5a90476766d53e901500df9215e927 +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.1-hcf80075_0.conda#eecce068c7e4eddeb169591baac20ac4 +https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda#172bf1cd1ff8629f2b1179945ed45055 +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda#920bb03579f15389b9e512095ad995b7 +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda#2a45e7f8af083626f009645a6481f12d +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda#c277e0a4d549b03ac1e9d6cbbe3d017b +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda#b38117a3c920364aff79f870c984b4a3 +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda#fb53fb07ce46a575c5d004bbc96032c2 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.20.0-hcf29cc6_0.conda#c3cc2864f82a944bc90a7beb4d3b0e88 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.1-he0df7b0_0.conda#b23619e5e9009eaa070ead0342034027 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.7.2-py312hbc8341d_4.conda#9d5ea11aefbdf548a2d8c965c1b2ebad +https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 +https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda#66a1db55ecdb7377d2b91f54cd56eafa +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda#7a3bff861a6583f1889021facefc08b1 +https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda#ab3e3db511033340e75e7002e80ce8c0 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda#915f5995e94f60e9a4826e0b0920ee88 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda#e79d2c2f24b027aa8d5ab1b1ba3061e7 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda#995d8c8bad2a3cc8db14675a153dec2b +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda#c2a01a08fc991620a74b32420e97868a +https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.3-h49c6c72_0.conda#be70cb50dd0ecc1531c58034d38f72e0 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda#4d4efd0645cd556fab54617c4ad477ef +https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda#df81fd57eacf341588d728c97920e86d +https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda#da01bb40572e689bd1535a5cee6b1d68 +https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda#ecb5d11305b8ba1801543002e69d2f2f +https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda#5947bdda89ced07ab0d8a5d6503082c3 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda#eba48a68a1a2b9d3c0d9511548db85db +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_18.conda#6235adb93d064ecdf3d44faee6f468de +https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda#d71d3a66528853c0a1ac2c02d79a0284 +https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1023.conda#953b7cca897e21215302dbfe2af5cd0c +https://conda.anaconda.org/conda-forge/linux-64/libhwy-1.4.0-h10be129_0.conda#3a9428b74c403c71048104d38437b48c +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda#72c8fd1af66bd67bf580645b426513ed +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda#4ffbb341c8b616aa2494b6afb26a0c5f +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda#366b40a69f0ad6072561c1d09301c886 +https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-h174a0a3_1.conda#850f48943d6b4589800a303f0de6a816 +https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda#45161d96307e3a447cc3eb5896cf6f8c +https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.7-gpl_hc2c16d8_100.conda#dbeb5c8321cb2408d406a3da16a0ff0d +https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda#38f5dbc9ac808e31c00650f7be1db93f +https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda#3bf7b9fd5a7136126e0234db4b87c8b6 +https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.2-h03e3b7b_1.conda#98b6c9dc80eb87b2519b97bcf7e578dd +https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda#2c2fae981fd2afd00812c92ac47d023d +https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.4-h08c5dba_2.conda#f5af6a9a075af46eff6a6a55a995e44d +https://conda.anaconda.org/conda-forge/linux-64/pyogrio-0.12.1-py312h053e1f3_0.conda#f8e7e5ddfbdca16b65335b0b6615eb4c +https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda#353823361b1d27eb3960efb076dfcaf6 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda#3687cc0b82a8b4c17e1f0eb7e47163d5 +https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda#2aadb0d17215603a82a2a6b0afd9a4cb +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda#11b3379b191f63139e29c0d19dee24cd +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda#1dafce8548e38671bea82e3f5c6ce22f +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda#b2895afaf55bf96a8c8282a2e47a5de0 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda#b3c17d95b5a10c6e64a21fa17573e70e +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda#92ed62436b625154323d40d5f2f11dd7 +https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda#fb16b4b69e3f1dcfe79d80db8fd0c55d +https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda#e289f3d17880e44b633ba911d57a321b +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19-h0c24ade_0.conda#f302dbf397ac82eaf9618575d0b5fe33 +https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py312h50c33e8_0.conda#9e5609720e31213d4f39afe377f6217e +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py312h0a2e395_0.conda#cd74a9525dc74bbbf93cf8aa2fa9eb5b +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_0.conda#8462b5322567212beeb025f3519fb3e2 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py312h4c3975b_0.conda#0b6c506ec1f272b685240e70a29261b8 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda#37293a85a0f4f77bbd9cf7aaefc62609 +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-hb03c661_1.conda#af39b9a8711d4a8d437b52c1d78eb6a1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-hed03a55_1.conda#8ccf913aaba749a5496c17629d859ed1 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.62.1-py312h8a5da7c_0.conda#2396fee22e84f69dffc6e23135905ce8 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda#4c2a8fef270f6c69591889b93f9f55c1 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py312h0a2e395_4.conda#43c2bc96af3ae5ed9e8a10ded942aa50 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.10.9-py312he3d6523_0.conda#7d499b5b6d150f133800dc3a582771c7 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.17.1-py312h54fa4ab_0.conda#3e38daeb1fb05a95656ff5af089d2e4c +https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda#9d64911b31d57ca443e9f1e36b04385f +https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda#8e194e7b992f99a5015edbd4ebd38efd +https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda#615de2a4d97af50c350e5cf160149e77 +https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.8.0-np2py312h3226591_1.conda#38decbeae260892040709cafc0514162 +https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda#a2c1eeadae7a309daed9d62c96012a2b +https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda#cc293b4cad9909bf66ca117ea90d4631 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.1.2-py312h383787d_2.conda#69e400d3deca12ee7afd4b73a5596905 +https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_0.conda#18789a85c307970ae1786dfc6dfd234f +https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda#1fcdf88e7a8c296d3df8409bf0690db4 +https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda#a6997a7dcd6673c0692c61dfeaea14ab +https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.3-pyhd8ed1ab_0.conda#4eb8b870142ca06d2a1d2c74662eac7d +https://conda.anaconda.org/conda-forge/noarch/earth-osm-3.0.2-pyhd8ed1ab_0.conda#eea693a0e40b4aa17590a63d055a068a diff --git a/workflow/envs/module.osx-arm64.pin.txt b/workflow/envs/module.osx-arm64.pin.txt new file mode 100644 index 0000000..a02f1f0 --- /dev/null +++ b/workflow/envs/module.osx-arm64.pin.txt @@ -0,0 +1,157 @@ +# Generated by `pixi workspace export` +# platform: osx-arm64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda#78a0fe9e9c50d2c381e8ee47e3ea437d +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda#4c32076993e6270825441d059ab5c18b +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_2.conda#bc5a5721b6439f2f62a84f2548136082 +https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda#a9d86bc62f39b94c4661716624eb21b0 +https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-h1d4f5a5_0.conda#343d10ed5b44030a2f67193905aea159 +https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda#f8381319127120ce51e081dce4865cf4 +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-hbd8a1cb_0.conda#e18ad67cf881dcadee8b8d9e2f8e5f73 +https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.2-hd24854e_0.conda#25dcccd4f80f1638428613e0d7c9b4e1 +https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-hef89b57_0.conda#f1182c91c0de31a7abd40cedf6a5ebef +https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda#fde96d40ebe9a9cb34e4122380189ddb +https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.4-h55c6f16_0.conda#448a1af83a9205655ee1cf48d3875ca3 +https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.12.14-h4e5ec87_3_cpython.conda#7fd5ad6d9d1ae0af3ae9f039a992a148 +https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_0.conda#b1fd823b5ae54fbec272cea0811bd8a9 +https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda#216bbcc23c11e9695b3db4a8771d76eb +https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.4-h6111c0a_0.conda#f161bcc1e6de30d76093b4534b8f1a77 +https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-hd037594_9.conda#620b85a3f45526a8bc4d23fd78fc22f0 +https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.14-hd05a0c4_3_cpython.conda#287a1be640df74bd3e2b9d76b2a7b300 +https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py312h04c11ed_1.conda#95a5f0831b5e0b1075bbd80fcffc52ac +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda#461219d1a5bd61342293efa2c0c90eac +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py312ha52686f_4.conda#a99e0c53fc3932defa0c89757c14b8ce +https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda#ab136e4c34e97f34fb621d2592a393d8 +https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.7.0-py312h1a36842_1.conda#4f0bd292b5aeab40ebcb3aae5afdf1a8 +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda#fb7130c190f9b4ec91219840a05ba3ac +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda#a9167b9571f3baa9d448faa2139d1089 +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda#929471569c93acefb30282a22060dcd5 +https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda#9659f587a8ceacc21864260acd02fc67 +https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.10.0-h286801f_1.conda#01511afc6cc1909c5303cf31be17b44f +https://conda.anaconda.org/conda-forge/osx-arm64/pyosmium-4.3.1-py312h892b0b2_1.conda#9608fc822b765f7a72282a2c2ef1de06 +https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.4-hc7d1edf_0.conda#46d04a647df7a4525e487d88068d19ef +https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda#a44032f282e7d2acdeb1c240308052dd +https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-15.2.0-hcbb3090_18.conda#92df6107310b1fff92c4cc84f0de247b +https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-15.2.0-hdae7583_18.conda#c4a6f7989cffb0544bfd9207b6789971 +https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-15.2.0-h07b0088_18.conda#26981599908ed2205366e8fc91b37fc6 +https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.32-openmp_he657e61_0.conda#3a1111a4b6626abebe8b978bb5a323bf +https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-6_h51639a9_openblas.conda#e551103471911260488a02155cef9c94 +https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-6_hd9741b5_openblas.conda#ee33d2d05a7c5ea1f67653b37eb74db1 +https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-6_hb0561ab_openblas.conda#805c6d31c5621fd75e53dfcf21fb243a +https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.3-py312hff34920_0.conda#6c950b1c9f19db9df914704508f3be20 +https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py312h04c11ed_1.conda#11d95ab83ef0a82cc2de12c1e0b47fe4 +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_0.conda#7c94c948b32d681345f134ae11892fee +https://conda.anaconda.org/conda-forge/osx-arm64/libboost-1.90.0-h30c325d_3.conda#71cd9902ca7347767f4ea7170c0acdbb +https://conda.anaconda.org/conda-forge/osx-arm64/osmium-tool-1.19.1-h693c552_0.conda#8d50fef448dfb3621b347453e57e584c +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda#e5ce43272193b38c2e9037446c1d9206 +https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260526.0-cxx17_h4c27e2a_2.conda#2aa5e7dc7b5d218908effd2a8c70a8a8 +https://conda.anaconda.org/conda-forge/osx-arm64/protobuf-7.35.1-py312h5674827_3.conda#b3a91d2e886988232fb2eedc016aa22c +https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda#03cb60f505ad3ada0a95277af5faeb1a +https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda#eefc8d916bd2e708d76d40398ef9a1ee +https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda#3339e3b65d58accf4ca4fb8748ab16b3 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda#5b8d21249ff20967101ffa321cab24e8 +https://conda.anaconda.org/conda-forge/osx-arm64/pandas-2.3.3-py312h5978115_2.conda#fcbba82205afa4956c39136c68929385 +https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda#4487b9c371d0161d54b5c7bbd890c0fc +https://conda.anaconda.org/conda-forge/osx-arm64/sqlite-3.53.4-ha9f7ffe_1.conda#c144cc02c82671616e0fb4caf731bc88 +https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h07db88b_0.conda#e5e7d467f80da752be17796b87fe6385 +https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda#b2f8c8e5a7651a1d7c05404c3a159517 +https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-hc11a715_0.conda#a6130c709305cd9828b4e1bd9ba0000c +https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda#e429aec4037d5cb8fd34ded9f5dadd39 +https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-hf67920b_1.conda#b6b191267455bf202af79f973690ed5d +https://conda.anaconda.org/conda-forge/osx-arm64/libssh2-1.11.1-h1590b86_0.conda#b68e8f66b94b44aaa8de4583d3d4cc40 +https://conda.anaconda.org/conda-forge/osx-arm64/libpsl-0.23.1-hdb0c161_1.conda#5d270f716a2b4bc13df9af8612071b17 +https://conda.anaconda.org/conda-forge/osx-arm64/libev-4.33-h93a5062_2.conda#36d33e440c31857372a72137f78bacf5 +https://conda.anaconda.org/conda-forge/osx-arm64/c-ares-1.34.6-hc919400_0.conda#bcb3cba70cf1eec964a03b4ba7775f01 +https://conda.anaconda.org/conda-forge/osx-arm64/libnghttp2-1.68.1-h8f3e76b_0.conda#6ea18834adbc3b33df9bd9fb45eaf95b +https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321hafb1f1b_0.conda#44083d2d2c2025afca315c7a172eab2b +https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h385eeb1_0.conda#e446e1822f4da8e5080a9de93474184d +https://conda.anaconda.org/conda-forge/osx-arm64/libcurl-8.22.0-h6651222_0.conda#a7234b8d8e19a089dcb1eaaa18de1045 +https://conda.anaconda.org/conda-forge/osx-arm64/proj-9.9.0-h51dee2d_0.conda#3e1d545c468056c085841e97773978fd +https://conda.anaconda.org/conda-forge/osx-arm64/pyproj-3.8.0-py312h16d7083_2.conda#46be4aa3f1d295deba4ee929795d4d97 +https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 +https://conda.anaconda.org/conda-forge/osx-arm64/xerces-c-3.3.0-h25f632f_1.conda#0b886d06130b774f086d3b2ce0b7277a +https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda#9b4190c4055435ca3502070186eba53a +https://conda.anaconda.org/conda-forge/osx-arm64/muparser-2.3.5-h11e0b38_0.conda#1cdbe54881794ee356d3cba7e3ed6668 +https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-h23cfdf5_2.conda#4d5a7445f0b25b6a3ddbb56e790f5251 +https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.4-heb56d2d_0.conda#b40633f711a5eb8617917e0e944b9965 +https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.4-h550178d_0.conda#4b97a34d06740b578c32deba195daf5a +https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_2.conda#f1c0bce276210bed45a04949cfe8dc20 +https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-devel-2.15.4-h550178d_0.conda#df2f9521a1b80a859d6d329dba7df2a0 +https://conda.anaconda.org/conda-forge/osx-arm64/geos-3.14.1-h5afe852_0.conda#4238412c29eff0bb2bb5c60a720c035a +https://conda.anaconda.org/conda-forge/osx-arm64/librttopo-1.1.0-ha909e78_20.conda#d07359797436cfc891b38e203cf0caac +https://conda.anaconda.org/conda-forge/osx-arm64/minizip-4.0.10-hff1a8ea_0.conda#93def148863d840e500490d6d78722f9 +https://conda.anaconda.org/conda-forge/osx-arm64/freexl-2.0.0-h3ab3353_2.conda#dd655a29b40fe0d1bf95c64cf3cb348d +https://conda.anaconda.org/conda-forge/osx-arm64/libspatialite-5.1.0-gpl_h16ba230_122.conda#59c144f60bb80b88854f96f62d0936ad +https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-h132b30e_0.conda#2259ae0949dbe20c0665850365109b27 +https://conda.anaconda.org/conda-forge/osx-arm64/uriparser-0.9.8-h00cdb27_0.conda#e8ff9e11babbc8cd77af5a4258dc2802 +https://conda.anaconda.org/conda-forge/osx-arm64/libkml-1.3.0-hb833057_1023.conda#3d8eeedb8e541d1cb593e8204fcc397d +https://conda.anaconda.org/conda-forge/osx-arm64/libhwy-1.4.0-ha332bbd_0.conda#7394850583ca88325244b68b532c7a39 +https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-h1dcdb26_4.conda#8f3981871d167a6cd323e636e1929dd4 +https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-h2ddc9cb_4.conda#39a25d500b191c16996239c4afa104f1 +https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-h5295a6a_4.conda#da537a1643ef3e13bdccf16cca206f2c +https://conda.anaconda.org/conda-forge/osx-arm64/libjxl-0.12.0-hb71b141_2.conda#f62de5ae42f3c1f54f87ae2b303f6e90 +https://conda.anaconda.org/conda-forge/osx-arm64/lzo-2.10-h925e9cb_1002.conda#e56eaa1beab0e7fed559ae9c0264dd88 +https://conda.anaconda.org/conda-forge/osx-arm64/libarchive-3.8.9-gpl_h76147b0_101.conda#bdcec8546f15ded1b3718309e027e603 +https://conda.anaconda.org/conda-forge/osx-arm64/json-c-0.18-he4178ee_0.conda#94f14ef6157687c30feb44e1abecd577 +https://conda.anaconda.org/conda-forge/osx-arm64/giflib-6.1.3-h74c22ad_2.conda#edc562dcbe5872664817e64575aa325f +https://conda.anaconda.org/conda-forge/osx-arm64/snappy-1.2.2-hada39a4_1.conda#fca4a2222994acd7f691e57f94b750c5 +https://conda.anaconda.org/conda-forge/osx-arm64/blosc-1.21.6-h7dd00d9_1.conda#925acfb50a750aa178f7a0aced77f351 +https://conda.anaconda.org/conda-forge/osx-arm64/libgdal-core-3.13.3-hc1e97f0_2.conda#298c053b25279574b587cfbc3e24c1ee +https://conda.anaconda.org/conda-forge/osx-arm64/pyogrio-0.13.0-py312hd1c4548_0.conda#8a4e8760220acc83b445a0feb56d7b7a +https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda#6483b1f59526e05d7d894e466b5b6924 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda#3687cc0b82a8b4c17e1f0eb7e47163d5 +https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda#d99c2a23a31b0172e90f456f580b695e +https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda#4b5d3a91320976eec71678fad1e3569b +https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda#9d1299ace1924aa8f4e0bc8e71dd0cf7 +https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda#78b548eed8227a689f93775d5d23ae09 +https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-hd74edd7_1002.conda#415816daf82e0b23a736a069a75e9da7 +https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hdb1d25a_0.conda#af523aae2eca6dfa1c8eec693f5b9a79 +https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-hdfa99f5_0.conda#e98ba7b5f09a5f450eca083d5a1c4649 +https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_0.conda#f73b109d49568d5d1dda43bb147ae37f +https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hce41798_3.conda#4eed1ee02ef1992c046f2e81d5606633 +https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py312hf9dc30a_4.conda#1c7b0a12a4657e8a296306b32712f0ed +https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda#5103f6a6b210a3912faf8d7db516918c +https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.90.0-hccd281b_0.conda#8f63a5f35547d0c6d819830ebe0d3245 +https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.15-h784d473_1.conda#0c7b78d9ffff4f5a6ca28d346f734d8f +https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda#17c3d745db6ea72ae2fce17e7338547f +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda#49023d73832ef61042f6a237cb2687e7 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda#a7970cd949a077b7cb9696379d338681 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.17.1-h2b252f5_0.conda#d06ae1a11b46cc4c74177ecd28de7c7a +https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda#36200ecfbbfbcb82063c87725434161f +https://conda.anaconda.org/conda-forge/osx-arm64/libharfbuzz-14.4.0-hcda0f7c_1.conda#4afba4fae30b467d6c699bb685fa35ce +https://conda.anaconda.org/conda-forge/osx-arm64/fribidi-1.0.16-hc919400_0.conda#04bdce8d93a4ed181d1d726163c2d447 +https://conda.anaconda.org/conda-forge/osx-arm64/libraqm-0.11.0-h45af499_0.conda#46ea0eb4e71bffa35ab7070678bcb053 +https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.1-py312h67ba313_3.conda#0f9c3cfcf6106a7f6e99acf39095d3a6 +https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_0.conda#6dcc75ba2e04c555e881b72793d3282f +https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-18.0.0-py312ha053593_0.conda#639c032b3599544afe156821a816895b +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda#37293a85a0f4f77bbd9cf7aaefc62609 +https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-he4a93e4_4.conda#d065d0732d02ace747059c3d58338fcd +https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-hf00d406_4.conda#e32ad50b0f977e3fa616a578a2643e46 +https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.65.0-py312hf4454c7_0.conda#4e3e046489bab9885148f2b228108d5d +https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda#4c2a8fef270f6c69591889b93f9f55c1 +https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.4.0-py312h67ba313_1.conda#e2ed1d619ef9e3e0fb5f4703d029d8e2 +https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.11.2-py312h11997a8_1.conda#5b7f99b5b32911c24f0e40e3cf38861e +https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py312h4519d97_0.conda#5a352ca7e4f49ca6585dd30a4812bd20 +https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda#9d64911b31d57ca443e9f1e36b04385f +https://conda.anaconda.org/conda-forge/noarch/narwhals-2.26.0-pyh5ded981_0.conda#afb7146667be7c8121f5f8337be60680 +https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda#8e194e7b992f99a5015edbd4ebd38efd +https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda#615de2a4d97af50c350e5cf160149e77 +https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.1-np2py312hda66c2e_1.conda#981a4a060c154c440ebb298ad6f96b24 +https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda#a2c1eeadae7a309daed9d62c96012a2b +https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda#cc293b4cad9909bf66ca117ea90d4631 +https://conda.anaconda.org/conda-forge/osx-arm64/shapely-2.1.2-py312h0fc4a54_3.conda#24f93f977d15383977247b1b6e356626 +https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_0.conda#18789a85c307970ae1786dfc6dfd234f +https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda#1fcdf88e7a8c296d3df8409bf0690db4 +https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda#a6997a7dcd6673c0692c61dfeaea14ab +https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.3-pyhd8ed1ab_0.conda#4eb8b870142ca06d2a1d2c74662eac7d +https://conda.anaconda.org/conda-forge/noarch/earth-osm-3.0.2-pyhd8ed1ab_0.conda#eea693a0e40b4aa17590a63d055a068a diff --git a/workflow/envs/module.win-64.pin.txt b/workflow/envs/module.win-64.pin.txt new file mode 100644 index 0000000..8c160b6 --- /dev/null +++ b/workflow/envs/module.win-64.pin.txt @@ -0,0 +1,149 @@ +# Generated by `pixi workspace export` +# platform: win-64 +@EXPLICIT +https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda#71b24316859acd00bdb8b38f5e2ce328 +https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda#242d9f25d2ae60c76b38a5e42858e51d +https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda#37eb311485d2d8b2c419449582046a42 +https://conda.anaconda.org/conda-forge/win-64/vc-14.3-h41ae7f8_34.conda#1e610f2416b6acdd231c5f573d754a0f +https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda#433699cba6602098ae8957a323da2664 +https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda#4c32076993e6270825441d059ab5c18b +https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda#ad659d0a2b3e47e38d829aa8cad2d610 +https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda#0481bfd9814bf525bd4b3ee4b51494c4 +https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.4.22-h4c7d964_0.conda#56fb2c6c73efc627b40c77d14caecfba +https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.2-hf411b9b_0.conda#05c7d624cff49dbd8db1ad5ba537a8a3 +https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda#dbabbd6234dea34040e631f87676292f +https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda#a72d495965b144bb7da033642d389047 +https://conda.anaconda.org/conda-forge/win-64/libpython-3.12.14-hcc31f7b_3_cpython.conda#2a7b3fc2441dc4df29a5804d677fb720 +https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_0.conda#8f83619ab1588b98dd99c90b0bfc5c6d +https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_1.conda#ceb38b0ba900847d8da4289c3c8e5708 +https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.4-hac47afa_0.conda#7e9023b0034e87d324ad64809859184e +https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda#4cb8e6b48f67de0b018719cdf1136306 +https://conda.anaconda.org/conda-forge/win-64/python-3.12.14-hb12b558_3_cpython.conda#ca6b5db05602d2a98f8694c2ad6aa6fb +https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py312h05f76fc_1.conda#9f6ebef672522cb9d9a6257215ca5743 +https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda#46e441ba871f524e2b067929da3051c2 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda#e2fd202833c4a981ce8a65974fe4abd1 +https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda#8e6923fc12f1fe8f8c4e5c9f343256ac +https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda#0a802cb9888dd14eeefc611f05c40b6e +https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda#164fc43f0b53b6e3a7bc7dce5e4f1dc9 +https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py312ha763cb9_4.conda#fd844f78ba9802979d0e6d856d7bcac3 +https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda#053b84beec00b71ea8ff7a4f84b55207 +https://conda.anaconda.org/conda-forge/win-64/backports.zstd-1.7.0-py312h06d0912_1.conda#fd4bf31a33cda994eea805305cf1ed97 +https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda#9272daa869e03efe68833e3dc7a02130 +https://conda.anaconda.org/conda-forge/noarch/idna-3.13-pyhcf101f3_0.conda#fb7130c190f9b4ec91219840a05ba3ac +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda#a9167b9571f3baa9d448faa2139d1089 +https://conda.anaconda.org/conda-forge/noarch/certifi-2026.4.22-pyhd8ed1ab_0.conda#929471569c93acefb30282a22060dcd5 +https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_1.conda#9659f587a8ceacc21864260acd02fc67 +https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda#0b69331897a92fac3d8923549d48d092 +https://conda.anaconda.org/conda-forge/win-64/pyosmium-4.3.1-py312h3e3ada1_1.conda#11fe8c8b785065defc642792be2a591a +https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda#64571d1dd6cdcfa25d0664a5950fdaa2 +https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h637d24d_0.conda#0097b24800cb696915c3dbd1f5335d3f +https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.4-h3cfd58e_0.conda#bab5489f4bd38494ffaa83d6ab924c1f +https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.4-he095d88_0.conda#3ce54d0f8f24dfab99d82414411640aa +https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda#8a86073cf3b343b87d03f41790d8b4e5 +https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda#3b576f6860f838f950c570f4433b086e +https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-ha3553a1_1.conda#34aa94d586fe95fa121966c0d4e73cf4 +https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2025.3.1-h57928b3_12.conda#29ad104e60faa7ed1dc549ec029764bb +https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.4-h4fa8253_0.conda#761757ab617e8bfef18cc422dd02bbad +https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_12.conda#a3b1ee571898432da7e13ecb7bfd76ae +https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda#95543eec964b4a4a7ca3c4c9be481aa1 +https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda#7e9cdaf6f302142bc363bbab3b5e7074 +https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda#9e4bf521c07f4d423cba9296b7927e3c +https://conda.anaconda.org/conda-forge/win-64/numpy-2.5.3-py312ha3f287d_0.conda#78895c70424556e2bf7bf2f628fc8778 +https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py312h05f76fc_1.conda#a73298d225c7852f97403ca105d10a13 +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda#04558c96691bed63104678757beb4f8d +https://conda.anaconda.org/conda-forge/noarch/pydeck-0.9.3-pyhd8ed1ab_0.conda#7c94c948b32d681345f134ae11892fee +https://conda.anaconda.org/conda-forge/win-64/libboost-1.90.0-h9dfe17d_3.conda#15620ea8e1fa6d5eb275ee8665f88b32 +https://conda.anaconda.org/conda-forge/win-64/osmium-tool-1.19.1-h60971b7_0.conda#c5ea6678eb9b3a4c7de28dd592e68662 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda#962b9857ee8e7018c22f2776ffa0b2d7 +https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyha7b4d00_0.conda#af77160f8428924c17db94e04aa69409 +https://conda.anaconda.org/conda-forge/win-64/protobuf-7.35.1-py312h87014b1_3.conda#92f82e85bdf7b9e69f7406c7c71c8e7d +https://conda.anaconda.org/conda-forge/noarch/pytz-2026.2-pyhcf101f3_0.conda#03cb60f505ad3ada0a95277af5faeb1a +https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda#f6ad7450fc21e00ecc23812baed6d2e4 +https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda#3339e3b65d58accf4ca4fb8748ab16b3 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda#5b8d21249ff20967101ffa321cab24e8 +https://conda.anaconda.org/conda-forge/win-64/pandas-2.3.3-py312hc128f0a_2.conda#57d80e87a8b3161bcf26472deceaa556 +https://conda.anaconda.org/conda-forge/noarch/xyzservices-2026.3.0-pyhd8ed1ab_0.conda#4487b9c371d0161d54b5c7bbd890c0fc +https://conda.anaconda.org/conda-forge/win-64/sqlite-3.53.4-hdb435a2_1.conda#df9d7c5d34602e0f2655a524d31fce87 +https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda#fc2c23bacefd1e733f1e1d6cd3b2aaeb +https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda#e77030e67343e28b084fabd7db0ce43e +https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda#add59e2b60ac9d4299d17c938185c75a +https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_1.conda#970dbe48f843e7fbd179a9b6c22f865a +https://conda.anaconda.org/conda-forge/win-64/libssh2-1.11.1-h9aa295b_0.conda#9dce2f112bfd3400f4f432b3d0ac07b2 +https://conda.anaconda.org/conda-forge/win-64/libpsl-0.23.1-h9b16d47_1.conda#39cdf8c4f59e4d253cfa8091c8b3d7de +https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda#4432f52dc0c8eb6a7a6abc00a037d93c +https://conda.anaconda.org/conda-forge/win-64/libcurl-8.22.0-hdb0ef4a_0.conda#e8405e754eaac42d66f957222fcfd876 +https://conda.anaconda.org/conda-forge/win-64/proj-9.9.0-hd30e2cd_0.conda#0b883556cc76611b4231a33577a26335 +https://conda.anaconda.org/conda-forge/win-64/pyproj-3.8.0-py312h5a521b9_2.conda#2e48fc674b51a8a1bcfc887fc18f2abb +https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda#58335b26c38bf4a20f399384c33cbcf9 +https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda#d1097e01041cfed41c81f1e3d1f52572 +https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda#77eaf2336f3ae749e712f63e36b0f0a1 +https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda#013aabb169d59009bdf7d70319360e9b +https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_0.conda#f9bbae5e2537e3b06e0f7310ba76c893 +https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda#5187ecf958be3c39110fe691cbd6873e +https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.4-he095d88_0.conda#d50e00d25a326eb487f9f5540e09b77b +https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda#8c75d7e401a4d799ce8d4bb922320967 +https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda#7eeb5aed49853f8b3e1ca0463ef55a8e +https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda#2ffdc180adc65f509e996d63513c04b7 +https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda#d6a8059de245e53478b581742b53f71d +https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_hfd1cb16_122.conda#6bb7178f7f280a88c1a79c0c0a79213a +https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-h7351971_0.conda#52f1280563f3b48b5f75414cd2d15dd1 +https://conda.anaconda.org/conda-forge/win-64/uriparser-0.9.8-h5a68840_0.conda#28b4cf9065681f43cc567410edf8243d +https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1023.conda#b44e0d9eb84c6c086d809787aab0a1da +https://conda.anaconda.org/conda-forge/win-64/libhwy-1.4.0-h172a326_0.conda#aeca1cb6665f19e560c1fbd20b5bcf34 +https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hf02afa3_4.conda#35d7e4a581c8364e8c675f6b95d183d0 +https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-he2a975b_4.conda#562b5e39b7797423e7fd04bae1c6ad70 +https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-h84f9c24_4.conda#1b4637ca71b21c0d31ab28c3c0b34c8e +https://conda.anaconda.org/conda-forge/win-64/libjxl-0.12.0-h932607e_2.conda#cf415a2296a1d862e93559645c91fd30 +https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda#c5cb4159f0eea65663b31dd1e49bbb71 +https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.9-gpl_h8fdc8af_101.conda#74c32a74e1b188a4e14fc359c721e4b9 +https://conda.anaconda.org/conda-forge/win-64/snappy-1.2.2-h7fa0ca8_1.conda#3075846de68f942150069d4289aaad63 +https://conda.anaconda.org/conda-forge/win-64/blosc-1.21.6-hfd34d9b_1.conda#357d7be4146d5fec543bfaa96a8a40de +https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.13.3-h6864167_2.conda#39b2b64a9e7b6e5320659d5167c843a6 +https://conda.anaconda.org/conda-forge/win-64/pyogrio-0.13.0-py312h0e78342_0.conda#d915f85f4ddce1a8827055b6dcd8d217 +https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda#854fbdff64b572b5c0b470f334d34c11 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda#3687cc0b82a8b4c17e1f0eb7e47163d5 +https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda#46a21c0a4e65f1a135251fc7c8663f83 +https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda#e723ab7cc2794c954e1b22fde51c16e4 +https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_0.conda#f9975a0177ee6cdda10c86d1db1186b0 +https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_0.conda#d9f70dd06674e26b6d5a657ddd22b568 +https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_3.conda#a817b8a8f09864652c8700bd1893fe81 +https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py312h7bb5964_4.conda#7088244066ff5fbc9b786805a4d918cf +https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda#2cf0cf76cc15d360dfa2f17fd6cf9772 +https://conda.anaconda.org/conda-forge/win-64/libglib-2.90.0-ha564072_0.conda#29c689012c8a53a0e36a70dfd43227e4 +https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.15-h5112557_1.conda#79538e7a7bc024084eda5989f73fde35 +https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda#08c8fa3b419df480d985e304f7884d35 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda#49023d73832ef61042f6a237cb2687e7 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda#a7970cd949a077b7cb9696379d338681 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.17.1-hd47e2ca_0.conda#a0b1b87e871011ca3b783bbf410bc39f +https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda#52ea1beba35b69852d210242dd20f97d +https://conda.anaconda.org/conda-forge/win-64/libharfbuzz-14.4.0-h03b5201_1.conda#d7b92075b7461e40fd8e90f08669a742 +https://conda.anaconda.org/conda-forge/win-64/fribidi-1.0.16-hfd05255_0.conda#c27bd87e70f970010c1c6db104b88b18 +https://conda.anaconda.org/conda-forge/win-64/libraqm-0.11.0-h50d6d30_0.conda#df92be5685f712989830bdb7ee39383a +https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.1-py312h78d62e6_3.conda#b400442c59cd935bf39778129e13f549 +https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_0.conda#507b36518b5a595edda64066c820a6ef +https://conda.anaconda.org/conda-forge/win-64/unicodedata2-18.0.0-py312he06e257_0.conda#99623d0df152a82f2ef345e8add291e1 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda#37293a85a0f4f77bbd9cf7aaefc62609 +https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hd477307_4.conda#5353bd43aaade891fc9b614c5d537ce3 +https://conda.anaconda.org/conda-forge/win-64/brotli-1.2.0-hc8c2fe1_4.conda#bdbb2939efe8af10e7c5885367395710 +https://conda.anaconda.org/conda-forge/win-64/fonttools-4.65.0-py312h05f76fc_0.conda#2f922eaa948c1ae762c088582ad34b18 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda#4c2a8fef270f6c69591889b93f9f55c1 +https://conda.anaconda.org/conda-forge/win-64/contourpy-1.4.0-py312h78d62e6_1.conda#fa5b76e5fabf5cf5ac3660bd33c659d0 +https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.11.2-py312hf9659c1_1.conda#f199e184d70b2de6bc1ec1129da2690a +https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.1-py312h8847828_1.conda#b617dec223af024a143fe34569ec7b1d +https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda#9d64911b31d57ca443e9f1e36b04385f +https://conda.anaconda.org/conda-forge/noarch/narwhals-2.26.0-pyh5ded981_0.conda#afb7146667be7c8121f5f8337be60680 +https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda#8e194e7b992f99a5015edbd4ebd38efd +https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda#615de2a4d97af50c350e5cf160149e77 +https://conda.anaconda.org/conda-forge/win-64/scikit-learn-1.9.1-np2py312hea30aaf_1.conda#eb91fb49540ffb040c0cb445bb22988f +https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda#a2c1eeadae7a309daed9d62c96012a2b +https://conda.anaconda.org/conda-forge/noarch/mapclassify-2.10.0-pyhd8ed1ab_1.conda#cc293b4cad9909bf66ca117ea90d4631 +https://conda.anaconda.org/conda-forge/win-64/shapely-2.1.2-py312h37f46ab_3.conda#a74fffec4ad2a2893de16bf2acdcddf5 +https://conda.anaconda.org/conda-forge/noarch/geopandas-base-1.1.3-pyha770c72_0.conda#18789a85c307970ae1786dfc6dfd234f +https://conda.anaconda.org/conda-forge/noarch/branca-0.8.2-pyhd8ed1ab_0.conda#1fcdf88e7a8c296d3df8409bf0690db4 +https://conda.anaconda.org/conda-forge/noarch/folium-0.20.0-pyhd8ed1ab_0.conda#a6997a7dcd6673c0692c61dfeaea14ab +https://conda.anaconda.org/conda-forge/noarch/geopandas-1.1.3-pyhd8ed1ab_0.conda#4eb8b870142ca06d2a1d2c74662eac7d +https://conda.anaconda.org/conda-forge/noarch/earth-osm-3.0.2-pyhd8ed1ab_0.conda#eea693a0e40b4aa17590a63d055a068a diff --git a/workflow/envs/module.yaml b/workflow/envs/module.yaml new file mode 100644 index 0000000..bf9150e --- /dev/null +++ b/workflow/envs/module.yaml @@ -0,0 +1,20 @@ +name: module +channels: +- conda-forge +- bioconda +- nodefaults +dependencies: +- python >=3.12,<3.13 +- pyproj >=3.7,<4 +- pyyaml >=6,<7 +- earth-osm >=3.0.2 +- geopandas >=1.0,<2 +- networkx >=3,<4 +- numpy >=2,<3 +- osmium-tool >=1.19,<2 +- pandas >=2,<3 +- pyogrio >=0.10,<1 +- pyosmium >=4,<5 +- pydeck >=0.9,<1 +- requests >=2,<3 +- shapely >=2,<3 diff --git a/workflow/envs/network.yaml b/workflow/envs/network.yaml deleted file mode 100644 index 451eca8..0000000 --- a/workflow/envs/network.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: network -channels: - - conda-forge - - nodefaults -dependencies: - - python >=3.12,<3.13 - - geopandas >=1.0,<2 - - networkx >=3,<4 - - numpy >=2,<3 - - pandas >=2,<3 - - pyogrio >=0.10,<1 - - pyproj >=3.7,<4 - - pydeck >=0.9,<1 - - pyyaml >=6,<7 - - shapely >=2,<3 diff --git a/workflow/envs/retrieve.yaml b/workflow/envs/retrieve.yaml deleted file mode 100644 index c201444..0000000 --- a/workflow/envs/retrieve.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: retrieve -channels: - - conda-forge - - nodefaults -dependencies: - - python >=3.12,<3.13 - - earth-osm =3.0.2 - - osmium-tool >=1.19,<2 - - pyosmium >=4,<5 - - pyproj >=3.7,<4 - - requests >=2,<3 diff --git a/workflow/internal/config.schema.yaml b/workflow/internal/config.schema.yaml new file mode 100644 index 0000000..ac257fc --- /dev/null +++ b/workflow/internal/config.schema.yaml @@ -0,0 +1,188 @@ +additionalProperties: false +description: Top-level grid-builder config. +properties: + countries: + default: + - BE + description: List of countries to retrieve OSM data for + items: + type: string + minItems: 1 + type: array + retrieve: + additionalProperties: false + description: Settings for retrieve_osm_pbf.py/retrieve_osm_overpass.py. + properties: + source: + default: geofabrik + description: Retrieval backend for OSM data + enum: + - geofabrik + - overpass + type: string + force_redownload: + default: false + description: Force refresh of cached data + type: boolean + target_date: + anyOf: + - format: date-time + type: string + - type: 'null' + default: + description: 'Optional historical date for data retrieval in ISO 8601 datetime format. Only honoured for retrieve.source: geofabrik.' + overpass_api: + additionalProperties: false + description: Settings for retrieve_osm_overpass.py's own Overpass API client. + properties: + url: + default: https://overpass-api.de/api/interpreter + description: Overpass API endpoint to query + type: string + max_tries: + default: 5 + description: Maximum number of attempts per query before giving up + minimum: 1 + type: integer + timeout: + default: 600 + description: Per-request timeout in seconds + exclusiveMinimum: 0 + type: integer + user_agent: + additionalProperties: false + description: Identifies this tool to the Overpass API, per its fair-use policy. + properties: + project_name: + default: grid-builder + description: Project name sent in the User-Agent header + type: string + email: + default: '' + description: Contact email sent in the User-Agent header + type: string + website: + default: https://github.com/pypsa/grid-builder + description: Project URL sent in the User-Agent header + type: string + network: + additionalProperties: false + description: Global assumptions for cleaning and connecting OSM grid features. + properties: + include_relations: + default: true + description: Whether the network should consider OSM route=power/power=circuit relations, grouping their member ways into a single line matching the relation's real-world circuit; retrieval respects this too, so relations aren't fetched at all when it's off + type: boolean + minimum_voltage_kv: + default: 220.0 + description: Minimum nominal AC voltage retained from OSM, in kV + exclusiveMinimum: 0 + type: number + frequency_hz: + additionalProperties: false + description: AC/DC frequency in Hz, used to classify and normalise OSM ``frequency`` tags. + properties: + AC: + default: 50.0 + description: AC frequency in Hz (50 for most of the world; 60 for the Americas and parts of Asia) + exclusiveMinimum: 0 + type: number + DC: + default: 0.0 + description: Frequency tag value OSM uses to mark a DC line/converter + minimum: 0 + type: number + station_merge_radius_m: + default: 500.0 + description: Buffer radius used to merge nearby substations and line endpoints, in metres + exclusiveMinimum: 0 + type: number + remove_under_construction: + default: true + description: Whether assets tagged as under construction are dropped + type: boolean + remove_after: + anyOf: + - format: date + type: string + - type: 'null' + default: '2026-12-31' + description: Exclude assets with a later planned start date; null disables this filter + regions: + additionalProperties: + additionalProperties: false + description: Optional country-specific overrides loaded from config/regions. + properties: + minimum_voltage_kv: + anyOf: + - exclusiveMinimum: 0 + type: number + - type: 'null' + default: + frequency_hz: + anyOf: + - additionalProperties: false + description: Optional per-country AC/DC override; unset fields fall back to network.frequency_hz. + properties: + AC: + anyOf: + - exclusiveMinimum: 0 + type: number + - type: 'null' + default: + DC: + anyOf: + - minimum: 0 + type: number + - type: 'null' + default: + - type: 'null' + default: + description: Country-specific network overrides loaded from config/regions + type: object + interactive_map: + additionalProperties: false + description: Geometry simplification and coordinate rounding for build_interactive_map.py. + properties: + coordinate_decimals: + default: 5 + description: Decimal places kept for coordinates embedded in the interactive map; 5 is about 1.1m of precision at the equator, comfortably below every simplification tolerance below + minimum: 0 + type: integer + simplify_geometries: + additionalProperties: false + description: Douglas-Peucker simplification tolerances for the interactive map. + properties: + enable: + default: true + description: Whether to simplify station/bus-polygon/line geometries before embedding them in the interactive map + type: boolean + stations_m: + default: 100.0 + description: Simplification tolerance for station polygon outlines, in metres + minimum: 0 + type: number + buses_polygon_m: + default: 5.0 + description: Simplification tolerance for individual bus/substation footprint polygons, in metres + minimum: 0 + type: number + lines_m: + default: 30.0 + description: Simplification tolerance for line geometries, in metres + minimum: 0 + type: number + crs: + additionalProperties: false + description: Coordinate reference systems used throughout clean/build_network. + properties: + geo: + default: EPSG:4326 + description: Geographic CRS used to store and exchange coordinates + type: string + distance: + default: EPSG:3035 + description: Equal-area/equal-distance CRS used for buffering and length calculations; must suit the geographic extent in use (the default, ETRS89-LAEA, covers Europe) + type: string +title: ConfigSchema +type: object diff --git a/workflow/rules/network.smk b/workflow/rules/network.smk index 3d5476b..66faa3c 100644 --- a/workflow/rules/network.smk +++ b/workflow/rules/network.smk @@ -6,49 +6,47 @@ rule clean: input: lines_way=expand( - "/retrieve/{country}_lines_way.json", + workflow.pathvars.apply(""), country=config["countries"], ), cables_way=expand( - "/retrieve/{country}_cables_way.json", + workflow.pathvars.apply(""), country=config["countries"], ), substations_way=expand( - "/retrieve/{country}_substations_way.json", + workflow.pathvars.apply(""), country=config["countries"], ), substations_node=expand( - "/retrieve/{country}_substations_node.json", + workflow.pathvars.apply(""), country=config["countries"], ), substations_relation=expand( - "/retrieve/{country}_substations_relation.json", + workflow.pathvars.apply(""), country=config["countries"], ), routes_relation=( expand( - "/retrieve/{country}_routes_relation.json", + workflow.pathvars.apply(""), country=config["countries"], ) if config["network"]["include_relations"] else [] ), output: - substations="/clean/substations.geojson", - substations_polygon="/clean/substations_polygon.geojson", - lines="/clean/lines.geojson", + substations="/automatic/clean/substations.geojson", + substations_polygon="/automatic/clean/substations_polygon.geojson", + lines="/automatic/clean/lines.geojson", log: "/clean.log", conda: - "../envs/network.yaml" + "../envs/module.yaml" threads: 1 params: - network=config["network"].model_dump(mode="json"), - regions={ - code: value.model_dump(mode="json") - for code, value in config["regions"].items() - }, - crs=config["crs"].model_dump(mode="json"), + countries=[_schema.get_region_tuple(c).short for c in config["countries"]], + network=config["network"], + regions=config["regions"], + crs=config["crs"], message: "Cleaning retrieved OSM power features." script: @@ -61,24 +59,24 @@ rule build_network: substations_polygon=rules.clean.output.substations_polygon, lines=rules.clean.output.lines, output: - buses="/build/csv/buses.csv", - lines="/build/csv/lines.csv", - transformers="/build/csv/transformers.csv", - buses_geojson="/build/geojson/buses.geojson", - lines_geojson="/build/geojson/lines.geojson", - transformers_geojson="/build/geojson/transformers.geojson", - stations_polygon="/build/geojson/stations_polygon.geojson", - buses_polygon="/build/geojson/buses_polygon.geojson", + buses="", + lines="", + transformers="", + buses_geojson="", + lines_geojson="", + transformers_geojson="", + stations_polygon="", + buses_polygon="", log: "/build_network.log", conda: - "../envs/network.yaml" + "../envs/module.yaml" threads: 1 params: station_merge_radius_m=config["network"]["station_merge_radius_m"], remove_under_construction=config["network"]["remove_under_construction"], remove_after=config["network"]["remove_after"], - crs=config["crs"].model_dump(mode="json"), + crs=config["crs"], message: "Building a connected generic OSM network." script: diff --git a/workflow/rules/plot.smk b/workflow/rules/plot.smk index 3c06d71..9eef7fe 100644 --- a/workflow/rules/plot.smk +++ b/workflow/rules/plot.smk @@ -11,15 +11,15 @@ rule build_interactive_map: stations_polygon=rules.build_network.output.stations_polygon, buses_polygon=rules.build_network.output.buses_polygon, output: - map="/map.html", + map="", log: "/build_interactive_map.log", conda: - "../envs/network.yaml" + "../envs/module.yaml" threads: 1 params: - crs=config["crs"].model_dump(mode="json"), - interactive_map=config["interactive_map"].model_dump(mode="json"), + crs=config["crs"], + interactive_map=config["interactive_map"], message: "Building an interactive OSM network map." script: diff --git a/workflow/rules/retrieve.smk b/workflow/rules/retrieve.smk index cc6144f..ca18f24 100644 --- a/workflow/rules/retrieve.smk +++ b/workflow/rules/retrieve.smk @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -from pathlib import Path # Both retrieve_osm_pbf and retrieve_osm_overpass produce this same fixed # set of six files per country (routes_relation included even when @@ -21,10 +20,7 @@ _OSM_FEATURES = [ "substations_relation", "routes_relation", ] -_OSM_OUTPUTS = { - feature: f"/retrieve/{{country}}_{feature}.json" - for feature in _OSM_FEATURES -} +_OSM_OUTPUTS = {feature: f"" for feature in _OSM_FEATURES} if config["retrieve"]["source"] == "geofabrik": @@ -35,12 +31,12 @@ if config["retrieve"]["source"] == "geofabrik": log: "/retrieve_osm_pbf/{country}.log", conda: - "../envs/retrieve.yaml" + "../envs/module.yaml" threads: 1 params: include_relations=config["network"]["include_relations"], force_redownload=config["retrieve"]["force_redownload"], - data_dir=str(Path(workflow.basedir).parent / "data" / "earth-osm"), + data_dir=workflow.pathvars.apply("/automatic/earth-osm"), message: "Retrieve OSM power features for one country from a local PBF file." script: @@ -54,11 +50,11 @@ elif config["retrieve"]["source"] == "overpass": log: "/retrieve_osm_overpass/{country}.log", conda: - "../envs/retrieve.yaml" + "../envs/module.yaml" threads: 1 params: include_relations=config["network"]["include_relations"], - overpass_api=config["retrieve"]["overpass_api"].model_dump(mode="json"), + overpass_api=config["retrieve"]["overpass_api"], message: "Retrieve OSM power features for one country from the Overpass API." script: @@ -68,7 +64,6 @@ elif config["retrieve"]["source"] == "overpass": rule retrieve_osm_all: input: expand( - "/retrieve/{country}_{feature}.json", + [workflow.pathvars.apply(path) for path in _OSM_OUTPUTS.values()], country=config["countries"], - feature=_OSM_FEATURES, ), diff --git a/workflow/scripts/_helpers.py b/workflow/scripts/_helpers.py index 0ca446b..8864824 100644 --- a/workflow/scripts/_helpers.py +++ b/workflow/scripts/_helpers.py @@ -7,15 +7,9 @@ Logging setup, internal data loading, shared geometry constants, and mock_snakemake for testing. -Other scripts import this module as ``from scripts._helpers import ...``, -not ``workflow.scripts._helpers``. That plain form resolves in both places -these scripts run: under a real -Snakemake ``script:`` execution, Snakemake puts ``workflow/`` on ``sys.path`` -(via the Snakefile's own ``sys.path.insert(0, workflow.basedir)``, captured -and propagated into every script's execution preamble); under pytest, -``pytest.ini``'s ``pythonpath`` setting puts ``workflow/`` on ``sys.path`` -too, alongside the project root that lets test files import -``workflow.scripts.X``. +Scripts import this module directly from their own script directory. The Snakefile caches +it and its internal YAML dependencies using workflow.source_path(); Snakemake +adds the cached script directory to sys.path for both local and remote jobs. """ import logging @@ -42,18 +36,7 @@ def configure_logging(log_path: str) -> None: def load_internal_yaml(filename: str) -> Any: - """Load a YAML file from workflow/internal/, resolved relative to this checkout. - - Uses ``__file__`` rather than ``workflow.source_path()``: this is called - from plain executed scripts (via Snakemake's ``script:`` directive), - which only get a ``snakemake`` object in scope, not the ``workflow`` - object that ``source_path()`` needs — that API is only reachable from - Snakefile-level rule code. Resolving via ``__file__`` still works - correctly when this project is used as a Snakemake submodule, because - Snakemake's own ``script:`` resolution already points each script at its - real location inside this checkout (the same reasoning ``mock_snakemake`` - below relies on for ``script_dir``). - """ + """Load an internal YAML file explicitly cached by the Snakefile.""" path = Path(__file__).resolve().parent.parent / "internal" / filename with open(path) as handle: return yaml.safe_load(handle) diff --git a/workflow/scripts/_schema.py b/workflow/scripts/_schema.py index 4877b15..f5e22c9 100644 --- a/workflow/scripts/_schema.py +++ b/workflow/scripts/_schema.py @@ -303,7 +303,7 @@ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any def load_region_configs( - config: dict[str, Any], regions_dir: str | Path + config: dict[str, Any], regions_dir: str | Path | dict[str, str] ) -> dict[str, Any]: """Load country settings selected by ``countries`` before validation. @@ -320,7 +320,12 @@ def load_region_configs( for country in countries: region = get_region_tuple(country).short - path = Path(regions_dir) / f"config.{region}.yaml" + if isinstance(regions_dir, dict): + if region not in regions_dir: + continue + path = Path(regions_dir[region]) + else: + path = Path(regions_dir) / f"config.{region}.yaml" if path.exists(): loaded = yaml_reader.load(path) or {} if not isinstance(loaded, dict): @@ -359,7 +364,9 @@ def str_representer(dumper, data): yaml_writer.representer.add_representer(str, str_representer) data = CommentedMap() - data.yaml_set_start_comment("yaml-language-server: $schema=./config.schema.json") + data.yaml_set_start_comment( + "yaml-language-server: $schema=../workflow/internal/config.schema.yaml" + ) for key, value in defaults.items(): data[key] = value @@ -374,7 +381,7 @@ def str_representer(dumper, data): return defaults -def generate_config_schema(path: str = "config/config.schema.json") -> dict: +def generate_config_schema(path: str = "workflow/internal/config.schema.yaml") -> dict: """Generate JSON schema file and return the schema dict.""" def resolve_refs(obj, defs): @@ -442,18 +449,43 @@ def remove_object_type(obj, is_root=True): schema = remove_object_type(schema) with open(path, "w") as f: - json.dump(schema, f, indent=2) - f.write("\n") + if Path(path).suffix == ".json": + json.dump(schema, f, indent=2) + f.write("\n") + else: + yaml_writer = YAML() + yaml_writer.width = 4096 + yaml_writer.dump(schema, f) return schema +def generate_region_index( + regions_dir: str | Path = "config/regions", path: str | Path | None = None +) -> list[str]: + """Generate the remote-import manifest from the available regional files.""" + regions_dir = Path(regions_dir) + codes = sorted( + file.stem.removeprefix("config.") + for file in regions_dir.glob("config.*.yaml") + if file.is_file() + ) + output = Path(path) if path is not None else regions_dir / "index.yaml" + writer = YAML() + writer.default_flow_style = False + with output.open("w") as handle: + handle.write("# Generated by pixi run generate-config; do not edit manually.\n") + writer.dump(codes, handle) + return codes + + __all__ = [ "ConfigSchema", "load_region_configs", "validate_config", "generate_config_defaults", "generate_config_schema", + "generate_region_index", "ValidationError", ] @@ -461,3 +493,5 @@ def remove_object_type(obj, is_root=True): if __name__ == "__main__": generate_config_defaults() generate_config_schema() + generate_config_schema("config/config.schema.json") + generate_region_index() diff --git a/workflow/scripts/build_interactive_map.py b/workflow/scripts/build_interactive_map.py index 4fb5e97..b2e1859 100644 --- a/workflow/scripts/build_interactive_map.py +++ b/workflow/scripts/build_interactive_map.py @@ -23,7 +23,7 @@ import geopandas as gpd import pandas as pd import pydeck as pdk -from scripts._helpers import configure_logging, load_internal_yaml +from _helpers import configure_logging, load_internal_yaml if TYPE_CHECKING: snakemake: Any @@ -1080,7 +1080,7 @@ def compress_json_in_script(match: re.Match[str]) -> str: if __name__ == "__main__": if "snakemake" not in globals(): - from scripts._helpers import mock_snakemake + from _helpers import mock_snakemake snakemake = mock_snakemake("build_interactive_map") configure_logging(snakemake.log[0]) diff --git a/workflow/scripts/build_network.py b/workflow/scripts/build_network.py index e4803af..c3e08eb 100644 --- a/workflow/scripts/build_network.py +++ b/workflow/scripts/build_network.py @@ -22,8 +22,8 @@ import networkx as nx import numpy as np import pandas as pd +from _helpers import BUS_TOL, configure_logging from pyproj import Transformer -from scripts._helpers import BUS_TOL, configure_logging from shapely import get_point from shapely.algorithms.polylabel import polylabel from shapely.geometry import LineString, MultiLineString, Point @@ -940,7 +940,7 @@ def _write_components( if __name__ == "__main__": if "snakemake" not in globals(): - from scripts._helpers import mock_snakemake + from _helpers import mock_snakemake snakemake = mock_snakemake("build_network") diff --git a/workflow/scripts/clean.py b/workflow/scripts/clean.py index 32f5fcd..819bb95 100644 --- a/workflow/scripts/clean.py +++ b/workflow/scripts/clean.py @@ -27,7 +27,7 @@ import geopandas as gpd import numpy as np import pandas as pd -from scripts._helpers import BUS_TOL, configure_logging, load_internal_yaml +from _helpers import BUS_TOL, configure_logging, load_internal_yaml from shapely.algorithms.polylabel import polylabel from shapely.geometry import LineString, MultiLineString, Point, Polygon from shapely.ops import linemerge, unary_union @@ -748,7 +748,9 @@ def _create_line(row: pd.Series) -> tuple[Any, list[str]]: ] -def _load_elements(paths: list[str], element_type: str) -> pd.DataFrame: +def _load_elements( + paths: list[str], element_type: str, countries: dict[str, str] | None = None +) -> pd.DataFrame: """Load our own raw Overpass-JSON-shaped retrieval output for one element type. Both retrieve_osm_pbf.py (pyosmium reading a local PBF) and @@ -763,7 +765,11 @@ def _load_elements(paths: list[str], element_type: str) -> pd.DataFrame: continue with open(path) as handle: payload = json.load(handle) - country = Path(path).stem.split("_", maxsplit=1)[0] + country = ( + countries[path] + if countries is not None + else Path(path).stem.split("_", maxsplit=1)[0] + ) elements = [ e for e in payload.get("elements", []) if e.get("type") == element_type ] @@ -796,11 +802,16 @@ def _flatten_tags(frame: pd.DataFrame, columns: list[str]) -> pd.DataFrame: def _import_lines_and_cables( - line_paths: list[str], cable_paths: list[str] + line_paths: list[str], + cable_paths: list[str], + countries: dict[str, str] | None = None, ) -> pd.DataFrame: """Read lines_way/cables_way JSON into the shape ``_clean_lines`` expects.""" frame = pd.concat( - [_load_elements(line_paths, "way"), _load_elements(cable_paths, "way")], + [ + _load_elements(line_paths, "way", countries), + _load_elements(cable_paths, "way", countries), + ], ignore_index=True, ) frame = _flatten_tags(frame, _LINE_TAG_COLUMNS) @@ -812,14 +823,16 @@ def _import_lines_and_cables( return frame.reset_index(drop=True) -def _import_substation_relations(paths: list[str]) -> pd.DataFrame: +def _import_substation_relations( + paths: list[str], countries: dict[str, str] | None = None +) -> pd.DataFrame: """Build one polygon per substation relation from its non-node members. Excludes members with an empty/"incoming_line"/"substation"/"inner" role (the actual boundary is only the "outer" ring), linemerges the rest, and takes the convex hull as the substation's polygon. """ - df_relations = _load_elements(paths, "relation") + df_relations = _load_elements(paths, "relation", countries) if df_relations.empty: return df_relations @@ -861,19 +874,24 @@ def _import_substation_relations(paths: list[str]) -> pd.DataFrame: return df_relations.dropna(subset=["geometry"]).reset_index(drop=True) -def _import_routes_relation(paths: list[str]) -> pd.DataFrame: +def _import_routes_relation( + paths: list[str], countries: dict[str, str] | None = None +) -> pd.DataFrame: """Read routes_relation JSON into the shape ``_create_line`` expects. Unlike substation relations, a route relation's ``members`` column is kept as-is (not converted to a polygon) — ``_create_line`` merges the member ways' own geometry into the relation's line. """ - frame = _load_elements(paths, "relation") + frame = _load_elements(paths, "relation", countries) return _flatten_tags(frame, _RELATION_TAG_COLUMNS) def _import_substations( - way_paths: list[str], node_paths: list[str], relation_paths: list[str] + way_paths: list[str], + node_paths: list[str], + relation_paths: list[str], + countries: dict[str, str] | None = None, ) -> pd.DataFrame: """Read substations_way/node/relation JSON into ``_clean_substations`` shape. @@ -889,7 +907,7 @@ def _import_substations( ``substations_node`` query, added for parity between the two retrieve.source options. """ - df_way = _load_elements(way_paths, "way") + df_way = _load_elements(way_paths, "way", countries) df_way = df_way[ df_way["geometry"].apply(lambda g: isinstance(g, list) and len(g) >= 1) ] @@ -899,7 +917,7 @@ def _import_substations( lambda coords: _create_polygon({"geometry": coords}) ) - df_node = _load_elements(node_paths, "node") + df_node = _load_elements(node_paths, "node", countries) df_node = df_node[ df_node["geometry"].apply(lambda g: isinstance(g, list) and len(g) >= 1) ] @@ -909,7 +927,7 @@ def _import_substations( lambda coords: Point(coords[0]["lon"], coords[0]["lat"]) ) - df_relation = _import_substation_relations(relation_paths) + df_relation = _import_substation_relations(relation_paths, countries) df_relation = _flatten_tags(df_relation, _SUBSTATION_TAG_COLUMNS) df_way["is_node"] = False @@ -957,6 +975,7 @@ def clean( network: dict[str, Any], regions: dict[str, Any], geo_crs: str, + countries: dict[str, str] | None = None, ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]: """Return clean substation buses, substation polygons, and AC lines/cables. @@ -964,7 +983,9 @@ def clean( ``substations_way``, ``substations_node``, ``substations_relation``, ``routes_relation``) to the list of per-country JSON files retrieved for it. A feature with no paths (e.g. ``routes_relation`` when - ``include_relations`` is off) is simply skipped. + ``include_relations`` is off) is simply skipped. ``countries`` maps input + paths to ISO codes so rewiring filenames preserves regional settings. + Without it, standalone callers retain the legacy filename convention. """ crs = geo_crs min_voltage_ac = network["minimum_voltage_kv"] * 1000 # V @@ -976,6 +997,7 @@ def clean( inputs.get("substations_way", []), inputs.get("substations_node", []), inputs.get("substations_relation", []), + countries, ) if df_substations.empty: empty_buses = gpd.GeoDataFrame(geometry=gpd.GeoSeries([], crs=crs), crs=crs) @@ -1046,7 +1068,7 @@ def clean( route_paths = inputs.get("routes_relation", []) if route_paths: logger.info("Importing power route relations.") - df_relation = _import_routes_relation(route_paths) + df_relation = _import_routes_relation(route_paths, countries) if not df_relation.empty: df_relation = _drop_duplicate_lines(df_relation) df_relation["under_construction"] = ( @@ -1125,7 +1147,7 @@ def clean( # --- AC lines/cables via individual ways --------------------------- logger.info("Importing lines and cables.") df_lines = _import_lines_and_cables( - inputs.get("lines_way", []), inputs.get("cables_way", []) + inputs.get("lines_way", []), inputs.get("cables_way", []), countries ) if not df_lines.empty: df_lines = _drop_duplicate_lines(df_lines) @@ -1197,7 +1219,7 @@ def clean( if __name__ == "__main__": if "snakemake" not in globals(): - from scripts._helpers import mock_snakemake + from _helpers import mock_snakemake snakemake = mock_snakemake("clean") @@ -1208,6 +1230,12 @@ def clean( snakemake.params.network, snakemake.params.regions, snakemake.params.crs["geo"], + { + path: country + for paths in inputs.values() + if paths + for path, country in zip(paths, snakemake.params.countries, strict=True) + }, ) logger.info( "Retained %d buses, %d substation polygons, and %d AC lines/cables.", diff --git a/workflow/scripts/retrieve_osm_overpass.py b/workflow/scripts/retrieve_osm_overpass.py index 6495dde..2a3da4b 100644 --- a/workflow/scripts/retrieve_osm_overpass.py +++ b/workflow/scripts/retrieve_osm_overpass.py @@ -27,9 +27,9 @@ from typing import TYPE_CHECKING, Any import requests +from _helpers import configure_logging from earth_osm.regions import get_region_tuple from requests.adapters import HTTPAdapter -from scripts._helpers import configure_logging from urllib3.util.retry import Retry if TYPE_CHECKING: @@ -172,7 +172,7 @@ def retrieve_from_overpass( if __name__ == "__main__": if "snakemake" not in globals(): - from scripts._helpers import mock_snakemake + from _helpers import mock_snakemake snakemake = mock_snakemake("retrieve_osm_overpass", country="BE") diff --git a/workflow/scripts/retrieve_osm_pbf.py b/workflow/scripts/retrieve_osm_pbf.py index ee1cadb..ba52fc0 100644 --- a/workflow/scripts/retrieve_osm_pbf.py +++ b/workflow/scripts/retrieve_osm_pbf.py @@ -41,8 +41,8 @@ from typing import TYPE_CHECKING, Any import osmium +from _helpers import configure_logging from earth_osm.regions import download_region_pbf, get_region_tuple -from scripts._helpers import configure_logging if TYPE_CHECKING: snakemake: Any @@ -334,7 +334,7 @@ def _extract_from_filtered_pbf( if __name__ == "__main__": if "snakemake" not in globals(): - from scripts._helpers import mock_snakemake + from _helpers import mock_snakemake snakemake = mock_snakemake("retrieve_osm_pbf", country="BE")