How to update the tile provider in Bokeh server plot without overlapping the plot?

I am plotting some points on a map with some tiles in the background. I am adding a select menu to chose from different tiles providers. When I change the provider the new tiles are displayed but on top of the points AND the former tiles which stay below.

How can I just replace the tiles by the new ones without piling up layers and keeping them below the points?

Example code below:

tiles = {‘OpenStreetMap c’: WMTSTileSource(url=‘http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png’),
‘ESRI’: WMTSTileSource(url=‘https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg’)}

#Setting up the figure
p = figure()
p.circle(‘x’, ‘y’, source=source)
p.add_tiles(tiles[‘ESRI’])

#select menu
tile_prov_select = Select(title=“Tile Provider”, value=‘NA’, options=[‘OpenStreetMap c’, ‘ESRI’])

#select menu action
def change_tiles_callback(attr, old, new):
p.add_tile(tiles[new])
tile_prov_select.on_change(‘value’, change_tiles_callback)

Thanks!

Adding a renderer to a Bokeh plot does not automatically remove any old ones. And renderers are drawn (within their render level) in the order they were added. So I’d say that what you see is exactly the expected behavior.

No matter what, you will need to remove the old TileRenderer. Bokeh stores renderers in plot.renderers so you would delete it from that list. Then, to get a new tile renderer to plot earlier you will want to explicitly put the renderer earlier/first the renderers list, instead of using add_tile. Something like:

tile_renderer = TileRenderer(tile_source=tile_source, ...)
plot.renderers.insert(0, tile_renderer)

Alternatively, you could keep using add_tile and try to adjust the .render_level of the tile renderer manually to something lower like "underlay", but then you might have issues with the grid lines showing up on top of the tiles.

Many thanks Bryan for your prompt reply, I worked exactly as expected!

Adding the piece of code for those interested:

def change_tiles_callback(attr, old, new):

#removing the renderer corresponding to the tile layer
plot.renderers = [x for x in plot.renderers if not str(x).startswith('TileRenderer')]
##inserting the new tile renderer
tile_renderer = TileRenderer(tile_source=tiles[new])
plot.renderers.insert(0, tile_renderer)
1 Like

Hi Johan_Monard,

I try to do it too; inside python notebook, but my callback don’t update my map on the generated html, could you adapt your first code for my understanding, please?

Thanks.

Hi tibo_m, here is my initial code corrected

tiles = {‘OpenStreetMap c’: WMTSTileSource(url=‘[http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png](http://c.tile.openstreetmap.org/%7BZ%7D/%7BX%7D/%7BY%7D.png)’),
         ‘ESRI’: WMTSTileSource(url=‘[https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg](https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/%7BZ%7D/%7BY%7D/%7BX%7D.jpg)’)}

#Setting up the figure
p = figure()
p.circle(‘x’, ‘y’, source=source)
p.add_tiles(tiles[‘ESRI’])

#select menu
tile_prov_select = Select(title=“Tile Provider”, value=‘NA’, options=[‘OpenStreetMap c’, ‘ESRI’])

#callback
def change_tiles_callback(attr, old, new):
    #removing the renderer corresponding to the tile layer
    p.renderers = [x for x in plot.renderers if not str(x).startswith('TileRenderer')]
    #inserting the new tile renderer
    tile_renderer = TileRenderer(tile_source=tiles[new])
    p.renderers.insert(0, tile_renderer)

#Assign callback to select menu
tile_prov_select.on_change(change_tiles_callback)

It will not work if you generate your html using show(), you need to use Bokeh server for the callback to work.

Apologies for reviving such an old thread but I thought my question would be most appropriate here as its related to switching tile_providers.

When I’m using the above method and switching the tiles, the attributions continue to stack on top of each other with every change. Screenclip below
image

I’ve also tried the handler below which I prefer which suffers the same issues

def tile_change_handler(new):
    p.renderers[0] = tiles[list(tiles.keys())[new]] 
#tiles is a dictionary with all the TileRenderers

It would appear that the attribution must be added to another part of the plot which I haven’t been able to locate. Any tips on to how I can clear that in my handler before the new one is loaded would be much appreciated.

@sida please open a new topic with a complete Minimal Reproducible Example to investigate.