[fiona] 01/07: Imported Upstream version 1.4.6

Johan Van de Wauw johanvdw-guest at moszumanska.debian.org
Wed Oct 22 21:54:29 UTC 2014


This is an automated email from the git hooks/post-receive script.

johanvdw-guest pushed a commit to branch master
in repository fiona.

commit 276e641265b49fc882e00d2bd3cd1cc1978de1a7
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date:   Wed Oct 22 22:26:58 2014 +0200

    Imported Upstream version 1.4.6
---
 CHANGES.txt                 |  13 +-
 Fiona.egg-info/PKG-INFO     |  15 +-
 Fiona.egg-info/SOURCES.txt  |   4 +
 Fiona.egg-info/requires.txt |   2 +-
 PKG-INFO                    |  15 +-
 VERSION.txt                 |   2 +-
 docs/cli.rst                |  27 +++
 fiona/__init__.py           |   2 +-
 fiona/fio/bounds.py         |  85 +++++++
 fiona/fio/cat.py            |  82 +++++--
 fiona/fio/cli.py            |  24 ++
 fiona/fio/fio.py            |   7 +-
 fiona/fio/stream.py         |  55 +++++
 fiona/ogrext.c              | 554 ++++++++++++++++++++------------------------
 setup.cfg                   |   2 +-
 tests/test_bounds.py        |   4 +
 tests/test_fio_bounds.py    |  86 +++++++
 tests/test_fio_cat.py       | 105 +++++++++
 tests/test_geopackage.py    |   9 +
 tests/test_schema.py        |  20 ++
 20 files changed, 782 insertions(+), 331 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 59152eb..5932ba9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,9 +1,20 @@
 Changes
 =======
 
+1.4.6 (2014-10-21)
+------------------
+- Handle 3D coordinates in bounds() #178.
+
+1.4.5 (2014-10-18)
+------------------
+- Add --bbox option to fio-cat (#163).
+- Skip geopackage tests if run from an sdist (#167).
+- Add fio-bounds and fio-distrib.
+- Restore fio-dump to working order.
+
 1.4.4 (2014-10-13)
 ------------------
-- Fix accidental requirement on GDAL 1.10 introduced in 1.4.3 (#164).
+- Fix accidental requirement on GDAL 1.11 introduced in 1.4.3 (#164).
 
 1.4.3 (2014-10-10)
 ------------------
diff --git a/Fiona.egg-info/PKG-INFO b/Fiona.egg-info/PKG-INFO
index 27b36f1..dc48fef 100644
--- a/Fiona.egg-info/PKG-INFO
+++ b/Fiona.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Fiona
-Version: 1.4.4
+Version: 1.4.6
 Summary: Fiona reads and writes spatial data files
 Home-page: http://github.com/Toblerity/Fiona
 Author: Sean Gillies
@@ -288,9 +288,20 @@ Description: =====
         Changes
         =======
         
+        1.4.6 (2014-10-21)
+        ------------------
+        - Handle 3D coordinates in bounds() #178.
+        
+        1.4.5 (2014-10-18)
+        ------------------
+        - Add --bbox option to fio-cat (#163).
+        - Skip geopackage tests if run from an sdist (#167).
+        - Add fio-bounds and fio-distrib.
+        - Restore fio-dump to working order.
+        
         1.4.4 (2014-10-13)
         ------------------
-        - Fix accidental requirement on GDAL 1.10 introduced in 1.4.3 (#164).
+        - Fix accidental requirement on GDAL 1.11 introduced in 1.4.3 (#164).
         
         1.4.3 (2014-10-10)
         ------------------
diff --git a/Fiona.egg-info/SOURCES.txt b/Fiona.egg-info/SOURCES.txt
index 5b2796c..55a7ccf 100644
--- a/Fiona.egg-info/SOURCES.txt
+++ b/Fiona.egg-info/SOURCES.txt
@@ -16,9 +16,11 @@ setup.py
 ./fiona/tool.py
 ./fiona/transform.py
 ./fiona/fio/__init__.py
+./fiona/fio/bounds.py
 ./fiona/fio/cat.py
 ./fiona/fio/cli.py
 ./fiona/fio/fio.py
+./fiona/fio/stream.py
 Fiona.egg-info/PKG-INFO
 Fiona.egg-info/SOURCES.txt
 Fiona.egg-info/dependency_links.txt
@@ -47,6 +49,8 @@ tests/test_collection.py
 tests/test_crs.py
 tests/test_drivers.py
 tests/test_feature.py
+tests/test_fio_bounds.py
+tests/test_fio_cat.py
 tests/test_geojson.py
 tests/test_geometry.py
 tests/test_geopackage.py
diff --git a/Fiona.egg-info/requires.txt b/Fiona.egg-info/requires.txt
index 424e67c..19f7fd7 100644
--- a/Fiona.egg-info/requires.txt
+++ b/Fiona.egg-info/requires.txt
@@ -1,2 +1,2 @@
 click
-six
+six
\ No newline at end of file
diff --git a/PKG-INFO b/PKG-INFO
index 27b36f1..dc48fef 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Fiona
-Version: 1.4.4
+Version: 1.4.6
 Summary: Fiona reads and writes spatial data files
 Home-page: http://github.com/Toblerity/Fiona
 Author: Sean Gillies
@@ -288,9 +288,20 @@ Description: =====
         Changes
         =======
         
+        1.4.6 (2014-10-21)
+        ------------------
+        - Handle 3D coordinates in bounds() #178.
+        
+        1.4.5 (2014-10-18)
+        ------------------
+        - Add --bbox option to fio-cat (#163).
+        - Skip geopackage tests if run from an sdist (#167).
+        - Add fio-bounds and fio-distrib.
+        - Restore fio-dump to working order.
+        
         1.4.4 (2014-10-13)
         ------------------
-        - Fix accidental requirement on GDAL 1.10 introduced in 1.4.3 (#164).
+        - Fix accidental requirement on GDAL 1.11 introduced in 1.4.3 (#164).
         
         1.4.3 (2014-10-10)
         ------------------
diff --git a/VERSION.txt b/VERSION.txt
index e1df5de..7b5753f 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-1.4.4
\ No newline at end of file
+1.4.6
\ No newline at end of file
diff --git a/docs/cli.rst b/docs/cli.rst
index d33d87c..be4d25d 100644
--- a/docs/cli.rst
+++ b/docs/cli.rst
@@ -15,15 +15,42 @@ Fiona's new command line interface is a program named "fio".
       --help         Show this message and exit.
 
     Commands:
+      bounds   Print the extent of GeoJSON objects
       cat      Concatenate and print the features of datasets
       collect  Collect a sequence of features.
       dump     Dump a dataset to GeoJSON.
+      env      Print information about the rio environment.
       info     Print information about a dataset.
       insp     Open a dataset and start an interpreter.
       load     Load GeoJSON to a dataset in another format.
 
 It is developed using the ``click`` package and is new in 1.1.6.
 
+bounds
+------
+
+New in 1.4.5.
+
+Fio-bounds reads LF or RS-delimited GeoJSON texts, either features or
+collections, from stdin and prints their bounds with or without other data to
+stdout.
+
+With no options, it works like this:
+
+.. code-block:: console
+
+    $ fio cat docs/data/test_uk.shp | head -n 1 \
+    > | fio bounds
+    [0.735, 51.357216, 0.947778, 51.444717]
+
+Using ``--with-id`` gives you
+
+.. code-block:: console
+
+    $ fio cat docs/data/test_uk.shp | head -n 1 \
+    > | fio bounds --with-id
+    {"id": "0", "bbox": [0.735, 51.357216, 0.947778, 51.444717]}
+
 cat
 ---
 
diff --git a/fiona/__init__.py b/fiona/__init__.py
index 38a6e0b..829be19 100644
--- a/fiona/__init__.py
+++ b/fiona/__init__.py
@@ -63,7 +63,7 @@ writing modes) flush contents to disk when their ``with`` blocks end.
 """
 
 __all__ = ['bounds', 'listlayers', 'open', 'prop_type', 'prop_width']
-__version__ = "1.4.4"
+__version__ = "1.4.6"
 
 import logging
 import os
diff --git a/fiona/fio/bounds.py b/fiona/fio/bounds.py
new file mode 100644
index 0000000..cf30132
--- /dev/null
+++ b/fiona/fio/bounds.py
@@ -0,0 +1,85 @@
+import json
+import logging
+import sys
+
+import click
+
+import fiona
+from fiona.fio.cli import cli, obj_gen
+
+
+# Bounds command
+ at cli.command(short_help="Print the extent of GeoJSON objects")
+ at click.option('--precision', type=int, default=-1, metavar="N",
+              help="Decimal precision of coordinates.")
+ at click.option('--explode/--no-explode', default=False,
+              help="Explode collections into features (default: no).")
+ at click.option('--with-id/--without-id', default=False,
+              help="Print GeoJSON ids and bounding boxes together "
+                   "(default: without).")
+ at click.option('--with-obj/--without-obj', default=False,
+              help="Print GeoJSON objects and bounding boxes together "
+                   "(default: without).")
+ at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
+              help="Use RS as text separator instead of LF. "
+                   "Experimental (default: no).")
+ at click.pass_context
+def bounds(ctx, precision, explode, with_id, with_obj, x_json_seq_rs):
+    """Print the bounding boxes of GeoJSON objects read from stdin.
+    
+    Optionally explode collections and print the bounds of their
+    features.
+
+    To print identifiers for input objects along with their bounds
+    as a {id: identifier, bbox: bounds} JSON object, use --with-id.
+
+    To print the input objects themselves along with their bounds
+    as GeoJSON object, use --with-obj. This has the effect of updating
+    input objects with {id: identifier, bbox: bounds}.
+    """
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+    logger = logging.getLogger('fio')
+    stdin = click.get_text_stream('stdin')
+    stdout = click.get_text_stream('stdout')
+    try:
+        source = obj_gen(stdin)
+        for i, obj in enumerate(source):
+            obj_id = obj.get('id', 'collection:' + str(i))
+            xs = []
+            ys = []
+            features = obj.get('features') or [obj]
+            for j, feat in enumerate(features):
+                feat_id = feat.get('id', 'feature:' + str(i))
+                w, s, e, n = fiona.bounds(feat)
+                if precision > 0:
+                    w, s, e, n = (round(v, precision) 
+                                  for v in (w, s, e, n))
+                if explode:
+                    if with_id:
+                        rec = {'parent': obj_id, 'id': feat_id, 'bbox': (w, s, e, n)}
+                    elif with_obj:
+                        feat.update(parent=obj_id, bbox=(w, s, e, n))
+                        rec = feat
+                    else:
+                        rec = (w, s, e, n)
+                    stdout.write(json.dumps(rec))
+                    stdout.write('\n')
+                else:
+                    xs.extend([w, e])
+                    ys.extend([s, n])
+            if not explode:
+                w, s, e, n = (min(xs), min(ys), max(xs), max(ys))
+                if with_id:
+                    rec = {'id': obj_id, 'bbox': (w, s, e, n)}
+                elif with_obj:
+                    obj.update(id=obj_id, bbox=(w, s, e, n))
+                    rec = obj
+                else:
+                    rec = (w, s, e, n)
+                stdout.write(json.dumps(rec))
+                stdout.write('\n')
+
+        sys.exit(0)
+    except Exception:
+        logger.exception("Failed. Exception caught")
+        sys.exit(1)
diff --git a/fiona/fio/cat.py b/fiona/fio/cat.py
index 2986d8c..e650cbd 100644
--- a/fiona/fio/cat.py
+++ b/fiona/fio/cat.py
@@ -7,7 +7,7 @@ import click
 
 import fiona
 from fiona.transform import transform_geom
-from fiona.fio.cli import cli
+from fiona.fio.cli import cli, obj_gen
 
 
 def make_ld_context(context_items):
@@ -64,26 +64,29 @@ def id_record(rec):
 # One or more files.
 @click.argument('input', nargs=-1, type=click.Path(exists=True))
 # Coordinate precision option.
- at click.option('--precision', type=int, default=-1,
+ at click.option('--precision', type=int, default=-1, metavar="N",
               help="Decimal precision of coordinates.")
- at click.option('--indent', default=None, type=int,
+ at click.option('--indent', default=None, type=int, metavar="N",
               help="Indentation level for pretty printed output.")
 @click.option('--compact/--no-compact', default=False,
               help="Use compact separators (',', ':').")
 @click.option('--ignore-errors/--no-ignore-errors', default=False,
               help="log errors but do not stop serialization.")
- at click.option('--dst_crs', default=None, help="Destination CRS.")
+ at click.option('--dst_crs', default=None, metavar="EPSG:NNNN",
+              help="Destination CRS.")
 # Use ASCII RS control code to signal a sequence item (False is default).
 # See http://tools.ietf.org/html/draft-ietf-json-text-sequence-05.
 # Experimental.
 @click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=True,
         help="Use RS as text separator instead of LF. Experimental.")
+ at click.option('--bbox', default=None, metavar="w,s,e,n",
+              help="filter for features intersecting a bounding box")
 @click.pass_context
 def cat(ctx, input, precision, indent, compact, ignore_errors, dst_crs,
-        x_json_seq_rs):
+        x_json_seq_rs, bbox):
     """Concatenate and print the features of input datasets as a
     sequence of GeoJSON features."""
-    verbosity = ctx.obj['verbosity']
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
     logger = logging.getLogger('fio')
     sink = click.get_text_stream('stdout')
 
@@ -98,13 +101,16 @@ def cat(ctx, input, precision, indent, compact, ignore_errors, dst_crs,
         with fiona.drivers(CPL_DEBUG=verbosity>2):
             for path in input:
                 with fiona.open(path) as src:
-                    for feat in src:
+                    if bbox:
+                        bbox = tuple(map(float, bbox.split(',')))
+                    for i, feat in src.items(bbox=bbox):
                         if dst_crs or precision > 0:
                             g = transform_geom(
                                     src.crs, dst_crs, feat['geometry'],
                                     antimeridian_cutting=True,
                                     precision=precision)
                             feat['geometry'] = g
+                            feat['bbox'] = fiona.bounds(g)
                         if x_json_seq_rs:
                             sink.write(u'\u001e')
                         json.dump(feat, sink, **dump_kwds)
@@ -118,28 +124,29 @@ def cat(ctx, input, precision, indent, compact, ignore_errors, dst_crs,
 # Collect command
 @cli.command(short_help="Collect a sequence of features.")
 # Coordinate precision option.
- at click.option('--precision', type=int, default=-1,
+ at click.option('--precision', type=int, default=-1, metavar="N",
               help="Decimal precision of coordinates.")
- at click.option('--indent', default=None, type=int,
+ at click.option('--indent', default=None, type=int, metavar="N",
               help="Indentation level for pretty printed output.")
 @click.option('--compact/--no-compact', default=False,
               help="Use compact separators (',', ':').")
 @click.option('--record-buffered/--no-record-buffered', default=False,
-    help="Economical buffering of writes at record, not collection "
-         "(default), level.")
+              help="Economical buffering of writes at record, not collection "
+              "(default), level.")
 @click.option('--ignore-errors/--no-ignore-errors', default=False,
               help="log errors but do not stop serialization.")
- at click.option('--src_crs', default=None, help="Source CRS.")
+ at click.option('--src_crs', default=None, metavar="EPSG:NNNN",
+              help="Source CRS.")
 @click.option('--with-ld-context/--without-ld-context', default=False,
-        help="add a JSON-LD context to JSON output.")
+              help="add a JSON-LD context to JSON output.")
 @click.option('--add-ld-context-item', multiple=True,
-        help="map a term to a URI and add it to the output's JSON LD context.")
+              help="map a term to a URI and add it to the output's JSON LD context.")
 @click.pass_context
 def collect(ctx, precision, indent, compact, record_buffered, ignore_errors,
             src_crs, with_ld_context, add_ld_context_item):
     """Make a GeoJSON feature collection from a sequence of GeoJSON
     features and print it."""
-    verbosity = ctx.obj['verbosity']
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
     logger = logging.getLogger('fio')
     stdin = click.get_text_stream('stdin')
     sink = click.get_text_stream('stdout')
@@ -178,7 +185,9 @@ def collect(ctx, precision, indent, compact, record_buffered, ignore_errors,
                 yield feat
     else:
         def feature_gen():
-            yield json.loads(first_line)
+            feat = json.loads(first_line)
+            feat['geometry'] = transformer(feat['geometry'])
+            yield feat
             for line in stdin:
                 feat = json.loads(line)
                 feat['geometry'] = transformer(feat['geometry'])
@@ -292,6 +301,37 @@ def collect(ctx, precision, indent, compact, record_buffered, ignore_errors,
         sys.exit(1)
 
 
+# Distribute command
+ at cli.command(short_help="Distribute features from a collection")
+ at click.option('--x-json-seq-rs/--x-json-seq-no-rs', default=False,
+              help="Use RS as text separator instead of LF. "
+                   "Experimental (default: no).")
+ at click.pass_context
+def distrib(ctx, x_json_seq_rs):
+    """Print the features of GeoJSON objects read from stdin.
+    """
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+    logger = logging.getLogger('fio')
+    stdin = click.get_text_stream('stdin')
+    stdout = click.get_text_stream('stdout')
+    try:
+        source = obj_gen(stdin)
+        for i, obj in enumerate(source):
+            obj_id = obj.get('id', 'collection:' + str(i))
+            features = obj.get('features') or [obj]
+            for j, feat in enumerate(features):
+                if obj.get('type') == 'FeatureCollection':
+                    feat['parent'] = obj_id
+                feat_id = feat.get('id', 'feature:' + str(i))
+                feat['id'] = feat_id
+                stdout.write(json.dumps(feat))
+                stdout.write('\n')
+        sys.exit(0)
+    except Exception:
+        logger.exception("Failed. Exception caught")
+        sys.exit(1)
+
+
 # Dump command
 @cli.command(short_help="Dump a dataset to GeoJSON.")
 @click.argument('input', type=click.Path(), required=True)
@@ -327,7 +367,7 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
          x_json_seq, x_json_seq_rs):
     """Dump a dataset either as a GeoJSON feature collection (the default)
     or a sequence of GeoJSON features."""
-    verbosity = ctx.obj['verbosity']
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
     logger = logging.getLogger('fio')
     sink = click.get_text_stream('stdout')
 
@@ -342,8 +382,8 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
     if encoding:
         open_kwds['encoding'] = encoding
 
-    def transformer(feat):
-        tg = partial(transform_geom, src_crs, 'EPSG:4326',
+    def transformer(crs, feat):
+        tg = partial(transform_geom, crs, 'EPSG:4326',
                      antimeridian_cutting=True, precision=precision)
         feat['geometry'] = tg(feat['geometry'])
         return feat
@@ -356,7 +396,7 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
 
                 if x_json_seq:
                     for feat in source:
-                        feat = transformer(feat)
+                        feat = transformer(source.crs, feat)
                         if x_json_seq_rs:
                             sink.write(u'\u001e')
                         json.dump(feat, sink, **dump_kwds)
@@ -466,7 +506,7 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
                         collection['features'] = [
                             id_record(transformer(rec)) for rec in source]
                     else:
-                        collection['features'] = [transformer(rec) for rec in source]
+                        collection['features'] = [transformer(source.crs, rec) for rec in source]
                     json.dump(collection, sink, **dump_kwds)
 
         sys.exit(0)
diff --git a/fiona/fio/cli.py b/fiona/fio/cli.py
index 8d38fa0..9ad6bd8 100644
--- a/fiona/fio/cli.py
+++ b/fiona/fio/cli.py
@@ -1,3 +1,4 @@
+import json
 import logging
 import sys
 import warnings
@@ -34,3 +35,26 @@ def cli(ctx, verbose, quiet):
     configure_logging(verbosity)
     ctx.obj = {}
     ctx.obj['verbosity'] = verbosity
+
+
+def obj_gen(lines):
+    """Return a generator of JSON objects loaded from ``lines``."""
+    first_line = next(lines)
+    if first_line.startswith(u'\x1e'):
+        def gen():
+            buffer = first_line.strip(u'\x1e')
+            for line in lines:
+                if line.startswith(u'\x1e'):
+                    if buffer:
+                        yield json.loads(buffer)
+                    buffer = line.strip(u'\x1e')
+                else:
+                    buffer += line
+            else:
+                yield json.loads(buffer)
+    else:
+        def gen():
+            yield json.loads(first_line)
+            for line in lines:
+                yield json.loads(line)
+    return gen()
diff --git a/fiona/fio/fio.py b/fiona/fio/fio.py
index 0bb61d4..925eebe 100644
--- a/fiona/fio/fio.py
+++ b/fiona/fio/fio.py
@@ -17,7 +17,8 @@ import fiona
 import fiona.crs
 from fiona.transform import transform_geom
 from fiona.fio.cli import cli
-from fiona.fio.cat import cat, dump
+from fiona.fio.cat import cat, collect, dump, distrib
+from fiona.fio.bounds import bounds
 
 
 FIELD_TYPES_MAP_REV = dict([(v, k) for k, v in fiona.FIELD_TYPES_MAP.items()])
@@ -26,12 +27,12 @@ warnings.simplefilter('default')
 
 # Commands are below.
 
- at cli.command(short_help="Print information about the rio environment.")
+ at cli.command(short_help="Print information about the fio environment.")
 @click.option('--formats', 'key', flag_value='formats', default=True,
               help="Enumerate the available formats.")
 @click.pass_context
 def env(ctx, key):
-    """Print information about the Rasterio environment: available
+    """Print information about the Fiona environment: available
     formats, etc.
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
diff --git a/fiona/fio/stream.py b/fiona/fio/stream.py
new file mode 100644
index 0000000..e7513d2
--- /dev/null
+++ b/fiona/fio/stream.py
@@ -0,0 +1,55 @@
+import json
+import logging
+import sys
+import warnings
+
+import click
+
+import fiona
+from fiona.fio.bounds import bounds
+from fiona.fio.cli import obj_gen, generator, processor, streaming
+
+
+ at streaming.command('open', short_help="Stream GeoJSON objects read from stdin")
+ at generator
+ at click.pass_context
+def open_stream(ctx):
+    """Read lines of JSON text and for each, yield a GeoJSON object.
+
+    Begins a pipeline of internally streamed GeoJSON objects.
+    """
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+    logger = logging.getLogger('fio')
+    stdin = click.get_text_stream('stdin')
+    try:
+        source = obj_gen(stdin)
+        for obj in source:
+            yield obj
+        sys.exit(0)
+    except Exception:
+        logger.exception("Failed. Exception caught")
+        sys.exit(1)
+
+
+ at streaming.command('close', short_help="Write streamed GeoJSON objects to stdout")
+ at click.option('--seq', is_flag=True, default=False,
+              help="Use RS as text separator instead of LF. "
+                   "Experimental (default: no).")
+ at processor
+ at click.pass_context
+def close_stream(stream, ctx, seq):
+    """Write streamed GeoJSON objects to stdout.
+
+    Closes a pipeline of internally streamed GeoJSON objects.
+    """
+    verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+    logger = logging.getLogger('fio')
+    try:
+        for obj in stream:
+            if seq:
+                click.echo(u'\001e')
+            click.echo(json.dumps(obj))
+        sys.exit(0)
+    except Exception:
+        logger.exception("Failed. Exception caught")
+        sys.exit(1)
diff --git a/fiona/ogrext.c b/fiona/ogrext.c
index 895cfbd..a04c333 100644
--- a/fiona/ogrext.c
+++ b/fiona/ogrext.c
@@ -813,13 +813,26 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject
 
 static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg);
 
-static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected);
-
-static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index);
-
-static CYTHON_INLINE int __Pyx_IterFinish(void);
-
-static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected);
+#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
+    __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) : \
+    (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) : \
+               __Pyx_GetItemInt_Generic(o, to_py_func(i))))
+#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
+    __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) : \
+    (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck);
+#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
+    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
+    __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) : \
+    (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL))
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck);
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j);
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
+                                                     int is_list, int wraparound, int boundscheck);
 
 static CYTHON_INLINE void __Pyx_ExceptionSave(PyObject **type, PyObject **value, PyObject **tb);
 static void __Pyx_ExceptionReset(PyObject *type, PyObject *value, PyObject *tb);
@@ -844,27 +857,6 @@ static CYTHON_INLINE PyObject* __Pyx_decode_c_string(
          const char* encoding, const char* errors,
          PyObject* (*decode_func)(const char *s, Py_ssize_t size, const char *errors));
 
-#define __Pyx_GetItemInt(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
-    __Pyx_GetItemInt_Fast(o, (Py_ssize_t)i, is_list, wraparound, boundscheck) : \
-    (is_list ? (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL) : \
-               __Pyx_GetItemInt_Generic(o, to_py_func(i))))
-#define __Pyx_GetItemInt_List(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
-    __Pyx_GetItemInt_List_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) : \
-    (PyErr_SetString(PyExc_IndexError, "list index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck);
-#define __Pyx_GetItemInt_Tuple(o, i, type, is_signed, to_py_func, is_list, wraparound, boundscheck) \
-    (__Pyx_fits_Py_ssize_t(i, type, is_signed) ? \
-    __Pyx_GetItemInt_Tuple_Fast(o, (Py_ssize_t)i, wraparound, boundscheck) : \
-    (PyErr_SetString(PyExc_IndexError, "tuple index out of range"), (PyObject*)NULL))
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck);
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j);
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
-                                                     int is_list, int wraparound, int boundscheck);
-
 static CYTHON_INLINE int __Pyx_PySequence_Contains(PyObject* item, PyObject* seq, int eq) {
     int result = PySequence_Contains(seq, item);
     return unlikely(result < 0) ? result : (result == (eq == Py_EQ));
@@ -872,6 +864,14 @@ static CYTHON_INLINE int __Pyx_PySequence_Contains(PyObject* item, PyObject* seq
 
 static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type);
 
+static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected);
+
+static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index);
+
+static CYTHON_INLINE int __Pyx_IterFinish(void);
+
+static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected);
+
 static CYTHON_INLINE int __Pyx_PyBytes_Equals(PyObject* s1, PyObject* s2, int equals);
 
 static CYTHON_INLINE int __Pyx_PyUnicode_Equals(PyObject* s1, PyObject* s2, int equals);
@@ -1082,12 +1082,12 @@ static void __Pyx_AddTraceback(const char *funcname, int c_line,
 
 static PyObject *__Pyx_Import(PyObject *name, PyObject *from_list, int level);
 
+static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value);
+
 static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value);
 
 static CYTHON_INLINE int __Pyx_PyInt_As_int(PyObject *);
 
-static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value);
-
 static CYTHON_INLINE PyObject* __Pyx_PyInt_From_unsigned_int(unsigned int value);
 
 static CYTHON_INLINE unsigned int __Pyx_PyInt_As_unsigned_int(PyObject *);
@@ -1233,8 +1233,6 @@ static char __pyx_k_f[] = "f";
 static char __pyx_k_i[] = "i";
 static char __pyx_k_s[] = "+%s";
 static char __pyx_k_w[] = "w";
-static char __pyx_k_x[] = "x";
-static char __pyx_k_y[] = "y";
 static char __pyx_k_3D[] = "3D ";
 static char __pyx_k__7[] = "-";
 static char __pyx_k_id[] = "id";
@@ -1262,6 +1260,7 @@ static char __pyx_k_s_s[] = "+%s=%s";
 static char __pyx_k_six[] = "six";
 static char __pyx_k_str[] = "str";
 static char __pyx_k_sys[] = "sys";
+static char __pyx_k_xyz[] = "xyz";
 static char __pyx_k_zip[] = "zip";
 static char __pyx_k_EPSG[] = "EPSG";
 static char __pyx_k_args[] = "args";
@@ -1775,8 +1774,7 @@ static PyObject *__pyx_n_s_warn;
 static PyObject *__pyx_n_s_warnings;
 static PyObject *__pyx_n_s_wktext;
 static PyObject *__pyx_n_s_writerecs_locals_validate_geomet;
-static PyObject *__pyx_n_s_x;
-static PyObject *__pyx_n_s_y;
+static PyObject *__pyx_n_s_xyz;
 static PyObject *__pyx_n_s_year;
 static PyObject *__pyx_n_s_zip;
 static PyObject *__pyx_int_0;
@@ -2340,8 +2338,7 @@ static PyObject *__pyx_pw_5fiona_6ogrext_4_bounds(PyObject *__pyx_self, PyObject
 }
 
 static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_geometry) {
-  PyObject *__pyx_v_x = NULL;
-  PyObject *__pyx_v_y = NULL;
+  PyObject *__pyx_v_xyz = NULL;
   PyObject *__pyx_r = NULL;
   __Pyx_RefNannyDeclarations
   PyObject *__pyx_t_1 = NULL;
@@ -2352,8 +2349,7 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
   PyObject *__pyx_t_6 = NULL;
   PyObject *__pyx_t_7 = NULL;
   PyObject *__pyx_t_8 = NULL;
-  PyObject *(*__pyx_t_9)(PyObject *);
-  int __pyx_t_10;
+  int __pyx_t_9;
   int __pyx_lineno = 0;
   const char *__pyx_filename = NULL;
   int __pyx_clineno = 0;
@@ -2363,8 +2359,8 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
  * def _bounds(geometry):
  *     """Bounding box of a GeoJSON geometry"""
  *     try:             # <<<<<<<<<<<<<<
- *         x, y = zip(*list(_explode(geometry['coordinates'])))
- *         return min(x), min(y), max(x), max(y)
+ *         xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
+ *         return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
  */
   {
     __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3);
@@ -2376,8 +2372,8 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
       /* "fiona/ogrext.pyx":110
  *     """Bounding box of a GeoJSON geometry"""
  *     try:
- *         x, y = zip(*list(_explode(geometry['coordinates'])))             # <<<<<<<<<<<<<<
- *         return min(x), min(y), max(x), max(y)
+ *         xyz = tuple(zip(*list(_explode(geometry['coordinates']))))             # <<<<<<<<<<<<<<
+ *         return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
  *     except (KeyError, TypeError):
  */
       __pyx_t_5 = __Pyx_GetModuleGlobalName(__pyx_n_s_explode); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
@@ -2424,143 +2420,107 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
       __pyx_t_4 = __Pyx_PyObject_Call(__pyx_builtin_zip, __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
       __Pyx_GOTREF(__pyx_t_4);
       __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
-      if ((likely(PyTuple_CheckExact(__pyx_t_4))) || (PyList_CheckExact(__pyx_t_4))) {
-        PyObject* sequence = __pyx_t_4;
-        #if CYTHON_COMPILING_IN_CPYTHON
-        Py_ssize_t size = Py_SIZE(sequence);
-        #else
-        Py_ssize_t size = PySequence_Size(sequence);
-        #endif
-        if (unlikely(size != 2)) {
-          if (size > 2) __Pyx_RaiseTooManyValuesError(2);
-          else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);
-          {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        }
-        #if CYTHON_COMPILING_IN_CPYTHON
-        if (likely(PyTuple_CheckExact(sequence))) {
-          __pyx_t_5 = PyTuple_GET_ITEM(sequence, 0); 
-          __pyx_t_8 = PyTuple_GET_ITEM(sequence, 1); 
-        } else {
-          __pyx_t_5 = PyList_GET_ITEM(sequence, 0); 
-          __pyx_t_8 = PyList_GET_ITEM(sequence, 1); 
-        }
-        __Pyx_INCREF(__pyx_t_5);
-        __Pyx_INCREF(__pyx_t_8);
-        #else
-        __pyx_t_5 = PySequence_ITEM(sequence, 0); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        __Pyx_GOTREF(__pyx_t_5);
-        __pyx_t_8 = PySequence_ITEM(sequence, 1); if (unlikely(!__pyx_t_8)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        __Pyx_GOTREF(__pyx_t_8);
-        #endif
-        __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-      } else {
-        Py_ssize_t index = -1;
-        __pyx_t_6 = PyObject_GetIter(__pyx_t_4); if (unlikely(!__pyx_t_6)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        __Pyx_GOTREF(__pyx_t_6);
-        __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-        __pyx_t_9 = Py_TYPE(__pyx_t_6)->tp_iternext;
-        index = 0; __pyx_t_5 = __pyx_t_9(__pyx_t_6); if (unlikely(!__pyx_t_5)) goto __pyx_L11_unpacking_failed;
-        __Pyx_GOTREF(__pyx_t_5);
-        index = 1; __pyx_t_8 = __pyx_t_9(__pyx_t_6); if (unlikely(!__pyx_t_8)) goto __pyx_L11_unpacking_failed;
-        __Pyx_GOTREF(__pyx_t_8);
-        if (__Pyx_IternextUnpackEndCheck(__pyx_t_9(__pyx_t_6), 2) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        __pyx_t_9 = NULL;
-        __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-        goto __pyx_L12_unpacking_done;
-        __pyx_L11_unpacking_failed:;
-        __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
-        __pyx_t_9 = NULL;
-        if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);
-        {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-        __pyx_L12_unpacking_done:;
-      }
-      __pyx_v_x = __pyx_t_5;
-      __pyx_t_5 = 0;
-      __pyx_v_y = __pyx_t_8;
-      __pyx_t_8 = 0;
+      __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_5);
+      PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_4);
+      __Pyx_GIVEREF(__pyx_t_4);
+      __pyx_t_4 = 0;
+      __pyx_t_4 = __Pyx_PyObject_Call(((PyObject *)((PyObject*)(&PyTuple_Type))), __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 110; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_4);
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+      __pyx_v_xyz = ((PyObject*)__pyx_t_4);
+      __pyx_t_4 = 0;
 
       /* "fiona/ogrext.pyx":111
  *     try:
- *         x, y = zip(*list(_explode(geometry['coordinates'])))
- *         return min(x), min(y), max(x), max(y)             # <<<<<<<<<<<<<<
+ *         xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
+ *         return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])             # <<<<<<<<<<<<<<
  *     except (KeyError, TypeError):
  *         return None
  */
       __Pyx_XDECREF(__pyx_r);
-      __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __pyx_t_4 = __Pyx_GetItemInt_Tuple(__pyx_v_xyz, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(__pyx_t_4 == NULL)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;};
       __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_INCREF(__pyx_v_x);
-      PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_x);
-      __Pyx_GIVEREF(__pyx_v_x);
-      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_min, __pyx_t_4, NULL); if (unlikely(!__pyx_t_8)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-      __Pyx_GOTREF(__pyx_t_8);
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-      __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-      __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_INCREF(__pyx_v_y);
-      PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_y);
-      __Pyx_GIVEREF(__pyx_v_y);
-      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_min, __pyx_t_4, NULL); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __pyx_t_5 = PyTuple_New(1); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
       __Pyx_GOTREF(__pyx_t_5);
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-      __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      PyTuple_SET_ITEM(__pyx_t_5, 0, __pyx_t_4);
+      __Pyx_GIVEREF(__pyx_t_4);
+      __pyx_t_4 = 0;
+      __pyx_t_4 = __Pyx_PyObject_Call(__pyx_builtin_min, __pyx_t_5, NULL); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
       __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_INCREF(__pyx_v_x);
-      PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_x);
-      __Pyx_GIVEREF(__pyx_v_x);
-      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_builtin_max, __pyx_t_4, NULL); if (unlikely(!__pyx_t_6)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
+      __pyx_t_5 = __Pyx_GetItemInt_Tuple(__pyx_v_xyz, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(__pyx_t_5 == NULL)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;};
+      __Pyx_GOTREF(__pyx_t_5);
+      __pyx_t_8 = PyTuple_New(1); if (unlikely(!__pyx_t_8)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_8);
+      PyTuple_SET_ITEM(__pyx_t_8, 0, __pyx_t_5);
+      __Pyx_GIVEREF(__pyx_t_5);
+      __pyx_t_5 = 0;
+      __pyx_t_5 = __Pyx_PyObject_Call(__pyx_builtin_min, __pyx_t_8, NULL); if (unlikely(!__pyx_t_5)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_5);
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
+      __pyx_t_8 = __Pyx_GetItemInt_Tuple(__pyx_v_xyz, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(__pyx_t_8 == NULL)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;};
+      __Pyx_GOTREF(__pyx_t_8);
+      __pyx_t_6 = PyTuple_New(1); if (unlikely(!__pyx_t_6)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
       __Pyx_GOTREF(__pyx_t_6);
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-      __pyx_t_4 = PyTuple_New(1); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-      __Pyx_GOTREF(__pyx_t_4);
-      __Pyx_INCREF(__pyx_v_y);
-      PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_v_y);
-      __Pyx_GIVEREF(__pyx_v_y);
-      __pyx_t_7 = __Pyx_PyObject_Call(__pyx_builtin_max, __pyx_t_4, NULL); if (unlikely(!__pyx_t_7)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-      __Pyx_GOTREF(__pyx_t_7);
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
-      __pyx_t_4 = PyTuple_New(4); if (unlikely(!__pyx_t_4)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
-      __Pyx_GOTREF(__pyx_t_4);
-      PyTuple_SET_ITEM(__pyx_t_4, 0, __pyx_t_8);
+      PyTuple_SET_ITEM(__pyx_t_6, 0, __pyx_t_8);
       __Pyx_GIVEREF(__pyx_t_8);
-      PyTuple_SET_ITEM(__pyx_t_4, 1, __pyx_t_5);
+      __pyx_t_8 = 0;
+      __pyx_t_8 = __Pyx_PyObject_Call(__pyx_builtin_max, __pyx_t_6, NULL); if (unlikely(!__pyx_t_8)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_8);
+      __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
+      __pyx_t_6 = __Pyx_GetItemInt_Tuple(__pyx_v_xyz, 1, long, 1, __Pyx_PyInt_From_long, 0, 0, 1); if (unlikely(__pyx_t_6 == NULL)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;};
+      __Pyx_GOTREF(__pyx_t_6);
+      __pyx_t_7 = PyTuple_New(1); if (unlikely(!__pyx_t_7)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_7);
+      PyTuple_SET_ITEM(__pyx_t_7, 0, __pyx_t_6);
+      __Pyx_GIVEREF(__pyx_t_6);
+      __pyx_t_6 = 0;
+      __pyx_t_6 = __Pyx_PyObject_Call(__pyx_builtin_max, __pyx_t_7, NULL); if (unlikely(!__pyx_t_6)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_6);
+      __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
+      __pyx_t_7 = PyTuple_New(4); if (unlikely(!__pyx_t_7)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 111; __pyx_clineno = __LINE__; goto __pyx_L3_error;}
+      __Pyx_GOTREF(__pyx_t_7);
+      PyTuple_SET_ITEM(__pyx_t_7, 0, __pyx_t_4);
+      __Pyx_GIVEREF(__pyx_t_4);
+      PyTuple_SET_ITEM(__pyx_t_7, 1, __pyx_t_5);
       __Pyx_GIVEREF(__pyx_t_5);
-      PyTuple_SET_ITEM(__pyx_t_4, 2, __pyx_t_6);
+      PyTuple_SET_ITEM(__pyx_t_7, 2, __pyx_t_8);
+      __Pyx_GIVEREF(__pyx_t_8);
+      PyTuple_SET_ITEM(__pyx_t_7, 3, __pyx_t_6);
       __Pyx_GIVEREF(__pyx_t_6);
-      PyTuple_SET_ITEM(__pyx_t_4, 3, __pyx_t_7);
-      __Pyx_GIVEREF(__pyx_t_7);
-      __pyx_t_8 = 0;
+      __pyx_t_4 = 0;
       __pyx_t_5 = 0;
+      __pyx_t_8 = 0;
       __pyx_t_6 = 0;
+      __pyx_r = __pyx_t_7;
       __pyx_t_7 = 0;
-      __pyx_r = __pyx_t_4;
-      __pyx_t_4 = 0;
       goto __pyx_L7_try_return;
     }
     __pyx_L3_error:;
-    __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
+    __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
     __Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
+    __Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
     __Pyx_XDECREF(__pyx_t_6); __pyx_t_6 = 0;
     __Pyx_XDECREF(__pyx_t_7); __pyx_t_7 = 0;
-    __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
 
     /* "fiona/ogrext.pyx":112
- *         x, y = zip(*list(_explode(geometry['coordinates'])))
- *         return min(x), min(y), max(x), max(y)
+ *         xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
+ *         return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
  *     except (KeyError, TypeError):             # <<<<<<<<<<<<<<
  *         return None
  * 
  */
-    __pyx_t_10 = PyErr_ExceptionMatches(__pyx_builtin_KeyError) || PyErr_ExceptionMatches(__pyx_builtin_TypeError);
-    if (__pyx_t_10) {
+    __pyx_t_9 = PyErr_ExceptionMatches(__pyx_builtin_KeyError) || PyErr_ExceptionMatches(__pyx_builtin_TypeError);
+    if (__pyx_t_9) {
       __Pyx_AddTraceback("fiona.ogrext._bounds", __pyx_clineno, __pyx_lineno, __pyx_filename);
-      if (__Pyx_GetException(&__pyx_t_4, &__pyx_t_7, &__pyx_t_6) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 112; __pyx_clineno = __LINE__; goto __pyx_L5_except_error;}
-      __Pyx_GOTREF(__pyx_t_4);
+      if (__Pyx_GetException(&__pyx_t_7, &__pyx_t_6, &__pyx_t_8) < 0) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 112; __pyx_clineno = __LINE__; goto __pyx_L5_except_error;}
       __Pyx_GOTREF(__pyx_t_7);
       __Pyx_GOTREF(__pyx_t_6);
+      __Pyx_GOTREF(__pyx_t_8);
 
       /* "fiona/ogrext.pyx":113
- *         return min(x), min(y), max(x), max(y)
+ *         return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
  *     except (KeyError, TypeError):
  *         return None             # <<<<<<<<<<<<<<
  * 
@@ -2569,9 +2529,9 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
       __Pyx_XDECREF(__pyx_r);
       __Pyx_INCREF(Py_None);
       __pyx_r = Py_None;
-      __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
       __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
       __Pyx_DECREF(__pyx_t_7); __pyx_t_7 = 0;
+      __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
       goto __pyx_L6_except_return;
     }
     goto __pyx_L5_except_error;
@@ -2613,8 +2573,7 @@ static PyObject *__pyx_pf_5fiona_6ogrext_3_bounds(CYTHON_UNUSED PyObject *__pyx_
   __Pyx_AddTraceback("fiona.ogrext._bounds", __pyx_clineno, __pyx_lineno, __pyx_filename);
   __pyx_r = NULL;
   __pyx_L0:;
-  __Pyx_XDECREF(__pyx_v_x);
-  __Pyx_XDECREF(__pyx_v_y);
+  __Pyx_XDECREF(__pyx_v_xyz);
   __Pyx_XGIVEREF(__pyx_r);
   __Pyx_RefNannyFinishContext();
   return __pyx_r;
@@ -18700,8 +18659,7 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
   {&__pyx_n_s_warnings, __pyx_k_warnings, sizeof(__pyx_k_warnings), 0, 0, 1, 1},
   {&__pyx_n_s_wktext, __pyx_k_wktext, sizeof(__pyx_k_wktext), 0, 0, 1, 1},
   {&__pyx_n_s_writerecs_locals_validate_geomet, __pyx_k_writerecs_locals_validate_geomet, sizeof(__pyx_k_writerecs_locals_validate_geomet), 0, 0, 1, 1},
-  {&__pyx_n_s_x, __pyx_k_x, sizeof(__pyx_k_x), 0, 0, 1, 1},
-  {&__pyx_n_s_y, __pyx_k_y, sizeof(__pyx_k_y), 0, 0, 1, 1},
+  {&__pyx_n_s_xyz, __pyx_k_xyz, sizeof(__pyx_k_xyz), 0, 0, 1, 1},
   {&__pyx_n_s_year, __pyx_k_year, sizeof(__pyx_k_year), 0, 0, 1, 1},
   {&__pyx_n_s_zip, __pyx_k_zip, sizeof(__pyx_k_zip), 0, 0, 1, 1},
   {0, 0, 0, 0, 0, 0, 0}
@@ -19478,10 +19436,10 @@ static int __Pyx_InitCachedConstants(void) {
  *     """Bounding box of a GeoJSON geometry"""
  *     try:
  */
-  __pyx_tuple__80 = PyTuple_Pack(3, __pyx_n_s_geometry, __pyx_n_s_x, __pyx_n_s_y); if (unlikely(!__pyx_tuple__80)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 107; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
+  __pyx_tuple__80 = PyTuple_Pack(2, __pyx_n_s_geometry, __pyx_n_s_xyz); if (unlikely(!__pyx_tuple__80)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 107; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
   __Pyx_GOTREF(__pyx_tuple__80);
   __Pyx_GIVEREF(__pyx_tuple__80);
-  __pyx_codeobj__81 = (PyObject*)__Pyx_PyCode_New(1, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__80, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_Users_sean_code_Fiona_fiona_ogr, __pyx_n_s_bounds, 107, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__81)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 107; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
+  __pyx_codeobj__81 = (PyObject*)__Pyx_PyCode_New(1, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__80, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_Users_sean_code_Fiona_fiona_ogr, __pyx_n_s_bounds, 107, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__81)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 107; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
 
   /* "fiona/ogrext.pyx":115
  *         return None
@@ -20839,60 +20797,82 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObjec
 }
 #endif
 
-static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected) {
-    PyErr_Format(PyExc_ValueError,
-                 "too many values to unpack (expected %" CYTHON_FORMAT_SSIZE_T "d)", expected);
-}
-
-static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
-    PyErr_Format(PyExc_ValueError,
-                 "need more than %" CYTHON_FORMAT_SSIZE_T "d value%.1s to unpack",
-                 index, (index == 1) ? "" : "s");
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) {
+    PyObject *r;
+    if (!j) return NULL;
+    r = PyObject_GetItem(o, j);
+    Py_DECREF(j);
+    return r;
 }
-
-static CYTHON_INLINE int __Pyx_IterFinish(void) {
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck) {
 #if CYTHON_COMPILING_IN_CPYTHON
-    PyThreadState *tstate = PyThreadState_GET();
-    PyObject* exc_type = tstate->curexc_type;
-    if (unlikely(exc_type)) {
-        if (likely(exc_type == PyExc_StopIteration) || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)) {
-            PyObject *exc_value, *exc_tb;
-            exc_value = tstate->curexc_value;
-            exc_tb = tstate->curexc_traceback;
-            tstate->curexc_type = 0;
-            tstate->curexc_value = 0;
-            tstate->curexc_traceback = 0;
-            Py_DECREF(exc_type);
-            Py_XDECREF(exc_value);
-            Py_XDECREF(exc_tb);
-            return 0;
-        } else {
-            return -1;
-        }
+    if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
+    if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
+        PyObject *r = PyList_GET_ITEM(o, i);
+        Py_INCREF(r);
+        return r;
     }
-    return 0;
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
 #else
-    if (unlikely(PyErr_Occurred())) {
-        if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) {
-            PyErr_Clear();
-            return 0;
-        } else {
-            return -1;
-        }
+    return PySequence_GetItem(o, i);
+#endif
+}
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
+                                                              int wraparound, int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
+    if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
+        PyObject *r = PyTuple_GET_ITEM(o, i);
+        Py_INCREF(r);
+        return r;
     }
-    return 0;
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
+#else
+    return PySequence_GetItem(o, i);
 #endif
 }
-
-static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) {
-    if (unlikely(retval)) {
-        Py_DECREF(retval);
-        __Pyx_RaiseTooManyValuesError(expected);
-        return -1;
+static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
+                                                     int is_list, int wraparound, int boundscheck) {
+#if CYTHON_COMPILING_IN_CPYTHON
+    if (is_list || PyList_CheckExact(o)) {
+        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
+        if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
+            PyObject *r = PyList_GET_ITEM(o, n);
+            Py_INCREF(r);
+            return r;
+        }
+    }
+    else if (PyTuple_CheckExact(o)) {
+        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o);
+        if ((!boundscheck) || likely((n >= 0) & (n < PyTuple_GET_SIZE(o)))) {
+            PyObject *r = PyTuple_GET_ITEM(o, n);
+            Py_INCREF(r);
+            return r;
+        }
     } else {
-        return __Pyx_IterFinish();
+        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
+        if (likely(m && m->sq_item)) {
+            if (wraparound && unlikely(i < 0) && likely(m->sq_length)) {
+                Py_ssize_t l = m->sq_length(o);
+                if (likely(l >= 0)) {
+                    i += l;
+                } else {
+                    if (PyErr_ExceptionMatches(PyExc_OverflowError))
+                        PyErr_Clear();
+                    else
+                        return NULL;
+                }
+            }
+            return m->sq_item(o, i);
+        }
     }
-    return 0;
+#else
+    if (is_list || PySequence_Check(o)) {
+        return PySequence_GetItem(o, i);
+    }
+#endif
+    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
 }
 
 static CYTHON_INLINE void __Pyx_ExceptionSave(PyObject **type, PyObject **value, PyObject **tb) {
@@ -21204,93 +21184,71 @@ static CYTHON_INLINE PyObject* __Pyx_decode_c_string(
     }
 }
 
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Generic(PyObject *o, PyObject* j) {
-    PyObject *r;
-    if (!j) return NULL;
-    r = PyObject_GetItem(o, j);
-    Py_DECREF(j);
-    return r;
-}
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_List_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck) {
-#if CYTHON_COMPILING_IN_CPYTHON
-    if (wraparound & unlikely(i < 0)) i += PyList_GET_SIZE(o);
-    if ((!boundscheck) || likely((0 <= i) & (i < PyList_GET_SIZE(o)))) {
-        PyObject *r = PyList_GET_ITEM(o, i);
-        Py_INCREF(r);
-        return r;
+static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) {
+    if (unlikely(!type)) {
+        PyErr_SetString(PyExc_SystemError, "Missing type object");
+        return 0;
     }
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
-#else
-    return PySequence_GetItem(o, i);
-#endif
+    if (likely(PyObject_TypeCheck(obj, type)))
+        return 1;
+    PyErr_Format(PyExc_TypeError, "Cannot convert %.200s to %.200s",
+                 Py_TYPE(obj)->tp_name, type->tp_name);
+    return 0;
 }
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Tuple_Fast(PyObject *o, Py_ssize_t i,
-                                                              int wraparound, int boundscheck) {
-#if CYTHON_COMPILING_IN_CPYTHON
-    if (wraparound & unlikely(i < 0)) i += PyTuple_GET_SIZE(o);
-    if ((!boundscheck) || likely((0 <= i) & (i < PyTuple_GET_SIZE(o)))) {
-        PyObject *r = PyTuple_GET_ITEM(o, i);
-        Py_INCREF(r);
-        return r;
-    }
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
-#else
-    return PySequence_GetItem(o, i);
-#endif
+
+static CYTHON_INLINE void __Pyx_RaiseTooManyValuesError(Py_ssize_t expected) {
+    PyErr_Format(PyExc_ValueError,
+                 "too many values to unpack (expected %" CYTHON_FORMAT_SSIZE_T "d)", expected);
 }
-static CYTHON_INLINE PyObject *__Pyx_GetItemInt_Fast(PyObject *o, Py_ssize_t i,
-                                                     int is_list, int wraparound, int boundscheck) {
+
+static CYTHON_INLINE void __Pyx_RaiseNeedMoreValuesError(Py_ssize_t index) {
+    PyErr_Format(PyExc_ValueError,
+                 "need more than %" CYTHON_FORMAT_SSIZE_T "d value%.1s to unpack",
+                 index, (index == 1) ? "" : "s");
+}
+
+static CYTHON_INLINE int __Pyx_IterFinish(void) {
 #if CYTHON_COMPILING_IN_CPYTHON
-    if (is_list || PyList_CheckExact(o)) {
-        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyList_GET_SIZE(o);
-        if ((!boundscheck) || (likely((n >= 0) & (n < PyList_GET_SIZE(o))))) {
-            PyObject *r = PyList_GET_ITEM(o, n);
-            Py_INCREF(r);
-            return r;
-        }
-    }
-    else if (PyTuple_CheckExact(o)) {
-        Py_ssize_t n = ((!wraparound) | likely(i >= 0)) ? i : i + PyTuple_GET_SIZE(o);
-        if ((!boundscheck) || likely((n >= 0) & (n < PyTuple_GET_SIZE(o)))) {
-            PyObject *r = PyTuple_GET_ITEM(o, n);
-            Py_INCREF(r);
-            return r;
-        }
-    } else {
-        PySequenceMethods *m = Py_TYPE(o)->tp_as_sequence;
-        if (likely(m && m->sq_item)) {
-            if (wraparound && unlikely(i < 0) && likely(m->sq_length)) {
-                Py_ssize_t l = m->sq_length(o);
-                if (likely(l >= 0)) {
-                    i += l;
-                } else {
-                    if (PyErr_ExceptionMatches(PyExc_OverflowError))
-                        PyErr_Clear();
-                    else
-                        return NULL;
-                }
-            }
-            return m->sq_item(o, i);
+    PyThreadState *tstate = PyThreadState_GET();
+    PyObject* exc_type = tstate->curexc_type;
+    if (unlikely(exc_type)) {
+        if (likely(exc_type == PyExc_StopIteration) || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)) {
+            PyObject *exc_value, *exc_tb;
+            exc_value = tstate->curexc_value;
+            exc_tb = tstate->curexc_traceback;
+            tstate->curexc_type = 0;
+            tstate->curexc_value = 0;
+            tstate->curexc_traceback = 0;
+            Py_DECREF(exc_type);
+            Py_XDECREF(exc_value);
+            Py_XDECREF(exc_tb);
+            return 0;
+        } else {
+            return -1;
         }
     }
+    return 0;
 #else
-    if (is_list || PySequence_Check(o)) {
-        return PySequence_GetItem(o, i);
+    if (unlikely(PyErr_Occurred())) {
+        if (likely(PyErr_ExceptionMatches(PyExc_StopIteration))) {
+            PyErr_Clear();
+            return 0;
+        } else {
+            return -1;
+        }
     }
+    return 0;
 #endif
-    return __Pyx_GetItemInt_Generic(o, PyInt_FromSsize_t(i));
 }
 
-static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) {
-    if (unlikely(!type)) {
-        PyErr_SetString(PyExc_SystemError, "Missing type object");
-        return 0;
+static int __Pyx_IternextUnpackEndCheck(PyObject *retval, Py_ssize_t expected) {
+    if (unlikely(retval)) {
+        Py_DECREF(retval);
+        __Pyx_RaiseTooManyValuesError(expected);
+        return -1;
+    } else {
+        return __Pyx_IterFinish();
     }
-    if (likely(PyObject_TypeCheck(obj, type)))
-        return 1;
-    PyErr_Format(PyExc_TypeError, "Cannot convert %.200s to %.200s",
-                 Py_TYPE(obj)->tp_name, type->tp_name);
     return 0;
 }
 
@@ -22511,6 +22469,32 @@ bad:
     return module;
 }
 
+static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
+    const long neg_one = (long) -1, const_zero = 0;
+    const int is_unsigned = neg_one > const_zero;
+    if (is_unsigned) {
+        if (sizeof(long) < sizeof(long)) {
+            return PyInt_FromLong((long) value);
+        } else if (sizeof(long) <= sizeof(unsigned long)) {
+            return PyLong_FromUnsignedLong((unsigned long) value);
+        } else if (sizeof(long) <= sizeof(unsigned long long)) {
+            return PyLong_FromUnsignedLongLong((unsigned long long) value);
+        }
+    } else {
+        if (sizeof(long) <= sizeof(long)) {
+            return PyInt_FromLong((long) value);
+        } else if (sizeof(long) <= sizeof(long long)) {
+            return PyLong_FromLongLong((long long) value);
+        }
+    }
+    {
+        int one = 1; int little = (int)*(unsigned char *)&one;
+        unsigned char *bytes = (unsigned char *)&value;
+        return _PyLong_FromByteArray(bytes, sizeof(long),
+                                     little, !is_unsigned);
+    }
+}
+
 static CYTHON_INLINE PyObject* __Pyx_PyInt_From_int(int value) {
     const int neg_one = (int) -1, const_zero = 0;
     const int is_unsigned = neg_one > const_zero;
@@ -22653,32 +22637,6 @@ raise_neg_overflow:
     return (int) -1;
 }
 
-static CYTHON_INLINE PyObject* __Pyx_PyInt_From_long(long value) {
-    const long neg_one = (long) -1, const_zero = 0;
-    const int is_unsigned = neg_one > const_zero;
-    if (is_unsigned) {
-        if (sizeof(long) < sizeof(long)) {
-            return PyInt_FromLong((long) value);
-        } else if (sizeof(long) <= sizeof(unsigned long)) {
-            return PyLong_FromUnsignedLong((unsigned long) value);
-        } else if (sizeof(long) <= sizeof(unsigned long long)) {
-            return PyLong_FromUnsignedLongLong((unsigned long long) value);
-        }
-    } else {
-        if (sizeof(long) <= sizeof(long)) {
-            return PyInt_FromLong((long) value);
-        } else if (sizeof(long) <= sizeof(long long)) {
-            return PyLong_FromLongLong((long long) value);
-        }
-    }
-    {
-        int one = 1; int little = (int)*(unsigned char *)&one;
-        unsigned char *bytes = (unsigned char *)&value;
-        return _PyLong_FromByteArray(bytes, sizeof(long),
-                                     little, !is_unsigned);
-    }
-}
-
 static CYTHON_INLINE PyObject* __Pyx_PyInt_From_unsigned_int(unsigned int value) {
     const unsigned int neg_one = (unsigned int) -1, const_zero = 0;
     const int is_unsigned = neg_one > const_zero;
diff --git a/setup.cfg b/setup.cfg
index b4e5efc..5c205e9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,6 +5,6 @@ verbosity = 3
 
 [egg_info]
 tag_build = 
-tag_date = 0
 tag_svn_revision = 0
+tag_date = 0
 
diff --git a/tests/test_bounds.py b/tests/test_bounds.py
index 9261c0d..420f5e2 100644
--- a/tests/test_bounds.py
+++ b/tests/test_bounds.py
@@ -13,3 +13,7 @@ def test_bounds():
                                                          51.357216,
                                                          0.947778,
                                                          51.444717)
+
+def test_bounds_z():
+    g = {'type': 'Point', 'coordinates': [10,10,10]}
+    assert fiona.bounds(g) == (10, 10, 10, 10)
diff --git a/tests/test_fio_bounds.py b/tests/test_fio_bounds.py
new file mode 100644
index 0000000..45a77a1
--- /dev/null
+++ b/tests/test_fio_bounds.py
@@ -0,0 +1,86 @@
+import json
+
+import click
+from click.testing import CliRunner
+
+from fiona.fio import bounds
+
+
+input = u'\x1e{"geometry": {"coordinates": [[[100094.81257811641, 6684726.008762141], [98548.69617048775, 6684924.5976624405], [87664.09899970173, 6686905.046363058], [86952.87877302397, 6687103.688267614], [85283.08641112497, 6688045.252446961], [84540.91936600611, 6688936.450241844], [82963.96745943041, 6691364.418923092], [82469.15232285221, 6692405.682380612], [81819.82573305666, 6693843.436658373], [82438.31682390235, 6697660.772804541], [83365.94214068248, 6700140.454427341], [8463 [...]
+
+
+
+def test_fail():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, [], '5')
+    assert result.exit_code == 1
+
+
+def test_bounds():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, [], input)
+    assert result.exit_code == 0
+    assert len(json.loads(result.output.strip())) == 4
+    assert round(json.loads(result.output.strip())[0], 1) ==  81819.8
+
+
+def test_bounds_precision():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--precision', 1], input)
+    assert result.exit_code == 0
+    assert len(json.loads(result.output.strip())) == 4
+    assert json.loads(result.output.strip())[0] ==  81819.8
+
+
+def test_bounds_explode():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--explode'], input)
+    assert result.exit_code == 0
+    assert len(json.loads(result.output.strip())) == 4
+    assert round(json.loads(result.output.strip())[0], 1) ==  81819.8
+
+
+def test_bounds_with_id():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--with-id'], input)
+    assert result.exit_code == 0
+    obj = json.loads(result.output.strip())
+    assert 'id' in obj
+    assert 'bbox' in obj
+    assert len(obj['bbox']) == 4
+    assert round(obj['bbox'][0], 1) ==  81819.8
+
+
+def test_bounds_explode_with_id():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--explode', '--with-id'], input)
+    assert result.exit_code == 0
+    obj = json.loads(result.output.strip())
+    assert 'id' in obj
+    assert 'bbox' in obj
+    assert len(obj['bbox']) == 4
+    assert round(obj['bbox'][0], 1) ==  81819.8
+
+
+def test_bounds_with_obj():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--with-obj'], input)
+    assert result.exit_code == 0
+    obj = json.loads(result.output.strip())
+    assert 'geometry' in obj
+    assert 'id' in obj
+    assert 'bbox' in obj
+    assert len(obj['bbox']) == 4
+    assert round(obj['bbox'][0], 1) ==  81819.8
+
+
+def test_bounds_explode_with_obj():
+    runner = CliRunner()
+    result = runner.invoke(bounds.bounds, ['--explode', '--with-obj'], input)
+    assert result.exit_code == 0
+    obj = json.loads(result.output.strip())
+    assert 'geometry' in obj
+    assert 'id' in obj
+    assert 'bbox' in obj
+    assert len(obj['bbox']) == 4
+    assert round(obj['bbox'][0], 1) ==  81819.8
diff --git a/tests/test_fio_cat.py b/tests/test_fio_cat.py
new file mode 100644
index 0000000..e865a08
--- /dev/null
+++ b/tests/test_fio_cat.py
@@ -0,0 +1,105 @@
+import json
+
+import click
+from click.testing import CliRunner
+
+from fiona.fio import cat
+
+
+input = u'\x1e{"geometry": {"coordinates": [[[100094.81257811641, 6684726.008762141], [98548.69617048775, 6684924.5976624405], [87664.09899970173, 6686905.046363058], [86952.87877302397, 6687103.688267614], [85283.08641112497, 6688045.252446961], [84540.91936600611, 6688936.450241844], [82963.96745943041, 6691364.418923092], [82469.15232285221, 6692405.682380612], [81819.82573305666, 6693843.436658373], [82438.31682390235, 6697660.772804541], [83365.94214068248, 6700140.454427341], [8463 [...]
+
+input_collection = u'\x1e{"features": [{"geometry": {"coordinates": [[[100094.81257811641, 6684726.008762141], [98548.69617048775, 6684924.5976624405], [87664.09899970173, 6686905.046363058], [86952.87877302397, 6687103.688267614], [85283.08641112497, 6688045.252446961], [84540.91936600611, 6688936.450241844], [82963.96745943041, 6691364.418923092], [82469.15232285221, 6692405.682380612], [81819.82573305666, 6693843.436658373], [82438.31682390235, 6697660.772804541], [83365.94214068248,  [...]
+
+
+def test_cat():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.cat,
+        ['docs/data/test_uk.shp'],
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert result.output.count('"Feature"') == 48
+
+
+def test_cat_bbox_no():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.cat,
+        ['docs/data/test_uk.shp', '--bbox', '-90,10,-80,20'],
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert result.output == ""
+
+
+def test_cat_bbox_yes():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.cat,
+        ['docs/data/test_uk.shp', '--bbox', '-10,50,0,60'],
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert result.output.count('"Feature"') == 44
+
+
+def test_collect_rs():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.collect,
+        ['--src_crs', 'EPSG:3857'],
+        input,
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert result.output == u'{"features": [{"geometry": {"coordinates": [[[0.8991670000000086, 51.357216], [0.8852780000000007, 51.358329999999995], [0.7874999999999889, 51.369438], [0.7811109999999931, 51.37055199999999], [0.766110999999994, 51.375831999999996], [0.7594439999999931, 51.380828999999984], [0.7452780000000093, 51.39443999999999], [0.7408329999999906, 51.40027599999999], [0.735000000000005, 51.408332999999985], [0.7405559999999894, 51.42971799999999], [0.7488889999999875,  [...]
+
+
+def test_collect_no_rs():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.collect,
+        ['--src_crs', 'EPSG:3857'],
+        input,
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert result.output == u'{"features": [{"geometry": {"coordinates": [[[0.8991670000000086, 51.357216], [0.8852780000000007, 51.358329999999995], [0.7874999999999889, 51.369438], [0.7811109999999931, 51.37055199999999], [0.766110999999994, 51.375831999999996], [0.7594439999999931, 51.380828999999984], [0.7452780000000093, 51.39443999999999], [0.7408329999999906, 51.40027599999999], [0.735000000000005, 51.408332999999985], [0.7405559999999894, 51.42971799999999], [0.7488889999999875,  [...]
+
+
+def test_collect_ld():
+    runner = CliRunner()
+    result = runner.invoke(
+        cat.collect,
+        ['--with-ld-context', '--add-ld-context-item', 'foo=bar'],
+        input,
+        catch_exceptions=False)
+    assert result.exit_code == 0
+    assert '"@context": {' in result.output
+    assert '"foo": "bar"' in result.output
+
+
+def test_collect_rec_buffered():
+    runner = CliRunner()
+    result = runner.invoke(cat.collect, ['--record-buffered'], input)
+    assert result.exit_code == 0
+    assert '"FeatureCollection"' in result.output
+
+
+def test_distrib():
+    runner = CliRunner()
+    result = runner.invoke(cat.distrib, [], input)
+    assert result.exit_code == 0
+    assert json.loads(result.output.strip())['id'] == '0'
+
+
+
+def test_distrib():
+    runner = CliRunner()
+    result = runner.invoke(cat.distrib, [], input_collection)
+    assert result.exit_code == 0
+    assert json.loads(result.output.strip())['parent'] == 'collection:0'
+    assert json.loads(result.output.strip())['id'] == '0'
+
+
+def test_dump():
+    runner = CliRunner()
+    result = runner.invoke(cat.dump, ['docs/data/test_uk.shp'])
+    assert result.exit_code == 0
+    assert '"FeatureCollection"' in result.output
diff --git a/tests/test_geopackage.py b/tests/test_geopackage.py
index 3cc599d..d990d34 100644
--- a/tests/test_geopackage.py
+++ b/tests/test_geopackage.py
@@ -1,15 +1,20 @@
 
 import logging
 import os
+import os.path
 import shutil
 import sys
 import tempfile
 import unittest
 
+import pytest
+
 import fiona
 from fiona.collection import supported_drivers
 from fiona.errors import FionaValueError, DriverError, SchemaError, CRSError
 from fiona.ogrext import calc_gdal_version_num, get_gdal_version_num
+
+
 logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 
@@ -21,6 +26,8 @@ class ReadingTest(unittest.TestCase):
     def tearDown(self):
         pass
 
+    @pytest.mark.skipif(not os.path.exists('docs/data/test_uk.gpkg'),
+                        reason="Requires geopackage fixture")
     def test_gpkg(self):
         if get_gdal_version_num() < calc_gdal_version_num(1, 11, 0):
             self.assertRaises(DriverError, fiona.open, 'docs/data/test_uk.gpkg', 'r', driver="GPKG")
@@ -37,6 +44,8 @@ class WritingTest(unittest.TestCase):
     def tearDown(self):
         shutil.rmtree(self.tempdir)
 
+    @pytest.mark.skipif(not os.path.exists('docs/data/test_uk.gpkg'),
+                        reason="Requires geopackage fixture")
     def test_gpkg(self):
         schema = {'geometry': 'Point',
                   'properties': [('title', 'str')]}
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 092c3e7..d922601 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -124,3 +124,23 @@ class ShapefileSchema(unittest.TestCase):
             f = next(c)
             self.assertEqual(f['properties']['EstimatedP'], 27773.0)
 
+def test_issue177(tmpdir):
+    name = str(tmpdir.join("output.shp"))
+
+    kwargs = {
+        'driver': 'ESRI Shapefile',
+        'crs': 'EPSG:4326',
+        'schema': {
+            'geometry': 'Point',
+            'properties': [('a_fieldname', 'float')]}}
+
+    with fiona.open(name, 'w', **kwargs) as dst:
+        rec = {}
+        rec['geometry'] = {'type': 'Point', 'coordinates': (0, 0)}
+        rec['properties'] = {'a_fieldname': 3.0}
+        dst.write(rec)
+
+    with fiona.open(name) as src:
+        first = next(src)
+        assert first['geometry'] == {'type': 'Point', 'coordinates': (0, 0)}
+        assert first['properties']['a_fieldnam'] == 3.0

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/fiona.git



More information about the Pkg-grass-devel mailing list