527 lines
15 KiB
Python
527 lines
15 KiB
Python
"""
|
|
Fiona is OGR's neat, nimble API.
|
|
|
|
Fiona provides a minimal, uncomplicated Python interface to the open
|
|
source GIS community's most trusted geodata access library and
|
|
integrates readily with other Python GIS packages such as pyproj, Rtree
|
|
and Shapely.
|
|
|
|
A Fiona feature is a Python mapping inspired by the GeoJSON format. It
|
|
has ``id``, ``geometry``, and ``properties`` attributes. The value of
|
|
``id`` is a string identifier unique within the feature's parent
|
|
collection. The ``geometry`` is another mapping with ``type`` and
|
|
``coordinates`` keys. The ``properties`` of a feature is another mapping
|
|
corresponding to its attribute table.
|
|
|
|
Features are read and written using the ``Collection`` class. These
|
|
``Collection`` objects are a lot like Python ``file`` objects. A
|
|
``Collection`` opened in reading mode serves as an iterator over
|
|
features. One opened in a writing mode provides a ``write`` method.
|
|
|
|
"""
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import warnings
|
|
|
|
if platform.system() == "Windows":
|
|
_whl_dir = os.path.join(os.path.dirname(__file__), ".libs")
|
|
if os.path.exists(_whl_dir):
|
|
os.add_dll_directory(_whl_dir)
|
|
else:
|
|
if "PATH" in os.environ:
|
|
for p in os.environ["PATH"].split(os.pathsep):
|
|
if glob.glob(os.path.join(p, "gdal*.dll")):
|
|
os.add_dll_directory(p)
|
|
|
|
|
|
from fiona._env import (
|
|
calc_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,
|
|
_listdir,
|
|
_listlayers,
|
|
_remove,
|
|
_remove_layer,
|
|
)
|
|
from fiona.path import ParsedPath, parse_path, vsi_path
|
|
from fiona.vfs import parse_paths as vfs_parse_paths
|
|
|
|
# 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
|
|
|
|
|
|
__all__ = [
|
|
"Feature",
|
|
"Geometry",
|
|
"Properties",
|
|
"bounds",
|
|
"listlayers",
|
|
"listdir",
|
|
"open",
|
|
"prop_type",
|
|
"prop_width",
|
|
"remove",
|
|
]
|
|
|
|
__version__ = "1.9.5"
|
|
__gdal_version__ = get_gdal_release_name()
|
|
|
|
gdal_version = get_gdal_version_tuple()
|
|
|
|
log = logging.getLogger(__name__)
|
|
log.addHandler(logging.NullHandler())
|
|
|
|
|
|
@ensure_env_with_credentials
|
|
def open(
|
|
fp,
|
|
mode="r",
|
|
driver=None,
|
|
schema=None,
|
|
crs=None,
|
|
encoding=None,
|
|
layer=None,
|
|
vfs=None,
|
|
enabled_drivers=None,
|
|
crs_wkt=None,
|
|
allow_unsupported_drivers=False,
|
|
**kwargs
|
|
):
|
|
"""Open a collection for read, append, or write
|
|
|
|
In write mode, a driver name such as "ESRI Shapefile" or "GPX" (see
|
|
OGR docs or ``ogr2ogr --help`` on the command line) and a schema
|
|
mapping such as:
|
|
|
|
{'geometry': 'Point',
|
|
'properties': [('class', 'int'), ('label', 'str'),
|
|
('value', 'float')]}
|
|
|
|
must be provided. If a particular ordering of properties ("fields"
|
|
in GIS parlance) in the written file is desired, a list of (key,
|
|
value) pairs as above or an ordered dict is required. If no ordering
|
|
is needed, a standard dict will suffice.
|
|
|
|
A coordinate reference system for collections in write mode can be
|
|
defined by the ``crs`` parameter. It takes Proj4 style mappings like
|
|
|
|
{'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84',
|
|
'no_defs': True}
|
|
|
|
short hand strings like
|
|
|
|
EPSG:4326
|
|
|
|
or WKT representations of coordinate reference systems.
|
|
|
|
The drivers used by Fiona will try to detect the encoding of data
|
|
files. If they fail, you may provide the proper ``encoding``, such
|
|
as 'Windows-1252' for the original Natural Earth datasets.
|
|
|
|
When the provided path is to a file containing multiple named layers
|
|
of data, a layer can be singled out by ``layer``.
|
|
|
|
The drivers enabled for opening datasets may be restricted to those
|
|
listed in the ``enabled_drivers`` parameter. This and the ``driver``
|
|
parameter afford much control over opening of files.
|
|
|
|
# Trying only the GeoJSON driver when opening to read, the
|
|
# following raises ``DataIOError``:
|
|
fiona.open('example.shp', driver='GeoJSON')
|
|
|
|
# Trying first the GeoJSON driver, then the Shapefile driver,
|
|
# the following succeeds:
|
|
fiona.open(
|
|
'example.shp', enabled_drivers=['GeoJSON', 'ESRI Shapefile'])
|
|
|
|
Parameters
|
|
----------
|
|
fp : URI (str or pathlib.Path), or file-like object
|
|
A dataset resource identifier or file object.
|
|
mode : str
|
|
One of 'r', to read (the default); 'a', to append; or 'w', to
|
|
write.
|
|
driver : str
|
|
In 'w' mode a format driver name is required. In 'r' or 'a'
|
|
mode this parameter has no effect.
|
|
schema : dict
|
|
Required in 'w' mode, has no effect in 'r' or 'a' mode.
|
|
crs : str or dict
|
|
Required in 'w' mode, has no effect in 'r' or 'a' mode.
|
|
encoding : str
|
|
Name of the encoding used to encode or decode the dataset.
|
|
layer : int or str
|
|
The integer index or name of a layer in a multi-layer dataset.
|
|
vfs : str
|
|
This is a deprecated parameter. A URI scheme such as "zip://"
|
|
should be used instead.
|
|
enabled_drivers : list
|
|
An optional list of driver names to used when opening a
|
|
collection.
|
|
crs_wkt : str
|
|
An optional WKT representation of a coordinate reference
|
|
system.
|
|
ignore_fields : list
|
|
List of field names to ignore on load.
|
|
ignore_geometry : bool
|
|
Ignore the geometry on load.
|
|
include_fields : list
|
|
List of a subset of field names to include on load.
|
|
wkt_version : fiona.enums.WktVersion or str, optional
|
|
Version to use to for the CRS WKT.
|
|
Defaults to GDAL's default (WKT1_GDAL for GDAL 3).
|
|
allow_unsupported_drivers : bool
|
|
If set to true do not limit GDAL drivers to set set of known working.
|
|
kwargs : mapping
|
|
Other driver-specific parameters that will be interpreted by
|
|
the OGR library as layer creation or opening options.
|
|
|
|
Returns
|
|
-------
|
|
Collection
|
|
|
|
"""
|
|
if mode == "r" and hasattr(fp, "read"):
|
|
memfile = MemoryFile(fp.read())
|
|
colxn = memfile.open(
|
|
driver=driver,
|
|
crs=crs,
|
|
schema=schema,
|
|
layer=layer,
|
|
encoding=encoding,
|
|
enabled_drivers=enabled_drivers,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
**kwargs
|
|
)
|
|
colxn._env.enter_context(memfile)
|
|
return colxn
|
|
|
|
elif mode == "w" and hasattr(fp, "write"):
|
|
memfile = MemoryFile()
|
|
colxn = memfile.open(
|
|
driver=driver,
|
|
crs=crs,
|
|
schema=schema,
|
|
layer=layer,
|
|
encoding=encoding,
|
|
enabled_drivers=enabled_drivers,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
crs_wkt=crs_wkt,
|
|
**kwargs
|
|
)
|
|
colxn._env.enter_context(memfile)
|
|
|
|
# For the writing case we push an extra callback onto the
|
|
# ExitStack. It ensures that the MemoryFile's contents are
|
|
# copied to the open file object.
|
|
def func(*args, **kwds):
|
|
memfile.seek(0)
|
|
fp.write(memfile.read())
|
|
|
|
colxn._env.callback(func)
|
|
return colxn
|
|
|
|
elif mode == "a" and hasattr(fp, "write"):
|
|
raise OSError(
|
|
"Append mode is not supported for datasets in a Python file object."
|
|
)
|
|
|
|
# TODO: test for a shared base class or abstract type.
|
|
elif isinstance(fp, MemoryFile):
|
|
if mode.startswith("r"):
|
|
colxn = fp.open(
|
|
driver=driver,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
**kwargs
|
|
)
|
|
|
|
# Note: FilePath does not support writing and an exception will
|
|
# result from this.
|
|
elif mode.startswith("w"):
|
|
colxn = fp.open(
|
|
driver=driver,
|
|
crs=crs,
|
|
schema=schema,
|
|
layer=layer,
|
|
encoding=encoding,
|
|
enabled_drivers=enabled_drivers,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
crs_wkt=crs_wkt,
|
|
**kwargs
|
|
)
|
|
return colxn
|
|
|
|
# At this point, the fp argument is a string or path-like object
|
|
# which can be converted to a string.
|
|
else:
|
|
# If a pathlib.Path instance is given, convert it to a string path.
|
|
if isinstance(fp, Path):
|
|
fp = str(fp)
|
|
|
|
if vfs:
|
|
warnings.warn(
|
|
"The vfs keyword argument is deprecated and will be removed in version 2.0.0. Instead, pass a URL that uses a zip or tar (for example) scheme.",
|
|
FionaDeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
path, scheme, archive = vfs_parse_paths(fp, vfs=vfs)
|
|
path = ParsedPath(path, archive, scheme)
|
|
else:
|
|
path = parse_path(fp)
|
|
|
|
if mode in ("a", "r"):
|
|
colxn = Collection(
|
|
path,
|
|
mode,
|
|
driver=driver,
|
|
encoding=encoding,
|
|
layer=layer,
|
|
enabled_drivers=enabled_drivers,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
**kwargs
|
|
)
|
|
elif mode == "w":
|
|
colxn = Collection(
|
|
path,
|
|
mode,
|
|
crs=crs,
|
|
driver=driver,
|
|
schema=schema,
|
|
encoding=encoding,
|
|
layer=layer,
|
|
enabled_drivers=enabled_drivers,
|
|
crs_wkt=crs_wkt,
|
|
allow_unsupported_drivers=allow_unsupported_drivers,
|
|
**kwargs
|
|
)
|
|
else:
|
|
raise ValueError("mode string must be one of {'r', 'w', 'a'}")
|
|
|
|
return colxn
|
|
|
|
|
|
collection = open
|
|
|
|
|
|
@ensure_env_with_credentials
|
|
def remove(path_or_collection, driver=None, layer=None):
|
|
"""Delete an OGR data source or one of its layers.
|
|
|
|
If no layer is specified, the entire dataset and all of its layers
|
|
and associated sidecar files will be deleted.
|
|
|
|
Parameters
|
|
----------
|
|
path_or_collection : str, pathlib.Path, or Collection
|
|
The target Collection or its path.
|
|
driver : str, optional
|
|
The name of a driver to be used for deletion, optional. Can
|
|
usually be detected.
|
|
layer : str or int, optional
|
|
The name or index of a specific layer.
|
|
|
|
Returns
|
|
-------
|
|
None
|
|
|
|
Raises
|
|
------
|
|
DatasetDeleteError
|
|
If the data source cannot be deleted.
|
|
|
|
"""
|
|
if isinstance(path_or_collection, Collection):
|
|
collection = path_or_collection
|
|
path = collection.path
|
|
driver = collection.driver
|
|
collection.close()
|
|
elif isinstance(path_or_collection, Path):
|
|
path = str(path_or_collection)
|
|
else:
|
|
path = path_or_collection
|
|
if layer is None:
|
|
_remove(path, driver)
|
|
else:
|
|
_remove_layer(path, layer, driver)
|
|
|
|
|
|
@ensure_env_with_credentials
|
|
def listdir(fp):
|
|
"""Lists the datasets in a directory or archive file.
|
|
|
|
Archive files must be prefixed like "zip://" or "tar://".
|
|
|
|
Parameters
|
|
----------
|
|
fp : str or pathlib.Path
|
|
Directory or archive path.
|
|
|
|
Returns
|
|
-------
|
|
list of str
|
|
A list of datasets.
|
|
|
|
Raises
|
|
------
|
|
TypeError
|
|
If the input is not a str or Path.
|
|
|
|
"""
|
|
if isinstance(fp, Path):
|
|
fp = str(fp)
|
|
|
|
if not isinstance(fp, str):
|
|
raise TypeError("invalid path: %r" % fp)
|
|
|
|
pobj = parse_path(fp)
|
|
return _listdir(vsi_path(pobj))
|
|
|
|
|
|
@ensure_env_with_credentials
|
|
def listlayers(fp, vfs=None, **kwargs):
|
|
"""Lists the layers (collections) in a dataset.
|
|
|
|
Archive files must be prefixed like "zip://" or "tar://".
|
|
|
|
Parameters
|
|
----------
|
|
fp : str, pathlib.Path, or file-like object
|
|
A dataset identifier or file object containing a dataset.
|
|
vfs : str
|
|
This is a deprecated parameter. A URI scheme such as "zip://"
|
|
should be used instead.
|
|
kwargs : dict
|
|
Dataset opening options and other keyword args.
|
|
|
|
Returns
|
|
-------
|
|
list of str
|
|
A list of layer name strings.
|
|
|
|
Raises
|
|
------
|
|
TypeError
|
|
If the input is not a str, Path, or file object.
|
|
|
|
"""
|
|
if hasattr(fp, 'read'):
|
|
with MemoryFile(fp.read()) as memfile:
|
|
return _listlayers(memfile.name, **kwargs)
|
|
else:
|
|
if isinstance(fp, Path):
|
|
fp = str(fp)
|
|
|
|
if not isinstance(fp, str):
|
|
raise TypeError("invalid path: %r" % fp)
|
|
if vfs and not isinstance(vfs, str):
|
|
raise TypeError("invalid vfs: %r" % vfs)
|
|
|
|
if vfs:
|
|
warnings.warn(
|
|
"The vfs keyword argument is deprecated and will be removed in 2.0. "
|
|
"Instead, pass a URL that uses a zip or tar (for example) scheme.",
|
|
FionaDeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
pobj_vfs = parse_path(vfs)
|
|
pobj_path = parse_path(fp)
|
|
pobj = ParsedPath(pobj_path.path, pobj_vfs.path, pobj_vfs.scheme)
|
|
else:
|
|
pobj = parse_path(fp)
|
|
|
|
return _listlayers(vsi_path(pobj), **kwargs)
|
|
|
|
|
|
def prop_width(val):
|
|
"""Returns the width of a str type property.
|
|
|
|
Undefined for non-str properties.
|
|
|
|
Parameters
|
|
----------
|
|
val : str
|
|
A type:width string from a collection schema.
|
|
|
|
Returns
|
|
-------
|
|
int or None
|
|
|
|
Examples
|
|
--------
|
|
>>> prop_width('str:25')
|
|
25
|
|
>>> prop_width('str')
|
|
80
|
|
|
|
"""
|
|
if val.startswith('str'):
|
|
return int((val.split(":")[1:] or ["80"])[0])
|
|
return None
|
|
|
|
|
|
def prop_type(text):
|
|
"""Returns a schema property's proper Python type.
|
|
|
|
Parameters
|
|
----------
|
|
text : str
|
|
A type name, with or without width.
|
|
|
|
Returns
|
|
-------
|
|
obj
|
|
A Python class.
|
|
|
|
Examples
|
|
--------
|
|
>>> prop_type('int')
|
|
<class 'int'>
|
|
>>> prop_type('str:25')
|
|
<class 'str'>
|
|
|
|
"""
|
|
key = text.split(':')[0]
|
|
return FIELD_TYPES_MAP[key]
|
|
|
|
|
|
def drivers(*args, **kwargs):
|
|
"""Returns a context manager with registered drivers.
|
|
|
|
DEPRECATED
|
|
"""
|
|
warnings.warn("Use fiona.Env() instead.", FionaDeprecationWarning, stacklevel=2)
|
|
|
|
if driver_count == 0:
|
|
log.debug("Creating a chief GDALEnv in drivers()")
|
|
return Env(**kwargs)
|
|
else:
|
|
log.debug("Creating a not-responsible GDALEnv in drivers()")
|
|
return Env(**kwargs)
|
|
|
|
|
|
def bounds(ob):
|
|
"""Returns a (minx, miny, maxx, maxy) bounding box.
|
|
|
|
The ``ob`` may be a feature record or geometry."""
|
|
geom = ob.get('geometry') or ob
|
|
return _bounds(geom)
|