holoviz / geoviews

Simple, concise geographical visualization in Python
http://geoviews.org
BSD 3-Clause "New" or "Revised" License
578 stars 75 forks source link

Issues with projection (from hvplot) #368

Open jsignell opened 4 years ago

jsignell commented 4 years ago

Try with all combinations of project, rasterize, and projections:

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

rasm = xr.tutorial.open_dataset('rasm').load()

method = rasm.mean(dim='time').hvplot.quadmesh
opts = dict(x='xc', y='yc', cmap='rainbow', coastline=True, geo=True)

pn.interact(lambda **kwargs: method(**kwargs, **opts), 
            rasterize=True, 
            project=True, 
            projection=[ccrs.GOOGLE_MERCATOR, ccrs.Orthographic(90, 30)])

When rasterize=True and project=False there are issues with striping (maybe project should be set to true by default when rasterizing?) similar to #362

Screen Shot 2019-10-16 at 1 33 08 PM

When rasterize=False and project=True hover shows NaNs

Screen Shot 2019-10-16 at 1 35 10 PM

Also this is likely a bokeh issue, but for all cases when zooming out, the scroll zoom stops working when a full nan slice is encountered along one edge of frame:

Screen Shot 2019-10-16 at 12 06 24 PM

When using the orthographic projection the lat/lon are never right on hover. Also there are some failures. For instance when using rasterize=True and project=True:

