Custom plots over a Map

from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, HoverTool, Legend
from bokeh.palettes import Spectral4
from bokeh.transform import cumsum
import numpy as np
import pandas as pd
from math import pi

# Convert to Web Mercator coordinates
def convert_to_mercator(lon, lat):
    """Convert longitude/latitude to Web Mercator coordinates"""
    k = 6378137
    x = lon * (k * pi/180.0)
    y = np.log(np.tan((90 + lat) * pi/360.0)) * k
    return x, y

# Define data for regions
regions = {
    "Europe": {
        "coords": (48.8566, 15.3522),
        "distribution": {"North America": 95, "South America": 65, "Central America": 35, "Caribbean": 5}
    },
    "Americas": {
        "coords": (-20.0000, -60.0000),
        "distribution": {"North America": 35, "South America": 85, "Central America": 35, "Caribbean": 15}
    },
    "Australia": {
        "coords": (-30.0000, 150.0000),
        "distribution": {"North America": 5, "South America": 65, "Central America": 45, "Caribbean": 35}
    }
}

# Prepare data for pie charts
pie_sources = []
for region, data in regions.items():
    lat, lon = data["coords"]
    x, y = convert_to_mercator(lon, lat)  # Convert to Mercator

    # Create pie chart data
    pie_data = pd.DataFrame.from_dict(data["distribution"], orient='index', columns=['value']).reset_index()
    pie_data['angle'] = pie_data['value'] / pie_data['value'].sum() * 2 * pi
    pie_data['color'] = Spectral4[:len(pie_data)]
    
    pie_sources.append({
        'name': region,
        'x': x,
        'y': y,
        'source': ColumnDataSource(pie_data),
        'color': pie_data['color']
    })

# Create vertical bars data 
vbar_data = {
    'x': [convert_to_mercator(regions["Australia"]['coords'][1]-90, regions["Australia"]['coords'][0])[0] + 1000000, 
          convert_to_mercator(regions["Australia"]['coords'][1]-88, regions["Australia"]['coords'][0])[0] + 2000000, 
          convert_to_mercator(regions["Australia"]['coords'][1]-86, regions["Australia"]['coords'][0])[0] + 3000000],
    'top': np.random.randint(300000, 3530000, size=3),  # Random height values for bars
    'color': ['#6dbcf5', '#63ff63', '#ec5c5c']
}

vbar_source = ColumnDataSource(vbar_data)

# Create main figure
p = figure(width=1000, height=800,
           title="Regional Overview",
           background_fill_color='#252525',
           border_fill_color='#252525',
           outline_line_color=None,
           x_range=(-18000000, 18000000), y_range=(-8000000, 8000000),
           x_axis_type="mercator", y_axis_type="mercator")

# Add map background (CartoDB Dark Matter)
p.add_tile("CartoDB Dark Matter", retina=True)

# Add pie charts (larger than city charts)
for region_data in pie_sources:
    p.wedge(x=region_data['x'], y=region_data['y'], radius=1000000,
            start_angle=cumsum('angle', include_zero=True),
            end_angle=cumsum('angle'),
            line_color="white", line_width=2,
            fill_color='color', source=region_data['source'],
            alpha=0.8, legend_field='index')

# Add vertical bars (vbar)
p.vbar(x='x', top='top', width=800000, color='color', source=vbar_source)

# Add hover tool for pie charts and bars
hover = HoverTool()
hover.tooltips = [("Region", "@index"), ("Value", "@value"), ("Height", "@top")]
p.add_tools(hover)

# Remove grid lines
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.xaxis.axis_line_color = None
p.yaxis.axis_line_color = None

# Output to file
output_file("geoplot2.html")
show(p)

One of the very first examples we used to have back around 2013 was very similar to this, I’ll have to try and dig up a reference!

1 Like