holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.7k stars 402 forks source link

Datashader receives wrong range infromation from geoviews axis when viewing a cross section #6350

Open wx4stg opened 3 months ago

wx4stg commented 3 months ago

versions

I have a geoviews axis (lat and lon) of points that have multiple attributes attached to them, one of them is altitude. I want to have a cross section of longitude vs altitude above the plan axis. This works well.

However, when I enable datashader, the x-range of the plot does not update when I zoom in, so datashader does not return the correct pixel size of the rasterized images.

Here's a live example of what I'm talking about... https://colab.research.google.com/drive/1sgYc2Y9HyEtMmggxMrRakzLw6Og53isv?usp=sharing

Code to reproduce [lma.parquet](https://wx4stg.com/public/lma.parquet) ```python3 import pandas as pd import holoviews as hv import holoviews.operation.datashader import datashader import panel as pn import geoviews as gv hv.extension('bokeh') gv.extension('bokeh') ds = pd.read_parquet('lma.parquet') easting3857, northing3857 = hv.util.transform.lon_lat_to_easting_northing(ds.lon, ds.lat) data_plan = (easting3857, northing3857, ds.alt, ds.power) kdims_plan = ['Longitude', 'Latitude'] vdims_plan = ['Altitude', 'Power'] data_cross = (easting3857, ds.alt, ds.lat, ds.power) kdims_cross = ['Longitude', 'Altitude'] vdims_cross = ['Latitude', 'Power'] plan_points = hv.Points(data_plan, kdims=kdims_plan, vdims=vdims_plan) plan_shaded = hv.operation.datashader.datashade(plan_points, aggregator=datashader.max('Power'), cmap='plasma').opts( width=300, height=300) cross_points = hv.Points(data_cross, kdims=kdims_cross, vdims=vdims_cross) cross_shaded = hv.operation.datashader.datashade(cross_points, aggregator=datashader.max('Power'), cmap='plasma').opts( width=300, height=100, ylim=(0, 20000)) pn.Column(cross_shaded, plan_shaded * gv.tile_sources.OSM()) ```

Screenshots or screencasts of the bug in action

Screencast from 2024-07-11 13-23-16.webm

ahuang11 commented 3 months ago

Could be related to https://github.com/holoviz/geoviews/issues/726

ahuang11 commented 3 months ago

To reproduce easier:

import requests
from io import BytesIO

import pandas as pd

import holoviews as hv
import holoviews.operation.datashader
import datashader
import panel as pn

import geoviews as gv

hv.extension('bokeh', logo=False)
gv.extension('bokeh', logo=False)
data = BytesIO(requests.get('http://wx4stg.com/public/lma.parquet').content)
ds = pd.read_parquet(data)
easting3857, northing3857 = hv.util.transform.lon_lat_to_easting_northing(ds.lon, ds.lat)

data_plan = (easting3857, northing3857, ds.alt, ds.power)
kdims_plan = ['Longitude', 'Latitude']
vdims_plan = ['Altitude', 'Power']

data_cross = (easting3857, ds.alt, ds.lat, ds.power)
kdims_cross = ['Longitude', 'Altitude']
vdims_cross = ['Latitude', 'Power']

plan_points = hv.Points(data_plan, kdims=kdims_plan, vdims=vdims_plan)
plan_shaded = hv.operation.datashader.datashade(plan_points, aggregator=datashader.max('Power'), cmap='plasma').opts(
    width=300, height=300)
cross_points = hv.Points(data_cross, kdims=kdims_cross, vdims=vdims_cross)
cross_shaded = hv.operation.datashader.datashade(cross_points, aggregator=datashader.max('Power'), cmap='plasma').opts(
    width=300, height=100, ylim=(0, 20000))
pn.Column(cross_shaded, plan_shaded * gv.tile_sources.OSM())
ahuang11 commented 3 months ago

I think to get around this, you can use geoviews for your map, and manually project for cross section; I use hvplot here because I was testing if I could get around it with hvplot, but it didn't work initially.

import requests
from io import BytesIO

import pandas as pd

import holoviews as hv
import geoviews as gv
import datashader
import hvplot.pandas
import cartopy.crs as ccrs
from holoviews.util.transform import lon_lat_to_easting_northing

hv.extension("bokeh", logo=False)
gv.extension("bokeh", logo=False)
data = BytesIO(requests.get("http://wx4stg.com/public/lma.parquet").content)
ds = pd.read_parquet(data)
x, y = lon_lat_to_easting_northing(ds["lon"], ds["lat"])
ds["x"] = x

(
    ds.hvplot(
        x="x",  # easting3857
        y="alt",
        kind="scatter",
        c="power",
        cmap="plasma",
        datashade=True,
        ylim=(0, 20000),
        frame_width=580,
        xaxis=False,
    ).redim.label(x="Longitude")   # match xdim of map
    + ds.hvplot(
        x="lon",
        y="lat",
        kind="points",
        c="power",
        cmap="plasma",
        datashade=True,
        tiles=True,
        crs=ccrs.PlateCarree(),
        projection=ccrs.GOOGLE_MERCATOR,
        frame_width=600,
    )
).cols(1)
image
ahuang11 commented 3 months ago

Also, this issue probably is more holoviews/datashader related than geoviews since your example doesn't use geoviews at all.

Edit: actually it does, but you can call tiles from HoloViews:

hv.element.tiles.OSM()
hoxbro commented 3 months ago

Are you sure this is a HoloViews problem? Holoviews tiles are different from Geoviews tiles. Mainly, IIRC, no transformation is done with HoloViews tiles.

ahuang11 commented 3 months ago

I mean it works with geoviews, but stripping out all the calls to geoviews, using vanilla HoloViews, the problem arises (demoed in the video)

import pandas as pd

import holoviews as hv
import holoviews.operation.datashader
import datashader
import panel as pn

hv.extension('bokeh')

data = BytesIO(requests.get("http://wx4stg.com/public/lma.parquet").content)
ds = pd.read_parquet(data)
easting3857, northing3857 = hv.util.transform.lon_lat_to_easting_northing(ds.lon, ds.lat)

data_plan = (easting3857, northing3857, ds.alt, ds.power)
kdims_plan = ['Longitude', 'Latitude']
vdims_plan = ['Altitude', 'Power']

data_cross = (easting3857, ds.alt, ds.lat, ds.power)
kdims_cross = ['Longitude', 'Altitude']
vdims_cross = ['Latitude', 'Power']

plan_points = hv.Points(data_plan, kdims=kdims_plan, vdims=vdims_plan)
plan_shaded = hv.operation.datashader.datashade(plan_points, aggregator=datashader.max('Power'), cmap='plasma').opts(
    width=300, height=300)
cross_points = hv.Points(data_cross, kdims=kdims_cross, vdims=vdims_cross)
cross_shaded = hv.operation.datashader.datashade(cross_points, aggregator=datashader.max('Power'), cmap='plasma').opts(
    width=300, height=100, ylim=(0, 20000))
pn.Column(cross_shaded, plan_shaded * hv.element.tiles.OSM())