```python-traceback --------------------------------------------------------------------------- ValueError Traceback (most recent call last) ~/hvplot/envs/dev/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude) 968 969 if method is not None: --> 970 return method(include=include, exclude=exclude) 971 return None 972 else: ~/holoviews/holoviews/core/dimension.py in _repr_mimebundle_(self, include, exclude) 1292 combined and returned. 1293 """ -> 1294 return Store.render(self) 1295 1296 ~/holoviews/holoviews/core/options.py in render(cls, obj) 1366 data, metadata = {}, {} 1367 for hook in hooks: -> 1368 ret = hook(obj) 1369 if ret is None: 1370 continue ~/holoviews/holoviews/ipython/display_hooks.py in pprint_display(obj) 279 if not ip.display_formatter.formatters['text/plain'].pprint: 280 return None --> 281 return display(obj, raw_output=True) 282 283 ~/holoviews/holoviews/ipython/display_hooks.py in display(obj, raw_output, **kwargs) 255 elif isinstance(obj, (HoloMap, DynamicMap)): 256 with option_state(obj): --> 257 output = map_display(obj) 258 elif isinstance(obj, Plot): 259 output = render(obj) ~/holoviews/holoviews/ipython/display_hooks.py in wrapped(element) 144 try: 145 max_frames = OutputSettings.options['max_frames'] --> 146 mimebundle = fn(element, max_frames=max_frames) 147 if mimebundle is None: 148 return {}, {} ~/holoviews/holoviews/ipython/display_hooks.py in map_display(vmap, max_frames) 204 return None 205 --> 206 return render(vmap) 207 208 ~/holoviews/holoviews/ipython/display_hooks.py in render(obj, **kwargs) 66 renderer = renderer.instance(fig='png') 67 ---> 68 return renderer.components(obj, **kwargs) 69 70 ~/holoviews/holoviews/plotting/bokeh/renderer.py in components(self, obj, fmt, comm, **kwargs) 248 # Bokeh has to handle comms directly in <0.12.15 249 comm = False if bokeh_version < '0.12.15' else comm --> 250 return super(BokehRenderer, self).components(obj,fmt, comm, **kwargs) 251 252 ~/holoviews/holoviews/plotting/renderer.py in components(self, obj, fmt, comm, **kwargs) 319 plot = obj 320 else: --> 321 plot, fmt = self._validate(obj, fmt) 322 323 data, metadata = {}, {} ~/holoviews/holoviews/plotting/renderer.py in _validate(self, obj, fmt, **kwargs) 218 if isinstance(obj, tuple(self.widgets.values())): 219 return obj, 'html' --> 220 plot = self.get_plot(obj, renderer=self, **kwargs) 221 222 fig_formats = self.mode_formats['fig'][self.mode] ~/holoviews/holoviews/plotting/bokeh/renderer.py in get_plot(self_or_cls, obj, doc, renderer, **kwargs) 133 curdoc().theme = self_or_cls.theme 134 doc.theme = self_or_cls.theme --> 135 plot = super(BokehRenderer, self_or_cls).get_plot(obj, renderer, **kwargs) 136 plot.document = doc 137 return plot ~/holoviews/holoviews/plotting/renderer.py in get_plot(self_or_cls, obj, renderer, **kwargs) 184 185 # Initialize DynamicMaps with first data item --> 186 initialize_dynamic(obj) 187 188 if not isinstance(obj, Plot): ~/holoviews/holoviews/plotting/util.py in initialize_dynamic(obj) 248 continue 249 if not len(dmap): --> 250 dmap[dmap._initial_key()] 251 252 ~/holoviews/holoviews/core/spaces.py in __getitem__(self, key) 1325 # Not a cross product and nothing cached so compute element. 1326 if cache is not None: return cache -> 1327 val = self._execute_callback(*tuple_key) 1328 if data_slice: 1329 val = self._dataslice(val, data_slice) ~/holoviews/holoviews/core/spaces.py in _execute_callback(self, *args) 1098 1099 with dynamicmap_memoization(self.callback, self.streams): -> 1100 retval = self.callback(*args, **kwargs) 1101 return self._style(retval) 1102 ~/holoviews/holoviews/core/spaces.py in __call__(self, *args, **kwargs) 701 kwarg_hash = kwargs.pop('_memoization_hash_', ()) 702 (self.args, self.kwargs) = (args, kwargs) --> 703 if not args and not kwargs and not any(kwarg_hash): return self.callable() 704 inputs = [i for i in self.inputs if isinstance(i, DynamicMap)] 705 streams = [] ~/holoviews/holoviews/util/__init__.py in dynamic_operation(*key, **kwargs) 943 if map_obj._posarg_keys and not key: 944 key = tuple(kwargs[k] for k in map_obj._posarg_keys) --> 945 return self._process(map_obj[key], key, kwargs) 946 if isinstance(self.p.operation, Operation): 947 return OperationCallable(dynamic_operation, inputs=[map_obj], ~/holoviews/holoviews/core/spaces.py in __getitem__(self, key) 1325 # Not a cross product and nothing cached so compute element. 1326 if cache is not None: return cache -> 1327 val = self._execute_callback(*tuple_key) 1328 if data_slice: 1329 val = self._dataslice(val, data_slice) ~/holoviews/holoviews/core/spaces.py in _execute_callback(self, *args) 1098 1099 with dynamicmap_memoization(self.callback, self.streams): -> 1100 retval = self.callback(*args, **kwargs) 1101 return self._style(retval) 1102 ~/holoviews/holoviews/core/spaces.py in __call__(self, *args, **kwargs) 701 kwarg_hash = kwargs.pop('_memoization_hash_', ()) 702 (self.args, self.kwargs) = (args, kwargs) --> 703 if not args and not kwargs and not any(kwarg_hash): return self.callable() 704 inputs = [i for i in self.inputs if isinstance(i, DynamicMap)] 705 streams = [] ~/holoviews/holoviews/core/spaces.py in dynamic_mul(*args, **kwargs) 290 if isinstance(self, DynamicMap): 291 def dynamic_mul(*args, **kwargs): --> 292 element = self[args] 293 if reverse: 294 return other * element ~/holoviews/holoviews/core/spaces.py in __getitem__(self, key) 1325 # Not a cross product and nothing cached so compute element. 1326 if cache is not None: return cache -> 1327 val = self._execute_callback(*tuple_key) 1328 if data_slice: 1329 val = self._dataslice(val, data_slice) ~/holoviews/holoviews/core/spaces.py in _execute_callback(self, *args) 1098 1099 with dynamicmap_memoization(self.callback, self.streams): -> 1100 retval = self.callback(*args, **kwargs) 1101 return self._style(retval) 1102 ~/holoviews/holoviews/core/spaces.py in __call__(self, *args, **kwargs) 732 733 try: --> 734 ret = self.callable(*args, **kwargs) 735 except KeyError: 736 # KeyError is caught separately because it is used to signal ~/holoviews/holoviews/util/__init__.py in dynamic_operation(*key, **kwargs) 937 kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs) 938 obj = map_obj[key] if isinstance(map_obj, HoloMap) else map_obj --> 939 return self._process(obj, key, kwargs) 940 else: 941 def dynamic_operation(*key, **kwargs): ~/holoviews/holoviews/util/__init__.py in _process(self, element, key, kwargs) 924 elif isinstance(self.p.operation, Operation): 925 kwargs = {k: v for k, v in kwargs.items() if k in self.p.operation.param} --> 926 return self.p.operation.process_element(element, key, **kwargs) 927 else: 928 return self.p.operation(element, **kwargs) ~/holoviews/holoviews/core/operation.py in process_element(self, element, key, **params) 141 """ 142 self.p = param.ParamOverrides(self, params) --> 143 return self._apply(element, key) 144 145 ~/holoviews/holoviews/core/operation.py in _apply(self, element, key) 119 for hook in self._preprocess_hooks: 120 kwargs.update(hook(self, element)) --> 121 ret = self._process(element, key) 122 for hook in self._postprocess_hooks: 123 ret = hook(self, ret, **kwargs) ~/holoviews/holoviews/operation/datashader.py in _process(self, element, key) 946 op = transform.instance(**op_params) 947 op._precomputed = self._precomputed --> 948 element = element.map(op, predicate) 949 self._precomputed = op._precomputed 950 return element ~/holoviews/holoviews/core/dimension.py in map(self, map_fn, specs, clone) 702 return deep_mapped 703 else: --> 704 return map_fn(self) if applies else self 705 706 ~/holoviews/holoviews/core/operation.py in __call__(self, element, **kwargs) 159 for k, el in element.items()]) 160 elif isinstance(element, ViewableElement): --> 161 return self._apply(element) 162 elif 'streams' not in kwargs: 163 kwargs['streams'] = self.p.streams ~/holoviews/holoviews/core/operation.py in _apply(self, element, key) 119 for hook in self._preprocess_hooks: 120 kwargs.update(hook(self, element)) --> 121 ret = self._process(element, key) 122 for hook in self._postprocess_hooks: 123 ret = hook(self, ret, **kwargs) ~/holoviews/holoviews/operation/datashader.py in _process(self, element, key) 889 agg[ydim] = (agg[ydim]/1e3).astype('datetime64[us]') 890 --> 891 return Image(agg, **params) 892 893 ~/holoviews/holoviews/element/raster.py in __init__(self, data, kdims, vdims, bounds, extents, xdensity, ydensity, rtol, **params) 324 'x- or y-axis ensure you declare the bounds and/or ' 325 'density.') --> 326 SheetCoordinateSystem.__init__(self, bounds, xdensity, ydensity) 327 self._validate(data_bounds, supplied_bounds) 328 ~/holoviews/holoviews/core/sheetcoords.py in __init__(self, bounds, xdensity, ydensity) 156 """ 157 if not ydensity: --> 158 bounds,xdensity = self.__equalize_densities(bounds,xdensity) 159 160 self.bounds = bounds ~/holoviews/holoviews/core/sheetcoords.py in __equalize_densities(self, nominal_bounds, nominal_density) 190 # True density is not equal to the nominal_density when 191 # nominal_density*(right-left) is not an integer. --> 192 true_density = int(nominal_density*(width))/float(width) 193 194 n_cells = round(height*true_density,0) ValueError: cannot convert float NaN to integer ```

