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

Improve x-limit, y-limit, and rasterize support for geo features #1135

Merged
merged 11 commits into from
Oct 3, 2023

Conversation

hoxbro
Copy link
Member

@hoxbro hoxbro commented Sep 14, 2023

Fixes #1093
Fixes #1090
Fixes https://discourse.holoviz.org/t/any-smart-approach-for-determining-what-holoviz-package-broke-my-notebook/5944

If geo is true, we will use GeoViews tile sources, and if false, we will use HoloViews. GeoViews features and tiles are projected to the respective projection.


1093 code:

import cartopy.crs as ccrs
import geoviews as gv
import hvplot.xarray  # noqa
import numpy as np
import xarray as xr


def sample_data(shape=(20, 30)):
    """
    Return ``(x, y, u, v, crs)`` of some vector data
    computed mathematically. The returned crs will be a rotated
    pole CRS, meaning that the vectors will be unevenly spaced in
    regular PlateCarree space.

    """
    crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5)

    x = np.linspace(311.9, 391.1, shape[1])
    y = np.linspace(-23.6, 24.8, shape[0])

    x2d, y2d = np.meshgrid(x, y)
    u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2)
    v = 20 * np.cos(6 * np.deg2rad(x2d))

    return x, y, u, v, crs


xs, ys, U, V, crs = sample_data()

mag = np.sqrt(U**2 + V**2)
angle = (np.pi / 2.0) - np.arctan2(U / mag, V / mag)

ds = xr.Dataset(
    {
        "mag": xr.DataArray(mag, dims=("y", "x"), coords={"y": ys, "x": xs}),
        "angle": xr.DataArray(angle, dims=("y", "x"), coords={"y": ys, "x": xs}),
    },
    attrs={"crs": crs},
)
ds.hvplot.vectorfield(
    x="x",
    y="y",
    angle="angle",
    mag="mag",
    hover=False,
    geo=True,
    tiles="CartoLight",
)

image

1090 code:

import hvplot.pandas
import pandas as pd

df = pd.DataFrame({"lon": [0, 10], "lat": [40, 50], "v": [0, 1]})

(
    df.hvplot.points(x="lon", y="lat", s=200, c="y", geo=True, tiles="CartoLight")
    * df.hvplot.points(x="lon", y="lat", c="v", geo=True)
)

image


Code from #1053 (Comments are from original PR; it seems that every case works)

import xarray as xr
import hvplot.xarray

dz = xr.tutorial.open_dataset('air_temperature')
dz.hvplot('lon', 'lat', coastline=True).opts(ylim=(20, 30), xlim=(-160, -140))

image

Case 1:

import hvplot.pandas  # noqa
import cartopy.crs as ccrs
import geoviews as gv
from bokeh.sampledata.airport_routes import airports

# Case 1: This example works
airports.hvplot.points(
    "Longitude",
    "Latitude",
    color="red",
    alpha=0.2,
    xlim=(-180, -30),
    ylim=(0, 72),
    geo=True,
    tiles="OSM",
)

image

# Case 2: xlim/ylim are not obeyed when the map tile is removed
airports.hvplot.points(
    "Longitude",
    "Latitude",
    geo=True,
    color="red",
    alpha=0.2,
    xlim=(-180, -30),
    ylim=(0, 72),
)

image

# Case 3: Setting ymin > 0 raises errors
# Change ymin from 0 to 10
airports.hvplot.points(
    "Longitude",
    "Latitude",
    geo=True,
    color="red",
    alpha=0.2,
    xlim=(-180, -30),
    ylim=(10, 72),
)

image

# Case 4: Setting ymax < 0 also raises errors
airports.hvplot.points(
    "Longitude",
    "Latitude",
    geo=True,
    color="red",
    alpha=0.2,
    xlim=(-180, -30),
    ylim=(-20, -5),
)

It works but gives a long empty plot, with one red dot.

# Works if geo=False
airports.hvplot.points(
    "Longitude",
    "Latitude",
    geo=False,
    color="red",
    alpha=0.2,
    xlim=(-180, -30),
    ylim=(10, 72),
)

image

@maximlt maximlt requested a review from ahuang11 October 2, 2023 08:23
@maximlt maximlt added this to the 0.9.0 milestone Oct 2, 2023
hvplot/converter.py Outdated Show resolved Hide resolved
@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

This looks good. I just wanted to ask whether this should this work (xlim/ylim in hvplot call), or out of scope in this PR?

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), rasterize=True, features=["coastline", "land"],
    xlim=(-140, -80), ylim=(20, 45)
)
image

This works:

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), rasterize=True, features=["coastline", "land"],
).opts(xlim=(-140, -80), ylim=(20, 45))
image

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Okay, confusingly sometimes xlim & ylim should be in kwargs, sometimes in opts.

