[fiona] 01/02: Imported Upstream version 1.6.0
Johan Van de Wauw
johanvdw-guest at moszumanska.debian.org
Sat Aug 1 23:03:01 UTC 2015
This is an automated email from the git hooks/post-receive script.
johanvdw-guest pushed a commit to branch master
in repository fiona.
commit 021f3000994a4e5de7715d0c95b806ecbfb1efc1
Author: Johan Van de Wauw <johan.vandewauw at gmail.com>
Date: Thu Jul 23 22:17:24 2015 +0200
Imported Upstream version 1.6.0
---
CHANGES.txt | 9 ++
CREDITS.txt | 44 +++++----
README.rst | 19 ++++
fiona/__init__.py | 8 +-
fiona/_drivers.pyx | 1 +
fiona/_geometry.pxd | 20 ++--
fiona/collection.py | 40 +++++++-
fiona/fio/bounds.py | 9 +-
fiona/fio/cat.py | 138 ++++++++++++++++++++++-----
fiona/fio/cli.py | 61 ------------
fiona/fio/fio.py | 202 ----------------------------------------
fiona/fio/helpers.py | 33 +++++++
fiona/fio/info.py | 99 ++++++++++++++++++++
fiona/fio/main.py | 36 +++++++
fiona/fio/options.py | 8 ++
fiona/inspector.py | 15 +--
fiona/ograpi.pxd | 10 ++
fiona/ogrext.pyx | 20 ++++
requirements-dev.txt | 2 +-
setup.py | 80 +++++++++++-----
tests/fixtures.py | 7 ++
tests/test_bytescollection.py | 212 ++++++++++++++++++++++++++++++++++++++++++
tests/test_cli.py | 17 ++--
tests/test_fio_cat.py | 4 +-
tests/test_fio_load.py | 69 ++++++++++++--
25 files changed, 794 insertions(+), 369 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 23f4b97..b6b043b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,15 @@
Changes
=======
+1.6.0 (2015-07-21)
+------------------
+- Upgrade Cython requirement to 0.22 (#214).
+- New BytesCollection class (#215).
+- Add GDAL's OpenFileGDB driver to registered drivers (#221).
+- Implement CLI commands as plugins (#228).
+- Raise click.abort instead of calling sys.exit, preventing suprising exits
+ (#236).
+
1.5.1 (2015-03-19)
------------------
- Restore test data to sdists by fixing MANIFEST.in (#216).
diff --git a/CREDITS.txt b/CREDITS.txt
index 4fcf276..03a2998 100644
--- a/CREDITS.txt
+++ b/CREDITS.txt
@@ -3,26 +3,30 @@ Credits
Fiona is written by:
-* Sean Gillies (https://github.com/sgillies)
-
-With contributions by:
-
-* Joshua Arnott (https://github.com/snorfalorpagus)
-* René Buffat (https://github.com/rbuffat)
-* Michele Citterio (https://github.com/citterio)
-* Stefano Costa (https://github.com/steko)
-* Ludovic Delauné (https://github.com/ldgeo)
-* Kelsey Jordahl (https://github.com/kjordahl)
-* Frédéric Junod (https://github.com/fredj)
-* jwass (https://github.com/jwass)
-* Brandon Liu (https://github.com/bdon)
-* lordi (https://github.com/lordi)
-* Ariel Núñez (https://github.com/ingenieroariel)
-* Oliver Tonnhofer (https://github.com/olt)
-* Brendan Wards (https://github.com/brendan-ward)
-* Michael Weisman (https://github.com/mweisman)
-* Andy Wilson (https://github.com/wilsaj)
-* Martijn Visser (https://github.com/visr)
+* Sean Gillies <sean.gillies at gmail.com>
+* Rene Buffat <buffat at gmail.com>
+* Kevin Wurster <wursterk at gmail.com>
+* Kelsey Jordahl <kjordahl at enthought.com>
+* Patrick Young <patrick.mckendree.young at gmail.com>
+* Hannes Gräuler <graeuler at geoplex.de>
+* Ryan Grout <rgrout at continuum.io>
+* Michael Weisman <mweisman at gmail.com>
+* Joshua Arnott <josh at snorfalorpagus.net>
+* Jacob Wasserman <jwasserman at gmail.com>
+* Miro Hrončok <miro at hroncok.cz>
+* Michele Citterio <michele at citterio.net>
+* Johan Van de Wauw <johan.vandewauw at gmail.com>
+* fredj <frederic.junod at camptocamp.com>
+* Brendan Ward <bcward at consbio.org>
+* wilsaj <wilson.andrew.j+github at gmail.com>
+* Stefano Costa <steko at iosa.it>
+* Oliver Tonnhofer <olt at bogosoft.com>
+* Martijn Visser <mgvisser at gmail.com>
+* Ludovic Delauné <ludotux at gmail.com>
+* Hannes Gräuler <hgraeule at uos.de>
+* dimlev <dimlev at gmail.com>
+* Brandon Liu <bdon at bdon.org>
+* Ariel Nunez <ingenieroariel at gmail.com>
Fiona would not be possible without the great work of Frank Warmerdam and other
GDAL/OGR developers.
diff --git a/README.rst b/README.rst
index 546a4b5..54cce6d 100644
--- a/README.rst
+++ b/README.rst
@@ -286,6 +286,25 @@ Windows
Binary installers are available at
http://www.lfd.uci.edu/~gohlke/pythonlibs/#fiona and coming eventually to PyPI.
+You can download a binary distribution of GDAL from `here
+<http://www.gisinternals.com/release.php>`_. You will also need to download
+the compiled libraries and headers (include files).
+
+When building from source on Windows, it is important to know that setup.py
+cannot rely on gdal-config, which is only present on UNIX systems, to discover
+the locations of header files and libraries that Fiona needs to compile its
+C extensions. On Windows, these paths need to be provided by the user.
+You will need to find the include files and the library files for gdal and
+use setup.py as follows.
+
+.. code-block:: console
+
+ $ python setup.py build_ext -I<path to gdal include files> -lgdal_i -L<path to gdal library>
+ $ python setup.py install
+
+Note: The GDAL dll (gdal111.dll) and gdal-data directory need to be in your
+Windows PATH otherwise Fiona will fail to work.
+
Development and testing
=======================
diff --git a/fiona/__init__.py b/fiona/__init__.py
index 70ba9ef..5eac18c 100644
--- a/fiona/__init__.py
+++ b/fiona/__init__.py
@@ -63,17 +63,21 @@ writing modes) flush contents to disk when their ``with`` blocks end.
"""
__all__ = ['bounds', 'listlayers', 'open', 'prop_type', 'prop_width']
-__version__ = "1.5.1"
+__version__ = "1.6.0"
import logging
import os
from six import string_types
-from fiona.collection import Collection, vsi_path
+from fiona.collection import Collection, BytesCollection, vsi_path
from fiona._drivers import driver_count, GDALEnv, supported_drivers
from fiona.odict import OrderedDict
from fiona.ogrext import _bounds, _listlayers, FIELD_TYPES_MAP
+# These modules are imported by fiona.ogrext, but are also import here to
+# help tools like cx_Freeze find them automatically
+from fiona import _geometry, _err, rfc3339
+import uuid
log = logging.getLogger('Fiona')
class NullHandler(logging.Handler):
diff --git a/fiona/_drivers.pyx b/fiona/_drivers.pyx
index 604ce22..c320455 100644
--- a/fiona/_drivers.pyx
+++ b/fiona/_drivers.pyx
@@ -186,6 +186,7 @@ supported_drivers = dict([
#ESRI FileGDB FileGDB Yes Yes No, needs FileGDB API library
# multi-layer
("FileGDB", "raw"),
+ ("OpenFileGDB", "r"),
#ESRI Personal GeoDatabase PGeo No Yes No, needs ODBC library
#ESRI ArcSDE SDE No Yes No, needs ESRI SDE
#ESRI Shapefile ESRI Shapefile Yes Yes Yes
diff --git a/fiona/_geometry.pxd b/fiona/_geometry.pxd
index 1ff29cf..2bf2cc1 100644
--- a/fiona/_geometry.pxd
+++ b/fiona/_geometry.pxd
@@ -19,14 +19,14 @@ cdef class GeomBuilder:
cdef class OGRGeomBuilder:
- cdef void * _createOgrGeometry(self, int geom_type)
+ cdef void * _createOgrGeometry(self, int geom_type) except NULL
cdef _addPointToGeometry(self, void *cogr_geometry, object coordinate)
- cdef void * _buildPoint(self, object coordinates)
- cdef void * _buildLineString(self, object coordinates)
- cdef void * _buildLinearRing(self, object coordinates)
- cdef void * _buildPolygon(self, object coordinates)
- cdef void * _buildMultiPoint(self, object coordinates)
- cdef void * _buildMultiLineString(self, object coordinates)
- cdef void * _buildMultiPolygon(self, object coordinates)
- cdef void * _buildGeometryCollection(self, object coordinates)
- cdef void * build(self, object geom)
+ cdef void * _buildPoint(self, object coordinates) except NULL
+ cdef void * _buildLineString(self, object coordinates) except NULL
+ cdef void * _buildLinearRing(self, object coordinates) except NULL
+ cdef void * _buildPolygon(self, object coordinates) except NULL
+ cdef void * _buildMultiPoint(self, object coordinates) except NULL
+ cdef void * _buildMultiLineString(self, object coordinates) except NULL
+ cdef void * _buildMultiPolygon(self, object coordinates) except NULL
+ cdef void * _buildGeometryCollection(self, object coordinates) except NULL
+ cdef void * build(self, object geom) except NULL
diff --git a/fiona/collection.py b/fiona/collection.py
index 3478b7b..e284242 100644
--- a/fiona/collection.py
+++ b/fiona/collection.py
@@ -8,9 +8,10 @@ from fiona.ogrext import Iterator, ItemsIterator, KeysIterator
from fiona.ogrext import Session, WritingSession
from fiona.ogrext import (
calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name)
+from fiona.ogrext import buffer_to_virtual_file, remove_virtual_file
from fiona.errors import DriverError, SchemaError, CRSError
from fiona._drivers import driver_count, GDALEnv, supported_drivers
-from six import string_types
+from six import string_types, binary_type
class Collection(object):
@@ -406,6 +407,43 @@ class Collection(object):
self.__exit__(None, None, None)
+class BytesCollection(Collection):
+ """BytesCollection takes a buffer of bytes and maps that to
+ a virtual file that can then be opened by fiona.
+ """
+ def __init__(self, bytesbuf):
+ """Takes buffer of bytes whose contents is something we'd like
+ to open with Fiona and maps it to a virtual file.
+ """
+ if not isinstance(bytesbuf, binary_type):
+ raise ValueError("input buffer must be bytes")
+
+ # Hold a reference to the buffer, as bad things will happen if
+ # it is garbage collected while in use.
+ self.bytesbuf = bytesbuf
+
+ # Map the buffer to a file.
+ self.virtual_file = buffer_to_virtual_file(self.bytesbuf)
+
+ # Instantiate the parent class.
+ super(BytesCollection, self).__init__(self.virtual_file)
+
+ def close(self):
+ """Removes the virtual file associated with the class."""
+ super(BytesCollection, self).close()
+ if self.virtual_file:
+ remove_virtual_file(self.virtual_file)
+ self.virtual_file = None
+ self.bytesbuf = None
+
+ def __repr__(self):
+ return "<%s BytesCollection '%s', mode '%s' at %s>" % (
+ self.closed and "closed" or "open",
+ self.path + ":" + str(self.name),
+ self.mode,
+ hex(id(self)))
+
+
def vsi_path(path, vsi=None, archive=None):
# If a VSF and archive file are specified, we convert the path to
# an OGR VSI path (see cpl_vsi.h).
diff --git a/fiona/fio/bounds.py b/fiona/fio/bounds.py
index f35398f..2ba1074 100644
--- a/fiona/fio/bounds.py
+++ b/fiona/fio/bounds.py
@@ -6,11 +6,11 @@ import click
from cligj import precision_opt, use_rs_opt
import fiona
-from fiona.fio.cli import cli, obj_gen
+from .helpers import obj_gen
# Bounds command
- at cli.command(short_help="Print the extent of GeoJSON objects")
+ at click.command(short_help="Print the extent of GeoJSON objects")
@precision_opt
@click.option('--explode/--no-explode', default=False,
help="Explode collections into features (default: no).")
@@ -79,7 +79,6 @@ def bounds(ctx, precision, explode, with_id, with_obj, use_rs):
click.echo(u'\u001e', nl=False)
click.echo(json.dumps(rec))
- sys.exit(0)
except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
diff --git a/fiona/fio/cat.py b/fiona/fio/cat.py
index b97d1ba..a2ed042 100644
--- a/fiona/fio/cat.py
+++ b/fiona/fio/cat.py
@@ -1,15 +1,24 @@
from functools import partial
+import itertools
import json
import logging
import sys
+import warnings
import click
-from cligj import files_in_arg
-from cligj import precision_opt, indent_opt, compact_opt, use_rs_opt
+from cligj import (
+ compact_opt, files_in_arg, indent_opt,
+ sequence_opt, precision_opt, use_rs_opt)
import fiona
from fiona.transform import transform_geom
-from fiona.fio.cli import cli, obj_gen
+from .helpers import obj_gen
+from . import options
+
+
+FIELD_TYPES_MAP_REV = dict([(v, k) for k, v in fiona.FIELD_TYPES_MAP.items()])
+
+warnings.simplefilter('default')
def make_ld_context(context_items):
@@ -62,15 +71,14 @@ def id_record(rec):
# Cat command
- at cli.command(short_help="Concatenate and print the features of datasets")
+ at click.command(short_help="Concatenate and print the features of datasets")
@files_in_arg
@precision_opt
@indent_opt
@compact_opt
@click.option('--ignore-errors/--no-ignore-errors', default=False,
help="log errors but do not stop serialization.")
- at click.option('--dst_crs', default=None, metavar="EPSG:NNNN",
- help="Destination CRS.")
+ at options.dst_crs_opt
@use_rs_opt
@click.option('--bbox', default=None, metavar="w,s,e,n",
help="filter for features intersecting a bounding box")
@@ -109,14 +117,14 @@ def cat(ctx, files, precision, indent, compact, ignore_errors, dst_crs,
if use_rs:
click.echo(u'\u001e', nl=False)
click.echo(json.dumps(feat, **dump_kwds))
- sys.exit(0)
+
except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
# Collect command
- at cli.command(short_help="Collect a sequence of features.")
+ at click.command(short_help="Collect a sequence of features.")
@precision_opt
@indent_opt
@compact_opt
@@ -125,8 +133,7 @@ def cat(ctx, files, precision, indent, compact, ignore_errors, dst_crs,
"(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, metavar="EPSG:NNNN",
- help="Source CRS.")
+ at options.src_crs_opt
@click.option('--with-ld-context/--without-ld-context', default=False,
help="add a JSON-LD context to JSON output.")
@click.option('--add-ld-context-item', multiple=True,
@@ -285,14 +292,13 @@ def collect(ctx, precision, indent, compact, record_buffered, ignore_errors,
json.dump(collection, sink, **dump_kwds)
sink.write("\n")
- sys.exit(0)
except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
# Distribute command
- at cli.command(short_help="Distribute features from a collection")
+ at click.command(short_help="Distribute features from a collection")
@use_rs_opt
@click.pass_context
def distrib(ctx, use_rs):
@@ -314,13 +320,13 @@ def distrib(ctx, use_rs):
if use_rs:
click.echo(u'\u001e', nl=False)
click.echo(json.dumps(feat))
- sys.exit(0)
except Exception:
- raise
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
# Dump command
- at cli.command(short_help="Dump a dataset to GeoJSON.")
+ at click.command(short_help="Dump a dataset to GeoJSON.")
@click.argument('input', type=click.Path(), required=True)
@click.option('--encoding', help="Specify encoding of the input file.")
@precision_opt
@@ -475,7 +481,95 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
collection['features'] = [transformer(source.crs, rec) for rec in source]
json.dump(collection, sink, **dump_kwds)
- sys.exit(0)
except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
+
+
+# Load command.
+ at click.command(short_help="Load GeoJSON to a dataset in another format.")
+ at click.argument('output', type=click.Path(), required=True)
+ at click.option('-f', '--format', '--driver', required=True,
+ help="Output format driver name.")
+ at options.src_crs_opt
+ at click.option(
+ '--dst-crs', '--dst_crs',
+ help="Destination CRS. Defaults to --src-crs when not given.")
+ at click.option(
+ '--sequence / --no-sequence', default=False,
+ help="Specify whether the input stream is a LF-delimited sequence of GeoJSON "
+ "features (the default) or a single GeoJSON feature collection.")
+ at click.pass_context
+def load(ctx, output, driver, src_crs, dst_crs, sequence):
+ """Load features from JSON to a file in another format.
+
+ The input is a GeoJSON feature collection or optionally a sequence of
+ GeoJSON feature objects."""
+ verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+ logger = logging.getLogger('fio')
+ stdin = click.get_text_stream('stdin')
+
+ dst_crs = dst_crs or src_crs
+
+ if src_crs and dst_crs and src_crs != dst_crs:
+ transformer = partial(transform_geom, src_crs, dst_crs,
+ antimeridian_cutting=True, precision=-1)
+ else:
+ transformer = lambda x: x
+
+ first_line = next(stdin)
+
+ # If input is RS-delimited JSON sequence.
+ if first_line.startswith(u'\x1e'):
+ def feature_gen():
+ buffer = first_line.strip(u'\x1e')
+ for line in stdin:
+ if line.startswith(u'\x1e'):
+ if buffer:
+ feat = json.loads(buffer)
+ feat['geometry'] = transformer(feat['geometry'])
+ yield feat
+ buffer = line.strip(u'\x1e')
+ else:
+ buffer += line
+ else:
+ feat = json.loads(buffer)
+ feat['geometry'] = transformer(feat['geometry'])
+ yield feat
+ elif sequence:
+ def feature_gen():
+ yield json.loads(first_line)
+ for line in stdin:
+ feat = json.loads(line)
+ feat['geometry'] = transformer(feat['geometry'])
+ yield feat
+ else:
+ def feature_gen():
+ text = "".join(itertools.chain([first_line], stdin))
+ for feat in json.loads(text)['features']:
+ feat['geometry'] = transformer(feat['geometry'])
+ yield feat
+
+ try:
+ source = feature_gen()
+
+ # Use schema of first feature as a template.
+ # TODO: schema specified on command line?
+ first = next(source)
+ schema = {'geometry': first['geometry']['type']}
+ schema['properties'] = dict([
+ (k, FIELD_TYPES_MAP_REV.get(type(v)) or 'str')
+ for k, v in first['properties'].items()])
+
+ with fiona.drivers(CPL_DEBUG=verbosity>2):
+ with fiona.open(
+ output, 'w',
+ driver=driver,
+ crs=dst_crs,
+ schema=schema) as dst:
+ dst.write(first)
+ dst.writerecords(source)
+
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
diff --git a/fiona/fio/cli.py b/fiona/fio/cli.py
deleted file mode 100644
index bb120e8..0000000
--- a/fiona/fio/cli.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import json
-import logging
-import sys
-import warnings
-
-import click
-from cligj import verbose_opt, quiet_opt
-
-from fiona import __version__ as fio_version
-
-
-warnings.simplefilter('default')
-
-def configure_logging(verbosity):
- log_level = max(10, 30 - 10*verbosity)
- logging.basicConfig(stream=sys.stderr, level=log_level)
-
-
-def print_version(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
- click.echo(fio_version)
- ctx.exit()
-
-
-# The CLI command group.
- at click.group(help="Fiona command line interface.")
- at verbose_opt
- at quiet_opt
- at click.option('--version', is_flag=True, callback=print_version,
- expose_value=False, is_eager=True,
- help="Print Fiona version.")
- at click.pass_context
-def cli(ctx, verbose, quiet):
- verbosity = 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
deleted file mode 100644
index 989c16a..0000000
--- a/fiona/fio/fio.py
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/usr/bin/env python
-
-"""Fiona command line interface"""
-
-import code
-from functools import partial
-import itertools
-import json
-import logging
-import pprint
-import sys
-import warnings
-
-import click
-from cligj import indent_opt, sequence_opt
-import six.moves
-
-import fiona
-import fiona.crs
-from fiona.transform import transform_geom
-from fiona.fio.cli import cli
-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()])
-
-warnings.simplefilter('default')
-
-# Commands are below.
-
- at cli.command(short_help="Print information about the fio environment.")
- at click.option('--formats', 'key', flag_value='formats', default=True,
- help="Enumerate the available formats.")
- at click.pass_context
-def env(ctx, key):
- """Print information about the Fiona environment: available
- formats, etc.
- """
- verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
- logger = logging.getLogger('fio')
- stdout = click.get_text_stream('stdout')
- with fiona.drivers(CPL_DEBUG=(verbosity > 2)) as env:
- if key == 'formats':
- for k, v in sorted(fiona.supported_drivers.items()):
- modes = ', '.join("'" + m + "'" for m in v)
- stdout.write("%s (modes %s)\n" % (k, modes))
- stdout.write('\n')
-
-
-# Info command.
- at cli.command(short_help="Print information about a dataset.")
-# One or more files.
- at click.argument('input', type=click.Path(exists=True))
- at indent_opt
-# Options to pick out a single metadata item and print it as
-# a string.
- at click.option('--count', 'meta_member', flag_value='count',
- help="Print the count of features.")
- at click.option('-f', '--format', '--driver', 'meta_member', flag_value='driver',
- help="Print the format driver.")
- at click.option('--crs', 'meta_member', flag_value='crs',
- help="Print the CRS as a PROJ.4 string.")
- at click.option('--bounds', 'meta_member', flag_value='bounds',
- help="Print the boundary coordinates "
- "(left, bottom, right, top).")
- at click.pass_context
-def info(ctx, input, indent, meta_member):
- verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
- logger = logging.getLogger('rio')
- try:
- with fiona.drivers(CPL_DEBUG=verbosity>2):
- with fiona.open(input) as src:
- info = src.meta
- info.update(bounds=src.bounds, count=len(src))
- proj4 = fiona.crs.to_string(src.crs)
- if proj4.startswith('+init=epsg'):
- proj4 = proj4.split('=')[1].upper()
- info['crs'] = proj4
- if meta_member:
- if isinstance(info[meta_member], (list, tuple)):
- click.echo(" ".join(map(str, info[meta_member])))
- else:
- click.echo(info[meta_member])
- else:
- click.echo(json.dumps(info, indent=indent))
- sys.exit(0)
- except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
-
-# Insp command.
- at cli.command(short_help="Open a dataset and start an interpreter.")
- at click.argument('src_path', type=click.Path(exists=True))
- at click.pass_context
-def insp(ctx, src_path):
- verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
- logger = logging.getLogger('fio')
- try:
- with fiona.drivers(CPL_DEBUG=verbosity>2):
- with fiona.open(src_path) as src:
- code.interact(
- 'Fiona %s Interactive Inspector (Python %s)\n'
- 'Type "src.schema", "next(src)", or "help(src)" '
- 'for more information.' % (
- fiona.__version__, '.'.join(
- map(str, sys.version_info[:3]))),
- local=locals())
- sys.exit(0)
- except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
-
-
-# Load command.
- at cli.command(short_help="Load GeoJSON to a dataset in another format.")
- at click.argument('output', type=click.Path(), required=True)
- at click.option('-f', '--format', '--driver', required=True,
- help="Output format driver name.")
- at click.option('--src_crs', default=None, help="Source CRS.")
- at click.option('--dst_crs', default=None, help="Destination CRS.")
- at sequence_opt
- at click.pass_context
-
-def load(ctx, output, driver, src_crs, dst_crs, sequence):
- """Load features from JSON to a file in another format.
-
- The input is a GeoJSON feature collection or optionally a sequence of
- GeoJSON feature objects."""
- verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
- logger = logging.getLogger('fio')
- stdin = click.get_text_stream('stdin')
-
- if src_crs and dst_crs:
- transformer = partial(transform_geom, src_crs, dst_crs,
- antimeridian_cutting=True, precision=-1)
- else:
- transformer = lambda x: x
-
- first_line = next(stdin)
-
- # If input is RS-delimited JSON sequence.
- if first_line.startswith(u'\x1e'):
- def feature_gen():
- buffer = first_line.strip(u'\x1e')
- for line in stdin:
- if line.startswith(u'\x1e'):
- if buffer:
- feat = json.loads(buffer)
- feat['geometry'] = transformer(feat['geometry'])
- yield feat
- buffer = line.strip(u'\x1e')
- else:
- buffer += line
- else:
- feat = json.loads(buffer)
- feat['geometry'] = transformer(feat['geometry'])
- yield feat
- elif sequence:
- def feature_gen():
- yield json.loads(first_line)
- for line in stdin:
- feat = json.loads(line)
- feat['geometry'] = transformer(feat['geometry'])
- yield feat
- else:
- def feature_gen():
- text = "".join(itertools.chain([first_line], stdin))
- for feat in json.loads(text)['features']:
- feat['geometry'] = transformer(feat['geometry'])
- yield feat
-
- try:
- source = feature_gen()
-
- # Use schema of first feature as a template.
- # TODO: schema specified on command line?
- first = next(source)
- schema = {'geometry': first['geometry']['type']}
- schema['properties'] = dict([
- (k, FIELD_TYPES_MAP_REV.get(type(v)) or 'str')
- for k, v in first['properties'].items()])
-
- with fiona.drivers(CPL_DEBUG=verbosity>2):
- with fiona.open(
- output, 'w',
- driver=driver,
- crs=dst_crs,
- schema=schema) as dst:
- dst.write(first)
- dst.writerecords(source)
- sys.exit(0)
- except IOError:
- logger.info("IOError caught")
- sys.exit(0)
- except Exception:
- logger.exception("Failed. Exception caught")
- sys.exit(1)
-
-
-if __name__ == '__main__':
- cli()
diff --git a/fiona/fio/helpers.py b/fiona/fio/helpers.py
new file mode 100644
index 0000000..76f261b
--- /dev/null
+++ b/fiona/fio/helpers.py
@@ -0,0 +1,33 @@
+"""
+Helper objects needed by multiple CLI commands.
+"""
+
+
+import json
+import warnings
+
+
+warnings.simplefilter('default')
+
+
+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/info.py b/fiona/fio/info.py
new file mode 100644
index 0000000..96080f4
--- /dev/null
+++ b/fiona/fio/info.py
@@ -0,0 +1,99 @@
+"""
+Commands to get info about datasources and the Fiona environment
+"""
+
+
+import code
+import logging
+import json
+import sys
+
+import click
+from cligj import indent_opt
+
+import fiona
+import fiona.crs
+
+
+ at click.command(short_help="Print information about the fio environment.")
+ at click.option('--formats', 'key', flag_value='formats', default=True,
+ help="Enumerate the available formats.")
+ at click.pass_context
+def env(ctx, key):
+ """Print information about the Fiona environment: available
+ formats, etc.
+ """
+ verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
+ logger = logging.getLogger('fio')
+ stdout = click.get_text_stream('stdout')
+ with fiona.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+ if key == 'formats':
+ for k, v in sorted(fiona.supported_drivers.items()):
+ modes = ', '.join("'" + m + "'" for m in v)
+ stdout.write("%s (modes %s)\n" % (k, modes))
+ stdout.write('\n')
+
+
+# Info command.
+ at click.command(short_help="Print information about a dataset.")
+# One or more files.
+ at click.argument('input', type=click.Path(exists=True))
+ at indent_opt
+# Options to pick out a single metadata item and print it as
+# a string.
+ at click.option('--count', 'meta_member', flag_value='count',
+ help="Print the count of features.")
+ at click.option('-f', '--format', '--driver', 'meta_member', flag_value='driver',
+ help="Print the format driver.")
+ at click.option('--crs', 'meta_member', flag_value='crs',
+ help="Print the CRS as a PROJ.4 string.")
+ at click.option('--bounds', 'meta_member', flag_value='bounds',
+ help="Print the boundary coordinates "
+ "(left, bottom, right, top).")
+ at click.pass_context
+def info(ctx, input, indent, meta_member):
+ verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+ logger = logging.getLogger('rio')
+ try:
+ with fiona.drivers(CPL_DEBUG=verbosity>2):
+ with fiona.open(input) as src:
+ info = src.meta
+ info.update(bounds=src.bounds, count=len(src))
+ proj4 = fiona.crs.to_string(src.crs)
+ if proj4.startswith('+init=epsg'):
+ proj4 = proj4.split('=')[1].upper()
+ info['crs'] = proj4
+ if meta_member:
+ if isinstance(info[meta_member], (list, tuple)):
+ click.echo(" ".join(map(str, info[meta_member])))
+ else:
+ click.echo(info[meta_member])
+ else:
+ click.echo(json.dumps(info, indent=indent))
+
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
+
+
+# Insp command.
+ at click.command(short_help="Open a dataset and start an interpreter.")
+ at click.argument('src_path', type=click.Path(exists=True))
+ at click.pass_context
+def insp(ctx, src_path):
+ verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
+ logger = logging.getLogger('fio')
+ try:
+ with fiona.drivers(CPL_DEBUG=verbosity>2):
+ with fiona.open(src_path) as src:
+ code.interact(
+ 'Fiona %s Interactive Inspector (Python %s)\n'
+ 'Type "src.schema", "next(src)", or "help(src)" '
+ 'for more information.' % (
+ fiona.__version__, '.'.join(
+ map(str, sys.version_info[:3]))),
+ local=locals())
+
+ except Exception:
+ logger.exception("Exception caught during processing")
+ raise click.Abort()
diff --git a/fiona/fio/main.py b/fiona/fio/main.py
new file mode 100644
index 0000000..758ff42
--- /dev/null
+++ b/fiona/fio/main.py
@@ -0,0 +1,36 @@
+"""
+Main click group for the CLI. Needs to be isolated for entry-point loading.
+"""
+
+
+import logging
+from pkg_resources import iter_entry_points
+import warnings
+import sys
+
+import click
+from cligj import verbose_opt, quiet_opt
+import cligj.plugins
+
+from fiona import __version__ as fio_version
+
+
+def configure_logging(verbosity):
+ log_level = max(10, 30 - 10*verbosity)
+ logging.basicConfig(stream=sys.stderr, level=log_level)
+
+
+ at cligj.plugins.group(plugins=(
+ ep for ep in list(iter_entry_points('fiona.fio_commands')) +
+ list(iter_entry_points('fiona.fio_plugins'))))
+ at verbose_opt
+ at quiet_opt
+ at click.version_option(fio_version)
+ at click.pass_context
+def main_group(ctx, verbose, quiet):
+
+ """Fiona command line interface."""
+
+ verbosity = verbose - quiet
+ configure_logging(verbosity)
+ ctx.obj = {'verbosity': verbosity}
diff --git a/fiona/fio/options.py b/fiona/fio/options.py
new file mode 100644
index 0000000..47fb1ae
--- /dev/null
+++ b/fiona/fio/options.py
@@ -0,0 +1,8 @@
+"""Common commandline options for `fio`"""
+
+
+import click
+
+
+src_crs_opt = click.option('--src-crs', '--src_crs', help="Source CRS.")
+dst_crs_opt = click.option('--dst-crs', '--dst_crs', help="Destination CRS.")
diff --git a/fiona/inspector.py b/fiona/inspector.py
index e5e44c1..669f866 100644
--- a/fiona/inspector.py
+++ b/fiona/inspector.py
@@ -12,14 +12,15 @@ logger = logging.getLogger('fiona.inspector')
def main(srcfile):
- with fiona.drivers(), fiona.open(srcfile) as src:
+ with fiona.drivers():
+ with fiona.open(srcfile) as src:
- code.interact(
- 'Fiona %s Interactive Inspector (Python %s)\n'
- 'Type "src.schema", "next(src)", or "help(src)" '
- 'for more information.' % (
- fiona.__version__, '.'.join(map(str, sys.version_info[:3]))),
- local=locals())
+ code.interact(
+ 'Fiona %s Interactive Inspector (Python %s)\n'
+ 'Type "src.schema", "next(src)", or "help(src)" '
+ 'for more information.' % (
+ fiona.__version__, '.'.join(map(str, sys.version_info[:3]))),
+ local=locals())
return 1
diff --git a/fiona/ograpi.pxd b/fiona/ograpi.pxd
index de1abfe..c84ca7d 100644
--- a/fiona/ograpi.pxd
+++ b/fiona/ograpi.pxd
@@ -18,6 +18,16 @@ cdef extern from "cpl_string.h":
char ** CSLSetNameValue (char **list, char *name, char *value)
void CSLDestroy (char **list)
+cdef extern from "cpl_vsi.h":
+ ctypedef struct VSILFILE:
+ pass
+ int VSIFCloseL (VSILFILE *)
+ VSILFILE * VSIFileFromMemBuffer (const char * filename,
+ unsigned char * data,
+ int data_len,
+ int take_ownership)
+ int VSIUnlink (const char * pathname)
+
ctypedef int OGRErr
ctypedef struct OGREnvelope:
double MinX
diff --git a/fiona/ogrext.pyx b/fiona/ogrext.pyx
index fb125c4..3434553 100644
--- a/fiona/ogrext.pyx
+++ b/fiona/ogrext.pyx
@@ -7,6 +7,7 @@ import os
import sys
import warnings
import math
+import uuid
from six import integer_types, string_types, text_type
@@ -1165,3 +1166,22 @@ def _listlayers(path):
return layer_names
+def buffer_to_virtual_file(bytesbuf):
+ """Maps a bytes buffer to a virtual file.
+ """
+ vsi_filename = os.path.join('/vsimem', uuid.uuid4().hex)
+ vsi_cfilename = vsi_filename if not isinstance(vsi_filename, string_types) else vsi_filename.encode('utf-8')
+
+ vsi_handle = ograpi.VSIFileFromMemBuffer(vsi_cfilename, bytesbuf, len(bytesbuf), 0)
+ if vsi_handle == NULL:
+ raise OSError('failed to map buffer to file')
+ if ograpi.VSIFCloseL(vsi_handle) != 0:
+ raise OSError('failed to close mapped file handle')
+
+ return vsi_filename
+
+def remove_virtual_file(vsi_filename):
+ vsi_cfilename = vsi_filename if not isinstance(vsi_filename, string_types) else vsi_filename.encode('utf-8')
+ return ograpi.VSIUnlink(vsi_cfilename)
+
+
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 2a82b05..3012d81 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,4 +1,4 @@
-cython==0.21.2
+cython>=0.21.2
nose
pytest
setuptools
diff --git a/setup.py b/setup.py
index 02950da..0deb1fd 100644
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,26 @@ log = logging.getLogger()
if 'all' in sys.warnoptions:
log.level = logging.DEBUG
+def check_output(cmd):
+ # since subprocess.check_output doesn't exist in 2.6
+ # we wrap it here.
+ try:
+ out = subprocess.check_output(cmd)
+ return out.decode('utf')
+ except AttributeError:
+ # For some reasone check_output doesn't exist
+ # So fall back on Popen
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ out, err = p.communicate()
+ return out
+
+def copy_data_tree(datadir, destdir):
+ try:
+ shutil.rmtree(destdir)
+ except OSError:
+ pass
+ shutil.copytree(datadir, destdir)
+
# Parse the version from the fiona module.
with open('fiona/__init__.py', 'r') as f:
for line in f:
@@ -52,21 +72,17 @@ include_dirs = []
library_dirs = []
libraries = []
extra_link_args = []
+gdal_output = [None]*3
try:
gdal_config = os.environ.get('GDAL_CONFIG', 'gdal-config')
- with open("gdal-config.txt", "w") as gcfg:
- subprocess.call([gdal_config, "--cflags"], stdout=gcfg)
- subprocess.call([gdal_config, "--libs"], stdout=gcfg)
- subprocess.call([gdal_config, "--datadir"], stdout=gcfg)
- with open("gdal-config.txt", "r") as gcfg:
- cflags = gcfg.readline().strip()
- libs = gcfg.readline().strip()
- datadir = gcfg.readline().strip()
- for item in cflags.split():
+ for i, flag in enumerate(("--cflags", "--libs", "--datadir")):
+ gdal_output[i] = check_output([gdal_config, flag]).strip()
+
+ for item in gdal_output[0].split():
if item.startswith("-I"):
include_dirs.extend(item[2:].split(":"))
- for item in libs.split():
+ for item in gdal_output[1].split():
if item.startswith("-L"):
library_dirs.extend(item[2:].split(":"))
elif item.startswith("-l"):
@@ -75,6 +91,13 @@ try:
# e.g. -framework GDAL
extra_link_args.append(item)
+except Exception as e:
+ if os.name == "nt":
+ log.info(("Building on Windows requires extra options to setup.py to locate needed GDAL files.\n"
+ "More information is available in the README."))
+ else:
+ log.warning("Failed to get options via gdal-config: %s", str(e))
+
# Conditionally copy the GDAL data. To be used in conjunction with
# the bdist_wheel command to make self-contained binary wheels.
if os.environ.get('PACKAGE_DATA'):
@@ -83,19 +106,23 @@ try:
except OSError:
pass
shutil.copytree(datadir, 'fiona/gdal_data')
-
-except Exception as e:
- log.warning("Failed to get options via gdal-config: %s", str(e))
-
-# Conditionally copy PROJ.4 data.
if os.environ.get('PACKAGE_DATA'):
+ destdir = 'fiona/gdal_data'
+ if gdal_output[2]:
+ log.info("Copying gdal data from %s" % gdal_output[2])
+ copy_data_tree(gdal_output[2], destdir)
+ else:
+ # check to see if GDAL_DATA is defined
+ gdal_data = os.environ.get('GDAL_DATA', None)
+ if gdal_data:
+ log.info("Copying gdal data from %s" % gdal_data)
+ copy_data_tree(gdal_data, destdir)
+
+ # Conditionally copy PROJ.4 data.
projdatadir = os.environ.get('PROJ_LIB', '/usr/local/share/proj')
if os.path.exists(projdatadir):
- try:
- shutil.rmtree('fiona/proj_data')
- except OSError:
- pass
- shutil.copytree(projdatadir, 'fiona/proj_data')
+ log.info("Copying proj data from %s" % projdatadir)
+ copy_data_tree(projdatadir, 'finoa/proj_data')
ext_options = dict(
include_dirs=include_dirs,
@@ -150,7 +177,18 @@ setup_args = dict(
packages=['fiona', 'fiona.fio'],
entry_points='''
[console_scripts]
- fio=fiona.fio.fio:cli
+ fio=fiona.fio.main:main_group
+
+ [fiona.fio_commands]
+ bounds=fiona.fio.bounds:bounds
+ cat=fiona.fio.cat:cat
+ collect=fiona.fio.cat:collect
+ distrib=fiona.fio.cat:distrib
+ dump=fiona.fio.cat:dump
+ env=fiona.fio.info:env
+ info=fiona.fio.info:info
+ insp=fiona.fio.info:insp
+ load=fiona.fio.cat:load
''',
install_requires=requirements,
tests_require=['nose'],
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 1a38cab..2b6722d 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -4,7 +4,14 @@ import os.path
def read_file(name):
return open(os.path.join(os.path.dirname(__file__), name)).read()
+# GeoJSON feature collection on a single line
feature_collection = read_file('data/collection.txt')
+
+# Same as above but with pretty-print styling applied
feature_collection_pp = read_file('data/collection-pp.txt')
+
+# One feature per line
feature_seq = read_file('data/sequence.txt')
+
+# Same as above but each feature has pretty-print styling
feature_seq_pp_rs = read_file('data/sequence-pp.txt')
diff --git a/tests/test_bytescollection.py b/tests/test_bytescollection.py
new file mode 100644
index 0000000..e9eaccf
--- /dev/null
+++ b/tests/test_bytescollection.py
@@ -0,0 +1,212 @@
+# Testing BytesCollection
+import unittest
+
+import six
+
+import fiona
+
+class ReadingTest(unittest.TestCase):
+
+ def setUp(self):
+ with open('tests/data/coutwildrnp.json') as src:
+ bytesbuf = src.read().encode('utf-8')
+ self.c = fiona.BytesCollection(bytesbuf)
+
+ def tearDown(self):
+ self.c.close()
+
+ @unittest.skipIf(six.PY2, 'string are bytes in Python 2')
+ def test_construct_with_str(self):
+ with open('tests/data/coutwildrnp.json') as src:
+ strbuf = src.read()
+ self.assertRaises(ValueError, fiona.BytesCollection, strbuf)
+
+ def test_open_repr(self):
+ # I'm skipping checking the name of the virtual file as it produced by uuid.
+ self.failUnless(
+ repr(self.c).startswith("<open BytesCollection '/vsimem/") and
+ repr(self.c).endswith(":OGRGeoJSON', mode 'r' at %s>" % hex(id(self.c))))
+
+ def test_closed_repr(self):
+ # I'm skipping checking the name of the virtual file as it produced by uuid.
+ self.c.close()
+ self.failUnless(
+ repr(self.c).startswith("<closed BytesCollection '/vsimem/") and
+ repr(self.c).endswith(":OGRGeoJSON', mode 'r' at %s>" % hex(id(self.c))))
+
+ def test_path(self):
+ self.failUnlessEqual(self.c.path, self.c.virtual_file)
+
+ def test_closed_virtual_file(self):
+ self.c.close()
+ self.failUnless(self.c.virtual_file is None)
+
+ def test_closed_buf(self):
+ self.c.close()
+ self.failUnless(self.c.bytesbuf is None)
+
+ def test_name(self):
+ self.failUnlessEqual(self.c.name, 'OGRGeoJSON')
+
+ def test_mode(self):
+ self.failUnlessEqual(self.c.mode, 'r')
+
+ def test_collection(self):
+ self.failUnlessEqual(self.c.encoding, 'utf-8')
+
+ def test_iter(self):
+ self.failUnless(iter(self.c))
+
+ def test_closed_no_iter(self):
+ self.c.close()
+ self.assertRaises(ValueError, iter, self.c)
+
+ def test_len(self):
+ self.failUnlessEqual(len(self.c), 67)
+
+ def test_closed_len(self):
+ # Len is lazy, it's never computed in this case. TODO?
+ self.c.close()
+ self.failUnlessEqual(len(self.c), 0)
+
+ def test_len_closed_len(self):
+ # Lazy len is computed in this case and sticks.
+ len(self.c)
+ self.c.close()
+ self.failUnlessEqual(len(self.c), 67)
+
+ def test_driver(self):
+ self.failUnlessEqual(self.c.driver, "GeoJSON")
+
+ def test_closed_driver(self):
+ self.c.close()
+ self.failUnlessEqual(self.c.driver, None)
+
+ def test_driver_closed_driver(self):
+ self.c.driver
+ self.c.close()
+ self.failUnlessEqual(self.c.driver, "GeoJSON")
+
+ def test_schema(self):
+ s = self.c.schema['properties']
+ self.failUnlessEqual(s['PERIMETER'], "float")
+ self.failUnlessEqual(s['NAME'], "str")
+ self.failUnlessEqual(s['URL'], "str")
+ self.failUnlessEqual(s['STATE_FIPS'], "str")
+ self.failUnlessEqual(s['WILDRNP020'], "int")
+
+ def test_closed_schema(self):
+ # Schema is lazy too, never computed in this case. TODO?
+ self.c.close()
+ self.failUnlessEqual(self.c.schema, None)
+
+ def test_schema_closed_schema(self):
+ self.c.schema
+ self.c.close()
+ self.failUnlessEqual(
+ sorted(self.c.schema.keys()),
+ ['geometry', 'properties'])
+
+ def test_crs(self):
+ crs = self.c.crs
+ self.failUnlessEqual(crs['init'], 'epsg:4326')
+
+ def test_crs_wkt(self):
+ crs = self.c.crs_wkt
+ self.failUnless(crs.startswith('GEOGCS["WGS 84"'))
+
+ def test_closed_crs(self):
+ # Crs is lazy too, never computed in this case. TODO?
+ self.c.close()
+ self.failUnlessEqual(self.c.crs, None)
+
+ def test_crs_closed_crs(self):
+ self.c.crs
+ self.c.close()
+ self.failUnlessEqual(
+ sorted(self.c.crs.keys()),
+ ['init'])
+
+ def test_meta(self):
+ self.failUnlessEqual(
+ sorted(self.c.meta.keys()),
+ ['crs', 'driver', 'schema'])
+
+ def test_bounds(self):
+ self.failUnlessAlmostEqual(self.c.bounds[0], -113.564247, 6)
+ self.failUnlessAlmostEqual(self.c.bounds[1], 37.068981, 6)
+ self.failUnlessAlmostEqual(self.c.bounds[2], -104.970871, 6)
+ self.failUnlessAlmostEqual(self.c.bounds[3], 41.996277, 6)
+
+ def test_iter_one(self):
+ itr = iter(self.c)
+ f = next(itr)
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_iter_list(self):
+ f = list(self.c)[0]
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_re_iter_list(self):
+ f = list(self.c)[0] # Run through iterator
+ f = list(self.c)[0] # Run through a new, reset iterator
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_getitem_one(self):
+ f = self.c[0]
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_no_write(self):
+ self.assertRaises(IOError, self.c.write, {})
+
+ def test_iter_items_list(self):
+ i, f = list(self.c.items())[0]
+ self.failUnlessEqual(i, 0)
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_iter_keys_list(self):
+ i = list(self.c.keys())[0]
+ self.failUnlessEqual(i, 0)
+
+ def test_in_keys(self):
+ self.failUnless(0 in self.c.keys())
+ self.failUnless(0 in self.c)
+
+class FilterReadingTest(unittest.TestCase):
+
+ def setUp(self):
+ with open('tests/data/coutwildrnp.json') as src:
+ bytesbuf = src.read().encode('utf-8')
+ self.c = fiona.BytesCollection(bytesbuf)
+
+ def tearDown(self):
+ self.c.close()
+
+ def test_filter_1(self):
+ results = list(self.c.filter(bbox=(-120.0, 30.0, -100.0, 50.0)))
+ self.failUnlessEqual(len(results), 67)
+ f = results[0]
+ self.failUnlessEqual(f['id'], "0")
+ self.failUnlessEqual(f['properties']['STATE'], 'UT')
+
+ def test_filter_reset(self):
+ results = list(self.c.filter(bbox=(-112.0, 38.0, -106.0, 40.0)))
+ self.failUnlessEqual(len(results), 26)
+ results = list(self.c.filter())
+ self.failUnlessEqual(len(results), 67)
+
+ def test_filter_mask(self):
+ mask = {
+ 'type': 'Polygon',
+ 'coordinates': (
+ ((-112, 38), (-112, 40), (-106, 40), (-106, 38), (-112, 38)),)}
+ results = list(self.c.filter(mask=mask))
+ self.failUnlessEqual(len(results), 26)
+
+
+
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 0a9bfb1..ab659f1 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,10 +1,9 @@
-import json
+from pkg_resources import iter_entry_points
import re
-import click
from click.testing import CliRunner
-from fiona.fio import fio
+from fiona.fio.main import main_group
WILDSHP = 'tests/data/coutwildrnp.shp'
@@ -12,7 +11,7 @@ WILDSHP = 'tests/data/coutwildrnp.shp'
def test_info_json():
runner = CliRunner()
- result = runner.invoke(fio.info, [WILDSHP])
+ result = runner.invoke(main_group, ['info', WILDSHP])
assert result.exit_code == 0
assert '"count": 67' in result.output
assert '"crs": "EPSG:4326"' in result.output
@@ -21,13 +20,19 @@ def test_info_json():
def test_info_count():
runner = CliRunner()
- result = runner.invoke(fio.info, ['--count', WILDSHP])
+ result = runner.invoke(main_group, ['info', '--count', WILDSHP])
assert result.exit_code == 0
assert result.output == "67\n"
def test_info_bounds():
runner = CliRunner()
- result = runner.invoke(fio.info, ['--bounds', WILDSHP])
+ result = runner.invoke(main_group, ['info', '--bounds', WILDSHP])
assert result.exit_code == 0
assert len(re.findall(r'\d*\.\d*', result.output)) == 4
+
+
+def test_all_registered():
+ # Make sure all the subcommands are actually registered to the main CLI group
+ for ep in iter_entry_points('fiona.fio_commands'):
+ assert ep.name in main_group.commands
diff --git a/tests/test_fio_cat.py b/tests/test_fio_cat.py
index 7ba5b4e..94f5d1f 100644
--- a/tests/test_fio_cat.py
+++ b/tests/test_fio_cat.py
@@ -60,7 +60,7 @@ def test_collect_rs():
runner = CliRunner()
result = runner.invoke(
cat.collect,
- ['--src_crs', 'EPSG:3857'],
+ ['--src-crs', 'EPSG:3857'],
feature_seq_pp_rs,
catch_exceptions=False)
assert result.exit_code == 0
@@ -71,7 +71,7 @@ def test_collect_no_rs():
runner = CliRunner()
result = runner.invoke(
cat.collect,
- ['--src_crs', 'EPSG:3857'],
+ ['--src-crs', 'EPSG:3857'],
feature_seq,
catch_exceptions=False)
assert result.exit_code == 0
diff --git a/tests/test_fio_load.py b/tests/test_fio_load.py
index 5702576..cfe254f 100644
--- a/tests/test_fio_load.py
+++ b/tests/test_fio_load.py
@@ -1,21 +1,19 @@
-import json
import os
import tempfile
-import click
from click.testing import CliRunner
import fiona
-from fiona.fio import fio
+from fiona.fio.main import main_group
from .fixtures import (
- feature_collection, feature_collection_pp, feature_seq, feature_seq_pp_rs)
+ feature_collection, feature_seq, feature_seq_pp_rs)
def test_err():
runner = CliRunner()
result = runner.invoke(
- fio.load, [], '', catch_exceptions=False)
+ main_group, ['load'], '', catch_exceptions=False)
assert result.exit_code == 2
@@ -27,7 +25,7 @@ def test_exception(tmpdir=None):
tmpfile = str(tmpdir.join('test.shp'))
runner = CliRunner()
result = runner.invoke(
- fio.load, ['-f', 'Shapefile', tmpfile], '42', catch_exceptions=False)
+ main_group, ['load', '-f', 'Shapefile', tmpfile], '42', catch_exceptions=False)
assert result.exit_code == 1
@@ -39,7 +37,7 @@ def test_collection(tmpdir=None):
tmpfile = str(tmpdir.join('test.shp'))
runner = CliRunner()
result = runner.invoke(
- fio.load, ['-f', 'Shapefile', tmpfile], feature_collection)
+ main_group, ['load', '-f', 'Shapefile', tmpfile], feature_collection)
assert result.exit_code == 0
assert len(fiona.open(tmpfile)) == 2
@@ -52,7 +50,7 @@ def test_seq_rs(tmpdir=None):
tmpfile = str(tmpdir.join('test.shp'))
runner = CliRunner()
result = runner.invoke(
- fio.load, ['-f', 'Shapefile', tmpfile], feature_seq_pp_rs)
+ main_group, ['load', '-f', 'Shapefile', tmpfile], feature_seq_pp_rs)
assert result.exit_code == 0
assert len(fiona.open(tmpfile)) == 2
@@ -65,6 +63,59 @@ def test_seq_no_rs(tmpdir=None):
tmpfile = str(tmpdir.join('test.shp'))
runner = CliRunner()
result = runner.invoke(
- fio.load, ['-f', 'Shapefile', '--sequence', tmpfile], feature_seq)
+ main_group, ['load', '-f', 'Shapefile', '--sequence', tmpfile], feature_seq)
assert result.exit_code == 0
assert len(fiona.open(tmpfile)) == 2
+
+
+def test_dst_crs_default_to_src_crs(tmpdir=None):
+ # When --dst-crs is not given default to --src-crs.
+ if tmpdir is None:
+ tmpdir = tempfile.mkdtemp()
+ tmpfile = os.path.join(tmpdir, 'test.shp')
+ else:
+ tmpfile = str(tmpdir.join('test.shp'))
+ runner = CliRunner()
+ result = runner.invoke(
+ main_group, [
+ 'load', '--src-crs', 'EPSG:32617', '-f', 'Shapefile', '--sequence', tmpfile
+ ], feature_seq)
+ assert result.exit_code == 0
+ with fiona.open(tmpfile) as src:
+ assert src.crs == {'init': 'epsg:32617'}
+ assert len(src) == len(feature_seq.splitlines())
+
+
+def test_different_crs(tmpdir=None):
+ if tmpdir is None:
+ tmpdir = tempfile.mkdtemp()
+ tmpfile = os.path.join(tmpdir, 'test.shp')
+ else:
+ tmpfile = str(tmpdir.join('test.shp'))
+ runner = CliRunner()
+ result = runner.invoke(
+ main_group, [
+ 'load', '--src-crs', 'EPSG:32617', '--dst-crs', 'EPSG:32610',
+ '-f', 'Shapefile', '--sequence', tmpfile
+ ], feature_seq)
+ assert result.exit_code == 0
+ with fiona.open(tmpfile) as src:
+ assert src.crs == {'init': 'epsg:32610'}
+ assert len(src) == len(feature_seq.splitlines())
+
+
+def test_dst_crs_no_src(tmpdir=None):
+ if tmpdir is None:
+ tmpdir = tempfile.mkdtemp()
+ tmpfile = os.path.join(tmpdir, 'test.shp')
+ else:
+ tmpfile = str(tmpdir.join('test.shp'))
+ runner = CliRunner()
+ result = runner.invoke(
+ main_group, [
+ 'load', '--dst-crs', 'EPSG:32610', '-f', 'Shapefile', '--sequence', tmpfile
+ ], feature_seq)
+ assert result.exit_code == 0
+ with fiona.open(tmpfile) as src:
+ assert src.crs == {'init': 'epsg:32610'}
+ assert len(src) == len(feature_seq.splitlines())
--
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