Skip to content

Commit

Permalink
Introduce LatLngPoly and LatLngMultiPoly (#364)
Browse files Browse the repository at this point in the history
* introduce LatLngPoly

* LatLngPoly in files other than *.py

* drop 3.7 tests

* intro LatLngMultiPoly to .py files

* ...and to non .py files

* h3shape to just shape in .py files

* Class names

* function names

* H3Shape -> Shape

* filename

* Revert "H3Shape -> Shape"

This reverts commit 3897253.

* Revert "filename"

This reverts commit b263a3e.

* revert

* fix docs

* fix what David found

* rename params from `shape` to `h3shape`

* ...and a few more

* one more

* def cells_to_h3shape(cells, *, tight=True)
  • Loading branch information
ajfriend authored May 18, 2024
1 parent 26285ef commit f0fc6df
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 149 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-22.04]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/[email protected]
Expand Down
6 changes: 3 additions & 3 deletions docs/api_quick.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Functions relating H3 objects to geographic (lat/lng) coordinates.

## Polygon interface

The ``H3Poly`` and ``H3MultiPoly`` objects and their related functions allow users to represent (multi)polygons of lat/lng points and convert back and forth between H3 cells.
The ``LatLngPoly`` and ``LatLngMultiPoly`` objects and their related functions allow users to represent (multi)polygons of lat/lng points and convert back and forth between H3 cells.

