diff --git a/.github/workflows/lint_and_coverage.yml b/.github/workflows/lint_and_coverage.yml index ab23b27c..723677cc 100644 --- a/.github/workflows/lint_and_coverage.yml +++ b/.github/workflows/lint_and_coverage.yml @@ -28,10 +28,10 @@ jobs: - name: Coverage Requirement - Library run: | - pytest --cov=tests/test_lib --cov-fail-under=100 + pytest tests/test_lib --cov=h3 --cov=tests/test_lib --cov-fail-under=100 - name: Coverage - Cython run: | pip install cython cythonize tests/test_cython/cython_example.pyx - pytest --cov=tests/test_cython + pytest tests/test_cython --cov=tests/test_cython diff --git a/docs/api_quick.md b/docs/api_quick.md index 5503baef..33b37b8f 100644 --- a/docs/api_quick.md +++ b/docs/api_quick.md @@ -161,6 +161,16 @@ Note that this is reversed from [``__geo_interface__``](https://gist.github.com/ cells_to_geo ``` +#### Additional functions + +```{eval-rst} +.. currentmodule:: h3 + +.. autosummary:: + polygon_to_cells + h3shape_to_cells_experimental +``` + ## Specialized functions diff --git a/makefile b/makefile index e6b02837..df39c8f4 100644 --- a/makefile +++ b/makefile @@ -35,11 +35,11 @@ purge: clear -@rm -rf env test: - ./env/bin/pytest --cov=tests/test_lib --cov-fail-under=100 + ./env/bin/pytest tests/test_lib --cov=h3 --cov=tests/test_lib --cov-fail-under=100 ./env/bin/pip install cython ./env/bin/cythonize tests/test_cython/cython_example.pyx - ./env/bin/pytest --cov=tests/test_cython + ./env/bin/pytest tests/test_cython --cov=tests/test_cython lint: ./env/bin/ruff check diff --git a/pyproject.toml b/pyproject.toml index e1cc0ba9..06ce0e3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ all = [ ] [tool.pytest.ini_options] -addopts = "--cov=h3 --cov-report=term-missing --durations=10" +addopts = "--cov-report=term-missing --durations=10" [tool.coverage.run] omit = [ diff --git a/src/h3/_cy/latlng.pyx b/src/h3/_cy/latlng.pyx index 60b9053f..d83a9d3d 100644 --- a/src/h3/_cy/latlng.pyx +++ b/src/h3/_cy/latlng.pyx @@ -196,7 +196,7 @@ def polygons_to_cells(polygons, int res): return hmm.to_mv() -def polygon_to_cells_experimental(outer, int res, int flags, holes=None): +def polygon_to_cells_experimental(outer, int res, int flag, holes=None): """ Get the set of cells whose center is contained in a polygon. The polygon is defined similarity to the GeoJson standard, with an exterior @@ -221,8 +221,8 @@ def polygon_to_cells_experimental(outer, int res, int flags, holes=None): A ring given by a sequence of lat/lng pairs. res : int The resolution of the output hexagons - flags : int - Polygon to cells flags, such as containment mode. + flag : int + Polygon to cells flag, such as containment mode. holes : list or tuple A collection of rings, each given by a sequence of lat/lng pairs. These describe any the "holes" in the polygon. @@ -238,21 +238,21 @@ def polygon_to_cells_experimental(outer, int res, int flags, holes=None): gp = GeoPolygon(outer, holes=holes) check_for_error( - h3lib.maxPolygonToCellsSizeExperimental(&gp.gp, res, flags, &n) + h3lib.maxPolygonToCellsSizeExperimental(&gp.gp, res, flag, &n) ) hmm = H3MemoryManager(n) check_for_error( - h3lib.polygonToCellsExperimental(&gp.gp, res, flags, n, hmm.ptr) + h3lib.polygonToCellsExperimental(&gp.gp, res, flag, n, hmm.ptr) ) mv = hmm.to_mv() return mv -def polygons_to_cells_experimental(polygons, int res, int flags): +def polygons_to_cells_experimental(polygons, int res, int flag): mvs = [ - polygon_to_cells_experimental(outer=poly.outer, res=res, holes=poly.holes, flags=flags) + polygon_to_cells_experimental(outer=poly.outer, res=res, holes=poly.holes, flag=flag) for poly in polygons ] diff --git a/src/h3/_h3shape.py b/src/h3/_h3shape.py index 261dfa92..5c362951 100644 --- a/src/h3/_h3shape.py +++ b/src/h3/_h3shape.py @@ -1,15 +1,4 @@ from abc import ABCMeta, abstractmethod -from enum import Enum - - -class ContainmentMode(int, Enum): - """ - Containment modes for use with ``polygon_to_cells_experimental``. - """ - containment_center = 0 - containment_full = 1 - containment_overlapping = 2 - containment_overlapping_bbox = 3 class H3Shape(metaclass=ABCMeta): diff --git a/src/h3/api/basic_int/__init__.py b/src/h3/api/basic_int/__init__.py index cf450c89..46579107 100644 --- a/src/h3/api/basic_int/__init__.py +++ b/src/h3/api/basic_int/__init__.py @@ -1,8 +1,8 @@ # This file is **symlinked** across the APIs to ensure they are exactly the same. +from typing import Literal from ... import _cy from ..._h3shape import ( - ContainmentMode, H3Shape, LatLngPoly, LatLngMultiPoly, @@ -526,18 +526,35 @@ def polygon_to_cells(h3shape, res): return h3shape_to_cells(h3shape, res) -def h3shape_to_cells_experimental(h3shape, res, flags=0): +def h3shape_to_cells_experimental( + h3shape: H3Shape, + res: int, + contain: Literal['center', 'full', 'overlap', 'bbox_overlap'] = 'center', +): """ - Return the collection of H3 cells at a given resolution whose center points - are contained within an ``LatLngPoly`` or ``LatLngMultiPoly``. + Experimental function similar to ``h3shape_to_cells``, but with support for + multiple cell containment modes. + + Using ``contain='center'`` should give identical behavior as + ``h3shape_to_cells``. + + Note that this function is **experimental** and has no API stability gaurantees + across versions, so it may change in the future. + Parameters ---------- h3shape : ``H3Shape`` res : int Resolution of the output cells - flags : ``ContainmentMode``, int, or string - Containment mode flags + contain : {'center', 'full', 'overlap', 'bbox_overlap'}, optional + Specifies the containment condition. + - 'center': Cell center is contained in shape + - 'full': Cell is fully contained in shape + - 'overlap': Cell is partially contained in shape + - 'bbox_overlap': Cell bounding box is partially contained in shape + + Default is 'center'. Returns ------- @@ -550,7 +567,7 @@ def h3shape_to_cells_experimental(h3shape, res, flags=0): ... [(37.68, -122.54), (37.68, -122.34), (37.82, -122.34), ... (37.82, -122.54)], ... ) - >>> h3.h3shape_to_cells_experimental(poly, 6, h3.ContainmentMode.containment_center) + >>> h3.h3shape_to_cells_experimental(poly, 6, 'center') ['862830807ffffff', '862830827ffffff', '86283082fffffff', @@ -564,30 +581,27 @@ def h3shape_to_cells_experimental(h3shape, res, flags=0): There is currently no guaranteed order of the output cells. """ - if isinstance(flags, str): - try: - flags = ContainmentMode[flags] - except KeyError as e: - raise ValueError('Unrecognized flags: ' + flags) from e - if isinstance(flags, ContainmentMode): - flags = int(flags) - if not isinstance(flags, int): - raise ValueError( - 'Flags should be ContainmentMode, str, or int, but got: ' + str(type(flags)) - ) + contain_modes = { + 'center': 0, + 'full': 1, + 'overlap': 2, + 'bbox_overlap': 3, + } + + flag = contain_modes[contain] # todo: not sure if i want this dispatch logic here. maybe in the objects? if isinstance(h3shape, LatLngPoly): poly = h3shape mv = _cy.polygon_to_cells_experimental( poly.outer, - res=res, - holes=poly.holes, - flags=flags + res = res, + holes = poly.holes, + flag = flag, ) elif isinstance(h3shape, LatLngMultiPoly): mpoly = h3shape - mv = _cy.polygons_to_cells_experimental(mpoly.polys, res=res, flags=flags) + mv = _cy.polygons_to_cells_experimental(mpoly.polys, res=res, flag=flag) elif isinstance(h3shape, H3Shape): raise ValueError('Unrecognized H3Shape: ' + str(h3shape)) else: @@ -596,13 +610,6 @@ def h3shape_to_cells_experimental(h3shape, res, flags=0): return _out_collection(mv) -def polygon_to_cells_experimental(h3shape, res, flags=0): - """ - Alias for ``h3shape_to_cells_experimental``. - """ - return h3shape_to_cells_experimental(h3shape, res, flags=flags) - - def cells_to_h3shape(cells, *, tight=True): """ Return an ``H3Shape`` describing the area covered by a collection of H3 cells. diff --git a/tests/test_lib/polyfill/test_h3.py b/tests/test_lib/polyfill/test_h3.py index 933f404c..cb26fdd4 100644 --- a/tests/test_lib/polyfill/test_h3.py +++ b/tests/test_lib/polyfill/test_h3.py @@ -117,65 +117,54 @@ def test_polygon_to_cells(): assert '89283095edbffff' in out -def test_polygon_to_cells_experimental(): +def test_h3shape_to_cells_experimental(): poly = h3.LatLngPoly(sf_7x7) - for flags in [0, 'containment_center', h3.ContainmentMode.containment_center]: - # Note that `polygon_to_cells` is an alias for `h3shape_to_cells` - out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags) - assert len(out) == 1253 - assert '89283080527ffff' in out - assert '89283095edbffff' in out + out = h3.h3shape_to_cells_experimental(poly, res=9, contain='center') + assert len(out) == 1253 + assert '89283080527ffff' in out + assert '89283095edbffff' in out -def test_polygon_to_cells_experimental_full(): + +def test_h3shape_to_cells_experimental_full(): poly = h3.LatLngPoly(sf_7x7) - for flags in [1, 'containment_full', h3.ContainmentMode.containment_full]: - # Note that `polygon_to_cells` is an alias for `h3shape_to_cells` - out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags) - assert len(out) == 1175 - assert '89283082a1bffff' in out - assert '89283080527ffff' not in out - assert '89283095edbffff' in out + out = h3.h3shape_to_cells_experimental(poly, res=9, contain='full') + + assert len(out) == 1175 + assert '89283082a1bffff' in out + assert '89283080527ffff' not in out + assert '89283095edbffff' in out -def test_polygon_to_cells_experimental_overlapping(): +def test_h3shape_to_cells_experimental_overlapping(): poly = h3.LatLngPoly(sf_7x7) - for flags in [ - 2, - 'containment_overlapping', - h3.ContainmentMode.containment_overlapping - ]: - # Note that `polygon_to_cells` is an alias for `h3shape_to_cells` - out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags) - assert len(out) == 1334 - assert '89283080527ffff' in out - assert '89283095edbffff' in out + out = h3.h3shape_to_cells_experimental(poly, res=9, contain='overlap') + + assert len(out) == 1334 + assert '89283080527ffff' in out + assert '89283095edbffff' in out -def test_polygon_to_cells_experimental_overlapping_bbox(): +def test_h3shape_to_cells_experimental_overlapping_bbox(): poly = h3.LatLngPoly(sf_7x7) - for flags in [ - 3, - 'containment_overlapping_bbox', - h3.ContainmentMode.containment_overlapping_bbox - ]: - # Note that `polygon_to_cells` is an alias for `h3shape_to_cells` - out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags) + out = h3.h3shape_to_cells_experimental(poly, res=9, contain='bbox_overlap') - assert len(out) == 1416 - assert '89283080527ffff' in out - assert '89283095edbffff' in out + assert len(out) == 1416 + assert '89283080527ffff' in out + assert '89283095edbffff' in out -def test_polygon_to_cells_experimental_invalid_mode(): +def test_h3shape_to_cells_experimental_invalid_mode(): poly = h3.LatLngPoly(sf_7x7) - for flags in [1.0, 'containment_overlapping_bbox_abc', None]: - with pytest.raises(ValueError): - # Note that `polygon_to_cells` is an alias for `h3shape_to_cells` - h3.polygon_to_cells_experimental(poly, res=9, flags=flags) + with pytest.raises(KeyError): + h3.h3shape_to_cells_experimental( + poly, + res = 9, + contain = 'contain_overlapping_bbox_abc', + ) def test_poly_to_cells_experimental_mpoly(): @@ -185,25 +174,25 @@ def test_poly_to_cells_experimental_mpoly(): ) assert ( - set(h3.polygon_to_cells_experimental(mpoly, res=9)) + set(h3.h3shape_to_cells_experimental(mpoly, res=9)) == - set(h3.polygon_to_cells_experimental(mpoly, res=9, flags='containment_center')) + set(h3.h3shape_to_cells_experimental(mpoly, res=9, contain='center')) ) assert ( - set(h3.polygon_to_cells_experimental(mpoly, res=9)) + set(h3.h3shape_to_cells_experimental(mpoly, res=9)) < - set(h3.polygon_to_cells_experimental( + set(h3.h3shape_to_cells_experimental( mpoly, - res=9, - flags='containment_overlapping' + res = 9, + contain = 'overlap' )) ) - assert 120 == len(h3.polygon_to_cells_experimental( + assert 120 == len(h3.h3shape_to_cells_experimental( mpoly, - res=9, - flags='containment_overlapping' + res = 9, + contain = 'overlap' ))