Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft of a geometry operations applicator #1112

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions fiona/_transform.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because OGR_G_Segmentize is an oddball and returns nothing (void).

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] = <geometry_func>segmentize

cdef geometry_func func_alias(int choice):
return func_map[choice]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't just put OGR_G_Segmentize (for example) in a Python dict. https://stackoverflow.com/a/56708967 was very helpful in showing a solution.



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
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map to the map of OGR functions.


for geom in geoms:
ogr_geom1 = _geometry.OGRGeomBuilder().build(geom)

for op_name, op_arg in operations:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically it's a per-geometry pipeline implemented in C.

The other obvious design is to add segmentize &c as methods to our Geometry class. But performance won't be great until version 2.0 of fiona when Geometry is an extension class and carries an OGRGeometryH or a zero-copy data structure around with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not be bad to wait until 2.0 for this as the Geometry class seems to have the potential to significantly impact on the design.

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
3 changes: 3 additions & 0 deletions fiona/gdal.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions fiona/ogrext.pyx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions tests/test_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,23 @@ 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)


def test_apply_geom_twice():
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), ("segmentize", 0.5)], [input_geom]))
assert len(output_geom.coordinates) == 21
assert output_geom.coordinates[1] == (0.0, 0.5)