diff --git a/CHANGELOG.md b/CHANGELOG.md index 0638b8e60..9734bf115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## 4.11.36 - dev +## 4.11.37 - dev Novas Funcionalidades: @@ -11,6 +11,7 @@ Novas Funcionalidades: - Novo processo de criar grid por meio de informar as coordenadas dos extremos; - Adicionado atalho para ativar/desativar modo reclassificaĆ§Ć£o; - Novo processo de criar pacote de shapefile (utilizado normalmente para preparar a carga no BDGEx); +- Novo processo de generalizar trechos de drenagem de acordo com o comprimento; Melhorias: diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/__init__.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/generalizeNetworkEdgesFromLengthAlgorithm.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/generalizeNetworkEdgesFromLengthAlgorithm.py new file mode 100644 index 000000000..1e862633a --- /dev/null +++ b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/generalizeNetworkEdgesFromLengthAlgorithm.py @@ -0,0 +1,304 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + DsgTools + A QGIS plugin + Brazilian Army Cartographic Production Tools + ------------------- + begin : 2023-11-27 + git sha : $Format:%H$ + copyright : (C) 2023 by Philipe Borba - Cartographic Engineer @ Brazilian Army + email : borba.philipe@eb.mil.br + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 itertools import chain +from typing import Dict, List, Optional, Set +from PyQt5.QtCore import QCoreApplication +from DsgTools.core.GeometricTools import graphHandler +from qgis.PyQt.QtCore import QByteArray +from qgis.core import ( + Qgis, + QgsProcessing, + QgsProcessingException, + QgsProcessingMultiStepFeedback, + QgsProcessingParameterEnum, + QgsProcessingParameterVectorLayer, + QgsFeedback, + QgsProcessingContext, + QgsVectorLayer, + QgsProcessingParameterNumber, + QgsProcessingParameterMultipleLayers, +) + +from ...algRunner import AlgRunner +from ..ValidationAlgs.validationAlgorithm import ValidationAlgorithm + + +class GeneralizeNetworkEdgesWithLengthAlgorithm(ValidationAlgorithm): + NETWORK_LAYER = "NETWORK_LAYER" + MIN_LENGTH = "MIN_LENGTH" + GEOGRAPHIC_BOUNDS_LAYER = "GEOGRAPHIC_BOUNDS_LAYER" + POINT_CONSTRAINT_LAYER_LIST = "POINT_CONSTRAINT_LAYER_LIST" + LINE_CONSTRAINT_LAYER_LIST = "LINE_CONSTRAINT_LAYER_LIST" + POLYGON_CONSTRAINT_LAYER_LIST = "POLYGON_CONSTRAINT_LAYER_LIST" + METHOD = "METHOD" + + def initAlgorithm(self, config): + """ + Parameter setting. + """ + self.addParameter( + QgsProcessingParameterVectorLayer( + self.NETWORK_LAYER, + self.tr("Network layer"), + [QgsProcessing.TypeVectorLine], + ) + ) + self.addParameter( + QgsProcessingParameterNumber( + self.MIN_LENGTH, + self.tr("Minimum size"), + minValue=0, + type=QgsProcessingParameterNumber.Double, + defaultValue=0.001, + ) + ) + self.addParameter( + QgsProcessingParameterMultipleLayers( + self.POINT_CONSTRAINT_LAYER_LIST, + self.tr("Point constraint Layers"), + QgsProcessing.TypeVectorPoint, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterMultipleLayers( + self.LINE_CONSTRAINT_LAYER_LIST, + self.tr("Line constraint Layers"), + QgsProcessing.TypeVectorLine, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterMultipleLayers( + self.POLYGON_CONSTRAINT_LAYER_LIST, + self.tr("Polygon constraint Layers"), + QgsProcessing.TypeVectorPolygon, + optional=True, + ) + ) + self.addParameter( + QgsProcessingParameterVectorLayer( + self.GEOGRAPHIC_BOUNDS_LAYER, + self.tr("Reference layer"), + [QgsProcessing.TypeVectorPolygon], + optional=True, + ) + ) + self.selectionIdDict = { + 1: Qgis.SelectBehavior.SetSelection, + 2: Qgis.SelectBehavior.AddToSelection, + 3: Qgis.SelectBehavior.IntersectSelection, + 4: Qgis.SelectBehavior.RemoveFromSelection, + } + self.method = [ + self.tr("Remove features from input layer"), + self.tr("Modify current selection by creating new selection"), + self.tr("Modify current selection by adding to current selection"), + self.tr("Modify current selection by selecting within current selection"), + self.tr("Modify current selection by removing from current selection"), + ] + + self.addParameter( + QgsProcessingParameterEnum( + self.METHOD, self.tr("Method"), options=self.method, defaultValue=0 + ) + ) + + def processAlgorithm(self, parameters, context, feedback): + """ + Here is where the processing itself takes place. + """ + try: + import networkx as nx + except ImportError: + raise QgsProcessingException( + self.tr( + "This algorithm requires the Python networkx library. Please install this library and try again." + ) + ) + # get the network handler + self.algRunner = AlgRunner() + networkLayer = self.parameterAsLayer(parameters, self.NETWORK_LAYER, context) + threshold = self.parameterAsDouble(parameters, self.MIN_LENGTH, context) + geographicBoundsLayer = self.parameterAsLayer( + parameters, self.GEOGRAPHIC_BOUNDS_LAYER, context + ) + pointLayerList = self.parameterAsLayerList( + parameters, self.POINT_CONSTRAINT_LAYER_LIST, context + ) + lineLayerList = self.parameterAsLayerList( + parameters, self.LINE_CONSTRAINT_LAYER_LIST, context + ) + polygonLayerList = self.parameterAsLayerList( + parameters, self.POLYGON_CONSTRAINT_LAYER_LIST, context + ) + method = self.parameterAsEnum(parameters, self.METHOD, context) + + nSteps = 5 + multiStepFeedback = QgsProcessingMultiStepFeedback(nSteps, feedback) + currentStep = 0 + multiStepFeedback.setCurrentStep(currentStep) + multiStepFeedback.setProgressText(self.tr("Building aux structures")) + localCache, nodesLayer = graphHandler.buildAuxLayersPriorGraphBuilding( + networkLayer=networkLayer, + context=context, + geographicBoundsLayer=geographicBoundsLayer, + feedback=multiStepFeedback, + ) + currentStep += 1 + multiStepFeedback.setCurrentStep(currentStep) + multiStepFeedback.setProgressText(self.tr("Building graph aux structures")) + ( + nodeDict, + nodeIdDict, + edgeDict, + hashDict, + networkBidirectionalGraph, + nodeLayerIdDict, + ) = graphHandler.buildAuxStructures( + nx, + nodesLayer=nodesLayer, + edgesLayer=localCache, + feedback=multiStepFeedback, + useWkt=False, + computeNodeLayerIdDict=True, + addEdgeLength=True, + ) + currentStep += 1 + multiStepFeedback.setCurrentStep(currentStep) + multiStepFeedback.setProgressText(self.tr("Getting constraint points")) + constraintSet = self.getConstraintSet( + nodeDict=nodeDict, + nodesLayer=nodesLayer, + nodeLayerIdDict=nodeLayerIdDict, + geographicBoundsLayer=geographicBoundsLayer, + pointLayerList=pointLayerList, + lineLayerList=lineLayerList, + polygonLayerList=polygonLayerList, + context=context, + feedback=multiStepFeedback, + ) + currentStep += 1 + multiStepFeedback.setCurrentStep(currentStep) + multiStepFeedback.setProgressText(self.tr("Applying algorithm heurisic")) + G_out = graphHandler.generalize_edges_according_to_degrees( + G=networkBidirectionalGraph, + constraintSet=constraintSet, + threshold=threshold, + feedback=multiStepFeedback + ) + idsToRemove = set(networkBidirectionalGraph[a][b]["featid"] for a, b in networkBidirectionalGraph.edges) - set(G_out[a][b]["featid"] for a, b in G_out.edges) + currentStep += 1 + multiStepFeedback.setCurrentStep(currentStep) + if method != 0: + networkLayer.selectByIds(list(idsToRemove), self.selectionIdDict[method]) + return {} + networkLayer.startEditing() + networkLayer.beginEditCommand(self.tr("Deleting features")) + networkLayer.deleteFeatures(list(idsToRemove)) + networkLayer.endEditCommand() + return {} + + def getConstraintSet( + self, + nodeDict: Dict[QByteArray, int], + nodesLayer: QgsVectorLayer, + nodeLayerIdDict: Dict[int, Dict[int, QByteArray]], + geographicBoundsLayer: QgsVectorLayer, + pointLayerList = List[QgsVectorLayer], + lineLayerList = List[QgsVectorLayer], + polygonLayerList = List[QgsVectorLayer], + context: Optional[QgsProcessingContext] = None, + feedback: Optional[QgsFeedback] = None, + ) -> Set[int]: + multiStepFeedback = QgsProcessingMultiStepFeedback(4, feedback) + currentStep = 0 + multiStepFeedback.setCurrentStep(currentStep) + (fixedInNodeSet, fixedOutNodeSet) = graphHandler.getInAndOutNodesOnGeographicBounds( + nodeDict=nodeDict, + nodesLayer=nodesLayer, + geographicBoundsLayer=geographicBoundsLayer, + context=context, + feedback=feedback, + ) if geographicBoundsLayer is not None else (set(), set()) + currentStep += 1 + + constraintPointSet = fixedInNodeSet | fixedOutNodeSet + + computeLambda = lambda x: graphHandler.find_constraint_points( + nodesLayer=nodesLayer, + constraintLayer=x, + nodeDict=nodeDict, + nodeLayerIdDict=nodeLayerIdDict, + useBuffer=False, + context=context, + feedback=multiStepFeedback, + ) + + multiStepFeedback.setCurrentStep(currentStep) + constraintPointSetFromLambda = set(i for i in chain.from_iterable(map(computeLambda, pointLayerList + lineLayerList + polygonLayerList))) + constraintPointSet |= constraintPointSetFromLambda + return constraintPointSet + + def name(self): + """ + Returns the algorithm name, used for identifying the algorithm. This + string should be fixed for the algorithm, and must not be localised. + The name should be unique within each provider. Names should contain + lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return "generalizenetworkedgeswithlengthalgorithm" + + def displayName(self): + """ + Returns the translated algorithm name, which should be used for any + user-visible display of the algorithm name. + """ + return self.tr("Generalize Network Edges With Length Algorithm") + + def group(self): + """ + Returns the name of the group this algorithm belongs to. This string + should be localised. + """ + return self.tr("Generalization Algorithms") + + def groupId(self): + """ + Returns the unique ID of the group this algorithm belongs to. This + string should be fixed for the algorithm, and must not be localised. + The group id should be unique within each provider. Group id should + contain lowercase alphanumeric characters only and no spaces or other + formatting characters. + """ + return "DSGTools - Generalization Algorithms" + + def tr(self, string): + return QCoreApplication.translate( + "GeneralizeNetworkEdgesWithLengthAlgorithm", string + ) + + def createInstance(self): + return GeneralizeNetworkEdgesWithLengthAlgorithm() diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/reclassifyAdjecentPolygonsAlgorithm.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/reclassifyAdjecentPolygonsAlgorithm.py similarity index 100% rename from DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/reclassifyAdjecentPolygonsAlgorithm.py rename to DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/reclassifyAdjecentPolygonsAlgorithm.py diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/splitPolygonsAlgorithm.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/splitPolygonsAlgorithm.py similarity index 100% rename from DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/splitPolygonsAlgorithm.py rename to DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/splitPolygonsAlgorithm.py diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/splitPolygonsByGrid.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/splitPolygonsByGrid.py similarity index 100% rename from DsgTools/core/DSGToolsProcessingAlgs/Algs/GeometricAlgs/splitPolygonsByGrid.py rename to DsgTools/core/DSGToolsProcessingAlgs/Algs/GeneralizationAlgs/splitPolygonsByGrid.py diff --git a/DsgTools/core/DSGToolsProcessingAlgs/Algs/ValidationAlgs/fixNetworkAlgorithm.py b/DsgTools/core/DSGToolsProcessingAlgs/Algs/ValidationAlgs/fixNetworkAlgorithm.py index a2e440774..aa678a00e 100644 --- a/DsgTools/core/DSGToolsProcessingAlgs/Algs/ValidationAlgs/fixNetworkAlgorithm.py +++ b/DsgTools/core/DSGToolsProcessingAlgs/Algs/ValidationAlgs/fixNetworkAlgorithm.py @@ -22,28 +22,15 @@ """ from PyQt5.QtCore import QCoreApplication -import processing from DsgTools.core.GeometricTools.layerHandler import LayerHandler from qgis.core import ( - QgsDataSourceUri, - QgsFeature, - QgsFeatureSink, - QgsGeometry, QgsProcessing, - QgsProcessingAlgorithm, QgsProcessingMultiStepFeedback, QgsProcessingOutputVectorLayer, QgsProcessingParameterBoolean, QgsProcessingParameterDistance, - QgsProcessingParameterEnum, - QgsProcessingParameterFeatureSink, - QgsProcessingParameterFeatureSource, QgsProcessingParameterField, - QgsProcessingParameterMultipleLayers, - QgsProcessingParameterNumber, QgsProcessingParameterVectorLayer, - QgsProcessingUtils, - QgsSpatialIndex, QgsWkbTypes, ) diff --git a/DsgTools/core/DSGToolsProcessingAlgs/dsgtoolsProcessingAlgorithmProvider.py b/DsgTools/core/DSGToolsProcessingAlgs/dsgtoolsProcessingAlgorithmProvider.py index 153c9199d..12965fe74 100644 --- a/DsgTools/core/DSGToolsProcessingAlgs/dsgtoolsProcessingAlgorithmProvider.py +++ b/DsgTools/core/DSGToolsProcessingAlgs/dsgtoolsProcessingAlgorithmProvider.py @@ -33,6 +33,7 @@ from DsgTools.core.DSGToolsProcessingAlgs.Algs.DataManagementAlgs.appendFeaturesToLayerAlgorithm import ( AppendFeaturesToLayerAlgorithm, ) +from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeneralizationAlgs.generalizeNetworkEdgesFromLengthAlgorithm import GeneralizeNetworkEdgesWithLengthAlgorithm from DsgTools.core.DSGToolsProcessingAlgs.Algs.LayerManagementAlgs.buildZipPackagesAlgorithm import BuildZipPackageAlgorithm from DsgTools.core.DSGToolsProcessingAlgs.Algs.OtherAlgs.createGridFromCoordinatesAlgorithm import CreateGridFromCoordinatesAlgorithm from DsgTools.core.DSGToolsProcessingAlgs.Algs.ValidationAlgs.fixSegmentErrorsBetweenLinesAlgorithm import ( @@ -67,7 +68,7 @@ from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.line2Multiline import ( Line2Multiline, ) -from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.reclassifyAdjecentPolygonsAlgorithm import ( +from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeneralizationAlgs.reclassifyAdjecentPolygonsAlgorithm import ( ReclassifyAdjacentPolygonsAlgorithm, ) from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.selectByDE9IM import ( @@ -76,10 +77,10 @@ from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.smallHoleRemoverAlgorithm import ( SmallHoleRemoverAlgorithm, ) -from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.splitPolygonsAlgorithm import ( +from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeneralizationAlgs.splitPolygonsAlgorithm import ( SplitPolygons, ) -from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeometricAlgs.splitPolygonsByGrid import ( +from DsgTools.core.DSGToolsProcessingAlgs.Algs.GeneralizationAlgs.splitPolygonsByGrid import ( SplitPolygonsByGrid, ) from DsgTools.core.DSGToolsProcessingAlgs.Algs.LayerManagementAlgs.applyStylesFromDatabaseToLayersAlgorithm import ( @@ -624,6 +625,7 @@ def getAlgList(self): IdentifyWaterBodyAndContourInconsistencies(), CreateGridFromCoordinatesAlgorithm(), BuildZipPackageAlgorithm(), + GeneralizeNetworkEdgesWithLengthAlgorithm(), ] return algList diff --git a/DsgTools/core/GeometricTools/graphHandler.py b/DsgTools/core/GeometricTools/graphHandler.py index 7698630cd..05c4ee0f7 100644 --- a/DsgTools/core/GeometricTools/graphHandler.py +++ b/DsgTools/core/GeometricTools/graphHandler.py @@ -19,19 +19,12 @@ * * ***************************************************************************/ """ -import itertools -import operator -import concurrent.futures -from functools import reduce -from collections import defaultdict, Counter +from collections import defaultdict from itertools import tee -import os from typing import Any, Dict, Iterable, List, Optional, Set, Tuple from qgis.PyQt.QtCore import QByteArray from itertools import chain from itertools import product -from itertools import starmap -from functools import partial from qgis.core import ( QgsGeometry, @@ -39,8 +32,12 @@ QgsProcessingMultiStepFeedback, QgsVectorLayer, QgsFeedback, + QgsProcessingContext, + QgsWkbTypes, ) +from DsgTools.core.DSGToolsProcessingAlgs.algRunner import AlgRunner + def fetch_connected_nodes( G, node: int, max_degree: int, feedback: Optional[QgsFeedback] = None @@ -893,3 +890,229 @@ def identify_unmerged_edges_on_graph( continue outputIdSet.add(nodeId) return outputIdSet + + +def buildAuxLayersPriorGraphBuilding( + networkLayer, context=None, geographicBoundsLayer=None, feedback=None +): + algRunner = AlgRunner() + nSteps = 6 if geographicBoundsLayer is not None else 4 + multiStepFeedback = ( + QgsProcessingMultiStepFeedback(nSteps, feedback) + if feedback is not None + else None + ) + context = QgsProcessingContext() if context is None else context + currentStep = 0 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + localCache = algRunner.runCreateFieldWithExpression( + inputLyr=networkLayer, + expression="$id", + fieldName="featid", + fieldType=1, + context=context, + feedback=multiStepFeedback, + ) + currentStep += 1 + if geographicBoundsLayer is not None: + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + algRunner.runCreateSpatialIndex( + inputLyr=localCache, context=context, feedback=multiStepFeedback + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + localCache = algRunner.runExtractByLocation( + inputLyr=localCache, + intersectLyr=geographicBoundsLayer, + context=context, + feedback=multiStepFeedback, + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + algRunner.runCreateSpatialIndex( + inputLyr=localCache, context=context, feedback=multiStepFeedback + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + nodesLayer = algRunner.runExtractSpecificVertices( + inputLyr=localCache, + vertices="0,-1", + context=context, + feedback=multiStepFeedback, + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + nodesLayer = algRunner.runCreateFieldWithExpression( + inputLyr=nodesLayer, + expression="$id", + fieldName="nfeatid", + fieldType=1, + context=context, + feedback=multiStepFeedback, + ) + return localCache, nodesLayer + +def getInAndOutNodesOnGeographicBounds( + nodeDict: Dict[QByteArray, int], + nodesLayer: QgsVectorLayer, + geographicBoundsLayer: QgsVectorLayer, + context: Optional[QgsProcessingContext] = None, + feedback: Optional[QgsFeedback] = None, + ) -> Tuple[Set[int], Set[int]]: + """ + Get the in-nodes and out-nodes that fall within the geographic bounds. + + Args: + self: The instance of the class. + nodeDict: A dictionary mapping node geometry to an auxiliary ID. + nodesLayer: A QgsVectorLayer representing nodes in the network. + geographicBoundsLayer: The geographic bounds layer. + context: The context object for the processing. + feedback: The QgsFeedback object for providing feedback during processing. + + Returns: + A tuple containing two sets: fixedInNodeSet and fixedOutNodeSet. + - fixedInNodeSet: A set of in-nodes that fall within the geographic bounds. + - fixedOutNodeSet: A set of out-nodes that fall within the geographic bounds. + + Notes: + This function performs the following steps: + 1. Creates a spatial index for the nodesLayer. + 2. Extracts the nodes that are outside the geographic bounds. + 3. Iterates over the nodes outside the geographic bounds and adds them to the appropriate set. + 4. Returns the sets of in-nodes and out-nodes within the geographic bounds. + + The feedback object is used to monitor the progress of the function. + """ + multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) if feedback is not None else None + context = context if context is not None else QgsProcessingContext() + algRunner = AlgRunner() + currentStep = 0 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + algRunner.runCreateSpatialIndex( + inputLyr=nodesLayer, + context=context, + feedback=multiStepFeedback, + is_child_algorithm=True, + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + nodesOutsideGeographicBounds = algRunner.runExtractByLocation( + inputLyr=nodesLayer, + intersectLyr=geographicBoundsLayer, + predicate=[AlgRunner.Disjoint], + context=context, + feedback=multiStepFeedback, + ) + currentStep += 1 + if multiStepFeedback is not None: + multiStepFeedback.setCurrentStep(currentStep) + fixedInNodeSet, fixedOutNodeSet = set(), set() + nFeats = nodesOutsideGeographicBounds.featureCount() + if nFeats == 0: + return fixedInNodeSet, fixedOutNodeSet + stepSize = 100 / nFeats + for current, nodeFeat in enumerate(nodesOutsideGeographicBounds.getFeatures()): + if multiStepFeedback is not None and multiStepFeedback.isCanceled(): + break + selectedSet = ( + fixedInNodeSet if nodeFeat["vertex_pos"] == 0 else fixedOutNodeSet + ) + geom = nodeFeat.geometry() + selectedSet.add(nodeDict[geom.asWkb()]) + if multiStepFeedback is not None: + multiStepFeedback.setProgress(current * stepSize) + return fixedInNodeSet, fixedOutNodeSet + +def find_constraint_points( + nodesLayer: QgsVectorLayer, + constraintLayer: QgsVectorLayer, + nodeDict: Dict[QByteArray, int], + nodeLayerIdDict: Dict[int, Dict[int, QByteArray]], + useBuffer: bool =True, + context: Optional[QgsProcessingContext] = None, + feedback: Optional[QgsFeedback] = None, +) -> Set[int]: + multiStepFeedback = QgsProcessingMultiStepFeedback(3, feedback) if feedback is not None else None + context = context if context is not None else QgsProcessingContext() + algRunner = AlgRunner() + constraintSet = set() + layerToRelate = algRunner.runBuffer( + inputLayer=constraintLayer, + distance=1e-6, + context=context, + is_child_algorithm=True, + ) if constraintLayer.geometryType() != QgsWkbTypes.PointGeometry and useBuffer else constraintLayer + predicate = AlgRunner.Intersect if constraintLayer.geometryType() != QgsWkbTypes.PointGeometry else AlgRunner.Equal + selectedNodesFromOcean = algRunner.runExtractByLocation( + inputLyr=nodesLayer, + intersectLyr=layerToRelate, + context=context, + predicate=[predicate], + feedback=multiStepFeedback, + ) + for feat in selectedNodesFromOcean.getFeatures(): + if multiStepFeedback.isCanceled(): + break + constraintSet.add(nodeDict[nodeLayerIdDict[feat["nfeatid"]]]) + return constraintSet + +def generalize_edges_according_to_degrees( + G, + constraintSet: Set[int], + threshold: float, + feedback: Optional[QgsFeedback] = None, +): + G_copy = G.copy() + pairsToRemove = find_smaller_first_order_path_with_length_smaller_than_threshold( + G=G_copy, + constraintSet=constraintSet, + threshold=threshold, + feedback=feedback + ) + while pairsToRemove is not None: + if feedback is not None and feedback.isCanceled(): + break + for n0, n1 in pairsToRemove: + G_copy.remove_edge(n0, n1) + pairsToRemove = find_smaller_first_order_path_with_length_smaller_than_threshold( + G=G_copy, + constraintSet=constraintSet, + threshold=threshold, + feedback=feedback + ) + return G_copy + + +def find_smaller_first_order_path_with_length_smaller_than_threshold( + G, + constraintSet: Set[int], + threshold: float, + feedback: Optional[QgsFeedback] = None +) -> frozenset[frozenset]: + total_length_dict = dict() + edges_to_remove_dict = dict() + for node in set(node for node in G.nodes if G.degree(node) == 1 and node not in constraintSet): + if feedback is not None and feedback.isCanceled(): + return None + connectedNodes = fetch_connected_nodes(G, node, 2) + if set(connectedNodes).intersection(constraintSet): + continue + pairs = frozenset([frozenset([a, b]) for i in connectedNodes for a, b in G.edges(i)]) + total_length = sum(G[a][b]["length"] for a, b in pairs) + if total_length >= threshold: + continue + edges_to_remove_dict[node] = pairs + total_length_dict[node] = total_length + if len(total_length_dict) == 0: + return None + smaller_path_node = min(total_length_dict.keys(), key=lambda x: total_length_dict[x]) + return edges_to_remove_dict[smaller_path_node] diff --git a/DsgTools/metadata.txt b/DsgTools/metadata.txt index b280c4bb1..bc3e64121 100644 --- a/DsgTools/metadata.txt +++ b/DsgTools/metadata.txt @@ -10,7 +10,7 @@ name=DSG Tools qgisMinimumVersion=3.22 description=Brazilian Army Cartographic Production Tools -version=4.11.36 +version=4.11.37 author=Brazilian Army Geographic Service email=dsgtools@dsg.eb.mil.br about=