"""Rasterio""" from collections import namedtuple from contextlib import ExitStack import glob import logging from logging import NullHandler import os import platform # On Windows we must explicitly register the directories that contain # the GDAL and supporting DLLs starting with Python 3.8. Presently, we # support the rasterio-wheels location or directories on the system's # executable path. 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 p and glob.glob(os.path.join(p, "gdal*.dll")): os.add_dll_directory(os.path.abspath(p)) from rasterio._base import DatasetBase from rasterio._io import Statistics from rasterio._vsiopener import _opener_registration from rasterio._show_versions import show_versions from rasterio._version import gdal_version, get_geos_version, get_proj_version from rasterio.crs import CRS from rasterio.drivers import driver_from_extension, is_blacklisted from rasterio.dtypes import ( bool_, ubyte, sbyte, uint8, int8, uint16, int16, uint32, int32, int64, uint64, float32, float64, complex_, check_dtype, complex_int16, ) from rasterio.env import ensure_env_with_credentials, Env from rasterio.errors import ( RasterioIOError, DriverCapabilityError, RasterioDeprecationWarning, ) from rasterio.io import ( DatasetReader, get_writer_for_path, get_writer_for_driver, MemoryFile, ) from rasterio.profiles import default_gtiff_profile from rasterio.transform import Affine, guard_transform from rasterio._path import _parse_path, _UnparsedPath # These modules are imported from the Cython extensions, but are also import # here to help tools like cx_Freeze find them automatically import rasterio._err import rasterio.coords import rasterio.enums import rasterio._path try: from rasterio.io import FilePath have_vsi_plugin = True except ImportError: class FilePath: pass have_vsi_plugin = False __all__ = ['band', 'open', 'pad', 'Band', 'Env', 'CRS'] __version__ = "1.4.3" __gdal_version__ = gdal_version() __proj_version__ = ".".join([str(version) for version in get_proj_version()]) __geos_version__ = ".".join([str(version) for version in get_geos_version()]) # Rasterio attaches NullHandler to the 'rasterio' logger and its # descendents. See # https://docs.python.org/2/howto/logging.html#configuring-logging-for-a-library # Applications must attach their own handlers in order to see messages. # See rasterio/rio/main.py for an example. log = logging.getLogger(__name__) log.addHandler(NullHandler()) @ensure_env_with_credentials def open( fp, mode="r", driver=None, width=None, height=None, count=None, crs=None, transform=None, dtype=None, nodata=None, sharing=False, opener=None, **kwargs ): """Open a dataset for reading or writing. The dataset may be located in a local file, in a resource located by a URL, or contained within a stream of bytes. This function accepts different types of fp parameters. However, it is almost always best to pass a string that has a dataset name as its value. These are passed directly to GDAL protocol and format handlers. A path to a zipfile is more efficiently used by GDAL than a Python ZipFile object, for example. In read ('r') or read/write ('r+') mode, no keyword arguments are required: these attributes are supplied by the opened dataset. In write ('w' or 'w+') mode, the driver, width, height, count, and dtype keywords are strictly required. Parameters ---------- fp : str, os.PathLike, file-like, or rasterio.io.MemoryFile A filename or URL, a file object opened in binary ('rb') mode, a Path object, or one of the rasterio classes that provides the dataset-opening interface (has an open method that returns a dataset). Use a string when possible: GDAL can more efficiently access a dataset if it opens it natively. mode : str, optional 'r' (read, the default), 'r+' (read/write), 'w' (write), or 'w+' (write/read). driver : str, optional A short format driver name (e.g. "GTiff" or "JPEG") or a list of such names (see GDAL docs at https://gdal.org/drivers/raster/index.html). In 'w' or 'w+' modes a single name is required. In 'r' or 'r+' modes the driver can usually be omitted. Registered drivers will be tried sequentially until a match is found. When multiple drivers are available for a format such as JPEG2000, one of them can be selected by using this keyword argument. width : int, optional The number of columns of the raster dataset. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. height : int, optional The number of rows of the raster dataset. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. count : int, optional The count of dataset bands. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. crs : str, dict, or CRS, optional The coordinate reference system. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. transform : affine.Affine, optional Affine transformation mapping the pixel space to geographic space. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. dtype : str or numpy.dtype, optional The data type for bands. For example: 'uint8' or `rasterio.uint16`. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. nodata : int, float, or nan, optional Defines the pixel value to be interpreted as not valid data. Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+' modes. sharing : bool, optional To reduce overhead and prevent programs from running out of file descriptors, rasterio maintains a pool of shared low level dataset handles. If True this function will use a shared handle if one is available. Multithreaded programs must avoid sharing and should set *sharing* to `False`. opener : callable, optional A custom dataset opener which can serve GDAL's virtual filesystem machinery via Python file-like objects. The underlying file-like object is obtained by calling *opener* with (*fp*, *mode*) or (*fp*, *mode* + "b") depending on the format driver's native mode. *opener* must return a Python file-like object that provides read, seek, tell, and close methods. Note: only one opener at a time per fp, mode pair is allowed. kwargs : optional These are passed to format drivers as directives for creating or interpreting datasets. For example: in 'w' or 'w+' modes a `tiled=True` keyword argument will direct the GeoTIFF format driver to create a tiled, rather than striped, TIFF. Returns ------- :class:`rasterio.io.DatasetReader` If `mode` is "r". :class:`rasterio.io.DatasetWriter` If `mode` is "r+", "w", or "w+". Raises ------ :class:`TypeError` If arguments are of the wrong Python type. :class:`rasterio.errors.RasterioIOError` If the dataset can not be opened. Such as when there is no dataset with the given name. :class:`rasterio.errors.DriverCapabilityError` If the detected format driver does not support the requested opening mode. Examples -------- To open a local GeoTIFF dataset for reading using standard driver discovery and no directives: >>> import rasterio >>> with rasterio.open('example.tif') as dataset: ... print(dataset.profile) To open a local JPEG2000 dataset using only the JP2OpenJPEG driver: >>> with rasterio.open( ... 'example.jp2', driver='JP2OpenJPEG') as dataset: ... print(dataset.profile) To create a new 8-band, 16-bit unsigned, tiled, and LZW-compressed GeoTIFF with a global extent and 0.5 degree resolution: >>> from rasterio.transform import from_origin >>> with rasterio.open( ... 'example.tif', 'w', driver='GTiff', dtype='uint16', ... width=720, height=360, count=8, crs='EPSG:4326', ... transform=from_origin(-180.0, 90.0, 0.5, 0.5), ... nodata=0, tiled=True, compress='lzw') as dataset: ... dataset.write(...) """ if not isinstance(fp, str): if not ( hasattr(fp, "open") or hasattr(fp, "read") or hasattr(fp, "write") or isinstance(fp, (os.PathLike, MemoryFile, FilePath)) ) or isinstance(fp, DatasetBase): raise TypeError(f"invalid path or file: {fp!r}") if not isinstance(mode, str): raise TypeError(f"invalid mode: {mode!r}") elif mode[0] not in ("r", "w"): raise ValueError(f"invalid mode: {mode!r}") if driver and not isinstance(driver, str): raise TypeError(f"invalid driver: {driver!r}") if dtype and not check_dtype(dtype): raise TypeError(f"invalid dtype: {dtype!r}") if nodata is not None: nodata = float(nodata) if transform: transform = guard_transform(transform) # Check driver/mode blacklist. if driver and is_blacklisted(driver, mode): raise RasterioIOError( "Blacklisted: file cannot be opened by " "driver '{}' in '{}' mode".format(driver, mode) ) # This check should come first, since MemoryFile has read/write methods # TODO: test for a shared base class or abstract type. elif isinstance(fp, (FilePath, MemoryFile)): if mode.startswith("r"): dataset = fp.open(driver=driver, sharing=sharing, **kwargs) # Note: FilePath does not support writing and an exception will # result from this. else: dataset = fp.open( driver=driver, width=width, height=height, count=count, crs=crs, transform=transform, dtype=dtype, nodata=nodata, sharing=sharing, **kwargs ) return dataset # If the fp argument is a file-like object and can be adapted by # rasterio's FilePath we do so. Otherwise, we use a MemoryFile to # hold fp's contents and store that in an ExitStack attached to the # dataset object that we will return. When a dataset's close method # is called, this ExitStack will be unwound and the MemoryFile's # storage will be cleaned up. elif mode == 'r' and hasattr(fp, 'read'): memfile = MemoryFile(fp.read()) fp.seek(0) dataset = memfile.open(driver=driver, sharing=sharing, **kwargs) dataset._env.enter_context(memfile) return dataset elif mode in ('w', 'w+') and hasattr(fp, 'write'): memfile = MemoryFile() dataset = memfile.open( driver=driver, width=width, height=height, count=count, crs=crs, transform=transform, dtype=dtype, nodata=nodata, sharing=sharing, **kwargs ) dataset._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()) dataset._env.callback(func) return dataset # At this point, the fp argument is a string or path-like object # which can be converted to a string. else: stack = ExitStack() if hasattr(fp, "path") and hasattr(fp, "fs"): log.debug("Detected fp is an OpenFile: fp=%r", fp) raw_dataset_path = fp.path opener = fp.fs.open else: raw_dataset_path = os.fspath(fp) try: # when opener is a callable that takes a filename or URL and returns # a file-like object with read, seek, tell, and close methods, we # can register it with GDAL and use it as the basis for a GDAL # virtual filesystem. This is generally better than the FilePath # approach introduced in version 1.3. if opener: vsi_path_ctx = _opener_registration(raw_dataset_path, opener) registered_vsi_path = stack.enter_context(vsi_path_ctx) path = _UnparsedPath(registered_vsi_path) else: path = _parse_path(raw_dataset_path) if mode == "r": dataset = DatasetReader(path, driver=driver, sharing=sharing, **kwargs) elif mode == "r+": dataset = get_writer_for_path(path, driver=driver)( path, mode, driver=driver, sharing=sharing, **kwargs ) elif mode.startswith("w"): if not driver: driver = driver_from_extension(path) writer = get_writer_for_driver(driver) if writer is not None: dataset = writer( path, mode, driver=driver, width=width, height=height, count=count, crs=crs, transform=transform, dtype=dtype, nodata=nodata, sharing=sharing, **kwargs ) else: raise DriverCapabilityError( "Writer does not exist for driver: %s" % str(driver) ) else: raise DriverCapabilityError( "mode must be one of 'r', 'r+', or 'w', not %s" % mode) except Exception: stack.close() raise dataset._env = stack return dataset Band = namedtuple('Band', ['ds', 'bidx', 'dtype', 'shape']) Band.__doc__ = """ Band(s) of a Dataset. Parameters ---------- ds: dataset object An opened rasterio dataset object. bidx: int or sequence of ints Band number(s), index starting at 1. dtype: str rasterio data type of the data. shape: tuple Width, height of band. """ def band(ds, bidx): """A dataset and one or more of its bands Parameters ---------- ds: dataset object An opened rasterio dataset object. bidx: int or sequence of ints Band number(s), index starting at 1. Returns ------- Band """ return Band(ds, bidx, set(ds.dtypes).pop(), ds.shape) def pad(array, transform, pad_width, mode=None, **kwargs): """pad array and adjust affine transform matrix. Parameters ---------- array: numpy.ndarray Numpy ndarray, for best results a 2D array transform: Affine transform transform object mapping pixel space to coordinates pad_width: int number of pixels to pad array on all four mode: str or function define the method for determining padded values Returns ------- (array, transform): tuple Tuple of new array and affine transform Notes ----- See :func:`numpy.pad` for details on mode and other kwargs. """ import numpy as np transform = guard_transform(transform) padded_array = np.pad(array, pad_width, mode, **kwargs) padded_trans = list(transform) padded_trans[2] -= pad_width * padded_trans[0] padded_trans[5] -= pad_width * padded_trans[4] return padded_array, Affine(*padded_trans[:6])