Migration Guide: Xee v0.1.0

This guide helps you update your code from Xee v0.0.x to v0.1.0. The 0.1 release includes two major improvements:

  1. New grid parameter system for specifying output geography

  2. Updated dimension ordering from [time, x, y] to [time, y, x]

Quick Migration Checklist

  • [ ] Replace scale and geometry parameters with grid parameter helpers

  • [ ] Remove .transpose() calls before plotting or passing to libraries expecting [time, y, x]

  • [ ] Update any code that explicitly references dimension order

  • [ ] Test your workflows with the new API

1. Geography Specification Changes

Old API (v0.0.x)

The old API used simple crs, scale, and geometry parameters:

import ee
import xarray as xr

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    crs='EPSG:4326',
    scale=0.25,  # pixel size in degrees
    geometry=ee.Geometry.Rectangle([-180, -90, 180, 90])
)

New API (v0.1.0)

The new API requires explicit grid parameters: crs, crs_transform, and shape_2d. We provide helper functions to make this easy:

Option 2: Fit Geometry with Specific Scale

import ee
import xarray as xr
from xee import helpers
import shapely

# Define your area of interest using shapely
aoi = shapely.geometry.box(-180, -90, 180, 90)  # Global extent

grid_params = helpers.fit_geometry(
    geometry=aoi,
    grid_crs='EPSG:4326',
    grid_scale=(0.25, -0.25)  # (x_scale, y_scale) in degrees
)

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    **grid_params
)

Option 3: Fit Geometry with Specific Shape

import shapely
from xee import helpers

aoi = shapely.geometry.box(113.33, -43.63, 153.56, -10.66)  # Australia

grid_params = helpers.fit_geometry(
    geometry=aoi,
    grid_crs='EPSG:4326',
    grid_shape=(256, 256)  # (width, height) in pixels
)

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    **grid_params
)

Migration Examples

Example 1: Global dataset at fixed scale

Before (v0.1.0):

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    crs='EPSG:4326',
    scale=1.0,
    geometry=ee.Geometry.Rectangle([-180, -90, 180, 90])
)

After (v0.1.0):

import shapely
from xee import helpers

global_geom = shapely.geometry.box(-180, -90, 180, 90)
grid_params = helpers.fit_geometry(
    geometry=global_geom,
    grid_crs='EPSG:4326',
    grid_scale=(1.0, -1.0)  # Note: negative y-scale for north-up orientation
)

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    **grid_params
)

Example 2: Regional dataset with EE geometry

Before (v0.1.0):

import ee

aoi = ee.Geometry.Rectangle([-122.5, 37.0, -121.5, 38.0])
ds = xr.open_dataset(
    'LANDSAT/LC09/C02/T1_L2',
    engine='ee',
    crs='EPSG:32610',
    scale=30,
    geometry=aoi
)

After (v0.1.0):

import ee
import shapely
from xee import helpers

# Convert EE geometry to shapely (or create directly with shapely)
aoi = shapely.geometry.box(-122.5, 37.0, -121.5, 38.0)

grid_params = helpers.fit_geometry(
    geometry=aoi,
    geometry_crs='EPSG:4326',  # Input geometry CRS
    grid_crs='EPSG:32610',     # Output grid CRS (UTM Zone 10N)
    grid_scale=(30, -30)       # 30m resolution
)

ds = xr.open_dataset(
    'LANDSAT/LC09/C02/T1_L2',
    engine='ee',
    **grid_params
)

Example 3: Using source resolution for a custom area

Before (v0.1.0):

# You had to manually determine the scale from the dataset
ds = xr.open_dataset(
    collection,
    engine='ee',
    crs='EPSG:4326',
    scale=0.25,  # Manually determined
    geometry=my_region
)

After (v0.1.0):

from xee import helpers
import shapely

# 1. Extract source grid parameters
ic = ee.ImageCollection('ECMWF/ERA5_LAND/MONTHLY_AGGR')
source_params = helpers.extract_grid_params(ic)

# 2. Get the source scale
source_crs = source_params['crs']
source_transform = source_params['crs_transform']
source_scale = (source_transform[0], source_transform[4])

# 3. Apply to your custom region
my_region = shapely.geometry.box(-10, 35, 5, 50)  # Western Europe
grid_params = helpers.fit_geometry(
    geometry=my_region,
    geometry_crs='EPSG:4326',
    grid_crs=source_crs,
    grid_scale=source_scale
)

ds = xr.open_dataset(ic, engine='ee', **grid_params)

