Skip to content

geotiff: VRT read silently flattens mixed-CRS mosaic (#2321 gap) #2444

@brendancol

Description

@brendancol

Reason or Problem

read_vrt silently flattens a mixed-CRS mosaic to the VRT-declared SRS. If an underlying source TIFF carries a different CRS than the VRT's <SRS>, the read still succeeds, attrs['crs'] reports the VRT-declared CRS, and the pixel content from the disagreeing source has been written into the mosaic regardless. No error, no warning. The output array claims one projection while some of its pixels came from another.

Two test pins already exist for this gap:

  • test_mixed_crs_vrt_does_not_silently_flatten in xrspatial/geotiff/tests/vrt/test_metadata.py (strict xfail).
  • test_mixed_source_crs_rejected in xrspatial/geotiff/tests/vrt/test_validation.py (non-strict xfail).

Both should flip to pass once the validator opens each source TIFF and checks its CRS.

Proposal

Add a per-source CRS check to validate_parsed_vrt in xrspatial/geotiff/_vrt_validation.py. The check opens each SimpleSource, pulls its CRS via extract_geo_info, and compares to the VRT-declared SRS. On disagreement, raise VRTUnsupportedError naming the source path and both CRSes.

Design:

  • Walk every _Source in every _VRTBand on the parsed VRTDataset.
  • For each unique source path, open the file via _FileSource, parse the header and first IFD, and pull crs_wkt / crs_epsg via extract_geo_info.
  • Compare each source CRS to parsed.crs_wkt using pyproj.CRS.equals after canonicalisation. If the VRT carries no <SRS>, all sources must agree among themselves.
  • On disagreement raise VRTUnsupportedError with both disagreeing CRSes named.
  • Runs at graph-build (chunked path) and eager-read setup (eager path), matching the existing validator wiring in _backends/vrt.py.
  • If pyproj is not installed, skip the check (same disposition as _check_write_conflicting_crs). A string compare would false-positive on equivalent representations.
  • If a source is unreadable, skip silently. The existing missing_sources path fires on the real read.

Usage:

No new public API. The check fires automatically from both read_vrt entry points. No opt-out flag: the mosaic reader has no reprojection step, so a mixed-CRS VRT cannot be served correctly.

Value: Closes the silent-correctness gap pinned by both xfails. After this change they pass with VRTUnsupportedError raised at validation time.

Stakeholders and Impacts

Affects callers feeding mixed-CRS VRTs to read_vrt. They get a typed error instead of a mis-georeferenced mosaic. Single-CRS VRTs (the common case) see no change beyond one extra metadata read per unique source at validation time. Implementation is local to _vrt_validation.py; the call sites in _backends/vrt.py pick up the new check automatically.

Drawbacks

One extra mmap + IFD parse per unique source at validation time. Cheap; no pixel decode.

Alternatives

Defer the check to a per-chunk task. Not used because the documented validator contract is graph-build / eager-read setup, and a chunked-path failure deep in compute() is the opaque user experience the centralised validator was added to avoid.

Unresolved Questions

None.

Additional Notes or Context

Test pins: test_mixed_crs_vrt_does_not_silently_flatten and test_mixed_source_crs_rejected. Threading pattern follows PR #2442.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggeotiffGeoTIFF moduleinput-validationInput validation and error messagesvrtVRT support contract

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions