here.inspector.utils
Source code for here.inspector.utils
Copyright (C) 2019-2022 HERE Global B.V. and its affiliate(s).
All rights reserved.
This software and other materials contain proprietary information
controlled by HERE and are protected by applicable copyright legislation.
Any use and utilization of this software and other materials and
disclosure to any third parties is conditional upon having a separate
agreement with HERE for the access, use, utilization or disclosure of this
software. In the absence of such agreement, the use of the software is not
allowed.
"""
HERE inspector utilities.
"""
from dataclasses import dataclass, field
from math import asin, cos, log2, sin, sqrt
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
import geopandas as gpd
import pandas as pd
from geojson import factory, utils
from here.inspector.styles import Color, Theme
from shapely.geometry.base import BaseGeometry
[docs]
@dataclass
class Options:
"""Global options for any inspector."""
#: styles are assigned automatically picked from this list of colors, when not specified.
default_colors: List[Color] = field(default_factory=lambda: list(Color))
#: the default inspector theme
default_theme: Theme = Theme.LIGHT_MAP
#: API key for the HERE platform to use HERE-provided background maps
api_key: Optional[str] = None
#: Name of the backend class to use for visualisation
inspector_class: Optional[Type] = None
options = Options()
Tiles = Union[pd.Series, Iterable[int]]
Features = Union[
gpd.GeoSeries, # geometries with ids (index)
gpd.GeoDataFrame, # geometries with ids (index) and attributes
BaseGeometry, # single geometries
Iterable[BaseGeometry], # geometries
Iterable[Tuple[BaseGeometry, Dict[str, Any]]], # geometries with ids and attributes
Dict[str, Any], # one single GeoJSON FeatureCollection
Iterable[Dict[str, Any]], # collection of GeoJSON Feature
]
Bounds = Tuple[Tuple[float, float], Tuple[float, float]]
[docs]
def bounds_obj(geojson_obj: dict) -> Bounds:
"""Calculate the bounds of given GeoJSON object.
:param geojson_obj: The GeoJSON object (dict).
:return: Bounds of the GeoJSON object in this format:
((south, west), (north, east)).
:raises ValueError: in case the GeoJSON object has no coordinates
"""
coords = list(utils.coords(geojson_obj))
if not coords:
raise ValueError("No coordinates to calculate the bounds")
pos can be (lon, lat) or (lon, lat, alt)
south = min(pos[1] for pos in coords)
north = max(pos[1] for pos in coords)
west = min(pos[0] for pos in coords)
east = max(pos[0] for pos in coords)
bounds = ((south, west), (north, east))
return bounds
[docs]
def bounds_gdf(gdf: gpd.GeoDataFrame) -> Bounds:
"""Calculate the bounds of one GeoDataFrame.
:param gdf: The GeoDataFrame.
:return: Bounds of the GeoDataFrame object in this format:
((south, west), (north, east)).
:raises ValueError: in case GeoDataFrame is empty
"""
if gdf.empty:
raise ValueError("GeoDataFrame is empty.")
tb = gdf.geometry.total_bounds
minx = tb[0]
miny = tb[1]
maxx = tb[2]
maxy = tb[3]
return (miny, minx), (maxy, maxx)
[docs]
def merge_bounds(bounds: List[Bounds]) -> Bounds:
"""
Merge multiple bounds together into a single one that covers them all.
:param bounds: the list of bounds to merge
:return: overall bounds, None if the input list is empty
"""
south = min(b[0][0] for b in bounds)
west = min(b[0][1] for b in bounds)
north = max(b[1][0] for b in bounds)
east = max(b[1][1] for b in bounds)
return (south, west), (north, east)
[docs]
def center_bounds(bounds: Bounds) -> Tuple[float, float]:
"""The center of the bound area, for map display, latitude and longitude"""
return (bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2
[docs]
def zoom_bounds(bounds: Bounds, min_zoom: float) -> float:
"""The approximate zoom level to visualize the bound area, for map display"""
sx = abs(bounds[0][1] - bounds[1][1])
sy = abs(bounds[0][0] - bounds[1][0])
TODO: these should depend on the viewport, so far zoom level is approximate
if sx != 0 and sy != 0:
zx = log2(360 / sx)
zy = log2(180 / sy)
avg = (min(zx, zy) + max(zx, zy)) / 2
zoom_level = max(avg, min_zoom) if bounds else min_zoom
return zoom_level
else:
haversine formula
a = sin(sx / 2) 2 + cos(bounds[1][1]) * cos(bounds[0][1]) * sin(sy / 2) 2
c = 2 * asin(sqrt(a))
distance = 6371 * c
equator length = 40000000
zoom_level = int(256 distance / 40000000 2) ^ 4
return zoom_level
[docs]
def is_geojson(d: dict) -> bool:
"""Check if the dictionary contains a valid GeoJSON object"""
if "type" in d:
try:
Check if the type is supported by the GeoJSON factory
getattr(factory, d["type"])
return True
except AttributeError:
return False
else:
return False