def write(self, process_tile, data):
        """
        Write data into output format.

        Parameters
        ----------
        process_tile : BufferedTile or tile index tuple
            process tile
        data : NumPy array or features
            data to be written
        """
        if isinstance(process_tile, tuple):
            process_tile = self.config.process_pyramid.tile(*process_tile)
        elif not isinstance(process_tile, BufferedTile):
            raise ValueError("invalid process_tile type: %s" % type(process_tile))
        if self.config.mode not in ["continue", "overwrite"]:
            raise ValueError("cannot write output in current process mode")

        if self.config.mode == "continue" and (
            self.config.output.tiles_exist(process_tile)
        ):
            message = "output exists, not overwritten"
            logger.debug((process_tile.id, message))
            return ProcessInfo(
                tile=process_tile,
                processed=False,
                process_msg=None,
                written=False,
                write_msg=message
            )
        elif data is None:
            message = "output empty, nothing written"
            logger.debug((process_tile.id, message))
            return ProcessInfo(
                tile=process_tile,
                processed=False,
                process_msg=None,
                written=False,
                write_msg=message
            )
        else:
            with Timer() as t:
                self.config.output.write(process_tile=process_tile, data=data)
            message = "output written in %s" % t
            logger.debug((process_tile.id, message))
            return ProcessInfo(
                tile=process_tile,
                processed=False,
                process_msg=None,
                written=True,
                write_msg=message
            )


def get_raw_output(self, tile, _baselevel_readonly=False):
        """
        Get output raw data.

        This function won't work with multiprocessing, as it uses the
        ``threading.Lock()`` class.

        Parameters
        ----------
        tile : tuple, Tile or BufferedTile
            If a tile index is given, a tile from the output pyramid will be
            assumed. Tile cannot be bigger than process tile!

        Returns
        -------
        data : NumPy array or features
            process output
        """
        if not isinstance(tile, (BufferedTile, tuple)):
            raise TypeError("'tile' must be a tuple or BufferedTile")
        if isinstance(tile, tuple):
            tile = self.config.output_pyramid.tile(*tile)
        if _baselevel_readonly:
            tile = self.config.baselevels["tile_pyramid"].tile(*tile.id)

        # Return empty data if zoom level is outside of process zoom levels.
        if tile.zoom not in self.config.zoom_levels:
            return self.config.output.empty(tile)

        # TODO implement reprojection
        if tile.crs != self.config.process_pyramid.crs:
            raise NotImplementedError(
                "reprojection between processes not yet implemented"
            )

        if self.config.mode == "memory":
            # Determine affected process Tile and check whether it is already
            # cached.
            process_tile = self.config.process_pyramid.intersecting(tile)[0]
            return self._extract(
                in_tile=process_tile,
                in_data=self._execute_using_cache(process_tile),
                out_tile=tile
            )

        # TODO: cases where tile intersects with multiple process tiles
        process_tile = self.config.process_pyramid.intersecting(tile)[0]

        # get output_tiles that intersect with current tile
        if tile.pixelbuffer > self.config.output.pixelbuffer:
            output_tiles = list(self.config.output_pyramid.tiles_from_bounds(
                tile.bounds, tile.zoom
            ))
        else:
            output_tiles = self.config.output_pyramid.intersecting(tile)

        if self.config.mode == "readonly" or _baselevel_readonly:
            if self.config.output.tiles_exist(process_tile):
                return self._read_existing_output(tile, output_tiles)
            else:
                return self.config.output.empty(tile)
        elif self.config.mode == "continue" and not _baselevel_readonly:
            if self.config.output.tiles_exist(process_tile):
                return self._read_existing_output(tile, output_tiles)
            else:
                return self._process_and_overwrite_output(tile, process_tile)
        elif self.config.mode == "overwrite" and not _baselevel_readonly:
            return self._process_and_overwrite_output(tile, process_tile)


def _extract(self, in_tile=None, in_data=None, out_tile=None):
        """Extract data from tile."""
        return self.config.output.extract_subset(
            input_data_tiles=[(in_tile, in_data)],
            out_tile=out_tile
        )

def _extract ( self , in_tile = None , in_data = None , out_tile = None ) : return self . config . output . extract_subset ( input_data_tiles = [ ( in_tile , in_data ) ] , out_tile = out_tile )


def read(self, **kwargs):
        """
        Read existing output data from a previous run.

        Returns
        -------
        process output : NumPy array (raster) or feature iterator (vector)
        """
        if self.tile.pixelbuffer > self.config.output.pixelbuffer:
            output_tiles = list(self.config.output_pyramid.tiles_from_bounds(
                self.tile.bounds, self.tile.zoom
            ))
        else:
            output_tiles = self.config.output_pyramid.intersecting(self.tile)
        return self.config.output.extract_subset(
            input_data_tiles=[
                (output_tile, self.config.output.read(output_tile))
                for output_tile in output_tiles
            ],
            out_tile=self.tile,
        )

def read ( self , * * kwargs ) : if self . tile . pixelbuffer > self . config . output . pixelbuffer : output_tiles = list ( self . config . output_pyramid . tiles_from_bounds ( self . tile . bounds , self . tile . zoom ) ) else : output_tiles = self . config . output_pyramid . intersecting ( self . tile ) return self . config . output . extract_subset ( input_data_tiles = [ ( output_tile , self . config . output . read ( output_tile ) ) for output_tile in output_tiles ] , out_tile = self . tile , )

Read existing output data from a previous run .


def open(self, input_id, **kwargs):
        """
        Open input data.

        Parameters
        ----------
        input_id : string
            input identifier from configuration file or file path
        kwargs : driver specific parameters (e.g. resampling)

        Returns
        -------
        tiled input data : InputTile
            reprojected input data within tile
        """
        if not isinstance(input_id, str):
            return input_id.open(self.tile, **kwargs)
        if input_id not in self.params["input"]:
            raise ValueError("%s not found in config as input file" % input_id)
        return self.params["input"][input_id].open(self.tile, **kwargs)

def
open
(
self
,
input_id
,
*
*
kwargs
)
:
if
not
isinstance
(
input_id
,
str
)
:
return
input_id
.
open
(
self
.
tile
,
*
*
kwargs
)
if
input_id
not
in
self
.
params
[
"input"
]
:
raise
ValueError
(
"%s not found in config as input file"
%
input_id
)
return
self
.
params
[
"input"
]
[
input_id
]
.
open
(
self
.
tile
,
*
*
kwargs
)


def hillshade(
        self, elevation, azimuth=315.0, altitude=45.0, z=1.0, scale=1.0
    ):
        """
        Calculate hillshading from elevation data.

        Parameters
        ----------
        elevation : array
            input elevation data
        azimuth : float
            horizontal angle of light source (315: North-West)
        altitude : float
            vertical angle of light source (90 would result in slope shading)
        z : float
            vertical exaggeration factor
        scale : float
            scale factor of pixel size units versus height units (insert 112000
            when having elevation values in meters in a geodetic projection)

        Returns
        -------
        hillshade : array
        """
        return commons_hillshade.hillshade(
            elevation, self, azimuth, altitude, z, scale)

def hillshade ( self , elevation , azimuth = 315.0 , altitude = 45.0 , z = 1.0 , scale = 1.0 ) : return commons_hillshade . hillshade ( elevation , self , azimuth , altitude , z , scale )

Calculate hillshading from elevation data .


def contours(
        self, elevation, interval=100, field='elev', base=0
    ):
        """
        Extract contour lines from elevation data.

        Parameters
        ----------
        elevation : array
            input elevation data
        interval : integer
            elevation value interval when drawing contour lines
        field : string
            output field name containing elevation value
        base : integer
            elevation base value the intervals are computed from

        Returns
        -------
        contours : iterable
            contours as GeoJSON-like pairs of properties and geometry
        """
        return commons_contours.extract_contours(
            elevation, self.tile, interval=interval, field=field, base=base)

def contours ( self , elevation , interval = 100 , field = 'elev' , base = 0 ) : return commons_contours . extract_contours ( elevation , self . tile , interval = interval , field = field , base = base )

Extract contour lines from elevation data .


def clip(
        self, array, geometries, inverted=False, clip_buffer=0
    ):
        """
        Clip array by geometry.

        Parameters
        ----------
        array : array
            raster data to be clipped
        geometries : iterable
            geometries used to clip source array
        inverted : bool
            invert clipping (default: False)
        clip_buffer : int
            buffer (in pixels) geometries before applying clip

        Returns
        -------
        clipped array : array
        """
        return commons_clip.clip_array_with_vector(
            array, self.tile.affine, geometries,
            inverted=inverted, clip_buffer=clip_buffer*self.tile.pixel_x_size)

def clip ( self , array , geometries , inverted = False , clip_buffer = 0 ) : return commons_clip . clip_array_with_vector ( array , self . tile . affine , geometries , inverted = inverted , clip_buffer = clip_buffer * self . tile . pixel_x_size )


def clip_array_with_vector(
    array, array_affine, geometries, inverted=False, clip_buffer=0
):
    """
    Clip input array with a vector list.

    Parameters
    ----------
    array : array
        input raster data
    array_affine : Affine
        Affine object describing the raster's geolocation
    geometries : iterable
        iterable of dictionaries, where every entry has a 'geometry' and
        'properties' key.
    inverted : bool
        invert clip (default: False)
    clip_buffer : integer
        buffer (in pixels) geometries before clipping

    Returns
    -------
    clipped array : array
    """
    # buffer input geometries and clean up
    buffered_geometries = []
    for feature in geometries:
        feature_geom = to_shape(feature["geometry"])
        if feature_geom.is_empty:
            continue
        if feature_geom.geom_type == "GeometryCollection":
            # for GeometryCollections apply buffer to every subgeometry
            # and make union
            buffered_geom = unary_union([
                g.buffer(clip_buffer) for g in feature_geom])
        else:
            buffered_geom = feature_geom.buffer(clip_buffer)
        if not buffered_geom.is_empty:
            buffered_geometries.append(buffered_geom)

    # mask raster by buffered geometries
    if buffered_geometries:
        if array.ndim == 2:
            return ma.masked_array(
                array, geometry_mask(
                    buffered_geometries, array.shape, array_affine,
                    invert=inverted))
        elif array.ndim == 3:
            mask = geometry_mask(
                buffered_geometries, (array.shape[1], array.shape[2]),
                array_affine, invert=inverted)
            return ma.masked_array(
                array, mask=np.stack((mask for band in array)))

    # if no geometries, return unmasked array
    else:
        fill = False if inverted else True
        return ma.masked_array(
            array, mask=np.full(array.shape, fill, dtype=bool))


def pyramid(
    input_raster,
    output_dir,
    pyramid_type=None,
    output_format=None,
    resampling_method=None,
    scale_method=None,
    zoom=None,
    bounds=None,
    overwrite=False,
    debug=False
):
    """Create tile pyramid out of input raster."""
    bounds = bounds if bounds else None
    options = dict(
        pyramid_type=pyramid_type,
        scale_method=scale_method,
        output_format=output_format,
        resampling=resampling_method,
        zoom=zoom,
        bounds=bounds,
        overwrite=overwrite
    )
    raster2pyramid(input_raster, output_dir, options)

def pyramid ( input_raster , output_dir , pyramid_type = None , output_format = None , resampling_method = None , scale_method = None , zoom = None , bounds = None , overwrite = False , debug = False ) : bounds = bounds if bounds else None options = dict ( pyramid_type = pyramid_type , scale_method = scale_method , output_format = output_format , resampling = resampling_method , zoom = zoom , bounds = bounds , overwrite = overwrite ) raster2pyramid ( input_raster , output_dir , options )

