Skip to content

Commit

Permalink
Improve csv_to_vector and lonboard module (#864)
Browse files Browse the repository at this point in the history
* Improve csv_to_vector and lonboard module

* Add docstrings
  • Loading branch information
giswqs authored Aug 5, 2024
1 parent 6c5a765 commit 1d201ce
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 39 deletions.
34 changes: 28 additions & 6 deletions leafmap/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,13 +900,23 @@ def csv_to_geojson(
f.write(json.dumps(geojson))


def csv_to_gdf(in_csv, latitude="latitude", longitude="longitude", encoding="utf-8"):
def csv_to_gdf(
in_csv,
latitude="latitude",
longitude="longitude",
geometry=None,
crs="EPSG:4326",
encoding="utf-8",
**kwargs,
):
"""Creates points for a CSV file and converts them to a GeoDataFrame.
Args:
in_csv (str): The file path to the input CSV file.
latitude (str, optional): The name of the column containing latitude coordinates. Defaults to "latitude".
longitude (str, optional): The name of the column containing longitude coordinates. Defaults to "longitude".
geometry (str, optional): The name of the column containing geometry. Defaults to None.
crs (str, optional): The coordinate reference system. Defaults to "EPSG:4326".
encoding (str, optional): The encoding of characters. Defaults to "utf-8".
Returns:
Expand All @@ -916,14 +926,21 @@ def csv_to_gdf(in_csv, latitude="latitude", longitude="longitude", encoding="utf
check_package(name="geopandas", URL="https://geopandas.org")

import geopandas as gpd
import pandas as pd
from shapely import wkt

out_dir = os.getcwd()

out_geojson = os.path.join(out_dir, random_string() + ".geojson")
csv_to_geojson(in_csv, out_geojson, latitude, longitude, encoding)
if geometry is None:
out_geojson = os.path.join(out_dir, random_string() + ".geojson")
csv_to_geojson(in_csv, out_geojson, latitude, longitude, encoding=encoding)

gdf = gpd.read_file(out_geojson)
os.remove(out_geojson)
gdf = gpd.read_file(out_geojson)
os.remove(out_geojson)
else:
df = pd.read_csv(in_csv, encoding=encoding)
df["geometry"] = df[geometry].apply(wkt.loads)
gdf = gpd.GeoDataFrame(df, geometry="geometry", crs=crs, **kwargs)
return gdf


Expand All @@ -932,6 +949,8 @@ def csv_to_vector(
output,
latitude="latitude",
longitude="longitude",
geometry=None,
crs="EPSG:4326",
encoding="utf-8",
**kwargs,
):
Expand All @@ -942,10 +961,13 @@ def csv_to_vector(
output (str): The file path to the output vector dataset.
latitude (str, optional): The name of the column containing latitude coordinates. Defaults to "latitude".
longitude (str, optional): The name of the column containing longitude coordinates. Defaults to "longitude".
geometry (str, optional): The name of the column containing geometry. Defaults to None.
crs (str, optional): The coordinate reference system. Defaults to "EPSG:4326".
encoding (str, optional): The encoding of characters. Defaults to "utf-8".
**kwargs: Additional keyword arguments to pass to gdf.to_file().
"""
gdf = csv_to_gdf(in_csv, latitude, longitude, encoding)
gdf = csv_to_gdf(in_csv, latitude, longitude, geometry, crs, encoding)
gdf.to_file(output, **kwargs)


Expand Down
201 changes: 168 additions & 33 deletions leafmap/deckgl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from box import Box

from typing import Union, List, Dict, Optional, Tuple, Any
from .basemaps import xyz_to_leaflet
from .common import *
from .map_widgets import *
from .plot import *
Expand All @@ -12,6 +15,8 @@
"lonboard needs to be installed to use this module. Use 'pip install lonboard' to install the package."
)

basemaps = Box(xyz_to_leaflet(), frozen_box=True)


class Map(lonboard.Map):
"""The Map class inherits lonboard.Map.
Expand Down Expand Up @@ -67,6 +72,8 @@ def add_gdf(
color_map: Optional[Union[str, Dict]] = None,
color_k: Optional[int] = 5,
color_args: dict = {},
alpha: Optional[float] = 1.0,
rescale: bool = True,
zoom: Optional[float] = 10.0,
**kwargs: Any,
) -> None:
Expand Down Expand Up @@ -97,6 +104,7 @@ def add_gdf(
"""

from lonboard import ScatterplotLayer, PathLayer, SolidPolygonLayer
import matplotlib.pyplot as plt

geom_type = gdf.geometry.iloc[0].geom_type
kwargs["pickable"] = pickable
Expand All @@ -106,18 +114,14 @@ def add_gdf(
kwargs["get_radius"] = 10
if color_column is not None:
if isinstance(color_map, str):
kwargs["get_fill_color"] = assign_continuous_colors(
gdf,
color_column,
color_map,
scheme=color_scheme,
k=color_k,
**color_args,
kwargs["get_fill_color"] = apply_continuous_cmap(
gdf[color_column], color_map, alpha, rescale
)
elif isinstance(color_map, dict):
kwargs["get_fill_color"] = assign_discrete_colors(
gdf, color_column, color_map, to_rgb=True, return_type="array"
kwargs["get_fill_color"] = apply_categorical_cmap(
gdf[color_column], color_map, alpha
)

if "get_fill_color" not in kwargs:
kwargs["get_fill_color"] = [255, 0, 0, 180]
layer = ScatterplotLayer.from_geopandas(gdf, **kwargs)
Expand All @@ -126,33 +130,24 @@ def add_gdf(
kwargs["get_width"] = 5
if color_column is not None:
if isinstance(color_map, str):
kwargs["get_color"] = assign_continuous_colors(
gdf,
color_column,
color_map,
scheme=color_scheme,
k=color_k,
**color_args,
cmap = plt.get_cmap(color_map)
kwargs["get_color"] = apply_continuous_cmap(
gdf[color_column], cmap, alpha, rescale
)
elif isinstance(color_map, dict):
kwargs["get_color"] = assign_discrete_colors(
gdf, color_column, color_map, to_rgb=True, return_type="array"
kwargs["get_color"] = apply_categorical_cmap(
gdf[color_column], color_map, alpha
)
layer = PathLayer.from_geopandas(gdf, **kwargs)
elif geom_type in ["Polygon", "MultiPolygon"]:
if color_column is not None:
if isinstance(color_map, str):
kwargs["get_fill_color"] = assign_continuous_colors(
gdf,
color_column,
color_map,
scheme=color_scheme,
k=color_k,
**color_args,
kwargs["get_fill_color"] = apply_continuous_cmap(
gdf[color_column], color_map, alpha, rescale
)
elif isinstance(color_map, dict):
kwargs["get_fill_color"] = assign_discrete_colors(
gdf, color_column, color_map, to_rgb=True, return_type="array"
kwargs["get_fill_color"] = apply_categorical_cmap(
gdf[color_column], color_map, alpha
)
if "get_fill_color" not in kwargs:
kwargs["get_fill_color"] = [0, 0, 255, 128]
Expand Down Expand Up @@ -254,18 +249,37 @@ def add_layer(
None
"""

from lonboard import ScatterplotLayer, PathLayer, SolidPolygonLayer
from lonboard import (
BitmapLayer,
BitmapTileLayer,
HeatmapLayer,
PathLayer,
PointCloudLayer,
PolygonLayer,
ScatterplotLayer,
SolidPolygonLayer,
)

if type(layer) in [ScatterplotLayer, PathLayer, SolidPolygonLayer]:
if type(layer) in [
BitmapLayer,
BitmapTileLayer,
HeatmapLayer,
ScatterplotLayer,
PathLayer,
PointCloudLayer,
PolygonLayer,
SolidPolygonLayer,
]:
self.layers = self.layers + [layer]

if zoom_to_layer:
from lonboard._viewport import compute_view

try:
self.view_state = compute_view([self.layers[-1].table])
except Exception as e:
print(e)
if hasattr(layer, "table"):
try:
self.view_state = compute_view([self.layers[-1].table])
except Exception as e:
print(e)
else:
self.add_vector(
layer, zoom_to_layer=zoom_to_layer, pickable=pickable, **kwargs
Expand Down Expand Up @@ -317,3 +331,124 @@ def to_streamlit(

except Exception as e:
raise e

def add_basemap(self, basemap="HYBRID", visible=True, **kwargs) -> None:
"""Adds a basemap to the map.
Args:
basemap (str, optional): Can be one of string from basemaps. Defaults to 'HYBRID'.
visible (bool, optional): Whether the basemap is visible or not. Defaults to True.
**kwargs: Keyword arguments for the TileLayer.
"""
import xyzservices

try:

map_dict = {
"ROADMAP": "Google Maps",
"SATELLITE": "Google Satellite",
"TERRAIN": "Google Terrain",
"HYBRID": "Google Hybrid",
}

if isinstance(basemap, str):
if basemap.upper() in map_dict:
tile = get_google_map(basemap.upper())

layer = lonboard.BitmapTileLayer(
data=tile.url,
min_zoom=tile.min_zoom,
max_zoom=tile.max_zoom,
visible=visible,
**kwargs,
)

self.add_layer(layer)
return

if isinstance(basemap, xyzservices.TileProvider):
url = basemap.build_url()
if "max_zoom" in basemap.keys():
max_zoom = basemap["max_zoom"]
else:
max_zoom = 22
layer = lonboard.BitmapTileLayer(
data=url,
min_zoom=tile.min_zoom,
max_zoom=max_zoom,
visible=visible,
**kwargs,
)

self.add_layer(layer)
elif basemap in basemaps and basemaps[basemap].name:
tile = basemaps[basemap]
layer = lonboard.BitmapTileLayer(
data=tile.url,
min_zoom=tile.get("min_zoom", 0),
max_zoom=tile.get("max_zoom", 24),
visible=visible,
**kwargs,
)
self.add_layer(layer)
else:
print(
"Basemap can only be one of the following:\n {}".format(
"\n ".join(basemaps.keys())
)
)

except Exception as e:
raise ValueError(
"Basemap can only be one of the following:\n {}".format(
"\n ".join(basemaps.keys())
)
)


def apply_continuous_cmap(values, cmap, alpha=None, rescale=True, **kwargs):
"""
Apply a continuous colormap to a set of values.
This function rescales the input values to the range [0, 1] if `rescale` is True,
and then applies the specified colormap.
Args:
values (array-like): The input values to which the colormap will be applied.
cmap (str or Colormap): The colormap to apply. Can be a string name of a matplotlib colormap or a Colormap object.
alpha (float, optional): The alpha transparency to apply to the colormap. Defaults to None.
rescale (bool, optional): If True, rescales the input values to the range [0, 1]. Defaults to True.
**kwargs: Additional keyword arguments to pass to the colormap function.
Returns:
array: The colors mapped to the input values.
"""
import numpy as np
import matplotlib.pyplot as plt

if rescale:
values = np.array(values)
values = (values - values.min()) / (values.max() - values.min())

if isinstance(cmap, str):
cmap = plt.get_cmap(cmap)

return lonboard.colormap.apply_continuous_cmap(values, cmap, alpha=alpha, **kwargs)


def apply_categorical_cmap(values, cmap, alpha=None, **kwargs):
"""
Apply a categorical colormap to a set of values.
This function applies a specified categorical colormap to the input values.
Args:
values (array-like): The input values to which the colormap will be applied.
cmap (str or Colormap): The colormap to apply. Can be a string name of a matplotlib colormap or a Colormap object.
alpha (float, optional): The alpha transparency to apply to the colormap. Defaults to None.
**kwargs: Additional keyword arguments to pass to the colormap function.
Returns:
array: The colors mapped to the input values.
"""
return lonboard.colormap.apply_categorical_cmap(values, cmap, alpha=alpha, **kwargs)

0 comments on commit 1d201ce

Please sign in to comment.