2. Dimension Ordering Changes

What Changed

Xee v0.1.0 outputs dimensions in [time, y, x] order (matching CF conventions and most geospatial tools), instead of the previous [time, x, y] order.

Impact on Your Code

Plotting

Before (v0.1.0):

# Required transpose for correct visualization
ds['temperature_2m'].isel(time=0).transpose().plot()

After (v0.1.0):

# No transpose needed - plots correctly by default
ds['temperature_2m'].isel(time=0).plot()

Integration with Other Libraries

Many geospatial libraries expect [time, y, x] ordering. You may have been using .transpose() to accommodate this.

Before (v0.1.0):

# Had to transpose for libraries expecting [time, y, x]
data_array = ds['temperature_2m'].transpose('time', 'y', 'x')
export_to_geotiff(data_array)

After (v0.1.0):

# Dimension order is already correct
data_array = ds['temperature_2m']
export_to_geotiff(data_array)

Explicit Dimension Access

If you have code that explicitly references dimension positions, update it:

Before (v0.1.0):

# Dimensions were [time, x, y]
time_dim, x_dim, y_dim = ds['temperature_2m'].dims
# or
width = ds['temperature_2m'].shape[1]   # x dimension
height = ds['temperature_2m'].shape[2]  # y dimension

After (v0.1.0):

# Dimensions are now [time, y, x]
time_dim, y_dim, x_dim = ds['temperature_2m'].dims
# or
height = ds['temperature_2m'].shape[1]  # y dimension
width = ds['temperature_2m'].shape[2]   # x dimension

Better approach (dimension-agnostic):

# This works in both versions
dims = ds['temperature_2m'].dims
width = ds.sizes['x']
height = ds.sizes['y']
time_length = ds.sizes['time']

3. Common Migration Patterns

Pattern 1: Simple global analysis

Before (v0.1.0):

import ee
import xarray as xr

ee.Initialize()
ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    crs='EPSG:4326',
    scale=1.0,
    geometry=ee.Geometry.Rectangle([-180, -90, 180, 90])
)
mean_temp = ds['temperature_2m'].mean(dim='time')
mean_temp.transpose().plot()

After (v0.1.0):

import ee
import xarray as xr
from xee import helpers
import shapely

ee.Initialize()
global_geom = shapely.geometry.box(-180, -90, 180, 90)
grid_params = helpers.fit_geometry(
    geometry=global_geom,
    grid_crs='EPSG:4326',
    grid_scale=(1.0, -1.0)
)

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    **grid_params
)
mean_temp = ds['temperature_2m'].mean(dim='time')
mean_temp.plot()  # No transpose needed

Pattern 2: Regional analysis with preprocessing

Before (v0.1.0):

import ee
import xarray as xr

def add_ndvi(image):
    ndvi = image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI')
    return image.addBands(ndvi)

aoi = ee.Geometry.Rectangle([-122.5, 37.5, -122.0, 38.0])
collection = (ee.ImageCollection('LANDSAT/LC09/C02/T1_L2')
    .filterDate('2024-01-01', '2024-12-31')
    .filterBounds(aoi)
    .map(add_ndvi)
    .select(['NDVI']))

ds = xr.open_dataset(
    collection,
    engine='ee',
    crs='EPSG:32610',
    scale=30,
    geometry=aoi
)

After (v0.1.0):

import ee
import xarray as xr
from xee import helpers
import shapely

def add_ndvi(image):
    ndvi = image.normalizedDifference(['SR_B5', 'SR_B4']).rename('NDVI')
    return image.addBands(ndvi)

# Use shapely for geometry
aoi_shapely = shapely.geometry.box(-122.5, 37.5, -122.0, 38.0)

# Create ee.Geometry for server-side filtering
coords = list(aoi_shapely.exterior.coords)
aoi_ee = ee.Geometry.Polygon(coords)

collection = (ee.ImageCollection('LANDSAT/LC09/C02/T1_L2')
    .filterDate('2024-01-01', '2024-12-31')
    .filterBounds(aoi_ee)
    .map(add_ndvi)
    .select(['NDVI']))

grid_params = helpers.fit_geometry(
    geometry=aoi_shapely,
    geometry_crs='EPSG:4326',
    grid_crs='EPSG:32610',
    grid_scale=(30, -30)
)

ds = xr.open_dataset(collection, engine='ee', **grid_params)

Pattern 3: Export workflows

Before (v0.1.0):

import xarray as xr

