Skip to content

feat: update ee_to_xarray() to support xee v0.1.0 API with backward compatibility#2782

Open
jdbcode wants to merge 3 commits into
masterfrom
feat/update-ee-to-xarray-xee-v0.1
Open

feat: update ee_to_xarray() to support xee v0.1.0 API with backward compatibility#2782
jdbcode wants to merge 3 commits into
masterfrom
feat/update-ee-to-xarray-xee-v0.1

Conversation

@jdbcode
Copy link
Copy Markdown
Member

@jdbcode jdbcode commented May 14, 2026

Closes #2780

Summary

Updates geemap.ee_to_xarray() to work with the breaking API changes introduced in xee v0.1.0, while remaining fully backward compatible with existing user code.

What changed in xee v0.1.0

xee v0.1.0 replaced the old grid parameters (scale, geometry) with explicit grid parameters (crs_transform, shape_2d). Calls using the old API raise errors with the new xee version.

Changes in this PR

geemap/common.pyee_to_xarray():

The function now implements a three-path compatibility layer:

  1. New API passthrough — if crs_transform and shape_2d are already in kwargs, pass them directly to xee (new API users).
  2. Legacy API conversion — if scale and/or geometry are provided, convert them to the new crs_transform/shape_2d grid params using xee.helpers.fit_geometry(). This preserves full backward compatibility.
  3. Auto-infer native grid — if no grid params are provided at all (e.g. ee_to_xarray("projects/...") with just an asset ID), infer the native source grid via xee.helpers.extract_grid_params(). This is the most common usage pattern.

Additional changes:

  • shapely is now an optional import (graceful fallback with a clear error message if missing and legacy params are used)
  • The crs parameter is correctly preserved when converting legacy params (was accidentally removed in the original path)

tests/test_common.py:

  • Added test_ee_to_xarray_with_scale_parameter — verifies scale → new grid params conversion
  • Added test_ee_to_xarray_with_geometry — verifies scale + geometry → new grid params conversion

Backward compatibility

All existing call patterns continue to work:

Pattern Result
ee_to_xarray(asset_id) auto-infers native grid
ee_to_xarray(asset_id, crs=..., scale=...) converts to new grid params
ee_to_xarray(asset_id, crs=..., scale=..., geometry=...) converts + clips
ee_to_xarray(image_collection) auto-infers native grid
ee_to_xarray(image) auto-infers native grid
ee_to_xarray(..., crs_transform=..., shape_2d=...) passes through directly

Known limitation: Passing a list of asset IDs still does not work with xee (this is a pre-existing limitation unrelated to the v0.1.0 migration).

jdbcode added 2 commits May 13, 2026 17:51
…ompatibility

- Convert legacy scale/geometry parameters to new grid_params format
- Use xee.helpers.fit_geometry() and extract_grid_params() for grid system
- Handle ee.Geometry to shapely.geometry conversion
- Detect geographic vs projected CRS for proper scale handling
- Maintain backward compatibility with xee v0.0.x
- Add helpful warnings if conversion fails
- Closes #2780
- Add test for legacy scale parameter conversion
- Add test for geometry parameter handling
- Both tests validate proper grid parameter generation and old parameter removal
Copilot AI review requested due to automatic review settings May 14, 2026 00:51
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the ee_to_xarray function to support the xee v0.1.0+ API by automatically converting legacy parameters into the new grid parameter system and adds corresponding unit tests. Review feedback suggests several improvements, including handling ee.Projection objects to prevent potential attribute errors, simplifying redundant logic in conditional checks and scale assignments, and using shapely.geometry.shape() for more robust geometry parsing.

Comment thread geemap/common.py
)

# Determine if we need to convert legacy parameters to grid_params.
# Also handle no-grid input by defaulting to source/native grid.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic in this if statement contains a redundant check for scale is not None. Since scale is not None is the first part of the or condition, the second check inside the parenthesis will only be evaluated if scale is already None.

Suggested change
# Also handle no-grid input by defaulting to source/native grid.
if scale is not None or (crs is not None and geometry is not None):

