mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-04-22 02:23:48 +00:00
173 lines
6.6 KiB
Python
173 lines
6.6 KiB
Python
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)
|