Skip to content

Commit

Permalink
pydeck: Update for new @deck.gl/json API and add additional tes… (#4020)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajduberstein authored and chrisgervang committed Jan 31, 2020
1 parent 6d0a7d0 commit 851909b
Show file tree
Hide file tree
Showing 16 changed files with 657 additions and 100 deletions.
44 changes: 25 additions & 19 deletions bindings/python/pydeck/pydeck/bindings/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ class Deck(JSONMixin):
def __init__(
self,
layers=[],
views=[View(type='MapView', controller=True)],
map_style='mapbox://styles/mapbox/dark-v9',
views=[View(type="MapView", controller=True)],
map_style="mapbox://styles/mapbox/dark-v9",
mapbox_key=None,
initial_view_state=None,
width='100%',
width="100%",
height=500,
tooltip=True,
description=None,
):
'''This is the renderer and configuration for a deck.gl visualization, similar to the
"""This is the renderer and configuration for a deck.gl visualization, similar to the
`Deck <https://deck.gl/#/documentation/deckgl-api-reference/deck>`_ class from deck.gl.
Pass `Deck` a Mapbox API token to display a basemap; see the notes below.
Expand Down Expand Up @@ -58,7 +59,7 @@ def __init__(
https://deck.gl/#/documentation/deckgl-api-reference/deck
.. _gallery:
https://www.mapbox.com/gallery/
'''
"""
self.layers = []
if isinstance(layers, Layer):
self.layers.append(layers)
Expand All @@ -69,14 +70,17 @@ def __init__(
# Use passed view state
self.initial_view_state = initial_view_state
self.deck_widget = DeckGLWidget()
self.mapbox_key = mapbox_key or os.getenv('MAPBOX_API_KEY')
self.mapbox_key = mapbox_key or os.getenv("MAPBOX_API_KEY")
self.deck_widget.mapbox_key = self.mapbox_key
self.deck_widget.height = height
self.deck_widget.width = width
self.deck_widget.tooltip = tooltip
self.description = description
if self.mapbox_key is None:
warnings.warn(
'Mapbox API key is not set. This may impact available features of pydeck.', UserWarning)
"Mapbox API key is not set. This may impact available features of pydeck.",
UserWarning,
)

@property
def selected_data(self):
Expand All @@ -85,28 +89,29 @@ def selected_data(self):
return literal_eval(self.deck_widget.selected_data)

def show(self):
'''Display current Deck object for a Jupyter notebook'''
"""Display current Deck object for a Jupyter notebook"""
self.update()
return self.deck_widget

def update(self):
'''Update a deck.gl map to reflect the current configuration
"""Update a deck.gl map to reflect the current configuration
For example, if you've modified data passed to Layer and rendered the map using `.show()`,
you can call `update` to change the data on the map.
Intended for use in a Jupyter environment.
'''
"""
self.deck_widget.json_input = self.to_json()

def to_html(
self,
filename=None,
open_browser=False,
notebook_display=True,
iframe_width=700,
iframe_height=500):
'''Write a file and loads it to an iframe, if in a Jupyter environment;
self,
filename=None,
open_browser=False,
notebook_display=True,
iframe_width=700,
iframe_height=500,
):
"""Write a file and loads it to an iframe, if in a Jupyter environment;
otherwise, write a file and optionally open it in a web browser
Parameters
Expand All @@ -125,7 +130,7 @@ def to_html(
Returns
-------
str : Returns absolute path of the file
'''
"""
json_blob = self.to_json()
f = deck_to_html(
json_blob,
Expand All @@ -135,5 +140,6 @@ def to_html(
notebook_display=notebook_display,
iframe_height=iframe_height,
iframe_width=iframe_width,
tooltip=self.deck_widget.tooltip)
tooltip=self.deck_widget.tooltip,
)
return f
26 changes: 26 additions & 0 deletions bindings/python/pydeck/pydeck/bindings/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@


TYPE_IDENTIFIER = '@@type'
FUNCTION_IDENTIFIER = '@@='
QUOTE_CHARS = {"'", '"', "`"}


class Layer(JSONMixin):
Expand Down Expand Up @@ -78,6 +80,30 @@ def __init__(

# Add any other kwargs to the JSON output
if kwargs:
for k, v in kwargs.items():
# We assume strings and arrays of strings are identifiers
# ["lng", "lat"] would be converted to '[lng, lat]'
# TODO given that data here is usually a list of records,
# we could probably check that the identifier is in the row
# Errors on case like get_position='-', however

if isinstance(v, str) and v[0] in QUOTE_CHARS and v[0] == v[-1]:
# Skip quoted strings
kwargs[k] = v.replace(v[0], '')
elif isinstance(v, str):
# Have @deck.gl/json treat strings values as functions
kwargs[k] = FUNCTION_IDENTIFIER + v

elif isinstance(v, list) and v != [] and isinstance(v[0], str):
# Allows the user to pass lists e.g. to specify coordinates
array_as_str = ''
for i, identifier in enumerate(v):
if i == len(v) - 1:
array_as_str += '{}'.format(identifier)
else:
array_as_str += '{}, '.format(identifier)
kwargs[k] = '{}[{}]'.format(FUNCTION_IDENTIFIER, array_as_str)

self.__dict__.update(kwargs)

@property
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .minimal import create_minimal_test_object # noqa
from .hexagon_layer_function import create_heatmap_test_object # noqa
from .geojson_layer import create_geojson_layer_test_object # noqa
from .multilayers import create_multi_layer_test_object # noqa
from .scatterplot import create_scatterplot_test_object # noqa
from .stacked import create_stacked_test_object # noqa
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pydeck import Deck, Layer, ViewState

features = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-122.42923736572264, 37.80544394934271],
[0, 37.80544394934271],
[-122.42923736572264, 0],
[-122.42923736572264, 37.80544394934271],
]
],
},
}
],
}


