Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

load_tile_map: Add the new parameter 'crs' to set the CRS of the returned dataarray #3554

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

seisman
Copy link
Member

@seisman seisman commented Oct 25, 2024

Description of proposed changes

This PR adds the crs parameter to the load_tile_map function so that users can change the CRS of the returned raster image without needing to know the details.

Address #2125 (comment).
Closes #3484.

Notes for maintainers:

As far as I know, there are multiple different ways to reproject raster images.

  1. contextily.warp_tiles: Internally calls rasterio.vrt.WarpedVRT
  2. rioxarray's rio.reproject: Internally calls rasterio.warp.reproject
  3. rasterio.vrt.WarpedVRT
  4. rasterio.warp.reproject
  5. gdal.Warp
  6. ...

Options 3-5 are complicated and I don't think we want to call them directly. Ideally, we should use option 1, so reprojection doesn't rely on an extra dependency rioxarray. However, as shown below, the result from contextily.warp_tiles doesn't work well with rio.to_raster.

Here are codes to compare the results with options 1 and 2:

import xarray as xr
import rioxarray
import contextily as cx
import numpy as np

def image2raster(image, extent, crs="EPSG:3857"):
    """
    Convert an image to a xarray.DataArray raster.

    Copied from pygmt.datasets.tile_map.load_tile_map, except that crs is an input parameter.
    """
    # Turn RGBA img from channel-last to channel-first and get 3-band RGB only
    _image = image.transpose(2, 0, 1)  # Change image from (H, W, C) to (C, H, W)
    rgb_image = _image[0:3, :, :]  # Get just RGB by dropping RGBA's alpha channel
    
    # Georeference RGB image into an xarray.DataArray
    left, right, bottom, top = extent
    dataarray = xr.DataArray(
        data=rgb_image,
        coords={
            "band": np.array(object=[1, 2, 3], dtype=np.uint8),  # Red, Green, Blue
            "y": np.linspace(start=top, stop=bottom, num=rgb_image.shape[1]),
            "x": np.linspace(start=left, stop=right, num=rgb_image.shape[2]),
        },
        dims=("band", "y", "x"),
    )
    dataarray = dataarray.rio.write_crs(input_crs=crs)
    return dataarray

# Image and extent of the global tile
image, extent = cx.bounds2img(w=-180, e=180, s=-90, n=90, ll=True, zoom=0)

# Option 1: Use contextily.warp_tiles
image_warp, extent_warp = cx.warp_tiles(image, extent, "OGC:CRS84")
dataarray_warp = image2raster(image_warp, extent_warp, crs="OGC:CRS84")
print("Using contextily.warp_tiles:", dataarray_warp.rio.bounds())
dataarray_warp.rio.to_raster("result_warp.tiff")

# Option 2: Use rio.reproject
dataarray = image2raster(image, extent, crs="EPSG:3857")
dataarray_reproject = dataarray.rio.reproject("OGC:CRS84")
print("Using rio.reproject:", dataarray_reproject.rio.bounds())
dataarray_reproject.rio.to_raster("result_reproject.tiff")

The outputs are:

Using contextily.warp_tiles: (-180.55157789333614, -85.9082903958547, 180.18036434850873, 85.66487761291295)
Using rio.reproject: (-180.0, -84.8307625666717, 180.00000000000006, 85.11165075221737)

As you can see, the longitude range is (-180.55157789333614, 180.18036434850873) with contextily.warp_tiles, which will lead to GMT errors like:

grdimage [ERROR]: Map region exceeds 360 degrees
grdimage [ERROR]: General map projection error

Two more notes:

Preview: https://pygmt-dev--3554.org.readthedocs.build/en/3554/api/generated/pygmt.datasets.load_tile_map.html

Reminders

  • Run make format and make check to make sure the code follows the style guide.
  • Add tests for new features or tests that would have caught the bug that you're fixing.
  • Add new public functions/methods/classes to doc/api/index.rst.
  • Write detailed docstrings for all functions/methods.
  • If wrapping a new module, open a 'Wrap new GMT module' issue and submit reasonably-sized PRs.
  • If adding new functionality, add an example to docstrings or tutorials.
  • Use underscores (not hyphens) in names of Python files and directories.

Slash Commands

You can write slash commands (/command) in the first line of a comment to perform
specific operations. Supported slash command is:

  • /format: automatically format and lint the code

@seisman seisman self-assigned this Oct 30, 2024
@seisman seisman force-pushed the load_tile_map/crs branch 3 times, most recently from c34a56f to d79af32 Compare November 27, 2024 06:01
@seisman seisman force-pushed the load_tile_map/crs branch 2 times, most recently from 259b0dd to a48f9df Compare November 27, 2024 06:26
@seisman seisman changed the title WIP: load_tile_map: Add the new parameter 'crs' to set the CRS of the returned dataarray load_tile_map: Add the new parameter 'crs' to set the CRS of the returned dataarray Nov 27, 2024
@seisman seisman marked this pull request as ready for review November 27, 2024 06:52
"""
kwargs = self._preprocess(**kwargs)

if not _HAS_RIOXARRAY:
Copy link
Member Author

Choose a reason for hiding this comment

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

The Figure.tilemap method no longer requires rioxarray explicitly. Instead, load_tile_map will raise the error.

raster = load_tile_map(
region=region,
zoom=zoom,
source=source,
lonlat=lonlat,
crs="OGC:CRS84" if lonlat is True else "EPSG:3857",
Copy link
Member Author

Choose a reason for hiding this comment

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

If lonlat=True, set the CRS to let load_tile_map do the raster reprojection for us.

@seisman seisman added enhancement Improving an existing feature needs review This PR has higher priority and needs review. labels Nov 27, 2024
@seisman seisman added this to the 0.14.0 milestone Nov 27, 2024
from collections.abc import Sequence
from typing import Literal

from packaging.version import Version

try:
import contextily
from rasterio.crs import CRS
Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -176,8 +196,12 @@ def load_tile_map(
dims=("band", "y", "x"),
)

# If rioxarray is installed, set the coordinate reference system
# If rioxarray is installed, set the coordinate reference system.
Copy link
Member Author

Choose a reason for hiding this comment

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

These lines are also covered.

@seisman seisman removed the needs review This PR has higher priority and needs review. label Nov 27, 2024
@seisman seisman marked this pull request as draft November 27, 2024 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Improving an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

load_map_tiles: Allow a new parameter to control the projection of the returned DataArray
1 participant