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 testMeasureLine(self): # +-+ # | | # +-+ + linestring = QgsGeometry.fromPolyline( [QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 0)] ) da = QgsDistanceArea() length = da.measureLength(linestring) myMessage = "Expected:\n%f\nGot:\n%f\n" % (4, length) assert length == 4, myMessage
def testMeasureLine(self): # +-+ # | | # +-+ + linestring = QgsGeometry.fromPolyline( [QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 0), ] ) da = QgsDistanceArea() length = da.measureLength(linestring) myMessage = ('Expected:\n%f\nGot:\n%f\n' % (4, length)) assert length == 4, myMessage
def testMeasureMultiLine(self): # +-+ +-+-+ # | | | | # +-+ + + +-+ linestring = QgsGeometry.fromMultiPolyline( [ [QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 0)], [QgsPoint(3, 0), QgsPoint(3, 1), QgsPoint(5, 1), QgsPoint(5, 0), QgsPoint(6, 0)], ] ) da = QgsDistanceArea() length = da.measureLength(linestring) myMessage = "Expected:\n%f\nGot:\n%f\n" % (9, length) assert length == 9, myMessage
def testMeasureMultiLine(self): # +-+ +-+-+ # | | | | # +-+ + + +-+ linestring = QgsGeometry.fromMultiPolyline( [ [QgsPoint(0, 0), QgsPoint(1, 0), QgsPoint(1, 1), QgsPoint(2, 1), QgsPoint(2, 0), ], [QgsPoint(3, 0), QgsPoint(3, 1), QgsPoint(5, 1), QgsPoint(5, 0), QgsPoint(6, 0), ] ] ) da = QgsDistanceArea() length = da.measureLength(linestring) myMessage = ('Expected:\n%f\nGot:\n%f\n' % (9, length)) assert length == 9, myMessage
def highlight_courant(self): """highlight animated flowline layer where Courant number is higher than a given value (use velocity variable as flowline-results)""" if len(QgsProject.instance().mapLayersByName("line_results")) == 0: self.iface.messageBar().pushMessage( "Warning", 'Couldn\'t find line_results layer, click "Animation on" button', level=Qgis.Warning, ) return # layer found canvas = self.iface.mapCanvas() line_results = QgsProject.instance().mapLayersByName("line_results")[0] global_settings_layer = QgsProject.instance().mapLayersByName( "v2_global_settings" )[0] timestep = list(global_settings_layer.getFeatures())[0][ "sim_time_step" ] # [0] -> [self.selected_scenario_index] d = QgsDistanceArea() d.setEllipsoid("WGS84") features = line_results.getFeatures() for feature in features: kcu = feature["kcu"] if kcu in [0, 1, 2, 3, 5, 100, 101]: geometry = feature.geometry() length = d.measureLength(geometry) velocity = abs(feature["result"]) courant = velocity * timestep / length if courant > self.courantThreshold.value(): color = QtGui.QColor(Qt.red) highlight = QgsHighlight(canvas, feature, line_results) highlight.setColor(color) highlight.setMinWidth(courant / 2) # highlight.setBuffer() color.setAlpha(50) highlight.setFillColor(color) highlight.show() self.highlights.append(highlight)
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 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
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
def processAlgorithm(self, progress): lineLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.LINES)) polyLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.POLYGONS)) lengthFieldName = self.getParameterValue(self.LEN_FIELD) countFieldName = self.getParameterValue(self.COUNT_FIELD) (idxLength, fieldList) = vector.findOrCreateField(polyLayer, polyLayer.fields(), lengthFieldName) (idxCount, fieldList) = vector.findOrCreateField(polyLayer, fieldList, countFieldName) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fieldList.toList(), polyLayer.wkbType(), polyLayer.crs()) spatialIndex = vector.spatialindex(lineLayer) ftLine = QgsFeature() ftPoly = QgsFeature() outFeat = QgsFeature() inGeom = QgsGeometry() outGeom = QgsGeometry() distArea = QgsDistanceArea() features = vector.features(polyLayer) total = 100.0 / len(features) hasIntersections = False for current, ftPoly in enumerate(features): inGeom = ftPoly.geometry() attrs = ftPoly.attributes() count = 0 length = 0 hasIntersections = False lines = spatialIndex.intersects(inGeom.boundingBox()) engine = None if len(lines) > 0: hasIntersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() if hasIntersections: request = QgsFeatureRequest().setFilterFids(lines).setSubsetOfAttributes([]) for ftLine in lineLayer.getFeatures(request): tmpGeom = ftLine.geometry() if engine.intersects(tmpGeom.geometry()): outGeom = inGeom.intersection(tmpGeom) length += distArea.measureLength(outGeom) count += 1 outFeat.setGeometry(inGeom) if idxLength == len(attrs): attrs.append(length) else: attrs[idxLength] = length if idxCount == len(attrs): attrs.append(count) else: attrs[idxCount] = count outFeat.setAttributes(attrs) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) del writer
def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = flowTraceDialog() # get current selected layer clayer = self.iface.mapCanvas().currentLayer() # make sure that the selected layer is a QgsVectorLayer of # QgsWkbTypes.GeometryType.LineGeometry type before opening the dialog if clayer is None or clayer.type() != 0 or clayer.geometryType() != 1: return # set layer name in dialog self.dlg.labelLayer.setText(clayer.name()) # set number of selected features in dialog self.dlg.labelNumFeatures.setText(str(len(clayer.selectedFeatures()))) # print(self.iface.mapCanvas().mapUnits()) # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # get direct from dialog direction = self.dlg.downstream_radio_button.isChecked() #setup distance distance = QgsDistanceArea() # the unit of measure will be set to the same as the layer # maybe it would be better to set it to the map CRS distance.setSourceCrs(clayer.sourceCrs(), QgsProject.instance().transformContext()) if result: # get crs for tolerance setting crs = self.iface.activeLayer().crs().authid() # print (crs) if crs == 'EPSG:4269': tolerance = .0001 else: tolerance = self.dlg.SpinBoxTolerance.value() #index and create sets from layer index = QgsSpatialIndex(clayer) selection_set = set(clayer.selectedFeatureIds()) final_set = selection_set.copy() dict_features = { feature.id(): feature for feature in clayer.getFeatures() } #loop thru selection set while selection_set: # get upstream/downstream node of next feature feature = dict_features[selection_set.pop()] nodes = self.get_geometry(feature.geometry()) upstream_coord = nodes[0 - direction] # select all features around selected node # using a bounding box upstream_coord_x = upstream_coord.x() upstream_coord_y = upstream_coord.y() rectangle = QgsRectangle(upstream_coord_x - tolerance, upstream_coord_y - tolerance, upstream_coord_x + tolerance, upstream_coord_y + tolerance) ls_fids = index.intersects(rectangle) #iterate thru intersected features for fid in ls_fids: if fid not in final_set: # get downstream/upstream coordinates feature = dict_features[fid] nodes = self.get_geometry(feature.geometry()) downstream_coord = nodes[direction - 1] #get distance between downstream and upstream nodes dist = math.sqrt( (downstream_coord.x() - upstream_coord_x)**2 + (downstream_coord.y() - upstream_coord_y)**2) if dist <= tolerance: # if within tolerance, adds feature to selection and final set # set values being unique, a duplicate won't be created if # already present in the selection set final_set.add(fid) selection_set.add(fid) #calculate total length list_length = [ distance.measureLength(dict_features[fid].geometry()) for fid in final_set ] total_length = sum(list_length) #select features using final_set clayer.selectByIds(list(final_set)) self.iface.mapCanvas().refresh() #add message bar about number of features selected and length message = self.tr( "{} features selected totalling {} {} in length.".format( len(final_set), round(total_length, 2), QgsUnitTypes.toString(distance.lengthUnits()))) self.iface.messageBar().pushMessage("Flow Trace Completed", message, 0, 10)
def processAlgorithm(self, progress): lineLayer = dataobjects.getObjectFromUri( self.getParameterValue(self.LINES)) polyLayer = dataobjects.getObjectFromUri( self.getParameterValue(self.POLYGONS)) lengthFieldName = self.getParameterValue(self.LEN_FIELD) countFieldName = self.getParameterValue(self.COUNT_FIELD) (idxLength, fieldList) = vector.findOrCreateField(polyLayer, polyLayer.fields(), lengthFieldName) (idxCount, fieldList) = vector.findOrCreateField(polyLayer, fieldList, countFieldName) writer = self.getOutputFromName(self.OUTPUT).getVectorWriter( fieldList.toList(), polyLayer.wkbType(), polyLayer.crs()) spatialIndex = vector.spatialindex(lineLayer) ftLine = QgsFeature() ftPoly = QgsFeature() outFeat = QgsFeature() inGeom = QgsGeometry() outGeom = QgsGeometry() distArea = QgsDistanceArea() features = vector.features(polyLayer) total = 100.0 / len(features) hasIntersections = False for current, ftPoly in enumerate(features): inGeom = ftPoly.geometry() attrs = ftPoly.attributes() count = 0 length = 0 hasIntersections = False lines = spatialIndex.intersects(inGeom.boundingBox()) engine = None if len(lines) > 0: hasIntersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() if hasIntersections: request = QgsFeatureRequest().setFilterFids( lines).setSubsetOfAttributes([]) for ftLine in lineLayer.getFeatures(request): tmpGeom = ftLine.geometry() if engine.intersects(tmpGeom.geometry()): outGeom = inGeom.intersection(tmpGeom) length += distArea.measureLength(outGeom) count += 1 outFeat.setGeometry(inGeom) if idxLength == len(attrs): attrs.append(length) else: attrs[idxLength] = length if idxCount == len(attrs): attrs.append(count) else: attrs[idxCount] = count outFeat.setAttributes(attrs) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) del writer
def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = flowTraceDialog() # get current selected layer clayer = self.iface.mapCanvas().currentLayer() # set layer name in dialog self.dlg.labelLayer.setText(clayer.name()) # set number of selected features in dialog self.dlg.labelNumFeatures.setText(str(len(clayer.selectedFeatures()))) # print(self.iface.mapCanvas().mapUnits()) # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # get direct from dialog direction = self.dlg.downstream_radio_button.isChecked() final_list = [] #add tolerance value # setup total length totallength = 0.0 # distance = 0 #setup distance distance = QgsDistanceArea() # the unit of measure will be set to the same as the layer # maybe it would be better to set it to the map CRS distance.setSourceCrs(clayer.sourceCrs(), QgsProject.instance().transformContext()) if result: #setup final selection list #setup temporary selection list selection_list = [] #add tolerance value tolerance = 1 #get current layer clayer = self.iface.mapCanvas().currentLayer() # print (clayer.name()) if clayer is None: return #get provider provider = clayer.dataProvider() #get selected features features = clayer.selectedFeatures() # get crs for tolerance setting crs = self.iface.activeLayer().crs().authid() # print (crs) if crs == 'EPSG:4269': rec = .0001 tolerance = .0001 else: #rec = .1 rec = self.dlg.SpinBoxTolerance.value() #iterate thru features to add to lists for feature in features: # add selected features to final list final_list.append(feature.id()) # add selected features to selection list for while loop selection_list.append(feature.id()) #get feature geometry geom = feature.geometry() totallength = totallength + distance.measureLength(geom) # print (QgsDistanceArea.lengthUnits) # https://qgis.org/api/classQgsWkbTypes.html if geom.type() != 1: print("Geometry not allowed") QMessageBox.information( None, "Flow Trace", "Geometry not allowed, \nPlease select line geometry only." ) return #loop thru selection list while selection_list: #get selected features request = QgsFeatureRequest().setFilterFid(selection_list[0]) # request = QgsFeatureRequest() feature = next(clayer.getFeatures(request)) geom = feature.geometry() # get nodes nodes = self.get_geometry(feature.geometry()) # get upstream node if direction: upstream_coord = nodes[-1] # print (upstream_coord) else: upstream_coord = nodes[0] # print (upstream_coord) # select all features around upstream coordinate # using a bounding box rectangle = QgsRectangle(upstream_coord.x() - rec, upstream_coord.y() - rec, upstream_coord.x() + rec, upstream_coord.y() + rec) # rectangle = QgsRectangle (minx, miny, maxx, maxy) request = QgsFeatureRequest().setFilterRect(rectangle) features = clayer.getFeatures(request) #iterate thru requested features for feature in features: # get nodes nodes = self.get_geometry(feature.geometry()) #downstream_coord = nodes[-1] # get upstream node if direction: downstream_coord = nodes[0] # print (upstream_coord) else: downstream_coord = nodes[-1] # print (upstream_coord) #get distance from downstream node to upstream node dist = distance.measureLine(downstream_coord, upstream_coord) if dist < tolerance: #add feature to final list final_list.append(feature.id()) if feature.id() not in selection_list: #add feature to selection list selection_list.append(feature.id()) # Length from Line totallength = totallength + distance.measureLength( feature.geometry()) #remove feature from selection list selection_list.pop(0) #select features using final_list for fid in final_list: clayer.select(fid) #refresh the canvas self.iface.mapCanvas().refresh() #add message box about length and number of features QMessageBox.information( None, "Flow Trace Complete", "Total Features Selected: " + str(len(final_list)) + "\r\n" + " Length: " + str(round(totallength, 2)) + ' ' + QgsUnitTypes.toString(distance.lengthUnits()))
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]
def write_line_length(self, layer, ellipsoid, transform_context, m=False, km=False, nm=False): '''Write length attribute [m/km/nm] to layer Parameters ---------- layer : QgsVectorLayer input line vector layer ellipsoid : str QgsCoordinateReferenceSystem.ellipsoidAcronym() transform_context : QgsCoordinateTransformContext transform context for coordinate transformation m : boolean write meters (Default value = False) km : boolean write kilometers (Default value = False) nm : boolean write nautical miles (Default value = False) Returns ------- error : boolean 0/1 - no error/error result : str or None output or error msg if error == 1 ''' # if no lengths shall be written, return if m == km == nm == False: return 1, 'No length units selected, no attributes created!\n' # set some field names prefix = 'length_' m_field = f'{prefix}m' km_field = f'{prefix}km' nm_field = f'{prefix}nm' # get CRS of input layer crs_layer = layer.crs() # Initialize Distance calculator class with ellipsoid da = QgsDistanceArea() da.setSourceCrs(crs_layer, transform_context) da.setEllipsoid(ellipsoid) with edit(layer): # delete fields previously created by Cruise Tools self.delete_fields_by_prefix(layer, prefix) # create fields for length_m and/or length_nm if m: layer.addAttribute( QgsField(m_field, QVariant.Double, len=15, prec=2)) if km: layer.addAttribute( QgsField(km_field, QVariant.Double, len=15, prec=3)) if nm: layer.addAttribute( QgsField(nm_field, QVariant.Double, len=15, prec=3)) # update attribute table fields layer.updateFields() # get all features features = self.get_features(layer, selected=False) for feature in features: # get geometry of feature geom = feature.geometry() # measure feature length in meters len = da.measureLength(geom) # set field values according to the calculated length if m: len_m = da.convertLengthMeasurement( len, QgsUnitTypes.DistanceMeters) len_m = round(len_m, 2) feature.setAttribute(layer.fields().indexFromName(m_field), len_m) if km: len_km = da.convertLengthMeasurement( len, QgsUnitTypes.DistanceKilometers) len_km = round(len_km, 5) feature.setAttribute( layer.fields().indexFromName(km_field), len_km) if nm: len_nm = da.convertLengthMeasurement( len, QgsUnitTypes.DistanceNauticalMiles) len_nm = round(len_nm, 5) feature.setAttribute( layer.fields().indexFromName(nm_field), len_nm) # check if speed_kn exists f_idx_speed = layer.fields().indexFromName('speed_kn') f_idx_time = layer.fields().indexFromName('time_h') if (f_idx_speed != -1) and (f_idx_time != -1): # if yes, get value speed_kn = feature.attributes()[f_idx_speed] if speed_kn != None: # if value not NULL, calculate time and write it to time_h field len_nm = da.convertLengthMeasurement( len, QgsUnitTypes.DistanceNauticalMiles) time_h = round(len_nm / speed_kn, 2) feature.setAttribute(f_idx_time, time_h) # update attribute table layer.updateFeature(feature) return 0, None
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 run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = BathCreatorDialog() # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: # Get Values from GUI POLYGON_LAYER_NAME = self.dlg.polygone_value DEM_LAYER_NAME = self.dlg.dem_value DEM_BAND = '1' DELTA = float(self.dlg.delta_value) OUTPUT_FILE_NAME = self.dlg.csv_value LINE_LAYER = self.dlg.line_value # Input check ret = self._checkInput(POLYGON_LAYER_NAME, LINE_LAYER) if ret == False: return False # Start work layer = QgsProject.instance().mapLayersByName(LINE_LAYER) features = layer[0].getFeatures() d = QgsDistanceArea() data = [] for feat in features: data_dic = {} # Read fields from the vector line data_dic['SEGMENT'] = feat["SEGMENT"] data_dic['ELWS'] = feat["ELWS"] data_dic['FRIC'] = feat["FRIC"] # Get vector line length data_dic['DLX'] = round(d.measureLength(feat.geometry()), 2) # Calculate line angle data_dic['PHI0'] = self._calculateAangle(feat) data.append(data_dic) QgsMessageLog.logMessage('Data collected from features: ' + str(data), tag="Create_Bathymetry", level=Qgis.Info) # Calculate volume by the cells value in each buffer histograms = processing.run( "native:zonalhistogram", { 'INPUT_RASTER': DEM_LAYER_NAME, 'RASTER_BAND': DEM_BAND, 'INPUT_VECTOR': POLYGON_LAYER_NAME, 'COLUMN_PREFIX': '', 'OUTPUT': 'memory:' }) dem = QgsProject.instance().mapLayersByName(DEM_LAYER_NAME) cell_size = dem[0].rasterUnitsPerPixelX( ) * dem[0].rasterUnitsPerPixelY() # get cell size m^2 # remove all none number fields (like SEGMENT or anything else in the layer) fields_list = histograms['OUTPUT'].fields().names() heights_list = [] for i in fields_list: try: f = float(i) except: pass else: heights_list.append(f) features = histograms['OUTPUT'].getFeatures() volume_data = calcVolumes(heights_list, features, DELTA, cell_size) # output is list of dic QgsMessageLog.logMessage('Volume clculation summary: ' + str(volume_data), tag="Create_Bathymetry", level=Qgis.Info) # Calculate width width_data = self._calcWidth(data, volume_data, DELTA) # Get the border segments into the data sorted_width = sorted(width_data, key=lambda k: k['SEGMENT']) n = len(sorted_width[0]['data']) # number of layers sorted_width.insert(0, { 'SEGMENT': 1, 'data': [0] * n }) # insert first empty segment data.append({ 'SEGMENT': 1, 'ELWS': '0', 'FRIC': '0', 'DLX': '0', 'PHI0': '0' }) seq = [x['SEGMENT'] for x in sorted_width] for i in range( max(seq) ): #Iterate over all segments and add the missing border empty segments if i + 1 != sorted_width[i]['SEGMENT']: sorted_width.insert(i, {'SEGMENT': i + 1, 'data': [0] * n}) data.append({ 'SEGMENT': i + 1, 'ELWS': '0', 'FRIC': '0', 'DLX': '0', 'PHI0': '0' }) sorted_width.insert(i + 1, { 'SEGMENT': i + 2, 'data': [0] * n }) data.append({ 'SEGMENT': i + 2, 'ELWS': '0', 'FRIC': '0', 'DLX': '0', 'PHI0': '0' }) sorted_width.insert(i + 1, {'SEGMENT': i + 2, 'data': [0] * n}) data.append({ 'SEGMENT': i + 2, 'ELWS': '0', 'FRIC': '0', 'DLX': '0', 'PHI0': '0' }) # insert last empty segment regular_list = [ ] # simplify the data structure before writing the csv for d in range(len(sorted_width)): regular_list.append(sorted_width[d]['data']) ret = self._writeExcel(data, regular_list, DELTA, OUTPUT_FILE_NAME) QgsMessageLog.logMessage(message='Execution finished ', tag="Create_Bathymetry", level=Qgis.Info) if ret == True: iface.messageBar().pushMessage( "Whoo", "Finished creating the bathymetric file", level=Qgis.Success, duration=10)
def processAlgorithm(self, parameters, context, feedback): # get input variables source = self.parameterAsSource(parameters, self.INPUT_LINE, context) swath_angle_field = self.parameterAsString(parameters, self.SWATH_ANGLE_FIELD, context) swath_angle_fallback = self.parameterAsInt(parameters, self.SWATH_ANGLE, context) raster_layer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context) band_number = self.parameterAsInt(parameters, self.BAND, context) # copy of the field name for later swath_angle_field_name = swath_angle_field # set new default values in config feedback.pushConsoleInfo( self.tr(f'Storing new default settings in config...')) self.config.set(self.module, 'swath_angle', swath_angle_fallback) # get crs's crs_line = source.sourceCrs() crs_raster = raster_layer.crs() # get project transform_context transform_context = context.transformContext() # CRS transformation to "WGS84/World Mercator" for MBES coverage operations crs_mercator = QgsCoordinateReferenceSystem('EPSG:3395') trans_line2merc = QgsCoordinateTransform(crs_line, crs_mercator, transform_context) # CRS transformation to raster layer CRS for depth sampling trans_merc2raster = QgsCoordinateTransform(crs_mercator, crs_raster, transform_context) trans_line2raster = QgsCoordinateTransform(crs_line, crs_raster, transform_context) crs_geo = QgsCoordinateReferenceSystem('EPSG:4326') trans_line2geo = QgsCoordinateTransform(crs_line, crs_geo, transform_context) # initialize distance tool da = QgsDistanceArea() da.setSourceCrs(crs_mercator, transform_context) da.setEllipsoid(crs_mercator.ellipsoidAcronym()) # empty lists for unioned buffers buffer_union_list = [] # get (selected) features features = source.getFeatures() # feedback total = 100.0 / source.featureCount() if source.featureCount() else 0 # loop through features feedback.pushConsoleInfo(self.tr(f'Densifying line features...')) feedback.pushConsoleInfo(self.tr(f'Extracting vertices...')) feedback.pushConsoleInfo(self.tr(f'Sampling values...')) feedback.pushConsoleInfo( self.tr(f'Computing depth dependent buffers...')) feedback.pushConsoleInfo(self.tr(f'Unionizing output features...')) for feature_id, feature in enumerate(features): # get feature geometry feature_geom = feature.geometry() # check for LineString geometry if QgsWkbTypes.isSingleType(feature_geom.wkbType()): # get list of vertices vertices_list = feature_geom.asPolyline() vertices_list = [vertices_list] # check for MultiLineString geometry elif QgsWkbTypes.isMultiType(feature_geom.wkbType()): # get list of list of vertices per multiline part vertices_list = feature_geom.asMultiPolyline() for part_id, vertices in enumerate(vertices_list): # transform vertices CRS to "WGS 84 / World Mercator" vertices_t = [] for vertex in vertices: vertex_trans = trans_line2merc.transform(vertex) vertices_t.append(vertex_trans) # get centroid as point for UTM zone selection centroid = feature_geom.centroid() centroid_point = centroid.asPoint() # check if centroid needs to be transformed to get x/y in lon/lat if not crs_line.isGeographic(): centroid_point = trans_line2geo.transform(centroid_point) # get UTM zone of feature for buffering lat, lon = centroid_point.y(), centroid_point.x() crs_utm = self.get_UTM_zone(lat, lon) # create back and forth transformations for later trans_merc2utm = QgsCoordinateTransform( crs_mercator, crs_utm, transform_context) trans_utm2line = QgsCoordinateTransform( crs_utm, crs_line, transform_context) # split line into segments for segment_id in range(len(vertices_t) - 1): # ===== (1) DENSIFY LINE VERTICES ===== # create new Polyline geometry for line segment segment_geom = QgsGeometry.fromPolylineXY( [vertices_t[segment_id], vertices_t[segment_id + 1]]) # measure ellipsoidal distance between start and end vertex segment_length = da.measureLength(segment_geom) # calculate number of extra vertices to insert extra_vertices = int(segment_length // self.vertex_distance) # create additional vertices along line segment segment_geom_dense = segment_geom.densifyByCount( extra_vertices - 1) # initialize additional fields feature_id_field = QgsField('feature_id', QVariant.Int, 'Integer', len=5, prec=0) part_id_field = QgsField('part_id', QVariant.Int, 'Integer', len=5, prec=0) segment_id_field = QgsField('segment_id', QVariant.Int, 'Integer', len=5, prec=0) # list for segment buffers buffer_list = [] # loop over all vertices of line segment for i, vertex in enumerate(segment_geom_dense.vertices()): # === CREATE POINT FEATURE === # initialize feature and set geometry fpoint = QgsFeature() fpoint.setGeometry(vertex) # sample bathymetry grid # get point geometry (as QgsPointXY) fpoint_geom = fpoint.geometry() pointXY = fpoint_geom.asPoint() # transform point to raster CRS pointXY_raster = trans_merc2raster.transform( pointXY) #trans_line2raster.transform(pointXY) # sample raster at point location pointXY_depth, error_check = raster_layer.dataProvider( ).sample(pointXY_raster, 1) # check if valid depth was sampled, otherwise skip point if error_check == False: continue # create depth-dependant buffer: # check if swath_angle field is selected if swath_angle_field != '': # get value from field swath_angle_field_value = feature.attribute( swath_angle_field) # check if value is set (not NOLL) if swath_angle_field_value == None: # if NULL, set fallback swath_angle = swath_angle_fallback else: # othervise take value from field swath_angle = swath_angle_field_value # or if no field was selected use fallback value right away else: swath_angle = swath_angle_fallback # calculate buffer radius (swath width from depth and swath angle) buffer_radius = round( tan(radians(swath_angle / 2)) * abs(pointXY_depth), 0) # transform point from mercator zu UTM fpoint_geom.transform(trans_merc2utm) # create buffer buffer = fpoint_geom.buffer(buffer_radius, 10) # transform buffer back to initial input CRS buffer.transform(trans_utm2line) # store buffer in list buffer_list.append(buffer) # check if any points in this segment have been sampled if buffer_list == []: continue # dissolve point buffers of line segment: # dissolve all polygons based on line vertices into single feature buffer_union = QgsGeometry().unaryUnion(buffer_list) # set fields and attributes: # empty fields buffer_fields = QgsFields() # loop through line feature fields for field in feature.fields(): # and append all but the 'fid' field (if it exists) if field.name() != 'fid': buffer_fields.append(field) # append extra buffer fields (intial feature id, part id and segment id for buffer_field in [ feature_id_field, part_id_field, segment_id_field ]: buffer_fields.append(buffer_field) # if no input swath_angle field was selected on input, create one if swath_angle_field == '': swath_angle_field_name = 'mbes_swath_angle' buffer_fields.append( QgsField(swath_angle_field_name, QVariant.Int, 'Integer', len=5, prec=0)) # initialize polygon feature fpoly = QgsFeature(buffer_fields) # set attributes for polygon feature for field in feature.fields(): # ignore 'fid' again if field.name() != 'fid': # set attribute from feature to buffer fpoly.setAttribute(field.name(), feature.attribute(field.name())) # set addtional buffer fields fpoly.setAttribute('feature_id', feature_id) fpoly.setAttribute('part_id', part_id) fpoly.setAttribute('segment_id', segment_id) fpoly.setAttribute(swath_angle_field_name, swath_angle) # set geometry fpoly.setGeometry(buffer_union) # store segment coverage polygon if fpoly.hasGeometry() and fpoly.isValid(): buffer_union_list.append(fpoly) # set progess feedback.setProgress(int(feature_id * total)) # if buffer_union_list is empty, no buffer features where created if buffer_union_list == []: raise Exception( 'No depth values could be sampled from the input raster!') # creating feature sink feedback.pushConsoleInfo(self.tr(f'Creating feature sink...')) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, buffer_fields, QgsWkbTypes.MultiPolygon, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # write coverage features to sink feedback.pushConsoleInfo(self.tr(f'Writing features...')) sink.addFeatures(buffer_union_list, QgsFeatureSink.FastInsert) # make variables accessible for post processing self.output = dest_id result = {self.OUTPUT: self.output} return result
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: if QgsWkbTypes.isMultiType(source.wkbType()): new_fields.append(QgsField('numparts', QVariant.Int)) 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): attrs = [] if not geometry.isMultipart(): pt = geometry.constGet() 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()) else: attrs = [geometry.constGet().numGeometries()] 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): line_source = self.parameterAsSource(parameters, self.LINES, context) poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context) count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context) fields = poly_source.fields() if fields.lookupField(length_field_name) < 0: fields.append(QgsField(length_field_name, QVariant.Double)) length_field_index = fields.lookupField(length_field_name) if fields.lookupField(count_field_name) < 0: fields.append(QgsField(count_field_name, QVariant.Int)) count_field_index = fields.lookupField(count_field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) spatialIndex = QgsSpatialIndex( line_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(poly_source.sourceCrs())), feedback) distArea = QgsDistanceArea() distArea.setSourceCrs(poly_source.sourceCrs()) distArea.setEllipsoid(context.project().ellipsoid()) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount( ) else 0 for current, poly_feature in enumerate(features): if feedback.isCanceled(): break output_feature = QgsFeature() count = 0 length = 0 if poly_feature.hasGeometry(): poly_geom = poly_feature.geometry() has_intersections = False lines = spatialIndex.intersects(poly_geom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine( poly_geom.constGet()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids( lines).setSubsetOfAttributes([]).setDestinationCrs( poly_source.sourceCrs()) for line_feature in line_source.getFeatures(request): if feedback.isCanceled(): break if engine.intersects( line_feature.geometry().constGet()): outGeom = poly_geom.intersection( line_feature.geometry()) length += distArea.measureLength(outGeom) count += 1 output_feature.setGeometry(poly_geom) attrs = poly_feature.attributes() if length_field_index == len(attrs): attrs.append(length) else: attrs[length_field_index] = length if count_field_index == len(attrs): attrs.append(count) else: attrs[count_field_index] = count output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): line_source = self.parameterAsSource(parameters, self.LINES, context) if line_source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.LINES)) poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.POLYGONS)) length_field_name = self.parameterAsString(parameters, self.LEN_FIELD, context) count_field_name = self.parameterAsString(parameters, self.COUNT_FIELD, context) fields = poly_source.fields() if fields.lookupField(length_field_name) < 0: fields.append(QgsField(length_field_name, QVariant.Double)) length_field_index = fields.lookupField(length_field_name) if fields.lookupField(count_field_name) < 0: fields.append(QgsField(count_field_name, QVariant.Int)) count_field_index = fields.lookupField(count_field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) spatialIndex = QgsSpatialIndex(line_source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) distArea = QgsDistanceArea() distArea.setSourceCrs(poly_source.sourceCrs(), context.transformContext()) distArea.setEllipsoid(context.project().ellipsoid()) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0 for current, poly_feature in enumerate(features): if feedback.isCanceled(): break output_feature = QgsFeature() count = 0 length = 0 if poly_feature.hasGeometry(): poly_geom = poly_feature.geometry() has_intersections = False lines = spatialIndex.intersects(poly_geom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(poly_geom.constGet()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids(lines).setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) for line_feature in line_source.getFeatures(request): if feedback.isCanceled(): break if engine.intersects(line_feature.geometry().constGet()): outGeom = poly_geom.intersection(line_feature.geometry()) length += distArea.measureLength(outGeom) count += 1 output_feature.setGeometry(poly_geom) attrs = poly_feature.attributes() if length_field_index == len(attrs): attrs.append(length) else: attrs[length_field_index] = length if count_field_index == len(attrs): attrs.append(count) else: attrs[count_field_index] = count output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) MaxTriesPerPoint = self.parameterAsDouble(parameters, self.MAXTRIESPERPOINT, 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)) totNPoints = 0 # The total number of points generated featureCount = source.featureCount() total = 100.0 / (pointCount * featureCount) if pointCount else 1 random.seed() index = QgsSpatialIndex() points = dict() da = QgsDistanceArea() da.setSourceCrs(source.sourceCrs(), context.transformContext()) da.setEllipsoid(context.project().ellipsoid()) maxIterations = pointCount * MaxTriesPerPoint for f in source.getFeatures(): lineGeoms = [] lineCount = 0 fGeom = f.geometry() feedback.pushInfo('fGeom: ' + str(fGeom)) totLineLength = da.measureLength(fGeom) feedback.pushInfo('fGeom totLineLength: ' + str(totLineLength)) # Explode multi part if fGeom.isMultipart(): for aLine in fGeom.asMultiPolyline(): lineGeoms.append(aLine) #lines = fGeom.asMultiPolyline() # pick random line #lineId = random.randint(0, len(lines) - 1) #vertices = lines[lineId] else: lineGeoms.append(fGeom.asPolyline()) #vertices = fGeom.asPolyline() feedback.pushInfo('lineGeoms: ' + str(lineGeoms)) # Generate points on the line geometry / geometries nPoints = 0 nIterations = 0 while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break #feedback.pushInfo('nIterations: ' + str(nIterations)) # Get the random "position" for this point randomLength = random.random() * totLineLength feedback.pushInfo('randomLength: ' + str(randomLength)) currLength = 0 prefLength = 0 # Go through the parts for l in lineGeoms: if feedback.isCanceled(): break currGeom = QgsGeometry.fromPolylineXY(l) #lineLength = da.measureLength(QgsGeometry.fromPolylineXY(l)) lineLength = da.measureLength(currGeom) prevLength = currLength currLength += lineLength feedback.pushInfo('l lineLength: ' + str(lineLength) + ' currLength: ' + str(currLength)) vertices = l # Skip if this is not the "selected" part if currLength < randomLength: continue #randomLength -= currLength #vertices = QgsGeometry.fromPolylineXY(l) feedback.pushInfo('l/vertices: ' + str(vertices)) #randomLength = random.random() * lineLength #distanceToVertex(vid) # find the segment for the new point # and calculate the offset (remainDistance) on that segment remainDist = randomLength - prevLength feedback.pushInfo('remainDist1: ' + str(remainDist)) if len(vertices) == 2: vid = 0 #remainDist = randomLength - currLength else: vid = 0 #while (fGeom.distanceToVertex(vid)) < randomLength: #while (currGeom.distanceToVertex(vid)) < randomLength: currDist = currGeom.distanceToVertex(vid) prevDist = currDist while currDist < remainDist and vid < len(vertices): vid += 1 prevDist = currDist currDist = currGeom.distanceToVertex(vid) feedback.pushInfo('currdist: ' + str(currDist) + ' vid: ' + str(vid)) if vid == len(vertices): feedback.pushInfo('**** vid = len(vertices)! ****') vid -= 1 feedback.pushInfo('currdist2: ' + str(currDist) + ' vid: ' + str(vid)) remainDist = remainDist - prevDist feedback.pushInfo('remainDist2: ' + str(remainDist)) if remainDist <= 0: continue startPoint = vertices[vid] endPoint = vertices[vid + 1] length = da.measureLine(startPoint, endPoint) # if remainDist > minDistance: d = remainDist / (length - remainDist) rx = (startPoint.x() + d * endPoint.x()) / (1 + d) ry = (startPoint.y() + d * endPoint.y()) / (1 + d) # generate random point p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) if vector.checkMinDistance(p, index, minDistance, points): f = QgsFeature(totNPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', totNPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.addFeature(f) points[nPoints] = p nPoints += 1 totNPoints += 1 feedback.setProgress(int(totNPoints * total)) break nIterations += 1 if nPoints < pointCount: #feedback.pushInfo(self.tr('Could not generate requested number of random points. ' feedback.reportError(self.tr('Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.'), False) return {self.OUTPUT: dest_id, self.OUTPUT_POINTS: nPoints}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. source = self.parameterAsSource(parameters, self.INPUT, context) nearest_source = self.parameterAsSource(parameters, self.NEAREST, context) # If source was not found, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSourceError method to return a standard # helper text for when a source cannot be evaluated if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) if nearest_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.NEAREST)) output_fields = QgsProcessingUtils.combineFields( source.fields(), nearest_source.fields(), 'nearest_') output_fields.append(QgsField('distance', QVariant.Double)) # create an empty layer for the results to go into (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, output_fields, QgsWkbTypes.LineString, source.sourceCrs()) # If sink was not created, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSinkError method to return a standard # helper text for when a sink cannot be evaluated if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # Compute the number of steps to display within the progress bar and # get features from source total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() da = QgsDistanceArea() da.setEllipsoid(context.project().ellipsoid()) da.setSourceCrs(source.sourceCrs(), context.transformContext()) for current, feature in enumerate(features): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break # feature is our polygon feature we need to join to something else # find the nearest feature in the other layer # create a line which joins ours polygon to the nearest feature # (shortest line possible!) shortest_line = None shortest_line_length = 99999999999999 closest_feature = None for candidate_feature in nearest_source.getFeatures( QgsFeatureRequest().setDestinationCrs( source.sourceCrs(), context.transformContext())): line = feature.geometry().shortestLine( candidate_feature.geometry()) line_length = da.measureLength(line) if line_length < shortest_line_length: shortest_line = line shortest_line_length = line_length closest_feature = candidate_feature # now shortest_line is the best one! # create the output feature output_feature = QgsFeature() output_feature.setGeometry(shortest_line) attrs = feature.attributes() attrs.extend(closest_feature.attributes()) attrs.append(shortest_line_length) output_feature.setAttributes(attrs) # Add a feature in the sink sink.addFeature(output_feature, QgsFeatureSink.FastInsert) # Update the progress bar feedback.setProgress(int(current * total)) # Return the results of the algorithm. In this case our only result is # the feature sink which contains the processed features, but some # algorithms may return multiple feature sinks, calculated numeric # statistics, etc. These should all be included in the returned # dictionary, with keys matching the feature corresponding parameter # or output names. return {self.OUTPUT: dest_id}