Comment thread geemap/common.py
raise ImportError(
"shapely is required to convert legacy grid parameters "
"for xee>=0.1.0"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The crs parameter can be an ee.Projection object. If it is, target_crs will be an object that does not support the .upper() method used later on line 3153, leading to an AttributeError. It is safer to extract the CRS string if an ee.Projection is provided.

            if isinstance(crs, ee.Projection):
                target_crs = crs.getInfo().get("crs", "EPSG:4326")
            else:
                target_crs = crs or "EPSG:4326"

Comment thread geemap/common.py
Comment on lines +3125 to +3136
# Determine the geometry to use
if geometry is not None:
# Convert EE geometry to shapely if needed
if isinstance(geometry, ee.Geometry):
geom_dict = geometry.getInfo()
if geom_dict["type"] == "Polygon":
coords = geom_dict["coordinates"][0]
geom_shapely = shapely.geometry.Polygon(coords)
elif geom_dict["type"] == "Rectangle":
geom_shapely = shapely.geometry.box(*geom_dict["coordinates"])
else:
raise ValueError(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Instead of manually parsing specific geometry types, you can use shapely.geometry.shape() which is more robust and handles all GeoJSON-compatible geometry types (including holes in Polygons, MultiPolygons, etc.) automatically.

                if isinstance(geometry, ee.Geometry):
                    geom_dict = geometry.getInfo()
                    geom_shapely = shapely.geometry.shape(geom_dict)

Comment thread geemap/common.py
Comment on lines +3151 to +3158
geom_shapely = shapely.geometry.box(-180, -90, 180, 90)

# Convert scale to (x_scale, y_scale) tuple
if scale is not None:
# Determine if scale is in degrees (geographic) or meters (projected)
if "EPSG:4326" in target_crs or "WGS" in target_crs.upper():
# Geographic CRS - scale is in degrees
grid_scale = (scale, -scale)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The if/else block inside if scale is not None is redundant because both branches perform the exact same assignment: grid_scale = (scale, -scale). The CRS check is only necessary when determining the default scale.

Suggested change
geom_shapely = shapely.geometry.box(-180, -90, 180, 90)
# Convert scale to (x_scale, y_scale) tuple
if scale is not None:
# Determine if scale is in degrees (geographic) or meters (projected)
if "EPSG:4326" in target_crs or "WGS" in target_crs.upper():
# Geographic CRS - scale is in degrees
grid_scale = (scale, -scale)
if scale is not None:
grid_scale = (scale, -scale)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates geemap.ee_to_xarray() to support xee v0.1.0’s new grid-parameter API (crs_transform, shape_2d) while attempting to preserve legacy call patterns (crs/scale/geometry) via conversion and auto-inference.

Changes:

  • Added a compatibility layer in ee_to_xarray() to (1) convert legacy grid args to v0.1.0 grid params and (2) infer native grid params when none are provided.
  • Made shapely an optional import used for legacy-parameter conversion.
  • Added unit tests intended to validate legacy scale and geometry conversions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
geemap/common.py Adds xee v0.1.0 grid-param compatibility logic (conversion + inference) to ee_to_xarray().
tests/test_common.py Adds tests for legacy scale and geometry call patterns for ee_to_xarray().
Comments suppressed due to low confidence (4)

geemap/common.py:3111

  • The legacy-conversion condition doesn’t trigger when callers pass only geometry (with neither scale nor crs). In that case, the code falls through to extract_grid_params() inference and silently ignores the provided geometry, which is a backward-compat behavior change from the legacy API where geometry constrained bounds. Consider treating geometry alone as legacy usage (e.g., infer native grid first, then fit_geometry to the provided geometry) or raising a clear error telling users to pass scale/explicit grid params when using geometry.

    # Determine if we need to convert legacy parameters to grid_params.
    # Also handle no-grid input by defaulting to source/native grid.
    if scale is not None or (

geemap/common.py:3136

  • The EE-geometry conversion only supports Polygon and Rectangle and drops inner rings/holes (it uses only coordinates[0]). Previously, geometry was forwarded to xee and could support other EE geometry types, so this narrows backward compatibility and can produce incorrect bounds for polygons with holes. Use a generic GeoJSON→Shapely conversion (e.g., shapely.geometry.shape(geom_dict)) so all geometry types and rings are handled correctly.
            # Determine the geometry to use
            if geometry is not None:
                # Convert EE geometry to shapely if needed
                if isinstance(geometry, ee.Geometry):
                    geom_dict = geometry.getInfo()
                    if geom_dict["type"] == "Polygon":
                        coords = geom_dict["coordinates"][0]
                        geom_shapely = shapely.geometry.Polygon(coords)
                    elif geom_dict["type"] == "Rectangle":
                        geom_shapely = shapely.geometry.box(*geom_dict["coordinates"])
                    else:
                        raise ValueError(

tests/test_common.py:781

  • mock_geometry = mock.MagicMock(spec=ee.Geometry) is not an actual ee.Geometry instance, so isinstance(geometry, ee.Geometry) will be false in ee_to_xarray(), causing a TypeError in the conversion path. Use a real geometry object that passes the type check (e.g., construct an ee.Geometry.Polygon(...) if feasible in tests, or use the existing tests.fake_ee.Geometry() pattern and adjust the production check accordingly), or change the production code to accept geometry-like objects via duck-typing (hasattr(getInfo)) if that’s intended.

        mock_ds = xr.Dataset()
        mock_open_ds.return_value = mock_ds

        # Mock ee.Geometry
        mock_geometry = mock.MagicMock(spec=ee.Geometry)

geemap/common.py:3180

  • When legacy→new conversion fails (including when shapely is missing), the code only emits a warning and then falls back to passing legacy params (scale/geometry/projection) through to xee. With xee>=0.1.0 this is guaranteed to raise (e.g., “unexpected keyword argument 'scale'”), so the warning+fallback path just defers a harder-to-understand failure. Consider raising a clear exception immediately when conversion fails and legacy params are present (especially for ImportError from missing shapely), with guidance to install shapely or pass explicit crs_transform/shape_2d.
                grid_crs=target_crs,
                grid_scale=grid_scale,
            )
        except Exception as e:
            import warnings

            warnings.warn(
                f"Failed to convert legacy API parameters to new grid format: {e}. "

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread geemap/common.py
Comment on lines +3104 to +3106
has_explicit_grid_params = all(
k in kwargs and kwargs[k] is not None
for k in ["crs", "crs_transform", "shape_2d"]
Comment thread geemap/common.py
from xee import helpers

try:
import shapely
Comment thread geemap/common.py
projection (optional): An `ee.Projection` object (legacy API, xee v0.0.x).
Automatically converted to `crs` and `scale` for the new API.
geometry (optional): An `ee.Geometry` or shapely geometry to define the
region of interest. Converted to the new grid system for xee v0.1.0+.
Comment thread tests/test_common.py
Comment on lines +732 to +740
@mock.patch("geemap.common.xr.open_dataset")
@mock.patch("geemap.common.ee.Initialize")
@mock.patch("geemap.common.ee.data.is_initialized", return_value=True)
def test_ee_to_xarray_with_scale_parameter(
self, mock_is_init, mock_init, mock_open_ds
):
"""Test ee_to_xarray with legacy scale parameter (xee v0.0.x API)."""
# Mock xr.open_dataset to return a dummy dataset
import xarray as xr
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 2026

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.

Update geemap.ee_to_xarray() method for updated xee syntax

2 participants