Skip to content

Commit

Permalink
fix: add h3ronpy 0.22.0 compatibility (#480)
Browse files Browse the repository at this point in the history
* fix: add h3ronpy 0.22.0 compatibility

* chore: add tests for missing h3 operations

* chore: add todo comment

* chore: add changelog entry

* fix: regenerate lockfile

* chore: migrate pre-commit config

* chore: pin torch version for macos
  • Loading branch information
RaczeQ authored Dec 28, 2024
1 parent 2ff7c85 commit a0a0de7
Show file tree
Hide file tree
Showing 8 changed files with 2,479 additions and 1,907 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [commit]
default_stages: [pre-commit]
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.4.0
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Made code compliant with `osmnx`'s new `2.0.0` release [#473](https://github.com/kraina-ai/srai/issues/473)
- Made code compliant with `h3ronpy`'s new `0.22.0` release [#471](https://github.com/kraina-ai/srai/issues/471)

## [0.7.8] - 2024-12-28

Expand Down
4,227 changes: 2,359 additions & 1,868 deletions pdm.lock

Large diffs are not rendered by default.

28 changes: 15 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = [
"s2>=0.1.9",
"typeguard>=3.0.0",
"requests",
"h3ronpy>=0.20.1,<0.22.0",
"h3ronpy>=0.20.1",
"osmnx>=1.3.0",
]
requires-python = ">=3.9"
Expand Down Expand Up @@ -57,14 +57,14 @@ Changelog = "https://github.com/kraina-ai/srai/blob/main/CHANGELOG.md"
# add tests

# pdm add -G osm <library>
osm = [
"overpass>=0.7",
"pillow>=8.0.0",
"beautifulsoup4",
"quackosm>=0.11.0",
]
osm = ["overpass>=0.7", "pillow>=8.0.0", "beautifulsoup4", "quackosm>=0.11.0"]
# pdm add -G voronoi <library>
voronoi = ["pymap3d>=3.0.0", "haversine>=2.0.0", "scipy>=1.10.0", "spherical-geometry>=1.3.2"]
voronoi = [
"pymap3d>=3.0.0",
"haversine>=2.0.0",
"scipy>=1.10.0",
"spherical-geometry>=1.3.2",
]
# pdm add -G gtfs <library>
gtfs = ["gtfs-kit"]
# pdm add -G plotting <library>
Expand All @@ -77,7 +77,12 @@ plotting = [
"kaleido<=0.2.1,>=0.2.0",
] # kaleido<=0.2.1 because installation breaks on 0.2.1.post1
# pdm add -G torch <library>
torch = ["pytorch-lightning>=2.0.0", "torch"]
torch = [
"pytorch-lightning>=2.0.0",
# Torch 2.2.* is the last oficially supported torch version on macOS x86_64
"torch<2.3; sys_platform == 'darwin' and platform_machine == 'x86_64'",
"torch",
]
all = ["srai[osm,voronoi,gtfs,plotting,torch]"]


Expand Down Expand Up @@ -121,10 +126,7 @@ docs = [
"umap-learn",
]
# performance = ["scalene"]
license = [
"licensecheck==2024.3",
"pipdeptree",
]
license = ["licensecheck==2024.3", "pipdeptree"]

[tool.pdm.scripts]
post_install = "pre-commit install"
Expand Down
39 changes: 33 additions & 6 deletions srai/h3.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,26 @@
import h3
import numpy as np
import numpy.typing as npt
from h3ronpy.arrow import cells_to_string, grid_disk
from h3ronpy.arrow.vector import ContainmentMode, cells_to_wkb_polygons, wkb_to_cells
from h3ronpy import __version__ as h3ronpy_version
from packaging import version
from shapely.geometry import Polygon
from shapely.geometry.base import BaseGeometry

from srai.constants import GEOMETRY_COLUMN, REGIONS_INDEX, WGS84_CRS

is_new_h3ronpy_api = version.parse(h3ronpy_version) >= version.parse("0.22.0")

if is_new_h3ronpy_api:
from h3ronpy import ContainmentMode, cells_to_string, grid_disk
from h3ronpy.vector import cells_to_wkb_polygons, wkb_to_cells
else:
from h3ronpy.arrow import cells_to_string, grid_disk
from h3ronpy.arrow.vector import (
ContainmentMode,
cells_to_wkb_polygons,
wkb_to_cells,
)

__all__ = [
"shapely_geometry_to_h3",
"h3_to_geoseries",
Expand Down Expand Up @@ -65,7 +78,12 @@ def shapely_geometry_to_h3(
containment_mode = ContainmentMode.Covers if buffer else ContainmentMode.ContainsCentroid
h3_indexes = wkb_to_cells(
wkb, resolution=h3_resolution, containment_mode=containment_mode, flatten=True
).unique()
)

if is_new_h3ronpy_api:
h3_indexes = np.unique(h3_indexes.to_numpy())
else:
h3_indexes = h3_indexes.unique()

return [h3.int_to_str(h3_index) for h3_index in h3_indexes.tolist()]

Expand All @@ -85,7 +103,7 @@ def h3_to_geoseries(h3_index: Union[int, str, Iterable[Union[int, str]]]) -> gpd
if isinstance(h3_index, (str, int)):
return h3_to_geoseries([h3_index])
else:
h3_int_indexes = (
h3_int_indexes = list(
h3_cell if isinstance(h3_cell, int) else h3.str_to_int(h3_cell) for h3_cell in h3_index
)
return gpd.GeoSeries.from_wkb(cells_to_wkb_polygons(h3_int_indexes), crs=WGS84_CRS)
Expand Down Expand Up @@ -192,10 +210,19 @@ def ring_buffer_h3_indexes(h3_indexes: Iterable[Union[int, str]], distance: int)
h3.is_valid_cell(h3_cell) for h3_cell in h3_indexes
), "Not all values in h3_indexes are valid H3 cells."

h3_int_indexes = (
h3_int_indexes = list(
h3_cell if isinstance(h3_cell, int) else h3.str_to_int(h3_cell) for h3_cell in h3_indexes
)
buffered_h3s = set(cells_to_string(grid_disk(h3_int_indexes, distance, flatten=True)).tolist())

if is_new_h3ronpy_api:
buffered_h3s = np.unique(
cells_to_string(grid_disk(h3_int_indexes, distance, flatten=True)).to_numpy()
)
else:
buffered_h3s = set(
cells_to_string(grid_disk(h3_int_indexes, distance, flatten=True)).tolist()
)

return list(buffered_h3s)


Expand Down
19 changes: 18 additions & 1 deletion tests/h3/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import geopandas as gpd
import pytest
from shapely import geometry
from shapely.geometry.base import BaseGeometry

from srai.constants import WGS84_CRS
from srai.constants import GEOMETRY_COLUMN, WGS84_CRS


@pytest.fixture # type: ignore
Expand Down Expand Up @@ -106,3 +107,19 @@ def expected_unbuffered_h3_indexes() -> list[str]:
return [
"83754efffffffff",
]


def _gdf_noop(gdf_fixture: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
return gdf_fixture


def _gdf_to_geoseries(gdf_fixture: gpd.GeoDataFrame) -> gpd.GeoSeries: # noqa: FURB118
return gdf_fixture[GEOMETRY_COLUMN]


def _gdf_to_geometry_list(gdf_fixture: gpd.GeoDataFrame) -> list[BaseGeometry]:
return list(gdf_fixture[GEOMETRY_COLUMN])


def _gdf_to_single_geometry(gdf_fixture: gpd.GeoDataFrame) -> BaseGeometry:
return gdf_fixture[GEOMETRY_COLUMN][0]
51 changes: 51 additions & 0 deletions tests/h3/test_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Module for testing srai.h3 module functionality."""

from typing import Any, Callable

import geopandas as gpd
import pytest

from srai.h3 import (
ring_buffer_geometry,
ring_buffer_h3_regions_gdf,
)
from srai.regionalizers.geocode import geocode_to_region_gdf
from srai.regionalizers.h3_regionalizer import H3Regionalizer
from tests.h3.conftest import _gdf_noop, _gdf_to_geometry_list, _gdf_to_geoseries
from tests.regionalizers.test_h3_regionalizer import H3_RESOLUTION

# TODO: add expected values, now only checks if runs without errors


@pytest.mark.parametrize(
"geometry_fixture, resolution, distance",
[
("gdf_single_point", 10, 10),
("gdf_multipolygon", H3_RESOLUTION, 2),
("gdf_polygons", H3_RESOLUTION, 2),
],
) # type: ignore
@pytest.mark.parametrize(
"geometry_parser_function",
[_gdf_noop, _gdf_to_geoseries, _gdf_to_geometry_list],
) # type: ignore
def test_ring_buffer_geometry(
geometry_fixture: str,
resolution: int,
distance: int,
geometry_parser_function: Callable[[gpd.GeoDataFrame], Any],
request: pytest.FixtureRequest,
) -> None:
"""Test checks if ring_buffer_geometry function works."""
geometry = request.getfixturevalue(geometry_fixture)

parsed_geometry = geometry_parser_function(geometry)
ring_buffer_geometry(parsed_geometry, h3_resolution=resolution, distance=distance)


def test_ring_buffer_h3_regions_gdf() -> None:
"""Test checks if ring_buffer_h3_regions_gdf function works."""
gdf_wro = geocode_to_region_gdf("Wrocław, PL")
regions_gdf = H3Regionalizer(8).transform(gdf_wro)

ring_buffer_h3_regions_gdf(regions_gdf, distance=10)
19 changes: 1 addition & 18 deletions tests/h3/test_shapely_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,14 @@

import geopandas as gpd
import pytest
from shapely.geometry.base import BaseGeometry

from srai.constants import GEOMETRY_COLUMN
from srai.h3 import shapely_geometry_to_h3
from tests.h3.conftest import _gdf_noop, _gdf_to_geometry_list, _gdf_to_geoseries
from tests.regionalizers.test_h3_regionalizer import H3_RESOLUTION

ut = TestCase()


def _gdf_noop(gdf_fixture: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
return gdf_fixture


def _gdf_to_geoseries(gdf_fixture: gpd.GeoDataFrame) -> gpd.GeoSeries: # noqa: FURB118
return gdf_fixture[GEOMETRY_COLUMN]


def _gdf_to_geometry_list(gdf_fixture: gpd.GeoDataFrame) -> list[BaseGeometry]:
return list(gdf_fixture[GEOMETRY_COLUMN])


def _gdf_to_single_geometry(gdf_fixture: gpd.GeoDataFrame) -> BaseGeometry:
return gdf_fixture[GEOMETRY_COLUMN][0]


@pytest.mark.parametrize(
"geometry_fixture, resolution, expected_h3_cells_fixture",
[
Expand Down

0 comments on commit a0a0de7

Please sign in to comment.