H3-Pandas
H3-Pandas copied to clipboard
ENH: Support LineStrings
A great feature of the hexagonal shape of H3 is how naturally it can delineate LineStrings such as roads, rivers in a connected and visually pleasing way.
It would thus make sense to provide a method that would provide a "linestring polyfill", generating a continuous string of H3 cells that cover the linestring. This could be done in two steps:
The interface and implementation could be similar to other similar methods, e.g. polyfill
I was just working on an H3 demo for a GIS forum, and wanted to demonstrate this. Came up with:
from shapely.geometry.linestring import LineString
from typing import Iterator
def sequential_deduplication(func: Iterator[str]) -> Iterator[str]:
'''
Decorator that doesn't permit two consecutive items to be the same
'''
def inner(*args):
iterable = func(*args)
last = None
while (cell := next(iterable, None)) is not None:
if cell != last:
yield cell
last = cell
return inner
@sequential_deduplication # prevent consecutive repetition, esp. at low res
def h3polyline(line: LineString, resolution: int) -> Iterator[str]:
'''
Iterator yielding H3 cells representing a line,
retaining order and any self-intersections
'''
coords = zip(line.coords, line.coords[1:])
while (vertex_pair := next(coords, None)) is not None:
i, j = vertex_pair
a = h3.geo_to_h3(*i[::-1], resolution)
b = h3.geo_to_h3(*j[::-1], resolution)
yield from h3.h3_line(a, b) # inclusive of a and b
Just thought it might help someone. I'm not sure there's any particular advantage doing this with generators, unless/until h3.h3_line is itself a generator.
Not sure what is supposed to happen with repeated cells, but intuitively I thought it made sense to attempt to retain order and self-intersections rather than to just return an unordered set.
Doesn't consider MultiLineStrings.
Version 2, adding multilinestring as valid input type.
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from typing import Iterator, Union
def sequential_deduplication(func: Iterator[str]) -> Iterator[str]:
'''
Decorator that doesn't permit two consecutive items to be the same
'''
def inner(*args):
iterable = func(*args)
last = None
while (cell := next(iterable, None)) is not None:
if cell != last:
yield cell
last = cell
return inner
@sequential_deduplication
def h3polyline(line: Union[LineString, MultiLineString], resolution: int) -> Iterator[str]:
'''
Iterator yeilding H3 cells representing a (multi)line,
retaining order and self-intersections
'''
if line.geom_type == 'MultiLineString':
# Recurse after getting component linestrings from the multiline
for l in map(lambda geom: h3polyline(geom, resolution), line.geoms):
yield from l
else:
coords = zip(line.coords, line.coords[1:])
while (vertex_pair := next(coords, None)) is not None:
i, j = vertex_pair
a = h3.geo_to_h3(*i[::-1], resolution)
b = h3.geo_to_h3(*j[::-1], resolution)
yield from h3.h3_line(a, b) # inclusive of a and b
Closed by https://github.com/DahnJ/H3-Pandas/pull/24 released as 0.2.6 🎉