From 2259e4307c290ad14bac349ad2b574e614df8eb5 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Fri, 3 Jun 2022 12:31:39 -0600 Subject: [PATCH] Draft of a geometry operations applicator Resolves #982 --- fiona/_transform.pyx | 61 +++++++++++++++++++++++++++++++++++++++++ fiona/gdal.pxi | 3 ++ fiona/ogrext.pyx | 4 +-- tests/test_transform.py | 10 +++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/fiona/_transform.pyx b/fiona/_transform.pyx index 23a70b164..b8d454a10 100644 --- a/fiona/_transform.pyx +++ b/fiona/_transform.pyx @@ -11,6 +11,7 @@ from collections import UserDict from fiona cimport _cpl, _csl, _geometry from fiona.crs cimport OGRSpatialReferenceH, osr_set_traditional_axis_mapping_strategy +from fiona._err cimport exc_wrap_pointer from fiona.compat import DICT_TYPES from fiona.crs import CRS @@ -263,3 +264,63 @@ def _transform_geom(src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_o OSRRelease(dst) return out_geom + + +from libcpp.unordered_map cimport unordered_map + +cdef OGRGeometryH segmentize(OGRGeometryH geom, double max_length): + cdef OGRGeometryH new_geom = NULL + new_geom = OGR_G_Clone(geom) + OGR_G_Segmentize(new_geom, max_length) + return new_geom + + +ctypedef OGRGeometryH (*geometry_func)(OGRGeometryH, double) +cdef unordered_map[int, geometry_func] func_map + +func_map[1] = segmentize + +cdef geometry_func func_alias(int choice): + return func_map[choice] + + +def apply_geom(operations, geoms): + """Apply a series of geometry transforming operations to one or more geometries. + + Parameters + ---------- + operations: list + A list of operation names and extra arg as a tuple. + geoms: Geometry or Sequence[Geometry] + Geometries on which to apply operations. + + Yields + ------ + Geometry + + """ + cdef OGRGeometryH ogr_geom1 = NULL + cdef OGRGeometryH ogr_geom2 = NULL + cdef int choice = 0 + + op_map = { + "segmentize": 1 + } + + for geom in geoms: + ogr_geom1 = _geometry.OGRGeomBuilder().build(geom) + + for op_name, op_arg in operations: + choice = op_map[op_name] + ogr_geom2 = func_alias(choice)(ogr_geom1, op_arg) + ogr_geom2 = exc_wrap_pointer(ogr_geom2) + OGR_G_DestroyGeometry(ogr_geom1) + ogr_geom1 = ogr_geom2 + + if ogr_geom2 == NULL: + raise Exception("NULL geometry") + + output_geom = _geometry.GeomBuilder().build(ogr_geom2) + OGR_G_DestroyGeometry(ogr_geom2) + + yield output_geom diff --git a/fiona/gdal.pxi b/fiona/gdal.pxi index d15c84cf1..5466960f6 100644 --- a/fiona/gdal.pxi +++ b/fiona/gdal.pxi @@ -541,6 +541,9 @@ cdef extern from "ogr_api.h" nogil: void OGR_G_ImportFromWkb(OGRGeometryH geometry, unsigned char *bytes, int nbytes) int OGR_G_WkbSize(OGRGeometryH geometry) + + void OGR_G_Segmentize(OGRGeometryH hGeom, double dfMaxLength) + OGRErr OGR_L_CreateFeature(OGRLayerH layer, OGRFeatureH feature) int OGR_L_CreateField(OGRLayerH layer, OGRFieldDefnH, int flexible) OGRErr OGR_L_GetExtent(OGRLayerH layer, void *extent, int force) diff --git a/fiona/ogrext.pyx b/fiona/ogrext.pyx index b95eb4d9f..bf1a34714 100644 --- a/fiona/ogrext.pyx +++ b/fiona/ogrext.pyx @@ -1,4 +1,5 @@ -# These are extension functions and classes using the OGR C API. +"""Extension functions and classes using the OGR C API.""" + from __future__ import absolute_import include "gdal.pxi" @@ -165,7 +166,6 @@ cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NU CSLDestroy(creation_opts) - def _explode(coords): """Explode a GeoJSON geometry's coordinates object and yield coordinate tuples. As long as the input is conforming, the type of diff --git a/tests/test_transform.py b/tests/test_transform.py index f5e637028..eebb936ed 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -127,3 +127,13 @@ def test_transform_issue971(): (512364.6014999999, 5866328.260199999)]}]} geom_transformed = transform.transform_geom(source_crs, dest_src, geom, precision=3) assert geom_transformed['geometries'][0]['coordinates'][0] == pytest.approx((9.184, 52.946)) + + +def test_apply_geom(): + from fiona._transform import apply_geom + from fiona.model import Geometry + + input_geom = Geometry(type="LineString", coordinates=[(0, 0), (0, 10)]) + output_geom = next(apply_geom([("segmentize", 1.0)], [input_geom])) + assert len(output_geom.coordinates) == 11 + assert output_geom.coordinates[1] == (0.0, 1.0)