class LayerIndex: def __init__(self, layer): self.__layer = layer self.__index = QgsSpatialIndex() feats = vector.features(layer) for ft in feats: self.__index.insertFeature(ft) def contains(self, point): """Return true if the point intersects the layer""" intersects = self.__index.intersects(point.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() tmpGeom = QgsGeometry(feat.geometry()) if point.intersects(tmpGeom): return True return False def countIntersection(self,bufferGeom,nb): """Return true if the buffer intersects enough entities""" count = 0 intersects = self.__index.intersects(bufferGeom.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() tmpGeom = QgsGeometry(feat.geometry()) if bufferGeom.intersects(tmpGeom): count += 1 if count >= nb: return True return False
def poly2nb(self): lst = [] index = QgsSpatialIndex() featsA = self.lyr.getFeatures() featsB = self.lyr.getFeatures() for ft in featsA: index.insertFeature(ft) featB = QgsFeature() prv = self.lyr.dataProvider() while featsB.nextFeature(featB): geomB = featB.constGeometry() idb = featB.id() idxs = index.intersects(geomB.boundingBox()) sor = [] for idx in idxs: rqst = QgsFeatureRequest().setFilterFid(idx) featA = prv.getFeatures(rqst).next() ida = featA.id() geomA = QgsGeometry(featA.geometry()) if idb!=ida: if geomB.touches(geomA)==True: sor.append(ida) lst.append(sor) return lst
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) bbox = source.sourceExtent() sourceIndex = QgsSpatialIndex(source, feedback) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(p, index, minDistance, points): request = QgsFeatureRequest().setFilterFids(ids).setSubsetOfAttributes([]) for f in source.getFeatures(request): if feedback.isCanceled(): break tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo(self.tr('Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}
def testIndex(self): idx = QgsSpatialIndex() fid = 0 for y in range(5, 15, 5): for x in range(5, 25, 5): ft = QgsFeature() ft.setFeatureId(fid) ft.setGeometry(QgsGeometry.fromPoint(QgsPoint(x, y))) idx.insertFeature(ft) fid += 1 # intersection test rect = QgsRectangle(7.0, 3.0, 17.0, 13.0) fids = idx.intersects(rect) myExpectedValue = 4 myValue = len(fids) myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue) self.assertEqual(myValue, myExpectedValue, myMessage) fids.sort() myMessage = ('Expected: %s\nGot: %s\n' % ([1, 2, 5, 6], fids)) assert fids == [1, 2, 5, 6], myMessage # nearest neighbor test fids = idx.nearestNeighbor(QgsPoint(8.75, 6.25), 3) myExpectedValue = 0 myValue = len(fids) myMessage = 'Expected: %s Got: %s' % (myExpectedValue, myValue) fids.sort() myMessage = ('Expected: %s\nGot: %s\n' % ([0, 1, 5], fids)) assert fids == [0, 1, 5], myMessage
class TriangleMesh: # 0 - 3 # | / | # 1 - 2 def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.flen = 0 self.quadrangles = [] self.spatial_index = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments for y in range(y_segments): for x in range(x_segments): pt0 = QgsPoint(xmin + x * xres, ymax - y * yres) pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres) pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres) pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres) self._addQuadrangle(pt0, pt1, pt2, pt3) def _addQuadrangle(self, pt0, pt1, pt2, pt3): f = QgsFeature(self.flen) f.setGeometry(QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]])) self.quadrangles.append(f) self.spatial_index.insertFeature(f) self.flen += 1 def intersects(self, geom): for fid in self.spatial_index.intersects(geom.boundingBox()): quad = self.quadrangles[fid].geometry() if quad.intersects(geom): yield quad def splitPolygons(self, geom): for quad in self.intersects(geom): pts = quad.asPolygon()[0] tris = [[[pts[0], pts[1], pts[3], pts[0]]], [[pts[3], pts[1], pts[2], pts[3]]]] if geom.contains(quad): yield tris[0] yield tris[1] else: for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)): if geom.contains(tri): yield tris[i] elif geom.intersects(tri): poly = geom.intersection(tri) if poly.isMultipart(): for sp in poly.asMultiPolygon(): yield sp else: yield poly.asPolygon()
def compute_graph(features, feedback, create_id_graph=False, min_distance=0): """ compute topology from a layer/field """ s = Graph(sort_graph=False) id_graph = None if create_id_graph: id_graph = Graph(sort_graph=True) # skip features without geometry features_with_geometry = {f_id: f for (f_id, f) in features.items() if f.hasGeometry()} total = 70.0 / len(features_with_geometry) if features_with_geometry else 1 index = QgsSpatialIndex() i = 0 for feature_id, f in features_with_geometry.items(): if feedback.isCanceled(): break g = f.geometry() if min_distance > 0: g = g.buffer(min_distance, 5) engine = QgsGeometry.createGeometryEngine(g.constGet()) engine.prepareGeometry() feature_bounds = g.boundingBox() # grow bounds a little so we get touching features feature_bounds.grow(feature_bounds.width() * 0.01) intersections = index.intersects(feature_bounds) for l2 in intersections: f2 = features_with_geometry[l2] if engine.intersects(f2.geometry().constGet()): s.add_edge(f.id(), f2.id()) s.add_edge(f2.id(), f.id()) if id_graph: id_graph.add_edge(f.id(), f2.id()) index.insertFeature(f) i += 1 feedback.setProgress(int(i * total)) for feature_id, f in features_with_geometry.items(): if feedback.isCanceled(): break if feature_id not in s.node_edge: s.add_edge(feature_id, None) return s, id_graph
class LayerIndex(object): """Check an intersection between a QgsGeometry and a QgsVectorLayer.""" def __init__(self, layer): self.__layer = layer if QGis.QGIS_VERSION_INT >= 20700: self.__index = QgsSpatialIndex(layer.getFeatures()) else: self.__index = QgsSpatialIndex() for ft in layer.getFeatures(): self.__index.insertFeature(ft) def contains(self, point): """Return true if the point intersects the layer.""" intersects = self.__index.intersects(point.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() if point.intersects(QgsGeometry(feat.geometry())): return True return False def count_intersection(self, buffer_geom, nb): """Return true if the buffer intersects enough entities.""" count = 0 intersects = self.__index.intersects(buffer_geom.boundingBox()) for i in intersects: request = QgsFeatureRequest().setFilterFid(i) feat = self.__layer.getFeatures(request).next() if buffer_geom.intersects(QgsGeometry(feat.geometry())): count += 1 if count >= nb: return True return False
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, sourceA.fields(), geomType, sourceA.sourceCrs()) featB = QgsFeature() outFeat = QgsFeature() indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break if featA.hasGeometry(): geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) else: sink.addFeature(featA, QgsFeatureSink.FastInsert) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): fileName = self.getParameterValue(self.INPUT) layer = dataobjects.getObjectFromUri(fileName) fieldName = self.getParameterValue(self.FIELD) value = self.getParameterValue(self.VALUE) selected = layer.selectedFeaturesIds() if len(selected) == 0: GeoAlgorithmExecutionException( self.tr('There is no selection in the input layer. ' 'Select one feature and try again.')) ft = layer.selectedFeatures()[0] geom = ft.geometry() attrSum = ft[fieldName] idx = QgsSpatialIndex(layer.getFeatures(QgsFeatureRequest.setSubsetOfAttributes([]))) req = QgsFeatureRequest() completed = False while not completed: intersected = idx.intersects(geom.boundingBox()) if len(intersected) < 0: progress.setInfo(self.tr('No adjacent features found.')) break req = QgsFeatureRequest().setFilterFids(intersected).setSubsetOfAttributes([fieldName], layer.fields()) for ft in layer.getFeatures(req): tmpGeom = ft.geometry() if tmpGeom.touches(geom): geom = tmpGeom.combine(geom) selected.append(i) attrSum += ft[fieldName] if attrSum >= value: completed = True break layer.selectByIds(selected) self.setOutputValue(self.OUTPUT, fileName)
def spaced(bar,buildings_layer_path,receiver_points_layer_path,spaced_pts_distance): distance_from_facades = 0.1 buildings_layer_name = os.path.splitext(os.path.basename(buildings_layer_path))[0] buildings_layer = QgsVectorLayer(buildings_layer_path,buildings_layer_name,"ogr") # cp building layer to delete all fields buildings_memory_layer = QgsVectorLayer("Polygon?crs=" + str(buildings_layer.crs().authid()), "polygon_memory_layer", "memory") buildings_memory_layer.dataProvider().addAttributes([]) buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_feat_list = [] for buildings_feat in buildings_feat_all: buildings_feat_list.append(buildings_feat) buildings_memory_layer.dataProvider().addFeatures(buildings_feat_list) buildings_memory_layer.updateExtents() # this is crazy: I had to addd this line otherwise the first processing doesn't work... QgsProject.instance().addMapLayers([buildings_memory_layer]) bar.setValue(1) # this processing alg has as output['OUTPUT'] the layer output = processing.run("native:buffer", {'INPUT': buildings_memory_layer, 'DISTANCE': distance_from_facades, 'DISSOLVE': False, 'OUTPUT': 'memory:'}) # I can now remove the layer from map... QgsProject.instance().removeMapLayers( [buildings_memory_layer.id()] ) bar.setValue(25) # this processing alg has as output['OUTPUT'] the layer output = processing.run("qgis:polygonstolines", {'INPUT': output['OUTPUT'], 'OUTPUT': 'memory:'}) bar.setValue(50) # this processing alg has as output['output'] the layer path... poly_to_lines = output['OUTPUT'] output = processing.run("qgis:pointsalonglines", {'INPUT': poly_to_lines, 'DISTANCE': spaced_pts_distance, 'START_OFFSET': 0, 'END_OFFSET': 0, 'OUTPUT': 'memory:'}) bar.setValue(75) receiver_points_memory_layer = output['OUTPUT'] del output ## Delete pts in buildings # creates SpatialIndex buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_spIndex = QgsSpatialIndex() buildings_feat_all_dict = {} for buildings_feat in buildings_feat_all: buildings_spIndex.insertFeature(buildings_feat) buildings_feat_all_dict[buildings_feat.id()] = buildings_feat receiver_points_memory_layer_all = receiver_points_memory_layer.dataProvider().getFeatures() receiver_points_layer_fields = QgsFields() receiver_points_layer_fields.append(QgsField("id_pt", QVariant.Int)) receiver_points_layer_fields.append(QgsField("id_bui", QVariant.Int)) receiver_points_layer_writer = QgsVectorFileWriter(receiver_points_layer_path, "System", receiver_points_layer_fields, QgsWkbTypes.Point, buildings_layer.crs(), "ESRI Shapefile") receiver_points_feat_id = 0 receiver_memory_feat_total = receiver_points_memory_layer.dataProvider().featureCount() receiver_memory_feat_number = 0 for receiver_memory_feat in receiver_points_memory_layer_all: receiver_memory_feat_number = receiver_memory_feat_number + 1 barValue = receiver_memory_feat_number/float(receiver_memory_feat_total)*25 + 75 bar.setValue(barValue) rect = QgsRectangle() rect.setXMinimum(receiver_memory_feat.geometry().asPoint().x() - distance_from_facades) rect.setXMaximum(receiver_memory_feat.geometry().asPoint().x() + distance_from_facades) rect.setYMinimum(receiver_memory_feat.geometry().asPoint().y() - distance_from_facades) rect.setYMaximum(receiver_memory_feat.geometry().asPoint().y() + distance_from_facades) buildings_selection = buildings_spIndex.intersects(rect) to_add = True receiver_geom = receiver_memory_feat.geometry() building_id_correct = None for buildings_id in buildings_selection: building_geom = buildings_feat_all_dict[buildings_id].geometry() intersectBuilding = QgsGeometry.intersects(receiver_geom, building_geom) building_id_correct = buildings_id if intersectBuilding: to_add = False building_id_correct = None break # picking the nearest building to the receiver point analysed nearestIds = buildings_spIndex.nearestNeighbor(receiver_geom.asPoint(), 1) building_fid = [] for featureId in nearestIds: request = QgsFeatureRequest().setFilterFid(featureId) for feature in buildings_layer.getFeatures(request): dist = receiver_geom.distance(feature.geometry()) building_fid.append((dist, feature.id())) building_fid_correct = min(building_fid, key=lambda x: x[0])[-1] if to_add: attributes = [receiver_points_feat_id, building_fid_correct] fet = QgsFeature() fet.setGeometry(receiver_memory_feat.geometry()) fet.setAttributes(attributes) receiver_points_layer_writer.addFeature(fet) receiver_points_feat_id = receiver_points_feat_id + 1 del receiver_points_layer_writer receiver_points_layer_name = os.path.splitext(os.path.basename(receiver_points_layer_path))[0] receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr") QgsProject.instance().addMapLayers([receiver_points_layer]) QgsProject.instance().reloadAllLayers()
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.INTERSECT, context) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] fieldListB = vector.testForUniqueness(fieldListA, fieldListB) for b in fieldListB: fieldListA.append(b) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fieldListA, QgsWkbTypes.Point, sourceA.sourceCrs()) spatialIndex = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) outFeat = QgsFeature() features = sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0 for current, inFeatA in enumerate(features): if feedback.isCanceled(): break if not inFeatA.hasGeometry(): continue inGeom = inFeatA.geometry() has_intersections = False lines = spatialIndex.intersects(inGeom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids(lines) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) for inFeatB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = inFeatB.geometry() points = [] if engine.intersects(tmpGeom.geometry()): tempGeom = inGeom.intersection(tmpGeom) out_attributes = [inFeatA.attributes()[i] for i in field_indices_a] out_attributes.extend([inFeatB.attributes()[i] for i in field_indices_b]) if tempGeom.type() == QgsWkbTypes.PointGeometry: if tempGeom.isMultipart(): points = tempGeom.asMultiPoint() else: points.append(tempGeom.asPoint()) for j in points: outFeat.setGeometry(tempGeom.fromPoint(j)) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
class Serval(object): LINE_SELECTION = "line" POLYGON_SELECTION = "polygon" RGB = "RGB" SINGLE_BAND = "Single band" def __init__(self, iface): self.iface = iface self.canvas = self.iface.mapCanvas() self.plugin_dir = os.path.dirname(__file__) self.uc = UserCommunication(iface, 'Serval') self.load_settings() self.raster = None self.handler = None self.spin_boxes = None self.exp_dlg = None self.exp_builder = None self.block_pts_layer = None self.px, self.py = [0, 0] self.last_point = QgsPointXY(0, 0) self.rbounds = None self.changes = dict() # dict with rasters changes {raster_id: RasterChanges instance} self.project = QgsProject.instance() self.crs_transform = None self.all_touched = None self.selection_mode = None self.spatial_index_time = dict() # {layer_id: creation time} self.spatial_index = dict() # {layer_id: spatial index} self.selection_layers_count = 1 self.debug = DEBUG self.logger = get_logger() if self.debug else None self.menu = u'Serval' self.actions = [] self.actions_always_on = [] self.toolbar = self.iface.addToolBar(u'Serval Main Toolbar') self.toolbar.setObjectName(u'Serval Main Toolbar') self.toolbar.setToolTip(u'Serval Main Toolbar') self.sel_toolbar = self.iface.addToolBar(u'Serval Selection Toolbar') self.sel_toolbar.setObjectName(u'Serval Selection Toolbar') self.sel_toolbar.setToolTip(u'Serval Selection Toolbar') # Map tools self.probe_tool = QgsMapToolEmitPoint(self.canvas) self.probe_tool.setObjectName('ServalProbeTool') self.probe_tool.setCursor(QCursor(QPixmap(icon_path('probe_tool.svg')), hotX=2, hotY=22)) self.probe_tool.canvasClicked.connect(self.point_clicked) self.draw_tool = QgsMapToolEmitPoint(self.canvas) self.draw_tool.setObjectName('ServalDrawTool') self.draw_tool.setCursor(QCursor(QPixmap(icon_path('draw_tool.svg')), hotX=2, hotY=22)) self.draw_tool.canvasClicked.connect(self.point_clicked) self.selection_tool = RasterCellSelectionMapTool(self.iface, self.uc, self.raster, debug=self.debug) self.selection_tool.setObjectName('RasterSelectionTool') self.map_tool_btn = dict() # {map tool: button activating the tool} self.iface.currentLayerChanged.connect(self.set_active_raster) self.project.layersAdded.connect(self.set_active_raster) self.canvas.mapToolSet.connect(self.check_active_tool) self.register_exp_functions() def load_settings(self): """Return plugin settings dict - default values are overriden by user prefered values from QSettings.""" self.default_settings = { "undo_steps": {"value": 3, "vtype": int}, } self.settings = dict() s = QSettings() s.beginGroup("serval") for k, v in self.default_settings.items(): user_val = s.value(k, v["value"], v["vtype"]) self.settings[k] = user_val def edit_settings(self): """Open dialog with plugin settings.""" s = QSettings() s.beginGroup("serval") k = "undo_steps" cur_val = self.settings[k] val_type = self.default_settings[k]["vtype"] cur_steps = s.value(k, cur_val, val_type) label = 'Nr of Undo/Redo steps:' steps, ok = QInputDialog.getInt(None, "Serval Settings", label, cur_steps) if not ok: return if steps >= 0: s.setValue("undo_steps", steps) self.load_settings() self.uc.show_info("Some new settings may require QGIS restart.") def initGui(self): _ = self.add_action( 'serval_icon.svg', text=u'Show Serval Toolbars', add_to_menu=True, callback=self.show_toolbar, always_on=True, ) _ = self.add_action( 'serval_icon.svg', text=u'Hide Serval Toolbars', add_to_menu=True, callback=self.hide_toolbar, always_on=True, ) self.probe_btn = self.add_action( 'probe.svg', text="Probe raster", callback=self.activate_probing, add_to_toolbar=self.toolbar, checkable=True, ) self.map_tool_btn[self.probe_tool] = self.probe_btn self.color_btn = QgsColorButton() self.color_btn.setColor(Qt.gray) self.color_btn.setMinimumSize(QSize(40, 24)) self.color_btn.setMaximumSize(QSize(40, 24)) self.toolbar.addWidget(self.color_btn) self.color_picker_connection(connect=True) self.color_btn.setDisabled(True) self.toolbar.addWidget(QLabel("Band:")) self.bands_cbo = QComboBox() self.bands_cbo.addItem("1", 1) self.toolbar.addWidget(self.bands_cbo) self.bands_cbo.currentIndexChanged.connect(self.update_active_bands) self.bands_cbo.setDisabled(True) self.spin_boxes = BandBoxes() self.toolbar.addWidget(self.spin_boxes) self.spin_boxes.enter_hit.connect(self.apply_spin_box_values) self.draw_btn = self.add_action( 'draw.svg', text="Apply Value(s) To Single Cell", callback=self.activate_drawing, add_to_toolbar=self.toolbar, checkable=True, ) self.map_tool_btn[self.draw_tool] = self.draw_btn self.apply_spin_box_values_btn = self.add_action( 'apply_const_value.svg', text="Apply Value(s) to Selection", callback=self.apply_spin_box_values, add_to_toolbar=self.toolbar, ) self.gom_btn = self.add_action( 'apply_nodata_value.svg', text="Apply NoData to Selection", callback=self.apply_nodata_value, add_to_toolbar=self.toolbar, ) self.exp_dlg_btn = self.add_action( 'apply_expression_value.svg', text="Apply Expression Value To Selection", callback=self.define_expression, add_to_toolbar=self.toolbar, checkable=False, ) self.low_pass_filter_btn = self.add_action( 'apply_low_pass_filter.svg', text="Apply Low-Pass 3x3 Filter To Selection", callback=self.apply_low_pass_filter, add_to_toolbar=self.toolbar, checkable=False, ) self.undo_btn = self.add_action( 'undo.svg', text="Undo", callback=self.undo, add_to_toolbar=self.toolbar, ) self.redo_btn = self.add_action( 'redo.svg', text="Redo", callback=self.redo, add_to_toolbar=self.toolbar, ) self.set_nodata_btn = self.add_action( 'set_nodata.svg', text="Edit Raster NoData Values", callback=self.set_nodata, add_to_toolbar=self.toolbar, ) self.settings_btn = self.add_action( 'edit_settings.svg', text="Serval Settings", callback=self.edit_settings, add_to_toolbar=self.toolbar, always_on=True, ) self.show_help = self.add_action( 'help.svg', text="Help", add_to_menu=True, callback=self.show_website, add_to_toolbar=self.toolbar, always_on=True, ) # Selection Toolbar line_width_icon = QIcon(icon_path("line_width.svg")) line_width_lab = QLabel() line_width_lab.setPixmap(line_width_icon.pixmap(22, 12)) self.sel_toolbar.addWidget(line_width_lab) self.line_width_sbox = QgsDoubleSpinBox() self.line_width_sbox.setMinimumSize(QSize(50, 24)) self.line_width_sbox.setMaximumSize(QSize(50, 24)) # self.line_width_sbox.setButtonSymbols(QAbstractSpinBox.NoButtons) self.line_width_sbox.setValue(1) self.line_width_sbox.setMinimum(0.01) self.line_width_sbox.setShowClearButton(False) self.line_width_sbox.setToolTip("Selection Line Width") self.line_width_sbox.valueChanged.connect(self.update_selection_tool) self.width_unit_cbo = QComboBox() self.width_units = ("map units", "pixel width", "pixel height", "hairline",) for u in self.width_units: self.width_unit_cbo.addItem(u) self.width_unit_cbo.setToolTip("Selection Line Width Unit") self.sel_toolbar.addWidget(self.line_width_sbox) self.sel_toolbar.addWidget(self.width_unit_cbo) self.width_unit_cbo.currentIndexChanged.connect(self.update_selection_tool) self.line_select_btn = self.add_action( 'select_line.svg', text="Select Raster Cells by Line", callback=self.activate_line_selection, add_to_toolbar=self.sel_toolbar, checkable=True, ) self.polygon_select_btn = self.add_action( 'select_polygon.svg', text="Select Raster Cells by Polygon", callback=self.activate_polygon_selection, add_to_toolbar=self.sel_toolbar, checkable=True, ) self.selection_from_layer_btn = self.add_action( 'select_from_layer.svg', text="Create Selection From Layer", callback=self.selection_from_layer, add_to_toolbar=self.sel_toolbar, ) self.selection_to_layer_btn = self.add_action( 'selection_to_layer.svg', text="Create Memory Layer From Selection", callback=self.selection_to_layer, add_to_toolbar=self.sel_toolbar, ) self.clear_selection_btn = self.add_action( 'clear_selection.svg', text="Clear selection", callback=self.clear_selection, add_to_toolbar=self.sel_toolbar, ) self.toggle_all_touched_btn = self.add_action( 'all_touched.svg', text="Toggle All Touched Get Selected", callback=self.toggle_all_touched, checkable=True, checked=True, add_to_toolbar=self.sel_toolbar, ) self.all_touched = True self.enable_toolbar_actions(enable=False) self.check_undo_redo_btns() def add_action(self, icon_name, callback=None, text="", enabled_flag=True, add_to_menu=False, add_to_toolbar=None, status_tip=None, whats_this=None, checkable=False, checked=False, always_on=False): icon = QIcon(icon_path(icon_name)) action = QAction(icon, text, self.iface.mainWindow()) action.triggered.connect(callback) action.setEnabled(enabled_flag) action.setCheckable(checkable) action.setChecked(checked) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar is not None: add_to_toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) if always_on: self.actions_always_on.append(action) return action def unload(self): self.changes = None if self.selection_tool: self.selection_tool.reset() if self.spin_boxes is not None: self.spin_boxes.remove_spinboxes() for action in self.actions: self.iface.removePluginMenu('Serval', action) self.iface.removeToolBarIcon(action) del self.toolbar del self.sel_toolbar self.iface.actionPan().trigger() self.unregister_exp_functions() def show_toolbar(self): if self.toolbar: self.toolbar.show() self.sel_toolbar.show() def hide_toolbar(self): if self.toolbar: self.toolbar.hide() self.sel_toolbar.hide() @staticmethod def register_exp_functions(): QgsExpression.registerFunction(nearest_feature_attr_value) QgsExpression.registerFunction(nearest_pt_on_line_interpolate_z) QgsExpression.registerFunction(intersecting_features_attr_average) QgsExpression.registerFunction(interpolate_from_mesh) @staticmethod def unregister_exp_functions(): QgsExpression.unregisterFunction('nearest_feature_attr_value') QgsExpression.unregisterFunction('nearest_pt_on_line_interpolate_z') QgsExpression.unregisterFunction('intersecting_features_attr_average') QgsExpression.unregisterFunction('interpolate_from_mesh') def uncheck_all_btns(self): self.probe_btn.setChecked(False) self.draw_btn.setChecked(False) self.gom_btn.setChecked(False) self.line_select_btn.setChecked(False) self.polygon_select_btn.setChecked(False) def check_active_tool(self, cur_tool): self.uncheck_all_btns() if cur_tool in self.map_tool_btn: self.map_tool_btn[cur_tool].setChecked(True) if cur_tool == self.selection_tool: if self.selection_mode == self.LINE_SELECTION: self.line_select_btn.setChecked(True) else: self.polygon_select_btn.setChecked(True) def activate_probing(self): self.mode = 'probe' self.canvas.setMapTool(self.probe_tool) def define_expression(self): if not self.selection_tool.selected_geometries: self.uc.bar_warn("No selection for raster layer. Select some cells and retry...") return self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.create_cell_pts_layer() if self.handler.cell_pts_layer.featureCount() == 0: self.uc.bar_warn("No selection for raster layer. Select some cells and retry...") return self.exp_dlg = QgsExpressionBuilderDialog(self.handler.cell_pts_layer) self.exp_builder = self.exp_dlg.expressionBuilder() self.exp_dlg.accepted.connect(self.apply_exp_value) self.exp_dlg.show() def apply_exp_value(self): if not self.exp_dlg.expressionText() or not self.exp_builder.isExpressionValid(): return QApplication.setOverrideCursor(Qt.WaitCursor) exp = self.exp_dlg.expressionText() idx = self.handler.cell_pts_layer.addExpressionField(exp, QgsField('exp_val', QVariant.Double)) self.handler.exp_field_idx = idx self.handler.write_block() QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def activate_drawing(self): self.mode = 'draw' self.canvas.setMapTool(self.draw_tool) def get_cur_line_width(self): width_coef = { "map units": 1., "pixel width": self.raster.rasterUnitsPerPixelX(), "pixel height": self.raster.rasterUnitsPerPixelY(), "hairline": 0.000001, } return self.line_width_sbox.value() * width_coef[self.width_unit_cbo.currentText()] def set_selection_tool(self, mode): if self.raster is None: self.uc.bar_warn("Select a raster layer") return self.selection_mode = mode self.selection_tool.init_tool(self.raster, mode=self.selection_mode, line_width=self.get_cur_line_width()) self.selection_tool.set_prev_tool(self.canvas.mapTool()) self.canvas.setMapTool(self.selection_tool) def activate_line_selection(self): self.set_selection_tool(self.LINE_SELECTION) def activate_polygon_selection(self): self.set_selection_tool(self.POLYGON_SELECTION) def update_selection_tool(self): """Reactivate the selection tool with updated line width and units.""" if self.selection_mode == self.LINE_SELECTION: self.activate_line_selection() elif self.selection_mode == self.POLYGON_SELECTION: self.activate_polygon_selection() else: pass def apply_values(self, new_values): QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.write_block(new_values) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def apply_values_single_cell(self, new_vals): """Create single cell selection and apply the new values.""" cp = self.last_point if self.logger: self.logger.debug(f"Changing single cell for pt {cp}") col, row = self.handler.point_to_index([cp.x(), cp.y()]) px, py = self.handler.index_to_point(row, col, upper_left=False) d = 0.001 bbox = QgsRectangle(px - d, py - d, px + d, py + d) if self.logger: self.logger.debug(f"Changing single cell in {bbox}") QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select([QgsGeometry.fromRect(bbox)], all_touched_cells=False, transform=False) self.handler.write_block(new_vals) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def apply_spin_box_values(self): if not self.selection_tool.selected_geometries: return self.apply_values(self.spin_boxes.get_values()) def apply_nodata_value(self): if not self.selection_tool.selected_geometries: return self.apply_values(self.handler.nodata_values) def apply_low_pass_filter(self): QApplication.setOverrideCursor(Qt.WaitCursor) self.handler.select(self.selection_tool.selected_geometries, all_touched_cells=self.all_touched) self.handler.write_block(low_pass_filter=True) QApplication.restoreOverrideCursor() self.raster.triggerRepaint() def clear_selection(self): if self.selection_tool: self.selection_tool.clear_all_selections() def selection_from_layer(self): """Create a new selection from layer.""" self.selection_tool.init_tool(self.raster, mode=self.POLYGON_SELECTION, line_width=self.get_cur_line_width()) dlg = LayerSelectDialog() if not dlg.exec_(): return cur_layer = dlg.cbo.currentLayer() if not cur_layer.type() == QgsMapLayerType.VectorLayer: return self.selection_tool.selection_from_layer(cur_layer) def selection_to_layer(self): """Create a memory layer from current selection""" geoms = self.selection_tool.selected_geometries if geoms is None or not self.raster: return crs_str = self.raster.crs().toProj() nr = self.selection_layers_count self.selection_layers_count += 1 mlayer = QgsVectorLayer(f"Polygon?crs={crs_str}&field=fid:int", f"Raster selection {nr}", "memory") fields = mlayer.dataProvider().fields() features = [] for i, geom in enumerate(geoms): feat = QgsFeature(fields) feat["fid"] = i + 1 feat.setGeometry(geom) features.append(feat) mlayer.dataProvider().addFeatures(features) self.project.addMapLayer(mlayer) def toggle_all_touched(self): """Toggle selection mode.""" # button is toggled automatically when clicked, just update the attribute self.all_touched = self.toggle_all_touched_btn.isChecked() def point_clicked(self, point=None, button=None): if self.raster is None: self.uc.bar_warn("Choose a raster to work with...", dur=3) return if self.logger: self.logger.debug(f"Clicked point in canvas CRS: {point if point else self.last_point}") if point is None: ptxy_in_src_crs = self.last_point else: if self.crs_transform: if self.logger: self.logger.debug(f"Transforming clicked point {point}") try: ptxy_in_src_crs = self.crs_transform.transform(point) except QgsCsException as err: self.uc.show_warn( "Point coordinates transformation failed! Check the raster projection:\n\n{}".format(repr(err))) return else: ptxy_in_src_crs = QgsPointXY(point.x(), point.y()) if self.logger: self.logger.debug(f"Clicked point in raster CRS: {ptxy_in_src_crs}") self.last_point = ptxy_in_src_crs ident_vals = self.handler.provider.identify(ptxy_in_src_crs, QgsRaster.IdentifyFormatValue).results() cur_vals = list(ident_vals.values()) # check if the point is within active raster extent if not self.rbounds[0] <= ptxy_in_src_crs.x() <= self.rbounds[2]: self.uc.bar_info("Out of x bounds", dur=3) return if not self.rbounds[1] <= ptxy_in_src_crs.y() <= self.rbounds[3]: self.uc.bar_info("Out of y bounds", dur=3) return if self.mode == 'draw': new_vals = self.spin_boxes.get_values() if self.logger: self.logger.debug(f"Applying const value {new_vals}") self.apply_values_single_cell(new_vals) else: self.spin_boxes.set_values(cur_vals) if 2 < self.handler.bands_nr < 5: self.color_picker_connection(connect=False) self.color_btn.setColor(QColor(*self.spin_boxes.get_values()[:4])) self.color_picker_connection(connect=True) def set_values_from_picker(self, c): """Set bands spinboxes values after color change in the color picker""" values = None if self.handler.bands_nr > 2: values = [c.red(), c.green(), c.blue()] if self.handler.bands_nr == 4: values.append(c.alpha()) if values: self.spin_boxes.set_values(values) def set_nodata(self): """Set NoData value(s) for each band of current raster.""" if not self.raster: self.uc.bar_warn('Select a raster layer to define/change NoData value!') return if self.handler.provider.userNoDataValues(1): note = '\nNote: there is a user defined NODATA value.\nCheck the raster properties (Transparency).' else: note = '' dt = self.handler.provider.dataType(1) # current NODATA value if self.handler.provider.sourceHasNoDataValue(1): cur_nodata = self.handler.provider.sourceNoDataValue(1) if dt < 6: cur_nodata = '{0:d}'.format(int(float(cur_nodata))) else: cur_nodata = '' label = 'Define/change raster NODATA value.\n\n' label += 'Raster src_data type: {}.{}'.format(dtypes[dt]['name'], note) nd, ok = QInputDialog.getText(None, "Define NODATA Value", label, QLineEdit.Normal, str(cur_nodata)) if not ok: return if not is_number(nd): self.uc.show_warn('Wrong NODATA value!') return new_nodata = int(nd) if dt < 6 else float(nd) # set the NODATA value for each band res = [] for nr in self.handler.bands_range: res.append(self.handler.provider.setNoDataValue(nr, new_nodata)) self.handler.provider.sourceHasNoDataValue(nr) if False in res: self.uc.show_warn('Setting new NODATA value failed!') else: self.uc.bar_info('Successful setting new NODATA values!', dur=2) self.set_active_raster() self.raster.triggerRepaint() def check_undo_redo_btns(self): """Enable/Disable undo and redo buttons based on availability of undo/redo for current raster.""" self.undo_btn.setDisabled(True) self.redo_btn.setDisabled(True) if self.raster is None or self.raster.id() not in self.changes: return changes = self.changes[self.raster.id()] if changes.nr_undos() > 0: self.undo_btn.setEnabled(True) if changes.nr_redos() > 0: self.redo_btn.setEnabled(True) def enable_toolbar_actions(self, enable=True): """Enable / disable all toolbar actions but Help (for vectors and unsupported rasters)""" for widget in self.actions + [self.width_unit_cbo, self.line_width_sbox]: widget.setEnabled(enable) if widget in self.actions_always_on: widget.setEnabled(True) self.spin_boxes.enable(enable) @staticmethod def check_layer(layer): """Check if we can work with the raster""" if layer is None: return False if layer.type() != QgsMapLayerType.RasterLayer: return False if layer.providerType() != 'gdal': return False if all([ layer.isValid(), layer.crs() is not None, check_gdal_driver_create_option(layer), # GDAL driver has CREATE option os.path.isfile(layer.dataProvider().dataSourceUri()), # is it a local file? ]): return True else: return False def set_bands_cbo(self): self.bands_cbo.currentIndexChanged.disconnect(self.update_active_bands) self.bands_cbo.clear() for band in self.handler.bands_range: self.bands_cbo.addItem(f"{band}", [band]) if self.handler.bands_nr > 1: self.bands_cbo.addItem(self.RGB, [1, 2, 3]) self.bands_cbo.setCurrentIndex(0) self.bands_cbo.currentIndexChanged.connect(self.update_active_bands) def update_active_bands(self, idx): bands = self.bands_cbo.currentData() self.handler.active_bands = bands self.spin_boxes.create_spinboxes(bands, self.handler.data_types, self.handler.nodata_values) self.color_btn.setEnabled(len(bands) > 1) self.exp_dlg_btn.setEnabled(len(bands) == 1) def set_active_raster(self): """Active layer has changed - check if it is a raster layer and prepare it for the plugin""" old_spin_boxes_values = self.spin_boxes.get_values() self.crs_transform = None layer = self.iface.activeLayer() if self.check_layer(layer): self.raster = layer self.crs_transform = None if self.project.crs() == self.raster.crs() else \ QgsCoordinateTransform(self.project.crs(), self.raster.crs(), self.project) self.handler = RasterHandler(self.raster, self.uc, self.debug) supported, unsupported_type = self.handler.write_supported() if supported: self.enable_toolbar_actions() self.set_bands_cbo() self.spin_boxes.create_spinboxes(self.handler.active_bands, self.handler.data_types, self.handler.nodata_values) if self.handler.bands_nr == len(old_spin_boxes_values): self.spin_boxes.set_values(old_spin_boxes_values) self.bands_cbo.setEnabled(self.handler.bands_nr > 1) self.color_btn.setEnabled(len(self.handler.active_bands) > 1) self.rbounds = self.raster.extent().toRectF().getCoords() self.handler.raster_changed.connect(self.add_to_undo) if self.raster.id() not in self.changes: self.changes[self.raster.id()] = RasterChanges(nr_to_keep=self.settings["undo_steps"]) else: msg = f"The raster has unsupported src_data type: {unsupported_type}" msg += "\nServal can't work with it, sorry..." self.uc.show_warn(msg) self.enable_toolbar_actions(enable=False) self.reset_raster() else: # unsupported raster self.enable_toolbar_actions(enable=False) self.reset_raster() self.check_undo_redo_btns() def add_to_undo(self, change): """Add the old and new blocks to undo stack.""" self.changes[self.raster.id()].add_change(change) self.check_undo_redo_btns() if self.logger: self.logger.debug(self.get_undo_redo_values()) def get_undo_redo_values(self): changes = self.changes[self.raster.id()] return f"nr undos: {changes.nr_undos()}, redos: {changes.nr_redos()}" def undo(self): undo_data = self.changes[self.raster.id()].undo() self.handler.write_block_undo(undo_data) self.raster.triggerRepaint() self.check_undo_redo_btns() def redo(self): redo_data = self.changes[self.raster.id()].redo() self.handler.write_block_undo(redo_data) self.raster.triggerRepaint() self.check_undo_redo_btns() def reset_raster(self): self.raster = None self.color_btn.setDisabled(True) def color_picker_connection(self, connect=True): if connect: self.color_btn.colorChanged.connect(self.set_values_from_picker) else: self.color_btn.colorChanged.disconnect(self.set_values_from_picker) @staticmethod def show_website(): QDesktopServices.openUrl(QUrl("https://github.com/lutraconsulting/serval/blob/master/Serval/docs/user_manual.md")) def recreate_spatial_index(self, layer): """Check if spatial index exists for the layer and if it is relatively old and eventually recreate it.""" ctime = self.spatial_index_time[layer.id()] if layer.id() in self.spatial_index_time else None if ctime is None or datetime.now() - ctime > timedelta(seconds=30): self.spatial_index = QgsSpatialIndex(layer.getFeatures(), None, QgsSpatialIndex.FlagStoreFeatureGeometries) self.spatial_index_time[layer.id()] = datetime.now() def get_nearest_feature(self, pt_feat, vlayer_id): """Given the point feature, return nearest feature from vlayer.""" vlayer = self.project.mapLayer(vlayer_id) self.recreate_spatial_index(vlayer) ptxy = pt_feat.geometry().asPoint() near_fid = self.spatial_index.nearestNeighbor(ptxy)[0] return vlayer.getFeature(near_fid) def nearest_feature_attr_value(self, pt_feat, vlayer_id, attr_name): """Find nearest feature to pt_feat and return its attr_name attribute value.""" near_feat = self.get_nearest_feature(pt_feat, vlayer_id) return near_feat[attr_name] def nearest_pt_on_line_interpolate_z(self, pt_feat, vlayer_id): """Find nearest line feature to pt_feat and interpolate z value from vertices.""" near_feat = self.get_nearest_feature(pt_feat, vlayer_id) near_geom = near_feat.geometry() closest_pt_dist = near_geom.lineLocatePoint(pt_feat.geometry()) closest_pt = near_geom.interpolate(closest_pt_dist) return closest_pt.get().z() def intersecting_features_attr_average(self, pt_feat, vlayer_id, attr_name, only_center): """ Find all features intersecting current feature (cell center, or raster cell polygon) and calculate average value of their attr_name attribute. """ vlayer = self.project.mapLayer(vlayer_id) self.recreate_spatial_index(vlayer) ptxy = pt_feat.geometry().asPoint() pt_x, pt_y = ptxy.x(), ptxy.y() dxy = 0.001 half_pix_x = self.handler.pixel_size_x / 2. half_pix_y = self.handler.pixel_size_y / 2. if only_center: cell = QgsRectangle(pt_x, pt_y, pt_x + dxy, pt_y + dxy) else: cell = QgsRectangle(pt_x - half_pix_x, pt_y - half_pix_y, pt_x + half_pix_x, pt_y + half_pix_y) inter_fids = self.spatial_index.intersects(cell) values = [] for fid in inter_fids: feat = vlayer.getFeature(fid) if not feat.geometry().intersects(cell): continue val = feat[attr_name] if not is_number(val): continue values.append(val) if len(values) == 0: return None return sum(values) / float(len(values)) def interpolate_from_mesh(self, pt_feat, mesh_layer_id, group, dataset, above_existing): """Interpolate from mesh.""" mesh_layer = self.project.mapLayer(mesh_layer_id) ptxy = pt_feat.geometry().asPoint() dataset_val = mesh_layer.datasetValue(QgsMeshDatasetIndex(group, dataset), ptxy) val = dataset_val.scalar() if math.isnan(val): return val if above_existing: ident_vals = self.handler.provider.identify(ptxy, QgsRaster.IdentifyFormatValue).results() org_val = list(ident_vals.values())[0] if org_val == self.handler.nodata_values[0]: return val return max(org_val, val) else: return val
def get_pair_boundary_plot(self, boundary_layer, plot_layer, id_field, use_selection=True): id_field_idx = plot_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) polygons = plot_layer.getSelectedFeatures( request) if use_selection else plot_layer.getFeatures(request) intersect_more_pairs = list() intersect_less_pairs = list() if boundary_layer.featureCount() == 0: return (intersect_more_pairs, intersect_less_pairs) id_field_idx = boundary_layer.fields().indexFromName(id_field) request = QgsFeatureRequest().setSubsetOfAttributes([id_field_idx]) dict_features = { feature.id(): feature for feature in boundary_layer.getFeatures(request) } index = QgsSpatialIndex(boundary_layer) candidate_features = None for polygon in polygons: bbox = polygon.geometry().boundingBox() bbox.scale(1.001) candidates_ids = index.intersects(bbox) candidate_features = [ dict_features[candidate_id] for candidate_id in candidates_ids ] for candidate_feature in candidate_features: polygon_geom = polygon.geometry() is_multipart = polygon_geom.isMultipart() candidate_geometry = candidate_feature.geometry() if polygon_geom.intersects(candidate_geometry): # Does the current multipolygon have inner rings? has_inner_rings = False multi_polygon = None single_polygon = None if is_multipart: multi_polygon = polygon_geom.get() for part in range(multi_polygon.numGeometries()): if multi_polygon.ringCount(part) > 1: has_inner_rings = True break else: single_polygon = polygon_geom.get() if single_polygon.numInteriorRings() > 0: has_inner_rings = True # Now we'll test intersections against borders if has_inner_rings: # In this case we need to identify whether the # intersection is with outer rings (goes to MOREBFS # table) or with inner rings (goes to LESS table) multi_outer_rings = QgsMultiLineString() multi_inner_rings = QgsMultiLineString() if is_multipart and multi_polygon: for i in range(multi_polygon.numGeometries()): temp_polygon = multi_polygon.geometryN(i) multi_outer_rings.addGeometry( temp_polygon.exteriorRing().clone()) for j in range( temp_polygon.numInteriorRings()): multi_inner_rings.addGeometry( temp_polygon.interiorRing(j).clone()) elif not is_multipart and single_polygon: multi_outer_rings.addGeometry( single_polygon.exteriorRing().clone()) for j in range(single_polygon.numInteriorRings()): multi_inner_rings.addGeometry( single_polygon.interiorRing(j).clone()) intersection_type = QgsGeometry( multi_outer_rings).intersection( candidate_geometry).type() if intersection_type == QgsWkbTypes.LineGeometry: intersect_more_pairs.append( (polygon[id_field], candidate_feature[id_field])) else: self.logger.warning( __name__, "(MoreBFS) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}" .format(polygon[id_field], candidate_feature[id_field], intersection_type)) intersection_type = QgsGeometry( multi_inner_rings).intersection( candidate_geometry).type() if intersection_type == QgsWkbTypes.LineGeometry: intersect_less_pairs.append( (polygon[id_field], candidate_feature[id_field])) else: self.logger.warning( __name__, "(Less) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}" .format(polygon[id_field], candidate_feature[id_field], intersection_type)) else: boundary = None if is_multipart and multi_polygon: boundary = multi_polygon.boundary() elif not is_multipart and single_polygon: boundary = single_polygon.boundary() intersection_type = QgsGeometry(boundary).intersection( candidate_geometry).type() if boundary and intersection_type == QgsWkbTypes.LineGeometry: intersect_more_pairs.append( (polygon[id_field], candidate_feature[id_field])) else: self.logger.warning( __name__, "(MoreBFS) Intersection between plot (t_id={}) and boundary (t_id={}) is a geometry of type: {}" .format(polygon[id_field], candidate_feature[id_field], intersection_type)) # free up memory del candidate_features del dict_features gc.collect() return (intersect_more_pairs, intersect_less_pairs)
class DsgGeometrySnapper(QObject): SnappedToRefNode, SnappedToRefSegment, Unsnapped = range(3) PreferNodes, PreferClosest = range(2) featureSnapped = pyqtSignal() def __init__(self, referenceLayer): """ Constructor :param referenceLayer: QgsVectorLayer """ super(self.__class__,self).__init__() self.referenceLayer = referenceLayer # Build spatial index self.index = QgsSpatialIndex(self.referenceLayer.getFeatures()) def polyLineSize(self, geom, iPart, iRing): """ Gets the number of vertexes :param geom: QgsAbstractGeometryV2 :param iPart: int :param iRing: int :return: """ nVerts = geom.vertexCount( iPart, iRing) if isinstance(geom, QgsMultiPolygonV2) or isinstance(geom, QgsPolygonV2) or isinstance(geom, QgsCircularStringV2): front = geom.vertexAt(QgsVertexId( iPart, iRing, 0, QgsVertexId.SegmentVertex)) back = geom.vertexAt(QgsVertexId( iPart, iRing, nVerts - 1, QgsVertexId.SegmentVertex)) if front == back: return nVerts - 1 return nVerts def snapFeatures(self, features, snapTolerance, mode=PreferNodes): """ Snap features from a layer :param features: list of QgsFeatures :param snapTolerance: float :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest :return: """ for feature in features: self.processFeature(feature, snapTolerance, mode) self.featureSnapped.emit() return features def processFeature(self, feature, snapTolerance, mode): """ Process QgsFeature :param feature: QgsFeature :param snapTolerance: float :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest :return: """ if feature.geometry(): feature.setGeometry(self.snapGeometry(feature.geometry(), snapTolerance, mode)) def projPointOnSegment(self, p, s1, s2): """ p: QgsPointV2 s1: QgsPointV2 of segment s2: QgsPointV2 of segment """ nx = s2.y() - s1.y() ny = -( s2.x() - s1.x() ) a = ( p.x() * ny - p.y() * nx - s1.x() * ny + s1.y() * nx ) b = ( ( s2.x() - s1.x() ) * ny - ( s2.y() - s1.y() ) * nx ) if s1 == s2: return s1 t = a / b if t < 0.: return s1 elif t > 1.: return s2 else: return QgsPointV2( s1.x() + ( s2.x() - s1.x() ) * t, s1.y() + ( s2.y() - s1.y() ) * t ) def buildReferenceIndex(self, segments): refDict = {} index = QgsSpatialIndex() for i, segment in enumerate(segments): refDict[i] = segment feature = QgsFeature(i) feature.setGeometry(segment) index.insertFeature(feature) return refDict, index def segmentFromPoints(self, start, end): """ Makes a QgsGeometry from start and end points :param start: QgsPoint :param end: QgsPoint :return: """ return QgsGeometry.fromPolyline([start, end]) def breakQgsGeometryIntoSegments(self, geometry): """ Makes a list of QgsGeometry made with segments :param geometry: QgsGeometry :return: list of QgsGeometry """ segments = [] wbkType = geometry.wkbType() if wbkType == QGis.WKBPoint: return [geometry] elif wbkType == QGis.WKBMultiPoint: return [geometry] elif wbkType == QGis.WKBLineString: line = geometry.asPolyline() for i in xrange(len(line) - 1): segments.append(self.segmentFromPoints(line[i], line[i + 1])) elif wbkType == QGis.WKBMultiLineString: multiLine = geometry.asMultiPolyline() for j in xrange(len(multiLine)): line = multiLine[j] for i in xrange(len(line) - 1): segments.append(self.segmentFromPoints(line[i], line[i + 1])) elif wbkType == QGis.WKBPolygon: poly = geometry.asPolygon() for j in xrange(len(poly)): line = poly[j] for i in xrange(len(line) - 1): segments.append(self.segmentFromPoints(line[i], line[i + 1])) elif wbkType == QGis.WKBMultiPolygon: multiPoly = geometry.asMultiPolygon() for k in xrange(len(multiPoly)): poly = multiPoly[k] for j in xrange(len(poly)): line = poly[j] for i in xrange(len(line) - 1): segments.append(self.segmentFromPoints(line[i], line[i + 1])) return segments def snapGeometry(self, geometry, snapTolerance, mode=PreferNodes): """ Snaps a QgsGeometry in the reference layer :param geometry: QgsGeometry :param snapTolerance: float :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest :return: """ center = QgsPointV2(geometry.boundingBox().center()) # Get potential reference features and construct snap index refGeometries = [] searchBounds = geometry.boundingBox() searchBounds.grow(snapTolerance) # filter by bounding box to get candidates refFeatureIds = self.index.intersects(searchBounds) # End here in case we don't find candidates if len(refFeatureIds) == 0: return geometry # speeding up the process to consider only intersecting geometries refFeatureRequest = QgsFeatureRequest().setFilterFids(refFeatureIds) for refFeature in self.referenceLayer.getFeatures(refFeatureRequest): refGeometry = refFeature.geometry() segments = self.breakQgsGeometryIntoSegments(refGeometry) # testing intersection for segment in segments: if segment.intersects(searchBounds): refGeometries.append(segment) # End here in case we don't find geometries if len(refGeometries) == 0: return geometry # building geometry index refDict, index = self.buildReferenceIndex(refGeometries) refSnapIndex = DsgSnapIndex(center, 10*snapTolerance) for geom in refGeometries: refSnapIndex.addGeometry(geom.geometry()) # Snap geometries subjGeom = geometry.geometry().clone() subjPointFlags = [] # Pass 1: snap vertices of subject geometry to reference vertices for iPart in xrange(subjGeom.partCount()): subjPointFlags.append([]) for iRing in xrange(subjGeom.ringCount(iPart)): subjPointFlags[iPart].append([]) for iVert in xrange(self.polyLineSize(subjGeom, iPart, iRing)): vidx = QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex) p = QgsPointV2(subjGeom.vertexAt(vidx)) pF = QgsPoint(p.toQPointF()) snapPoint, snapSegment = refSnapIndex.getSnapItem(p, snapTolerance) success = snapPoint or snapSegment if not success: subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.Unsnapped ) else: if mode == DsgGeometrySnapper.PreferNodes: # Prefer snapping to point if snapPoint: subjGeom.moveVertex(vidx, snapPoint.getSnapPoint(p)) subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefNode) elif snapSegment: subjGeom.moveVertex( vidx, snapSegment.getSnapPoint(p)) subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefSegment) elif mode == DsgGeometrySnapper.PreferClosest: nodeSnap = None segmentSnap = None distanceNode = sys.float_info.max distanceSegment = sys.float_info.max if snapPoint: nodeSnap = snapPoint.getSnapPoint(p) nodeSnapF = QgsPoint(nodeSnap.toQPointF()) distanceNode = nodeSnapF.sqrDist(pF) if snapSegment: segmentSnap = snapSegment.getSnapPoint(p) segmentSnapF = QgsPoint(segmentSnap.toQPointF()) distanceSegment = segmentSnapF.sqrDist(pF) if snapPoint and (distanceNode < distanceSegment): subjGeom.moveVertex( vidx, nodeSnap ) subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefNode) elif snapSegment: subjGeom.moveVertex(vidx, segmentSnap) subjPointFlags[iPart][iRing].append(DsgGeometrySnapper.SnappedToRefSegment) #nothing more to do for points if isinstance(subjGeom, QgsPointV2): return QgsGeometry(subjGeom) # SnapIndex for subject feature subjSnapIndex = DsgSnapIndex(center, 10*snapTolerance) subjSnapIndex.addGeometry(subjGeom) origSubjGeom = subjGeom.clone() origSubjSnapIndex = DsgSnapIndex(center, 10*snapTolerance) origSubjSnapIndex.addGeometry(origSubjGeom) # Pass 2: add missing vertices to subject geometry for refGeom in refGeometries: for iPart in xrange(refGeom.geometry().partCount()): for iRing in xrange(refGeom.geometry().ringCount(iPart)): for iVert in xrange(self.polyLineSize(refGeom.geometry(), iPart, iRing)): point = refGeom.geometry().vertexAt(QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) # QgsPoint used to calculate squared distance pointF = QgsPoint(point.toQPointF()) snapPoint, snapSegment = subjSnapIndex.getSnapItem(point, snapTolerance) success = snapPoint or snapSegment if success: # Snap to segment, unless a subject point was already snapped to the reference point if snapPoint and (QgsPoint(snapPoint.getSnapPoint(point).toQPointF()).sqrDist(pointF) < 1E-16): continue elif snapSegment: # Look if there is a closer reference segment, if so, ignore this point pProj = snapSegment.getSnapPoint(point) pProjF = QgsPoint(pProj.toQPointF()) closest = refSnapIndex.getClosestSnapToPoint(point, pProj) closestF = QgsPoint(closest.toQPointF()) if pProjF.sqrDist(pointF) > pProjF.sqrDist(closestF): continue # If we are too far away from the original geometry, do nothing if not origSubjSnapIndex.getSnapItem(point, snapTolerance): continue idx = snapSegment.idxFrom subjGeom.insertVertex(QgsVertexId(idx.vidx.part, idx.vidx.ring, idx.vidx.vertex + 1, QgsVertexId.SegmentVertex), point) subjPointFlags[idx.vidx.part][idx.vidx.ring].insert(idx.vidx.vertex + 1, DsgGeometrySnapper.SnappedToRefNode ) subjSnapIndex = DsgSnapIndex(center, 10*snapTolerance) subjSnapIndex.addGeometry(subjGeom) # Pass 3: remove superfluous vertices: all vertices which are snapped to a segment and not preceded or succeeded by an unsnapped vertex for iPart in xrange(subjGeom.partCount()): for iRing in xrange(subjGeom.ringCount(iPart)): ringIsClosed = subjGeom.vertexAt(QgsVertexId(iPart, iRing, 0, QgsVertexId.SegmentVertex)) == subjGeom.vertexAt(QgsVertexId(iPart, iRing, subjGeom.vertexCount( iPart, iRing ) - 1, QgsVertexId.SegmentVertex)) nVerts = self.polyLineSize(subjGeom, iPart, iRing) iVert = 0 while iVert < nVerts: iPrev = ( iVert - 1 + nVerts ) % nVerts iNext = ( iVert + 1 ) % nVerts pMid = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iVert, QgsVertexId.SegmentVertex)) pPrev = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iPrev, QgsVertexId.SegmentVertex)) pNext = subjGeom.vertexAt(QgsVertexId( iPart, iRing, iNext, QgsVertexId.SegmentVertex)) pointOnSeg = self.projPointOnSegment( pMid, pPrev, pNext) pointOnSegF = QgsPoint(pointOnSeg.toQPointF()) pMidF = QgsPoint(pMid.toQPointF()) dist = pointOnSegF.sqrDist(pMidF) if subjPointFlags[iPart][iRing][iVert] == DsgGeometrySnapper.SnappedToRefSegment \ and subjPointFlags[iPart][iRing][iPrev] != DsgGeometrySnapper.Unsnapped \ and subjPointFlags[iPart][iRing][iNext] != DsgGeometrySnapper.Unsnapped \ and dist < 1E-12: if (ringIsClosed and nVerts > 3 ) or ( not ringIsClosed and nVerts > 2 ): subjGeom.deleteVertex(QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) del subjPointFlags[iPart][iRing][iVert] iVert -= 1 nVerts -= 1 else: # Don't delete vertices if this would result in a degenerate geometry break iVert += 1 return QgsGeometry(subjGeom)
def enclaveRemover(self): field_id = self.activeLayer.fieldNameIndex(self.distfield) self.activeLayer.startEditing() # Create a dictionary of all features feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()} QgsMessageLog.logMessage("Building spatial index...") # Build a spatial index index = QgsSpatialIndex() for f in feature_dict.values(): index.insertFeature(f) QgsMessageLog.logMessage("Finding neighbors...") # Loop through all features and find features that touch each feature for f in feature_dict.values(): geom = f.geometry() # Find all features that intersect the bounding box of the current feature. # We use spatial index to find the features intersecting the bounding box # of the current feature. This will narrow down the features that we need # to check neighboring features. intersecting_ids = index.intersects(geom.boundingBox()) # Initalize neighbors list and sum neighbors = [] neighbors_district = -1 finished = 0 if f[self.distfield] == 0: QgsMessageLog.logMessage("feature " + str(f.id()) + " with null distfield found!") while neighbors_district <> -2 and finished == 0: finished = 0 for intersecting_id in intersecting_ids: # Look up the feature from the dictionary intersecting_f = feature_dict[intersecting_id] QgsMessageLog.logMessage("Neighbor found!") # For our purpose we consider a feature as 'neighbor' if it touches or # intersects a feature. We use the 'disjoint' predicate to satisfy # these conditions. So if a feature is not disjoint, it is a neighbor. if (f != intersecting_f and not intersecting_f.geometry().disjoint(geom)): if intersecting_f[self.distfield] > 0: QgsMessageLog.logMessage( "Neighbor found with > 0!") if neighbors_district == -1: neighbors_district = intersecting_f[ self.distfield] QgsMessageLog.logMessage( "neighbors_district set to " + str(neighbors_district)) elif neighbors_district != intersecting_f[ self.distfield]: neighbors_district = -2 QgsMessageLog.logMessage( "neighbors_district set to " + str(neighbors_district) + ", " + str(intersecting_f[self.distfield]) + " not matching") if neighbors_district > 0: QgsMessageLog.logMessage( str(f.id()) + " updating district to " + str(neighbors_district)) self.activeLayer.changeAttributeValue( f.id(), field_id, neighbors_district) # Update the layer with new attribute values. finished = 1 self.activeLayer.commitChanges()
class Network(object): def __init__(self, network_id, feature_source, index=True): self._id = network_id if isinstance(feature_source, dict): self._src = None self._edge_map = feature_source else: self._src = feature_source self._edge_map = {} self._edge_index = QgsSpatialIndex() self._edge_nodes = {} self._node_map = {} self._node_index = QgsSpatialIndex() self._node_edges = defaultdict(set) if index: self.build_indexes() def __repr__(self): return '<Network %s>' % (str(self._id), ) def build_indexes(self, index_nodes=True, iterate=False): next_node_id = 1 coords_node = {} if self._src is not None: request = QgsFeatureRequest() request.FetchAttributes = False self._edge_map = dict([(f.id(), f) for f in self._src.getFeatures(request)]) total = len(self._edge_map) for (i, (fid, feature)) in enumerate(self._edge_map.items(), start=1): fid = feature.id() ls = feature.geometry().constGet() endpoints = [] for point in [ls.startPoint(), ls.endPoint()]: coords = (point.x(), point.y()) node_id = coords_node.get(coords, None) if node_id is None: node_id = next_node_id next_node_id += 1 coords_node[coords] = node_id node_feature = self._make_node_feature(node_id, point) self._node_map[node_id] = node_feature if index_nodes: self._node_index.insertFeature(node_feature) endpoints.append(node_id) self._node_edges[node_id].add(fid) self._edge_index.insertFeature(feature) self._edge_nodes[fid] = endpoints if iterate: yield float(i) / total def _make_node_feature(self, fid, point): feature = QgsFeature(fid) # TODO: Figure out why point.clone() does not work in the line below. # (It creates weird issues with the original geometry later in # the process.) feature.setGeometry(QgsGeometry(QgsPoint(point.x(), point.y()))) return feature def _vertex_id(self, eid, nid): if self.get_edge_nids(eid)[0] == nid: return 0 return self.get_edge(eid).geometry().constGet().nCoordinates() - 1 def _node_angle(self, eid, nid): vid = self._vertex_id(eid, nid) angle = self.get_edge(eid).geometry().angleAtVertex(vid) \ * 180 / math.pi if vid > 0: angle += 180 if angle < 180 else -180 return angle def eids(self): return self._edge_map.keys() def nids(self): return self._node_map.keys() def get_edge(self, eid): return self._edge_map[eid] def get_node(self, nid): return self._node_map[nid] def find_nids(self, bbox): return self._node_index.intersects(bbox) def find_eids(self, bbox): return self._edge_index.intersects(bbox) def get_edge_nids(self, eid): return self._edge_nodes[eid] def get_node_eids(self, nid): return self._node_edges[nid] def get_other_nid(self, eid, nid): nids = self._edge_nodes[eid] if nids[0] == nid: return nids[1] return nids[0] def get_node_angles(self, nid): eids = self.get_node_eids(nid) return dict([(eid, self._node_angle(eid, nid)) for eid in eids]) def is_loop(self, eid): return len(set(self.get_edge_nids(eid))) == 1
def run(self): """Experimental impact function.""" self.validate() self.prepare() # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword("field") self.hazard_class_mapping = self.hazard.keyword("value_map") self.exposure_class_attribute = self.exposure.keyword("structure_class_field") # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex(self.hazard_class_attribute) if affected_field_index == -1: message = ( tr( 'Field "%s" is not present in the attribute table of the ' "hazard layer. Please change the Affected Field parameter in " "the IF Option." ) % self.hazard_class_attribute ) raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex(self.exposure_class_attribute) if building_type_field_index == -1: message = ( tr( 'Field "%s" is not present in the attribute table of ' "the exposure layer. Please change the Building Type " "Field parameter in the IF Option." ) % self.exposure_class_attribute ) raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes([QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex(self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the lines from E and extent building_layer = QgsVectorLayer("Polygon?crs=" + srs, "impact_buildings", "memory") building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:%i" % self._requested_extent_crs), self.hazard.layer.crs() ) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( "There are no objects in the hazard layer with %s " "value in %s. Please check your data or use another " "attribute." ) % (self.hazard_class_attribute, ", ".join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) features = [] for feature in self.exposure.layer.getFeatures(request): building_geom = feature.geometry() affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_geom.boundingBox()) for fid in ids: # run (slow) exact intersection test if hazard_geometries[fid].intersects(building_geom): affected = True break f = QgsFeature() f.setGeometry(building_geom) f.setAttributes(feature.attributes()) f[target_field_index] = 1 if affected else 0 features.append(f) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report self.buildings = {} self.affected_buildings = OrderedDict([(tr("Flooded"), {})]) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex(self.exposure_class_attribute) for building in buildings_data: record = building.attributes() building_type = record[building_type_field_index] if building_type in [None, "NULL", "null", "Null"]: building_type = "Unknown type" if building_type not in self.buildings: self.buildings[building_type] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][building_type] = OrderedDict([(tr("Buildings Affected"), 0)]) self.buildings[building_type] += 1 if record[target_field_index] == 1: self.affected_buildings[tr("Flooded")][building_type][tr("Buildings Affected")] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() impact_summary = self.generate_html_report() # For printing map purpose map_title = tr("Buildings inundated") legend_title = tr("Structure inundated status") style_classes = [ dict(label=tr("Not Inundated"), value=0, colour="#1EFC7C", transparency=0, size=0.5), dict(label=tr("Inundated"), value=1, colour="#F31A1C", transparency=0, size=0.5), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type="categorizedSymbol") # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr("No buildings were impacted by this flood.")) building_layer = Vector( data=building_layer, name=tr("Flooded buildings"), keywords={ "impact_summary": impact_summary, "map_title": map_title, "legend_title": legend_title, "target_field": self.target_field, "buildings_total": self.total_buildings, "buildings_affected": self.total_affected_buildings, }, style_info=style_info, ) self._impact = building_layer return building_layer
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, layerA.wkbType(), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) multiGeoms = 0 # how many multi geometries were encountered for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() if inGeom.isMultipart(): multiGeoms += 1 # MultiGeometries are not allowed because the result of a splitted part cannot be clearly defined: # 1) add both new parts as new features # 2) store one part as a new feature and the other one as part of the multi geometry # 2a) which part should be which, seems arbitrary else: attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged QgsMessageLog.logMessage("appending") outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: QgsMessageLog.logMessage("appending else") outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry \ and not QgsWkbTypes.isMultiType(aGeom.wkbType()): passed = len(aGeom.asPolyline()) > 2 if not passed: passed = (len(aGeom.asPolyline()) == 2 and aGeom.asPolyline()[0] != aGeom.asPolyline()[1]) # sometimes splitting results in lines of zero length if passed: outFeat.setGeometry(aGeom) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) if multiGeoms > 0: ProcessingLog.addToLog(ProcessingLog.LOG_INFO, self.tr('Feature geometry error: %s input features ignored due to multi-geometry.') % str(multiGeoms)) del writer
def get_feats_on_bbox(layer, bbox): index = QgsSpatialIndex(layer.getFeatures()) feats_int = index.intersects(bbox) return feats_int
class LeastCommonDenominatorProcedure(WorkerThread): def __init__(self, parentThread, flayer, tlayer, ffield, tfield): WorkerThread.__init__(self, parentThread) self.flayer = flayer self.tlayer = tlayer self.ffield = ffield self.tfield = tfield self.error = None self.result = None self.output_type = None self.poly_types = poly_types + multi_poly self.line_types = line_types + multi_line self.point_types = point_types + multi_point def doWork(self): flayer = self.flayer tlayer = self.tlayer ffield = self.ffield tfield = self.tfield self.from_layer = get_vector_layer_by_name(flayer) self.to_layer = get_vector_layer_by_name(tlayer) self.transform = None if self.from_layer.dataProvider().crs() != self.to_layer.dataProvider().crs(): self.transform = QgsCoordinateTransform(self.from_layer.dataProvider().crs(), self.to_layer.dataProvider().crs()) # FIELDS INDICES idx = self.from_layer.fieldNameIndex(ffield) fid = self.to_layer.fieldNameIndex(tfield) # We create an spatial self.index to hold all the features of the layer that will receive the data # And a dictionary that will hold all the features IDs found to intersect with each feature in the spatial index self.emit(SIGNAL("ProgressMaxValue( PyQt_PyObject )"), self.to_layer.dataProvider().featureCount()) self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Building Spatial Index') allfeatures = {} merged = {} self.index = QgsSpatialIndex() for i, feature in enumerate(self.to_layer.getFeatures()): allfeatures[feature.id()] = feature merged[feature.id()] = feature self.index.insertFeature(feature) self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), i) self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Duplicating Layers') self.all_attr = {} # We create the memory layer that will have the analysis result, which is the lowest common # denominator of both layers epsg_code = int(self.to_layer.crs().authid().split(":")[1]) if self.from_layer.wkbType() in self.poly_types and self.to_layer.wkbType() in self.poly_types: lcd_layer = QgsVectorLayer("MultiPolygon?crs=epsg:" + str(epsg_code), "output", "memory") self.output_type = 'Poly' elif self.from_layer.wkbType() in self.poly_types + self.line_types and \ self.to_layer.wkbType() in self.poly_types + self.line_types: lcd_layer = QgsVectorLayer("MultiLineString?crs=epsg:" + str(epsg_code), "output", "memory") self.output_type = 'Line' else: lcd_layer = QgsVectorLayer("MultiPoint?crs=epsg:" + str(epsg_code), "output", "memory") self.output_type = 'Point' lcdpr = lcd_layer.dataProvider() lcdpr.addAttributes([QgsField("Part_ID", QVariant.Int), QgsField(ffield, self.from_layer.fields().field(idx).type()), QgsField(tfield, self.to_layer.fields().field(fid).type()), QgsField('P-' + str(ffield), QVariant.Double), # percentage of the from field QgsField('P-' + str(tfield), QVariant.Double)]) # percentage of the to field lcd_layer.updateFields() # PROGRESS BAR self.emit(SIGNAL("ProgressMaxValue( PyQt_PyObject )"), self.from_layer.dataProvider().featureCount()) self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Running Analysis') part_id = 1 features = [] for fc, feat in enumerate(self.from_layer.getFeatures()): geom = feat.geometry() if geom is not None: if self.transform is not None: a = geom.transform(self.transform) geometry, statf = self.find_geometry(geom) uncovered, statf = self.find_geometry(geom) # uncovered = copy.deepcopy(geometry) intersecting = self.index.intersects(geometry.boundingBox()) # Find all intersecting parts for f in intersecting: g = geometry.intersection(allfeatures[f].geometry()) if g.area() > 0: feature = QgsFeature() geo, stati = self.find_geometry(g) feature.setGeometry(geo) geo, statt = self.find_geometry(allfeatures[f].geometry()) perct = stati / statt percf = stati / statf feature.setAttributes([part_id, feat.attributes()[idx], allfeatures[f].attributes()[fid], percf, perct]) features.append(feature) # prepare the data for the non overlapping if uncovered is not None: uncovered = uncovered.difference(g) aux = merged[f].geometry().difference(g) if aux is not None: merged[f].setGeometry(aux) part_id += 1 # Find the part that does not intersect anything if uncovered is not None: if uncovered.area() > 0: feature = QgsFeature() geo, stati = self.find_geometry(uncovered) feature.setGeometry(geo) perct = 0 percf = stati / statf feature.setAttributes([part_id, feat.attributes()[idx], '', percf, perct]) features.append(feature) part_id += 1 self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), fc) self.emit(SIGNAL("ProgressText( PyQt_PyObject )"), 'Running Analysis (' + "{:,}".format(fc) + '/' + "{:,}".format( self.from_layer.featureCount()) + ')') # Find the features on TO that have no correspondence in FROM for f, feature in merged.iteritems(): geom = feature.geometry() aux, statt = self.find_geometry(allfeatures[f].geometry()) if geom.area() > 0: feature = QgsFeature() geo, stati = self.find_geometry(geom) feature.setGeometry(geo) perct = stati / statt percf = 0 feature.setAttributes([part_id, '', allfeatures[f].attributes()[fid], percf, perct]) features.append(feature) part_id += 1 if features: a = lcdpr.addFeatures(features) self.result = lcd_layer self.emit(SIGNAL("ProgressValue( PyQt_PyObject )"), self.from_layer.dataProvider().featureCount()) self.emit(SIGNAL("finished_threaded_procedure( PyQt_PyObject )"), "procedure") def find_geometry(self, g): if self.output_type == 'Poly': stat = g.area() if g.isMultipart(): geometry = QgsGeometry.fromMultiPolygon(g.asMultiPolygon()) else: geometry = QgsGeometry.fromPolygon(g.asPolygon()) elif self.output_type == 'Line': stat = g.length() if g.isMultipart(): geometry = QgsGeometry.fromMultiLineString(g.asMultiPolyLine()) else: geometry = QgsGeometry.fromLineString(g.asPoly()) else: stat = 1 if g.isMultipart(): geometry = QgsGeometry.fromMultiPoint(g.asMultiPoint()) else: geometry = QgsGeometry.fromPoint(g.asPoint()) return geometry, stat
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.INTERSECT, context) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.INTERSECT_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] fieldListB = vector.testForUniqueness(fieldListA, fieldListB) for b in fieldListB: fieldListA.append(b) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fieldListA, QgsWkbTypes.Point, sourceA.sourceCrs()) spatialIndex = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs())), feedback) outFeat = QgsFeature() features = sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 0 for current, inFeatA in enumerate(features): if feedback.isCanceled(): break if not inFeatA.hasGeometry(): continue inGeom = inFeatA.geometry() has_intersections = False lines = spatialIndex.intersects(inGeom.boundingBox()) engine = None if len(lines) > 0: has_intersections = True # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() if has_intersections: request = QgsFeatureRequest().setFilterFids(lines) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) for inFeatB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = inFeatB.geometry() points = [] if engine.intersects(tmpGeom.geometry()): tempGeom = inGeom.intersection(tmpGeom) out_attributes = [ inFeatA.attributes()[i] for i in field_indices_a ] out_attributes.extend( [inFeatB.attributes()[i] for i in field_indices_b]) if tempGeom.type() == QgsWkbTypes.PointGeometry: if tempGeom.isMultipart(): points = tempGeom.asMultiPoint() else: points.append(tempGeom.asPoint()) for j in points: outFeat.setGeometry(tempGeom.fromPoint(j)) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
class TriangleMesh: # 0 - 3 # | / | # 1 - 2 def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.vbands = [] self.hbands = [] self.vidx = QgsSpatialIndex() self.hidx = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres def addVBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.vbands.append(f) self.vidx.insertFeature(f) def addHBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.hbands.append(f) self.hidx.insertFeature(f) for x in range(x_segments): addVBand( x, QgsGeometry.fromRect( QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres, ymax))) for y in range(y_segments): addHBand( y, QgsGeometry.fromRect( QgsRectangle(xmin, ymax - (y + 1) * yres, xmax, ymax - y * yres))) def vSplit(self, geom): """split polygon vertically""" for idx in self.vidx.intersects(geom.boundingBox()): yield idx, geom.intersection(self.vbands[idx].geometry()) def hIntersects(self, geom): """indices of horizontal bands that intersect with geom""" for idx in self.hidx.intersects(geom.boundingBox()): if geom.intersects(self.hbands[idx].geometry()): yield idx def splitPolygons(self, geom): xmin, ymax, xres, yres = self.xmin, self.ymax, self.xres, self.yres for x, vi in self.vSplit(geom): for y in self.hIntersects(vi): pt0 = QgsPoint(xmin + x * xres, ymax - y * yres) pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres) pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres) pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres) quad = QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]]) tris = [[[pt0, pt1, pt3, pt0]], [[pt3, pt1, pt2, pt3]]] if geom.contains(quad): yield tris[0] yield tris[1] else: for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)): if geom.contains(tri): yield tris[i] elif geom.intersects(tri): poly = geom.intersection(tri) if poly.isMultipart(): for sp in poly.asMultiPolygon(): yield sp else: yield poly.asPolygon()
class autoRedistrict: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(self.plugin_dir, 'i18n', 'autoRedistrict_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&Automated Redistricting') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None #initialise the other variables self.distfield = None self.popfield = None self.geofield = None self.activeLayer = None self.sortType = 0 self.totalpop = 0 self.targetpop = 0 self.districts = 0 self.sortIndex = 0 self.spatialIndex = QgsSpatialIndex() self.distpop = [] self.feature_dict = {} # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('autoRedistrict', message) def add_action(self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: # Adds plugin icon to Plugins toolbar self.iface.addToolBarIcon(action) if add_to_menu: self.iface.addPluginToMenu(self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/autoRedistrict/icon.png' self.add_action(icon_path, text=self.tr(u'Auto Redistricting'), callback=self.run, parent=self.iface.mainWindow()) # will be set False in run() self.first_start = True def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'&Automated Redistricting'), action) self.iface.removeToolBarIcon(action) def run(self): """Run method that performs all the real work""" #create the triggers - only need one # 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 = autoRedistrictDialog() self.dlg.cmbActiveLayer.currentIndexChanged.connect( self.updateFieldCombos) self.dlg.cmbDirection.clear() self.dlg.cmbDirection.addItems([ 'West to East', 'East to West', 'North to South', 'South to North' ]) self.updateDialog() # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: self.createVariables() self.initialiseSpatialIndex() # self.resetDistrictColumn() self.redistrictLayer() def updateDialog(self): layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] layer_list = [] for layer in layers: layer_list.append(layer.name()) self.dlg.cmbActiveLayer.clear() self.dlg.cmbActiveLayer.addItems(layer_list) def updateFieldCombos(self): self.dlg.cmbPopField.clear() self.dlg.cmbDistField.clear() self.dlg.cmbGeoField.clear() # activeDirectionIndex = self.dlg.cmbDirection.currentIndex() layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex() selectedLayer = layers[selectedLayerIndex] self.dlg.cmbGeoField.addItems(['None']) if hasattr(selectedLayer, 'fields'): fields = selectedLayer.fields() field_names = [field.name() for field in fields] self.dlg.cmbPopField.addItems(field_names) self.dlg.cmbDistField.addItems(field_names) self.dlg.cmbGeoField.addItems(field_names) def createVariables(self): self.iface.statusBarIface().showMessage(u"Creating variables...") QCoreApplication.processEvents() self.popfield = self.dlg.cmbPopField.currentText() self.distfield = self.dlg.cmbDistField.currentText() self.geofield = self.dlg.cmbGeoField.currentText() self.sortIndex = self.dlg.cmbDirection.currentIndex() layers = [ tree_layer.layer() for tree_layer in QgsProject.instance().layerTreeRoot().findLayers() ] self.districts = self.dlg.inpDistricts.value() del self.distpop self.distpop = [] self.distpop.append(0) selectedLayerIndex = self.dlg.cmbActiveLayer.currentIndex() selectedLayer = layers[selectedLayerIndex] self.activeLayer = selectedLayer for feature in self.activeLayer.getFeatures(): self.totalpop = self.totalpop + int(feature[self.popfield]) if self.districts > 0: self.targetpop = self.totalpop / self.districts print('districts:' + str(self.districts)) print('targetpop:' + str(self.targetpop)) else: pass def redistrictLayer(self): field_id = self.activeLayer.fields().indexFromName(self.distfield) self.iface.statusBarIface().showMessage(u"Redistricting layer...") QCoreApplication.processEvents() districtctr = 0 found = 0 while found != -1: strExpr = "\"" + self.distfield + "\" = '0' or \"" + self.distfield + "\" = None" expr = QgsExpression(strExpr) if self.sortIndex == 1: # east to west features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_x, reverse=True) elif self.sortIndex == 2: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_y, reverse=False) elif self.sortIndex == 3: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_y, reverse=True) else: features = sorted(self.activeLayer.getFeatures(strExpr), key=sort_by_x) found = 0 for f in features: found = 1 if f[self.distfield] == 0 or f[self.distfield] == None or f[ self.distfield] == 'NULL': print('New District:' + str(f['BLOCKID10']) + '|' + str(f[self.distfield])) districtctr = districtctr + 1 """ if districtctr == 2: strExpr = "\"" + self.distfield + "\" = '1'" expr = QgsExpression(strExpr) iterator = self.activeLayer.getFeatures(QgsFeatureRequest(expr)) ids = [i.id() for i in iterator] self.activeLayer.select(ids) """ print('New district being created:' + str(districtctr)) self.iface.statusBarIface().showMessage( u"Redistricting layer (starting district " + str(districtctr) + ")") QCoreApplication.processEvents() self.distpop.append(0) is_enclave = self.floodFillDistrict(f, districtctr) if is_enclave == 1: districtctr = districtctr - 1 break if found == 0: #close the loop found = -1 self.iface.statusBarIface().showMessage(u"...done.") def initialiseSpatialIndex(self): self.iface.statusBarIface().showMessage( u"Initialising spatial index...") QCoreApplication.processEvents() self.feature_dict = {f.id(): f for f in self.activeLayer.getFeatures()} for f in list(self.feature_dict.values()): self.spatialIndex.addFeature(f) def resetDistrictColumn(self): self.iface.statusBarIface().showMessage(u"Clearing district column:") QCoreApplication.processEvents() field_id = self.activeLayer.fields().indexFromName(self.distfield) self.activeLayer.startEditing() counter = 0 request = QgsFeatureRequest().setFlags( QgsFeatureRequest.NoGeometry).setSubsetOfAttributes( [self.distfield], self.activeLayer.fields()) for f in self.activeLayer.getFeatures(request): counter = counter + 1 self.activeLayer.changeAttributeValue(f.id(), field_id, 0) if counter % 300 == 0: #speeds things up significantly self.activeLayer.commitChanges() self.activeLayer.startEditing() self.iface.statusBarIface().showMessage( u"Clearing district column: " + str(counter) + " records processed") QCoreApplication.processEvents() self.activeLayer.commitChanges() def floodFillDistrict(self, feature, district_number): QgsMessageLog.logMessage("Starting district " + str(district_number)) print('district_number:' + str(district_number)) field_id = self.activeLayer.fields().indexFromName(self.distfield) select_list = [] outside_geo_list = [] neighboring_districts = [] if self.geofield != 'None': activeGeoField = feature[self.geofield] QgsMessageLog.logMessage("GeoField set to " + activeGeoField) geoCounter = 0 self.distpop[district_number] = feature[self.popfield] self.activeLayer.startEditing() feature[self.distfield] = district_number self.activeLayer.changeAttributeValue(feature.id(), field_id, district_number, 0) self.activeLayer.commitChanges() self.activeLayer.startEditing() print('starting at ' + str(feature.id()) + '|' + str(feature[self.distfield]), end=' ') select_list.append(feature) geoCounter = geoCounter + 1 QCoreApplication.processEvents() surrounded = 0 #whether the feature is completely surrounded by another district # Loop through all features and find features that touch each feature counter = 0 for f in select_list: geom = f.geometry() # Find all features that intersect the bounding box of the current feature. # We use spatial index to find the features intersecting the bounding box # of the current feature. This will narrow down the features that we need # to check neighboring features. intersecting_ids = self.spatialIndex.intersects(geom.boundingBox()) counter = counter + 1 if counter % 500 == 0: self.iface.statusBarIface().showMessage( u"Redistricting layer (district " + str(district_number) + ") " + str(counter) + " polygons added, current population " + str(self.distpop[district_number]) + "/" + str(self.targetpop)) QCoreApplication.processEvents() self.activeLayer.commitChanges() self.activeLayer.startEditing() for intersecting_id in intersecting_ids: # Look up the feature from the dictionary intersecting_f = self.feature_dict[intersecting_id] # For our purpose we consider a feature as 'neighbor' if it touches or # intersects a feature. We use the 'disjoint' predicate to satisfy # these conditions. So if a feature is not disjoint, it is a neighbor. # if (f.id() != intersecting_f.id() and feature.id() != intersecting_f.id() and not intersecting_f.geometry().disjoint(geom)): if (f.id() != intersecting_f.id() and feature.id() != intersecting_f.id() and intersecting_f.geometry().intersects(geom)): if intersecting_f[self.distfield] == 0 or intersecting_f[ self.distfield] == None: if intersecting_f[ self. geofield] == activeGeoField or self.geofield == 'None': if intersecting_f not in select_list: if surrounded != -1: surrounded = -1 self.distpop[district_number] = self.distpop[ district_number] + intersecting_f[ self.popfield] intersecting_f[ self.distfield] = district_number self.activeLayer.changeAttributeValue( intersecting_f.id(), field_id, district_number, 0) # print(':' + str(intersecting_f[self.distfield]), end=":") select_list.append(intersecting_f) geoCounter = geoCounter + 1 if self.distpop[ district_number] > self.targetpop: self.activeLayer.commitChanges() return 0 else: if intersecting_f not in outside_geo_list and intersecting_f not in select_list: outside_geo_list.append(intersecting_f) surrounded = -1 elif surrounded != -1: #kill one-district, completely surrounded enclaves as we find them if surrounded == 0: surrounded = intersecting_f[self.distfield] elif surrounded != intersecting_f[self.distfield]: if surrounded > 0: neighboring_districts.append(surrounded) surrounded = -1 elif surrounded == -1: if intersecting_f[ self.distfield] not in neighboring_districts: neighboring_districts.append( intersecting_f[self.distfield]) """ #old code commented out which gets greedy and expands districts, but it doesn't work well - shoots down interstates, etc if self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield] > self.targetpop: self.distpop[intersecting_f[self.distfield]] = self.distpop[intersecting_f[self.distfield]] - intersecting_f[self.popfield] self.distpop[district_number] = self.distpop[district_number] + intersecting_f[self.popfield] intersecting_f[self.distfield] = district_number self.activeLayer.changeAttributeValue(intersecting_f.id(),field_id,district_number,0) select_list.append(intersecting_f) """ if self.distpop[district_number] > self.targetpop: self.activeLayer.commitChanges() return 0 if surrounded > 0: QgsMessageLog.logMessage( "enclave found, reassigning to district " + str(surrounded)) self.activeLayer.changeAttributeValue(feature.id(), field_id, surrounded, 0) self.distpop[district_number] = self.distpop[ district_number] - feature[self.popfield] self.distpop[surrounded] = self.distpop[surrounded] + feature[ self.popfield] self.activeLayer.commitChanges() return 1 #if geoCounter is zero, this means we change the active geography geoCounter = geoCounter - 1 activeGeoField = '' if geoCounter == 0 and self.geofield != 'None': for of in outside_geo_list: if activeGeoField == '': activeGeoField = of[self.geofield] QgsMessageLog.logMessage("GeoField updated to " + activeGeoField) if of[self.geofield] == activeGeoField: select_list.append(of) outside_geo_list.remove(of) self.distpop[district_number] = self.distpop[ district_number] + of[self.popfield] of[self.distfield] = district_number self.activeLayer.changeAttributeValue( of.id(), field_id, district_number, 0) geoCounter = geoCounter + 1 if self.distpop[district_number] < (self.targetpop * (1 - 0.9)): new_district = 0 lower_pop = -1 for n in neighboring_districts: if self.distpop[n] > lower_pop or lower_pop == -1: lower_pop = self.distpop[n] new_district = n QgsMessageLog.logMessage( "target population too low, reassigning to district " + str(new_district)) for f in select_list: self.activeLayer.changeAttributeValue(f.id(), field_id, new_district, 0) self.distpop[district_number] = self.distpop[ district_number] - feature[self.popfield] self.distpop[new_district] = self.distpop[ new_district] + feature[self.popfield] self.activeLayer.commitChanges() return 1
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context) radius = self.parameterAsDouble(parameters, self.DISTANCE, context) horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 def searchRect(p): return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity) index = QgsSpatialIndex() # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm, # please port the changes to QgsPointDistanceRenderer::renderFeature also! clustered_groups = [] group_index = {} group_locations = {} for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue point = f.geometry().asPoint() other_features_within_radius = index.intersects(searchRect(point)) if not other_features_within_radius: index.insertFeature(f) group = [f] clustered_groups.append(group) group_index[f.id()] = len(clustered_groups) - 1 group_locations[f.id()] = point else: # find group with closest location to this point (may be more than one within search tolerance) min_dist_feature_id = other_features_within_radius[0] min_dist = group_locations[min_dist_feature_id].distance(point) for i in range(1, len(other_features_within_radius)): candidate_id = other_features_within_radius[i] new_dist = group_locations[candidate_id].distance(point) if new_dist < min_dist: min_dist = new_dist min_dist_feature_id = candidate_id group_index_pos = group_index[min_dist_feature_id] group = clustered_groups[group_index_pos] # calculate new centroid of group old_center = group_locations[min_dist_feature_id] group_locations[min_dist_feature_id] = QgsPointXY( (old_center.x() * len(group) + point.x()) / (len(group) + 1.0), (old_center.y() * len(group) + point.y()) / (len(group) + 1.0)) # add to a group clustered_groups[group_index_pos].append(f) group_index[f.id()] = group_index_pos feedback.setProgress(int(current * total)) current = 0 total = 100.0 / len(clustered_groups) if clustered_groups else 1 feedback.setProgress(0) fullPerimeter = 2 * math.pi for group in clustered_groups: if feedback.isCanceled(): break count = len(group) if count == 1: sink.addFeature(group[0], QgsFeatureSink.FastInsert) else: angleStep = fullPerimeter / count if count == 2 and horizontal: currentAngle = math.pi / 2 else: currentAngle = 0 old_point = group_locations[group[0].id()] for f in group: if feedback.isCanceled(): break sinusCurrentAngle = math.sin(currentAngle) cosinusCurrentAngle = math.cos(currentAngle) dx = radius * sinusCurrentAngle dy = radius * cosinusCurrentAngle # we want to keep any existing m/z values point = f.geometry().constGet().clone() point.setX(old_point.x() + dx) point.setY(old_point.y() + dy) f.setGeometry(QgsGeometry(point)) sink.addFeature(f, QgsFeatureSink.FastInsert) currentAngle += angleStep current += 1 feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
class ContourTool(): def updateReference(self, referenceLayer): self.reference = referenceLayer self.populateIndex() def populateIndex(self): #spatial index self.index = QgsSpatialIndex() for feat in self.reference.getFeatures(): self.index.insertFeature(feat) def getCandidates(self, bbox): #features that might satisfy the query ids = self.index.intersects(bbox) candidates = [] for id in ids: candidates.append(self.reference.getFeatures(QgsFeatureRequest().setFilterFid(id)).next()) return candidates def getFeatures(self, geom): #features that satisfy the query ret = [] rect = geom.boundingBox() candidates = self.getCandidates(rect) for candidate in candidates: featGeom = candidate.geometry() if featGeom.intersects(geom): ret.append(candidate) return ret def getKey(self, item): return item[0] def sortFeatures(self, geom, features): #sorting by distance distances = [] firstPoint = geom.asPolyline()[0] pointGeom = QgsGeometry.fromPoint(firstPoint) for intersected in features: intersection = geom.intersection(intersected.geometry()) if intersection.type() == QGis.Point: distance = intersection.distance(pointGeom) distances.append((distance, intersected)) ordered = sorted(distances, key=self.getKey) #returning a list of tuples (distance, feature) return ordered def reproject(self, geom, canvasCrs): destCrs = self.reference.crs() if canvasCrs.authid() != destCrs.authid(): coordinateTransformer = QgsCoordinateTransform(canvasCrs, destCrs) geom.transform(coordinateTransformer) def assignValues(self, attribute, pace, geom, canvasCrs): self.reproject(geom, canvasCrs) features = self.getFeatures(geom) ordered = self.sortFeatures(geom, features) #the first feature must have the initial value already assigned first_feature = ordered[0][1] #getting the initial value first_value = first_feature.attribute(attribute) #getting the filed index that must be updated fieldIndex = self.reference.fieldNameIndex(attribute) self.reference.startEditing() for i in range(1, len(ordered)): value = first_value + pace*i feature = ordered[i][1] #feature id that will be updated id = feature.id() #attribute pair that will be changed attrs = {fieldIndex:value} #actual update in the database self.reference.dataProvider().changeAttributeValues({id:attrs}) return self.reference.commitChanges()
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = vector.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA) indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs()))) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs()) for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() attrs = [NULL] * length + attrs intersects = indexA.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) for featB in sourceA.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
class BaseCollection(object): """Collection with same functions as fiona.collection, but which can be created in memory. Uses rtree to speedup spatial filtering""" def __init__(self, geometry_type='Point'): self.geometry_type = geometry_type self._spatial_index = QgsSpatialIndex() self.ordered_dict = OrderedDict() @property def schema(self): # todo return @property def meta(self): # todo return def filter(self, *args, **kwds): """Returns an iterator over records, but filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = self.keys(*args, **kwds) for i in selected: if i in self.ordered_dict: yield self.ordered_dict[i] def items(self, *args, **kwds): """Returns an iterator over FID, record pairs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = self.keys(*args, **kwds) for i in selected: if i in self.ordered_dict: yield (i, self.ordered_dict[i]) def keys(self, start=0, stop=None, step=1, **kwds): """Returns an iterator over FIDs, optionally filtered by a test for spatial intersection with the provided ``bbox``, a (minx, miny, maxx, maxy) tuple or a geometry ``mask``. Positional arguments ``stop`` or ``start, stop[, step]`` allows iteration to skip over items or stop at a specific item. """ selected = set(self.ordered_dict.keys()) # warning: this is not supported by Fiona if len(selected) == 0: return selected if stop is None: stop = max(selected) + 1 elif stop < 0: stop = max(0, max(selected) + stop + 1) if start is None: start = min(selected) elif start < 0: start = max(0, max(selected) + start + 1) selected.intersection_update(set(range(start, stop, step))) bbox = kwds.get('bbox') bbox_precision = kwds.get('precision', 0.0) mask = kwds.get('mask') if bbox is not None: bbox = ( bbox[0] - bbox_precision, bbox[1] - bbox_precision, bbox[2] + bbox_precision, bbox[3] + bbox_precision, ) # rtree # selected.intersection_update(set(self._spatial_index.intersection(bbox))) # qgis qbbox = QgsRectangle(*bbox) selected.intersection_update(set(self._spatial_index.intersects(qbbox))) if mask: # todo pass return selected @property def bounds(self): """Returns (minx, miny, maxx, maxy).""" # rtree # return self._spatial_index.bounds # qgis: not implemented return [None, None, None, None] def writerecords(self, records): """Stages multiple records.""" if type(records) != list: raise ValueError('list expected, got {0}'.format(type(records))) if len(self) == 0: nr = 0 else: nr = next(reversed(self.ordered_dict)) + 1 for record in records: record['id'] = nr self.ordered_dict[nr] = record # rtree # self._spatial_index.insert(nr, geom) # QGIS: feature = QgsFeature() feature.setFeatureId(nr) try: geom = shape(record['geometry']) qgeom = QgsGeometry() qgeom.fromWkb(geom.to_wkb()) except: wkt = "{}({})".format( record['geometry']['type'], ",".join(["{} {}".format(*c) for c in record['geometry']['coordinates']])) qgeom = QgsGeometry() qgeom.fromWkt(wkt) feature.setGeometry(qgeom) self._spatial_index.insertFeature(feature) nr += 1 def write(self, record): """Stages a record.""" self.writerecords([record]) def save(self, filename, crs=None, driver='ESRI Shapefile', schema=None): """ """ import fiona f = fiona.open(filename, 'w', crs=crs, driver=driver, schema=schema) records = [feat for feat in self.filter()] f.writerecords(records) f.close() # todo: check fields and append field metadata dynamicaly def __len__(self): return len(self.ordered_dict) def __getitem__(self, key): return self.ordered_dict[key] def __iter__(self): return self.filter()
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featA = QgsFeature() featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount( ) and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break lstIntersectingB = [] geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): int_geom = geom.intersection(tmpGeom) lstIntersectingB.append(tmpGeom) if not int_geom: # There was a problem creating the intersection feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.wkbType( )) == QgsWkbTypes.GeometryCollection: # Intersection produced different geomety types temp_list = int_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): int_geom = QgsGeometry(i) try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature( outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: # Geometry list: prevents writing error # in geometries of different types # produced by the intersection # fix #3549 if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): try: int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) if diff_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( diff_geom.wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): diff_geom = QgsGeometry(i) try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) atMapA = [None] * length for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs( sourceA.sourceCrs(), context.transformContext())): if feedback.isCanceled(): break add = False geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length atMap.extend(featA.attributes()) intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: geom.convertToMultiType() outFeat.setGeometry(geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) else: request = QgsFeatureRequest().setFilterFids( intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.constGet()) engine.prepareGeometry() for featB in sourceA.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): add = True diff_geom = QgsGeometry(diff_geom.difference(tmpGeom)) else: try: # Ihis only happens if the bounding box # intersects, but the geometry doesn't diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) if add: try: diff_geom.convertToMultiType() outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo( self. tr('Feature geometry error: One or more output features ignored due to invalid geometry.' )) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, progress): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if inGeom.isEmpty(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList == None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType( aGeom.wkbType() ) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) writer.addFeature(outFeat) progress.setPercentage(int(current * total)) del writer
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ # Retrieve the feature origins_source and sink. The 'sink_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. network_source = self.parameterAsVectorLayer(parameters, self.NETWORK, context) rlid_field = None from_field = None to_field = None class_field = None oper_field = None speedf_field = None speedb_field = None adt_field = None type_field = None for field in network_source.fields(): name = field.name() lname = name.lower() if lname == 'route_id': rlid_field = name elif lname == 'from_measure': from_field = name elif lname == 'to_measure': to_field = name elif lname == 'klass_181': class_field = name elif lname == 'vagha_6': oper_field = name elif lname == 'f_hogst_225': speedf_field = name elif lname == 'b_hogst_225': speedb_field = name elif lname == 'adt_f_117': adt_field = name elif lname == 'vagtr_474': type_field = name expr = f'"{type_field}" = 2' if not network_source.setSubsetString(expr): raise Exception('Failed to set subset') result = processing.run( 'native:buffer', { 'INPUT': network_source, #'DISSOLVE': True, 'DISTANCE': 100, 'END_CAP_STYLE': 2, 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT, }, context=context, feedback=feedback, is_child_algorithm=True, )['OUTPUT'] buffer_layer = context.takeResultLayer(result) print(len(buffer_layer)) bike_buffer = buffer_layer.getGeometry(1) print(bike_buffer) index = QgsSpatialIndex(buffer_layer.getFeatures(), feedback=feedback) geoms = {f.id(): f.geometry() for f in buffer_layer.getFeatures()} ids = index.intersects(bike_buffer.boundingBox()) print(ids) for i in ids: if bike_buffer.intersects(geometry=geoms[i]): print('BOOOOM!') fields = QgsFields() fields.append(QgsField('rlid', QVariant.String)) fields.append(QgsField('from_measure', QVariant.Double)) fields.append(QgsField('to_measure', QVariant.Double)) fields.append(QgsField('oper', QVariant.Int)) fields.append(QgsField('class', QVariant.Int)) fields.append(QgsField('speed', QVariant.Int)) fields.append(QgsField('adt', QVariant.Int)) fields.append(QgsField('vgu', QVariant.Int)) fields.append(QgsField('lts', QVariant.Int)) fields.append(QgsField('ratio', QVariant.Double)) ratios = {1: 0, 2: 1, 3: 1.58, 4: 2} fields.append(QgsField('gc', QVariant.Bool)) expr = ( f'"{type_field}" = 1 AND "{oper_field}" IN (1, 2) AND "{class_field}" <= 5' ) print(expr) if not network_source.setSubsetString(expr): raise Exception('Failed to set subset') network_layer = make_single( network_source, context=context, feedback=feedback, is_child_algorithm=True, ) if not network_source.setSubsetString(''): raise Exception('Failed to set subset') (sink, sink_id) = self.parameterAsSink( parameters, name=self.OUTPUT, context=context, fields=fields, geometryType=network_layer.wkbType(), crs=network_layer.sourceCrs(), ) n = len(network_layer) chunk = n // 100 print(n, chunk) for i, feat in enumerate(network_layer.getFeatures()): """if not feat[type_field] == 1: continue if not feat[oper_field] in [1, 2]: continue if feat[class_field] > 5: continue""" geom = feat.geometry() _feat = QgsFeature(fields) _feat.setGeometry(geom) _feat['rlid'] = feat[rlid_field] _feat['from_measure'] = feat[from_field] _feat['to_measure'] = feat[to_field] _feat['speed'] = speed = max(feat[speedf_field], feat[speedb_field]) _feat['adt'] = adt = feat[adt_field] _feat['class'] = clss = feat[class_field] _feat['oper'] = oper = feat[oper_field] # Find existing bike infra gc = False for fid in index.intersects(geom.boundingBox()): if geom.intersects(geometry=geoms[fid]): gc = True break _feat['gc'] = gc if not adt: if clss <= 3: adt = 9001 elif clss == 4: adt = 3000 else: adt = 900 if not speed: speed = 70 # VGU classify required infra vgu = None if clss <= 2: vgu = 5 elif speed <= 30: vgu = 1 elif speed <= 60 and adt <= 2000: vgu = 2 elif speed <= 80 and adt <= 4000 or speed <= 40 and adt > 4000: vgu = 3 elif speed > 80 or adt > 4000: vgu = 4 # Debug missing if not adt: vgu = -1 if not speed: vgu = -2 _feat['vgu'] = vgu # LTS lts = None if clss <= 2 or speed > 80 or (adt > 4000 and speed > 40 and not gc): lts = 4 elif speed <= 30 or (adt <= 1000 and speed <= 40): lts = 1 elif gc and (adt <= 1000 or (speed <= 60 and adt <= 2000) or (speed <= 40 and adt <= 4000)): lts = 1 elif gc and speed > 60 and adt > 4000: lts = 3 elif not gc and (adt > 4000 or (speed > 40 and adt >= 2000) or (speed > 60 and adt >= 1000)): lts = 3 else: lts = 2 _feat['lts'] = lts _feat['ratio'] = ratios[lts] # Done sink.addFeature(_feat) if feedback and i % chunk == 0: p = 100 * i / n feedback.setProgress(p) return {self.OUTPUT: sink_id}
class breakTool(QObject): finished = pyqtSignal(object) error = pyqtSignal(Exception, basestring) progress = pyqtSignal(float) warning = pyqtSignal(str) killed = pyqtSignal(bool) def __init__(self,layer, tolerance, uid, errors, unlinks): QObject.__init__(self) self.layer = layer self.feat_count = self.layer.featureCount() self.tolerance = tolerance self.uid = uid self.errors = errors self.errors_features = {} self.unlinks = unlinks self.unlinked_features = [] self.unlinks_count = 0 self.ml_keys = {} self.br_keys = {} self.features = [] self.attributes = {} self.geometries = {} self.geometries_wkt = {} self.geometries_vertices = {} # create spatial index object self.spIndex = QgsSpatialIndex() self.layer_fields = [QgsField(i.name(), i.type()) for i in self.layer.dataProvider().fields()] def add_edges(self): new_key_count = 0 f_count = 1 for f in self.layer.getFeatures(): self.progress.emit(3 * f_count / self.feat_count) f_count += 1 if self.killed is True: break geom_type = f.geometry().wkbType() if geom_type not in [5,2,1] and f.geometry().geometry().is3D(): f.geometry().geometry().dropZValue() geom_type = f.geometry().wkbType() if geom_type == 5: if self.errors: self.errors_features[f.id()] = ('multipart', f.geometry().exportToWkt()) for multipart in f.geometry().asGeometryCollection(): new_key_count += 1 attr = f.attributes() new_feat = QgsFeature() new_feat.setAttributes(attr) new_feat.setFeatureId(new_key_count) if self.tolerance: snapped_wkt = make_snapped_wkt(multipart.exportToWkt(), self.tolerance) else: snapped_wkt = multipart.exportToWkt() snapped_geom = QgsGeometry.fromWkt(snapped_wkt) new_feat.setGeometry(snapped_geom) self.features.append(new_feat) self.attributes[new_key_count] = attr self.geometries[new_key_count] = new_feat.geometryAndOwnership() self.geometries_wkt[new_key_count] = snapped_wkt self.geometries_vertices[new_key_count] = [vertex for vertex in vertices_from_wkt_2(snapped_wkt)] # insert features to index self.spIndex.insertFeature(new_feat) self.ml_keys[new_key_count] = f.id() elif geom_type == 1: if self.errors: self.errors_features[f.id()] = ('point', QgsGeometry().exportToWkt()) elif not f.geometry().isGeosValid(): if self.errors: self.errors_features[f.id()] = ('invalid', QgsGeometry().exportToWkt()) elif geom_type == 2: attr = f.attributes() if self.tolerance: snapped_wkt = make_snapped_wkt(f.geometry().exportToWkt(), self.tolerance) else: snapped_wkt = f.geometry().exportToWkt() snapped_geom = QgsGeometry.fromWkt(snapped_wkt) f.setGeometry(snapped_geom) new_key_count += 1 f.setFeatureId(new_key_count) self.features.append(f) self.attributes[f.id()] = attr self.geometries[f.id()] = f.geometryAndOwnership() self.geometries_wkt[f.id()] = snapped_wkt self.geometries_vertices[f.id()] = [vertex for vertex in vertices_from_wkt_2(snapped_wkt)] # insert features to index self.spIndex.insertFeature(f) self.ml_keys[new_key_count] = f.id() def break_features(self): broken_features = [] f_count = 1 for fid in self.geometries.keys(): if self.killed is True: break f_geom = self.geometries[fid] f_attrs = self.attributes[fid] # intersecting lines gids = self.spIndex.intersects(f_geom.boundingBox()) self.progress.emit((45 * f_count / self.feat_count) + 5) f_count += 1 f_errors, vertices = self.find_breakages(fid, gids) if self.errors and f_errors: original_id = self.ml_keys[fid] try: updated_errors = self.errors_features[original_id][0] + f_errors self.errors_features[original_id] = (updated_errors, self.errors_features[original_id][1]) except KeyError: self.errors_features[original_id] = (f_errors, self.geometries[fid].exportToWkt()) if f_errors is None: vertices = [0, len(f_geom.asPolyline()) - 1 ] if f_errors in ['breakage, overlap', 'breakage', 'overlap', None]: for ind, index in enumerate(vertices): if ind != len(vertices) - 1: points = [self.geometries_vertices[fid][i] for i in range(index, vertices[ind + 1] + 1)] p = '' for point in points: p += point[0] + ' ' + point[1] + ', ' wkt = 'LINESTRING(' + p[:-2] + ')' self.feat_count += 1 new_fid = self.feat_count new_feat = [new_fid, f_attrs, wkt] broken_features.append(new_feat) self.br_keys[new_fid] = fid return broken_features def kill(self): self.br_killed = True def find_breakages(self, fid, gids): f_geom = self.geometries[fid] # errors checks must_break = False is_closed = False if f_geom.asPolyline()[0] == f_geom.asPolyline()[-1]: is_closed = True is_orphan = True is_duplicate = False has_overlaps = False # get breaking points breakages = [] # is self intersecting is_self_intersersecting = False for i in f_geom.asPolyline(): if f_geom.asPolyline().count(i) > 1: point = QgsGeometry().fromPoint(QgsPoint(i[0], i[1])) breakages.append(point) is_self_intersersecting = True must_break = True for gid in gids: g_geom = self.geometries[gid] if gid < fid: # duplicate geometry if f_geom.isGeosEqual(g_geom): is_duplicate = True if self.unlinks: if f_geom.crosses(g_geom): crossing_point = f_geom.intersection(g_geom) if crossing_point.wkbType() == 1: self.unlinks_count += 1 unlinks_attrs = [[self.unlinks_count], [gid], [fid], [crossing_point.asPoint()[0]], [crossing_point.asPoint()[1]]] self.unlinked_features.append([self.unlinks_count, unlinks_attrs, crossing_point.exportToWkt()]) elif crossing_point.wkbType() == 4: for cr_point in crossing_point.asGeometryCollection(): self.unlinks_count += 1 unlinks_attrs = [[self.unlinks_count], [gid], [fid], [cr_point.asPoint()[0]], [cr_point.asPoint()[1]]] self.unlinked_features.append([self.unlinks_count, unlinks_attrs, cr_point.exportToWkt()]) if is_duplicate is False: intersection = f_geom.intersection(g_geom) # intersecting geometries at point if intersection.wkbType() == 1 and point_is_vertex(intersection, f_geom): breakages.append(intersection) is_orphan = False must_break = True # intersecting geometries at multiple points elif intersection.wkbType() == 4: for point in intersection.asGeometryCollection(): if point_is_vertex(point, f_geom): breakages.append(point) is_orphan = False must_break = True # overalpping geometries elif intersection.wkbType() == 2 and intersection.length() != f_geom.length(): point1 = QgsGeometry.fromPoint(QgsPoint(intersection.asPolyline()[0])) point2 = QgsGeometry.fromPoint(QgsPoint(intersection.asPolyline()[-1])) if point_is_vertex(point1, f_geom): breakages.append(point1) is_orphan = False must_break = True if point_is_vertex(point2, f_geom): breakages.append(point2) is_orphan = False must_break = True # overalpping multi-geometries # every feature overlaps with itself as a multilinestring elif intersection.wkbType() == 5 and intersection.length() != f_geom.length(): point1 = QgsGeometry.fromPoint(QgsPoint(intersection.asGeometryCollection()[0].asPolyline()[0])) point2 = QgsGeometry.fromPoint(QgsPoint(intersection.asGeometryCollection()[-1].asPolyline()[-1])) if point_is_vertex(point1, f_geom): is_orphan = False has_overlaps = True breakages.append(point1) if point_is_vertex(point2, f_geom): is_orphan = False has_overlaps = True breakages.append(point2) if is_duplicate is True: return 'duplicate', [] else: # add first and last vertex vertices = set([vertex for vertex in find_vertex_index(breakages, f_geom)]) vertices = list(vertices) + [0] + [len(f_geom.asPolyline()) - 1] vertices = list(set(vertices)) vertices.sort() if is_orphan: if is_closed is True: return 'closed polyline', [] else: return 'orphan', [] elif is_self_intersersecting: if has_overlaps: return 'breakage, overlap', vertices else: return 'breakage', vertices elif has_overlaps or must_break: if has_overlaps is True and must_break is True: return 'breakage, overlap', vertices elif has_overlaps is True and must_break is False: return 'overlap', vertices elif has_overlaps is False and must_break is True: if len(vertices) > 2: return 'breakage', vertices else: return None, [] else: return None, [] def updateErrors(self, errors_dict): for k, v in errors_dict.items(): try: original_id = self.br_keys[k] try: original_id = self.ml_keys[k] except KeyError: pass except KeyError: original_id = None if original_id: try: updated_errors = self.errors_features[original_id][0] if ', continuous line' not in self.errors_features[original_id][0]: updated_errors += ', continuous line' self.errors_features[original_id] = (updated_errors, self.errors_features[original_id][1]) except KeyError: self.errors_features[original_id] = ('continuous line', self.geometries[original_id].exportToWkt())
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([])) total = 100.0 / source.featureCount() if source.featureCount() else 0 geoms = dict() null_geom_features = set() index = QgsSpatialIndex() for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): null_geom_features.add(f.id()) continue geoms[f.id()] = f.geometry() index.addFeature(f) feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time # start by assuming everything is unique, and chop away at this list unique_features = dict(geoms) current = 0 removed = 0 for feature_id, geometry in geoms.items(): if feedback.isCanceled(): break if feature_id not in unique_features: # feature was already marked as a duplicate continue candidates = index.intersects(geometry.boundingBox()) candidates.remove(feature_id) for candidate_id in candidates: if candidate_id not in unique_features: # candidate already marked as a duplicate (not sure if this is possible, # since it would mean the current feature would also have to be a duplicate! # but let's be safe!) continue if geometry.isGeosEqual(geoms[candidate_id]): # candidate is a duplicate of feature del unique_features[candidate_id] removed += 1 current += 1 feedback.setProgress(int(0.80 * current * total) + 10) # takes about 80% of time # now, fetch all the feature attributes for the unique features only # be super-smart and don't re-fetch geometries distinct_geoms = set(unique_features.keys()) output_feature_ids = distinct_geoms.union(null_geom_features) total = 100.0 / len(output_feature_ids) if output_feature_ids else 1 request = QgsFeatureRequest().setFilterFids( list(output_feature_ids)).setFlags(QgsFeatureRequest.NoGeometry) for current, f in enumerate(source.getFeatures(request)): if feedback.isCanceled(): break # use already fetched geometry if f.id() not in null_geom_features: f.setGeometry(unique_features[f.id()]) sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time feedback.pushInfo( self.tr('{} duplicate features removed'.format(removed))) return { self.OUTPUT: dest_id, self.DUPLICATE_COUNT: removed, self.RETAINED_COUNT: len(output_feature_ids) }
class PW_OCR_Algorithm(QgsProcessingAlgorithm): INPUT = 'INPUT' RASTER_INPUT = 'RASTER INPUT' FIELD = 'FIELD' ALL_ACTIVE_RASTERS = 'ALL ACTIVE RASTERS' PSM = 'PSM' OEM = 'OEM' REMOVE_COMMA = 'REMOVE_COMMA' TEMP_FILES_LOC = 'TEMP_FILES_LOC' OUTPUT = 'OUTPUT' def tr(self, string): return QCoreApplication.translate('Processing', string) def createInstance(self): return PW_OCR_Algorithm() def name(self): return 'pw_ocr' def displayName(self): return self.tr('PW OCR') def group(self): return self.tr('PW') def groupId(self): return 'pw' def shortHelpString(self): help = """This algorithm recognizes text from raster images inside input polygon features and saves as attribute value of output layer.\ <hr> <b>Input polygon layer</b>\ <br>The features used to recognize text inside them.\ <br><br><b>Text output field</b>\ <br>The field in the input table in which the recognized text will be add.\ <br><br><b>Run for all raster layers</b>\ <br>The algorithm will recognize text from all active raster layers, if checked.\ <br><br><b>Input raster layer</b>\ <br>If above checkbox unchecked, the algorithm will recognize text only from this raster layer.\ <br>In case of multiband raster images, the only first band will be used.\ <br><br><b>Page Segmentation Mode</b>\ <br><i>Tesseract</i> Page Segmentation Mode.\ <br><br><b>OCR Engine Model</b>\ <br><i>Tesseract</i> OCR Engine Model.\ <br><br><b>Remove comma</b>\ <br>If comma is the last character in recognized text, it will be removed.\ <br><br><b>Temporary files location</b>\ <br>Location of such transitional files like image translated to 8bit TIFF, image clipped to the single feature and shapefile contains only one feature. These files are created during iterating over all input features.\ <br><br><b>Output layer</b>\ <br>Location of the output layer with filled text attribute.\ """ return self.tr(help) def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterFeatureSource( self.INPUT, self.tr('Input polygon layer'), [QgsProcessing.TypeVectorPolygon])) self.addParameter( QgsProcessingParameterField( self.FIELD, self.tr('Text output field'), parentLayerParameterName=self.INPUT, type=QgsProcessingParameterField.DataType.String)) self.addParameter( QgsProcessingParameterBoolean( self.ALL_ACTIVE_RASTERS, self.tr('Run for all raster layers'))) self.addParameter( QgsProcessingParameterRasterLayer( self.RASTER_INPUT, self.tr('Input raster layer'), optional=True, )) self.addParameter( QgsProcessingParameterEnum( self.PSM, self.tr('Page Segmentation Mode'), options=[ 'Orientation and script detection (OSD) only.', 'Automatic page segmentation with OSD.', 'Automatic page segmentation, but no OSD, or OCR.', 'Fully automatic page segmentation, but no OSD. (Default if no config)', 'Assume a single column of text of variable sizes.', 'Assume a single uniform block of vertically aligned text.', 'Assume a single uniform block of text.', 'Treat the image as a single text line.', 'Treat the image as a single word.', 'Treat the image as a single word in a circle.', 'Treat the image as a single character.', 'Sparse text. Find as much text as possible in no particular order.', 'Sparse text with OSD.', 'Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.' ], defaultValue=7)) self.addParameter( QgsProcessingParameterEnum( self.OEM, self.tr('OCR Engine Model'), options=['Legacy Tesseract', 'LSTM', '2', '3'], defaultValue=1)) self.addParameter( QgsProcessingParameterBoolean(self.REMOVE_COMMA, self.tr('Remove comma'), True)) self.addParameter( QgsProcessingParameterFolderDestination( self.TEMP_FILES_LOC, self.tr('Temporary files location'), optional=True)) self.addParameter( QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Output layer'))) def processAlgorithm(self, parameters, context, feedback): self.feature_source = self.parameterAsSource(parameters, self.INPUT, context) raster_lyr = self.parameterAsRasterLayer(parameters, self.RASTER_INPUT, context) all_rasters = self.parameterAsBool(parameters, self.ALL_ACTIVE_RASTERS, context) temp_path = self.parameterAsString(parameters, '', context) self.dest_field = self.parameterAsString(parameters, self.FIELD, context) psm = self.parameterAsInt(parameters, 'PSM', context) oem = self.parameterAsInt(parameters, 'OEM', context) self.comma = self.parameterAsBool(parameters, 'Remove_comma', context) (self.sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, self.feature_source.fields(), self.feature_source.wkbType(), self.feature_source.sourceCrs()) self.source_layer = self.feature_source.materialize( QgsFeatureRequest()) feedback.pushInfo('Temporary files path: ' + str(temp_path)) self.source_encod = self.source_layer.dataProvider().encoding() '''context.setDefaultEncoding(self.source_encod) self.output_encod = context.defaultEncoding() feedback.pushInfo('sys.getdefaultencoding(): ' + sys.getdefaultencoding()) feedback.pushInfo('in: ' + self.source_encod + ', out: ' + self.output_encod)''' if self.source_layer == None: list = QgsProject.instance().mapLayersByName( self.feature_source.sourceName()) for lyr in list: if self.feature_source.sourceCrs() == lyr.sourceCrs(): self.source_layer = lyr #feedback.pushInfo('self.source_layer.name(): ' + self.source_layer.name()) if self.feature_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) if raster_lyr is None and not all_rasters: feedback.pushInfo('\nNo raster layer selected!\n') raise QgsProcessingException( self.invalidSourceError(parameters, self.RASTER_INPUT)) self.output_temp_tif = os.path.normpath( os.path.join(temp_path, 'output.tif')) self.output_temp_shp = os.path.normpath( os.path.join(temp_path, 'each_feature.shp')) self.output_temp_page = os.path.normpath( os.path.join(temp_path, 'current_page.tif')) '''here is tesseract config string''' self.config = '--psm ' + str(psm) + ' --oem ' + str(oem) feedback.pushInfo('Tessearct config: ' + self.config) '''creating temporary shp file, necessary for clipping''' self.crs = self.feature_source.sourceCrs().authid() layer = QgsVectorLayer( "multipolygon?crs=" + self.crs + "&field=id:integer", "temporary layer", "memory") QgsVectorFileWriter.writeAsVectorFormat( layer, self.output_temp_shp, self.source_encod, self.feature_source.sourceCrs(), "ESRI Shapefile", False) self.temp_shp_layer = QgsVectorLayer(self.output_temp_shp, "temp", "ogr") features = self.feature_source.getFeatures(QgsFeatureRequest()) self.index = QgsSpatialIndex() for feat in features: self.index.insertFeature(feat) feedback.pushInfo('\nprocessing time calculating...\n') n = [] if not all_rasters and raster_lyr: n = self.index.intersects(raster_lyr.extent()) else: for layer in iface.mapCanvas().layers(): if layer.type() == 1: n = n + self.index.intersects(layer.extent()) self.total = len(n) self.actual = 0 if self.total > 0: feedback.setProgress(self.actual / self.total * 100) if not all_rasters: self.OnThisRaster(feedback, raster_lyr) else: for layer in iface.mapCanvas().layers(): if feedback.isCanceled(): break if layer.type() == 1: self.OnThisRaster(feedback, layer) return {self.OUTPUT: dest_id} def OnThisRaster(self, feedback, Raster_lyr): idsList = self.index.intersects(Raster_lyr.extent()) if idsList: translateopts = gdal.TranslateOptions( outputType=gdal.GDT_Byte, # Eight bit unsigned integer bandList=[1], # Notice that exports only first band format='GTiff') ds = gdal.Translate(self.output_temp_page, Raster_lyr.source(), options=translateopts) if ds is not None: ds = 'image translated to GTiff' else: ds = '<red> something went wrong with translating to GTiff' feedback.pushCommandInfo('\nComputing image ' + str(Raster_lyr.name()) + '\n' + str(ds) + '\n') for id in idsList: #Gdyby tutaj się udało wkomponować coś jak getFeaturebyID byłoby pewnie szybciej for feat in self.feature_source.getFeatures( QgsFeatureRequest()): if int(feat.id()) == id: self.OnThisFeature(feedback, feat, self.output_temp_page) break def OnThisFeature(self, feedback, feat, Raster_lyr_source): pr = self.temp_shp_layer.dataProvider() for temp_feature in pr.getFeatures(): pr.deleteFeatures([temp_feature.id()]) pr.addFeatures([feat]) self.ClipRasterByPolygon(feedback, Raster_lyr_source, self.output_temp_shp, self.output_temp_tif) img = Image.open(self.output_temp_tif) text = pytesseract.image_to_string(img, lang='pol', config=self.config) if self.comma: if text[-1:] == ',': text = text[:-1] feat[self.dest_field] = text #.encode('utf8')#.decode('CP1250') self.sink.addFeature(feat, QgsFeatureSink.FastInsert) self.actual = self.actual + 1 feedback.setProgress(self.actual / self.total * 100) feedback.setProgressText( str(self.actual) + '/' + str(self.total) + ' ' + 'id: ' + str(feat.id())) feedback.pushCommandInfo(text) def ClipRasterByPolygon(self, feedback, rasterPath, polygonPath, outputPath): warpopts = gdal.WarpOptions( outputType=gdal.GDT_Byte, srcSRS=self.crs, cutlineDSName=polygonPath, cropToCutline=True, dstNodata= 255.0, # to jest rozwiązanie wszystkich światowych problemów z dziedziny OCR ) ds = gdal.Warp(outputPath, rasterPath, options=warpopts)
def processAlgorithm(self, feedback): layerA = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_A)) splitLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT_B)) sameLayer = self.getParameterValue(self.INPUT_A) == self.getParameterValue(self.INPUT_B) fieldList = layerA.fields() writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fieldList, QgsWkbTypes.multiType(layerA.wkbType()), layerA.crs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) for aSplitFeature in vector.features(splitLayer, request): splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = vector.features(layerA) if len(features) == 0: total = 100 else: total = 100.0 / float(len(features)) for current, inFeatA in enumerate(features): inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: inGeom = inGeoms.pop() if inGeom.isNull(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList is None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: ProcessingLog.addToLog(ProcessingLog.LOG_WARNING, self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: passed = True if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) writer.addFeature(outFeat) feedback.setProgress(int(current * total)) del writer
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) line_source = self.parameterAsSource(parameters, self.LINES, context) sameLayer = parameters[self.INPUT] == parameters[self.LINES] (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs()) spatialIndex = QgsSpatialIndex() splitGeoms = {} request = QgsFeatureRequest() request.setSubsetOfAttributes([]) request.setDestinationCrs(source.sourceCrs()) for aSplitFeature in line_source.getFeatures(request): if feedback.isCanceled(): break splitGeoms[aSplitFeature.id()] = aSplitFeature.geometry() spatialIndex.insertFeature(aSplitFeature) # honor the case that user has selection on split layer and has setting "use selection" outFeat = QgsFeature() features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 100 for current, inFeatA in enumerate(features): if feedback.isCanceled(): break inGeom = inFeatA.geometry() attrsA = inFeatA.attributes() outFeat.setAttributes(attrsA) if inGeom.isMultipart(): inGeoms = [] for g in inGeom.asGeometryCollection(): inGeoms.append(g) else: inGeoms = [inGeom] lines = spatialIndex.intersects(inGeom.boundingBox()) if len(lines) > 0: # has intersection of bounding boxes splittingLines = [] engine = QgsGeometry.createGeometryEngine(inGeom.geometry()) engine.prepareGeometry() for i in lines: try: splitGeom = splitGeoms[i] except: continue # check if trying to self-intersect if sameLayer: if inFeatA.id() == i: continue if engine.intersects(splitGeom.geometry()): splittingLines.append(splitGeom) if len(splittingLines) > 0: for splitGeom in splittingLines: splitterPList = None outGeoms = [] split_geom_engine = QgsGeometry.createGeometryEngine(splitGeom.geometry()) split_geom_engine.prepareGeometry() while len(inGeoms) > 0: if feedback.isCanceled(): break inGeom = inGeoms.pop() if inGeom.isNull(): # this has been encountered and created a run-time error continue if split_geom_engine.intersects(inGeom.geometry()): inPoints = vector.extractPoints(inGeom) if splitterPList is None: splitterPList = vector.extractPoints(splitGeom) try: result, newGeometries, topoTestPoints = inGeom.splitGeometry(splitterPList, False) except: feedback.reportError(self.tr('Geometry exception while splitting')) result = 1 # splitGeometry: If there are several intersections # between geometry and splitLine, only the first one is considered. if result == 0: # split occurred if inPoints == vector.extractPoints(inGeom): # bug in splitGeometry: sometimes it returns 0 but # the geometry is unchanged outGeoms.append(inGeom) else: inGeoms.append(inGeom) for aNewGeom in newGeometries: inGeoms.append(aNewGeom) else: outGeoms.append(inGeom) else: outGeoms.append(inGeom) inGeoms = outGeoms parts = [] for aGeom in inGeoms: if feedback.isCanceled(): break passed = True if QgsWkbTypes.geometryType(aGeom.wkbType()) == QgsWkbTypes.LineGeometry: numPoints = aGeom.geometry().numPoints() if numPoints <= 2: if numPoints == 2: passed = not aGeom.geometry().isClosed() # tests if vertex 0 = vertex 1 else: passed = False # sometimes splitting results in lines of zero length if passed: parts.append(aGeom) if len(parts) > 0: outFeat.setGeometry(QgsGeometry.collectGeometry(parts)) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
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. noeuds = self.parameterAsSource(parameters, self.NODES, context) num = self.parameterAsFields(parameters, self.NODE_ID, context)[0] mode = self.parameterAsString(parameters, self.MODE, context) rayon = self.parameterAsDouble(parameters, self.RAYON, context) vitesse = self.parameterAsDouble(parameters, self.VITESSE, context) # Compute the number of steps to display within the progress bar and # get features from source ##a=fenetre.split(",") ##fenetre2=QgsRectangle(float(a[0]),float(a[2]),float(a[1]),float(a[3])) index = QgsSpatialIndex(noeuds.getFeatures()) champs = QgsFields() champs.append(QgsField('i', QVariant.String, len=15)) champs.append(QgsField('j', QVariant.String, len=15)) champs.append(QgsField('longueur', QVariant.Double)) champs.append(QgsField('temps', QVariant.Double)) champs.append(QgsField('mode', QVariant.String, len=10)) f = {feature.id(): feature for (feature) in noeuds.getFeatures()} (table_connecteurs, dest_id) = self.parameterAsSink(parameters, self.CONNECTEURS, context, champs, QgsWkbTypes.LineString, noeuds.sourceCrs()) nom_fichier = dest_id fichier_connecteurs = os.path.splitext(nom_fichier)[0] + ".txt" sortie = codecs.open(fichier_connecteurs, "w", encoding="utf-8") nb = len(f) nbc = 0 for i, n in enumerate(noeuds.getFeatures()): near = index.intersects( QgsRectangle(n.geometry().buffer(rayon, 12).boundingBox())) feedback.setProgress(i * 100 / nb) if len(near) > 0: for fid in near: g = f[fid] if not (n[num] == g[num]): l = n.geometry().distance(g.geometry()) id_node = unicode(g.attribute(num)) id_stop = unicode(n.attribute(num)) if l < rayon: nbc += 1 gline = QgsGeometry.fromPolylineXY([ QgsPointXY(n.geometry().centroid().asPoint()), QgsPointXY(g.geometry().centroid().asPoint()) ]) hline = QgsGeometry.fromPolylineXY([ QgsPointXY(g.geometry().centroid().asPoint()), QgsPointXY(n.geometry().centroid().asPoint()) ]) fline = QgsFeature() fline.setGeometry(gline) ll = gline.length() mode = unicode(mode) if vitesse <= 0: fline.setAttributes( [id_stop, id_node, ll / 1000, 0, mode]) else: fline.setAttributes([ id_stop, id_node, ll / 1000, ll * 60 / (vitesse * 1000), mode ]) table_connecteurs.addFeature(fline) if vitesse > 0: sortie.write(id_node + ';' + id_stop + ';' + str((60 / vitesse) * (ll / 1000.0)) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + mode + ';' + mode + '\n') sortie.write(id_stop + ';' + id_node + ';' + str((60 / vitesse) * (ll / 1000.0)) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + mode + ';' + mode + '\n') else: sortie.write(id_node + ';' + id_stop + ';' + str(0.0) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + mode + ';' + mode + '\n') sortie.write(id_stop + ';' + id_node + ';' + str(0.0) + ';' + str(ll / 1000.0) + ';-1;-1;-1;-1;-1;' + mode + ';' + mode + '\n') feedback.setProgressText( unicode(nbc) + "/" + unicode(nb) + self.tr(" connected nodes")) sortie.close() return {self.OUTPUT: dest_id}
def middle(bar,buildings_layer_path,receiver_points_layer_path): buildings_layer_name = os.path.splitext(os.path.basename(buildings_layer_path))[0] buildings_layer = QgsVectorLayer(buildings_layer_path,buildings_layer_name,"ogr") # defines emission_points layer receiver_points_fields = QgsFields() receiver_points_fields.append(QgsField("id_pt", QVariant.Int)) receiver_points_fields.append(QgsField("id_bui", QVariant.Int)) receiver_points_writer = QgsVectorFileWriter(receiver_points_layer_path, "System", receiver_points_fields, QgsWkbTypes.Point, buildings_layer.crs(),"ESRI Shapefile") # gets features from layer buildings_feat_all = buildings_layer.dataProvider().getFeatures() # creates SpatialIndex buildings_spIndex = QgsSpatialIndex() buildings_feat_all_dict = {} for buildings_feat in buildings_feat_all: buildings_spIndex.insertFeature(buildings_feat) buildings_feat_all_dict[buildings_feat.id()] = buildings_feat # defines distanze_point distance_point = 0.1 # re-gets features from layer buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_feat_total = buildings_layer.dataProvider().featureCount() pt_id = 0 buildings_feat_number = 0 for buildings_feat in buildings_feat_all: buildings_feat_number = buildings_feat_number + 1 barValue = buildings_feat_number/float(buildings_feat_total)*100 bar.setValue(barValue) building_geom = buildings_feat.geometry() if building_geom.isMultipart(): buildings_pt = building_geom.asMultiPolygon()[0] #building_geom.convertToSingleType() else: buildings_pt = buildings_feat.geometry().asPolygon() # creates the search rectangle to match the receiver point in the building and del them rect = QgsRectangle() rect.setXMinimum( buildings_feat.geometry().boundingBox().xMinimum() - distance_point ) rect.setXMaximum( buildings_feat.geometry().boundingBox().xMaximum() + distance_point ) rect.setYMinimum( buildings_feat.geometry().boundingBox().yMinimum() - distance_point ) rect.setYMaximum( buildings_feat.geometry().boundingBox().yMaximum() + distance_point ) buildings_selection = buildings_spIndex.intersects(rect) if len(buildings_pt) > 0: for i in range(0,len(buildings_pt)): buildings_pts = buildings_pt[i] #### # start part to delete pseudo vertex # this part it's different from the diffraction delete pseudo vertex part pts_index_to_delete_list = [] m_delta = 0.01 for ii in range(0,len(buildings_pts)-1): x1 = buildings_pts[ii-1][0] x2 = buildings_pts[ii][0] x3 = buildings_pts[ii+1][0] y1 = buildings_pts[ii-1][1] y2 = buildings_pts[ii][1] y3 = buildings_pts[ii+1][1] # particular cases: first point to delete! (remember that the first and the last have the same coordinates) if ii == 0 and (x2 == x1 and y2 == y1): x1 = buildings_pts[ii-2][0] y1 = buildings_pts[ii-2][1] # angular coefficient to find pseudo vertex if x2 - x1 != 0 and x3 - x1 != 0: m1 = ( y2 - y1 ) / ( x2 - x1 ) m2 = ( y3 - y1 ) / ( x3 - x1 ) #if round(m1,2) <= round(m2,2) + m_delta and round(m1,2) >= round(m2,2) - m_delta: if m1 <= m2 + m_delta and m1 >= m2 - m_delta: pts_index_to_delete_list.append(ii) # particular cases: first point to delete! (remember that the first and the last have the same coordinates) # here we delete the last and add x3,y3 (buildings_pts[ii+1] - the new last point) if ii == 0: pts_index_to_delete_list.append(len(buildings_pts)-1) buildings_pts.append(buildings_pts[ii+1]) # del pseudo vertex pts_index_to_delete_list = sorted(pts_index_to_delete_list, reverse=True) for pt_index_to_del in pts_index_to_delete_list: del buildings_pts[pt_index_to_del] # end part to delete pseudo vertex # for to generate receiver points for ii in range(0,len(buildings_pts)-1): x1 = buildings_pts[ii][0] x2 = buildings_pts[ii+1][0] y1 = buildings_pts[ii][1] y2 = buildings_pts[ii+1][1] xm = ( x1 + x2 )/2 ym = ( y1 + y2 )/2 if y2 == y1: dx = 0 dy = distance_point elif x2 == x1: dx = distance_point dy = 0 else: m = ( y2 - y1 )/ ( x2 - x1 ) m_p = -1/m dx = sqrt((distance_point**2)/(1 + m_p**2)) dy = sqrt(((distance_point**2)*(m_p**2))/(1 + m_p**2)) if (x2 >= x1 and y2 >= y1) or (x2 < x1 and y2 < y1): pt1 = QgsPointXY(xm + dx, ym - dy) pt2 = QgsPointXY(xm - dx, ym + dy) if (x2 >= x1 and y2 < y1) or (x2 < x1 and y2 >= y1): pt1 = QgsPointXY(xm + dx, ym + dy) pt2 = QgsPointXY(xm - dx, ym - dy) pt = QgsFeature() # pt1, check if is in a building and eventually add it pt.setGeometry(QgsGeometry.fromPointXY(pt1)) intersect = 0 for buildings_id in buildings_selection: if buildings_feat_all_dict[buildings_id].geometry().intersects(pt.geometry()) == 1: intersect = 1 break if intersect == 0: pt.setAttributes([pt_id, buildings_feat.id()]) receiver_points_writer.addFeature(pt) pt_id = pt_id + 1 # pt2, check if is in a building and eventually add it pt.setGeometry(QgsGeometry.fromPointXY(pt2)) intersect = 0 for buildings_id in buildings_selection: if buildings_feat_all_dict[buildings_id].geometry().intersects(pt.geometry()) == 1: intersect = 1 break if intersect == 0: pt.setAttributes([pt_id, buildings_feat.id()]) receiver_points_writer.addFeature(pt) pt_id = pt_id + 1 del receiver_points_writer #print receiver_points_layer_path receiver_points_layer_name = os.path.splitext(os.path.basename(receiver_points_layer_path))[0] #print receiver_points_layer_name receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr") QgsProject.instance().addMapLayers([receiver_points_layer])
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): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] output_fields = QgsProcessingUtils.combineFields( fieldListA, fieldListB) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, output_fields, geomType, sourceA.sourceCrs()) outFeat = QgsFeature() indexB = QgsSpatialIndex( sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(sourceA.sourceCrs(), context.transformContext())), feedback) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1 count = 0 for featA in sourceA.getFeatures( QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)): if feedback.isCanceled(): break if not featA.hasGeometry(): continue geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects) request.setDestinationCrs(sourceA.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(field_indices_b) engine = None if len(intersects) > 0: # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if engine.intersects(tmpGeom.constGet()): out_attributes = [ featA.attributes()[i] for i in field_indices_a ] out_attributes.extend( [featB.attributes()[i] for i in field_indices_b]) int_geom = QgsGeometry(geom.intersection(tmpGeom)) if int_geom.wkbType( ) == QgsWkbTypes.Unknown or QgsWkbTypes.flatType( int_geom.wkbType( )) == QgsWkbTypes.GeometryCollection: int_com = geom.combine(tmpGeom) int_geom = QgsGeometry() if int_com: int_sym = geom.symDifference(tmpGeom) int_geom = QgsGeometry(int_com.difference(int_sym)) if int_geom.isEmpty() or not int_geom.isGeosValid(): raise QgsProcessingException( self.tr('GEOS geoprocessing error: One or ' 'more input features have invalid ' 'geometry.')) try: if QgsWkbTypes.geometryType(int_geom.wkbType( )) == QgsWkbTypes.geometryType(geomType): int_geom.convertToMultiType() outFeat.setGeometry(int_geom) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Feature geometry error: One or more ' 'output features ignored due to invalid ' 'geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def run_stats(self): self.progressBar_stats.setValue(0) self.label_progressStats.setText('') # noinspection PyArgumentList QApplication.processEvents() blurred_layer = self.comboBox_blurredLayer.currentLayer() stats_layer = self.comboBox_statsLayer.currentLayer() try: if not blurred_layer or not stats_layer: raise NoLayerProvidedException crs_blurred_layer = blurred_layer.crs() crs_stats_layer = stats_layer.crs() if crs_blurred_layer != crs_stats_layer: raise DifferentCrsException( epsg1=crs_blurred_layer.authid(), epsg2=crs_stats_layer.authid()) if blurred_layer == stats_layer: raise NoLayerProvidedException if not blurred_layer or not stats_layer: raise NoLayerProvidedException nb_feature_stats = stats_layer.featureCount() nb_feature_blurred = blurred_layer.featureCount() features_stats = {} label_preparing = tr('Preparing index on the stats layer') label_creating = tr('Creating index on the stats layer') label_calculating = tr('Calculating') if QGis.QGIS_VERSION_INT < 20700: self.label_progressStats.setText('%s 1/3' % label_preparing) for i, feature in enumerate(stats_layer.getFeatures()): features_stats[feature.id()] = feature percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 2/3' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex() for i, f in enumerate(stats_layer.getFeatures()): index.insertFeature(f) percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 3/3' % label_calculating) else: # If QGIS >= 2.7, we can speed up the spatial index. # From 1 min 15 to 7 seconds on my PC. self.label_progressStats.setText('%s 1/2' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex(stats_layer.getFeatures()) self.label_progressStats.setText('%s 2/2' % label_calculating) # noinspection PyArgumentList QApplication.processEvents() self.tab = [] for i, feature in enumerate(blurred_layer.getFeatures()): count = 0 ids = index.intersects(feature.geometry().boundingBox()) for unique_id in ids: request = QgsFeatureRequest().setFilterFid(unique_id) f = stats_layer.getFeatures(request).next() if f.geometry().intersects(feature.geometry()): count += 1 self.tab.append(count) percent = int((i + 1) * 100 / nb_feature_blurred) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() stats = Stats(self.tab) items_stats = [ 'Count(blurred),%d' % nb_feature_blurred, 'Count(stats),%d' % nb_feature_stats, 'Min,%d' % stats.min(), 'Average,%f' % stats.average(), 'Max,%d' % stats.max(), 'Median,%f' % stats.median(), 'Range,%d' % stats.range(), 'Variance,%f' % stats.variance(), 'Standard deviation,%f' % stats.standard_deviation() ] self.tableWidget.clear() self.tableWidget.setColumnCount(2) labels = ['Parameters', 'Values'] self.tableWidget.setHorizontalHeaderLabels(labels) self.tableWidget.setRowCount(len(items_stats)) for i, item in enumerate(items_stats): s = item.split(',') self.tableWidget.setItem(i, 0, QTableWidgetItem(s[0])) self.tableWidget.setItem(i, 1, QTableWidgetItem(s[1])) self.tableWidget.resizeRowsToContents() self.draw_plot(self.tab) except GeoHealthException, e: self.label_progressStats.setText('') display_message_bar(msg=e.msg, level=e.level, duration=e.duration)
def processAlgorithm(self, parameters, context, feedback): poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) if poly_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POLYGONS)) point_source = self.parameterAsSource(parameters, self.POINTS, context) if point_source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.POINTS)) weight_field = self.parameterAsString(parameters, self.WEIGHT, context) weight_field_index = -1 if weight_field: weight_field_index = point_source.fields().lookupField( weight_field) class_field = self.parameterAsString(parameters, self.CLASSFIELD, context) class_field_index = -1 if class_field: class_field_index = point_source.fields().lookupField(class_field) field_name = self.parameterAsString(parameters, self.FIELD, context) fields = poly_source.fields() if fields.lookupField(field_name) < 0: fields.append(QgsField(field_name, QVariant.Int)) field_index = fields.lookupField(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( point_source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes( []).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) point_attribute_indices = [] if weight_field_index >= 0: point_attribute_indices.append(weight_field_index) if class_field_index >= 0: point_attribute_indices.append(class_field_index) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount( ) else 0 for current, polygon_feature in enumerate(features): if feedback.isCanceled(): break count = 0 output_feature = QgsFeature() if polygon_feature.hasGeometry(): geom = polygon_feature.geometry() engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() count = 0 classes = set() points = spatialIndex.intersects(geom.boundingBox()) if len(points) > 0: request = QgsFeatureRequest().setFilterFids( points).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(point_attribute_indices) for point_feature in point_source.getFeatures(request): if feedback.isCanceled(): break if engine.contains( point_feature.geometry().constGet()): if weight_field_index >= 0: weight = point_feature.attributes( )[weight_field_index] try: count += float(weight) except: # Ignore fields with non-numeric values pass elif class_field_index >= 0: point_class = point_feature.attributes( )[class_field_index] if point_class not in classes: classes.add(point_class) else: count += 1 output_feature.setGeometry(geom) attrs = polygon_feature.attributes() if class_field_index >= 0: score = len(classes) else: score = count if field_index == len(attrs): attrs.append(score) else: attrs[field_index] = score output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
class Worker(QtCore.QObject): '''The worker that does the heavy lifting. /* QGIS offers spatial indexes to make spatial search more * effective. QgsSpatialIndex will find the nearest index * (approximate) geometry (rectangle) for a supplied point. * QgsSpatialIndex will only give correct results when searching * for the nearest neighbour of a point in a point data set. * So something has to be done for non-point data sets * * Non-point join data set: * A two pass search is performed. First the index is used to * find the nearest index geometry (approximation - rectangle), * and then compute the distance to the actual indexed geometry. * A rectangle is constructed from this (maximum minimum) * distance, and this rectangle is used to find all features in * the join data set that may be the closest feature to the given * point. * For all the features is this candidate set, the actual * distance to the given point is calculated, and the nearest * feature is returned. * * Non-point input data set: * First the centroid of the non-point input geometry is * calculated. Then the index is used to find the nearest * neighbour to this point (using the approximate index * geometry). * The distance vector to this feature, combined with the * bounding rectangle of the input feature is used to create a * search rectangle to find the candidate join geometries. * For all the features is this candidate set, the actual * distance to the given feature is calculated, and the nearest * feature is returned. * * Joins involving multi-geometry data sets are not supported * by a spatial index. * */ ''' # Define the signals used to communicate back to the application progress = QtCore.pyqtSignal(float) # For reporting progress status = QtCore.pyqtSignal(str) # For reporting status error = QtCore.pyqtSignal(str) # For reporting errors # Signal for sending over the result: finished = QtCore.pyqtSignal(bool, object) def __init__(self, inputvectorlayer, joinvectorlayer, outputlayername, joinprefix, distancefieldname="distance", approximateinputgeom=False, usejoinlayerapproximation=False, usejoinlayerindex=True, selectedinputonly=True, selectedjoinonly=True): """Initialise. Arguments: inputvectorlayer -- (QgsVectorLayer) The base vector layer for the join joinvectorlayer -- (QgsVectorLayer) the join layer outputlayername -- (string) the name of the output memory layer joinprefix -- (string) the prefix to use for the join layer attributes in the output layer distancefieldname -- name of the (new) field where neighbour distance is stored approximateinputgeom -- (boolean) should the input geometry be approximated? Is only be set for non-single-point layers usejoinlayerindexapproximation -- (boolean) should the index geometry approximations be used for the join? usejoinlayerindex -- (boolean) should an index for the join layer be used. """ QtCore.QObject.__init__(self) # Essential! # Set a variable to control the use of indexes and exact # geometries for non-point input geometries self.nonpointexactindex = usejoinlayerindex # Creating instance variables from the parameters self.inpvl = inputvectorlayer self.joinvl = joinvectorlayer self.outputlayername = outputlayername self.joinprefix = joinprefix self.approximateinputgeom = approximateinputgeom self.usejoinlayerapprox = usejoinlayerapproximation self.selectedinonly = selectedinputonly self.selectedjoonly = selectedjoinonly # Check if the layers are the same (self join) self.selfjoin = False if self.inpvl is self.joinvl: # This is a self join self.selfjoin = True # The name of the attribute for the calculated distance self.distancename = distancefieldname # Creating instance variables for the progress bar ++ # Number of elements that have been processed - updated by # calculate_progress self.processed = 0 # Current percentage of progress - updated by # calculate_progress self.percentage = 0 # Flag set by kill(), checked in the loop self.abort = False # Number of features in the input layer - used by # calculate_progress (set when needed) self.feature_count = 1 # The number of elements that is needed to increment the # progressbar (set when needed) self.increment = 0 def run(self): try: # Check if the layers look OK if self.inpvl is None or self.joinvl is None: self.status.emit('Layer is missing!') self.finished.emit(False, None) return # Check if there are features in the layers incount = 0 if self.selectedinonly: incount = self.inpvl.selectedFeatureCount() else: incount = self.inpvl.featureCount() joincount = 0 if self.selectedjoonly: joincount = self.joinvl.selectedFeatureCount() else: joincount = self.joinvl.featureCount() if incount == 0 or joincount == 0: self.status.emit('Layer without features!') self.finished.emit(False, None) return # Check the geometry type and prepare the output layer geometryType = self.inpvl.geometryType() geometrytypetext = 'Point' if geometryType == QgsWkbTypes.PointGeometry: geometrytypetext = 'Point' elif geometryType == QgsWkbTypes.LineGeometry: geometrytypetext = 'LineString' elif geometryType == QgsWkbTypes.PolygonGeometry: geometrytypetext = 'Polygon' # Does the input vector contain multi-geometries? # Try to check the first feature # This is not used for anything yet self.inputmulti = False if self.selectedinonly: #feats = self.inpvl.selectedFeaturesIterator() feats = self.inpvl.getSelectedFeatures() else: feats = self.inpvl.getFeatures() if feats is not None: testfeature = next(feats) feats.rewind() feats.close() if testfeature is not None: if testfeature.hasGeometry(): if testfeature.geometry().isMultipart(): self.inputmulti = True geometrytypetext = 'Multi' + geometrytypetext else: pass else: self.status.emit('No geometry!') self.finished.emit(False, None) return else: self.status.emit('No input features!') self.finished.emit(False, None) return else: self.status.emit('getFeatures returns None for input layer!') self.finished.emit(False, None) return geomttext = geometrytypetext # Set the coordinate reference system to the input # layer's CRS using authid (proj4 may be more robust) if self.inpvl.crs() is not None: geomttext = (geomttext + "?crs=" + str(self.inpvl.crs().authid())) # Retrieve the fields from the input layer outfields = self.inpvl.pendingFields().toList() # Retrieve the fields from the join layer if self.joinvl.pendingFields() is not None: jfields = self.joinvl.pendingFields().toList() for joinfield in jfields: outfields.append( QgsField(self.joinprefix + str(joinfield.name()), joinfield.type())) else: self.status.emit('Unable to get any join layer fields') # Add the nearest neighbour distance field # Check if there is already a "distance" field # (should be avoided in the user interface) # Try a new name if there is a collission collission = True while collission: # Iterate until there are no collissions collission = False for field in outfields: if field.name() == self.distancename: self.status.emit( 'Distance field already exists - renaming!') #self.abort = True #self.finished.emit(False, None) #break collission = True self.distancename = self.distancename + '1' outfields.append(QgsField(self.distancename, QVariant.Double)) # Create a memory layer using a CRS description self.mem_joinl = QgsVectorLayer(geomttext, self.outputlayername, "memory") # Set the CRS to the inputlayer's CRS self.mem_joinl.setCrs(self.inpvl.crs()) self.mem_joinl.startEditing() # Add the fields for field in outfields: self.mem_joinl.dataProvider().addAttributes([field]) # For an index to be used, the input layer has to be a # point layer, the input layer geometries have to be # approximated to centroids, or the user has to have # accepted that a join layer index is used (for # non-point input layers). # (Could be extended to multipoint) if (self.inpvl.wkbType() == QgsWkbTypes.Point or self.inpvl.wkbType() == QgsWkbTypes.Point25D or self.approximateinputgeom or self.nonpointexactindex): # Create a spatial index to speed up joining self.status.emit('Creating join layer index...') # Number of features in the input layer - used by # calculate_progress if self.selectedjoonly: self.feature_count = self.joinvl.selectedFeatureCount() else: self.feature_count = self.joinvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 self.joinlind = QgsSpatialIndex() if self.selectedjoonly: #for feat in self.joinvl.selectedFeaturesIterator(): for feat in self.joinvl.getSelectedFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() else: for feat in self.joinvl.getFeatures(): # Allow user abort if self.abort is True: break self.joinlind.insertFeature(feat) self.calculate_progress() self.status.emit('Join layer index created!') self.processed = 0 self.percentage = 0 #self.calculate_progress() # Does the join layer contain multi geometries? # Try to check the first feature # This is not used for anything yet self.joinmulti = False if self.selectedjoonly: feats = self.joinvl.getSelectedFeatures() else: feats = self.joinvl.getFeatures() if feats is not None: testfeature = next(feats) feats.rewind() feats.close() if testfeature is not None: if testfeature.hasGeometry(): if testfeature.geometry().isMultipart(): self.joinmulti = True else: self.status.emit('No join geometry!') self.finished.emit(False, None) return else: self.status.emit('No join features!') self.finished.emit(False, None) return # Prepare for the join by fetching the layers into memory # Add the input features to a list self.inputf = [] if self.selectedinonly: for f in self.inpvl.getSelectedFeatures(): self.inputf.append(f) else: for f in self.inpvl.getFeatures(): self.inputf.append(f) # Add the join features to a list self.joinf = [] if self.selectedjoonly: for f in self.joinvl.getSelectedFeatures(): self.joinf.append(f) else: for f in self.joinvl.getFeatures(): self.joinf.append(f) self.features = [] # Do the join! # Number of features in the input layer - used by # calculate_progress if self.selectedinonly: self.feature_count = self.inpvl.selectedFeatureCount() else: self.feature_count = self.inpvl.featureCount() # The number of elements that is needed to increment the # progressbar - set early in run() self.increment = self.feature_count // 1000 # Using the original features from the input layer for feat in self.inputf: # Allow user abort if self.abort is True: break self.do_indexjoin(feat) self.calculate_progress() self.mem_joinl.dataProvider().addFeatures(self.features) self.status.emit('Join finished') except: import traceback self.error.emit(traceback.format_exc()) self.finished.emit(False, None) if self.mem_joinl is not None: self.mem_joinl.rollBack() else: self.mem_joinl.commitChanges() if self.abort: self.finished.emit(False, None) else: self.status.emit('Delivering the memory layer...') self.finished.emit(True, self.mem_joinl) def calculate_progress(self): '''Update progress and emit a signal with the percentage''' self.processed = self.processed + 1 # update the progress bar at certain increments if (self.increment == 0 or self.processed % self.increment == 0): # Calculate percentage as integer perc_new = (self.processed * 100) / self.feature_count if perc_new > self.percentage: self.percentage = perc_new self.progress.emit(self.percentage) def kill(self): '''Kill the thread by setting the abort flag''' self.abort = True def do_indexjoin(self, feat): '''Find the nearest neigbour using an index, if possible Parameter: feat -- The feature for which a neighbour is sought ''' infeature = feat # Get the feature ID infeatureid = infeature.id() # Get the feature geometry inputgeom = infeature.geometry() # Shall approximate input geometries be used? if self.approximateinputgeom: # Use the centroid as the input geometry inputgeom = infeature.geometry().centroid() # Check if the coordinate systems are equal, if not, # transform the input feature! if (self.inpvl.crs() != self.joinvl.crs()): try: inputgeom.transform( QgsCoordinateTransform(self.inpvl.crs(), self.joinvl.crs())) except: import traceback self.error.emit( self.tr('CRS Transformation error!') + ' - ' + traceback.format_exc()) self.abort = True return ## Find the closest feature! nnfeature = None mindist = float("inf") if (self.approximateinputgeom or self.inpvl.wkbType() == QgsWkbTypes.Point or self.inpvl.wkbType() == QgsWkbTypes.Point25D): # The input layer's geometry type is point, or has been # approximated to point (centroid). # Then a join index will always be used. if (self.usejoinlayerapprox or self.joinvl.wkbType() == QgsWkbTypes.Point or self.joinvl.wkbType() == QgsWkbTypes.Point25D): # The join index nearest neighbour function can # be used without refinement. if self.selfjoin: # Self join! # Have to get the two nearest neighbours nearestids = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) if nearestids[0] == infeatureid and len(nearestids) > 1: # The first feature is the same as the input # feature, so choose the second one if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestids[1]))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestids[1]))) else: # The first feature is not the same as the # input feature, so choose it if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestids[0]))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestids[0]))) else: # Not a self join, so we can search for only the # nearest neighbour (1) nearestid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestid))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestid))) mindist = inputgeom.distance(nnfeature.geometry()) elif (self.joinvl.wkbType() == QgsWkbTypes.Polygon or self.joinvl.wkbType() == QgsWkbTypes.Polygon25D or self.joinvl.wkbType() == QgsWkbTypes.LineString or self.joinvl.wkbType() == QgsWkbTypes.LineString25D): # Use the join layer index to speed up the join when # the join layer geometry type is polygon or line # and the input layer geometry type is point or an # approximation (point) nearestindexid = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 1)[0] # Check for self join if self.selfjoin and nearestindexid == infeatureid: # Self join and same feature, so get the # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( inputgeom.asPoint(), 2) nearestindexid = nearestindexes[0] if (nearestindexid == infeatureid and len(nearestindexes) > 1): nearestindexid = nearestindexes[1] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestindexid))) else: nnfeature = next( self.joinvl.getFeatures( QgsFeatureRequest(nearestindexid))) mindist = inputgeom.distance(nnfeature.geometry()) px = inputgeom.asPoint().x() py = inputgeom.asPoint().y() closefids = self.joinlind.intersects( QgsRectangle(px - mindist, py - mindist, px + mindist, py + mindist)) for closefid in closefids: if self.abort is True: break # Check for self join and same feature if self.selfjoin and closefid == infeatureid: continue if self.selectedjoonly: closef = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next( self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Go through all the features from the join layer! for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break else: # non-simple point input geometries (could be multipoint) if (self.nonpointexactindex): # Use the spatial index on the join layer (default). # First we do an approximate search # Get the input geometry centroid centroid = infeature.geometry().centroid() centroidgeom = centroid.asPoint() # Find the nearest neighbour (index geometries only) nearestid = self.joinlind.nearestNeighbor(centroidgeom, 1)[0] # Check for self join if self.selfjoin and nearestid == infeatureid: # Self join and same feature, so get the two # first two neighbours nearestindexes = self.joinlind.nearestNeighbor( centroidgeom, 2) nearestid = nearestindexes[0] if nearestid == infeatureid and len(nearestindexes) > 1: nearestid = nearestindexes[1] if self.selectedjoonly: nnfeature = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(nearestid))) else: nnfeature = next( self.joinvl.getFeatures(QgsFeatureRequest(nearestid))) mindist = inputgeom.distance(nnfeature.geometry()) # Calculate the search rectangle (inputgeom BBOX inpbbox = infeature.geometry().boundingBox() minx = inpbbox.xMinimum() - mindist maxx = inpbbox.xMaximum() + mindist miny = inpbbox.yMinimum() - mindist maxy = inpbbox.yMaximum() + mindist #minx = min(inpbbox.xMinimum(), centroidgeom.x() - mindist) #maxx = max(inpbbox.xMaximum(), centroidgeom.x() + mindist) #miny = min(inpbbox.yMinimum(), centroidgeom.y() - mindist) #maxy = max(inpbbox.yMaximum(), centroidgeom.y() + mindist) searchrectangle = QgsRectangle(minx, miny, maxx, maxy) # Fetch the candidate join geometries closefids = self.joinlind.intersects(searchrectangle) # Loop through the geometries and choose the closest # one for closefid in closefids: if self.abort is True: break # Check for self join and identical feature if self.selfjoin and closefid == infeatureid: continue if self.selectedjoonly: closef = next( self.joinvl.getSelectedFeatures( QgsFeatureRequest(closefid))) else: closef = next( self.joinvl.getFeatures( QgsFeatureRequest(closefid))) thisdistance = inputgeom.distance(closef.geometry()) if thisdistance < mindist: mindist = thisdistance nnfeature = closef if mindist == 0: break else: # Join with no index use # Check all the features of the join layer! mindist = float("inf") # should not be necessary for inFeatJoin in self.joinf: if self.abort is True: break joingeom = inFeatJoin.geometry() thisdistance = inputgeom.distance(joingeom) # If the distance is 0, check for equality of the # features (in case it is a self join) if (thisdistance == 0 and self.selfjoin and infeatureid == inFeatJoin.id()): continue if thisdistance < mindist: mindist = thisdistance nnfeature = inFeatJoin # For 0 distance, settle with the first feature if mindist == 0: break if not self.abort: # Collect the attribute atMapA = infeature.attributes() atMapB = nnfeature.attributes() attrs = [] attrs.extend(atMapA) attrs.extend(atMapB) attrs.append(mindist) # Create the feature outFeat = QgsFeature() # Use the original input layer geometry!: outFeat.setGeometry(infeature.geometry()) # Use the modified input layer geometry (could be # centroid) #outFeat.setGeometry(inputgeom) # Add the attributes outFeat.setAttributes(attrs) self.calculate_progress() self.features.append(outFeat) #self.mem_joinl.dataProvider().addFeatures([outFeat]) def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('NNJoinEngine', message)
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fieldsA = self.parameterAsFields(parameters, self.INPUT_FIELDS, context) fieldsB = self.parameterAsFields(parameters, self.OVERLAY_FIELDS, context) fieldListA = QgsFields() field_indices_a = [] if len(fieldsA) > 0: for f in fieldsA: idxA = sourceA.fields().lookupField(f) if idxA >= 0: field_indices_a.append(idxA) fieldListA.append(sourceA.fields()[idxA]) else: fieldListA = sourceA.fields() field_indices_a = [i for i in range(0, fieldListA.count())] fieldListB = QgsFields() field_indices_b = [] if len(fieldsB) > 0: for f in fieldsB: idxB = sourceB.fields().lookupField(f) if idxB >= 0: field_indices_b.append(idxB) fieldListB.append(sourceB.fields()[idxB]) else: fieldListB = sourceB.fields() field_indices_b = [i for i in range(0, fieldListB.count())] fieldListB = vector.testForUniqueness(fieldListA, fieldListB) for b in fieldListB: fieldListA.append(b) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fieldListA, geomType, sourceA.sourceCrs()) outFeat = QgsFeature() indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / sourceA.featureCount() if sourceA.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(field_indices_a)): if feedback.isCanceled(): break if not featA.hasGeometry(): continue geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects) request.setDestinationCrs(sourceA.sourceCrs()) request.setSubsetOfAttributes(field_indices_b) engine = None if len(intersects) > 0: # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): out_attributes = [featA.attributes()[i] for i in field_indices_a] out_attributes.extend([featB.attributes()[i] for i in field_indices_b]) int_geom = QgsGeometry(geom.intersection(tmpGeom)) if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: int_com = geom.combine(tmpGeom) int_geom = QgsGeometry() if int_com: int_sym = geom.symDifference(tmpGeom) int_geom = QgsGeometry(int_com.difference(int_sym)) if int_geom.isEmpty() or not int_geom.isGeosValid(): raise QgsProcessingException( self.tr('GEOS geoprocessing error: One or ' 'more input features have invalid ' 'geometry.')) try: if int_geom.wkbType() in wkbTypeGroups[wkbTypeGroups[int_geom.wkbType()]]: outFeat.setGeometry(int_geom) outFeat.setAttributes(out_attributes) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: raise QgsProcessingException( self.tr('Feature geometry error: One or more ' 'output features ignored due to invalid ' 'geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context) minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context) bbox = source.sourceExtent() sourceIndex = QgsSpatialIndex(source, feedback) fields = QgsFields() fields.append(QgsField('id', QVariant.Int, '', 10, 0)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.Point, source.sourceCrs()) nPoints = 0 nIterations = 0 maxIterations = pointCount * 200 total = 100.0 / pointCount if pointCount else 1 index = QgsSpatialIndex() points = dict() random.seed() while nIterations < maxIterations and nPoints < pointCount: if feedback.isCanceled(): break rx = bbox.xMinimum() + bbox.width() * random.random() ry = bbox.yMinimum() + bbox.height() * random.random() p = QgsPointXY(rx, ry) geom = QgsGeometry.fromPointXY(p) ids = sourceIndex.intersects(geom.buffer(5, 5).boundingBox()) if len(ids) > 0 and \ vector.checkMinDistance(p, index, minDistance, points): request = QgsFeatureRequest().setFilterFids( ids).setSubsetOfAttributes([]) for f in source.getFeatures(request): if feedback.isCanceled(): break tmpGeom = f.geometry() if geom.within(tmpGeom): f = QgsFeature(nPoints) f.initAttributes(1) f.setFields(fields) f.setAttribute('id', nPoints) f.setGeometry(geom) sink.addFeature(f, QgsFeatureSink.FastInsert) index.insertFeature(f) points[nPoints] = p nPoints += 1 feedback.setProgress(int(nPoints * total)) nIterations += 1 if nPoints < pointCount: feedback.pushInfo( self.tr( 'Could not generate requested number of random points. ' 'Maximum number of attempts exceeded.')) return {self.OUTPUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = QgsProcessingUtils.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs()) for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): if feedback.isCanceled(): break geom = featA.geometry() diffGeom = QgsGeometry(geom) attrs = featA.attributes() attrs = [NULL] * length + attrs intersects = indexA.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) for featB in sourceA.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() if diffGeom.intersects(tmpGeom): diffGeom = QgsGeometry(diffGeom.difference(tmpGeom)) try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) continue count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def run(self): """Experimental impact function.""" # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') # There is no wet in the class mapping if self.wet not in self.hazard_class_mapping: raise ZeroImpactException(tr( 'There is no flooded area in the hazard layers, thus there ' 'is no affected building.')) self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) if affected_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of the ' 'hazard layer. Please change the Affected Field parameter in ' 'the IF Option.') % self.hazard_class_attribute raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( self.exposure_class_attribute) if building_type_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of ' 'the exposure layer. Please change the Building Type ' 'Field parameter in the IF Option.' ) % self.exposure_class_attribute raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes( [QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex( self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the buildings from E and extent buildings_are_points = is_point_layer(self.exposure.layer) if buildings_are_points: building_layer = QgsVectorLayer( 'Point?crs=' + srs, 'impact_buildings', 'memory') else: building_layer = QgsVectorLayer( 'Polygon?crs=' + srs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( 'There are no objects in the hazard layer with %s ' 'value in %s. Please check your data or use another ' 'attribute.') % ( self.hazard_class_attribute, ', '.join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Filter out just those EXPOSURE features in the analysis extents transform = QgsCoordinateTransform( self.requested_extent_crs, self.exposure.layer.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # We will use this transform to project each exposure feature into # the CRS of the Hazard. transform = QgsCoordinateTransform( self.exposure.crs(), self.hazard.crs()) features = [] for feature in self.exposure.layer.getFeatures(request): # Make a deep copy as the geometry is passed by reference # If we don't do this, subsequent operations will affect the # original feature geometry as well as the copy TS building_geom = QgsGeometry(feature.geometry()) # Project the building geometry to hazard CRS building_bounds = transform.transform(building_geom.boundingBox()) affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_bounds) for fid in ids: # run (slow) exact intersection test building_geom.transform(transform) if hazard_geometries[fid].intersects(building_geom): affected = True break new_feature = QgsFeature() # We write out the original feature geom, not the projected one new_feature.setGeometry(feature.geometry()) new_feature.setAttributes(feature.attributes()) new_feature[target_field_index] = 1 if affected else 0 features.append(new_feature) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report hazard_classes = [tr('Flooded')] self.init_report_var(hazard_classes) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() usage = record[building_type_field_index] usage = main_type(usage, exposure_value_mapping) affected = False if record[target_field_index] == 1: affected = True self.classify_feature(hazard_classes[0], usage, affected) self.reorder_dictionaries() style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr( 'No buildings were impacted by this flood.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=building_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
class TriangleMesh: # 0 - 3 # | / | # 1 - 2 def __init__(self, xmin, ymin, xmax, ymax, x_segments, y_segments): self.vbands = [] self.hbands = [] self.vidx = QgsSpatialIndex() self.hidx = QgsSpatialIndex() xres = (xmax - xmin) / x_segments yres = (ymax - ymin) / y_segments self.xmin, self.ymax, self.xres, self.yres = xmin, ymax, xres, yres def addVBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.vbands.append(f) self.vidx.insertFeature(f) def addHBand(idx, geom): f = QgsFeature(idx) f.setGeometry(geom) self.hbands.append(f) self.hidx.insertFeature(f) for x in range(x_segments): addVBand(x, QgsGeometry.fromRect(QgsRectangle(xmin + x * xres, ymin, xmin + (x + 1) * xres, ymax))) for y in range(y_segments): addHBand(y, QgsGeometry.fromRect(QgsRectangle(xmin, ymax - (y + 1) * yres, xmax, ymax - y * yres))) def vSplit(self, geom): """split polygon vertically""" for idx in self.vidx.intersects(geom.boundingBox()): yield idx, geom.intersection(self.vbands[idx].geometry()) def hIntersects(self, geom): """indices of horizontal bands that intersect with geom""" for idx in self.hidx.intersects(geom.boundingBox()): if geom.intersects(self.hbands[idx].geometry()): yield idx def splitPolygons(self, geom): xmin, ymax, xres, yres = self.xmin, self.ymax, self.xres, self.yres for x, vi in self.vSplit(geom): for y in self.hIntersects(vi): pt0 = QgsPoint(xmin + x * xres, ymax - y * yres) pt1 = QgsPoint(xmin + x * xres, ymax - (y + 1) * yres) pt2 = QgsPoint(xmin + (x + 1) * xres, ymax - (y + 1) * yres) pt3 = QgsPoint(xmin + (x + 1) * xres, ymax - y * yres) quad = QgsGeometry.fromPolygon([[pt0, pt1, pt2, pt3, pt0]]) tris = [[[pt0, pt1, pt3, pt0]], [[pt3, pt1, pt2, pt3]]] if geom.contains(quad): yield tris[0] yield tris[1] else: for i, tri in enumerate(map(QgsGeometry.fromPolygon, tris)): if geom.contains(tri): yield tris[i] elif geom.intersects(tri): poly = geom.intersection(tri) if poly.isMultipart(): for sp in poly.asMultiPolygon(): yield sp else: yield poly.asPolygon()
def processAlgorithm(self, parameters, context, feedback): poly_source = self.parameterAsSource(parameters, self.POLYGONS, context) point_source = self.parameterAsSource(parameters, self.POINTS, context) weight_field = self.parameterAsString(parameters, self.WEIGHT, context) weight_field_index = -1 if weight_field: weight_field_index = point_source.fields().lookupField(weight_field) class_field = self.parameterAsString(parameters, self.CLASSFIELD, context) class_field_index = -1 if class_field: class_field_index = point_source.fields().lookupField(class_field) field_name = self.parameterAsString(parameters, self.FIELD, context) fields = poly_source.fields() if fields.lookupField(field_name) < 0: fields.append(QgsField(field_name, QVariant.Int)) field_index = fields.lookupField(field_name) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, poly_source.wkbType(), poly_source.sourceCrs()) spatialIndex = QgsSpatialIndex(point_source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(poly_source.sourceCrs(), context.transformContext())), feedback) point_attribute_indices = [] if weight_field_index >= 0: point_attribute_indices.append(weight_field_index) if class_field_index >= 0: point_attribute_indices.append(class_field_index) features = poly_source.getFeatures() total = 100.0 / poly_source.featureCount() if poly_source.featureCount() else 0 for current, polygon_feature in enumerate(features): if feedback.isCanceled(): break count = 0 output_feature = QgsFeature() if polygon_feature.hasGeometry(): geom = polygon_feature.geometry() engine = QgsGeometry.createGeometryEngine(geom.constGet()) engine.prepareGeometry() count = 0 classes = set() points = spatialIndex.intersects(geom.boundingBox()) if len(points) > 0: request = QgsFeatureRequest().setFilterFids(points).setDestinationCrs(poly_source.sourceCrs(), context.transformContext()) request.setSubsetOfAttributes(point_attribute_indices) for point_feature in point_source.getFeatures(request): if feedback.isCanceled(): break if engine.contains(point_feature.geometry().constGet()): if weight_field_index >= 0: weight = point_feature.attributes()[weight_field_index] try: count += float(weight) except: # Ignore fields with non-numeric values pass elif class_field_index >= 0: point_class = point_feature.attributes()[class_field_index] if point_class not in classes: classes.add(point_class) else: count += 1 output_feature.setGeometry(geom) attrs = polygon_feature.attributes() if class_field_index >= 0: score = len(classes) else: score = count if field_index == len(attrs): attrs.append(score) else: attrs[field_index] = score output_feature.setAttributes(attrs) sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def interpolate_polygon_polygon(source, target, wgs84_extent): """ Transfer values from source polygon layer to the target polygon layer. This method will do a spatial join: the output layer will contain all features from target layer, with the addition of attributes of intersecting feature from the source layer. If there is not intersecting source feature, the output layer will still contain the target feature, with null attributes from the source layer. The intersection test considers only centroids of target features (not the whole polygon geometry). If more features from source layer intersect a target feature, only the first intersecting source feature will be used. :param source: Source polygon layer :type source: QgsVectorLayer :param target: Target polygon layer :type target: QgsVectorLayer :param wgs84_extent: Requested extent for analysis, in WGS84 coordinates :type wgs84_extent: QgsRectangle :return: output layer :rtype: QgsVectorLayer """ source_field_count = source.dataProvider().fields().count() target_field_count = target.dataProvider().fields().count() # Create new layer for writing resulting features. # It will contain attributes of both target and source layers result = create_layer(target) new_fields = source.dataProvider().fields().toList() new_fields.append(QgsField('polygon_id', QVariant.Int)) result.dataProvider().addAttributes(new_fields) result.updateFields() result_fields = result.dataProvider().fields() # setup transform objects between different CRS crs_wgs84 = QgsCoordinateReferenceSystem("EPSG:4326") wgs84_to_source = QgsCoordinateTransform(crs_wgs84, source.crs()) wgs84_to_target = QgsCoordinateTransform(crs_wgs84, target.crs()) source_to_target = QgsCoordinateTransform(source.crs(), target.crs()) # compute extents in CRS of layers source_extent = wgs84_to_source.transformBoundingBox(wgs84_extent) target_extent = wgs84_to_target.transformBoundingBox(wgs84_extent) # cache source layer (in CRS of target layer) source_index = QgsSpatialIndex() source_geometries = {} # key = feature ID, value = QgsGeometry source_attributes = {} for f in source.getFeatures(QgsFeatureRequest(source_extent)): f.geometry().transform(source_to_target) source_index.insertFeature(f) source_geometries[f.id()] = QgsGeometry(f.geometry()) source_attributes[f.id()] = f.attributes() # Go through all features in target layer and for each decide # whether it is intersected by any source feature result_features = [] for f in target.getFeatures(QgsFeatureRequest(target_extent)): # we use just centroids of target polygons centroid_geometry = f.geometry().centroid() centroid = centroid_geometry.asPoint() rect = QgsRectangle( centroid.x(), centroid.y(), centroid.x(), centroid.y()) ids = source_index.intersects(rect) has_matching_source = False for source_id in ids: if source_geometries[source_id].intersects(centroid_geometry): # we have found intersection between source and target f_result = QgsFeature(result_fields) f_result.setGeometry(f.geometry()) for i in xrange(target_field_count): f_result[i] = f[i] for i in xrange(source_field_count): f_result[i + target_field_count] = source_attributes[ source_id][i] f_result['polygon_id'] = source_id result_features.append(f_result) has_matching_source = True break # assuming just one source for each target feature # if there is no intersecting feature from source layer, # we will keep the source attributes null if not has_matching_source: f_result = QgsFeature(result_fields) f_result.setGeometry(f.geometry()) for i in xrange(target_field_count): f_result[i] = f[i] result_features.append(f_result) if len(result_features) == 1000: result.dataProvider().addFeatures(result_features) result_features = [] result.dataProvider().addFeatures(result_features) return result
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([])) total = 100.0 / source.featureCount() if source.featureCount() else 0 geoms = dict() index = QgsSpatialIndex() for current, f in enumerate(features): if feedback.isCanceled(): break geoms[f.id()] = f.geometry() index.addFeature(f) feedback.setProgress(int(0.10 * current * total)) # takes about 10% of time # start by assuming everything is unique, and chop away at this list unique_features = dict(geoms) current = 0 for feature_id, geometry in geoms.items(): if feedback.isCanceled(): break if feature_id not in unique_features: # feature was already marked as a duplicate continue candidates = index.intersects(geometry.boundingBox()) candidates.remove(feature_id) for candidate_id in candidates: if candidate_id not in unique_features: # candidate already marked as a duplicate (not sure if this is possible, # since it would mean the current feature would also have to be a duplicate! # but let's be safe!) continue if geometry.isGeosEqual(geoms[candidate_id]): # candidate is a duplicate of feature del unique_features[candidate_id] current += 1 feedback.setProgress(int(0.80 * current * total) + 10) # takes about 80% of time total = 100.0 / len(unique_features) if unique_features else 1 # now, fetch all the feature attributes for the unique features only # be super-smart and don't re-fetch geometries request = QgsFeatureRequest().setFilterFids(list(unique_features.keys())).setFlags(QgsFeatureRequest.NoGeometry) for current, f in enumerate(source.getFeatures(request)): if feedback.isCanceled(): break # use already fetched geometry f.setGeometry(unique_features[f.id()]) sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(0.10 * current * total) + 90) # takes about 10% of time return {self.OUTPUT: dest_id}
layer.startEditing() layer.dataProvider().addAttributes( [QgsField('right', QVariant.String), QgsField('left', QVariant.String), QgsField('above', QVariant.String), QgsField('below', QVariant.String)] ) layer.updateFields() feature_dict = {f.id(): f for f in layer.getFeatures()} index = QgsSpatialIndex( layer.getFeatures() ) for f in feature_dict.values(): geom = f.geometry() bbox1 = geom.boundingBox().toString(2).replace(" : ",",").split(",") intersecting_ids = index.intersects( geom.boundingBox() ) for intersecting_id in intersecting_ids: intersecting_f = feature_dict[intersecting_id] if ( f != intersecting_f and not intersecting_f.geometry().disjoint(geom) ): bbox2 = intersecting_f.geometry().boundingBox().toString(2).replace(" : ",",").split(",") relX = [bbox1[0:3:2].index( c ) for c in bbox1[0:3:2] if c not in bbox2[0:3:2]] relY = [bbox1[1:4:2].index( c ) for c in bbox1[1:4:2] if c not in bbox2[1:4:2]] if relX == [0] and relY == []: f['right'] = intersecting_f[Page_number_field] elif relX == [] and relY == [0]: f['above'] = intersecting_f[Page_number_field] elif relX == [1] and relY == []: f['left'] = intersecting_f[Page_number_field] elif relX == [] and relY == [1]:
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) proximity = self.parameterAsDouble(parameters, self.PROXIMITY, context) radius = self.parameterAsDouble(parameters, self.DISTANCE, context) horizontal = self.parameterAsBool(parameters, self.HORIZONTAL, context) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 def searchRect(p): return QgsRectangle(p.x() - proximity, p.y() - proximity, p.x() + proximity, p.y() + proximity) index = QgsSpatialIndex() # NOTE: this is a Python port of QgsPointDistanceRenderer::renderFeature. If refining this algorithm, # please port the changes to QgsPointDistanceRenderer::renderFeature also! clustered_groups = [] group_index = {} group_locations = {} for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): continue point = f.geometry().asPoint() other_features_within_radius = index.intersects(searchRect(point)) if not other_features_within_radius: index.addFeature(f) group = [f] clustered_groups.append(group) group_index[f.id()] = len(clustered_groups) - 1 group_locations[f.id()] = point else: # find group with closest location to this point (may be more than one within search tolerance) min_dist_feature_id = other_features_within_radius[0] min_dist = group_locations[min_dist_feature_id].distance(point) for i in range(1, len(other_features_within_radius)): candidate_id = other_features_within_radius[i] new_dist = group_locations[candidate_id].distance(point) if new_dist < min_dist: min_dist = new_dist min_dist_feature_id = candidate_id group_index_pos = group_index[min_dist_feature_id] group = clustered_groups[group_index_pos] # calculate new centroid of group old_center = group_locations[min_dist_feature_id] group_locations[min_dist_feature_id] = QgsPointXY((old_center.x() * len(group) + point.x()) / (len(group) + 1.0), (old_center.y() * len(group) + point.y()) / (len(group) + 1.0)) # add to a group clustered_groups[group_index_pos].append(f) group_index[f.id()] = group_index_pos feedback.setProgress(int(current * total)) current = 0 total = 100.0 / len(clustered_groups) if clustered_groups else 1 feedback.setProgress(0) fullPerimeter = 2 * math.pi for group in clustered_groups: if feedback.isCanceled(): break count = len(group) if count == 1: sink.addFeature(group[0], QgsFeatureSink.FastInsert) else: angleStep = fullPerimeter / count if count == 2 and horizontal: currentAngle = math.pi / 2 else: currentAngle = 0 old_point = group_locations[group[0].id()] for f in group: if feedback.isCanceled(): break sinusCurrentAngle = math.sin(currentAngle) cosinusCurrentAngle = math.cos(currentAngle) dx = radius * sinusCurrentAngle dy = radius * cosinusCurrentAngle # we want to keep any existing m/z values point = f.geometry().constGet().clone() point.setX(old_point.x() + dx) point.setY(old_point.y() + dy) f.setGeometry(QgsGeometry(point)) sink.addFeature(f, QgsFeatureSink.FastInsert) currentAngle += angleStep current += 1 feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def run(self): """Experimental impact function.""" self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) if affected_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of the ' 'hazard layer. Please change the Affected Field parameter in ' 'the IF Option.') % self.hazard_class_attribute raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( self.exposure_class_attribute) if building_type_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of ' 'the exposure layer. Please change the Building Type ' 'Field parameter in the IF Option.' ) % self.exposure_class_attribute raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes( [QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex( self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the buildings from E and extent buildings_are_points = is_point_layer(self.exposure.layer) if buildings_are_points: building_layer = QgsVectorLayer( 'Point?crs=' + srs, 'impact_buildings', 'memory') else: building_layer = QgsVectorLayer( 'Polygon?crs=' + srs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( 'There are no objects in the hazard layer with %s ' 'value in %s. Please check your data or use another ' 'attribute.') % ( self.hazard_class_attribute, ', '.join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Filter out just those EXPOSURE features in the analysis extents transform = QgsCoordinateTransform( self.requested_extent_crs, self.exposure.layer.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # We will use this transform to project each exposure feature into # the CRS of the Hazard. transform = QgsCoordinateTransform( self.exposure.crs(), self.hazard.crs()) features = [] for feature in self.exposure.layer.getFeatures(request): # Make a deep copy as the geometry is passed by reference # If we don't do this, subsequent operations will affect the # original feature geometry as well as the copy TS building_geom = QgsGeometry(feature.geometry()) # Project the building geometry to hazard CRS building_bounds = transform.transform(building_geom.boundingBox()) affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_bounds) for fid in ids: # run (slow) exact intersection test building_geom.transform(transform) if hazard_geometries[fid].intersects(building_geom): affected = True break new_feature = QgsFeature() # We write out the original feature geom, not the projected one new_feature.setGeometry(feature.geometry()) new_feature.setAttributes(feature.attributes()) new_feature[target_field_index] = 1 if affected else 0 features.append(new_feature) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report self.buildings = {} self.affected_buildings = OrderedDict([ (tr('Flooded'), {}) ]) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() building_type = record[building_type_field_index] if building_type in [None, 'NULL', 'null', 'Null']: building_type = 'Unknown type' if building_type not in self.buildings: self.buildings[building_type] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][ building_type] = OrderedDict([ (tr('Buildings Affected'), 0)]) self.buildings[building_type] += 1 if record[target_field_index] == 1: self.affected_buildings[tr('Flooded')][building_type][ tr('Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category # Building threshold #2468 postprocessors = self.parameters['postprocessors'] building_postprocessors = postprocessors['BuildingType'][0] self.building_report_threshold = building_postprocessors.value[0].value self._consolidate_to_other() impact_summary = self.html_report() # For printing map purpose map_title = tr('Buildings inundated') legend_title = tr('Structure inundated status') style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr( 'No buildings were impacted by this flood.')) extra_keywords = { 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) building_layer = Vector( data=building_layer, name=tr('Flooded buildings'), keywords=impact_layer_keywords, style_info=style_info) self._impact = building_layer return building_layer
def run_stats(self): self.progressBar_stats.setValue(0) self.label_progressStats.setText('') # noinspection PyArgumentList QApplication.processEvents() blurred_layer = self.comboBox_blurredLayer.currentLayer() stats_layer = self.comboBox_statsLayer.currentLayer() try: if not blurred_layer or not stats_layer: raise NoLayerProvidedException crs_blurred_layer = blurred_layer.crs() crs_stats_layer = stats_layer.crs() if crs_blurred_layer != crs_stats_layer: raise DifferentCrsException( epsg1=crs_blurred_layer.authid(), epsg2=crs_stats_layer.authid()) if blurred_layer == stats_layer: raise NoLayerProvidedException if not blurred_layer or not stats_layer: raise NoLayerProvidedException nb_feature_stats = stats_layer.featureCount() nb_feature_blurred = blurred_layer.featureCount() features_stats = {} label_preparing = tr('Preparing index on the stats layer') label_creating = tr('Creating index on the stats layer') label_calculating = tr('Calculating') if Qgis.QGIS_VERSION_INT < 20700: self.label_progressStats.setText('%s 1/3' % label_preparing) for i, feature in enumerate(stats_layer.getFeatures()): features_stats[feature.id()] = feature percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 2/3' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex() for i, f in enumerate(stats_layer.getFeatures()): index.insertFeature(f) percent = int((i + 1) * 100 / nb_feature_stats) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() self.label_progressStats.setText('%s 3/3' % label_calculating) else: # If QGIS >= 2.7, we can speed up the spatial index. # From 1 min 15 to 7 seconds on my PC. self.label_progressStats.setText('%s 1/2' % label_creating) # noinspection PyArgumentList QApplication.processEvents() index = QgsSpatialIndex(stats_layer.getFeatures()) self.label_progressStats.setText('%s 2/2' % label_calculating) # noinspection PyArgumentList QApplication.processEvents() self.tab = [] for i, feature in enumerate(blurred_layer.getFeatures()): count = 0 ids = index.intersects(feature.geometry().boundingBox()) for unique_id in ids: request = QgsFeatureRequest().setFilterFid(unique_id) f = next(stats_layer.getFeatures(request)) if f.geometry().intersects(feature.geometry()): count += 1 self.tab.append(count) percent = int((i + 1) * 100 / nb_feature_blurred) self.progressBar_stats.setValue(percent) # noinspection PyArgumentList QApplication.processEvents() stats = Stats(self.tab) items_stats = [ 'Count(blurred),%d' % nb_feature_blurred, 'Count(stats),%d' % nb_feature_stats, 'Min,%d' % stats.min(), 'Average,%f' % stats.average(), 'Max,%d' % stats.max(), 'Median,%f' % stats.median(), 'Range,%d' % stats.range(), 'Variance,%f' % stats.variance(), 'Standard deviation,%f' % stats.standard_deviation() ] self.tableWidget.clear() self.tableWidget.setColumnCount(2) labels = ['Parameters', 'Values'] self.tableWidget.setHorizontalHeaderLabels(labels) self.tableWidget.setRowCount(len(items_stats)) for i, item in enumerate(items_stats): s = item.split(',') self.tableWidget.setItem(i, 0, QTableWidgetItem(s[0])) self.tableWidget.setItem(i, 1, QTableWidgetItem(s[1])) self.tableWidget.resizeRowsToContents() self.draw_plot(self.tab) except GeoPublicHealthException as e: self.label_progressStats.setText('') display_message_bar(msg=e.msg, level=e.level, duration=e.duration)
def run(self): """Experimental impact function.""" self.validate() self.prepare() # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) if affected_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of the ' 'hazard layer. Please change the Affected Field parameter in ' 'the IF Option.') % self.hazard_class_attribute raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( self.exposure_class_attribute) if building_type_field_index == -1: message = tr('Field "%s" is not present in the attribute table of ' 'the exposure layer. Please change the Building Type ' 'Field parameter in the IF Option.' ) % self.exposure_class_attribute raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes( [QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex( self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the lines from E and extent building_layer = QgsVectorLayer('Polygon?crs=' + srs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem('EPSG:%i' % self._requested_extent_crs), self.hazard.layer.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( 'There are no objects in the hazard layer with %s ' 'value in %s. Please check your data or use another ' 'attribute.') % (self.hazard_class_attribute, ', '.join( self.hazard_class_mapping[self.wet])) raise GetDataError(message) features = [] for feature in self.exposure.layer.getFeatures(request): building_geom = feature.geometry() affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_geom.boundingBox()) for fid in ids: # run (slow) exact intersection test if hazard_geometries[fid].intersects(building_geom): affected = True break f = QgsFeature() f.setGeometry(building_geom) f.setAttributes(feature.attributes()) f[target_field_index] = 1 if affected else 0 features.append(f) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report self.buildings = {} self.affected_buildings = OrderedDict([(tr('Flooded'), {})]) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() building_type = record[building_type_field_index] if building_type in [None, 'NULL', 'null', 'Null']: building_type = 'Unknown type' if building_type not in self.buildings: self.buildings[building_type] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][ building_type] = OrderedDict([ (tr('Buildings Affected'), 0) ]) self.buildings[building_type] += 1 if record[target_field_index] == 1: self.affected_buildings[tr('Flooded')][building_type][tr( 'Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() impact_summary = self.html_report() # For printing map purpose map_title = tr('Buildings inundated') legend_title = tr('Structure inundated status') style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5) ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException( tr('No buildings were impacted by this flood.')) building_layer = Vector(data=building_layer, name=tr('Flooded buildings'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings }, style_info=style_info) self._impact = building_layer return building_layer
def processAlgorithm(self, parameters, context, feedback): sourceA = self.parameterAsSource(parameters, self.INPUT, context) sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) geomType = QgsWkbTypes.multiType(sourceA.wkbType()) fields = vector.combineFields(sourceA.fields(), sourceB.fields()) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, geomType, sourceA.sourceCrs()) featA = QgsFeature() featB = QgsFeature() outFeat = QgsFeature() indexA = QgsSpatialIndex(sourceA, feedback) indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs())), feedback) total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 for featA in sourceA.getFeatures(): if feedback.isCanceled(): break lstIntersectingB = [] geom = featA.geometry() atMapA = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) if len(intersects) < 1: try: outFeat.setGeometry(geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: # This really shouldn't happen, as we haven't # edited the input geom at all feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs()) engine = QgsGeometry.createGeometryEngine(geom.geometry()) engine.prepareGeometry() for featB in sourceB.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): int_geom = geom.intersection(tmpGeom) lstIntersectingB.append(tmpGeom) if not int_geom: # There was a problem creating the intersection feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) int_geom = QgsGeometry() else: int_geom = QgsGeometry(int_geom) if int_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(int_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: # Intersection produced different geomety types temp_list = int_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): int_geom = QgsGeometry(i) try: outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: # Geometry list: prevents writing error # in geometries of different types # produced by the intersection # fix #3549 if int_geom.wkbType() in wkbTypeGroups[wkbTypeGroups[int_geom.wkbType()]]: try: outFeat.setGeometry(int_geom) outFeat.setAttributes(atMapA + atMapB) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) # the remaining bit of featA's geometry # if there is nothing left, this will just silently fail and we're good diff_geom = QgsGeometry(geom) if len(lstIntersectingB) != 0: intB = QgsGeometry.unaryUnion(lstIntersectingB) diff_geom = diff_geom.difference(intB) if diff_geom.wkbType() == QgsWkbTypes.Unknown or QgsWkbTypes.flatType(diff_geom.geometry().wkbType()) == QgsWkbTypes.GeometryCollection: temp_list = diff_geom.asGeometryCollection() for i in temp_list: if i.type() == geom.type(): diff_geom = QgsGeometry(i) try: outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMapA) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) count += 1 feedback.setProgress(int(count * total)) length = len(sourceA.fields()) atMapA = [None] * length for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): if feedback.isCanceled(): break add = False geom = featA.geometry() diff_geom = QgsGeometry(geom) atMap = [None] * length atMap.extend(featA.attributes()) intersects = indexA.intersects(geom.boundingBox()) if len(intersects) < 1: try: outFeat.setGeometry(geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) else: request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) request.setDestinationCrs(sourceA.sourceCrs()) # use prepared geometries for faster intersection tests engine = QgsGeometry.createGeometryEngine(diff_geom.geometry()) engine.prepareGeometry() for featB in sourceA.getFeatures(request): atMapB = featB.attributes() tmpGeom = featB.geometry() if engine.intersects(tmpGeom.geometry()): add = True diff_geom = QgsGeometry(diff_geom.difference(tmpGeom)) else: try: # Ihis only happens if the bounding box # intersects, but the geometry doesn't outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) if add: try: outFeat.setGeometry(diff_geom) outFeat.setAttributes(atMap) sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: feedback.pushInfo(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.')) count += 1 feedback.setProgress(int(count * total)) return {self.OUTPUT: dest_id}
def middle(bar, buildings_layer_path, receiver_points_layer_path): buildings_layer_name = os.path.splitext( os.path.basename(buildings_layer_path))[0] buildings_layer = QgsVectorLayer(buildings_layer_path, buildings_layer_name, "ogr") # defines emission_points layer receiver_points_fields = QgsFields() receiver_points_fields.append(QgsField("id_pt", QVariant.Int)) receiver_points_fields.append(QgsField("id_bui", QVariant.Int)) receiver_points_writer = QgsVectorFileWriter(receiver_points_layer_path, "System", receiver_points_fields, QgsWkbTypes.Point, buildings_layer.crs(), "ESRI Shapefile") # gets features from layer buildings_feat_all = buildings_layer.dataProvider().getFeatures() # creates SpatialIndex buildings_spIndex = QgsSpatialIndex() buildings_feat_all_dict = {} for buildings_feat in buildings_feat_all: buildings_spIndex.insertFeature(buildings_feat) buildings_feat_all_dict[buildings_feat.id()] = buildings_feat # defines distanze_point distance_point = 0.1 # re-gets features from layer buildings_feat_all = buildings_layer.dataProvider().getFeatures() buildings_feat_total = buildings_layer.dataProvider().featureCount() pt_id = 0 buildings_feat_number = 0 for buildings_feat in buildings_feat_all: buildings_feat_number = buildings_feat_number + 1 barValue = buildings_feat_number / float(buildings_feat_total) * 100 bar.setValue(barValue) building_geom = buildings_feat.geometry() if building_geom.isMultipart(): buildings_pt = building_geom.asMultiPolygon()[0] #building_geom.convertToSingleType() else: buildings_pt = buildings_feat.geometry().asPolygon() # creates the search rectangle to match the receiver point in the building and del them rect = QgsRectangle() rect.setXMinimum(buildings_feat.geometry().boundingBox().xMinimum() - distance_point) rect.setXMaximum(buildings_feat.geometry().boundingBox().xMaximum() + distance_point) rect.setYMinimum(buildings_feat.geometry().boundingBox().yMinimum() - distance_point) rect.setYMaximum(buildings_feat.geometry().boundingBox().yMaximum() + distance_point) buildings_selection = buildings_spIndex.intersects(rect) if len(buildings_pt) > 0: for i in range(0, len(buildings_pt)): buildings_pts = buildings_pt[i] #### # start part to delete pseudo vertex # this part it's different from the diffraction delete pseudo vertex part pts_index_to_delete_list = [] m_delta = 0.01 for ii in range(0, len(buildings_pts) - 1): x1 = buildings_pts[ii - 1][0] x2 = buildings_pts[ii][0] x3 = buildings_pts[ii + 1][0] y1 = buildings_pts[ii - 1][1] y2 = buildings_pts[ii][1] y3 = buildings_pts[ii + 1][1] # particular cases: first point to delete! (remember that the first and the last have the same coordinates) if ii == 0 and (x2 == x1 and y2 == y1): x1 = buildings_pts[ii - 2][0] y1 = buildings_pts[ii - 2][1] # angular coefficient to find pseudo vertex if x2 - x1 != 0 and x3 - x1 != 0: m1 = (y2 - y1) / (x2 - x1) m2 = (y3 - y1) / (x3 - x1) #if round(m1,2) <= round(m2,2) + m_delta and round(m1,2) >= round(m2,2) - m_delta: if m1 <= m2 + m_delta and m1 >= m2 - m_delta: pts_index_to_delete_list.append(ii) # particular cases: first point to delete! (remember that the first and the last have the same coordinates) # here we delete the last and add x3,y3 (buildings_pts[ii+1] - the new last point) if ii == 0: pts_index_to_delete_list.append( len(buildings_pts) - 1) buildings_pts.append(buildings_pts[ii + 1]) # del pseudo vertex pts_index_to_delete_list = sorted(pts_index_to_delete_list, reverse=True) for pt_index_to_del in pts_index_to_delete_list: del buildings_pts[pt_index_to_del] # end part to delete pseudo vertex # for to generate receiver points for ii in range(0, len(buildings_pts) - 1): x1 = buildings_pts[ii][0] x2 = buildings_pts[ii + 1][0] y1 = buildings_pts[ii][1] y2 = buildings_pts[ii + 1][1] xm = (x1 + x2) / 2 ym = (y1 + y2) / 2 if y2 == y1: dx = 0 dy = distance_point elif x2 == x1: dx = distance_point dy = 0 else: m = (y2 - y1) / (x2 - x1) m_p = -1 / m dx = sqrt((distance_point**2) / (1 + m_p**2)) dy = sqrt( ((distance_point**2) * (m_p**2)) / (1 + m_p**2)) if (x2 >= x1 and y2 >= y1) or (x2 < x1 and y2 < y1): pt1 = QgsPointXY(xm + dx, ym - dy) pt2 = QgsPointXY(xm - dx, ym + dy) if (x2 >= x1 and y2 < y1) or (x2 < x1 and y2 >= y1): pt1 = QgsPointXY(xm + dx, ym + dy) pt2 = QgsPointXY(xm - dx, ym - dy) pt = QgsFeature() # pt1, check if is in a building and eventually add it pt.setGeometry(QgsGeometry.fromPointXY(pt1)) intersect = 0 for buildings_id in buildings_selection: if buildings_feat_all_dict[buildings_id].geometry( ).intersects(pt.geometry()) == 1: intersect = 1 break if intersect == 0: pt.setAttributes([pt_id, buildings_feat.id()]) receiver_points_writer.addFeature(pt) pt_id = pt_id + 1 # pt2, check if is in a building and eventually add it pt.setGeometry(QgsGeometry.fromPointXY(pt2)) intersect = 0 for buildings_id in buildings_selection: if buildings_feat_all_dict[buildings_id].geometry( ).intersects(pt.geometry()) == 1: intersect = 1 break if intersect == 0: pt.setAttributes([pt_id, buildings_feat.id()]) receiver_points_writer.addFeature(pt) pt_id = pt_id + 1 del receiver_points_writer #print receiver_points_layer_path receiver_points_layer_name = os.path.splitext( os.path.basename(receiver_points_layer_path))[0] #print receiver_points_layer_name receiver_points_layer = QgsVectorLayer(receiver_points_layer_path, str(receiver_points_layer_name), "ogr") QgsProject.instance().addMapLayers([receiver_points_layer])