Create tile pyramid out of input raster .


def raster2pyramid(input_file, output_dir, options):
    """Create a tile pyramid out of an input raster dataset."""
    pyramid_type = options["pyramid_type"]
    scale_method = options["scale_method"]
    output_format = options["output_format"]
    resampling = options["resampling"]
    zoom = options["zoom"]
    bounds = options["bounds"]
    mode = "overwrite" if options["overwrite"] else "continue"

    # Prepare process parameters
    minzoom, maxzoom = _get_zoom(zoom, input_file, pyramid_type)
    with rasterio.open(input_file, "r") as input_raster:
        output_bands = input_raster.count
        input_dtype = input_raster.dtypes[0]
        output_dtype = input_raster.dtypes[0]
        nodataval = input_raster.nodatavals[0]
        nodataval = nodataval if nodataval else 0
        if output_format == "PNG" and output_bands > 3:
            output_bands = 3
            output_dtype = 'uint8'
        scales_minmax = ()
        if scale_method == "dtype_scale":
            for index in range(1, output_bands+1):
                scales_minmax += (DTYPE_RANGES[input_dtype], )
        elif scale_method == "minmax_scale":
            for index in range(1, output_bands+1):
                band = input_raster.read(index)
                scales_minmax += ((band.min(), band.max()), )
        elif scale_method == "crop":
            for index in range(1, output_bands+1):
                scales_minmax += ((0, 255), )
        if input_dtype == "uint8":
            scale_method = None
            scales_minmax = ()
            for index in range(1, output_bands+1):
                scales_minmax += ((None, None), )

    # Create configuration
    config = dict(
        process="mapchete.processes.pyramid.tilify",
        output={
            "path": output_dir,
            "format": output_format,
            "bands": output_bands,
            "dtype": output_dtype
            },
        pyramid=dict(pixelbuffer=5, grid=pyramid_type),
        scale_method=scale_method,
        scales_minmax=scales_minmax,
        input={"raster": input_file},
        config_dir=os.getcwd(),
        zoom_levels=dict(min=minzoom, max=maxzoom),
        nodataval=nodataval,
        resampling=resampling,
        bounds=bounds,
        baselevel={"zoom": maxzoom, "resampling": resampling},
        mode=mode
    )

    # create process
    with mapchete.open(config, zoom=zoom, bounds=bounds) as mp:
        # prepare output directory
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        # run process
        mp.batch_process(zoom=[minzoom, maxzoom])

def
raster2pyramid
(
input_file
,
output_dir
,
options
)
:
pyramid_type
=
options
[
"pyramid_type"
]
scale_method
=
options
[
"scale_method"
]
output_format
=
options
[
"output_format"
]
resampling
=
options
[
"resampling"
]
zoom
=
options
[
"zoom"
]
bounds
=
options
[
"bounds"
]
mode
=
"overwrite"
if
options
[
"overwrite"
]
else
"continue"
# Prepare process parameters
minzoom
,
maxzoom
=
_get_zoom
(
zoom
,
input_file
,
pyramid_type
)
with
rasterio
.
open
(
input_file
,
"r"
)
as
input_raster
:
output_bands
=
input_raster
.
count
input_dtype
=
input_raster
.
dtypes
[
0
]
output_dtype
=
input_raster
.
dtypes
[
0
]
nodataval
=
input_raster
.
nodatavals
[
0
]
nodataval
=
nodataval
if
nodataval
else
0
if
output_format
==
"PNG"
and
output_bands
>
3
:
output_bands
=
3
output_dtype
=
'uint8'
scales_minmax
=
(
)
if
scale_method
==
"dtype_scale"
:
for
index
in
range
(
1
,
output_bands
+
1
)
:
scales_minmax
+=
(
DTYPE_RANGES
[
input_dtype
]
,
)
elif
scale_method
==
"minmax_scale"
:
for
index
in
range
(
1
,
output_bands
+
1
)
:
band
=
input_raster
.
read
(
index
)
scales_minmax
+=
(
(
band
.
min
(
)
,
band
.
max
(
)
)
,
)
elif
scale_method
==
"crop"
:
for
index
in
range
(
1
,
output_bands
+
1
)
:
scales_minmax
+=
(
(
0
,
255
)
,
)
if
input_dtype
==
"uint8"
:
scale_method
=
scales_minmax
=
(
)
for
index
in
range
(
1
,
output_bands
+
1
)
:
scales_minmax
+=
(
(
,
)
,
)
# Create configuration
config
=
dict
(
process
=
"mapchete.processes.pyramid.tilify"
,
output
=
{
"path"
:
output_dir
,
"format"
:
output_format
,
"bands"
:
output_bands
,
"dtype"
:
output_dtype
}
,
pyramid
=
dict
(
pixelbuffer
=
5
,
grid
=
pyramid_type
)
,
scale_method
=
scale_method
,
scales_minmax
=
scales_minmax
,
input
=
{
"raster"
:
input_file
}
,
config_dir
=
os
.
getcwd
(
)
,
zoom_levels
=
dict
(
min
=
minzoom
,
max
=
maxzoom
)
,
nodataval
=
nodataval
,
resampling
=
resampling
,
bounds
=
bounds
,
baselevel
=
{
"zoom"
:
maxzoom
,
"resampling"
:
resampling
}
,
mode
=
mode
)
# create process
with
mapchete
.
open
(
config
,
zoom
=
zoom
,
bounds
=
bounds
)
as
mp
:
# prepare output directory
if
not
os
.
path
.
exists
(
output_dir
)
:
os
.
makedirs
(
output_dir
)
# run process
mp
.
batch_process
(
zoom
=
[
minzoom
,
maxzoom
]
)

Create a tile pyramid out of an input raster dataset .


def _get_zoom(zoom, input_raster, pyramid_type):
    """Determine minimum and maximum zoomlevel."""
    if not zoom:
        minzoom = 1
        maxzoom = get_best_zoom_level(input_raster, pyramid_type)
    elif len(zoom) == 1:
        minzoom = zoom[0]
        maxzoom = zoom[0]
    elif len(zoom) == 2:
        if zoom[0] < zoom[1]:
            minzoom = zoom[0]
            maxzoom = zoom[1]
        else:
            minzoom = zoom[1]
            maxzoom = zoom[0]
    return minzoom, maxzoom

def _get_zoom ( zoom , input_raster , pyramid_type ) : if not zoom : minzoom = 1 maxzoom = get_best_zoom_level ( input_raster , pyramid_type ) elif len ( zoom ) == 1 : minzoom = zoom [ 0 ] maxzoom = zoom [ 0 ] elif len ( zoom ) == 2 : if zoom [ 0 ] < zoom [ 1 ] : minzoom = zoom [ 0 ] maxzoom = zoom [ 1 ] else : minzoom = zoom [ 1 ] maxzoom = zoom [ 0 ] return minzoom , maxzoom

Determine minimum and maximum zoomlevel .


def validate_values(config, values):
    """
    Validate whether value is found in config and has the right type.

    Parameters
    ----------
    config : dict
        configuration dictionary
    values : list
        list of (str, type) tuples of values and value types expected in config

    Returns
    -------
    True if config is valid.

    Raises
    ------
    Exception if value is not found or has the wrong type.
    """
    if not isinstance(config, dict):
        raise TypeError("config must be a dictionary")
    for value, vtype in values:
        if value not in config:
            raise ValueError("%s not given" % value)
        if not isinstance(config[value], vtype):
            raise TypeError("%s must be %s" % (value, vtype))
    return True

def
validate_values
(
config
,
values
)
:
if
not
isinstance
(
config
,
dict
)
:
raise
TypeError
(
"config must be a dictionary"
)
for
value
,
vtype
in
values
:
if
value
not
in
config
:
raise
ValueError
(
"%s not given"
%
value
)
if
not
isinstance
(
config
[
value
]
,
vtype
)
:
raise
TypeError
(
"%s must be %s"
%
(
value
,
vtype
)
)
return
True

Validate whether value is found in config and has the right type .


def get_hash(x):
    """Return hash of x."""
    if isinstance(x, str):
        return hash(x)
    elif isinstance(x, dict):
        return hash(yaml.dump(x))

def get_hash ( x ) : if isinstance ( x , str ) : return hash ( x ) elif isinstance ( x , dict ) : return hash ( yaml . dump ( x ) )


def get_zoom_levels(process_zoom_levels=None, init_zoom_levels=None):
    """Validate and return zoom levels."""
    process_zoom_levels = _validate_zooms(process_zoom_levels)
    if init_zoom_levels is None:
        return process_zoom_levels
    else:
        init_zoom_levels = _validate_zooms(init_zoom_levels)
        if not set(init_zoom_levels).issubset(set(process_zoom_levels)):
            raise MapcheteConfigError(
                "init zooms must be a subset of process zoom")
        return init_zoom_levels


def snap_bounds(bounds=None, pyramid=None, zoom=None):
    """
    Snaps bounds to tiles boundaries of specific zoom level.

    Parameters
    ----------
    bounds : bounds to be snapped
    pyramid : TilePyramid
    zoom : int

    Returns
    -------
    Bounds(left, bottom, right, top)
    """
    if not isinstance(bounds, (tuple, list)):
        raise TypeError("bounds must be either a tuple or a list")
    if len(bounds) != 4:
        raise ValueError("bounds has to have exactly four values")
    if not isinstance(pyramid, BufferedTilePyramid):
        raise TypeError("pyramid has to be a BufferedTilePyramid")

    bounds = Bounds(*bounds)
    lb = pyramid.tile_from_xy(bounds.left, bounds.bottom, zoom, on_edge_use="rt").bounds
    rt = pyramid.tile_from_xy(bounds.right, bounds.top, zoom, on_edge_use="lb").bounds
    return Bounds(lb.left, lb.bottom, rt.right, rt.top)

Snaps bounds to tiles boundaries of specific zoom level .


def clip_bounds(bounds=None, clip=None):
    """
    Clips bounds by clip.

    Parameters
    ----------
    bounds : bounds to be clipped
    clip : clip bounds

    Returns
    -------
    Bounds(left, bottom, right, top)
    """
    bounds = Bounds(*bounds)
    clip = Bounds(*clip)
    return Bounds(
        max(bounds.left, clip.left),
        max(bounds.bottom, clip.bottom),
        min(bounds.right, clip.right),
        min(bounds.top, clip.top)
    )

def clip_bounds ( bounds = None , clip = None ) : bounds = Bounds ( * bounds ) clip = Bounds ( * clip ) return Bounds ( max ( bounds . left , clip . left ) , max ( bounds . bottom , clip . bottom ) , min ( bounds . right , clip . right ) , min ( bounds . top , clip . top ) )


def _validate_zooms(zooms):
    """
    Return a list of zoom levels.

    Following inputs are converted:
    - int --> [int]
    - dict{min, max} --> range(min, max + 1)
    - [int] --> [int]
    - [int, int] --> range(smaller int, bigger int + 1)
    """
    if isinstance(zooms, dict):
        if any([a not in zooms for a in ["min", "max"]]):
            raise MapcheteConfigError("min and max zoom required")
        zmin = _validate_zoom(zooms["min"])
        zmax = _validate_zoom(zooms["max"])
        if zmin > zmax:
            raise MapcheteConfigError(
                "max zoom must not be smaller than min zoom")
        return list(range(zmin, zmax + 1))
    elif isinstance(zooms, list):
        if len(zooms) == 1:
            return zooms
        elif len(zooms) == 2:
            zmin, zmax = sorted([_validate_zoom(z) for z in zooms])
            return list(range(zmin, zmax + 1))
        else:
            return zooms
    else:
        return [_validate_zoom(zooms)]


