Deck Gl Global Power Plants#

Download this notebook from GitHub (right-click to download).


import panel as pn
import pydeck as pdk
pn.extension('deckgl', sizing_mode="stretch_width")

In order to use Deck.gl you need a MAP BOX Key which you can acquire for free for limited use at mapbox.com.

Now we can define a JSON spec and pass it to the DeckGL pane along with the Mapbox key:

Advanced Example Based on Global Power Plant Database#

We will base this example on data from the Global Power Plant Database.

We will create an interactive application to explore the location and types of power plants globally.

import param
import pandas as pd

POWER_PLANT_URL = (
    "https://raw.githubusercontent.com/MarcSkovMadsen/awesome-streamlit/master/"
    "gallery/global_power_plant_database/global_power_plant_database.csv"
)

MAPBOX_KEY = "pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ"

pp_data = pd.read_csv(POWER_PLANT_URL)
pp_data.head(1)
country country_long name gppd_idnr capacity_mw latitude longitude primary_fuel other_fuel1 other_fuel2 ... url geolocation_source wepp_id year_of_capacity_data generation_gwh_2013 generation_gwh_2014 generation_gwh_2015 generation_gwh_2016 generation_gwh_2017 estimated_generation_gwh
0 AFG Afghanistan Kajaki Hydroelectric Power Plant Afghanistan GEODB0040538 33.0 32.322 65.119 Hydro NaN NaN ... http://globalenergyobservatory.org GEODB 1009793 2017.0 NaN NaN NaN NaN NaN NaN

1 rows × 24 columns

Clean the data as PyDeck does not handle NA data well

pp_data.primary_fuel = pp_data.primary_fuel.fillna("NA")
pp_data.capacity_mw = pp_data.capacity_mw.fillna(1)

Transform the data#

FUEL_COLORS = {
    "Oil": "black",
    "Solar": "green",
    "Gas": "black",
    "Other": "gray",
    "Hydro": "blue",
    "Coal": "black",
    "Petcoke": "black",
    "Biomass": "green",
    "Waste": "green",
    "Cogeneration": "gray",
    "Storage": "orange",
    "Wind": "green",
}

COLORS_R = {"black": 0, "green": 0, "blue": 0, "orange": 255, "gray": 128}
COLORS_G = {"black": 0, "green": 128, "blue": 0, "orange": 165, "gray": 128}
COLORS_B = {"black": 0, "green": 0, "blue": 255, "orange": 0, "gray": 128}

pp_data["primary_fuel_color"] = pp_data.primary_fuel.map(FUEL_COLORS)
pp_data["primary_fuel_color"] = pp_data["primary_fuel_color"].fillna("gray")
pp_data["color_r"] = pp_data["primary_fuel_color"].map(COLORS_R)
pp_data["color_g"] = pp_data["primary_fuel_color"].map(COLORS_G)
pp_data["color_b"] = pp_data["primary_fuel_color"].map(COLORS_B)
pp_data["color_a"] = 140

# "name", "primary_fuel", "capacity_mw",
pp_data = pp_data[[
    "latitude","longitude", "name", "capacity_mw",
    "color_r","color_g","color_b","color_a",]
]
pp_data.head(1)
latitude longitude name capacity_mw color_r color_g color_b color_a
0 32.322 65.119 Kajaki Hydroelectric Power Plant Afghanistan 33.0 0 0 255 140

Create the app#

Here we create a parameterized class which creates a PyDeck plot in the constructor and connects it to some parameters.

class GlobalPowerPlantDatabaseApp(param.Parameterized):
    
    data = param.DataFrame(pp_data, precedence=-1)

    opacity = param.Number(default=0.8, step=0.05, bounds=(0, 1))
    
    pitch = param.Number(default=0, bounds=(0, 90))
    
    zoom = param.Integer(default=1, bounds=(1, 10))
    
    def __init__(self, **params):
        super(GlobalPowerPlantDatabaseApp, self).__init__(**params)
        self._view_state = pdk.ViewState(
            latitude=52.2323,
            longitude=-1.415,
            zoom=self.zoom,
            min_zoom=self.param.zoom.bounds[0],
            max_zoom=self.param.zoom.bounds[1],
        )
        self._scatter = pdk.Layer(
            "ScatterplotLayer",
            data=self.data,
            get_position=["longitude", "latitude"],
            get_fill_color="[color_r, color_g, color_b, color_a]",
            get_radius="capacity_mw*10",
            pickable=True,
            opacity=self.opacity,
            filled=True,
            wireframe=True,
        )
        self._deck = pdk.Deck(
            map_style="mapbox://styles/mapbox/light-v9",
            initial_view_state=self._view_state,
            layers=[self._scatter],
            tooltip=True,
            api_keys={'mapbox': MAPBOX_KEY},
        )
        self.pane = pn.pane.DeckGL(self._deck, sizing_mode="stretch_width", height=700)
        self.param.watch(self._update, ['data', 'opacity', 'pitch', 'zoom'])

    @pn.depends('pane.hover_state', 'data')
    def _info(self):
        index = self.pane.hover_state.get('index', -1)
        if index == -1:
            index = slice(0, 0)
        return self.data.iloc[index][['name', 'capacity_mw']]

    @pn.depends('pane.view_State', watch=True)
    def _update(self):
        state = self.pane.view_state
        self._view_state.longitude = state['longitude']
        self._view_state.latitude = state['latitude']   

    def _update(self, event):
        if event.name == 'data':
            self._scatter.data = self.data
        if event.name == 'opacity':
            self._scatter.opacity = self.opacity
        if event.name == 'zoom':
            self._view_state.zoom = self.zoom
        if event.name == 'pitch':
            self._view_state.pitch = self.pitch
        self.pane.param.trigger('object')
    
    def view(self):
        return pn.Row(pn.Column(self.param, pn.panel(self._info, height=200)), self.pane)

component = GlobalPowerPlantDatabaseApp()
component.view()