Nyc Deckgl#

Explore NYC taxi pickups by hour with a deck.gl HexagonLayer. Click on the map to show arcs from the pickups near that point to their dropoffs.

import pandas as pd
import panel as pn
import param

pn.extension('deckgl')

Load data#

@pn.cache
def load_taxi():
    return pd.read_parquet('https://datasets.holoviz.org/nyc_taxi_small/v1/nyc_taxi_small.parq')

EMPIRE_STATE = (-73.9857, 40.7484)

def hourly(data, hour):
    return data[data.hour == hour]

def arcs(data, coordinate, radius):
    lon, lat = coordinate
    tol = radius / 100000
    return data[
        data.pickup_x.between(lon - tol, lon + tol) &
        data.pickup_y.between(lat - tol, lat + tol)
    ]

Define App#

The deck.gl spec is a param.depends method bound into a single DeckGL pane. Clicks on the map are mirrored into the click_state parameter, so the arcs are derived from parameters like everything else, and a Player advances the hour.

class App(pn.viewable.Viewer):

    data = param.DataFrame(precedence=-1)

    radius = param.Integer(default=50, bounds=(20, 1000))

    elevation = param.Integer(default=10, bounds=(0, 50))

    hour = param.Integer(default=0, bounds=(0, 23))

    click_state = param.Dict(default={}, precedence=-1)

    def __init__(self, **params):
        super().__init__(**params)
        self.deck = pn.ui.DeckGL(
            self.spec,
            throttle={'click': 10},
            sizing_mode='stretch_both',
            margin=0,
            min_height=800
        )
        self.deck.param.watch(lambda event: setattr(self, 'click_state', event.new), 'click_state')
        self.controls = pn.ui.Column(
            pn.ui.IntSlider.from_param(self.param.radius),
            pn.ui.IntSlider.from_param(self.param.elevation),
            pn.ui.Player.from_param(self.param.hour, interval=1000, loop_policy='loop', show_value=True),
        )

    @param.depends('data', 'hour')
    def view(self):
        return hourly(self.data, self.hour)

    @param.depends('data', 'hour', 'radius', 'click_state')
    def arc_view(self):
        return arcs(self.view(), self.click_state.get('coordinate', EMPIRE_STATE), self.radius)

    @param.depends('data', 'hour', 'radius', 'elevation', 'click_state')
    def spec(self):
        return {
            "initialViewState": {
                "bearing": 0,
                "latitude": 40.7,
                "longitude": -73.9,
                "maxZoom": 15,
                "minZoom": 5,
                "pitch": 40.5,
                "zoom": 11
            },
            "mapStyle": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
            "layers": [{
                "@@type": "HexagonLayer",
                "autoHighlight": True,
                "coverage": 1,
                "data": self.view(),
                "elevationRange": [0, 100],
                "elevationScale": self.elevation,
                "radius": self.radius,
                "extruded": True,
                "getPosition": "@@=[pickup_x, pickup_y]",
                "id": "hexagons",
            }, {
                "@@type": "ArcLayer",
                "id": 'arc-layer',
                "data": self.arc_view(),
                "pickable": True,
                "getWidth": 2,
                "getSourcePosition": "@@=[pickup_x, pickup_y]",
                "getTargetPosition": "@@=[dropoff_x, dropoff_y]",
                "getSourceColor": [0, 255, 0, 180],
                "getTargetColor": [240, 100, 0, 180]
            }],
            "views": [
                {"@@type": "MapView", "controller": True}
            ]
        }

    def __panel__(self):
        return pn.ui.Page(
            main=[self.deck],
            sidebar=[self.controls],
            sidebar_width=350,
            title='NYC Taxi Deck.GL Explorer',
            sx={".MuiToolbar-root": {"mb": 0}}
        )

Display app#

app = App(data=load_taxi())

app

Served App#

if pn.state.served:
    app.servable()