Skip to content

Commit

Permalink
Merge pull request #732 from dsgoficial/dev
Browse files Browse the repository at this point in the history
Versão 4.6.0
  • Loading branch information
phborba authored Dec 19, 2022
2 parents ef17498 + bd35e55 commit 3522ec5
Show file tree
Hide file tree
Showing 54 changed files with 150,853 additions and 123,471 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# CHANGELOG

## 4.6.0

Novas funcionalidades:
- Novo processo de estender linhas próximas da moldura;
- Novo algoritmo de detecção de geometrias nulas;
- Novo processo de adicionar vértices não compartilhados nas intersecções (processo de correção associado ao processo de Identificar vértices não compartilhados na intersecção);
- Novo processo de adicionar vértices não compartilhados nos segmentos compartilhados (processo de correção associado ao processo de Identificar vértices não compartilhados nos segmentos compartilhados);
- Adicionada integração da Ferramenta de Controle de Qualidade (QA Toolbox) com o Ferramentas de Produção. Dessa forma, a QA Toolbox pode ser integrada à produção utilizando o Sistema de Apoio à Produção;
- Nova funcionalidade de adicionar filtro espacial às camadas (portado do ferramentas experimentais);
- Nova funcionalidade de filtrar por selecionados (portado do ferramentas experimentais);
- Nova funcionalidade de filtrar todos por geometria de selecionados (portado do ferramentas experimentais);
- Nova funcionalidade de remover filtros (portado do ferramentas experimentais);
- Nova funcionalidade de copiar geometrias selecionadas como WKT (portado do ferramentas experimentais);

Melhorias:
- Adicionada a opção de atribuir um id de atividade para o grid de revisão criado no processo de criar grid de edição;
- Melhorado o estilo do grid utilizado pela barra de ferramentas de revisão;
- Adicionada a funcionalidade de resetar o grid na barra ferramentas de revisão;
- Adicionado o caso de snap dentro da camada no snap hierárquico. Agora para cada camada de entrada, primeiramente é feito o snap dentro da camada de referência antes de atrair os elementos com hierarquia menor;
- Barra de atalhos refatorada. Alguns atalhos não utilizados frequentemente foram retirados e foram criadas novas barras para dar a opção do usuário escolher quais ele quer ativar.

Correção de bug:
- Correção de bug no identificar pontas soltas (o algoritmo estava levantando flag em vértice ocupado dentro do raio de busca);
- Correção de bug no identificar erros no terreno (o algoritmo estava levantando a geometria da flag confusa);
- Correção de crash ao rodar o snap hierárquico (o algoritmo agora só transmite as mudanças para o banco ao final do processo, mantendo os cálculos intermediários em camada de cache gravadas em camada temporária do processing do QGIS, ativado por meio da flag is_child_algorithm=True ao rodar o processo);

## 4.5.0 - 2022-09-08