When rasterize=False and project=True we get a similar error, although is makes it all the way to bokeh.

```python-traceback --------------------------------------------------------------------------- ValueError Traceback (most recent call last) ~/hvplot/envs/dev/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude) 968 969 if method is not None: --> 970 return method(include=include, exclude=exclude) 971 return None 972 else: ~/holoviews/holoviews/core/dimension.py in _repr_mimebundle_(self, include, exclude) 1292 combined and returned. 1293 """ -> 1294 return Store.render(self) 1295 1296 ~/holoviews/holoviews/core/options.py in render(cls, obj) 1366 data, metadata = {}, {} 1367 for hook in hooks: -> 1368 ret = hook(obj) 1369 if ret is None: 1370 continue ~/holoviews/holoviews/ipython/display_hooks.py in pprint_display(obj) 279 if not ip.display_formatter.formatters['text/plain'].pprint: 280 return None --> 281 return display(obj, raw_output=True) 282 283 ~/holoviews/holoviews/ipython/display_hooks.py in display(obj, raw_output, **kwargs) 249 elif isinstance(obj, (CompositeOverlay, ViewableElement)): 250 with option_state(obj): --> 251 output = element_display(obj) 252 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)): 253 with option_state(obj): ~/holoviews/holoviews/ipython/display_hooks.py in wrapped(element) 144 try: 145 max_frames = OutputSettings.options['max_frames'] --> 146 mimebundle = fn(element, max_frames=max_frames) 147 if mimebundle is None: 148 return {}, {} ~/holoviews/holoviews/ipython/display_hooks.py in element_display(element, max_frames) 190 return None 191 --> 192 return render(element) 193 194 ~/holoviews/holoviews/ipython/display_hooks.py in render(obj, **kwargs) 66 renderer = renderer.instance(fig='png') 67 ---> 68 return renderer.components(obj, **kwargs) 69 70 ~/holoviews/holoviews/plotting/bokeh/renderer.py in components(self, obj, fmt, comm, **kwargs) 248 # Bokeh has to handle comms directly in <0.12.15 249 comm = False if bokeh_version < '0.12.15' else comm --> 250 return super(BokehRenderer, self).components(obj,fmt, comm, **kwargs) 251 252 ~/holoviews/holoviews/plotting/renderer.py in components(self, obj, fmt, comm, **kwargs) 319 plot = obj 320 else: --> 321 plot, fmt = self._validate(obj, fmt) 322 323 data, metadata = {}, {} ~/holoviews/holoviews/plotting/renderer.py in _validate(self, obj, fmt, **kwargs) 218 if isinstance(obj, tuple(self.widgets.values())): 219 return obj, 'html' --> 220 plot = self.get_plot(obj, renderer=self, **kwargs) 221 222 fig_formats = self.mode_formats['fig'][self.mode] ~/holoviews/holoviews/plotting/bokeh/renderer.py in get_plot(self_or_cls, obj, doc, renderer, **kwargs) 133 curdoc().theme = self_or_cls.theme 134 doc.theme = self_or_cls.theme --> 135 plot = super(BokehRenderer, self_or_cls).get_plot(obj, renderer, **kwargs) 136 plot.document = doc 137 return plot ~/holoviews/holoviews/plotting/renderer.py in get_plot(self_or_cls, obj, renderer, **kwargs) 205 init_key = tuple(v if d is None else d for v, d in 206 zip(plot.keys[0], defaults)) --> 207 plot.update(init_key) 208 else: 209 plot = obj ~/holoviews/holoviews/plotting/plot.py in update(self, key) 612 def update(self, key): 613 if len(self) == 1 and ((key == 0) or (key == self.keys[0])) and not self.drawn: --> 614 return self.initialize_plot() 615 item = self.__getitem__(key) 616 self.traverse(lambda x: setattr(x, '_updated', True)) ~/geoviews/geoviews/plotting/bokeh/plot.py in initialize_plot(self, ranges, plot, plots, source) 110 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): 111 opts = {} if isinstance(self, HvOverlayPlot) else {'source': source} --> 112 fig = super(GeoPlot, self).initialize_plot(ranges, plot, plots, **opts) 113 if self.geographic and self.show_bounds and not self.overlaid: 114 from . import GeoShapePlot ~/holoviews/holoviews/plotting/bokeh/element.py in initialize_plot(self, ranges, plot, plots) 2102 if plot and not self.overlaid: 2103 self._update_plot(key, plot, element) -> 2104 self._update_ranges(element, ranges) 2105 2106 panels = [] ~/geoviews/geoviews/plotting/bokeh/plot.py in _update_ranges(self, element, ranges) 89 90 def _update_ranges(self, element, ranges): ---> 91 super(GeoPlot, self)._update_ranges(element, ranges) 92 if not self.geographic: 93 return ~/holoviews/holoviews/plotting/bokeh/element.py in _update_ranges(self, element, ranges) 817 if fixed_height: 818 plot.frame_height = height --> 819 plot.frame_width = int(height/aspect) 820 plot.plot_width, plot.plot_height = None, None 821 elif fixed_width: ValueError: cannot convert float NaN to integer ```
philippjfr commented 4 years ago

Also this is likely a bokeh issue, but for all cases when zooming out, the scroll zoom stops working when a full nan slice is encountered along one edge of frame:

I noticed this recently, quite worrying. A regression in bokeh?

When rasterize=False and project=True hover shows NaNs

That's odd.

When using the orthographic projection the lat/lon are never right on hover.

This is known and can't be fixed easily without adding a considerably complex Javascript dependency.