def create_geojson_layer_test_object():
return Deck(
description="Test of GeoJsonLayer",
map_style=None,
initial_view_state=ViewState(longitude=-122.45, latitude=37.8, zoom=0),
layers=[
Layer(
"GeoJsonLayer",
id='geojson-layer',
data=features,
stroked=True,
filled=True,
line_width_min_pixels=2,
opacity=0.4,
get_line_color=[255, 100, 100],
get_fill_color=[200, 160, 0, 180],
)
],
views=None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from pydeck import Deck, Layer, View, ViewState

color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]

data = [
{"lat": 0, "lon": 0},
{"lat": 0, "lon": 0},
{"lat": 0, "lon": 0},
{"lat": 0, "lon": 1},
{"lat": 0.1, "lon": 1},
{"lat": 0.1, "lon": 0.1},
{"lat": 0.1, "lon": 0.1},
{"lat": 0.1, "lon": 0.1},
{"lat": 0.1, "lon": 0.1},
{"lat": 0.2, "lon": 1.2},
{"lat": 0.2, "lon": 1.2},
{"lat": 0.2, "lon": 1.2},
{"lat": 0.2, "lon": 1.2},
{"lat": 0.1, "lon": 0.1},
{"lat": 0.1, "lon": 0.1},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
{"lat": 0.1, "lon": 0.1},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
{"lat": 0.1, "lon": 0.1},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
{"lat": 1.2, "lon": 1.2},
]


def create_heatmap_test_object():
DESCRIPTION = "HexagonLayer without a function string should fail but HeatmapLayer should succeed"
# This actually shouldn't render
# Tries to detect if strings are being interpreted by the
# expression parser in @deck.gl/json
failed_hexagon_layer = Layer(
type="HexagonLayer",
id="failed-heatmap",
data=data,
elevation_range=[0, 15],
elevation_scale=1800,
get_position="'[lon, lat]'",
radius=10000,
upper_percentile=100,
color_range=color_range,
)
# This will render
successful_heatmap_layer = Layer(
type="HeatmapLayer",
id="successful-heatmap",
data=data,
get_position=["lon", "lat"],
color_range=color_range,
)

return Deck(
description=DESCRIPTION,
initial_view_state=ViewState(
**{
"longitude": 0,
"latitude": 0,
"zoom": 5,
"pitch": 40.5,
"bearing": -27.396674584323023,
}
),
views=[View(type="MapView", controller=True)],
layers=[failed_hexagon_layer, successful_heatmap_layer],
map_style=None,
)
29 changes: 29 additions & 0 deletions bindings/python/pydeck/tests/bindings/pydeck_examples/minimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydeck import Deck, Layer, LightSettings, View, ViewState

def create_minimal_test_object():
lights = LightSettings(
lights_position=[-0.144528, 49.739968, 8000, -3.807751, 54.104682, 8000],
ambient_ratio=0.4,
diffuse_ratio=0.6,
)
layer = Layer(
"HexagonLayer",
"https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv",
id="heatmap",
elevation_scale=50,
elevation_range=[0, 3000],
extruded=True,
coverage=1,
light_settings=lights
)
view_state = ViewState(
longitude=-1.4157267858730052,
latitude=52.232395363869415,
zoom=6.6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.396674584323023
)
view = View(type="MapView", controller=True)
return Deck(layers=[layer], initial_view_state=view_state, views=[view])
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from pydeck import Deck, Layer, ViewState


def create_multi_layer_test_object():
view_state = ViewState(latitude=-122.45, longitude=37.8, zoom=12)
scatterplot = Layer(
"ScatterplotLayer",
id="scatterplot",
data=[{"position": [-122.45, 37.8]}],
get_position="position",
get_fill_color=[255, 0, 0, 255],
get_radius=1000,
)
text_layer = Layer(
"TextLayer",
id="textlayer",
data=[{"position": [-122.45, 37.8], "text": "Hello World"}],
get_position="position",
get_text_anchor="`end`",
)
geojson_layer = Layer(
"GeoJsonLayer",
id="geojsonlayer",
data={
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-122.42923736572264, 37.80544394934271],
},
}
],
},
stroked=True,
filled=True,
line_width_min_pixels=2,
opacity=0.4,
get_line_color=[255, 100, 100],
get_fill_color=[200, 160, 0, 180],
)
return Deck(
layers=[scatterplot, text_layer, geojson_layer], initial_view_state=view_state
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pydeck import Deck, Layer, ViewState


def create_scatterplot_test_object():
view_state = ViewState(latitude=0, longitude=0, zoom=12)
layers = [
Layer(
"ScatterplotLayer",
id='scatterplot',
data=[[0, 0], [0.01, 0.01]],
get_position="-",
get_radius=500,
get_fill_color=[255, 0, 0],
)
]
return Deck(layers=layers, initial_view_state=view_state, map_style=None, views=None)
Loading

0 comments on commit 851909b

Please sign in to comment.