[Git][debian-gis-team/fiona][upstream] New upstream version 1.9~b2
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Sun Jan 22 19:31:19 GMT 2023
Bas Couwenberg pushed to branch upstream at Debian GIS Project / fiona
Commits:
216cbde5 by Bas Couwenberg at 2023-01-22T19:52:19+01:00
New upstream version 1.9~b2
- - - - -
26 changed files:
- CHANGES.txt
- README.rst
- docs/index.rst
- + docs/install.rst
- docs/manual.rst
- fiona/__init__.py
- fiona/_geometry.pyx
- fiona/fio/bounds.py
- fiona/fio/calc.py
- fiona/fio/cat.py
- fiona/fio/collect.py
- fiona/fio/distrib.py
- fiona/fio/dump.py
- fiona/fio/filter.py
- fiona/fio/info.py
- fiona/fio/insp.py
- fiona/fio/load.py
- fiona/model.py
- fiona/ogrext.pyx
- fiona/transform.py
- tests/test_collection.py
- tests/test_feature.py
- tests/test_fio_calc.py
- tests/test_geometry.py
- tests/test_model.py
- tests/test_transform.py
Changes:
=====================================
CHANGES.txt
=====================================
@@ -3,6 +3,24 @@ Changes
All issue numbers are relative to https://github.com/Toblerity/Fiona/issues.
+1.9b2 (2023-01-22)
+------------------
+
+- Add Feature.__geo_interface__ property (#1181).
+- Invalid creation options are filtered and ignored (#1180).
+- The readme doc has been shortened and freshened up, with a modern example for
+ version 1.9.0 (#1174).
+- The Geometry class now provides and looks for __geo_interface__ (#1174).
+- The top level fiona module now exports Feature, Geometry, and Properties
+ (#1174).
+- Functions that take Feature or Geometry objects will continue to take dicts
+ or objects that provide __geo_interface__ (#1177). This reverses the
+ deprecation introduced in 1.9a2.
+- Python ignores SIGPIPE by default. By never catching BrokenPipeError via
+ `except Exception` when, for example, piping the output of rio-shapes to
+ the Unix head program, we avoid getting an unhandled BrokenPipeError message
+ when the interpreter shuts down (#2689).
+
1.9b1 (2022-12-13)
------------------
=====================================
README.rst
=====================================
@@ -2,339 +2,115 @@
Fiona
=====
-Fiona is GDAL_'s neat and nimble vector API for Python programmers.
-
.. image:: https://github.com/Toblerity/Fiona/workflows/Linux%20CI/badge.svg?branch=maint-1.9
:target: https://github.com/Toblerity/Fiona/actions?query=branch%3Amaint-1.9
-.. image:: https://ci.appveyor.com/api/projects/status/github/Toblerity/Fiona?svg=true
- :target: https://ci.appveyor.com/project/sgillies/fiona/branch/maint-1.9
-
-.. image:: https://coveralls.io/repos/Toblerity/Fiona/badge.svg
- :target: https://coveralls.io/r/Toblerity/Fiona
+Fiona streams simple feature data to and from GIS formats like GeoPackage and
+Shapefile.
-Fiona is designed to be simple and dependable. It focuses on reading and
-writing data in standard Python IO style and relies upon familiar Python types
-and protocols such as files, dictionaries, mappings, and iterators instead of
-classes specific to OGR. Fiona can read and write real-world data using
-multi-layered GIS formats and zipped virtual file systems and integrates
-readily with other Python GIS packages such as pyproj_, Rtree_, and Shapely_.
+Fiona can read and write real-world data using multi-layered GIS formats,
+zipped and in-memory virtual file systems, from files on your hard drive or in
+cloud storage. This project includes Python modules and a command line
+interface (CLI).
-Fiona is supported only on CPython versions 3.6+.
+Fiona depends on `GDAL <https://gdal.org>`__ but is different from GDAL's own
+`bindings <https://gdal.org/api/python_bindings.html>`__. Fiona is designed to
+be highly productive and to make it easy to write code which is easy to read.
-Why the name "Fiona"? Because Fiona Is OGR's Neat and Nimble API for Python programmers. And a Shrek reference made us laugh.
+Installation
+============
-For more details, see:
+Fiona has several `extension modules
+<https://docs.python.org/3/extending/extending.html>`__ which link against
+libgdal. This complicates installation. Binary distributions (wheels)
+containing libgdal and its own dependencies are available from the Python
+Package Index and can be installed using pip.
-* Fiona `home page <https://github.com/Toblerity/Fiona>`__
-* `Docs and manual <https://fiona.readthedocs.io/>`__
-* `Examples <https://github.com/Toblerity/Fiona/tree/master/examples>`__
-* Main `user discussion group <https://fiona.groups.io/g/main>`__
-* `Developers discussion group <https://fiona.groups.io/g/dev>`__
+.. code-block:: console
-Usage
-=====
+ pip install fiona
-Collections
------------
+These wheels are mainly intended to make installation easy for simple
+applications, not so much for production. They are not tested for compatibility
+with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's
+optional format drivers. If you need, for example, GML support you will need to
+build and install Fiona from a source distribution.
-Records are read from and written to ``file``-like `Collection` objects
-returned from the ``fiona.open()`` function. Records are mappings modeled on
-the GeoJSON format. They don't have any spatial methods of their own, so if you
-want to do anything fancy with them you will probably need Shapely or something
-like it. Here is an example of using Fiona to read some records from one data
-file, change their geometry attributes, and write them to a new data file.
+Many users find Anaconda and conda-forge a good way to install Fiona and get
+access to more optional format drivers (like GML).
-.. code-block:: python
+Fiona 1.9 (coming soon) requires Python 3.7 or higher and GDAL 3.2 or higher.
- import fiona
+Python Usage
+============
- # Open a file for reading. We'll call this the "source."
+Features are read from and written to file-like ``Collection`` objects
+returned from the ``fiona.open()`` function. Features are data classes modeled
+on the GeoJSON format. They don't have any spatial methods of their own, so if
+you want to do anything fancy with them you will need Shapely or something like
+it. Here is an example of using Fiona to read some features from one data file,
+change their geometry attributes using Shapely, and write them to a new data
+file.
- with fiona.open('tests/data/coutwildrnp.shp') as src:
+.. code-block:: python
- # The file we'll write to, the "destination", must be initialized
- # with a coordinate system, a format driver name, and
- # a record schema. We can get initial values from the open
- # collection's ``meta`` property and then modify them as
- # desired.
+ import fiona
+ from fiona import Feature, Geometry
+ from shapely.geometry import mapping, shape
- meta = src.meta
- meta['schema']['geometry'] = 'Point'
+ # Open a file for reading. We'll call this the source.
+ with fiona.open("tests/data/coutwildrnp.shp") as src:
- # Open an output file, using the same format driver and
- # coordinate reference system as the source. The ``meta``
- # mapping fills in the keyword parameters of fiona.open().
+ # The file we'll write to must be initialized with a coordinate
+ # system, a format driver name, and a record schema. We can get
+ # initial values from the open source's profile property and then
+ # modify them as we need.
+ profile = src.profile
+ profile["schema"]["geometry"] = "Point"
+ profile["driver"] = "GPKG"
- with fiona.open('test_write.shp', 'w', **meta) as dst:
+ # Open an output file, using the same format driver and coordinate
+ # reference system as the source. The profile mapping fills in the
+ # keyword parameters of fiona.open.
+ with fiona.open("/tmp/example.gpkg", "w", **profile) as dst:
# Process only the records intersecting a box.
for f in src.filter(bbox=(-107.0, 37.0, -105.0, 39.0)):
- # Get a point on the boundary of the record's
- # geometry.
-
- f['geometry'] = {
- 'type': 'Point',
- 'coordinates': f['geometry']['coordinates'][0][0]}
-
- # Write the record out.
-
- dst.write(f)
-
- # The destination's contents are flushed to disk and the file is
- # closed when its ``with`` block ends. This effectively
- # executes ``dst.flush(); dst.close()``.
-
-Reading Multilayer data
------------------------
-
-Collections can also be made from single layers within multilayer files or
-directories of data. The target layer is specified by name or by its integer
-index within the file or directory. The ``fiona.listlayers()`` function
-provides an index ordered list of layer names.
-
-.. code-block:: python
+ # Get the feature's centroid.
+ centroid_shp = shape(f.geometry).centroid
+ new_geom = Geometry.from_dict(centroid_shp)
- for layername in fiona.listlayers('tests/data'):
- with fiona.open('tests/data', layer=layername) as src:
- print(layername, len(src))
-
- # Output:
- # ('coutwildrnp', 67)
-
-Layer can also be specified by index. In this case, ``layer=0`` and
-``layer='test_uk'`` specify the same layer in the data file or directory.
-
-.. code-block:: python
-
- for i, layername in enumerate(fiona.listlayers('tests/data')):
- with fiona.open('tests/data', layer=i) as src:
- print(i, layername, len(src))
-
- # Output:
- # (0, 'coutwildrnp', 67)
-
-Writing Multilayer data
------------------------
-
-Multilayer data can be written as well. Layers must be specified by name when
-writing.
-
-.. code-block:: python
-
- with open('tests/data/cowildrnp.shp') as src:
- meta = src.meta
- f = next(src)
-
- with fiona.open('/tmp/foo', 'w', layer='bar', **meta) as dst:
- dst.write(f)
-
- print(fiona.listlayers('/tmp/foo'))
-
- with fiona.open('/tmp/foo', layer='bar') as src:
- print(len(src))
- f = next(src)
- print(f['geometry']['type'])
- print(f['properties'])
-
- # Output:
- # ['bar']
- # 1
- # Polygon
- # OrderedDict([('PERIMETER', 1.22107), ('FEATURE2', None), ('NAME', 'Mount Naomi Wilderness'), ('FEATURE1', 'Wilderness'), ('URL', 'http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi'), ('AGBUR', 'FS'), ('AREA', 0.0179264), ('STATE_FIPS', '49'), ('WILDRNP020', 332), ('STATE', 'UT')])
-
-A view of the /tmp/foo directory will confirm the creation of the new files.
-
-.. code-block:: console
-
- $ ls /tmp/foo
- bar.cpg bar.dbf bar.prj bar.shp bar.shx
-
-Collections from archives and virtual file systems
---------------------------------------------------
-
-Zip and Tar archives can be treated as virtual filesystems and Collections can
-be made from paths and layers within them. In other words, Fiona lets you read
-and write zipped Shapefiles.
-
-.. code-block:: python
+ # Write the feature out.
+ dst.write(
+ Feature(geometry=new_geom, properties=f.properties)
+ )
- for i, layername in enumerate(fiona.listlayers('zip://tests/data/coutwildrnp.zip')):
- with fiona.open('zip://tests/data/coutwildrnp.zip', layer=i) as src:
- print(i, layername, len(src))
+ # The destination's contents are flushed to disk and the file is
+ # closed when its with block ends. This effectively
+ # executes ``dst.flush(); dst.close()``.
- # Output:
- # (0, 'coutwildrnp', 67)
-
-Fiona can also read from more exotic file systems. For instance, a
-zipped shape file in S3 can be accessed like so:
-
-.. code-block:: python
-
- with fiona.open('zip+s3://mapbox/rasterio/coutwildrnp.zip') as src:
- print(len(src))
-
- # Output:
- # 67
-
-
-Fiona CLI
+CLI Usage
=========
Fiona's command line interface, named "fio", is documented at `docs/cli.rst
-<https://github.com/Toblerity/Fiona/blob/master/docs/cli.rst>`__. Its ``fio
-info`` pretty prints information about a data file.
-
-.. code-block:: console
-
- $ fio info --indent 2 tests/data/coutwildrnp.shp
- {
- "count": 67,
- "crs": "EPSG:4326",
- "driver": "ESRI Shapefile",
- "bounds": [
- -113.56424713134766,
- 37.0689811706543,
- -104.97087097167969,
- 41.99627685546875
- ],
- "schema": {
- "geometry": "Polygon",
- "properties": {
- "PERIMETER": "float:24.15",
- "FEATURE2": "str:80",
- "NAME": "str:80",
- "FEATURE1": "str:80",
- "URL": "str:101",
- "AGBUR": "str:80",
- "AREA": "float:24.15",
- "STATE_FIPS": "str:80",
- "WILDRNP020": "int:10",
- "STATE": "str:80"
- }
- }
- }
-
-Installation
-============
-
-Fiona requires Python 3.6+ and GDAL/OGR 1.8+. To build from
-a source distribution you will need a C compiler and GDAL and Python
-development headers and libraries (libgdal1-dev for Debian/Ubuntu, gdal-dev for
-CentOS/Fedora).
-
-To build from a repository copy, you will also need Cython to build C sources
-from the project's .pyx files. See the project's requirements-dev.txt file for
-guidance.
-
-The `Kyngchaos GDAL frameworks
-<https://www.kyngchaos.com/software/frameworks/#gdal_complete>`__ will satisfy
-the GDAL/OGR dependency for OS X, as will Homebrew's GDAL Formula (``brew install
-gdal``).
-
-Python Requirements
--------------------
-
-Fiona depends on the modules ``cligj`` and ``munch``.
-Pip will fetch these requirements for you, but users installing Fiona from a
-Windows installer must get them separately.
-
-Unix-like systems
------------------
-
-Assuming you're using a virtualenv (if not, skip to the 4th command) and
-GDAL/OGR libraries, headers, and `gdal-config`_ program are installed to well
-known locations on your system via your system's package manager (``brew
-install gdal`` using Homebrew on OS X), installation is this simple.
-
-.. code-block:: console
-
- $ mkdir fiona_env
- $ virtualenv fiona_env
- $ source fiona_env/bin/activate
- (fiona_env)$ pip install fiona
-
-If gdal-config is not available or if GDAL/OGR headers and libs aren't
-installed to a well known location, you must set include dirs, library dirs,
-and libraries options via the setup.cfg file or setup command line as shown
-below (using ``git``). You must also specify the version of the GDAL API on the
-command line using the ``--gdalversion`` argument (see example below) or with
-the ``GDAL_VERSION`` environment variable (e.g. ``export GDAL_VERSION=2.1``).
-
-.. code-block:: console
-
- (fiona_env)$ git clone git://github.com/Toblerity/Fiona.git
- (fiona_env)$ cd Fiona
- (fiona_env)$ python setup.py build_ext -I/path/to/gdal/include -L/path/to/gdal/lib -lgdal install --gdalversion 2.1
-
-Or specify that build options and GDAL API version should be provided by a
-particular gdal-config program.
-
-.. code-block:: console
-
- (fiona_env)$ GDAL_CONFIG=/path/to/gdal-config pip install fiona
-
-Windows
--------
-
-Binary installers are available at
-https://www.lfd.uci.edu/~gohlke/pythonlibs/#fiona and coming eventually to PyPI.
-
-You can download a binary distribution of GDAL from `here
-<https://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. You must also specify the version of the GDAL API on the
-command line using the ``--gdalversion`` argument (see example below) or with
-the ``GDAL_VERSION`` environment variable (e.g. ``set GDAL_VERSION=2.1``).
+<https://github.com/Toblerity/Fiona/blob/master/docs/cli.rst>`__. The CLI has a
+number of different commands. Its ``fio cat`` command streams GeoJSON features
+from any dataset.
.. code-block:: console
- $ python setup.py build_ext -I<path to gdal include files> -lgdal_i -L<path to gdal library> install --gdalversion 2.1
+ $ fio cat --compact tests/data/coutwildrnp.shp | jq -c '.'
+ {"geometry":{"coordinates":[[[-111.73527526855469,41.995094299316406],...]]}}
+ ...
-Note: The following environment variables needs to be set so that Fiona works correctly:
+Documentation
+=============
-* The directory containing the GDAL DLL (``gdal304.dll`` or similar) needs to be in your
- Windows ``PATH`` (e.g. ``C:\gdal\bin``).
-* The gdal-data directory needs to be in your Windows ``PATH`` or the environment variable
- ``GDAL_DATA`` must be set (e.g. ``C:\gdal\bin\gdal-data``).
-* The environment variable ``PROJ_LIB`` (PROJ < 9.1) | ``PROJ_DATA`` (PROJ 9.1+) must be set to the proj data directory (e.g.
- ``C:\gdal\bin\proj6\share``)
+For more details about this project, please see:
-The `Appveyor CI build <https://ci.appveyor.com/project/sgillies/fiona/history>`__
-uses the GISInternals GDAL binaries to build Fiona. This produces a binary wheel
-for successful builds, which includes GDAL and other dependencies, for users
-wanting to try an unstable development version.
-The `Appveyor configuration file <https://github.com/Toblerity/Fiona/blob/master/appveyor.yml>`__ may be a useful example for
-users building from source on Windows.
-
-Development and testing
-=======================
-
-Building from the source requires Cython. Tests require `pytest <https://pytest.org>`_. If the GDAL/OGR
-libraries, headers, and `gdal-config`_ program are installed to well known
-locations on your system (via your system's package manager), you can do this::
-
- (fiona_env)$ git clone git://github.com/Toblerity/Fiona.git
- (fiona_env)$ cd Fiona
- (fiona_env)$ pip install cython
- (fiona_env)$ pip install -e .[test]
- (fiona_env)$ pytest
-
-If you have a non-standard environment, you'll need to specify the include and
-lib dirs and GDAL library on the command line::
-
- (fiona_env)$ python setup.py build_ext -I/path/to/gdal/include -L/path/to/gdal/lib -lgdal --gdalversion 2 develop
- (fiona_env)$ pytest
-
-.. _GDAL: https://gdal.org
-.. _pyproj: https://pypi.org/project/pyproj/
-.. _Rtree: https://pypi.org/project/Rtree/
-.. _Shapely: https://pypi.org/project/Shapely/
-.. _gdal-config: https://gdal.org/programs/gdal-config.html
+* Fiona `home page <https://github.com/Toblerity/Fiona>`__
+* `Docs and manual <https://fiona.readthedocs.io/>`__
+* `Examples <https://github.com/Toblerity/Fiona/tree/master/examples>`__
+* Main `user discussion group <https://fiona.groups.io/g/main>`__
+* `Developers discussion group <https://fiona.groups.io/g/dev>`__
=====================================
docs/index.rst
=====================================
@@ -5,6 +5,7 @@ Fiona Documentation Contents
:maxdepth: 2
README
+ Installation <install>
User Manual <manual>
API Documentation <modules>
CLI Documentation <cli>
=====================================
docs/install.rst
=====================================
@@ -0,0 +1,76 @@
+============
+Installation
+============
+
+Installation of the Fiona package is complicated by its dependency on libgdal
+and other C libraries. There are easy installations paths and an advanced
+installation path.
+
+Easy installation
+=================
+
+Fiona has several `extension modules
+<https://docs.python.org/3/extending/extending.html>`__ which link against
+libgdal. This complicates installation. Binary distributions (wheels)
+containing libgdal and its own dependencies are available from the Python
+Package Index and can be installed using pip.
+
+.. code-block:: console
+
+ pip install fiona
+
+These wheels are mainly intended to make installation easy for simple
+applications, not so much for production. They are not tested for compatibility
+with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's
+optional format drivers. If you need, for example, GML support you will need to
+build and install Fiona from a source distribution.
+
+Many users find Anaconda and conda-forge a good way to install Fiona and get
+access to more optional format drivers (like GML).
+
+Fiona 1.9 (coming soon) requires Python 3.7 or higher and GDAL 3.2 or higher.
+
+Advanced installation
+=====================
+
+Once GDAL and its dependencies are installed on your computer (how to do this
+is documented at https://gdal.org) Fiona can be built and installed using
+setuptools or pip. If your GDAL installation provides the ``gdal-config``
+program, the process is simpler.
+
+Without pip:
+
+.. code-block:: console
+
+ GDAL_CONFIG=/path/to/gdal-config python setup.py install
+
+With pip:
+
+.. code-block:: console
+
+ GDAL_CONFIG=/path/to/gdal-config python -m pip install --user .
+
+These are pretty much equivalent. Pip will use setuptools as the build backend.
+If the gdal-config program is on your executable path, then you don't need to
+set the environment variable.
+
+Without gdal-config you will need to configure header and library locations for
+the build in another way. One way to do this is to create a setup.cfg file in
+the source directory with content like this:
+
+.. code-block:: ini
+
+ [build_ext]
+ include_dirs = C:/vcpkg/installed/x64-windows/include
+ libraries = gdal
+ library_dirs = C:/vcpkg/installed/x64-windows/lib
+
+This is the approach taken by Fiona's `wheel-building workflow
+<https://github.com/sgillies/fiona-wheels/blob/master/.github/workflows/win-wheels.yaml#L67-L74>`__.
+With this file in place you can run either ``python setup.py install`` or ``python
+-m pip install --user .``.
+
+You can also pass those three values on the command line following the
+`setuptools documentation
+<https://setuptools.pypa.io/en/latest/userguide/ext_modules.html#compiler-and-linker-options>`__.
+However, the setup.cfg approach is easier.
=====================================
docs/manual.rst
=====================================
@@ -5,7 +5,7 @@ The Fiona User Manual
:Author: Sean Gillies, <sean.gillies at gmail.com>
:Version: |release|
:Date: |today|
-:Copyright:
+:Copyright:
This work is licensed under a `Creative Commons Attribution 3.0
United States License`__.
@@ -88,23 +88,24 @@ In what cases would you not benefit from using Fiona?
Example
-------
-The first example of using Fiona is this: copying records from one file to
-another, adding two attributes and making sure that all polygons are facing
-"up". Orientation of polygons is significant in some applications, extruded
-polygons in Google Earth for one. No other library (like :py:mod:`Shapely`) is
-needed here, which keeps it uncomplicated. There's a :file:`test_uk` file in
-the Fiona repository for use in this and other examples.
+The first example of using Fiona is this: copying features (another word for
+record) from one file to another, adding two attributes and making sure that
+all polygons are facing "up". Orientation of polygons is significant in some
+applications, extruded polygons in Google Earth for one. No other library (like
+:py:mod:`Shapely`) is needed here, which keeps it uncomplicated. There's a
+:file:`test_uk` file in the Fiona repository for use in this and other
+examples.
.. code-block:: python
import datetime
import logging
import sys
-
+
import fiona
-
+
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
-
+
def signed_area(coords):
"""Return the signed area enclosed by a ring using the linear time
algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0
@@ -112,17 +113,17 @@ the Fiona repository for use in this and other examples.
"""
xs, ys = map(list, zip(*coords))
xs.append(xs[1])
- ys.append(ys[1])
+ ys.append(ys[1])
return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0
-
+
with fiona.open('docs/data/test_uk.shp', 'r') as source:
-
+
# Copy the source schema and add two new properties.
sink_schema = source.schema
sink_schema['properties']['s_area'] = 'float'
sink_schema['properties']['timestamp'] = 'datetime'
-
- # Create a sink for processed features with the same format and
+
+ # Create a sink for processed features with the same format and
# coordinate reference system as the source.
with fiona.open(
'oriented-ccw.shp', 'w',
@@ -130,11 +131,11 @@ the Fiona repository for use in this and other examples.
driver=source.driver,
schema=sink_schema,
) as sink:
-
+
for f in source:
-
+
try:
-
+
# If any feature's polygon is facing "down" (has rings
# wound clockwise), its rings will be reordered to flip
# it "up".
@@ -146,15 +147,15 @@ the Fiona repository for use in this and other examples.
rings = [r[::-1] for r in rings]
g['coordinates'] = rings
f['geometry'] = g
-
+
# Add the signed area of the polygon and a timestamp
# to the feature properties map.
f['properties'].update(
s_area=sa,
timestamp=datetime.datetime.now().isoformat() )
-
+
sink.write(f)
-
+
except Exception as e:
logging.exception("Error processing feature %s:", f['id'])
@@ -199,7 +200,7 @@ not treat features as dictionaries? Use of existing Python idioms is one of
Fiona's major design principles.
.. admonition:: TL;DR
-
+
Fiona subscribes to the conventional record model of data, but provides
GeoJSON-like access to the data via Python file-like and mapping protocols.
@@ -221,7 +222,7 @@ Reading a GIS vector file begins by opening it in mode ``'r'`` using Fiona's
.. admonition:: API Change
- :py:func:`fiona.collection` is deprecated, but aliased to
+ :py:func:`fiona.collection` is deprecated, but aliased to
:py:func:`fiona.open` in version 0.9.
Mode ``'r'`` is the default and will be omitted in following examples.
@@ -264,7 +265,7 @@ collection to get back to the beginning.
encoding of the Natural Earth dataset is Windows-1252. In this case, the
proper encoding can be specified explicitly by using the ``encoding``
keyword parameter of :py:func:`fiona.open`: ``encoding='Windows-1252'``.
-
+
New in version 0.9.1.
Collection indexing
@@ -318,7 +319,7 @@ is a context guard, it is closed no matter what happens within the block.
... except:
... print(c.closed)
... raise
- ...
+ ...
48
True
Traceback (most recent call last):
@@ -329,7 +330,7 @@ An exception is raised in the :keyword:`with` block above, but as you can see
from the print statement in the :keyword:`except` clause :py:meth:`c.__exit__`
(and thereby :py:meth:`c.close`) has been called.
-.. important:: Always call :py:meth:`~fiona.collection.Collection.close` or
+.. important:: Always call :py:meth:`~fiona.collection.Collection.close` or
use :keyword:`with` and you'll never stumble over tied-up external resources,
locked files, etc.
@@ -422,7 +423,7 @@ dict with items having the same order as the fields in the data file.
'CNTRY_NAME': 'str',
'AREA': 'float:15.2',
'POP_CNTRY': 'float:15.2'}}
-
+
Keeping Schemas Simple
----------------------
@@ -719,7 +720,7 @@ A vector file can be opened for writing in mode ``'a'`` (append) or mode
``'w'`` (write).
.. admonition:: Note
-
+
The in situ "update" mode of :program:`OGR` is quite format dependent
and is therefore not supported by Fiona.
@@ -752,7 +753,7 @@ of the file grows from 48 to 49.
... print(len(c))
... c.write(rec)
... print(len(c))
- ...
+ ...
48
49
@@ -763,7 +764,7 @@ type of record, remember). You'll get a :py:class:`ValueError` if it doesn't.
>>> with fiona.open('/tmp/test_uk.shp', 'a') as c:
... c.write({'properties': {'foo': 'bar'}})
- ...
+ ...
Traceback (most recent call last):
...
ValueError: Record data not match collection schema
@@ -794,7 +795,7 @@ iterator) of records.
>>> with fiona.open('/tmp/test_uk.shp', 'a') as c:
... c.writerecords([rec, rec, rec])
... print(len(c))
- ...
+ ...
52
.. admonition:: Duplication
@@ -810,6 +811,11 @@ iterator) of records.
You may also call :py:meth:`flush` periodically to write the buffer contents
to disk.
+.. admonition:: Format requirements
+
+ Format drivers may have specific requirements about what they store. For
+ example, the Shapefile driver may "fix" topologically invalid features.
+
Creating files of the same structure
------------------------------------
@@ -827,7 +833,7 @@ Review the parameters of our demo file.
... source_driver = source.driver
... source_crs = source.crs
... source_schema = source.schema
- ...
+ ...
>>> source_driver
'ESRI Shapefile'
>>> source_crs
@@ -853,7 +859,7 @@ We can create a new file using them.
... print(len(c))
... c.write(rec)
... print(len(c))
- ...
+ ...
0
1
>>> c.closed
@@ -870,7 +876,7 @@ same order as those of the source file.
$ ogrinfo /tmp/foo.shp foo -so
INFO: Open of `/tmp/foo.shp'
using driver `ESRI Shapefile' successful.
-
+
Layer name: foo
Geometry: 3D Polygon
Feature Count: 1
@@ -987,7 +993,7 @@ dict is given, the ordering is determined by the output of that dict's
For example, since
.. code-block:: pycon
-
+
>>> {'bar': 'int', 'foo': 'str'}.keys()
['foo', 'bar']
@@ -1066,13 +1072,13 @@ If you call ``fiona.open()`` with no surrounding ``Env`` environment, one will
be created for you.
When your program exits the environment's with block the configuration reverts
-to its previous state.
+to its previous state.
Driver configuration options
----------------------------
Drivers can have dataset open, dataset creation, respectively layer creation options. These options can be found
-on the drivers page on `GDAL's homepage. <https://gdal.org/drivers/vector/index.html>`_ or using the
+on the drivers page on `GDAL's homepage. <https://gdal.org/drivers/vector/index.html>`_ or using the
``fiona.meta`` module:
.. code-block:: pycon
@@ -1148,7 +1154,7 @@ builtin :py:func:`list` as shown below.
7
The iterator method takes the same ``stop`` or ``start, stop[, step]``
-slicing arguments as :py:func:`itertools.islice`.
+slicing arguments as :py:func:`itertools.islice`.
To get just the first two items from that iterator, pass a stop index.
.. code-block:: pycon
@@ -1328,7 +1334,7 @@ The single shapefile may also be accessed like so:
.. code-block:: pycon
>>> with fiona.open(
- ... '/docs/data/test_uk.shp',
+ ... '/docs/data/test_uk.shp',
... vfs='zip:///tmp/zed.zip') as c:
... print(len(c))
...
@@ -1340,10 +1346,10 @@ Unsupported drivers
Fiona maintains a list of OGR drivers in :py:attr:`fiona.supported_drivers`
that are tested and known to work together with Fiona. Opening a dataset using
an unsupported driver or access mode results in an :py:attr: `DriverError`
-exception. By passing `allow_unsupported_drivers=True` to :py:attr:`fiona.open`
+exception. By passing `allow_unsupported_drivers=True` to :py:attr:`fiona.open`
no compatibility checks are performed and unsupported OGR drivers can be used.
-However, there are no guarantees that Fiona will be able to access or write
-data correctly using an unsupported driver.
+However, there are no guarantees that Fiona will be able to access or write
+data correctly using an unsupported driver.
.. code-block:: python
@@ -1352,7 +1358,7 @@ data correctly using an unsupported driver.
with fiona.open("file.kmz", allow_unsupported_drivers=True) as collection:
...
-Not all OGR drivers are necessarily enabled in every GDAL distribution. The
+Not all OGR drivers are necessarily enabled in every GDAL distribution. The
following code snippet lists the drivers included in the GDAL installation
used by Fiona:
@@ -1399,7 +1405,7 @@ in an instance of ZipMemoryFile.
Fiona command line interface
============================
-Fiona comes with a command line interface called "fio". See the
+Fiona comes with a command line interface called "fio". See the
`CLI Documentation <cli.html>`__ for detailed usage instructions.
Final Notes
=====================================
fiona/__init__.py
=====================================
@@ -85,29 +85,30 @@ if platform.system() == "Windows":
os.add_dll_directory(p)
-from fiona.collection import BytesCollection, Collection
-from fiona.drvsupport import supported_drivers
-from fiona.env import ensure_env_with_credentials, Env
-from fiona.errors import FionaDeprecationWarning
-from fiona._env import driver_count
from fiona._env import (
calc_gdal_version_num,
- get_gdal_version_num,
get_gdal_release_name,
+ get_gdal_version_num,
get_gdal_version_tuple,
)
+from fiona._env import driver_count
+from fiona._show_versions import show_versions
+from fiona.collection import BytesCollection, Collection
+from fiona.drvsupport import supported_drivers
+from fiona.env import ensure_env_with_credentials, Env
+from fiona.errors import FionaDeprecationWarning
from fiona.io import MemoryFile
+from fiona.model import Feature, Geometry, Properties
from fiona.ogrext import (
+ FIELD_TYPES_MAP,
_bounds,
- _listlayers,
_listdir,
- FIELD_TYPES_MAP,
+ _listlayers,
_remove,
_remove_layer,
)
from fiona.path import ParsedPath, parse_path, vsi_path
from fiona.vfs import parse_paths as vfs_parse_paths
-from fiona._show_versions import show_versions
# These modules are imported by fiona.ogrext, but are also import here to
# help tools like cx_Freeze find them automatically
@@ -115,8 +116,18 @@ from fiona import _geometry, _err, rfc3339
import uuid
-__all__ = ['bounds', 'listlayers', 'listdir', 'open', 'prop_type', 'prop_width']
-__version__ = "1.9b1"
+__all__ = [
+ "Feature",
+ "Geometry",
+ "Properties",
+ "bounds",
+ "listlayers",
+ "listdir",
+ "open",
+ "prop_type",
+ "prop_width",
+]
+__version__ = "1.9b2dev"
__gdal_version__ = get_gdal_release_name()
gdal_version = get_gdal_version_tuple()
=====================================
fiona/_geometry.pyx
=====================================
@@ -7,7 +7,7 @@ include "gdal.pxi"
import logging
from fiona.errors import UnsupportedGeometryTypeError
-from fiona.model import _guard_model_object, GEOMETRY_TYPES, Geometry, OGRGeometryType
+from fiona.model import decode_object, GEOMETRY_TYPES, Geometry, OGRGeometryType
from fiona._err cimport exc_wrap_int
@@ -363,7 +363,7 @@ cdef class OGRGeomBuilder:
def geometryRT(geom):
# For testing purposes only, leaks the JSON data
- geometry = _guard_model_object(geom)
+ geometry = decode_object(geom)
cdef void *cogr_geometry = OGRGeomBuilder().build(geometry)
result = GeomBuilder().build(cogr_geometry)
_deleteOgrGeom(cogr_geometry)
=====================================
fiona/fio/bounds.py
=====================================
@@ -1,8 +1,6 @@
"""$ fio bounds"""
-
import json
-import logging
import click
from cligj import precision_opt, use_rs_opt
@@ -38,56 +36,30 @@ def bounds(ctx, precision, explode, with_id, with_obj, use_rs):
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}.
+
"""
- logger = logging.getLogger(__name__)
stdin = click.get_text_stream('stdin')
+ source = obj_gen(stdin)
- 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)
+ for i, obj in enumerate(source):
+ obj_id = obj.get("id", "collection:" + str(i))
+ xs = []
+ ys = []
+ features = obj.get("features") or [obj]
- if precision > 0:
- w, s, e, n = (round(v, precision)
- for v in (w, s, e, n))
- if explode:
+ for j, feat in enumerate(features):
+ feat_id = feat.get("id", "feature:" + str(i))
+ w, s, e, n = fiona.bounds(feat)
- 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)
-
- if use_rs:
- click.echo('\x1e', nl=False)
-
- click.echo(json.dumps(rec, cls=ObjectEncoder))
-
- 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 precision > 0:
+ w, s, e, n = (round(v, precision) for v in (w, s, e, n))
+ if explode:
if with_id:
- rec = {'id': obj_id, 'bbox': (w, s, e, n)}
+ rec = {"parent": obj_id, "id": feat_id, "bbox": (w, s, e, n)}
elif with_obj:
- obj.update(id=obj_id, bbox=(w, s, e, n))
- rec = obj
+ feat.update(parent=obj_id, bbox=(w, s, e, n))
+ rec = feat
else:
rec = (w, s, e, n)
@@ -96,6 +68,22 @@ def bounds(ctx, precision, explode, with_id, with_obj, use_rs):
click.echo(json.dumps(rec, cls=ObjectEncoder))
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ 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)
+
+ if use_rs:
+ click.echo("\x1e", nl=False)
+
+ click.echo(json.dumps(rec, cls=ObjectEncoder))
=====================================
fiona/fio/calc.py
=====================================
@@ -1,6 +1,5 @@
from __future__ import division
import json
-import logging
import click
from cligj import use_rs_opt
@@ -39,27 +38,27 @@ def calc(ctx, property_name, expression, overwrite, use_rs):
\b
$ fio cat data.shp | fio calc sumAB "f.properties.A + f.properties.B"
+
"""
- logger = logging.getLogger(__name__)
stdin = click.get_text_stream('stdin')
- try:
- source = obj_gen(stdin)
- for i, obj in enumerate(source):
- features = obj.get('features') or [obj]
- for j, feat in enumerate(features):
+ source = obj_gen(stdin)
+
+ for i, obj in enumerate(source):
+ features = obj.get("features") or [obj]
+
+ for j, feat in enumerate(features):
- if not overwrite and property_name in feat['properties']:
- raise click.UsageError(
- '{0} already exists in properties; '
- 'rename or use --overwrite'.format(property_name))
+ if not overwrite and property_name in feat["properties"]:
+ raise click.UsageError(
+ "{0} already exists in properties; "
+ "rename or use --overwrite".format(property_name)
+ )
- feat['properties'][property_name] = eval_feature_expression(
- feat, expression)
+ feat["properties"][property_name] = eval_feature_expression(
+ feat, expression
+ )
- if use_rs:
- click.echo('\x1e', nl=False)
- click.echo(json.dumps(feat, cls=ObjectEncoder))
+ if use_rs:
+ click.echo("\x1e", nl=False)
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ click.echo(json.dumps(feat, cls=ObjectEncoder))
=====================================
fiona/fio/cat.py
=====================================
@@ -1,7 +1,6 @@
"""fio-cat"""
import json
-import logging
import warnings
import click
@@ -80,8 +79,6 @@ def cat(
Use the '--layer' option to select a different layer.
"""
- log = logging.getLogger(__name__)
-
dump_kwds = {"sort_keys": True}
if indent:
dump_kwds["indent"] = indent
@@ -104,11 +101,13 @@ def cat(
bbox = tuple(map(float, bbox.split(",")))
except ValueError:
bbox = json.loads(bbox)
+
for i, path in enumerate(files, 1):
for lyr in layer[str(i)]:
with fiona.open(path, layer=lyr) as src:
for i, feat in src.items(bbox=bbox, where=where):
geom = feat.geometry
+
if dst_crs:
geom = transform_geom(
src.crs,
@@ -126,12 +125,11 @@ def cat(
geometry=geom,
bbox=fiona.bounds(geom),
)
+
if use_rs:
click.echo("\x1e", nl=False)
+
click.echo(json.dumps(feat, cls=ObjectEncoder, **dump_kwds))
except AttributeFilterError as e:
raise click.BadParameter("'where' clause is invalid: " + str(e))
- except Exception:
- log.exception("Exception caught during processing")
- raise click.Abort()
=====================================
fiona/fio/collect.py
=====================================
@@ -150,47 +150,74 @@ def collect(
for line in stdin:
yield line
- try:
- source = feature_text_gen()
+ source = feature_text_gen()
- if record_buffered:
- # Buffer GeoJSON data at the feature level for smaller
- # memory footprint.
- indented = bool(indent)
- rec_indent = "\n" + " " * (2 * (indent or 0))
+ if record_buffered:
+ # Buffer GeoJSON data at the feature level for smaller
+ # memory footprint.
+ indented = bool(indent)
+ rec_indent = "\n" + " " * (2 * (indent or 0))
- collection = {"type": "FeatureCollection", "features": []}
- if with_ld_context:
- collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+ collection = {"type": "FeatureCollection", "features": []}
+ if with_ld_context:
+ collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+
+ head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
- head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split(
- "[]"
- )
+ sink.write(head)
+ sink.write("[")
- sink.write(head)
- sink.write("[")
+ # Try the first record.
+ try:
+ i, first = 0, next(source)
+ if with_ld_context:
+ first = helpers.id_record(first)
+ if indented:
+ sink.write(rec_indent)
+ sink.write(first.replace("\n", rec_indent))
+ except StopIteration:
+ pass
+ except Exception as exc:
+ # Ignoring errors is *not* the default.
+ if ignore_errors:
+ logger.error(
+ "failed to serialize file record %d (%s), " "continuing", i, exc
+ )
+ else:
+ # Log error and close up the GeoJSON, leaving it
+ # more or less valid no matter what happens above.
+ logger.critical(
+ "failed to serialize file record %d (%s), " "quiting", i, exc
+ )
+ sink.write("]")
+ sink.write(tail)
+ if indented:
+ sink.write("\n")
+ raise
- # Try the first record.
+ # Because trailing commas aren't valid in JSON arrays
+ # we'll write the item separator before each of the
+ # remaining features.
+ for i, rec in enumerate(source, 1):
try:
- i, first = 0, next(source)
if with_ld_context:
- first = helpers.id_record(first)
+ rec = helpers.id_record(rec)
if indented:
sink.write(rec_indent)
- sink.write(first.replace("\n", rec_indent))
- except StopIteration:
- pass
+ sink.write(item_sep)
+ sink.write(rec.replace("\n", rec_indent))
except Exception as exc:
- # Ignoring errors is *not* the default.
if ignore_errors:
logger.error(
- "failed to serialize file record %d (%s), " "continuing", i, exc
+ "failed to serialize file record %d (%s), " "continuing",
+ i,
+ exc,
)
else:
- # Log error and close up the GeoJSON, leaving it
- # more or less valid no matter what happens above.
logger.critical(
- "failed to serialize file record %d (%s), " "quiting", i, exc
+ "failed to serialize file record %d (%s), " "quiting",
+ i,
+ exc,
)
sink.write("]")
sink.write(tail)
@@ -198,58 +225,22 @@ def collect(
sink.write("\n")
raise
- # Because trailing commas aren't valid in JSON arrays
- # we'll write the item separator before each of the
- # remaining features.
- for i, rec in enumerate(source, 1):
- try:
- if with_ld_context:
- rec = helpers.id_record(rec)
- if indented:
- sink.write(rec_indent)
- sink.write(item_sep)
- sink.write(rec.replace("\n", rec_indent))
- except Exception as exc:
- if ignore_errors:
- logger.error(
- "failed to serialize file record %d (%s), " "continuing",
- i,
- exc,
- )
- else:
- logger.critical(
- "failed to serialize file record %d (%s), " "quiting",
- i,
- exc,
- )
- sink.write("]")
- sink.write(tail)
- if indented:
- sink.write("\n")
- raise
-
- # Close up the GeoJSON after writing all features.
- sink.write("]")
- sink.write(tail)
- if indented:
- sink.write("\n")
-
- else:
- # Buffer GeoJSON data at the collection level. The default.
- collection = {"type": "FeatureCollection", "features": []}
- if with_ld_context:
- collection["@context"] = helpers.make_ld_context(add_ld_context_item)
-
- head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split(
- "[]"
- )
- sink.write(head)
- sink.write("[")
- sink.write(",".join(source))
- sink.write("]")
- sink.write(tail)
+ # Close up the GeoJSON after writing all features.
+ sink.write("]")
+ sink.write(tail)
+ if indented:
sink.write("\n")
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ else:
+ # Buffer GeoJSON data at the collection level. The default.
+ collection = {"type": "FeatureCollection", "features": []}
+ if with_ld_context:
+ collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+
+ head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
+ sink.write(head)
+ sink.write("[")
+ sink.write(",".join(source))
+ sink.write("]")
+ sink.write(tail)
+ sink.write("\n")
=====================================
fiona/fio/distrib.py
=====================================
@@ -1,8 +1,6 @@
"""$ fio distrib"""
-
import json
-import logging
import click
import cligj
@@ -21,23 +19,17 @@ def distrib(ctx, use_rs):
Print the features of GeoJSON objects read from stdin.
"""
- logger = logging.getLogger(__name__)
stdin = click.get_text_stream('stdin')
-
- try:
- source = helpers.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
- if use_rs:
- click.echo('\x1e', nl=False)
- click.echo(json.dumps(feat, cls=ObjectEncoder))
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ source = helpers.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
+ if use_rs:
+ click.echo("\x1e", nl=False)
+ click.echo(json.dumps(feat, cls=ObjectEncoder))
=====================================
fiona/fio/dump.py
=====================================
@@ -1,6 +1,5 @@
"""fio-dump"""
-
from functools import partial
import json
import logging
@@ -63,56 +62,88 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
antimeridian_cutting=True, precision=precision)
return Feature(id=feat.id, properties=feat.properties, geometry=tg(feat.geometry))
- try:
- with fiona.open(input, **open_kwds) as source:
- meta = source.meta
- meta['fields'] = dict(source.schema['properties'].items())
-
- if record_buffered:
- # Buffer GeoJSON data at the feature level for smaller
- # memory footprint.
- indented = bool(indent)
- rec_indent = "\n" + " " * (2 * (indent or 0))
-
- collection = {
- 'type': 'FeatureCollection',
- 'fiona:schema': meta['schema'],
- 'fiona:crs': meta['crs'],
- 'features': []}
- if with_ld_context:
- collection['@context'] = helpers.make_ld_context(
- add_ld_context_item)
+ with fiona.open(input, **open_kwds) as source:
+ meta = source.meta
+ meta["fields"] = dict(source.schema["properties"].items())
+
+ if record_buffered:
+ # Buffer GeoJSON data at the feature level for smaller
+ # memory footprint.
+ indented = bool(indent)
+ rec_indent = "\n" + " " * (2 * (indent or 0))
+
+ collection = {
+ "type": "FeatureCollection",
+ "fiona:schema": meta["schema"],
+ "fiona:crs": meta["crs"],
+ "features": [],
+ }
+ if with_ld_context:
+ collection["@context"] = helpers.make_ld_context(add_ld_context_item)
- head, tail = json.dumps(
- collection, **dump_kwds).split('[]')
+ head, tail = json.dumps(collection, **dump_kwds).split("[]")
- sink.write(head)
- sink.write("[")
+ sink.write(head)
+ sink.write("[")
- itr = iter(source)
+ itr = iter(source)
- # Try the first record.
+ # Try the first record.
+ try:
+ i, first = 0, next(itr)
+ first = transformer(first)
+ if with_ld_context:
+ first = helpers.id_record(first)
+ if indented:
+ sink.write(rec_indent)
+ sink.write(
+ json.dumps(first, cls=ObjectEncoder, **dump_kwds).replace(
+ "\n", rec_indent
+ )
+ )
+ except StopIteration:
+ pass
+ except Exception as exc:
+ # Ignoring errors is *not* the default.
+ if ignore_errors:
+ logger.error(
+ "failed to serialize file record %d (%s), " "continuing", i, exc
+ )
+ else:
+ # Log error and close up the GeoJSON, leaving it
+ # more or less valid no matter what happens above.
+ logger.critical(
+ "failed to serialize file record %d (%s), " "quiting", i, exc
+ )
+ sink.write("]")
+ sink.write(tail)
+ if indented:
+ sink.write("\n")
+ raise
+
+ # Because trailing commas aren't valid in JSON arrays
+ # we'll write the item separator before each of the
+ # remaining features.
+ for i, rec in enumerate(itr, 1):
+ rec = transformer(rec)
try:
- i, first = 0, next(itr)
- first = transformer(first)
if with_ld_context:
- first = helpers.id_record(first)
+ rec = helpers.id_record(rec)
if indented:
sink.write(rec_indent)
- sink.write(json.dumps(
- first, cls=ObjectEncoder, **dump_kwds).replace("\n", rec_indent))
- except StopIteration:
- pass
+ sink.write(item_sep)
+ sink.write(
+ json.dumps(rec, cls=ObjectEncoder, **dump_kwds).replace(
+ "\n", rec_indent
+ )
+ )
except Exception as exc:
- # Ignoring errors is *not* the default.
if ignore_errors:
logger.error(
"failed to serialize file record %d (%s), "
"continuing",
i, exc)
else:
- # Log error and close up the GeoJSON, leaving it
- # more or less valid no matter what happens above.
logger.critical(
"failed to serialize file record %d (%s), "
"quiting",
@@ -123,60 +154,26 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
sink.write("\n")
raise
- # Because trailing commas aren't valid in JSON arrays
- # we'll write the item separator before each of the
- # remaining features.
- for i, rec in enumerate(itr, 1):
- rec = transformer(rec)
- try:
- if with_ld_context:
- rec = helpers.id_record(rec)
- if indented:
- sink.write(rec_indent)
- sink.write(item_sep)
- sink.write(json.dumps(
- rec, cls=ObjectEncoder, **dump_kwds).replace("\n", rec_indent))
- except Exception as exc:
- if ignore_errors:
- logger.error(
- "failed to serialize file record %d (%s), "
- "continuing",
- i, exc)
- else:
- logger.critical(
- "failed to serialize file record %d (%s), "
- "quiting",
- i, exc)
- sink.write("]")
- sink.write(tail)
- if indented:
- sink.write("\n")
- raise
-
- # Close up the GeoJSON after writing all features.
- sink.write("]")
- sink.write(tail)
- if indented:
- sink.write("\n")
-
+ # Close up the GeoJSON after writing all features.
+ sink.write("]")
+ sink.write(tail)
+ if indented:
+ sink.write("\n")
+
+ else:
+ # Buffer GeoJSON data at the collection level. The default.
+ collection = {
+ "type": "FeatureCollection",
+ "fiona:schema": meta["schema"],
+ "fiona:crs": meta["crs"].to_string(),
+ }
+ if with_ld_context:
+ collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+ collection["features"] = [
+ helpers.id_record(transformer(rec)) for rec in source
+ ]
else:
- # Buffer GeoJSON data at the collection level. The default.
- collection = {
- "type": "FeatureCollection",
- "fiona:schema": meta["schema"],
- "fiona:crs": meta["crs"].to_string(),
- }
- if with_ld_context:
- collection['@context'] = helpers.make_ld_context(
- add_ld_context_item)
- collection['features'] = [
- helpers.id_record(transformer(rec))
- for rec in source]
- else:
- collection['features'] = [
- transformer(source.crs, rec) for rec in source]
- json.dump(collection, sink, cls=ObjectEncoder, **dump_kwds)
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ collection["features"] = [
+ transformer(source.crs, rec) for rec in source
+ ]
+ json.dump(collection, sink, cls=ObjectEncoder, **dump_kwds)
=====================================
fiona/fio/filter.py
=====================================
@@ -1,7 +1,6 @@
"""$ fio filter"""
import json
-import logging
import click
from cligj import use_rs_opt
@@ -10,9 +9,6 @@ from fiona.fio.helpers import obj_gen, eval_feature_expression
from fiona.fio import with_context_env
-logger = logging.getLogger(__name__)
-
-
@click.command()
@click.argument('filter_expression')
@use_rs_opt
@@ -41,19 +37,14 @@ def filter(ctx, filter_expression, use_rs):
"""
stdin = click.get_text_stream('stdin')
+ source = obj_gen(stdin)
+
+ for i, obj in enumerate(source):
+ features = obj.get("features") or [obj]
+ for j, feat in enumerate(features):
+ if not eval_feature_expression(feat, filter_expression):
+ continue
- try:
- source = obj_gen(stdin)
- for i, obj in enumerate(source):
- features = obj.get('features') or [obj]
- for j, feat in enumerate(features):
- if not eval_feature_expression(feat, filter_expression):
- continue
-
- if use_rs:
- click.echo('\x1e', nl=False)
- click.echo(json.dumps(feat))
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ if use_rs:
+ click.echo("\x1e", nl=False)
+ click.echo(json.dumps(feat))
=====================================
fiona/fio/info.py
=====================================
@@ -12,6 +12,8 @@ import fiona.crs
from fiona.errors import DriverError
from fiona.fio import options, with_context_env
+logger = logging.getLogger(__name__)
+
@click.command()
# One or more files.
@@ -42,36 +44,34 @@ def info(ctx, input, indent, meta_member, layer):
When working with a multi-layer dataset the first layer is used by default.
Use the '--layer' option to select a different layer.
- """
- logger = logging.getLogger(__name__)
- try:
- with fiona.open(input, layer=layer) as src:
- info = src.meta
- info.update(name=src.name)
+ """
+ with fiona.open(input, layer=layer) as src:
+ info = src.meta
+ info.update(name=src.name)
- try:
- info.update(bounds=src.bounds)
- except DriverError:
- info.update(bounds=None)
- logger.debug("Setting 'bounds' to None - driver was not able to calculate bounds")
+ try:
+ info.update(bounds=src.bounds)
+ except DriverError:
+ info.update(bounds=None)
+ logger.debug(
+ "Setting 'bounds' to None - driver was not able to calculate bounds"
+ )
- try:
- info.update(count=len(src))
- except TypeError:
- info.update(count=None)
- logger.debug("Setting 'count' to None/null - layer does not support counting")
+ try:
+ info.update(count=len(src))
+ except TypeError:
+ info.update(count=None)
+ logger.debug(
+ "Setting 'count' to None/null - layer does not support counting"
+ )
- info["crs"] = src.crs.to_string()
+ info["crs"] = src.crs.to_string()
- if meta_member:
- if isinstance(info[meta_member], (list, tuple)):
- click.echo(" ".join(map(str, info[meta_member])))
- else:
- click.echo(info[meta_member])
+ if meta_member:
+ if isinstance(info[meta_member], (list, tuple)):
+ click.echo(" ".join(map(str, info[meta_member])))
else:
- click.echo(json.dumps(info, indent=indent))
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ click.echo(info[meta_member])
+ else:
+ click.echo(json.dumps(info, indent=indent))
=====================================
fiona/fio/insp.py
=====================================
@@ -2,7 +2,6 @@
import code
-import logging
import sys
import click
@@ -18,28 +17,25 @@ from fiona.fio import with_context_env
@click.pass_context
@with_context_env
def insp(ctx, src_path, interpreter):
- """Open a collection within an interactive interpreter.
- """
- logger = logging.getLogger(__name__)
- banner = '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])))
-
- try:
- with fiona.open(src_path) as src:
- scope = locals()
- if not interpreter:
- code.interact(banner, local=scope)
- elif interpreter == 'ipython':
- import IPython
- IPython.InteractiveShell.banner1 = banner
- IPython.start_ipython(argv=[], user_ns=scope)
- else:
- raise click.ClickException(
- 'Interpreter {} is unsupported or missing '
- 'dependencies'.format(interpreter))
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ """Open a collection within an interactive interpreter."""
+ banner = (
+ "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])))
+ )
+
+ with fiona.open(src_path) as src:
+ scope = locals()
+ if not interpreter:
+ code.interact(banner, local=scope)
+ elif interpreter == "ipython":
+ import IPython
+
+ IPython.InteractiveShell.banner1 = banner
+ IPython.start_ipython(argv=[], user_ns=scope)
+ else:
+ raise click.ClickException(
+ "Interpreter {} is unsupported or missing "
+ "dependencies".format(interpreter)
+ )
=====================================
fiona/fio/load.py
=====================================
@@ -1,8 +1,6 @@
"""$ fio load"""
-
from functools import partial
-import logging
import click
import cligj
@@ -80,8 +78,6 @@ def load(ctx, output, driver, src_crs, dst_crs, features, layer, creation_option
GeoJSON feature objects.
"""
- logger = logging.getLogger(__name__)
-
dst_crs = dst_crs or src_crs
if src_crs and dst_crs and src_crs != dst_crs:
@@ -101,37 +97,39 @@ def load(ctx, output, driver, src_crs, dst_crs, features, layer, creation_option
Feature
"""
- for feat in features:
- feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"]))
- yield Feature.from_dict(**feat)
+ try:
+ for feat in features:
+ feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"]))
+ yield Feature.from_dict(**feat)
+ except TypeError:
+ raise click.ClickException("Invalid input.")
- try:
- source = feature_gen()
+ source = feature_gen()
- # Use schema of first feature as a template.
- # TODO: schema specified on command line?
+ # Use schema of first feature as a template.
+ # TODO: schema specified on command line?
+ try:
first = next(source)
- # print(first, first.geometry)
- 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.open(
- output,
- "w",
- driver=driver,
- crs=dst_crs,
- schema=schema,
- layer=layer,
- **creation_options
- ) as dst:
- dst.write(first)
- dst.writerecords(source)
-
- except Exception:
- logger.exception("Exception caught during processing")
- raise click.Abort()
+ except TypeError:
+ raise click.ClickException("Invalid input.")
+
+ # print(first, first.geometry)
+ 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.open(
+ output,
+ "w",
+ driver=driver,
+ crs=dst_crs,
+ schema=schema,
+ layer=layer,
+ **creation_options
+ ) as dst:
+ dst.write(first)
+ dst.writerecords(source)
=====================================
fiona/model.py
=====================================
@@ -194,8 +194,8 @@ class Geometry(Object):
super(Geometry, self).__init__(**data)
@classmethod
- def from_dict(cls, mapping=None, **kwargs):
- data = dict(mapping or {}, **kwargs)
+ def from_dict(cls, ob=None, **kwargs):
+ data = dict(getattr(ob, "__geo_interface__", ob) or {}, **kwargs)
return Geometry(
coordinates=data.pop("coordinates", None),
type=data.pop("type", None),
@@ -239,6 +239,10 @@ class Geometry(Object):
"""
return self._delegate.geometries
+ @property
+ def __geo_interface__(self):
+ return ObjectEncoder().default(self)
+
class _Feature(object):
def __init__(self, geometry=None, id=None, properties=None):
@@ -266,8 +270,8 @@ class Feature(Object):
super(Feature, self).__init__(**data)
@classmethod
- def from_dict(cls, mapping=None, **kwargs):
- data = dict(mapping or {}, **kwargs)
+ def from_dict(cls, ob=None, **kwargs):
+ data = dict(getattr(ob, "__geo_interface__", ob) or {}, **kwargs)
geom_data = data.pop("geometry", None)
if isinstance(geom_data, Geometry):
@@ -309,7 +313,7 @@ class Feature(Object):
Returns
------
- obejct
+ object
"""
return self._delegate.id
@@ -320,7 +324,7 @@ class Feature(Object):
Returns
-------
- Object
+ object
"""
return self._delegate.properties
@@ -336,6 +340,10 @@ class Feature(Object):
"""
return "Feature"
+ @property
+ def __geo_interface__(self):
+ return ObjectEncoder().default(self)
+
class Properties(Object):
"""A GeoJSON-like feature's properties"""
@@ -380,12 +388,17 @@ def decode_object(obj):
Feature, Geometry, or dict
"""
- if (obj.get("type", None) == "Feature") or "geometry" in obj:
- return Feature.from_dict(**obj)
- elif obj.get("type", None) in list(GEOMETRY_TYPES.values())[:8]:
- return Geometry.from_dict(**obj)
- else:
+ if isinstance(obj, Object):
return obj
+ else:
+ obj = obj.get("__geo_interface__", obj)
+
+ if (obj.get("type", None) == "Feature") or "geometry" in obj:
+ return Feature.from_dict(**obj)
+ elif obj.get("type", None) in list(GEOMETRY_TYPES.values())[:8]:
+ return Geometry.from_dict(**obj)
+ else:
+ return obj
def to_dict(val):
@@ -396,18 +409,3 @@ def to_dict(val):
return val
else:
return obj
-
-
-def _guard_model_object(obj):
- """Convert dict to Geometry or Feature.
-
- For use during the 1.9-2.0 transition. Will be removed in 2.0.
-
- """
- if not isinstance(obj, Object):
- warn(
- "Support for feature and geometry dicts is deprecated. Instances of Feature and Geometry will be required in 2.0.",
- FionaDeprecationWarning,
- stacklevel=2,
- )
- return decode_object(obj)
=====================================
fiona/ogrext.pyx
=====================================
@@ -32,8 +32,8 @@ from fiona.env import Env
from fiona.errors import (
DriverError, DriverIOError, SchemaError, CRSError, FionaValueError,
TransactionError, GeometryTypeValidationError, DatasetDeleteError,
- AttributeFilterError, FeatureWarning, FionaDeprecationWarning)
-from fiona.model import _guard_model_object, Feature, Geometry, Properties
+ AttributeFilterError, FeatureWarning, FionaDeprecationWarning, UnsupportedGeometryTypeError)
+from fiona.model import decode_object, Feature, Geometry, Properties
from fiona.path import vsi_path
from fiona.rfc3339 import parse_date, parse_datetime, parse_time
from fiona.rfc3339 import FionaDateType, FionaDateTimeType, FionaTimeType
@@ -119,14 +119,16 @@ cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NUL
drvs = CSLAddString(drvs, name_c)
for k, v in options.items():
- if v is None:
- continue
- k = k.upper().encode('utf-8')
- if isinstance(v, bool):
- v = ('ON' if v else 'OFF').encode('utf-8')
- else:
- v = str(v).encode('utf-8')
- open_opts = CSLAddNameValue(open_opts, <const char *>k, <const char *>v)
+
+ if v is not None:
+ kb = k.upper().encode('utf-8')
+
+ if isinstance(v, bool):
+ vb = ('ON' if v else 'OFF').encode('utf-8')
+ else:
+ vb = str(v).encode('utf-8')
+
+ open_opts = CSLAddNameValue(open_opts, <const char *>kb, <const char *>vb)
open_opts = CSLAddNameValue(open_opts, "VALIDATE_OPEN_OPTIONS", "NO")
@@ -148,13 +150,26 @@ cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NU
cdef char **creation_opts = NULL
cdef void *cogr_ds = NULL
+ db = <const char *>GDALGetDriverShortName(cogr_driver)
+
+ # To avoid a circular import.
+ from fiona import meta
+
+ option_keys = set(key.upper() for key in options.keys())
+ creation_option_keys = option_keys & set(meta.dataset_creation_options(db.decode("utf-8")))
+
for k, v in options.items():
- k = k.upper().encode('utf-8')
- if isinstance(v, bool):
- v = ('ON' if v else 'OFF').encode('utf-8')
- else:
- v = str(v).encode('utf-8')
- creation_opts = CSLAddNameValue(creation_opts, <const char *>k, <const char *>v)
+
+ if k.upper() in creation_option_keys:
+
+ kb = k.upper().encode('utf-8')
+
+ if isinstance(v, bool):
+ vb = ('ON' if v else 'OFF').encode('utf-8')
+ else:
+ vb = str(v).encode('utf-8')
+
+ creation_opts = CSLAddNameValue(creation_opts, <const char *>kb, <const char *>vb)
try:
return exc_wrap_pointer(GDALCreate(cogr_driver, path_c, 0, 0, 0, GDT_Unknown, creation_opts))
@@ -508,7 +523,7 @@ cdef _deleteOgrFeature(void *cogr_feature):
def featureRT(feat, collection):
# For testing purposes only, leaks the JSON data
- feature = _guard_model_object(feat)
+ feature = decode_object(feat)
cdef void *cogr_feature = OGRFeatureBuilder().build(feature, collection)
cdef void *cogr_geometry = OGR_F_GetGeometryRef(cogr_feature)
if cogr_geometry == NULL:
@@ -1131,40 +1146,48 @@ cdef class WritingSession(Session):
name_b = collection.name.encode('utf-8')
name_c = name_b
- for k, v in kwargs.items():
+ # To avoid circular import.
+ from fiona import meta
- if v is None:
- continue
+ kwarg_keys = set(key.upper() for key in kwargs.keys())
+ lyr_creation_option_keys = kwarg_keys & set(meta.layer_creation_options(collection.driver))
- # We need to remove encoding from the layer creation
- # options if we're not creating a shapefile.
- if k == 'encoding' and "Shapefile" not in collection.driver:
- continue
+ for k, v in kwargs.items():
- k = k.upper().encode('utf-8')
+ if v is not None and k.upper() in lyr_creation_option_keys:
+ kb = k.upper().encode('utf-8')
- if isinstance(v, bool):
- v = ('ON' if v else 'OFF').encode('utf-8')
- else:
- v = str(v).encode('utf-8')
- options = CSLAddNameValue(options, <const char *>k, <const char *>v)
+ if isinstance(v, bool):
+ vb = ('ON' if v else 'OFF').encode('utf-8')
+ else:
+ vb = str(v).encode('utf-8')
+
+ options = CSLAddNameValue(options, <const char *>kb, <const char *>vb)
geometry_type = collection.schema.get("geometry", "Unknown")
+
if not isinstance(geometry_type, str) and geometry_type is not None:
geometry_types = set(geometry_type)
+
if len(geometry_types) > 1:
geometry_type = "Unknown"
else:
geometry_type = geometry_types.pop()
+
if geometry_type == "Any" or geometry_type is None:
geometry_type = "Unknown"
+
geometry_code = geometry_type_code(geometry_type)
try:
- self.cogr_layer = exc_wrap_pointer(
- GDALDatasetCreateLayer(
- self.cogr_ds, name_c, cogr_srs,
- <OGRwkbGeometryType>geometry_code, options))
+ # In GDAL versions > 3.6.0 the following directive may
+ # suffice and we might be able to eliminate the import
+ # of fiona.meta in a future version of Fiona.
+ with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"):
+ self.cogr_layer = exc_wrap_pointer(
+ GDALDatasetCreateLayer(
+ self.cogr_ds, name_c, cogr_srs,
+ <OGRwkbGeometryType>geometry_code, options))
except Exception as exc:
GDALClose(self.cogr_ds)
@@ -1273,19 +1296,28 @@ cdef class WritingSession(Session):
log.debug("Writing started")
def writerecs(self, records, collection):
- """Writes buffered records to OGR."""
+ """Writes records to collection storage.
+
+ Parameters
+ ----------
+ records : Iterable
+ A stream of feature records.
+ collection : Collection
+ The collection in which feature records are stored.
+
+ Returns
+ -------
+ None
+
+ """
cdef void *cogr_driver
cdef void *cogr_feature
cdef int features_in_transaction = 0
-
cdef void *cogr_layer = self.cogr_layer
+
if cogr_layer == NULL:
raise ValueError("Null layer")
- schema_geom_type = collection.schema['geometry']
- cogr_driver = GDALGetDatasetDriver(self.cogr_ds)
- driver_name = OGR_Dr_GetName(cogr_driver).decode("utf-8")
-
valid_geom_types = collection._valid_geom_types
def validate_geometry_type(record):
@@ -1295,6 +1327,7 @@ cdef class WritingSession(Session):
transactions_supported = GDALDatasetTestCapability(self.cogr_ds, ODsCTransactions)
log.debug("Transaction supported: {}".format(transactions_supported))
+
if transactions_supported:
log.debug("Starting transaction (initial)")
result = GDALDatasetStartTransaction(self.cogr_ds, 0)
@@ -1304,13 +1337,7 @@ cdef class WritingSession(Session):
schema_props_keys = set(collection.schema['properties'].keys())
for _rec in records:
- record = _guard_model_object(_rec)
-
- # Check for optional elements
- # if 'properties' not in _rec:
- # _rec['properties'] = {}
- # if 'geometry' not in _rec:
- # _rec['geometry'] = None
+ record = decode_object(_rec)
# Validate against collection's schema.
if set(record.properties.keys()) != schema_props_keys:
@@ -1328,24 +1355,33 @@ cdef class WritingSession(Session):
cogr_feature = OGRFeatureBuilder().build(record, collection)
result = OGR_L_CreateFeature(cogr_layer, cogr_feature)
+
if result != OGRERR_NONE:
msg = get_last_error_msg()
- raise RuntimeError("GDAL Error: {msg} \n \n Failed to write record: "
- "{record}".format(msg=msg, record=record))
+ raise RuntimeError(
+ "GDAL Error: {msg}. Failed to write record: {record}".format(
+ msg=msg, record=record
+ )
+ )
_deleteOgrFeature(cogr_feature)
if transactions_supported:
features_in_transaction += 1
+
if features_in_transaction == DEFAULT_TRANSACTION_SIZE:
log.debug("Committing transaction (intermediate)")
result = GDALDatasetCommitTransaction(self.cogr_ds)
+
if result == OGRERR_FAILURE:
raise TransactionError("Failed to commit transaction")
+
log.debug("Starting transaction (intermediate)")
result = GDALDatasetStartTransaction(self.cogr_ds, 0)
+
if result == OGRERR_FAILURE:
raise TransactionError("Failed to start transaction")
+
features_in_transaction = 0
if transactions_supported:
@@ -1475,7 +1511,7 @@ cdef class Iterator:
OGR_L_SetSpatialFilterRect(
cogr_layer, bbox[0], bbox[1], bbox[2], bbox[3])
elif mask:
- mask_geom = _guard_model_object(mask)
+ mask_geom = decode_object(mask)
cogr_geometry = OGRGeomBuilder().build(mask_geom)
OGR_L_SetSpatialFilter(cogr_layer, cogr_geometry)
OGR_G_DestroyGeometry(cogr_geometry)
@@ -2071,7 +2107,7 @@ def _get_metadata_item(driver, metadata_item):
if get_gdal_version_tuple() < (2, ):
return None
-
+
if driver is None:
return None
=====================================
fiona/transform.py
=====================================
@@ -5,7 +5,7 @@ from warnings import warn
from fiona._transform import _transform, _transform_geom
from fiona.compat import DICT_TYPES
from fiona.errors import FionaDeprecationWarning
-from fiona.model import _guard_model_object, Geometry
+from fiona.model import decode_object, Geometry
def transform(src_crs, dst_crs, xs, ys):
@@ -108,7 +108,7 @@ def transform_geom(
return _transform_geom(
src_crs,
dst_crs,
- _guard_model_object(geom),
+ decode_object(geom),
antimeridian_cutting,
antimeridian_offset,
precision,
@@ -117,7 +117,7 @@ def transform_geom(
return _transform_geom(
src_crs,
dst_crs,
- (_guard_model_object(g) for g in geom),
+ (decode_object(g) for g in geom),
antimeridian_cutting,
antimeridian_offset,
precision,
=====================================
tests/test_collection.py
=====================================
@@ -1,11 +1,12 @@
# Testing collections and workspaces
+from collections import OrderedDict
import datetime
+import logging
import os
import random
-import sys
import re
-from collections import OrderedDict
+import sys
import pytest
@@ -1047,14 +1048,16 @@ def test_collection_zip_http():
def test_encoding_option_warning(tmpdir, caplog):
"""There is no ENCODING creation option log warning for GeoJSON"""
- fiona.Collection(
- str(tmpdir.join("test.geojson")),
- "w",
- driver="GeoJSON",
- crs="epsg:4326",
- schema={"geometry": "Point", "properties": {"foo": "int"}},
- )
- assert not caplog.text
+ with caplog.at_level(logging.WARNING):
+ fiona.Collection(
+ str(tmpdir.join("test.geojson")),
+ "w",
+ driver="GeoJSON",
+ crs="EPSG:4326",
+ schema={"geometry": "Point", "properties": {"foo": "int"}},
+ encoding="bogus",
+ )
+ assert not caplog.text
def test_closed_session_next(gdalenv, path_coutwildrnp_shp):
=====================================
tests/test_feature.py
=====================================
@@ -10,7 +10,6 @@ import pytest
import fiona
from fiona import collection
from fiona.collection import Collection
-from fiona.errors import FionaDeprecationWarning
from fiona.model import Feature
from fiona.ogrext import featureRT
@@ -36,10 +35,9 @@ class TestPointRoundTrip(object):
"geometry": {"type": "Point", "coordinates": (0.0, 0.0)},
"properties": {"title": "foo"},
}
- with pytest.warns(FionaDeprecationWarning):
- g = featureRT(f, self.c)
- assert g.geometry.type == "Point"
- assert g.geometry.coordinates == (0.0, 0.0)
+ g = featureRT(f, self.c)
+ assert g.geometry.type == "Point"
+ assert g.geometry.coordinates == (0.0, 0.0)
def test_properties(self):
f = Feature.from_dict(
=====================================
tests/test_fio_calc.py
=====================================
@@ -54,9 +54,10 @@ def test_bool_seq(feature_seq, runner):
def test_existing_property(feature_seq, runner):
- result = runner.invoke(main_group, ['calc', "AREA", "f.properties.AREA * 2"],
- feature_seq)
- assert result.exit_code == 1
+ result = runner.invoke(
+ main_group, ["calc", "AREA", "f.properties.AREA * 2"], feature_seq
+ )
+ assert result.exit_code == 2
result = runner.invoke(main_group, ['calc', "--overwrite", "AREA", "f.properties.AREA * 2"],
feature_seq)
=====================================
tests/test_geometry.py
=====================================
@@ -71,10 +71,9 @@ def test_geometry_collection_round_trip():
],
}
- with pytest.warns(DeprecationWarning):
- result = geometryRT(geom)
- assert len(result["geometries"]) == 2
- assert [g["type"] for g in result["geometries"]] == ["Point", "LineString"]
+ result = geometryRT(geom)
+ assert len(result["geometries"]) == 2
+ assert [g["type"] for g in result["geometries"]] == ["Point", "LineString"]
def test_point_wkb():
=====================================
tests/test_model.py
=====================================
@@ -130,6 +130,13 @@ def test_geometry__props():
}
+def test_geometry_gi():
+ """Geometry __geo_interface__"""
+ gi = Geometry(coordinates=(0, 0), type="Point").__geo_interface__
+ assert gi["type"] == "Point"
+ assert gi["coordinates"] == (0, 0)
+
+
def test_feature_no_geometry():
"""Feature has no attribute"""
feat = Feature()
@@ -294,3 +301,16 @@ def test_decode_object_hook_fallback(o):
def test_properties():
"""Property factory works"""
assert Properties.from_dict(a=1, foo="bar")["a"] == 1
+
+
+def test_feature_gi():
+ """Feature __geo_interface__."""
+ gi = Feature(
+ id="foo",
+ geometry=Geometry(type="Point", coordinates=(0, 0)),
+ properties=Properties(a=1, foo="bar"),
+ )
+
+ assert gi["id"] == "foo"
+ assert gi["geometry"]["type"] == "Point"
+ assert gi["geometry"]["coordinates"] == (0, 0)
=====================================
tests/test_transform.py
=====================================
@@ -119,15 +119,14 @@ def test_transform_issue971():
}
],
}
- with pytest.warns(FionaDeprecationWarning):
- geom_transformed = transform.transform_geom(source_crs, dest_src, geom)
- assert geom_transformed.geometries[0].coordinates[0] == pytest.approx(
- (9.18427, 52.94630)
- )
+ geom_transformed = transform.transform_geom(source_crs, dest_src, geom)
+ assert geom_transformed.geometries[0].coordinates[0] == pytest.approx(
+ (9.18427, 52.94630)
+ )
def test_transform_geom_precision_deprecation():
- """Get a deprecation warning in 1.9"""
+ """Get a precision deprecation warning in 1.9."""
with pytest.warns(FionaDeprecationWarning):
transform.transform_geom(
"epsg:4326",
View it on GitLab: https://salsa.debian.org/debian-gis-team/fiona/-/commit/216cbde590cd39c53b1153b80f665b9ada4754eb
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/fiona/-/commit/216cbde590cd39c53b1153b80f665b9ada4754eb
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230122/64388144/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list