def simpleMeasure(geom, method=0, ellips=None, crs=None): # Method defines calculation type: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal if geom.type() == QgsWkbTypes.PointGeometry: if not geom.isMultipart(): pt = geom.geometry() attr1 = pt.x() attr2 = pt.y() else: pt = geom.asMultiPoint() attr1 = pt[0].x() attr2 = pt[0].y() else: measure = QgsDistanceArea() if method == 2: measure.setSourceCrs(crs) measure.setEllipsoid(ellips) measure.setEllipsoidalMode(True) if geom.type() == QgsWkbTypes.PolygonGeometry: attr1 = measure.measureArea(geom) attr2 = measure.measurePerimeter(geom) else: attr1 = measure.measureLength(geom) attr2 = None return (attr1, attr2)
def testMeasurePolygon(self): # +-+-+ # | | # + +-+ # | | # +-+ polygon = QgsGeometry.fromPolygon( [ [ QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(0, 2), QgsPoint(0, 0), ] ] ) da = QgsDistanceArea() area = da.measureArea(polygon) assert area == 3, "Expected:\n%f\nGot:\n%f\n" % (3, area) perimeter = da.measurePerimeter(polygon) assert perimeter == 8, "Expected:\n%f\nGot:\n%f\n" % (8, perimeter)
def testMeasureMultiPolygon(self): # +-+-+ +-+-+ # | | | | # + +-+ +-+ + # | | | | # +-+ +-+ polygon = QgsGeometry.fromMultiPolygon( [ [[QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(0, 2), QgsPoint(0, 0), ]], [[QgsPoint(4, 0), QgsPoint(5, 0), QgsPoint(5, 2), QgsPoint(3, 2), QgsPoint(3, 1), QgsPoint(4, 1), QgsPoint(4, 0), ]] ] ) da = QgsDistanceArea() area = da.measureArea(polygon) assert area == 6, 'Expected:\n%f\nGot:\n%f\n' % (6, area) perimeter = da.measurePerimeter(polygon) assert perimeter == 16, "Expected:\n%f\nGot:\n%f\n" % (16, perimeter)
def testMeasurePolygonWithHole(self): # +-+-+-+ # | | # + +-+ + # | | | | # + +-+ + # | | # +-+-+-+ polygon = QgsGeometry.fromPolygon( [ [QgsPoint(0, 0), QgsPoint(3, 0), QgsPoint(3, 3), QgsPoint(0, 3), QgsPoint(0, 0)], [QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(1, 2), QgsPoint(1, 1)], ] ) da = QgsDistanceArea() area = da.measureArea(polygon) assert area == 8, "Expected:\n%f\nGot:\n%f\n" % (8, area) # MH150729: Changed behaviour to consider inner rings for perimeter calculation. Therefore, expected result is 16. perimeter = da.measurePerimeter(polygon) assert perimeter == 16, "Expected:\n%f\nGot:\n%f\n" % (16, perimeter)
def evaluation(self=None, parameters={},feature=None): from PyQt4.QtCore import QVariant from qgis.core import QgsDistanceArea, QgsCoordinateReferenceSystem ar = NULL per = NULL id = NULL flr = NULL usage = NULL kind = NULL da_engine=QgsDistanceArea() da_engine.setSourceCrs(QgsCoordinateReferenceSystem(int(config.project_crs.split(':')[-1]), QgsCoordinateReferenceSystem.EpsgCrsId)) da_engine.setEllipsoid(config.project_ellipsoid) da_engine.setEllipsoidalMode(True) if feature: geometry = feature.geometry() #print geometry ar = da_engine.measureArea(geometry) per =da_engine.measurePerimeter(geometry) id = feature[config.building_id_key] #necessary to safe dependency check flr = feature[u'FLRS_ALK'] # necessary to safe dependency check usage = feature[u'FUNC_ALK'] # necessary to safe dependency check kind = feature[u'KIND_ALK'] # necessary to safe dependency check #print ar #print per #print id return {config.building_id_key: {'type': QVariant.String, 'value': id}, 'AREA_ALK': {'type': QVariant.Double, 'value': ar}, 'PERI_ALK': {'type': QVariant.Double, 'value': per}, 'FLRS_ALK': {'type': QVariant.Double, 'value': flr}, 'FUNC_ALK': {'type': QVariant.Double, 'value': usage}, 'KIND_ALK': {'type': QVariant.Double, 'value': kind}, }
def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.VECTOR)) value = float(self.getParameterValue(self.VALUE)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) strategy = self.getParameterValue(self.STRATEGY) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Point, layer.crs()) da = QgsDistanceArea() features = vector.features(layer) for current, f in enumerate(features): fGeom = f.geometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(fGeom) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 progress.setPercentage(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) progress.setPercentage(0) del writer
def testAreaMeasureAndUnits(self): """Test a variety of area measurements in different CRS and ellipsoid modes, to check that the calculated areas and units are always consistent """ da = QgsDistanceArea() da.setSourceCrs(QgsCoordinateReferenceSystem.fromSrsId(3452), QgsProject.instance().transformContext()) da.setEllipsoid("NONE") polygon = QgsGeometry.fromPolygonXY( [[ QgsPointXY(0, 0), QgsPointXY(1, 0), QgsPointXY(1, 1), QgsPointXY(2, 1), QgsPointXY(2, 2), QgsPointXY(0, 2), QgsPointXY(0, 0), ]] ) # We check both the measured area AND the units, in case the logic regarding # ellipsoids and units changes in future area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.AreaSquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.AreaSquareMeters)) da.setEllipsoid("WGS84") area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) # should always be in Meters Squared self.assertAlmostEqual(area, 36918093794.121284, delta=0.1) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareMiles) self.assertAlmostEqual(area, 14254.155703182701, delta=0.001) # now try with a source CRS which is in feet polygon = QgsGeometry.fromPolygonXY( [[ QgsPointXY(1850000, 4423000), QgsPointXY(1851000, 4423000), QgsPointXY(1851000, 4424000), QgsPointXY(1852000, 4424000), QgsPointXY(1852000, 4425000), QgsPointXY(1851000, 4425000), QgsPointXY(1850000, 4423000) ]] ) da.setSourceCrs(QgsCoordinateReferenceSystem.fromSrsId(27469), QgsProject.instance().transformContext()) da.setEllipsoid("NONE") # measurement should be in square feet area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 2000000, delta=0.001) self.assertEqual(units, QgsUnitTypes.AreaSquareFeet) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 222222.2222, delta=0.001) da.setEllipsoid("WGS84") # now should be in Square Meters again area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 185818.59096575077, delta=1.0) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 222237.18521272976, delta=1.0)
def processAlgorithm(self, progress): layer = dataobjects.getObjectFromUri( self.getParameterValue(self.VECTOR)) value = float(self.getParameterValue(self.VALUE)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) strategy = self.getParameterValue(self.STRATEGY) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fields, QgsWkbTypes.Point, layer.crs()) da = QgsDistanceArea() features = vector.features(layer) for current, f in enumerate(features): fGeom = f.geometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPoint(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(fGeom) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 progress.setPercentage(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: ProcessingLog.addToLog( ProcessingLog.LOG_INFO, self.tr('Can not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) progress.setPercentage(0) del writer
class ExportGeometryInfo(QgisAlgorithm): INPUT = 'INPUT' METHOD = 'CALC_METHOD' OUTPUT = 'OUTPUT' def icon(self): return QIcon( os.path.join(pluginPath, 'images', 'ftools', 'export_geometry.png')) def tags(self): return self.tr( 'export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons' ).split(',') def group(self): return self.tr('Vector geometry') def groupId(self): return 'vectorgeometry' def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.calc_methods = [ self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal') ] def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) self.addParameter( QgsProcessingParameterEnum(self.METHOD, self.tr('Calculate using'), options=self.calc_methods, defaultValue=0)) self.addParameter( QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info'))) def name(self): return 'exportaddgeometrycolumns' def displayName(self): return self.tr('Export geometry columns') def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id} def point_attributes(self, geometry): pt = None if not geometry.isMultipart(): pt = geometry.constGet() else: if geometry.numGeometries() > 0: pt = geometry.geometryN(0) attrs = [] if pt: attrs.append(pt.x()) attrs.append(pt.y()) # add point z/m if self.export_z: attrs.append(pt.z()) if self.export_m: attrs.append(pt.m()) return attrs def line_attributes(self, geometry): return [self.distance_area.measureLength(geometry)] def polygon_attributes(self, geometry): area = self.distance_area.measureArea(geometry) perimeter = self.distance_area.measurePerimeter(geometry) return [area, perimeter]
class SplitMapTool(QgsMapToolEdit): def __init__(self, canvas, layer, actionMoveVertices, actionAddVertices, actionRemoveVertices, actionMoveSegment, actionLineClose, actionLineOpen, actionMoveLine): super(SplitMapTool, self).__init__(canvas) self.canvas = canvas self.scene = canvas.scene() self.layer = layer self.actionMoveVertices = actionMoveVertices self.actionAddVertices = actionAddVertices self.actionRemoveVertices = actionRemoveVertices self.actionMoveSegment = actionMoveSegment self.actionLineClose = actionLineClose self.actionLineOpen = actionLineOpen self.actionMoveLine = actionMoveLine self.initialize() def initialize(self): try: self.canvas.renderStarting.disconnect(self.mapCanvasChanged) except: pass self.canvas.renderStarting.connect(self.mapCanvasChanged) try: self.layer.editingStopped.disconnect(self.stopCapturing) except: pass self.layer.editingStopped.connect(self.stopCapturing) self.selectedFeatures = self.layer.selectedFeatures() self.rubberBand = None self.tempRubberBand = None self.capturedPoints = [] self.capturing = False self.setCursor(Qt.CrossCursor) self.proj = QgsProject.instance() self.labels = [] self.vertices = [] self.calculator = QgsDistanceArea() self.calculator.setSourceCrs(self.layer.dataProvider().crs(), QgsProject.instance().transformContext()) self.calculator.setEllipsoid( self.layer.dataProvider().crs().ellipsoidAcronym()) self.drawingLine = False self.movingVertices = False self.addingVertices = False self.removingVertices = False self.movingSegment = False self.movingLine = False self.showingVertices = False self.movingVertex = -1 self.movingSegm = -1 self.movingLineInitialPoint = None self.lineClosed = False def restoreAction(self): self.addingVertices = False self.removingVertices = False self.movingVertices = False self.movingSegment = False self.movingLine = False self.showingVertices = False self.drawingLine = True self.movingVertex = -1 self.movingLineInitialPoint = None self.deleteVertices() self.redrawRubberBand() self.redrawTempRubberBand() self.canvas.scene().addItem(self.tempRubberBand) self.redrawActions() def mapCanvasChanged(self): self.redrawAreas() if self.showingVertices: self.redrawVertices() def canvasMoveEvent(self, event): if self.drawingLine and not self.lineClosed: if self.tempRubberBand != None and self.capturing: mapPoint = self.toMapCoordinates(event.pos()) self.tempRubberBand.movePoint(mapPoint) self.redrawAreas(event.pos()) if self.movingVertices and self.movingVertex >= 0: layerPoint = self.toLayerCoordinates(self.layer, event.pos()) self.capturedPoints[self.movingVertex] = layerPoint if self.lineClosed and self.movingVertex == 0: self.capturedPoints[len(self.capturedPoints) - 1] = layerPoint self.redrawRubberBand() self.redrawVertices() self.redrawAreas() if self.movingSegment and self.movingSegm >= 0: currentPoint = self.toLayerCoordinates(self.layer, event.pos()) distance = self.distancePoint(currentPoint, self.movingLineInitialPoint) bearing = self.movingLineInitialPoint.azimuth(currentPoint) self.capturedPoints[self.movingSegm] = self.projectPoint( self.capturedPoints[self.movingSegm], distance, bearing) self.capturedPoints[self.movingSegm + 1] = self.projectPoint( self.capturedPoints[self.movingSegm + 1], distance, bearing) if self.lineClosed: if self.movingSegm == 0: self.capturedPoints[ len(self.capturedPoints) - 1] = self.projectPoint( self.capturedPoints[len(self.capturedPoints) - 1], distance, bearing) elif self.movingSegm == len(self.capturedPoints) - 2: self.capturedPoints[0] = self.projectPoint( self.capturedPoints[0], distance, bearing) self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.movingLineInitialPoint = currentPoint if self.movingLine and self.movingLineInitialPoint != None: currentPoint = self.toLayerCoordinates(self.layer, event.pos()) distance = self.distancePoint(currentPoint, self.movingLineInitialPoint) bearing = self.movingLineInitialPoint.azimuth(currentPoint) for i in range(len(self.capturedPoints)): self.capturedPoints[i] = self.projectPoint( self.capturedPoints[i], distance, bearing) self.redrawRubberBand() self.redrawAreas() self.movingLineInitialPoint = currentPoint def projectPoint(self, point, distance, bearing): rads = bearing * pi / 180.0 dx = distance * sin(rads) dy = distance * cos(rads) return QgsPointXY(point.x() + dx, point.y() + dy) def redrawAreas(self, mousePos=None): self.deleteLabels() if self.capturing and len(self.capturedPoints) > 0: for i in range(len(self.selectedFeatures)): geometry = QgsGeometry(self.selectedFeatures[i].geometry()) movingPoints = list(self.capturedPoints) if mousePos != None: movingPoints.append( self.toLayerCoordinates(self.layer, mousePos)) result, newGeometries, topoTestPoints = geometry.splitGeometry( movingPoints, self.proj.topologicalEditing()) self.addLabel(geometry) if newGeometries != None and len(newGeometries) > 0: for i in range(len(newGeometries)): self.addLabel(newGeometries[i]) def addLabel(self, geometry): area = self.calculator.measureArea(geometry) labelPoint = geometry.pointOnSurface().vertexAt(0) label = QGraphicsTextItem("%.2f" % round(area, 2)) label.setHtml( "<div style=\"color:#ffffff;background:#111111;padding:5px\">" + "%.2f" % round(area, 2) + " " + areaUnits[self.calculator.areaUnits()] + "</div>") point = self.toMapCoordinatesV2(self.layer, labelPoint) label.setPos(self.toCanvasCoordinates(QgsPointXY(point.x(), point.y()))) self.scene.addItem(label) self.labels.append(label) def deleteLabels(self): for i in range(len(self.labels)): self.scene.removeItem(self.labels[i]) self.labels = [] def canvasPressEvent(self, event): if self.movingVertices: for i in range(len(self.capturedPoints)): point = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex = self.toCanvasCoordinates( QgsPointXY(point.x(), point.y())) if self.distancePoint(event.pos(), currentVertex) <= maxDistanceHitTest: self.movingVertex = i break if self.movingSegment: for i in range(len(self.capturedPoints) - 1): vertex1 = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex1 = self.toCanvasCoordinates( QgsPointXY(vertex1.x(), vertex1.y())) vertex2 = self.toMapCoordinates(self.layer, self.capturedPoints[i + 1]) currentVertex2 = self.toCanvasCoordinates( QgsPointXY(vertex2.x(), vertex2.y())) if self.distancePointLine( event.pos().x(), event.pos().y(), currentVertex1.x(), currentVertex1.y(), currentVertex2.x(), currentVertex2.y()) <= maxDistanceHitTest: self.movingSegm = i break self.movingLineInitialPoint = self.toLayerCoordinates( self.layer, event.pos()) def distancePoint(self, eventPos, vertexPos): return sqrt((eventPos.x() - vertexPos.x())**2 + (eventPos.y() - vertexPos.y())**2) def canvasReleaseEvent(self, event): if self.movingVertices or self.movingSegment or self.movingLine: if event.button() == Qt.RightButton: self.finishOperation() elif self.addingVertices: if event.button() == Qt.LeftButton: self.addVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() elif self.removingVertices: if event.button() == Qt.LeftButton: self.removeVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() else: if event.button() == Qt.LeftButton: if not self.lineClosed: if not self.capturing: self.startCapturing() self.addEndingVertex(event.pos()) elif event.button() == Qt.RightButton: self.finishOperation() self.movingVertex = -1 self.movingSegm = -1 self.movingLineInitialPoint = None self.redrawActions() def keyReleaseEvent(self, event): if event.key() == Qt.Key_Escape: self.stopCapturing() if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete: self.removeLastVertex() if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: self.finishOperation() event.accept() self.redrawActions() def finishOperation(self): self.doSplit() self.stopCapturing() self.initialize() self.startCapturing() def doSplit(self): if self.capturedPoints != None: self.layer.splitFeatures(self.capturedPoints, self.proj.topologicalEditing()) def startCapturing(self): self.prepareRubberBand() self.prepareTempRubberBand() self.drawingLine = True self.capturing = True self.redrawActions() def prepareRubberBand(self): color = QColor("red") color.setAlphaF(0.78) self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.rubberBand.setWidth(1) self.rubberBand.setColor(color) self.rubberBand.show() def prepareTempRubberBand(self): color = QColor("red") color.setAlphaF(0.78) self.tempRubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.LineGeometry) self.tempRubberBand.setWidth(1) self.tempRubberBand.setColor(color) self.tempRubberBand.setLineStyle(Qt.DotLine) self.tempRubberBand.show() def redrawRubberBand(self): self.canvas.scene().removeItem(self.rubberBand) self.prepareRubberBand() for i in range(len(self.capturedPoints)): point = self.capturedPoints[i] if point.__class__ == QgsPoint: vertexCoord = self.toMapCoordinatesV2(self.layer, self.capturedPoints[i]) vertexCoord = QgsPointXY(vertexCoord.x(), vertexCoord.y()) else: vertexCoord = self.toMapCoordinates(self.layer, self.capturedPoints[i]) self.rubberBand.addPoint(vertexCoord) def redrawTempRubberBand(self): if self.tempRubberBand != None: self.tempRubberBand.reset(QgsWkbTypes.LineGeometry) self.tempRubberBand.addPoint( self.toMapCoordinates( self.layer, self.capturedPoints[len(self.capturedPoints) - 1])) def stopCapturing(self): self.deleteLabels() self.deleteVertices() if self.rubberBand: self.canvas.scene().removeItem(self.rubberBand) self.rubberBand = None if self.tempRubberBand: self.canvas.scene().removeItem(self.tempRubberBand) self.tempRubberBand = None self.drawingLine = False self.movingVertices = False self.showingVertices = False self.capturing = False self.capturedPoints = [] self.canvas.refresh() self.redrawActions() def addEndingVertex(self, canvasPoint): mapPoint = self.toMapCoordinates(canvasPoint) layerPoint = self.toLayerCoordinates(self.layer, canvasPoint) self.rubberBand.addPoint(mapPoint) self.capturedPoints.append(layerPoint) self.tempRubberBand.reset(QgsWkbTypes.LineGeometry) self.tempRubberBand.addPoint(mapPoint) def removeLastVertex(self): if not self.capturing: return rubberBandSize = self.rubberBand.numberOfVertices() tempRubberBandSize = self.tempRubberBand.numberOfVertices() numPoints = len(self.capturedPoints) if rubberBandSize < 1 or numPoints < 1: return self.rubberBand.removePoint(-1) if rubberBandSize > 1: if tempRubberBandSize > 1: point = self.rubberBand.getPoint(0, rubberBandSize - 2) self.tempRubberBand.movePoint(tempRubberBandSize - 2, point) else: self.tempRubberBand.reset(self.bandType()) del self.capturedPoints[-1] def addVertex(self, pos): newCapturedPoints = [] for i in range(len(self.capturedPoints) - 1): newCapturedPoints.append(self.capturedPoints[i]) vertex1 = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex1 = self.toCanvasCoordinates( QgsPointXY(vertex1.x(), vertex1.y())) vertex2 = self.toMapCoordinates(self.layer, self.capturedPoints[i + 1]) currentVertex2 = self.toCanvasCoordinates( QgsPointXY(vertex2.x(), vertex2.y())) distance = self.distancePointLine(pos.x(), pos.y(), currentVertex1.x(), currentVertex1.y(), currentVertex2.x(), currentVertex2.y()) if distance <= maxDistanceHitTest: layerPoint = self.toLayerCoordinates(self.layer, pos) newCapturedPoints.append(layerPoint) newCapturedPoints.append(self.capturedPoints[len(self.capturedPoints) - 1]) self.capturedPoints = newCapturedPoints self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.redrawActions() def removeVertex(self, pos): deletedFirst = False deletedLast = False newCapturedPoints = [] for i in range(len(self.capturedPoints)): vertex = self.toMapCoordinates(self.layer, self.capturedPoints[i]) currentVertex = self.toCanvasCoordinates( QgsPointXY(vertex.x(), vertex.y())) if not self.distancePoint(pos, currentVertex) <= maxDistanceHitTest: newCapturedPoints.append(self.capturedPoints[i]) elif i == 0: deletedFirst = True elif i == len(self.capturedPoints) - 1: deletedLast = True self.capturedPoints = newCapturedPoints if deletedFirst and deletedLast: self.lineClosed = False self.redrawRubberBand() self.redrawVertices() self.redrawAreas() self.redrawActions() if len(self.capturedPoints) <= 2: self.stopRemovingVertices() def startMovingVertices(self): self.stopMovingLine() self.stopAddingVertices() self.stopRemovingVertices() self.stopMovingSegment() self.actionMoveVertices.setChecked(True) self.movingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopMovingVertices(self): self.movingVertices = False self.actionMoveVertices.setChecked(False) self.restoreAction() def startAddingVertices(self): self.stopMovingVertices() self.stopRemovingVertices() self.stopMovingLine() self.stopMovingSegment() self.actionAddVertices.setChecked(True) self.addingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopAddingVertices(self): self.addVertices = False self.actionAddVertices.setChecked(False) self.restoreAction() def startRemovingVertices(self): self.stopMovingVertices() self.stopAddingVertices() self.stopMovingLine() self.stopMovingSegment() self.actionRemoveVertices.setChecked(True) self.removingVertices = True self.showingVertices = True self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopRemovingVertices(self): self.removingVertices = False self.actionRemoveVertices.setChecked(False) self.restoreAction() def startMovingSegment(self): self.stopMovingVertices() self.stopMovingLine() self.stopAddingVertices() self.stopRemovingVertices() self.actionMoveSegment.setChecked(True) self.movingSegment = True self.showingVertices = False self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawVertices() self.redrawAreas() self.redrawActions() def stopMovingSegment(self): self.movingSegment = False self.actionMoveSegment.setChecked(False) self.restoreAction() def startMovingLine(self): self.stopMovingVertices() self.stopAddingVertices() self.stopRemovingVertices() self.stopMovingSegment() self.actionMoveLine.setChecked(True) self.movingLine = True self.showingVertices = False self.drawingLine = False self.canvas.scene().removeItem(self.tempRubberBand) self.redrawAreas() self.redrawActions() def stopMovingLine(self): self.actionMoveLine.setChecked(False) self.restoreAction() def lineClose(self): self.lineClosed = True self.capturedPoints.append(self.capturedPoints[0]) self.redrawRubberBand() self.redrawTempRubberBand() self.redrawAreas() self.redrawActions() def lineOpen(self): self.lineClosed = False del self.capturedPoints[-1] self.redrawRubberBand() self.redrawTempRubberBand() self.redrawAreas() self.redrawActions() def showVertices(self): for i in range(len(self.capturedPoints)): vertexc = self.toMapCoordinates(self.layer, self.capturedPoints[i]) vertexCoords = self.toCanvasCoordinates( QgsPointXY(vertexc.x(), vertexc.y())) if i == self.movingVertex: vertex = self.scene.addRect(vertexCoords.x() - 5, vertexCoords.y() - 5, 10, 10, QPen(QColor("green")), QBrush(QColor("green"))) self.vertices.append(vertex) elif i == len(self.capturedPoints ) - 1 and self.movingVertex == 0 and self.lineClosed: vertex = self.scene.addRect(vertexCoords.x() - 5, vertexCoords.y() - 5, 10, 10, QPen(QColor("green")), QBrush(QColor("green"))) self.vertices.append(vertex) else: vertex = self.scene.addRect(vertexCoords.x() - 4, vertexCoords.y() - 4, 8, 8, QPen(QColor("red")), QBrush(QColor("red"))) self.vertices.append(vertex) def deleteVertices(self): for i in range(len(self.vertices)): self.scene.removeItem(self.vertices[i]) self.vertices = [] def lineMagnitude(self, x1, y1, x2, y2): return sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2)) def distancePointLine(self, px, py, x1, y1, x2, y2): magnitude = self.lineMagnitude(x1, y1, x2, y2) if magnitude < 0.00000001: distance = 9999 return distance u1 = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) u = u1 / (magnitude * magnitude) if (u < 0.00001) or (u > 1): ix = self.lineMagnitude(px, py, x1, y1) iy = self.lineMagnitude(px, py, x2, y2) if ix > iy: distance = iy else: distance = ix else: ix = x1 + u * (x2 - x1) iy = y1 + u * (y2 - y1) distance = self.lineMagnitude(px, py, ix, iy) return distance def redrawVertices(self): self.deleteVertices() self.showVertices() def redrawActions(self): self.redrawActionMoveVertices() self.redrawActionAddVertices() self.redrawActionRemoveVertices() self.redrawActionMoveSegment() self.redrawActionLineClose() self.redrawActionLineOpen() self.redrawActionMoveLine() def redrawActionMoveVertices(self): self.actionMoveVertices.setEnabled(False) if len(self.capturedPoints) > 0: self.actionMoveVertices.setEnabled(True) def redrawActionAddVertices(self): self.actionAddVertices.setEnabled(False) if len(self.capturedPoints) >= 2: self.actionAddVertices.setEnabled(True) def redrawActionRemoveVertices(self): self.actionRemoveVertices.setEnabled(False) if len(self.capturedPoints) > 2: self.actionRemoveVertices.setEnabled(True) def redrawActionMoveSegment(self): self.actionMoveSegment.setEnabled(False) if len(self.capturedPoints) > 2: self.actionMoveSegment.setEnabled(True) def redrawActionLineClose(self): self.actionLineClose.setEnabled(False) if not self.lineClosed and len(self.capturedPoints) >= 3: self.actionLineClose.setEnabled(True) def redrawActionLineOpen(self): self.actionLineOpen.setEnabled(False) if self.lineClosed: self.actionLineOpen.setEnabled(True) def redrawActionMoveLine(self): self.actionMoveLine.setEnabled(False) if len(self.capturedPoints) > 0: self.actionMoveLine.setEnabled(True)
class MeasureSelectedFeatures: def __init__(self, iface): self.iface = iface self.window = self.iface.mainWindow() self.proj_close_action = [ a for a in self.iface.projectMenu().actions() if a.objectName() == 'mActionCloseProject' ][0] self.dlg = MeasureSelectedFeaturesDialog() self.toolbar = self.iface.pluginToolBar() self.folder_name = os.path.dirname(os.path.abspath(__file__)) self.icon_path = os.path.join(self.folder_name, 'msf_icon.png') self.action = QAction(QIcon(self.icon_path), 'Sum selected feature size', self.window) self.action.setToolTip('Display total dimensions of selected features') self.da = QgsDistanceArea() # self.da.setEllipsoid('WGS84') self.Distance_Units = { 0: 'm', 1: 'km', 2: 'Feet', 3: 'NM', 4: 'Yards', 5: 'Miles', 6: 'Degrees', 7: 'cm', 8: 'mm', 9: 'Unknown units' } self.Area_Units = { 0: 'm2', 1: 'km2', 2: 'Square feet', 3: 'Square yards', 4: 'Square miles', 5: 'Hectares', 6: 'Acres', 7: 'NM2', 8: 'Square degrees', 9: 'cm2', 10: 'mm2', 11: 'Unknown units' } self.cb_linear_items = [ 'meters', 'kilometers', 'feet', 'nautical miles', 'yards', 'miles', 'degrees', 'centimeters', 'millimeters' ] self.cb_area_items = [ 'square meters', 'square kilometers', 'square feet', 'square yards', 'square miles', 'hectares', 'acres', 'square nautical miles', 'square degrees', 'square centimeters', 'square millimeters' ] self.project = None self.layer = None def initGui(self): """This method is where we add the plugin action to the plugin toolbar.""" self.action.setObjectName('btnMSF') self.toolbar.addAction(self.action) if self.iface.activeLayer(): self.layer = self.iface.activeLayer() else: self.action.setEnabled(False) self.action.triggered.connect(self.action_triggered) self.iface.projectRead.connect(self.project_opened) self.iface.newProjectCreated.connect(self.project_opened) self.dlg.was_closed.connect(self.dockwidget_closed) self.dlg.topLevelChanged.connect(self.widget_moved) self.iface.projectMenu().aboutToShow.connect(self.project_menu_shown) self.proj_close_action.triggered.connect( self.project_closed_via_menu_action) self.dlg.rad_1.setChecked(True) # 03-06-21 #####31-05-21 self.dlg.rad_1.toggled.connect( self.radios_toggled) #############25-06-21 self.dlg.cb_units.currentIndexChanged.connect(self.total_length) def project_menu_shown(self): if self.dlg.isVisible(): self.dlg.close() def project_opened(self): if self.project is not None: self.project.layerWasAdded.disconnect(self.layer_added) self.project.layersRemoved.disconnect(self.layers_removed) self.project = QgsProject.instance() a = [a for a in self.toolbar.actions() if a.objectName() == 'btnMSF'][0] if self.iface.activeLayer(): self.layer = self.iface.activeLayer() if not a.isEnabled(): a.setEnabled(True) self.set_title() else: if a.isEnabled(): a.setEnabled(False) self.project.layerWasAdded.connect(self.layer_added) self.project.layersRemoved.connect(self.layers_removed) def layer_added(self, l): if self.layer is None: if isinstance(l, QgsVectorLayer): self.layer = l if len(self.project.mapLayers()) == 1: a = [ a for a in self.toolbar.actions() if a.objectName() == 'btnMSF' ][0] if not a.isEnabled(): a.setEnabled(True) def layers_removed(self, lyr_ids): if len(self.project.mapLayers()) == 0: self.layer = None if self.dlg.isVisible(): self.dlg.close() a = [ a for a in self.toolbar.actions() if a.objectName() == 'btnMSF' ][0] if a.isEnabled(): a.setEnabled(False) def project_closed_via_menu_action(self): a = [a for a in self.toolbar.actions() if a.objectName() == 'btnMSF'][0] a.setEnabled(False) QgsProject.instance().layerWasAdded.disconnect(self.layer_added) def widget_moved(self, top_level): if top_level is True: self.set_gui_geometry() def set_gui_geometry(self): self.dlg.setGeometry(750, 300, 750, 50) def action_triggered(self): self.window.addDockWidget(Qt.TopDockWidgetArea, self.dlg) self.dlg.setAllowedAreas(Qt.TopDockWidgetArea) self.dlg.show() if self.layer is not None: if isinstance(self.iface.activeLayer(), QgsVectorLayer): self.layer = self.iface.activeLayer() if isinstance(self.layer, QgsVectorLayer): self.layer.selectionChanged.connect(self.total_length) self.iface.currentLayerChanged.connect(self.active_changed) self.set_title() # V2 change self.total_length() # V2 change #####25-05-21 self.action.setEnabled(False) ##### def active_changed(self, new_layer): self.tool_reset(self.layer) self.set_title() # V3 change if isinstance(new_layer, QgsVectorLayer): if self.layer is not None: # print(self.layer.name()) if len(QgsProject.instance().mapLayers()) > 1: self.layer.selectionChanged.disconnect(self.total_length) self.layer = new_layer self.layer.selectionChanged.connect(self.total_length) self.total_length() # V2 change def tool_reset(self, layer): if layer is not None: if isinstance( layer, QgsVectorLayer) and layer.geometryType() == 0: # V2 change layer.selectByIds([]) for le in self.dlg.findChildren(QLineEdit): le.clear() for cb in self.dlg.findChildren(QComboBox): cb.clear() cb.setEnabled(False) def set_title(self): self.dlg.lbl_1.setText('Total') for le in self.dlg.findChildren(QLineEdit): le.setEnabled(False) active_layer = self.iface.activeLayer() if isinstance(active_layer, QgsVectorLayer): if active_layer.isSpatial(): #####25-05-21 if active_layer.geometryType() == 0: # points self.dlg.setWindowTitle('Point layer selected') for le in self.dlg.findChildren(QLineEdit): le.setEnabled(False) for rb in self.dlg.findChildren(QRadioButton): rb.setEnabled(False) for cb in self.dlg.findChildren(QComboBox): cb.clear() cb.setEnabled(False) elif active_layer.geometryType() in [1, 2]: # self.dlg.setWindowTitle('Measuring {} selected features from layer: {}'.format(active_layer.selectedFeatureCount(), active_layer.name())) # V2 change for le in self.dlg.findChildren(QLineEdit): le.setEnabled(True) self.dlg.cb_units.setEnabled(True) if active_layer.crs().isGeographic(): self.dlg.rad_1.setChecked(True) self.dlg.rad_1.setEnabled(True) self.dlg.rad_2.setEnabled(False) ###25-06-21 self.dlg.cb_units.clear() if active_layer.geometryType() == 1: # lines self.dlg.cb_units.addItems(self.cb_linear_items) elif active_layer.geometryType() == 2: # polygons self.dlg.cb_units.addItems(self.cb_area_items) else: # projected CRS for rb in self.dlg.findChildren(QRadioButton): rb.setEnabled(True) ###25-06-21 if active_layer.geometryType() == 1: # lines self.dlg.cb_units.clear() self.dlg.cb_units.addItems(self.cb_linear_items) if self.dlg.rad_2.isChecked(): self.dlg.cb_units.removeItem( self.cb_linear_items.index('degrees')) elif active_layer.geometryType() == 2: # polygons self.dlg.cb_units.clear() self.dlg.cb_units.addItems(self.cb_area_items) if self.dlg.rad_2.isChecked(): self.dlg.cb_units.removeItem( self.cb_area_items.index('square degrees')) ##### elif not active_layer.isSpatial(): self.dlg.setWindowTitle( 'Raster or non-spatial vector layer selected') for le in self.dlg.findChildren(QLineEdit): le.setEnabled(False) for rb in self.dlg.findChildren(QRadioButton): rb.setEnabled(False) for cb in self.dlg.findChildren(QComboBox): cb.clear() cb.setEnabled(False) elif isinstance(active_layer, QgsRasterLayer): self.dlg.setWindowTitle( 'Raster or non-spatial vector layer selected') for le in self.dlg.findChildren(QLineEdit): le.setEnabled(False) for rb in self.dlg.findChildren(QRadioButton): rb.setEnabled(False) for cb in self.dlg.findChildren(QComboBox): cb.clear() cb.setEnabled(False) elif active_layer is None: self.dlg.setWindowTitle('No layer selected') def radios_toggled(self): if self.iface.activeLayer().geometryType() == 1: # lines if self.dlg.rad_2.isChecked(): # planimetric if self.dlg.cb_units.currentText() == 'degrees': # reload combobox items without degree option self.dlg.cb_units.clear() self.dlg.cb_units.addItems(self.cb_linear_items) self.dlg.cb_units.removeItem( self.cb_linear_items.index('degrees')) else: # just remove the degree option self.dlg.cb_units.removeItem( self.cb_linear_items.index('degrees')) elif self.dlg.rad_1.isChecked(): # ellipsoidal if self.dlg.cb_units.count() == 0: self.dlg.cb_units.addItems(self.cb_linear_items) if not self.dlg.cb_units.isEnabled(): self.dlg.cb_units.setEnabled(True) if self.layer.crs().mapUnits( ) != QgsUnitTypes.DistanceUnknownUnit: self.dlg.cb_units.setCurrentText( QgsUnitTypes.encodeUnit( self.layer.crs().mapUnits())) else: self.dlg.cb_units.insertItem(6, 'degrees') elif self.iface.activeLayer().geometryType() == 2: # polygons if self.dlg.rad_2.isChecked(): if self.dlg.cb_units.currentText() == 'square degrees': self.dlg.cb_units.clear() self.dlg.cb_units.addItems(self.cb_area_items) self.dlg.cb_units.removeItem( self.cb_area_items.index('square degrees')) else: self.dlg.cb_units.removeItem( self.cb_area_items.index('square degrees')) elif self.dlg.rad_1.isChecked(): if self.dlg.cb_units.count() == 0: self.dlg.cb_units.addItems(self.cb_area_items) if not self.dlg.cb_units.isEnabled(): self.dlg.cb_units.setEnabled(True) if self.layer.crs().mapUnits( ) != QgsUnitTypes.DistanceUnknownUnit: self.dlg.cb_units.setCurrentText('square {}'.format( QgsUnitTypes.encodeUnit( self.layer.crs().mapUnits()))) else: self.dlg.cb_units.insertItem(8, 'square degrees') self.total_length() def geodetic_length(self, feat): geo_m = self.da.measureLength(feat.geometry()) return geo_m def geodetic_area(self, feat): geo_m2 = self.da.measureArea(feat.geometry()) return geo_m2 def planar_length(self, feat): proj_m = feat.geometry().length() return proj_m def planar_area(self, feat): proj_m2 = feat.geometry().area() return proj_m2 def total_length(self): # print('func called') layer = self.layer # self.set_title() if isinstance(layer, QgsVectorLayer) and layer.isSpatial(): #####04-06-21 self.da.setSourceCrs(layer.crs(), QgsProject.instance().transformContext()) self.da.setEllipsoid(layer.crs().ellipsoidAcronym()) #####04-06-21 select_fts = [f for f in layer.selectedFeatures()] epsg_code = layer.crs().authid() if layer.crs().isGeographic(): crs_type = 'Geographic' else: crs_type = 'Projected' l_units = layer.crs().mapUnits() if layer.geometryType() == 1: # Lines self.dlg.setWindowTitle( 'Measuring {} selected features from layer: {} - {} ({})'. format(layer.selectedFeatureCount(), layer.name(), epsg_code, crs_type)) self.dlg.lbl_1.setText('Total length of selected features: ') if layer.crs().isGeographic() or ( not layer.crs().isGeographic() and self.dlg.rad_1.isChecked()): total_geo_m = sum( [self.geodetic_length(f) for f in select_fts]) if self.dlg.cb_units.currentText() == 'meters': self.dlg.le_total.setText( str('{:.3f}m'.format(total_geo_m))) elif self.dlg.cb_units.currentText() == 'kilometers': total_geo_km = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceKilometers) self.dlg.le_total.setText( str('{:.3f}km'.format(total_geo_km))) elif self.dlg.cb_units.currentText() == 'feet': total_geo_ft = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceFeet) self.dlg.le_total.setText( str('{:.3f}ft'.format(total_geo_ft))) elif self.dlg.cb_units.currentText() == 'nautical miles': total_geo_nm = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceNauticalMiles) self.dlg.le_total.setText( str('{:.3f}NM'.format(total_geo_nm))) elif self.dlg.cb_units.currentText() == 'yards': total_geo_yds = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceYards) self.dlg.le_total.setText( str('{:.3f}yds'.format(total_geo_yds))) elif self.dlg.cb_units.currentText() == 'miles': total_geo_mi = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceMiles) self.dlg.le_total.setText( str('{:.3f}mi'.format(total_geo_mi))) elif self.dlg.cb_units.currentText() == 'degrees': total_geo_deg = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceDegrees) self.dlg.le_total.setText( str('{:.3f}deg'.format(total_geo_deg))) elif self.dlg.cb_units.currentText() == 'centimeters': total_geo_cm = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceCentimeters) self.dlg.le_total.setText( str('{:.3f}cm'.format(total_geo_cm))) elif self.dlg.cb_units.currentText() == 'millimeters': total_geo_mm = self.da.convertLengthMeasurement( total_geo_m, QgsUnitTypes.DistanceMillimeters) self.dlg.le_total.setText( str('{:.3f}mm'.format(total_geo_mm))) else: # projected CRS total_length_proj = sum( [self.planar_length(f) for f in select_fts]) if l_units != 6: # Units are NOT degrees if self.dlg.cb_units.currentText() == 'meters': self.dlg.le_total.setText( str('{:.3f}m'.format( self.convert_planar_length( total_length_proj, l_units, 0)))) elif self.dlg.cb_units.currentText() == 'kilometers': self.dlg.le_total.setText( str('{:.3f}km'.format( self.convert_planar_length( total_length_proj, l_units, 1)))) elif self.dlg.cb_units.currentText() == 'feet': self.dlg.le_total.setText( str('{:.3f}ft'.format( self.convert_planar_length( total_length_proj, l_units, 2)))) elif self.dlg.cb_units.currentText( ) == 'nautical miles': self.dlg.le_total.setText( str('{:.3f}NM'.format( self.convert_planar_length( total_length_proj, l_units, 3)))) elif self.dlg.cb_units.currentText() == 'yards': self.dlg.le_total.setText( str('{:.3f}yd'.format( self.convert_planar_length( total_length_proj, l_units, 4)))) elif self.dlg.cb_units.currentText() == 'miles': self.dlg.le_total.setText( str('{:.3f}mi'.format( self.convert_planar_length( total_length_proj, l_units, 5)))) elif self.dlg.cb_units.currentText() == 'centimeters': self.dlg.le_total.setText( str('{:.3f}cm'.format( self.convert_planar_length( total_length_proj, l_units, 7)))) elif self.dlg.cb_units.currentText() == 'millimeters': self.dlg.le_total.setText( str('{:.3f}mm'.format( self.convert_planar_length( total_length_proj, l_units, 8)))) else: # degree units self.dlg.cb_units.clear() self.dlg.cb_units.setEnabled(False) self.dlg.le_total.setText( str('{:.3f}{}'.format( total_length_proj, self.Distance_Units[l_units]))) elif layer.geometryType() == 2: # Polygons self.dlg.setWindowTitle( 'Measuring {} selected features from layer: {} - {} ({})'. format(layer.selectedFeatureCount(), layer.name(), epsg_code, crs_type)) self.dlg.lbl_1.setText('Total area of selected features: ') if layer.crs().isGeographic() or ( not layer.crs().isGeographic() and self.dlg.rad_1.isChecked()): total_geo_m = sum( [self.geodetic_area(f) for f in select_fts]) if self.dlg.cb_units.currentText() == 'square meters': self.dlg.le_total.setText( str('{:.3f}m2'.format(total_geo_m))) elif self.dlg.cb_units.currentText( ) == 'square kilometers': total_geo_km = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareKilometers) self.dlg.le_total.setText( str('{:.3f}km2'.format(total_geo_km))) elif self.dlg.cb_units.currentText() == 'square feet': total_geo_ft = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareFeet) self.dlg.le_total.setText( str('{:.3f}ft2'.format(total_geo_ft))) elif self.dlg.cb_units.currentText() == 'square yards': total_geo_yds = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareYards) self.dlg.le_total.setText( str('{:.3f}yd2'.format(total_geo_yds))) elif self.dlg.cb_units.currentText() == 'square miles': total_geo_mi = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareMiles) self.dlg.le_total.setText( str('{:.3f}mi2'.format(total_geo_mi))) elif self.dlg.cb_units.currentText() == 'hectares': total_geo_ha = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaHectares) self.dlg.le_total.setText( str('{:.3f}ha'.format(total_geo_ha))) elif self.dlg.cb_units.currentText() == 'acres': total_geo_ac = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaAcres) self.dlg.le_total.setText( str('{:.3f}ac'.format(total_geo_ac))) elif self.dlg.cb_units.currentText( ) == 'square nautical miles': total_geo_nm = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareNauticalMiles) self.dlg.le_total.setText( str('{:.3f}NM2'.format(total_geo_nm))) elif self.dlg.cb_units.currentText() == 'square degrees': total_geo_deg = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareDegrees) self.dlg.le_total.setText( str('{:.3f}deg2'.format(total_geo_deg))) elif self.dlg.cb_units.currentText( ) == 'square centimeters': total_geo_cm = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareCentimeters) self.dlg.le_total.setText( str('{:.3f}cm2'.format(total_geo_cm))) elif self.dlg.cb_units.currentText( ) == 'square millimeters': total_geo_mm = self.da.convertAreaMeasurement( total_geo_m, QgsUnitTypes.AreaSquareMillimeters) self.dlg.le_total.setText( str('{:.3f}mm2'.format(total_geo_mm))) else: # projected CRS total_area_proj = sum( [self.planar_area(f) for f in select_fts]) if l_units != 6: # Units are NOT degrees if self.dlg.cb_units.currentText() == 'square meters': self.dlg.le_total.setText( str('{:.3f}m2'.format( self.convert_planar_area( total_area_proj, l_units, 'square meters')))) elif self.dlg.cb_units.currentText( ) == 'square kilometers': self.dlg.le_total.setText( str('{:.3f}km2'.format( self.convert_planar_area( total_area_proj, l_units, 'square kilometers')))) elif self.dlg.cb_units.currentText() == 'square feet': self.dlg.le_total.setText( str('{:.3f}ft2'.format( self.convert_planar_area( total_area_proj, l_units, 'square feet')))) elif self.dlg.cb_units.currentText() == 'square yards': self.dlg.le_total.setText( str('{:.3f}yd2'.format( self.convert_planar_area( total_area_proj, l_units, 'square yards')))) elif self.dlg.cb_units.currentText() == 'square miles': self.dlg.le_total.setText( str('{:.3f}mi2'.format( self.convert_planar_area( total_area_proj, l_units, 'square miles')))) elif self.dlg.cb_units.currentText() == 'hectares': self.dlg.le_total.setText( str('{:.3f}ha'.format( self.convert_planar_area( total_area_proj, l_units, 'hectares')))) elif self.dlg.cb_units.currentText() == 'acres': self.dlg.le_total.setText( str('{:.3f}ac'.format( self.convert_planar_area( total_area_proj, l_units, 'acres')))) elif self.dlg.cb_units.currentText( ) == 'square nautical miles': self.dlg.le_total.setText( str('{:.3f}NM2'.format( self.convert_planar_area( total_area_proj, l_units, 'square nautical miles')))) elif self.dlg.cb_units.currentText( ) == 'square centimeters': self.dlg.le_total.setText( str('{:.3f}cm2'.format( self.convert_planar_area( total_area_proj, l_units, 'square centimeters')))) elif self.dlg.cb_units.currentText( ) == 'square millimeters': self.dlg.le_total.setText( str('{:.3f}mm2'.format( self.convert_planar_area( total_area_proj, l_units, 'square millimeters')))) else: # Degree units self.dlg.cb_units.clear() if self.dlg.cb_units.isEnabled(): self.dlg.cb_units.setEnabled(False) self.dlg.le_total.setText( str('{:.3f}{}2'.format( total_area_proj, self.Distance_Units[l_units]))) if layer.geometryType() in [3, 4]: self.iface.messageBar().pushMessage( 'Layer has unknown or Null geometry type', duration=2) ###########################UNIT CONVERSIONS FOR PROJECTED CRS'S##################################### def convert_planar_length(self, length, input_units, output_units): if input_units == 0: # Meters if output_units == 0: # Meters result = length elif output_units == 1: # Kilometers result = length / 1000 elif output_units == 2: # Imperial feet result = length * 3.28084 elif output_units == 3: # Nautical miles result = length / 1852 elif output_units == 4: # Imperial yards result = length * 1.09361 elif output_units == 5: # Terrestrial miles result = length / 1609.344 elif output_units == 7: # Centimeters result = length * 100 elif output_units == 8: # Millimeters result = length * 1000 elif input_units == 1: # Kilometers if output_units == 0: # Meters result = length * 1000 elif output_units == 1: # Kilometers result = length elif output_units == 2: # Imperial feet result = length * 3280.84 elif output_units == 3: # Nautical miles result = length / 1.852 elif output_units == 4: # Imperial yards result = length * 1093.61 elif output_units == 5: # Terrestrial miles result = length / 1.609 elif output_units == 7: # Centimeters result = length * 100000 elif output_units == 8: # Millimeters result = length * 1000000 elif input_units == 2: # Imperial feet if output_units == 0: # Meters result = length / 3.281 elif output_units == 1: # Kilometers result = length / 3281 elif output_units == 2: # Imperial feet result = length elif output_units == 3: # Nautical Miles result = length / 6076 elif output_units == 4: # Imperial yards result = length / 3 elif output_units == 5: # Terrestrial miles result = length / 5280 elif output_units == 7: # Centimeters result = length * 30.48 elif output_units == 8: # Millimeters result = length * 304.8 elif input_units == 3: # Nautical miles if output_units == 0: # Meters result = length * 1852 if output_units == 1: # Kilometers result = length * 1.852 elif output_units == 2: # Imperial feet result = length * 6076 elif output_units == 3: # Nautical miles result = length elif output_units == 4: # Imperial yards result = length * 2025.37 elif output_units == 5: # Terrestrial miles result = length * 1.15078 elif output_units == 7: # Centimeters result = length * 185200 elif output_units == 8: # Millimeters result = length * 1852000 elif input_units == 4: # Imperial yards if output_units == 0: # Meters result = length / 1.094 elif output_units == 1: # Kilometers result = length / 1094 elif output_units == 2: # Imperial feet result = length * 3 elif output_units == 3: # Nautical miles result = length / 2025 elif output_units == 4: # Imperial yards result = length elif output_units == 5: # Terrestrial miles result = length / 1760 elif output_units == 7: # Centimeters result = length * 91.44 elif output_units == 8: # Millimeters result = length * 914.4 elif input_units == 5: # Terrestrial miles if output_units == 0: # Meters result = length * 1609.34 elif output_units == 1: # Kilometers result = length * 1.609 elif output_units == 2: # Imperial feet result = length * 5280 elif output_units == 3: # Nautical miles result = length / 1.151 elif output_units == 4: # Imperial yards result = length * 1760 elif output_units == 5: # Terrestrial miles result = length elif output_units == 7: # Centimeters result = length * 160934 elif output_units == 8: # Millimeters result = length * 1609340 elif input_units == 7: # Centimeters if output_units == 0: # Meters result = length / 100 elif output_units == 1: # Kilometers result = length / 100000 elif output_units == 2: # Imperial feet result = length / 30.48 elif output_units == 3: # Nautical miles result = length / 185200 elif output_units == 4: # Imperial yards result = length / 91.44 elif output_units == 5: # Terrestrial miles result = length / 160934 elif output_units == 7: # Centimeters result = length elif output_units == 8: # Millimeters result = length * 10 elif input_units == 8: # Millimeters if output_units == 0: # Meters result = length / 1000 elif output_units == 1: # Kilometers result = length / 1000000 elif output_units == 2: # Imperial feet result = length / 305 elif output_units == 3: # Nautical miles result = length / 1852000 elif output_units == 4: # Imperial yards result = length / 914 elif output_units == 5: # Terrestrial miles result = length / 1609000 elif output_units == 7: # Centimeters result = length / 10 elif output_units == 8: # Millimeters result = length return result #####################################AREA UNITS##################################################### def convert_planar_area(self, area, input_units, output_units): if input_units == 0: # Meters if output_units == 'square meters': # Square meters result = area elif output_units == 'square kilometers': # Square kilometers result = area / 1000000 elif output_units == 'square feet': # Square feet result = area * 10.764 elif output_units == 'square yards': # Square yards result = area * 1.196 elif output_units == 'square miles': # Square miles result = area / 2589988.1 elif output_units == 'hectares': # Hectares result = area / 10000 elif output_units == 'acres': # Acres result = area / 4047 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 3429904 elif output_units == 'square centimeters': # Square centimeters result = area * 10000 elif output_units == 'square millimeters': # Square millimeters result = area * 1000000 #-------------------------------------------------------------------- elif input_units == 1: # Kilometers if output_units == 'square meters': # Square meters result = area * 10000 elif output_units == 'square kilometers': # Square kilometers result = area elif output_units == 'square feet': # Square feet result = area * 10763910.417 elif output_units == 'square yards': # Square yards result = area * 1195990.05 elif output_units == 'square miles': # Square miles result = area / 2.59 elif output_units == 'hectares': # Hectares result = area * 100 elif output_units == 'acres': # Acres result = area * 247.105 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 3.43 elif output_units == 'square centimeters': # Square centimeters result = area * 10000000000 elif output_units == 'square millimeters': # Square millimeters result = area * 1000000000000 #-------------------------------------------------------------------- elif input_units == 2: # Imperial feet if output_units == 'square meters': # Square meters result = area / 10.764 elif output_units == 'square kilometers': # Square kilometers result = area / 10763910.417 elif output_units == 'square feet': # Square feet result = area elif output_units == 'square yards': # Square yards result = area / 9 elif output_units == 'square miles': # Square miles result = area / 27878400 elif output_units == 'hectares': # Hectares result = area / 107639 elif output_units == 'acres': # Acres result = area / 43560 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 36920000 elif output_units == 'square centimeters': # Square centimeters result = area * 929 elif output_units == 'square millimeters': # Square millimeters result = area * 92903 #-------------------------------------------------------------------- elif input_units == 3: # Nautical miles if output_units == 'square meters': # Square meters result = area * 3430000 elif output_units == 'square kilometers': # Square kilometers result = area * 3.43 elif output_units == 'square feet': # Square feet result = area * 36920000 elif output_units == 'square yards': # Square yards result = area * 4102000 elif output_units == 'square miles': # Square miles result = area * 1.324 elif output_units == 'hectares': # Hectares result = area * 343 elif output_units == 'acres': # Acres result = area * 847.548 elif output_units == 'square nautical miles': # Square Nautical miles result = area elif output_units == 'square centimeters': # Square centimeters result = area * 34300000000 elif output_units == 'square millimeters': # Square millimeters result = area * 3430000000000 #-------------------------------------------------------------------- elif input_units == 4: # Imperial yards if output_units == 'square meters': # Square meters result = area / 1.196 elif output_units == 'square kilometers': # Square kilometers result = area / 1196000 elif output_units == 'square feet': # Square feet result = area * 9 elif output_units == 'square yards': # Square yards result = area elif output_units == 'square miles': # Square miles result = area / 3098000 elif output_units == 'hectares': # Hectares result = area / 11960 elif output_units == 'acres': # Acres result = area / 4840 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 4102000 elif output_units == 'square centimeters': # Square centimeters result = area * 8361 elif output_units == 'square millimeters': # Square millimeters result = area * 836127 #-------------------------------------------------------------------- elif input_units == 5: # Terrestrial miles if output_units == 'square meters': # Square meters result = area * 2590000 elif output_units == 'square kilometers': # Square kilometers result = area * 2.59 elif output_units == 'square feet': # Square feet result = area * 27880000 elif output_units == 'square yards': # Square yards result = area * 3098000 elif output_units == 'square miles': # Square miles result = area elif output_units == 'hectares': # Hectares result = area * 259 elif output_units == 'acres': # Acres result = length * 640 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 1.324 elif output_units == 'square centimeters': # Square centimeters result = area * 25900000000 elif output_units == 'square millimeters': # Square millimeters result = area * 2590000000000 #-------------------------------------------------------------------- elif input_units == 7: # Centimeters if output_units == 'square meters': # Square meters result = area / 10000 elif output_units == 'square kilometers': # Square kilometers result = area / 10000000000 elif output_units == 'square feet': # Square feet result = area / 929.03 elif output_units == 'square yards': # Square yards result = area / 8361.27 elif output_units == 'square miles': # Square miles result = area / 25899881103.36 elif output_units == 'hectares': # Hectares result = area / 100000000 elif output_units == 'acres': # Acres result = area / 40468564.224 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 34299040000 elif output_units == 'square centimeters': # Square centimeters result = area elif output_units == 'square millimeters': # Square millimeters result = area * 100 #-------------------------------------------------------------------- elif input_units == 8: # Millimeters if output_units == 'square meters': # Square meters result = area / 1000000 elif output_units == 'square kilometers': # Square kilometers result = area / 1000000000000 elif output_units == 'square feet': # Square feet result = area / 92903 elif output_units == 'square yards': # Square yards result = area / 836127 elif output_units == 'square miles': # Square miles result = area / 2589988110336 elif output_units == 'hectares': # Hectares result = area / 10000000000 elif output_units == 'acres': # Acres result = area / 4046856422 elif output_units == 'square nautical miles': # Square Nautical miles result = area / 3429904000000 elif output_units == 'square centimeters': # Square centimeters result = area / 100 elif output_units == 'square millimeters': # Square millimeters result = area return result #################################################################################################### def dockwidget_closed(self): # print('dockwidget closed!!') self.dlg.setFloating(False) if self.layer is not None: self.tool_reset(self.layer) if isinstance(self.layer, QgsVectorLayer): self.layer.selectionChanged.disconnect(self.total_length) self.iface.currentLayerChanged.disconnect(self.active_changed) #####25-05-21 self.action.setEnabled(True) def unload(self): self.toolbar.removeAction(self.action) del self.action
class MorphALPolygonPerimeterArea(PTM4QgisAlgorithm): INPUT = 'INPUT' METHOD = 'CALC_METHOD' OUTPUT = 'OUTPUT' def help(self): # TODO improve help text return self.tr('Compute the perimeters and areas of a layer of polygons') def group(self): return self.tr('MorphAL') def groupId(self): return 'morphal' def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.calc_methods = [self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal')] def initAlgorithm(self, config): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT, self.tr('Input layer'), types=[QgsProcessing.TypeVectorPolygon] ) ) self.addParameter( QgsProcessingParameterEnum( self.METHOD, self.tr('Calculate using'), options=self.calc_methods, defaultValue=0 ) ) self.addParameter( QgsProcessingParameterFeatureSink( self.OUTPUT, self.tr('Layer with added perimeters and areas') ) ) def name(self): return 'morphalpolygonperimeterarea' def displayName(self): # TODO IMPROVE TEXT return self.tr('Compute the perimeters and areas of a layer of polygons') def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() if QgsWkbTypes.geometryType(wkb_type) != QgsWkbTypes.PolygonGeometry: # TODO IMPROVE FEEDBACK feedback.reportError('The layer geometry type is different from a polygon') return {} fields = source.fields() new_fields = QgsFields() new_fields.append(QgsField('perimeter', QVariant.Double)) new_fields.append(QgsField('area', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.ellipsoid()) elif method == 1: if not context.project(): raise QgsProcessingException(self.tr('No project is available in this context')) coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) attrs.extend(self.polygon_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id} def polygon_attributes(self, geometry): perimeter = self.distance_area.measurePerimeter(geometry) area = self.distance_area.measureArea(geometry) return [perimeter, area]
def processAlgorithm(self, parameters, context, feedback): # get input variables raster_layer = self.parameterAsRasterLayer(parameters, self.INPUT, context) band_number = self.parameterAsInt(parameters, self.BAND, context) output = self.parameterAsFileOutput(parameters, self.OUTPUT, context) # layer name name = raster_layer.name() # layer provider provider = raster_layer.dataProvider() # get CRS crs_raster = raster_layer.crs() # set project ellipsoid (for measurements) to CRS ellipsoid ellipsoid = context.project().crs().ellipsoidAcronym() # get transform context from project trans_context = context.project().transformContext() # 5% done feedback.setProgress(5) # Initialize Area calculator class with ellipsoid da = QgsDistanceArea() da.setSourceCrs(crs_raster, trans_context) da.setEllipsoid(ellipsoid) # get raster extent extent = raster_layer.extent() extent = QgsGeometry().fromRect(extent) # 20% done feedback.setProgress(20) # get area of extent feedback.pushConsoleInfo( self.tr(f'Measuring area of raster rectangle...')) area = da.measureArea(extent) # convert area from area_m2 = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareMeters) # 30% done feedback.setProgress(30) # check if NoData value is set if provider.sourceHasNoDataValue(band_number): feedback.pushConsoleInfo( self.tr(f'Calculating NoData percentage...')) # unique values parameters rastervalue_params = {'INPUT': raster_layer, 'BAND': band_number} # run unique values result = processing.run('native:rasterlayeruniquevaluesreport', rastervalue_params) # get total pixel and nodata count cells = result['TOTAL_PIXEL_COUNT'] nodata_cells = result['NODATA_PIXEL_COUNT'] # calculate nodata percentage nodata_percentage = nodata_cells / cells # calclate data coverage feedback.pushConsoleInfo( self.tr(f'Calculating data coverage...\n')) coverage_m2 = area_m2 * (1 - nodata_percentage) coverage_percentage = (1 - nodata_percentage) else: feedback.reportError(self.tr( 'Missing NoData value(s) detected. Check settings of the raster layer!' ), fatalError=False) coverage_m2 = area_m2 coverage_percentage = 1.0 # 80% done feedback.setProgress(80) # calculate area units area_km2 = area_m2 / (1000 * 1000) coverage_km2 = coverage_m2 / (1000 * 1000) feedback.pushConsoleInfo( self.tr(f'------------------------------------------\n')) feedback.pushConsoleInfo( self.tr(f'Raster Coverage of Layer [ {name} ]:\n')) feedback.pushConsoleInfo( self.tr(f'Raster Area [km2] ....... : {round(area_km2,3)}')) feedback.pushConsoleInfo( self.tr(f'Data Coverage [km2] ..... : {round(coverage_km2,3)}')) feedback.pushConsoleInfo( self.tr(f'Data Coverage [m2] ...... : {round(coverage_m2,2)}')) feedback.pushConsoleInfo( self. tr(f'Data Coverage [%] ....... : {round(coverage_percentage * 100,2)}\n' )) feedback.pushConsoleInfo( self. tr(f'This is {round(coverage_km2 / self.bremen_area,2)} times the area of Bremen\n' )) feedback.pushConsoleInfo( self.tr(f'------------------------------------------\n')) # 100% done feedback.setProgress(100) feedback.pushInfo( self.tr( f'{utils.return_success()}! Raster area has been calculated!\n' )) result = { self.RASTER_AREA_KM2: area_km2, self.DATA_COVERAGE_KM2: coverage_km2, self.DATA_COVERAGE_M2: coverage_m2, self.DATA_COVERAGE_PERCENT: coverage_percentage, self.OUTPUT: output } if output != '': self.write_output(name, result, output) return result
def generateFootprintsForFilmOblique(self): self.reloadFpLayer() self.reloadCpLayer() caps = self.fpLayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.AddFeatures: if self.cpLayer.dataProvider().featureCount() > 0: iter = self.cpLayer.getFeatures() existingFootpints = QgsVectorLayerUtils.getValues( self.fpLayer, "bildnummer")[0] cpFt = QgsFeature() fpFts = [] #iterate over points from CP Layer > LON, LAT while iter.nextFeature(cpFt): if cpFt['bildnummer'] in existingFootpints: #QMessageBox.warning(None, u"Bild Nummern", u"Footprint für das Bild mit der Nummer {0} wurde bereits erstellt.".format(ft['BILD'])) continue cp = cpFt.geometry() cpMetric = QgsGeometry(cp) destCrs = QgsCoordinateReferenceSystem() destCrs.createFromProj4(self.Proj4Utm(cp.asPoint())) coordTransformF = QgsCoordinateTransform( self.cpLayer.crs(), destCrs, QgsProject.instance()) coordTransformB = QgsCoordinateTransform( destCrs, self.cpLayer.crs(), QgsProject.instance()) cpMetric.transform(coordTransformF) if cpFt['radius'] == '': r = 175 else: r = float(cpFt['radius']) fpMetric = QgsGeometry(cpMetric.buffer(r, 18)) fp = QgsGeometry(fpMetric) fp.transform(coordTransformB) fpFt = QgsFeature(self.fpLayer.fields()) fpFt.setGeometry(fp) fpFt.setAttribute("bildnummer", cpFt["bildnummer"]) fpFt.setAttribute("filmnummer", cpFt["filmnummer"]) da = QgsDistanceArea() da.setEllipsoid(self.fpLayer.crs().ellipsoidAcronym()) fpFt.setAttribute('shape_length', da.measurePerimeter(fp)) fpFt.setAttribute('shape_area', da.measureArea(fp)) fpFts.append(fpFt) (res, outFeats) = self.fpLayer.dataProvider().addFeatures(fpFts) self.fpLayer.updateExtents() if self.canvas.isCachingEnabled(): self.fpLayer.triggerRepaint() else: self.canvas.refresh() else: QMessageBox.warning( None, "Keine Bildmittelpunkte", "Keine Bildmittelpunkte für den Film {0} vorhanden.". format(self.currentFilmNumber)) else: QMessageBox.warning( None, "Layer Capabilities", "AddFeature is not enabled ({0})".format( self.fpLayer.dataProvider().capabilitiesString()))
def generateFootprintsForFilmVertical(self): self.reloadFpLayer() self.reloadCpLayer() # Error wenn nur ein punkt vorhanden if self.cpLayer.featureCount() > 1: caps = self.fpLayer.dataProvider().capabilities() if caps & QgsVectorDataProvider.AddFeatures: #Get FORM1 from FilmInfoDict f1 = self.currentFilmInfoDict["form1"] # Image height f2 = self.currentFilmInfoDict["form2"] # Image width iterFeatures = self.cpLayer.getFeatures() iterNext = self.cpLayer.getFeatures() existingFootpints = QgsVectorLayerUtils.getValues( self.fpLayer, "bildnummer")[0] ft = QgsFeature() ftNext = QgsFeature() iterNext.nextFeature(ftNext) fpFeats = [] kappasToUpdate = {} # iterate over points from CP Layer > LON, LAT i = 0 while iterFeatures.nextFeature(ft): i += 1 iterNext.nextFeature(ftNext) p = QgsPointXY(ft.geometry().asPoint()) if ft['bildnummer'] in existingFootpints: pPrevGeom = QgsGeometry(ft.geometry()) #QMessageBox.warning(None, u"Bild Nummern", u"Footprint für das Bild mit der Nummer {0} wurde bereits erstellt.".format(ft['BILD'])) continue if i == 1: pPrevGeom = QgsGeometry(ftNext.geometry()) #if iterNext.isClosed(): # #use pPrev as pNext # pNext = QgsPoint(pPrev) #else: # pNext = QgsPoint(ftNext.geometry().asPoint()) #kappa = p.azimuth(pPrev) #kappa = p.azimuth(pNext) # d = math.sqrt(2*((f1/2 * ft['MASS']/1000)**2)) d1 = f1 / 2 * ft['massstab'] / 1000 d2 = f2 / 2 * ft['massstab'] / 1000 #QMessageBox.warning(None, u"Bild Nummern", "{0}".format(d)) calcCrs = QgsCoordinateReferenceSystem() calcCrs.createFromProj4(self.Proj4Utm(p)) ctF = QgsCoordinateTransform(self.cpLayer.crs(), calcCrs, QgsProject.instance()) cpMetric = QgsGeometry(ft.geometry()) cpMetric.transform(ctF) pPrevGeom.transform(ctF) pMetric = QgsPointXY(cpMetric.asPoint()) pPrevMetric = QgsPointXY(pPrevGeom.asPoint()) kappaMetric = pMetric.azimuth(pPrevMetric) pPrevGeom = QgsGeometry(ft.geometry()) left = pMetric.x() - d2 bottom = pMetric.y() - d1 right = pMetric.x() + d2 top = pMetric.y() + d1 #R = 6371 #D = (d/1000) #cpLat = math.radians(p.y()) #cpLon = math.radians(p.x()) #urLat = math.asin( math.sin(cpLat)*math.cos(D/R) + math.cos(cpLat)*math.sin(D/R)*math.cos(urBrng) ) #urLon = cpLon + math.atan2(math.sin(urBrng)*math.sin(D/R)*math.cos(cpLat), math.cos(D/R)-math.sin(cpLat)*math.sin(urLat)) #top = math.asin( math.sin(cpLat)*math.cos(D/R) + math.cos(cpLat)*math.sin(D/R) ) #bottom = math.asin( math.sin(cpLat)*math.cos(D/R) + math.cos(cpLat)*math.sin(D/R)*-1 ) #lat = math.asin( math.sin(cpLat)*math.cos(D/R) ) #right = cpLon + math.atan2(math.sin(D/R)*math.cos(cpLat), math.cos(D/R)-math.sin(cpLat)*math.sin(lat)) #left = cpLon + math.atan2(-1*math.sin(D/R)*math.cos(cpLat), math.cos(D/R)-math.sin(cpLat)*math.sin(lat)) #QMessageBox.warning(None, u"Bild Nummern", "{0}, {1}, {2}, {3}".format(math.degrees(top), math.degrees(bottom), math.degrees(left), math.degrees(right))) #rect = QgsRectangle(math.degrees(left), math.degrees(bottom), math.degrees(right), math.degrees(top)) #l = math.degrees(left) #b = math.degrees(bottom) #r = math.degrees(right) #t = math.degrees(top) p1 = QgsGeometry.fromPointXY(QgsPointXY(left, bottom)) p2 = QgsGeometry.fromPointXY(QgsPointXY(right, bottom)) p3 = QgsGeometry.fromPointXY(QgsPointXY(right, top)) p4 = QgsGeometry.fromPointXY(QgsPointXY(left, top)) #p1.rotate(kappa+90, p) #p2.rotate(kappa+90, p) #p3.rotate(kappa+90, p) #p4.rotate(kappa+90, p) pol = [[ p1.asPoint(), p2.asPoint(), p3.asPoint(), p4.asPoint() ]] geom = QgsGeometry.fromPolygonXY(pol) geom.rotate(kappaMetric, pMetric) #Transform to DestinationCRS ctB = QgsCoordinateTransform(calcCrs, self.fpLayer.crs(), QgsProject.instance()) geom.transform(ctB) feat = QgsFeature(self.fpLayer.fields()) feat.setGeometry(geom) feat.setAttribute('filmnummer', self.currentFilmNumber) feat.setAttribute('bildnummer', ft['bildnummer']) da = QgsDistanceArea() da.setEllipsoid(self.fpLayer.crs().ellipsoidAcronym()) feat.setAttribute('shape_length', da.measurePerimeter(geom)) feat.setAttribute('shape_area', da.measureArea(geom)) fpFeats.append(feat) # update Kappa in cpLayer kappasToUpdate[ft.id()] = { ft.fieldNameIndex('kappa'): kappaMetric } iterFeatures.close() iterNext.close() resCAVs = self.cpLayer.dataProvider().changeAttributeValues( kappasToUpdate) QgsMessageLog.logMessage( f"Kappa Update for {kappasToUpdate}, Success: {resCAVs}", tag="APIS", level=Qgis.Success if resCAVs else Qgis.Critical) (res, outFeats) = self.fpLayer.dataProvider().addFeatures(fpFeats) self.fpLayer.updateExtents() if self.canvas.isCachingEnabled(): self.fpLayer.triggerRepaint() else: self.canvas.refresh() else: #Caps QMessageBox.warning(None, "Layer Capabilities!", "Layer Capabilities!") else: #small feature count QMessageBox.warning( None, "Footprints", "Zum Berechnen der senkrecht Footprint müssen mindestens zwei Bilder kartiert werden!" )
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) expression = QgsExpression( self.parameterAsString(parameters, self.EXPRESSION, context)) if expression.hasParserError(): raise ProcessingException(expression.parserErrorString()) expressionContext = self.createExpressionContext(parameters, context) if not expression.prepare(expressionContext): raise ProcessingException( self.tr('Evaluation error: {0}').format( expression.evalErrorString())) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs()) da.setEllipsoid(context.project().ellipsoid()) total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(source.getFeatures()): if feedback.isCanceled(): break expressionContext.setFeature(f) value = expression.evaluate(expressionContext) if expression.hasEvalError(): feedback.pushInfo( self.tr('Evaluation error for feature ID {}: {}').format( f.id(), expression.evalErrorString())) continue fGeom = f.geometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo( "Skip feature {} as number of points for it is 0.") continue index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPoint(p) if geom.within(fGeom) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo( self.tr('Could not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) feedback.setProgress(0) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, model_feedback): # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the # overall progress through the model feedback = QgsProcessingMultiStepFeedback(25, model_feedback) results = {} outputs = {} nlcd_rast_output = self.parameterAsBool(parameters, "OutputNLCDLandCoverRaster", context) nlcd_vect_output = self.parameterAsBool(parameters, "OutputNLCDLandCoverVector", context) nlcd_rast_imp_output = self.parameterAsBool( parameters, "OutputNLCDImperviousRaster", context) soil_output = self.parameterAsBool(parameters, "OutputSoilLayer", context) curve_number_output = self.parameterAsBool(parameters, "OutputCurveNumberLayer", context) # Assiging Default CN_Lookup Table if parameters["cnlookup"] == None: csv_uri = ("file:///" + os.path.join(cmd_folder, "CN_Lookup.csv") + "?delimiter=,") csv = QgsVectorLayer(csv_uri, "CN_Lookup.csv", "delimitedtext") parameters["cnlookup"] = csv area_layer = self.parameterAsVectorLayer(parameters, "areaboundary", context) EPSGCode = area_layer.crs().authid() origEPSGCode = EPSGCode # preserve orignal EPSGCode to project back to it # feedback.pushInfo(str(EPSGCode)) if check_crs_acceptable(EPSGCode): pass else: # Reproject layer to EPSG:5070 alg_params = { "INPUT": parameters["areaboundary"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem("EPSG:5070"), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectLayer5070"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) area_layer = context.takeResultLayer( outputs["ReprojectLayer5070"]["OUTPUT"]) EPSGCode = area_layer.crs().authid() # Check if area of the extent is less than 100,000 Acres d = QgsDistanceArea() tr_cont = QgsCoordinateTransformContext() d.setSourceCrs(area_layer.crs(), tr_cont) # d.setEllipsoid(area_layer.crs().ellipsoidAcronym()) extent_area = d.measureArea(QgsGeometry().fromRect( area_layer.extent())) area_acres = d.convertAreaMeasurement(extent_area, QgsUnitTypes.AreaAcres) if area_acres > 500000: feedback.reportError( f"Area Boundary layer extent area should be less than 500,000 acres.\nArea Boundary layer extent area is {round(area_acres,4):,} acres.\n\nExecution Failed", True, ) return results elif area_acres > 100000: feedback.reportError( f"Your Area Boundary layer extent area is {round(area_acres,4):,} acres. The recommended extent area is 100,000 acres or less. If the Algorithm fails, rerun with a smaller input layer.\n", False, ) else: feedback.pushInfo( f"Area Boundary layer extent area is {round(area_acres,4):,} acres\n" ) # Get extent of the area boundary layer xmin = area_layer.extent().xMinimum() ymin = area_layer.extent().yMinimum() xmax = area_layer.extent().xMaximum() ymax = area_layer.extent().yMaximum() BBOX_width = (xmax - xmin) / 30 BBOX_height = (ymax - ymin) / 30 BBOX_width_int = round(BBOX_width) BBOX_height_int = round(BBOX_height) # NLCD Impervious Raster if nlcd_rast_imp_output == True: request_URL = f"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Impervious_L48/ows?version=1.3.0&service=WMS&layers=NLCD_2016_Impervious_L48&styles&crs={str(EPSGCode)}&format=image/geotiff&request=GetMap&width={str(BBOX_width_int)}&height={str(BBOX_height_int)}&BBOX={str(xmin)},{str(ymin)},{str(xmax)},{str(ymax)}&" # Download NLCD Impervious Raster try: ping_URL = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Impervious_L48/ows" r = requests.head(ping_URL, verify=False) r.raise_for_status() alg_params = { "URL": request_URL, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcdImp"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except (QgsProcessingException, requests.exceptions.HTTPError) as e: feedback.reportError( f"Error: {str(e)}\n\nError requesting land use data from 'www.mrlc.gov'. Most probably because either their server is down or there is a certification issue.\nThis should be temporary. Try again later.\n", True, ) return results feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # reproject to original crs # Warp (reproject) if EPSGCode != origEPSGCode: alg_params = { "DATA_TYPE": 0, "EXTRA": "", "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "MULTITHREADING": False, "NODATA": None, "OPTIONS": "", "RESAMPLING": 0, "SOURCE_CRS": None, "TARGET_CRS": QgsCoordinateReferenceSystem(str(origEPSGCode)), "TARGET_EXTENT": None, "TARGET_EXTENT_CRS": None, "TARGET_RESOLUTION": None, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcdImp"] = processing.run( "gdal:warpreproject", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # Set layer style alg_params = { "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Raster_Imp.qml"), } try: # for QGIS Version later than 3.12 outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForRasterLayer"] = processing.run( "qgis:setstyleforrasterlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(2) if feedback.isCanceled(): return {} # NLCD Land Cover Data if (curve_number_output == True or nlcd_vect_output == True or nlcd_rast_output == True): request_URL = f"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Land_Cover_L48/ows?version=1.3.0&service=WMS&layers=NLCD_2016_Land_Cover_L48&styles&crs={str(EPSGCode)}&format=image/geotiff&request=GetMap&width={str(BBOX_width_int)}&height={str(BBOX_height_int)}&BBOX={str(xmin)},{str(ymin)},{str(xmax)},{str(ymax)}&" # Download NLCD try: ping_URL = "https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Land_Cover_L48/ows" r = requests.head(ping_URL, verify=False) r.raise_for_status() alg_params = { "URL": request_URL, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcd"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except (QgsProcessingException, requests.exceptions.HTTPError) as e: feedback.reportError( f"Error: {str(e)}\n\nError requesting land use data from 'www.mrlc.gov'. Most probably because either their server is down or there is a certification issue.\nThis should be temporary. Try again later.\n", True, ) return results feedback.setCurrentStep(3) if feedback.isCanceled(): return {} # reproject to original crs # Warp (reproject) if EPSGCode != origEPSGCode: alg_params = { "DATA_TYPE": 0, "EXTRA": "", "INPUT": outputs["DownloadNlcd"]["OUTPUT"], "MULTITHREADING": False, "NODATA": None, "OPTIONS": "", "RESAMPLING": 0, "SOURCE_CRS": None, "TARGET_CRS": QgsCoordinateReferenceSystem(str(origEPSGCode)), "TARGET_EXTENT": None, "TARGET_EXTENT_CRS": None, "TARGET_RESOLUTION": None, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadNlcd"] = processing.run( "gdal:warpreproject", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # Reclassify by table alg_params = { "DATA_TYPE": 5, "INPUT_RASTER": outputs["DownloadNlcd"]["OUTPUT"], "NODATA_FOR_MISSING": False, "NO_DATA": -9999, "RANGE_BOUNDARIES": 0, "RASTER_BAND": 1, "TABLE": QgsExpression( "'0,1,11,1,2,12,2,3,21,3,4,22,4,5,23,5,6,24,6,7,31,7,8,32,8,9,41,9,10,42,10,11,43,11,12,51,12,13,52,13,14,71,14,15,72,15,16,73,16,17,74,17,18,81,18,19,82,19,20,90,20,21,95'" ).evaluate(), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReclassifyByTable"] = processing.run( "native:reclassifybytable", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(4) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Raster.qml"), } try: # for QGIS Version later than 3.12 outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForRasterLayer"] = processing.run( "qgis:setstyleforrasterlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(5) if feedback.isCanceled(): return {} if curve_number_output == True or nlcd_vect_output == True: # Polygonize (raster to vector) alg_params = { "BAND": 1, "EIGHT_CONNECTEDNESS": False, "EXTRA": "", "FIELD": "VALUE", "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["PolygonizeRasterToVector"] = processing.run( "gdal:polygonize", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(6) if feedback.isCanceled(): return {} # Fix geometries alg_params = { "INPUT": outputs["PolygonizeRasterToVector"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["FixGeometries"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(7) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["FixGeometries"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "NLCD_Vector.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(8) if feedback.isCanceled(): return {} # Soil Layer if soil_output == True or curve_number_output == True: # Reproject layer alg_params = { "INPUT": parameters["areaboundary"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem("EPSG:4326"), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectLayer4326"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(9) if feedback.isCanceled(): return {} # Get Area Boundary layer extent in EPSG:4326 area_layer_reprojected = context.takeResultLayer( outputs["ReprojectLayer4326"]["OUTPUT"]) # Download Soil try: # request using post rest # create vector layer structure to store data feedback.pushInfo("Creating POST request...") uri = "Polygon?crs=epsg:4326" soil_layer = QgsVectorLayer(uri, "soil layer", "memory") provider = soil_layer.dataProvider() attributes = [] attr_dict = [ { "name": "musym", "type": "str" }, { "name": "muname", "type": "str" }, { "name": "mustatus", "type": "str" }, { "name": "slopegraddcp", "type": "str" }, { "name": "slopegradwta", "type": "str" }, { "name": "brockdepmin", "type": "str" }, { "name": "wtdepannmin", "type": "str" }, { "name": "wtdepaprjunmin", "type": "str" }, { "name": "flodfreqdcd", "type": "str" }, { "name": "flodfreqmax", "type": "str" }, { "name": "pondfreqprs", "type": "str" }, { "name": "aws025wta", "type": "str" }, { "name": "aws050wta", "type": "str" }, { "name": "aws0100wta", "type": "str" }, { "name": "aws0150wta", "type": "str" }, { "name": "drclassdcd", "type": "str" }, { "name": "drclasswettest", "type": "str" }, { "name": "hydgrpdcd", "type": "str" }, { "name": "iccdcd", "type": "str" }, { "name": "iccdcdpct", "type": "str" }, { "name": "niccdcd", "type": "str" }, { "name": "niccdcdpct", "type": "str" }, { "name": "engdwobdcd", "type": "str" }, { "name": "engdwbdcd", "type": "str" }, { "name": "engdwbll", "type": "str" }, { "name": "engdwbml", "type": "str" }, { "name": "engstafdcd", "type": "str" }, { "name": "engstafll", "type": "str" }, { "name": "engstafml", "type": "str" }, { "name": "engsldcd", "type": "str" }, { "name": "engsldcp", "type": "str" }, { "name": "englrsdcd", "type": "str" }, { "name": "engcmssdcd", "type": "str" }, { "name": "engcmssmp", "type": "str" }, { "name": "urbrecptdcd", "type": "str" }, { "name": "urbrecptwta", "type": "str" }, { "name": "forpehrtdcp", "type": "str" }, { "name": "hydclprs", "type": "str" }, { "name": "awmmfpwwta", "type": "str" }, { "name": "mukey", "type": "str" }, { "name": "mupolygonkey", "type": "str" }, { "name": "areasymbol", "type": "str" }, { "name": "nationalmusym", "type": "str" }, ] # initialize fields for field in attr_dict: attributes.append(QgsField(field["name"], QVariant.String)) provider.addAttributes(attributes) soil_layer.updateFields() # get area layer extent polygon as WKT in 4326 aoi_reproj_wkt = area_layer_reprojected.extent().asWktPolygon() # send post request body = { "format": "JSON", "query": f"select Ma.*, M.mupolygonkey, M.areasymbol, M.nationalmusym, M.mupolygongeo from mupolygon M, muaggatt Ma where M.mupolygonkey in (select * from SDA_Get_Mupolygonkey_from_intersection_with_WktWgs84('{aoi_reproj_wkt.lower()}')) and M.mukey=Ma.mukey", } url = "https://sdmdataaccess.sc.egov.usda.gov/TABULAR/post.rest" soil_response = requests.post(url, json=body).json() feedback.setCurrentStep(10) if feedback.isCanceled(): return {} for row in soil_response["Table"]: # None attribute for empty data row = [None if not attr else attr for attr in row] feat = QgsFeature(soil_layer.fields()) # populate data for index, col in enumerate(row): if index != len(attr_dict): feat.setAttribute(attr_dict[index]["name"], col) else: feat.setGeometry(QgsGeometry.fromWkt(col)) provider.addFeatures([feat]) feedback.setCurrentStep(11) if feedback.isCanceled(): return {} except: # try wfs request feedback.reportError( "Error getting soil data through post request. Your input layer maybe too large. Trying WFS download now.\nIf the Algorithm get stuck during download. Terminate the Algorithm and rerun with a smaller input layer.", False, ) xmin_reprojected = area_layer_reprojected.extent().xMinimum() ymin_reprojected = area_layer_reprojected.extent().yMinimum() xmax_reprojected = area_layer_reprojected.extent().xMaximum() ymax_reprojected = area_layer_reprojected.extent().yMaximum() request_URL_soil = f"https://sdmdataaccess.sc.egov.usda.gov/Spatial/SDMWGS84GEOGRAPHIC.wfs?SERVICE=WFS&VERSION=1.1.0&REQUEST=GetFeature&TYPENAME=mapunitpolyextended&SRSNAME=EPSG:4326&BBOX={str(xmin_reprojected)},{str(ymin_reprojected)},{str(xmax_reprojected)},{str(ymax_reprojected)}" alg_params = { "URL": request_URL_soil, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DownloadSoil"] = processing.run( "native:filedownloader", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(12) if feedback.isCanceled(): return {} # Swap X and Y coordinates alg_params = { "INPUT": outputs["DownloadSoil"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["SwapXAndYCoordinates"] = processing.run( "native:swapxy", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(13) if feedback.isCanceled(): return {} soil_layer = outputs["SwapXAndYCoordinates"]["OUTPUT"] # Fix soil layer geometries alg_params = { "INPUT": soil_layer, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT } outputs["FixGeometries2"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(14) if feedback.isCanceled(): return {} # Clip Soil Layer alg_params = { "INPUT": outputs["FixGeometries2"]["OUTPUT"], "OVERLAY": parameters["areaboundary"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Clip"] = processing.run( "native:clip", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(15) if feedback.isCanceled(): return {} # Reproject Soil alg_params = { "INPUT": outputs["Clip"]["OUTPUT"], "OPERATION": "", "TARGET_CRS": QgsCoordinateReferenceSystem(origEPSGCode), "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["ReprojectSoil"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(16) if feedback.isCanceled(): return {} # Fix soil layer geometries second time alg_params = { "INPUT": outputs["ReprojectSoil"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["FixGeometries3"] = processing.run( "native:fixgeometries", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(17) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "Soil_Layer.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(18) if feedback.isCanceled(): return {} # Curve Number Calculations if curve_number_output == True: feedback.pushInfo( "Generating Curve Number Layer. This may take a while. Do not cancel." ) # Intersection alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "INPUT_FIELDS": ["MUSYM", "HYDGRPDCD", "MUNAME"], "OVERLAY": outputs["FixGeometries"]["OUTPUT"], "OVERLAY_FIELDS": ["VALUE"], "OVERLAY_FIELDS_PREFIX": "", "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["Intersection"] = processing.run( "native:intersection", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(19) if feedback.isCanceled(): return {} # Create GDCodeTemp alg_params = { "FIELD_LENGTH": 5, "FIELD_NAME": "GDCodeTemp", "FIELD_PRECISION": 3, "FIELD_TYPE": 2, "FORMULA": 'IF ("HYDGRPDCD" IS NOT NULL, "Value" || "HYDGRPDCD", IF (("MUSYM" = \'W\' OR lower("MUSYM") = \'water\' OR lower("MUNAME") = \'water\' OR "MUNAME" = \'W\'), 11, "VALUE"))', "INPUT": outputs["Intersection"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGdcodetemp"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(20) if feedback.isCanceled(): return {} # Create GDCode alg_params = { "FIELD_LENGTH": 5, "FIELD_NAME": "GDCode", "FIELD_PRECISION": 3, "FIELD_TYPE": 2, "FORMULA": "if( var('drainedsoilsleaveuncheckedifnotsure') = True,replace(\"GDCodeTemp\", '/D', ''),replace(\"GDCodeTemp\", map('A/', '', 'B/', '', 'C/', '')))", "INPUT": outputs["CreateGdcodetemp"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGdcode"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(21) if feedback.isCanceled(): return {} # Create NLCD_LU alg_params = { "FIELD_LENGTH": 2, "FIELD_NAME": "NLCD_LU", "FIELD_PRECISION": 3, "FIELD_TYPE": 1, "FORMULA": '"Value"', "INPUT": outputs["CreateGdcode"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateNlcd_lu"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(22) if feedback.isCanceled(): return {} # Join with CNLookup alg_params = { "DISCARD_NONMATCHING": False, "FIELD": "GDCode", "FIELDS_TO_COPY": ["CN_Join"], "FIELD_2": "GDCode", "INPUT": outputs["CreateNlcd_lu"]["OUTPUT"], "INPUT_2": parameters["cnlookup"], "METHOD": 1, "PREFIX": "", "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["JoinWithCnlookup"] = processing.run( "native:joinattributestable", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(23) if feedback.isCanceled(): return {} # Create Integer CN alg_params = { "FIELD_LENGTH": 3, "FIELD_NAME": "CN", "FIELD_PRECISION": 0, "FIELD_TYPE": 1, "FORMULA": "CN_Join * 1", "INPUT": outputs["JoinWithCnlookup"]["OUTPUT"], "NEW_FIELD": True, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateIntegerCn"] = processing.run( "qgis:fieldcalculator", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(24) if feedback.isCanceled(): return {} # Drop field(s) alg_params = { "COLUMN": ["VALUE", "GDCodeTemp", "CN_Join"], "INPUT": outputs["CreateIntegerCn"]["OUTPUT"], "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DropFields"] = processing.run( "qgis:deletecolumn", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(25) if feedback.isCanceled(): return {} # Set layer style alg_params = { "INPUT": outputs["DropFields"]["OUTPUT"], "STYLE": os.path.join(cmd_folder, "CN_Grid.qml"), } try: # for QGIS Version 3.12 and later outputs["SetLayerStyle"] = processing.run( "native:setlayerstyle", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) except: # for QGIS Version older than 3.12 outputs["SetStyleForVectorLayer"] = processing.run( "qgis:setstyleforvectorlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_rast_output: # Load NLCD Raster into project alg_params = { "INPUT": outputs["ReclassifyByTable"]["OUTPUT"], "NAME": "NLCD Land Cover Raster", } outputs["LoadLayerIntoProject1"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_vect_output: # Load NLCD Vector Layer into project alg_params = { "INPUT": outputs["FixGeometries"]["OUTPUT"], "NAME": "NLCD Land Cover Vector", } outputs["LoadLayerIntoProject2"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if nlcd_rast_imp_output: # Load NLCD Impervious Raster into project alg_params = { "INPUT": outputs["DownloadNlcdImp"]["OUTPUT"], "NAME": "NLCD Impervious Raster", } outputs["LoadLayerIntoProject3"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if soil_output: # Load Soil Layer into project alg_params = { "INPUT": outputs["FixGeometries3"]["OUTPUT"], "NAME": "SSURGO Soil Layer", } outputs["LoadLayerIntoProject4"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) if curve_number_output: # Load Curve Number Layer into project alg_params = { "INPUT": outputs["DropFields"]["OUTPUT"], "NAME": "Curve Number Layer", } outputs["LoadLayerIntoProject5"] = processing.run( "native:loadlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) # log usage with open(os.path.join(cmd_folder, "usage_counter.log"), "r+") as f: counter = int(f.readline()) f.seek(0) f.write(str(counter + 1)) # check if counter is milestone if (counter + 1) % 25 == 0: appeal_file = NamedTemporaryFile("w", suffix=".html", delete=False) self.createHTML(appeal_file.name, counter + 1) results["Message"] = appeal_file.name return results
def testAreaMeasureAndUnits(self): """Test a variety of area measurements in different CRS and ellipsoid modes, to check that the calculated areas and units are always consistent """ da = QgsDistanceArea() da.setSourceCrs(3452) da.setEllipsoidalMode(False) da.setEllipsoid("NONE") daCRS = QgsCoordinateReferenceSystem() daCRS.createFromSrsId(da.sourceCrs()) polygon = QgsGeometry.fromPolygon([[ QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(0, 2), QgsPoint(0, 0), ]]) # We check both the measured area AND the units, in case the logic regarding # ellipsoids and units changes in future area = da.measureArea(polygon) units = da.areaUnits() print("measured {} in {}".format(area, QgsUnitTypes.toString(units))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.SquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.SquareMeters)) da.setEllipsoid("WGS84") area = da.measureArea(polygon) units = da.areaUnits() print("measured {} in {}".format(area, QgsUnitTypes.toString(units))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.SquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.SquareMeters)) da.setEllipsoidalMode(True) area = da.measureArea(polygon) units = da.areaUnits() print("measured {} in {}".format(area, QgsUnitTypes.toString(units))) # should always be in Meters Squared self.assertAlmostEqual(area, 37416879192.9, delta=0.1) self.assertEqual(units, QgsUnitTypes.SquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareMiles) self.assertAlmostEqual(area, 14446.7378, delta=0.001) # now try with a source CRS which is in feet polygon = QgsGeometry.fromPolygon([[ QgsPoint(1850000, 4423000), QgsPoint(1851000, 4423000), QgsPoint(1851000, 4424000), QgsPoint(1852000, 4424000), QgsPoint(1852000, 4425000), QgsPoint(1851000, 4425000), QgsPoint(1850000, 4423000) ]]) da.setSourceCrs(27469) da.setEllipsoidalMode(False) # measurement should be in square feet area = da.measureArea(polygon) units = da.areaUnits() print("measured {} in {}".format(area, QgsUnitTypes.toString(units))) self.assertAlmostEqual(area, 2000000, delta=0.001) self.assertEqual(units, QgsUnitTypes.SquareFeet) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareYards) self.assertAlmostEqual(area, 222222.2222, delta=0.001) da.setEllipsoidalMode(True) # now should be in Square Meters again area = da.measureArea(polygon) units = da.areaUnits() print("measured {} in {}".format(area, QgsUnitTypes.toString(units))) self.assertAlmostEqual(area, 184149.37, delta=1.0) self.assertEqual(units, QgsUnitTypes.SquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.SquareYards) self.assertAlmostEqual(area, 220240.8172549, delta=1.0)
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) if self.MIN_DISTANCE in parameters and parameters[self.MIN_DISTANCE] is not None: minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) else: minDistance = None expressionContext = self.createExpressionContext(parameters, context, source) dynamic_value = QgsProcessingParameters.isDynamic(parameters, "VALUE") value_property = None if self.EXPRESSION in parameters and parameters[self.EXPRESSION] is not None: expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context)) value = None if expression.hasParserError(): raise QgsProcessingException(expression.parserErrorString()) expression.prepare(expressionContext) else: expression = None if dynamic_value: value_property = parameters["VALUE"] value = self.parameterAsDouble(parameters, self.VALUE, context) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.ellipsoid()) total = 100.0 / source.featureCount() if source.featureCount() else 0 current_progress = 0 pointId = 0 for current, f in enumerate(source.getFeatures()): if feedback.isCanceled(): break if not f.hasGeometry(): continue current_progress = total * current feedback.setProgress(current_progress) this_value = value if value_property is not None or expression is not None: expressionContext.setFeature(f) if value_property: this_value, _ = value_property.valueAsDouble(expressionContext, value) else: this_value = expression.evaluate(expressionContext) if expression.hasEvalError(): feedback.pushInfo( self.tr('Evaluation error for feature ID {}: {}').format(f.id(), expression.evalErrorString())) continue fGeom = f.geometry() engine = QgsGeometry.createGeometryEngine(fGeom.constGet()) engine.prepareGeometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(this_value) else: pointCount = int(round(this_value * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo("Skip feature {} as number of points for it is 0.".format(f.id())) continue index = None if minDistance: index = QgsSpatialIndex() points = {} nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 feature_total = total / pointCount if pointCount else 1 random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if engine.contains(geom.constGet()) and \ (not minDistance or vector.checkMinDistance(p, index, minDistance, points)): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', pointId) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) if minDistance: index.addFeature(f) points[nPoints] = p nPoints += 1 pointId += 1 feedback.setProgress(current_progress + int(nPoints * feature_total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) feedback.setProgress(100) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) value = float(self.getParameterValue(self.VALUE)) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) strategy = self.getParameterValue(self.STRATEGY) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) da = QgsDistanceArea() features = QgsProcessingUtils.getFeatures(layer, context) for current, f in enumerate(features): fGeom = f.geometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo("Skip feature {} as number of points for it is 0.") continue index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPointXY(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(fGeom) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: QgsMessageLog.logMessage(self.tr('Can not generate requested number of random ' 'points. Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) feedback.setProgress(0) del writer
def testAreaMeasureAndUnits(self): """Test a variety of area measurements in different CRS and ellipsoid modes, to check that the calculated areas and units are always consistent """ da = QgsDistanceArea() da.setSourceCrs(QgsCoordinateReferenceSystem.fromSrsId(3452), QgsProject.instance().transformContext()) da.setEllipsoid("NONE") polygon = QgsGeometry.fromPolygonXY( [[ QgsPointXY(0, 0), QgsPointXY(1, 0), QgsPointXY(1, 1), QgsPointXY(2, 1), QgsPointXY(2, 2), QgsPointXY(0, 2), QgsPointXY(0, 0), ]] ) # We check both the measured area AND the units, in case the logic regarding # ellipsoids and units changes in future area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.AreaSquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.AreaSquareMeters)) da.setEllipsoid("WGS84") area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) # should always be in Meters Squared self.assertAlmostEqual(area, 36918093794.121284, delta=0.1) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareMiles) self.assertAlmostEqual(area, 14254.155703182701, delta=0.001) # now try with a source CRS which is in feet polygon = QgsGeometry.fromPolygonXY( [[ QgsPointXY(1850000, 4423000), QgsPointXY(1851000, 4423000), QgsPointXY(1851000, 4424000), QgsPointXY(1852000, 4424000), QgsPointXY(1852000, 4425000), QgsPointXY(1851000, 4425000), QgsPointXY(1850000, 4423000) ]] ) da.setSourceCrs(QgsCoordinateReferenceSystem.fromSrsId(27469), QgsProject.instance().transformContext()) da.setEllipsoid("NONE") # measurement should be in square feet area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 2000000, delta=0.001) self.assertEqual(units, QgsUnitTypes.AreaSquareFeet) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 222222.2222, delta=0.001) da.setEllipsoid("WGS84") # now should be in Square Meters again area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 185818.59096575077, delta=1.0) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 222237.18521272976, delta=1.0)
class SizeCalculator(): """Special object to handle size calculation with an output unit.""" def __init__( self, coordinate_reference_system, geometry_type, exposure_key): """Constructor for the size calculator. :param coordinate_reference_system: The Coordinate Reference System of the layer. :type coordinate_reference_system: QgsCoordinateReferenceSystem :param exposure_key: The geometry type of the layer. :type exposure_key: qgis.core.QgsWkbTypes.GeometryType """ self.calculator = QgsDistanceArea() self.calculator.setSourceCrs( coordinate_reference_system, QgsProject.instance().transformContext() ) self.calculator.setEllipsoid('WGS84') if geometry_type == QgsWkbTypes.LineGeometry: self.default_unit = unit_metres LOGGER.info('The size calculator is set to use {unit}'.format( unit=distance_unit[self.calculator.lengthUnits()])) else: self.default_unit = unit_square_metres LOGGER.info('The size calculator is set to use {unit}'.format( unit=distance_unit[self.calculator.areaUnits()])) self.geometry_type = geometry_type self.output_unit = None if exposure_key: exposure_definition = definition(exposure_key) self.output_unit = exposure_definition['size_unit'] def measure_distance(self, point_a, point_b): """Measure the distance between two points. This is added here since QgsDistanceArea object is already called here. :param point_a: First Point. :type point_a: QgsPoint :param point_b: Second Point. :type point_b: QgsPoint :return: The distance between input points. :rtype: float """ return self.calculator.measureLine(point_a, point_b) def measure(self, geometry): """Measure the length or the area of a geometry. :param geometry: The geometry. :type geometry: QgsGeometry :return: The geometric size in the expected exposure unit. :rtype: float """ message = 'Size with NaN value : geometry valid={valid}, WKT={wkt}' feature_size = 0 if geometry.isMultipart(): # Be careful, the size calculator is not working well on a # multipart. # So we compute the size part per part. See ticket #3812 for single in geometry.asGeometryCollection(): if self.geometry_type == QgsWkbTypes.LineGeometry: geometry_size = self.calculator.measureLength(single) else: geometry_size = self.calculator.measureArea(single) if not isnan(geometry_size): feature_size += geometry_size else: LOGGER.debug(message.format( valid=single.isGeosValid(), wkt=single.asWkt())) else: if self.geometry_type == QgsWkbTypes.LineGeometry: geometry_size = self.calculator.measureLength(geometry) else: geometry_size = self.calculator.measureArea(geometry) if not isnan(geometry_size): feature_size = geometry_size else: LOGGER.debug(message.format( valid=geometry.isGeosValid(), wkt=geometry.asWkt())) feature_size = round(feature_size) if self.output_unit: if self.output_unit != self.default_unit: feature_size = convert_unit( feature_size, self.default_unit, self.output_unit) return feature_size
def processAlgorithm(self, parameters, context, feedback): # Output table: # source_id | class | area | percentage # Base (source) layer source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) source_crs = source.sourceCrs() source_count = source.featureCount() source_features = source.getFeatures() # Source id field id_field = self.parameterAsFields(parameters, self.SOURCE_ID_FIELD, context)[0] # Overlay layer overlay = self.parameterAsVectorLayer(parameters, self.OVERLAY, context) #overlay = self.parameterAsSource( # parameters, # self.OVERLAY, # context #) if overlay is None: raise QgsProcessingException( "Overlay layer could not be loaded! Is it a valid layer?") # Class field class_field = self.parameterAsFields(parameters, self.CLASS_FIELD, context)[0] # Output layer destId = '' fields = QgsFields() fields.append(QgsField("id", source.fields().field(id_field).type())) fields.append( QgsField("class", overlay.fields().field(class_field).type())) fields.append(QgsField("area", QVariant.Double)) fields.append(QgsField("percentage", QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.NoGeometry) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) da = QgsDistanceArea() da.setSourceCrs(source_crs, context.transformContext()) da.setEllipsoid(context.ellipsoid()) # loop through input step = 100.0 / source_count if source_count > 0 else 0 i = 0 feedback.setProgress(1) for feature in source_features: if feedback.isCanceled(): break id_value = feature[id_field] feedback.pushInfo( '\n{}) Preparing input feature with "{}" {}...'.format( i + 1, id_field, id_value)) # 1) Get the current feature in its own layer feedback.pushInfo('\nExtracting base polygon...') feature_layer = processing.run( "native:extractbyattribute", { 'INPUT': parameters[self.INPUT], 'FIELD': id_field, 'OPERATOR': 0, # = 'VALUE': id_value, 'OUTPUT': 'TEMPORARY_OUTPUT' }, context=context)['OUTPUT'] if feedback.isCanceled(): break # 2) Extract all overlay polygons by location for the current feature feedback.pushInfo('Getting class features by location...') overlay_subset = processing.run( "native:extractbylocation", { 'INPUT': overlay, 'PREDICATE': [0], # Intersects 'INTERSECT': feature_layer, 'OUTPUT': 'TEMPORARY_OUTPUT' }, context=context)['OUTPUT'] if feedback.isCanceled(): break out_features = list() idx_class_field = overlay_subset.fields().indexOf(class_field) feedback.pushInfo( '\nAnalysing overlaps in input feature with id {}...'.format( id_value)) if feature.hasGeometry() and not qgsDoubleNear( feature.geometry().area(), 0.0): input_geom = feature.geometry() input_area = da.measureArea(input_geom) # Prepare for lots of intersection tests (for speed) geom_engine = QgsGeometry.createGeometryEngine( input_geom.constGet()) geom_engine.prepareGeometry() if feedback.isCanceled(): break # Iterate classes unique_values = overlay_subset.uniqueValues(idx_class_field) sub_step = step / len(unique_values) if unique_values else 0 j = 0 for class_name in unique_values: feedback.pushInfo( "...Analysing class '{}'...".format(class_name)) exp = QgsExpression("{} = '{}'".format( class_field, class_name)) request = QgsFeatureRequest(exp).setSubsetOfAttributes( list()).setDestinationCrs( source_crs, context.transformContext( )).setInvalidGeometryCheck( context.invalidGeometryCheck()) class_overlay_area = 0 # Iterate features by class for class_feature in overlay_subset.getFeatures(request): if feedback.isCanceled(): break # Since we know in 2 we get all overlay features that intersect, # we skip the otherwise must-have intersects check and proceed # to get the intersection right away if class_feature.hasGeometry() and not qgsDoubleNear( class_feature.geometry().area(), 0.0): overlay_intersection = geom_engine.intersection( class_feature.geometry().constGet()) class_overlay_area += da.measureArea( QgsGeometry(overlay_intersection)) if feedback.isCanceled(): break out_feature = QgsFeature() out_attrs = [ id_value, class_name, class_overlay_area, 100 * class_overlay_area / input_area ] out_feature.setAttributes(out_attrs) out_features.append(out_feature) j += 1 feedback.setProgress(i * step + j * sub_step) else: # Input feature has no geometry for class_feature in class_layer_features: out_feature = QgsFeature() out_feature.setAttributes( [id_value, class_feature[class_field], None, None]) out_features.append(out_feature) if not sink.addFeatures(out_features, QgsFeatureSink.FastInsert): raise QgsProcessingException(sink.lastError()) i += 1 feedback.setProgress(i * step) return {self.OUTPUT: dest_id}
class ExportGeometryInfo(QgisAlgorithm): INPUT = 'INPUT' METHOD = 'CALC_METHOD' OUTPUT = 'OUTPUT' def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'export_geometry.png')) def tags(self): return self.tr('export,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons').split(',') def group(self): return self.tr('Vector table tools') def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.calc_methods = [self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal')] def initAlgorithm(self, config=None): self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) self.addParameter(QgsProcessingParameterEnum(self.METHOD, self.tr('Calculate using'), options=self.calc_methods, defaultValue=0)) self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info'))) def name(self): return 'exportaddgeometrycolumns' def displayName(self): return self.tr('Export/Add geometry columns') def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: areaName = vector.createUniqueFieldName('area', fields) fields.append(QgsField(areaName, QVariant.Double)) perimeterName = vector.createUniqueFieldName('perimeter', fields) fields.append(QgsField(perimeterName, QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: lengthName = vector.createUniqueFieldName('length', fields) fields.append(QgsField(lengthName, QVariant.Double)) else: xName = vector.createUniqueFieldName('xcoord', fields) fields.append(QgsField(xName, QVariant.Double)) yName = vector.createUniqueFieldName('ycoord', fields) fields.append(QgsField(yName, QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True zName = vector.createUniqueFieldName('zcoord', fields) fields.append(QgsField(zName, QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True zName = vector.createUniqueFieldName('mvalue', fields) fields.append(QgsField(zName, QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id} def point_attributes(self, geometry): pt = None if not geometry.isMultipart(): pt = geometry.geometry() else: if geometry.numGeometries() > 0: pt = geometry.geometryN(0) attrs = [] if pt: attrs.append(pt.x()) attrs.append(pt.y()) # add point z/m if self.export_z: attrs.append(pt.z()) if self.export_m: attrs.append(pt.m()) return attrs def line_attributes(self, geometry): return [self.distance_area.measureLength(geometry)] def polygon_attributes(self, geometry): area = self.distance_area.measureArea(geometry) perimeter = self.distance_area.measurePerimeter(geometry) return [area, perimeter]
class SizeCalculator(): """Special object to handle size calculation with an output unit.""" def __init__(self, coordinate_reference_system, geometry_type, exposure_key): """Constructor for the size calculator. :param coordinate_reference_system: The Coordinate Reference System of the layer. :type coordinate_reference_system: QgsCoordinateReferenceSystem :param exposure_key: The geometry type of the layer. :type exposure_key: qgis.core.QgsWkbTypes.GeometryType """ self.calculator = QgsDistanceArea() self.calculator.setSourceCrs(coordinate_reference_system, QgsProject.instance().transformContext()) self.calculator.setEllipsoid('WGS84') if geometry_type == QgsWkbTypes.LineGeometry: self.default_unit = unit_metres LOGGER.info('The size calculator is set to use {unit}'.format( unit=distance_unit[self.calculator.lengthUnits()])) else: self.default_unit = unit_square_metres LOGGER.info('The size calculator is set to use {unit}'.format( unit=distance_unit[self.calculator.areaUnits()])) self.geometry_type = geometry_type self.output_unit = None if exposure_key: exposure_definition = definition(exposure_key) self.output_unit = exposure_definition['size_unit'] def measure_distance(self, point_a, point_b): """Measure the distance between two points. This is added here since QgsDistanceArea object is already called here. :param point_a: First Point. :type point_a: QgsPoint :param point_b: Second Point. :type point_b: QgsPoint :return: The distance between input points. :rtype: float """ return self.calculator.measureLine(point_a, point_b) def measure(self, geometry): """Measure the length or the area of a geometry. :param geometry: The geometry. :type geometry: QgsGeometry :return: The geometric size in the expected exposure unit. :rtype: float """ message = 'Size with NaN value : geometry valid={valid}, WKT={wkt}' feature_size = 0 if geometry.isMultipart(): # Be careful, the size calculator is not working well on a # multipart. # So we compute the size part per part. See ticket #3812 for single in geometry.asGeometryCollection(): if self.geometry_type == QgsWkbTypes.LineGeometry: geometry_size = self.calculator.measureLength(single) else: geometry_size = self.calculator.measureArea(single) if not isnan(geometry_size): feature_size += geometry_size else: LOGGER.debug( message.format(valid=single.isGeosValid(), wkt=single.asWkt())) else: if self.geometry_type == QgsWkbTypes.LineGeometry: geometry_size = self.calculator.measureLength(geometry) else: geometry_size = self.calculator.measureArea(geometry) if not isnan(geometry_size): feature_size = geometry_size else: LOGGER.debug( message.format(valid=geometry.isGeosValid(), wkt=geometry.asWkt())) feature_size = round(feature_size) if self.output_unit: if self.output_unit != self.default_unit: feature_size = convert_unit(feature_size, self.default_unit, self.output_unit) return feature_size
def testAreaMeasureAndUnits(self): """Test a variety of area measurements in different CRS and ellipsoid modes, to check that the calculated areas and units are always consistent """ da = QgsDistanceArea() da.setSourceCrs(3452) da.setEllipsoidalMode(False) da.setEllipsoid("NONE") daCRS = QgsCoordinateReferenceSystem() daCRS = da.sourceCrs() polygon = QgsGeometry.fromPolygon( [[ QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 2), QgsPoint(0, 2), QgsPoint(0, 0), ]] ) # We check both the measured area AND the units, in case the logic regarding # ellipsoids and units changes in future area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.AreaSquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.AreaSquareMeters)) da.setEllipsoid("WGS84") area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) assert ((abs(area - 3.0) < 0.00000001 and units == QgsUnitTypes.AreaSquareDegrees) or (abs(area - 37176087091.5) < 0.1 and units == QgsUnitTypes.AreaSquareMeters)) da.setEllipsoidalMode(True) area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) # should always be in Meters Squared self.assertAlmostEqual(area, 37416879192.9, delta=0.1) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareMiles) self.assertAlmostEqual(area, 14446.7378, delta=0.001) # now try with a source CRS which is in feet polygon = QgsGeometry.fromPolygon( [[ QgsPoint(1850000, 4423000), QgsPoint(1851000, 4423000), QgsPoint(1851000, 4424000), QgsPoint(1852000, 4424000), QgsPoint(1852000, 4425000), QgsPoint(1851000, 4425000), QgsPoint(1850000, 4423000) ]] ) da.setSourceCrs(27469) da.setEllipsoidalMode(False) # measurement should be in square feet area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 2000000, delta=0.001) self.assertEqual(units, QgsUnitTypes.AreaSquareFeet) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 222222.2222, delta=0.001) da.setEllipsoidalMode(True) # now should be in Square Meters again area = da.measureArea(polygon) units = da.areaUnits() print(("measured {} in {}".format(area, QgsUnitTypes.toString(units)))) self.assertAlmostEqual(area, 184149.37, delta=1.0) self.assertEqual(units, QgsUnitTypes.AreaSquareMeters) # test converting the resultant area area = da.convertAreaMeasurement(area, QgsUnitTypes.AreaSquareYards) self.assertAlmostEqual(area, 220240.8172549, delta=1.0)
def processAlgorithm(self, parameters, context, feedback): layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.VECTOR), context) fieldName = self.getParameterValue(self.FIELD) minDistance = float(self.getParameterValue(self.MIN_DISTANCE)) strategy = self.getParameterValue(self.STRATEGY) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Point, layer.crs(), context) da = QgsDistanceArea() features = QgsProcessingUtils.getFeatures(layer, context) for current, f in enumerate(features): fGeom = f.geometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(f[fieldName]) else: pointCount = int(round(f[fieldName] * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo("Skip feature {} as number of points for it is 0.") continue index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount random.seed() while nIterations < maxIterations and nPoints < pointCount: rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() pnt = QgsPointXY(rx, ry) geom = QgsGeometry.fromPoint(pnt) if geom.within(fGeom) and \ vector.checkMinDistance(pnt, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) writer.addFeature(f) index.insertFeature(f) points[nPoints] = pnt nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: QgsMessageLog.logMessage(self.tr('Can not generate requested number of random ' 'points. Maximum number of attempts exceeded.'), self.tr('Processing'), QgsMessageLog.INFO) feedback.setProgress(0) del writer
class ExportGeometryInfo(QgisAlgorithm): INPUT = 'INPUT' METHOD = 'CALC_METHOD' OUTPUT = 'OUTPUT' def icon(self): return QgsApplication.getThemeIcon("/algorithms/mAlgorithmAddGeometryAttributes.svg") def svgIconPath(self): return QgsApplication.iconPath("/algorithms/mAlgorithmAddGeometryAttributes.svg") def tags(self): return self.tr('export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields').split(',') def group(self): return self.tr('Vector geometry') def groupId(self): return 'vectorgeometry' def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.calc_methods = [self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal')] def initAlgorithm(self, config=None): self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) self.addParameter(QgsProcessingParameterEnum(self.METHOD, self.tr('Calculate using'), options=self.calc_methods, defaultValue=0)) self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info'))) def name(self): return 'exportaddgeometrycolumns' def displayName(self): return self.tr('Add geometry attributes') def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id} def point_attributes(self, geometry): pt = None if not geometry.isMultipart(): pt = geometry.constGet() else: if geometry.numGeometries() > 0: pt = geometry.geometryN(0) attrs = [] if pt: attrs.append(pt.x()) attrs.append(pt.y()) # add point z/m if self.export_z: attrs.append(pt.z()) if self.export_m: attrs.append(pt.m()) return attrs def line_attributes(self, geometry): if geometry.isMultipart(): return [self.distance_area.measureLength(geometry)] else: curve = geometry.constGet() p1 = curve.startPoint() p2 = curve.endPoint() straight_distance = self.distance_area.measureLine(QgsPointXY(p1), QgsPointXY(p2)) sinuosity = curve.sinuosity() if math.isnan(sinuosity): sinuosity = NULL return [self.distance_area.measureLength(geometry), straight_distance, sinuosity] def polygon_attributes(self, geometry): area = self.distance_area.measureArea(geometry) perimeter = self.distance_area.measurePerimeter(geometry) return [area, perimeter]
class ExportGeometryInfo(QgisAlgorithm): INPUT = 'INPUT' METHOD = 'CALC_METHOD' OUTPUT = 'OUTPUT' def icon(self): return QgsApplication.getThemeIcon( "/algorithms/mAlgorithmAddGeometryAttributes.svg") def svgIconPath(self): return QgsApplication.iconPath( "/algorithms/mAlgorithmAddGeometryAttributes.svg") def tags(self): return self.tr( 'export,add,information,measurements,areas,lengths,perimeters,latitudes,longitudes,x,y,z,extract,points,lines,polygons,sinuosity,fields' ).split(',') def group(self): return self.tr('Vector geometry') def groupId(self): return 'vectorgeometry' def __init__(self): super().__init__() self.export_z = False self.export_m = False self.distance_area = None self.calc_methods = [ self.tr('Layer CRS'), self.tr('Project CRS'), self.tr('Ellipsoidal') ] def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) self.addParameter( QgsProcessingParameterEnum(self.METHOD, self.tr('Calculate using'), options=self.calc_methods, defaultValue=0)) self.addParameter( QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Added geom info'))) def name(self): return 'exportaddgeometrycolumns' def displayName(self): return self.tr('Add geometry attributes') def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) method = self.parameterAsEnum(parameters, self.METHOD, context) wkb_type = source.wkbType() fields = source.fields() new_fields = QgsFields() if QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.PolygonGeometry: new_fields.append(QgsField('area', QVariant.Double)) new_fields.append(QgsField('perimeter', QVariant.Double)) elif QgsWkbTypes.geometryType(wkb_type) == QgsWkbTypes.LineGeometry: new_fields.append(QgsField('length', QVariant.Double)) if not QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('straightdis', QVariant.Double)) new_fields.append(QgsField('sinuosity', QVariant.Double)) else: new_fields.append(QgsField('xcoord', QVariant.Double)) new_fields.append(QgsField('ycoord', QVariant.Double)) if QgsWkbTypes.hasZ(source.wkbType()): self.export_z = True new_fields.append(QgsField('zcoord', QVariant.Double)) if QgsWkbTypes.hasM(source.wkbType()): self.export_m = True new_fields.append(QgsField('mvalue', QVariant.Double)) fields = QgsProcessingUtils.combineFields(fields, new_fields) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, wkb_type, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) coordTransform = None # Calculate with: # 0 - layer CRS # 1 - project CRS # 2 - ellipsoidal self.distance_area = QgsDistanceArea() if method == 2: self.distance_area.setSourceCrs(source.sourceCrs(), context.transformContext()) self.distance_area.setEllipsoid(context.project().ellipsoid()) elif method == 1: coordTransform = QgsCoordinateTransform(source.sourceCrs(), context.project().crs(), context.project()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break outFeat = f attrs = f.attributes() inGeom = f.geometry() if inGeom: if coordTransform is not None: inGeom.transform(coordTransform) if inGeom.type() == QgsWkbTypes.PointGeometry: attrs.extend(self.point_attributes(inGeom)) elif inGeom.type() == QgsWkbTypes.PolygonGeometry: attrs.extend(self.polygon_attributes(inGeom)) else: attrs.extend(self.line_attributes(inGeom)) # ensure consistent count of attributes - otherwise null # geometry features will have incorrect attribute length # and provider may reject them if len(attrs) < len(fields): attrs += [NULL] * (len(fields) - len(attrs)) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id} def point_attributes(self, geometry): pt = None if not geometry.isMultipart(): pt = geometry.constGet() else: if geometry.numGeometries() > 0: pt = geometry.geometryN(0) attrs = [] if pt: attrs.append(pt.x()) attrs.append(pt.y()) # add point z/m if self.export_z: attrs.append(pt.z()) if self.export_m: attrs.append(pt.m()) return attrs def line_attributes(self, geometry): if geometry.isMultipart(): return [self.distance_area.measureLength(geometry)] else: curve = geometry.constGet() p1 = curve.startPoint() p2 = curve.endPoint() straight_distance = self.distance_area.measureLine( QgsPointXY(p1), QgsPointXY(p2)) sinuosity = curve.sinuosity() if math.isnan(sinuosity): sinuosity = NULL return [ self.distance_area.measureLength(geometry), straight_distance, sinuosity ] def polygon_attributes(self, geometry): area = self.distance_area.measureArea(geometry) perimeter = self.distance_area.measurePerimeter(geometry) return [area, perimeter]
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) expression = QgsExpression(self.parameterAsString(parameters, self.EXPRESSION, context)) if expression.hasParserError(): raise QgsProcessingException(expression.parserErrorString()) expressionContext = self.createExpressionContext(parameters, context, source) expression.prepare(expressionContext) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs(), QgsFeatureSink.RegeneratePrimaryKey) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.project().ellipsoid()) total = 100.0 / source.featureCount() if source.featureCount() else 0 current_progress = 0 for current, f in enumerate(source.getFeatures()): if feedback.isCanceled(): break if not f.hasGeometry(): continue current_progress = total * current feedback.setProgress(current_progress) expressionContext.setFeature(f) value = expression.evaluate(expressionContext) if expression.hasEvalError(): feedback.pushInfo( self.tr('Evaluation error for feature ID {}: {}').format(f.id(), expression.evalErrorString())) continue fGeom = f.geometry() engine = QgsGeometry.createGeometryEngine(fGeom.constGet()) engine.prepareGeometry() bbox = fGeom.boundingBox() if strategy == 0: pointCount = int(value) else: pointCount = int(round(value * da.measureArea(fGeom))) if pointCount == 0: feedback.pushInfo("Skip feature {} as number of points for it is 0.".format(f.id())) continue index = QgsSpatialIndex() points = dict() nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 feature_total = total / pointCount if pointCount else 1 random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if engine.contains(geom.constGet()) and \ vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.addFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(current_progress + int(nPoints * feature_total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random ' 'points. Maximum number of attempts exceeded.')) feedback.setProgress(100) return {self.OUTPUT: dest_id}
class MeasureAreaTool(QgsMapTool): finished = pyqtSignal() def __init__(self, canvas, msglog): super().__init__(canvas) self.canvas = canvas self.msglog = msglog self.start_point = self.middle_point = self.end_point = None self.rubber_band = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry) self.rubber_band.setColor(QColor(255, 0, 0, 100)) self.rubber_band.setWidth(3) self.rubber_band_points = QgsRubberBand(self.canvas, QgsWkbTypes.PointGeometry) self.rubber_band_points.setIcon(QgsRubberBand.ICON_CIRCLE) self.rubber_band_points.setIconSize(10) self.rubber_band_points.setColor(QColor(255, 0, 0, 150)) crs = self.canvas.mapSettings().destinationCrs() self.area_calc = QgsDistanceArea() self.area_calc.setSourceCrs(crs, QgsProject.instance().transformContext()) self.area_calc.setEllipsoid(crs.ellipsoidAcronym()) self.reset() def reset(self): """ Reset log message and rubber band""" self.msglog.logMessage("") self.start_point = self.end_point = None self.rubber_band.reset(QgsWkbTypes.PolygonGeometry) self.rubber_band_points.reset(QgsWkbTypes.PointGeometry) def canvasPressEvent(self, event): pass def canvasReleaseEvent(self, event): transform = self.canvas.getCoordinateTransform() point = transform.toMapCoordinates(event.pos().x(), event.pos().y()) if self.start_point and event.button() == Qt.RightButton: multipoint = self.rubber_band.asGeometry() area = self.area_calc.measureArea(multipoint) anglemsg = QMessageBox(self.parent()) anglemsg.finished.connect(self.deactivate) anglemsg.setWindowTitle("Measure area tool") anglemsg.setText("Area: {} ".format( self.area_calc.formatArea(area, 3, QgsUnitTypes.AreaSquareMeters, True))) anglemsg.exec() self.finish() elif self.start_point: self.rubber_band.addPoint(point) self.rubber_band_points.addPoint(point) else: self.start_point = point self.rubber_band.addPoint(self.start_point) self.rubber_band_points.addPoint(self.start_point) def canvasMoveEvent(self, e): if self.start_point and not self.end_point: transform = self.canvas.getCoordinateTransform() point = transform.toMapCoordinates(e.pos().x(), e.pos().y()) self.rubber_band.movePoint(point) multipoint = self.rubber_band.asGeometry() area = self.area_calc.measureArea(multipoint) self.msglog.logMessage("") self.msglog.logMessage( "Current area: {} ".format( self.area_calc.formatArea(area, 3, QgsUnitTypes.AreaSquareMeters, True)), "Measure Area:", 0) def keyPressEvent(self, event): """ When escape key is pressed, line is restarted """ if event.key() == Qt.Key_Escape: self.reset() def finish(self): self.reset() self.finished.emit()