mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-01 14:07:48 +00:00
first commit
This commit is contained in:
15
.venv/Lib/site-packages/pydeck/__init__.py
Normal file
15
.venv/Lib/site-packages/pydeck/__init__.py
Normal file
@ -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
|
1
.venv/Lib/site-packages/pydeck/_version.py
Normal file
1
.venv/Lib/site-packages/pydeck/_version.py
Normal file
@ -0,0 +1 @@
|
||||
__version__ = "0.7.1"
|
7
.venv/Lib/site-packages/pydeck/bindings/__init__.py
Normal file
7
.venv/Lib/site-packages/pydeck/bindings/__init__.py
Normal file
@ -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"
|
201
.venv/Lib/site-packages/pydeck/bindings/deck.py
Normal file
201
.venv/Lib/site-packages/pydeck/bindings/deck.py
Normal file
@ -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
|
100
.venv/Lib/site-packages/pydeck/bindings/json_tools.py
Normal file
100
.venv/Lib/site-packages/pydeck/bindings/json_tools.py
Normal file
@ -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)
|
172
.venv/Lib/site-packages/pydeck/bindings/layer.py
Normal file
172
.venv/Lib/site-packages/pydeck/bindings/layer.py
Normal file
@ -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)
|
36
.venv/Lib/site-packages/pydeck/bindings/light_settings.py
Normal file
36
.venv/Lib/site-packages/pydeck/bindings/light_settings.py
Normal file
@ -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
|
59
.venv/Lib/site-packages/pydeck/bindings/map_styles.py
Normal file
59
.venv/Lib/site-packages/pydeck/bindings/map_styles.py
Normal file
@ -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
|
33
.venv/Lib/site-packages/pydeck/bindings/view.py
Normal file
33
.venv/Lib/site-packages/pydeck/bindings/view.py
Normal file
@ -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)
|
42
.venv/Lib/site-packages/pydeck/bindings/view_state.py
Normal file
42
.venv/Lib/site-packages/pydeck/bindings/view_state.py
Normal file
@ -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)
|
3
.venv/Lib/site-packages/pydeck/data_utils/__init__.py
Normal file
3
.venv/Lib/site-packages/pydeck/data_utils/__init__.py
Normal file
@ -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
|
61
.venv/Lib/site-packages/pydeck/data_utils/binary_transfer.py
Normal file
61
.venv/Lib/site-packages/pydeck/data_utils/binary_transfer.py
Normal file
@ -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)
|
34
.venv/Lib/site-packages/pydeck/data_utils/color_scales.py
Normal file
34
.venv/Lib/site-packages/pydeck/data_utils/color_scales.py
Normal file
@ -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)])
|
24
.venv/Lib/site-packages/pydeck/data_utils/type_checking.py
Normal file
24
.venv/Lib/site-packages/pydeck/data_utils/type_checking.py
Normal file
@ -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
|
176
.venv/Lib/site-packages/pydeck/data_utils/viewport_helpers.py
Normal file
176
.venv/Lib/site-packages/pydeck/data_utils/viewport_helpers.py
Normal file
@ -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
|
1
.venv/Lib/site-packages/pydeck/exceptions/__init__.py
Normal file
1
.venv/Lib/site-packages/pydeck/exceptions/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .exceptions import BinaryTransportException, PydeckException # noqa,
|
6
.venv/Lib/site-packages/pydeck/exceptions/exceptions.py
Normal file
6
.venv/Lib/site-packages/pydeck/exceptions/exceptions.py
Normal file
@ -0,0 +1,6 @@
|
||||
class PydeckException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class BinaryTransportException(PydeckException):
|
||||
pass
|
1
.venv/Lib/site-packages/pydeck/frontend_semver.py
Normal file
1
.venv/Lib/site-packages/pydeck/frontend_semver.py
Normal file
@ -0,0 +1 @@
|
||||
DECKGL_SEMVER = "~8.5.*"
|
0
.venv/Lib/site-packages/pydeck/io/__init__.py
Normal file
0
.venv/Lib/site-packages/pydeck/io/__init__.py
Normal file
160
.venv/Lib/site-packages/pydeck/io/html.py
Normal file
160
.venv/Lib/site-packages/pydeck/io/html.py
Normal 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)
|
41
.venv/Lib/site-packages/pydeck/io/templates/index.j2
Normal file
41
.venv/Lib/site-packages/pydeck/io/templates/index.j2
Normal file
@ -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>
|
34
.venv/Lib/site-packages/pydeck/io/templates/style.j2
Normal file
34
.venv/Lib/site-packages/pydeck/io/templates/style.j2
Normal file
@ -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;
|
||||
}
|
11
.venv/Lib/site-packages/pydeck/nbextension/__init__.py
Normal file
11
.venv/Lib/site-packages/pydeck/nbextension/__init__.py
Normal file
@ -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"}
|
||||
]
|
@ -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() {}
|
||||
};
|
||||
});
|
19
.venv/Lib/site-packages/pydeck/nbextension/static/index.js
Normal file
19
.venv/Lib/site-packages/pydeck/nbextension/static/index.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
38
.venv/Lib/site-packages/pydeck/settings.py
Normal file
38
.venv/Lib/site-packages/pydeck/settings.py
Normal file
@ -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()
|
2
.venv/Lib/site-packages/pydeck/types/__init__.py
Normal file
2
.venv/Lib/site-packages/pydeck/types/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .string import String # noqa
|
||||
from .image import Image # noqa
|
7
.venv/Lib/site-packages/pydeck/types/base.py
Normal file
7
.venv/Lib/site-packages/pydeck/types/base.py
Normal file
@ -0,0 +1,7 @@
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
|
||||
class PydeckType(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
62
.venv/Lib/site-packages/pydeck/types/image.py
Normal file
62
.venv/Lib/site-packages/pydeck/types/image.py
Normal file
@ -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")))
|
27
.venv/Lib/site-packages/pydeck/types/string.py
Normal file
27
.venv/Lib/site-packages/pydeck/types/string.py
Normal file
@ -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
|
1
.venv/Lib/site-packages/pydeck/widget/__init__.py
Normal file
1
.venv/Lib/site-packages/pydeck/widget/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .widget import DeckGLWidget # noqa
|
14
.venv/Lib/site-packages/pydeck/widget/_frontend.py
Normal file
14
.venv/Lib/site-packages/pydeck/widget/_frontend.py
Normal file
@ -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
|
34
.venv/Lib/site-packages/pydeck/widget/debounce.py
Normal file
34
.venv/Lib/site-packages/pydeck/widget/debounce.py
Normal file
@ -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
|
125
.venv/Lib/site-packages/pydeck/widget/widget.py
Normal file
125
.venv/Lib/site-packages/pydeck/widget/widget.py
Normal file
@ -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)
|
Reference in New Issue
Block a user