Skip to content

get bounding box for data in UTM or polar-stereo projections - #1522

Open
Alex-Lewandowski wants to merge 4 commits into
mainfrom
bug/get_polar_bounding_box
Open

Alex-Lewandowski wants to merge 4 commits into
mainfrom
bug/get_polar_bounding_box

Conversation

@Alex-Lewandowski

@Alex-Lewandowski Alex-Lewandowski commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Description of proposed changes

tropo_pyaps3.get_bounding_box assumes that if meta['Y_UNIT'] is not degrees, it must be UTM, and then transforms bbox coords to lat/lon using the utm package. This causes incorrect transformations for data in polar-stereo projections, which like data in UTM, also have units of meters.

For example, a subset Copernicus DEM for NISAR in EPSG:3413 had bounding coords of:

  • y_min: 558768
  • y_max: 659319
  • x_min: -2731382
  • x_max: -2662085

When running tropo_opera, a call to tropo_pyaps3.get_bounding_box resulted in nonsensical lat/lon coords:

  • lat_min: 91.55
  • lat_max: 256.91
  • lon_min: -132.26
  • lon_max: 41.63

Instead of the expected:

  • lat_min: 64.47
  • lat_max: 65.27
  • lon_min: -148.91
  • lon_max: -146.56

The proposed solution performs the transformation with pyproj.Transformer instead of utm.to_latlon. Additionally, all four corners are used to find the maximum rectangular bounding box when performing transformations from polar stereographic coordinates. This is important because significant tilting may occur when projecting from polar coordinates which makes using only two corner coordinates an unreliable way to cover all the valid data.

Reminders

  • Pass Pre-commit check (green)
  • Pass Codacy code review (green)
  • Pass Circle CI test (green)
  • Make sure that your code follows our style. Use the other functions/files as a basis.
  • If modifying functionality, describe changes to function behavior and arguments in a comment below the function declaration.
  • If adding new functionality, add a detailed description to the documentation and/or an example.

Summary by Sourcery

Use CRS-aware transformations to derive accurate geographic bounding boxes from UTM and polar-stereo data.

Bug Fixes:

  • Correct bounding-box conversion for datasets using projected meter-based coordinate systems, including polar stereographic projections.
  • Preserve the existing UTM conversion path while avoiding incorrect UTM assumptions for other projected data.

Enhancements:

  • Compute geographic bounds from all four projected bounding-box corners to accurately cover data affected by polar-projection distortion.

@sourcery-ai

sourcery-ai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Projected datasets now use their declared EPSG coordinate reference system to calculate geographic bounding boxes, correctly supporting polar stereographic data while preserving the existing UTM-specific path.

Flow diagram for projected-data bounding-box calculation

flowchart TD
    A[get_bounding_box] --> B{Y_UNIT is degrees?}
    B -->|Yes| C[Keep geographic coordinates]
    B -->|No| D{EPSG is declared}
    D -->|Yes| E[Transformer.from_crs EPSG to EPSG:4326]
    E --> F[Transform all four bounding-box corners]
    F --> G[Compute latitude and longitude extrema]
    D -->|No| H{UTM_ZONE is declared}
    H -->|Yes| I[ut.utm2latlon for bounding corners]
    H -->|No| J[No projected conversion]
    C --> K[Return geographic bounding box]
    G --> K
    I --> K
    J --> K
Loading

File-Level Changes

Change Details Files
Replace the projected-coordinate bounding-box conversion with EPSG-driven transformations and account for projection-induced corner extrema.
  • Create a pyproj transformer from the metadata EPSG to EPSG:4326 with x/y ordering preserved.
  • Transform all four projected bounding-box corners, then derive latitude and longitude bounds while retaining input-axis orientation.
  • Restrict the legacy utm conversion to datasets explicitly marked with a UTM zone.
src/mintpy/tropo_pyaps3.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/mintpy/tropo_pyaps3.py" line_range="344" />
<code_context>
         if not meta.get('Y_UNIT', 'degrees').lower().startswith('deg'):
