""" 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') >>> prop_type('str:25') """ 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)