The objects and functions also compatible with the popular [``__geo_interface__`` protocol](https://gist.github.com/sgillies/2217756), which is used by Python geospatial libraries like [GeoPandas](https://geopandas.org/en/stable/docs/reference/api/geopandas.GeoDataFrame.__geo_interface__.html) and many plotting libraries.

Expand All @@ -128,8 +128,8 @@ Note that this is reversed from [``__geo_interface__``](https://gist.github.com/
.. autosummary::
H3Shape
H3Poly
H3MultiPoly
LatLngPoly
LatLngMultiPoly
```

### Conversion functions
Expand Down
82 changes: 41 additions & 41 deletions docs/polygon_tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
"\n",
"`h3-py` can convert between sets of cells and GeoJSON-like polygon and multipolygon shapes.\n",
"\n",
"We use the abstract base class `H3Shape` and its concrete child classes `H3Poly` and `H3MultiPoly`\n",
"We use the abstract base class `H3Shape` and its concrete child classes `LatLngPoly` and `LatLngMultiPoly`\n",
"to represent these shapes. \n",
"Any references or function names that use \"H3Shape\" will apply to both `H3Poly` and `H3MultiPoly` objects.\n",
"Any references or function names that use \"H3Shape\" will apply to both `LatLngPoly` and `LatLngMultiPoly` objects.\n",
"\n",
"`h3-py` is also compatible with Python objects that implement `__geo_interface__` (https://gist.github.com/sgillies/2217756),\n",
"making it easy to interface with other Python geospatial libraries.\n",
"We'll refer to any such object as a \"geo\" or \"geo object\".\n",
"\n",
"`H3Poly` and `H3MultiPoly` both implement `__geo_interface__` (making them geo objects themselves),\n",
"`LatLngPoly` and `LatLngMultiPoly` both implement `__geo_interface__` (making them geo objects themselves),\n",
"and can be created from other geo objects, like Shapely's `Polygon` or `MultiPolygon` objects that occur\n",
"when using `geopandas`.\n",
"\n",
Expand Down Expand Up @@ -79,9 +79,9 @@
"id": "c5a41e2b-0c9e-45f5-8005-51af2ef8af32",
"metadata": {},
"source": [
"# H3Poly\n",
"# LatLngPoly\n",
"\n",
"We can create a simple `H3Poly` object by providing a list of the **latitude/longitude pairs** that describe its exterior.\n",
"We can create a simple `LatLngPoly` object by providing a list of the **latitude/longitude pairs** that describe its exterior.\n",
"Optionally, holes can be added to the polygon by appending additional lat/lng lists do describe them."
]
},
Expand All @@ -98,7 +98,7 @@
" (37.733, -122.501)\n",
"]\n",
"\n",
"poly = h3.H3Poly(outer)\n",
"poly = h3.LatLngPoly(outer)\n",
"print(poly)\n",
"plot_shape(poly)"
]
Expand All @@ -116,7 +116,7 @@
" (37.788, -122.454),\n",
"]\n",
"\n",
"poly = h3.H3Poly(outer, hole1)\n",
"poly = h3.LatLngPoly(outer, hole1)\n",
"print(poly)\n",
"plot_shape(poly)"
]
Expand All @@ -135,7 +135,7 @@
" (37.769, -122.496),\n",
"]\n",
"\n",
"poly = h3.H3Poly(outer, hole1, hole2)\n",
"poly = h3.LatLngPoly(outer, hole1, hole2)\n",
"print(poly)\n",
"plot_shape(poly)"
]
Expand All @@ -147,22 +147,22 @@
"source": [
"## String representation and attributes\n",
"\n",
"The `H3Poly` string representation given by its `__repr__` shows the number of vertices in the outer loop of the polygon, followed\n",
"The `LatLngPoly` string representation given by its `__repr__` shows the number of vertices in the outer loop of the polygon, followed\n",
"by the number of vertices in each hole.\n",
"\n",
"A representation like\n",
"\n",
"```\n",
"<H3Poly: [3/(3, 4)]>\n",
"<LatLngPoly: [3/(3, 4)]>\n",
"```\n",
"\n",
"denotes a polygon whose outer boundary consists of 3 vertices and which has 2 holes,\n",
"with 3 and 4 vertices respectively.\n",
"\n",
"We can access the coordinates describing the polygon through the attributes:\n",
"\n",
"- `H3Poly.outer` gives the list of lat/lng points making up the outer loop of the polygon.\n",
"- `H3Poly.holes` gives each of the lists of lat/lng points making up the holes of the polygon."
"- `LatLngPoly.outer` gives the list of lat/lng points making up the outer loop of the polygon.\n",
"- `LatLngPoly.holes` gives each of the lists of lat/lng points making up the holes of the polygon."
]
},
{
Expand All @@ -172,7 +172,7 @@
"metadata": {},
"outputs": [],
"source": [
"poly = h3.H3Poly(outer, hole1, hole2)\n",
"poly = h3.LatLngPoly(outer, hole1, hole2)\n",
"poly"
]
},
Expand Down Expand Up @@ -203,9 +203,9 @@
"source": [
"## `__geo_interface__`\n",
"\n",
"`H3Poly.__geo_interface__` gives a GeoJSON representation of the polygon as described in https://gist.github.com/sgillies/2217756\n",
"`LatLngPoly.__geo_interface__` gives a GeoJSON representation of the polygon as described in https://gist.github.com/sgillies/2217756\n",
"\n",
"**Note the differences in this representation**: Points are given in **lng/lat** order (but the `H3Poly` constructor expects **lat/lng** order), and the last vertex repeats the first."
"**Note the differences in this representation**: Points are given in **lng/lat** order (but the `LatLngPoly` constructor expects **lat/lng** order), and the last vertex repeats the first."
]
},
{
Expand Down Expand Up @@ -235,7 +235,7 @@
"id": "8f567fae-9f4e-472f-afcd-bd6f7bb32cba",
"metadata": {},
"source": [
"We can create an `H3Poly` object from a GeoJSON-like dictionary or an object that implements `__geo_interface__` using `h3.geo_to_h3shape()`."
"We can create an `LatLngPoly` object from a GeoJSON-like dictionary or an object that implements `__geo_interface__` using `h3.geo_to_h3shape()`."
]
},
{
Expand Down Expand Up @@ -272,7 +272,7 @@
"id": "0ab82119-9d9e-465d-94b5-61083e9d9df3",
"metadata": {},
"source": [
"Also note that `H3Poly.__geo_interface__` is equivalent to calling `h3.h3shape_to_geo()` on an `H3Poly` object."
"Also note that `LatLngPoly.__geo_interface__` is equivalent to calling `h3.h3shape_to_geo()` on an `LatLngPoly` object."
]
},
{
Expand All @@ -292,7 +292,7 @@
"source": [
"## Polygon to cells\n",
"\n",
"We can get all the H3 cells whose centroids fall within an `H3Poly` by using `h3.h3shape_to_cells()` and specifying the resolution."
"We can get all the H3 cells whose centroids fall within an `LatLngPoly` by using `h3.h3shape_to_cells()` and specifying the resolution."
]
},
{
Expand Down Expand Up @@ -350,15 +350,15 @@
"source": [
"## H3 Polygons don't need to follow the right-hand rule\n",
"\n",
"`H3Poly` objects do not need to follow the \"right-hand rule\", unlike GeoJSON Polygons. \n",
"`LatLngPoly` objects do not need to follow the \"right-hand rule\", unlike GeoJSON Polygons. \n",
"The right-hand rule requires that vertices in outer loops are listed in counterclockwise\n",
"order and holes are listed in clockwise order.\n",
"`h3-py` accepts loops in any order and will usually interpret them as the user intended, for example,\n",
"converting to sets of cells. However, `h3-py` won't re-order your loops to\n",
"conform to the right-hand rule, so be careful if you're using `__geo_interface__` to plot them.\n",
"\n",
"Obeying the right-hand rule is only a concern when creating `H3Poly` objects from external input; `H3Poly` or `H3MultiPoly`\n",
"objects created through `h3.cells_to_shape()` **will respect the right-hand rule**.\n",
"Obeying the right-hand rule is only a concern when creating `LatLngPoly` objects from external input; `LatLngPoly` or `LatLngMultiPoly`\n",
"objects created through `h3.cells_to_h3shape()` **will respect the right-hand rule**.\n",
"\n",
"For example, if we reverse the order of one of the holes in our example polygon above,\n",
"the hole won't be rendered correctly, but the conversion to cells will remain unchanged."
Expand All @@ -372,7 +372,7 @@
"outputs": [],
"source": [
"# Respects right-hand rule\n",
"poly = h3.H3Poly(outer, hole1, hole2)\n",
"poly = h3.LatLngPoly(outer, hole1, hole2)\n",
"plot_shape_and_cells(poly, res=10)"
]
},
Expand All @@ -385,7 +385,7 @@
"source": [
"# Does not respect right-hand-rule; second hole is reversed\n",
"# Conversion to cells still works, tho!\n",
"poly = h3.H3Poly(outer, hole1[::-1], hole2)\n",
"poly = h3.LatLngPoly(outer, hole1[::-1], hole2)\n",
"plot_shape_and_cells(poly, res=10)"
]
},
Expand All @@ -398,7 +398,7 @@
"source": [
"# Does not respect right-hand-rule; outer loop and second hole are both reversed\n",
"# Conversion to cells still works, tho!\n",
"poly = h3.H3Poly(outer[::-1], hole1[::-1], hole2)\n",
"poly = h3.LatLngPoly(outer[::-1], hole1[::-1], hole2)\n",
"plot_shape_and_cells(poly, res=10)"
]
},
Expand All @@ -407,13 +407,13 @@
"id": "b0b13d4a-a376-4a8f-b314-898d9cf94c59",
"metadata": {},
"source": [
"# H3MultiPoly\n",
"# LatLngMultiPoly\n",
"\n",
"An `H3MultiPoly` can be created from `H3Poly` objects. The string representation of the `H3MultiPoly`\n",
"gives the number of vertices in the outer loop of each `H3Poly`, along with the number of vertices\n",
"An `LatLngMultiPoly` can be created from `LatLngPoly` objects. The string representation of the `LatLngMultiPoly`\n",
"gives the number of vertices in the outer loop of each `LatLngPoly`, along with the number of vertices\n",
"in each hole (if there are any).\n",
"\n",
"For example `<H3MultiPoly: [3], [4/(5,)]>` represents an `H3MultiPoly` consisting of two `H3Poly` polygons:\n",
"For example `<LatLngMultiPoly: [3], [4/(5,)]>` represents an `LatLngMultiPoly` consisting of two `LatLngPoly` polygons:\n",
"\n",
"- the first polygon has 3 outer vertices and no holes\n",
"- the second polygon has 4 outer vertices and 1 hole with 5 vertices"
Expand All @@ -426,12 +426,12 @@
"metadata": {},
"outputs": [],
"source": [
"poly1 = h3.H3Poly([(37.804, -122.412), (37.778, -122.507), (37.733, -122.501)])\n",
"poly2 = h3.H3Poly(\n",
"poly1 = h3.LatLngPoly([(37.804, -122.412), (37.778, -122.507), (37.733, -122.501)])\n",
"poly2 = h3.LatLngPoly(\n",
" [(37.803, -122.408), (37.736, -122.491), (37.738, -122.380), (37.787, -122.39)],\n",
" [(37.760, -122.441), (37.772, -122.427), (37.773, -122.404), (37.758, -122.401), (37.745, -122.428)]\n",
")\n",
"mpoly = h3.H3MultiPoly(poly1, poly2)\n",
"mpoly = h3.LatLngMultiPoly(poly1, poly2)\n",
"\n",
"print(poly1)\n",
"print(poly2)\n",
Expand All @@ -455,7 +455,7 @@
"source": [
"## MultiPolygon to cells\n",
"\n",
"`h3.h3shape_to_cells()` works on both `H3MultiPoly` and `H3Poly` objects (both are subclasses of `H3Shape`)."
"`h3.h3shape_to_cells()` works on both `LatLngMultiPoly` and `LatLngPoly` objects (both are subclasses of `H3Shape`)."
]
},
{
Expand All @@ -474,10 +474,10 @@
"id": "f3712d99-cbe2-4f42-80e3-0acf3fcb8e25",
"metadata": {},
"source": [
"## H3MultiPoly affordances\n",
"## LatLngMultiPoly affordances\n",
"\n",
"- Calling `len()` on an `H3MultiPoly` gives the number of polygons\n",
"- You can iterate through a `H3MultiPoly`, with the elements being the underlying `H3Poly`s"
"- Calling `len()` on an `LatLngMultiPoly` gives the number of polygons\n",
"- You can iterate through a `LatLngMultiPoly`, with the elements being the underlying `LatLngPoly`s"
]
},
{
Expand Down Expand Up @@ -518,7 +518,7 @@
"source": [
"## `__geo_interface__`\n",
"\n",
"`H3MultiPoly` implements `__geo_interface__`, and `H3MultiPoly` objects can also be created through `h3.geo_to_h3shape()`."
"`LatLngMultiPoly` implements `__geo_interface__`, and `LatLngMultiPoly` objects can also be created through `h3.geo_to_h3shape()`."
]
},
{
Expand Down Expand Up @@ -548,9 +548,9 @@
"id": "a2b71ee2-deed-4336-8b2c-2ab740555188",
"metadata": {},
"source": [
"# Cells to H3Poly or H3MultiPoly\n",
"# Cells to LatLngPoly or LatLngMultiPoly\n",
"\n",
"If you have a set of H3 cells that you would like to visualize, you may want to convert them to `H3Poly` or `H3MultiPoly` objects using `h3.cells_to_h3shape()` and then use `__geo_interface__` to get their GeoJSON representation. Or you could\n",
"If you have a set of H3 cells that you would like to visualize, you may want to convert them to `LatLngPoly` or `LatLngMultiPoly` objects using `h3.cells_to_h3shape()` and then use `__geo_interface__` to get their GeoJSON representation. Or you could\n",
"use `h3.cells_to_geo()` to get the GeoJSON dictionary directly."
]
},
Expand Down Expand Up @@ -610,7 +610,7 @@
"\"Geo objects\" are any Python object that implements `__geo_interface__`. This is a widely used\n",
"standard in geospatial Python and is used by libraries like `geopandas` and Shapely.\n",
"\n",
"`H3Shape` is an abstract class implemented by `H3Poly` and `H3MultiPoly`, each of which\n",
"`H3Shape` is an abstract class implemented by `LatLngPoly` and `LatLngMultiPoly`, each of which\n",
"implement `__geo_interface__` and can be created from external \"Geo objects\":\n",
"\n",
"- `geo_to_h3shape()`\n",
Expand Down Expand Up @@ -810,7 +810,7 @@
"If we assign `df.geometry = cell_column` we'll get an error because the `geometry` column of a `geopandas.GeoDataFrame` must contain valid geometry objects.\n",
"We can obtain compatible objects by converting the cells to `H3Shape` by applying `h3.cells_to_h3shape()`.\n",
"\n",
"(Note that, unfortunately, Pandas has some logic to identify iterable members of a series and then renders a tuple of the elements, rather than our preferred `H3MultiPoly.__repr__` representation.)"
"(Note that, unfortunately, Pandas has some logic to identify iterable members of a series and then renders a tuple of the elements, rather than our preferred `LatLngMultiPoly.__repr__` representation.)"
]
},
{
Expand All @@ -829,7 +829,7 @@
"id": "1965d53f-2bba-4840-a627-1bbf55405be3",
"metadata": {},
"source": [
"Note that the column now consists of `H3Poly` and `H3MultiPoly` objects."
"Note that the column now consists of `LatLngPoly` and `LatLngMultiPoly` objects."
]
},
{
Expand Down
Loading

0 comments on commit f0fc6df

Please sign in to comment.