def __get_text_flaeche(self, quelle, gstk, layer, fld_name): text = {} #performance! filter by bb of gstk first feat_req = QgsFeatureRequest() feat_req.setFilterRect(gstk.geometry().boundingBox()) for feat in layer.getFeatures(feat_req): if feat.geometry().intersects(gstk.geometry()): #no fld_name defined: means yes/no only if fld_name is None: attr_val = u'Ja' else: attr_val = feat[fld_name] #convert everything to string #JSON only allows for string keys -> settingsfile if isinstance( attr_val, (int, long)): attr_val = unicode(attr_val) elif isinstance(attr_val, float): attr_val = u'{0:.0f}'.format(attr_val) #replace attribute values with mapping text from settings file if not quelle.text is None: if attr_val in quelle.text: attr_val = quelle.text[attr_val] flaeche = feat.geometry().intersection(gstk.geometry()).area() if fld_name in text: text[attr_val] += flaeche else: text[attr_val] = flaeche if len(text) < 1 and fld_name is None: text[u'Nein'] = 0 elif len(text) < 1 and not fld_name is None: text[u'Nein'] = 0 return text
def writePolygonFeature(self, layer, attributes_dict): polygons_svg = [] request = QgsFeatureRequest() request.setFilterRect(self.iface.mapCanvas().extent()) for feature in layer.getFeatures(request): polygons_svg.append(self.writePolygonToSVG(feature, attributes_dict)) return polygons_svg
def __call__(self): if self.rect: rq = QgsFeatureRequest() rq.setFilterRect(self.rect) features = self.layer.getFeatures(rq) else: features = self.layer.getFeatures() for where in self.wheres: if self.DEBUG: "Has filter" #TODO Index lookup # if self.index: # if self.DEBUG: print "Has index" # min = 6163 # max = 6164 # iters = [iter(self.index[code]) for code in xrange(min, max + 1)] # features = itertools.chain(*iters) features = where(features) # TODO Clean up if self.limit: if self.DEBUG: print "Has Limit" for count in xrange(self.limit): if self.selectstatment: yield self.selectstatment(features.next()) else: yield features.next() else: for f in features: if self.selectstatment: yield self.selectstatment(f) else: yield f
def findFeaturesAt(mapPoint, layerConfig, mapTool): """ To find features from a given position in a given layer :param mapPoint: the map position :param layerConfig: the layer in which we are looking for features :param mapTool: a QgsMapTool instance :return: features found in layer """ if layerConfig is None: return None tolerance = layerConfig.tolerance if layerConfig.unit == QgsTolerance.Pixels: layTolerance = Finder.calcCanvasTolerance(mapTool.toCanvasCoordinates(mapPoint), layerConfig.layer, mapTool, tolerance) elif layerConfig.unit == QgsTolerance.ProjectUnits: layTolerance = Finder.calcMapTolerance(mapPoint, layerConfig.layer, mapTool, tolerance) else: layTolerance = tolerance layPoint = mapTool.toLayerCoordinates(layerConfig.layer, mapPoint) searchRect = QgsRectangle(layPoint.x() - layTolerance, layPoint.y() - layTolerance, layPoint.x() + layTolerance, layPoint.y() + layTolerance) request = QgsFeatureRequest() request.setFilterRect(searchRect) request.setFlags(QgsFeatureRequest.ExactIntersect) features = [] for feature in layerConfig.layer.getFeatures(request): if layerConfig.layer.geometryType() == QGis.Polygon: dist, nearest, vertex = feature.geometry().closestSegmentWithContext(mapPoint) if QgsGeometry.fromPoint(nearest).intersects(searchRect): features.append(QgsFeature(feature)) else: features.append(QgsFeature(feature)) return features
def intersecting_blocks(line, destination, grid): """Function to fetch intersectings polygons from the grid.""" dest_id_field = grid.fieldNameIndex('destination_id') request = QgsFeatureRequest() request.setFilterRect(line.boundingBox()) request.setFilterExpression('"destination_id" is None') for feature in grid.getFeatures(request): if feature.geometry().intersects(line): grid.changeAttributeValue(feature.id(), dest_id_field, destination)
def layerData(self, layer, request={}, offset=0): # Retrieve the data for a layer first = True data = {} fields = [] fieldTypes = [] fr = QgsFeatureRequest() if request: if 'exact' in request and request['exact']: fr.setFlags(QgsFeatureRequest.ExactIntersect) if 'nogeom' in request and request['nogeom']: fr.setFlags(QgsFeatureRequest.NoGeometry) if 'fid' in request: fr.setFilterFid(request['fid']) elif 'extents' in request: fr.setFilterRect(QgsRectangle(*request['extents'])) if 'attributes' in request: fr.setSubsetOfAttributes(request['attributes']) # IMPORTANT - we do not use `for f in layer.getFeatures(fr):` as we need # to verify that existing attributes and geometry are correctly cleared # from the feature when calling nextFeature() it = layer.getFeatures(fr) f = QgsFeature() while it.nextFeature(f): if first: first = False for field in f.fields(): fields.append(str(field.name())) fieldTypes.append(str(field.typeName())) if sys.version_info.major == 2: fielddata = dict((name, str(f[name])) for name in fields) else: fielddata = dict((name, str(f[name])) for name in fields) g = f.geometry() if not g.isEmpty(): fielddata[geomkey] = str(g.exportToWkt()) else: fielddata[geomkey] = "None" fielddata[fidkey] = f.id() id = fielddata[fields[0]] description = fielddata[fields[1]] fielddata['id'] = id fielddata['description'] = description data[f.id() + offset] = fielddata if 'id' not in fields: fields.insert(0, 'id') if 'description' not in fields: fields.insert(1, 'description') fields.append(fidkey) fields.append(geomkey) return fields, fieldTypes, data
def get_nearby_nodes(layer, node, threshold): """Return all nodes that has distance less than threshold from node_id. The list will be divided into two groups, upstream nodes and downstream nodes. :param layer: A vector point layer. :type layer: QGISVectorLayer :param node: The point/node. :type node: QgsFeature :param threshold: Distance threshold. :type threshold: float :returns: Tuple of list of nodes. (upstream_nodes, downstream_nodes). :rtype: tuple """ id_index = layer.fieldNameIndex('id') node_attributes = node.attributes() node_id = node_attributes[id_index] id_index = layer.fieldNameIndex('id') node_type_index = layer.fieldNameIndex('node_type') center_node_point = node.geometry().asPoint() rectangle = QgsRectangle( center_node_point.x() - threshold, center_node_point.y() - threshold, center_node_point.x() + threshold, center_node_point.y() + threshold) # iterate through all nodes upstream_nodes = [] downstream_nodes = [] request = QgsFeatureRequest() request.setFilterRect(rectangle) for feature in layer.getFeatures(request): attributes = feature.attributes() if feature[id_index] == node_id: continue if attributes[node_type_index] == 'upstream': upstream_nodes.append(attributes[id_index]) if attributes[node_type_index] == 'downstream': downstream_nodes.append(attributes[id_index]) return upstream_nodes, downstream_nodes
def dissolvePolygonsOnCanvas(writer, layer): """dissolve polygons of the layer and clip the dissolution with base extent""" settings = writer.settings baseExtent = settings.baseExtent baseExtentGeom = baseExtent.geometry() rotation = baseExtent.rotation() transform = QgsCoordinateTransform(layer.crs(), settings.crs) combi = None request = QgsFeatureRequest() request.setFilterRect(transform.transformBoundingBox(baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) for f in layer.getFeatures(request): geometry = f.geometry() if geometry is None: logMessage("null geometry skipped") continue # coordinate transformation - layer crs to project crs geom = QgsGeometry(geometry) if geom.transform(transform) != 0: logMessage("Failed to transform geometry") continue # check if geometry intersects with the base extent (rotated rect) if rotation and not baseExtentGeom.intersects(geom): continue if combi: combi = combi.combine(geom) else: combi = geom # clip geom with slightly smaller extent than base extent # to make sure that the clipped polygon stays within the base extent geom = combi.intersection(baseExtent.clone().scale(0.999999).geometry()) if geom is None: return None # check if geometry is empty if geom.isGeosEmpty(): logMessage("empty geometry") return None return geom
def layerData(layer, request={}, offset=0): first = True data = {} fields = [] fieldTypes = [] fr = QgsFeatureRequest() if request: if 'exact' in request and request['exact']: fr.setFlags(QgsFeatureRequest.ExactIntersect) if 'nogeom' in request and request['nogeom']: fr.setFlags(QgsFeatureRequest.NoGeometry) if 'fid' in request: fr.setFilterFid(request['fid']) elif 'extents' in request: fr.setFilterRect(QgsRectangle(*request['extents'])) if 'attributes' in request: fr.setSubsetOfAttributes(request['attributes']) for f in layer.getFeatures(fr): if first: first = False for field in f.fields(): fields.append(str(field.name())) fieldTypes.append(str(field.typeName())) fielddata = dict((name, unicode(f[name])) for name in fields) g = f.geometry() if g: fielddata[geomkey] = str(g.exportToWkt()) else: fielddata[geomkey] = "None" fielddata[fidkey] = f.id() id = fielddata[fields[0]] description = fielddata[fields[1]] fielddata['id'] = id fielddata['description'] = description data[f.id() + offset] = fielddata if 'id' not in fields: fields.insert(0, 'id') if 'description' not in fields: fields.insert(1, 'description') fields.append(fidkey) fields.append(geomkey) return fields, fieldTypes, data
def read(self, feature_type, bbox = None, attributes = None, geometry=True, feature_filter=None): if not isinstance(feature_type, FeatureType): raise TypeError() lyr = self._connectlayer(feature_type) request = None if bbox or attributes is not None or not geometry or feature_filter: request = QgsFeatureRequest() if bbox: rect = QgsRectangle(*bbox) request.setFilterRect(rect) if attributes: request.setSubsetOfAttributes(attributes, lyr.pendingFields()) if not geometry: request.setFlags(QgsFeatureRequest.NoGeometry) if feature_filter: request.setFilterExpression(feature_filter) #lyr.setSubsetString(feature_filter) # return listoffeatures # filter is maybe a QgsFeatureRequest # http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/vector.html#iterating-over-a-subset-of-features return list(lyr.getFeatures(request) if request else lyr.getFeatures())
def __call__(self): if self.rect: rq = QgsFeatureRequest() rq.setFilterRect(self.rect) features = self.layer.getFeatures(rq) else: features = self.layer.getFeatures() for where in self.wheres: if self.DEBUG: "Has filter" features = where(features) if self.limit: if self.DEBUG: print "Has Limit" features = itertools.islice(features, 0, self.limit) if self.selectstatment: cols = self.selectstatment[0] namedcols = self.selectstatment[1] features = (self._project(f, *cols, **namedcols) for f in features) else: features = (self._project(f) for f in features) return features
def run(self): """Experimental impact function for flood polygons on roads.""" # 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( 'road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_provider = self.hazard.layer.dataProvider() affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) # see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) # raise GetDataError(message) # LOGGER.info('Affected field: %s' % self.hazard_class_attribute) # LOGGER.info('Affected field index: %s' % affected_field_index) # Filter geometry and data using the 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 line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = self.hazard.layer.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: value = attributes[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( 'There are no objects in the hazard layer with %s (Affected ' 'Field) in %s (Affected Value). Please check the value or use ' 'a different extent.' % ( self.hazard_class_attribute, self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(requested_extent) line_layer = clip_by_polygon(self.exposure.layer, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon( line_layer, hazard_poly, request, mark_value=(self.target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), destination_crs) roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex( self.exposure_class_attribute) target_field_index = line_layer.fieldNameIndex(self.target_field) classes = [tr('Temporarily closed')] self.init_report_var(classes) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, 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 line_layer.featureCount() == 0: # Raising an exception seems poor semantics here.... raise ZeroImpactException( tr('No roads are flooded in this scenario.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info ) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Get parameters from IF parameter threshold_min = self.parameters['min threshold'].value threshold_max = self.parameters['max threshold'].value if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater than the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation index, flood_cells_map = _raster_to_vector_cells( small_raster, threshold_min, threshold_max, self.exposure.layer.crs()) if len(flood_cells_map) == 0: message = tr( 'There are no objects in the hazard layer with "value" > %s. ' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) classes = [tr('Flooded in the threshold (m)')] self.init_report_var(classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
class MultiselectTool(AreaTool): """ Map tool class to select object from multiple layers """ selectedSignal = pyqtSignal() def __init__(self, iface, identified=False): """ Constructor :param iface: interface """ AreaTool.__init__(self, iface) self.types = [QGis.Point, QGis.Line, QGis.Polygon] self.releasedSignal.connect(self.__select) self.identified = identified self.request = None def disabled(self): """ to get disabled layers :return disabled layers """ return QgsProject.instance().readListEntry("Identify", "disabledLayers", "None")[0] def __select(self): """ To select objects in multiples layers inside a selection rectangle """ searchRect = QgsRectangle(self.first, self.last) for layer in self.canvas().layers(): if not self.identified or layer.id() not in self.disabled(): if layer.type( ) == QgsMapLayer.VectorLayer and layer.geometryType( ) in self.types: renderer = layer.rendererV2() context = QgsRenderContext() if renderer: renderer.startRender(context, layer.pendingFields()) self.request = QgsFeatureRequest() self.request.setFilterRect(searchRect) self.request.setFlags(QgsFeatureRequest.ExactIntersect) fIds = [] for feature in layer.getFeatures(self.request): try: will = renderer.willRenderFeature( feature, context) except: try: will = renderer.willRenderFeature(feature) except: self.__iface.messageBar().pushMessage( QCoreApplication.translate( "VDLTools", "Error"), "will renderer still not working", level=QgsMessageBar.CRITICAL, duration=0) return if will: fIds.append(feature.id()) renderer.stopRender(context) layer.selectByIds(fIds) self.selectedSignal.emit()
def canvasReleaseEvent(self, event): if self.doubleclick: self.doubleclick = False return if event.button() <> Qt.LeftButton: return layer = self.plugin.getLayerByTableName('ca_parcel') if not layer: return # find out map coordinates from mouse click mapPoint = self.toLayerCoordinates(layer, event.pos()) tolerance = self.plugin.getTolerance(layer) area = QgsRectangle(mapPoint.x() - tolerance, mapPoint.y() - tolerance, mapPoint.x() + tolerance, mapPoint.y() + tolerance) request = QgsFeatureRequest() request.setFilterRect(area).setFlags(QgsFeatureRequest.ExactIntersect) request.setSubsetOfAttributes([0]) result = False for feature in layer.getFeatures(request): self.rubBandPol.reset(True) self.rubBandPol.addGeometry(feature.geometry(), layer) parcel_no = int(feature[0]) possession_type = self.__possessionType(parcel_no) if not self.currentDialog or possession_type != self.currentPossessionType: if self.currentDialog: self.dialogPosition = self.currentDialog.pos() self.currentDialog.reject() # if possession_type == "INDIVIDUAL": # self.currentDialog = PossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow()) # else: # self.currentDialog = CooperativePossessionDetailsDialog(self.plugin, self.plugin.iface.mainWindow()) self.currentPossessionType = possession_type self.connect(self.currentDialog, SIGNAL("rejected()"), self.__dialogClosed) if self.dialogPosition: self.currentDialog.move(self.dialogPosition) self.currentDialog.setParcelNo(parcel_no) if self.currentDialog.isHidden(): self.currentDialog.show() result = True break if not result: self.__resetTool()
def canvasReleaseEvent(self, event): """Slot called when the mouse button is released on the canvas. :param event: Canvas event containing position of click, which button was clicked etc. """ if not event.button() == Qt.LeftButton: return def progress_callback(current, maximum, message=None): """GUI based callback implementation for showing progress. :param current: Current progress. :type current: int :param maximum: Maximum range (point at which task is complete. :type maximum: int :param message: Optional message to display in the progress bar :type message: str, QString """ if message is not None: self.message_bar.setText(message) if self.progress_bar is not None: self.progress_bar.setMaximum(maximum) self.progress_bar.setValue(current) self.iface.messageBar().pushMessage(self.tr('SG Downloader.'), self.tr('Preparing for download'), level=QgsMessageBar.INFO, duration=10) # No need to check that it is a valid, polygon layer # as the QAction for this map tool already does that layer = self.canvas.currentLayer() place = self.toMapCoordinates(event.pos()) rectangle = point_to_rectangle(place) request = QgsFeatureRequest(QgsFeatureRequest.FilterRect) # Ensure only those features really intersecting the rect are returned request.setFlags(QgsFeatureRequest.ExactIntersect) request.setFilterRect(rectangle) polygons = layer.getFeatures(request) feature = QgsFeature() fetch_list = [] all_fields = layer.pendingFields() text_fields = [] # Ignore any columns that don't contain text data for field in all_fields: if field.typeName() == 'String' or field.typeName() == 'Text': text_fields.append(field) self.setup_message_bar() sg_field = None while polygons.nextFeature(feature): # geom = feature.geometry() # attributes = feature.attributes() # matched = False # sg_code = None if sg_field is None: for field in text_fields: value = str(feature[field.name()]) if not is_valid_sg_code(value): continue sg_field = field.name() fetch_list.append(value) else: # We already know which column has SG codes value = str(feature[sg_field]) fetch_list.append(value) if len(fetch_list) == 0: self.iface.messageBar().pushMessage( self.tr('SG Downloader.'), self.tr('No parcels found with a valid 21 Digit code'), level=QgsMessageBar.WARNING, duration=10) return province = province_for_point(self.db_manager, place) report = '' sg_diagrams_database = os.path.join(DATA_DIR, 'sg_diagrams.sqlite') data_manager = DatabaseManager(sg_diagrams_database) i = 0 for sg_code in fetch_list: i += 1 message = 'Downloading SG Code %s from %s' % (sg_code, province) progress_callback(i, len(fetch_list), message) report += download_sg_diagram(data_manager, sg_code, province, self.output_directory, callback=progress_callback) data_manager.close() try: write_log(report, self.log_file) except IOError as e: print e self.show_log(report, self.log_file)
def run(self, layers): """ Experimental impact function Input layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of roads """ target_field = self.parameters['target_field'] road_type_field = self.parameters['road_type_field'] threshold_min = self.parameters['min threshold [m]'] threshold_max = self.parameters['max threshold [m]'] if threshold_min > threshold_max: message = tr('''The minimal threshold is greater then the maximal specified threshold. Please check the values.''') raise GetDataError(message) # Extract data H = get_hazard_layer(layers) # Flood E = get_exposure_layer(layers) # Roads question = get_question(H.get_name(), E.get_name(), self) H = H.get_layer() E = E.get_layer() # Get necessary width and height of raster height = (self.extent[3] - self.extent[1]) / H.rasterUnitsPerPixelY() height = int(height) width = (self.extent[2] - self.extent[0]) / H.rasterUnitsPerPixelX() width = int(width) # Align raster extent and self.extent raster_extent = H.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / H.width() x = xmin for i in range(H.width()): if abs(x - self.extent[0]) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / H.height() y = ymin for i in range(H.width()): if abs(y - self.extent[1]) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip and polygonize small_raster = clip_raster(H, width, height, QgsRectangle(*clip_extent)) flooded_polygon = polygonize(small_raster, threshold_min, threshold_max) # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) if flooded_polygon is None: message = tr('''There are no objects in the hazard layer with "value">'%s'. Please check the value or use other extent.''' % (threshold_min, )) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(extent) line_layer = clip_by_polygon(E, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon(line_layer, flooded_polygon, request, mark_value=(target_field, 1)) # Find inundated roads, mark them # line_layer = split_by_polygon( # E, # flooded_polygon, # request, # mark_value=(target_field, 1)) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.extent[0], self.extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(E.crs(), output_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if not road_type in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = [ question, TableRow([ tr('Road Type'), tr('Flooded in the threshold (m)'), tr('Total (m)') ], header=True), TableRow([tr('All'), int(flooded_len), int(road_len)]) ] table_body.append(TableRow(tr('Breakdown by road type'), header=True)) for t, v in roads_by_type.iteritems(): table_body.append(TableRow([t, int(v['flooded']), int(v['total'])])) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector(data=line_layer, name=tr('Flooded roads'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field }, style_info=style_info) return line_layer
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 processing(options, f, progressBar, progressMessage): ''' Select trees which are on the contour of the forest and isolated trees. ''' # Export Grid contour and isolated to crowns values forestSelectedPath = options['dst'] + 'tif/' + f + \ '_forest_selected.tif' crownsPath = options['dst'] + 'shp/' + f + '_crowns.shp' # crownsStatsPath = options['dst'] + 'shp/' + f + '_crowns_stats.shp' outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("gridstatisticsforpolygons started\n") fileTxt.close() crowns = QgsVectorLayer(crownsPath, "crowns", "ogr") inputStatRaster = QgsRasterLayer(forestSelectedPath, "forestSelected") z_stat = QgsZonalStatistics(crowns, inputStatRaster, '_', 1, QgsZonalStatistics.Max) result_z_stat = z_stat.calculateStatistics(QgsFeedback()) outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("gridstatisticsforpolygons passed\n") fileTxt.close() # crowns = QgsVectorLayer(crownsStatsPath, 'Crowns stats', 'ogr') crowns.selectByExpression('"_max"=1.0') selected_array = crowns.getValues("N", True) crowns.invertSelection() unselected_array = crowns.getValues("N", True) unselected_crowns_ids = crowns.getValues("$id", True) unselected_top_ids = crowns.getValues('"N" - 1', True) crowns.dataProvider().deleteFeatures(unselected_crowns_ids[0]) treetopsPath = options['dst'] + 'shp/' + f + '_treetops.shp' treetops = QgsVectorLayer(treetopsPath, 'Tree tops', 'ogr') treetops.dataProvider().deleteFeatures(unselected_top_ids[0]) treetopsSelectedPath = options['dst'] + 'shp/' + f + \ '_treetops_selected.shp' crownsSelectedPath = options['dst'] + 'shp/' + f + '_crowns_selected.shp' treetopsTrianglesPath = options['dst'] + 'shp/' + f + \ '_treetops_triangles.shp' outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("advancedpythonfieldcalculator started\n") fileTxt.close() treetops.dataProvider().addAttributes([QgsField('N', QVariant.Int)]) treetops.updateFields() treetops.startEditing() for treetop in treetops.getFeatures(): treetops.changeAttributeValue(treetop.id(), treetop.fieldNameIndex('N'), treetop.id()) treetops.commitChanges() outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("joinattributesbylocation started\n") fileTxt.close() # Adapted from https://github.com/qgis/QGIS-Processing # TODO: replace by native QGIS c++ algo when available... crowns.dataProvider().addAttributes([QgsField('tid', QVariant.Int)]) crowns.updateFields() crowns.startEditing() fcount = crowns.featureCount() counter = 0 for crown in crowns.getFeatures(): counter += 1 progressBar.setValue(100 + int(counter * (600 / fcount))) progressMessage.setText('Joining crown ' + str(counter) + '/' + str(fcount)) request = QgsFeatureRequest() request.setFilterRect(crown.geometry().boundingBox()) dp = treetops.dataProvider() for r in dp.getFeatures(request): if crown.geometry().intersects(r.geometry()): crowns.changeAttributeValue(crown.id(), crown.fieldNameIndex('tid'), r.id()) crowns.commitChanges() fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("delaunaytriangulation started\n") fileTxt.close() # delaunay triangulation Adapted from official Python plugin # TODO: replace by native QGIS c++ algo when available... fields = QgsFields() fields.append(QgsField('POINTA', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTB', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTC', QVariant.Double, '', 24, 15)) crs = QgsCoordinateReferenceSystem('EPSG:2056') triangleFile = QgsVectorFileWriter(treetopsTrianglesPath, 'utf-8', fields, QgsWkbTypes.Polygon, crs, 'ESRI Shapefile') pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() features = treetops.getFeatures() total = 100.0 / treetops.featureCount() if treetops.featureCount() else 0 progressMessage.setText('Starting triangulation...') for current, inFeat in enumerate(features): geom = QgsGeometry(inFeat.geometry()) if geom.isNull(): continue if geom.isMultipart(): points = geom.asMultiPoint() else: points = [geom.asPoint()] for n, point in enumerate(points): x = point.x() y = point.y() pts.append((x, y)) ptNdx += 1 ptDict[ptNdx] = (inFeat.id(), n) progressMessage.setText('Triangulation step 1 ok') if len(pts) < 3: raise QgsProcessingException( 'Input file should contain at least 3 points. Choose ' 'another file and try again.') uniqueSet = set(item for item in pts) ids = [pts.index(item) for item in uniqueSet] sl = voronoi.SiteList([voronoi.Site(*i) for i in uniqueSet]) c.triangulate = True voronoi.voronoi(sl, c) triangles = c.triangles feat = QgsFeature() total = 100.0 / len(triangles) if triangles else 1 for current, triangle in enumerate(triangles): indices = list(triangle) indices.append(indices[0]) polygon = [] attrs = [] step = 0 for index in indices: fid, n = ptDict[ids[index]] request = QgsFeatureRequest().setFilterFid(fid) inFeat = next(treetops.getFeatures(request)) geom = QgsGeometry(inFeat.geometry()) point = QgsPoint(geom.asPoint()) polygon.append(point) if step <= 3: attrs.append(ids[index]) step += 1 linestring = QgsLineString(polygon) poly = QgsPolygon() poly.setExteriorRing(linestring) feat.setAttributes(attrs) geometry = QgsGeometry().fromWkt(poly.asWkt()) feat.setGeometry(geometry) triangleFile.addFeature(feat) progressMessage.setText('Triangulation terminated') # Remove triangles with perimeter higher than threshold triangles = QgsVectorLayer(treetopsTrianglesPath, 'triangles', 'ogr') maxPeri = str(options['MaxTrianglePerimeter']) triangles.selectByExpression('$perimeter > ' + maxPeri) triangles_to_delete_ids = triangles.getValues("$id", True) triangles.dataProvider().deleteFeatures(triangles_to_delete_ids[0]) outputDir = options["dst"] fileTxt = open(outputDir + "/log.txt", "a") fileTxt.write("treeSelector passed\n") fileTxt.close() progressMessage.setText('Starting convexhull computing...')
def writeVectors(writer, legendInterface=None, progress=None): settings = writer.settings baseExtent = settings.baseExtent progress = progress or dummyProgress renderer = QgsMapRenderer() layers = [] if legendInterface is None: for parentId in [ObjectTreeItem.ITEM_POINT, ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON]: for layerId, properties in settings.get(parentId, {}).iteritems(): if properties.get("visible", False): layers.append([layerId, properties]) else: # use vector layer order in legendInterface for layer in legendInterface.layers(): if layer.type() != QgsMapLayer.VectorLayer: continue parentId = ObjectTreeItem.parentIdByLayer(layer) properties = settings.get(parentId, {}).get(layer.id(), {}) if properties.get("visible", False): layers.append([layer.id(), properties]) finishedLayers = 0 for layerId, properties in layers: mapLayer = QgsMapLayerRegistry.instance().mapLayer(layerId) if mapLayer is None: continue prop = VectorPropertyReader(writer.objectTypeManager, mapLayer, properties) obj_mod = writer.objectTypeManager.module(prop.mod_index) if obj_mod is None: logMessage("Module not found") continue # prepare triangle mesh geom_type = mapLayer.geometryType() if geom_type == QGis.Polygon and prop.type_index == 1 and prop.isHeightRelativeToDEM(): # Overlay progress(None, "Initializing triangle mesh for overlay polygons") writer.triangleMesh() progress(30 + 30 * finishedLayers / len(layers), u"Writing vector layer ({0} of {1}): {2}".format(finishedLayers + 1, len(layers), mapLayer.name())) # write layer object layer = VectorLayer(writer, mapLayer, prop, obj_mod) writer.writeLayer(layer.layerObject(), layer.fieldNames) # initialize symbol rendering mapLayer.rendererV2().startRender(renderer.rendererContext(), mapLayer.pendingFields() if QGis.QGIS_VERSION_INT >= 20300 else mapLayer) # features to export request = QgsFeatureRequest() clipGeom = None if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect(layer.transform.transformBoundingBox(baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale(0.999999) # clip with slightly smaller extent than map canvas extent clipGeom = extent.geometry() for feat in layer.features(request, clipGeom): # write geometry obj_mod.write(writer, layer, feat) # writer.writeFeature(layer, feat, obj_mod) # stack attributes in writer if layer.writeAttrs: writer.addAttributes(feat.attributes()) # write attributes if layer.writeAttrs: writer.writeAttributes() # write materials writer.writeMaterials(layer.materialManager) mapLayer.rendererV2().stopRender(renderer.rendererContext()) finishedLayers += 1
def _clip_vector_layer(layer, extent, extra_keywords=None, explode_flag=True, hard_clip_flag=False, explode_attribute=None): """Clip a Hazard or Exposure layer to the extents provided. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS vector or raster layer :type layer: :param extent: Either an array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. or: A QgsGeometry of type polygon. **Polygon clipping is currently only supported for vector datasets.** :type extent: list(float, float, float, float) :param extra_keywords: Optional keywords dictionary to be added to output layer. :type extra_keywords: dict :param explode_flag: A bool specifying whether multipart features should be 'exploded' into singleparts. **This parameter is ignored for raster layer clipping.** :type explode_flag: bool :param hard_clip_flag: A bool specifying whether line and polygon features that extend beyond the extents should be clipped such that they are reduced in size to the part of the geometry that intersects the extent only. Default is False. **This parameter is ignored for raster layer clipping.** :type hard_clip_flag: bool :param explode_attribute: A str specifying to which attribute #1, #2 and so on will be added in case of explode_flag being true. The attribute is modified only if there are at least 2 parts. :type explode_attribute: str :returns: Clipped layer (placed in the system temp dir). The output layer will be reprojected to EPSG:4326 if needed. :rtype: QgsVectorLayer """ if not layer or not extent: message = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(message) if layer.type() != QgsMapLayer.VectorLayer: message = tr('Expected a vector layer but received a %s.' % str(layer.type())) raise InvalidParameterError(message) # handle, file_name = tempfile.mkstemp('.sqlite', 'clip_', # temp_dir()) handle, file_name = tempfile.mkstemp('.shp', 'clip_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(file_name) # Get the clip extents in the layer's native CRS geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) transform = QgsCoordinateTransform(geo_crs, layer.crs()) allowed_clip_values = [QGis.WKBPolygon, QGis.WKBPolygon25D] if isinstance(extent, list): rectangle = QgsRectangle(extent[0], extent[1], extent[2], extent[3]) # noinspection PyCallByClass # noinspection PyTypeChecker polygon = QgsGeometry.fromRect(rectangle) elif (isinstance(extent, QgsGeometry) and extent.wkbType in allowed_clip_values): rectangle = extent.boundingBox().toRectF() polygon = extent else: raise InvalidClipGeometryError( tr('Clip geometry must be an extent or a single part' 'polygon based geometry.')) projected_extent = transform.transformBoundingBox(rectangle) # Get vector layer provider = layer.dataProvider() if provider is None: message = tr('Could not obtain data provider from ' 'layer "%s"' % layer.source()) raise Exception(message) # Get the layer field list, select by our extent then write to disk # .. todo:: FIXME - for different geometry types we should implement # different clipping behaviour e.g. reject polygons that # intersect the edge of the bbox. Tim request = QgsFeatureRequest() if not projected_extent.isEmpty(): request.setFilterRect(projected_extent) request.setFlags(QgsFeatureRequest.ExactIntersect) field_list = provider.fields() writer = QgsVectorFileWriter( file_name, None, field_list, layer.wkbType(), geo_crs, # 'SQLite') # FIXME (Ole): This works but is far too slow 'ESRI Shapefile') if writer.hasError() != QgsVectorFileWriter.NoError: message = tr('Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (file_name, writer.hasError())) raise Exception(message) # Reverse the coordinate xform now so that we can convert # geometries from layer crs to geocrs. transform = QgsCoordinateTransform(layer.crs(), geo_crs) # Retrieve every feature with its geometry and attributes count = 0 has_multipart = False for feature in provider.getFeatures(request): geometry = feature.geometry() # Loop through the parts adding them to the output file # we write out single part features unless explode_flag is False if explode_flag: geometry_list = explode_multipart_geometry(geometry) else: geometry_list = [geometry] for part_index, part in enumerate(geometry_list): part.transform(transform) if hard_clip_flag: # Remove any dangling bits so only intersecting area is # kept. part = clip_geometry(polygon, part) if part is None: continue feature.setGeometry(part) # There are multiple parts and we want to show it in the # explode_attribute if part_index > 0 and explode_attribute is not None: has_multipart = True writer.addFeature(feature) count += 1 del writer # Flush to disk if count < 1: message = tr( 'No features fall within the clip extents. Try panning / zooming ' 'to an area containing data and then try to run your analysis ' 'again. If hazard and exposure data doesn\'t overlap at all, it ' 'is not possible to do an analysis. Another possibility is that ' 'the layers do overlap but because they may have different ' 'spatial references, they appear to be disjointed. If this is the ' 'case, try to turn on reproject on-the-fly in QGIS.') raise NoFeaturesInExtentError(message) keyword_io = KeywordIO() if extra_keywords is None: extra_keywords = {} extra_keywords['had multipart polygon'] = has_multipart keyword_io.copy_keywords(layer, file_name, extra_keywords=extra_keywords) base_name = '%s clipped' % layer.name() layer = QgsVectorLayer(file_name, base_name, 'ogr') return layer
def writeVector(writer, layerId, properties, progress=None, renderer=None, noFeature=False): mapLayer = QgsProject.instance().mapLayer(layerId) if mapLayer is None: return settings = writer.settings baseExtent = settings.baseExtent progress = progress or dummyProgress renderContext = QgsRenderContext.fromMapSettings(settings.mapSettings) expContext = settings.mapSettings.expressionContext() prop = VectorPropertyReader(writer.objectTypeManager, renderContext, expContext, mapLayer, properties) obj_mod = writer.objectTypeManager.module(prop.mod_index) if obj_mod is None: logMessage("Module not found") return # prepare triangle mesh geom_type = mapLayer.geometryType() if geom_type == QgsWkbTypes.PolygonGeometry and prop.type_index == 1 and prop.isHeightRelativeToDEM( ): # Overlay progress(None, "Initializing triangle mesh for overlay polygons") writer.triangleMesh() progress(None, "Writing vector layer: {0}".format(mapLayer.name())) # write layer object layer = VectorLayer(writer, mapLayer, prop, obj_mod) writer.writeLayer(layer, layer.fieldNames) if noFeature: return # initialize symbol rendering mapLayer.renderer().startRender(renderContext, mapLayer.pendingFields()) # features to export request = QgsFeatureRequest() clipGeom = None if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect( layer.transform.transformBoundingBox( baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale( 0.999999 ) # clip with slightly smaller extent than map canvas extent clipGeom = extent.geometry() for feat in layer.features(request, clipGeom): if writer.isCanceled: break # write feature obj_mod.write(writer, layer, feat) # writer.writeFeature(layer, feat, obj_mod) # stack attributes in writer if layer.writeAttrs: writer.addAttributes(feat.attributes()) # write attributes if layer.writeAttrs: writer.writeAttributes() # write materials writer.writeMaterials(layer.materialManager) mapLayer.renderer().stopRender(renderContext)
def setSelectFeatures(canvas, selectGeometry, doContains, doDifference, singleSelect=None): """ QgsMapCanvas* canvas, QgsGeometry* selectGeometry, bool doContains, bool doDifference, bool singleSelect """ if selectGeometry.type() != QGis.Polygon: return vlayer = getCurrentVectorLayer(canvas) if vlayer == None: return #toLayerCoordinates will throw an exception for any 'invalid' points in #the rubber band. #For example, if you project a world map onto a globe using EPSG 2163 #and then click somewhere off the globe, an exception will be thrown. selectGeomTrans = QgsGeometry(selectGeometry) if canvas.mapSettings().hasCrsTransformEnabled(): try: ct = QgsCoordinateTransform(canvas.mapSettings().destinationCrs(), vlayer.crs()) selectGeomTrans.transform( ct ) except QgsCsException as cse: Q_UNUSED(cse) #catch exception for 'invalid' point and leave existing selection unchanged """ QgsLogger::warning( "Caught CRS exception " + QString( __FILE__ ) + ": " + QString::number( __LINE__ ) ); QgisApp::instance()->messageBar()->pushMessage( QObject::tr( "CRS Exception" ), QObject::tr( "Selection extends beyond layer's coordinate system" ), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() ); """ return QApplication.setOverrideCursor(Qt.WaitCursor) """ QgsDebugMsg( "Selection layer: " + vlayer->name() ); QgsDebugMsg( "Selection polygon: " + selectGeomTrans.exportToWkt() ); QgsDebugMsg( "doContains: " + QString( doContains ? "T" : "F" ) ); QgsDebugMsg( "doDifference: " + QString( doDifference ? "T" : "F" ) ); """ context = QgsRenderContext().fromMapSettings(canvas.mapSettings()) r = vlayer.rendererV2() if r: r.startRender(context, vlayer.pendingFields()) request = QgsFeatureRequest() request.setFilterRect(selectGeomTrans.boundingBox()) request.setFlags(QgsFeatureRequest.ExactIntersect) if r: request.setSubsetOfAttributes(r.usedAttributes(), vlayer.pendingFields()) else: request.setSubsetOfAttributes(QgsAttributeList) fit = vlayer.getFeatures(request) newSelectedFeatures = [] #QgsFeatureIds f = QgsFeature() closestFeatureId = 0 #QgsFeatureId foundSingleFeature = False #double closestFeatureDist = std::numeric_limits<double>::max(); closestFeatureDist = sys.float_info.max while fit.nextFeature(f): # make sure to only use features that are visible if r and not r.willRenderFeature( f ): continue; g = QgsGeometry(f.geometry()) if doContains: if not selectGeomTrans.contains(g): continue else: if not selectGeomTrans.intersects(g): continue if singleSelect: foundSingleFeature = True distance = float(g.distance(selectGeomTrans)) if ( distance <= closestFeatureDist ): closestFeatureDist = distance closestFeatureId = f.id() else: newSelectedFeatures.insert(0, f.id()) if singleSelect and foundSingleFeature: newSelectedFeatures.insert(0, closestFeatureId) if r: r.stopRender(context) #QgsDebugMsg( "Number of new selected features: " + QString::number( newSelectedFeatures.size() ) if doDifference: layerSelectedFeatures = vlayer.selectedFeaturesIds() selectedFeatures = [] #QgsFeatureIds deselectedFeatures = []# QgsFeatureIds # i = QgsFeatureIds.const_iterator(newSelectedFeatures.constEnd()) # while i != newSelectedFeatures.constBegin(): # i = i - 1 # if layerSelectedFeatures.contains(i): # deselectedFeatures.insert(0, i) # else: # selectedFeatures.insert(0, i) for item in newSelectedFeatures: if item in layerSelectedFeatures: deselectedFeatures.insert(0, item) else: selectedFeatures.insert(0, item) vlayer.modifySelection(selectedFeatures, deselectedFeatures) else: vlayer.setSelectedFeatures(newSelectedFeatures) QApplication.restoreOverrideCursor() """
def run(self, layers): """Run the impact function. :param layers: List of layers expected to contain at least: H: Polygon layer of inundation areas E: Vector layer of roads :type layers: list :returns: A new line layer with inundated roads marked. :type: safe_layer """ target_field = self.parameters['target_field'] road_type_field = self.parameters['road_type_field'] threshold_min = self.parameters['min threshold [m]'] threshold_max = self.parameters['max threshold [m]'] if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater then the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # Extract data H = get_hazard_layer(layers) # Flood E = get_exposure_layer(layers) # Roads question = get_question( H.get_name(), E.get_name(), self) H = H.get_layer() E = E.get_layer() # reproject self.extent to the hazard projection hazard_crs = H.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.extent), geo_crs, hazard_crs) # Align raster extent and viewport # assuming they are both in the same projection raster_extent = H.dataProvider().extent() clip_xmin = raster_extent.xMinimum() # clip_xmax = raster_extent.xMaximum() clip_ymin = raster_extent.yMinimum() # clip_ymax = raster_extent.yMaximum() if viewport_extent[0] > clip_xmin: clip_xmin = viewport_extent[0] if viewport_extent[1] > clip_ymin: clip_ymin = viewport_extent[1] # TODO: Why have these two clauses when they are not used? # Commenting out for now. # if viewport_extent[2] < clip_xmax: # clip_xmax = viewport_extent[2] # if viewport_extent[3] < clip_ymax: # clip_ymax = viewport_extent[3] height = ((viewport_extent[3] - viewport_extent[1]) / H.rasterUnitsPerPixelY()) height = int(height) width = ((viewport_extent[2] - viewport_extent[0]) / H.rasterUnitsPerPixelX()) width = int(width) raster_extent = H.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / H.width() x = xmin for i in range(H.width()): if abs(x - clip_xmin) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / H.height() y = ymin for i in range(H.width()): if abs(y - clip_ymin) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip and polygonize small_raster = clip_raster( H, width, height, QgsRectangle(*clip_extent)) (flooded_polygon_inside, flooded_polygon_outside) = polygonize_gdal( small_raster, threshold_min, threshold_max) # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) if flooded_polygon_inside is None: message = tr( 'There are no objects in the hazard layer with "value">%s.' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) # reproject the flood polygons to exposure projection exposure_crs = E.crs() exposure_authid = exposure_crs.authid() if hazard_authid != exposure_authid: flooded_polygon_inside = reproject_vector_layer( flooded_polygon_inside, E.crs()) flooded_polygon_outside = reproject_vector_layer( flooded_polygon_outside, E.crs()) # Clip exposure by the extent # extent_as_polygon = QgsGeometry().fromRect(extent) # no need to clip since It is using a bbox request # line_layer = clip_by_polygon( # E, # extent_as_polygon # ) # Find inundated roads, mark them line_layer = split_by_polygon_in_out( E, flooded_polygon_inside, flooded_polygon_outside, target_field, 1, request) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.extent[0], self.extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(E.crs(), output_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if road_type not in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = [ question, TableRow( [ tr('Road Type'), tr('Flooded in the threshold (m)'), tr('Total (m)')], header=True), TableRow([tr('All'), int(flooded_len), int(road_len)]), TableRow(tr('Breakdown by road type'), header=True)] for t, v in roads_by_type.iteritems(): table_body.append( TableRow([t, int(v['flooded']), int(v['total'])]) ) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector( data=line_layer, name=tr('Flooded roads'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) return line_layer
def calculateAzimuthToRoadCentreLine(feature): # find the shortest line from this point to the road centre line layer # http://www.lutraconsulting.co.uk/blog/2014/10/17/getting-started-writing-qgis-python-plugins/ - generates "closest feature" function # QgsMessageLog.logMessage("In setAzimuthToRoadCentreLine(helper):", tag="TOMs panel") RoadCentreLineLayer = QgsProject.instance().mapLayersByName( "RoadCentreLine")[0] """if feature.geometry(): geom = feature.geometry() else: QgsMessageLog.logMessage("In setAzimuthToRoadCentreLine(helper): geometry not found", tag="TOMs panel") return 0""" # take the a point from the geometry # line = feature.geometry().asPolyline() line = generateGeometryUtils.getLineForAz(feature) if len(line) == 0: return 0 # Get the mid point of the line - https://gis.stackexchange.com/questions/58079/finding-middle-point-midpoint-of-line-in-qgis testPt = feature.geometry().centroid().asPoint( ) #lineGeom = QgsGeometry.fromPolyline((line[::]) #lineLength = lineGeom.length() #QgsMessageLog.logMessage("In setAzimuthToRoadCentreLine(helper): lineLength: " + str(lineLength), tag="TOMs panel") #testPt = lineGeom.interpolate(lineLength / 2.0) #testPt = line[0] # choose second point to (try to) move away from any "ends" (may be best to get midPoint ...) # QgsMessageLog.logMessage("In setAzimuthToRoadCentreLine: secondPt: " + str(testPt.x()), tag="TOMs panel") # Find all Road Centre Line features within a "reasonable" distance and then check each one to find the shortest distance tolerance_roadwidth = 25 searchRect = QgsRectangle(testPt.x() - tolerance_roadwidth, testPt.y() - tolerance_roadwidth, testPt.x() + tolerance_roadwidth, testPt.y() + tolerance_roadwidth) request = QgsFeatureRequest() request.setFilterRect(searchRect) request.setFlags(QgsFeatureRequest.ExactIntersect) shortestDistance = float("inf") featureFound = False # Loop through all features in the layer to find the closest feature for f in RoadCentreLineLayer.getFeatures(request): dist = f.geometry().distance(QgsGeometry.fromPointXY(testPt)) if dist < shortestDistance: shortestDistance = dist closestFeature = f featureFound = True # QgsMessageLog.logMessage("In calculateAzimuthToRoadCentreLine: shortestDistance: " + str(shortestDistance), tag="TOMs panel") if featureFound: # now obtain the line between the testPt and the nearest feature # f_lineToCL = closestFeature.geometry().shortestLine(QgsGeometry.fromPointXY(testPt)) startPt = QgsPoint( QgsGeometry.asPoint(closestFeature.geometry().nearestPoint( QgsGeometry.fromPointXY(testPt)))) # get the start point (we know the end point) """startPtV2 = f_lineToCL.geometry().startPoint() startPt = QgsPoint() startPt.setX(startPtV2.x()) startPt.setY(startPtV2.y())""" QgsMessageLog.logMessage( "In calculateAzimuthToRoadCentreLine: startPoint: " + str(startPt.x()), tag="TOMs panel") Az = QgsPoint(testPt).azimuth(startPt) # Az = generateGeometryUtils.checkDegrees(testPt.azimuth(startPt)) # QgsMessageLog.logMessage("In calculateAzimuthToRoadCentreLine: Az: " + str(Az), tag="TOMs panel") return Az else: return 0
def findNearestFeatureAt(self, pos): # def findFeatureAt(self, pos, excludeFeature=None): # http://www.lutraconsulting.co.uk/blog/2014/10/17/getting-started-writing-qgis-python-plugins/ - generates "closest feature" function """ Find the feature close to the given position. 'pos' is the position to check, in canvas coordinates. if 'excludeFeature' is specified, we ignore this feature when finding the clicked-on feature. If no feature is close to the given coordinate, we return None. """ mapPt = self.transformCoordinates(pos) #tolerance = self.calcTolerance(pos) tolerance = 0.5 searchRect = QgsRectangle(mapPt.x() - tolerance, mapPt.y() - tolerance, mapPt.x() + tolerance, mapPt.y() + tolerance) request = QgsFeatureRequest() request.setFilterRect(searchRect) request.setFlags(QgsFeatureRequest.ExactIntersect) '''for feature in self.layer.getFeatures(request): if excludeFeature != None: if feature.id() == excludeFeature.id(): continue return feature ''' self.RestrictionLayers = QgsProject.instance().mapLayersByName("RestrictionLayers")[0] #currLayer = self.TOMslayer # need to loop through the layers and choose closest to click point #iface.setActiveLayer(currLayer) shortestDistance = float("inf") featureList = [] layerList = [] for layerDetails in self.RestrictionLayers.getFeatures(): # Exclude consideration of CPZs if layerDetails.attribute("id") >= 6: # CPZs continue self.currLayer = RestrictionTypeUtilsMixin.getRestrictionsLayer (layerDetails) # Loop through all features in the layer to find the closest feature for f in self.currLayer.getFeatures(request): # Add any features that are found should be added to a list featureList.append(f) layerList.append(self.currLayer) dist = f.geometry().distance(QgsGeometry.fromPointXY(mapPt)) if dist < shortestDistance: shortestDistance = dist closestFeature = f closestLayer = self.currLayer #QgsMessageLog.logMessage("In findNearestFeatureAt: shortestDistance: " + str(shortestDistance), level=Qgis.Info) TOMsMessageLog.logMessage("In findNearestFeatureAt: nrFeatures: " + str(len(featureList)), level=Qgis.Info) if shortestDistance < float("inf"): return closestFeature, closestLayer else: return None, None pass
def doStart(self): # first see if we can pull data from the predictions layer startTime = self.datetime endTime = self.datetime.addDays(1) featureRequest = QgsFeatureRequest() stationPt = QgsPointXY(self.stationFeature.geometry().vertexAt(0)) searchRect = QgsRectangle(stationPt, stationPt) searchRect.grow( 0.01 / 60 ) # in the neighborhood of .01 nm as 1/60 = 1 arc minute in this proj. featureRequest.setFilterRect(searchRect) # Create a time based query ctx = featureRequest.expressionContext() scope = QgsExpressionContextScope() scope.setVariable('startTime', startTime) scope.setVariable('endTime', endTime) scope.setVariable('station', self.stationFeature['station']) ctx.appendScope(scope) featureRequest.setFilterExpression( "station = @station and time >= @startTime and time < @endTime") featureRequest.addOrderBy('time') savedFeatureIterator = self.manager.predictionsLayer.getFeatures( featureRequest) savedFeatures = list(savedFeatureIterator) if len(savedFeatures) > 0: # We have some features, so go ahead and stash them in the layer and resolve this promise print('{}: retrieved {} features from layer'.format( self.stationFeature['station'], len(savedFeatures))) self.predictions = savedFeatures self.resolve() else: # The layer didn't have what we wanted, so we must request the data we need. # At this point, the situation falls into several possible cases. # Case 1: A Harmonic station with known flood/ebb directions. Here # we need two requests which can simply be combined and sorted: # 1a: EventType, i.e. slack, flood and ebb # 1b: SpeedDirType, as velocity can be calculated by projecting along flood/ebb # # Case 2: A Harmonic station with unknown flood and/or ebb. # We actually need to combine 3 requests: # 2a: EventType # 2b: SpeedDirType, which only provides vector magnitude/angle # 2c: VelocityMajorType, which only provides current velocity (but for same times as 2b) # Here we set up requests for cases 1 and 2 if self.stationFeature['type'] == 'H': self.speedDirRequest = CurrentPredictionRequest( self.manager, self.stationFeature, startTime, endTime, CurrentPredictionRequest.SpeedDirectionType) self.addDependency(self.speedDirRequest) self.eventRequest = CurrentPredictionRequest( self.manager, self.stationFeature, startTime, endTime, CurrentPredictionRequest.EventType) self.addDependency(self.eventRequest) floodDir = self.stationFeature['meanFloodDir'] ebbDir = self.stationFeature['meanEbbDir'] if floodDir == NULL or ebbDir == NULL: self.velocityRequest = CurrentPredictionRequest( self.manager, self.stationFeature, startTime, endTime, CurrentPredictionRequest.VelocityMajorType) self.addDependency(self.velocityRequest) else: self.velocityRequest = None # Case 3: A Subordinate station which only knows its events. Here we need the following: # 3a: PredictionEventPromises for this station in a 3-day window surrounding the date of interest # 3b: PredictionDataPromises for the reference station in the same 3-day window. else: self.eventPromises = [] self.refPromises = [] refStation = self.manager.getStation( self.stationFeature['refStation']) if refStation is None: print("Could not find ref station {} for {}".format( self.stationFeature['refStation'], self.stationFeature['station'])) else: for dayOffset in [-1, 0, 1]: windowDate = self.localDate.addDays(dayOffset) dataPromise = self.manager.getDataPromise( refStation, windowDate) self.refPromises.append(dataPromise) self.addDependency(dataPromise) eventPromise = self.manager.getEventPromise( self.stationFeature, windowDate) self.eventPromises.append(eventPromise) self.addDependency(eventPromise)
def run(self): """Experimental impact function for flood polygons on roads.""" # 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 road.')) self.exposure_class_attribute = self.exposure.keyword( 'road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_provider = self.hazard.layer.dataProvider() affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) # see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) # raise GetDataError(message) # LOGGER.info('Affected field: %s' % self.hazard_class_attribute) # LOGGER.info('Affected field index: %s' % affected_field_index) # Filter geometry and data using the 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 line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = self.hazard.layer.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: value = attributes[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( 'There are no objects in the hazard layer with %s (Affected ' 'Field) in %s (Affected Value). Please check the value or use ' 'a different extent.' % (self.hazard_class_attribute, self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(requested_extent) line_layer = clip_by_polygon(self.exposure.layer, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon(line_layer, hazard_poly, request, mark_value=(self.target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(self.exposure.layer.crs(), destination_crs) roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex( self.exposure_class_attribute) target_field_index = line_layer.fieldNameIndex(self.target_field) classes = [tr('Temporarily closed')] self.init_report_var(classes) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, 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 line_layer.featureCount() == 0: # Raising an exception seems poor semantics here.... raise ZeroImpactException( tr('No roads are flooded in this scenario.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector(data=line_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
def mapLayer_getFeatures(iface, request): """ Return information about features of the given vector layer. Retrieve information about (and, optionally, geometry of) features of the given vector layer by querying the underlying datasource programmatically. To retrieve features that were manually selected by the user within QGIS, see /qgis/mapLayer/selectedFeatures HTTP query arguments: id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer. geometry (optional, default false): if true, returns all feature information including their geometry in GeoJSON format. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...). orderBy (optional): expression that the results should be ordered by. If you want to order by a field, you'll have to give its name in quotes, e.g. ?orderBy="length" ascending (optional, default true): whether the results should be listen in ascending or descending order. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...). nullsfirst (optional): how null values should be treated in the ordering. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...). The different ways to filter features follow the different constructor signatures defined by the QgsFeatureRequest class, in particular: If the request is a HTTP POST request, the request body is treated as a QGIS filter expression, and the result of applying that filter is returned. If the request is a HTTP GET request, the following query arguments are considered in order, and the first one provided is applied: fid (integer): Construct a request with a QGIS feature ID filter rect (string): Construct a request with a rectangle filter. The rectangle should be specified as four numbers in the format "xmin,ymin,xmax,ymax". A GET request in which none of the arguments are specified returns ALL features of the given vector layer, which can produce very large results. Returns: If the 'geometry' argument was passed: a GeoJSON FeatureCollection with complete attribute and geometry data for all features of the layer. If the 'geometry' argument was not passed: a list of all features of the vector layer in JSON format, where each feature is an object specifying the feature's 'id' and all its 'attributes'. """ layer = qgis_layer_by_id_or_current(iface, request) # construct and run QgsFeatureRequest depending on arguments featurerequest = QgsFeatureRequest() featurerequest.addOrderBy(request.args.get('orderBy', ''), strtobool(request.args.get('ascending', 'y')), strtobool(request.args.get('nullsfirst', 'n'))) if request.command == 'POST': # POST request: complex QgsExpression passed as string featurerequest.setFilterExpression(QgsExpression(request.headers.get_payload())) else: if request.args.get('fid'): # query by feature id featurerequest.setFilterFid(int(request.args['fid'])) elif request.args.get('rect'): # query by rectangle r = [float(x) for x in request.args['rect'].split(',')] if len(r) != 4: raise ValueError('"rect" argument to getFeatures requires exactly four floats in the format "xmin,ymin,xmax,ymax"') featurerequest.setFilterRect(QgsRectangle(r[0], r[1], r[2], r[3])) result = layer.getFeatures(featurerequest) if strtobool(request.args.get('geometry', 'n')): return NetworkAPIResult(toGeoJSON(layer, result), 'application/geo+json; charset=utf-8') else: # note that the lazy QgsFeatureIterator returned here is currently # turned into a full in-memory list during conversion to JSON return NetworkAPIResult(result)
def build(self, export_blocks=False): mapLayer = self.layer.mapLayer if mapLayer is None: return properties = self.layer.properties baseExtent = self.settings.baseExtent mapSettings = self.settings.mapSettings renderContext = QgsRenderContext.fromMapSettings(mapSettings) self.prop = VectorPropertyReader(objectTypeRegistry(), renderContext, mapLayer, properties) if self.prop.objType is None: logMessage("Object type not found") return # prepare triangle mesh if self.prop.objType.name == "Overlay" and self.prop.isHeightRelativeToDEM(): # get the grid size of the DEM layer which polygons overlay demProp = self.settings.getPropertyReaderByLayerId(properties.get("comboBox_altitudeMode")) if demProp: self.demSize = demProp.demSize(mapSettings.outputSize()) layer = VectorLayer(self.settings, mapLayer, self.prop, self.materialManager) self._layer = layer self.hasLabel = layer.hasLabel() self.clipGeom = None # feature request request = QgsFeatureRequest() if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect(layer.transform.transformBoundingBox(baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) # geometry for clipping if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale(0.999999) # clip with slightly smaller extent than map canvas extent self.clipGeom = extent.geometry() # initialize symbol rendering, and then get features (geometry, attributes, color, etc.) mapLayer.renderer().startRender(renderContext, mapLayer.fields()) self.features = layer.features(request) mapLayer.renderer().stopRender(renderContext) # materials for feat in self.features: feat.material = self.prop.objType.material(self.settings, layer, feat) data = {} data["materials"] = self.materialManager.buildAll(self.imageManager, base64=self.settings.base64) if export_blocks: data["blocks"] = [block.build() for block in self.blocks()] d = { "type": "layer", "id": self.layer.jsLayerId, "properties": self.layerProperties(), "data": data } if debug_mode: d["PROPERTIES"] = properties return d
def run_checks(): self.assertEqual([f.name() for f in vl.fields()], ['fid', 'type', 'value']) # expression req = QgsFeatureRequest() req.setFilterExpression("value=16") it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fid req = QgsFeatureRequest() req.setFilterFid(5) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fids req = QgsFeatureRequest() req.setFilterFids([5]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # check with subset of attributes req = QgsFeatureRequest() req.setFilterFids([5]) req.setSubsetOfAttributes([2]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes()[2], 16) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and expression req = QgsFeatureRequest() req.setFilterExpression("value=16 or value=14") req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and fids req = QgsFeatureRequest() req.setFilterFids([3, 5]) req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # Ensure that orig_ogc_fid is still retrieved even if attribute subset is passed req = QgsFeatureRequest() req.setSubsetOfAttributes([]) it = vl.getFeatures(req) ids = [] geoms = {} while it.nextFeature(f): ids.append(f.id()) geoms[f.id()] = f.geometry().asWkt() self.assertCountEqual(ids, [3, 4, 5]) self.assertEqual(geoms, {3: 'Point (3 3)', 4: 'Point (4 4)', 5: 'Point (5 5)'})
def identifyLayer(self, point): self.results[:] = [] if not self.layer.hasGeometryType(): return False if (self.layer.hasScaleBasedVisibility() and (self.layer.minimumScale() > self.canvas.mapSettings().scale() or self.layer.maximumScale() <= self.canvas.mapSettings().scale())): print 'Out of scale limits' return False QApplication.setOverrideCursor(Qt.WaitCursor) featureCount = 0 featureList = [] try: searchRadius = self.searchRadiusMU(self.canvas) r = QgsRectangle() r.setXMinimum(point.x() - searchRadius) r.setXMaximum(point.x() + searchRadius) r.setYMinimum(point.y() - searchRadius) r.setYMaximum(point.y() + searchRadius) r = self.toLayerCoordinates(self.layer, r) req = QgsFeatureRequest() req.setFilterRect(r) req.setFlags(QgsFeatureRequest.ExactIntersect) for f in self.layer.getFeatures(req): featureList.append(QgsFeature(f)) except QgsCsException as cse: print 'Caught CRS exception', cse.what() myFilter = False context = QgsRenderContext( QgsRenderContext.fromMapSettings(self.canvas.mapSettings())) renderer = self.layer.rendererV2() if (renderer is not None and (renderer.capabilities() | QgsFeatureRendererV2.ScaleDependent)): renderer.startRender(context, self.layer.pendingFields()) myFilter = renderer.capabilities() and QgsFeatureRendererV2.Filter for f in featureList: if myFilter and not renderer.willRenderFeature(f): continue featureCount += 1 self.results.append((self.layer, f)) if (renderer is not None and (renderer.capabilities() | QgsFeatureRendererV2.ScaleDependent)): renderer.stopRender(context) print 'Feature count on identify:', featureCount QApplication.restoreOverrideCursor() return featureCount > 0
class HotlinkMT(QgsMapTool): """Hotlink tool. It is this class that manages the mouse capture...""" def __init__(self, plugin): """Tool initialization""" QgsMapTool.__init__(self, plugin.canvas) self.canvas = plugin.canvas self.plugin = plugin self.featuresFound = [] self.ixFeature = 0 self.__pos = None self.chooserDlg = None self.request = QgsFeatureRequest() self.request.setFlags( QgsFeatureRequest.Flags(QgsFeatureRequest.NoGeometry | QgsFeatureRequest.ExactIntersect)) def pos(self): try: return self.toMapCoordinates(self.__pos) except: return None def canvasPressEvent(self, event): pass def _layer_tooltip(self, layer, feat): try: df = layer.displayField() if df: return str(feat.attribute(df)) else: context = QgsExpressionContext() context.appendScope(QgsExpressionContextUtils.globalScope()) context.appendScope( QgsExpressionContextUtils.projectScope( QgsProject.instance())) context.appendScope( QgsExpressionContextUtils.layerScope(layer)) context.appendScope( QgsExpressionContextUtils.mapSettingsScope( self.canvas.mapSettings())) context.setFeature(feat) x = QgsExpression(layer.displayExpression()) x.prepare(context) return x.evaluate(context).replace("\n", "<br/>") except: return "" def findUnderlyingObjects(self, event, saveFeatures): """On mouse movement, we identify the underlying objects""" if not self.plugin.active: return try: # find objects features = self._getFeatures() # if there are if features: # adjust the cursor self.canvas.setCursor(QCursor(Qt.WhatsThisCursor)) # build a list of tuples Name / feature / layer / id for construction of the tool tip, the interface of choice if saveFeatures: self.featuresFound = [] tooltip = [] for featData in features: feat = featData["feature"] layer = featData["layer"] for action in layer.actions().actions(): tip = self._layer_tooltip(layer, feat) try: actionName = "{0} {1}".format( action.shortTitle() or action.name(), tip) except: actionName = action.name() if saveFeatures: self.featuresFound.append({ "actionName": "" + actionName, "feature": feat, "layer": layer, "idxAction": action.id(), "icon": action.iconPath() }) try: tooltip.index(tip) except: tooltip.append(tip) # display if self.plugin.optionShowTips: self.canvas.setToolTip("<p>" + "<br/>".join(tooltip) + "</p>") else: # without objects, restore the cursor ... if saveFeatures: self.featuresFound = [] self.canvas.setCursor(QCursor(Qt.ArrowCursor)) if self.plugin.optionShowTips: self.canvas.setToolTip("") except: pass def canvasMoveEvent(self, event): """On mouse movement, we identify the underlying objects""" if self.plugin.optionShowTips: self.findUnderlyingObjects(event, False) def canvasDoubleClickEvent(self, event): pass def keyPressEvent(self, event): pass def canvasReleaseEvent(self, event): """On click, do action""" if not self.plugin.active: return # left click only if event.button() not in (Qt.LeftButton, Qt.RightButton): return self.__pos = event.pos() # Add click_x and click_y to context if self.pos(): QgsExpressionContextUtils.setProjectVariable( QgsProject.instance(), "click_x", self.pos().x()) QgsExpressionContextUtils.setProjectVariable( QgsProject.instance(), "click_y", self.pos().y()) self.findUnderlyingObjects(event, True) # if a single action (2 lines in the list) if len(self.featuresFound) == 1: layer = self.featuresFound[0]["layer"] id = self.featuresFound[0]["idxAction"] feature = self.featuresFound[0]["feature"] self.doAction(layer, id, feature) else: # to choose the action to trigger canvasPos = self.canvas.mapToGlobal(QPoint(0, 0)) self.chooserDlg = ChooserDlg( self, self.featuresFound, canvasPos.x() + self.__pos.x(), canvasPos.y() + self.__pos.y(), ) self.chooserDlg.go() def activate(self): pass def deactivate(self): pass def doAction(self, layer, uid, feature): if layer.actions().action(uid).name() == "openFeatureForm": self.plugin.iface.openFeatureForm(layer, feature) else: ctxt = QgsExpressionContext() ctxt.appendScope(QgsExpressionContextUtils.globalScope()) ctxt.appendScope( QgsExpressionContextUtils.projectScope(QgsProject.instance())) ctxt.appendScope( QgsExpressionContextUtils.mapSettingsScope( self.canvas.mapSettings())) layer.actions().doAction(uid, feature, ctxt) def onChoose(self, idx): """Do action""" tab = self.featuresFound[idx] self.doAction(tab["layer"], tab["idxAction"], tab["feature"]) def _getFeatures(self): """Identify objects under the mouse, having actions""" searchRadius = self.searchRadiusMU(self.canvas) point = self.toMapCoordinates(self.__pos) features = [] rect = QgsRectangle() rect.setXMinimum(point.x() - searchRadius) rect.setXMaximum(point.x() + searchRadius) rect.setYMinimum(point.y() - searchRadius) rect.setYMaximum(point.y() + searchRadius) for layer in self.canvas.layers(): # treat only vector layers having actions if (layer.type() == QgsMapLayer.VectorLayer and len(layer.actions().actions()) > 0): layerRect = self.toLayerCoordinates(layer, rect) self.request.setFilterRect(layerRect) for feature in layer.getFeatures(layerRect if ( layer.dataProvider().name() == "WFS" ) else self.request): features.append({"layer": layer, "feature": feature}) return features
def run_checks(): self.assertEqual([f.name() for f in vl.fields()], ['fid', 'type', 'value']) # expression req = QgsFeatureRequest() req.setFilterExpression("value=16") it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fid req = QgsFeatureRequest() req.setFilterFid(5) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter fids req = QgsFeatureRequest() req.setFilterFids([5]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # check with subset of attributes req = QgsFeatureRequest() req.setFilterFids([5]) req.setSubsetOfAttributes([2]) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes()[2], 16) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and expression req = QgsFeatureRequest() req.setFilterExpression("value=16 or value=14") req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # filter rect and fids req = QgsFeatureRequest() req.setFilterFids([3, 5]) req.setFilterRect(QgsRectangle(4.5, 4.5, 5.5, 5.5)) it = vl.getFeatures(req) f = QgsFeature() self.assertTrue(it.nextFeature(f)) self.assertEqual(f.id(), 5) self.assertEqual(f.attributes(), [5, 2, 16]) self.assertEqual([field.name() for field in f.fields()], ['fid', 'type', 'value']) self.assertEqual(f.geometry().asWkt(), 'Point (5 5)') # Ensure that orig_ogc_fid is still retrieved even if attribute subset is passed req = QgsFeatureRequest() req.setSubsetOfAttributes([]) it = vl.getFeatures(req) ids = [] geoms = {} while it.nextFeature(f): ids.append(f.id()) geoms[f.id()] = f.geometry().asWkt() self.assertCountEqual(ids, [3, 4, 5]) self.assertEqual(geoms, { 3: 'Point (3 3)', 4: 'Point (4 4)', 5: 'Point (5 5)' })
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ inputLyr = self.parameterAsVectorLayer( parameters, self.INPUT, context ) referenceLyr = self.parameterAsVectorLayer( parameters, self.REFERENCE, context ) tol = self.parameterAsDouble( parameters, self.TOLERANCE, context ) distanceDict = dict() featList = [i for i in inputLyr.getFeatures()] step = 100/len(featList) if featList else 0 for current, feat in enumerate(featList): if feedback.isCanceled(): break if not feat.geometry().isGeosValid(): continue id = feat.id() geom = feat.geometry().asGeometryCollection()[0].asPoint() x = geom.x() y = geom.y() bbox = QgsRectangle( x-tol, y-tol, x+tol, y+tol ) request = QgsFeatureRequest() request.setFilterRect(bbox) minDistance = 0 candidateId = None for candidateFeat in referenceLyr.getFeatures(request): dist = feat.geometry().distance(candidateFeat.geometry()) if candidateId is None: minDistance = dist candidateId = candidateFeat.id() continue elif dist < minDistance: minDistance = dist candidateId = candidateFeat.id() if candidateId is not None: distanceDict[id] = { 'minDistance' : minDistance, 'candidateId' : candidateId } feedback.setProgress(current*step) distanceList = [i['minDistance'] for i in distanceDict.values()] n = len(distanceList) distanceSquared = [i['minDistance']**2 for i in distanceDict.values()] rms = math.sqrt(sum(distanceSquared)/n) percFunc = functools.partial(self.percentile, frequency=0.9) perc = percFunc(distanceList) mean = sum(distanceList)/n feedback.pushInfo('MEAN: {mean}'.format(mean=mean)) feedback.pushInfo('RMS: {rms}'.format(rms=rms)) feedback.pushInfo('PERC: {perc}'.format(perc=perc)) return {}
def build(self, export_blocks=False): mapLayer = self.layer.mapLayer if mapLayer is None: return properties = self.layer.properties baseExtent = self.settings.baseExtent mapSettings = self.settings.mapSettings renderContext = QgsRenderContext.fromMapSettings(mapSettings) self.prop = VectorPropertyReader(objectTypeRegistry(), renderContext, mapLayer, properties) if self.prop.objType is None: logMessage("Object type not found") return # prepare triangle mesh if self.prop.objType.name == "Overlay" and self.prop.isHeightRelativeToDEM(): # get the grid size of the DEM layer which polygons overlay demProp = self.settings.getPropertyReaderByLayerId(properties.get("comboBox_altitudeMode")) if demProp: self.demSize = demProp.demSize(mapSettings.outputSize()) layer = VectorLayer(self.settings, mapLayer, self.prop, self.materialManager) self._layer = layer #if noFeature: # return self.hasLabel = layer.hasLabel() self.clipGeom = None # feature request request = QgsFeatureRequest() if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect(layer.transform.transformBoundingBox(baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) # geometry for clipping if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale(0.999999) # clip with slightly smaller extent than map canvas extent self.clipGeom = extent.geometry() # initialize symbol rendering, and then get features (geometry, attributes, color, etc.) mapLayer.renderer().startRender(renderContext, mapLayer.fields()) self.features = layer.features(request) mapLayer.renderer().stopRender(renderContext) # materials for feat in self.features: #if self.isCancelled: # break feat.material = self.prop.objType.material(self.settings, layer, feat) gt2str = { QgsWkbTypes.PointGeometry: "point", QgsWkbTypes.LineGeometry: "line", QgsWkbTypes.PolygonGeometry: "polygon" } # properties p = { "type": gt2str.get(mapLayer.geometryType()), "objType": self.prop.objType.name, "name": self.layer.name, "queryable": 1, "visible": self.layer.visible } if layer.writeAttrs: p["propertyNames"] = layer.fieldNames writeAttrs = properties.get("checkBox_ExportAttrs", False) labelAttrIndex = properties.get("comboBox_Label") if writeAttrs and labelAttrIndex is not None: widgetValues = properties.get("labelHeightWidget", {}) p["label"] = {"index": labelAttrIndex, "relative": widgetValues.get("comboData", 0) == 1} data = {} data["materials"] = self.materialManager.buildAll(self.imageManager, base64=self.settings.base64) if export_blocks: data["blocks"] = [block.build() for block in self.blocks()] d = { "type": "layer", "id": self.layer.jsLayerId, "properties": p, "data": data } if debug_mode: d["PROPERTIES"] = properties return d
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') # Get parameters from IF parameter threshold_min = self.parameters['min threshold'].value threshold_max = self.parameters['max threshold'].value if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater than the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Align raster extent and viewport # assuming they are both in the same projection raster_extent = self.hazard.layer.dataProvider().extent() clip_xmin = raster_extent.xMinimum() # clip_xmax = raster_extent.xMaximum() clip_ymin = raster_extent.yMinimum() # clip_ymax = raster_extent.yMaximum() if viewport_extent[0] > clip_xmin: clip_xmin = viewport_extent[0] if viewport_extent[1] > clip_ymin: clip_ymin = viewport_extent[1] # TODO: Why have these two clauses when they are not used? # Commenting out for now. # if viewport_extent[2] < clip_xmax: # clip_xmax = viewport_extent[2] # if viewport_extent[3] < clip_ymax: # clip_ymax = viewport_extent[3] height = ((viewport_extent[3] - viewport_extent[1]) / self.hazard.layer.rasterUnitsPerPixelY()) height = int(height) width = ((viewport_extent[2] - viewport_extent[0]) / self.hazard.layer.rasterUnitsPerPixelX()) width = int(width) raster_extent = self.hazard.layer.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / self.hazard.layer.width() x = xmin for i in range(self.hazard.layer.width()): if abs(x - clip_xmin) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / self.hazard.layer.height() y = ymin for i in range(self.hazard.layer.width()): if abs(y - clip_ymin) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip hazard raster small_raster = clip_raster(self.hazard.layer, width, height, QgsRectangle(*clip_extent)) # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation index, flood_cells_map = _raster_to_vector_cells( small_raster, threshold_min, threshold_max, self.exposure.layer.crs()) # Filter geometry and data using the extent ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) if len(flood_cells_map) == 0: message = tr( 'There are no objects in the hazard layer with "value" > %s. ' 'Please check the value or use other extent.' % (threshold_min, )) raise GetDataError(message) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat(line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells(self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(self.exposure.layer.crs(), output_crs) flooded_keyword = tr('Flooded in the threshold (m)') self.affected_road_categories = [flooded_keyword] self.affected_road_lengths = OrderedDict([(flooded_keyword, {})]) self.road_lengths = OrderedDict() if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() if road_type not in self.road_lengths: self.affected_road_lengths[flooded_keyword][road_type] = 0 self.road_lengths[road_type] = 0 self.road_lengths[road_type] += length if attributes[target_field_index] == 1: self.affected_road_lengths[flooded_keyword][ road_type] += length impact_summary = self.html_report() # For printing map purpose map_title = tr('Roads inundated') legend_title = tr('Road 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') extra_keywords = { 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': target_field } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector(data=line_layer, name=tr('Flooded roads'), keywords=impact_layer_keywords, style_info=style_info) self._impact = line_layer return line_layer
def export(self, layerId, properties, jsLayerId, visible=True, pathRoot=None, urlRoot=None): mapLayer = QgsProject.instance().mapLayer(layerId) if mapLayer is None: return baseExtent = self.settings.baseExtent mapSettings = self.settings.mapSettings renderContext = QgsRenderContext.fromMapSettings(mapSettings) expContext = mapSettings.expressionContext() otm = objectTypeManager() prop = VectorPropertyReader(otm, renderContext, expContext, mapLayer, properties) obj_mod = otm.module(prop.mod_index) if obj_mod is None: logMessage("Module not found") return # prepare triangle mesh geom_type = mapLayer.geometryType() if geom_type == QgsWkbTypes.PolygonGeometry and prop.type_index == 1 and prop.isHeightRelativeToDEM( ): # Overlay self.progress(None, "Initializing triangle mesh for overlay polygons") self.triangleMesh() self.progress(None, "Writing vector layer: {0}".format(mapLayer.name())) # write layer object layer = VectorLayer(self.settings, mapLayer, prop, obj_mod, self.materialManager) #if noFeature: # return # initialize symbol rendering mapLayer.renderer().startRender(renderContext, mapLayer.pendingFields()) # features to export request = QgsFeatureRequest() clipGeom = None if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect( layer.transform.transformBoundingBox( baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale( 0.999999 ) # clip with slightly smaller extent than map canvas extent clipGeom = extent.geometry() features = [] for feat in layer.features(request, clipGeom, self.settings.demProvider()): #if writer.isCanceled: # break geom, mat = obj_mod.write( self.settings, layer, feat) # writer.writeFeature(layer, feat, obj_mod) f = {"geom": geom, "mat": mat} if layer.writeAttrs: f["prop"] = feat.attributes() features.append(f) mapLayer.renderer().stopRender(renderContext) gt2str = { QgsWkbTypes.PointGeometry: "point", QgsWkbTypes.LineGeometry: "line", QgsWkbTypes.PolygonGeometry: "polygon" } p = { "type": gt2str.get(geom_type), "objType": prop.type_name, "name": mapLayer.name(), "queryable": 1, "visible": visible } d = { "features": features, "materials": self.materialManager.export(self.imageManager) } if layer.writeAttrs: d["propertyNames"] = layer.fieldNames return { "type": "layer", "id": jsLayerId, "properties": p, "data": d, "PROPERTIES": properties # debug }
def writeVectors(writer, legendInterface=None, progress=None): settings = writer.settings baseExtent = settings.baseExtent progress = progress or dummyProgress renderer = QgsMapRenderer() layers = [] if legendInterface is None: for parentId in [ ObjectTreeItem.ITEM_POINT, ObjectTreeItem.ITEM_LINE, ObjectTreeItem.ITEM_POLYGON ]: for layerId, properties in settings.get(parentId, {}).iteritems(): if properties.get("visible", False): layers.append([layerId, properties]) else: # use vector layer order in legendInterface for layer in legendInterface.layers(): if layer.type() != QgsMapLayer.VectorLayer: continue parentId = ObjectTreeItem.parentIdByLayer(layer) properties = settings.get(parentId, {}).get(layer.id(), {}) if properties.get("visible", False): layers.append([layer.id(), properties]) finishedLayers = 0 for layerId, properties in layers: mapLayer = QgsMapLayerRegistry.instance().mapLayer(layerId) if mapLayer is None: continue prop = VectorPropertyReader(writer.objectTypeManager, mapLayer, properties) obj_mod = writer.objectTypeManager.module(prop.mod_index) if obj_mod is None: logMessage("Module not found") continue # prepare triangle mesh geom_type = mapLayer.geometryType() if geom_type == QGis.Polygon and prop.type_index == 1 and prop.isHeightRelativeToDEM( ): # Overlay progress(None, "Initializing triangle mesh for overlay polygons") writer.triangleMesh() progress( 30 + 30 * finishedLayers / len(layers), u"Writing vector layer ({0} of {1}): {2}".format( finishedLayers + 1, len(layers), mapLayer.name())) # write layer object layer = VectorLayer(writer, mapLayer, prop, obj_mod) writer.writeLayer(layer.layerObject(), layer.fieldNames) # initialize symbol rendering mapLayer.rendererV2().startRender( renderer.rendererContext(), mapLayer.pendingFields() if QGis.QGIS_VERSION_INT >= 20300 else mapLayer) # features to export request = QgsFeatureRequest() clipGeom = None if properties.get("radioButton_IntersectingFeatures", False): request.setFilterRect( layer.transform.transformBoundingBox( baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) if properties.get("checkBox_Clip"): extent = baseExtent.clone().scale( 0.999999 ) # clip with slightly smaller extent than map canvas extent clipGeom = extent.geometry() for feat in layer.features(request, clipGeom): # write geometry obj_mod.write(writer, layer, feat) # writer.writeFeature(layer, feat, obj_mod) # stack attributes in writer if layer.writeAttrs: writer.addAttributes(feat.attributes()) # write attributes if layer.writeAttrs: writer.writeAttributes() # write materials writer.writeMaterials(layer.materialManager) mapLayer.rendererV2().stopRender(renderer.rendererContext()) finishedLayers += 1
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ inputLyr = self.parameterAsVectorLayer(parameters, self.INPUT, context) if inputLyr is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) onlySelected = self.parameterAsBool(parameters, self.SELECTED, context) attributeName = self.parameterAsFields(parameters, self.IMAGE_ATTRIBUTE, context)[0] groupExpression = self.parameterAsExpression(parameters, self.GROUP_EXPRESSION, context) imageTag = self.parameterAsString(parameters, self.NAME_TAG, context) boundingBoxGeometry = self.parameterAsExtentGeometry( parameters, self.EXTENT, context) loadToCanvas = self.parameterAsBoolean(parameters, self.ADD_TO_CANVAS, context) uniqueLoad = self.parameterAsBoolean(parameters, self.UNIQUE_LOAD, context) loadedLayers = { i.dataProvider().dataSourceUri() : i.id() for i in iface.mapCanvas().layers()\ if isinstance(i, QgsRasterLayer) } if uniqueLoad else {} outputLayers = {} request = QgsFeatureRequest() if boundingBoxGeometry is not None: request.setFilterRect(boundingBoxGeometry.boundingBox()) request.setFlags(QgsFeatureRequest.NoGeometry) request.addOrderBy(attributeName, ascending=True) features = inputLyr.getFeatures(request) if not onlySelected \ else inputLyr.getSelectedFeatures(request) #calculate size featList = [i for i in features] listSize = len(featList) progressStep = 100 / listSize if listSize else 0 #remaining parameters if loadToCanvas: rootNode = QgsProject.instance().layerTreeRoot() datasetImageNode = self.createGroup(self.tr('Dataset Images'), rootNode) iface.mapCanvas().freeze(True) else: datasetImageNode = None for current, feat in enumerate(featList): if feedback.isCanceled(): break image_path = feat[attributeName] if image_path in loadedLayers: outputLayers[image_path] = loadedLayers[image_path] feedback.setProgress(current * progressStep) continue newImage = QgsRasterLayer( image_path, '_'.join( [imageTag, os.path.basename(image_path)] if imageTag != ''\ else [os.path.basename(image_path)] ) ) QgsProject.instance().addMapLayer(newImage, False) if datasetImageNode is not None: currentNode = datasetImageNode if groupExpression is None\ else self.getLayerCategoryNode( newImage, datasetImageNode, groupExpression ) currentNode.addLayer(newImage) outputLayers[image_path] = newImage.id() feedback.setProgress(current * progressStep) if loadToCanvas: iface.mapCanvas().freeze(False) iface.mapCanvas().refresh() return {self.OUTPUT: list(outputLayers.values())}
def run(self, layers): """ Experimental impact function Input layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of roads """ target_field = self.parameters['target_field'] road_type_field = self.parameters['road_type_field'] threshold_min = self.parameters['min threshold [m]'] threshold_max = self.parameters['max threshold [m]'] if threshold_min > threshold_max: message = tr('''The minimal threshold is greater then the maximal specified threshold. Please check the values.''') raise GetDataError(message) # Extract data H = get_hazard_layer(layers) # Flood E = get_exposure_layer(layers) # Roads question = get_question( H.get_name(), E.get_name(), self) H = H.get_layer() E = E.get_layer() # Get necessary width and height of raster height = (self.extent[3] - self.extent[1]) / H.rasterUnitsPerPixelY() height = int(height) width = (self.extent[2] - self.extent[0]) / H.rasterUnitsPerPixelX() width = int(width) # Align raster extent and self.extent raster_extent = H.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / H.width() x = xmin for i in range(H.width()): if abs(x - self.extent[0]) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / H.height() y = ymin for i in range(H.width()): if abs(y - self.extent[1]) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip and polygonize small_raster = clip_raster( H, width, height, QgsRectangle(*clip_extent)) flooded_polygon = polygonize( small_raster, threshold_min, threshold_max) # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) if flooded_polygon is None: message = tr('''There are no objects in the hazard layer with "value">'%s'. Please check the value or use other extent.''' % (threshold_min, )) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(extent) line_layer = clip_by_polygon( E, extent_as_polygon ) # Find inundated roads, mark them line_layer = split_by_polygon( line_layer, flooded_polygon, request, mark_value=(target_field, 1)) # Find inundated roads, mark them # line_layer = split_by_polygon( # E, # flooded_polygon, # request, # mark_value=(target_field, 1)) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.extent[0], self.extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(E.crs(), output_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if not road_type in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = [ question, TableRow([ tr('Road Type'), tr('Flooded in the threshold (m)'), tr('Total (m)')], header=True), TableRow([ tr('All'), int(flooded_len), int(road_len)])] table_body.append(TableRow( tr('Breakdown by road type'), header=True)) for t, v in roads_by_type.iteritems(): table_body.append( TableRow([t, int(v['flooded']), int(v['total'])]) ) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector(data=line_layer, name=tr('Flooded roads'), keywords={'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) return line_layer
def prepareSelfClosingBays(self, inputLine, traceLayer): """ identify bays that loop 2 ---------------------3 | | | | | | 4 ------------------- 1 0 """ tolerance = 0.5 intesectingPts = [] newLine = [] geomShapeID = 1 lineA = self.removeDanglingEndFromLoop(inputLine) # check proximity of end points if not QgsGeometry.fromPointXY(lineA[0]).distance(QgsGeometry.fromPointXY(lineA[len(lineA)-1])) < tolerance: return inputLine, geomShapeID TOMsMessageLog.logMessage("In prepareSelfClosingBays: we have a loop ... ", level=Qgis.Warning) line = lineA # we have a loop - find the intersection points on the trace line # get a bounding box of the line lineGeom = QgsGeometry.fromPolylineXY(line) bbox = lineGeom.boundingBox() request = QgsFeatureRequest() request.setFilterRect(bbox) request.setFlags(QgsFeatureRequest.ExactIntersect) # Loop through all features in the layer to find the intersecting feature(s) for f in traceLayer.getFeatures(request): TOMsMessageLog.logMessage("In prepareSelfClosingBays: traceLayer id: {}".format(f.id()), level=Qgis.Info) # now check to see whether there is an intersection with this feature on the traceLayer and the lineGeom intesectingPtsGeom = f.geometry().intersection(lineGeom) if intesectingPtsGeom: # add them to a list of pts TOMsMessageLog.logMessage( "In prepareSelfClosingBays: intersecting geom: {}".format( intesectingPtsGeom.asWkt()), level = Qgis.Info) for part in intesectingPtsGeom.parts(): TOMsMessageLog.logMessage( "In prepareSelfClosingBays: intersecting part: {}".format( part.asWkt()), level=Qgis.Info) intesectingPts.append(QgsPointXY(part)) TOMsMessageLog.logMessage("In prepareSelfClosingBays: nr of pts intersecting tracelayer: {}".format(len(intesectingPts)), level=Qgis.Info) if len(intesectingPts) == 2: # if 2, generate list of points between the two and return. # work out distance along line for each intersection point startDistance = float("inf") endDistance = 0.0 for ptXY in intesectingPts: # ptXY = QgsPointXY(pt) TOMsMessageLog.logMessage( "In prepareSelfClosingBays: considering intersection point: {}".format(ptXY.asWkt()), level=Qgis.Info) vertexCoord, vertex, prevVertex, nextVertex, distSquared = lineGeom.closestVertex(ptXY) TOMsMessageLog.logMessage( "In prepareSelfClosingBays: considering closestVertex: {}; prevVertex: {}; nextVertex: {}".format(vertex, prevVertex, nextVertex), level=Qgis.Info) vertex, prevVertex, nextVertex = self.ensureVerticesNumberFromLineStart(len(line)-1, vertex, prevVertex, nextVertex) distToVertex = lineGeom.distanceToVertex(vertex) distToPt, prevVertex = self.getDistanceToPoint(ptXY, line) #dist = math.sqrt(distSquared) TOMsMessageLog.logMessage( "In prepareSelfClosingBays: considering closestVertex: {}; distToVertex: {}; distToPt: {}".format(vertex, distToVertex, distToPt), level=Qgis.Info) if distToPt < startDistance: startDistance = distToPt startPt = ptXY if distToVertex <= distToPt: startVertex = nextVertex else: startVertex = vertex TOMsMessageLog.logMessage("In prepareSelfClosingBays: new startVertex: {}".format(startVertex), level=Qgis.Info) if vertex == 0: distToVertex = lineGeom.length() TOMsMessageLog.logMessage("In prepareSelfClosingBays: with vertex = 0, changing distToVertex to {}".format(distToVertex), level=Qgis.Info) if distToPt > endDistance: endPt = ptXY endDistance = distToPt if distToPt > distToVertex: endVertex = vertex else: endVertex = prevVertex TOMsMessageLog.logMessage("In prepareSelfClosingBays: new endVertex: {}".format(endVertex), level=Qgis.Info) # break TOMsMessageLog.logMessage( "In prepareSelfClosingBays:two intersecting pts: startVertex: {}; endVertex: {}".format(startVertex, endVertex), level=Qgis.Info) # move along line ... #if not QgsGeometry.fromPointXY(QgsPointXY(startPt)).equals(QgsGeometry.fromPointXY(QgsPointXY(lineGeom.vertexAt(startVertex)))): # # add start pt newLine.append(startPt) for i in range(startVertex, endVertex + 1, 1): newLine.append(line[i]) #if not QgsGeometry.fromPointXY(QgsPointXY(endPt)).equals(QgsGeometry.fromPointXY(QgsPointXY(lineGeom.vertexAt(endVertex)))): # # add end pt newLine.append(endPt) geomShapeID = 2 # half-on/half-off bay elif len(intesectingPts) == 0: # check whether or not shape is inside or outside the road casement. # move around shape and include any points that are within tolerance of the traceLayer newLine = inputLine # return the original geometry #geomShapeID = 3 # on pavement bay else: newLine = inputLine, geomShapeID # return the original geometry TOMsMessageLog.logMessage("In prepareSelfClosingBays: len newLine {}".format(len(newLine)), level=Qgis.Info) return newLine, geomShapeID
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation ranges = ranges_according_thresholds(low_max, medium_max, high_max) index, flood_cells_map = _raster_to_vector_cells( small_raster, ranges, self.exposure.layer.crs()) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) # Roads breakdown self.init_report_var(self.hazard_classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() affected = attributes[target_field_index] if isinstance(affected, QPyNullVariant): continue else: hazard_zone = self.hazard_classes[affected] usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False num_classes = len(self.hazard_classes) if attributes[target_field_index] in range(num_classes): affected = True self.classify_feature(hazard_zone, usage, length, affected) self.reorder_dictionaries() style_classes = [ # FIXME 0 - 0.1 dict( label=self.hazard_classes[0] + ': 0m', value=0, colour='#00FF00', transparency=0, size=1 ), dict( label=self.hazard_classes[1] + ': >0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1 ), dict( label=self.hazard_classes[2] + ': %.1f - %.1f m' % ( low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1 ), dict( label=self.hazard_classes[3] + ': %.1f - %.1f m' % ( medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1 ), dict( label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1 ), ] style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def canvasReleaseEvent(self, event): """Slot called when the mouse button is released on the canvas. :param event: Canvas event containing position of click, which button was clicked etc. """ if not event.button() == Qt.LeftButton: return def progress_callback(current, maximum, message=None): """GUI based callback implementation for showing progress. :param current: Current progress. :type current: int :param maximum: Maximum range (point at which task is complete. :type maximum: int :param message: Optional message to display in the progress bar :type message: str, QString """ if message is not None: self.message_bar.setText(message) if self.progress_bar is not None: self.progress_bar.setMaximum(maximum) self.progress_bar.setValue(current) self.iface.messageBar().pushMessage( self.tr('SG Downloader.'), self.tr('Preparing for download'), level=QgsMessageBar.INFO) # No need to check that it is a valid, polygon layer # as the QAction for this map tool already does that layer = self.canvas.currentLayer() place = self.toMapCoordinates(event.pos()) rectangle = point_to_rectangle(place) request = QgsFeatureRequest(QgsFeatureRequest.FilterRect) # Ensure only those features really intersecting the rect are returned request.setFlags(QgsFeatureRequest.ExactIntersect) request.setFilterRect(rectangle) polygons = layer.getFeatures(request) feature = QgsFeature() fetch_list = [] all_fields = layer.pendingFields() text_fields = [] # Ignore any columns that don't contain text data for field in all_fields: if field.typeName() == 'TEXT': text_fields.append(field) self.setup_messagebar() sg_field = None while polygons.nextFeature(feature): geom = feature.geometry() attributes = feature.attributes() matched = False sg_code = None if sg_field is None: for field in text_fields: value = str(feature[field.name()]) if not is_valid_sg_code(value): continue sg_field = field.name() fetch_list.append(value) else: # We already know which column has SG codes value = str(feature[sg_field]) fetch_list.append(value) if len(fetch_list) == 0: self.iface.messageBar().pushMessage( self.tr('SG Downloader.'), self.tr('No parcels found with a valid 21 Digit code'), level=QgsMessageBar.WARNING, duration=10) return province = province_for_point(place, self.provinces_layer) result = '' output_path = diagram_directory() for sg_code in fetch_list: result += download_sg_diagram( sg_code, province, output_path, progress_callback) log = file('sg_downloader.log', 'a') log.write(result) log.close()
def run(self, layers): """Experimental impact function for flood polygons on roads. :param layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of roads """ target_field = self.parameters['target_field'] road_type_field = self.parameters['road_type_field'] affected_field = self.parameters['affected_field'] affected_value = self.parameters['affected_value'] # Extract data hazard = get_hazard_layer(layers) # Flood exposure = get_exposure_layer(layers) # Roads question = get_question(hazard.get_name(), exposure.get_name(), self) hazard = hazard.get_layer() hazard_provider = hazard.dataProvider() affected_field_index = hazard_provider.fieldNameIndex(affected_field) #see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) #raise GetDataError(message) LOGGER.info('Affected field: %s' % affected_field) LOGGER.info('Affected field index: %s' % affected_field_index) exposure = exposure.get_layer() # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) # Split line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) if affected_field_index != -1: affected_field_type = hazard_provider.fields()[ affected_field_index].typeName() if affected_field_type in ['Real', 'Integer']: affected_value = float(affected_value) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = hazard.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: if attributes[affected_field_index] != affected_value: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( '''There are no objects in the hazard layer with "Affected value"='%s'. Please check the value or use a different extent.''' % (affected_value, )) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(extent) line_layer = clip_by_polygon(exposure, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon( line_layer, hazard_poly, request, mark_value=(target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.extent[0], self.extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(exposure.crs(), destination_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) target_field_index = line_layer.fieldNameIndex(target_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if not road_type in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = [ question, TableRow( [tr('Road Type'), tr('Temporarily closed (m)'), tr('Total (m)')], header=True), TableRow([tr('All'), int(flooded_len), int(road_len)]), TableRow(tr('Breakdown by road type'), header=True)] for road_type, value in roads_by_type.iteritems(): table_body.append( TableRow([ road_type, int(value['flooded']), int(value['total'])]) ) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector( data=line_layer, name=tr('Flooded roads'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) return line_layer
def build(self, build_blocks=False, cancelSignal=None): if self.layer.mapLayer is None: return vlayer = VectorLayer(self.settings, self.layer, self.materialManager, self.modelManager) if vlayer.objectType is None: logMessage("Object type not found") return self.logMessage("Object type is {}.".format(vlayer.objectType.name)) self.vlayer = vlayer be = self.settings.baseExtent() p = self.layer.properties # feature request request = QgsFeatureRequest() if p.get("radioButton_IntersectingFeatures", False): request.setFilterRect( vlayer.transform.transformBoundingBox( be.boundingBox(), QgsCoordinateTransform.ReverseTransform)) # geometry for clipping if p.get("checkBox_Clip" ) and vlayer.objectType != ObjectType.Polygon: self.clipExtent = be.clone().scale( 0.9999 ) # clip to slightly smaller extent than map canvas extent self.features = [] data = {} # materials/models if vlayer.objectType != ObjectType.ModelFile: for feat in vlayer.features(request): feat.material = vlayer.objectType.material( self.settings, vlayer, feat) self.features.append(feat) data["materials"] = self.materialManager.buildAll( self.pathRoot, self.urlRoot, base64=self.settings.base64) else: for feat in vlayer.features(request): feat.model = vlayer.objectType.model(self.settings, vlayer, feat) self.features.append(feat) data["models"] = self.modelManager.build( self.pathRoot is not None, base64=self.settings.base64) self.logMessage( "This layer has reference to 3D model file(s). If there are relevant files, you need to copy them to data directory for this export." ) if build_blocks: self._startBuildBlocks(cancelSignal) nf = 0 blocks = [] for block in self.blocks(): if self.canceled: break b = block.build() nf += b["featureCount"] blocks.append(b) self._endBuildBlocks(cancelSignal) nb = len(blocks) if nb > 1: self.logMessage( "{} features were splitted into {} parts.".format(nf, nb)) else: self.logMessage("{} feature{}.".format(nf, "s" if nf > 1 else "")) data["blocks"] = blocks d = { "type": "layer", "id": self.layer.jsLayerId, "properties": self.layerProperties(), "data": data } if self.canceled: return None if DEBUG_MODE: d["PROPERTIES"] = p return d
def run(self, layers): """Experimental impact function. Input layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of roads """ target_field = self.parameters['target_field'] building_type_field = self.parameters['building_type_field'] affected_field = self.parameters['affected_field'] affected_value = self.parameters['affected_value'] # Extract data H = get_hazard_layer(layers) # Flood E = get_exposure_layer(layers) # Roads question = get_question(H.get_name(), E.get_name(), self) H = H.get_layer() h_provider = H.dataProvider() affected_field_index = h_provider.fieldNameIndex(affected_field) if affected_field_index == -1: message = tr('''Parameter "Affected Field"(='%s') is not present in the attribute table of the hazard layer.''' % (affected_field, )) raise GetDataError(message) E = E.get_layer() srs = E.crs().toWkt() e_provider = E.dataProvider() fields = e_provider.fields() # If target_field does not exist, add it: if fields.indexFromName(target_field) == -1: e_provider.addAttributes([QgsField(target_field, QVariant.Int)]) target_field_index = e_provider.fieldNameIndex(target_field) fields = e_provider.fields() # Create layer for 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(fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(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) affected_field_type = \ h_provider.fields()[affected_field_index].typeName() if affected_field_type in ['Real', 'Integer']: affected_value = float(affected_value) h_data = H.getFeatures(request) hazard_poly = None for mpolygon in h_data: attrs = mpolygon.attributes() if attrs[affected_field_index] != affected_value: continue if hazard_poly is None: hazard_poly = QgsGeometry(mpolygon.geometry()) else: # Make geometry union of inundated polygons # But some mpolygon.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(mpolygon.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass if hazard_poly is None: message = tr('''There are no objects in the hazard layer with "Affected value"='%s'. Please check the value or use other extent.''' % (affected_value, )) raise GetDataError(message) e_data = E.getFeatures(request) for feat in e_data: building_geom = feat.geometry() attrs = feat.attributes() l_feat = QgsFeature() l_feat.setGeometry(building_geom) l_feat.setAttributes(attrs) if hazard_poly.intersects(building_geom): l_feat.setAttribute(target_field_index, 1) else: l_feat.setAttribute(target_field_index, 0) (_, __) = \ building_layer.dataProvider().addFeatures([l_feat]) building_layer.updateExtents() # Generate simple impact report building_count = flooded_count = 0 # Count of buildings buildings_by_type = dict() # Length of flooded roads by types buildings_data = building_layer.getFeatures() building_type_field_index = \ building_layer.fieldNameIndex(building_type_field) for building in buildings_data: building_count += 1 attrs = building.attributes() building_type = attrs[building_type_field_index] if building_type in [None, 'NULL', 'null', 'Null' ]: building_type = 'Unknown type' if not building_type in buildings_by_type: buildings_by_type[building_type] = {'flooded': 0, 'total': 0} buildings_by_type[building_type]['total'] += 1 if attrs[target_field_index] == 1: flooded_count += 1 buildings_by_type[building_type]['flooded'] += 1 table_body = [question, TableRow([tr('Building Type'), tr('Flooded'), tr('Total')], header=True), TableRow([tr('All'), int(flooded_count), int(building_count)])] table_body.append(TableRow(tr('Breakdown by building type'), header=True)) for t, v in buildings_by_type.iteritems(): table_body.append( TableRow([t, int(v['flooded']), int(v['total'])]) ) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Buildings inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. building_layer = Vector(data=building_layer, name=tr('Flooded buildings'), keywords={'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) return building_layer
def run(self, layers=None): """Experimental impact function. Input layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of buildings """ self.validate() self.prepare(layers) # Set the target field in impact layer target_field = 'INUNDATED' # Get the IF parameters building_type_field = self.parameters['building_type_field'] affected_field = self.parameters['affected_field'] affected_value = self.parameters['affected_value'] # Extract data hazard_layer = self.hazard # Flood exposure_layer = self.exposure # Roads # Prepare Hazard Layer hazard_layer = hazard_layer.get_layer() hazard_provider = hazard_layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex(affected_field) 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.') % affected_field raise GetDataError(message) # Prepare Exposure Layer exposure_layer = exposure_layer.get_layer() srs = exposure_layer.crs().toWkt() exposure_provider = exposure_layer.dataProvider() exposure_fields = exposure_provider.fields() # Check building_type_field exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( building_type_field) 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.') % building_type_field raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(target_field) == -1: exposure_provider.addAttributes( [QgsField(target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex(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), 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) affected_field_type = hazard_provider.fields( )[affected_field_index].typeName() if affected_field_type in ['Real', 'Integer']: affected_value = float(affected_value) hazard_data = hazard_layer.getFeatures(request) hazard_poly = None for feature in hazard_data: record = feature.attributes() if record[affected_field_index] != affected_value: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some polygon.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass if hazard_poly is None: message = tr('There are no objects in the hazard layer with %s ' 'value=%s. Please check your data or use another ' 'attribute.') % (affected_field, affected_value) raise GetDataError(message) exposure_data = exposure_layer.getFeatures(request) for feature in exposure_data: building_geom = feature.geometry() record = feature.attributes() l_feat = QgsFeature() l_feat.setGeometry(building_geom) l_feat.setAttributes(record) if hazard_poly.intersects(building_geom): l_feat.setAttribute(target_field_index, 1) else: l_feat.setAttribute(target_field_index, 0) (_, __) = building_layer.dataProvider().addFeatures([l_feat]) 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( building_type_field) 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() map_title = tr('Buildings inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. building_layer = Vector(data=building_layer, name=tr('Flooded buildings'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': 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 run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Align raster extent and viewport # assuming they are both in the same projection raster_extent = self.hazard.layer.dataProvider().extent() clip_xmin = raster_extent.xMinimum() # clip_xmax = raster_extent.xMaximum() clip_ymin = raster_extent.yMinimum() # clip_ymax = raster_extent.yMaximum() if viewport_extent[0] > clip_xmin: clip_xmin = viewport_extent[0] if viewport_extent[1] > clip_ymin: clip_ymin = viewport_extent[1] height = ((viewport_extent[3] - viewport_extent[1]) / self.hazard.layer.rasterUnitsPerPixelY()) height = int(height) width = ((viewport_extent[2] - viewport_extent[0]) / self.hazard.layer.rasterUnitsPerPixelX()) width = int(width) raster_extent = self.hazard.layer.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / self.hazard.layer.width() x = xmin for i in range(self.hazard.layer.width()): if abs(x - clip_xmin) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / self.hazard.layer.height() y = ymin for i in range(self.hazard.layer.width()): if abs(y - clip_ymin) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip hazard raster small_raster = clip_raster( self.hazard.layer, width, height, QgsRectangle(*clip_extent)) # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation ranges = OrderedDict() ranges[0] = [0.0, 0.0] ranges[1] = [0.0, low_max] ranges[2] = [low_max, medium_max] ranges[3] = [medium_max, high_max] ranges[4] = [high_max, None] index, flood_cells_map = _raster_to_vector_cells( small_raster, ranges, self.exposure.layer.crs()) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) """ if len(low_max_flood_cells_map) == 0 and \ len(medium_max_flood_cells_map) == 0 and \ len(high_max_flood_cells_map) == 0 and \ len(high_min_flood_cells_map) == 0: message = tr( 'There are no objects in the hazard layer with "value" > 0. ' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) """ # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) # Roads breakdown self.road_lengths = OrderedDict() self.affected_road_categories = self.hazard_classes # Impacted roads breakdown self.affected_road_lengths = OrderedDict([ (self.hazard_classes[0], {}), (self.hazard_classes[1], {}), (self.hazard_classes[2], {}), (self.hazard_classes[3], {}), (self.hazard_classes[4], {}), ]) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() affected = attributes[target_field_index] hazard_zone = self.hazard_classes[affected] road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() if road_type not in self.road_lengths: self.road_lengths[road_type] = 0 if hazard_zone not in self.affected_road_lengths: self.affected_road_lengths[hazard_zone] = {} if road_type not in self.affected_road_lengths[hazard_zone]: self.affected_road_lengths[hazard_zone][road_type] = 0 self.road_lengths[road_type] += length num_classes = len(self.hazard_classes) if attributes[target_field_index] in range(num_classes): self.affected_road_lengths[hazard_zone][road_type] += length impact_summary = self.html_report() # For printing map purpose map_title = tr('Roads inundated') legend_title = tr('Road inundated status') style_classes = [ # FIXME 0 - 0.1 dict( label=self.hazard_classes[0] + ': 0m', value=0, colour='#00FF00', transparency=0, size=1 ), dict( label=self.hazard_classes[1] + ': <0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1 ), dict( label=self.hazard_classes[2] + ': %.1f - %.1f m' % ( low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1 ), dict( label=self.hazard_classes[3] + ': %.1f - %.1f m' % ( medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1 ), dict( label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1 ), ] style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') extra_keywords = { 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': target_field } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector( data=line_layer, name=tr('Flooded roads'), keywords=impact_layer_keywords, style_info=style_info) self._impact = line_layer return line_layer
def identify(self, params): self.check_required_params(params) feature_collections = [] with change_directory(self.project_root): crs = QgsCoordinateReferenceSystem() crs.createFromSrid(params.get('srs')) search_box = self._calcSearchBox( params.get('bbox'), params.get('image_size')[0], params.get('image_size')[1], params.get('click_point')[0], params.get('click_point')[1] ) # initialize mapRenderer and a rendering context in order to be # to check if a feature will actually be rendered # we don't want to return features that are not visible img = QImage(QSize( settings.SUNLUMO_GFI_BUFFER*2, settings.SUNLUMO_GFI_BUFFER*2), QImage.Format_ARGB32_Premultiplied ) dpm = 1 / 0.00028 img.setDotsPerMeterX(dpm) img.setDotsPerMeterY(dpm) mapRenderer = QgsMapRenderer() mapRenderer.clearLayerCoordinateTransforms() mapRenderer.setOutputSize(QSize( settings.SUNLUMO_GFI_BUFFER*2, settings.SUNLUMO_GFI_BUFFER*2), img.logicalDpiX() ) mapRenderer.setDestinationCrs(crs) mapRenderer.setProjectionsEnabled(True) mapUnits = crs.mapUnits() mapRenderer.setMapUnits(mapUnits) mapExtent = QgsRectangle(*search_box) mapRenderer.setExtent(mapExtent) renderContext = QgsRenderContext() renderContext.setExtent(mapRenderer.extent()) renderContext.setRasterScaleFactor(1.0) renderContext.setMapToPixel(mapRenderer.coordinateTransform()) renderContext.setRendererScale(mapRenderer.scale()) renderContext.setScaleFactor(mapRenderer.outputDpi() / 25.4) renderContext.setPainter(None) qfr = QgsFeatureRequest() search_rectangle = QgsRectangle(*search_box) qfr.setFilterRect(search_rectangle) for q_layer in params.get('query_layers'): layer = self.layerRegistry.mapLayer(q_layer) if layer.type() == QgsMapLayer.RasterLayer: # skip raster layer processing continue # update layer fields (expressions, calculated, joined) layer.updateFields() scaleCalc = QgsScaleCalculator( (img.logicalDpiX() + img.logicalDpiY()) / 2, mapRenderer.destinationCrs().mapUnits() ) scaleDenom = scaleCalc.calculate(mapExtent, img.width()) # skip the layer if it's not visible at the current map scale if layer.hasScaleBasedVisibility(): if not(layer.minimumScale() < scaleDenom < layer.maximumScale()): continue # check if features actually intersect search rectangle intersected_features = self._intersectedFeatures( layer.getFeatures(qfr), search_rectangle ) # visible features generator visible_features = self._visibleFeatures( layer, renderContext, intersected_features ) layer_features = [featureToGeoJSON( feature.id(), feature.geometry(), self._collectAttributes(layer, feature) ) for feature in visible_features ] feature_collections.append(layer_features) return writeGeoJSON(chain(*feature_collections))
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 fetch_values_from_layer(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements """ (Re)fetches plot values from the source layer. """ # Note: we keep things nice and efficient and only iterate a single time over the layer! if not self.context_generator: context = QgsExpressionContext() context.appendScopes( QgsExpressionContextUtils.globalProjectLayerScopes( self.source_layer)) else: context = self.context_generator.createExpressionContext() # add a new scope corresponding to the source layer -- this will potentially overwrite any other # layer scopes which may be present in the context (e.g. from atlas layers), but we need to ensure # that source layer fields and attributes are present in the context context.appendScope( self.source_layer.createExpressionContextScope()) self.settings.data_defined_properties.prepare(context) self.fetch_layout_properties(context) def add_source_field_or_expression(field_or_expression): field_index = self.source_layer.fields().lookupField( field_or_expression) if field_index == -1: expression = QgsExpression(field_or_expression) if not expression.hasParserError(): expression.prepare(context) return expression, expression.needsGeometry( ), expression.referencedColumns() return None, False, {field_or_expression} x_expression, x_needs_geom, x_attrs = add_source_field_or_expression(self.settings.properties['x_name']) if \ self.settings.properties[ 'x_name'] else (None, False, set()) y_expression, y_needs_geom, y_attrs = add_source_field_or_expression(self.settings.properties['y_name']) if \ self.settings.properties[ 'y_name'] else (None, False, set()) z_expression, z_needs_geom, z_attrs = add_source_field_or_expression(self.settings.properties['z_name']) if \ self.settings.properties[ 'z_name'] else (None, False, set()) additional_info_expression, additional_needs_geom, additional_attrs = add_source_field_or_expression( self.settings.layout['additional_info_expression'] ) if self.settings.layout['additional_info_expression'] else (None, False, set()) attrs = set().union( self.settings.data_defined_properties.referencedFields(), x_attrs, y_attrs, z_attrs, additional_attrs) request = QgsFeatureRequest() if self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).isActive(): expression = self.settings.data_defined_properties.property( PlotSettings.PROPERTY_FILTER).asExpression() request.setFilterExpression(expression) request.setExpressionContext(context) request.setSubsetOfAttributes(attrs, self.source_layer.fields()) if not x_needs_geom and not y_needs_geom and not z_needs_geom and not additional_needs_geom and not self.settings.data_defined_properties.hasActiveProperties( ): request.setFlags(QgsFeatureRequest.NoGeometry) visible_geom_engine = None if self.settings.properties.get( 'visible_features_only', False) and self.visible_region is not None: ct = QgsCoordinateTransform( self.visible_region.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox(self.visible_region) request.setFilterRect(rect) except QgsCsException: pass elif self.settings.properties.get( 'visible_features_only', False) and self.polygon_filter is not None: ct = QgsCoordinateTransform( self.polygon_filter.crs(), self.source_layer.crs(), QgsProject.instance().transformContext()) try: rect = ct.transformBoundingBox( self.polygon_filter.geometry.boundingBox()) request.setFilterRect(rect) g = self.polygon_filter.geometry g.transform(ct) visible_geom_engine = QgsGeometry.createGeometryEngine( g.constGet()) visible_geom_engine.prepareGeometry() except QgsCsException: pass if self.selected_features_only: it = self.source_layer.getSelectedFeatures(request) else: it = self.source_layer.getFeatures(request) # Some plot types don't draw individual glyphs for each feature, but aggregate them instead. # In that case it doesn't make sense to evaluate expressions for settings like marker size or color for each # feature. Instead, the evaluation should be executed only once for these settings. aggregating = self.settings.plot_type in ['box', 'histogram'] executed = False xx = [] yy = [] zz = [] additional_hover_text = [] marker_sizes = [] colors = [] stroke_colors = [] stroke_widths = [] for f in it: if visible_geom_engine and not visible_geom_engine.intersects( f.geometry().constGet()): continue self.settings.feature_ids.append(f.id()) context.setFeature(f) x = None if x_expression: x = x_expression.evaluate(context) if x == NULL or x is None: continue elif self.settings.properties['x_name']: x = f[self.settings.properties['x_name']] if x == NULL or x is None: continue y = None if y_expression: y = y_expression.evaluate(context) if y == NULL or y is None: continue elif self.settings.properties['y_name']: y = f[self.settings.properties['y_name']] if y == NULL or y is None: continue z = None if z_expression: z = z_expression.evaluate(context) if z == NULL or z is None: continue elif self.settings.properties['z_name']: z = f[self.settings.properties['z_name']] if z == NULL or z is None: continue if additional_info_expression: additional_hover_text.append( additional_info_expression.evaluate(context)) elif self.settings.layout['additional_info_expression']: additional_hover_text.append( f[self.settings.layout['additional_info_expression']]) if x is not None: xx.append(x) if y is not None: yy.append(y) if z is not None: zz.append(z) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_MARKER_SIZE): default_value = self.settings.properties['marker_size'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_MARKER_SIZE, context, default_value) marker_sizes.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_WIDTH): default_value = self.settings.properties['marker_width'] context.setOriginalValueVariable(default_value) value, _ = self.settings.data_defined_properties.valueAsDouble( PlotSettings.PROPERTY_STROKE_WIDTH, context, default_value) stroke_widths.append(value) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['in_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead colors.append(default_value.name()) if self.settings.data_defined_properties.isActive( PlotSettings.PROPERTY_STROKE_COLOR) and (not aggregating or not executed): default_value = QColor(self.settings.properties['out_color']) value, conversion_success = self.settings.data_defined_properties.valueAsColor( PlotSettings.PROPERTY_STROKE_COLOR, context, default_value) if conversion_success: # We were given a valid color specification, use that color stroke_colors.append(value.name()) else: try: # Attempt to interpret the value as a list of color specifications value_list = self.settings.data_defined_properties.value( PlotSettings.PROPERTY_STROKE_COLOR, context) color_list = [ QgsSymbolLayerUtils.decodeColor(item).name() for item in value_list ] stroke_colors.extend(color_list) except TypeError: # Not a list of color specifications, use the default color instead stroke_colors.append(default_value.name()) executed = True self.settings.additional_hover_text = additional_hover_text self.settings.x = xx self.settings.y = yy self.settings.z = zz if marker_sizes: self.settings.data_defined_marker_sizes = marker_sizes if colors: self.settings.data_defined_colors = colors if stroke_colors: self.settings.data_defined_stroke_colors = stroke_colors if stroke_widths: self.settings.data_defined_stroke_widths = stroke_widths
def run(self, layers): """Experimental impact function. :param layers: List of layers expected to contain at least: H: Polygon layer of inundation areas E: Vector layer of roads :type layers: list :returns: A new line layer with inundated roads marked. :type: safe_layer """ target_field = self.parameters['target_field'] road_type_field = self.parameters['road_type_field'] threshold_min = self.parameters['min threshold [m]'] threshold_max = self.parameters['max threshold [m]'] if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater then the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # Extract data H = get_hazard_layer(layers) # Flood E = get_exposure_layer(layers) # Roads question = get_question( H.get_name(), E.get_name(), self) H = H.get_layer() E = E.get_layer() #reproject self.extent to the hazard projection hazard_crs = H.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.extent), geo_crs, hazard_crs) #Align raster extent and viewport #assuming they are both in the same projection raster_extent = H.dataProvider().extent() clip_xmin = raster_extent.xMinimum() # clip_xmax = raster_extent.xMaximum() clip_ymin = raster_extent.yMinimum() # clip_ymax = raster_extent.yMaximum() if viewport_extent[0] > clip_xmin: clip_xmin = viewport_extent[0] if viewport_extent[1] > clip_ymin: clip_ymin = viewport_extent[1] # TODO: Why have these two clauses when they are not used? # Commenting out for now. # if viewport_extent[2] < clip_xmax: # clip_xmax = viewport_extent[2] # if viewport_extent[3] < clip_ymax: # clip_ymax = viewport_extent[3] height = ((viewport_extent[3] - viewport_extent[1]) / H.rasterUnitsPerPixelY()) height = int(height) width = ((viewport_extent[2] - viewport_extent[0]) / H.rasterUnitsPerPixelX()) width = int(width) raster_extent = H.dataProvider().extent() xmin = raster_extent.xMinimum() xmax = raster_extent.xMaximum() ymin = raster_extent.yMinimum() ymax = raster_extent.yMaximum() x_delta = (xmax - xmin) / H.width() x = xmin for i in range(H.width()): if abs(x - clip_xmin) < x_delta: # We have found the aligned raster boundary break x += x_delta _ = i y_delta = (ymax - ymin) / H.height() y = ymin for i in range(H.width()): if abs(y - clip_ymin) < y_delta: # We have found the aligned raster boundary break y += y_delta clip_extent = [x, y, x + width * x_delta, y + height * y_delta] # Clip and polygonize small_raster = clip_raster( H, width, height, QgsRectangle(*clip_extent)) (flooded_polygon_inside, flooded_polygon_outside) = polygonize_gdal( small_raster, threshold_min, threshold_max) # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) if flooded_polygon_inside is None: message = tr( 'There are no objects in the hazard layer with "value">%s.' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) #reproject the flood polygons to exposure projection exposure_crs = E.crs() exposure_authid = exposure_crs.authid() if hazard_authid != exposure_authid: flooded_polygon_inside = reproject_vector_layer( flooded_polygon_inside, E.crs()) flooded_polygon_outside = reproject_vector_layer( flooded_polygon_outside, E.crs()) # Clip exposure by the extent #extent_as_polygon = QgsGeometry().fromRect(extent) #no need to clip since It is using a bbox request #line_layer = clip_by_polygon( # E, # extent_as_polygon #) # Find inundated roads, mark them line_layer = split_by_polygon_in_out( E, flooded_polygon_inside, flooded_polygon_outside, target_field, 1, request) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.extent[0], self.extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(E.crs(), output_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if not road_type in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = [ question, TableRow([ tr('Road Type'), tr('Flooded in the threshold (m)'), tr('Total (m)')], header=True), TableRow([tr('All'), int(flooded_len), int(road_len)]) ] table_body.append(TableRow( tr('Breakdown by road type'), header=True)) for t, v in roads_by_type.iteritems(): table_body.append( TableRow([t, int(v['flooded']), int(v['total'])]) ) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector( data=line_layer, name=tr('Flooded roads'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) return line_layer
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. source = self.parameterAsSource( parameters, self.INPUT, context ) compare = self.parameterAsSource( parameters, self.COMPARISON, context ) # If source was not found, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSourceError method to return a standard # helper text for when a source cannot be evaluated if source is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT)) if compare is None: raise QgsProcessingException(self.invalidSourceError(parameters, self.COMPARISON)) new_fields = source.fields() new_fields.append(QgsField('overlap', QVariant.Double)) (sink, dest_id) = self.parameterAsSink( parameters, self.OUTPUT, context, new_fields, source.wkbType(), source.sourceCrs() ) # Send some information to the user feedback.pushInfo('CRS is {}'.format(source.sourceCrs().authid())) # If sink was not created, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSinkError method to return a standard # helper text for when a sink cannot be evaluated if sink is None: raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT)) # Compute the number of steps to display within the progress bar and # get features from source total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() for current, feature in enumerate(features): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break #narrow down from compare layer by bounding box req = QgsFeatureRequest() compare_features = compare.getFeatures(req.setFilterRect(feature.geometry().boundingBox())) overlap = 0 for c_feature in compare_features: compare_geom = c_feature.geometry() intersect = feature.geometry().intersection(compare_geom) overlap = overlap + intersect.area()/feature.geometry().area() # Add a feature in the sink new_feat = QgsFeature() new_feat.setGeometry(feature.geometry()) new_feat.setFields(new_fields) new_feat['overlap'] = overlap new_feat['id'] = feature['id'] sink.addFeature(new_feat, QgsFeatureSink.FastInsert) # Update the progress bar feedback.setProgress(int(current * total)) # To run another Processing algorithm as part of this algorithm, you can use # processing.run(...). Make sure you pass the current context and feedback # to processing.run to ensure that all temporary layer outputs are available # to the executed algorithm, and that the executed algorithm can send feedback # reports to the user (and correctly handle cancelation and progress reports!) if False: buffered_layer = processing.run("native:buffer", { 'INPUT': dest_id, 'DISTANCE': 1.5, 'SEGMENTS': 5, 'END_CAP_STYLE': 0, 'JOIN_STYLE': 0, 'MITER_LIMIT': 2, 'DISSOLVE': False, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)['OUTPUT'] # Return the results of the algorithm. In this case our only result is # the feature sink which contains the processed features, but some # algorithms may return multiple feature sinks, calculated numeric # statistics, etc. These should all be included in the returned # dictionary, with keys matching the feature corresponding parameter # or output names. return {self.OUTPUT: dest_id}
def _clip_vector_layer( layer, extent, extra_keywords=None, explode_flag=True, hard_clip_flag=False, explode_attribute=None): """Clip a Hazard or Exposure layer to the extents provided. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS vector or raster layer :type layer: :param extent: Either an array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. or: A QgsGeometry of type polygon. **Polygon clipping is currently only supported for vector datasets.** :type extent: list(float, float, float, float) :param extra_keywords: Optional keywords dictionary to be added to output layer. :type extra_keywords: dict :param explode_flag: A bool specifying whether multipart features should be 'exploded' into singleparts. **This parameter is ignored for raster layer clipping.** :type explode_flag: bool :param hard_clip_flag: A bool specifying whether line and polygon features that extend beyond the extents should be clipped such that they are reduced in size to the part of the geometry that intersects the extent only. Default is False. **This parameter is ignored for raster layer clipping.** :type hard_clip_flag: bool :param explode_attribute: A str specifying to which attribute #1, #2 and so on will be added in case of explode_flag being true. The attribute is modified only if there are at least 2 parts. :type explode_attribute: str :returns: Clipped layer (placed in the system temp dir). The output layer will be reprojected to EPSG:4326 if needed. :rtype: QgsVectorLayer """ if not layer or not extent: myMessage = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(myMessage) if layer.type() != QgsMapLayer.VectorLayer: myMessage = tr('Expected a vector layer but received a %s.' % str(layer.type())) raise InvalidParameterError(myMessage) #myHandle, myFilename = tempfile.mkstemp('.sqlite', 'clip_', # temp_dir()) myHandle, myFilename = tempfile.mkstemp('.shp', 'clip_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(myHandle) os.remove(myFilename) # Get the clip extents in the layer's native CRS myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromSrid(4326) myXForm = QgsCoordinateTransform(myGeoCrs, layer.crs()) myAllowedClipTypes = [QGis.WKBPolygon, QGis.WKBPolygon25D] if type(extent) is list: myRect = QgsRectangle( extent[0], extent[1], extent[2], extent[3]) # noinspection PyCallByClass myClipPolygon = QgsGeometry.fromRect(myRect) elif (type(extent) is QgsGeometry and extent.wkbType in myAllowedClipTypes): myRect = extent.boundingBox().toRectF() myClipPolygon = extent else: raise InvalidClipGeometryError( tr( 'Clip geometry must be an extent or a single part' 'polygon based geometry.')) myProjectedExtent = myXForm.transformBoundingBox(myRect) # Get vector layer myProvider = layer.dataProvider() if myProvider is None: myMessage = tr('Could not obtain data provider from ' 'layer "%s"' % layer.source()) raise Exception(myMessage) # Get the layer field list, select by our extent then write to disk # .. todo:: FIXME - for different geometry types we should implement # different clipping behaviour e.g. reject polygons that # intersect the edge of the bbox. Tim myRequest = QgsFeatureRequest() if not myProjectedExtent.isEmpty(): myRequest.setFilterRect(myProjectedExtent) myRequest.setFlags(QgsFeatureRequest.ExactIntersect) myFieldList = myProvider.fields() myWriter = QgsVectorFileWriter( myFilename, 'UTF-8', myFieldList, layer.wkbType(), myGeoCrs, #'SQLite') # FIXME (Ole): This works but is far too slow 'ESRI Shapefile') if myWriter.hasError() != QgsVectorFileWriter.NoError: myMessage = tr('Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (myFilename, myWriter.hasError())) raise Exception(myMessage) # Reverse the coordinate xform now so that we can convert # geometries from layer crs to geocrs. myXForm = QgsCoordinateTransform(layer.crs(), myGeoCrs) # Retrieve every feature with its geometry and attributes myCount = 0 myHasMultipart = False for myFeature in myProvider.getFeatures(myRequest): myGeometry = myFeature.geometry() # Loop through the parts adding them to the output file # we write out single part features unless explode_flag is False if explode_flag: myGeometryList = explode_multipart_geometry(myGeometry) else: myGeometryList = [myGeometry] for myPartIndex, myPart in enumerate(myGeometryList): myPart.transform(myXForm) if hard_clip_flag: # Remove any dangling bits so only intersecting area is # kept. myPart = clip_geometry(myClipPolygon, myPart) if myPart is None: continue myFeature.setGeometry(myPart) # There are multiple parts and we want to show it in the # explode_attribute if myPartIndex > 0 and explode_attribute is not None: myHasMultipart = True myWriter.addFeature(myFeature) myCount += 1 del myWriter # Flush to disk if myCount < 1: myMessage = tr( 'No features fall within the clip extents. Try panning / zooming ' 'to an area containing data and then try to run your analysis ' 'again. If hazard and exposure data doesn\'t overlap at all, it ' 'is not possible to do an analysis. Another possibility is that ' 'the layers do overlap but because they may have different ' 'spatial references, they appear to be disjointed. If this is the ' 'case, try to turn on reproject on-the-fly in QGIS.') raise NoFeaturesInExtentError(myMessage) myKeywordIO = KeywordIO() if extra_keywords is None: extra_keywords = {} extra_keywords['had multipart polygon'] = myHasMultipart myKeywordIO.copy_keywords( layer, myFilename, extra_keywords=extra_keywords) myBaseName = '%s clipped' % layer.name() myLayer = QgsVectorLayer(myFilename, myBaseName, 'ogr') return myLayer
def run(self, layers=None): """Experimental impact function for flood polygons on roads. :param layers: List of layers expected to contain H: Polygon layer of inundation areas E: Vector layer of roads """ self.validate() self.prepare(layers) # Set the target field target_field = 'FLOODED' # Get the parameters from IF options road_type_field = self.parameters['road_type_field'] affected_field = self.parameters['affected_field'] affected_value = self.parameters['affected_value'] # Extract data hazard_layer = self.hazard # Flood exposure_layer = self.exposure # Roads hazard_layer = hazard_layer.get_layer() hazard_provider = hazard_layer.dataProvider() affected_field_index = hazard_provider.fieldNameIndex(affected_field) # see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) # raise GetDataError(message) LOGGER.info('Affected field: %s' % affected_field) LOGGER.info('Affected field index: %s' % affected_field_index) exposure_layer = exposure_layer.get_layer() # Filter geometry and data using the 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), hazard_layer.crs() ) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) if affected_field_index != -1: affected_field_type = hazard_provider.fields()[ affected_field_index].typeName() if affected_field_type in ['Real', 'Integer']: affected_value = float(affected_value) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = hazard_layer.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: if attributes[affected_field_index] != affected_value: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( 'There are no objects in the hazard layer with %s (Affected ' 'Field) = %s (Affected Value). Please check the value or use ' 'a different extent.' % (affected_field, affected_value)) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(requested_extent) line_layer = clip_by_polygon(exposure_layer, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon( line_layer, hazard_poly, request, mark_value=(target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( exposure_layer.crs(), destination_crs) road_len = flooded_len = 0 # Length of roads roads_by_type = dict() # Length of flooded roads by types roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_type_field) target_field_index = line_layer.fieldNameIndex(target_field) for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() road_len += length if road_type not in roads_by_type: roads_by_type[road_type] = {'flooded': 0, 'total': 0} roads_by_type[road_type]['total'] += length if attributes[target_field_index] == 1: flooded_len += length roads_by_type[road_type]['flooded'] += length table_body = self._tabulate( flooded_len, self.question, road_len, roads_by_type) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('Roads inundated') 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=target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it line_layer = Vector( data=line_layer, name=tr('Flooded roads'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'target_field': target_field}, style_info=style_info) self._impact = line_layer return line_layer
def run(self): """Run the impact function.""" # First fo any generic run work defined in the ABC. self.prepare() target_field = self.parameters['target_field'] building_type_field = self.parameters['building_type_field'] affected_value = self.parameters['affected_value'] crs = self.exposure.crs().toWkt() exposure_provider = self.exposure.dataProvider() fields = exposure_provider.fields() # If target_field does not exist, add it: if fields.indexFromName(target_field) == -1: exposure_provider.addAttributes( [QgsField(target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex(target_field) fields = exposure_provider.fields() # Create layer for store the lines from exposure and extent building_layer = QgsVectorLayer( 'Polygon?crs=' + crs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the extent extent = QgsRectangle(*self.extent) request = QgsFeatureRequest() request.setFilterRect(extent) # Split building_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark buildings as inundated (1) or not inundated (0) affected_field_type = self.hazard_provider.fields()[ self.affected_field_index].typeName() if affected_field_type in ['Real', 'Integer']: affected_value = float(affected_value) hazard_data = self.hazard.getFeatures(request) hazard_poly = None for multi_polygon in hazard_data: attributes = multi_polygon.attributes() if attributes[self.affected_field_index] != affected_value: continue if hazard_poly is None: hazard_poly = QgsGeometry(multi_polygon.geometry()) else: # Make geometry union of inundated polygons # But some multi_polygon.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(multi_polygon.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass if hazard_poly is None: message = tr( '''There are no objects in the hazard layer with "Affected value"='%s'. Please check the value or use other extent.''' % (affected_value, )) raise GetDataError(message) exposure_features = self.exposure.getFeatures(request) for feature in exposure_features: building_geometry = feature.geometry() attributes = feature.attributes() l_feat = QgsFeature() l_feat.setGeometry(building_geometry) l_feat.setAttributes(attributes) if hazard_poly.intersects(building_geometry): l_feat.setAttribute(target_field_index, 1) else: l_feat.setAttribute(target_field_index, 0) # Synctactic sugar to discard return values (_, __) = building_layer.dataProvider().addFeatures([l_feat]) building_layer.updateExtents() # Generate simple impact report building_count = flooded_count = 0 # Count of buildings buildings_by_type = dict() # Length of flooded roads by types buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( building_type_field) for building in buildings_data: building_count += 1 attributes = building.attributes() building_type = attributes[building_type_field_index] if building_type in [None, 'NULL', 'null', 'Null']: building_type = 'Unknown type' if not building_type in buildings_by_type: buildings_by_type[building_type] = {'flooded': 0, 'total': 0} buildings_by_type[building_type]['total'] += 1 if attributes[target_field_index] == 1: flooded_count += 1 buildings_by_type[building_type]['flooded'] += 1 self._tabulate(building_count, buildings_by_type, flooded_count) self._style(target_field) self._impact = building_layer
def qgisUpdateIDs(self, unlinktype): # create spatial index unlinksindex = lfh.createIndex(self.unlinks_layer) axialindex = lfh.createIndex(self.axial_layer) # add line id columns if necessary lfh.addFields(self.unlinks_layer, ['line1', 'line2'], [QVariant.Int, QVariant.Int]) line1 = lfh.getFieldIndex(self.unlinks_layer, 'line1') line2 = lfh.getFieldIndex(self.unlinks_layer, 'line2') update_id = False if self.user_id == '': features = self.unlinks_layer.getFeatures( QgsFeatureRequest().setSubsetOfAttributes([line1, line2])) else: update_id = not lfh.isValidIdField(self.unlinks_layer, self.user_id) field = lfh.getFieldIndex(self.unlinks_layer, self.user_id) features = self.unlinks_layer.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( [field, line1, line2])) # run unlinks tests chunk = 100.0 / float(self.unlinks_layer.featureCount()) steps = chunk / 3.0 progress = 0.0 for feature in features: geom = feature.geometry() # get intersection results if unlinktype == QgsWkbTypes.PointGeometry and self.threshold > 0.0: buff = geom.buffer(self.threshold, 4) else: buff = geom box = buff.boundingBox() request = QgsFeatureRequest() if axialindex: # should be faster to retrieve from index (if available) ints = axialindex.intersects(box) request.setFilterFids(ints) else: # can retrieve objects using bounding box request.setFilterRect(box) if self.axial_id == '': request.setSubsetOfAttributes([]) else: ax_field = lfh.getFieldIndex(self.axial_layer, self.axial_id) request.setSubsetOfAttributes([ax_field]) axiallines = self.axial_layer.getFeatures(request) progress += steps self.verificationProgress.emit(progress) # parse intersection results intersects = [] for line in axiallines: if self.axial_id == '': id_b = line.id() else: id_b = line.attribute(self.axial_id) if buff.intersects(line.geometry()): intersects.append(id_b) progress += steps self.verificationProgress.emit(progress) # update line ids in unlinks table attrs = {line1: NULL, line2: NULL} if len(intersects) == 1: attrs = {line1: intersects[0]} elif len(intersects) > 1: attrs = {line1: intersects[0], line2: intersects[1]} if update_id and field: attrs[field] = feature.id() self.unlinks_layer.dataProvider().changeAttributeValues( {feature.id(): attrs}) progress += steps self.verificationProgress.emit(progress) self.unlinks_layer.updateFields()