ds = xr.open_dataset(
    collection,
    engine='ee',
    crs='EPSG:4326',
    scale=0.1,
    geometry=region
)

# Transpose for proper export
data = ds['variable'].transpose('time', 'y', 'x')
data.to_netcdf('output.nc')

After (v0.1.0):

import xarray as xr
from xee import helpers

grid_params = helpers.fit_geometry(
    geometry=region,
    grid_crs='EPSG:4326',
    grid_scale=(0.1, -0.1)
)

ds = xr.open_dataset(collection, engine='ee', **grid_params)

# Already in correct dimension order
data = ds['variable']
data.to_netcdf('output.nc')

4. Understanding Grid Parameters

The Three Required Parameters

  1. crs: Coordinate Reference System (e.g., 'EPSG:4326', 'EPSG:32610')

  2. crs_transform: Affine transformation tuple (a, b, c, d, e, f) where:

    • a = pixel width (x-scale)

    • b = row rotation (typically 0)

    • c = x-coordinate of upper-left corner

    • d = column rotation (typically 0)

    • e = pixel height (y-scale, typically negative for north-up)

    • f = y-coordinate of upper-left corner

  3. shape_2d: Tuple of (width, height) in pixels

Helper Function Summary

Function

Use Case

Parameters

helpers.extract_grid_params(ee_obj)

Match the native grid of an EE Image/ImageCollection

EE object

helpers.fit_geometry(..., grid_scale=...)

Define grid by pixel size

geometry, CRS, scale

helpers.fit_geometry(..., grid_shape=...)

Define grid by pixel count

geometry, CRS, shape

Manual Grid Definition

For advanced use cases, you can still define grid parameters manually:

ds = xr.open_dataset(
    'ECMWF/ERA5_LAND/MONTHLY_AGGR',
    engine='ee',
    crs='EPSG:4326',
    crs_transform=(1.0, 0, -180.0, 0, -1.0, 90.0),
    shape_2d=(360, 180)
)

5. Troubleshooting

Issue: “Missing required parameter”

Error: TypeError: missing required argument: 'crs_transform'

Solution: You’re using the old API syntax. Update to use grid parameter helpers:

# Add these imports
from xee import helpers
import shapely

# Replace your old xr.open_dataset call with helper-based approach
grid_params = helpers.fit_geometry(
    geometry=your_geometry,
    grid_crs='EPSG:4326',
    grid_scale=(your_scale, -your_scale)
)
ds = xr.open_dataset(collection, engine='ee', **grid_params)

Issue: “Plots are rotated/flipped”

Problem: You’re still using .transpose() from v0.0.x code

Solution: Remove the .transpose() call - v0.1.0 outputs in the correct orientation by default

Issue: “Dimension order is wrong for my export”

Check: What order does your export library expect?

Most modern geospatial tools expect [time, y, x] (which v0.1.0 provides). If you have legacy code expecting [time, x, y], you can still transpose:

# Only if your downstream tool requires the old ordering
data = ds['variable'].transpose('time', 'x', 'y')

Issue: “I need the old behavior”

If you must maintain the old API temporarily, you can pin to v0.0.x:

pip install "xee<0.1.0"

However, we strongly recommend migrating to v0.1.0 for better CF compliance and ecosystem compatibility.

6. Testing Your Migration

After updating your code, verify it works correctly:

import ee
import xarray as xr
from xee import helpers
import shapely

# Initialize Earth Engine
ee.Initialize(project='YOUR-PROJECT')

# Test 1: Open a dataset
ic = ee.ImageCollection('ECMWF/ERA5_LAND/MONTHLY_AGGR').limit(5)
grid_params = helpers.extract_grid_params(ic)
ds = xr.open_dataset(ic, engine='ee', **grid_params)

# Test 2: Check dimensions
print("Dimensions:", ds['temperature_2m'].dims)
# Should print: ('time', 'y', 'x')

# Test 3: Plot without transpose
ds['temperature_2m'].isel(time=0).plot()

# Test 4: Verify CRS and transform
print("CRS:", grid_params['crs'])
print("Transform:", grid_params['crs_transform'])
print("Shape:", grid_params['shape_2d'])

7. Additional Resources

Need Help?

If you encounter issues during migration:

  1. Check this guide for common patterns

  2. Review the updated examples

  3. Open a GitHub Discussion

  4. File an issue with a reproducible example


Welcome to Xee v0.1.0! We believe these changes make the library more powerful, standards-compliant, and easier to integrate with the broader scientific Python ecosystem.