def _raw_at_zoom(config, zooms):
    """Return parameter dictionary per zoom level."""
    params_per_zoom = {}
    for zoom in zooms:
        params = {}
        for name, element in config.items():
            if name not in _RESERVED_PARAMETERS:
                out_element = _element_at_zoom(name, element, zoom)
                if out_element is not None:
                    params[name] = out_element
        params_per_zoom[zoom] = params
    return params_per_zoom

def _raw_at_zoom ( config , zooms ) : params_per_zoom = { } for zoom in zooms : params = { } for name , element in config . items ( ) : if name not in _RESERVED_PARAMETERS : out_element = _element_at_zoom ( name , element , zoom ) if out_element is not None : params [ name ] = out_element params_per_zoom [ zoom ] = params return params_per_zoom

Return parameter dictionary per zoom level .


def _element_at_zoom(name, element, zoom):
        """
        Return the element filtered by zoom level.

        - An input integer or float gets returned as is.
        - An input string is checked whether it starts with "zoom". Then, the
          provided zoom level gets parsed and compared with the actual zoom
          level. If zoom levels match, the element gets returned.
        TODOs/gotchas:
        - Elements are unordered, which can lead to unexpected results when
          defining the YAML config.
        - Provided zoom levels for one element in config file are not allowed
          to "overlap", i.e. there is not yet a decision mechanism implemented
          which handles this case.
        """
        # If element is a dictionary, analyze subitems.
        if isinstance(element, dict):
            if "format" in element:
                # we have an input or output driver here
                return element
            out_elements = {}
            for sub_name, sub_element in element.items():
                out_element = _element_at_zoom(sub_name, sub_element, zoom)
                if name == "input":
                    out_elements[sub_name] = out_element
                elif out_element is not None:
                    out_elements[sub_name] = out_element
            # If there is only one subelement, collapse unless it is
            # input. In such case, return a dictionary.
            if len(out_elements) == 1 and name != "input":
                return next(iter(out_elements.values()))
            # If subelement is empty, return None
            if len(out_elements) == 0:
                return None
            return out_elements
        # If element is a zoom level statement, filter element.
        elif isinstance(name, str):
            if name.startswith("zoom"):
                return _filter_by_zoom(
                    conf_string=name.strip("zoom").strip(), zoom=zoom,
                    element=element)
            # If element is a string but not a zoom level statement, return
            # element.
            else:
                return element
        # Return all other types as they are.
        else:
            return element

Return the element filtered by zoom level .


def _filter_by_zoom(element=None, conf_string=None, zoom=None):
    """Return element only if zoom condition matches with config string."""
    for op_str, op_func in [
        # order of operators is important:
        # prematurely return in cases of "<=" or ">=", otherwise
        # _strip_zoom() cannot parse config strings starting with "<"
        # or ">"
        ("=", operator.eq),
        ("<=", operator.le),
        (">=", operator.ge),
        ("<", operator.lt),
        (">", operator.gt),
    ]:
        if conf_string.startswith(op_str):
            return element if op_func(zoom, _strip_zoom(conf_string, op_str)) else None

Return element only if zoom condition matches with config string .


def _strip_zoom(input_string, strip_string):
    """Return zoom level as integer or throw error."""
    try:
        return int(input_string.strip(strip_string))
    except Exception as e:
        raise MapcheteConfigError("zoom level could not be determined: %s" % e)

Return zoom level as integer or throw error .


def _flatten_tree(tree, old_path=None):
    """Flatten dict tree into dictionary where keys are paths of old dict."""
    flat_tree = []
    for key, value in tree.items():
        new_path = "/".join([old_path, key]) if old_path else key
        if isinstance(value, dict) and "format" not in value:
            flat_tree.extend(_flatten_tree(value, old_path=new_path))
        else:
            flat_tree.append((new_path, value))
    return flat_tree

def _flatten_tree ( tree , old_path = None ) : flat_tree = [ ] for key , value in tree . items ( ) : new_path = "/" . join ( [ old_path , key ] ) if old_path else key if isinstance ( value , dict ) and "format" not in value : flat_tree . extend ( _flatten_tree ( value , old_path = new_path ) ) else : flat_tree . append ( ( new_path , value ) ) return flat_tree

Flatten dict tree into dictionary where keys are paths of old dict .


def _unflatten_tree(flat):
    """Reverse tree flattening."""
    tree = {}
    for key, value in flat.items():
        path = key.split("/")
        # we are at the end of a branch
        if len(path) == 1:
            tree[key] = value
        # there are more branches
        else:
            # create new dict
            if not path[0] in tree:
                tree[path[0]] = _unflatten_tree({"/".join(path[1:]): value})
            # add keys to existing dict
            else:
                branch = _unflatten_tree({"/".join(path[1:]): value})
                if not path[1] in tree[path[0]]:
                    tree[path[0]][path[1]] = branch[path[1]]
                else:
                    tree[path[0]][path[1]].update(branch[path[1]])
    return tree

def
_unflatten_tree
(
flat
)
:
tree
=
{
}
for
key
,
value
in
flat
.
items
(
)
:
path
=
key
.
split
(
"/"
)
# we are at the end of a branch
if
len
(
path
)
==
1
:
tree
[
key
]
=
value
# there are more branches
else
:
# create new dict
if
not
path
[
0
]
in
tree
:
tree
[
path
[
0
]
]
=
_unflatten_tree
(
{
"/"
.
join
(
path
[
1
:
]
)
:
value
}
)
# add keys to existing dict
else
:
branch
=
_unflatten_tree
(
{
"/"
.
join
(
path
[
1
:
]
)
:
value
}
)
if
not
path
[
1
]
in
tree
[
path
[
0
]
]
:
tree
[
path
[
0
]
]
[
path
[
1
]
]
=
branch
[
path
[
1
]
]
else
:
tree
[
path
[
0
]
]
[
path
[
1
]
]
.
update
(
branch
[
path
[
1
]
]
)
return
tree


def bounds(self):
        """Process bounds as defined in the configuration."""
        if self._raw["bounds"] is None:
            return self.process_pyramid.bounds
        else:
            return Bounds(*_validate_bounds(self._raw["bounds"]))

def bounds ( self ) : if self . _raw [ "bounds" ] is None : return self . process_pyramid . bounds else : return Bounds ( * _validate_bounds ( self . _raw [ "bounds" ] ) )

Process bounds as defined in the configuration .


def init_bounds(self):
        """
        Process bounds this process is currently initialized with.

        This gets triggered by using the ``init_bounds`` kwarg. If not set, it will
        be equal to self.bounds.
        """
        if self._raw["init_bounds"] is None:
            return self.bounds
        else:
            return Bounds(*_validate_bounds(self._raw["init_bounds"]))

def init_bounds ( self ) : if self . _raw [ "init_bounds" ] is None : return self . bounds else : return Bounds ( * _validate_bounds ( self . _raw [ "init_bounds" ] ) )

Process bounds this process is currently initialized with .


def effective_bounds(self):
        """
        Effective process bounds required to initialize inputs.

        Process bounds sometimes have to be larger, because all intersecting process
        tiles have to be covered as well.
        """
        return snap_bounds(
            bounds=clip_bounds(bounds=self.init_bounds, clip=self.process_pyramid.bounds),
            pyramid=self.process_pyramid,
            zoom=min(
                self.baselevels["zooms"]
            ) if self.baselevels else min(
                self.init_zoom_levels
            )
        )

def effective_bounds ( self ) : return snap_bounds ( bounds = clip_bounds ( bounds = self . init_bounds , clip = self . process_pyramid . bounds ) , pyramid = self . process_pyramid , zoom = min ( self . baselevels [ "zooms" ] ) if self . baselevels else min ( self . init_zoom_levels ) )

Effective process bounds required to initialize inputs .


def output(self):
        """Output object of driver."""
        output_params = dict(
            self._raw["output"],
            grid=self.output_pyramid.grid,
            pixelbuffer=self.output_pyramid.pixelbuffer,
            metatiling=self.output_pyramid.metatiling
        )
        if "path" in output_params:
            output_params.update(
                path=absolute_path(path=output_params["path"], base_dir=self.config_dir)
            )

        if "format" not in output_params:
            raise MapcheteConfigError("output format not specified")

        if output_params["format"] not in available_output_formats():
            raise MapcheteConfigError(
                "format %s not available in %s" % (
                    output_params["format"], str(available_output_formats())
                )
            )
        writer = load_output_writer(output_params)
        try:
            writer.is_valid_with_config(output_params)
        except Exception as e:
            logger.exception(e)
            raise MapcheteConfigError(
                "driver %s not compatible with configuration: %s" % (
                    writer.METADATA["driver_name"], e
                )
            )
        return writer


def input(self):
        """
        Input items used for process stored in a dictionary.

        Keys are the hashes of the input parameters, values the respective
        InputData classes.
        """
        # the delimiters are used by some input drivers
        delimiters = dict(
            zoom=self.init_zoom_levels,
            bounds=self.init_bounds,
            process_bounds=self.bounds,
            effective_bounds=self.effective_bounds
        )

        # get input items only of initialized zoom levels
        raw_inputs = {
            # convert input definition to hash
            get_hash(v): v
            for zoom in self.init_zoom_levels
            if "input" in self._params_at_zoom[zoom]
            # to preserve file groups, "flatten" the input tree and use
            # the tree paths as keys
            for key, v in _flatten_tree(self._params_at_zoom[zoom]["input"])
            if v is not None
        }

        initalized_inputs = {}
        for k, v in raw_inputs.items():

            # for files and tile directories
            if isinstance(v, str):
                logger.debug("load input reader for simple input %s",  v)
                try:
                    reader = load_input_reader(
                        dict(
                            path=absolute_path(path=v, base_dir=self.config_dir),
                            pyramid=self.process_pyramid,
                            pixelbuffer=self.process_pyramid.pixelbuffer,
                            delimiters=delimiters
                        ),
                        readonly=self.mode == "readonly")
                except Exception as e:
                    logger.exception(e)
                    raise MapcheteDriverError("error when loading input %s: %s" % (v, e))
                logger.debug("input reader for simple input %s is %s", v, reader)

            # for abstract inputs
            elif isinstance(v, dict):
                logger.debug("load input reader for abstract input %s", v)
                try:
                    reader = load_input_reader(
                        dict(
                            abstract=deepcopy(v),
                            pyramid=self.process_pyramid,
                            pixelbuffer=self.process_pyramid.pixelbuffer,
                            delimiters=delimiters,
                            conf_dir=self.config_dir
                        ),
                        readonly=self.mode == "readonly")
                except Exception as e:
                    logger.exception(e)
                    raise MapcheteDriverError("error when loading input %s: %s" % (v, e))
                logger.debug("input reader for abstract input %s is %s", v, reader)
            else:
                raise MapcheteConfigError("invalid input type %s", type(v))
            # trigger bbox creation
            reader.bbox(out_crs=self.process_pyramid.crs)
            initalized_inputs[k] = reader

        return initalized_inputs

Input items used for process stored in a dictionary .


