I have a Panel dashboard that looks like this:
The user selects one or more file inputs, sets some fields via param.Selector()
widgets, and draws boxes on the map via hv.streams.BoxEdit
. When the user pushes the ‘Run’ button, the input data is queried for points that intersect the drawn boxes and results are streamed back to the map via hv.streams.Buffer
and a corresponding hv.DynamicMap(hv.Points, streams=[dfstream])
. This all works very well.
The map in my dashboard is controlled by an instance of this class:
class Mapview(param.Parameterized):
opts = dict(width=1200,height=750,xaxis=None,yaxis=None,show_grid=False)
tiles = gv.tile_sources.CartoEco().apply.opts(**opts)
extents = param.Parameter(default=(-168, -60, 168, 83), precedence=-1)
tiles.extents = extents.default
box_polygons = gv.Polygons([]).opts(fill_alpha=0.1)
box_colours = ['red','blue','green','orange','purple']
box_stream = hv.streams.BoxEdit(source=box_polygons, num_objects=5, styles={'fill_color': box_colours})
template_df = pd.DataFrame({'x_meters': [], 'y_meters': []}, columns=['x_meters', 'y_meters'])
dfstream = hv.streams.Buffer(template_df, index=False, length=10000)
points = hv.DynamicMap(hv.Points, streams=[dfstream]).opts(
size=5, color='green', fill_alpha=0.3, line_alpha=0.4)
def show_map(self):
return self.tiles * self.box_polygons * self.points
Because my dashboard is initialized with empty hv.Points
and gv.Polygons
objects (there are no points on the map & no boxes have been drawn but the placeholder objects have been created empty), Geoviews auto-zooms the map to these empty datasets and the map appears zoomed in on 0,0. To solve this problem I set the .extents
property on my gv.tile_source
to zoom to a global extent. The problem is that when the query results are streamed back to the hv.streams.Buffer
the map is refreshed and is reset back to the extents I defined above.
I either want the map to stay zoomed where it is (i.e. where the user put it) or I want to be able to control the map zoom programmatically but I haven’t been able to accomplish either. For the latter I tried adding a new class param.Parameter e
and modifying my Mapview.show_map()
method like this:
Note: I don’t think I should have to set watch=True but without it the function is not triggered when e
is set as shown below.
@param.depends('e',watch=True)
def show_map(self):
print('show_map() has been called.')
try:
if self.e[0] is None:
print('Using global extent')
return (self.tiles * self.box_polygons * self.points).options(xlim=(-20000000,20000000),ylim=(-6000000,6000000))
else:
print(f'Using new extents: {self.e[0]},{self.e[2]},{self.e[1]},{self.e[3]}')
return (self.tiles * self.box_polygons * self.points).options(xlim=(self.e[0],self.e[2]),ylim=(self.e[1],self.e[3]))
except:
print('Problem in show_map()')
and then setting e
like this after the user pushes the run button.
boxes = self.mapview.box_stream.data
xmin = min(boxes['x0'])
xmax = max(boxes['x1'])
ymin = min(boxes['y0'])
ymax = max(boxes['y1'])
new_extent = box(xmin,ymin,xmax,ymax)
self.mapview.e = new_extent.bounds
But it doesn’t zoom to the desired extent and also skews (reprojects?) the map to look like this:
How can I prevent the map from zooming to the initial extent after data is sent to the hv.streams.Buffer
? Or, how can I get the map to zoom where I want it to and not skew? I’d be happy to provide the full Jupyter Notebook code but didn’t want to pollute this post by making it even longer.