Novas funcionalidades:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,31 @@
***************************************************************************/
"""

import processing

from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsGeometry, QgsProcessing, QgsProcessingException, QgsProcessingParameterBoolean,
QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSource, QgsProcessingParameterRasterLayer,
QgsProcessingParameterField, QgsProject, QgsField, QgsProcessingMultiStepFeedback,
QgsProcessingParameterNumber, QgsFeatureSink, QgsProcessingParameterRasterDestination,
QgsProcessingParameterVectorLayer, QgsWkbTypes, QgsProcessingAlgorithm, QgsFeature,
QgsFields, QgsProcessingUtils)
import numpy as np
import processing
from osgeo import gdal
from PyQt5.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsFeature, QgsFeatureSink, QgsField, QgsFields,
QgsGeometry, QgsProcessing, QgsProcessingAlgorithm,
QgsProcessingMultiStepFeedback,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterNumber,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterRasterLayer, QgsProcessingUtils,
QgsProject, QgsVectorLayer, QgsWkbTypes)

from DsgTools.core.DSGToolsProcessingAlgs.algRunner import AlgRunner
from DsgTools.core.GeometricTools.geometryHandler import GeometryHandler


class BuildTerrainSlicingFromContoursAlgorihtm(QgsProcessingAlgorithm):

INPUT = 'INPUT'
CONTOUR_INTERVAL = 'CONTOUR_INTERVAL'
GEOGRAPHIC_BOUNDARY = 'GEOGRAPHIC_BOUNDARY'
AREA_WITHOUT_INFORMATION_POLYGONS = 'AREA_WITHOUT_INFORMATION_POLYGONS'
WATER_BODIES_POLYGONS = 'WATER_BODIES_POLYGONS'
MIN_PIXEL_GROUP_SIZE = 'MIN_PIXEL_GROUP_SIZE'
SMOOTHING_PARAMETER = 'SMOOTHING_PARAMETER'
OUTPUT_POLYGONS = 'OUTPUT_POLYGONS'
Expand Down Expand Up @@ -72,6 +77,24 @@ def initAlgorithm(self, config=None):
)
)

self.addParameter(
QgsProcessingParameterFeatureSource(
self.AREA_WITHOUT_INFORMATION_POLYGONS,
self.tr('Area without information layer'),
[QgsProcessing.TypeVectorPolygon],
optional=True
)
)

self.addParameter(
QgsProcessingParameterFeatureSource(
self.WATER_BODIES_POLYGONS,
self.tr('Water bodies layer'),
[QgsProcessing.TypeVectorPolygon],
optional=True
)
)

self.addParameter(
QgsProcessingParameterNumber(
self.MIN_PIXEL_GROUP_SIZE,
Expand Down Expand Up @@ -108,11 +131,16 @@ def initAlgorithm(self, config=None):

def processAlgorithm(self, parameters, context, feedback):
algRunner = AlgRunner()
self.geometryHandler = GeometryHandler()
inputRaster = self.parameterAsRasterLayer(parameters, self.INPUT, context)
threshold = self.parameterAsInt(
parameters, self.CONTOUR_INTERVAL, context)
geoBoundsSource = self.parameterAsSource(
parameters, self.GEOGRAPHIC_BOUNDARY, context)
areaWithoutInformationSource = self.parameterAsSource(
parameters, self.AREA_WITHOUT_INFORMATION_POLYGONS, context)
waterBodiesSource = self.parameterAsSource(
parameters, self.WATER_BODIES_POLYGONS, context)
minPixelGroupSize = self.parameterAsInt(
parameters, self.MIN_PIXEL_GROUP_SIZE, context
)
Expand All @@ -123,17 +151,41 @@ def processAlgorithm(self, parameters, context, feedback):
outputFields = self.getOutputFields()
(output_sink, output_sink_id) = self.getOutputSink(inputRaster, outputFields, parameters, context)

multiStepFeedback = QgsProcessingMultiStepFeedback(11, feedback) #ajustar depois
multiStepFeedback = QgsProcessingMultiStepFeedback(15, feedback) #ajustar depois
currentStep = 0
multiStepFeedback.setCurrentStep(currentStep)

geographicBounds = self.overlayPolygonLayer(
inputLyr=parameters[self.GEOGRAPHIC_BOUNDARY],
polygonLyr=parameters[self.AREA_WITHOUT_INFORMATION_POLYGONS],
crs=inputRaster.crs() if inputRaster is not None else QgsProject.instance().crs(),
context=context,
feedback=multiStepFeedback,
operator=2
) if areaWithoutInformationSource is not None and \
areaWithoutInformationSource.featureCount() > 0 else parameters[self.GEOGRAPHIC_BOUNDARY]

currentStep += 1

multiStepFeedback.setCurrentStep(currentStep)
geographicBounds = self.overlayPolygonLayer(
inputLyr=geographicBounds,
polygonLyr=parameters[self.WATER_BODIES_POLYGONS],
crs=inputRaster.crs() if inputRaster is not None else QgsProject.instance().crs(),
context=context,
feedback=multiStepFeedback,
operator=2
) if waterBodiesSource is not None and \
waterBodiesSource.featureCount() > 0 else geographicBounds
currentStep += 1

multiStepFeedback.setCurrentStep(currentStep)
clippedRaster = algRunner.runClipRasterLayer(
inputRaster,
mask=parameters[self.GEOGRAPHIC_BOUNDARY],
mask=geographicBounds,
context=context,
feedback=multiStepFeedback
feedback=multiStepFeedback,
noData=-9999
)
currentStep += 1

Expand Down Expand Up @@ -163,6 +215,7 @@ def processAlgorithm(self, parameters, context, feedback):
feedback=multiStepFeedback
)
currentStep += 1
multiStepFeedback.setCurrentStep(currentStep)
clippedRaster = algRunner.runClipRasterLayer(
inputRaster,
mask=bufferedGeographicBounds,
Expand All @@ -189,7 +242,7 @@ def processAlgorithm(self, parameters, context, feedback):
multiStepFeedback.setCurrentStep(currentStep)
finalRaster = algRunner.runClipRasterLayer(
sieveOutput,
mask=parameters[self.GEOGRAPHIC_BOUNDARY],
mask=geographicBounds,
context=context,
feedback=multiStepFeedback,
outputRaster=outputRaster
Expand All @@ -200,7 +253,8 @@ def processAlgorithm(self, parameters, context, feedback):
polygonLayer = algRunner.runGdalPolygonize(
sieveOutput,
context=context,
feedback=multiStepFeedback
feedback=multiStepFeedback,
is_child_algorithm=True
)
currentStep += 1

Expand All @@ -209,18 +263,26 @@ def processAlgorithm(self, parameters, context, feedback):
polygonLayer,
threshold=smoothingThreshold,
context=context,
feedback=multiStepFeedback
)
feedback=multiStepFeedback,
is_child_algorithm=True,
) if smoothingThreshold > 0 else polygonLayer
currentStep += 1

multiStepFeedback.setCurrentStep(currentStep)
overlayedPolygons = self.overlayGeographicBounds(
overlayedPolygons = self.overlayPolygonLayer(
inputLyr=smoothPolygons,
geoBounds=parameters[self.GEOGRAPHIC_BOUNDARY],
polygonLyr=geographicBounds,
crs=inputRaster.crs() if inputRaster is not None else QgsProject.instance().crs(),
context=context,
feedback=multiStepFeedback
)
currentStep += 1
# multiStepFeedback.setCurrentStep(currentStep)
# cleanedPolygons = self.cleanPolyonLayer()
# currentStep += 1
multiStepFeedback.setCurrentStep(currentStep)
algRunner.runCreateSpatialIndex(overlayedPolygons, context, feedback=multiStepFeedback)
currentStep += 1

featCount = overlayedPolygons.featureCount()
if featCount == 0:
Expand All @@ -231,53 +293,127 @@ def processAlgorithm(self, parameters, context, feedback):

multiStepFeedback.setCurrentStep(currentStep)
stepSize = 100/featCount
valueSet = set(int(feat['a_DN']) for feat in overlayedPolygons.getFeatures())
diff = valueSet.difference(set(range(len(valueSet))))
def classLambda(x):
x = int(x)
if x == 0:
return x
return x - 1 if diff != set() else x

for current, feat in enumerate(overlayedPolygons.getFeatures()):
if multiStepFeedback.isCanceled():
break
newFeat = QgsFeature(outputFields)
newFeat['class'] = feat['a_DN']
newFeat['class'] = classLambda(feat['a_DN'])
newFeat['class_min'], newFeat['class_max'] = slicingThresholdDict[feat['a_DN']]
newFeat.setGeometry(feat.geometry())
geom = self.validatePolygon(feat, overlayedPolygons)
newFeat.setGeometry(geom)
output_sink.addFeature(newFeat, QgsFeatureSink.FastInsert)
multiStepFeedback.setProgress(current * stepSize)

return {
"OUTPUT_POLYGONS": output_sink_id,
"OUTPUT_RASTER": finalRaster,
}

def overlayGeographicBounds(self, inputLyr, geoBounds, context, feedback):

def validatePolygon(self, feat, overlayerPolygons):
geom = feat.geometry()
_, donutholes = self.geometryHandler.getOuterShellAndHoles(geom, False)
filteredHoles = []
holesIdsToDelete = set()
if donutholes == []:
return geom
def holeWithValue(centerPoint):
centerPointBB = centerPoint.boundingBox()
for polygonFeat in overlayerPolygons.getFeatures(centerPointBB):
polygonGeom = polygonFeat.geometry()
if polygonGeom.equals(geom):
continue
if polygonGeom.intersects(centerPoint):
return True
return False
for idx, hole in enumerate(donutholes):
centerPoint = hole.pointOnSurface()
hasValue = holeWithValue(centerPoint)
if not hasValue:
holesIdsToDelete.add(idx+1)
continue
filteredHoles.append(hole)
if donutholes == filteredHoles:
return geom
geom = QgsGeometry(geom)
for idx in holesIdsToDelete:
geom.deleteRing(idx)
return geom

def overlayPolygonLayer(self, inputLyr, polygonLyr, crs, context, feedback, operator=0):
parameters = {
'ainput': inputLyr,
'atype': 0,
'binput': geoBounds,
'binput': polygonLyr,
'btype': 0,
'operator': 0,
'operator': operator,
'snap': 0,
'-t': False,
'output':'TEMPORARY_OUTPUT',
'output': 'TEMPORARY_OUTPUT',
'GRASS_REGION_PARAMETER': None,
'GRASS_SNAP_TOLERANCE_PARAMETER':-1,
'GRASS_SNAP_TOLERANCE_PARAMETER': -1,
'GRASS_MIN_AREA_PARAMETER': 1e-15,
'GRASS_OUTPUT_TYPE_PARAMETER':0,
'GRASS_VECTOR_DSCO':'',
'GRASS_VECTOR_LCO':'',
'GRASS_OUTPUT_TYPE_PARAMETER': 3,
'GRASS_VECTOR_DSCO': '',
'GRASS_VECTOR_LCO': '',
'GRASS_VECTOR_EXPORT_NOCAT': False
}
x = processing.run('grass7:v.overlay', parameters, context=context, feedback=feedback)
x = processing.run(
'grass7:v.overlay',
parameters,
context=context,
feedback=feedback
)
lyr = QgsProcessingUtils.mapLayerFromString(x['output'], context)
lyr.setCrs(inputLyr.crs())
lyr.setCrs(crs)
return lyr

def findSlicingThresholdDict(self, inputRaster):
ds = gdal.Open(inputRaster)
npRaster = np.array(ds.GetRasterBand(1).ReadAsArray())
npRaster = npRaster[~np.isnan(npRaster)] # removes nodata values
minValue = np.amin(npRaster)
maxValue = np.amax(npRaster)
numberOfElevationBands = self.getNumberOfElevationBands(maxValue - minValue)
areaRatioList = self.getAreaRatioList(numberOfElevationBands)
uniqueValues, uniqueCount = np.unique(npRaster, return_counts=True)
cumulativePercentage = np.cumsum(uniqueCount) / np.prod(npRaster.shape)
areaPercentageValues = uniqueCount / np.prod(npRaster.shape)
if any(areaPercentageValues >= 0.48) and numberOfElevationBands > 2:
"""
The MTM spec states that if there is an elevation slice that covers more than
50% of the map, there must only be 2 elevation bands.
"""
idx = np.argmax(areaPercentageValues >= 0.5)
if idx == 0:
return {
0: (int(uniqueValues[0]), int(uniqueValues[1])),
1: (int(uniqueValues[1]), int(uniqueValues[-1])),
}
elif idx == len(areaPercentageValues):
return {
0: (int(uniqueValues[0]), int(uniqueValues[-2])),
1: (int(uniqueValues[-2]), int(uniqueValues[-1])),
}
else:
return {
0: (int(uniqueValues[0]), int(uniqueValues[idx])),
1: (int(uniqueValues[idx]), int(uniqueValues[idx+1])),
}

if numberOfElevationBands == 2 and np.argmax(areaPercentageValues >= 0.5) == 0:
return {
0: (int(uniqueValues[0]), int(uniqueValues[1])),
1: (int(uniqueValues[1]), int(uniqueValues[-1])),
}

classThresholds = list(uniqueValues[
np.searchsorted(
cumulativePercentage,
Expand All @@ -286,7 +422,8 @@ def findSlicingThresholdDict(self, inputRaster):
]
)
classDict = dict()
for i, (a, b) in enumerate(zip([minValue]+classThresholds, classThresholds)):
lowerBounds = [minValue]+classThresholds if minValue not in classThresholds else classThresholds
for i, (a, b) in enumerate(zip(lowerBounds, classThresholds)):
classDict[i] = (int(a), int(b))
return classDict

Expand Down Expand Up @@ -319,7 +456,7 @@ def getNumberOfElevationBands(self, range):

def getOutputFields(self):
fields = QgsFields()
fields.append(QgsField('class', QVariant.String))
fields.append(QgsField('class', QVariant.Int))
fields.append(QgsField('class_min', QVariant.Int))
fields.append(QgsField('class_max', QVariant.Int))

Expand Down
Loading

0 comments on commit 3522ec5

Please sign in to comment.