first commit

This commit is contained in:
Ayxan committed 2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 files changed
+4428337

No files matched your search

@@ -0,0 +1,15 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright (c) Uber Technologies, Inc.
# Distributed under the terms of the Modified BSD License.
from .bindings import map_styles, Deck, Layer, LightSettings, View, ViewState # noqa
from .widget import DeckGLWidget # noqa
from .nbextension import _jupyter_nbextension_paths # noqa
from ._version import __version__ # noqa
from .settings import settings # noqa
@@ -0,0 +1 @@
__version__ = "0.7.1"
@@ -0,0 +1,7 @@
from .deck import Deck # noqa
from .layer import Layer # noqa
from .light_settings import LightSettings # noqa
from .view import View # noqa
from .view_state import ViewState # noqa
from . import map_styles # noqa
@@ -0,0 +1,9 @@
from enum import Enum
class BaseMapProvider(Enum):
"""Basemap provider available in pydeck"""
MAPBOX = "mapbox"
GOOGLE_MAPS = "google_maps"
CARTO = "carto"
@@ -0,0 +1,201 @@
import os
from .json_tools import JSONMixin
from .layer import Layer
from ..io.html import deck_to_html
from ..settings import settings as pydeck_settings
from ..widget import DeckGLWidget
from .view import View
from .view_state import ViewState
from .base_map_provider import BaseMapProvider
from .map_styles import DARK, get_from_map_identifier
class Deck(JSONMixin):
def __init__(
self,
layers=None,
views=[View(type="MapView", controller=True)],
map_style=DARK,
api_keys=None,
initial_view_state=ViewState(latitude=0, longitude=0, zoom=1),
width="100%",
height=500,
tooltip=True,
description=None,
effects=None,
map_provider=BaseMapProvider.CARTO.value,
parameters=None,
):
"""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.
Parameters
----------
layers : pydeck.Layer or list of pydeck.Layer, default None
List of :class:`pydeck.bindings.layer.Layer` layers to render.
views : list of pydeck.View, default ``[pydeck.View(type="MapView", controller=True)]``
List of :class:`pydeck.bindings.view.View` objects to render.
api_keys : dict, default None
Dictionary of geospatial API service providers, where the keys are ``mapbox``, ``google_maps``, or ``carto``
and the values are the API key. Defaults to None if not set. Any of the environment variables
``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``
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/>`.
The default is Carto's Dark Matter map. For Mapbox examples, see Mapbox's `gallery <https://www.mapbox.com/gallery/>`.
If not using a basemap, set ``map_provider=None``.
initial_view_state : pydeck.ViewState, default ``pydeck.ViewState(latitude=0, longitude=0, zoom=1)``
Initial camera angle relative to the map, defaults to a fully zoomed out 0, 0-centered map
To compute a viewport from data, see :func:`pydeck.data_utils.viewport_helpers.compute_view`
height : int, default 500
Height of Jupyter notebook cell, in pixels.
width : int` or string, default '100%'
Width of visualization, in pixels (if a number) or as a CSS value string.
tooltip : bool or dict of {str: str}, default True
If ``True``/``False``, toggles a default tooltip on visualization hover.
Layers must have ``pickable=True`` set in order to display a tooltip.
For more advanced usage, the user can pass a dict to configure more custom tooltip features.
Further documentation is `here <tooltip.html>`_.
.. _Deck:
https://deck.gl/#/documentation/deckgl-api-reference/deck
.. _gallery:
https://www.mapbox.com/gallery/
"""
self.layers = []
if isinstance(layers, Layer):
self.layers.append(layers)
else:
self.layers = layers or []
self.views = views
# Use passed view state
self.initial_view_state = initial_view_state
self.deck_widget = DeckGLWidget()
self.deck_widget.custom_libraries = pydeck_settings.custom_libraries
api_keys = api_keys or {}
self._set_api_keys(api_keys)
self.deck_widget.height = height
self.deck_widget.width = width
self.deck_widget.tooltip = tooltip
self.description = description
self.effects = effects
self.map_provider = str(map_provider).lower() if map_provider else None
self.deck_widget.map_provider = map_provider
custom_map_style_error = "The map_provider parameter must be 'mapbox' when map_style is provided as a dict."
if isinstance(map_style, dict):
assert map_provider == BaseMapProvider.MAPBOX.value, custom_map_style_error
self.map_style = map_style
else:
self.map_style = get_from_map_identifier(map_style, map_provider)
self.parameters = parameters
@property
def selected_data(self):
if not self.deck_widget.selected_data:
return None
return self.deck_widget.selected_data
def _set_api_keys(self, api_keys: dict = None):
"""Sets API key for base map provider for both HTML embedding and the Jupyter widget"""
for k in api_keys:
k and BaseMapProvider(k)
for provider in BaseMapProvider:
attr_name = f"{provider.value}_key"
provider_env_var = f"{provider.name}_API_KEY"
attr_value = api_keys.get(provider.value) or os.getenv(provider_env_var)
setattr(self, attr_name, attr_value)
setattr(self.deck_widget, attr_name, attr_value)
def show(self):
"""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
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()
has_binary = False
binary_data_sets = []
for layer in self.layers:
if layer.use_binary_transport:
binary_data_sets.extend(layer.get_binary_data())
has_binary = True
if has_binary:
self.deck_widget.data_buffer = binary_data_sets
def to_html(
self,
filename=None,
open_browser=False,
notebook_display=None,
iframe_width="100%",
iframe_height=500,
as_string=False,
offline=False,
**kwargs,
):
"""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
----------
filename : str, default None
Name of the file.
open_browser : bool, default False
Whether a browser window will open or not after write.
notebook_display : bool, default None
Display the HTML output in an iframe if True. Set to True automatically if rendering in Jupyter.
iframe_width : str or int, default '100%'
Width of Jupyter notebook iframe in pixels, if rendered in a Jupyter environment.
iframe_height : int, default 500
Height of Jupyter notebook iframe in pixels, if rendered in Jupyter or Colab.
as_string : bool, default False
Returns HTML as a string, if True and ``filename`` is None.
css_background_color : str, default None
Background color for visualization, specified as a string in any format accepted for CSS colors.
Returns
-------
str
Returns absolute path of the file
"""
deck_json = self.to_json()
f = deck_to_html(
deck_json,
mapbox_key=self.mapbox_key,
google_maps_key=self.google_maps_key,
filename=filename,
open_browser=open_browser,
notebook_display=notebook_display,
iframe_height=iframe_height,
iframe_width=iframe_width,
tooltip=self.deck_widget.tooltip,
custom_libraries=pydeck_settings.custom_libraries,
as_string=as_string,
offline=offline,
**kwargs,
)
return f
def _repr_html_(self):
# doesn't actually need the HTML packaging in iframe_with_srcdoc,
# so we just take the HTML.data part
return self.to_html(notebook_display=True).data
@@ -0,0 +1,100 @@
"""
Support serializing objects into JSON
"""
import json
from pydeck.types.base import PydeckType
# Attributes to ignore during JSON serialization
IGNORE_KEYS = [
"mapbox_key",
"google_maps_key",
"deck_widget",
"binary_data_sets",
"_binary_data",
"_kwargs",
]
def to_camel_case(snake_case):
"""Makes a snake case string into a camel case one
Parameters
-----------
snake_case : str
Snake-cased string (e.g., "snake_cased") to be converted to camel-case (e.g., "camelCase")
Returns
-------
str
Camel-cased (e.g., "camelCased") version of input string
"""
output_str = ""
should_upper_case = False
for i, c in enumerate(snake_case):
if c == "_" and i != 0:
should_upper_case = True
continue
output_str = output_str + c.upper() if should_upper_case else output_str + c
should_upper_case = False
return output_str
def lower_first_letter(s):
return s[:1].lower() + s[1:] if s else ""
def camel_and_lower(w):
return lower_first_letter(to_camel_case(w))
def lower_camel_case_keys(attrs):
"""Makes all the keys in a dictionary camel-cased and lower-case
Parameters
----------
attrs : dict
Dictionary for which all the keys should be converted to camel-case
"""
for snake_key in list(attrs.keys()):
if "_" not in snake_key:
continue
if snake_key == "_data":
camel_key = "data"
else:
camel_key = camel_and_lower(snake_key)
attrs[camel_key] = attrs.pop(snake_key)
def default_serialize(o, remap_function=lower_camel_case_keys):
"""Default method for rendering JSON from a dictionary"""
if issubclass(type(o), PydeckType):
return repr(o)
attrs = vars(o)
attrs = {k: v for k, v in attrs.items() if v is not None}
for ignore_attr in IGNORE_KEYS:
if attrs.get(ignore_attr):
del attrs[ignore_attr]
if remap_function:
remap_function(attrs)
return attrs
def serialize(serializable):
"""Takes a serializable object and JSONifies it"""
return json.dumps(serializable, sort_keys=True, default=default_serialize)
class JSONMixin(object):
def __repr__(self):
"""
Override of string representation method to return a JSON-ified version of the
Deck object.
"""
return serialize(self)
def to_json(self):
"""
Return a JSON-ified version of the Deck object.
"""
return serialize(self)
@@ -0,0 +1,172 @@
import uuid
import numpy as np
from ..data_utils import is_pandas_df, has_geo_interface, records_from_geo_interface
from .json_tools import JSONMixin, camel_and_lower
from pydeck.types import Image
from pydeck.exceptions import BinaryTransportException
TYPE_IDENTIFIER = "@@type"
FUNCTION_IDENTIFIER = "@@="
QUOTE_CHARS = {"'", '"', "`"}
class Layer(JSONMixin):
def __init__(self, type, data=None, id=None, use_binary_transport=None, **kwargs):
"""Configures a deck.gl layer for rendering on a map. Parameters passed
here will be specific to the particular deck.gl layer that you are choosing to use.
Please see the deck.gl
`Layer catalog <https://deck.gl/docs/api-reference/layers>`_
to determine the particular parameters of your layer. You are highly encouraged to look
at the examples in the pydeck documentation.
Parameters
==========
type : str
Type of layer to render, e.g., `HexagonLayer`
id : str, default None
Unique name for layer
data : str or list of dict of {str: Any} or pandas.DataFrame, default None
Either a URL of data to load in or an array of data
use_binary_transport : bool, default None
Boolean indicating binary data
**kwargs
Any of the parameters passable to a deck.gl layer.
Examples
========
For example, here is a HexagonLayer which reads data from a URL.
>>> import pydeck
>>> # 2014 location of car accidents in the UK
>>> UK_ACCIDENTS_DATA = ('https://raw.githubusercontent.com/uber-common/'
>>> 'deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv')
>>> # Define a layer to display on a map
>>> layer = pydeck.Layer(
>>> 'HexagonLayer',
>>> UK_ACCIDENTS_DATA,
>>> get_position=['lng', 'lat'],
>>> auto_highlight=True,
>>> elevation_scale=50,
>>> pickable=True,
>>> elevation_range=[0, 3000],
>>> extruded=True,
>>> coverage=1)
Alternately, input can be a pandas.DataFrame:
>>> import pydeck
>>> df = pd.read_csv(UK_ACCIDENTS_DATA)
>>> layer = pydeck.Layer(
>>> 'HexagonLayer',
>>> df,
>>> get_position=['lng', 'lat'],
>>> auto_highlight=True,
>>> elevation_scale=50,
>>> pickable=True,
>>> elevation_range=[0, 3000],
>>> extruded=True,
>>> coverage=1)
"""
self.type = type
self.id = id or str(uuid.uuid4())
# Add any other kwargs to the JSON output
self._kwargs = kwargs.copy()
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) and Image.validate(v):
# Have pydeck convert local images to strings and/or apply extra quotes
kwargs[k] = Image(v)
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)
self._data = None
self.use_binary_transport = use_binary_transport
self._binary_data = None
self.data = data
@property
def data(self):
return self._data
@data.setter
def data(self, data_set):
"""Make the data attribute a list no matter the input type, unless
use_binary_transport is specified, which case we circumvent
serializing the data to JSON
"""
if self.use_binary_transport:
self._binary_data = self._prepare_binary_data(data_set)
elif is_pandas_df(data_set):
self._data = data_set.to_dict(orient="records")
elif has_geo_interface(data_set):
self._data = records_from_geo_interface(data_set)
else:
self._data = data_set
def get_binary_data(self):
if not self.use_binary_transport:
raise BinaryTransportException("Layer must be flagged with `use_binary_transport=True`")
return self._binary_data
def _prepare_binary_data(self, data_set):
# Binary format conversion gives a sizable speedup but requires
# slightly stricter standards for data input
if not is_pandas_df(data_set):
raise BinaryTransportException("Layer data must be a `pandas.DataFrame` type")
layer_accessors = self._kwargs
inverted_accessor_map = {v: k for k, v in layer_accessors.items() if type(v) not in [list, dict, set]}
binary_transmission = []
# Loop through data columns and convert them to numpy arrays
for column in data_set.columns:
# np.stack will take data arrays and conveniently extract the shape
np_data = np.stack(data_set[column].to_numpy())
# Get rid of the accessor so it doesn't appear in the JSON output
del self.__dict__[inverted_accessor_map[column]]
binary_transmission.append(
{
"layer_id": self.id,
"column_name": column,
"accessor": camel_and_lower(inverted_accessor_map[column]),
"np_data": np_data,
}
)
return binary_transmission
@property
def type(self):
return getattr(self, TYPE_IDENTIFIER)
@type.setter
def type(self, type_name):
self.__setattr__(TYPE_IDENTIFIER, type_name)
@@ -0,0 +1,36 @@
from .json_tools import JSONMixin
class LightSettings(JSONMixin):
"""
Configuration of lights on the plane
Parameters
---------
lights_position : array, default None
Location of lights in an array of X/Y/Z coordinates
diffuse_ratio : float, default None
Proportion of light at many angles
specular_ratio : float, default None
Proportion of light reflected in a mirror-like manner
lights_strength : array, default None
Brightness of lights
number_of_lights : int, default None
Number of lights in visualization
"""
def __init__(
self,
number_of_lights=2,
lights_position=None,
diffuse_ratio=None,
specular_ratio=None,
lights_strength=None,
ambient_ratio=None,
):
self.ambient_ratio = ambient_ratio
self.diffuse_ratio = diffuse_ratio
self.lights_position = lights_position
self.lights_strength = lights_strength
self.number_of_lights = number_of_lights
self.specular_ratio = specular_ratio
@@ -0,0 +1,59 @@
import warnings
DARK = "dark"
LIGHT = "light"
SATELLITE = "satellite"
ROAD = "road"
DARK_NO_LABELS = "dark_no_labels"
LIGHT_NO_LABELS = "light_no_labels"
MAPBOX_LIGHT = "mapbox://styles/mapbox/light-v9"
MAPBOX_DARK = "mapbox://styles/mapbox/dark-v9"
MAPBOX_ROAD = "mapbox://styles/mapbox/streets-v9"
MAPBOX_SATELLITE = "mapbox://styles/mapbox/satellite-v9"
CARTO_DARK = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
CARTO_DARK_NO_LABELS = "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json"
CARTO_LIGHT = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
CARTO_LIGHT_NO_LABELS = "https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json"
CARTO_ROAD = "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
GOOGLE_SATELLITE = "satellite"
GOOGLE_ROAD = "roadmap"
styles = {
DARK: {"mapbox": MAPBOX_DARK, "carto": CARTO_DARK},
DARK_NO_LABELS: {"carto": CARTO_DARK_NO_LABELS},
LIGHT: {"mapbox": MAPBOX_LIGHT, "carto": CARTO_LIGHT},
LIGHT_NO_LABELS: {"carto": CARTO_LIGHT_NO_LABELS},
ROAD: {"carto": CARTO_ROAD, "google_maps": GOOGLE_ROAD, "mapbox": MAPBOX_ROAD},
SATELLITE: {"mapbox": MAPBOX_SATELLITE, "google_maps": GOOGLE_SATELLITE},
}
def get_from_map_identifier(map_identifier: str, provider: str) -> str:
"""Attempt to get a style URI by map provider, otherwise pass the map identifier
to the API service
Provide reasonable cross-provider default map styles
Parameters
----------
map_identifier : str
Either a specific map provider style or a token indicating a map style. Currently
tokens are "dark", "light", "satellite", "road", "dark_no_labels", or "light_no_labels".
Not all map styles are available for all providers.
provider : str
One of "carto", "mapbox", or "google_maps", indicating the associated base map tile provider.
Returns
-------
str
Base map URI
"""
try:
return styles[map_identifier][provider]
except KeyError:
return map_identifier
@@ -0,0 +1,33 @@
from .json_tools import JSONMixin
TYPE_IDENTIFIER = "@@type"
class View(JSONMixin):
"""
Represents a "hard configuration" of a camera location
Parameters
---------
type : str, default None
deck.gl view to display, e.g., MapView
controller : bool, default None
If enabled, camera becomes interactive.
**kwargs
Any of the parameters passable to a deck.gl View
"""
def __init__(self, type=None, controller=None, width=None, height=None, **kwargs):
self.type = type
self.controller = controller
self.width = width
self.height = height
self.__dict__.update(kwargs)
@property
def type(self):
return getattr(self, TYPE_IDENTIFIER)
@type.setter
def type(self, type_name):
self.__setattr__(TYPE_IDENTIFIER, type_name)
@@ -0,0 +1,42 @@
from .json_tools import JSONMixin
class ViewState(JSONMixin):
"""An object that represents where the state of a viewport, essentially where the screen is focused.
If you have two dimensional data and you don't want to set this manually,
see :func:`pydeck.data_utils.viewport_helpers.compute_view`.
Parameters
---------
longitude : float, default None
x-coordinate of focus
latitude : float, default None
y-coordinate of focus
zoom : float, default None
Magnification level of the map, usually between 0 (representing the whole world)
and 24 (close to individual buildings)
min_zoom : float, default None
Least mangified zoom level the user can navigate to
max_zoom : float, default None
Most magnified zoom level the user can navigate to
pitch : float, default None
Up/down angle relative to the map's plane, with 0 being looking directly at the map
bearing : float, default None
Left/right angle relative to the map's true north, with 0 being aligned to true north
"""
def __init__(
self, longitude=None, latitude=None, zoom=None, min_zoom=None, max_zoom=None, pitch=None, bearing=None, **kwargs
):
self.longitude = longitude
self.latitude = latitude
self.zoom = zoom
self.min_zoom = min_zoom
self.max_zoom = max_zoom
self.pitch = pitch
self.bearing = bearing
if kwargs:
self.__dict__.update(kwargs)
@@ -0,0 +1,3 @@
from .viewport_helpers import compute_view # noqa
from .type_checking import has_geo_interface, is_pandas_df, records_from_geo_interface # noqa
from .color_scales import assign_random_colors # noqa
@@ -0,0 +1,61 @@
from collections import defaultdict
import numpy as np
# Grafted from
# https://github.com/maartenbreddels/ipyvolume/blob/d13828dfd8b57739004d5daf7a1d93ad0839ed0f/ipyvolume/serialize.py#L219
def array_to_binary(ar, obj=None, force_contiguous=True):
if ar is None:
return None
if ar.dtype.kind not in ["u", "i", "f"]: # ints and floats
raise ValueError("unsupported dtype: %s" % (ar.dtype))
# WebGL does not support float64, case it here
if ar.dtype == np.float64:
ar = ar.astype(np.float32)
# JS does not support int64
if ar.dtype == np.int64:
ar = ar.astype(np.int32)
# make sure it's contiguous
if force_contiguous and not ar.flags["C_CONTIGUOUS"]:
ar = np.ascontiguousarray(ar)
return {
# binary data representation of a numpy matrix
"value": memoryview(ar),
# dtype convertible to a typed array
"dtype": str(ar.dtype),
# height of np matrix
"length": ar.shape[0],
# width of np matrix
"size": 1 if len(ar.shape) == 1 else ar.shape[1],
}
def serialize_columns(data_set_cols, obj=None):
if data_set_cols is None:
return None
layers = defaultdict(dict)
# Number of records in data set
length = {}
for col in data_set_cols:
accessor_attribute = array_to_binary(col["np_data"])
if length.get(col["layer_id"]):
length[col["layer_id"]] = max(length[col["layer_id"]], accessor_attribute["length"])
else:
length[col["layer_id"]] = accessor_attribute["length"]
# attributes is deck.gl's expected argument name for
# binary data transfer
if not layers[col["layer_id"]].get("attributes"):
layers[col["layer_id"]]["attributes"] = {}
# Add new accessor
layers[col["layer_id"]]["attributes"][col["accessor"]] = {
"value": accessor_attribute["value"],
"dtype": accessor_attribute["dtype"],
"size": accessor_attribute["size"],
}
for layer_key, _ in layers.items():
layers[layer_key]["length"] = length[layer_key]
return layers
data_buffer_serialization = dict(to_json=serialize_columns, from_json=None)
@@ -0,0 +1,34 @@
from collections import OrderedDict
import random
def get_random_rgb():
"""Generate a random RGB value
Returns
-------
list of float
Random RGB array
"""
return [round(random.random() * 255) for _ in range(0, 3)]
def assign_random_colors(data_vector):
"""Produces lookup table keyed by each class of data, with value as an RGB array
Parameters
---------
data_vector : list
Vector of data classes to be categorized, passed from the data itself
Returns
-------
collections.OrderedDict
Dictionary of random RGBA value per class, keyed on class
"""
deduped_classes = list(set(data_vector))
classes = sorted([str(x) for x in deduped_classes])
colors = []
for _ in classes:
colors.append(get_random_rgb())
return OrderedDict([item for item in zip(classes, colors)])
@@ -0,0 +1,24 @@
def is_pandas_df(obj):
"""Check if an object is a Pandas DataFrame
Returns
-------
bool
Returns True if object is a Pandas DataFrame and False otherwise
"""
return obj.__class__.__module__ == "pandas.core.frame" and obj.to_records and obj.to_dict
def has_geo_interface(obj):
return hasattr(obj, "__geo_interface__")
def records_from_geo_interface(data):
"""Un-nest data from object implementing __geo_interface__ standard"""
flattened_records = []
for d in data.__geo_interface__.get("features"):
record = d.get("properties", {})
geom = d.get("geometry", {})
record["geometry"] = geom
flattened_records.append(record)
return flattened_records
@@ -0,0 +1,176 @@
"""
Functions that make it easier to provide a default centering
for a view state
"""
import math
from ..bindings.view_state import ViewState
from .type_checking import is_pandas_df
def _squared_diff(x, x0):
return (x0 - x) * (x0 - x)
def euclidean(y, y1):
"""Euclidean distance in n-dimensions
Parameters
----------
y : tuple of float
A point in n-dimensions
y1 : tuple of float
A point in n-dimensions
Examples
--------
>>> EPSILON = 0.001
>>> euclidean((3, 6, 5), (7, -5, 1)) - 12.369 < EPSILON
True
"""
if not len(y) == len(y1):
raise Exception("Input coordinates must be of the same length")
return math.sqrt(sum([_squared_diff(x, x0) for x, x0 in zip(y, y1)]))
def geometric_mean(points):
"""Gets centroid in a series of points
Parameters
----------
points : list of list of float
List of (x, y) coordinates
Returns
-------
tuple
The centroid of a list of points
"""
avg_x = sum([float(p[0]) for p in points]) / len(points)
avg_y = sum([float(p[1]) for p in points]) / len(points)
return (avg_x, avg_y)
def get_bbox(points):
"""Get the bounding box around the data,
Parameters
----------
points : list of list of float
List of (x, y) coordinates
Returns
-------
dict
Dictionary containing the top left and bottom right points of a bounding box
"""
xs = [p[0] for p in points]
ys = [p[1] for p in points]
max_x = max(xs)
max_y = max(ys)
min_x = min(xs)
min_y = min(ys)
return ((min_x, max_y), (max_x, min_y))
def k_nearest_neighbors(points, center, k):
"""Gets the k furthest points from the center
Parameters
----------
points : list of list of float
List of (x, y) coordinates
center : list of list of float
Center point
k : int
Number of points
Returns
-------
list
Index of the k furthest points
Todo
---
Currently implemently naively, needs to be more efficient
"""
pts_with_distance = [(pt, euclidean(pt, center)) for pt in points]
sorted_pts = sorted(pts_with_distance, key=lambda x: x[1])
return [x[0] for x in sorted_pts][: int(k)]
def get_n_pct(points, proportion=1):
"""Computes the bounding box of the maximum zoom for the specified list of points
Parameters
----------
points : list of list of float
List of (x, y) coordinates
proportion : float, default 1
Value between 0 and 1 representing the minimum proportion of data to be captured
Returns
-------
list
k nearest data points
"""
if proportion == 1:
return points
# Compute the medioid of the data
centroid = geometric_mean(points)
# Retain the closest n*proportion points
n_to_keep = math.floor(proportion * len(points))
return k_nearest_neighbors(points, centroid, n_to_keep)
def bbox_to_zoom_level(bbox):
"""Computes the zoom level of a lat/lng bounding box
Parameters
----------
bbox : list of list of float
Northwest and southeast corners of a bounding box, given as two points in a list
Returns
-------
int
Zoom level of map in a WGS84 Mercator projection (e.g., like that of Google Maps)
"""
lat_diff = max(bbox[0][0], bbox[1][0]) - min(bbox[0][0], bbox[1][0])
lng_diff = max(bbox[0][1], bbox[1][1]) - min(bbox[0][1], bbox[1][1])
max_diff = max(lng_diff, lat_diff)
zoom_level = None
if max_diff < (360.0 / math.pow(2, 20)):
zoom_level = 21
else:
zoom_level = int(-1 * ((math.log(max_diff) / math.log(2.0)) - (math.log(360.0) / math.log(2))))
if zoom_level < 1:
zoom_level = 1
return zoom_level
def compute_view(points, view_proportion=1, view_type=ViewState):
"""Automatically computes a zoom level for the points passed in.
Parameters
----------
points : list of list of float or pandas.DataFrame
A list of points
view_propotion : float, default 1
Proportion of the data that is meaningful to plot
view_type : class constructor for pydeck.ViewState, default :class:`pydeck.bindings.view_state.ViewState`
Class constructor for a viewport. In the current version of pydeck,
users most likely do not have to modify this attribute.
Returns
-------
pydeck.Viewport
Viewport fitted to the data
"""
if is_pandas_df(points):
points = points.to_records(index=False)
bbox = get_bbox(get_n_pct(points, view_proportion))
zoom = bbox_to_zoom_level(bbox)
center = geometric_mean(points)
instance = view_type(latitude=center[1], longitude=center[0], zoom=zoom)
return instance
@@ -0,0 +1 @@
from .exceptions import BinaryTransportException, PydeckException # noqa,
@@ -0,0 +1,6 @@
class PydeckException(BaseException):
pass
class BinaryTransportException(PydeckException):
pass
@@ -0,0 +1 @@
DECKGL_SEMVER = "~8.5.*"
Whitespace-only changes.
+160
View File
@@ -0,0 +1,160 @@
import html
import os
from os.path import realpath, join, dirname
import sys
import time
import warnings
import webbrowser
import jinja2
from ..frontend_semver import DECKGL_SEMVER
def in_jupyter():
try:
ip = get_ipython() # noqa
return ip.has_trait("kernel")
except NameError:
return False
def convert_js_bool(py_bool):
if type(py_bool) != bool:
return py_bool
return "true" if py_bool else "false"
in_google_colab = "google.colab" in sys.modules
TEMPLATES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "./templates/")
j2_loader = jinja2.FileSystemLoader(TEMPLATES_PATH)
j2_env = jinja2.Environment(loader=j2_loader, trim_blocks=True)
CDN_URL = "https://cdn.jsdelivr.net/npm/@deck.gl/jupyter-widget@{}/dist/index.js".format(DECKGL_SEMVER)
def cdn_picker(offline=False):
# Support hot-reloading
dev_port = os.getenv("PYDECK_DEV_PORT")
if dev_port:
print("pydeck running in development mode, expecting @deck.gl/jupyter-widget served at {}".format(dev_port))
return (
"<script type='text/javascript' src='http://localhost:{dev_port}/dist/index.js'></script>\n"
"<script type='text/javascript' src='http://localhost:{dev_port}/dist/index.js.map'></script>\n"
).format(dev_port=dev_port)
if offline:
RELPATH_TO_BUNDLE = "../nbextension/static/index.js"
with open(join(dirname(__file__), RELPATH_TO_BUNDLE), "r", encoding="utf-8") as file:
js = file.read()
return "<script type='text/javascript'>{}</script>".format(js)
return "<script src='{}'></script>".format(CDN_URL)
def render_json_to_html(
json_input,
mapbox_key=None,
google_maps_key=None,
tooltip=True,
css_background_color=None,
custom_libraries=None,
offline=False,
):
js = j2_env.get_template("index.j2")
css = j2_env.get_template("style.j2")
css_text = css.render(css_background_color=css_background_color)
html_str = js.render(
mapbox_key=mapbox_key,
google_maps_key=google_maps_key,
json_input=json_input,
deckgl_jupyter_widget_bundle=cdn_picker(offline=offline),
tooltip=convert_js_bool(tooltip),
css_text=css_text,
custom_libraries=custom_libraries,
)
return html_str
def display_html(filename):
"""Converts HTML into a temporary file and opens it in the system browser."""
url = "file://{}".format(filename)
# Hack to prevent blank page
time.sleep(0.5)
webbrowser.open(url)
def iframe_with_srcdoc(html_str, width="100%", height=500):
if isinstance(width, str):
width = f'"{width}"'
srcdoc = html.escape(html_str)
iframe = f"""
<iframe
width={width}
height={height}
frameborder="0"
srcdoc="{srcdoc}"
></iframe>
"""
from IPython.display import HTML # noqa
with warnings.catch_warnings():
msg = "Consider using IPython.display.IFrame instead"
warnings.filterwarnings("ignore", message=msg)
return HTML(iframe)
def render_for_colab(html_str, iframe_height):
from IPython.display import HTML, Javascript # noqa
js_height_snippet = f"google.colab.output.setIframeHeight({iframe_height}, true, {{minHeight: {iframe_height}}})"
display(Javascript(js_height_snippet)) # noqa
display(HTML(html_str)) # noqa
def deck_to_html(
deck_json,
mapbox_key=None,
google_maps_key=None,
filename=None,
open_browser=False,
notebook_display=None,
css_background_color=None,
iframe_height=500,
iframe_width="100%",
tooltip=True,
custom_libraries=None,
as_string=False,
offline=False,
):
"""Converts deck.gl format JSON to an HTML page"""
html_str = render_json_to_html(
deck_json,
mapbox_key=mapbox_key,
google_maps_key=google_maps_key,
tooltip=tooltip,
css_background_color=css_background_color,
custom_libraries=custom_libraries,
offline=offline,
)
if filename:
with open(filename, "w+", encoding="utf-8") as f:
f.write(html_str)
if open_browser:
display_html(realpath(f.name))
if notebook_display is None:
notebook_display = in_jupyter()
if notebook_display and in_google_colab:
render_for_colab(html_str, iframe_height)
return html_str
elif not filename and as_string:
return html_str
elif notebook_display:
return iframe_with_srcdoc(html_str, iframe_width, iframe_height)
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>pydeck</title>
{% if google_maps_key %}
<script src="https://maps.googleapis.com/maps/api/js?key={{google_maps_key}}&libraries=places"></script>
{% else %}
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.13.0/mapbox-gl.js"></script>
{% 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" />
{{ deckgl_jupyter_widget_bundle }}
<style>
{{ css_text }}
</style>
</head>
<body>
<div id="deck-container">
</div>
</body>
<script>
const jsonInput = {{json_input}};
const tooltip = {{tooltip}};
const customLibraries = {{ custom_libraries or 'null' }};
const deckInstance = createDeck({
{% if mapbox_key %}
mapboxApiKey: '{{mapbox_key}}',
{% endif %}
{% if google_maps_key %}
googleMapsKey: '{{google_maps_key}}',
{% endif %}
container: document.getElementById('deck-container'),
jsonInput,
tooltip,
customLibraries
});
</script>
</html>
@@ -0,0 +1,34 @@
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#deck-map-container {
width: 100%;
height: 100%;
background-color: black;
}
#map {
pointer-events: none;
height: 100%;
width: 100%;
position: absolute;
z-index: 1;
}
#deckgl-overlay {
z-index: 2;
background: {{css_background_color or 'none'}};
}
#deck-map-wrapper {
width: 100%;
height: 100%;
}
#deck-container {
width: 100vw;
height: 100vh;
}
@@ -0,0 +1,11 @@
def _jupyter_nbextension_paths():
"""Integrates Widget with a Jupyter notebook.
Required for building a widget. `See the Jupyter Notebook docs.`_
Users should not explicitly call this function.
_ https://testnb.readthedocs.io/en/latest/examples/Notebook/Distributing%20Jupyter%20Extensions%20as%20Python%20Packages.html#Defining-the-server-extension-and-nbextension
"""
return [
{"section": "notebook", "src": "nbextension/static", "dest": "pydeck", "require": "pydeck/extensionRequires"}
]
Whitespace-only changes.
@@ -0,0 +1,15 @@
/* eslint-disable */
define(function() {
'use strict';
requirejs.config({
map: {
'*': {
'@deck.gl/jupyter-widget': 'nbextensions/pydeck/index'
}
}
});
// Export the required load_ipython_extension function
return {
load_ipython_extension: function() {}
};
});
@@ -0,0 +1,19 @@
!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e(require("@jupyter-widgets/base"));else if("function"==typeof define&&define.amd)define(["@jupyter-widgets/base"],e);else{var r="object"==typeof exports?e(require("@jupyter-widgets/base")):e(t["@jupyter-widgets/base"]);for(var n in r)("object"==typeof exports?exports:t)[n]=r[n]}}(window,(function(t){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(n,i,function(e){return t[e]}.bind(null,i));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=247)}([function(t,e){t.exports=function(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}},function(t,e){function r(e){return t.exports=r=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},r(e)}t.exports=r},function(t,e){t.exports=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},function(t,e){function r(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}t.exports=function(t,e,n){return e&&r(t.prototype,e),n&&r(t,n),t}},function(t,e,r){"use strict";function n(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}r.d(e,"a",(function(){return n}))},function(t,e,r){var n=r(248);t.exports=function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&n(t,e)}},function(t,e,r){var n=r(24),i=r(189);t.exports=function(t,e){return!e||"object"!==n(e)&&"function"!=typeof e?i(t):e}},function(t,e,r){"use strict";const n=new(r(276).a)({id:"luma.gl"});var i=r(31),o=(r(202),r(84)),a=r(70),s=r(26),u=r(36);function c(t){const{luma:e}=t;if(t.canvas&&e){const{clientWidth:r}=e.canvasSizeInfo;return r?t.drawingBufferWidth/r:1}return 1}function l(t,e,r=!0){return function(t,e,r,n,i){const o=h(t[0],e,r);let a=f(t[1],e,n,i),s=h(t[0]+1,e,r);const u=s===r-1?s:s-1;let c;s=f(t[1]+1,e,n,i),i?(s=0===s?s:s+1,c=a,a=s):c=s===n-1?s:s-1;return{x:o,y:a,width:Math.max(u-o+1,1),height:Math.max(c-a+1,1)}}(e,c(t),t.drawingBufferWidth,t.drawingBufferHeight,r)}function h(t,e,r){return Math.min(Math.round(t*e),r-1)}function f(t,e,r,n){return n?Math.max(0,r-1-Math.round(t*e)):Math.min(Math.round(t*e),r-1)}const p=Object(s.isBrowser)(),d=p&&"undefined"!=typeof document,A={webgl2:!0,webgl1:!0,throwOnError:!0,manageState:!0,canvas:null,debug:!1,width:800,height:600};function g(t={}){Object(u.a)(p,"createGLContext only available in the browser.\nCreate your own headless context or use 'createHeadlessContext' from @luma.gl/test-utils"),t=Object.assign({},A,t);const{width:e,height:r}=t;function o(e){if(t.throwOnError)throw new Error(e);return console.error(e),null}let a;t.onError=o;const{canvas:s}=t;return a=function(t,e){const{onError:r}=e;let n=null;const i=t=>n=t.statusMessage||n;t.addEventListener("webglcontextcreationerror",i,!1);const{webgl1:o=!0,webgl2:a=!0}=e;let s=null;a&&(s=s||t.getContext("webgl2",e),s=s||t.getContext("experimental-webgl2",e));o&&(s=s||t.getContext("webgl",e),s=s||t.getContext("experimental-webgl",e));if(t.removeEventListener("webglcontextcreationerror",i,!1),!s)return r(`Failed to create ${a&&!o?"WebGL2":"WebGL"} context: ${n||"Unknown error"}`);e.onContextLost&&t.addEventListener("webglcontextlost",e.onContextLost,!1);e.onContextRestored&&t.addEventListener("webglcontextrestored",e.onContextRestored,!1);return s}(function({canvas:t,width:e=800,height:r=600,onError:n}){let i;if("string"==typeof t){d&&"complete"===document.readyState||n(`createGLContext called on canvas '${t}' before page was loaded`),i=document.getElementById(t)}else t?i=t:(i=document.createElement("canvas"),i.id="lumagl-canvas",i.style.width=Number.isFinite(e)?`${e}px`:"100%",i.style.height=Number.isFinite(r)?`${r}px`:"100%",document.body.insertBefore(i,document.body.firstChild));return i}({canvas:s,width:e,height:r,onError:o}),t),a?(a=m(a,t),function(t){const e=Object(i.e)(t)?"WebGL2":"WebGL1",r=function(t){const e=t.gLine truncated
/**
* @license long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
* Released under the Apache License, Version 2.0
* see: https://github.com/dcodeIO/long.js for details
*/i=[],void 0===(o="function"==typeof(n=function(){"use strict";function t(t,e,r){this.low=0|t,this.high=0|e,this.unsigned=!!r}function e(t){return!0===(t&&t.__isLong__)}t.prototype.__isLong__,Object.defineProperty(t.prototype,"__isLong__",{value:!0,enumerable:!1,configurable:!1}),t.isLong=e;var r={},n={};function i(t,e){var i,o,s;return e?(s=0<=(t>>>=0)&&t<256)&&(o=n[t])?o:(i=a(t,(0|t)<0?-1:0,!0),s&&(n[t]=i),i):(s=-128<=(t|=0)&&t<128)&&(o=r[t])?o:(i=a(t,t<0?-1:0,!1),s&&(r[t]=i),i)}function o(t,e){if(isNaN(t)||!isFinite(t))return e?A:d;if(e){if(t<0)return A;if(t>=h)return _}else{if(t<=-f)return b;if(t+1>=f)return v}return t<0?o(-t,e).neg():a(t%l|0,t/l|0,e)}function a(e,r,n){return new t(e,r,n)}t.fromInt=i,t.fromNumber=o,t.fromBits=a;var s=Math.pow;function u(t,e,r){if(0===t.length)throw Error("empty string");if("NaN"===t||"Infinity"===t||"+Infinity"===t||"-Infinity"===t)return d;if("number"==typeof e?(r=e,e=!1):e=!!e,(r=r||10)<2||36<r)throw RangeError("radix");var n;if((n=t.indexOf("-"))>0)throw Error("interior hyphen");if(0===n)return u(t.substring(1),e,r).neg();for(var i=o(s(r,8)),a=d,c=0;c<t.length;c+=8){var l=Math.min(8,t.length-c),h=parseInt(t.substring(c,c+l),r);if(l<8){var f=o(s(r,l));a=a.mul(f).add(o(h))}else a=(a=a.mul(i)).add(o(h))}return a.unsigned=e,a}function c(e){return e instanceof t?e:"number"==typeof e?o(e):"string"==typeof e?u(e):a(e.low,e.high,e.unsigned)}t.fromString=u,t.fromValue=c;var l=4294967296,h=l*l,f=h/2,p=i(1<<24),d=i(0);t.ZERO=d;var A=i(0,!0);t.UZERO=A;var g=i(1);t.ONE=g;var m=i(1,!0);t.UONE=m;var y=i(-1);t.NEG_ONE=y;var v=a(-1,2147483647,!1);t.MAX_VALUE=v;var _=a(-1,-1,!0);t.MAX_UNSIGNED_VALUE=_;var b=a(0,-2147483648,!1);t.MIN_VALUE=b;var x=t.prototype;return x.toInt=function(){return this.unsigned?this.low>>>0:this.low},x.toNumber=function(){return this.unsigned?(this.high>>>0)*l+(this.low>>>0):this.high*l+(this.low>>>0)},x.toString=function(t){if((t=t||10)<2||36<t)throw RangeError("radix");if(this.isZero())return"0";if(this.isNegative()){if(this.eq(b)){var e=o(t),r=this.div(e),n=r.mul(e).sub(this);return r.toString(t)+n.toInt().toString(t)}return"-"+this.neg().toString(t)}for(var i=o(s(t,6),this.unsigned),a=this,u="";;){var c=a.div(i),l=(a.sub(c.mul(i)).toInt()>>>0).toString(t);if((a=c).isZero())return l+u;for(;l.length<6;)l="0"+l;u=""+l+u}},x.getHighBits=function(){return this.high},x.getHighBitsUnsigned=function(){return this.high>>>0},x.getLowBits=function(){return this.low},x.getLowBitsUnsigned=function(){return this.low>>>0},x.getNumBitsAbs=function(){if(this.isNegative())return this.eq(b)?64:this.neg().getNumBitsAbs();for(var t=0!=this.high?this.high:this.low,e=31;e>0&&0==(t&1<<e);e--);return 0!=this.high?e+33:e+1},x.isZero=function(){return 0===this.high&&0===this.low},x.isNegative=function(){return!this.unsigned&&this.high<0},x.isPositive=function(){return this.unsigned||this.high>=0},x.isOdd=function(){return 1==(1&this.low)},x.isEven=function(){return 0==(1&this.low)},x.equals=function(t){return e(t)||(t=c(t)),(this.unsigned===t.unsigned||this.high>>>31!=1||t.high>>>31!=1)&&this.high===t.high&&this.low===t.low},x.eq=x.equals,x.notEquals=function(t){return!this.eq(t)},x.neq=x.notEquals,x.lessThan=function(t){return this.comp(t)<0},x.lt=x.lessThan,x.lessThanOrEqual=function(t){return this.comp(t)<=0},x.lte=x.lessThanOrEqual,x.greaterThan=function(t){return this.comp(t)>0},x.gt=x.greaterThan,x.greaterThanOrEqual=function(t){return this.comp(t)>=0},x.gte=x.greaterThanOrEqual,x.compare=function(t){if(e(t)||(t=c(t)),this.eq(t))return 0;var r=this.isNegative(),n=t.isNegative();return r&&!n?-1:!r&&n?1:this.unsigned?t.high>>>0>this.high>>>0||t.high===this.high&&t.low>>>0>this.low>>>0?-1:1:this.sub(t).isNegative()?-1:1},x.comp=x.compare,x.negate=function(){return!this.unsigned&&this.eq(b)?b:this.not().add(g)},x.neg=x.negate,x.add=function(t){e(t)||(t=c(t));var r=this.high>>>16,n=65535&this.high,i=this.low>>>16,o=65535&this.low,s=t.high>>>16,u=65535&t.high,l=t.low>>>16,h=0,f=0,p=0,d=0;return p+=(d+=o+(65535&t.low))>>>16,f+=(p+=i+l)>>>16,h+=(f+=n+u)>>>16,h+=r+s,a((p&=65535)<<16|(d&=65535),(h&=65535)<<16|(f&=65535),this.unsigned)},x.subtract=function(t){return e(t)||(t=c(t)),this.add(t.neg())},x.sub=x.subtract,x.multiply=function(t){if(this.isZero())return d;if(e(t)||(t=c(t)),t.isZero())return d;if(this.eq(b))return t.isOdd()?b:d;if(t.eq(b))return this.isOdd()?b:d;if(this.isNegative())return t.isNegative()?this.neg().mul(t.neg()):this.neg().mul(t).neg();if(t.isNegative())return this.mul(t.neg()).neg();if(this.lt(p)&&t.lt(p))return o(this.toNumber()*t.toNumber(),this.unsigned);var r=this.high>>>16,n=65535&this.high,i=this.low>>>16,s=65535&this.low,u=t.high>>>16,l=65535&t.high,h=t.low>>>16,f=65535&t.low,A=0,g=0,m=0,y=0;return m+=(y+=s*f)>>>16,g+=(m+=i*f)>>>16,m&=65535,g+=(m+=s*h)>>>16,A+=(g+=n*f)>>>16,g&=65535,A+=(g+=i*h)>>>16,g&=65535,A+=(g+=s*l)>>>16,A+=r*f+n*h+i*l+s*u,a((m&=65535)<<16|(y&=65535),(A&=65535)<<16|(g&=65535),this.unsigned)},x.mul=x.multiply,x.divide=function(t){iLine truncated
/* @license
Papa Parse
v5.0.0-beta.0
https://github.com/mholt/PapaParse
License: MIT
*/
var r="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==r?r:{},n={};function i(t){this._handle=null,this._finished=!1,this._completed=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},function(t){var e=c(t);e.chunkSize=parseInt(e.chunkSize),t.step||t.chunk||(e.chunkSize=null);this._handle=new a(e),this._handle.streamer=this,this._config=e}.call(this,t),this.parseChunk=function(t,e){if(this.isFirstChunk&&l(this._config.beforeFirstChunk)){var r=this._config.beforeFirstChunk(t);void 0!==r&&(t=r)}this.isFirstChunk=!1;var n=this._partialLine+t;this._partialLine="";var i=this._handle.parse(n,this._baseIndex,!this._finished);if(!this._handle.paused()&&!this._handle.aborted()){var o=i.meta.cursor;this._finished||(this._partialLine=n.substring(o-this._baseIndex),this._baseIndex=o),i&&i.data&&(this._rowCount+=i.data.length);var a=this._finished||this._config.preview&&this._rowCount>=this._config.preview;if(l(this._config.chunk)&&!e){if(this._config.chunk(i,this._handle),this._handle.paused()||this._handle.aborted())return;i=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(i.data),this._completeResults.errors=this._completeResults.errors.concat(i.errors),this._completeResults.meta=i.meta),this._completed||!a||!l(this._config.complete)||i&&i.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||i&&i.meta.paused||this._nextChunk(),i}},this._sendError=function(t){l(this._config.error)&&this._config.error(t)}}function o(t){var e;t=t||{},i.call(this,t),this.stream=function(t){return e=t,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var t=this._config.chunkSize,r=t?e.substr(0,t):e;return e=t?e.substr(t):"",this._finished=!e,this.parseChunk(r)}}}function a(t){var e,r,i,o=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,a=/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/,h=this,f=0,p=0,d=!1,A=!1,g=[],m={data:[],errors:[],meta:{}};if(l(t.step)){var y=t.step;t.step=function(e){if(m=e,b())_();else{if(_(),!m.data||0===m.data.length)return;f+=e.data.length,t.preview&&f>t.preview?r.abort():y(m,h)}}}function v(e){return"greedy"===t.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function _(){if(m&&i&&(w("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+n.DefaultDelimiter+"'"),i=!1),t.skipEmptyLines)for(var e=0;e<m.data.length;e++)v(m.data[e])&&m.data.splice(e--,1);return b()&&function(){if(!m)return;function e(e){l(t.transformHeader)&&(e=t.transformHeader(e)),g.push(e)}if(Array.isArray(m.data[0])){for(var r=0;b()&&r<m.data.length;r++)m.data[r].forEach(e);m.data.splice(0,1)}else m.data.forEach(e)}(),function(){if(!m||!m.data||!t.header&&!t.dynamicTyping&&!t.transform)return m;function e(e,r){var n,i=t.header?{}:[];for(n=0;n<e.length;n++){var o=n,a=e[n];t.header&&(o=n>=g.length?"__parsed_extra":g[n]),t.transform&&(a=t.transform(a,o)),a=x(o,a),"__parsed_extra"===o?(i[o]=i[o]||[],i[o].push(a)):i[o]=a}return t.header&&(n>g.length?w("FieldMismatch","TooManyFields","Too many fields: expected "+g.length+" fields but parsed "+n,p+r):n<g.length&&w("FieldMismatch","TooFewFields","Too few fields: expected "+g.length+" fields but parsed "+n,p+r)),i}var r=1;!m.data[0]||Array.isArray(m.data[0])?(m.data=m.data.map(e),r=m.data.length):m.data=e(m.data,0);t.header&&m.meta&&(m.meta.fields=g);return p+=r,m}()}function b(){return t.header&&0===g.length}function x(e,r){return function(e){return t.dynamicTypingFunction&&void 0===t.dynamicTyping[e]&&(t.dynamicTyping[e]=t.dynamicTypingFunction(e)),!0===(t.dynamicTyping[e]||t.dynamicTyping)}(e)?"true"===r||"TRUE"===r||"false"!==r&&"FALSE"!==r&&(o.test(r)?parseFloat(r):a.test(r)?new Date(r):""===r?null:r):r}function w(t,e,r,n){m.errors.push({type:t,code:e,message:r,row:n})}this.parse=function(o,a,h){var f=t.quoteChar||'"';if(t.newline||(t.newline=function(t,e){t=t.substr(0,1048576);var r=new RegExp(s(e)+"([^]*?)"+s(e),"gm"),n=(t=t.replace(r,"")).split("\r"),i=t.split("\n"),o=i.length>1&&i[0].length<n[0].length;if(1===n.length||o)return"\n";for(var a=0,u=0;u<n.length;u++)"\n"===n[u][0]&&a++;return a>=n.length/2?"\r\n":"\r"}(o,f)),i=!1,t.delimiter)l(t.delimiter)&&(t.delimiter=t.delimiter(o),m.meta.delimiter=t.delimiter);else{var p=function(e,r,i,o,a){var s,c,l;a=a||[",","\t","|",";",n.RECORD_SEP,n.UNIT_SEP];for(var h=0;h<a.length;h++){var f=a[h],p=0,d=0,A=0;l=void 0;for(var g=new u({comments:o,delimiter:f,newline:r,preview:10}).parse(e),m=0;m<g.data.length;m++)if(i&&v(g.data[m]))A++;else{var y=g.data[m].length;d+=y,void 0!==l?y>1&&(p+=Math.abs(y-l),l=y):l=0}g.data.length>0&&(d/=g.data.lLine truncated
/*! Hammer.JS - v2.0.7 - 2016-04-22
* http://hammerjs.github.io/
*
* Copyright (c) 2016 Jorik Tangelder;
* Licensed under the MIT license */!function(i,o,a,s){"use strict";var u,c=["","webkit","Moz","MS","ms","o"],l=o.createElement("div"),h=Math.round,f=Math.abs,p=Date.now;function d(t,e,r){return setTimeout(b(t,r),e)}function A(t,e,r){return!!Array.isArray(t)&&(g(t,r[e],r),!0)}function g(t,e,r){var n;if(t)if(t.forEach)t.forEach(e,r);else if(void 0!==t.length)for(n=0;n<t.length;)e.call(r,t[n],n,t),n++;else for(n in t)t.hasOwnProperty(n)&&e.call(r,t[n],n,t)}function m(t,e,r){var n="DEPRECATED METHOD: "+e+"\n"+r+" AT \n";return function(){var e=new Error("get-stack-trace"),r=e&&e.stack?e.stack.replace(/^[^\(]+?[\n$]/gm,"").replace(/^\s+at\s+/gm,"").replace(/^Object.<anonymous>\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",o=i.console&&(i.console.warn||i.console.log);return o&&o.call(i.console,n,r),t.apply(this,arguments)}}u="function"!=typeof Object.assign?function(t){if(null==t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),r=1;r<arguments.length;r++){var n=arguments[r];if(null!=n)for(var i in n)n.hasOwnProperty(i)&&(e[i]=n[i])}return e}:Object.assign;var y=m((function(t,e,r){for(var n=Object.keys(e),i=0;i<n.length;)(!r||r&&void 0===t[n[i]])&&(t[n[i]]=e[n[i]]),i++;return t}),"extend","Use `assign`."),v=m((function(t,e){return y(t,e,!0)}),"merge","Use `assign`.");function _(t,e,r){var n,i=e.prototype;(n=t.prototype=Object.create(i)).constructor=t,n._super=i,r&&u(n,r)}function b(t,e){return function(){return t.apply(e,arguments)}}function x(t,e){return"function"==typeof t?t.apply(e&&e[0]||void 0,e):t}function w(t,e){return void 0===t?e:t}function E(t,e,r){g(P(e),(function(e){t.addEventListener(e,r,!1)}))}function S(t,e,r){g(P(e),(function(e){t.removeEventListener(e,r,!1)}))}function O(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1}function T(t,e){return t.indexOf(e)>-1}function P(t){return t.trim().split(/\s+/g)}function M(t,e,r){if(t.indexOf&&!r)return t.indexOf(e);for(var n=0;n<t.length;){if(r&&t[n][r]==e||!r&&t[n]===e)return n;n++}return-1}function C(t){return Array.prototype.slice.call(t,0)}function I(t,e,r){for(var n=[],i=[],o=0;o<t.length;){var a=e?t[o][e]:t[o];M(i,a)<0&&n.push(t[o]),i[o]=a,o++}return r&&(n=e?n.sort((function(t,r){return t[e]>r[e]})):n.sort()),n}function k(t,e){for(var r,n,i=e[0].toUpperCase()+e.slice(1),o=0;o<c.length;){if((n=(r=c[o])?r+i:e)in t)return n;o++}}var R=1;function L(t){var e=t.ownerDocument||t;return e.defaultView||e.parentWindow||i}var j="ontouchstart"in i,D=void 0!==k(i,"PointerEvent"),B=j&&/mobile|tablet|ip(ad|hone|od)|android/i.test(navigator.userAgent),F=["x","y"],z=["clientX","clientY"];function N(t,e){var r=this;this.manager=t,this.callback=e,this.element=t.element,this.target=t.options.inputTarget,this.domHandler=function(e){x(t.options.enable,[t])&&r.handler(e)},this.init()}function U(t,e,r){var n=r.pointers.length,i=r.changedPointers.length,o=1&e&&n-i==0,a=12&e&&n-i==0;r.isFirst=!!o,r.isFinal=!!a,o&&(t.session={}),r.eventType=e,function(t,e){var r=t.session,n=e.pointers,i=n.length;r.firstInput||(r.firstInput=V(e));i>1&&!r.firstMultiple?r.firstMultiple=V(e):1===i&&(r.firstMultiple=!1);var o=r.firstInput,a=r.firstMultiple,s=a?a.center:o.center,u=e.center=G(n);e.timeStamp=p(),e.deltaTime=e.timeStamp-o.timeStamp,e.angle=Z(s,u),e.distance=q(s,u),function(t,e){var r=e.center,n=t.offsetDelta||{},i=t.prevDelta||{},o=t.prevInput||{};1!==e.eventType&&4!==o.eventType||(i=t.prevDelta={x:o.deltaX||0,y:o.deltaY||0},n=t.offsetDelta={x:r.x,y:r.y});e.deltaX=i.x+(r.x-n.x),e.deltaY=i.y+(r.y-n.y)}(r,e),e.offsetDirection=H(e.deltaX,e.deltaY);var c=W(e.deltaTime,e.deltaX,e.deltaY);e.overallVelocityX=c.x,e.overallVelocityY=c.y,e.overallVelocity=f(c.x)>f(c.y)?c.x:c.y,e.scale=a?(l=a.pointers,h=n,q(h[0],h[1],z)/q(l[0],l[1],z)):1,e.rotation=a?function(t,e){return Z(e[1],e[0],z)+Z(t[1],t[0],z)}(a.pointers,n):0,e.maxPointers=r.prevInput?e.pointers.length>r.prevInput.maxPointers?e.pointers.length:r.prevInput.maxPointers:e.pointers.length,function(t,e){var r,n,i,o,a=t.lastInterval||e,s=e.timeStamp-a.timeStamp;if(8!=e.eventType&&(s>25||void 0===a.velocity)){var u=e.deltaX-a.deltaX,c=e.deltaY-a.deltaY,l=W(s,u,c);n=l.x,i=l.y,r=f(l.x)>f(l.y)?l.x:l.y,o=H(u,c),t.lastInterval=e}else r=a.velocity,n=a.velocityX,i=a.velocityY,o=a.direction;e.velocity=r,e.velocityX=n,e.velocityY=i,e.direction=o}(r,e);var l,h;var d=t.element;O(e.srcEvent.target,d)&&(d=e.srcEvent.target);e.target=d}(t,r),t.emit("hammer.input",r),t.recognize(r),t.session.prevInput=r}function V(t){for(var e=[],r=0;r<t.pointers.length;)e[r]={clientX:h(t.pointers[r].clientX),clientY:h(t.pointers[r].clientY)},r++;return{timeStamp:p(),pointers:e,center:G(e),deltaX:t.deltaX,deltaY:t.deltaY}}function G(t){var e=t.length;if(1===e)return{x:h(t[0].clientX),y:h(t[0].clientY)};for(var r=0,n=0,i=0;i<e;)r+=t[i].clientX,n+=t[i].clientY,i++;return{x:h(r/e),y:h(n/e)}}function W(t,e,r){return{x:e/t||0,y:r/t||0}}function H(t,e){return t===e?1:f(t)>=f(e)?t<0?2:4:e<0?8:16}function q(t,e,r){r||(r=F);var n=e[r[0]]-t[rLine truncated
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/defineProperty.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/getPrototypeOf.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/classCallCheck.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/createClass.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/esm/defineProperty.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/inherits.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/possibleConstructorReturn.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/gltools/dist/esm/utils/log.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/gltools/dist/esm/utils/device-pixels.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/gltools/dist/esm/context/context.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/gltools/dist/esm/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/get.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/webgl/dist/esm/utils/assert.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/slicedToArray.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/math.gl/dist/esm/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/gl-matrix/esm/common.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/core/dist/esm/lib/common.js","webpack:///../core/src/lib/constants.js","webpack:///../core/src/effects/lighting/point-light.js","webpack:///../core/src/effects/lighting/camera-light.js","webpack:///../core/src/effects/lighting/suncalc.js","webpack:///../core/src/effects/lighting/sun-light.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@luma.gl/engine/dist/esm/utils/clip-space.js","webpack:///../core/src/passes/screen-pass.js","webpack:///../core/src/effects/post-process-effect.js","webpack:///../core/src/viewports/globe-viewport.js","webpack:///../core/src/controllers/first-person-controller.js","webpack:///../core/src/views/first-person-view.js","webpack:///../core/src/controllers/orbit-controller.js","webpack:///../core/src/views/orbit-view.js","webpack:///../core/src/controllers/orthographic-controller.js","webpack:///../core/src/views/orthographic-view.js","webpack:///../core/src/controllers/globe-controller.js","webpack:///../core/src/views/globe-view.js","webpack:///../core/src/transitions/viewport-fly-to-interpolator.js","webpack:///../core/src/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/gl-matrix/esm/mat4.js","webpack:///../core/src/utils/log.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@loaders.gl/core/dist/esm/javascript-utils/is-type.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@loaders.gl/loader-utils/dist/esm/lib/path-utils/path.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@loaders.gl/loader-utils/dist/esm/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/gl-matrix/esm/vec3.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/math-utils.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/assert.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/web-mercator-utils.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/fit-bounds.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/get-bounds.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/web-mercator-viewport.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/normalize-viewport-props.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/fly-to-viewport.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/web-mercator/dist/esm/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/core/dist/esm/lib/validators.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@babel/runtime/helpers/typeof.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/@math.gl/core/dist/esm/index.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/probe.gl/env.js","webpack:////Users/andrewduberstein/Desktop/deck.gl/node_modules/gLine truncated
@@ -0,0 +1,38 @@
settings = None
class Settings:
"""Global settings for pydeck
Parameters
----------
custom_libraries : list
List of dictionaries of the format {'libraryName': 'LibraryName', 'resouceUri': 'deck.gl class URL'}.
For example, if there was a custom deck.gl Layer classed `TagmapLayer`
bundled for distribution at the path `https://demourl.libpath/bundle.js`,
one could load it into pydeck by doing the following:
```
pydeck.settings.custom_libraries = [
{
'libraryName': 'tagmapLibrary',
'resourceUri': 'https://demourl.libpath/bundle.js'
}
]
layer = pydeck.Layer(
'TagmapLayer', # Assumes that tagmapLibrary exports TagmapLayer
# <... kwargs here ...>
)
```
"""
def __init__(self, custom_libraries: list = None):
assert not settings, "Cannot instantiate more than one Settings object"
self.custom_libraries = custom_libraries or []
def register_library(self, name, uri):
self.custom_libraries.append({"libraryName": name, "uri": uri})
if not settings:
settings = Settings()
@@ -0,0 +1,2 @@
from .string import String # noqa
from .image import Image # noqa
@@ -0,0 +1,7 @@
from abc import abstractmethod, ABC
class PydeckType(ABC):
@abstractmethod
def __init__(self):
pass
@@ -0,0 +1,62 @@
import base64
import os
import pathlib
import re
from pydeck.types import String
from pydeck.types.base import PydeckType
# See https://github.com/django/django/blob/stable/1.3.x/django/core/validators.py#L45
valid_url_regex = re.compile(
r"^(?:http|ftp)s?://"
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|"
r"localhost|"
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
r"(?::\d+)?"
r"(?:/?|[/?]\S+)$",
re.IGNORECASE,
)
valid_image_regex = re.compile(
r".(gif|jpe?g|tiff?|png|webp|bmp)$",
re.IGNORECASE,
)
def get_encoding(path: str) -> str:
extension = pathlib.Path(path).suffix.replace(".", "")
return f"data:image/{extension};base64,"
class Image(PydeckType):
"""Indicate an image for pydeck
Parameters
----------
path : str
Path to image (either remote or local)
"""
def __init__(self, path: str):
if not self.validate(path):
raise ValueError(f"{path} is not contain a valid image path")
self.path = path
self.is_local = not valid_url_regex.search(self.path)
def __repr__(self):
if self.is_local:
with open(os.path.expanduser(self.path), "rb") as img_file:
encoded_string = get_encoding(self.path) + base64.b64encode(img_file.read()).decode("utf-8")
return repr(String(encoded_string, quote_type=""))
else:
return self.path
def __eq__(self, other):
return str(self) == str(other)
@staticmethod
def validate(path):
# Necessary-but-not-sufficient checks for being a valid image for @deck.gl/json
return any((valid_image_regex.search(path), valid_url_regex.search(path), path.startswith("data/image")))
@@ -0,0 +1,27 @@
from functools import total_ordering
from .base import PydeckType
@total_ordering
class String(PydeckType):
"""Indicate a string value in pydeck
Parameters
----------
value : str
Value of the string
"""
def __init__(self, s: str, quote_type: str = ""):
self.value = f"{quote_type}{s}{quote_type}"
def __lt__(self, other):
return str(self) < str(other)
def __eq__(self, other):
return str(self) == str(other)
def __repr__(self):
return self.value
@@ -0,0 +1 @@
from .widget import DeckGLWidget # noqa
@@ -0,0 +1,14 @@
#!/usr/bin/env python
# coding: utf-8
from ..frontend_semver import DECKGL_SEMVER
"""
Information about the frontend package of the widget.
"""
# module_name is the name of the NPM package for the widget
module_name = "@deck.gl/jupyter-widget"
# module_version is the current version of the module of the JS portion of the widget
# It appears to be important only for JupyterLab and ignored for Jupyter Notebooks
module_version = DECKGL_SEMVER
@@ -0,0 +1,34 @@
import asyncio
class Timer:
def __init__(self, timeout, callback):
self._timeout = timeout
self._callback = callback
self._task = asyncio.ensure_future(self._job())
async def _job(self):
await asyncio.sleep(self._timeout)
self._callback()
def cancel(self):
self._task.cancel()
def debounce(wait):
def decorator(fn):
timer = None
def debounced(*args, **kwargs):
nonlocal timer
def call_it():
fn(*args, **kwargs)
if timer is not None:
timer.cancel()
timer = Timer(wait, call_it)
return debounced
return decorator
@@ -0,0 +1,125 @@
from ast import literal_eval
import json
from ipywidgets import register, CallbackDispatcher, DOMWidget
from traitlets import Any, Bool, Int, Unicode
from ..data_utils.binary_transfer import data_buffer_serialization
from ._frontend import module_name, module_version
from .debounce import debounce
def store_selection(widget_instance, payload):
"""Callback for storing data on click"""
try:
if payload.get("data") and payload["data"].get("object"):
datum = payload["data"]["object"]
widget_instance.selected_data.append(datum)
else:
widget_instance.selected_data = []
except Exception as e:
widget_instance.handler_exception = e
@register
class DeckGLWidget(DOMWidget):
"""
Jupyter environment widget that takes JSON and
renders a deck.gl visualization based on provided properties.
You may set a Mapbox API key as an environment variable to use Mapbox maps in your visualization
Attributes
----------
json_input : str, default ''
JSON as a string meant for reading into deck.gl JSON API
mapbox_key : str, default ''
API key for Mapbox map tiles
height : int, default 500
Height of Jupyter notebook cell, in pixels
width : int or str, default "100%"
Width of Jupyter notebook cell, in pixels or, if a string, a CSS width
tooltip : bool or dict of {str: str}, default True
See the ``Deck`` constructor.
google_maps_key : str, default ''
API key for Google Maps
selected_data : list of dict, default []
Data selected on click, if the pydeck Jupyter widget is enabled for server use
"""
_model_name = Unicode("JupyterTransportModel").tag(sync=True)
_model_module = Unicode(module_name).tag(sync=True)
_model_module_version = Unicode(module_version).tag(sync=True)
_view_name = Unicode("JupyterTransportView").tag(sync=True)
_view_module = Unicode(module_name).tag(sync=True)
_view_module_version = Unicode(module_version).tag(sync=True)
carto_key = Unicode("", allow_none=True).tag(sync=True)
mapbox_key = Unicode("", allow_none=True).tag(sync=True)
google_maps_key = Unicode("", allow_none=True).tag(sync=True)
json_input = Unicode("").tag(sync=True)
data_buffer = Any(default_value=None, allow_none=True).tag(
sync=True, **data_buffer_serialization
)
custom_libraries = Any(allow_none=True).tag(sync=True)
tooltip = Any(True).tag(sync=True)
height = Int(500).tag(sync=True)
width = Any("100%").tag(sync=True)
def __init__(self, **kwargs):
super(DeckGLWidget, self).__init__(**kwargs)
self._hover_handlers = CallbackDispatcher()
self._click_handlers = CallbackDispatcher()
self._resize_handlers = CallbackDispatcher()
self._view_state_handlers = CallbackDispatcher()
self._drag_handlers = CallbackDispatcher()
self._drag_start_handlers = CallbackDispatcher()
self._drag_end_handlers = CallbackDispatcher()
self.on_msg(self._handle_custom_msgs)
self.handler_exception = None
self.selected_data = []
self.on_click(store_selection)
def on_hover(self, callback, remove=False):
self._hover_handlers.register_callback(callback, remove=remove)
def on_resize(self, callback, remove=False):
self._resize_handlers.register_callback(callback, remove=remove)
def on_view_state_change(self, callback, debounce_seconds=0.2, remove=False):
callback = (
debounce(debounce_seconds)(callback) if debounce_seconds > 0 else callback
)
self._view_state_handlers.register_callback(callback, remove=remove)
def on_click(self, callback, remove=False):
self._click_handlers.register_callback(callback, remove=remove)
def on_drag_start(self, callback, remove=False):
self._drag_start_handlers.register_callback(callback, remove=remove)
def on_drag(self, callback, remove=False):
self._drag_handlers.register_callback(callback, remove=remove)
def on_drag_end(self, callback, remove=False):
self._drag_end_handlers.register_callback(callback, remove=remove)
def _handle_custom_msgs(self, _, content, buffers=None):
content = json.loads(content)
event_type = content.get("type", "")
if event_type == "deck-hover-event":
self._hover_handlers(self, content)
elif event_type == "deck-resize-event":
self._resize_handlers(self, content)
elif event_type == "deck-view-state-change-event":
self._view_state_handlers(self, content)
elif event_type == "deck-click-event":
self._click_handlers(self, content)
elif event_type == "deck-drag-start-event":
self._drag_start_handlers(self, content)
elif event_type == "deck-drag-event":
self._drag_handlers(self, content)
elif event_type == "deck-drag-end-event":
self._drag_end_handlers(self, content)