From 158f52d9b6e9bc2d19844db5a635e43add72fb7d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 1 Jan 2024 15:15:30 -0800 Subject: [PATCH] Use functions instead of methods for the interface functions (#334) * starting refactor * remove stuff * unindent * remove self from function signatures * remove self. * remove staticmethod * set up symlinks * some linting * test for bad H3Shape * symlink note * just put everything in __init__.py * reorganize conversion functions * rename _api_funcs.py to _convert.py * use lazy import of numpy so we don't have to special case h3.api.numpy_int * DRY --- .coveragerc | 5 + src/h3/api/__init__.py | 1 + src/h3/api/_api_template.py | 945 -------------------------- src/h3/api/basic_int/__init__.py | 944 ++++++++++++++++++++++++- src/h3/api/basic_int/_binding.py | 36 - src/h3/api/basic_int/_convert.py | 18 + src/h3/api/basic_int/_public_api.py | 63 -- src/h3/api/basic_str/__init__.py | 10 +- src/h3/api/basic_str/_binding.py | 43 -- src/h3/api/basic_str/_convert.py | 20 + src/h3/api/basic_str/_public_api.py | 1 - src/h3/api/memview_int/__init__.py | 10 +- src/h3/api/memview_int/_binding.py | 31 - src/h3/api/memview_int/_convert.py | 9 + src/h3/api/memview_int/_public_api.py | 1 - src/h3/api/numpy_int/__init__.py | 10 +- src/h3/api/numpy_int/_binding.py | 43 -- src/h3/api/numpy_int/_convert.py | 16 + src/h3/api/numpy_int/_public_api.py | 1 - tests/polyfill/test_polygon_class.py | 9 + tests/test_api_bindings_match.py | 9 +- 21 files changed, 1022 insertions(+), 1203 deletions(-) create mode 100644 .coveragerc delete mode 100644 src/h3/api/_api_template.py delete mode 100644 src/h3/api/basic_int/_binding.py create mode 100644 src/h3/api/basic_int/_convert.py delete mode 100644 src/h3/api/basic_int/_public_api.py mode change 100644 => 120000 src/h3/api/basic_str/__init__.py delete mode 100644 src/h3/api/basic_str/_binding.py create mode 100644 src/h3/api/basic_str/_convert.py delete mode 120000 src/h3/api/basic_str/_public_api.py mode change 100644 => 120000 src/h3/api/memview_int/__init__.py delete mode 100644 src/h3/api/memview_int/_binding.py create mode 100644 src/h3/api/memview_int/_convert.py delete mode 120000 src/h3/api/memview_int/_public_api.py mode change 100644 => 120000 src/h3/api/numpy_int/__init__.py delete mode 100644 src/h3/api/numpy_int/_binding.py create mode 100644 src/h3/api/numpy_int/_convert.py delete mode 120000 src/h3/api/numpy_int/_public_api.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..662935de --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +omit = + */h3/api/basic_int/__init__.py + */h3/api/memview_int/__init__.py + */h3/api/numpy_int/__init__.py diff --git a/src/h3/api/__init__.py b/src/h3/api/__init__.py index 9bc858a2..435b13a1 100644 --- a/src/h3/api/__init__.py +++ b/src/h3/api/__init__.py @@ -3,3 +3,4 @@ from . import basic_int from . import basic_str from . import memview_int +from . import numpy_int diff --git a/src/h3/api/_api_template.py b/src/h3/api/_api_template.py deleted file mode 100644 index 1b7fd0d8..00000000 --- a/src/h3/api/_api_template.py +++ /dev/null @@ -1,945 +0,0 @@ -""" -Module to DRY-up code which is repeated across API modules. - -Definitions of types --------------------- -H3Index: - An unsigned 64-bit integer representing a valid H3 cell or - unidirectional edge. - Depending on the API, an H3Index may be represented as an - unsigned integer type, or as a hexadecimal string. - -H3 cell: - A pentagon or hexagon that can be represented by an H3Index. - -H3Cell: - H3Index representation of an H3 cell. - -H3Edge: - H3Index representation of an H3 unidirectional edge. - - -Definitions of collections --------------------------- -Collection types vary between APIs. We'll use the following terms: - -unordered collection: - Inputs and outputs are interpreted as *unordered* collections. - Examples: `set`, `numpy.ndarray`. - -ordered collection: - Inputs and outputs are interpreted as *ordered* collections. - Examples: `list`, `numpy.ndarray`. - -Notes --------------------- -todo: how do we lint these functions and docstrings? it seems to currently -be skipped due to it being inside the `_api_functions` function. -""" - -from .. import _cy -from .._h3shape import ( - H3Poly, - H3MultiPoly, - geo_to_h3shape, - h3shape_to_geo, -) - - -class _API_FUNCTIONS(object): - def __init__( - self, - _in_scalar, - _out_scalar, - _in_collection, - _out_unordered, - _out_ordered, - ): - self._in_scalar = _in_scalar - self._out_scalar = _out_scalar - self._in_collection = _in_collection - self._out_unordered = _out_unordered - self._out_ordered = _out_ordered - - @staticmethod - def versions(): - """ - Version numbers for the Python (wrapper) and C (wrapped) libraries. - - Versions are output as strings of the form ``'X.Y.Z'``. - C and Python should match on ``X`` (major) and ``Y`` (minor), - but may differ on ``Z`` (patch). - - Returns - ------- - dict like ``{'c': 'X.Y.Z', 'python': 'A.B.C'}`` - """ - from .._version import __version__ - - v = { - 'c': _cy.c_version(), - 'python': __version__, - } - - return v - - @staticmethod - def str_to_int(h): - """ - Converts a hexadecimal string to an H3 64-bit integer index. - - Parameters - ---------- - h : str - Hexadecimal string like ``'89754e64993ffff'`` - - Returns - ------- - int - Unsigned 64-bit integer - """ - return _cy.str_to_int(h) - - @staticmethod - def int_to_str(x): - """ - Converts an H3 64-bit integer index to a hexadecimal string. - - Parameters - ---------- - x : int - Unsigned 64-bit integer - - Returns - ------- - str - Hexadecimal string like ``'89754e64993ffff'`` - """ - return _cy.int_to_str(x) - - @staticmethod - def get_num_cells(res): - """ - Return the total number of *cells* (hexagons and pentagons) - for the given resolution. - - Returns - ------- - int - """ - return _cy.get_num_cells(res) - - @staticmethod - def average_hexagon_area(res, unit='km^2'): - """ - Return the average area of an H3 *hexagon* - for the given resolution. - - This average *excludes* pentagons. - - Returns - ------- - float - """ - return _cy.average_hexagon_area(res, unit) - - @staticmethod - def average_hexagon_edge_length(res, unit='km'): - """ - Return the average *hexagon* edge length - for the given resolution. - - This average *excludes* pentagons. - - Returns - ------- - float - """ - return _cy.average_hexagon_edge_length(res, unit) - - def is_valid_cell(self, h): - """ - Validates an H3 cell (hexagon or pentagon). - - Returns - ------- - bool - """ - try: - h = self._in_scalar(h) - return _cy.is_valid_cell(h) - except (ValueError, TypeError): - return False - - def is_valid_directed_edge(self, edge): - """ - Validates an H3 unidirectional edge. - - Returns - ------- - bool - """ - try: - e = self._in_scalar(edge) - return _cy.is_valid_directed_edge(e) - except (ValueError, TypeError): - return False - - def latlng_to_cell(self, lat, lng, res): - """ - Return the cell containing the (lat, lng) point - for a given resolution. - - Returns - ------- - H3Cell - - """ - return self._out_scalar(_cy.latlng_to_cell(lat, lng, res)) - - def cell_to_latlng(self, h): - """ - Return the center point of an H3 cell as a lat/lng pair. - - Parameters - ---------- - h : H3Cell - - Returns - ------- - lat : float - Latitude - lng : float - Longitude - """ - return _cy.cell_to_latlng(self._in_scalar(h)) - - def get_resolution(self, h): - """ - Return the resolution of an H3 cell. - - Parameters - ---------- - h : H3Cell - - Returns - ------- - int - """ - # todo: could also work for edges - return _cy.get_resolution(self._in_scalar(h)) - - def cell_to_parent(self, h, res=None): - """ - Get the parent of a cell. - - Parameters - ---------- - h : H3Cell - res : int or None, optional - The resolution for the parent - If ``None``, then ``res = resolution(h) - 1`` - - Returns - ------- - H3Cell - """ - h = self._in_scalar(h) - p = _cy.cell_to_parent(h, res) - p = self._out_scalar(p) - - return p - - def grid_distance(self, h1, h2): - """ - Compute the H3 distance between two cells. - - The H3 distance is defined as the length of the shortest - path between the cells in the graph formed by connecting - adjacent cells. - - This function will raise an exception if the - cells are too far apart to compute the distance. - - Parameters - ---------- - h1 : H3Cell - h2 : H3Cell - - Returns - ------- - int - """ - h1 = self._in_scalar(h1) - h2 = self._in_scalar(h2) - - d = _cy.grid_distance(h1, h2) - - return d - - def cell_to_boundary(self, h): - """ - Return tuple of lat/lng pairs describing the cell boundary. - - Parameters - ---------- - h : H3Cell - - Returns - ------- - tuple of (lat, lng) tuples - """ - return _cy.cell_to_boundary(self._in_scalar(h)) - - def grid_disk(self, h, k=1): - """ - Return unordered set of cells with H3 distance ``<= k`` from ``h``. - That is, the "filled-in" disk. - - Parameters - ---------- - h : H3Cell - k : int - Size of disk. - - Returns - ------- - unordered collection of H3Cell - """ - mv = _cy.grid_disk(self._in_scalar(h), k) - - return self._out_unordered(mv) - - def grid_ring(self, h, k=1): - """ - Return unordered set of cells with H3 distance ``== k`` from ``h``. - That is, the "hollow" ring. - - Parameters - ---------- - h : H3Cell - k : int - Size of ring. - - Returns - ------- - unordered collection of H3Cell - """ - mv = _cy.grid_ring(self._in_scalar(h), k) - - return self._out_unordered(mv) - - def cell_to_children(self, h, res=None): - """ - Children of a cell. - - Parameters - ---------- - h : H3Cell - res : int or None, optional - The resolution for the children. - If ``None``, then ``res = resolution(h) + 1`` - - Returns - ------- - unordered collection of H3Cell - """ - mv = _cy.cell_to_children(self._in_scalar(h), res) - - return self._out_unordered(mv) - - # todo: nogil for expensive C operation? - def compact_cells(self, cells): - """ - Compact a collection of H3 cells by combining - smaller cells into larger cells, if all child cells - are present. Input cells must all share the same resolution. - - Parameters - ---------- - cells : iterable of H3 Cells - - Returns - ------- - unordered collection of H3Cell - """ - # todo: does compact_cells work on mixed-resolution collections? - hu = self._in_collection(cells) - hc = _cy.compact_cells(hu) - - return self._out_unordered(hc) - - def uncompact_cells(self, cells, res): - """ - Reverse the `compact_cells` operation. - - Return a collection of H3 cells, all of resolution ``res``. - - Parameters - ---------- - cells : iterable of H3Cell - res : int - Resolution of desired output cells. - - Returns - ------- - unordered collection of H3Cell - - Raises - ------ - todo: add test to make sure an error is returned when input - contains cell smaller than output res. - https://github.com/uber/h3/blob/master/src/h3lib/lib/h3Index.c#L425 - """ - hc = self._in_collection(cells) - hu = _cy.uncompact_cells(hc, res) - - return self._out_unordered(hu) - - def h3shape_to_cells(self, h3shape, res): - """ - Return the set of H3 cells at a given resolution whose center points - are contained within an `H3Poly` or `H3MultiPoly`. - - Parameters - ---------- - h3shape : H3shape - res : int - Resolution of the output cells - - Returns - ------- - unordered collection of H3Cell - - Examples - -------- - - >>> poly = H3Poly( - ... [(37.68, -122.54), (37.68, -122.34), (37.82, -122.34), - ... (37.82, -122.54)], - ... ) - >>> h3.h3shape_to_cells(poly, 6) - {'862830807ffffff', - '862830827ffffff', - '86283082fffffff', - '862830877ffffff', - '862830947ffffff', - '862830957ffffff', - '86283095fffffff'} - """ - - # todo: not sure if i want this dispatch logic here. maybe in the objects? - if isinstance(h3shape, H3Poly): - poly = h3shape - mv = _cy.polygon_to_cells(poly.outer, res, holes=poly.holes) - elif isinstance(h3shape, H3MultiPoly): - mpoly = h3shape - mv = _cy.polygons_to_cells(mpoly.polys, res) - else: - raise ValueError('Unrecognized type: ' + str(type(h3shape))) - - return self._out_unordered(mv) - - def cells_to_h3shape(self, cells, tight=True): - """ - Return a H3MultiPoly describing the area covered by a set of H3 cells. - - Parameters - ---------- - cells : iterable of H3 cells - tight : bool - If True, return H3Poly if possible. If False, always return H3MultiPoly - - Returns - ------- - H3Poly | H3MultiPoly - - Examples - -------- - - >>> cells = ['8428309ffffffff', '842830dffffffff'] - >>> h3.cells_to_h3shape(cells, tight=True) - - >>> h3.cells_to_h3shape(cells, tight=False) - - """ - cells = self._in_collection(cells) - mpoly = _cy.cells_to_multi_polygon(cells) - - polys = [H3Poly(*poly) for poly in mpoly] - out = H3MultiPoly(*polys) - - if tight and len(out) == 1: - out = out[0] - - return out - - def geo_to_cells(self, geo, res): - """ Convert from __geo_interface__ to cells. - - Parameters - ---------- - geo : an object implementing `__geo_interface__` or a dictionary in that format. - Both H3Poly and H3MultiPoly implement the interface. - res : int - Resolution of desired output cells. - """ - h3shape = geo_to_h3shape(geo) - return self.h3shape_to_cells(h3shape, res) - - def cells_to_geo(self, cells, tight=True): - """ - Parameters - ---------- - cells : iterable of H3 Cells - - Returns - ------- - dict - in `__geo_interface__` format - """ - h3shape = self.cells_to_h3shape(cells, tight=tight) - return h3shape_to_geo(h3shape) - - def is_pentagon(self, h): - """ - Identify if an H3 cell is a pentagon. - - Parameters - ---------- - h : H3Index - - Returns - ------- - bool - ``True`` if input is a valid H3 cell which is a pentagon. - - Notes - ----- - A pentagon should *also* pass ``is_valid_cell()``. - Will return ``False`` for valid H3Edge. - """ - return _cy.is_pentagon(self._in_scalar(h)) - - def get_base_cell_number(self, h): - """ - Return the base cell *number* (``0`` to ``121``) of the given cell. - - The base cell *number* and the H3Index are two different representations - of the same cell: the parent cell of resolution ``0``. - - The base cell *number* is encoded within the corresponding - H3Index. - - todo: could work with edges - - Parameters - ---------- - h : H3Cell - - Returns - ------- - int - """ - return _cy.get_base_cell_number(self._in_scalar(h)) - - def are_neighbor_cells(self, h1, h2): - """ - Returns ``True`` if ``h1`` and ``h2`` are neighboring cells. - - Parameters - ---------- - h1 : H3Cell - h2 : H3Cell - - Returns - ------- - bool - """ - h1 = self._in_scalar(h1) - h2 = self._in_scalar(h2) - - return _cy.are_neighbor_cells(h1, h2) - - def cells_to_directed_edge(self, origin, destination): - """ - Create an H3 Index denoting a unidirectional edge. - - The edge is constructed from neighboring cells ``origin`` and - ``destination``. - - Parameters - ---------- - origin : H3Cell - destination : H3Cell - - Raises - ------ - ValueError - When cells are not adjacent. - - Returns - ------- - H3Edge - """ - o = self._in_scalar(origin) - d = self._in_scalar(destination) - e = _cy.cells_to_directed_edge(o, d) - e = self._out_scalar(e) - - return e - - def get_directed_edge_origin(self, e): - """ - Origin cell from an H3 directed edge. - - Parameters - ---------- - e : H3Edge - - Returns - ------- - H3Cell - """ - e = self._in_scalar(e) - o = _cy.get_directed_edge_origin(e) - o = self._out_scalar(o) - - return o - - def get_directed_edge_destination(self, e): - """ - Destination cell from an H3 directed edge. - - Parameters - ---------- - e : H3Edge - - Returns - ------- - H3Cell - """ - e = self._in_scalar(e) - d = _cy.get_directed_edge_destination(e) - d = self._out_scalar(d) - - return d - - def directed_edge_to_cells(self, e): - """ - Return (origin, destination) tuple from H3 directed edge - - Parameters - ---------- - e : H3Edge - - Returns - ------- - H3Cell - Origin cell of edge - H3Cell - Destination cell of edge - """ - e = self._in_scalar(e) - o, d = _cy.directed_edge_to_cells(e) - o, d = self._out_scalar(o), self._out_scalar(d) - - return o, d - - def origin_to_directed_edges(self, origin): - """ - Return all directed edges starting from ``origin`` cell. - - Parameters - ---------- - origin : H3Cell - - Returns - ------- - unordered collection of H3Edge - """ - mv = _cy.origin_to_directed_edges(self._in_scalar(origin)) - - return self._out_unordered(mv) - - def directed_edge_to_boundary(self, edge): - return _cy.directed_edge_to_boundary(self._in_scalar(edge)) - - def grid_path_cells(self, start, end): - """ - Returns the ordered collection of cells denoting a - minimum-length non-unique path between cells. - - Parameters - ---------- - start : H3Cell - end : H3Cell - - Returns - ------- - ordered collection of H3Cell - Starting with ``start``, and ending with ``end``. - """ - mv = _cy.grid_path_cells(self._in_scalar(start), self._in_scalar(end)) - - return self._out_ordered(mv) - - def is_res_class_III(self, h): - """ - Determine if cell has orientation "Class II" or "Class III". - - The orientation of pentagons/hexagons on the icosahedron can be one - of two types: "Class II" or "Class III". - - All cells within a resolution have the same type, and the type - alternates between resolutions. - - "Class II" cells have resolutions: 0,2,4,6,8,10,12,14 - "Class III" cells have resolutions: 1,3,5,7,9,11,13,15 - - Parameters - ---------- - h : H3Cell - - Returns - ------- - bool - ``True`` if ``h`` is "Class III". - ``False`` if ``h`` is "Class II". - - References - ---------- - 1. https://uber.github.io/h3/#/documentation/core-library/coordinate-systems - """ - return _cy.is_res_class_iii(self._in_scalar(h)) - - def get_pentagons(self, res): - """ - Return all pentagons at a given resolution. - - Parameters - ---------- - res : int - Resolution of the pentagons - - Returns - ------- - unordered collection of H3Cell - """ - mv = _cy.get_pentagons(res) - - return self._out_unordered(mv) - - def get_res0_cells(self): - """ - Return all cells at resolution 0. - - Parameters - ---------- - None - - Returns - ------- - unordered collection of H3Cell - """ - mv = _cy.get_res0_cells() - - return self._out_unordered(mv) - - def cell_to_center_child(self, h, res=None): - """ - Get the center child of a cell at some finer resolution. - - Parameters - ---------- - h : H3Cell - res : int or None, optional - The resolution for the child cell - If ``None``, then ``res = resolution(h) + 1`` - - Returns - ------- - H3Cell - """ - h = self._in_scalar(h) - p = _cy.cell_to_center_child(h, res) - p = self._out_scalar(p) - - return p - - def get_icosahedron_faces(self, h): - """ - Return icosahedron faces intersecting a given H3 cell. - - There are twenty possible faces, ranging from 0--19. - - Note: Every interface returns a Python ``set`` of ``int``. - - Parameters - ---------- - h : H3Cell - - Returns - ------- - Python ``set`` of ``int`` - """ - h = self._in_scalar(h) - faces = _cy.get_icosahedron_faces(h) - - return faces - - def cell_to_local_ij(self, origin, h): - """ - Return local (i,j) coordinates of cell ``h`` in relation to ``origin`` cell - - - Parameters - ---------- - origin : H3Cell - Origin/central cell for defining i,j coordinates. - h: H3Cell - Destination cell whose i,j coordinates we'd like, based off - of the origin cell. - - - Returns - ------- - Tuple (i, j) of integer local coordinates of cell ``h`` - - - Notes - ----- - - The ``origin`` cell does not define (0, 0) for the IJ coordinate space. - (0, 0) refers to the center of the base cell containing origin at the - resolution of `origin`. - Subtracting the IJ coordinates of ``origin`` from every cell would get - you the property of (0, 0) being the ``origin``. - - This is done so we don't need to keep recomputing the coordinates of - ``origin`` if not needed. - """ - origin = self._in_scalar(origin) - h = self._in_scalar(h) - - i, j = _cy.cell_to_local_ij(origin, h) - - return i, j - - def local_ij_to_cell(self, origin, i, j): - """ - Return cell at local (i,j) position relative to the ``origin`` cell. - - Parameters - ---------- - origin : H3Cell - Origin/central cell for defining i,j coordinates. - i, j: int - Integer coordinates with respect to ``origin`` cell. - - - Returns - ------- - H3Cell at local (i,j) position relative to the ``origin`` cell - - - Notes - ----- - - The ``origin`` cell does not define (0, 0) for the IJ coordinate space. - (0, 0) refers to the center of the base cell containing origin at the - resolution of ``origin``. - Subtracting the IJ coordinates of ``origin`` from every cell would get - you the property of (0, 0) being the ``origin``. - - This is done so we don't need to keep recomputing the coordinates of - ``origin`` if not needed. - """ - origin = self._in_scalar(origin) - - h = _cy.local_ij_to_cell(origin, i, j) - h = self._out_scalar(h) - - return h - - def cell_area(self, h, unit='km^2'): - """ - Compute the spherical surface area of a specific H3 cell. - - Parameters - ---------- - h : H3Cell - unit: str - Unit for area result (``'km^2'``, 'm^2', or 'rads^2') - - - Returns - ------- - The area of the H3 cell in the given units - - - Notes - ----- - This function breaks the cell into spherical triangles, and computes - their spherical area. - The function uses the spherical distance calculation given by - `great_circle_distance`. - """ - h = self._in_scalar(h) - - return _cy.cell_area(h, unit=unit) - - def edge_length(self, e, unit='km'): - """ - Compute the spherical length of a specific H3 edge. - - Parameters - ---------- - h : H3Cell - unit: str - Unit for length result ('km', 'm', or 'rads') - - - Returns - ------- - The length of the edge in the given units - - - Notes - ----- - This function uses the spherical distance calculation given by - `great_circle_distance`. - """ - e = self._in_scalar(e) - - return _cy.edge_length(e, unit=unit) - - @staticmethod - def great_circle_distance(latlng1, latlng2, unit='km'): - """ - Compute the spherical distance between two (lat, lng) points. - AKA: great circle distance or "haversine" distance. - - todo: overload to allow two cell inputs? - - Parameters - ---------- - latlng1 : tuple - (lat, lng) tuple in degrees - latlng2 : tuple - (lat, lng) tuple in degrees - unit: str - Unit for distance result ('km', 'm', or 'rads') - - - Returns - ------- - The spherical distance between the points in the given units - """ - lat1, lng1 = latlng1 - lat2, lng2 = latlng2 - return _cy.great_circle_distance( - lat1, lng1, - lat2, lng2, - unit = unit - ) diff --git a/src/h3/api/basic_int/__init__.py b/src/h3/api/basic_int/__init__.py index a2974611..a9858cb8 100644 --- a/src/h3/api/basic_int/__init__.py +++ b/src/h3/api/basic_int/__init__.py @@ -1,9 +1,939 @@ -# flake8: noqa -from ._public_api import * +# This file is **symlinked** across the APIs to ensure they are exactly the same. + +from ... import _cy from ..._h3shape import ( - H3MultiPoly, - H3Poly, - H3Shape, - geo_to_h3shape, - h3shape_to_geo, + H3Shape, + H3Poly, + H3MultiPoly, + geo_to_h3shape, + h3shape_to_geo, ) + +from ._convert import ( + _in_scalar, + _out_scalar, + _in_collection, + _out_unordered, + _out_ordered, +) + + +def versions(): + """ + Version numbers for the Python (wrapper) and C (wrapped) libraries. + + Versions are output as strings of the form ``'X.Y.Z'``. + C and Python should match on ``X`` (major) and ``Y`` (minor), + but may differ on ``Z`` (patch). + + Returns + ------- + dict like ``{'c': 'X.Y.Z', 'python': 'A.B.C'}`` + """ + from ..._version import __version__ + + v = { + 'c': _cy.c_version(), + 'python': __version__, + } + + return v + + +def str_to_int(h): + """ + Converts a hexadecimal string to an H3 64-bit integer index. + + Parameters + ---------- + h : str + Hexadecimal string like ``'89754e64993ffff'`` + + Returns + ------- + int + Unsigned 64-bit integer + """ + return _cy.str_to_int(h) + + +def int_to_str(x): + """ + Converts an H3 64-bit integer index to a hexadecimal string. + + Parameters + ---------- + x : int + Unsigned 64-bit integer + + Returns + ------- + str + Hexadecimal string like ``'89754e64993ffff'`` + """ + return _cy.int_to_str(x) + + +def get_num_cells(res): + """ + Return the total number of *cells* (hexagons and pentagons) + for the given resolution. + + Returns + ------- + int + """ + return _cy.get_num_cells(res) + + +def average_hexagon_area(res, unit='km^2'): + """ + Return the average area of an H3 *hexagon* + for the given resolution. + + This average *excludes* pentagons. + + Returns + ------- + float + """ + return _cy.average_hexagon_area(res, unit) + + +def average_hexagon_edge_length(res, unit='km'): + """ + Return the average *hexagon* edge length + for the given resolution. + + This average *excludes* pentagons. + + Returns + ------- + float + """ + return _cy.average_hexagon_edge_length(res, unit) + + +def is_valid_cell(h): + """ + Validates an H3 cell (hexagon or pentagon). + + Returns + ------- + bool + """ + try: + h = _in_scalar(h) + return _cy.is_valid_cell(h) + except (ValueError, TypeError): + return False + + +def is_valid_directed_edge(edge): + """ + Validates an H3 unidirectional edge. + + Returns + ------- + bool + """ + try: + e = _in_scalar(edge) + return _cy.is_valid_directed_edge(e) + except (ValueError, TypeError): + return False + + +def latlng_to_cell(lat, lng, res): + """ + Return the cell containing the (lat, lng) point + for a given resolution. + + Returns + ------- + H3Cell + + """ + return _out_scalar(_cy.latlng_to_cell(lat, lng, res)) + + +def cell_to_latlng(h): + """ + Return the center point of an H3 cell as a lat/lng pair. + + Parameters + ---------- + h : H3Cell + + Returns + ------- + lat : float + Latitude + lng : float + Longitude + """ + return _cy.cell_to_latlng(_in_scalar(h)) + + +def get_resolution(h): + """ + Return the resolution of an H3 cell. + + Parameters + ---------- + h : H3Cell + + Returns + ------- + int + """ + # todo: could also work for edges + return _cy.get_resolution(_in_scalar(h)) + + +def cell_to_parent(h, res=None): + """ + Get the parent of a cell. + + Parameters + ---------- + h : H3Cell + res : int or None, optional + The resolution for the parent + If ``None``, then ``res = resolution(h) - 1`` + + Returns + ------- + H3Cell + """ + h = _in_scalar(h) + p = _cy.cell_to_parent(h, res) + p = _out_scalar(p) + + return p + + +def grid_distance(h1, h2): + """ + Compute the H3 distance between two cells. + + The H3 distance is defined as the length of the shortest + path between the cells in the graph formed by connecting + adjacent cells. + + This function will raise an exception if the + cells are too far apart to compute the distance. + + Parameters + ---------- + h1 : H3Cell + h2 : H3Cell + + Returns + ------- + int + """ + h1 = _in_scalar(h1) + h2 = _in_scalar(h2) + + d = _cy.grid_distance(h1, h2) + + return d + + +def cell_to_boundary(h): + """ + Return tuple of lat/lng pairs describing the cell boundary. + + Parameters + ---------- + h : H3Cell + + Returns + ------- + tuple of (lat, lng) tuples + """ + return _cy.cell_to_boundary(_in_scalar(h)) + + +def grid_disk(h, k=1): + """ + Return unordered set of cells with H3 distance ``<= k`` from ``h``. + That is, the 'filled-in' disk. + + Parameters + ---------- + h : H3Cell + k : int + Size of disk. + + Returns + ------- + unordered collection of H3Cell + """ + mv = _cy.grid_disk(_in_scalar(h), k) + + return _out_unordered(mv) + + +def grid_ring(h, k=1): + """ + Return unordered set of cells with H3 distance ``== k`` from ``h``. + That is, the "hollow" ring. + + Parameters + ---------- + h : H3Cell + k : int + Size of ring. + + Returns + ------- + unordered collection of H3Cell + """ + mv = _cy.grid_ring(_in_scalar(h), k) + + return _out_unordered(mv) + + +def cell_to_children(h, res=None): + """ + Children of a cell. + + Parameters + ---------- + h : H3Cell + res : int or None, optional + The resolution for the children. + If ``None``, then ``res = resolution(h) + 1`` + + Returns + ------- + unordered collection of H3Cell + """ + mv = _cy.cell_to_children(_in_scalar(h), res) + + return _out_unordered(mv) + + +# todo: nogil for expensive C operation? +def compact_cells(cells): + """ + Compact a collection of H3 cells by combining + smaller cells into larger cells, if all child cells + are present. Input cells must all share the same resolution. + + Parameters + ---------- + cells : iterable of H3 Cells + + Returns + ------- + unordered collection of H3Cell + """ + # todo: does compact_cells work on mixed-resolution collections? + hu = _in_collection(cells) + hc = _cy.compact_cells(hu) + + return _out_unordered(hc) + + +def uncompact_cells(cells, res): + """ + Reverse the `compact_cells` operation. + + Return a collection of H3 cells, all of resolution ``res``. + + Parameters + ---------- + cells : iterable of H3Cell + res : int + Resolution of desired output cells. + + Returns + ------- + unordered collection of H3Cell + + Raises + ------ + todo: add test to make sure an error is returned when input + contains cell smaller than output res. + https://github.com/uber/h3/blob/master/src/h3lib/lib/h3Index.c#L425 + """ + hc = _in_collection(cells) + hu = _cy.uncompact_cells(hc, res) + + return _out_unordered(hu) + + +def h3shape_to_cells(h3shape, res): + """ + Return the set of H3 cells at a given resolution whose center points + are contained within an `H3Poly` or `H3MultiPoly`. + + Parameters + ---------- + h3shape : H3shape + res : int + Resolution of the output cells + + Returns + ------- + unordered collection of H3Cell + + Examples + -------- + + >>> poly = H3Poly( + ... [(37.68, -122.54), (37.68, -122.34), (37.82, -122.34), + ... (37.82, -122.54)], + ... ) + >>> h3.h3shape_to_cells(poly, 6) + {'862830807ffffff', + '862830827ffffff', + '86283082fffffff', + '862830877ffffff', + '862830947ffffff', + '862830957ffffff', + '86283095fffffff'} + """ + + # todo: not sure if i want this dispatch logic here. maybe in the objects? + if isinstance(h3shape, H3Poly): + poly = h3shape + mv = _cy.polygon_to_cells(poly.outer, res, holes=poly.holes) + elif isinstance(h3shape, H3MultiPoly): + mpoly = h3shape + mv = _cy.polygons_to_cells(mpoly.polys, res) + elif isinstance(h3shape, H3Shape): + raise ValueError('Unrecognized H3Shape: ' + str(h3shape)) + else: + raise ValueError('Unrecognized type: ' + str(type(h3shape))) + + return _out_unordered(mv) + + +def cells_to_h3shape(cells, tight=True): + """ + Return a H3MultiPoly describing the area covered by a set of H3 cells. + + Parameters + ---------- + cells : iterable of H3 cells + tight : bool + If True, return H3Poly if possible. If False, always return H3MultiPoly + + Returns + ------- + H3Poly | H3MultiPoly + + Examples + -------- + + >>> cells = ['8428309ffffffff', '842830dffffffff'] + >>> h3.cells_to_h3shape(cells, tight=True) + + >>> h3.cells_to_h3shape(cells, tight=False) + + """ + cells = _in_collection(cells) + mpoly = _cy.cells_to_multi_polygon(cells) + + polys = [H3Poly(*poly) for poly in mpoly] + out = H3MultiPoly(*polys) + + if tight and len(out) == 1: + out = out[0] + + return out + + +def geo_to_cells(geo, res): + """Convert from __geo_interface__ to cells. + + Parameters + ---------- + geo : an object implementing `__geo_interface__` or a dictionary in that format. + Both H3Poly and H3MultiPoly implement the interface. + res : int + Resolution of desired output cells. + """ + h3shape = geo_to_h3shape(geo) + return h3shape_to_cells(h3shape, res) + + +def cells_to_geo(cells, tight=True): + """ + Parameters + ---------- + cells : iterable of H3 Cells + + Returns + ------- + dict + in `__geo_interface__` format + """ + h3shape = cells_to_h3shape(cells, tight=tight) + return h3shape_to_geo(h3shape) + + +def is_pentagon(h): + """ + Identify if an H3 cell is a pentagon. + + Parameters + ---------- + h : H3Index + + Returns + ------- + bool + ``True`` if input is a valid H3 cell which is a pentagon. + + Notes + ----- + A pentagon should *also* pass ``is_valid_cell()``. + Will return ``False`` for valid H3Edge. + """ + return _cy.is_pentagon(_in_scalar(h)) + + +def get_base_cell_number(h): + """ + Return the base cell *number* (``0`` to ``121``) of the given cell. + + The base cell *number* and the H3Index are two different representations + of the same cell: the parent cell of resolution ``0``. + + The base cell *number* is encoded within the corresponding + H3Index. + + todo: could work with edges + + Parameters + ---------- + h : H3Cell + + Returns + ------- + int + """ + return _cy.get_base_cell_number(_in_scalar(h)) + + +def are_neighbor_cells(h1, h2): + """ + Returns ``True`` if ``h1`` and ``h2`` are neighboring cells. + + Parameters + ---------- + h1 : H3Cell + h2 : H3Cell + + Returns + ------- + bool + """ + h1 = _in_scalar(h1) + h2 = _in_scalar(h2) + + return _cy.are_neighbor_cells(h1, h2) + + +def cells_to_directed_edge(origin, destination): + """ + Create an H3 Index denoting a unidirectional edge. + + The edge is constructed from neighboring cells ``origin`` and + ``destination``. + + Parameters + ---------- + origin : H3Cell + destination : H3Cell + + Raises + ------ + ValueError + When cells are not adjacent. + + Returns + ------- + H3Edge + """ + o = _in_scalar(origin) + d = _in_scalar(destination) + e = _cy.cells_to_directed_edge(o, d) + e = _out_scalar(e) + + return e + + +def get_directed_edge_origin(e): + """ + Origin cell from an H3 directed edge. + + Parameters + ---------- + e : H3Edge + + Returns + ------- + H3Cell + """ + e = _in_scalar(e) + o = _cy.get_directed_edge_origin(e) + o = _out_scalar(o) + + return o + + +def get_directed_edge_destination(e): + """ + Destination cell from an H3 directed edge. + + Parameters + ---------- + e : H3Edge + + Returns + ------- + H3Cell + """ + e = _in_scalar(e) + d = _cy.get_directed_edge_destination(e) + d = _out_scalar(d) + + return d + + +def directed_edge_to_cells(e): + """ + Return (origin, destination) tuple from H3 directed edge + + Parameters + ---------- + e : H3Edge + + Returns + ------- + H3Cell + Origin cell of edge + H3Cell + Destination cell of edge + """ + e = _in_scalar(e) + o, d = _cy.directed_edge_to_cells(e) + o, d = _out_scalar(o), _out_scalar(d) + + return o, d + + +def origin_to_directed_edges(origin): + """ + Return all directed edges starting from ``origin`` cell. + + Parameters + ---------- + origin : H3Cell + + Returns + ------- + unordered collection of H3Edge + """ + mv = _cy.origin_to_directed_edges(_in_scalar(origin)) + + return _out_unordered(mv) + + +def directed_edge_to_boundary(edge): + return _cy.directed_edge_to_boundary(_in_scalar(edge)) + + +def grid_path_cells(start, end): + """ + Returns the ordered collection of cells denoting a + minimum-length non-unique path between cells. + + Parameters + ---------- + start : H3Cell + end : H3Cell + + Returns + ------- + ordered collection of H3Cell + Starting with ``start``, and ending with ``end``. + """ + mv = _cy.grid_path_cells(_in_scalar(start), _in_scalar(end)) + + return _out_ordered(mv) + + +def is_res_class_III(h): + """ + Determine if cell has orientation "Class II" or "Class III". + + The orientation of pentagons/hexagons on the icosahedron can be one + of two types: "Class II" or "Class III". + + All cells within a resolution have the same type, and the type + alternates between resolutions. + + "Class II" cells have resolutions: 0,2,4,6,8,10,12,14 + "Class III" cells have resolutions: 1,3,5,7,9,11,13,15 + + Parameters + ---------- + h : H3Cell + + Returns + ------- + bool + ``True`` if ``h`` is "Class III". + ``False`` if ``h`` is "Class II". + + References + ---------- + 1. https://uber.github.io/h3/#/documentation/core-library/coordinate-systems + """ + return _cy.is_res_class_iii(_in_scalar(h)) + + +def get_pentagons(res): + """ + Return all pentagons at a given resolution. + + Parameters + ---------- + res : int + Resolution of the pentagons + + Returns + ------- + unordered collection of H3Cell + """ + mv = _cy.get_pentagons(res) + + return _out_unordered(mv) + + +def get_res0_cells(): + """ + Return all cells at resolution 0. + + Parameters + ---------- + None + + Returns + ------- + unordered collection of H3Cell + """ + mv = _cy.get_res0_cells() + + return _out_unordered(mv) + + +def cell_to_center_child(h, res=None): + """ + Get the center child of a cell at some finer resolution. + + Parameters + ---------- + h : H3Cell + res : int or None, optional + The resolution for the child cell + If ``None``, then ``res = resolution(h) + 1`` + + Returns + ------- + H3Cell + """ + h = _in_scalar(h) + p = _cy.cell_to_center_child(h, res) + p = _out_scalar(p) + + return p + + +def get_icosahedron_faces(h): + """ + Return icosahedron faces intersecting a given H3 cell. + + There are twenty possible faces, ranging from 0--19. + + Note: Every interface returns a Python ``set`` of ``int``. + + Parameters + ---------- + h : H3Cell + + Returns + ------- + Python ``set`` of ``int`` + """ + h = _in_scalar(h) + faces = _cy.get_icosahedron_faces(h) + + return faces + + +def cell_to_local_ij(origin, h): + """ + Return local (i,j) coordinates of cell ``h`` in relation to ``origin`` cell + + + Parameters + ---------- + origin : H3Cell + Origin/central cell for defining i,j coordinates. + h: H3Cell + Destination cell whose i,j coordinates we'd like, based off + of the origin cell. + + + Returns + ------- + Tuple (i, j) of integer local coordinates of cell ``h`` + + + Notes + ----- + + The ``origin`` cell does not define (0, 0) for the IJ coordinate space. + (0, 0) refers to the center of the base cell containing origin at the + resolution of `origin`. + Subtracting the IJ coordinates of ``origin`` from every cell would get + you the property of (0, 0) being the ``origin``. + + This is done so we don't need to keep recomputing the coordinates of + ``origin`` if not needed. + """ + origin = _in_scalar(origin) + h = _in_scalar(h) + + i, j = _cy.cell_to_local_ij(origin, h) + + return i, j + + +def local_ij_to_cell(origin, i, j): + """ + Return cell at local (i,j) position relative to the ``origin`` cell. + + Parameters + ---------- + origin : H3Cell + Origin/central cell for defining i,j coordinates. + i, j: int + Integer coordinates with respect to ``origin`` cell. + + + Returns + ------- + H3Cell at local (i,j) position relative to the ``origin`` cell + + + Notes + ----- + + The ``origin`` cell does not define (0, 0) for the IJ coordinate space. + (0, 0) refers to the center of the base cell containing origin at the + resolution of ``origin``. + Subtracting the IJ coordinates of ``origin`` from every cell would get + you the property of (0, 0) being the ``origin``. + + This is done so we don't need to keep recomputing the coordinates of + ``origin`` if not needed. + """ + origin = _in_scalar(origin) + + h = _cy.local_ij_to_cell(origin, i, j) + h = _out_scalar(h) + + return h + + +def cell_area(h, unit='km^2'): + """ + Compute the spherical surface area of a specific H3 cell. + + Parameters + ---------- + h : H3Cell + unit: str + Unit for area result (``'km^2'``, 'm^2', or 'rads^2') + + + Returns + ------- + The area of the H3 cell in the given units + + + Notes + ----- + This function breaks the cell into spherical triangles, and computes + their spherical area. + The function uses the spherical distance calculation given by + `great_circle_distance`. + """ + h = _in_scalar(h) + + return _cy.cell_area(h, unit=unit) + + +def edge_length(e, unit='km'): + """ + Compute the spherical length of a specific H3 edge. + + Parameters + ---------- + h : H3Cell + unit: str + Unit for length result ('km', 'm', or 'rads') + + + Returns + ------- + The length of the edge in the given units + + + Notes + ----- + This function uses the spherical distance calculation given by + `great_circle_distance`. + """ + e = _in_scalar(e) + + return _cy.edge_length(e, unit=unit) + + +def great_circle_distance(latlng1, latlng2, unit='km'): + """ + Compute the spherical distance between two (lat, lng) points. + AKA: great circle distance or "haversine" distance. + + todo: overload to allow two cell inputs? + + Parameters + ---------- + latlng1 : tuple + (lat, lng) tuple in degrees + latlng2 : tuple + (lat, lng) tuple in degrees + unit: str + Unit for distance result ('km', 'm', or 'rads') + + + Returns + ------- + The spherical distance between the points in the given units + """ + lat1, lng1 = latlng1 + lat2, lng2 = latlng2 + return _cy.great_circle_distance( + lat1, lng1, + lat2, lng2, + unit = unit + ) diff --git a/src/h3/api/basic_int/_binding.py b/src/h3/api/basic_int/_binding.py deleted file mode 100644 index 28002dd9..00000000 --- a/src/h3/api/basic_int/_binding.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -This API handles H3 Indexes of type `int`, using -basic Python collections (`set`, `list`, `tuple`). -`h3` will interpret these Indexes as unsigned 64-bit integers. - -Input collections: - -- `Iterable[int]` - -Output collections: - -- `Set[int]` for unordered -- `List[int]` for ordered -""" - -from ... import _cy -from .._api_template import _API_FUNCTIONS - - -def _id(x): - return x - - -def _in_collection(cells): - it = list(cells) - - return _cy.iter_to_mv(it) - - -_binding = _API_FUNCTIONS( - _in_scalar=_id, - _out_scalar=_id, - _in_collection=_in_collection, - _out_unordered=set, # todo: should this be an (immutable) frozenset? - _out_ordered=list, # todo: should this be an (immutable) tuple? -) diff --git a/src/h3/api/basic_int/_convert.py b/src/h3/api/basic_int/_convert.py new file mode 100644 index 00000000..48cfcb94 --- /dev/null +++ b/src/h3/api/basic_int/_convert.py @@ -0,0 +1,18 @@ +from ... import _cy + + +def _in_scalar(x): + return x + + +_out_scalar = _in_scalar + + +def _in_collection(cells): + it = list(cells) + + return _cy.iter_to_mv(it) + + +_out_unordered = set +_out_ordered = list diff --git a/src/h3/api/basic_int/_public_api.py b/src/h3/api/basic_int/_public_api.py deleted file mode 100644 index 8e87df1b..00000000 --- a/src/h3/api/basic_int/_public_api.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Binding of public API names to module scope - -Prior to binding the API to module scope explicitly, we dynamically modified the -`globals` object when `h3` was imported, which caused problems with static tooling not -being able to understand the H3 API. - -This file exists to avoid dynamically modifying `globals` and support static tooling. -""" -from ._binding import _binding as _b - - -is_valid_cell = _b.is_valid_cell -is_pentagon = _b.is_pentagon -is_valid_directed_edge = _b.is_valid_directed_edge -is_res_class_III = _b.is_res_class_III - -int_to_str = _b.int_to_str -str_to_int = _b.str_to_int - -cell_area = _b.cell_area -edge_length = _b.edge_length -great_circle_distance = _b.great_circle_distance -average_hexagon_area = _b.average_hexagon_area -average_hexagon_edge_length = _b.average_hexagon_edge_length - -latlng_to_cell = _b.latlng_to_cell -cell_to_latlng = _b.cell_to_latlng -cell_to_boundary = _b.cell_to_boundary -cell_to_local_ij = _b.cell_to_local_ij -local_ij_to_cell = _b.local_ij_to_cell - -grid_ring = _b.grid_ring -grid_disk = _b.grid_disk -grid_distance = _b.grid_distance -grid_path_cells = _b.grid_path_cells - -get_num_cells = _b.get_num_cells -get_pentagons = _b.get_pentagons -get_res0_cells = _b.get_res0_cells -get_resolution = _b.get_resolution -get_base_cell_number = _b.get_base_cell_number -get_icosahedron_faces = _b.get_icosahedron_faces - -cell_to_parent = _b.cell_to_parent -cell_to_children = _b.cell_to_children -cell_to_center_child = _b.cell_to_center_child -compact_cells = _b.compact_cells -uncompact_cells = _b.uncompact_cells - -h3shape_to_cells = _b.h3shape_to_cells -cells_to_h3shape = _b.cells_to_h3shape -cells_to_geo = _b.cells_to_geo -geo_to_cells = _b.geo_to_cells - -are_neighbor_cells = _b.are_neighbor_cells -cells_to_directed_edge = _b.cells_to_directed_edge -directed_edge_to_cells = _b.directed_edge_to_cells -origin_to_directed_edges = _b.origin_to_directed_edges -get_directed_edge_origin = _b.get_directed_edge_origin -get_directed_edge_destination = _b.get_directed_edge_destination -directed_edge_to_boundary = _b.directed_edge_to_boundary - -versions = _b.versions diff --git a/src/h3/api/basic_str/__init__.py b/src/h3/api/basic_str/__init__.py deleted file mode 100644 index a2974611..00000000 --- a/src/h3/api/basic_str/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# flake8: noqa -from ._public_api import * -from ..._h3shape import ( - H3MultiPoly, - H3Poly, - H3Shape, - geo_to_h3shape, - h3shape_to_geo, -) diff --git a/src/h3/api/basic_str/__init__.py b/src/h3/api/basic_str/__init__.py new file mode 120000 index 00000000..92056a20 --- /dev/null +++ b/src/h3/api/basic_str/__init__.py @@ -0,0 +1 @@ +../basic_int/__init__.py \ No newline at end of file diff --git a/src/h3/api/basic_str/_binding.py b/src/h3/api/basic_str/_binding.py deleted file mode 100644 index 4ebd5fb1..00000000 --- a/src/h3/api/basic_str/_binding.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -This API handles H3 Indexes of type `str`, using -basic Python collections (`set`, `list`, `tuple`). -`h3` will interpret these Indexes as hexadecimal -representations of unsigned 64-bit integers. - -Input collections: - -- `Iterable[str]` - -Output collections: - -- `Set[str]` for unordered -- `List[str]` for ordered -""" - -from ... import _cy -from .._api_template import _API_FUNCTIONS - - -def _in_collection(cells): - it = [_cy.str_to_int(h) for h in cells] - - return _cy.iter_to_mv(it) - - -def _out_unordered(mv): - # todo: should this be an (immutable) frozenset? - return set(_cy.int_to_str(h) for h in mv) - - -def _out_ordered(mv): - # todo: should this be an (immutable) tuple? - return list(_cy.int_to_str(h) for h in mv) - - -_binding = _API_FUNCTIONS( - _in_scalar = _cy.str_to_int, - _out_scalar = _cy.int_to_str, - _in_collection = _in_collection, - _out_unordered = _out_unordered, - _out_ordered = _out_ordered, -) diff --git a/src/h3/api/basic_str/_convert.py b/src/h3/api/basic_str/_convert.py new file mode 100644 index 00000000..bad3a955 --- /dev/null +++ b/src/h3/api/basic_str/_convert.py @@ -0,0 +1,20 @@ +from ... import _cy + +_in_scalar = _cy.str_to_int +_out_scalar = _cy.int_to_str + + +def _in_collection(cells): + it = [_cy.str_to_int(h) for h in cells] + + return _cy.iter_to_mv(it) + + +def _out_unordered(mv): + # todo: should this be an (immutable) frozenset? + return set(_cy.int_to_str(h) for h in mv) + + +def _out_ordered(mv): + # todo: should this be an (immutable) tuple? + return list(_cy.int_to_str(h) for h in mv) diff --git a/src/h3/api/basic_str/_public_api.py b/src/h3/api/basic_str/_public_api.py deleted file mode 120000 index c4a6ebd8..00000000 --- a/src/h3/api/basic_str/_public_api.py +++ /dev/null @@ -1 +0,0 @@ -../basic_int/_public_api.py \ No newline at end of file diff --git a/src/h3/api/memview_int/__init__.py b/src/h3/api/memview_int/__init__.py deleted file mode 100644 index a2974611..00000000 --- a/src/h3/api/memview_int/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# flake8: noqa -from ._public_api import * -from ..._h3shape import ( - H3MultiPoly, - H3Poly, - H3Shape, - geo_to_h3shape, - h3shape_to_geo, -) diff --git a/src/h3/api/memview_int/__init__.py b/src/h3/api/memview_int/__init__.py new file mode 120000 index 00000000..92056a20 --- /dev/null +++ b/src/h3/api/memview_int/__init__.py @@ -0,0 +1 @@ +../basic_int/__init__.py \ No newline at end of file diff --git a/src/h3/api/memview_int/_binding.py b/src/h3/api/memview_int/_binding.py deleted file mode 100644 index 96efb77d..00000000 --- a/src/h3/api/memview_int/_binding.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -This API handles H3 Indexes of type `int` (specifically, `uint64`), -using Python `memoryview` objects for collections. -`h3` will interpret these Indexes as unsigned 64-bit integers. - -Input collections: - -- `memoryview[uint64]`, i.e., anything that supports the buffer protocol - - `dtype` must be `uint64`. for example, `long` will raise an error - - `list` or `set` inputs will not be accepted - -Output collections: - -- `memoryview[uint64]` for unordered -- `memoryview[uint64]` for ordered -""" - -from .._api_template import _API_FUNCTIONS - - -def _id(x): - return x - - -_binding = _API_FUNCTIONS( - _in_scalar = _id, - _out_scalar = _id, - _in_collection = _id, - _out_unordered = _id, - _out_ordered = _id, -) diff --git a/src/h3/api/memview_int/_convert.py b/src/h3/api/memview_int/_convert.py new file mode 100644 index 00000000..9145baf0 --- /dev/null +++ b/src/h3/api/memview_int/_convert.py @@ -0,0 +1,9 @@ +def _id(x): + return x + + +_in_scalar = _id +_out_scalar = _id +_in_collection = _id +_out_unordered = _id +_out_ordered = _id diff --git a/src/h3/api/memview_int/_public_api.py b/src/h3/api/memview_int/_public_api.py deleted file mode 120000 index c4a6ebd8..00000000 --- a/src/h3/api/memview_int/_public_api.py +++ /dev/null @@ -1 +0,0 @@ -../basic_int/_public_api.py \ No newline at end of file diff --git a/src/h3/api/numpy_int/__init__.py b/src/h3/api/numpy_int/__init__.py deleted file mode 100644 index a2974611..00000000 --- a/src/h3/api/numpy_int/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# flake8: noqa -from ._public_api import * -from ..._h3shape import ( - H3MultiPoly, - H3Poly, - H3Shape, - geo_to_h3shape, - h3shape_to_geo, -) diff --git a/src/h3/api/numpy_int/__init__.py b/src/h3/api/numpy_int/__init__.py new file mode 120000 index 00000000..92056a20 --- /dev/null +++ b/src/h3/api/numpy_int/__init__.py @@ -0,0 +1 @@ +../basic_int/__init__.py \ No newline at end of file diff --git a/src/h3/api/numpy_int/_binding.py b/src/h3/api/numpy_int/_binding.py deleted file mode 100644 index c00275eb..00000000 --- a/src/h3/api/numpy_int/_binding.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -This API handles H3 Indexes of type `int` (specifically, `uint64`), -using `numpy.array` objects for collections. -`h3` will interpret these Indexes as unsigned 64-bit integers. - -This API is **optional**, and will only work if the -user has `numpy` installed. - -Input collections: - -- `Iterable[int]` - - works for `lists`, but not `sets` - - will attempt to convert `int` to `uint64` - - no memory copy is made if input dtype is `uint64` - -Output collections: - -- `np.ndarray[np.uint64]` for unordered -- `np.ndarray[np.uint64]` for ordered -""" - -import numpy as np - -from .._api_template import _API_FUNCTIONS - - -def _id(x): - return x - - -def _in_collection(x): - # array is copied only if dtype does not match - # `list`s should work, but not `set`s of integers - return np.asarray(x, dtype='uint64') - - -_binding = _API_FUNCTIONS( - _in_scalar = _id, - _out_scalar = _id, - _in_collection = _in_collection, - _out_unordered = np.asarray, - _out_ordered = np.asarray, -) diff --git a/src/h3/api/numpy_int/_convert.py b/src/h3/api/numpy_int/_convert.py new file mode 100644 index 00000000..2db5b14d --- /dev/null +++ b/src/h3/api/numpy_int/_convert.py @@ -0,0 +1,16 @@ +def _in_scalar(x): + return x + + +_out_scalar = _in_scalar + + +def _in_collection(x): + import numpy as np + # array is copied only if dtype does not match + # `list`s should work, but not `set`s of integers + return np.asarray(x, dtype='uint64') + + +_out_unordered = _in_collection +_out_ordered = _in_collection diff --git a/src/h3/api/numpy_int/_public_api.py b/src/h3/api/numpy_int/_public_api.py deleted file mode 120000 index c4a6ebd8..00000000 --- a/src/h3/api/numpy_int/_public_api.py +++ /dev/null @@ -1 +0,0 @@ -../basic_int/_public_api.py \ No newline at end of file diff --git a/tests/polyfill/test_polygon_class.py b/tests/polyfill/test_polygon_class.py index 721eb752..ec450f56 100644 --- a/tests/polyfill/test_polygon_class.py +++ b/tests/polyfill/test_polygon_class.py @@ -35,3 +35,12 @@ def test_h3poly_len(): with pytest.raises(NotImplementedError): len(poly) + + +def test_bad_subclass(): + class H3Shoop(h3.H3Shape): + def __geo_interface__(): + pass + + with pytest.raises(ValueError): + h3.h3shape_to_cells(H3Shoop(), res=9) diff --git a/tests/test_api_bindings_match.py b/tests/test_api_bindings_match.py index 9bcb60b7..ccc2a9c1 100644 --- a/tests/test_api_bindings_match.py +++ b/tests/test_api_bindings_match.py @@ -3,13 +3,12 @@ def test_api_copy_match(): import h3 - import h3.api.numpy_int apis = [ - h3.api.basic_int._public_api, - h3.api.basic_str._public_api, - h3.api.memview_int._public_api, - h3.api.numpy_int._public_api, + h3.api.basic_int, + h3.api.basic_str, + h3.api.memview_int, + h3.api.numpy_int, ] api_set = {inspect.getsource(api) for api in apis}