Source code for h3pandas.h3pandas

from typing import Union, Callable, Sequence, Any
import warnings

# Literal is not supported by Python <3.8
try:
    from typing import Literal
except ImportError:
    from typing_extensions import Literal

import numpy as np
import shapely
import pandas as pd
import geopandas as gpd

from h3 import h3
from pandas.core.frame import DataFrame
from geopandas.geodataframe import GeoDataFrame

from .const import COLUMN_H3_POLYFILL
from .util.decorator import catch_invalid_h3_address, doc_standard
from .util.functools import wrapped_partial
from .util.shapely import polyfill

AnyDataFrame = Union[DataFrame, GeoDataFrame]


[docs]@pd.api.extensions.register_dataframe_accessor("h3") class H3Accessor: def __init__(self, df: DataFrame): self._df = df # H3 API # These methods simply mirror the H3 API and apply H3 functions to all rows
[docs] def geo_to_h3( self, resolution: int, lat_col: str = "lat", lng_col: str = "lng", set_index: bool = True, ) -> AnyDataFrame: """Adds H3 index to (Geo)DataFrame. pd.DataFrame: uses `lat_col` and `lng_col` (default `lat` and `lng`) gpd.GeoDataFrame: uses `geometry` Assumes coordinates in epsg=4326. Parameters ---------- resolution : int H3 resolution lat_col : str Name of the latitude column (if used), default 'lat' lng_col : str Name of the longitude column (if used), default 'lng' set_index : bool If True, the columns with H3 addresses is set as index, default 'True' Returns ------- (Geo)DataFrame with H3 addresses added See Also -------- geo_to_h3_aggregate : Extended API method that aggregates points by H3 address Examples -------- >>> df = pd.DataFrame({'lat': [50, 51], 'lng':[14, 15]}) >>> df.h3.geo_to_h3(8) lat lng h3_08 881e309739fffff 50 14 881e2659c3fffff 51 15 >>> df.h3.geo_to_h3(8, set_index=False) lat lng h3_08 0 50 14 881e309739fffff 1 51 15 881e2659c3fffff >>> gdf = gpd.GeoDataFrame({'val': [5, 1]}, >>> geometry=gpd.points_from_xy(x=[14, 15], y=(50, 51))) >>> gdf.h3.geo_to_h3(8) val geometry h3_08 881e309739fffff 5 POINT (14.00000 50.00000) 881e2659c3fffff 1 POINT (15.00000 51.00000) """ if isinstance(self._df, gpd.GeoDataFrame): lngs = self._df.geometry.x lats = self._df.geometry.y else: lngs = self._df[lng_col] lats = self._df[lat_col] h3addresses = [ h3.geo_to_h3(lat, lng, resolution) for lat, lng in zip(lats, lngs) ] colname = self._format_resolution(resolution) assign_arg = {colname: h3addresses} df = self._df.assign(**assign_arg) if set_index: return df.set_index(colname) return df
[docs] def h3_to_geo(self) -> GeoDataFrame: """Add `geometry` with centroid of each H3 address to the DataFrame. Assumes H3 index. Returns ------- GeoDataFrame with Point geometry Raises ------ ValueError When an invalid H3 address is encountered See Also -------- h3_to_geo_boundary : Adds a hexagonal cell Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_to_geo() val geometry 881e309739fffff 5 POINT (14.00037 50.00055) 881e2659c3fffff 1 POINT (14.99715 51.00252) """ return self._apply_index_assign( h3.h3_to_geo, "geometry", lambda x: shapely.geometry.Point(reversed(x)), lambda x: gpd.GeoDataFrame(x, crs="epsg:4326"), )
[docs] def h3_to_geo_boundary(self) -> GeoDataFrame: """Add `geometry` with H3 hexagons to the DataFrame. Assumes H3 index. Returns ------- GeoDataFrame with H3 geometry Raises ------ ValueError When an invalid H3 address is encountered Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_to_geo_boundary() val geometry 881e309739fffff 5 POLYGON ((13.99527 50.00368, 13.99310 49.99929... 881e2659c3fffff 1 POLYGON ((14.99201 51.00565, 14.98973 51.00133... """ return self._apply_index_assign( wrapped_partial(h3.h3_to_geo_boundary, geo_json=True), "geometry", lambda x: shapely.geometry.Polygon(x), lambda x: gpd.GeoDataFrame(x, crs="epsg:4326"), )
[docs] @doc_standard("h3_resolution", "containing the resolution of each H3 address") def h3_get_resolution(self) -> AnyDataFrame: """ Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_get_resolution() val h3_resolution 881e309739fffff 5 8 881e2659c3fffff 1 8 """ return self._apply_index_assign(h3.h3_get_resolution, "h3_resolution")
[docs] @doc_standard("h3_base_cell", "containing the base cell of each H3 address") def h3_get_base_cell(self): """ Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_get_base_cell() val h3_base_cell 881e309739fffff 5 15 881e2659c3fffff 1 15 """ return self._apply_index_assign(h3.h3_get_base_cell, "h3_base_cell")
[docs] @doc_standard("h3_is_valid", "containing the validity of each H3 address") def h3_is_valid(self): """ Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, index=['881e309739fffff', 'INVALID']) >>> df.h3.h3_is_valid() val h3_is_valid 881e309739fffff 5 True INVALID 1 False """ return self._apply_index_assign(h3.h3_is_valid, "h3_is_valid")
[docs] @doc_standard( "h3_k_ring", "containing a list H3 addresses within a distance of `k`" ) def k_ring(self, k: int = 1, explode: bool = False) -> AnyDataFrame: """ Parameters ---------- k : int the distance from the origin H3 address. Default k = 1 explode : bool If True, will explode the resulting list vertically. All other columns' values are copied. Default: False See Also -------- k_ring_smoothing : Extended API method that distributes numeric values to the k-ring cells Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.k_ring(1) val h3_k_ring 881e309739fffff 5 [881e30973dfffff, 881e309703fffff, 881e309707f... 881e2659c3fffff 1 [881e2659ddfffff, 881e2659c3fffff, 881e2659cbf... >>> df.h3.k_ring(1, explode=True) val h3_k_ring 881e2659c3fffff 1 881e2659ddfffff 881e2659c3fffff 1 881e2659c3fffff 881e2659c3fffff 1 881e2659cbfffff 881e2659c3fffff 1 881e2659d5fffff 881e2659c3fffff 1 881e2659c7fffff 881e2659c3fffff 1 881e265989fffff 881e2659c3fffff 1 881e2659c1fffff 881e309739fffff 5 881e30973dfffff 881e309739fffff 5 881e309703fffff 881e309739fffff 5 881e309707fffff 881e309739fffff 5 881e30973bfffff 881e309739fffff 5 881e309715fffff 881e309739fffff 5 881e309739fffff 881e309739fffff 5 881e309731fffff """ func = wrapped_partial(h3.k_ring, k=k) column_name = "h3_k_ring" if explode: return self._apply_index_explode(func, column_name, list) return self._apply_index_assign(func, column_name, list)
[docs] @doc_standard( "h3_hex_ring", "containing a list H3 addresses forming a hollow hexagonal ring" "at a distance `k`", ) def hex_ring(self, k: int = 1, explode: bool = False) -> AnyDataFrame: """ Parameters ---------- k : int the distance from the origin H3 address. Default k = 1 explode : bool If True, will explode the resulting list vertically. All other columns' values are copied. Default: False Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.hex_ring(1) val h3_hex_ring 881e309739fffff 5 [881e30973dfffff, 881e309703fffff, 881e309707f... 881e2659c3fffff 1 [881e2659ddfffff, 881e2659cbfffff, 881e2659d5f... >>> df.h3.hex_ring(1, explode=True) val h3_hex_ring 881e2659c3fffff 1 881e2659ddfffff 881e2659c3fffff 1 881e2659cbfffff 881e2659c3fffff 1 881e2659d5fffff 881e2659c3fffff 1 881e2659c7fffff 881e2659c3fffff 1 881e265989fffff 881e2659c3fffff 1 881e2659c1fffff 881e309739fffff 5 881e30973dfffff 881e309739fffff 5 881e309703fffff 881e309739fffff 5 881e309707fffff 881e309739fffff 5 881e30973bfffff 881e309739fffff 5 881e309715fffff 881e309739fffff 5 881e309731fffff """ func = wrapped_partial(h3.hex_ring, k=k) column_name = "h3_hex_ring" if explode: return self._apply_index_explode(func, column_name, list) return self._apply_index_assign(func, column_name, list)
[docs] @doc_standard("h3_{resolution}", "containing the parent of each H3 address") def h3_to_parent(self, resolution: int = None) -> AnyDataFrame: """ Parameters ---------- resolution : int or None H3 resolution. If None, then returns the direct parent of each H3 cell. See Also -------- h3_to_parent_aggregate : Extended API method that aggregates cells by their parent cell Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_to_parent(5) val h3_05 881e309739fffff 5 851e3097fffffff 881e2659c3fffff 1 851e265bfffffff """ # TODO: Test `h3_parent` case column = self._format_resolution(resolution) if resolution else "h3_parent" return self._apply_index_assign( wrapped_partial(h3.h3_to_parent, res=resolution), column )
[docs] @doc_standard("h3_center_child", "containing the center child of each H3 address") def h3_to_center_child(self, resolution: int = None) -> AnyDataFrame: """ Parameters ---------- resolution : int or None H3 resolution. If none, then returns the child of resolution directly below that of each H3 cell Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_to_center_child() val h3_center_child 881e309739fffff 5 891e3097383ffff 881e2659c3fffff 1 891e2659c23ffff """ return self._apply_index_assign( wrapped_partial(h3.h3_to_center_child, res=resolution), "h3_center_child" )
[docs] @doc_standard( COLUMN_H3_POLYFILL, "containing a list H3 addresses whose centroid falls into the Polygon", ) def polyfill(self, resolution: int, explode: bool = False) -> AnyDataFrame: """ Parameters ---------- resolution : int H3 resolution explode : bool If True, will explode the resulting list vertically. All other columns' values are copied. Default: False See Also -------- polyfill_resample : Extended API method that distributes the polygon's values to the H3 cells contained in it Examples -------- >>> from shapely.geometry import box >>> gdf = gpd.GeoDataFrame(geometry=[box(0, 0, 1, 1)]) >>> gdf.h3.polyfill(4) geometry h3_polyfill 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... [84754e3ffffffff, 84754c7ffffffff, 84754c5ffff... # noqa E501 >>> gdf.h3.polyfill(4, explode=True) geometry h3_polyfill 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754e3ffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754c7ffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754c5ffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754ebffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754edffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754e1ffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 84754e9ffffffff 0 POLYGON ((1.00000 0.00000, 1.00000 1.00000, 0.... 8475413ffffffff """ def func(row): return list(polyfill(row.geometry, resolution, True)) result = self._df.apply(func, axis=1) if not explode: assign_args = {COLUMN_H3_POLYFILL: result} return self._df.assign(**assign_args) result = result.explode().to_frame(COLUMN_H3_POLYFILL) return self._df.join(result)
[docs] @doc_standard("h3_cell_area", "containing the area of each H3 address") def cell_area( self, unit: Literal["km^2", "m^2", "rads^2"] = "km^2" ) -> AnyDataFrame: """ Parameters ---------- unit : str, options: 'km^2', 'm^2', or 'rads^2' Unit for area result. Default: 'km^2` Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.cell_area() val h3_cell_area 881e309739fffff 5 0.695651 881e2659c3fffff 1 0.684242 """ return self._apply_index_assign( wrapped_partial(h3.cell_area, unit=unit), "h3_cell_area" )
# H3-Pandas Extended API # These methods extend the API to provide a convenient way to simplify workflows
[docs] def geo_to_h3_aggregate( self, resolution: int, operation: Union[dict, str, Callable] = "sum", lat_col: str = "lat", lng_col: str = "lng", return_geometry: bool = True, ) -> DataFrame: """Adds H3 index to DataFrame, groups points with the same index and performs `operation`. pd.DataFrame: uses `lat_col` and `lng_col` (default `lat` and `lng`) gpd.GeoDataFrame: uses `geometry` Parameters ---------- resolution : int H3 resolution operation : Union[dict, str, Callable] Argument passed to DataFrame's `agg` method, default 'sum' lat_col : str Name of the latitude column (if used), default 'lat' lng_col : str Name of the longitude column (if used), default 'lng' return_geometry: bool (Optional) Whether to add a `geometry` column with the hexagonal cells. Default = True Returns ------- (Geo)DataFrame aggregated by H3 address into which each row's point falls See Also -------- geo_to_h3 : H3 API method upon which this function builds Examples -------- >>> df = pd.DataFrame({'lat': [50, 51], 'lng':[14, 15], 'val': [10, 1]}) >>> df.h3.geo_to_h3(1) lat lng val h3_01 811e3ffffffffff 50 14 10 811e3ffffffffff 51 15 1 >>> df.h3.geo_to_h3_aggregate(1) val geometry h3_01 811e3ffffffffff 11 POLYGON ((12.34575 50.55428, 12.67732 46.40696... >>> df = pd.DataFrame({'lat': [50, 51], 'lng':[14, 15], 'val': [10, 1]}) >>> df.h3.geo_to_h3_aggregate(1, operation='mean') val geometry h3_01 811e3ffffffffff 5.5 POLYGON ((12.34575 50.55428, 12.67732 46.40696... >>> df.h3.geo_to_h3_aggregate(1, return_geometry=False) val h3_01 811e3ffffffffff 11 """ grouped = pd.DataFrame( self.geo_to_h3(resolution, lat_col, lng_col, False) .drop(columns=[lat_col, lng_col, "geometry"], errors="ignore") .groupby(self._format_resolution(resolution)) .agg(operation) ) return grouped.h3.h3_to_geo_boundary() if return_geometry else grouped
[docs] def h3_to_parent_aggregate( self, resolution: int, operation: Union[dict, str, Callable] = "sum", return_geometry: bool = True, ) -> GeoDataFrame: """Assigns parent cell to each row, groups by it and performs `operation`. Assumes H3 index. Parameters ---------- resolution : int H3 resolution operation : Union[dict, str, Callable] Argument passed to DataFrame's `agg` method, default 'sum' return_geometry: bool (Optional) Whether to add a `geometry` column with the hexagonal cells. Default = True Returns ------- (Geo)DataFrame aggregated by the parent of each H3 address Raises ------ ValueError When an invalid H3 address is encountered See Also -------- h3_to_parent : H3 API method upon which this function builds Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.h3_to_parent(1) val h3_01 881e309739fffff 5 811e3ffffffffff 881e2659c3fffff 1 811e3ffffffffff >>> df.h3.h3_to_parent_aggregate(1) val geometry h3_01 811e3ffffffffff 6 POLYGON ((12.34575 50.55428, 12.67732 46.40696... >>> df.h3.h3_to_parent_aggregate(1, operation='mean') val geometry h3_01 811e3ffffffffff 3 POLYGON ((12.34575 50.55428, 12.67732 46.40696... >>> df.h3.h3_to_parent_aggregate(1, return_geometry=False) val h3_01 811e3ffffffffff 6 """ parent_h3addresses = [ catch_invalid_h3_address(h3.h3_to_parent)(h3address, resolution) for h3address in self._df.index ] h3_parent_column = self._format_resolution(resolution) kwargs_assign = {h3_parent_column: parent_h3addresses} grouped = ( self._df.assign(**kwargs_assign) .groupby(h3_parent_column)[[c for c in self._df.columns if c != "geometry"]] .agg(operation) ) return grouped.h3.h3_to_geo_boundary() if return_geometry else grouped
# TODO: Needs to allow for handling relative values (e.g. percentage) # TODO: Will possibly fail in many cases (what are the existing columns?) # TODO: New cell behaviour
[docs] def k_ring_smoothing( self, k: int = None, weights: Sequence[float] = None, return_geometry: bool = True, ) -> AnyDataFrame: """Experimental. Creates a k-ring around each input cell and distributes the cell's values. The values are distributed either - uniformly (by setting `k`) or - by weighing their values using `weights`. Only numeric columns are modified. Parameters ---------- k : int The distance from the origin H3 address weights : Sequence[float] Weighting of the values based on the distance from the origin. First weight corresponds to the origin. Values are be normalized to add up to 1. return_geometry: bool (Optional) Whether to add a `geometry` column with the hexagonal cells. Default = True Returns ------- (Geo)DataFrame with smoothed values See Also -------- k_ring : H3 API method upon which this method builds Examples -------- >>> df = pd.DataFrame({'val': [5, 1]}, >>> index=['881e309739fffff', '881e2659c3fffff']) >>> df.h3.k_ring_smoothing(1) val geometry h3_k_ring 881e265989fffff 0.142857 POLYGON ((14.99488 50.99821, 14.99260 50.99389... 881e2659c1fffff 0.142857 POLYGON ((14.97944 51.00758, 14.97717 51.00326... 881e2659c3fffff 0.142857 POLYGON ((14.99201 51.00565, 14.98973 51.00133... 881e2659c7fffff 0.142857 POLYGON ((14.98231 51.00014, 14.98004 50.99582... 881e2659cbfffff 0.142857 POLYGON ((14.98914 51.01308, 14.98687 51.00877... 881e2659d5fffff 0.142857 POLYGON ((15.00458 51.00371, 15.00230 50.99940... 881e2659ddfffff 0.142857 POLYGON ((15.00171 51.01115, 14.99943 51.00684... 881e309703fffff 0.714286 POLYGON ((13.99235 50.01119, 13.99017 50.00681... 881e309707fffff 0.714286 POLYGON ((13.98290 50.00555, 13.98072 50.00116... 881e309715fffff 0.714286 POLYGON ((14.00473 50.00932, 14.00255 50.00494... 881e309731fffff 0.714286 POLYGON ((13.99819 49.99617, 13.99602 49.99178... 881e309739fffff 0.714286 POLYGON ((13.99527 50.00368, 13.99310 49.99929... 881e30973bfffff 0.714286 POLYGON ((14.00765 50.00181, 14.00547 49.99742... 881e30973dfffff 0.714286 POLYGON ((13.98582 49.99803, 13.98364 49.99365... >>> df.h3.k_ring_smoothing(weights=[2, 1]) val geometry h3_hex_ring 881e265989fffff 0.125 POLYGON ((14.99488 50.99821, 14.99260 50.99389... 881e2659c1fffff 0.125 POLYGON ((14.97944 51.00758, 14.97717 51.00326... 881e2659c3fffff 0.250 POLYGON ((14.99201 51.00565, 14.98973 51.00133... 881e2659c7fffff 0.125 POLYGON ((14.98231 51.00014, 14.98004 50.99582... 881e2659cbfffff 0.125 POLYGON ((14.98914 51.01308, 14.98687 51.00877... 881e2659d5fffff 0.125 POLYGON ((15.00458 51.00371, 15.00230 50.99940... 881e2659ddfffff 0.125 POLYGON ((15.00171 51.01115, 14.99943 51.00684... 881e309703fffff 0.625 POLYGON ((13.99235 50.01119, 13.99017 50.00681... 881e309707fffff 0.625 POLYGON ((13.98290 50.00555, 13.98072 50.00116... 881e309715fffff 0.625 POLYGON ((14.00473 50.00932, 14.00255 50.00494... 881e309731fffff 0.625 POLYGON ((13.99819 49.99617, 13.99602 49.99178... 881e309739fffff 1.250 POLYGON ((13.99527 50.00368, 13.99310 49.99929... 881e30973bfffff 0.625 POLYGON ((14.00765 50.00181, 14.00547 49.99742... 881e30973dfffff 0.625 POLYGON ((13.98582 49.99803, 13.98364 49.99365... >>> df.h3.k_ring_smoothing(1, return_geometry=False) val h3_k_ring 881e265989fffff 0.142857 881e2659c1fffff 0.142857 881e2659c3fffff 0.142857 881e2659c7fffff 0.142857 881e2659cbfffff 0.142857 881e2659d5fffff 0.142857 881e2659ddfffff 0.142857 881e309703fffff 0.714286 881e309707fffff 0.714286 881e309715fffff 0.714286 881e309731fffff 0.714286 881e309739fffff 0.714286 881e30973bfffff 0.714286 881e30973dfffff 0.714286 """ if sum([weights is None, k is None]) != 1: raise ValueError("Exactly one of `k` and `weights` must be set.") # If weights are all equal, use the computationally simpler option if (weights is not None) and (len(set(weights)) == 1): k = len(weights) - 1 weights = None # Unweighted case if weights is None: result = pd.DataFrame( self._df.h3.k_ring(k, explode=True) .groupby("h3_k_ring") .sum() .divide((1 + 3 * k * (k + 1))) ) return result.h3.h3_to_geo_boundary() if return_geometry else result if len(weights) == 0: raise ValueError("Weights cannot be empty.") # Weighted case weights = np.array(weights) multipliers = np.array([1] + [i * 6 for i in range(1, len(weights))]) weights = weights / (weights * multipliers).sum() # This should be exploded hex ring def weighted_hex_ring(df, k, normalized_weight): return df.h3.hex_ring(k, explode=True).h3._multiply_numeric( normalized_weight ) result = ( pd.concat( [ weighted_hex_ring(self._df, i, weights[i]) for i in range(len(weights)) ] ) .groupby("h3_hex_ring") .sum() ) return result.h3.h3_to_geo_boundary() if return_geometry else result
[docs] def polyfill_resample( self, resolution: int, return_geometry: bool = True ) -> AnyDataFrame: """Experimental. Currently essentially polyfill(..., explode=True) that sets the H3 index and adds the H3 cell geometry. Parameters ---------- resolution : int H3 resolution return_geometry: bool (Optional) Whether to add a `geometry` column with the hexagonal cells. Default = True Returns ------- (Geo)DataFrame with H3 cells with centroids within the input polygons. See Also -------- polyfill : H3 API method upon which this method builds Examples -------- >>> from shapely.geometry import box >>> gdf = gpd.GeoDataFrame(geometry=[box(0, 0, 1, 1)]) >>> gdf.h3.polyfill_resample(4) index geometry h3_polyfill 84754e3ffffffff 0 POLYGON ((0.33404 -0.11975, 0.42911 0.07901, 0... 84754c7ffffffff 0 POLYGON ((0.92140 -0.03115, 1.01693 0.16862, 0... 84754c5ffffffff 0 POLYGON ((0.91569 0.33807, 1.01106 0.53747, 0.... 84754ebffffffff 0 POLYGON ((0.62438 0.10878, 0.71960 0.30787, 0.... 84754edffffffff 0 POLYGON ((0.32478 0.61394, 0.41951 0.81195, 0.... 84754e1ffffffff 0 POLYGON ((0.32940 0.24775, 0.42430 0.44615, 0.... 84754e9ffffffff 0 POLYGON ((0.61922 0.47649, 0.71427 0.67520, 0.... 8475413ffffffff 0 POLYGON ((0.91001 0.70597, 1.00521 0.90497, 0.... """ result = self._df.h3.polyfill(resolution, explode=True) uncovered_rows = result[COLUMN_H3_POLYFILL].isna() n_uncovered_rows = uncovered_rows.sum() if n_uncovered_rows > 0: warnings.warn( f"{n_uncovered_rows} rows did not generate a H3 cell." "Consider using a finer resolution." ) result = result.loc[~uncovered_rows] result = result.reset_index().set_index(COLUMN_H3_POLYFILL) return result.h3.h3_to_geo_boundary() if return_geometry else result
# Private methods def _apply_index_assign( self, func: Callable, column_name: str, processor: Callable = lambda x: x, finalizer: Callable = lambda x: x, ) -> Any: """Helper method. Applies `func` to index and assigns the result to `column`. Parameters ---------- func : Callable single-argument function to be applied to each H3 address column_name : str name of the resulting column processor : Callable (Optional) further processes the result of func. Default: identity finalizer : Callable (Optional) further processes the resulting dataframe. Default: identity Returns ------- Dataframe with column `column` containing the result of `func`. If using `finalizer`, can return anything the `finalizer` returns. """ func = catch_invalid_h3_address(func) result = [processor(func(h3address)) for h3address in self._df.index] assign_args = {column_name: result} return finalizer(self._df.assign(**assign_args)) def _apply_index_explode( self, func: Callable, column_name: str, processor: Callable = lambda x: x, finalizer: Callable = lambda x: x, ) -> Any: """Helper method. Applies a list-making `func` to index and performs a vertical explode. Any additional values are simply copied to all the rows. Parameters ---------- func : Callable single-argument function to be applied to each H3 address column_name : str name of the resulting column processor : Callable (Optional) further processes the result of func. Default: identity finalizer : Callable (Optional) further processes the resulting dataframe. Default: identity Returns ------- Dataframe with column `column` containing the result of `func`. If using `finalizer`, can return anything the `finalizer` returns. """ func = catch_invalid_h3_address(func) result = ( pd.DataFrame.from_dict( {h3address: processor(func(h3address)) for h3address in self._df.index}, orient="index", ) .stack() .to_frame(column_name) .reset_index(level=1, drop=True) ) result = self._df.join(result) return finalizer(result) # TODO: types, doc, .. def _multiply_numeric(self, value): columns_numeric = self._df.select_dtypes(include=["number"]).columns assign_args = { column: self._df[column].multiply(value) for column in columns_numeric } return self._df.assign(**assign_args) @staticmethod def _format_resolution(resolution: int) -> str: return f"h3_{str(resolution).zfill(2)}"