This doesn't work (xlim, ylim in opts with tiles=True)

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), tiles=True
).opts(xlim=(-140, -80), ylim=(20, 45))
image

But this works:

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), tiles=True, xlim=(-140, -80), ylim=(20, 45)
)
image

hvplot/converter.py Outdated Show resolved Hide resolved
@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Not sure if this is out of scope of this PR, but this isn't correct: (with rasterize=True + projection)

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), features=["coastline", "land"],
    xlim=(-140, -80), ylim=(20, 45), projection=ccrs.LambertConformal(), rasterize=True
)
image

Dropping rasterize=True works:

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), features=["coastline", "land"],
    projection=ccrs.LambertConformal()
)
image

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

rasterize and datashade with geographic operations are out of scope for this PR. Though I will give it a try, but I won't spend too long on it.

At some point, I want it only to be needed to pass limits directly into hvplot like hvplot(..., xlim=(...) ylim=(...).

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

@ahuang11, can you try to play around with the latest commit?

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon",
    "lat",
    crs=ccrs.PlateCarree(),
    features=["coastline", "land"],
    xlim=(-140, -80),
    ylim=(20, 45),
    projection=ccrs.LambertConformal(),
    rasterize=True,
    dynspread=True,
)

image

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Thanks! Most of the things I tried worked. This still doesn't work:

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), projection=ccrs.GOOGLE_MERCATOR, tiles=True
).opts(xlim=(-140, -80), ylim=(20, 45))

Dropping tiles works.

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

But putting xlim and ylim inside the hvplot call works?

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Yes

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

I think this is more of a problem with GeoViews with projected overlays as this will also zoom in to small numbers:

import cartopy.crs as ccrs
import geoviews as gv

ds = xr.tutorial.open_dataset("air_temperature")
plot = gv.Points(ds.isel(time=0), kdims=["lon", "lat"]).opts(projection=ccrs.GOOGLE_MERCATOR)
(plot * plot).opts(xlim=(-140, -80), ylim=(20, 45))

image

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Right, I'm not sure how it gets handled without overlays, but xlim/ylim is not being projected properly in opts when there's an overlay.

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

My example above works fine if there is no overlaid: plot.opts(xlim=(-140, -80), ylim=(20, 45)).

I don't think we can do anything on the hvplot side of things.

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

It might be related to this:
holoviz/geoviews#340

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

One last comment is can we rename proj_crs to be more descriptive (like feature_projection)?

hvplot/converter.py Outdated Show resolved Hide resolved
@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

I don't mind a better name, but I'm not sure if it's correct to call it feature_projection. It is used for the features but also for other calculations like finding limits and the geo projection.

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

maybe output_projection?

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

So I'm using the latest released version of hvplot:

I wonder how image is handled under the hood (i.e. why does this work)

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.image(
    "lon", "lat", crs=ccrs.PlateCarree(), features=["coastline", "land"],
    projection=ccrs.LambertConformal(), rasterize=True
)
image

vs points (but not this?):

import hvplot.xarray
import xarray as xr
import cartopy.crs as ccrs

ds = xr.tutorial.open_dataset("air_temperature")
ds.hvplot.points(
    "lon", "lat", crs=ccrs.PlateCarree(), features=["coastline", "land"],
    projection=ccrs.LambertConformal(), rasterize=True
)
image

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

The latest release of hvplot has problems with xlimit and ylimit, which is what this PR is trying to fix.

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

All in all, I think this PR generally improves geo support!

Do you think a test should be added for rasterize + points?

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

Do you think a test should be added for rasterize + points?

Yes! But I didn't want to do it before we agreed this was the right approach.

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

The latest release of hvplot has problems with xlimit and ylimit, which is what this PR is trying to fix.

Right; I suppose I should have commented under obj = gv.project(obj, projection=self.proj_crs); my question is why does a rasterized image project properly but not rasterized points.

@hoxbro
Copy link
Member Author

hoxbro commented Oct 2, 2023

why does a rasterized image project properly but not rasterized points

I don't know. But if I move around the image on the latest release it disappears, which does not happen after this PR.

@ahuang11
Copy link
Collaborator

ahuang11 commented Oct 2, 2023

Okay; I think after tests are added, this can be merged.

@hoxbro hoxbro changed the title Improve x-limit and y-limit support for geo features Improve x-limit, y-limit, and rasterize support for geo features Oct 3, 2023
@hoxbro hoxbro merged commit bf34dd1 into main Oct 3, 2023
7 checks passed
@hoxbro hoxbro deleted the geo_lim_improvements branch October 3, 2023 06:19
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.

Vectorfield not overlaid properly on Tiles Tile + multiple Point breaks
3 participants