Skip to content

Commit

Permalink
Deprecate use of feature and geometry dicts (#1116)
Browse files Browse the repository at this point in the history
* Deprecate use of feature and geometry dicts

And eliminate internal usage.

All test modules with changes have been reformatted using black.

* Fix fio-cat --precision errors
  • Loading branch information
sgillies authored Jun 10, 2022
1 parent ff313b3 commit 90de37d
Show file tree
Hide file tree
Showing 42 changed files with 3,043 additions and 2,161 deletions.
15 changes: 15 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ All issue numbers are relative to https://github.com/Toblerity/Fiona/issues.
1.9a2 (TBD)
-----------

Deprecations:

- Fiona's API methods will accept feature and geometry dicts in 1.9.0, but this
usage is deprecated. Instances of Feature and Geometry will be required in
2.0.
- The precision keyword argument of fiona.transform.transform_geom is
deprecated and will be removed in version 2.0.
- Deprecated usage has been eliminated in the project. Fiona's tests pass when
run with a -Werror::DeprecationWarning filter.

Changes:

- Fiona's FionaDeprecationWarning now sub-classes DeprecationWarning.
- Some test modules have beeen re-formatted using black.

New features:

- Fiona Collections now carry a context exit stack into which we can push fiona
Expand Down
43 changes: 22 additions & 21 deletions fiona/_geometry.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from __future__ import absolute_import
import logging

from fiona.errors import UnsupportedGeometryTypeError
from fiona.model import GEOMETRY_TYPES, Geometry
from fiona.model import _guard_model_object, GEOMETRY_TYPES, Geometry

from fiona._err cimport exc_wrap_int

Expand Down Expand Up @@ -100,16 +100,16 @@ cdef class GeomBuilder:
values.append(OGR_G_GetZ(geom, i))
coords.append(tuple(values))
return coords

cpdef _buildPoint(self):
return {'type': 'Point', 'coordinates': self._buildCoords(self.geom)[0]}

cpdef _buildLineString(self):
return {'type': 'LineString', 'coordinates': self._buildCoords(self.geom)}

cpdef _buildLinearRing(self):
return {'type': 'LinearRing', 'coordinates': self._buildCoords(self.geom)}

cdef _buildParts(self, void *geom):
cdef int j
cdef void *part
Expand All @@ -120,27 +120,27 @@ cdef class GeomBuilder:
part = OGR_G_GetGeometryRef(geom, j)
parts.append(GeomBuilder().build(part))
return parts

cpdef _buildPolygon(self):
coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
return {'type': 'Polygon', 'coordinates': coordinates}

cpdef _buildMultiPoint(self):
coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
return {'type': 'MultiPoint', 'coordinates': coordinates}

cpdef _buildMultiLineString(self):
coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
return {'type': 'MultiLineString', 'coordinates': coordinates}

cpdef _buildMultiPolygon(self):
coordinates = [p['coordinates'] for p in self._buildParts(self.geom)]
return {'type': 'MultiPolygon', 'coordinates': coordinates}

cpdef _buildGeometryCollection(self):
parts = self._buildParts(self.geom)
return {'type': 'GeometryCollection', 'geometries': parts}

cdef build(self, void *geom):
# The only method anyone needs to call
if geom == NULL:
Expand All @@ -157,7 +157,7 @@ cdef class GeomBuilder:
self.ndims = OGR_G_GetCoordinateDimension(geom)
self.geom = geom
built = getattr(self, '_build' + self.geomtypename)()
return Geometry(**built)
return Geometry.from_dict(**built)

cpdef build_wkb(self, object wkb):
# The only other method anyone needs to call
Expand Down Expand Up @@ -189,20 +189,20 @@ cdef class OGRGeomBuilder:
cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['Point'])
self._addPointToGeometry(cogr_geometry, coordinates)
return cogr_geometry

cdef void * _buildLineString(self, object coordinates) except NULL:
cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['LineString'])
for coordinate in coordinates:
self._addPointToGeometry(cogr_geometry, coordinate)
return cogr_geometry

cdef void * _buildLinearRing(self, object coordinates) except NULL:
cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['LinearRing'])
for coordinate in coordinates:
self._addPointToGeometry(cogr_geometry, coordinate)
OGR_G_CloseRings(cogr_geometry)
return cogr_geometry

cdef void * _buildPolygon(self, object coordinates) except NULL:
cdef void *cogr_ring
cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['Polygon'])
Expand Down Expand Up @@ -235,17 +235,18 @@ cdef class OGRGeomBuilder:
exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part))
return cogr_geometry

cdef void * _buildGeometryCollection(self, object coordinates) except NULL:
cdef void * _buildGeometryCollection(self, object geometries) except NULL:
cdef void *cogr_part
cdef void *cogr_geometry = self._createOgrGeometry(GEOJSON2OGR_GEOMETRY_TYPES['GeometryCollection'])
for part in coordinates:
for part in geometries:
cogr_part = OGRGeomBuilder().build(part)
exc_wrap_int(OGR_G_AddGeometryDirectly(cogr_geometry, cogr_part))
return cogr_geometry

cdef void * build(self, object geometry) except NULL:
cdef object typename = geometry['type']
cdef object coordinates = geometry.get('coordinates')
cdef object typename = geometry.type
cdef object coordinates = geometry.coordinates
cdef object geometries = geometry.geometries
if typename == 'Point':
return self._buildPoint(coordinates)
elif typename == 'LineString':
Expand All @@ -261,14 +262,14 @@ cdef class OGRGeomBuilder:
elif typename == 'MultiPolygon':
return self._buildMultiPolygon(coordinates)
elif typename == 'GeometryCollection':
coordinates = geometry.get('geometries')
return self._buildGeometryCollection(coordinates)
return self._buildGeometryCollection(geometries)
else:
raise UnsupportedGeometryTypeError("Unsupported geometry type %s" % typename)


