here.inspector.keplergl.utils

Source code for here.inspector.keplergl.utils

Copyright (C) 2022-2023 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.

"""
Helper utilities for the Kepler-based inspector
"""

import json
from typing import Dict, Union

import geojson
import geopandas as gpd
from geopandas import GeoDataFrame
from here.inspector.utils import Features
from mapquadlib import HereQuad

def _geojson_with_longkey(tile_id: int) -> dict:
"""
Return GeoJSON geometry for given mapquad with its long key as parameter.

The quad's "long key" (an integer) will appear as "tile_id" in the
properties.
:param tile_id: Tile id
:returns: A dict representing a GeoJSON geometry (polygon) for the mapquad.
"""

quad = HereQuad.from_long_key(tile_id)
gj: dict = quad.bounding_box._geojson()
if gj:
fix_coordinates(gj)
feature = {"type": "Feature", "geometry": gj, "properties": {"tile_id": tile_id}}
return feature

[docs]
def fix_coordinates(geometry: Dict) -> Dict:
"""
fix_coordinates modifies any coordinate values of (180, 90) or (-180, -90)
in a geometry dict to (179.999, 89.999) or (-179.999, -89.999) respectively.

The function takes in a single argument:
:param geometry: A dictionary representing a geometry in GeoJSON format.
It should contain a 'coordinates' key with list of coordinates.

:returns: modified geometry dictionary.
"""
for i, coord in enumerate(geometry["coordinates"][0]):
if coord[0] == 180.0:
geometry["coordinates"][0][i][0] = 179.999
elif coord[0] == -180.0:
geometry["coordinates"][0][i][0] = -179.999
if coord[1] == 90.0:
geometry["coordinates"][0][i][1] = 89.999
elif coord[1] == -90.0:
geometry["coordinates"][0][i][1] = -89.999
return geometry

[docs]
def convert_to_2d_2(gdf: Union[Features, GeoDataFrame]) -> Union[Features, GeoDataFrame]:
"""
Convert the feature object from 3D to 2D
by removing the z coordinates from the geometry.
If a GeoDataFrame is passed, it will convert all
the geometries of all the features in the dataframe.
:param gdf: A Feature object or GeoDataFrame with geometry and coordinates in 3D.
:return: A Feature object or GeoDataFrame
with the same properties but in 2D by removing the z coordinates.
"""

Define a recursive function to convert nested coordinates to 2D

def convert_nested_coords_to_2d(coords):
for coord in coords:
if isinstance(coord[0], list):
convert_nested_coords_to_2d(coord)
else:
coord[0] = (
179.9999
if coord[0] == 180.0
else -179.9999
if coord[0] == -180.0
else coord[0]
)
coord[1] = (
89.9999 if coord[1] == 90.0 else -89.9999 if coord[1] == -90.0 else coord[1]
)
coord[:] = coord[:2]

def conversion(geom):

Check if the object is a Feature

if geom["type"] == "Feature":

Get the geometry of the feature

geometry = geom["geometry"]

Check if the geometry is a Point

if geometry["type"] == "Point":

Convert the coordinates to 2D

geometry["coordinates"] = geometry["coordinates"][:2]

Check if the geometry is a LineString or Polygon

elif geometry["type"] in ["LineString", "Polygon"]:

Convert the coordinates to 2D

convert_nested_coords_to_2d(geometry["coordinates"])

Check if the geometry is a MultiPoint, MultiLineString, or MultiPolygon

elif geometry["type"] in ["MultiPoint", "MultiLineString", "MultiPolygon"]:

Convert the coordinates to 2D

for subcoords in geometry["coordinates"]:
convert_nested_coords_to_2d(subcoords)

Check if the object is a FeatureCollection

elif geom["type"] == "FeatureCollection":

Loop through the features in the collection

for feature in geom["features"]:

Convert the feature to 2D

convert_to_2d_2(feature)
return geom

if isinstance(gdf, gpd.GeoDataFrame):
features = json.loads(gdf.to_json())
for feature in features["features"]:
conversion(feature["geometry"])
gdf.set_geometry(gpd.GeoDataFrame.from_features(features).geometry)
elif isinstance(gdf, (dict, geojson.feature.Feature, geojson.feature.FeatureCollection)):
geom = gdf
conversion(geom)
return gdf

[docs]
def process_dataframe(new_df_feature: Features):
"""
Convert the Feature object from GeoJSON format to a Python dictionary.
This function is useful when the add data method
doesn't accept FeatureCollection object.
:param new_df_feature: a Feature object in GeoJSON format.
:return: a Python dictionary containing the same
information as the input Feature object.
"""

Serialize the FeatureCollection to a JSON string

json_string = geojson.dumps(new_df_feature)

Parse the JSON string into a dictionary

new_json_string = json.loads(json_string)

return new_json_string

HEIGHT = 600