def baselevels(self):
        """
        Optional baselevels configuration.

        baselevels:
            min: <zoom>
            max: <zoom>
            lower: <resampling method>
            higher: <resampling method>
        """
        if "baselevels" not in self._raw:
            return {}
        baselevels = self._raw["baselevels"]
        minmax = {k: v for k, v in baselevels.items() if k in ["min", "max"]}

        if not minmax:
            raise MapcheteConfigError("no min and max values given for baselevels")
        for v in minmax.values():
            if not isinstance(v, int) or v < 0:
                raise MapcheteConfigError(
                    "invalid baselevel zoom parameter given: %s" % minmax.values()
                )

        zooms = list(range(
            minmax.get("min", min(self.zoom_levels)),
            minmax.get("max", max(self.zoom_levels)) + 1)
        )

        if not set(self.zoom_levels).difference(set(zooms)):
            raise MapcheteConfigError("baselevels zooms fully cover process zooms")

        return dict(
            zooms=zooms,
            lower=baselevels.get("lower", "nearest"),
            higher=baselevels.get("higher", "nearest"),
            tile_pyramid=BufferedTilePyramid(
                self.output_pyramid.grid,
                pixelbuffer=self.output_pyramid.pixelbuffer,
                metatiling=self.process_pyramid.metatiling
            )
        )


def params_at_zoom(self, zoom):
        """
        Return configuration parameters snapshot for zoom as dictionary.

        Parameters
        ----------
        zoom : int
            zoom level

        Returns
        -------
        configuration snapshot : dictionary
        zoom level dependent process configuration
        """
        if zoom not in self.init_zoom_levels:
            raise ValueError(
                "zoom level not available with current configuration")
        out = dict(self._params_at_zoom[zoom], input={}, output=self.output)
        if "input" in self._params_at_zoom[zoom]:
            flat_inputs = {}
            for k, v in _flatten_tree(self._params_at_zoom[zoom]["input"]):
                if v is None:
                    flat_inputs[k] = None
                else:
                    flat_inputs[k] = self.input[get_hash(v)]
            out["input"] = _unflatten_tree(flat_inputs)
        else:
            out["input"] = {}
        return out

Return configuration parameters snapshot for zoom as dictionary .


def area_at_zoom(self, zoom=None):
        """
        Return process bounding box for zoom level.

        Parameters
        ----------
        zoom : int or None
            if None, the union of all zoom level areas is returned

        Returns
        -------
        process area : shapely geometry
        """
        if zoom is None:
            if not self._cache_full_process_area:
                logger.debug("calculate process area ...")
                self._cache_full_process_area = cascaded_union([
                    self._area_at_zoom(z) for z in self.init_zoom_levels]
                ).buffer(0)
            return self._cache_full_process_area
        else:
            if zoom not in self.init_zoom_levels:
                raise ValueError(
                    "zoom level not available with current configuration")
            return self._area_at_zoom(zoom)

Return process bounding box for zoom level .


def bounds_at_zoom(self, zoom=None):
        """
        Return process bounds for zoom level.

        Parameters
        ----------
        zoom : integer or list

        Returns
        -------
        process bounds : tuple
            left, bottom, right, top
        """
        return () if self.area_at_zoom(zoom).is_empty else Bounds(
            *self.area_at_zoom(zoom).bounds)

def bounds_at_zoom ( self , zoom = None ) : return ( ) if self . area_at_zoom ( zoom ) . is_empty else Bounds ( * self . area_at_zoom ( zoom ) . bounds )


def zoom_index_gen(
    mp=None,
    out_dir=None,
    zoom=None,
    geojson=False,
    gpkg=False,
    shapefile=False,
    txt=False,
    vrt=False,
    fieldname="location",
    basepath=None,
    for_gdal=True,
    threading=False,
):
    """
    Generate indexes for given zoom level.

    Parameters
    ----------
    mp : Mapchete object
        process output to be indexed
    out_dir : path
        optionally override process output directory
    zoom : int
        zoom level to be processed
    geojson : bool
        generate GeoJSON index (default: False)
    gpkg : bool
        generate GeoPackage index (default: False)
    shapefile : bool
        generate Shapefile index (default: False)
    txt : bool
        generate tile path list textfile (default: False)
    vrt : bool
        GDAL-style VRT file (default: False)
    fieldname : str
        field name which contains paths of tiles (default: "location")
    basepath : str
        if set, use custom base path instead of output path
    for_gdal : bool
        use GDAL compatible remote paths, i.e. add "/vsicurl/" before path
        (default: True)
    """
    for zoom in get_zoom_levels(process_zoom_levels=zoom):
        with ExitStack() as es:
            # get index writers for all enabled formats
            index_writers = []
            if geojson:
                index_writers.append(
                    es.enter_context(
                        VectorFileWriter(
                            driver="GeoJSON",
                            out_path=_index_file_path(out_dir, zoom, "geojson"),
                            crs=mp.config.output_pyramid.crs,
                            fieldname=fieldname
                        )
                    )
                )
            if gpkg:
                index_writers.append(
                    es.enter_context(
                        VectorFileWriter(
                            driver="GPKG",
                            out_path=_index_file_path(out_dir, zoom, "gpkg"),
                            crs=mp.config.output_pyramid.crs,
                            fieldname=fieldname
                        )
                    )
                )
            if shapefile:
                index_writers.append(
                    es.enter_context(
                        VectorFileWriter(
                            driver="ESRI Shapefile",
                            out_path=_index_file_path(out_dir, zoom, "shp"),
                            crs=mp.config.output_pyramid.crs,
                            fieldname=fieldname
                        )
                    )
                )
            if txt:
                index_writers.append(
                    es.enter_context(
                        TextFileWriter(out_path=_index_file_path(out_dir, zoom, "txt"))
                    )
                )
            if vrt:
                index_writers.append(
                    es.enter_context(
                        VRTFileWriter(
                            out_path=_index_file_path(out_dir, zoom, "vrt"),
                            output=mp.config.output,
                            out_pyramid=mp.config.output_pyramid
                        )
                    )
                )

            logger.debug("use the following index writers: %s", index_writers)

            def _worker(tile):
                # if there are indexes to write to, check if output exists
                tile_path = _tile_path(
                    orig_path=mp.config.output.get_path(tile),
                    basepath=basepath,
                    for_gdal=for_gdal
                )
                indexes = [
                    i for i in index_writers
                    if not i.entry_exists(tile=tile, path=tile_path)
                ]
                if indexes:
                    output_exists = mp.config.output.tiles_exist(output_tile=tile)
                else:
                    output_exists = None
                return tile, tile_path, indexes, output_exists

            with concurrent.futures.ThreadPoolExecutor() as executor:
                for task in concurrent.futures.as_completed(
                    (
                        executor.submit(_worker, i)
                        for i in mp.config.output_pyramid.tiles_from_geom(
                            mp.config.area_at_zoom(zoom), zoom
                        )
                    )
                ):
                    tile, tile_path, indexes, output_exists = task.result()
                    # only write entries if there are indexes to write to and output
                    # exists
                    if indexes and output_exists:
                        logger.debug("%s exists", tile_path)
                        logger.debug("write to %s indexes" % len(indexes))
                        for index in indexes:
                            index.write(tile, tile_path)
                    # yield tile for progress information
                    yield tile


def profile(self):
        """Return raster metadata."""
        with rasterio.open(self.path, "r") as src:
            return deepcopy(src.meta)

def profile ( self ) : with rasterio . open ( self . path , "r" ) as src : return deepcopy ( src . meta )


def execute(mp):
    """
    Example process for testing.

    Inputs:
    -------
    file1
        raster file

    Parameters:
    -----------

    Output:
    -------
    np.ndarray
    """
    # Reading and writing data works like this:
    with mp.open("file1", resampling="bilinear") as raster_file:
        if raster_file.is_empty():
            return "empty"
            # This assures a transparent tile instead of a pink error tile
            # is returned when using mapchete serve.
        dem = raster_file.read()
    return dem


def is_valid_with_config(self, config):
        """
        Check if output format is valid with other process parameters.

        Parameters
        ----------
        config : dictionary
            output configuration parameters

        Returns
        -------
        is_valid : bool
        """
        validate_values(config, [("schema", dict), ("path", str)])
        validate_values(config["schema"], [("properties", dict), ("geometry", str)])
        if config["schema"]["geometry"] not in [
            "Geometry", "Point", "MultiPoint", "Line", "MultiLine",
            "Polygon", "MultiPolygon"
        ]:
            raise TypeError("invalid geometry type")
        return True

def
is_valid_with_config
(
self
,
config
)
:
validate_values
(
config
,
[
(
"schema"
,
dict
)
,
(
"path"
,
str
)
]
)
validate_values
(
config
[
"schema"
]
,
[
(
"properties"
,
dict
)
,
(
"geometry"
,
str
)
]
)
if
config
[
"schema"
]
[
"geometry"
]
not
in
[
"Geometry"
,
"Point"
,
"MultiPoint"
,
"Line"
,
"MultiLine"
,
"Polygon"
,
"MultiPolygon"
]
:
raise
TypeError
(
"invalid geometry type"
)
return
True

Check if output format is valid with other process parameters .


def read(self, validity_check=True, no_neighbors=False, **kwargs):
        """
        Read data from process output.

        Parameters
        ----------
        validity_check : bool
            run geometry validity check (default: True)
        no_neighbors : bool
            don't include neighbor tiles if there is a pixelbuffer (default:
            False)

        Returns
        -------
        features : list
            GeoJSON-like list of features
        """
        if no_neighbors:
            raise NotImplementedError()
        return self._from_cache(validity_check=validity_check)

def read ( self , validity_check = True , no_neighbors = False , * * kwargs ) : if no_neighbors : raise NotImplementedError ( ) return self . _from_cache ( validity_check = validity_check )


def available_output_formats():
    """
    Return all available output formats.

    Returns
    -------
    formats : list
        all available output formats
    """
    output_formats = []
    for v in pkg_resources.iter_entry_points(DRIVERS_ENTRY_POINT):
        driver_ = v.load()
        if hasattr(driver_, "METADATA") and (
            driver_.METADATA["mode"] in ["w", "rw"]
        ):
            output_formats.append(driver_.METADATA["driver_name"])
    return output_formats

def available_output_formats ( ) : output_formats = [ ] for v in pkg_resources . iter_entry_points ( DRIVERS_ENTRY_POINT ) : driver_ = v . load ( ) if hasattr ( driver_ , "METADATA" ) and ( driver_ . METADATA [ "mode" ] in [ "w" , "rw" ] ) : output_formats . append ( driver_ . METADATA [ "driver_name" ] ) return output_formats


def available_input_formats():
    """
    Return all available input formats.

    Returns
    -------
    formats : list
        all available input formats
    """
    input_formats = []
    for v in pkg_resources.iter_entry_points(DRIVERS_ENTRY_POINT):
        logger.debug("driver found: %s", v)
        driver_ = v.load()
        if hasattr(driver_, "METADATA") and (driver_.METADATA["mode"] in ["r", "rw"]):
            input_formats.append(driver_.METADATA["driver_name"])
    return input_formats

def
available_input_formats
(
)
:
input_formats
=
[
]
for
v
in
pkg_resources
.
iter_entry_points
(
DRIVERS_ENTRY_POINT
)
:
logger
.
debug
(
"driver found: %s"
,
v
)
driver_
=
v
.
load
(
)
if
hasattr
(
driver_
,
"METADATA"
)
and
(
driver_
.
METADATA
[
"mode"
]
in
[
"r"
,
"rw"
]
)
:
input_formats
.
append
(
driver_
.
METADATA
[
"driver_name"
]
)
return
input_formats


def load_output_writer(output_params, readonly=False):
    """
    Return output class of driver.

    Returns
    -------
    output : ``OutputData``
        output writer object
    """
    if not isinstance(output_params, dict):
        raise TypeError("output_params must be a dictionary")
    driver_name = output_params["format"]
    for v in pkg_resources.iter_entry_points(DRIVERS_ENTRY_POINT):
        _driver = v.load()
        if all(
            [hasattr(_driver, attr) for attr in ["OutputData", "METADATA"]]
            ) and (
            _driver.METADATA["driver_name"] == driver_name
        ):
            return _driver.OutputData(output_params, readonly=readonly)
    raise MapcheteDriverError("no loader for driver '%s' could be found." % driver_name)


def load_input_reader(input_params, readonly=False):
    """
    Return input class of driver.

    Returns
    -------
    input_params : ``InputData``
        input parameters
    """
    logger.debug("find input reader with params %s", input_params)
    if not isinstance(input_params, dict):
        raise TypeError("input_params must be a dictionary")
    if "abstract" in input_params:
        driver_name = input_params["abstract"]["format"]
    elif "path" in input_params:
        if os.path.splitext(input_params["path"])[1]:
            input_file = input_params["path"]
            driver_name = driver_from_file(input_file)
        else:
            logger.debug("%s is a directory", input_params["path"])
            driver_name = "TileDirectory"
    else:
        raise MapcheteDriverError("invalid input parameters %s" % input_params)
    for v in pkg_resources.iter_entry_points(DRIVERS_ENTRY_POINT):
        driver_ = v.load()
        if hasattr(driver_, "METADATA") and (
            driver_.METADATA["driver_name"] == driver_name
        ):
            return v.load().InputData(input_params, readonly=readonly)
    raise MapcheteDriverError("no loader for driver '%s' could be found." % driver_name)


def driver_from_file(input_file):
    """
    Guess driver from file extension.

    Returns
    -------
    driver : string
        driver name
    """
    file_ext = os.path.splitext(input_file)[1].split(".")[1]
    if file_ext not in _file_ext_to_driver():
        raise MapcheteDriverError(
            "no driver could be found for file extension %s" % file_ext
        )
    driver = _file_ext_to_driver()[file_ext]
    if len(driver) > 1:
        warnings.warn(
            DeprecationWarning(
                "more than one driver for file found, taking %s" % driver[0]
            )
        )
    return driver[0]


def write_output_metadata(output_params):
    """Dump output JSON and verify parameters if output metadata exist."""
    if "path" in output_params:
        metadata_path = os.path.join(output_params["path"], "metadata.json")
        logger.debug("check for output %s", metadata_path)
        try:
            existing_params = read_output_metadata(metadata_path)
            logger.debug("%s exists", metadata_path)
            logger.debug("existing output parameters: %s", pformat(existing_params))
            existing_tp = existing_params["pyramid"]
            current_params = params_to_dump(output_params)
            logger.debug("current output parameters: %s", pformat(current_params))
            current_tp = BufferedTilePyramid(**current_params["pyramid"])
            if existing_tp != current_tp:
                raise MapcheteConfigError(
                    "pyramid definitions between existing and new output do not match: "
                    "%s != %s" % (existing_tp, current_tp)
                )
            existing_format = existing_params["driver"]["format"]
            current_format = current_params["driver"]["format"]
            if existing_format != current_format:
                raise MapcheteConfigError(
                    "existing output format does not match new output format: "
                    "%s != %s" % (
                        (existing_format, current_format)
                    )
                )
        except FileNotFoundError:
            logger.debug("%s does not exist", metadata_path)
            dump_params = params_to_dump(output_params)
            # dump output metadata
            write_json(metadata_path, dump_params)
    else:
        logger.debug("no path parameter found")

Dump output JSON and verify parameters if output metadata exist .


def extract_contours(array, tile, interval=100, field='elev', base=0):
    """
    Extract contour lines from an array.

    Parameters
    ----------
    array : array
        input elevation data
    tile : Tile
        tile covering the array
    interval : integer
        elevation value interval when drawing contour lines
    field : string
        output field name containing elevation value
    base : integer
        elevation base value the intervals are computed from

    Returns
    -------
    contours : iterable
        contours as GeoJSON-like pairs of properties and geometry
    """
    import matplotlib.pyplot as plt
    levels = _get_contour_values(
        array.min(), array.max(), interval=interval, base=base)
    if not levels:
        return []
    contours = plt.contour(array, levels)
    index = 0
    out_contours = []
    for level in range(len(contours.collections)):
        elevation = levels[index]
        index += 1
        paths = contours.collections[level].get_paths()
        for path in paths:
            out_coords = [
                (
                    tile.left + (y * tile.pixel_x_size),
                    tile.top - (x * tile.pixel_y_size),
                )
                for x, y in zip(path.vertices[:, 1], path.vertices[:, 0])
            ]
            if len(out_coords) >= 2:
                out_contours.append(
                    dict(
                        properties={field: elevation},
                        geometry=mapping(LineString(out_coords))
                    )
                )
    return out_contours

def extract_contours ( array , tile , interval = 100 , field = 'elev' , base = 0 ) : import matplotlib . pyplot as plt levels = _get_contour_values ( array . min ( ) , array . max ( ) , interval = interval , base = base ) if not levels : return [ ] contours = plt . contour ( array , levels ) index = 0 out_contours = [ ] for level in range ( len ( contours . collections ) ) : elevation = levels [ index ] index += 1 paths = contours . collections [ level ] . get_paths ( ) for path in paths : out_coords = [ ( tile . left + ( y * tile . pixel_x_size ) , tile . top - ( x * tile . pixel_y_size ) , ) for x , y in zip ( path . vertices [ : , 1 ] , path . vertices [ : , 0 ] ) ] if len ( out_coords ) >= 2 : out_contours . append ( dict ( properties = { field : elevation } , geometry = mapping ( LineString ( out_coords ) ) ) ) return out_contours


def _get_contour_values(min_val, max_val, base=0, interval=100):
    """Return a list of values between min and max within an interval."""
    i = base
    out = []
    if min_val < base:
        while i >= min_val:
            i -= interval
    while i <= max_val:
        if i >= min_val:
            out.append(i)
        i += interval
    return out

def _get_contour_values ( min_val , max_val , base = 0 , interval = 100 ) : i = base out = [ ] if min_val < base : while i >= min_val : i -= interval while i <= max_val : if i >= min_val : out . append ( i ) i += interval return out

Return a list of values between min and max within an interval .


def create(
    mapchete_file,
    process_file,
    out_format,
    out_path=None,
    pyramid_type=None,
    force=False
):
    """Create an empty Mapchete and process file in a given directory."""
    if os.path.isfile(process_file) or os.path.isfile(mapchete_file):
        if not force:
            raise IOError("file(s) already exists")

    out_path = out_path if out_path else os.path.join(os.getcwd(), "output")

    # copy file template to target directory
    process_template = pkg_resources.resource_filename(
        "mapchete.static", "process_template.py"
    )
    process_file = os.path.join(os.getcwd(), process_file)
    copyfile(process_template, process_file)

    # modify and copy mapchete file template to target directory
    mapchete_template = pkg_resources.resource_filename(
        "mapchete.static", "mapchete_template.mapchete"
    )

    output_options = dict(
        format=out_format, path=out_path, **FORMAT_MANDATORY[out_format]
    )

    pyramid_options = {'grid': pyramid_type}

    substitute_elements = {
        'process_file': process_file,
        'output': dump({'output': output_options}, default_flow_style=False),
        'pyramid': dump({'pyramid': pyramid_options}, default_flow_style=False)
    }
    with open(mapchete_template, 'r') as config_template:
        config = Template(config_template.read())
        customized_config = config.substitute(substitute_elements)
    with open(mapchete_file, 'w') as target_config:
        target_config.write(customized_config)

Create an empty Mapchete and process file in a given directory .


def get_path(self, tile):
        """
        Determine target file path.

        Parameters
        ----------
        tile : ``BufferedTile``
            must be member of output ``TilePyramid``

        Returns
        -------
        path : string
        """
        return os.path.join(*[
            self.path,
            str(tile.zoom),
            str(tile.row),
            str(tile.col) + self.file_extension
        ])

def get_path ( self , tile ) : return os . path . join ( * [ self . path , str ( tile . zoom ) , str ( tile . row ) , str ( tile . col ) + self . file_extension ] )


def prepare_path(self, tile):
        """
        Create directory and subdirectory if necessary.

        Parameters
        ----------
        tile : ``BufferedTile``
            must be member of output ``TilePyramid``
        """
        makedirs(os.path.dirname(self.get_path(tile)))

def prepare_path ( self , tile ) : makedirs ( os . path . dirname ( self . get_path ( tile ) ) )

Create directory and subdirectory if necessary .


def output_is_valid(self, process_data):
        """
        Check whether process output is allowed with output driver.

        Parameters
        ----------
        process_data : raw process output

        Returns
        -------
        True or False
        """
        if self.METADATA["data_type"] == "raster":
            return (
                is_numpy_or_masked_array(process_data) or
                is_numpy_or_masked_array_with_tags(process_data)
            )
        elif self.METADATA["data_type"] == "vector":
            return is_feature_list(process_data)

def output_is_valid ( self , process_data ) : if self . METADATA [ "data_type" ] == "raster" : return ( is_numpy_or_masked_array ( process_data ) or is_numpy_or_masked_array_with_tags ( process_data ) ) elif self . METADATA [ "data_type" ] == "vector" : return is_feature_list ( process_data )

Check whether process output is allowed with output driver .


def output_cleaned(self, process_data):
        """
        Return verified and cleaned output.

        Parameters
        ----------
        process_data : raw process output

        Returns
        -------
        NumPy array or list of features.
        """
        if self.METADATA["data_type"] == "raster":
            if is_numpy_or_masked_array(process_data):
                return process_data
            elif is_numpy_or_masked_array_with_tags(process_data):
                data, tags = process_data
                return self.output_cleaned(data), tags
        elif self.METADATA["data_type"] == "vector":
            return list(process_data)

def output_cleaned ( self , process_data ) : if self . METADATA [ "data_type" ] == "raster" : if is_numpy_or_masked_array ( process_data ) : return process_data elif is_numpy_or_masked_array_with_tags ( process_data ) : data , tags = process_data return self . output_cleaned ( data ) , tags elif self . METADATA [ "data_type" ] == "vector" : return list ( process_data )


def extract_subset(self, input_data_tiles=None, out_tile=None):
        """
        Extract subset from multiple tiles.

        input_data_tiles : list of (``Tile``, process data) tuples
        out_tile : ``Tile``

        Returns
        -------
        NumPy array or list of features.
        """
        if self.METADATA["data_type"] == "raster":
            mosaic = create_mosaic(input_data_tiles)
            return extract_from_array(
                in_raster=prepare_array(
                    mosaic.data,
                    nodata=self.nodata,
                    dtype=self.output_params["dtype"]
                ),
                in_affine=mosaic.affine,
                out_tile=out_tile
            )
        elif self.METADATA["data_type"] == "vector":
            return [
                feature for feature in list(
                    chain.from_iterable([features for _, features in input_data_tiles])
                )
                if shape(feature["geometry"]).intersects(out_tile.bbox)
            ]

def extract_subset ( self , input_data_tiles = None , out_tile = None ) : if self . METADATA [ "data_type" ] == "raster" : mosaic = create_mosaic ( input_data_tiles ) return extract_from_array ( in_raster = prepare_array ( mosaic . data , nodata = self . nodata , dtype = self . output_params [ "dtype" ] ) , in_affine = mosaic . affine , out_tile = out_tile ) elif self . METADATA [ "data_type" ] == "vector" : return [ feature for feature in list ( chain . from_iterable ( [ features for _ , features in input_data_tiles ] ) ) if shape ( feature [ "geometry" ] ) . intersects ( out_tile . bbox ) ]


def calculate_slope_aspect(elevation, xres, yres, z=1.0, scale=1.0):
    """
    Calculate slope and aspect map.

    Return a pair of arrays 2 pixels smaller than the input elevation array.

    Slope is returned in radians, from 0 for sheer face to pi/2 for
    flat ground. Aspect is returned in radians, counterclockwise from -pi
    at north around to pi.

    Logic here is borrowed from hillshade.cpp:
    http://www.perrygeo.net/wordpress/?p=7

    Parameters
    ----------
    elevation : array
        input elevation data
    xres : float
        column width
    yres : float
        row  height
    z : float
        vertical exaggeration factor
    scale : float
        scale factor of pixel size units versus height units (insert 112000
        when having elevation values in meters in a geodetic projection)

    Returns
    -------
    slope shade : array
    """
    z = float(z)
    scale = float(scale)
    height, width = elevation.shape[0] - 2, elevation.shape[1] - 2
    window = [
        z * elevation[row:(row + height), col:(col + width)]
        for (row, col) in product(range(3), range(3))
    ]
    x = (
        (window[0] + window[3] + window[3] + window[6])
        - (window[2] + window[5] + window[5] + window[8])
        ) / (8.0 * xres * scale)
    y = (
        (window[6] + window[7] + window[7] + window[8])
        - (window[0] + window[1] + window[1] + window[2])
        ) / (8.0 * yres * scale)
    # in radians, from 0 to pi/2
    slope = math.pi/2 - np.arctan(np.sqrt(x*x + y*y))
    # in radians counterclockwise, from -pi at north back to pi
    aspect = np.arctan2(x, y)
    return slope, aspect


def hillshade(elevation, tile, azimuth=315.0, altitude=45.0, z=1.0, scale=1.0):
    """
    Return hillshaded numpy array.

    Parameters
    ----------
    elevation : array
        input elevation data
    tile : Tile
        tile covering the array
    z : float
        vertical exaggeration factor
    scale : float
        scale factor of pixel size units versus height units (insert 112000
        when having elevation values in meters in a geodetic projection)
    """
    azimuth = float(azimuth)
    altitude = float(altitude)
    z = float(z)
    scale = float(scale)
    xres = tile.tile.pixel_x_size
    yres = -tile.tile.pixel_y_size
    slope, aspect = calculate_slope_aspect(
        elevation, xres, yres, z=z, scale=scale)
    deg2rad = math.pi / 180.0
    shaded = np.sin(altitude * deg2rad) * np.sin(slope) \
        + np.cos(altitude * deg2rad) * np.cos(slope) \
        * np.cos((azimuth - 90.0) * deg2rad - aspect)
    # shaded now has values between -1.0 and +1.0
    # stretch to 0 - 255 and invert
    shaded = (((shaded+1.0)/2)*-255.0).astype("uint8")
    # add one pixel padding using the edge values
    return ma.masked_array(
        data=np.pad(shaded, 1, mode='edge'), mask=elevation.mask
    )


def tile(self, zoom, row, col):
        """
        Return ``BufferedTile`` object of this ``BufferedTilePyramid``.

        Parameters
        ----------
        zoom : integer
            zoom level
        row : integer
            tile matrix row
        col : integer
            tile matrix column

        Returns
        -------
        buffered tile : ``BufferedTile``
        """
        tile = self.tile_pyramid.tile(zoom, row, col)
        return BufferedTile(tile, pixelbuffer=self.pixelbuffer)

def tile ( self , zoom , row , col ) : tile = self . tile_pyramid . tile ( zoom , row , col ) return BufferedTile ( tile , pixelbuffer = self . pixelbuffer )

Return BufferedTile object of this BufferedTilePyramid .


def tiles_from_bounds(self, bounds, zoom):
        """
        Return all tiles intersecting with bounds.

        Bounds values will be cleaned if they cross the antimeridian or are
        outside of the Northern or Southern tile pyramid bounds.

        Parameters
        ----------
        bounds : tuple
            (left, bottom, right, top) bounding values in tile pyramid CRS
        zoom : integer
            zoom level

        Yields
        ------
        intersecting tiles : generator
            generates ``BufferedTiles``
        """
        for tile in self.tiles_from_bbox(box(*bounds), zoom):
            yield self.tile(*tile.id)

def tiles_from_bounds ( self , bounds , zoom ) : for tile in self . tiles_from_bbox ( box ( * bounds ) , zoom ) : yield self . tile ( * tile . id )

Return all tiles intersecting with bounds .


def tiles_from_bbox(self, geometry, zoom):
        """
        All metatiles intersecting with given bounding box.

        Parameters
        ----------
        geometry : ``shapely.geometry``
        zoom : integer
            zoom level

        Yields
        ------
        intersecting tiles : generator
            generates ``BufferedTiles``
        """
        for tile in self.tile_pyramid.tiles_from_bbox(geometry, zoom):
            yield self.tile(*tile.id)

def tiles_from_bbox ( self , geometry , zoom ) : for tile in self . tile_pyramid . tiles_from_bbox ( geometry , zoom ) : yield self . tile ( * tile . id )

All metatiles intersecting with given bounding box .


def tiles_from_geom(self, geometry, zoom):
        """
        Return all tiles intersecting with input geometry.

        Parameters
        ----------
        geometry : ``shapely.geometry``
        zoom : integer
            zoom level

        Yields
        ------
        intersecting tiles : ``BufferedTile``
        """
        for tile in self.tile_pyramid.tiles_from_geom(geometry, zoom):
            yield self.tile(*tile.id)

def tiles_from_geom ( self , geometry , zoom ) : for tile in self . tile_pyramid . tiles_from_geom ( geometry , zoom ) : yield self . tile ( * tile . id )

Return all tiles intersecting with input geometry .


def intersecting(self, tile):
        """
        Return all BufferedTiles intersecting with tile.

        Parameters
        ----------
        tile : ``BufferedTile``
            another tile
        """
        return [
            self.tile(*intersecting_tile.id)
            for intersecting_tile in self.tile_pyramid.intersecting(tile)
        ]

def intersecting ( self , tile ) : return [ self . tile ( * intersecting_tile . id ) for intersecting_tile in self . tile_pyramid . intersecting ( tile ) ]

Return all BufferedTiles intersecting with tile .


def to_dict(self):
        """
        Return dictionary representation of pyramid parameters.
        """
        return dict(
            grid=self.grid.to_dict(),
            metatiling=self.metatiling,
            tile_size=self.tile_size,
            pixelbuffer=self.pixelbuffer
        )

def to_dict ( self ) : return dict ( grid = self . grid . to_dict ( ) , metatiling = self . metatiling , tile_size = self . tile_size , pixelbuffer = self . pixelbuffer )

Return dictionary representation of pyramid parameters .


def get_neighbors(self, connectedness=8):
        """
        Return tile neighbors.

        Tile neighbors are unique, i.e. in some edge cases, where both the left
        and right neighbor wrapped around the antimeridian is the same. Also,
        neighbors ouside the northern and southern TilePyramid boundaries are
        excluded, because they are invalid.

        -------------
        | 8 | 1 | 5 |
        -------------
        | 4 | x | 2 |
        -------------
        | 7 | 3 | 6 |
        -------------

        Parameters
        ----------
        connectedness : int
            [4 or 8] return four direct neighbors or all eight.

        Returns
        -------
        list of BufferedTiles
        """
        return [
            BufferedTile(t, self.pixelbuffer)
            for t in self._tile.get_neighbors(connectedness=connectedness)
        ]

def get_neighbors ( self , connectedness = 8 ) : return [ BufferedTile ( t , self . pixelbuffer ) for t in self . _tile . get_neighbors ( connectedness = connectedness ) ]


def is_on_edge(self):
        """Determine whether tile touches or goes over pyramid edge."""
        return (
            self.left <= self.tile_pyramid.left or      # touches_left
            self.bottom <= self.tile_pyramid.bottom or  # touches_bottom
            self.right >= self.tile_pyramid.right or    # touches_right
            self.top >= self.tile_pyramid.top           # touches_top
        )

def
is_on_edge
(
self
)
:
return
(
self
.
left
<=
self
.
tile_pyramid
.
left
or
# touches_left
self
.
bottom
<=
self
.
tile_pyramid
.
bottom
or
# touches_bottom
self
.
right
>=
self
.
tile_pyramid
.
right
or
# touches_right
self
.
top
>=
self
.
tile_pyramid
.
top
# touches_top
)

Determine whether tile touches or goes over pyramid edge .


def execute(
    mp,
    resampling="nearest",
    scale_method=None,
    scales_minmax=None
):
    """
    Read, stretch and return raster data.

    Inputs:
    -------
    raster
        raster file

    Parameters:
    -----------
    resampling : str
        rasterio.Resampling method
    scale_method : str
        - dtype_scale: use dtype minimum and maximum values
        - minmax_scale: use dataset bands minimum and maximum values
        - crop: clip data to output dtype
    scales_minmax : tuple
        tuple of band specific scale values

    Output:
    -------
    np.ndarray
    """
    with mp.open("raster", resampling=resampling) as raster_file:

        # exit if input tile is empty
        if raster_file.is_empty():
            return "empty"

        # actually read data and iterate through bands
        scaled = ()
        mask = ()
        raster_data = raster_file.read()
        if raster_data.ndim == 2:
            raster_data = ma.expand_dims(raster_data, axis=0)
        if not scale_method:
            scales_minmax = [(i, i) for i in range(len(raster_data))]

        for band, (scale_min, scale_max) in zip(raster_data, scales_minmax):
            if scale_method in ["dtype_scale", "minmax_scale"]:
                scaled += (_stretch_array(band, scale_min, scale_max), )
            elif scale_method == "crop":
                scaled += (np.clip(band, scale_min, scale_max), )
            else:
                scaled += (band, )
            mask += (band.mask, )

    return ma.masked_array(np.stack(scaled), np.stack(mask))


def open(self, tile, process, **kwargs):
        """
        Open process output as input for other process.

        Parameters
        ----------
        tile : ``Tile``
        process : ``MapcheteProcess``
        kwargs : keyword arguments
        """
        return InputTile(tile, process, kwargs.get("resampling", None))

def open ( self , tile , process , * * kwargs ) : return InputTile ( tile , process , kwargs . get ( "resampling" , None ) )

Open process output as input for other process .


def for_web(self, data):
        """
        Convert data to web output.

        Parameters
        ----------
        data : array

        Returns
        -------
        web data : array
        """
        rgba = self._prepare_array_for_png(data)
        data = ma.masked_where(rgba == self.nodata, rgba)
        return memory_file(data, self.profile()), 'image/png'

def for_web ( self , data ) : rgba = self . _prepare_array_for_png ( data ) data = ma . masked_where ( rgba == self . nodata , rgba ) return memory_file ( data , self . profile ( ) ) , 'image/png'


def serve(
    mapchete_file,
    port=None,
    internal_cache=None,
    zoom=None,
    bounds=None,
    overwrite=False,
    readonly=False,
    memory=False,
    input_file=None,
    debug=False,
    logfile=None
):
    """
    Serve a Mapchete process.

    Creates the Mapchete host and serves both web page with OpenLayers and the
    WMTS simple REST endpoint.
    """
    app = create_app(
        mapchete_files=[mapchete_file], zoom=zoom,
        bounds=bounds, single_input_file=input_file,
        mode=_get_mode(memory, readonly, overwrite), debug=debug
    )
    if os.environ.get("MAPCHETE_TEST") == "TRUE":
        logger.debug("don't run flask app, MAPCHETE_TEST environment detected")
    else:
        app.run(
            threaded=True, debug=True, port=port, host='0.0.0.0',
            extra_files=[mapchete_file]
        )


def create_app(
    mapchete_files=None, zoom=None, bounds=None, single_input_file=None,
    mode="continue", debug=None
):
    """Configure and create Flask app."""
    from flask import Flask, render_template_string
    app = Flask(__name__)
    mapchete_processes = {
        os.path.splitext(os.path.basename(mapchete_file))[0]: mapchete.open(
            mapchete_file, zoom=zoom, bounds=bounds,
            single_input_file=single_input_file, mode=mode, with_cache=True,
            debug=debug)
        for mapchete_file in mapchete_files
    }

    mp = next(iter(mapchete_processes.values()))
    pyramid_type = mp.config.process_pyramid.grid
    pyramid_srid = mp.config.process_pyramid.crs.to_epsg()
    process_bounds = ",".join([str(i) for i in mp.config.bounds_at_zoom()])
    grid = "g" if pyramid_srid == 3857 else "WGS84"
    web_pyramid = BufferedTilePyramid(pyramid_type)

    @app.route('/', methods=['GET'])
    def index():
        """Render and hosts the appropriate OpenLayers instance."""
        return render_template_string(
            pkgutil.get_data(
                'mapchete.static', 'index.html').decode("utf-8"),
            srid=pyramid_srid,
            process_bounds=process_bounds,
            is_mercator=(pyramid_srid == 3857),
            process_names=mapchete_processes.keys()
        )

    @app.route(
        "/".join([
            "", "wmts_simple", "1.0.0", "<string:mp_name>", "default",
            grid, "<int:zoom>", "<int:row>", "<int:col>.<string:file_ext>"]),
        methods=['GET'])
    def get(mp_name, zoom, row, col, file_ext):
        """Return processed, empty or error (in pink color) tile."""
        logger.debug(
            "received tile (%s, %s, %s) for process %s", zoom, row, col,
            mp_name)
        # convert zoom, row, col into tile object using web pyramid
        return _tile_response(
            mapchete_processes[mp_name], web_pyramid.tile(zoom, row, col),
            debug)

    return app


def read_raster_window(
    input_files,
    tile,
    indexes=None,
    resampling="nearest",
    src_nodata=None,
    dst_nodata=None,
    gdal_opts=None
):
    """
    Return NumPy arrays from an input raster.

    NumPy arrays are reprojected and resampled to tile properties from input
    raster. If tile boundaries cross the antimeridian, data on the other side
    of the antimeridian will be read and concatenated to the numpy array
    accordingly.

    Parameters
    ----------
    input_files : string or list
        path to a raster file or list of paths to multiple raster files readable by
        rasterio.
    tile : Tile
        a Tile object
    indexes : list or int
        a list of band numbers; None will read all.
    resampling : string
        one of "nearest", "average", "bilinear" or "lanczos"
    src_nodata : int or float, optional
        if not set, the nodata value from the source dataset will be used
    dst_nodata : int or float, optional
        if not set, the nodata value from the source dataset will be used
    gdal_opts : dict
        GDAL options passed on to rasterio.Env()

    Returns
    -------
    raster : MaskedArray
    """
    with rasterio.Env(
        **get_gdal_options(
            gdal_opts,
            is_remote=path_is_remote(
                input_files[0] if isinstance(input_files, list) else input_files, s3=True
            )
        )
    ) as env:
        logger.debug("reading %s with GDAL options %s", input_files, env.options)
        return _read_raster_window(
            input_files,
            tile,
            indexes=indexes,
            resampling=resampling,
            src_nodata=src_nodata,
            dst_nodata=dst_nodata
        )

def
read_raster_window
(
input_files
,
tile
,
indexes
=
,
resampling
=
"nearest"
,
src_nodata
=
,
dst_nodata
=
,
gdal_opts
=
)
:
with
rasterio
.
Env
(
*
*
get_gdal_options
(
gdal_opts
,
is_remote
=
path_is_remote
(
input_files
[
0
]
if
isinstance
(
input_files
,
list
)
else
input_files
,
s3
=
True
)
)
)
as
env
:
logger
.
debug
(
"reading %s with GDAL options %s"
,
input_files
,
env
.
options
)
return
_read_raster_window
(
input_files
,
tile
,
indexes
=
indexes
,
resampling
=
resampling
,
src_nodata
=
src_nodata
,
dst_nodata
=
dst_nodata
)

Return NumPy arrays from an input raster .


def _get_warped_array(
    input_file=None,
    indexes=None,
    dst_bounds=None,
    dst_shape=None,
    dst_crs=None,
    resampling=None,
    src_nodata=None,
    dst_nodata=None
):
    """Extract a numpy array from a raster file."""
    try:
        return _rasterio_read(
            input_file=input_file,
            indexes=indexes,
            dst_bounds=dst_bounds,
            dst_shape=dst_shape,
            dst_crs=dst_crs,
            resampling=resampling,
            src_nodata=src_nodata,
            dst_nodata=dst_nodata
        )
    except Exception as e:
        logger.exception("error while reading file %s: %s", input_file, e)
        raise

def
_get_warped_array
(
input_file
=
,
indexes
=
,
dst_bounds
=
,
dst_shape
=
,
dst_crs
=
,
resampling
=
,
src_nodata
=
,
dst_nodata
=
)
:
try
:
return
_rasterio_read
(
input_file
=
input_file
,
indexes
=
indexes
,
dst_bounds
=
dst_bounds
,
dst_shape
=
dst_shape
,
dst_crs
=
dst_crs
,
resampling
=
resampling
,
src_nodata
=
src_nodata
,
dst_nodata
=
dst_nodata
)
except
Exception
as
e
:
logger
.
exception
(
"error while reading file %s: %s"
,
input_file
,
e
)
raise

Extract a numpy array from a raster file .


def write_raster_window(
    in_tile=None, in_data=None, out_profile=None, out_tile=None, out_path=None,
    tags=None, bucket_resource=None
):
    """
    Write a window from a numpy array to an output file.

    Parameters
    ----------
    in_tile : ``BufferedTile``
        ``BufferedTile`` with a data attribute holding NumPy data
    in_data : array
    out_profile : dictionary
        metadata dictionary for rasterio
    out_tile : ``Tile``
        provides output boundaries; if None, in_tile is used
    out_path : string
        output path to write to
    tags : optional tags to be added to GeoTIFF file
    bucket_resource : boto3 bucket resource to write to in case of S3 output
    """
    if not isinstance(out_path, str):
        raise TypeError("out_path must be a string")
    logger.debug("write %s", out_path)
    if out_path == "memoryfile":
        raise DeprecationWarning(
            "Writing to memoryfile with write_raster_window() is deprecated. "
            "Please use RasterWindowMemoryFile."
        )
    out_tile = in_tile if out_tile is None else out_tile
    _validate_write_window_params(in_tile, out_tile, in_data, out_profile)

    # extract data
    window_data = extract_from_array(
        in_raster=in_data,
        in_affine=in_tile.affine,
        out_tile=out_tile
    ) if in_tile != out_tile else in_data

    # use transform instead of affine
    if "affine" in out_profile:
        out_profile["transform"] = out_profile.pop("affine")

    # write if there is any band with non-masked data
    if window_data.all() is not ma.masked:

        try:
            if out_path.startswith("s3://"):
                with RasterWindowMemoryFile(
                    in_tile=out_tile,
                    in_data=window_data,
                    out_profile=out_profile,
                    out_tile=out_tile,
                    tags=tags
                ) as memfile:
                    logger.debug((out_tile.id, "upload tile", out_path))
                    bucket_resource.put_object(
                        Key="/".join(out_path.split("/")[3:]),
                        Body=memfile
                    )
            else:
                with rasterio.open(out_path, 'w', **out_profile) as dst:
                    logger.debug((out_tile.id, "write tile", out_path))
                    dst.write(window_data.astype(out_profile["dtype"], copy=False))
                    _write_tags(dst, tags)
        except Exception as e:
            logger.exception("error while writing file %s: %s", out_path, e)
            raise
    else:
        logger.debug((out_tile.id, "array window empty", out_path))

Write a window from a numpy array to an output file .


def extract_from_array(in_raster=None, in_affine=None, out_tile=None):
    """
    Extract raster data window array.

    Parameters
    ----------
    in_raster : array or ReferencedRaster
    in_affine : ``Affine`` required if in_raster is an array
    out_tile : ``BufferedTile``

    Returns
    -------
    extracted array : array
    """
    if isinstance(in_raster, ReferencedRaster):
        in_affine = in_raster.affine
        in_raster = in_raster.data

    # get range within array
    minrow, maxrow, mincol, maxcol = bounds_to_ranges(
        out_bounds=out_tile.bounds, in_affine=in_affine, in_shape=in_raster.shape
    )
    # if output window is within input window
    if (
        minrow >= 0 and
        mincol >= 0 and
        maxrow <= in_raster.shape[-2] and
        maxcol <= in_raster.shape[-1]
    ):
        return in_raster[..., minrow:maxrow, mincol:maxcol]
    # raise error if output is not fully within input
    else:
        raise ValueError("extraction fails if output shape is not within input")


def resample_from_array(
    in_raster=None,
    in_affine=None,
    out_tile=None,
    in_crs=None,
    resampling="nearest",
    nodataval=0
):
    """
    Extract and resample from array to target tile.

    Parameters
    ----------
    in_raster : array
    in_affine : ``Affine``
    out_tile : ``BufferedTile``
    resampling : string
        one of rasterio's resampling methods (default: nearest)
    nodataval : integer or float
        raster nodata value (default: 0)

    Returns
    -------
    resampled array : array
    """
    # TODO rename function
    if isinstance(in_raster, ma.MaskedArray):
        pass
    if isinstance(in_raster, np.ndarray):
        in_raster = ma.MaskedArray(in_raster, mask=in_raster == nodataval)
    elif isinstance(in_raster, ReferencedRaster):
        in_affine = in_raster.affine
        in_crs = in_raster.crs
        in_raster = in_raster.data
    elif isinstance(in_raster, tuple):
        in_raster = ma.MaskedArray(
            data=np.stack(in_raster),
            mask=np.stack([
                band.mask
                if isinstance(band, ma.masked_array)
                else np.where(band == nodataval, True, False)
                for band in in_raster
            ]),
            fill_value=nodataval
        )
    else:
        raise TypeError("wrong input data type: %s" % type(in_raster))
    if in_raster.ndim == 2:
        in_raster = ma.expand_dims(in_raster, axis=0)
    elif in_raster.ndim == 3:
        pass
    else:
        raise TypeError("input array must have 2 or 3 dimensions")
    if in_raster.fill_value != nodataval:
        ma.set_fill_value(in_raster, nodataval)
    out_shape = (in_raster.shape[0], ) + out_tile.shape
    dst_data = np.empty(out_shape, in_raster.dtype)
    in_raster = ma.masked_array(
        data=in_raster.filled(), mask=in_raster.mask, fill_value=nodataval
    )
    reproject(
        in_raster,
        dst_data,
        src_transform=in_affine,
        src_crs=in_crs if in_crs else out_tile.crs,
        dst_transform=out_tile.affine,
        dst_crs=out_tile.crs,
        resampling=Resampling[resampling]
    )
    return ma.MaskedArray(dst_data, mask=dst_data == nodataval)

