2022-05-23 00:16:32 +04:00

177 lines
4.7 KiB
Python

"""
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