From 4ff0354920812e5efaf87973394470d7dddee764 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Fri, 25 Sep 2026 12:18:02 -0400 Subject: [PATCH 1/3] feat: Add user-defined Oblique Stereographic CRS fixture 128x128 extract of a source.coop/dataforcanada COG whose CRS (New Brunswick Stereographic, equivalent to EPSG:2953) is stored as user-defined geo keys with the projection origin in ProjNatOrigin*/ProjScaleAtNatOrigin rather than ProjCenter*. Parsers that read the ProjCenter* keys for CT_ObliqueStereographic place the image near (0, 0). From https://github.com/source-cooperative/cog-viewer/issues/40 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../O2308000_7586000_cog.tif | Bin 0 -> 44402 bytes .../O2308000_7586000_cog_info.md | 61 ++++++++++++++++++ real_data/source-coop-dataforcanada/README.md | 54 ++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif create mode 100644 real_data/source-coop-dataforcanada/O2308000_7586000_cog_info.md create mode 100644 real_data/source-coop-dataforcanada/README.md diff --git a/real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif b/real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif new file mode 100644 index 0000000000000000000000000000000000000000..11adc1fc0cc69afb4271244491db64e469e99998 GIT binary patch literal 44402 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 literal 0 HcmV?d00001 diff --git a/real_data/source-coop-dataforcanada/O2308000_7586000_cog_info.md b/real_data/source-coop-dataforcanada/O2308000_7586000_cog_info.md new file mode 100644 index 0000000..cf3e88c --- /dev/null +++ b/real_data/source-coop-dataforcanada/O2308000_7586000_cog_info.md @@ -0,0 +1,61 @@ +``` +Driver: GTiff +File: real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif +COG: True +Compression: LZW +ColorSpace: None + +Profile + Width: 128 + Height: 128 + Bands: 3 + Tiled: False + Dtype: uint8 + NoData: None + Alpha Band: False + Internal Mask: False + Interleave: PIXEL + ColorMap: False + ColorInterp: ('red', 'green', 'blue') + Scales: (1.0, 1.0, 1.0) + Offsets: (0.0, 0.0, 0.0) + +Geo + Crs: EPSG:2953 + Origin: (2307999.0, 7588002.0) + Resolution: (0.1, -0.1) + BoundingBox: (2307999.0, 7587989.2, 2308011.8, 7588002.0) + MinZoom: 20 + MaxZoom: 20 + +Image Metadata + DataType: Generic + AREA_OR_POINT: Area + +Image Structure + LAYOUT: COG + COMPRESSION: LZW + INTERLEAVE: PIXEL + +Band 1 + ColorInterp: red + Metadata: + BandName: Band_1 + RepresentationType: ATHEMATIC + +Band 2 + ColorInterp: green + Metadata: + BandName: Band_2 + RepresentationType: ATHEMATIC + +Band 3 + ColorInterp: blue + Metadata: + BandName: Band_3 + RepresentationType: ATHEMATIC + +IFD + Id Size BlockSize Decimation + 0 128x128 128x128 0 +``` diff --git a/real_data/source-coop-dataforcanada/README.md b/real_data/source-coop-dataforcanada/README.md new file mode 100644 index 0000000..d5f9d18 --- /dev/null +++ b/real_data/source-coop-dataforcanada/README.md @@ -0,0 +1,54 @@ +Includes data from + +https://source.coop/dataforcanada + +## User-defined Oblique Stereographic CRS + +New Brunswick Stereographic, equivalent to EPSG:2953, but stored as a +user-defined projected CRS (`ProjectedCSTypeGeoKey = 32767`) with +`ProjCoordTransGeoKey = 16` (`CT_ObliqueStereographic`). The projection origin +is stored in `ProjNatOriginLatGeoKey`, `ProjNatOriginLongGeoKey` and +`ProjScaleAtNatOriginGeoKey`, not the `ProjCenter*` keys. + +See https://github.com/source-cooperative/cog-viewer/issues/40. + +`listgeo -no_norm O2308000_7586000_cog.tif`: + +``` + Keyed_Information: + GTModelTypeGeoKey (Short,1): ModelTypeProjected + GTRasterTypeGeoKey (Short,1): RasterPixelIsArea + GTCitationGeoKey (Ascii,42): "NAD83(CSRS) / New Brunswick Stereographic" + GeographicTypeGeoKey (Short,1): User-Defined + GeogCitationGeoKey (Ascii,43): "GCS Name = NAD83(CSRS)|Primem = Greenwich|" + GeogGeodeticDatumGeoKey (Short,1): Code-6140 (NAD83 Canadian Spatial Reference System) + GeogAngularUnitsGeoKey (Short,1): Angular_Degree + GeogSemiMajorAxisGeoKey (Double,1): 6378137 + GeogInvFlatteningGeoKey (Double,1): 298.257222101 + GeogPrimeMeridianLongGeoKey (Double,1): 0 + ProjectedCSTypeGeoKey (Short,1): User-Defined + ProjectionGeoKey (Short,1): User-Defined + ProjCoordTransGeoKey (Short,1): CT_ObliqueStereographic + ProjLinearUnitsGeoKey (Short,1): Linear_Meter + ProjNatOriginLongGeoKey (Double,1): -66.5 + ProjNatOriginLatGeoKey (Double,1): 46.5 + ProjFalseEastingGeoKey (Double,1): 2500000 + ProjFalseNorthingGeoKey (Double,1): 7500000 + ProjScaleAtNatOriginGeoKey (Double,1): 0.999912 + End_Of_Keys. +``` + +GDAL identifies the source CRS as EPSG:2953 by name when reading, so a plain +`gdal_translate` writes `ProjectedCSTypeGeoKey = 2953` instead. Assigning the +same WKT without the `PROJCS` and `GEOGCS` authority codes makes GDAL write the +same user-defined geo keys as the source file: + +```bash +pixi run gdal_translate \ + -srcwin 0 0 128 128 \ + -a_srs 'PROJCS["NAD83(CSRS) / New Brunswick Stereographic",GEOGCS["NAD83(CSRS)",DATUM["NAD83_Canadian_Spatial_Reference_System",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6140"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Oblique_Stereographic"],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",-66.5],PARAMETER["scale_factor",0.999912],PARAMETER["false_easting",2500000],PARAMETER["false_northing",7500000],UNIT["metre",1,AUTHORITY["EPSG","9001"]]]' \ + -of COG \ + -co BLOCKSIZE=128 \ + /vsicurl/https://data.source.coop/dataforcanada/test/O2308000_7586000_cog.tif \ + O2308000_7586000_cog.tif +``` From 0a0d6e1caf6234e6493abd9b6eb2da367c06667a Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Fri, 25 Sep 2026 12:57:06 -0400 Subject: [PATCH 2/3] test: Check raw GeoKeys of the dataforcanada fixture Regenerating the fixture with a plain gdal_translate silently writes ProjectedCSTypeGeoKey = 2953 instead of the user-defined keys the fixture exists to exercise. Read the raw GeoKeys with async-tiff and assert the full set, and run pytest in CI. async-tiff comes from PyPI because conda-forge has no osx-arm64 build. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/ci.yml | 3 + pixi.lock | 133 +++++++++++++++++++++++++++++++++++++++ pixi.toml | 3 + tests/test_real_data.py | 59 +++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 tests/test_real_data.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7198b49..236d26a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,5 +18,8 @@ jobs: - name: Check generated files are up to date run: pixi run check + - name: Run tests + run: pixi run test + # Use ruff-action so we get annotations in the Github UI - uses: astral-sh/ruff-action@v4.1.0 diff --git a/pixi.lock b/pixi.lock index 377244f..a00eb14 100644 --- a/pixi.lock +++ b/pixi.lock @@ -27,11 +27,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.2-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.18-py314h42812f9_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.1-py314hcb43362_0.conda @@ -39,6 +41,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2026.6.26-py314h8570d32_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyha191276_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.9.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda @@ -115,6 +118,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.7.1-h99ae125_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.1-py314h0f05182_0.conda @@ -122,6 +126,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.2-h32b2ec7_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.2-h4df99d1_100.conda @@ -139,6 +144,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-4.2.0-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.31-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda @@ -152,7 +158,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3e/8f/c8a7c48fd966e13ee7390984d43318f71f0eab76889108b3a9621b6ef62a/async_tiff-0.7.2-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/c3/cb8db47ed1a28b43d498e91b629d69886778ad5d9e91dcb953d411052728/morecantile-7.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl @@ -177,11 +185,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.2-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/dav1d-1.2.1-h0dc2134_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/debugpy-1.8.19-py314h655ac26_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/freexl-2.0.0-h3183152_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gdal-3.12.1-py314h36678e8_0.conda @@ -189,6 +199,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/giflib-5.2.2-h10d778d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/icu-78.2-h14c5de8_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/imagecodecs-2026.6.26-py314h4d4a0b9_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.9.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda @@ -259,6 +270,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/proj-9.7.0-h3124640_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/psutil-7.2.1-py314hd330473_0.conda @@ -266,6 +278,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/python-3.14.2-hf88997e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.2-h4df99d1_100.conda @@ -283,6 +296,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/svt-av1-4.2.0-hcc62823_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.31-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-hf689a15_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.4-py314h3d180e3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda @@ -296,7 +310,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-ng-2.3.3-h8bce59a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/8b/1591e69991987b43ccd7faecb843bd601dfb08a35395770c1562aaca73da/async_tiff-0.7.2-cp311-abi3-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f1/c3/cb8db47ed1a28b43d498e91b629d69886778ad5d9e91dcb953d411052728/morecantile-7.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl @@ -321,11 +337,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.3.1-pyh8f84b5b_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-plugins-1.1.1.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cligj-0.7.2-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.2-py314hd8ed1ab_100.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/dav1d-1.2.1-hb547adb_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.19-py314hf820bb6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freexl-2.0.0-h3ab3353_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gdal-3.12.1-py314h5484c7d_0.conda @@ -333,6 +351,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/giflib-5.2.2-h93a5062_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.2-h38cb7af_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/imagecodecs-2026.6.26-py314hdaaf36e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.9.0-pyh53cf698_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda @@ -403,6 +422,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/proj-9.7.1-h46dec42_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.1-py314ha14b1ff_0.conda @@ -410,6 +430,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.2-h40d2674_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.2-h4df99d1_100.conda @@ -427,6 +448,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/svt-av1-4.2.0-h0cb729a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.31-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.4-py314h0612a62_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda @@ -440,7 +462,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-hed4e4f5_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/58/fc2451333d389da41e8505a8edcc711fcacfb8f038c67a1629d7e87fbdc0/async_tiff-0.7.2-cp311-abi3-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/f1/c3/cb8db47ed1a28b43d498e91b629d69886778ad5d9e91dcb953d411052728/morecantile-7.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz @@ -468,6 +492,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/dav1d-1.2.1-hcfcfb64_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.19-py314hb98de8c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.1-py314hc17c739_0.conda @@ -475,6 +500,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/giflib-5.2.2-hb0bd012_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.2-h637d24d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/imagecodecs-2026.6.26-py314h37ae81f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh6dadd2b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.9.0-pyhe2676ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda @@ -537,12 +563,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.5-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.7.1-h7b1ce8f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.1-py314hc5dbbe4_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.2-h4b44e0e_100_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.2-h4df99d1_100.conda @@ -561,6 +589,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2022.3.0-h3155e25_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tifffile-2026.7.31-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h2c6b04d_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.4-py314h5a2d7ad_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda @@ -579,7 +608,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9f/cc/e106fcee3a8cd98d57c8842ba4ff2839fea6ca736970f0899b3162298fc9/async_tiff-0.7.2-cp311-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/f1/c3/cb8db47ed1a28b43d498e91b629d69886778ad5d9e91dcb953d411052728/morecantile-7.0.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl @@ -736,6 +767,34 @@ packages: - pkg:pypi/asttokens?source=hash-mapping size: 28797 timestamp: 1763410017955 +- pypi: https://files.pythonhosted.org/packages/3e/8f/c8a7c48fd966e13ee7390984d43318f71f0eab76889108b3a9621b6ef62a/async_tiff-0.7.2-cp311-abi3-manylinux_2_28_x86_64.whl + name: async-tiff + version: 0.7.2 + sha256: 84ca597d8a6354d7b1ae46d2c41432e0dad36ffd4ad54f45aa29d11857506e9b + requires_dist: + - obspec>=0.1.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6b/8b/1591e69991987b43ccd7faecb843bd601dfb08a35395770c1562aaca73da/async_tiff-0.7.2-cp311-abi3-macosx_10_12_x86_64.whl + name: async-tiff + version: 0.7.2 + sha256: 028e9d629dbe8d830581d8e46b92081a656953db020b424615af3c6e51ed9249 + requires_dist: + - obspec>=0.1.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9f/cc/e106fcee3a8cd98d57c8842ba4ff2839fea6ca736970f0899b3162298fc9/async_tiff-0.7.2-cp311-abi3-win_amd64.whl + name: async-tiff + version: 0.7.2 + sha256: 296647adac8b05ae1bacc69feea2e42ce17220d34df1bc419be0daa94364ebd5 + requires_dist: + - obspec>=0.1.0 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c5/58/fc2451333d389da41e8505a8edcc711fcacfb8f038c67a1629d7e87fbdc0/async_tiff-0.7.2-cp311-abi3-macosx_11_0_arm64.whl + name: async-tiff + version: 0.7.2 + sha256: 97d77fcad25824321645c3b83da060b1ece827f30e76fe64db74b4df97e5da1d + requires_dist: + - obspec>=0.1.0 + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.4.0-pyhcf101f3_1.conda sha256: c13d5e42d187b1d0255f591b7ce91201d4ed8a5370f0d986707a802c20c9d32f md5: 537296d57ea995666c68c821b00e360b @@ -1252,6 +1311,17 @@ packages: - pkg:pypi/decorator?source=hash-mapping size: 14129 timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + size: 21333 + timestamp: 1763918099466 - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad md5: ff9efb7f7469aed3c4a8106ffa29593c @@ -1691,6 +1761,17 @@ packages: - pkg:pypi/imagecodecs?source=hash-mapping size: 2008465 timestamp: 1782686195641 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 + md5: 9614359868482abba1bd15ce465e3c42 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/iniconfig?source=hash-mapping + size: 13387 + timestamp: 1760831448842 - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.1.0-pyh5552912_0.conda sha256: b5f7eaba3bb109be49d00a0a8bda267ddf8fa66cc1b54fc5944529ed6f3e8503 md5: 1849eec35b60082d2bd66b4e36dec2b6 @@ -4871,6 +4952,13 @@ packages: - pkg:pypi/numpy?source=hash-mapping size: 7306379 timestamp: 1768085588568 +- pypi: https://files.pythonhosted.org/packages/bb/69/96feeac84ce0b871567225c78515f3b557c023e72ed9b4f1833f3662bd6b/obspec-0.1.0-py3-none-any.whl + name: obspec + version: 0.1.0 + sha256: 307f0fa2c2998b324ecf0eed6a2a89049a4c40c9b1fa2b5e1af28f0ee72136b3 + requires_dist: + - typing-extensions ; python_full_version < '3.12' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d md5: 11b3379b191f63139e29c0d19dee24cd @@ -5124,6 +5212,18 @@ packages: - pkg:pypi/platformdirs?source=hash-mapping size: 23922 timestamp: 1764950726246 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pluggy?source=hash-mapping + size: 25877 + timestamp: 1764896838868 - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.7.1-h99ae125_0.conda sha256: 551cd2b779902ff88cb945cd69af9978561347a17023403b64f476a5a82b70c5 md5: 8bbc19a6e87fbe8b97796e9a42a47a30 @@ -5376,6 +5476,27 @@ packages: requires_dist: - certifi requires_python: '>=3.11' +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + sha256: 430051d80765207a7d782b2b188230ba1489d35c6e75fd9903f76cb9fda4af16 + md5: 64c98a12c4e23eb238bf66bbecafdf3c + depends: + - colorama + - pygments >=2.7.2 + - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 + - tomli >=1 + - exceptiongroup >=1 + - python + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pytest?source=hash-mapping + size: 306724 + timestamp: 1782127176429 - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.2-h32b2ec7_100_cp314.conda build_number: 100 sha256: a120fb2da4e4d51dd32918c149b04a08815fd2bd52099dad1334647984bb07f1 @@ -6076,6 +6197,18 @@ packages: purls: [] size: 3472313 timestamp: 1763055164278 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + size: 21561 + timestamp: 1774492402955 - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.3-py314h5bd0f2a_0.conda sha256: b8f9f9ae508d79c9c697eb01b6a8d2ed4bc1899370f44aa6497c8abbd15988ea md5: e35f08043f54d26a1be93fdbf90d30c3 diff --git a/pixi.toml b/pixi.toml index f770f72..6f22a6e 100644 --- a/pixi.toml +++ b/pixi.toml @@ -10,6 +10,7 @@ generate = "python generate_fixtures.py" info = "bash scripts/info.sh" check = "bash scripts/check.sh" generate-npy = "python scripts/generate_npy.py" +test = "pytest tests" [dependencies] gdal = ">=3.12.1,<4" @@ -19,6 +20,8 @@ python = ">=3.12" rasterio = ">=1.5.0,<2" tifffile = ">=2024.1.30" affine = ">=2.4.0,<3" +pytest = ">=8" [pypi-dependencies] rio-cogeo = ">=7.0.1, <8" +async-tiff = ">=0.7.2, <0.8" diff --git a/tests/test_real_data.py b/tests/test_real_data.py new file mode 100644 index 0000000..dd2d8f9 --- /dev/null +++ b/tests/test_real_data.py @@ -0,0 +1,59 @@ +"""Check the raw GeoKeys of real_data fixtures. + +Some fixtures exist to exercise a specific GeoKey layout, which regenerating +them with GDAL can silently change: GDAL may identify a user-defined CRS as an +EPSG code and write only that code. Reading the raw keys with async-tiff +catches that. +""" + +import asyncio +from pathlib import Path +from typing import Any + +from async_tiff import TIFF +from async_tiff.store import LocalStore + +REPO_ROOT = Path(__file__).parent.parent + + +def read_geo_keys(path: str) -> dict[str, Any]: + """Read the GeoKeys of the first IFD of a TIFF, relative to the repo root.""" + + async def _read() -> dict[str, Any]: + tiff = await TIFF.open(path, store=LocalStore(REPO_ROOT)) + gkd = tiff.ifds[0].geo_key_directory + return {key: gkd[key] for key in gkd.keys()} + + return asyncio.run(_read()) + + +def test_dataforcanada_user_defined_oblique_stereographic(): + """The CRS stays user-defined, with its origin in the ProjNatOrigin* keys. + + A plain gdal_translate of the source writes ProjectedCSTypeGeoKey = 2953 + instead. See real_data/source-coop-dataforcanada/README.md. + """ + geo_keys = read_geo_keys( + "real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif" + ) + assert geo_keys == { + "model_type": 1, + "raster_type": 1, + "citation": "NAD83(CSRS) / New Brunswick Stereographic", + "geographic_type": 32767, + "geog_citation": "GCS Name = NAD83(CSRS)|Primem = Greenwich|", + "geog_geodetic_datum": 6140, + "geog_angular_units": 9102, + "geog_semi_major_axis": 6378137.0, + "geog_inv_flattening": 298.257222101, + "geog_prime_meridian_long": 0.0, + "projected_type": 32767, + "projection": 32767, + "proj_coord_trans": 16, + "proj_linear_units": 9001, + "proj_nat_origin_long": -66.5, + "proj_nat_origin_lat": 46.5, + "proj_false_easting": 2500000.0, + "proj_false_northing": 7500000.0, + "proj_scale_at_nat_origin": 0.999912, + } From b12fc4505369be7fef95f519a5cd4d268b0f5b12 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Fri, 25 Sep 2026 13:24:06 -0400 Subject: [PATCH 3/3] test: Clarify what the dataforcanada GeoKey test asserts Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_real_data.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_real_data.py b/tests/test_real_data.py index dd2d8f9..daf30fb 100644 --- a/tests/test_real_data.py +++ b/tests/test_real_data.py @@ -28,10 +28,9 @@ async def _read() -> dict[str, Any]: def test_dataforcanada_user_defined_oblique_stereographic(): - """The CRS stays user-defined, with its origin in the ProjNatOrigin* keys. + """Assert that CRS is stored in custom geo keys, not simply as `EPSG:2953`. - A plain gdal_translate of the source writes ProjectedCSTypeGeoKey = 2953 - instead. See real_data/source-coop-dataforcanada/README.md. + See real_data/source-coop-dataforcanada/README.md. """ geo_keys = read_geo_keys( "real_data/source-coop-dataforcanada/O2308000_7586000_cog.tif"