diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40431962..b2953ab6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ -default_language_version: - python: python3.12 +# default_language_version: +# python: python3.13 repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/docs/conventions.rst b/docs/conventions.rst new file mode 100644 index 00000000..5e8fb106 --- /dev/null +++ b/docs/conventions.rst @@ -0,0 +1,156 @@ +Geospatial Metadata Conventions +=============================== + +Overview +-------- + +rioxarray supports two geospatial metadata conventions for storing coordinate reference system (CRS) and transform information: + +1. **CF (Climate and Forecasts) Convention** - NetCDF convention using grid_mapping coordinates +2. **Zarr Spatial and Proj Conventions** - Cloud-native conventions using direct attributes + +Convention Selection +-------------------- + +rioxarray uses CF conventions by default. When convention is set to ``None`` (the default), rioxarray uses CF conventions but will fallback to reading Zarr conventions if they are explicitly declared in the data. + +Global Setting +~~~~~~~~~~~~~~ + +Set the default convention globally using ``set_options``: + +.. code-block:: python + + import rioxarray + from rioxarray import Convention + + # Use CF convention with Zarr fallback (default) + rioxarray.set_options(convention=None) + + # Use CF conventions exclusively + rioxarray.set_options(convention=Convention.CF) + + # Use Zarr conventions exclusively + rioxarray.set_options(convention=Convention.Zarr) + +Per-Method Override +~~~~~~~~~~~~~~~~~~~ + +Override the global setting for individual method calls: + +.. code-block:: python + + # Write CRS using CF convention (default) + data.rio.write_crs("EPSG:4326") + + # Write CRS using Zarr convention + data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Write transform using Zarr convention + data.rio.write_transform(transform, convention=Convention.Zarr) + +CF Convention +------------- + +The CF (Climate and Forecasts) convention: + +- CRS information stored in a grid_mapping coordinate variable +- Transform information stored as ``GeoTransform`` attribute on the grid_mapping coordinate +- Compatible with NetCDF and GDAL tools +- Verbose but widely supported + +Example: + +.. code-block:: python + + import rioxarray + from rioxarray import Convention + + # Write using CF convention + data_cf = data.rio.write_crs("EPSG:4326", convention=Convention.CF) + data_cf = data_cf.rio.write_transform(transform, convention=Convention.CF) + + # Results in: + # - Grid mapping coordinate with CRS attributes + # - GeoTransform attribute with space-separated transform values + +Zarr Conventions +---------------- + +The Zarr spatial and proj conventions provide a cloud-native approach: + +- CRS information stored as direct attributes (``proj:code``, ``proj:wkt2``, ``proj:projjson``) +- Transform stored as ``spatial:transform`` numeric array attribute +- Spatial metadata in ``spatial:dimensions``, ``spatial:shape``, ``spatial:bbox`` +- Lightweight and efficient for cloud storage + +Example: + +.. code-block:: python + + import rioxarray + from rioxarray import Convention + + # Write using Zarr conventions + data_zarr = data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + data_zarr = data_zarr.rio.write_transform(transform, convention=Convention.Zarr) + + # Write both CRS and transform using Zarr conventions + data_zarr = data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + data_zarr = data_zarr.rio.write_transform(transform, convention=Convention.Zarr) + +Writing Zarr Conventions +------------------------ + +To write data using Zarr conventions, use the ``convention`` parameter: + +.. code-block:: python + + from affine import Affine + from rioxarray import Convention + + # Write CRS using Zarr conventions + data = data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Write transform using Zarr conventions + transform = Affine(1.0, 0.0, 0.0, 0.0, -1.0, 100.0) + data = data.rio.write_transform(transform, convention=Convention.Zarr) + + # Results in: + # - proj:wkt2: CRS as WKT2 string + # - spatial:transform: [1.0, 0.0, 0.0, 0.0, -1.0, 100.0] + # - spatial:dimensions: ["y", "x"] + # - spatial:shape: [height, width] + # - zarr_conventions: Convention declarations + +Reading Behavior +---------------- + +When reading geospatial metadata, rioxarray follows this priority order based on the global convention setting: + +- **None (default)**: CF conventions first, with Zarr conventions as fallback if explicitly declared +- **Convention.CF**: CF conventions only (grid_mapping coordinates and CF attributes) +- **Convention.Zarr**: Zarr conventions only (spatial: and proj: attributes) + +The fallback behavior ensures that CF remains the primary convention while allowing Zarr conventions to be read when they are the only available metadata. + +Convention Declaration +---------------------- + +According to the `Zarr conventions specification `, conventions must be explicitly declared in the ``zarr_conventions`` array. rioxarray automatically handles this when writing Zarr conventions: + +.. code-block:: python + + data_zarr = data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Automatically adds to zarr_conventions: + print(data_zarr.attrs["zarr_conventions"]) + # [{"name": "proj:", "uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", ...}] + +References +---------- + +- `CF Conventions `_ +- `Zarr Spatial Convention `_ +- `Zarr Geo-Proj Convention `_ +- `Zarr Conventions Specification `_ diff --git a/docs/examples/conventions.ipynb b/docs/examples/conventions.ipynb new file mode 100644 index 00000000..a1db91e7 --- /dev/null +++ b/docs/examples/conventions.ipynb @@ -0,0 +1,345 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "07369d60", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import xarray as xr\n", + "from affine import Affine\n", + "\n", + "import rioxarray\n", + "from rioxarray import Convention\n", + "\n", + "# Create sample data\n", + "data = np.random.rand(100, 100)\n", + "da = xr.DataArray(\n", + " data,\n", + " dims=[\"y\", \"x\"],\n", + " coords={\n", + " \"x\": np.linspace(-180, 180, 100),\n", + " \"y\": np.linspace(-90, 90, 100)\n", + " }\n", + ")\n", + "\n", + "transform = Affine(3.6, 0.0, -180.0, 0.0, -1.8, 90.0)\n", + "print(\"Sample data created\")" + ] + }, + { + "cell_type": "markdown", + "id": "3859ad94", + "metadata": {}, + "source": [ + "## CF Convention\n", + "\n", + "The CF convention stores geospatial metadata in grid_mapping coordinate variables." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a677d479", + "metadata": {}, + "outputs": [], + "source": [ + "# Write CRS and transform using CF convention\n", + "da_cf = da.rio.write_crs(\"EPSG:4326\", convention=Convention.CF)\n", + "da_cf = da_cf.rio.write_transform(transform, convention=Convention.CF)\n", + "\n", + "print(\"CF Convention attributes:\")\n", + "print(f\"Grid mapping: {da_cf.attrs.get('grid_mapping')}\")\n", + "print(f\"Grid mapping coordinate: {list(da_cf.coords.keys())}\")\n", + "print(f\"Grid mapping attrs: {da_cf.coords['spatial_ref'].attrs.keys()}\")\n", + "print(f\"GeoTransform: {da_cf.coords['spatial_ref'].attrs.get('GeoTransform')}\")" + ] + }, + { + "cell_type": "markdown", + "id": "648a701a", + "metadata": {}, + "source": [ + "## Zarr Conventions\n", + "\n", + "The Zarr conventions store geospatial metadata as direct attributes on the data array." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2b201ad", + "metadata": {}, + "outputs": [], + "source": [ + "# Write CRS and transform using Zarr conventions\n", + "da_zarr = da.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + "da_zarr = da_zarr.rio.write_transform(transform, convention=Convention.Zarr)\n", + "\n", + "print(\"Zarr Convention attributes:\")\n", + "print(f\"proj:code: {da_zarr.attrs.get('proj:code')}\")\n", + "print(f\"spatial:transform: {da_zarr.attrs.get('spatial:transform')}\")\n", + "print(f\"zarr_conventions: {[c['name'] for c in da_zarr.attrs.get('zarr_conventions', [])]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "1dbfaf50", + "metadata": {}, + "source": [ + "## Zarr-Specific Methods\n", + "\n", + "rioxarray provides specialized methods for working with Zarr conventions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "418ee389", + "metadata": {}, + "outputs": [], + "source": [ + "# Write CRS in multiple Zarr formats using convention module\n", + "from rioxarray._convention import zarr as zarr_conv\n", + "\n", + "da_zarr_full = da.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + "da_zarr_full = zarr_conv.write_crs(da_zarr_full, da_zarr_full.rio.crs, format=\"all\")\n", + "\n", + "print(\"Multiple CRS formats:\")\n", + "print(f\"proj:code: {da_zarr_full.attrs.get('proj:code')}\")\n", + "print(f\"proj:wkt2: {da_zarr_full.attrs.get('proj:wkt2')[:50]}...\")\n", + "print(f\"proj:projjson type: {type(da_zarr_full.attrs.get('proj:projjson'))}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "163b54d7", + "metadata": {}, + "outputs": [], + "source": [ + "# Write complete spatial metadata using convention module\n", + "da_spatial = da.rio.write_transform(transform, convention=Convention.Zarr)\n", + "da_spatial = zarr_conv._write_spatial_metadataa(da_spatial, \"y\", \"x\", transform=transform)\n", + "\n", + "print(\"Complete spatial metadata:\")\n", + "print(f\"spatial:dimensions: {da_spatial.attrs.get('spatial:dimensions')}\")\n", + "print(f\"spatial:shape: {da_spatial.attrs.get('spatial:shape')}\")\n", + "print(f\"spatial:bbox: {da_spatial.attrs.get('spatial:bbox')}\")\n", + "print(f\"spatial:registration: {da_spatial.attrs.get('spatial:registration')}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9879e81f", + "metadata": {}, + "outputs": [], + "source": [ + "# Write CRS and transform together\n", + "da_complete = da.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + "da_complete = da_complete.rio.write_transform(transform, convention=Convention.Zarr)\n", + "\n", + "print(\"Complete Zarr conventions:\")\n", + "print(f\"Has CRS: {'proj:wkt2' in da_complete.attrs}\")\n", + "print(f\"Has transform: {'spatial:transform' in da_complete.attrs}\")\n", + "print(f\"Has dimensions: {'spatial:dimensions' in da_complete.attrs}\")\n", + "print(f\"Number of attributes: {len(da_complete.attrs)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "8e13f1ab", + "metadata": {}, + "source": [ + "## Global Convention Setting\n", + "\n", + "You can set the default convention globally to avoid specifying it for each method call." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e509176", + "metadata": {}, + "outputs": [], + "source": [ + "# Set Zarr as the global default\n", + "with rioxarray.set_options(convention=Convention.Zarr):\n", + " da_global = da.rio.write_crs(\"EPSG:4326\") # Uses Zarr convention\n", + " da_global = da_global.rio.write_transform(transform) # Uses Zarr convention\n", + " \n", + " print(\"Using global Zarr convention:\")\n", + " print(f\"proj:wkt2: {da_global.attrs.get('proj:wkt2', 'Not found')[:50] if da_global.attrs.get('proj:wkt2') else 'Not found'}...\")\n", + " print(f\"spatial:transform: {da_global.attrs.get('spatial:transform')}\")\n", + " print(f\"Has grid_mapping: {'grid_mapping' in da_global.attrs}\")" + ] + }, + { + "cell_type": "markdown", + "id": "867eda68", + "metadata": {}, + "source": [ + "## Reading with Different Conventions\n", + "\n", + "The reading behavior follows this priority: CF first (default), Zarr as fallback when explicitly declared." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a180d4e3", + "metadata": {}, + "outputs": [], + "source": [ + "# Create data with both conventions\n", + "da_both = da.rio.write_crs(\"EPSG:4326\", convention=Convention.CF)\n", + "da_both = da_both.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + "\n", + "print(\"Data with both conventions:\")\n", + "print(f\"Has CF grid_mapping: {'grid_mapping' in da_both.attrs}\")\n", + "print(f\"Has Zarr proj:code: {'proj:wkt2' in da_both.attrs}\")\n", + "\n", + "# Default reading (CF first, Zarr fallback)\n", + "crs_default = da_both.rio.crs\n", + "print(f\"\\nDefault reading (CF first): {crs_default}\")\n", + "\n", + "# Read using CF convention exclusively\n", + "with rioxarray.set_options(convention=Convention.CF):\n", + " crs_cf = da_both.rio.crs\n", + " print(f\"CF convention only: {crs_cf}\")\n", + "\n", + "# Read using Zarr convention exclusively \n", + "with rioxarray.set_options(convention=Convention.Zarr):\n", + " crs_zarr = da_both.rio.crs\n", + " print(f\"Zarr convention only: {crs_zarr}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c40e8b78", + "metadata": {}, + "source": [ + "## Performance Comparison\n", + "\n", + "Zarr conventions can be faster for reading metadata since they use direct attribute access instead of coordinate variable lookups." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26335063", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import tempfile\n", + "import shutil\n", + "from pathlib import Path\n", + "\n", + "# Create a temporary directory for test files\n", + "temp_dir = Path(tempfile.mkdtemp())\n", + "\n", + "try:\n", + " # Create larger test data \n", + " large_data = xr.DataArray(\n", + " np.random.rand(2000, 2000),\n", + " dims=[\"y\", \"x\"],\n", + " coords={\n", + " \"x\": np.linspace(-180, 180, 2000),\n", + " \"y\": np.linspace(-90, 90, 2000)\n", + " }\n", + " )\n", + " \n", + " # Add geospatial metadata\n", + " transform = Affine(0.18, 0.0, -180.0, 0.0, -0.18, 90.0)\n", + " \n", + " # Create CF data and write to disk\n", + " cf_data = large_data.rio.write_crs(\"EPSG:4326\", convention=Convention.CF)\n", + " cf_data = cf_data.rio.write_transform(transform, convention=Convention.CF)\n", + " cf_path = temp_dir / \"cf_data.zarr\"\n", + " cf_data.to_zarr(cf_path)\n", + " \n", + " # Create Zarr data and write to disk \n", + " zarr_data = large_data.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + " zarr_data = zarr_data.rio.write_transform(transform, convention=Convention.Zarr)\n", + " zarr_path = temp_dir / \"zarr_data.zarr\"\n", + " zarr_data.to_zarr(zarr_path)\n", + " \n", + " print(\"Dataset info:\")\n", + " print(f\"Data shape: {large_data.shape}\")\n", + " print(f\"CF file size: {sum(f.stat().st_size for f in cf_path.rglob('*') if f.is_file()) / 1024**2:.1f} MB\")\n", + " print(f\"Zarr file size: {sum(f.stat().st_size for f in zarr_path.rglob('*') if f.is_file()) / 1024**2:.1f} MB\")\n", + " \n", + " # Time CF opening and metadata access\n", + " with rioxarray.set_options(convention=Convention.CF):\n", + " # Time opening from disk\n", + " start = time.time()\n", + " for _ in range(20):\n", + " cf_from_disk = xr.open_dataset(cf_path, decode_coords=\"all\")\n", + " cf_array = cf_from_disk[list(cf_from_disk.data_vars.keys())[0]]\n", + " cf_from_disk.close() # Clean up\n", + " cf_open_time = time.time() - start\n", + " \n", + " # Time metadata access (reopen once for the test)\n", + " cf_from_disk = xr.open_dataset(cf_path, decode_coords=\"all\")\n", + " cf_array = cf_from_disk[list(cf_from_disk.data_vars.keys())[0]]\n", + " start = time.time()\n", + " for _ in range(100):\n", + " _ = cf_array.rio.crs\n", + " _ = cf_array.rio.transform()\n", + " cf_access_time = time.time() - start\n", + " cf_from_disk.close()\n", + " \n", + " # Time Zarr opening and metadata access\n", + " with rioxarray.set_options(convention=Convention.Zarr):\n", + " # Time opening from disk\n", + " start = time.time()\n", + " for _ in range(20):\n", + " zarr_from_disk = xr.open_dataset(zarr_path, decode_coords=True)\n", + " zarr_array = zarr_from_disk[list(zarr_from_disk.data_vars.keys())[0]]\n", + " zarr_from_disk.close() # Clean up\n", + " zarr_open_time = time.time() - start\n", + " \n", + " # Time metadata access (reopen once for the test)\n", + " zarr_from_disk = xr.open_dataset(zarr_path, decode_coords=True)\n", + " zarr_array = zarr_from_disk[list(zarr_from_disk.data_vars.keys())[0]]\n", + " start = time.time()\n", + " for _ in range(100):\n", + " _ = zarr_array.rio.crs\n", + " _ = zarr_array.rio.transform()\n", + " zarr_access_time = time.time() - start\n", + " zarr_from_disk.close()\n", + " \n", + " print(f\"\\nPerformance Comparison:\")\n", + " print(f\"Dataset Opening (20 iterations each):\")\n", + " print(f\" CF convention: {cf_open_time:.4f} seconds\")\n", + " print(f\" Zarr convention: {zarr_open_time:.4f} seconds\")\n", + " print(f\" Opening speedup: {cf_open_time / zarr_open_time:.2f}x\")\n", + " \n", + " print(f\"\\nMetadata Access (100 iterations each):\")\n", + " print(f\" CF convention: {cf_access_time:.4f} seconds\")\n", + " print(f\" Zarr convention: {zarr_access_time:.4f} seconds\")\n", + " print(f\" Access speedup: {cf_access_time / zarr_access_time:.2f}x\")\n", + " \n", + " print(f\"\\nTotal Time (opening + access):\")\n", + " print(f\" CF total: {cf_open_time + cf_access_time:.4f} seconds\")\n", + " print(f\" Zarr total: {zarr_open_time + zarr_access_time:.4f} seconds\")\n", + " print(f\" Overall speedup: {(cf_open_time + cf_access_time) / (zarr_open_time + zarr_access_time):.2f}x\")\n", + " \n", + "finally:\n", + " # Clean up temporary files\n", + " shutil.rmtree(temp_dir, ignore_errors=True)" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/examples.rst b/docs/examples/examples.rst index 9afe8367..2234bdf3 100644 --- a/docs/examples/examples.rst +++ b/docs/examples/examples.rst @@ -9,6 +9,7 @@ This page contains links to a collection of examples of how to use rioxarray. :maxdepth: 1 :caption: Notebooks: + conventions.ipynb resampling.ipynb convert_to_raster.ipynb clip_geom.ipynb diff --git a/docs/getting_started/crs_management.ipynb b/docs/getting_started/crs_management.ipynb index 7e0ff7c7..b7955f3e 100644 --- a/docs/getting_started/crs_management.ipynb +++ b/docs/getting_started/crs_management.ipynb @@ -2,35 +2,59 @@ "cells": [ { "cell_type": "markdown", + "id": "d81f584f", "metadata": {}, "source": [ - "# Coordinate Reference System Management" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "xarray \"... is particularly tailored to working with netCDF files, which were the source of xarray’s data model...\" (http://xarray.pydata.org).\n", + "rioxarray supports two geospatial metadata conventions for storing coordinate reference system (CRS) and transform information:\n", + "\n", + "## CF Convention\n", + "xarray \"... is particularly tailored to working with netCDF files, which were the source of xarray's data model...\" (http://xarray.pydata.org).\n", "\n", - "For netCDF files, the GIS community uses CF conventions (http://cfconventions.org/).\n", + "For netCDF files, the GIS community uses CF conventions (http://cfconventions.org/). This stores geospatial metadata using grid_mapping coordinates.\n", "\n", "Additionally, GDAL also supports these attributes:\n", "\n", "- spatial_ref (Well Known Text)\n", "- GeoTransform (GeoTransform array)\n", "\n", + "## Zarr Conventions\n", + "rioxarray now supports cloud-native conventions from the Zarr community:\n", + "- **[Zarr spatial convention](https://github.com/zarr-conventions/spatial)**: Stores transform and spatial metadata as direct attributes\n", + "- **[Zarr proj convention](https://github.com/zarr-conventions/geo-proj)**: Stores CRS information in multiple formats (code, WKT2, PROJJSON)\n", + "\n", + "These conventions provide better performance for cloud storage and are more lightweight than CF conventions.\n", + "\n", + "## Convention Selection\n", + "You can choose which convention to use:\n", + "\n", + "```python\n", + "import rioxarray\n", + "from rioxarray import Convention\n", + "\n", + "# Set global default (CF is default for backward compatibility)\n", + "rioxarray.set_options(convention=Convention.CF) \n", + "rioxarray.set_options(convention=Convention.Zarr)\n", + "\n", + "# Or specify per-method\n", + "data.rio.write_crs(\"EPSG:4326\", convention=Convention.CF)\n", + "data.rio.write_crs(\"EPSG:4326\", convention=Convention.Zarr)\n", + "```\n", + "\n", "References:\n", "\n", + "- CF: https://cfconventions.org/\n", + "- Zarr Spatial: https://github.com/zarr-conventions/spatial\n", + "- Zarr Proj: https://github.com/zarr-experimental/geo-proj\n", "- Esri: https://pro.arcgis.com/en/pro-app/latest/help/data/multidimensional/spatial-reference-for-netcdf-data.htm\n", "- GDAL: https://gdal.org/drivers/raster/netcdf.html#georeference\n", "- pyproj: https://pyproj4.github.io/pyproj/stable/build_crs_cf.html\n", "\n", - "Operations on xarray objects can cause data loss. Due to this, rioxarray writes and expects the spatial reference information to exist in the coordinates." + "Operations on xarray objects can cause data loss. Due to this, rioxarray writes and expects the spatial reference information to exist appropriately based on the chosen convention." ] }, { "cell_type": "markdown", + "id": "db1f6cb9", "metadata": {}, "source": [ "## Accessing the CRS object" @@ -38,16 +62,24 @@ }, { "cell_type": "markdown", + "id": "c824b4de", "metadata": {}, "source": [ "If you have opened a dataset and the Coordinate Reference System (CRS) can be determined, you can access it via the `rio.crs` accessor.\n", "\n", - "#### Search order for the CRS (DataArray and Dataset):\n", + "#### Search behavior:\n", + "The CRS reading follows the global convention setting from `rioxarray.set_options(convention=...)`:\n", + "\n", + "**CF Convention (default)**:\n", "1. Look in `encoding` of your data array for the `grid_mapping` coordinate name.\n", " Inside the `grid_mapping` coordinate first look for `spatial_ref` then `crs_wkt` and lastly the CF grid mapping attributes.\n", " This is in line with the Climate and Forecast (CF) conventions for storing the CRS as well as GDAL netCDF conventions.\n", "2. Look in the `crs` attribute and load in the CRS from there. This is for backwards compatibility with `xarray.open_rasterio`, which is deprecated since version 0.20.0. We recommend using `rioxarray.open_rasterio` instead.\n", "\n", + "**Zarr Convention**:\n", + "1. Look for `proj:wkt2`, `proj:code`, or `proj:projjson` attributes on the data array (requires convention declaration in `zarr_conventions`)\n", + "2. For Datasets, check group-level `proj:*` attributes for inheritance\n", + "\n", "The value for the `crs` is anything accepted by `rasterio.crs.CRS.from_user_input()`\n", "\n", "#### Search order for the CRS for Dataset:\n", @@ -68,23 +100,29 @@ "- [rio.set_spatial_dims()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.set_spatial_dims)\n", "- [rio.write_coordinate_system()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_coordinate_system)\n", "- [rio.write_transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_transform)\n", - "- [rio.transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.transform)" + "- [rio.transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.transform)\n", + "- [rio.write_zarr_crs()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_zarr_crs) - New Zarr method\n", + "- [rio.write_zarr_transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_zarr_transform) - New Zarr method\n", + "- [rio.write_zarr_conventions()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_zarr_conventions) - New Zarr method" ] }, { "cell_type": "code", "execution_count": null, + "id": "7206fd28", "metadata": {}, "outputs": [], "source": [ "import rioxarray # activate the rio accessor\n", "import xarray\n", - "from affine import Affine" + "from affine import Affine\n", + "from rioxarray import Convention" ] }, { "cell_type": "code", "execution_count": null, + "id": "b4ffed70", "metadata": {}, "outputs": [], "source": [ @@ -94,19 +132,9 @@ { "cell_type": "code", "execution_count": null, + "id": "5ad3e031", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'units': 'DN', 'nodata': 0.0}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "rds.green.attrs" ] @@ -114,383 +142,9 @@ { "cell_type": "code", "execution_count": null, + "id": "c507a5ed", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray 'spatial_ref' ()>\n",
-       "array(0)\n",
-       "Coordinates:\n",
-       "    spatial_ref  int64 0\n",
-       "Attributes:\n",
-       "    spatial_ref:  PROJCS["WGS 84 / UTM zone 22S",GEOGCS["WGS 84",DATUM["WGS_1...
" - ], - "text/plain": [ - "\n", - "array(0)\n", - "Coordinates:\n", - " spatial_ref int64 0\n", - "Attributes:\n", - " spatial_ref: PROJCS[\"WGS 84 / UTM zone 22S\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1..." - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "rds.green.spatial_ref" ] @@ -498,478 +152,154 @@ { "cell_type": "code", "execution_count": null, + "id": "af1e2cd9", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "CRS.from_epsg(32722)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "rds.green.rio.crs" ] }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "id": "34d28ba5", + "metadata": {}, "source": [ "## Setting the CRS\n", "\n", "Use the `rio.write_crs` method to set the CRS on your `xarray.Dataset` or `xarray.DataArray`.\n", - "This modifies the `xarray.Dataset` or `xarray.DataArray` and sets the CRS in a CF compliant manner.\n", + "This modifies the `xarray.Dataset` or `xarray.DataArray` and sets the CRS based on the chosen convention.\n", + "\n", + "### CF Convention\n", + "The CF convention stores metadata in grid_mapping coordinates, which is compatible with NetCDF and GDAL tools.\n", "\n", "- [rio.write_crs()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_crs)\n", "- [rio.crs](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.crs)\n", "\n", - "**Note:** It is recommended to use `rio.write_crs()` if you want the CRS to persist on the Dataset/DataArray and to write the CRS CF compliant metadata. Calling only `rio.set_crs()` CRS storage method is lossy and will not modify the Dataset/DataArray metadata." + "### Zarr Conventions\n", + "The Zarr conventions store metadata as direct attributes, providing better performance for cloud storage.\n", + "\n", + "**Note:** It is recommended to use `rio.write_crs()` if you want the CRS to persist on the Dataset/DataArray and to write the CRS metadata. Calling only `rio.set_crs()` CRS storage method is lossy and will not modify the Dataset/DataArray metadata." + ] + }, + { + "cell_type": "markdown", + "id": "f8618772", + "metadata": {}, + "source": [ + "### Example: CF Convention (Default)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, + "id": "9cd71c90", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
<xarray.DataArray 'spatial_ref' ()>\n",
-       "array(0)\n",
-       "Coordinates:\n",
-       "    spatial_ref  int64 0\n",
-       "Attributes:\n",
-       "    crs_wkt:                      GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["...\n",
-       "    semi_major_axis:              6378137.0\n",
-       "    semi_minor_axis:              6356752.314245179\n",
-       "    inverse_flattening:           298.257223563\n",
-       "    reference_ellipsoid_name:     WGS 84\n",
-       "    longitude_of_prime_meridian:  0.0\n",
-       "    prime_meridian_name:          Greenwich\n",
-       "    geographic_crs_name:          WGS 84\n",
-       "    grid_mapping_name:            latitude_longitude\n",
-       "    spatial_ref:                  GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["...
" - ], - "text/plain": [ - "\n", - "array(0)\n", - "Coordinates:\n", - " spatial_ref int64 0\n", - "Attributes:\n", - " crs_wkt: GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"...\n", - " semi_major_axis: 6378137.0\n", - " semi_minor_axis: 6356752.314245179\n", - " inverse_flattening: 298.257223563\n", - " reference_ellipsoid_name: WGS 84\n", - " longitude_of_prime_meridian: 0.0\n", - " prime_meridian_name: Greenwich\n", - " geographic_crs_name: WGS 84\n", - " grid_mapping_name: latitude_longitude\n", - " spatial_ref: GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"..." - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "xda = xarray.DataArray(1)\n", + "# CF convention is the default\n", "xda.rio.write_crs(4326, inplace=True)\n", "xda.spatial_ref" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, + "id": "96ed350f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "CRS.from_epsg(4326)" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "xda.rio.crs" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "31f25350", + "metadata": {}, + "outputs": [], + "source": [ + "# Show the grid_mapping attribute\n", + "print(f\"grid_mapping: {xda.attrs.get('grid_mapping')}\")\n", + "print(f\"Has spatial_ref coordinate: {'spatial_ref' in xda.coords}\")" + ] + }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "id": "c688cf72", + "metadata": {}, + "source": [ + "### Example: Zarr Convention" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e88f1a0c", + "metadata": {}, + "outputs": [], + "source": [ + "xda_zarr = xarray.DataArray(1)\n", + "# Use Zarr convention explicitly\n", + "xda_zarr.rio.write_crs(4326, convention=Convention.Zarr, inplace=True)\n", + "\n", + "print(f\"proj:code: {xda_zarr.attrs.get('proj:code')}\")\n", + "print(f\"zarr_conventions: {[c['name'] for c in xda_zarr.attrs.get('zarr_conventions', [])]}\")\n", + "print(f\"Has spatial_ref coordinate: {'spatial_ref' in xda_zarr.coords}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff0d8e3c", + "metadata": {}, + "outputs": [], + "source": [ + "xda_zarr.rio.crs" + ] + }, + { + "cell_type": "markdown", + "id": "a1b28611", + "metadata": {}, + "source": [ + "### Example: Global Convention Setting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30eee14b", + "metadata": {}, + "outputs": [], + "source": [ + "# Set Zarr as global default\n", + "with rioxarray.set_options(convention=Convention.Zarr):\n", + " xda_global = xarray.DataArray(1)\n", + " xda_global.rio.write_crs(4326, inplace=True) # Uses Zarr convention\n", + " \n", + " print(f\"Global convention result:\")\n", + " print(f\"proj:code: {xda_global.attrs.get('proj:code')}\")\n", + " print(f\"Has grid_mapping: {'grid_mapping' in xda_global.attrs}\")" + ] + }, + { + "cell_type": "markdown", + "id": "d18f811f", + "metadata": {}, "source": [ "## Spatial dimensions\n", "\n", "Only 1-dimensional X and Y dimensions are supported.\n", "\n", - "The expected X/Y dimension names searched for in the `coords` are:\n", + "The spatial dimension detection follows the global convention setting:\n", "\n", + "**Zarr Convention**:\n", + "- `spatial:dimensions` attribute (e.g., `[\"y\", \"x\"]`)\n", + "\n", + "**CF Convention**:\n", "- x | y\n", - "- longitude | latitude\n", + "- longitude | latitude \n", "- Coordinates (`coords`) with the CF attributes in `attrs`:\n", " - axis: X | Y\n", " - standard_name: longitude | latitude or projection_x_coordinate | projection_y_coordinate" @@ -977,6 +307,7 @@ }, { "cell_type": "markdown", + "id": "f4706324", "metadata": {}, "source": [ "Option 1: Write the CF attributes for non-standard dimension names\n", @@ -991,24 +322,24 @@ { "cell_type": "code", "execution_count": null, + "id": "d8d365cb", "metadata": {}, "outputs": [], "source": [ "rds.rio.write_crs(\n", - " 4326\n", + " 4326,\n", " inplace=True,\n", ").rio.set_spatial_dims(\n", " x_dim=\"lon\",\n", - " y_dim=\"lat\"\n", + " y_dim=\"lat\",\n", " inplace=True,\n", ").rio.write_coordinate_system(inplace=True)" ] }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "id": "dbfa143b", + "metadata": {}, "source": [ "Option 2: Rename your coordinates\n", "\n", @@ -1018,17 +349,17 @@ { "cell_type": "code", "execution_count": null, + "id": "3eecbc02", "metadata": {}, "outputs": [], "source": [ - "rds = rds.rename(lon=longitude, lat=latitude) " + "rds = rds.rename({\"lon\": \"longitude\", \"lat\": \"latitude\"}) " ] }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "id": "2710362a", + "metadata": {}, "source": [ "## Setting the transform of the dataset\n", "\n", @@ -1036,73 +367,143 @@ "This method is useful if your netCDF file does not have coordinates present.\n", "Use the `rio.write_transform` method to set the transform on your `xarray.Dataset` or `xarray.DataArray`.\n", "\n", + "The transform storage follows the chosen convention:\n", + "- **CF Convention**: Stored as `GeoTransform` attribute on grid_mapping coordinate\n", + "- **Zarr Convention**: Stored as `spatial:transform` numeric array attribute\n", + "\n", "- [rio.write_transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_transform)\n", - "- [rio.transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.transform)" + "- [rio.transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.transform)\n", + "- [rio.write_zarr_transform()](../rioxarray.rst#rioxarray.rioxarray.XRasterBase.write_zarr_transform) - Zarr-specific method" + ] + }, + { + "cell_type": "markdown", + "id": "ca24cbe4", + "metadata": {}, + "source": [ + "### Example: CF Convention Transform" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, + "id": "af702cd1", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'466266.0 3.0 0.0 8084700.0 0.0 -3.0'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "transform = Affine(3.0, 0.0, 466266.0, 0.0, -3.0, 8084700.0)\n", - "xda.rio.write_transform(transform, inplace=True)\n", - "xda.spatial_ref.GeoTransform" + "xda.rio.write_transform(transform, convention=Convention.CF, inplace=True)\n", + "print(f\"GeoTransform: {xda.spatial_ref.GeoTransform}\")" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, + "id": "b504f388", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Affine(3.0, 0.0, 466266.0,\n", - " 0.0, -3.0, 8084700.0)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "xda.rio.transform()" ] + }, + { + "cell_type": "markdown", + "id": "bbb39bdb", + "metadata": {}, + "source": [ + "### Example: Zarr Convention Transform" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21aa24d8", + "metadata": {}, + "outputs": [], + "source": [ + "xda_zarr_transform = xarray.DataArray(1)\n", + "xda_zarr_transform.rio.write_transform(transform, convention=Convention.Zarr, inplace=True)\n", + "print(f\"spatial:transform: {xda_zarr_transform.attrs.get('spatial:transform')}\")\n", + "print(f\"zarr_conventions: {[c['name'] for c in xda_zarr_transform.attrs.get('zarr_conventions', [])]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb150a4f", + "metadata": {}, + "outputs": [], + "source": [ + "xda_zarr_transform.rio.transform()" + ] + }, + { + "cell_type": "markdown", + "id": "5e08da24", + "metadata": {}, + "source": [ + "## Zarr-Specific Methods\n", + "\n", + "rioxarray provides specialized methods for working with Zarr conventions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "856d2350", + "metadata": {}, + "outputs": [], + "source": [ + "# Write CRS in multiple Zarr formats\n", + "sample_data = xarray.DataArray([[1, 2], [3, 4]], dims=[\"y\", \"x\"])\n", + "zarr_all_formats = sample_data.rio.write_zarr_crs(\"EPSG:4326\", format=\"all\")\n", + "\n", + "print(f\"proj:code: {zarr_all_formats.attrs.get('proj:code')}\")\n", + "print(f\"Has proj:wkt2: {'proj:wkt2' in zarr_all_formats.attrs}\")\n", + "print(f\"Has proj:projjson: {'proj:projjson' in zarr_all_formats.attrs}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81a57c85", + "metadata": {}, + "outputs": [], + "source": [ + "# Write complete Zarr conventions\n", + "complete_zarr = sample_data.rio.write_zarr_conventions(\n", + " input_crs=\"EPSG:4326\",\n", + " transform=transform,\n", + " crs_format=\"code\"\n", + ")\n", + "\n", + "print(f\"Has CRS: {'proj:code' in complete_zarr.attrs}\")\n", + "print(f\"Has transform: {'spatial:transform' in complete_zarr.attrs}\")\n", + "print(f\"Has dimensions: {'spatial:dimensions' in complete_zarr.attrs}\")\n", + "print(f\"spatial:bbox: {complete_zarr.attrs.get('spatial:bbox')}\")" + ] + }, + { + "cell_type": "markdown", + "id": "42a5c097", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "- **CF Convention (default)**: Traditional approach, widely compatible, uses grid_mapping coordinates\n", + "- **Zarr Conventions**: Zarr approach, better performance for cloud storage, uses direct attributes\n", + "- **Convention choice**: Set globally with `set_options()` or per-method with `convention=` parameter\n", + "- **Backward compatibility**: All existing code continues to work unchanged\n", + "\n", + "For more examples and detailed information, see the [Geospatial Metadata Conventions](../conventions.rst) documentation." + ] } ], "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.4" + "name": "python" } }, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/docs/history.rst b/docs/history.rst index b3451e75..17ae4209 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -3,6 +3,7 @@ History Latest ------ +- ENH: Add support for Zarr spatial and proj conventions with Convention enum, global settings, and Zarr-specific methods (#883) 0.20.0 ------ diff --git a/docs/index.rst b/docs/index.rst index 348ebbd4..c4558894 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ GitHub: http://github.com/corteva/rioxarray installation getting_started/getting_started examples/examples + conventions modules contributing authors diff --git a/docs/rioxarray.rst b/docs/rioxarray.rst index b32f8658..4f80d5de 100644 --- a/docs/rioxarray.rst +++ b/docs/rioxarray.rst @@ -21,6 +21,15 @@ rioxarray.set_options .. autoclass:: rioxarray.set_options +rioxarray.Convention +-------------------- + +.. autoclass:: rioxarray.Convention + :members: + :undoc-members: + :show-inheritance: + + rioxarray.show_versions ----------------------- diff --git a/pyproject.toml b/pyproject.toml index 03dedf36..5d544cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ requires-python = ">=3.12" dependencies = [ "packaging", - "rasterio>=1.4.3", + "rasterio", # https://github.com/pydata/xarray/issues/11000 "xarray>=2024.7.0,<2025.12", "pyproj>=3.3", @@ -65,6 +65,19 @@ interp = [ all = [ "scipy" ] +test = [ + "pytest", + "pytest-cov", + "pytest-timeout", + "dask", + "netcdf4", +] +dev = [ + "rioxarray[test]", + "pylint", + "mypy", + "pre-commit", +] [tool.black] target_version = ["py310"] diff --git a/rioxarray/__init__.py b/rioxarray/__init__.py index 603030aa..2390006f 100644 --- a/rioxarray/__init__.py +++ b/rioxarray/__init__.py @@ -7,6 +7,7 @@ from rioxarray._io import open_rasterio from rioxarray._options import set_options from rioxarray._show_versions import show_versions +from rioxarray.enum import Convention __version__ = importlib.metadata.version(__package__) @@ -14,6 +15,7 @@ "open_rasterio", "set_options", "show_versions", + "Convention", "__author__", "__version__", ] diff --git a/rioxarray/_convention/__init__.py b/rioxarray/_convention/__init__.py new file mode 100644 index 00000000..9f860984 --- /dev/null +++ b/rioxarray/_convention/__init__.py @@ -0,0 +1 @@ +"""Convention handling modules.""" diff --git a/rioxarray/_convention/cf.py b/rioxarray/_convention/cf.py new file mode 100644 index 00000000..57e726bb --- /dev/null +++ b/rioxarray/_convention/cf.py @@ -0,0 +1,243 @@ +""" +CF (Climate and Forecasts) convention support for rioxarray. + +This module provides functions for reading and writing geospatial metadata according to +the CF conventions: https://github.com/cf-convention/cf-conventions +""" +from typing import Optional, Union + +import pyproj +import rasterio.crs +import xarray +from affine import Affine + +from rioxarray._options import EXPORT_GRID_MAPPING, get_option +from rioxarray.crs import crs_from_user_input + + +def read_crs( + obj: Union[xarray.Dataset, xarray.DataArray], grid_mapping: Optional[str] = None +) -> Optional[rasterio.crs.CRS]: + """ + Read CRS from CF conventions. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to read CRS from + grid_mapping : str, optional + Name of the grid_mapping coordinate variable + + Returns + ------- + rasterio.crs.CRS or None + CRS object, or None if not found + """ + if grid_mapping is None: + # Try to find grid_mapping attribute on data variables + if hasattr(obj, "data_vars"): + for data_var in obj.data_vars.values(): + if "grid_mapping" in data_var.attrs: + grid_mapping = data_var.attrs["grid_mapping"] + break + elif hasattr(obj, "attrs") and "grid_mapping" in obj.attrs: + grid_mapping = obj.attrs["grid_mapping"] + + if grid_mapping is None: + # look in attrs for 'crs' + try: + return crs_from_user_input(obj.attrs["crs"]) + except KeyError: + return None + + try: + grid_mapping_coord = obj.coords[grid_mapping] + except KeyError: + return None + + # Look in wkt attributes first for performance + for crs_attr in ("spatial_ref", "crs_wkt"): + try: + return crs_from_user_input(grid_mapping_coord.attrs[crs_attr]) + except KeyError: + pass + + # Look in grid_mapping CF attributes + try: + return pyproj.CRS.from_cf(grid_mapping_coord.attrs) + except (KeyError, pyproj.exceptions.CRSError): + pass + + return None + + +def read_transform( + obj: Union[xarray.Dataset, xarray.DataArray], grid_mapping: Optional[str] = None +) -> Optional[Affine]: + """ + Read transform from CF conventions. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to read transform from + grid_mapping : str, optional + Name of the grid_mapping coordinate variable + + Returns + ------- + affine.Affine or None + Transform object, or None if not found + """ + if grid_mapping is None: + # Try to find grid_mapping attribute on data variables + if hasattr(obj, "data_vars"): + for data_var in obj.data_vars.values(): + if "grid_mapping" in data_var.attrs: + grid_mapping = data_var.attrs["grid_mapping"] + break + elif hasattr(obj, "attrs") and "grid_mapping" in obj.attrs: + grid_mapping = obj.attrs["grid_mapping"] + + if grid_mapping is not None: + try: + grid_mapping_coord = obj.coords[grid_mapping] + geotransform = grid_mapping_coord.attrs.get("GeoTransform") + if geotransform is not None: + return _parse_geotransform(geotransform) + except KeyError: + pass + + # Look in dataset attributes for transform + try: + transform = obj.attrs["transform"] + if hasattr(transform, "__iter__") and len(transform) == 6: + return Affine(*transform) + return transform + except KeyError: + pass + + return None + + +def write_crs( + obj: Union[xarray.Dataset, xarray.DataArray], + input_crs: Optional[object] = None, + grid_mapping_name: str = "spatial_ref", + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write CRS using CF conventions. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write CRS to + input_crs : object, optional + CRS to write. Can be anything accepted by rasterio.crs.CRS.from_user_input + grid_mapping_name : str, default "spatial_ref" + Name for the grid_mapping coordinate + inplace : bool, default True + If True, modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Object with CRS written + """ + if input_crs is None: + return obj + + crs = crs_from_user_input(input_crs) + if crs is None: + return obj + + obj_out = obj if inplace else obj.copy(deep=True) + + # Create grid_mapping coordinate if it doesn't exist + if grid_mapping_name not in obj_out.coords: + obj_out = obj_out.assign_coords({grid_mapping_name: xarray.DataArray(0)}) + + # Write WKT for compatibility + obj_out.coords[grid_mapping_name].attrs["spatial_ref"] = crs.to_wkt() + obj_out.coords[grid_mapping_name].attrs["crs_wkt"] = crs.to_wkt() + + # Write CF attributes if enabled + if get_option(EXPORT_GRID_MAPPING): + try: + # Convert to pyproj.CRS for CF support + pyproj_crs = pyproj.CRS.from_user_input(crs) + cf_dict = pyproj_crs.to_cf() + obj_out.coords[grid_mapping_name].attrs.update(cf_dict) + except (pyproj.exceptions.CRSError, AttributeError): + pass + + # Set grid_mapping attribute on data variables + if hasattr(obj_out, "data_vars"): + for data_var_name in obj_out.data_vars: + obj_out[data_var_name].attrs["grid_mapping"] = grid_mapping_name + else: + obj_out.attrs["grid_mapping"] = grid_mapping_name + + return obj_out + + +def write_transform( + obj: Union[xarray.Dataset, xarray.DataArray], + transform: Optional[Affine] = None, + grid_mapping_name: str = "spatial_ref", + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write transform using CF conventions. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write transform to + transform : affine.Affine, optional + Transform to write + grid_mapping_name : str, default "spatial_ref" + Name for the grid_mapping coordinate + inplace : bool, default True + If True, modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Object with transform written + """ + if transform is None: + return obj + + obj_out = obj if inplace else obj.copy(deep=True) + + # Create grid_mapping coordinate if it doesn't exist + if grid_mapping_name not in obj_out.coords: + obj_out = obj_out.assign_coords({grid_mapping_name: xarray.DataArray(0)}) + + # Write GeoTransform as GDAL format string + geotransform_str = f"{transform.a} {transform.b} {transform.c} {transform.d} {transform.e} {transform.f}" + obj_out.coords[grid_mapping_name].attrs["GeoTransform"] = geotransform_str + + # Also write as dataset attribute for backward compatibility + obj_out.attrs["transform"] = tuple(transform) + + return obj_out + + +def _parse_geotransform(geotransform: Union[str, list, tuple]) -> Optional[Affine]: + """Parse GeoTransform from CF conventions.""" + if isinstance(geotransform, str): + try: + vals = [float(val) for val in geotransform.split()] + if len(vals) == 6: + return Affine(*vals) + except (ValueError, TypeError): + pass + elif hasattr(geotransform, "__iter__") and len(geotransform) == 6: + try: + return Affine(*geotransform) + except (ValueError, TypeError): + pass + return None diff --git a/rioxarray/_convention/zarr.py b/rioxarray/_convention/zarr.py new file mode 100644 index 00000000..634861c5 --- /dev/null +++ b/rioxarray/_convention/zarr.py @@ -0,0 +1,522 @@ +""" +Zarr spatial and proj convention support for rioxarray. + +This module provides functions for reading and writing geospatial metadata according to: +- Zarr spatial convention: https://github.com/zarr-conventions/spatial +- Zarr geo-proj convention: https://github.com/zarr-experimental/geo-proj +""" +import json +from typing import Optional, Tuple, Union + +import rasterio.crs +import xarray +from affine import Affine + +from rioxarray.crs import crs_from_user_input + +# Convention identifiers +PROJ_CONVENTION = { + "schema_url": "https://raw.githubusercontent.com/zarr-experimental/geo-proj/refs/tags/v1/schema.json", + "spec_url": "https://github.com/zarr-experimental/geo-proj/blob/v1/README.md", + "uuid": "f17cb550-5864-4468-aeb7-f3180cfb622f", + "name": "proj:", + "description": "Coordinate reference system information for geospatial data", +} + +SPATIAL_CONVENTION = { + "schema_url": "https://raw.githubusercontent.com/zarr-conventions/spatial/refs/tags/v1/schema.json", + "spec_url": "https://github.com/zarr-conventions/spatial/blob/v1/README.md", + "uuid": "689b58e2-cf7b-45e0-9fff-9cfc0883d6b4", + "name": "spatial:", + "description": "Spatial coordinate information", +} + + +def _parse_crs_from_attrs(attrs: dict, convention_check: bool = True) -> Optional[rasterio.crs.CRS]: + """ + Parse CRS from proj: attributes with fallback priority. + + Parameters + ---------- + attrs : dict + Attributes dictionary to parse from + convention_check : bool, default True + Whether to check for convention declaration + + Returns + ------- + rasterio.crs.CRS or None + Parsed CRS object, or None if not found + """ + if convention_check and not has_convention_declared(attrs, "proj:"): + return None + + for proj_attr, parser in [ + ("proj:wkt2", parse_proj_wkt2), + ("proj:code", parse_proj_code), + ("proj:projjson", parse_proj_projjson), + ]: + try: + proj_value = attrs.get(proj_attr) + if proj_value is not None: + parsed_crs = parser(proj_value) + if parsed_crs is not None: + return parsed_crs + except (KeyError, Exception): + pass + return None + + +def _parse_transform_from_attrs(attrs: dict, convention_check: bool = True) -> Optional[Affine]: + """ + Parse transform from spatial: attributes. + + Parameters + ---------- + attrs : dict + Attributes dictionary to parse from + convention_check : bool, default True + Whether to check for convention declaration + + Returns + ------- + affine.Affine or None + Parsed transform object, or None if not found + """ + if convention_check and not has_convention_declared(attrs, "spatial:"): + return None + + try: + spatial_transform = attrs.get("spatial:transform") + if spatial_transform is not None: + return parse_spatial_transform(spatial_transform) + except (KeyError, Exception): + pass + return None + + +def read_crs( + obj: Union[xarray.Dataset, xarray.DataArray] +) -> Optional[rasterio.crs.CRS]: + """ + Read CRS from Zarr proj: convention. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to read CRS from + + Returns + ------- + rasterio.crs.CRS or None + CRS object, or None if not found + """ + # Parse CRS from object attributes + return _parse_crs_from_attrs(obj.attrs) + + +def read_transform(obj: Union[xarray.Dataset, xarray.DataArray]) -> Optional[Affine]: + """ + Read transform from Zarr spatial: convention. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to read transform from + + Returns + ------- + affine.Affine or None + Transform object, or None if not found + """ + # Parse transform from object attributes + return _parse_transform_from_attrs(obj.attrs) + + +def read_spatial_dimensions( + obj: Union[xarray.Dataset, xarray.DataArray] +) -> Optional[Tuple[str, str]]: + """ + Read spatial dimensions from Zarr spatial: convention. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to read spatial dimensions from + + Returns + ------- + tuple of (y_dim, x_dim) or None + Tuple of dimension names, or None if not found + """ + # Only interpret spatial:* attributes if convention is declared + if not has_convention_declared(obj.attrs, "spatial:"): + return None + + try: + spatial_dims = obj.attrs.get("spatial:dimensions") + if spatial_dims is not None and len(spatial_dims) >= 2: + # spatial:dimensions format is ["y", "x"] or similar + y_dim_name, x_dim_name = spatial_dims[-2:] # Take last two + if y_dim_name in obj.dims and x_dim_name in obj.dims: + return y_dim_name, x_dim_name + except (KeyError, Exception): + pass + + return None + + +def write_crs( + obj: Union[xarray.Dataset, xarray.DataArray], + input_crs: Optional[object] = None, + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write CRS using Zarr proj: convention. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write CRS to + input_crs : object, optional + CRS to write. Can be anything accepted by rasterio.crs.CRS.from_user_input + inplace : bool, default True + If True, modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Object with CRS written + """ + if input_crs is None: + return obj + + crs = crs_from_user_input(input_crs) + if crs is None: + return obj + + obj_out = obj if inplace else obj.copy(deep=True) + + # Ensure proj: convention is declared + obj_out.attrs = add_convention_declaration(obj_out.attrs, "proj:", inplace=True) + + # Write as WKT2 format + obj_out.attrs["proj:wkt2"] = format_proj_wkt2(crs) + + return obj_out + + +def write_transform( + obj: Union[xarray.Dataset, xarray.DataArray], + transform: Optional[Affine] = None, + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write transform using Zarr spatial: convention. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write transform to + transform : affine.Affine, optional + Transform to write + inplace : bool, default True + If True, modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Object with transform written + """ + if transform is None: + return obj + + obj_out = obj if inplace else obj.copy(deep=True) + + # Ensure spatial: convention is declared + obj_out.attrs = add_convention_declaration(obj_out.attrs, "spatial:", inplace=True) + + # Write spatial:transform as numeric array + obj_out.attrs["spatial:transform"] = format_spatial_transform(transform) + + from rioxarray.raster_array import RasterArray + + rio = RasterArray(obj) + + if rio.y_dim and rio.x_dim: + obj_out = _write_spatial_metadata( + obj_out, rio.y_dim, rio.x_dim, transform=transform, inplace=True + ) + + return obj_out + + +def _write_spatial_metadata( + obj: Union[xarray.Dataset, xarray.DataArray], + y_dim: str, + x_dim: str, + transform: Optional[Affine] = None, + include_bbox: bool = True, + include_registration: bool = True, + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write complete Zarr spatial: metadata. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write metadata to + y_dim, x_dim : str + Names of spatial dimensions + transform : affine.Affine, optional + Transform to use for bbox calculation + include_bbox : bool, default True + Whether to include spatial:bbox + include_registration : bool, default True + Whether to include spatial:registration + inplace : bool, default True + If True, modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Object with spatial metadata written + """ + obj_out = obj if inplace else obj.copy(deep=True) + + # Ensure spatial: convention is declared + obj_out.attrs = add_convention_declaration(obj_out.attrs, "spatial:", inplace=True) + + # Write spatial:dimensions + obj_out.attrs["spatial:dimensions"] = [y_dim, x_dim] + + # Write spatial:shape + if y_dim in obj.dims and x_dim in obj.dims: + height = obj.sizes[y_dim] + width = obj.sizes[x_dim] + obj_out.attrs["spatial:shape"] = [height, width] + + # Write spatial:bbox if transform is available + if include_bbox and transform is not None: + # Write spatial:bbox if dimensions are available + if x_dim in obj.dims and y_dim in obj.dims: + height = obj.sizes[y_dim] + width = obj.sizes[x_dim] + bbox = calculate_spatial_bbox(transform, (height, width)) + obj_out.attrs["spatial:bbox"] = list(bbox) + + # Write spatial:registration (default to pixel) + if include_registration: + obj_out.attrs["spatial:registration"] = "pixel" + + return obj_out + + +# Utility functions moved from zarr_conventions.py +def parse_spatial_transform(spatial_transform: Union[list, tuple]) -> Optional[Affine]: + """Convert spatial:transform array to Affine object.""" + if not isinstance(spatial_transform, (list, tuple)): + return None + if len(spatial_transform) != 6: + return None + try: + return Affine(*spatial_transform) + except (TypeError, ValueError): + return None + + +def format_spatial_transform(affine: Affine) -> list: + """Convert Affine object to spatial:transform array.""" + return [affine.a, affine.b, affine.c, affine.d, affine.e, affine.f] + + +def parse_proj_code(proj_code: str) -> Optional[rasterio.crs.CRS]: + """Parse proj:code to CRS.""" + if not isinstance(proj_code, str): + return None + return rasterio.crs.CRS.from_user_input(proj_code) + + +def format_proj_code(crs: rasterio.crs.CRS) -> Optional[str]: + """Format CRS as proj:code if it has an authority code.""" + auth_code = crs.to_authority() + if auth_code: + authority, code = auth_code + return f"{authority}:{code}" + return None + + +def parse_proj_wkt2(proj_wkt2: str) -> Optional[rasterio.crs.CRS]: + """Parse proj:wkt2 to CRS.""" + if not isinstance(proj_wkt2, str): + return None + return rasterio.crs.CRS.from_wkt(proj_wkt2) + + +def format_proj_wkt2(crs: rasterio.crs.CRS) -> str: + """Format CRS as proj:wkt2 (WKT2 string).""" + return crs.to_wkt() + + +def parse_proj_projjson(proj_projjson: Union[dict, str]) -> Optional[rasterio.crs.CRS]: + """Parse proj:projjson to CRS.""" + if isinstance(proj_projjson, str): + proj_projjson = json.loads(proj_projjson) + + if not isinstance(proj_projjson, dict): + return None + + return rasterio.crs.CRS.from_json(json.dumps(proj_projjson)) + + +def format_proj_projjson(crs: rasterio.crs.CRS) -> dict: + """Format CRS as proj:projjson (PROJJSON object).""" + # Use _projjson() method for proper PROJJSON format + projjson_str = crs._projjson() + return json.loads(projjson_str) + + +def calculate_spatial_bbox( + transform: Affine, shape: Tuple[int, int] +) -> Tuple[float, float, float, float]: + """Calculate bounding box from transform and shape.""" + height, width = shape + + # Corner coordinates in pixel space + corners = [ + (0, 0), # top-left + (width, 0), # top-right + (width, height), # bottom-right + (0, height), # bottom-left + ] + + # Transform to spatial coordinates + spatial_corners = [transform * corner for corner in corners] + + # Extract x and y coordinates + x_coords = [corner[0] for corner in spatial_corners] + y_coords = [corner[1] for corner in spatial_corners] + + # Return bounding box as [xmin, ymin, xmax, ymax] + return (min(x_coords), min(y_coords), max(x_coords), max(y_coords)) + + +def has_convention_declared(attrs: dict, convention_name: str) -> bool: + """Check if a convention is declared in zarr_conventions.""" + zarr_conventions = attrs.get("zarr_conventions", []) + if not isinstance(zarr_conventions, list): + return False + + for convention in zarr_conventions: + if isinstance(convention, dict) and convention.get("name") == convention_name: + return True + + return False + + +def get_declared_conventions(attrs: dict) -> set: + """Get set of declared convention names.""" + zarr_conventions = attrs.get("zarr_conventions", []) + if not isinstance(zarr_conventions, list): + return set() + + declared = set() + for convention in zarr_conventions: + if isinstance(convention, dict) and "name" in convention: + declared.add(convention["name"]) + + return declared + + +def add_convention_declaration( + attrs: dict, convention_name: str, inplace: bool = False +) -> dict: + """Add convention declaration to zarr_conventions.""" + attrs_out = attrs if inplace else attrs.copy() + + # Get the convention identifier + if convention_name == "proj:": + convention = PROJ_CONVENTION + elif convention_name == "spatial:": + convention = SPATIAL_CONVENTION + else: + return attrs_out + + # Initialize zarr_conventions if needed + if "zarr_conventions" not in attrs_out: + attrs_out["zarr_conventions"] = [] + + # Check if already declared + if has_convention_declared(attrs_out, convention_name): + return attrs_out + + # Add the convention + attrs_out["zarr_conventions"].append(convention) + + return attrs_out + + +def write_conventions( + obj: Union[xarray.Dataset, xarray.DataArray], + input_crs: Optional[str] = None, + transform: Optional[Affine] = None, + inplace: bool = True, +) -> Union[xarray.Dataset, xarray.DataArray]: + """ + Write complete Zarr spatial and proj conventions. + + Convenience method that writes both CRS (proj:) and spatial (spatial:) + convention metadata in a single call. + + Parameters + ---------- + obj : xarray.Dataset or xarray.DataArray + Object to write metadata to + input_crs : str, optional + CRS to write. If not provided, object must have existing CRS. + transform : affine.Affine, optional + Transform to write. If not provided, it will be calculated from obj. + inplace : bool, default True + Whether to modify object in place + + Returns + ------- + xarray.Dataset or xarray.DataArray + Modified object with complete Zarr conventions + """ + from rioxarray.raster_array import RasterArray + + # Get CRS if provided + if input_crs: + crs = crs_from_user_input(input_crs) + else: + # Try to get CRS from object + rio = RasterArray(obj) + crs = rio.crs + if crs is None: + raise ValueError("No CRS available and input_crs not provided") + + # Write CRS (WKT2 format only) + obj_modified = write_crs(obj, crs, inplace=inplace) + + # Write transform and spatial metadata + if transform is not None: + # Get dimensions + rio = RasterArray(obj_modified) + obj_modified = write_transform( + obj_modified, + transform, + y_dim=rio.y_dim, + x_dim=rio.x_dim, + inplace=True + ) + else: + # Write just spatial metadata if no transform + rio = RasterArray(obj_modified) + if rio.x_dim and rio.y_dim: + obj_modified = _write_spatial_metadata( + obj_modified, rio.y_dim, rio.x_dim, transform=None, inplace=True + ) + + return obj_modified diff --git a/rioxarray/_options.py b/rioxarray/_options.py index 1dc55ffa..d95968b6 100644 --- a/rioxarray/_options.py +++ b/rioxarray/_options.py @@ -8,17 +8,22 @@ """ from typing import Any +from rioxarray.enum import Convention + EXPORT_GRID_MAPPING = "export_grid_mapping" SKIP_MISSING_SPATIAL_DIMS = "skip_missing_spatial_dims" +CONVENTION = "convention" OPTIONS = { EXPORT_GRID_MAPPING: True, SKIP_MISSING_SPATIAL_DIMS: False, + CONVENTION: None, } OPTION_NAMES = set(OPTIONS) VALIDATORS = { EXPORT_GRID_MAPPING: lambda choice: isinstance(choice, bool), + CONVENTION: lambda choice: choice is None or isinstance(choice, Convention), } @@ -60,6 +65,10 @@ class set_options: # pylint: disable=invalid-name If True, it will not perform spatial operations on variables within a :class:`xarray.Dataset` if the spatial dimensions are not found. + convention: Convention, default=Convention.CF + The default geospatial metadata convention to use for reading and writing. + Choose from Convention.CF (Climate and Forecasts) or Convention.Zarr + (Zarr spatial and proj conventions). Usage as a context manager:: @@ -70,6 +79,7 @@ class set_options: # pylint: disable=invalid-name Usage for global settings:: rioxarray.set_options(export_grid_mapping=False) + rioxarray.set_options(convention=Convention.Zarr) """ diff --git a/rioxarray/crs.py b/rioxarray/crs.py index cb3ba8c9..ad05e7d7 100644 --- a/rioxarray/crs.py +++ b/rioxarray/crs.py @@ -5,7 +5,6 @@ import rasterio import rasterio.crs -from packaging import version from pyproj import CRS from rasterio.errors import CRSError @@ -20,7 +19,12 @@ def crs_from_user_input(crs_input: Any) -> rasterio.crs.CRS: Parameters ---------- crs_input: Any - Input to create a CRS. + Input to create a CRS. Can be: + - rasterio.crs.CRS object + - WKT string + - PROJ string + - EPSG code (int or string) + - PROJJSON dict (Zarr proj:projjson format) Returns ------- @@ -29,6 +33,11 @@ def crs_from_user_input(crs_input: Any) -> rasterio.crs.CRS: """ if isinstance(crs_input, rasterio.crs.CRS): return crs_input + + # Handle PROJJSON dict (Zarr proj:projjson convention) + if isinstance(crs_input, dict): + crs_input = CRS.from_json_dict(crs_input) + try: # old versions of opendatacube CRS crs_input = crs_input.wkt @@ -40,6 +49,4 @@ def crs_from_user_input(crs_input: Any) -> rasterio.crs.CRS: pass # use pyproj for edge cases crs = CRS.from_user_input(crs_input) - if version.parse(rasterio.__gdal_version__) > version.parse("3.0.0"): - return rasterio.crs.CRS.from_wkt(crs.to_wkt()) - return rasterio.crs.CRS.from_wkt(crs.to_wkt("WKT1_GDAL")) + return rasterio.crs.CRS.from_wkt(crs.to_wkt()) diff --git a/rioxarray/enum.py b/rioxarray/enum.py new file mode 100644 index 00000000..d3975439 --- /dev/null +++ b/rioxarray/enum.py @@ -0,0 +1,46 @@ +"""Enums for rioxarray.""" +from enum import Enum + + +class Convention(Enum): + """ + Supported geospatial metadata conventions. + + rioxarray supports two conventions for storing geospatial metadata: + + - CF: Climate and Forecasts convention using grid_mapping coordinates + - Zarr: Zarr spatial and proj conventions using direct attributes + + The convention can be set globally using set_options() or per-method + using the convention parameter. + + Examples + -------- + Set global convention: + + >>> import rioxarray + >>> rioxarray.set_options(convention=rioxarray.Convention.Zarr) + + Use specific convention for a method: + + >>> data.rio.write_crs("EPSG:4326", convention=rioxarray.Convention.CF) + + See Also + -------- + rioxarray.set_options : Set global options including convention + + References + ---------- + .. [1] CF Conventions: https://github.com/cf-convention/cf-conventions + .. [2] Zarr Spatial Convention: https://github.com/zarr-conventions/spatial + .. [3] Zarr Geo-Proj Convention: https://github.com/zarr-experimental/geo-proj + """ + + #: Climate and Forecasts convention (default) + #: https://github.com/cf-convention/cf-conventions + CF = "CF" + + #: Zarr spatial and proj conventions + #: https://github.com/zarr-conventions/spatial + #: https://github.com/zarr-experimental/geo-proj + Zarr = "Zarr" diff --git a/rioxarray/rioxarray.py b/rioxarray/rioxarray.py index dfa33204..73643099 100644 --- a/rioxarray/rioxarray.py +++ b/rioxarray/rioxarray.py @@ -11,7 +11,6 @@ from typing import Any, Literal, Optional, Union import numpy -import pyproj import rasterio.warp import rasterio.windows import xarray @@ -21,8 +20,10 @@ from rasterio.control import GroundControlPoint from rasterio.crs import CRS -from rioxarray._options import EXPORT_GRID_MAPPING, get_option +from rioxarray._convention import cf, zarr +from rioxarray._options import CONVENTION, get_option from rioxarray.crs import crs_from_user_input +from rioxarray.enum import Convention from rioxarray.exceptions import ( DimensionError, DimensionMissingCoordinateError, @@ -270,30 +271,54 @@ def __init__(self, xarray_obj: Union[xarray.DataArray, xarray.Dataset]): self._x_dim: Optional[Hashable] = None self._y_dim: Optional[Hashable] = None - # Determine the spatial dimensions of the `xarray.DataArray` - if "x" in self._obj.dims and "y" in self._obj.dims: - self._x_dim = "x" - self._y_dim = "y" - elif "longitude" in self._obj.dims and "latitude" in self._obj.dims: - self._x_dim = "longitude" - self._y_dim = "latitude" - else: - # look for coordinates with CF attributes - for coord in self._obj.coords: - # make sure to only look in 1D coordinates - # that has the same dimension name as the coordinate - if self._obj.coords[coord].dims != (coord,): - continue - if (self._obj.coords[coord].attrs.get("axis", "").upper() == "X") or ( - self._obj.coords[coord].attrs.get("standard_name", "").lower() - in ("longitude", "projection_x_coordinate") - ): - self._x_dim = coord - elif (self._obj.coords[coord].attrs.get("axis", "").upper() == "Y") or ( - self._obj.coords[coord].attrs.get("standard_name", "").lower() - in ("latitude", "projection_y_coordinate") - ): - self._y_dim = coord + + # Read spatial dimensions using the global convention setting + convention = get_option(CONVENTION) + + if convention is Convention.Zarr: + spatial_dims = zarr.read_spatial_dimensions(self._obj) + if spatial_dims is not None: + self._y_dim, self._x_dim = spatial_dims + elif convention is Convention.CF or convention is None: + # Use CF convention logic for dimension detection + # Also use this as fallback when convention is None + if "x" in self._obj.dims and "y" in self._obj.dims: + self._x_dim = "x" + self._y_dim = "y" + elif "longitude" in self._obj.dims and "latitude" in self._obj.dims: + self._x_dim = "longitude" + self._y_dim = "latitude" + else: + # look for coordinates with CF attributes + for coord in self._obj.coords: + # make sure to only look in 1D coordinates + # that has the same dimension name as the coordinate + if self._obj.coords[coord].dims != (coord,): + continue + if ( + self._obj.coords[coord].attrs.get("axis", "").upper() == "X" + ) or ( + self._obj.coords[coord].attrs.get("standard_name", "").lower() + in ("longitude", "projection_x_coordinate") + ): + self._x_dim = coord + elif ( + self._obj.coords[coord].attrs.get("axis", "").upper() == "Y" + ) or ( + self._obj.coords[coord].attrs.get("standard_name", "").lower() + in ("latitude", "projection_y_coordinate") + ): + self._y_dim = coord + + # If no dimensions found by CF when convention is None and Zarr conventions are declared, try Zarr as fallback + if ( + (self._x_dim is None or self._y_dim is None) + and convention is None + and zarr.has_convention_declared(self._obj.attrs, "spatial:") + ): + spatial_dims = zarr.read_spatial_dimensions(self._obj) + if spatial_dims is not None: + self._y_dim, self._x_dim = spatial_dims # properties self._count: Optional[int] = None @@ -310,32 +335,31 @@ def crs(self) -> Optional[rasterio.crs.CRS]: if self._crs is not None: return None if self._crs is False else self._crs - # look in wkt attributes to avoid using - # pyproj CRS if possible for performance - for crs_attr in ("spatial_ref", "crs_wkt"): - try: - self._set_crs( - self._obj.coords[self.grid_mapping].attrs[crs_attr], - inplace=True, - ) - return self._crs - except KeyError: - pass - - # look in grid_mapping - try: - self._set_crs( - pyproj.CRS.from_cf(self._obj.coords[self.grid_mapping].attrs), - inplace=True, - ) - except (KeyError, pyproj.exceptions.CRSError): - try: - # look in attrs for 'crs' - self._set_crs(self._obj.attrs["crs"], inplace=True) - except KeyError: - self._crs = False - return None - return self._crs + # Read using global convention setting + parsed_crs = None + + # Check global convention setting + convention = get_option(CONVENTION) + if convention is Convention.CF: + parsed_crs = cf.read_crs(self._obj, self.grid_mapping) + elif convention is Convention.Zarr: + parsed_crs = zarr.read_crs(self._obj) + elif convention is None: + # Use CF as default when convention is None + parsed_crs = cf.read_crs(self._obj, self.grid_mapping) + # If CF didn't find anything and Zarr conventions are declared, try Zarr as fallback + if parsed_crs is None and zarr.has_convention_declared( + self._obj.attrs, "proj:" + ): + parsed_crs = zarr.read_crs(self._obj) + + if parsed_crs is not None: + self._set_crs(parsed_crs, inplace=True) + return self._crs + + # No CRS found + self._crs = False + return None def _get_obj(self, inplace: bool) -> Union[xarray.Dataset, xarray.DataArray]: """ @@ -491,12 +515,13 @@ def write_crs( self, input_crs: Optional[Any] = None, grid_mapping_name: Optional[str] = None, + convention: Optional[Convention] = None, inplace: bool = False, ) -> Union[xarray.Dataset, xarray.DataArray]: """ - Write the CRS to the dataset in a CF compliant manner. + Write the CRS to the dataset using the specified convention. - .. warning:: The grid_mapping attribute is written to the encoding. + .. warning:: When using CF convention, the grid_mapping attribute is written to the encoding. Parameters ---------- @@ -504,69 +529,61 @@ def write_crs( Anything accepted by `rasterio.crs.CRS.from_user_input`. grid_mapping_name: str, optional Name of the grid_mapping coordinate to store the CRS information in. - Default is the grid_mapping name of the dataset. + Only used with CF convention. Default is the grid_mapping name of the dataset. + convention: Convention, optional + Convention to use for writing CRS. If None, uses the global default + from set_options(). inplace: bool, optional If True, it will write to the existing dataset. Default is False. Returns ------- :obj:`xarray.Dataset` | :obj:`xarray.DataArray`: - Modified dataset with CF compliant CRS information. + Modified dataset with CRS information. Examples -------- - Write the CRS of the current `xarray` object: + Write the CRS using the default convention: >>> raster.rio.write_crs("epsg:4326", inplace=True) - Write the CRS on a copy: + Write the CRS using CF convention: - >>> raster = raster.rio.write_crs("epsg:4326") - """ - if input_crs is not None: - data_obj = self._set_crs(input_crs, inplace=inplace) - else: - data_obj = self._get_obj(inplace=inplace) + >>> raster = raster.rio.write_crs("epsg:4326", convention=Convention.CF) - # get original transform - transform = self._cached_transform() - # remove old grid maping coordinate if exists - grid_mapping_name = ( - self.grid_mapping if grid_mapping_name is None else grid_mapping_name - ) - try: - del data_obj.coords[grid_mapping_name] - except KeyError: - pass + Write the CRS using Zarr convention: - if data_obj.rio.crs is None: + >>> raster = raster.rio.write_crs("epsg:4326", convention=Convention.Zarr) + """ + if input_crs is None and self.crs is None: raise MissingCRS( "CRS not found. Please set the CRS with 'rio.write_crs()'." ) - # add grid mapping coordinate - data_obj.coords[grid_mapping_name] = xarray.Variable((), 0) - grid_map_attrs = {} - if get_option(EXPORT_GRID_MAPPING): - try: - grid_map_attrs = pyproj.CRS.from_user_input(data_obj.rio.crs).to_cf() - except KeyError: - pass - # spatial_ref is for compatibility with GDAL - crs_wkt = data_obj.rio.crs.to_wkt() - grid_map_attrs["spatial_ref"] = crs_wkt - grid_map_attrs["crs_wkt"] = crs_wkt - if transform is not None: - grid_map_attrs["GeoTransform"] = " ".join( - [str(item) for item in transform.to_gdal()] - ) - data_obj.coords[grid_mapping_name].rio.set_attrs(grid_map_attrs, inplace=True) - # remove old crs if exists - data_obj.attrs.pop("crs", None) + # Get the object to modify + data_obj = self._get_obj(inplace=inplace) + if input_crs is not None: + data_obj.rio._set_crs(input_crs, inplace=True) + + # Determine which convention to use + if convention is None: + convention = get_option(CONVENTION) or Convention.CF - return data_obj.rio.write_grid_mapping( - grid_mapping_name=grid_mapping_name, inplace=True - ) + if convention is Convention.CF: + return cf.write_crs( + data_obj, + data_obj.rio.crs, + grid_mapping_name or self.grid_mapping, + inplace=True, + ) + elif convention is Convention.Zarr: + return zarr.write_crs( + data_obj, + data_obj.rio.crs, + inplace=True, + ) + else: + raise ValueError(f"Unsupported convention: {convention}") def estimate_utm_crs(self, datum_name: str = "WGS 84") -> rasterio.crs.CRS: """Returns the estimated UTM CRS based on the bounds of the dataset. @@ -612,37 +629,38 @@ def estimate_utm_crs(self, datum_name: str = "WGS 84") -> rasterio.crs.CRS: def _cached_transform(self) -> Optional[Affine]: """ - Get the transform from: - 1. The GeoTransform metatada property in the grid mapping - 2. The transform attribute. + Get the transform using the global convention setting. """ - try: - # look in grid_mapping - transform = numpy.fromstring( - self._obj.coords[self.grid_mapping].attrs["GeoTransform"], sep=" " - ) - # Calling .tolist() to assure the arguments are Python float and JSON serializable - return Affine.from_gdal(*transform.tolist()) + # Read using the global convention setting + convention = get_option(CONVENTION) + + if convention is Convention.Zarr: + return zarr.read_transform(self._obj) + elif convention is Convention.CF: + return cf.read_transform(self._obj, self.grid_mapping) + elif convention is None: + # Use CF as default when convention is None + transform = cf.read_transform(self._obj, self.grid_mapping) + # If CF didn't find anything and Zarr conventions are declared, try Zarr + if transform is None and zarr.has_convention_declared( + self._obj.attrs, "spatial:" + ): + transform = zarr.read_transform(self._obj) + return transform - except KeyError: - try: - return Affine(*self._obj.attrs["transform"][:6]) - except KeyError: - pass return None def write_transform( self, transform: Optional[Affine] = None, grid_mapping_name: Optional[str] = None, + convention: Optional[Convention] = None, inplace: bool = False, ) -> Union[xarray.Dataset, xarray.DataArray]: """ .. versionadded:: 0.0.30 - Write the GeoTransform to the dataset where GDAL can read it in. - - https://gdal.org/drivers/raster/netcdf.html#georeference + Write the transform to the dataset using the specified convention. Parameters ---------- @@ -650,34 +668,36 @@ def write_transform( The transform of the dataset. If not provided, it will be calculated. grid_mapping_name: str, optional Name of the grid_mapping coordinate to store the transform information in. - Default is the grid_mapping name of the dataset. + Only used with CF convention. Default is the grid_mapping name of the dataset. + convention: Convention, optional + Convention to use for writing transform. If None, uses the global default + from set_options(). inplace: bool, optional If True, it will write to the existing dataset. Default is False. Returns ------- :obj:`xarray.Dataset` | :obj:`xarray.DataArray`: - Modified dataset with Geo Transform written. + Modified dataset with transform written. """ transform = transform or self.transform(recalc=True) data_obj = self._get_obj(inplace=inplace) - # delete the old attribute to prevent confusion - data_obj.attrs.pop("transform", None) - grid_mapping_name = ( - self.grid_mapping if grid_mapping_name is None else grid_mapping_name - ) - try: - grid_map_attrs = data_obj.coords[grid_mapping_name].attrs.copy() - except KeyError: - data_obj.coords[grid_mapping_name] = xarray.Variable((), 0) - grid_map_attrs = data_obj.coords[grid_mapping_name].attrs.copy() - grid_map_attrs["GeoTransform"] = " ".join( - [str(item) for item in transform.to_gdal()] - ) - data_obj.coords[grid_mapping_name].rio.set_attrs(grid_map_attrs, inplace=True) - return data_obj.rio.write_grid_mapping( - grid_mapping_name=grid_mapping_name, inplace=True - ) + + # Determine which convention to use + if convention is None: + convention = get_option(CONVENTION) or Convention.CF + + if convention is Convention.CF: + return cf.write_transform( + data_obj, + transform, + grid_mapping_name or self.grid_mapping, + inplace=True, + ) + elif convention is Convention.Zarr: + return zarr.write_transform(data_obj, transform, inplace=True) + else: + raise ValueError(f"Unsupported convention: {convention}") def transform(self, recalc: bool = False) -> Affine: """ diff --git a/test/integration/test_convention_architecture.py b/test/integration/test_convention_architecture.py new file mode 100644 index 00000000..7bf14036 --- /dev/null +++ b/test/integration/test_convention_architecture.py @@ -0,0 +1,75 @@ +""" +Tests for the new convention architecture. +""" +import numpy as np +import xarray as xr +from affine import Affine + +import rioxarray +from rioxarray.enum import Convention + + +class TestConventionArchitecture: + """Test integration scenarios for the convention architecture.""" + + def test_convention_interaction_with_existing_metadata(self): + """Test how conventions interact when metadata already exists.""" + data = np.random.rand(3, 3) + da = xr.DataArray(data, dims=("y", "x")) + + # Start with CF metadata + da_cf = da.rio.write_crs("EPSG:4326", convention=Convention.CF) + + # Add Zarr metadata on top (should coexist) + da_both = da_cf.rio.write_crs("EPSG:3857", convention=Convention.Zarr) + + # Should have both types of metadata + assert "spatial_ref" in da_both.coords # CF metadata + assert "zarr_conventions" in da_both.attrs # Zarr metadata + assert any( + conv.get("name") == "proj:" for conv in da_both.attrs["zarr_conventions"] + ) + + def test_convention_metadata_coexistence(self): + """Test that CF and Zarr transform metadata can coexist.""" + data = np.random.rand(3, 3) + da = xr.DataArray(data, dims=("y", "x")) + transform1 = Affine(1.0, 0.0, 0.0, 0.0, -1.0, 3.0) + transform2 = Affine(2.0, 0.0, 0.0, 0.0, -2.0, 6.0) + + # Add CF transform first + da_cf = da.rio.write_transform(transform1, convention=Convention.CF) + + # Add Zarr transform on top + da_both = da_cf.rio.write_transform(transform2, convention=Convention.Zarr) + + # Both should coexist + assert "spatial_ref" in da_both.coords # CF metadata + assert "GeoTransform" in da_both.coords["spatial_ref"].attrs + assert "spatial:transform" in da_both.attrs # Zarr metadata + assert da_both.attrs["spatial:transform"] == [2.0, 0.0, 0.0, 0.0, -2.0, 6.0] + + def test_crs_reading_follows_global_convention(self): + """Test that CRS reading follows the global convention setting.""" + data = np.random.rand(3, 3) + da = xr.DataArray(data, dims=("y", "x")) + + # Create data with both CF and Zarr CRS information + da_with_cf = da.rio.write_crs("EPSG:4326", convention=Convention.CF) + da_with_zarr = da_with_cf.rio.write_crs( + "EPSG:3857", convention=Convention.Zarr + ) # Different CRS + + # With CF convention setting, should read CF CRS (4326) + with rioxarray.set_options(convention=Convention.CF): + # Reset cached CRS before reading + da_with_zarr.rio._crs = None + crs = da_with_zarr.rio.crs + assert crs.to_epsg() == 4326 + + # With Zarr convention setting, should read Zarr CRS (3857) + with rioxarray.set_options(convention=Convention.Zarr): + # Reset cached CRS + da_with_zarr.rio._crs = None + crs = da_with_zarr.rio.crs + assert crs.to_epsg() == 3857 diff --git a/test/integration/test_integration_zarr_conventions.py b/test/integration/test_integration_zarr_conventions.py new file mode 100644 index 00000000..28c70d61 --- /dev/null +++ b/test/integration/test_integration_zarr_conventions.py @@ -0,0 +1,473 @@ +""" +Tests for Zarr spatial and proj conventions support. + +Tests reading and writing CRS/georeferencing using: +- Zarr spatial convention: https://github.com/zarr-conventions/spatial +- Zarr geo-proj convention: https://github.com/zarr-experimental/geo-proj +""" + +import json +import warnings + +import numpy as np +import pytest +import rasterio.crs +import xarray as xr +from affine import Affine +from pyproj import CRS as ProjCRS + +from rioxarray._convention.zarr import PROJ_CONVENTION, SPATIAL_CONVENTION +from rioxarray.enum import Convention + + +def add_proj_convention_declaration(attrs): + """Helper to add proj: convention declaration to attrs dict.""" + if "zarr_conventions" not in attrs: + attrs["zarr_conventions"] = [] + attrs["zarr_conventions"].append(PROJ_CONVENTION.copy()) + return attrs + + +def add_spatial_convention_declaration(attrs): + """Helper to add spatial: convention declaration to attrs dict.""" + if "zarr_conventions" not in attrs: + attrs["zarr_conventions"] = [] + attrs["zarr_conventions"].append(SPATIAL_CONVENTION.copy()) + return attrs + + +def test_read_crs_from_proj_code(): + """Test reading CRS from proj:code attribute.""" + attrs = {"proj:code": "EPSG:4326"} + add_proj_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + crs = da.rio.crs + assert crs is not None + assert crs.to_epsg() == 4326 + + +def test_read_crs_from_proj_wkt2(): + """Test reading CRS from proj:wkt2 attribute.""" + wkt2 = rasterio.crs.CRS.from_epsg(3857).to_wkt() + attrs = {"proj:wkt2": wkt2} + add_proj_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + crs = da.rio.crs + assert crs is not None + assert crs.to_epsg() == 3857 + + +def test_read_transform_from_spatial_transform(): + """Test reading transform from spatial:transform attribute.""" + transform_array = [10.0, 0.0, 100.0, 0.0, -10.0, 200.0] + attrs = {"spatial:transform": transform_array} + add_spatial_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + transform = da.rio.transform() + assert transform is not None + assert list(transform)[:6] == transform_array + + +def test_read_spatial_dimensions(): + """Test reading dimensions from spatial:dimensions attribute.""" + attrs = {"spatial:dimensions": ["lat", "lon"]} + add_spatial_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("lat", "lon"), + attrs=attrs, + ) + + # Should detect dimensions from spatial:dimensions + assert da.rio.y_dim == "lat" + assert da.rio.x_dim == "lon" + + +def test_spatial_dimensions_takes_precedence(): + """Test that spatial:dimensions takes precedence over standard names.""" + # Create a DataArray with both standard 'x'/'y' dims and spatial:dimensions + # spatial:dimensions should take precedence + attrs = {"spatial:dimensions": ["y", "x"]} + add_spatial_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + # Should use spatial:dimensions (y, x) not inferred from dim names + assert da.rio.y_dim == "y" + assert da.rio.x_dim == "x" + + # Test with non-standard names - spatial:dimensions should be used + attrs2 = {"spatial:dimensions": ["row", "col"]} + add_spatial_convention_declaration(attrs2) + da2 = xr.DataArray( + np.ones((5, 5)), + dims=("row", "col"), + attrs=attrs2, + ) + + assert da2.rio.y_dim == "row" + assert da2.rio.x_dim == "col" + + +def test_zarr_conventions_priority_over_cf(): + """Test that CF conventions are used as default when both are present.""" + # Create a DataArray with both Zarr and CF conventions + # Zarr has EPSG:4326, CF grid_mapping has EPSG:3857 + attrs = {"proj:code": "EPSG:4326"} + add_proj_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + coords={ + "spatial_ref": xr.Variable( + (), + 0, + attrs={"spatial_ref": rasterio.crs.CRS.from_epsg(3857).to_wkt()}, + ) + }, + attrs=attrs, + ) + + # CF convention should be used as default when convention is None + # TODO: Add warning when both conventions are present + crs = da.rio.crs + assert crs.to_epsg() == 3857 + + +def test_cf_conventions_as_fallback(): + """Test that CF conventions work as fallback when Zarr conventions absent.""" + # Create a DataArray with only CF conventions + wkt = rasterio.crs.CRS.from_epsg(4326).to_wkt() + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + coords={"spatial_ref": xr.Variable((), 0, attrs={"spatial_ref": wkt})}, + ) + + # Should still read CRS from CF conventions + crs = da.rio.crs + assert crs is not None + assert crs.to_epsg() == 4326 + + +def test_group_level_proj_inheritance_dataset(): + """Test reading proj attributes from group level in Datasets.""" + # Create a Dataset with group-level proj:code + attrs = {"proj:code": "EPSG:4326"} + add_proj_convention_declaration(attrs) + ds = xr.Dataset( + { + "var1": xr.DataArray(np.ones((5, 5)), dims=("y", "x")), + "var2": xr.DataArray(np.ones((5, 5)), dims=("y", "x")), + }, + attrs=attrs, + ) + + # Dataset should inherit group-level CRS + crs = ds.rio.crs + assert crs is not None + assert crs.to_epsg() == 4326 + + +def test_write_zarr_crs_wkt2(): + """Test writing CRS as proj:wkt2 (default format).""" + da = xr.DataArray(np.ones((5, 5)), dims=("y", "x")) + da = da.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Verify convention is declared + assert "zarr_conventions" in da.attrs + convention_names = [c["name"] for c in da.attrs["zarr_conventions"]] + assert "proj:" in convention_names + + assert "proj:wkt2" in da.attrs + assert "GEOG" in da.attrs["proj:wkt2"] # WKT contains GEOG or GEOGCRS + + # Verify it can be read back + assert da.rio.crs.to_epsg() == 4326 + + +def test_write_zarr_transform(): + """Test writing transform as spatial:transform.""" + transform = Affine(10.0, 0.0, 100.0, 0.0, -10.0, 200.0) + da = xr.DataArray(np.ones((5, 5)), dims=("y", "x")) + da = da.rio.write_transform(transform, convention=Convention.Zarr) + + # Verify convention is declared + assert "zarr_conventions" in da.attrs + convention_names = [c["name"] for c in da.attrs["zarr_conventions"]] + assert "spatial:" in convention_names + + assert "spatial:transform" in da.attrs + assert da.attrs["spatial:transform"] == list(transform)[:6] + + # Verify it can be read back + assert da.rio.transform() == transform + + +def test_write_zarr_spatial_metadata(): + """Test writing complete spatial metadata.""" + from rioxarray._convention import zarr + + da = xr.DataArray(np.ones((10, 20)), dims=("y", "x")) + da = zarr._write_spatial_metadata(da, "y", "x") + + assert "spatial:dimensions" in da.attrs + assert da.attrs["spatial:dimensions"] == ["y", "x"] + + assert "spatial:shape" in da.attrs + assert da.attrs["spatial:shape"] == [10, 20] + + assert "spatial:registration" in da.attrs + assert da.attrs["spatial:registration"] == "pixel" + + +def test_write_zarr_spatial_metadata_with_bbox(): + """Test writing spatial metadata with bbox.""" + from rioxarray._convention import zarr + + transform = Affine(1.0, 0.0, 0.0, 0.0, -1.0, 10.0) + da = xr.DataArray(np.ones((10, 20)), dims=("y", "x")) + da = da.rio.write_transform(transform, convention=Convention.Zarr) + da = zarr._write_spatial_metadata( + da, "y", "x", transform=transform, include_bbox=True + ) + + assert "spatial:bbox" in da.attrs + # bbox should be [xmin, ymin, xmax, ymax] + bbox = da.attrs["spatial:bbox"] + assert len(bbox) == 4 + assert bbox == [0.0, 0.0, 20.0, 10.0] + + +def test_write_zarr_conventions_all(): + """Test writing complete Zarr conventions.""" + from rioxarray._convention import zarr + + transform = Affine(10.0, 0.0, 100.0, 0.0, -10.0, 200.0) + da = xr.DataArray(np.ones((10, 20)), dims=("y", "x")) + # Write components separately for simplicity + da = da.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + da = da.rio.write_transform(transform, convention=Convention.Zarr) + da = zarr._write_spatial_metadata(da, "y", "x", transform=transform) + + # Check CRS attributes (WKT2 format only now) + assert "proj:wkt2" in da.attrs + + # Check transform attribute + assert "spatial:transform" in da.attrs + assert da.attrs["spatial:transform"] == list(transform)[:6] + + # Check spatial metadata + assert "spatial:dimensions" in da.attrs + assert "spatial:shape" in da.attrs + assert "spatial:bbox" in da.attrs + + # Verify everything can be read back + assert da.rio.crs.to_epsg() == 4326 + assert da.rio.transform() == transform + + +def test_roundtrip_proj_wkt2(): + """Test write then read of proj:wkt2 (default format).""" + from rioxarray._convention import zarr + + original_da = xr.DataArray(np.ones((5, 5)), dims=("y", "x")) + original_da = original_da.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Verify format + assert "proj:wkt2" in original_da.attrs + + # Simulate saving and reloading by creating new DataArray with same attrs + reloaded_da = xr.DataArray( + original_da.values, + dims=original_da.dims, + attrs=original_da.attrs.copy(), + ) + + assert reloaded_da.rio.crs.to_epsg() == 4326 + + +def test_roundtrip_spatial_transform(): + """Test write then read of spatial:transform.""" + transform = Affine(5.0, 0.0, -180.0, 0.0, -5.0, 90.0) + original_da = xr.DataArray(np.ones((36, 72)), dims=("y", "x")) + original_da = original_da.rio.write_transform( + transform, convention=Convention.Zarr + ) + + # Simulate saving and reloading + reloaded_da = xr.DataArray( + original_da.values, + dims=original_da.dims, + attrs=original_da.attrs.copy(), + ) + + assert reloaded_da.rio.transform() == transform + + +def test_roundtrip_complete_conventions(): + """Test write then read of complete Zarr conventions.""" + from rioxarray._convention import zarr + + transform = Affine(1.0, 0.0, 0.0, 0.0, -1.0, 100.0) + original_da = xr.DataArray(np.ones((100, 100)), dims=("y", "x")) + # Write components separately for simplicity + original_da = original_da.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + original_da = original_da.rio.write_transform( + transform, convention=Convention.Zarr + ) + original_da = zarr._write_spatial_metadata( + original_da, "y", "x", transform=transform + ) + + # Simulate saving and reloading + reloaded_da = xr.DataArray( + original_da.values, + dims=original_da.dims, + attrs=original_da.attrs.copy(), + ) + + # Verify CRS + assert reloaded_da.rio.crs.to_epsg() == 4326 + + # Verify transform + assert reloaded_da.rio.transform() == transform + + # Verify spatial metadata + assert reloaded_da.rio.x_dim == "x" + assert reloaded_da.rio.y_dim == "y" + assert reloaded_da.rio.height == 100 + assert reloaded_da.rio.width == 100 + + +def test_both_conventions_present(): + """Test that both conventions can be present simultaneously.""" + # Write CF conventions first + da = xr.DataArray(np.ones((5, 5)), dims=("y", "x")) + da = da.rio.write_crs("EPSG:4326") # CF format + + # Add Zarr conventions + from rioxarray._convention import zarr + + da = da.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Both should be present + assert "spatial_ref" in da.coords # CF grid_mapping + assert "proj:wkt2" in da.attrs # Zarr convention + + # Zarr should take priority when reading + assert da.rio.crs.to_epsg() == 4326 + + +def test_zarr_overrides_cf_when_both_present(): + """Test Zarr conventions override CF when both have different values.""" + # This is an edge case: if someone has both conventions with + # conflicting values, CF should win as default when convention is None + attrs = {"proj:code": "EPSG:4326"} + add_proj_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + coords={ + "spatial_ref": xr.Variable( + (), + 0, + attrs={"spatial_ref": rasterio.crs.CRS.from_epsg(3857).to_wkt()}, + ) + }, + attrs=attrs, + ) + + # CF convention (EPSG:3857) should be used as default when convention is None + assert da.rio.crs.to_epsg() == 3857 +def test_invalid_proj_code(): + """Test handling of invalid proj:code.""" + attrs = {"proj:code": "INVALID:9999"} + add_proj_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + # Should handle gracefully (return None or fall back) + _crs = da.rio.crs + # Depending on implementation, might be None or raise exception + # For now, just verify it doesn't crash + assert _crs is None + + +def test_invalid_spatial_transform_format(): + """Test handling of malformed spatial:transform.""" + # Wrong number of elements + attrs = {"spatial:transform": [1.0, 2.0, 3.0]} # Only 3 elements + add_spatial_convention_declaration(attrs) + da = xr.DataArray( + np.ones((5, 5)), + dims=("y", "x"), + attrs=attrs, + ) + + # Should handle gracefully + da.rio.transform() + # Should fall back to calculating from coordinates or return identity + + +def test_write_crs_without_setting(): + """Test writing Zarr CRS when no CRS is set.""" + da = xr.DataArray(np.ones((5, 5)), dims=("y", "x")) + + # Should handle None gracefully by returning unchanged object + from rioxarray._convention import zarr + + result = zarr.write_crs(da, None) + # Should not have any proj: attributes + assert not any(attr.startswith("proj:") for attr in result.attrs) + assert result is da # Should return same object when inplace=True + + +def test_write_spatial_metadata_without_dimensions(): + """Test writing spatial metadata when dimensions cannot be determined.""" + # Create a DataArray with non-standard dimension names + # and no spatial:dimensions attribute + da = xr.DataArray(np.ones((5, 5)), dims=("foo", "bar")) + + # Should raise MissingSpatialDimensionError + from rioxarray._convention import zarr + + with pytest.raises(Exception): # MissingSpatialDimensionError + zarr._write_spatial_metadata(da) + + +def test_crs_from_projjson_dict(): + """Test crs_from_user_input with PROJJSON dict.""" + import json + + from pyproj import CRS as ProjCRS + + from rioxarray.crs import crs_from_user_input + + pyproj_crs = ProjCRS.from_epsg(4326) + projjson = json.loads(pyproj_crs.to_json()) + + crs = crs_from_user_input(projjson) + assert crs is not None + assert crs.to_epsg() == 4326 diff --git a/test/unit/test_convention_architecture.py b/test/unit/test_convention_architecture.py new file mode 100644 index 00000000..7a7ec7b3 --- /dev/null +++ b/test/unit/test_convention_architecture.py @@ -0,0 +1,147 @@ +""" +Test the convention architecture for both CF and Zarr conventions. +""" +import numpy as np +import pytest +import xarray as xr +from affine import Affine + +import rioxarray +from rioxarray.enum import Convention + + +@pytest.fixture +def sample_data(): + """Create a simple test dataset.""" + da = xr.DataArray( + np.random.rand(10, 10), + dims=["y", "x"], + coords={"x": np.arange(10), "y": np.arange(10)}, + ) + return da + + +@pytest.fixture +def sample_transform(): + """Create a simple transform.""" + return Affine(1.0, 0.0, 0.0, 0.0, -1.0, 10.0) + + +class TestConventionArchitecture: + """Test the new convention-based architecture.""" + + def test_convention_enum(self): + """Test that Convention enum is available and works.""" + assert Convention.CF.value == "CF" + assert Convention.Zarr.value == "Zarr" + + def test_default_convention_options(self): + """Test that default options work.""" + with rioxarray.set_options(convention=Convention.CF): + from rioxarray._options import CONVENTION, get_option + + assert get_option(CONVENTION) is Convention.CF + + def test_zarr_convention_options(self): + """Test that Zarr convention can be set.""" + with rioxarray.set_options(convention=Convention.Zarr): + from rioxarray._options import CONVENTION, get_option + + assert get_option(CONVENTION) is Convention.Zarr + + def test_write_crs_cf_convention(self, sample_data): + """Test writing CRS with CF convention.""" + da_with_crs = sample_data.rio.write_crs("EPSG:4326", convention=Convention.CF) + + # Should have grid_mapping coordinate + assert "spatial_ref" in da_with_crs.coords + # Should have grid_mapping attribute + assert da_with_crs.attrs.get("grid_mapping") == "spatial_ref" + # Should have WKT in grid_mapping coordinate + assert "spatial_ref" in da_with_crs.coords["spatial_ref"].attrs + + def test_write_crs_zarr_convention(self, sample_data): + """Test writing CRS with Zarr convention.""" + da_with_crs = sample_data.rio.write_crs("EPSG:4326", convention=Convention.Zarr) + + # Should have proj:wkt2 attribute (default format) + assert "proj:wkt2" in da_with_crs.attrs + assert ( + "GEOGCS" in da_with_crs.attrs["proj:wkt2"] + or "GEOGCRS" in da_with_crs.attrs["proj:wkt2"] + ) + # Should have zarr_conventions declaration + assert "zarr_conventions" in da_with_crs.attrs + conventions = da_with_crs.attrs["zarr_conventions"] + assert any(conv.get("name") == "proj:" for conv in conventions) + + def test_write_transform_cf_convention(self, sample_data, sample_transform): + """Test writing transform with CF convention.""" + da_with_transform = sample_data.rio.write_transform( + sample_transform, convention=Convention.CF + ) + + # Should have grid_mapping coordinate with GeoTransform + assert "spatial_ref" in da_with_transform.coords + assert "GeoTransform" in da_with_transform.coords["spatial_ref"].attrs + + def test_write_transform_zarr_convention(self, sample_data, sample_transform): + """Test writing transform with Zarr convention.""" + da_with_transform = sample_data.rio.write_transform( + sample_transform, convention=Convention.Zarr + ) + + # Should have spatial:transform attribute + assert "spatial:transform" in da_with_transform.attrs + spatial_transform = da_with_transform.attrs["spatial:transform"] + assert len(spatial_transform) == 6 + assert spatial_transform == [1.0, 0.0, 0.0, 0.0, -1.0, 10.0] + # Should have zarr_conventions declaration + assert "zarr_conventions" in da_with_transform.attrs + conventions = da_with_transform.attrs["zarr_conventions"] + assert any(conv.get("name") == "spatial:" for conv in conventions) + + +class TestConventionModules: + """Test the individual convention modules.""" + + def test_cf_module_exists(self): + """Test that CF module can be imported.""" + from rioxarray._convention import cf + + assert hasattr(cf, "read_crs") + assert hasattr(cf, "read_transform") + assert hasattr(cf, "write_crs") + assert hasattr(cf, "write_transform") + + def test_zarr_module_exists(self): + """Test that Zarr module can be imported.""" + from rioxarray._convention import zarr + + assert hasattr(zarr, "read_crs") + assert hasattr(zarr, "read_transform") + assert hasattr(zarr, "write_crs") + assert hasattr(zarr, "write_transform") + + +class TestBackwardCompatibility: + """Test that existing code continues to work.""" + + def test_write_crs_without_convention_parameter(self, sample_data): + """Test that write_crs works without convention parameter (uses default).""" + # Default should be CF + da_with_crs = sample_data.rio.write_crs("EPSG:4326") + + # Should use CF convention by default + assert "spatial_ref" in da_with_crs.coords + assert da_with_crs.attrs.get("grid_mapping") == "spatial_ref" + + def test_write_transform_without_convention_parameter( + self, sample_data, sample_transform + ): + """Test that write_transform works without convention parameter.""" + da_with_transform = sample_data.rio.write_transform(sample_transform) + + # Should use CF convention by default + assert "spatial_ref" in da_with_transform.coords + assert "GeoTransform" in da_with_transform.coords["spatial_ref"].attrs diff --git a/test/unit/test_options.py b/test/unit/test_options.py index 4b7388f5..2e4344c1 100644 --- a/test/unit/test_options.py +++ b/test/unit/test_options.py @@ -1,7 +1,9 @@ import pytest +import rioxarray from rioxarray import set_options -from rioxarray._options import EXPORT_GRID_MAPPING, get_option +from rioxarray._options import CONVENTION, EXPORT_GRID_MAPPING, get_option +from rioxarray.enum import Convention def test_set_options__contextmanager(): @@ -37,3 +39,18 @@ def test_set_options__invalid_value(): ): with set_options(export_grid_mapping=12345): pass + + +def test_set_options_convention(): + """Test setting convention through set_options.""" + # Test default convention (None for CF-first with Zarr fallback) + with rioxarray.set_options(): + assert get_option(CONVENTION) is None + + # Test setting Zarr convention + with rioxarray.set_options(convention=Convention.Zarr): + assert get_option(CONVENTION) is Convention.Zarr + + # Test setting CF convention explicitly + with rioxarray.set_options(convention=Convention.CF): + assert get_option(CONVENTION) is Convention.CF diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..6a3d86d3 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1017 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "affine" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/98/d2f0bb06385069e799fc7d2870d9e078cfa0fa396dc8a2b81227d0da08b9/affine-2.4.0.tar.gz", hash = "sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea", size = 17132 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/f7/85273299ab57117850cc0a936c64151171fac4da49bc6fba0dad984a7c5f/affine-2.4.0-py3-none-any.whl", hash = "sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92", size = 15662 }, +] + +[[package]] +name = "astroid" +version = "4.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/22/97df040e15d964e592d3a180598ace67e91b7c559d8298bdb3c949dc6e42/astroid-4.0.2.tar.gz", hash = "sha256:ac8fb7ca1c08eb9afec91ccc23edbd8ac73bb22cbdd7da1d488d9fb8d6579070", size = 405714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/ac/a85b4bfb4cf53221513e27f33cc37ad158fce02ac291d18bee6b49ab477d/astroid-4.0.2-py3-none-any.whl", hash = "sha256:d7546c00a12efc32650b19a2bb66a153883185d3179ab0d4868086f807338b9b", size = 276354 }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615 }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438 }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445 }, +] + +[[package]] +name = "cftime" +version = "1.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/dc/470ffebac2eb8c54151eb893055024fe81b1606e7c6ff8449a588e9cd17f/cftime-1.6.5.tar.gz", hash = "sha256:8225fed6b9b43fb87683ebab52130450fc1730011150d3092096a90e54d1e81e", size = 326605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/c1/e8cb7f78a3f87295450e7300ebaecf83076d96a99a76190593d4e1d2be40/cftime-1.6.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eef25caed5ebd003a38719bd3ff8847cd52ef2ea56c3ebdb2c9345ba131fc7c5", size = 504175 }, + { url = "https://files.pythonhosted.org/packages/50/1a/86e1072b09b2f9049bb7378869f64b6747f96a4f3008142afed8955b52a4/cftime-1.6.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c87d2f3b949e45463e559233c69e6a9cf691b2b378c1f7556166adfabbd1c6b0", size = 485980 }, + { url = "https://files.pythonhosted.org/packages/35/28/d3177b60da3f308b60dee2aef2eb69997acfab1e863f0bf0d2a418396ce5/cftime-1.6.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:82cb413973cc51b55642b3a1ca5b28db5b93a294edbef7dc049c074b478b4647", size = 1591166 }, + { url = "https://files.pythonhosted.org/packages/d1/fd/a7266970312df65e68b5641b86e0540a739182f5e9c62eec6dbd29f18055/cftime-1.6.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85ba8e7356d239cfe56ef7707ac30feaf67964642ac760a82e507ee3c5db4ac4", size = 1642614 }, + { url = "https://files.pythonhosted.org/packages/c4/73/f0035a4bc2df8885bb7bd5fe63659686ea1ec7d0cc74b4e3d50e447402e5/cftime-1.6.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:456039af7907a3146689bb80bfd8edabd074c7f3b4eca61f91b9c2670addd7ad", size = 1688090 }, + { url = "https://files.pythonhosted.org/packages/88/15/8856a0ab76708553ff597dd2e617b088c734ba87dc3fd395e2b2f3efffe8/cftime-1.6.5-cp312-cp312-win_amd64.whl", hash = "sha256:da84534c43699960dc980a9a765c33433c5de1a719a4916748c2d0e97a071e44", size = 464840 }, + { url = "https://files.pythonhosted.org/packages/2e/60/74ea344b3b003fada346ed98a6899085d6fd4c777df608992d90c458fda6/cftime-1.6.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4aba66fd6497711a47c656f3a732c2d1755ad15f80e323c44a8716ebde39ddd5", size = 502453 }, + { url = "https://files.pythonhosted.org/packages/1e/14/adb293ac6127079b49ff11c05cf3d5ce5c1f17d097f326dc02d74ddfcb6e/cftime-1.6.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89e7cba699242366e67d6fb5aee579440e791063f92a93853610c91647167c0d", size = 484541 }, + { url = "https://files.pythonhosted.org/packages/4f/74/bb8a4566af8d0ef3f045d56c462a9115da4f04b07c7fbbf2b4875223eebd/cftime-1.6.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f1eb43d7a7b919ec99aee709fb62ef87ef1cf0679829ef93d37cc1c725781e9", size = 1591014 }, + { url = "https://files.pythonhosted.org/packages/ba/08/52f06ff2f04d376f9cd2c211aefcf2b37f1978e43289341f362fc99f6a0e/cftime-1.6.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e02a1d80ffc33fe469c7db68aa24c4a87f01da0c0c621373e5edadc92964900b", size = 1633625 }, + { url = "https://files.pythonhosted.org/packages/cf/33/03e0b23d58ea8fab94ecb4f7c5b721e844a0800c13694876149d98830a73/cftime-1.6.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18ab754805233cdd889614b2b3b86a642f6d51a57a1ec327c48053f3414f87d8", size = 1684269 }, + { url = "https://files.pythonhosted.org/packages/a4/60/a0cfba63847b43599ef1cdbbf682e61894994c22b9a79fd9e1e8c7e9de41/cftime-1.6.5-cp313-cp313-win_amd64.whl", hash = "sha256:6c27add8f907f4a4cd400e89438f2ea33e2eb5072541a157a4d013b7dbe93f9c", size = 465364 }, + { url = "https://files.pythonhosted.org/packages/ea/6c/a9618f589688358e279720f5c0fe67ef0077fba07334ce26895403ebc260/cftime-1.6.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c69ce3bdae6a322cbb44e9ebc20770d47748002fb9d68846a1e934f1bd5daf0b", size = 502725 }, + { url = "https://files.pythonhosted.org/packages/d8/e3/da3c36398bfb730b96248d006cabaceed87e401ff56edafb2a978293e228/cftime-1.6.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e62e9f2943e014c5ef583245bf2e878398af131c97e64f8cd47c1d7baef5c4e2", size = 485445 }, + { url = "https://files.pythonhosted.org/packages/32/93/b05939e5abd14bd1ab69538bbe374b4ee2a15467b189ff895e9a8cdaddf6/cftime-1.6.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da5fdaa4360d8cb89b71b8ded9314f2246aa34581e8105c94ad58d6102d9e4f", size = 1584434 }, + { url = "https://files.pythonhosted.org/packages/7f/89/648397f9936e0b330999c4e776ebf296ec3c6a65f9901687dbca4ab820da/cftime-1.6.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bff865b4ea4304f2744a1ad2b8149b8328b321dd7a2b9746ef926d229bd7cd49", size = 1609812 }, + { url = "https://files.pythonhosted.org/packages/e7/0f/901b4835aa67ad3e915605d4e01d0af80a44b114eefab74ae33de6d36933/cftime-1.6.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e552c5d1c8a58f25af7521e49237db7ca52ed2953e974fe9f7c4491e95fdd36c", size = 1669768 }, + { url = "https://files.pythonhosted.org/packages/22/d5/e605e4b28363e7a9ae98ed12cabbda5b155b6009270e6a231d8f10182a17/cftime-1.6.5-cp314-cp314-win_amd64.whl", hash = "sha256:e645b095dc50a38ac454b7e7f0742f639e7d7f6b108ad329358544a6ff8c9ba2", size = 463818 }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051 }, +] + +[[package]] +name = "cligj" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/0d/837dbd5d8430fd0f01ed72c4cfb2f548180f4c68c635df84ce87956cff32/cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27", size = 9803 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/86/43fa9f15c5b9fb6e82620428827cd3c284aa933431405d1bcf5231ae3d3e/cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df", size = 7069 }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274 }, + { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638 }, + { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129 }, + { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885 }, + { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974 }, + { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538 }, + { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912 }, + { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054 }, + { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619 }, + { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496 }, + { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808 }, + { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616 }, + { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261 }, + { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297 }, + { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673 }, + { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652 }, + { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251 }, + { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492 }, + { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850 }, + { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633 }, + { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586 }, + { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412 }, + { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191 }, + { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829 }, + { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640 }, + { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269 }, + { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990 }, + { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340 }, + { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638 }, + { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705 }, + { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125 }, + { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844 }, + { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700 }, + { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321 }, + { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222 }, + { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411 }, + { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505 }, + { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569 }, + { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841 }, + { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343 }, + { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672 }, + { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715 }, + { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225 }, + { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559 }, + { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724 }, + { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582 }, + { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538 }, + { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349 }, + { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011 }, + { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091 }, + { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904 }, + { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480 }, + { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074 }, + { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342 }, + { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713 }, + { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825 }, + { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233 }, + { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779 }, + { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700 }, + { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302 }, + { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136 }, + { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467 }, + { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875 }, + { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982 }, + { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016 }, + { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068 }, +] + +[[package]] +name = "dask" +version = "2025.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "partd" }, + { name = "pyyaml" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/33/eacaa72731f7fc64868caaf2d35060d50049eff889bd217263e68f76472f/dask-2025.11.0.tar.gz", hash = "sha256:23d59e624b80ee05b7cc8df858682cca58262c4c3b197ccf61da0f6543c8f7c3", size = 10984781 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/54/a46920229d12c3a6e9f0081d1bdaeffad23c1826353ace95714faee926e5/dask-2025.11.0-py3-none-any.whl", hash = "sha256:08c35a8146c05c93b34f83cf651009129c42ee71762da7ca452fb7308641c2b8", size = 1477108 }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668 }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054 }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422 }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183 }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672 }, +] + +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687 }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127 }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336 }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237 }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017 }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983 }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602 }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282 }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879 }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972 }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338 }, + { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742 }, + { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163 }, + { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840 }, + { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827 }, + { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612 }, + { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584 }, + { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269 }, + { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852 }, + { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936 }, + { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965 }, + { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350 }, + { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175 }, + { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881 }, + { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710 }, + { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471 }, + { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804 }, + { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817 }, + { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602 }, + { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497 }, + { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678 }, + { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689 }, + { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662 }, + { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347 }, + { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223 }, + { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861 }, + { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594 }, + { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759 }, + { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210 }, + { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708 }, + { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212 }, + { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586 }, + { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002 }, + { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647 }, +] + +[[package]] +name = "locket" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "mypy" +version = "1.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728 }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945 }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673 }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336 }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174 }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208 }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993 }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411 }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751 }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323 }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032 }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644 }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236 }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902 }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600 }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639 }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132 }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832 }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, +] + +[[package]] +name = "netcdf4" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cftime" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/76/7bc801796dee752c1ce9cd6935564a6ee79d5c9d9ef9192f57b156495a35/netcdf4-1.7.3.tar.gz", hash = "sha256:83f122fc3415e92b1d4904fd6a0898468b5404c09432c34beb6b16c533884673", size = 836095 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/62/d286c76cdf0f6faf6064dc032ba7df3d6172ccca6e7d3571eee5516661b9/netcdf4-1.7.3-cp311-abi3-macosx_13_0_x86_64.whl", hash = "sha256:801c222d8ad35fd7dc7e9aa7ea6373d184bcb3b8ee6b794c5fbecaa5155b1792", size = 2751401 }, + { url = "https://files.pythonhosted.org/packages/f8/5e/0bb5593df674971e9fe5d76f7a0dd2006f3ee6b3a9eaece8c01170bac862/netcdf4-1.7.3-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:83dbfd6f10a0ec785d5296016bd821bbe9f0df780be72fc00a1f0d179d9c5f0f", size = 2387517 }, + { url = "https://files.pythonhosted.org/packages/8e/27/9530c58ddec2c28297d1abbc2f3668cb7bf79864bcbfb0516634ad0d3908/netcdf4-1.7.3-cp311-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:949e086d4d2612b49e5b95f60119d216c9ceb7b17bc771e9e0fa0e9b9c0a2f9f", size = 9621631 }, + { url = "https://files.pythonhosted.org/packages/97/1a/78b19893197ed7525edfa7f124a461626541e82aec694a468ba97755c24e/netcdf4-1.7.3-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c764ba6f6a1421cab5496097e8a1c4d2e36be2a04880dfd288bb61b348c217e", size = 9453727 }, + { url = "https://files.pythonhosted.org/packages/2a/f8/a5509bc46faedae2b71df29c57e6525b7eb47aee44000fd43e2927a9a3a9/netcdf4-1.7.3-cp311-abi3-win_amd64.whl", hash = "sha256:1b6c646fa179fb1e5e8d6e8231bc78cc0311eceaa1241256b5a853f1d04055b9", size = 7149328 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873 }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838 }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378 }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559 }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702 }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086 }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985 }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976 }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274 }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922 }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667 }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251 }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652 }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172 }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990 }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902 }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430 }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551 }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275 }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637 }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090 }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710 }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292 }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897 }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391 }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275 }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855 }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359 }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374 }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587 }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940 }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341 }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507 }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706 }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507 }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049 }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603 }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696 }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350 }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190 }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749 }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432 }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388 }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651 }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503 }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612 }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042 }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502 }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962 }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054 }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613 }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147 }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806 }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760 }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846 }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618 }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212 }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693 }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002 }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971 }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722 }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671 }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807 }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872 }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371 }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333 }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120 }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991 }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227 }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056 }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189 }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912 }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160 }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233 }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635 }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079 }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049 }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638 }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834 }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925 }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071 }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504 }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702 }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535 }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582 }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963 }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175 }, +] + +[[package]] +name = "partd" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "locket" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pre-commit" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pylint" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d2/b081da1a8930d00e3fc06352a1d449aaf815d4982319fab5d8cdb2e9ab35/pylint-4.0.4.tar.gz", hash = "sha256:d9b71674e19b1c36d79265b5887bf8e55278cbe236c9e95d22dc82cf044fdbd2", size = 1571735 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/92/d40f5d937517cc489ad848fc4414ecccc7592e4686b9071e09e64f5e378e/pylint-4.0.4-py3-none-any.whl", hash = "sha256:63e06a37d5922555ee2c20963eb42559918c20bd2b21244e4ef426e7c43b92e0", size = 536425 }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890 }, +] + +[[package]] +name = "pyproj" +version = "3.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz", hash = "sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c", size = 226279 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/ab/9893ea9fb066be70ed9074ae543914a618c131ed8dff2da1e08b3a4df4db/pyproj-3.7.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:0a9bb26a6356fb5b033433a6d1b4542158fb71e3c51de49b4c318a1dff3aeaab", size = 6219832 }, + { url = "https://files.pythonhosted.org/packages/53/78/4c64199146eed7184eb0e85bedec60a4aa8853b6ffe1ab1f3a8b962e70a0/pyproj-3.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:567caa03021178861fad27fabde87500ec6d2ee173dd32f3e2d9871e40eebd68", size = 4620650 }, + { url = "https://files.pythonhosted.org/packages/b6/ac/14a78d17943898a93ef4f8c6a9d4169911c994e3161e54a7cedeba9d8dde/pyproj-3.7.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c203101d1dc3c038a56cff0447acc515dd29d6e14811406ac539c21eed422b2a", size = 9667087 }, + { url = "https://files.pythonhosted.org/packages/b8/be/212882c450bba74fc8d7d35cbd57e4af84792f0a56194819d98106b075af/pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1edc34266c0c23ced85f95a1ee8b47c9035eae6aca5b6b340327250e8e281630", size = 9552797 }, + { url = "https://files.pythonhosted.org/packages/ba/c0/c0f25c87b5d2a8686341c53c1792a222a480d6c9caf60311fec12c99ec26/pyproj-3.7.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa9f26c21bc0e2dc3d224cb1eb4020cf23e76af179a7c66fea49b828611e4260", size = 10837036 }, + { url = "https://files.pythonhosted.org/packages/5d/37/5cbd6772addde2090c91113332623a86e8c7d583eccb2ad02ea634c4a89f/pyproj-3.7.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9428b318530625cb389b9ddc9c51251e172808a4af79b82809376daaeabe5e9", size = 10775952 }, + { url = "https://files.pythonhosted.org/packages/69/a1/dc250e3cf83eb4b3b9a2cf86fdb5e25288bd40037ae449695550f9e96b2f/pyproj-3.7.2-cp312-cp312-win32.whl", hash = "sha256:b3d99ed57d319da042f175f4554fc7038aa4bcecc4ac89e217e350346b742c9d", size = 5898872 }, + { url = "https://files.pythonhosted.org/packages/4a/a6/6fe724b72b70f2b00152d77282e14964d60ab092ec225e67c196c9b463e5/pyproj-3.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:11614a054cd86a2ed968a657d00987a86eeb91fdcbd9ad3310478685dc14a128", size = 6312176 }, + { url = "https://files.pythonhosted.org/packages/5d/68/915cc32c02a91e76d02c8f55d5a138d6ef9e47a0d96d259df98f4842e558/pyproj-3.7.2-cp312-cp312-win_arm64.whl", hash = "sha256:509a146d1398bafe4f53273398c3bb0b4732535065fa995270e52a9d3676bca3", size = 6233452 }, + { url = "https://files.pythonhosted.org/packages/be/14/faf1b90d267cea68d7e70662e7f88cefdb1bc890bd596c74b959e0517a72/pyproj-3.7.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1", size = 6214580 }, + { url = "https://files.pythonhosted.org/packages/35/48/da9a45b184d375f62667f62eba0ca68569b0bd980a0bb7ffcc1d50440520/pyproj-3.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7", size = 4615388 }, + { url = "https://files.pythonhosted.org/packages/5e/e7/d2b459a4a64bca328b712c1b544e109df88e5c800f7c143cfbc404d39bfb/pyproj-3.7.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa", size = 9628455 }, + { url = "https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681", size = 9514269 }, + { url = "https://files.pythonhosted.org/packages/34/38/07a9b89ae7467872f9a476883a5bad9e4f4d1219d31060f0f2b282276cbe/pyproj-3.7.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5", size = 10808437 }, + { url = "https://files.pythonhosted.org/packages/12/56/fda1daeabbd39dec5b07f67233d09f31facb762587b498e6fc4572be9837/pyproj-3.7.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67", size = 10745540 }, + { url = "https://files.pythonhosted.org/packages/0d/90/c793182cbba65a39a11db2ac6b479fe76c59e6509ae75e5744c344a0da9d/pyproj-3.7.2-cp313-cp313-win32.whl", hash = "sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3", size = 5896506 }, + { url = "https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7", size = 6310195 }, + { url = "https://files.pythonhosted.org/packages/82/64/fc7598a53172c4931ec6edf5228280663063150625d3f6423b4c20f9daff/pyproj-3.7.2-cp313-cp313-win_arm64.whl", hash = "sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100", size = 6230748 }, + { url = "https://files.pythonhosted.org/packages/aa/f0/611dd5cddb0d277f94b7af12981f56e1441bf8d22695065d4f0df5218498/pyproj-3.7.2-cp313-cp313t-macosx_13_0_x86_64.whl", hash = "sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279", size = 6241729 }, + { url = "https://files.pythonhosted.org/packages/15/93/40bd4a6c523ff9965e480870611aed7eda5aa2c6128c6537345a2b77b542/pyproj-3.7.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6", size = 4652497 }, + { url = "https://files.pythonhosted.org/packages/1b/ae/7150ead53c117880b35e0d37960d3138fe640a235feb9605cb9386f50bb0/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220", size = 9942610 }, + { url = "https://files.pythonhosted.org/packages/d8/17/7a4a7eafecf2b46ab64e5c08176c20ceb5844b503eaa551bf12ccac77322/pyproj-3.7.2-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c", size = 9692390 }, + { url = "https://files.pythonhosted.org/packages/c3/55/ae18f040f6410f0ea547a21ada7ef3e26e6c82befa125b303b02759c0e9d/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c", size = 11047596 }, + { url = "https://files.pythonhosted.org/packages/e6/2e/d3fff4d2909473f26ae799f9dda04caa322c417a51ff3b25763f7d03b233/pyproj-3.7.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69", size = 10896975 }, + { url = "https://files.pythonhosted.org/packages/f2/bc/8fc7d3963d87057b7b51ebe68c1e7c51c23129eee5072ba6b86558544a46/pyproj-3.7.2-cp313-cp313t-win32.whl", hash = "sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2", size = 5953057 }, + { url = "https://files.pythonhosted.org/packages/cc/27/ea9809966cc47d2d51e6d5ae631ea895f7c7c7b9b3c29718f900a8f7d197/pyproj-3.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3", size = 6375414 }, + { url = "https://files.pythonhosted.org/packages/5b/f8/1ef0129fba9a555c658e22af68989f35e7ba7b9136f25758809efec0cd6e/pyproj-3.7.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd", size = 6262501 }, + { url = "https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02", size = 6214541 }, + { url = "https://files.pythonhosted.org/packages/03/68/68ada9c8aea96ded09a66cfd9bf87aa6db8c2edebe93f5bf9b66b0143fbc/pyproj-3.7.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08", size = 4617456 }, + { url = "https://files.pythonhosted.org/packages/81/e4/4c50ceca7d0e937977866b02cb64e6ccf4df979a5871e521f9e255df6073/pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b", size = 9615590 }, + { url = "https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281", size = 9474960 }, + { url = "https://files.pythonhosted.org/packages/51/07/9d48ad0a8db36e16f842f2c8a694c1d9d7dcf9137264846bef77585a71f3/pyproj-3.7.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516", size = 10799478 }, + { url = "https://files.pythonhosted.org/packages/85/cf/2f812b529079f72f51ff2d6456b7fef06c01735e5cfd62d54ffb2b548028/pyproj-3.7.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e", size = 10710030 }, + { url = "https://files.pythonhosted.org/packages/99/9b/4626a19e1f03eba4c0e77b91a6cf0f73aa9cb5d51a22ee385c22812bcc2c/pyproj-3.7.2-cp314-cp314-win32.whl", hash = "sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25", size = 5991181 }, + { url = "https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112", size = 6434721 }, + { url = "https://files.pythonhosted.org/packages/ae/ce/6c910ea2e1c74ef673c5d48c482564b8a7824a44c4e35cca2e765b68cfcc/pyproj-3.7.2-cp314-cp314-win_arm64.whl", hash = "sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6", size = 6363821 }, + { url = "https://files.pythonhosted.org/packages/e4/e4/5532f6f7491812ba782a2177fe9de73fd8e2912b59f46a1d056b84b9b8f2/pyproj-3.7.2-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37", size = 6241773 }, + { url = "https://files.pythonhosted.org/packages/20/1f/0938c3f2bbbef1789132d1726d9b0e662f10cfc22522743937f421ad664e/pyproj-3.7.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b", size = 4652537 }, + { url = "https://files.pythonhosted.org/packages/c7/a8/488b1ed47d25972f33874f91f09ca8f2227902f05f63a2b80dc73e7b1c97/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357", size = 9940864 }, + { url = "https://files.pythonhosted.org/packages/c7/cc/7f4c895d0cb98e47b6a85a6d79eaca03eb266129eed2f845125c09cf31ff/pyproj-3.7.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81", size = 9688868 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/c7e306b8bb0f071d9825b753ee4920f066c40fbfcce9372c4f3cfb2fc4ed/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888", size = 11045910 }, + { url = "https://files.pythonhosted.org/packages/42/fb/538a4d2df695980e2dde5c04d965fbdd1fe8c20a3194dc4aaa3952a4d1be/pyproj-3.7.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59", size = 10895724 }, + { url = "https://files.pythonhosted.org/packages/e8/8b/a3f0618b03957de9db5489a04558a8826f43906628bb0b766033aa3b5548/pyproj-3.7.2-cp314-cp314t-win32.whl", hash = "sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa", size = 6056848 }, + { url = "https://files.pythonhosted.org/packages/bc/56/413240dd5149dd3291eda55aa55a659da4431244a2fd1319d0ae89407cfb/pyproj-3.7.2-cp314-cp314t-win_amd64.whl", hash = "sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c", size = 6517676 }, + { url = "https://files.pythonhosted.org/packages/15/73/a7141a1a0559bf1a7aa42a11c879ceb19f02f5c6c371c6d57fd86cefd4d1/pyproj-3.7.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4", size = 6391844 }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424 }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +] + +[[package]] +name = "rasterio" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "affine" }, + { name = "attrs" }, + { name = "certifi" }, + { name = "click" }, + { name = "click-plugins" }, + { name = "cligj" }, + { name = "numpy" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/19/ab4326e419b543da623ce4191f68e3f36a4d9adc64f3df5c78f044d8d9ca/rasterio-1.4.3.tar.gz", hash = "sha256:201f05dbc7c4739dacb2c78a1cf4e09c0b7265b0a4d16ccbd1753ce4f2af350a", size = 442990 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/f2/b7417292ceace70d815760f7e41fe5b0244ebff78ede11b1ffa9ca01c370/rasterio-1.4.3-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:e703e4b2c74c678786d5d110a3f30e26f3acfd65f09ccf35f69683a532f7a772", size = 21514543 }, + { url = "https://files.pythonhosted.org/packages/b2/ea/e21010457847b26bb4aea3983e9b44afbcefef07defc5e9a3285a8fe2f0c/rasterio-1.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:38a126f8dbf405cd3450b5bd10c6cc493a2e1be4cf83442d26f5e4f412372d36", size = 18735924 }, + { url = "https://files.pythonhosted.org/packages/67/72/331727423b28fffdfd8bf18bdc55c18d374c0fefd2dde390cd833f8f4477/rasterio-1.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e90c2c300294265c16becc9822337ded0f01fb8664500b4d77890d633d8cd0e", size = 22251721 }, + { url = "https://files.pythonhosted.org/packages/be/cc/453816b489af94b9a243eda889865973d518989ba6923b2381f6d6722b43/rasterio-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:a962ad4c29feaf38b1d7a94389313127de3646a5b9b734fbf9a04e16051a27ff", size = 25430154 }, + { url = "https://files.pythonhosted.org/packages/2e/e0/718c06b825d1f62077913e5bff1e70b71ac673718b135d55a0256d88d4ba/rasterio-1.4.3-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:5d4fcb635379b3d7b2f5e944c153849e3d27e93f35ad73ad4d3f0b8a580f0c8e", size = 21532284 }, + { url = "https://files.pythonhosted.org/packages/bb/a8/3b6b11923300d6835453d1157fabb518338067a67366c5c52e9df9a2314f/rasterio-1.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:98a9c89eade8c779e8ac1e525269faaa18c6b9818fc3c72cfc4627df71c66d0d", size = 18729960 }, + { url = "https://files.pythonhosted.org/packages/05/19/94d6c66184c7d0f9374330c714f62c147dbb53eda9efdcc8fc6e2ac454c5/rasterio-1.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9bab1a0bb22b8bed1db34b5258db93d790ed4e61ef21ac055a7c6933c8d5e84", size = 22237518 }, + { url = "https://files.pythonhosted.org/packages/df/88/9db5f49ebfdd9c12365e4cac76c34ccb1a642b1c8cbab4124b3c681495de/rasterio-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1839960e2f3057a6daa323ccf67b330f8f2f0dbd4a50cc7031e88e649301c5c0", size = 25424949 }, +] + +[[package]] +name = "rioxarray" +version = "0.20.1.dev0" +source = { editable = "." } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pyproj" }, + { name = "rasterio" }, + { name = "xarray" }, +] + +[package.optional-dependencies] +all = [ + { name = "scipy" }, +] +dev = [ + { name = "dask" }, + { name = "mypy" }, + { name = "netcdf4" }, + { name = "pre-commit" }, + { name = "pylint" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-timeout" }, +] +interp = [ + { name = "scipy" }, +] +test = [ + { name = "dask" }, + { name = "netcdf4" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-timeout" }, +] + +[package.metadata] +requires-dist = [ + { name = "dask", marker = "extra == 'test'" }, + { name = "mypy", marker = "extra == 'dev'" }, + { name = "netcdf4", marker = "extra == 'test'" }, + { name = "numpy", specifier = ">=2" }, + { name = "packaging" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pylint", marker = "extra == 'dev'" }, + { name = "pyproj", specifier = ">=3.3" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-cov", marker = "extra == 'test'" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "rasterio" }, + { name = "rioxarray", extras = ["test"], marker = "extra == 'dev'" }, + { name = "scipy", marker = "extra == 'all'" }, + { name = "scipy", marker = "extra == 'interp'" }, + { name = "xarray", specifier = ">=2024.7.0,<2025.12" }, +] + +[[package]] +name = "scipy" +version = "1.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986 }, + { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814 }, + { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795 }, + { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476 }, + { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692 }, + { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345 }, + { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975 }, + { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926 }, + { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014 }, + { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856 }, + { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306 }, + { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371 }, + { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877 }, + { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103 }, + { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756 }, + { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566 }, + { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877 }, + { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366 }, + { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931 }, + { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081 }, + { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244 }, + { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912 }, + { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371 }, + { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477 }, + { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678 }, + { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178 }, + { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246 }, + { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469 }, + { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043 }, + { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952 }, + { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512 }, + { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639 }, + { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729 }, + { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251 }, + { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681 }, + { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423 }, + { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027 }, + { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379 }, + { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052 }, + { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183 }, + { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174 }, + { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852 }, + { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595 }, + { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269 }, + { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779 }, + { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128 }, + { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901 }, +] + +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093 }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] + +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095 }, +] + +[[package]] +name = "xarray" +version = "2025.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/ad/b072c970cfb2e2724509bf1e8d7fb4084cc186a90d486c9ac4a48ff83186/xarray-2025.11.0.tar.gz", hash = "sha256:d7a4aa4500edbfd60676b1613db97da309ab144cac0bcff0cbf483c61c662af1", size = 3072276 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/b4/cfa7aa56807dd2d9db0576c3440b3acd51bae6207338ec5610d4878e5c9b/xarray-2025.11.0-py3-none-any.whl", hash = "sha256:986893b995de4a948429356a3897d78e634243c1cac242bd59d03832b9d72dd1", size = 1375447 }, +]