Skip to content

Commit ab89bde

Browse files
committed
Better test coverage and handling for write failures.
1 parent cd02417 commit ab89bde

File tree

6 files changed

+190
-64
lines changed

6 files changed

+190
-64
lines changed

README.rst

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,64 +26,68 @@ Usage
2626
2727
Default settings - buffer geometries in the input CRS:
2828
29-
$ fio buffer in.geojson out.geojson --dist 10
29+
$ fio buffer in.geojson out.geojson --distance 10
3030
3131
Dynamically buffer geometries by a distance stored in the field
32-
`magnitude` and write as GeoJSON:
32+
'magnitude' and write as GeoJSON:
3333
3434
$ fio buffer \
3535
in.shp \
3636
out.geojson \
3737
--driver GeoJSON \
38-
--dist magnitude
38+
--distance magnitude
3939
4040
Read geometries from one CRS, buffer in another, and then write to a
4141
third:
4242
4343
$ fio buffer in.shp out.shp \
44-
--dist 10 \
44+
--distance 10 \
4545
--buf-crs EPSG:3857 \
4646
--dst-crs EPSG:32618
4747
4848
Control cap style, mitre limit, segment resolution, and join style:
4949
5050
$ fio buffer in.geojson out.geojson \
51-
--dist 0.1 \
51+
--distance 0.1 \
5252
--res 5 \
5353
--cap-style flat \
5454
--join-style mitre \
5555
--mitre-limit 0.1\
5656
5757
Options:
58-
-f, --format, --driver NAME Output driver name. [required]
58+
--version Show the version and exit.
59+
-f, --format, --driver NAME Output driver name. Derived from the input
60+
datasource if not given.
5961
--cap-style [flat|round|square]
6062
Where geometries terminate, use this style.
61-
(default: round)
63+
[default: round]
6264
--join-style [round|mitre|bevel]
6365
Where geometries touch, use this style.
64-
(default: round)
66+
[default: round]
6567
--res INTEGER Resolution of the buffer around each vertex
66-
of the object. (default: 16)
68+
of the geometry. [default: 16]
6769
--mitre-limit FLOAT When using a mitre join, limit the maximum
6870
length of the join corner according to this
69-
ratio. (default: 0.5)
70-
--dist FLOAT | FIELD Buffer distance in georeferenced units
71-
according to --buf-dist. [required]
71+
ratio. [default: 5.0]
72+
--distance FLOAT|FIELD Buffer distance or field containing distance
73+
values. Units match --buf-crs. When
74+
buffering with a field, feature's with a
75+
null value are unaltered. [required]
7276
--src-crs TEXT Specify CRS for input data. Not needed if
7377
set in input file.
7478
--buf-crs TEXT Perform buffer operations in a different
75-
CRS. (default: --src-crs)
79+
CRS. [default: --src-crs]
7680
--dst-crs TEXT Reproject geometries to a different CRS
7781
before writing. Must be combined with
78-
--buf-crs. (default: --src-crs)
79-
--otype GEOMTYPE Specify output geometry type. (default:
80-
MultiPolygon)
82+
--buf-crs. [default: --src-crs]
83+
--geom-type GEOMTYPE Output layer's geometry type. [default:
84+
MultiPolygon]
8185
--skip-failures Skip geometries that fail somewhere in the
8286
processing pipeline.
8387
--jobs CORES Process geometries in parallel across N
84-
cores. The goal of this flag is speed so
85-
feature order and ID's are not preserved.
86-
(default: 1)
88+
cores. Feature ID's and order are not
89+
preserved if more that 1 cores are used.
90+
[default: 1]
8791
--help Show this message and exit.
8892
8993
@@ -114,7 +118,7 @@ Developing
114118
$ cd fio-buffer
115119
$ virtualenv venv
116120
$ source venv/bin/activate
117-
$ pip install -e .[test]
121+
$ pip install -e .[dev]
118122
$ py.test tests --cov fio_buffer --cov-report term-missing
119123
120124

fio_buffer/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55

6-
__version__ = '0.1'
6+
__version__ = '0.1.1'
77
__author__ = 'Kevin Wurster'
88
__email__ = 'wursterk@gmail.com'
99
__source__ = 'https://github.com/geowurster/fio-buffer'
@@ -23,8 +23,9 @@
2323
this list of conditions and the following disclaimer in the documentation
2424
and/or other materials provided with the distribution.
2525
26-
* The names of its contributors may not be used to endorse or promote products
27-
derived from this software without specific prior written permission.
26+
* The names of fio-buffer or its contributors may not be used to endorse or
27+
promote products derived from this software without specific prior written
28+
permission.
2829
2930
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
3031
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE

fio_buffer/core.py

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def _cb_res(ctx, param, value):
5757
def _cb_dist(ctx, param, value):
5858

5959
"""
60-
Click callback to ensure `--dist` can be either a float or a field name.
60+
Click callback to ensure `--distance` can be either a float or a field name.
6161
"""
6262

6363
try:
@@ -69,7 +69,7 @@ def _cb_dist(ctx, param, value):
6969
def _processor(args):
7070

7171
"""
72-
Process a single feature
72+
Process a single feature.
7373
7474
Parameters
7575
----------
@@ -102,7 +102,13 @@ def _processor(args):
102102

103103
# Support buffering by a field's value
104104
if not isinstance(buf_args['distance'], (float, int)):
105-
buf_args['distance'] = feat['properties'][buf_args['distance']]
105+
field_val = feat['properties'][buf_args['distance']]
106+
107+
# Buffering according to a field but field is None so just return the feature
108+
if field_val is None:
109+
return feat
110+
else:
111+
buf_args['distance'] = field_val
106112

107113
try:
108114
# src_crs -> buf_crs
@@ -123,7 +129,7 @@ def _processor(args):
123129
return feat
124130

125131
except Exception:
126-
logger.exception("Feature with ID %s failed", feat.get('id'))
132+
logger.exception("Feature with ID %s failed during buffering", feat.get('id'))
127133
if not skip_failures:
128134
raise
129135

@@ -133,56 +139,61 @@ def _processor(args):
133139
@click.argument('infile', required=True)
134140
@click.argument('outfile', required=True)
135141
@click.option(
136-
'-f', '--format', '--driver', metavar='NAME', required=True,
137-
help="Output driver name."
142+
'-f', '--format', '--driver', metavar='NAME',
143+
help="Output driver name. Derived from the input datasource if not given."
138144
)
139145
@click.option(
140-
'--cap-style', type=click.Choice(['flat', 'round', 'square']), default='round',
141-
callback=_cb_cap_style, help="Where geometries terminate, use this style. (default: round)"
146+
'--cap-style', type=click.Choice(['flat', 'round', 'square']),
147+
default='round', show_default=True,
148+
callback=_cb_cap_style, help="Where geometries terminate, use this style."
142149
)
143150
@click.option(
144-
'--join-style', type=click.Choice(['round', 'mitre', 'bevel']), default='round',
145-
callback=_cb_join_style, help="Where geometries touch, use this style. (default: round)"
151+
'--join-style', type=click.Choice(['round', 'mitre', 'bevel']),
152+
default='round', show_default=True,
153+
callback=_cb_join_style, help="Where geometries touch, use this style."
146154
)
147155
@click.option(
148-
'--res', type=click.INT, callback=_cb_res, default=16,
149-
help="Resolution of the buffer around each vertex of the object. (default: 16)"
156+
'--res', type=click.INT, callback=_cb_res, default=16, show_default=True,
157+
help="Resolution of the buffer around each vertex of the geometry."
150158
)
151159
@click.option(
152-
'--mitre-limit', type=click.FLOAT, default=5.0,
160+
'--mitre-limit', type=click.FLOAT, default=5.0, show_default=True,
153161
help="When using a mitre join, limit the maximum length of the join corner according to "
154-
"this ratio. (default: 0.5)"
162+
"this ratio."
155163
)
156164
@click.option(
157-
'--dist', metavar='FLOAT | FIELD', required=True, callback=_cb_dist,
158-
help="Buffer distance in georeferenced units according to --buf-dist."
165+
'--distance', metavar='FLOAT|FIELD', required=True, callback=_cb_dist,
166+
help="Buffer distance or field containing distance values. Units match --buf-crs. "
167+
"When buffering with a field, feature's with a null value are unaltered."
159168
)
160169
@click.option(
161170
'--src-crs', help="Specify CRS for input data. Not needed if set in input file."
162171
)
163172
@click.option(
164-
'--buf-crs', help="Perform buffer operations in a different CRS. (default: --src-crs)"
173+
'--buf-crs', help="Perform buffer operations in a different CRS. [default: --src-crs]"
165174
)
166175
@click.option(
167176
'--dst-crs', help="Reproject geometries to a different CRS before writing. Must be "
168-
"combined with --buf-crs. (default: --src-crs)"
177+
"combined with --buf-crs. [default: --src-crs]"
169178
)
170179
@click.option(
171-
'--otype', 'output_geom_type', default='MultiPolygon', metavar='GEOMTYPE',
172-
help="Specify output geometry type. (default: MultiPolygon)"
180+
'--geom-type', 'output_geom_type', default='MultiPolygon',
181+
metavar='GEOMTYPE', show_default=True,
182+
help="Output layer's geometry type."
173183
)
174184
@click.option(
175185
'--skip-failures', is_flag=True,
176186
help="Skip geometries that fail somewhere in the processing pipeline."
177187
)
178188
@click.option(
179-
'--jobs', type=click.IntRange(1, cpu_count()), default=1, metavar="CORES",
180-
help="Process geometries in parallel across N cores. The goal of this flag is speed so "
181-
"feature order and ID's are not preserved. (default: 1)"
189+
'--jobs', type=click.IntRange(1, cpu_count()), default=1,
190+
metavar="CORES", show_default=True,
191+
help="Process geometries in parallel across N cores. Feature ID's and order are not "
192+
"preserved if more that 1 cores are used."
182193
)
183194
@click.pass_context
184195
def buffer(ctx, infile, outfile, driver, cap_style, join_style, res, mitre_limit,
185-
dist, src_crs, buf_crs, dst_crs, output_geom_type, skip_failures, jobs):
196+
distance, src_crs, buf_crs, dst_crs, output_geom_type, skip_failures, jobs):
186197

187198
"""
188199
Geometries can be dilated with a positive distance, eroded with a negative
@@ -195,31 +206,31 @@ def buffer(ctx, infile, outfile, driver, cap_style, join_style, res, mitre_limit
195206
Default settings - buffer geometries in the input CRS:
196207
197208
\b
198-
$ fio buffer in.geojson out.geojson --dist 10
209+
$ fio buffer in.geojson out.geojson --distance 10
199210
200-
Dynamically buffer geometries by a distance stored in the field `magnitude`
211+
Dynamically buffer geometries by a distance stored in the field 'magnitude'
201212
and write as GeoJSON:
202213
203214
\b
204215
$ fio buffer \\
205216
in.shp \\
206217
out.geojson \\
207218
--driver GeoJSON \\
208-
--dist magnitude
219+
--distance magnitude
209220
210221
Read geometries from one CRS, buffer in another, and then write to a third:
211222
212223
\b
213224
$ fio buffer in.shp out.shp \\
214-
--dist 10 \\
225+
--distance 10 \\
215226
--buf-crs EPSG:3857 \\
216227
--dst-crs EPSG:32618
217228
218229
Control cap style, mitre limit, segment resolution, and join style:
219230
220231
\b
221232
$ fio buffer in.geojson out.geojson \\
222-
--dist 0.1 \\
233+
--distance 0.1 \\
223234
--res 5 \\
224235
--cap-style flat \\
225236
--join-style mitre \\
@@ -235,15 +246,15 @@ def buffer(ctx, infile, outfile, driver, cap_style, join_style, res, mitre_limit
235246
if isinstance(getattr(ctx, 'obj'), dict):
236247
logger.setLevel(ctx.obj.get('verbosity', 1))
237248

238-
with fio.open(infile, 'r') as src:
249+
with fio.open(infile) as src:
239250

240251
logger.debug("Resolving CRS fall backs")
241252

242253
src_crs = src_crs or src.crs
243254
buf_crs = buf_crs or src_crs
244255
dst_crs = dst_crs or src_crs
245256

246-
if src_crs is None:
257+
if not src_crs:
247258
raise click.ClickException(
248259
"CRS is not set in input file. Use --src-crs to specify.")
249260

@@ -266,7 +277,7 @@ def buffer(ctx, infile, outfile, driver, cap_style, join_style, res, mitre_limit
266277

267278
# Keyword arguments for `<Geometry>.buffer()`
268279
buf_args = {
269-
'distance': dist,
280+
'distance': distance,
270281
'resolution': res,
271282
'cap_style': cap_style,
272283
'join_style': join_style,
@@ -287,6 +298,12 @@ def buffer(ctx, infile, outfile, driver, cap_style, join_style, res, mitre_limit
287298
logger.debug("Starting processing on %s cores", jobs)
288299
for o_feat in Pool(jobs).imap_unordered(_processor, task_generator):
289300
if o_feat is not None:
290-
dst.write(o_feat)
301+
try:
302+
dst.write(o_feat)
303+
except Exception:
304+
logger.exception(
305+
"Feature with ID %s failed during write", o_feat.get('id'))
306+
if not skip_failures:
307+
raise
291308

292309
logger.debug("Finished processing.")

setup.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,14 @@
5353
buffer=fio_buffer.core:buffer
5454
""",
5555
extras_require={
56-
'test': ['pytest', 'pytest-cov']
56+
'dev': ['pytest', 'pytest-cov']
5757
},
5858
include_package_data=True,
59-
install_requires=['click>=0.3', 'shapely', 'fiona>=0.6'],
59+
install_requires=[
60+
'click>=0.3',
61+
'shapely',
62+
'fiona>=1.6'
63+
],
6064
keywords='Fiona fio GIS vector buffer plugin',
6165
license="New BSD",
6266
long_description=readme,

0 commit comments

Comments
 (0)