Extract and resample from array to target tile .


def bounds_to_ranges(out_bounds=None, in_affine=None, in_shape=None):
    """
    Return bounds range values from geolocated input.

    Parameters
    ----------
    out_bounds : tuple
        left, bottom, right, top
    in_affine : Affine
        input geolocation
    in_shape : tuple
        input shape

    Returns
    -------
    minrow, maxrow, mincol, maxcol
    """
    return itertools.chain(
        *from_bounds(
            *out_bounds, transform=in_affine, height=in_shape[-2], width=in_shape[-1]
        ).round_lengths(pixel_precision=0).round_offsets(pixel_precision=0).toranges()
    )

def bounds_to_ranges ( out_bounds = None , in_affine = None , in_shape = None ) : return itertools . chain ( * from_bounds ( * out_bounds , transform = in_affine , height = in_shape [ - 2 ] , width = in_shape [ - 1 ] ) . round_lengths ( pixel_precision = 0 ) . round_offsets ( pixel_precision = 0 ) . toranges ( ) )

Return bounds range values from geolocated input .


def tiles_to_affine_shape(tiles):
    """
    Return Affine and shape of combined tiles.

    Parameters
    ----------
    tiles : iterable
        an iterable containing BufferedTiles

    Returns
    -------
    Affine, Shape
    """
    if not tiles:
        raise TypeError("no tiles provided")
    pixel_size = tiles[0].pixel_x_size
    left, bottom, right, top = (
        min([t.left for t in tiles]),
        min([t.bottom for t in tiles]),
        max([t.right for t in tiles]),
        max([t.top for t in tiles]),
    )
    return (
        Affine(pixel_size, 0, left, 0, -pixel_size, top),
        Shape(
            width=int(round((right - left) / pixel_size, 0)),
            height=int(round((top - bottom) / pixel_size, 0)),
        )
    )

def
tiles_to_affine_shape
(
tiles
)
:
if
not
tiles
:
raise
TypeError
(
"no tiles provided"
)
pixel_size
=
tiles
[
0
]
.
pixel_x_size
left
,
bottom
,
right
,
top
=
(
min
(
[
t
.
left
for
t
in
tiles
]
)
,
min
(
[
t
.
bottom
for
t
in
tiles
]
)
,
max
(
[
t
.
right
for
t
in
tiles
]
)
,
max
(
[
t
.
top
for
t
in
tiles
]
)
,
)
return
(
Affine
(
pixel_size
,
0
,
left
,
0
,
pixel_size
,
top
)
,
Shape
(
width
=
int
(
round
(
(
right
left
)
/
pixel_size
,
0
)
)
,
height
=
int
(
round
(
(
top
bottom
)
/
pixel_size
,
0
)
)
,
)
)

Return Affine and shape of combined tiles .


def _shift_required(tiles):
    """Determine if distance over antimeridian is shorter than normal distance."""
    if tiles[0][0].tile_pyramid.is_global:
        # get set of tile columns
        tile_cols = sorted(list(set([t[0].col for t in tiles])))
        # if tile columns are an unbroken sequence, tiles are connected and are not
        # passing the Antimeridian
        if tile_cols == list(range(min(tile_cols), max(tile_cols) + 1)):
            return False
        else:
            # look at column gaps and try to determine the smallest distance
            def gen_groups(items):
                """Groups tile columns by sequence."""
                j = items[0]
                group = [j]
                for i in items[1:]:
                    # item is next in expected sequence
                    if i == j + 1:
                        group.append(i)
                    # gap occured, so yield existing group and create new one
                    else:
                        yield group
                        group = [i]
                    j = i
                yield group

            groups = list(gen_groups(tile_cols))
            # in case there is only one group, don't shift
            if len(groups) == 1:
                return False
            # distance between first column of first group and last column of last group
            normal_distance = groups[-1][-1] - groups[0][0]
            # distance between last column of first group and last column of first group
            # but crossing the antimeridian
            antimeridian_distance = (
                groups[0][-1] + tiles[0][0].tile_pyramid.matrix_width(tiles[0][0].zoom)
            ) - groups[-1][0]
            # return whether distance over antimeridian is shorter
            return antimeridian_distance < normal_distance
    else:
        return False

Determine if distance over antimeridian is shorter than normal distance .


def memory_file(data=None, profile=None):
    """
    Return a rasterio.io.MemoryFile instance from input.

    Parameters
    ----------
    data : array
        array to be written
    profile : dict
        rasterio profile for MemoryFile
    """
    memfile = MemoryFile()
    profile.update(width=data.shape[-2], height=data.shape[-1])
    with memfile.open(**profile) as dataset:
        dataset.write(data)
    return memfile

def memory_file ( data = None , profile = None ) : memfile = MemoryFile ( ) profile . update ( width = data . shape [ - 2 ] , height = data . shape [ - 1 ] ) with memfile . open ( * * profile ) as dataset : dataset . write ( data ) return memfile

Return a rasterio . io . MemoryFile instance from input .


def prepare_array(data, masked=True, nodata=0, dtype="int16"):
    """
    Turn input data into a proper array for further usage.

    Outut array is always 3-dimensional with the given data type. If the output
    is masked, the fill_value corresponds to the given nodata value and the
    nodata value will be burned into the data array.

    Parameters
    ----------
    data : array or iterable
        array (masked or normal) or iterable containing arrays
    nodata : integer or float
        nodata value (default: 0) used if input is not a masked array and
        for output array
    masked : bool
        return a NumPy Array or a NumPy MaskedArray (default: True)
    dtype : string
        data type of output array (default: "int16")

    Returns
    -------
    array : array
    """
    # input is iterable
    if isinstance(data, (list, tuple)):
        return _prepare_iterable(data, masked, nodata, dtype)

    # special case if a 2D single band is provided
    elif isinstance(data, np.ndarray) and data.ndim == 2:
        data = ma.expand_dims(data, axis=0)

    # input is a masked array
    if isinstance(data, ma.MaskedArray):
        return _prepare_masked(data, masked, nodata, dtype)

    # input is a NumPy array
    elif isinstance(data, np.ndarray):
        if masked:
            return ma.masked_values(data.astype(dtype, copy=False), nodata, copy=False)
        else:
            return data.astype(dtype, copy=False)
    else:
        raise ValueError(
            "data must be array, masked array or iterable containing arrays."
        )

Turn input data into a proper array for further usage .


def reproject_geometry(
    geometry, src_crs=None, dst_crs=None, error_on_clip=False, validity_check=True,
    antimeridian_cutting=False
):
    """
    Reproject a geometry to target CRS.

    Also, clips geometry if it lies outside the destination CRS boundary.
    Supported destination CRSes for clipping: 4326 (WGS84), 3857 (Spherical
    Mercator) and 3035 (ETRS89 / ETRS-LAEA).

    Parameters
    ----------
    geometry : ``shapely.geometry``
    src_crs : ``rasterio.crs.CRS`` or EPSG code
        CRS of source data
    dst_crs : ``rasterio.crs.CRS`` or EPSG code
        target CRS
    error_on_clip : bool
        raises a ``RuntimeError`` if a geometry is outside of CRS bounds
        (default: False)
    validity_check : bool
        checks if reprojected geometry is valid and throws ``TopologicalError``
        if invalid (default: True)
    antimeridian_cutting : bool
        cut geometry at Antimeridian; can result in a multipart output geometry

    Returns
    -------
    geometry : ``shapely.geometry``
    """
    src_crs = _validated_crs(src_crs)
    dst_crs = _validated_crs(dst_crs)

    def _reproject_geom(geometry, src_crs, dst_crs):
        if geometry.is_empty:
            return geometry
        else:
            out_geom = to_shape(
                transform_geom(
                    src_crs.to_dict(),
                    dst_crs.to_dict(),
                    mapping(geometry),
                    antimeridian_cutting=antimeridian_cutting
                )
            )
            return _repair(out_geom) if validity_check else out_geom

    # return repaired geometry if no reprojection needed
    if src_crs == dst_crs or geometry.is_empty:
        return _repair(geometry)

    # geometry needs to be clipped to its CRS bounds
    elif (
        dst_crs.is_epsg_code and               # just in case for an CRS with EPSG code
        dst_crs.get("init") in CRS_BOUNDS and  # if CRS has defined bounds
        dst_crs.get("init") != "epsg:4326"     # and is not WGS84 (does not need clipping)
    ):
        wgs84_crs = CRS().from_epsg(4326)
        # get dst_crs boundaries
        crs_bbox = box(*CRS_BOUNDS[dst_crs.get("init")])
        # reproject geometry to WGS84
        geometry_4326 = _reproject_geom(geometry, src_crs, wgs84_crs)
        # raise error if geometry has to be clipped
        if error_on_clip and not geometry_4326.within(crs_bbox):
            raise RuntimeError("geometry outside target CRS bounds")
        # clip geometry dst_crs boundaries and return
        return _reproject_geom(crs_bbox.intersection(geometry_4326), wgs84_crs, dst_crs)

    # return without clipping if destination CRS does not have defined bounds
    else:
        return _reproject_geom(geometry, src_crs, dst_crs)


def segmentize_geometry(geometry, segmentize_value):
    """
    Segmentize Polygon outer ring by segmentize value.

    Just Polygon geometry type supported.

    Parameters
    ----------
    geometry : ``shapely.geometry``
    segmentize_value: float

    Returns
    -------
    geometry : ``shapely.geometry``
    """
    if geometry.geom_type != "Polygon":
        raise TypeError("segmentize geometry type must be Polygon")

    return Polygon(
        LinearRing([
            p
            # pick polygon linestrings
            for l in map(
                lambda x: LineString([x[0], x[1]]),
                zip(geometry.exterior.coords[:-1], geometry.exterior.coords[1:])
            )
            # interpolate additional points in between and don't forget end point
            for p in [
                l.interpolate(segmentize_value * i).coords[0]
                for i in range(int(l.length / segmentize_value))
            ] + [l.coords[1]]
        ])
    )

Segmentize Polygon outer ring by segmentize value .


def read_vector_window(input_files, tile, validity_check=True):
    """
    Read a window of an input vector dataset.

    Also clips geometry.

    Parameters:
    -----------
    input_file : string
        path to vector file
    tile : ``Tile``
        tile extent to read data from
    validity_check : bool
        checks if reprojected geometry is valid and throws ``RuntimeError`` if
        invalid (default: True)

    Returns
    -------
    features : list
      a list of reprojected GeoJSON-like features
    """
    if not isinstance(input_files, list):
        input_files = [input_files]
    return [
        feature
        for feature in chain.from_iterable([
            _read_vector_window(path, tile, validity_check=validity_check)
            for path in input_files
        ])
    ]

def read_vector_window ( input_files , tile , validity_check = True ) : if not isinstance ( input_files , list ) : input_files = [ input_files ] return [ feature for feature in chain . from_iterable ( [ _read_vector_window ( path , tile , validity_check = validity_check ) for path in input_files ] ) ]

Read a window of an input vector dataset .