Code source de hedge_tools.hedgetools_app
# ***************************************************************************
# PyQGIS-wrapper
# begin : 2025-01-11
# copyright : (C) 2025 by Dynafor
# email : gabriel.marques@toulouse-inp.fr
# ***************************************************************************/
# /***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation; either version 2 of the License, or *
# * (at your option) any later version. *
# * *
# ***************************************************************************/
from __future__ import annotations
import datetime
import functools
import logging
from enum import Enum
from typing import Dict, List, Optional, Union
from qgis.core import (
QgsFeatureRequest,
QgsPointCloudLayer,
QgsProcessingContext,
QgsProcessingProvider,
QgsProject,
QgsRasterLayer,
QgsRectangle,
QgsVectorLayer,
)
from hedge_tools.algorithm.utils import GridFields, WidthFields
from hedge_tools.toolbelt.pyqgis_wrapper.application import QgisApp
from hedge_tools.toolbelt.pyqgis_wrapper.utils.log_handler import (
LoggerMixin,
create_child_logger,
)
[docs]
class HedgeToolsApp(QgisApp, LoggerMixin):
"""
Provides access to the HedgeTools algorithms
"""
def __init__(
self,
processing_path: str,
prefix_path: str = "/usr",
providers: List[QgsProcessingProvider] = [],
project: Union[str, QgsProject] = QgsProject.instance(),
use_canvas: bool = False,
canvas_name: str = "HedgeTools Map Viewer",
debug_mode: bool = False,
):
# Init parent logger
if debug_mode:
log = logging.DEBUG
else:
log = logging.INFO
self.set_up_logger("hedge_tools", None, log)
super().__init__(
processing_path, prefix_path, providers, project, use_canvas, canvas_name
)
# Init after as QgisApp init is own logger that'll override self.logger
self.logger = create_child_logger(__name__)
def feedback(func):
"""
Decorator to log start of an algorithm using self.logger
"""
BYPASS = {
"cwa": "categorise wooded area",
"preprocess_cwa": "preprocess categorise wooded area",
}
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
method_name = func.__name__
if method_name in BYPASS:
alg_name = BYPASS[method_name]
else:
alg_name = method_name.replace("_", " ")
self.logger.info(f"Starting algorithm: {alg_name.upper()}")
start = datetime.datetime.now()
results = func(self, *args, **kwargs)
end = datetime.datetime.now()
duration = end - start
duration = duration - datetime.timedelta(microseconds=duration.microseconds)
self.logger.info(
f"Completed algorithm: {alg_name.upper()} in {duration} \n"
)
return results
return wrapper
[docs]
@feedback
def update_link(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
out_poly_path: Optional[str] = None,
out_arc_path: Optional[str] = None,
out_node_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Updates the links (pid, vid, eid, src_vid, tgt_vid) between polygon, arc and point layers.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of Arc
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of Node
:type node_layer: QgsVectorLayer
:param out_poly_path: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type out_poly_path: Optional[str], optional
:param out_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc_path: Optional[str], optional
:param out_node_path: Path for the output node layer
if None it'll create a memory layer, defaults to None
:type out_node_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.update import UpdateLinkAlgorithm
alg = UpdateLinkAlgorithm(
poly_layer,
arc_layer,
node_layer,
out_poly_path,
out_arc_path,
out_node_path,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def manage_raster_tiles(
self,
extent: QgsRectangle,
input_dir: str,
output_dir: str,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[None, None]:
"""
This algorithm intersects a vector layer extent
with the extent of all rasters within a directory, and
copies them in an other directory if they
intersect.
:param extent: Extent of your study area
:type extent: QgsRectangle
:param input_dir: Directory containing the files, defaults to None
:type input_dir: str, optional
:param output_dir: Directory
:type output_dir_dir: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: Empty dict
:rtype: Dict[None, None]
"""
from hedge_tools.algorithm.extraction import ManageRasterTilesAlgorithm
alg = ManageRasterTilesAlgorithm(extent, input_dir, output_dir, None, context)
results = alg.process_algorithm()
return results
[docs]
@feedback
def generate_dhm(
self,
dtm: QgsRasterLayer,
dsm: QgsRasterLayer,
dtm_band: int = 1,
dsm_band: int = 1,
threshold: float = 2.5,
extent: Optional[QgsRectangle] = None,
extent_option: int = 0,
resampling: int = -1,
resolution: Optional[float] = None,
output_path: str = "TEMPORARY_OUTPUT",
output_mask: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsRasterLayer]:
"""
Computes a DHM from a DTM and a DSM.
:param dtm: Digital Terrain Model raster
:type dtm: QgsRasterLayer
:param dsm: Digital Surface Model raster
:type dsm: QgsRasterLayer
:param dtm_band: Band of the dtm, GDAL numerotation, defaults to 1
:type dtm_band: int
:param dsm_band: Band of the dsm, GDAL numerotation, defaults to 1
:type dsm_band: int
:param extent: Subset extent if provided, defaults to None
:type extent: Optional[QgsRectangle]
:param threshold: Threshold to binarize the dhm, defaults to 2.5
:type threshold: float, optional
:param extent_option: Handling of extent differences, defaults to 0
- 0: Ignore - Will ignore CRS and only check both inputs dimensions. \
Raise an error if both inputs have different dimensions.
- 1: Fail - Will check if both inputs are in the same CRS \
and have the same dimensions (Equality predicate). Raise an error otherwise.
- 2: Union - Execute algorithm on the union of inputs.
- 3: Intersect - Execute algorithm on the intersection of inputs. \
Raise an error if there is no intersection.
:type extent_option: Union[ExtentMethod, int]
:param resampling: Resampling method for output resolution, defaults to -1
- -1: None
- 0: Nearest Neighbour
- 1: Bilinear (2x2 Kernel)
- 2: Cubic (4x4 Kernel)
- 3: Cubic B-Spline (4x4 Kernel)
- 4: Lanczos (6x6 Kernel)
- 5: Average
- 6: Mode
- 7: Maximum
- 8: Minimum
- 9: Median
- 10: First Quartile (Q1)
- 11: Third Quartile (Q3)
:type resampling: int, optional
:param resolution: Output resolution, defaults to None
:type resolution: Optional[float], optional
:param output_path: Path of the output mnh, defaults to "TEMPORARY_OUTPUT"
:type output_path: str
:param output_mask: Path of the output mask, defaults to "TEMPORARY_OUTPUT"
:type output_mask: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsRasterLayer as values, with keys being:
- OUTPUT_DHM: DHM raster
- OUTPUT_MASK: Binary mask of DHM raster
:rtype: Dict[str, QgsRasterLayer]
"""
from hedge_tools.algorithm.extraction import DHMAlgorithm
alg = DHMAlgorithm(
dtm,
dsm,
dtm_band,
dsm_band,
extent,
threshold,
extent_option,
resampling,
resolution,
output_path,
output_mask,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def generate_ndvi(
self,
red: QgsRasterLayer,
infrared: QgsRasterLayer,
red_band: int = 1,
infrared_band: int = 1,
extent: Optional[QgsRectangle] = None,
threshold: float = 0.2,
compute_sieve: bool = True,
sieve_threshold: int = 100,
extent_option: int = 0,
resampling: int = -1,
resolution: Optional[float] = None,
output_path: str = "TEMPORARY_OUTPUT",
output_mask: str = "TEMPORARY_OUTPUT",
output_sieve: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsRasterLayer]:
"""
Computes NDVI from a raster with a red band and a raster with an infrared band.
:param red: Raster with a red band
:type red: QgsRasterLayer
:param infrared: Raster with an infrared band
:type infrared: QgsRasterLayer
:param red_band: Red band, GDAL numerotation, defaults to 1
:type red_band: int
:param infrared_band: Infrared band, GDAL numerotation, defaults to 1
:type infrared_band: int
:param extent: Subset extent if provided, defaults to None
:type extent: Optional[QgsRectangle]
:param threshold: Threshold to binarize the ndvi, defaults to 0.2
:type threshold: float, optional
:param compute_sieve: If true will sieve the mask it is strongly advised
to avoid creating small features, defaults to True
:type compute_sieve: float, optional
:param sieve_threshold: Object size to delete in number of pixel, defaults to 100
:type sieve_threshold: float, optional
:param extent_option: Handling of extent differences, defaults to 0
- 0: Ignore - Will ignore CRS and only check both inputs dimensions. \
Raise an error if both inputs have different dimensions.
- 1: Fail - Will check if both inputs are in the same CRS \
and have the same dimensions (Equality predicate). Raise an error otherwise.
- 2: Union - Execute algorithm on the union of inputs.
- 3: Intersect - Execute algorithm on the intersection of inputs. \
Raise an error if there is no intersection.
:type extent_option: Union[ExtentMethod, int]
:param resampling: Resampling method for output resolution, defaults to -1
- -1: None
- 0: Nearest Neighbour
- 1: Bilinear (2x2 Kernel)
- 2: Cubic (4x4 Kernel)
- 3: Cubic B-Spline (4x4 Kernel)
- 4: Lanczos (6x6 Kernel)
- 5: Average
- 6: Mode
- 7: Maximum
- 8: Minimum
- 9: Median
- 10: First Quartile (Q1)
- 11: Third Quartile (Q3)
:type resampling: int, optional
:param resolution: Output resolution, defaults to None
:type resolution: Optional[float], optional
:param output_path: Path of the output mnh, defaults to "TEMPORARY_OUTPUT"
:type output_path: str
:param output_mask: Path of the output mask, defaults to "TEMPORARY_OUTPUT"
:type output_mask: str
:param output_sieve: Output path of the sieved mask, defaults to "TEMPORARY_OUTPUT"
:type output_sieve: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsRasterLayer as values, with keys being:
- OUTPUT_NDVI: NDVI raster
- OUTPUT_MASK: Binary mask of NDVI raster
- OUTPUT_SIEVE: Sieved mask of NDVI, optional
:rtype: Dict[str, QgsRasterLayer]
"""
from hedge_tools.algorithm.extraction import NDVIAlgorithm
alg = NDVIAlgorithm(
red,
infrared,
red_band,
infrared_band,
extent,
threshold,
compute_sieve,
sieve_threshold,
extent_option,
resampling,
resolution,
output_path,
output_mask,
output_sieve,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def generate_tree_cover(
self,
dhm: QgsRasterLayer,
ndvi: QgsRasterLayer,
dhm_band: int = 1,
ndvi_band: int = 1,
extent: Optional[QgsRectangle] = None,
compute_sieve: bool = True,
sieve_threshold: int = 100,
extent_option: int = 0,
resampling: int = -1,
resolution: Optional[float] = None,
polygonize: bool = True,
output_path: str = "TEMPORARY_OUTPUT",
output_sieve: str = "TEMPORARY_OUTPUT",
output_vector: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, Union[QgsRasterLayer, QgsVectorLayer]]:
"""
Computes a tree cover raster and vector layer from a DHM and a NDVI raster
:param dhm: Digital Height Model raster
:type dhm: QgsRasterLayer
:param ndvi: NDVI raster
:type ndvi: QgsRasterLayer
:param dhm_band: Band of the DHM, GDAL numerotation, defaults to 1
:type dhm_band: int
:param ndvi_band: Band of the NDVI, GDAL numerotation, defaults to 1
:type ndvi_band: int
:param extent: Subset extent if provided, defaults to None
:type extent: Optional[QgsRectangle]
:param compute_sieve: If true will sieve the mask it is strongly advised
to avoid creating small features, defaults to True
:type compute_sieve: float, optional
:param sieve_threshold: Object size to delete in number of pixel, defaults to 100
:type sieve_threshold: float, optional
:param extent_option: Handling of extent differences, defaults to 0
- 0: Ignore - Will ignore CRS and only check both inputs dimensions. \
Raise an error if both inputs have different dimensions.
- 1: Fail - Will check if both inputs are in the same CRS \
and have the same dimensions (Equality predicate). Raise an error otherwise.
- 2: Union - Execute algorithm on the union of inputs.
- 3: Intersect - Execute algorithm on the intersection of inputs. \
Raise an error if there is no intersection.
:type extent_option: Union[ExtentMethod, int]
:param resampling: Resampling method for output resolution, defaults to -1
- -1: None
- 0: Nearest Neighbour
- 1: Bilinear (2x2 Kernel)
- 2: Cubic (4x4 Kernel)
- 3: Cubic B-Spline (4x4 Kernel)
- 4: Lanczos (6x6 Kernel)
- 5: Average
- 6: Mode
- 7: Maximum
- 8: Minimum
- 9: Median
- 10: First Quartile (Q1)
- 11: Third Quartile (Q3)
:type resampling: int, optional
:param resolution: Output resolution, defaults to None
:type resolution: Optional[float], optional
:param polygonize: If True will output a vector, defaults to True
:type polygonize: Optional[float], optional
:param output_path: Path of the output mnh, defaults to "TEMPORARY_OUTPUT"
:type output_path: str
:param output_mask: Path of the output mask, defaults to "TEMPORARY_OUTPUT"
:type output_mask: st]
:param output_sieve: Output path of the sieved mask, defaults to "TEMPORARY_OUTPUT"
:type output_sieve: str
:param output_vector: Output path for the vector output, defaults to "TEMPORARY_OUTPUT"
:type output_vector: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsRasterLayer/QgsVectorLayer as values, with keys being:
- OUTPUT_TREE_COVER: tree cover raster
- OUTPUT_SIEVE: Sieved tree cover, optional
- OUTPUT_VECTOR: tree cover vector layer, optional
:rtype: Dict[str, Union[QgsRasterLayer, QgsVectorLayer]]
"""
from hedge_tools.algorithm.extraction import TreeCoverAlgorithm
alg = TreeCoverAlgorithm(
dhm,
ndvi,
dhm_band,
ndvi_band,
extent,
compute_sieve,
sieve_threshold,
extent_option,
resampling,
resolution,
polygonize,
output_path,
output_sieve,
output_vector,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def preprocess_cwa(
self,
layer: QgsVectorLayer,
min_area: float = 20,
tolerance: float = 0.25,
hole_size: float = 50,
request: Optional[QgsFeatureRequest] = None,
output_path: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
A preprocessing step is required before to categorize wooded area.
It consists in:
- eliminating small polygons with an area lower than the threshold
- simplifying geometry with a Douglas-Peucker algorithm to smooth
the vector borders obtained from a raster data
- removing holes in polygons with an area lower than the threshold
- correcting final geometry
:param layer: Layer to preprocess
:type layer: QgsVectorLayer
:param min_area: Minimum area of geometries, defaults to 20
:type min_area: float, optional
:param tolerance: Simplificaiton tolerance of the geometries, defaults to 0.25
:type tolerance: float, optional
:param hole_size: Minimum hole size in the geometries, defaults to 50
:type hole_size: float, optional
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path of the output layer, defaults to TEMPORARY_OUTPUT
:type output_path: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.extraction import PreprocessCWAAlgorithm
alg = PreprocessCWAAlgorithm(
layer,
min_area,
tolerance,
hole_size,
request,
output_path,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def cwa(
self,
layer: QgsVectorLayer,
opening: int = 20,
forest_area: int = 5000,
grove_area: int = 500,
request: Optional[QgsFeatureRequest] = None,
output_forest_path: str = "TEMPORARY_OUTPUT",
output_grove_path: str = "TEMPORARY_OUTPUT",
output_hedge_path: str = "TEMPORARY_OUTPUT",
output_tree_path: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
This algorithm classifies a tree cover layer into four categories:
forest, grove, linear element, and scattered tree.
The buffer parameter is used to disconnect
aggregated forests and linear element using an
erosion-dilatation process. This parameter should be
close to the maximum width of the linear element
(hedgerows).
Thresholds related to forest and grove area are used
to discriminate them based on their extent.
:param layer: Layer to categorize
:type layer: QgsVectorLayer
:param openening: Morphological opening value to disconnect continuous elements,
defaults to 20
:type openening: int, optional
:param forest_area: Minimum size of an element to be a forest, defaults to 5000
:type forest_area: int, optional
:param grove_area: Minimum size of an element to be a grove, defaults to 500
:type grove_area: int, optional
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_forest_path: Path of the output forest layer, defaults to TEMPORARY_OUTPUT
:type output_forest_path: str
:param output_grove_path: Path of the output grove layer, defaults to TEMPORARY_OUTPUT
:type output_grove_path: str
:param output_hedge_path: Path of the output hedge layer, defaults to TEMPORARY_OUTPUT
:type output_hedge_path: str
:param output_tree_path: Path of the output tree layer, defaults to TEMPORARY_OUTPUT
:type output_tree_path: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- FOREST: Forest polygon layer
- GROVE: Grove polygon layer
- HEDGE: Hedge polygon layer
- TREE: Tree polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.extraction import CategorizeWoodedAreaAlgorithm
alg = CategorizeWoodedAreaAlgorithm(
layer,
opening,
forest_area,
grove_area,
request,
output_forest_path,
output_grove_path,
output_hedge_path,
output_tree_path,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def generate_tree_cover_from_lidar(
self,
las: QgsPointCloudLayer,
category: List[float] = [4, 5],
extent: Optional[QgsRectangle] = None,
compute_sieve: bool = True,
sieve_threshold: int = 100,
resolution: Optional[float] = 1,
polygonize: bool = True,
output_path: str = "TEMPORARY_OUTPUT",
output_sieve: str = "TEMPORARY_OUTPUT",
output_vector: str = "TEMPORARY_OUTPUT",
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, Union[QgsRasterLayer, QgsVectorLayer]]:
"""
Computes a tree cover raster from a point cloud layer by extracting
vegetation points.
:param las: Classified point cloud layer
:type las: QgsPointCloudLayer
:param category: Vegetation category to keep. Accepted value are 3,4,5, defaults to [4,5]
:type category: List[float])
:param extent: Subset extent if provided, defaults to None
:type extent: Optional[QgsRectangle]
:param compute_sieve: If true will sieve the mask it is strongly advised
to avoid creating small features, defaults to True
:type compute_sieve: float, optional
:param sieve_threshold: Object size to delete in number of pixel, defaults to 100
:type sieve_threshold: float, optional
:param resolution: Output resolution, defaults to 1
:type resolution: Optional[float], optional
:param polygonize: If True will output a vector, defaults to True
:type polygonize: Optional[float], optional
:param output_path: Path of the output mnh, defaults to "TEMPORARY_OUTPUT"
:type output_path: str
:param output_mask: Path of the output mask, defaults to "TEMPORARY_OUTPUT"
:type output_mask: st]
:param output_sieve: Output path of the sieved mask, defaults to "TEMPORARY_OUTPUT"
:type output_sieve: str
:param output_vector: Output path for the vector output, defaults to "TEMPORARY_OUTPUT"
:type output_vector: str
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsRasterLayer/QgsVectorLayer as values, with keys being:
- OUTPUT_TREE_COVER: tree cover raster
- OUTPUT_SIEVE: Sieved tree cover, optional
- OUTPUT_VECTOR: tree cover vector layer, optional
:rtype: Dict[str, Union[QgsRasterLayer, QgsVectorLayer]]
"""
from hedge_tools.algorithm.lidar import TreeCoverLidarAlgorithm
alg = TreeCoverLidarAlgorithm(
las,
category,
extent,
compute_sieve,
sieve_threshold,
resolution,
polygonize,
output_path,
output_sieve,
output_vector,
None,
context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def topological_arc(
self,
poly_layer: QgsVectorLayer,
threshold: float = 30.0,
min_width: float = -1.0,
# tolerance: float = 0.0, # 5
# width: float = 0.0, # 10
request: Optional[QgsFeatureRequest] = None,
out_poly_path: Optional[str] = None,
out_arc_path: Optional[str] = None,
out_error_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the median axis of polygons
The polygons will be exploded to single part if multiparts geometry is provided
Each linestring will have an unique identifier "eid" and a link
with his parent polygon with the field "pid".
The "pid" field in the polygon layer will be computed again.
The dangle length parameter defines the length threshold below which a dangle is removed.
A dangle is a line segment that starts from a node with a degree of 3 or more and ends at a terminal node (an extremity).
The minimal width parameter is used to estimate the densification value applied internally.
If too high, the median axis may become disconnected in narrow areas of the polygon.
The default value of -1 allows to estimate automatically the minimal width but a
minimal value of 2 is hard coded to prevent increasing too much the processing times.
You can specify a value lower than 2 and different than -1 to override this safeguard.
An error layer will be created to output all disconnected arc form the main network.
You can reconnect them manually. The unique identifier should already be handle.
The output polygon layer will have a boolean field is_hedge. If the value is False,
it indicates that a complex line network (a network with many high degree nodes)
has been detected within the polygon.
This suggests that the feature may have been incorrectly classified as a hedge.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param threshold: Dangle length threshold for pruning, defaults to 30.0
:type threshold: float, optional
:param min_width: Minimal width used for densification, defaults to -1.0
:type min_width: float, optional
:param tolerance: Dangle length (threshold) tolerance, defaults to 10.0
From threshold - tolerance to threshold width of the polygon will be measured.
If the width is bigger than the width parameter value. The dangle will be kept.
:type tolerance: float, optional
:param width: Width value used for advanced dangle length pruning, defaults to -1.0
:type width: float, option
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_poly_path: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type output_poly_path: Optional[str], optional
:param output_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type output_arc_path: Optional[str], optional
:param out_error_path: Path for the output error layer
if None it'll create a memory layer, defaults to None
:type out_error_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
- OUTPUT_ERROR: Error layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.preparation import TopologicalArcAlgorithm
alg = TopologicalArcAlgorithm(
poly_layer,
threshold,
min_width,
# tolerance,
# width,
request,
out_poly_path,
out_arc_path,
out_error_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def topological_node(
self,
arc_layer: QgsVectorLayer,
request: Optional[QgsFeatureRequest] = None,
out_arc_path: Optional[str] = None,
out_node_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes nodes of a median axis layer
:param arc_layer: Layer of linestring
:type arc_layer: QgsVectorLayer
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param out_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc_path: Optional[str], optional
:param out_node_path: Path for the output node layer
if None it'll create a memory layer, defaults to None
:type out_node_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.preparation import TopologicalNodeAlgorithm
alg = TopologicalNodeAlgorithm(
arc_layer,
request,
out_arc_path,
out_node_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def topological_polygon(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
node_type: str = "node_type",
request: Optional[QgsFeatureRequest] = None,
out_poly_path: Optional[str] = None,
out_arc_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Splits the polygons to match the topological view of the arc.
Arc 'eid' and 'pid' fields will be updated to match the 'pid' polygon fields
The fields will be overwritten if it already exists.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of Arc
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of Node
:type node_layer: QgsVectorLayer
:type node_type: Field that store the node tpye (O,L,T,X)
:param node_type: str
:param request: Request to work on a subset of features of polygon_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param out_poly_path: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type out_poly_path: Optional[str], optional
:param out_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.preparation import TopologicalPolygonAlgorithm
alg = TopologicalPolygonAlgorithm(
poly_layer,
arc_layer,
node_layer,
node_type,
request,
out_poly_path,
out_arc_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def simplify_arc(
self,
arc_layer: QgsVectorLayer,
poly_layer: QgsVectorLayer,
tolerance: float = 1.0,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Simplifies arcs with a douglas-peucker algorithm. The resulting linear geometry
is constrained inside an englobing polygon.
Polygon and arc can share an unique identifier field 'pid' to speed up the process
If no solution was found the original geometry will be returned.
:param arc_layer: Layer of LineString
:type arc_layer: QgsVectorLayer
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param tolerance: Shift tolerance (meters), defaults to 1.0
:type tolerance: float, optional
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_ARC: Arc layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.preparation import SimplifyArcAlgorithm
alg = SimplifyArcAlgorithm(
arc_layer,
poly_layer,
tolerance,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def split_by_interval(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
node_type: str = "node_type",
length: float = 25,
request: Optional[QgsFeatureRequest] = None,
out_poly_path: Optional[str] = None,
out_arc_path: Optional[str] = None,
out_node_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Splits the polygons at a regular interval spiecifed by the length parameter.
All the PK and FK will be updated (vid, eid, pid, src_vid, tgt_vid)
If the cutted hedge is below the speicifed length the split will be ignored.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of Arc
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of Node
:type node_layer: QgsVectorLayer
:type node_type: Field that store the node tpye (O,L,T,X)
:param node_type: str
:type length: Minimal length of the hedge, defaults to 20.
:param length: float, optional
:param request: Request to work on a subset of features of polygon_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param out_poly_path: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type out_poly_path: Optional[str], optional
:param out_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc_path: Optional[str], optional
:param out_node_path: Path for the output node layer
if None it'll create a memory layer, defaults to None
:type out_node_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.transformation import SplitByIntervalAlgorithm
alg = SplitByIntervalAlgorithm(
poly_layer,
arc_layer,
node_layer,
node_type,
length,
request,
out_poly_path,
out_arc_path,
out_node_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def split_by_orientation(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
node_type: str = "node_type",
angle: float = 30,
min_length: float = 25,
request: Optional[QgsFeatureRequest] = None,
out_poly_path: Optional[str] = None,
out_arc_path: Optional[str] = None,
out_node_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Splits polygons when orientaiton of the arc changes more than the specified angle value.
A minimal length parameter allows to prevents the creation of hedge below this length.
All the PK and FK fields will be updated (vid, eid, pid, src_vid, tgt_vid)
If the cutted hedge is below the speicifed length the split will be ignored.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of Arc
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of Node
:type node_layer: QgsVectorLayer
:type node_type: Field that store the node tpye (O,L,T,X)
:param node_type: str
:param angle: Length of the new segments. If the last created segments
is below the provided value it'll be skept, defaults to 30.
:type angle: float, optional
:type min_length: Minimal length of the hedge, defaults to 25.
:param min_length: float, optional
:param request: Request to work on a subset of features of polygon_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param out_poly_path: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type out_poly_path: Optional[str], optional
:param out_arc_path: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc_path: Optional[str], optional
:param out_node_path: Path for the output node layer
if None it'll create a memory layer, defaults to None
:type out_node_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.transformation import SplitByOrientationAlgorithm
alg = SplitByOrientationAlgorithm(
poly_layer,
arc_layer,
node_layer,
node_type,
angle,
min_length,
request,
out_poly_path,
out_arc_path,
out_node_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def length(
self,
layer: QgsVectorLayer,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes length of a LineString geometry from a Layer with an 'eid' field.
The results will be stored inside a field called 'length'.
The field will be overwritten if it already exists.
:param layer: Layer of linear feature
:type layer: QgsVectorLayer
:param request: Subset of feature to work on, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path to write the results at, defaults to None
:type output_path: Optional[str], optional
:param context: Context to store the temporary layer, defaults to QgsProcessingContext()
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Arc layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.morphology import LengthAlgorithm
alg = LengthAlgorithm(
layer,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def orientation(
self,
layer: QgsVectorLayer,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes orientation of a LineString geometry from a Layer with an 'eid' field.
The results will be stored inside a field called 'direction'.
The field will be overwritten if it already exists.
:param layer: Layer of linear feature
:type layer: QgsVectorLayer
:param request: Subset of feature to work on, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path to write the results at, defaults to None
:type output_path: Optional[str], optional
:param context: Context to store the temporary layer, defaults to QgsProcessingContext()
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Arc layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.morphology import OrientationAlgorithm
alg = OrientationAlgorithm(
layer,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def shape(
self,
layer: QgsVectorLayer,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes shape metrics of a polygon geometry from a layer with an 'pid' field. \
The shape metrics are the following:
- elongation: Indicates the degree of stretching of a polygon. However, this indicator \
is not very reliable for complex shapes (e.g., U-shaped polygons). Values range between 0 \
(highly stretched) and 1 (less stretched).
- compactness: Indicates the density of a polygon. Values range between 0 (not compact) \
and 1 (highly compact).
- convexity: Indicates the regularity of the polygon’s outline, its sphericity. \
Values range between 0 (non-convex) and 1 (convex).
The results will be stored inside fields called 'elongation, compactness, convexity'. \
The field will be overwritten if it already exists.
:param layer: Layer of polygon feature
:type layer: QgsVectorLayer
:param request: Subset of feature to work on, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path to write the results at, defaults to None
:type output_path: Optional[str], optional
:param context: Context to store the temporary layer, defaults to QgsProcessingContext()
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.morphology import ShapeAlgorithm
alg = ShapeAlgorithm(
layer,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def width(
self,
polygon_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
request: Optional[QgsFeatureRequest] = None,
methods: Union[List[int], List[Enum], int, Enum] = [WidthFields.APPROX],
distance: float = 10.0,
percentile: Optional[int] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes width of a polygon geometry from a layer with a 'pid' field.
The results will be stored inside fields called 'width_*'. \
The mean, median, min, max and std will be computed. \
The fields will be overwritten if it already exists.
Two methods are available :
- accurate : slower but more accurate. \
Use transect perpendicular to the median axis to estimate the width.
- approximative : faster but less accurate. \
Use the property of the median axis to estimate the width of a polygon. \n
The distance parameter is the interval between each width computation. \
The smaller it gets and the longer the algorithm will take but width will be me more accurate.
An optional percentile parameter is available allowing to remove outliers in widths \
values before staitstic computation. An equal slice will be removed from each extremities, \
for example a percentile of 25 will remove the first and last quartile.
In both methods, if the length of the hedge is shorter than the specified distance plus \
an offset of 2 meters (required to allow multiple width samples), the width fields will be set to NULL.
:param polygon_layer: layer of Polygon
:type polygon_layer: QgsVectorLayer
:param arc_layer: layer of Linestring
:type arc_layer: QgsVectorLayer
:param request: Request to work on a subset of polygon features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param methods: Methods to compute width with. Can be APPROX (1) or ACCURATE (0) or both
:type methods: Union[List[int], List[Enum], int, Enum], defaults to [Method.APPROX]
:param distance: Distance between each node that'll be used either for transect
or semi distance computation, defaults to 3m.
:type distance: float
:param percentile: Percentile to use to compute iqr and remove outliers, defaults to None
:type percentile: Optional[int], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.morphology import WidthAlgorithm
alg = WidthAlgorithm(
polygon_layer,
arc_layer,
methods,
distance,
percentile,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def height(
self,
layer: QgsVectorLayer,
raster: QgsRasterLayer,
band: int = 1,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes height metrics of a Polygon geometry from a Layer with an 'pid' field
with the help of a DHM.
The results will be stored inside fields called 'height_*'.
The mean, median, min, max and std will be computed.
The fields will be overwritten if it already exists.
:param layer: Layer of polygon
:type layer: QgsVectorLayer
:param raster: DHM raster
:type raster: QgsRasterLayer
:param band: Band index, default to one
:type band: int
:param request: Request to work on a subset of features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
# Need to import after initialization of processing
from hedge_tools.algorithm.physiognomy import HeightAlgorithm
alg = HeightAlgorithm(
layer,
raster,
band,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def strata_proportion(
self,
layer: QgsVectorLayer,
raster: QgsRasterLayer,
band: int = 1,
bins: Optional[List[float]] = None,
spatial: bool = False,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
output_spatial: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes strata proportion within a polygon geometry from a layer with a 'pid' field
with the help of a DHM.
Returns each height category as a percentage in the polygon layer's fields.
It is also possible to ask for a spatial representation of each category.
The lower boundary and upper boundary are assumed to be 0
and maximum values of the height raster, respectively.
By default, AFAC strata are used: [0 - 0.3[, [0.3 - 2[, [2 - 7[, [7 - max value].
The `nb_strata` field will show the number of strata.
The `dom_strata` field will show the strata with the most overlap
The fields will be overwritten if it already exists.
:param layer: Layer of polygon
:type layer: QgsVectorLayer
:param raster: DHM raster
:type raster: QgsRasterLayer
:param band: Band index, default to one
:type band: int
:param bins: Bins for the strata edges, default to [0.3, 2, 7] (meters)
:type bins: Optional[List[float]]
:param spatial: Output a spatial representation of the bins, default to False
:type spatial: bool
:param request: Request to work on a subset of features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param output_spatial: Path for the optional spatial representation output
if None it'll create a memory layer, defaults to None
:type output_spatial: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
- OUTPUT_SPATIAL: Polygon layer, optional
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.physiognomy import StrataProportionAlgorithm
alg = StrataProportionAlgorithm(
layer,
raster,
band,
bins,
spatial,
request,
output_path,
output_spatial,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def topographic_position(
self,
layer: QgsVectorLayer,
raster: QgsRasterLayer,
band: int = 1,
search: float = 100,
skip: float = 50,
kernel_size: int = 7,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the topographic position of an hedge using a DTM.
The results will be stored in a topo_position field.
The fields will be overwritten if it already exists.
The default parameter values work well for a 5 meters resolution DTM,
and the DTM extent should be slightly bigger than the vector layers' extent,
as the search and skip parameters need contextual information.
If it is not possible, hedges in the border of the study area might be given
the wrong position or no data as a result.
The search parameter represents the radius in meters for searching terrain shapes.
A higher value will give more global terrain shapes.
The skip parameter is the radius in meters where the terrain shapes will be ignored.
A higher value will ignore bigger changes in topography.
The kernel radius parameter is used to further remove local changes.
A higher resolution DTM should use a bigger kernel (5 or 7).
:param polygon_layer: Layer of Polygon
:type polygon_layer: QgsVectorLayer
:param raster: Elevation raster (DTM)
:type raster: QgsRasterLayer
:param band: Band index, default to one
:type band: int
:param search: Neighbour context in meters for computing terrain form. Defaults to 100.
:type search: float
:param skip: Neighbour context to skip to avoid micro topography. Defaults to 50.
:type skip: float
:param kernel_size: Majority filter kernel radius (odd). Defaults to 100.
:type kernel_size: int
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param request: Request to work on a subset of polygon features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.context import TopographicPositionAlgorithm
alg = TopographicPositionAlgorithm(
layer,
raster,
band,
search,
skip,
kernel_size,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def relative_orientation(
self,
polygon_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
raster: QgsRasterLayer,
band: int = 1,
kernel_size: int = 7,
tolerance: int = 15,
min_slope: int = 3,
use_geomorphon: bool = False,
search: int = 100,
skip: int = 50,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the relative orientation of an hedge in the slope.
The results will be stored in a slope_position field.
It can either be perpendicular, parallel, diagonal, or no slope.
The fields will be overwritten if it already exists.
The hedge orientation will be computed unless if found in the arc layer attribute table.
If the orientation stored in the attribute table does
not reflect the current hedge you should delete the fields.
The median filter allows reducing noise induced by special features (like steep river banks).
Offset tolerance allows deviating from the perpendicular between
the hedge orientation and a slope orientation.
A minimum slope parameter determines if the hedge is on a slope or not,
and topographic position can be used to automatically categorize hedges
inside a ridge or a valley with no slope. Topographic position can either
be computed in the algorithm or users can use the topo_pos field
if they already have computed topographic_position.
Note: The use of topographic position will increase the 'no slope' category.
This will increase further as the skip distance increases.
Recommended values for a 5 meters resolution DTM are to search 100 meters
and skip either 0 or 50 meters.
:param polygon_layer: Layer of Polygon
:type polygon_layer: QgsVectorLayer
:param arc_layer: Layer of Linestring
:type arc_layer: QgsVectorLayer
:param raster: Elevation raster (DTM)
:type raster: QgsRasterLayer
:param band: Band index, default to one
:type band: int
:param kernel_size: Median filter kernel radius in pixels (odd). Defaults to 7.
:type kernel_size: int
:param tolerance: Angle tolerance between the slope aspect and hedge orientation, defaults to 15
:type tolerance: int, optional
:param min_slope: Below the hedge will be considered on flat ground, defaults to 3
:type min_slope: int, optional
:param use_geomorphon: If True will use geomorphon to classify ridge and valley
hedge as flat. If a topo_position field is available it'll use it, defaults to False.
:type use_geomorphon: bool, optional
:param search: Neighbour context in meters for computing terrain form. Defaults to 100.
:type search: float
:param skip: Neighbour context to skip to avoid micro topography. Defaults to 50.
:type skip: float
:param request: Request to work on a subset of polygon features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.context import RelativeOrientationAlgorithm
alg = RelativeOrientationAlgorithm(
polygon_layer,
arc_layer,
raster,
band,
kernel_size,
tolerance,
min_slope,
use_geomorphon,
search,
skip,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def euclidean_distance_to_forest(
self,
node_layer: QgsVectorLayer,
forest_layer: QgsVectorLayer,
id_field: str,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the euclidean distance of an hedge to the closest forest.
The results will be stored in a forest_dist field
and the unique identifier of the forest in a forest_id field.
The fields will be overwritten if it already exists.
:param node_layer: Layer of arc used to fetch
:type node_layer: QgsVectorLayer
:param forest_layer: Forest layer
:type forest_layer: QgsVectorLayer
:param id_field: Unique identifier field of the forest
:type id_field: int
:param request: Request to work on a subset of polygon features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.context import EuclDistanceToForestAlgorithm
alg = EuclDistanceToForestAlgorithm(
node_layer,
forest_layer,
id_field,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def connectivity(
self,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
poly_layer: Optional[QgsVectorLayer] = None,
threshold: float = 0.0,
override: bool = False,
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the connectivity metrics of hedge sub-networks.
The results will be stored inside fields called the selected metrics name.
The fields will be overwritten if it already exists.
The sub-network is composed of all topologically connected arcs,
i.e., all connected hedges forming a connected component (sub-network).
A spatial tolerance parameter can be provided to consider that spatially disjoint
topological arcs belong to the same sub-network.
If the layer already have a network_id field the network will be reconstructed
from it.
The n_threshold field is used if its value is different from 0.
This value is identical for all hedges in the input layer.
:param arc_layer: Layer of linestring
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of node
:type node_layer: QgsVectorLayer
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param threshold: Spatial threshold to consider hedges in the same network.
The threshold is applied between the polygons if a layer is provided, defaults to 0.0
:type threshold: float
:param override: If True will reconstruct the network and discard fields if available,
defaults to False
:type threshold: bool
:param request: Request to work on a subset of features of arc_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Arc layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.network import ConnectivityAlgorithm
alg = ConnectivityAlgorithm(
arc_layer,
node_layer,
poly_layer,
threshold,
override,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def network_distance_to_forest(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
forest_layer: QgsVectorLayer,
id_field: str,
threshold: float = 0.0,
override: bool = False,
request: Optional[QgsFeatureRequest] = None,
out_arc_path: Optional[str] = None,
out_node_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes the distance between a hedge sub-network and the forest closest to that sub-network.
The results will be stored in a n_forest_dist field
and the unique identifier of the forest in a n_forest_id field.
The fields will be overwritten if it already exists.
The sub-network is composed of all topologically connected arcs,
i.e., all connected hedges forming a connected component (sub-network).
A spatial tolerance parameter can be provided to consider that spatially disjoint
topological arcs belong to the same sub-network.
If the layer already have a network_id field the network will be reconstructed
from it. \
The n_threshold field is used if its value is different from 0.
This value is identical for all hedges in the input layer.
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of linestring
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of node
:type node_layer: QgsVectorLayer
:param forest_layer: Polygon layer of forest
:type forest_layer: QgsVectorLayer
:param id_field: Unique identifier field name of the forest
:type id_field: str
:param threshold: Spatial threshold to consider hedges in the same network.
The threshold is applied between the polygons if a layer is provided, defaults to 0.0
:type threshold: float
:param override: If True will reconstruct the network and discard fields if available,
defaults to False
:type threshold: bool
:param out_arc_path: Path for the output layer
:type out_arc_path: Optional[str], optional
:param out_node_path: Path for the output layer
:type out_node_path: Optional[str], optional
:param request: Request to work on a subset of polygon features, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.network import NetworkDistanceToForestAlgorithm
alg = NetworkDistanceToForestAlgorithm(
poly_layer,
arc_layer,
node_layer,
forest_layer,
id_field,
threshold,
override,
request,
out_arc_path,
out_node_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
@feedback
def grid(
self,
grid_layer: QgsVectorLayer,
metrics: Union[List[int], List[GridFields], int, GridFields],
arc_layer: Optional[QgsVectorLayer] = None,
node_layer: Optional[QgsVectorLayer] = None,
poly_layer: Optional[QgsVectorLayer] = None,
node_type: Optional[str] = "node_type",
forest_field: Optional[str] = "n_forest_dist",
request: Optional[QgsFeatureRequest] = None,
output_path: Optional[str] = None,
context: QgsProcessingContext = QgsProcessingContext(),
) -> Dict[str, QgsVectorLayer]:
"""
Computes landscape metrics for each tile of a grid layer \
The grid layer must be polygonal and can be created with the native qgis algorithm: create grid \n\
Landscape metrics can be:
- Count O - degree 1 nodes [0].
- Count L - degree 2 nodes [1].
- Count T - degree 3 nodes [2].
- Count X - degree 4 nodes [3].
- Count M - degree 5+ nodes [4].
- Icon [5]: Connectivity index, weighted sum of hedge intersections.
- Icoh (%) [6]: Consistency index, percentage of hedge intersections \
with other hedges and forest/grove over all hedge extremities;
- Density (ml/ha) [7]: Total length of hedges bring back to hectare;
- Density (%) [8]: Density of hedge for each tile;
- Icod [9]: Density and structure of bocage index;
- Total length [10]: Sum of arc length inside each tile
Some indices need additional data to be computed:
- Icoh and Icod needs a field of distance to the forest from the 'Network distance to forest' tool;
- Density (%) and Icod needs a polygon layer of hedges;
- Density (ml/ha) and total length needs an arc layer of hedges."
The results will be stored inside fields called the selected metrics name.
The fields will be overwritten if it already exists.
:param grid_layer: Layer of polygonal grid
:type grid_layer: QgsVectorLayer
:param metrics: Metrics available from 0 to 10 included
:type metrics: Union[List[int], List[Enum], int, Enum]
:param arc_layer: Layer of linestring, defaults to None
:type arc_layer: Optional[QgsVectorLayer]
:param node_layer: Layer of node, defaults to None
:type node_layer: Optional[QgsVectorLayer]
:param poly_layer: Layer of Polygon, defaults to None
:type poly_layer: Optional[QgsVectorLayer]
:param node_type: node type fields. Only used for node type count metrics, defaults to 'node_type'
:type node_type: Optional[str]
:param forest_field: Forest distance field in node layer. Only used for icohn defaults to 'forest_id'
:type forest_field: bool
:param request: Request to work on a subset of features of grid_layer, defaults to None
:type request: Optional[QgsFeatureRequest], optional
:param output_path: Path for the output layer
if None it'll create a memory layer, defaults to None
:type output_path: Optional[str], optional
:param context: Qgis context for avoiding gotcha, defaults to None
:type context: QgsProcessingContext, optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
from hedge_tools.algorithm.landscape import GridAlgorithm
alg = GridAlgorithm(
grid_layer,
metrics,
arc_layer,
node_layer,
poly_layer,
node_type,
forest_field,
request,
output_path,
feedback=None,
context=context,
)
results = alg.process_algorithm()
return results
[docs]
def extraction_from_lidar(
self,
las: QgsPointCloudLayer,
category: List[float] = [4, 5],
rst_res: float = 1,
min_area: float = 20,
tolerance: float = 0.25,
hole_size: float = 50,
opening: int = 20,
forest_area: int = 5000,
grove_area: int = 500,
output_forest: str = "TEMPORARY_OUTPUT",
output_grove: str = "TEMPORARY_OUTPUT",
output_hedge: str = "TEMPORARY_OUTPUT",
output_tree: str = "TEMPORARY_OUTPUT",
) -> Dict[str, QgsVectorLayer]:
"""
Performs the extraction module with a point cloud layer as input
All the parameters are not available and intermediate layers are not returned.
:param category: Vegetation category to keep. Accepted value are 3,4,5, defaults to [4,5]
:type category: List[float])
:param resolution: Output resolution, defaults to 1
:type resolution: Optional[float], optional
:param min_area: Minimum area of geometries, defaults to 20
:type min_area: float, optional
:param tolerance: Simplificaiton tolerance of the geometries, defaults to 0.25
:type tolerance: float, optional
:param hole_size: Minimum hole size in the geometries, defaults to 50
:type hole_size: float, optional
:param openening: Morphological opening value to disconnect continuous elements,
defaults to 20
:type openening: int, optional
:param forest_area: Minimum size of an element to be a forest, defaults to 5000
:type forest_area: int, optional
:param grove_area: Minimum size of an element to be a grove, defaults to 500
:type grove_area: int, optional
:param output_forest: Path of the output forest layer, defaults to TEMPORARY_OUTPUT
:type output_forest: str
:param output_grove: Path of the output grove layer, defaults to TEMPORARY_OUTPUT
:type output_grove: str
:param output_hedge: Path of the output hedge layer, defaults to TEMPORARY_OUTPUT
:type output_hedg: str
:param output_tree: Path of the output tree layer, defaults to TEMPORARY_OUTPUT
:type output_tree: str
:return: A dict with QgsVectorLayer as values, with keys being:
- FOREST: Forest polygon layer
- GROVE: Grove polygon layer
- HEDGE: Hedge polygon layer
- TREE: Tree polygon layer
:rtype: Dict[str, QgsVectorLayer]
"""
tree_cover = self.generate_tree_cover_from_lidar(
las, category=category, resolution=rst_res
)["OUTPUT_VECTOR"]
preprocess = self.preprocess_cwa(
tree_cover, min_area=min_area, tolerance=tolerance, hole_size=hole_size
)["OUTPUT"]
results = self.cwa(
preprocess,
opening=opening,
forest_area=forest_area,
grove_area=grove_area,
output_forest_path=output_forest,
output_grove_path=output_grove,
output_hedge_path=output_hedge,
output_tree_path=output_tree,
)
return results
[docs]
def data_preparation(
self,
poly_layer: QgsVectorLayer,
threshold: float = 30.0,
min_width: float = -1.0,
out_poly: Optional[str] = None,
out_arc: Optional[str] = None,
out_node: Optional[str] = None,
) -> Dict[str, QgsVectorLayer]:
"""
Performs the data preparation module with a polygon layer as input
All the parameters are not available and itnermediate layers are not returned
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param min_width: Minimal width used for densification, defaults to -1.0
:type min_width: float, optional
:param tolerance: Dangle length (threshold) tolerance, defaults to 10.0
From threshold - tolerance to threshold width of the polygon will be measured.
:param out_poly: Path for the output polygon layer
if None it'll create a memory layer, defaults to None
:type out_poly: Optional[str], optional
:param out_arc: Path for the output arc layer
if None it'll create a memory layer, defaults to None
:type out_arc: Optional[str], optional
:type out_arc: Optional[str], optional
:param out_node: Path for the output node layer
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Arc layer
- OUTPUT_ERROR: Error layer for the arc
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
results = self.topological_arc(
poly_layer, threshold=threshold, min_width=min_width
)
results.update(
self.topological_node(results["OUTPUT_ARC"], out_node_path=out_node)
)
results.update(
self.topological_polygon(
results["OUTPUT_POLY"],
results["OUTPUT_ARC"],
results["OUTPUT_NODE"],
out_poly_path=out_poly,
out_arc_path=out_arc,
)
)
return results
[docs]
def morphology(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
methods: Union[List[int], List[Enum], int, Enum] = [WidthFields.APPROX],
distance: float = 10.0,
out_poly: Optional[str] = None,
out_arc: Optional[str] = None,
) -> Dict[str, QgsVectorLayer]:
"""
Computes all available morphology metrics
All the parameters are not available and itnermediate layers are not returned
:param poly_layer: Layer of polygon feature
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of linear feature
:type arc_layer: QgsVectorLayer
:param methods: Methods to compute width with. Can be APPROX (1) or ACCURATE (0) or both
:type methods: Union[List[int], List[Enum], int, Enum], defaults to [Method.APPROX]
:param distance: Distance between each node that'll be used either for transect
or semi distance computation, defaults to 3m.
:type distance: float
:param out_poly: Path to write the results at, defaults to None
:type out_poly: Optional[str], optional
:param out_arc: Path to write the results at, defaults to None
:type out_arc: Optional[str], optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_ARC: Line layer
:rtype: Dict[str, QgsVectorLayer]
"""
outputs = {}
results = self.app.length(arc_layer)
outputs["OUTPUT_ARC"] = self.orientation(
results["OUTPUT"], output_path=out_arc
)["OUTPUT"]
results = self.shape(poly_layer)["OUTPUT"]
outputs["OUTPUT_ARC"] = self.width(
results["OUTPUT"], methods=methods, distance=distance, output_path=out_poly
)["OUTPUT"]
return outputs
[docs]
def physiognomy(
self,
poly_layer: QgsVectorLayer,
dhm: QgsRasterLayer,
band: int = 1,
bins: Optional[List[float]] = None,
spatial: bool = False,
out_poly: Optional[str] = None,
out_spatial: Optional[str] = None,
) -> Dict[str, QgsVectorLayer]:
"""
Computes all available physiognomy metrics
All the parameters are not available and itnermediate layers are not returned
:param poly_layer: Layer of polygon
:type poly_layer: QgsVectorLayer
:param dhm: DHM raster
:type dhm: QgsRasterLayer
:param band: Band index, default to one
:type band: int
:param bins: Bins for the strata edges, default to [0.3, 2, 7] (meters)
:type bins: Optional[List[float]]
:param spatial: Output a spatial representation of the bins, default to False
:type spatial: bool
:param out_poly: Path for the output layer
if None it'll create a memory layer, defaults to None
:type out_poly: Optional[str], optional
:param out_spatial: Path for the optional spatial representation output
if None it'll create a memory layer, defaults to None
:type out_spatial: Optional[str], optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT: Polygon layer
- OUTPUT_SPATIAL: Polygon layer, optional
:rtype: Dict[str, QgsVectorLayer]
"""
results = self.height(poly_layer, dhm, band=band)
results = self.strata_proportion(
results["OUTPUT"],
dhm,
band=band,
bins=bins,
spatial=spatial,
output_path=out_poly,
output_spatial=out_spatial,
)
return results
[docs]
def context(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
forest_layer: QgsVectorLayer,
id_field: str,
dtm: QgsRasterLayer,
band: int = 1,
search: float = 100,
skip: float = 50,
kernel_size: int = 7,
tolerance: int = 15,
min_slope: int = 3,
out_poly: Optional[str] = None,
out_node: Optional[str] = None,
) -> Dict[str, QgsVectorLayer]:
"""
Computes all available physiognomy metrics
All the parameters are not available and itnermediate layers are not returned
:param poly_layer: Layer of polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of Linestring
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of node
:type node_layer: QgsVectorLayer
:param forest_layer: Layer of forest
:type forest_layer: QgsVectorLayer
:param dtm: DTM raster
:type dtm: QgsRasterLayer
:param id_field: Unique identifier field of the forest
:type id_field: int
:param band: Band index, default to one
:type band: int
:param search: Neighbour context in meters for computing terrain form. Defaults to 100.
:type search: float
:param skip: Neighbour context to skip to avoid micro topography. Defaults to 50.
:type skip: float
:param kernel_size: Majority and median filter kernel radius (odd). Defaults to 100.
:type kernel_size: int
:param tolerance: Angle tolerance between the slope aspect and hedge orientation, defaults to 15
:type tolerance: int, optional
:param min_slope: Below the hedge will be considered on flat ground, defaults to 3
:type min_slope: int, optional
:param out_poly: Path for the output layer
if None it'll create a memory layer, defaults to None
:type out_poly: Optional[str], optional
:param out_node: Path for the output layer
if None it'll create a memory layer, defaults to None
:type out_node: Optional[str], optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_POLY: Polygon layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
outputs = {}
results = self.topographic_position(
poly_layer,
dtm,
band=band,
search=search,
skip=skip,
kernel_size=kernel_size,
)
outputs["OUTPUT_POLY"] = self.relative_orientation(
results["OUTPUT"],
arc_layer,
dtm,
use_geomorphon=True,
tolerance=tolerance,
min_slope=min_slope,
band=band,
search=search,
skip=skip,
kernel_size=kernel_size,
output_path=out_poly,
)["OUTPUT"]
outputs["OUTPUT_NODE"] = self.euclidean_distance_to_forest(
node_layer, forest_layer, id_field=id_field, output_path=out_node
)["OUTPUT"]
return outputs
[docs]
def network(
self,
poly_layer: QgsVectorLayer,
arc_layer: QgsVectorLayer,
node_layer: QgsVectorLayer,
forest_layer: QgsVectorLayer,
id_field: str,
threshold: float = 0.0,
override: bool = False,
out_arc: Optional[str] = None,
out_node: Optional[str] = None,
) -> Dict[str, QgsVectorLayer]:
"""
:param poly_layer: Layer of Polygon
:type poly_layer: QgsVectorLayer
:param arc_layer: Layer of linestring
:type arc_layer: QgsVectorLayer
:param node_layer: Layer of node
:type node_layer: QgsVectorLayer
:param forest_layer: Polygon layer of forest
:type forest_layer: QgsVectorLayer
:param id_field: Unique identifier field name of the forest
:type id_field: str
:param threshold: Spatial threshold to consider hedges in the same network.
The threshold is applied between the polygons if a layer is provided, defaults to 0.0
:type threshold: float
:param override: If True will reconstruct the network and discard fields if available,
defaults to False
:type threshold: bool
:param out_arc: Path for the output layer
:type out_arc: Optional[str], optional
:param out_node: Path for the output layer
:type out_node: Optional[str], optional
:return: A dict with QgsVectorLayer as values, with keys being:
- OUTPUT_ARC: Arc layer
- OUTPUT_NODE: Node layer
:rtype: Dict[str, QgsVectorLayer]
"""
arc = self.connectivity(
self,
arc_layer,
node_layer,
poly_layer,
threshold=threshold,
override=override,
)["OUTPUT"]
results = self.network_distance_to_forest(
poly_layer,
arc,
node_layer,
forest_layer,
id_field=id_field,
threshold=threshold,
override=False,
out_arc_path=out_arc,
out_node_path=out_node,
)
return results
@property
def exit(self):
self.unset_logger()
super().exit