-            lat0, lon0 = ut.utm2latlon(meta, easting=lon0, northing=lat0)
-            lat1, lon1 = ut.utm2latlon(meta, easting=lon1, northing=lat1)
+         epsg = meta.get('EPSG')
+         transformer = Transformer.from_crs(
+                 f'EPSG:{epsg}',
</code_context>
<issue_to_address>
**UTM bounds fail without EPSG**

When geocoded projected UTM metadata has `UTM_ZONE` but no `EPSG`, `meta.get('EPSG')` returns `None`, so `Transformer.from_crs` receives `EPSG:None` and raises; bounding-box calculation aborts and weather processing fails.

Use `UTM_ZONE` to determine the CRS when `EPSG` is absent.
</issue_to_address>

### Comment 2
<location path="src/mintpy/tropo_pyaps3.py" line_range="350-351" />
<code_context>
+                 'EPSG:4326',
+                 always_xy=True,
+                 )
+         lon0, lat0 = transformer.transform(lon0, lat0)
+         lon1, lat1 = transformer.transform(lon1, lat1)

     else:
</code_context>
<issue_to_address>
**Bounds exclude raster coverage**

When a projected raster's geographic extrema fall outside the two transformed diagonal endpoints, including along an edge, `get_bounding_box` transforms only two diagonal corners, so its bounds omit geographic extrema; weather requests or OPERA cropping then exclude part of the raster.

Transform or sample the full projected footprint, including its edges, and compute bounds from the resulting coordinates.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/mintpy/tropo_pyaps3.py Outdated
Comment thread src/mintpy/tropo_pyaps3.py Outdated
@Alex-Lewandowski
Alex-Lewandowski marked this pull request as draft October 7, 2026 22:58
…g box coords when transofrming polar-stereo to avoid underestimating bbox extents
@EJFielding

Copy link
Copy Markdown

I ran into this same problem today for NISAR data over Iceland.

@Alex-Lewandowski
Alex-Lewandowski marked this pull request as ready for review October 8, 2026 17:32

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/mintpy/tropo_pyaps3.py" line_range="362-365" />
<code_context>
+            else:
+                lat0, lat1 = min(lats), max(lats)
+
+            if lon0 > lon1:
+                lon0, lon1 = max(lons), min(lons)
+            else:
+                lon0, lon1 = min(lons), max(lons)
+
+        elif meta.get('UTM_ZONE'):
</code_context>
<issue_to_address>
**Dateline bounds span nearly globe**

When a projected footprint crosses the antimeridian and its transformed corner longitudes lie on both sides of ±180°, `get_bounding_box` takes numeric longitude minima and maxima, producing a nearly 360° interval; `get_snwe` can make an oversized or invalid weather request, and `get_opera_crop_indices` can select nearly the entire longitude axis.

Compute the narrow wrapped longitude interval across the antimeridian instead of taking the numeric minimum and maximum.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +362 to +365
if lon0 > lon1:
lon0, lon1 = max(lons), min(lons)
else:
lon0, lon1 = min(lons), max(lons)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Dateline bounds span nearly globe

When a projected footprint crosses the antimeridian and its transformed corner longitudes lie on both sides of ±180°, get_bounding_box takes numeric longitude minima and maxima, producing a nearly 360° interval; get_snwe can make an oversized or invalid weather request, and get_opera_crop_indices can select nearly the entire longitude axis.

Compute the narrow wrapped longitude interval across the antimeridian instead of taking the numeric minimum and maximum.

Prompt for AI agents
In `src/mintpy/tropo_pyaps3.py` at lines 362-365:

**Dateline bounds span nearly globe**

When a projected footprint crosses the antimeridian and its transformed corner longitudes lie on both sides of ±180°, `get_bounding_box` takes numeric longitude minima and maxima, producing a nearly 360° interval; `get_snwe` can make an oversized or invalid weather request, and `get_opera_crop_indices` can select nearly the entire longitude axis.

Compute the narrow wrapped longitude interval across the antimeridian instead of taking the numeric minimum and maximum.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants