Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions bindings/pydeck/examples/a5_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
A5Layer
=======

Plot of bike parking density in San Francisco using the A5 geospatial indexing system.

A5 is a pentagonal discrete global grid system that provides equal-area cells worldwide.
Learn more at https://a5geo.org

This example is adapted from the deck.gl documentation.
"""

import pydeck as pdk
import pandas as pd

A5_DATA = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/sf.bike.parking.a5.json"

df = pd.read_json(A5_DATA)

# Define a layer to display on a map
layer = pdk.Layer(
"A5Layer",
df,
pickable=True,
extruded=True,
get_pentagon="pentagon",
get_fill_color="[(1 - count / 211) * 235, 255 - 85 * count / 211, 255 - 170 * count / 211]",
get_elevation="count",
elevation_scale=10,
)

# Set the viewport location - centered on San Francisco
view_state = pdk.ViewState(
latitude=37.74,
longitude=-122.4,
zoom=11,
bearing=0,
pitch=40
)

# Render
r = pdk.Deck(
layers=[layer],
initial_view_state=view_state,
tooltip={"text": "{pentagon} count: {count}"}
)
r.to_html("a5_layer.html")
57 changes: 57 additions & 0 deletions bindings/pydeck/examples/maplibre_globe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
GlobeView
=========

Over 33,000 power plants of the world plotted by their production capacity (given by height)
and fuel type (green if renewable) on a MapLibre globe view.

This example demonstrates using MapLibre's globe projection with deck.gl layers by setting
map_provider='maplibre' and map_projection='globe'. The globe view uses MapLibre's
MapboxOverlay with interleaved rendering for optimal performance.
"""
import pydeck as pdk
import pandas as pd

POWER_PLANTS = "https://raw.githubusercontent.com/ajduberstein/geo_datasets/master/global_power_plant_database.csv"

df = pd.read_csv(POWER_PLANTS)


def is_green(fuel_type):
"""Return a green RGB value if a facility uses a renewable fuel type"""
if fuel_type.lower() in ("nuclear", "water", "wind", "hydro", "biomass", "solar", "geothermal"):
return [10, 230, 120]
return [230, 158, 10]


df["color"] = df["primary_fuel"].apply(is_green)

# Use MapView with a globe projection
view_state = pdk.ViewState(latitude=51.47, longitude=0.45, zoom=0)

layers = [
pdk.Layer(
"ColumnLayer",
id="power-plant",
data=df,
get_elevation="capacity_mw",
get_position=["longitude", "latitude"],
elevation_scale=100,
pickable=True,
auto_highlight=True,
radius=20000,
get_fill_color="color",
),
]

deck = pdk.Deck(
initial_view_state=view_state,
tooltip={"text": "{name}, {primary_fuel} plant, {country}"},
layers=layers,
# Use MapLibre with globe projection
map_provider="maplibre",
map_style="https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
map_projection="globe",
)

deck.to_html("maplibre_globe.html", css_background_color="black", offline=True)
126 changes: 126 additions & 0 deletions bindings/pydeck/examples/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""
Multi-View Widgets Example
===========================

Demonstrates deck.gl widgets with multiple synchronized views using SplitterWidget.

This example shows:
- Two side-by-side MapView instances
- SplitterWidget to adjust the split between views
- Widgets assigned to specific views using view_id
- Synchronized navigation between views
"""
import pydeck as pdk

# Data sources
COUNTRIES = 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_scale_rank.geojson'
AIR_PORTS = 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson'

# Initial view centered on London
INITIAL_VIEW_STATE = pdk.ViewState(
latitude=51.47,
longitude=0.45,
zoom=4,
bearing=0,
pitch=30
)

# Create two map views side-by-side
left_view = pdk.View(
type='MapView',
id='left-map',
x='0%',
width='50%',
controller=True
)

right_view = pdk.View(
type='MapView',
id='right-map',
x='50%',
width='50%',
controller=True
)

# Layers
countries_layer = pdk.Layer(
'GeoJsonLayer',
id='base-map',
data=COUNTRIES,
stroked=True,
filled=True,
line_width_min_pixels=2,
opacity=0.4,
get_line_color=[60, 60, 60],
get_fill_color=[200, 200, 200]
)

airports_layer = pdk.Layer(
'GeoJsonLayer',
id='airports',
data=AIR_PORTS,
filled=True,
point_radius_min_pixels=2,
point_radius_scale=2000,
get_point_radius='11 - properties.scalerank',
get_fill_color=[200, 0, 80, 180],
pickable=True,
auto_highlight=True
)

# Create widgets - some are assigned to specific views
widgets = [
# Left map widgets
pdk.Widget('ZoomWidget', view_id='left-map'),
pdk.Widget('CompassWidget', view_id='left-map'),
pdk.Widget('FullscreenWidget', view_id='left-map'),
pdk.Widget('ScreenshotWidget', view_id='left-map'),
pdk.Widget('ResetViewWidget', view_id='left-map'),
pdk.Widget('FpsWidget', view_id='left-map'),
pdk.Widget('LoadingWidget', view_id='left-map'),
pdk.Widget('ThemeWidget', view_id='left-map'),
pdk.Widget('StatsWidget', statsType='luma', view_id='left-map'),

# Right map widgets
pdk.Widget('GeocoderWidget', view_id='right-map'),

# Global widgets (not tied to a specific view)
pdk.Widget('ScaleWidget', placement='bottom-right'),
pdk.Widget('InfoWidget', mode='hover'),
pdk.Widget('InfoWidget', mode='click'),
pdk.Widget('ContextMenuWidget'),
pdk.Widget('TimelineWidget',
placement='bottom-left',
time_range=[0, 24],
step=1,
initial_time=0,
play_interval=1000
),

# Splitter widget to adjust view sizes
pdk.Widget('SplitterWidget',
view_id1='left-map',
view_id2='right-map',
orientation='vertical'
)
]

# Create deck with multiple views
deck = pdk.Deck(
views=[left_view, right_view],
initial_view_state={
'left-map': INITIAL_VIEW_STATE,
'right-map': INITIAL_VIEW_STATE
},
layers=[countries_layer, airports_layer],
widgets=widgets,
tooltip={
'text': '{properties.name} ({properties.abbrev})\n{properties.type}'
},
map_provider='carto',
map_style='https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'
)

# Save to HTML
deck.to_html('widgets.html', css_background_color='#f0f0f0', offline=True)
print('Saved to widgets.html')
9 changes: 8 additions & 1 deletion bindings/pydeck/pydeck/bindings/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(
parameters=None,
widgets=None,
show_error=False,
map_projection=None,
):
"""This is the renderer and configuration for a deck.gl visualization, similar to the
`Deck <https://deck.gl/docs/api-reference/core/deck>`_ class from deck.gl.
Expand All @@ -63,7 +64,7 @@ def __init__(
``MAPBOX_API_KEY``, ``GOOGLE_MAPS_API_KEY``, and ``CARTO_API_KEY`` can be set instead of hardcoding the key here.
map_provider : str, default 'carto'
If multiple API keys are set (e.g., both Mapbox and Google Maps), inform pydeck which basemap provider to prefer.
Values can be ``carto``, ``mapbox`` or ``google_maps``
Values can be ``carto``, ``mapbox``, ``google_maps``, or ``maplibre``.
map_style : str or dict, default 'dark'
One of 'light', 'dark', 'road', 'satellite', 'dark_no_labels', and 'light_no_labels', a URI for a basemap
style, which varies by provider, or a dict that follows the Mapbox style `specification <https://docs.mapbox.com/mapbox-gl-js/style-spec/>`_.
Expand All @@ -87,6 +88,10 @@ def __init__(
show_error : bool, default False
If ``True``, will display the error in the rendered output.
Otherwise, will only show error in browser console.
map_projection : str, default None
Map projection to use with ``map_provider='maplibre'``.
Values can be ``'globe'`` or ``'mercator'``. Defaults to ``'mercator'`` if not specified.
Only supported with ``map_provider='maplibre'``.

.. _Deck:
https://deck.gl/docs/api-reference/core/deck
Expand All @@ -108,6 +113,7 @@ def __init__(
self.description = description
self.effects = effects
self.map_provider = str(map_provider).lower() if map_provider else None
self.map_projection = map_projection
self._tooltip = tooltip
self._show_error = show_error

Expand Down Expand Up @@ -243,6 +249,7 @@ def to_html(
as_string=as_string,
offline=offline,
show_error=self._show_error,
map_projection=self.map_projection,
**kwargs,
)
return f
Expand Down
2 changes: 1 addition & 1 deletion bindings/pydeck/pydeck/frontend_semver.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
DECKGL_SEMVER = "~9.1.*"
DECKGL_SEMVER = "~9.2.*"
9 changes: 9 additions & 0 deletions bindings/pydeck/pydeck/io/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,16 @@ def render_json_to_html(
configuration=None,
offline=False,
show_error=False,
map_projection=None,
):
js = j2_env.get_template("index.j2")
css = j2_env.get_template("style.j2")
css_text = css.render(css_background_color=css_background_color)

# Convert map_projection to JSON string
import json
map_projection_json = json.dumps(map_projection) if map_projection else 'null'

html_str = js.render(
mapbox_key=mapbox_key,
google_maps_key=google_maps_key,
Expand All @@ -80,6 +86,7 @@ def render_json_to_html(
custom_libraries=custom_libraries,
configuration=configuration,
show_error=show_error,
map_projection=map_projection_json,
)
return html_str

Expand Down Expand Up @@ -138,6 +145,7 @@ def deck_to_html(
as_string=False,
offline=False,
show_error=False,
map_projection=None,
):
"""Converts deck.gl format JSON to an HTML page"""
html_str = render_json_to_html(
Expand All @@ -150,6 +158,7 @@ def deck_to_html(
configuration=configuration,
offline=offline,
show_error=show_error,
map_projection=map_projection,
)

if filename:
Expand Down
3 changes: 3 additions & 0 deletions bindings/pydeck/pydeck/io/templates/index.j2
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{% endif %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" />
{{ deckgl_jupyter_widget_bundle }}
<link rel="stylesheet" href={{ deckgl_widget_css_url }} />
<style>
Expand All @@ -26,6 +27,7 @@
const tooltip = {{tooltip}};
const customLibraries = {{custom_libraries or 'null'}};
const configuration = {{configuration or 'null'}};
const mapProjection = {{map_projection}};

const deckInstance = createDeck({
{% if mapbox_key %}
Expand All @@ -39,6 +41,7 @@
tooltip,
customLibraries,
configuration,
mapProjection,
{% if show_error %}
showError: true,
{% endif %}
Expand Down
20 changes: 11 additions & 9 deletions bindings/pydeck/tests/notebooks/notebook_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@


def nbconvert(notebook_path):
if not os.path.exists(os.path.exists(notebook_path)):
if not os.path.exists(notebook_path):
raise Exception("Invalid path %s" % notebook_path)
CMD = (
"python -m jupyter nbconvert --to=html --ExecutePreprocessor.timeout=600 "
"--ExecutePreprocessor.kernel_name='python3' "
"--ExecutePreprocessor.store_widget_state=True "
"--execute '{}'"
)
cmd = CMD.format(notebook_path)
status = subprocess.check_call(cmd, shell=True)
CMD = [
"jupyter", "nbconvert",
"--to=html",
"--ExecutePreprocessor.timeout=600",
"--ExecutePreprocessor.kernel_name=python3",
"--ExecutePreprocessor.store_widget_state=True",
"--execute",
notebook_path
]
status = subprocess.check_call(CMD)
return status == 0
3 changes: 2 additions & 1 deletion modules/jupyter-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"@luma.gl/core": "^9.2.2",
"@luma.gl/webgl": "^9.2.2",
"d3-dsv": "^1.0.8",
"mapbox-gl": "^1.13.2"
"mapbox-gl": "^1.13.2",
"maplibre-gl": "^5.14.0"
},
"jupyterlab": {
"extension": "src/plugin",
Expand Down
Loading
Loading