Deck gl global power plants

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


In [ ]:
import panel as pn
import pydeck as pdk
pn.extension('deckgl')

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.

In [ ]:
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)

Clean the data as PyDeck does not handle NA data well

In [ ]:
pp_data.primary_fuel = pp_data.primary_fuel.fillna("NA")
pp_data.capacity_mw = pp_data.capacity_mw.fillna(1)

Transform the data

In [ ]:
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)

Create the app

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

In [ ]:
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,
            mapbox_key=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, self._info), self.pane)

app = GlobalPowerPlantDatabaseApp()
app.view().servable()

Credits

The DeckGl pane and this reference example was contributed by

and improved by

  • Philipp Rüdiger

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