Skip to content

Commit

Permalink
upgrade to h3o 0.7 (#66)
Browse files Browse the repository at this point in the history
* upgrade to h3o 0.7

* upgrade to h3o 0.7 - spatialindex
  • Loading branch information
nmandery authored Nov 21, 2024
1 parent 1876aa5 commit ae3d0c9
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 58 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ resolver = "2"
members = ["h3ronpy", "crates/h3arrow"]

[workspace.dependencies]
geo = "0.28"
geo = "0.29"
geo-types = "0.7"
h3o = { version = "0.6" }
h3o = { version = "0.7" }
rayon = "^1"
arrow = { version = "53" }

Expand Down
1 change: 1 addition & 0 deletions crates/h3arrow/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased (YYYY-MM-DD TBD)

* Update h3o to 0.7.
* Added H3ArrayBuilder type.
* Added LocalIj coordinate support.

Expand Down
111 changes: 94 additions & 17 deletions crates/h3arrow/src/array/from_geo.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use arrow::array::OffsetSizeTrait;
use geo::HasDimensions;
use geo_types::*;
use h3o::geom::{ContainmentMode, PolyfillConfig, ToCells};
use h3o::{CellIndex, Resolution};
use h3o::geom::{ContainmentMode, Plotter, PlotterBuilder, Tiler, TilerBuilder};
use h3o::{CellIndex, LatLng, Resolution};
#[cfg(feature = "rayon")]
use rayon::prelude::{IntoParallelIterator, ParallelIterator};

Expand All @@ -12,20 +12,16 @@ use crate::error::Error;

#[derive(Clone, Copy, Debug)]
pub struct ToCellsOptions {
pub(crate) polyfill_config: PolyfillConfig,
pub(crate) h3_resolution: Resolution,
pub(crate) containment_mode: ContainmentMode,
pub(crate) compact: bool,
}

impl From<PolyfillConfig> for ToCellsOptions {
fn from(polyfill_config: PolyfillConfig) -> Self {
Self::new(polyfill_config)
}
}

impl ToCellsOptions {
pub fn new(polyfill_config: PolyfillConfig) -> Self {
pub fn new(h3_resolution: Resolution) -> Self {
Self {
polyfill_config,
h3_resolution,
containment_mode: ContainmentMode::ContainsCentroid,
compact: false,
}
}
Expand All @@ -34,13 +30,26 @@ impl ToCellsOptions {
self.compact = compact;
self
}

pub fn containment_mode(mut self, containment_mode: ContainmentMode) -> Self {
self.containment_mode = containment_mode;
self
}

pub(crate) fn tiler(&self) -> Tiler {
TilerBuilder::new(self.h3_resolution)
.containment_mode(self.containment_mode)
.build()
}

pub(crate) fn plotter(&self) -> Plotter {
PlotterBuilder::new(self.h3_resolution).build()
}
}

impl From<Resolution> for ToCellsOptions {
fn from(resolution: Resolution) -> Self {
PolyfillConfig::new(resolution)
.containment_mode(ContainmentMode::ContainsCentroid)
.into()
Self::new(resolution)
}
}

Expand Down Expand Up @@ -301,9 +310,9 @@ pub fn geometry_to_cells(
if geom.is_empty() {
return Ok(vec![]);
}
let mut cells: Vec<_> = h3o::geom::Geometry::from_degrees(geom.clone())?
.to_cells(options.polyfill_config)
.collect();

let mut cells = vec![];
geometry_to_cells_internal(geom, options, &mut cells)?;

// deduplicate, in the case of overlaps or lines
cells.sort_unstable();
Expand All @@ -317,6 +326,74 @@ pub fn geometry_to_cells(
Ok(cells)
}

fn geometry_to_cells_internal(
geom: &Geometry,
options: &ToCellsOptions,
out_cells: &mut Vec<CellIndex>,
) -> Result<(), Error> {
match geom {
Geometry::Point(pt) => {
out_cells.push(LatLng::try_from(pt.0)?.to_cell(options.h3_resolution))
}
Geometry::Line(line) => {
let mut plotter = options.plotter();
plotter.add(*line)?;
push_plotter_contents(out_cells, plotter)?;
}
Geometry::LineString(line_string) => {
let mut plotter = options.plotter();
plotter.add_batch(line_string.lines())?;
push_plotter_contents(out_cells, plotter)?;
}
Geometry::Polygon(polygon) => {
let mut tiler = options.tiler();
tiler.add(polygon.clone())?;
out_cells.extend(tiler.into_coverage());
}
Geometry::MultiPoint(multi_point) => {
out_cells.reserve(multi_point.len());
for point in multi_point.iter() {
out_cells.push(LatLng::try_from(point.0)?.to_cell(options.h3_resolution))
}
}
Geometry::MultiLineString(multi_line_string) => {
let mut plotter = options.plotter();
for line_string in multi_line_string.iter() {
plotter.add_batch(line_string.lines())?;
}
push_plotter_contents(out_cells, plotter)?;
}
Geometry::MultiPolygon(multi_polygon) => {
let mut tiler = options.tiler();
tiler.add_batch(multi_polygon.iter().cloned())?;
out_cells.extend(tiler.into_coverage());
}
Geometry::GeometryCollection(geometry_collection) => geometry_collection
.iter()
.try_for_each(|g| geometry_to_cells_internal(g, options, out_cells))?,
Geometry::Rect(rect) => {
let mut tiler = options.tiler();
tiler.add(rect.to_polygon())?;
out_cells.extend(tiler.into_coverage());
}
Geometry::Triangle(triangle) => {
let mut tiler = options.tiler();
tiler.add(triangle.to_polygon())?;
out_cells.extend(tiler.into_coverage());
}
}
Ok(())
}

fn push_plotter_contents(out_cells: &mut Vec<CellIndex>, plotter: Plotter) -> Result<(), Error> {
let cell_iter = plotter.plot();
out_cells.reserve(cell_iter.size_hint().0);
for cell_result in cell_iter {
out_cells.push(cell_result?);
}
Ok(())
}

fn to_cells(
geom: Geometry,
options: &ToCellsOptions,
Expand Down
32 changes: 24 additions & 8 deletions crates/h3arrow/src/array/to_geo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ use crate::array::{
VertexIndexArray,
};
use crate::error::Error;
use geo::CoordsIter;
use geo::{CoordsIter, ToRadians};
use geo_types::{Coord, Line, LineString, MultiPoint, MultiPolygon, Point, Polygon};
use h3o::geom::ToGeo;
use h3o::{CellIndex, DirectedEdgeIndex, LatLng, VertexIndex};
use std::convert::Infallible;
use std::iter::{repeat, Map, Repeat, Zip};
Expand All @@ -30,7 +29,15 @@ impl IterPolygons for CellIndexArray {
fn iter_polygons(&self, use_degrees: bool) -> Self::Iter<'_> {
self.iter()
.zip(repeat(use_degrees))
.map(|(v, use_degrees)| v.map(|cell| cell.to_geom(use_degrees)))
.map(|(v, use_degrees)| {
v.map(|cell| {
let mut poly = Polygon::new(LineString::from(cell.boundary()), vec![]);
if !use_degrees {
poly.to_radians_in_place();
}
Ok(poly)
})
})
}
}

Expand Down Expand Up @@ -141,7 +148,15 @@ impl IterLines for DirectedEdgeIndexArray {
fn iter_lines(&self, use_degrees: bool) -> Self::Iter<'_> {
self.iter()
.zip(repeat(use_degrees))
.map(|(v, use_degrees)| v.map(|cell| cell.to_geom(use_degrees)))
.map(|(v, use_degrees)| {
v.map(|edge| {
let mut line = Line::from(edge);
if !use_degrees {
line.to_radians_in_place();
}
Ok(line)
})
})
}
}

Expand Down Expand Up @@ -205,10 +220,11 @@ impl ToMultiPolygons for CellIndexArray {
type Output = MultiPolygon;

fn to_multipolygons(&self, use_degrees: bool) -> Result<Self::Output, Self::Error> {
self.iter()
.flatten()
.to_geom(use_degrees)
.map_err(Into::into)
let mut multi_polygons = h3o::geom::dissolve(self.iter().flatten())?;
if !use_degrees {
multi_polygons.to_radians_in_place();
}
Ok(multi_polygons)
}
}

Expand Down
5 changes: 4 additions & 1 deletion crates/h3arrow/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ pub enum Error {
CompactionError(#[from] h3o::error::CompactionError),

#[error(transparent)]
OutlinerError(#[from] h3o::error::OutlinerError),
PlotterError(#[from] h3o::error::PlotterError),

#[error(transparent)]
DissolutionError(#[from] h3o::error::DissolutionError),

#[error(transparent)]
LocalIJError(#[from] h3o::error::LocalIjError),
Expand Down
16 changes: 8 additions & 8 deletions crates/h3arrow/src/spatial_index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use arrow::array::{Array, BooleanArray, BooleanBufferBuilder};
use geo::{BoundingRect, Intersects};
use geo_types::{Coord, MultiPolygon, Polygon, Rect};
use h3o::geom::ToGeo;
use geo_types::{Coord, Line, LineString, MultiPolygon, Point, Polygon, Rect};
use h3o::{CellIndex, DirectedEdgeIndex, LatLng, VertexIndex};
use rstar::primitives::{GeomWithData, Rectangle};
use rstar::{RTree, AABB};
Expand All @@ -15,14 +14,15 @@ pub trait RectIndexable {

impl RectIndexable for CellIndex {
fn spatial_index_rect(&self) -> Option<Rect> {
self.to_geom(true).unwrap().bounding_rect()
LineString::from(self.boundary()).bounding_rect()
}

fn intersects_with_polygon(&self, poly: &Polygon) -> bool {
// do a cheaper centroid containment check first before comparing the polygons
let centroid: Coord = LatLng::from(*self).into();
if poly.intersects(&centroid) {
poly.intersects(&self.to_geom(true).unwrap())
let self_poly = Polygon::new(LineString::from(self.boundary()), vec![]);
poly.intersects(&self_poly)
} else {
false
}
Expand All @@ -31,21 +31,21 @@ impl RectIndexable for CellIndex {

impl RectIndexable for DirectedEdgeIndex {
fn spatial_index_rect(&self) -> Option<Rect> {
Some(self.to_geom(true).unwrap().bounding_rect())
Some(Line::from(*self).bounding_rect())
}

fn intersects_with_polygon(&self, poly: &Polygon) -> bool {
poly.intersects(&self.to_geom(true).unwrap())
poly.intersects(&Line::from(*self))
}
}

impl RectIndexable for VertexIndex {
fn spatial_index_rect(&self) -> Option<Rect> {
Some(self.to_geom(true).unwrap().bounding_rect())
Some(Point::from(*self).bounding_rect())
}

fn intersects_with_polygon(&self, poly: &Polygon) -> bool {
poly.intersects(&self.to_geom(true).unwrap())
poly.intersects(&Point::from(*self))
}
}

Expand Down
1 change: 1 addition & 0 deletions h3ronpy/CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Versioning <https://semver.org/spec/v2.0.0.html>`__.
Unreleased
----------

- Upgrade to h3o 0.7
- The minimum supported python version is now 3.9.

0.21.1 - 2024-10-04
Expand Down
2 changes: 1 addition & 1 deletion h3ronpy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pyo3 = { version = "^0.22", features = [
"abi3-py39",
] }
pyo3-arrow = { version = "0.5.1", default-features = false }
rasterh3 = { git = "https://github.com/kylebarron/rasterh3", branch = "kyle/bump-ndarray", features = [
rasterh3 = { git = "https://github.com/nmandery/rasterh3", branch = "main", features = [
"rayon",
] }
rayon = { workspace = true }
2 changes: 1 addition & 1 deletion h3ronpy/python/h3ronpy/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def cells_to_wkb_polygons(arr, radians: bool = False, link_cells: bool = False)
:param: arr: The cell array
:param radians: Generate geometries using radians instead of degrees
:param link_cells: Combine neighboring cells into a single polygon geometry.
:param link_cells: Combine neighboring cells into a single polygon geometry. All cell indexes must have the same resolution.
"""
return vector.cells_to_wkb_polygons(_to_uint64_array(arr), radians=radians, link_cells=link_cells)

Expand Down
6 changes: 4 additions & 2 deletions h3ronpy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ impl IntoPyErr for A3Error {
A3Error::InvalidLatLng(e) => e.into_pyerr(),
A3Error::InvalidGeometry(e) => e.into_pyerr(),
A3Error::CompactionError(e) => e.into_pyerr(),
A3Error::OutlinerError(e) => e.into_pyerr(),
A3Error::DissolutionError(e) => e.into_pyerr(),
A3Error::PlotterError(e) => e.into_pyerr(),
A3Error::LocalIJError(e) => e.into_pyerr(),
A3Error::Arrow2(e) => e.into_pyerr(),
A3Error::NotAUint64Array
Expand Down Expand Up @@ -63,7 +64,8 @@ impl_h3o_value_err!(
h3arrow::export::h3o::error::InvalidLatLng,
h3arrow::export::h3o::error::InvalidResolution,
h3arrow::export::h3o::error::InvalidVertexIndex,
h3arrow::export::h3o::error::OutlinerError,
h3arrow::export::h3o::error::DissolutionError,
h3arrow::export::h3o::error::PlotterError,
h3arrow::export::h3o::error::LocalIjError,
);

Expand Down
Loading

0 comments on commit ae3d0c9

Please sign in to comment.