def geometryRT(geometry):
def geometryRT(geom):
# For testing purposes only, leaks the JSON data
geometry = _guard_model_object(geom)
cdef void *cogr_geometry = OGRGeomBuilder().build(geometry)
result = GeomBuilder().build(cogr_geometry)
_deleteOgrGeom(cogr_geometry)
Expand Down
115 changes: 24 additions & 91 deletions fiona/_transform.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,10 @@ cdef object _transform_single_geom(
OGRGeometryFactory *factory,
void *transform,
char **options,
object precision
):
"""Transform a single geometry."""
cdef void *src_ogr_geom = NULL
cdef void *dst_ogr_geom = NULL
cdef int i

if not isinstance(single_geom, Geometry):
single_geom = Geometry.from_dict(**single_geom)

src_ogr_geom = _geometry.OGRGeomBuilder().build(single_geom)
dst_ogr_geom = factory.transformWithOptions(
Expand All @@ -120,10 +116,7 @@ cdef object _transform_single_geom(

if dst_ogr_geom == NULL:
warnings.warn(
"Full reprojection failed, but partial is possible. To enable partial "
"reprojection wrap the transform_geom call like so:\n"
"with fiona.Env(OGR_ENABLE_PARTIAL_REPROJECTION=True):\n"
" transform_geom(...)"
"Full reprojection failed. To enable partial reprojection set OGR_ENABLE_PARTIAL_REPROJECTION=True"
)
return None
else:
Expand All @@ -133,89 +126,11 @@ cdef object _transform_single_geom(
if src_ogr_geom != NULL:
_geometry.OGR_G_DestroyGeometry(src_ogr_geom)

if precision >= 0:

def round_point(g):
coords = list(g['coordinates'])
x, y = coords[:2]
x = round(x, precision)
y = round(y, precision)
new_coords = [x, y]
if len(coords) == 3:
z = coords[2]
new_coords.append(round(z, precision))
return new_coords

def round_linestring(g):
coords = list(zip(*g['coordinates']))
xp, yp = coords[:2]
xp = [round(v, precision) for v in xp]
yp = [round(v, precision) for v in yp]
if len(coords) == 3:
zp = coords[2]
zp = [round(v, precision) for v in zp]
new_coords = list(zip(xp, yp, zp))
else:
new_coords = list(zip(xp, yp))
return new_coords

def round_polygon(g):
new_coords = []
for piece in out_geom['coordinates']:
coords = list(zip(*piece))
xp, yp = coords[:2]
xp = [round(v, precision) for v in xp]
yp = [round(v, precision) for v in yp]
if len(coords) == 3:
zp = coords[2]
zp = [round(v, precision) for v in zp]
new_coords.append(list(zip(xp, yp, zp)))
else:
new_coords.append(list(zip(xp, yp)))
return new_coords

def round_multipolygon(g):
parts = g['coordinates']
new_coords = []
for part in parts:
inner_coords = []
for ring in part:
coords = list(zip(*ring))
xp, yp = coords[:2]
xp = [round(v, precision) for v in xp]
yp = [round(v, precision) for v in yp]
if len(coords) == 3:
zp = coords[2]
zp = [round(v, precision) for v in zp]
inner_coords.append(list(zip(xp, yp, zp)))
else:
inner_coords.append(list(zip(xp, yp)))
new_coords.append(inner_coords)
return new_coords

def round_geometry(g):
if g['type'] == 'Point':
g['coordinates'] = round_point(g)
elif g['type'] in ['LineString', 'MultiPoint']:
g['coordinates'] = round_linestring(g)
elif g['type'] in ['Polygon', 'MultiLineString']:
g['coordinates'] = round_polygon(g)
elif g['type'] == 'MultiPolygon':
g['coordinates'] = round_multipolygon(g)
else:
raise RuntimeError("Unsupported geometry type: {}".format(g['type']))

if out_geom['type'] == 'GeometryCollection':
for _g in out_geom['geometries']:
round_geometry(_g)
else:
round_geometry(out_geom)

return out_geom


def _transform_geom(src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_offset, precision):
"""Return a transformed geometry.
"""Return transformed geometries.
"""
cdef char *proj_c = NULL
Expand Down Expand Up @@ -246,11 +161,15 @@ def _transform_geom(src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_o

factory = new OGRGeometryFactory()

if isinstance(geom, DICT_TYPES):
out_geom = _transform_single_geom(geom, factory, transform, options, precision)
if isinstance(geom, Geometry):
out_geom = recursive_round(
_transform_single_geom(geom, factory, transform, options), precision)
else:
out_geom = [
_transform_single_geom(single_geom, factory, transform, options, precision)
recursive_round(
_transform_single_geom(single_geom, factory, transform, options),
precision,
)
for single_geom in geom
]

Expand All @@ -263,3 +182,17 @@ def _transform_geom(src_crs, dst_crs, geom, antimeridian_cutting, antimeridian_o
OSRRelease(dst)

return out_geom


def recursive_round(obj, precision):
"""Recursively round coordinates."""
if precision < 0:
return obj
if getattr(obj, 'geometries', None):
return Geometry(geometries=[recursive_round(part, precision) for part in obj.geometries])
elif getattr(obj, 'coordinates', None):
return Geometry(coordinates=[recursive_round(part, precision) for part in obj.coordinates])
if isinstance(obj, (int, float)):
return round(obj, precision)
else:
return [recursive_round(part, precision) for part in obj]
Loading

0 comments on commit 90de37d

Please sign in to comment.