def test_split_by_polygon(self): """Test split_by_polygon work""" line_before = QgsVectorLayer( self.line_before + '.shp', 'test', 'ogr') expected_lines = QgsVectorLayer( self.line_after + '.shp', 'test', 'ogr') polygon_layer = QgsVectorLayer( self.polygon_base + '.shp', 'test', 'ogr') # Only one polygon is stored in the layer for feature in polygon_layer.getFeatures(): polygon = feature.geometry() split_lines = split_by_polygon( line_before, polygon, mark_value=('flooded', 1)) # Test the lines is not multipart for feature in split_lines.getFeatures(): self.assertFalse(feature.geometry().isMultipart()) self.assertEqual(expected_lines.featureCount(), split_lines.featureCount()) # Assert for every line from split_lines # we can find the same line for feature in split_lines.getFeatures(): found = False for expected in expected_lines.getFeatures(): if (feature.attributes() == expected.attributes()) and \ (feature.geometry().isGeosEqual(expected.geometry())): found = True break self.assertTrue(found) # Split by the extent (The result is the copy of the layer) line_before.updateExtents() # Expand extent to cover the lines (add epsilon to bounds) epsilon = 0.0001 # A small number extent = line_before.extent() new_extent = QgsRectangle( extent.xMinimum() - epsilon, extent.yMinimum() - epsilon, extent.xMaximum() + epsilon, extent.yMaximum() + epsilon ) new_extent = QgsGeometry().fromRect(new_extent) split_lines = split_by_polygon( line_before, new_extent) for feature in split_lines.getFeatures(): found = False for expected in line_before.getFeatures(): if (feature.attributes() == expected.attributes()) and \ (feature.geometry().isGeosEqual(expected.geometry())): found = True break self.assertTrue(found)
def test_split_by_polygon(self): """Test split_by_polygon work""" line_before = QgsVectorLayer(self.line_before + '.shp', 'test', 'ogr') expected_lines = QgsVectorLayer(self.line_after + '.shp', 'test', 'ogr') polygon_layer = QgsVectorLayer(self.polygon_base + '.shp', 'test', 'ogr') # Only one polygon is stored in the layer for feature in polygon_layer.getFeatures(): polygon = feature.geometry() split_lines = split_by_polygon(line_before, polygon, mark_value=('flooded', 1)) # Test the lines is not multipart for feature in split_lines.getFeatures(): self.assertFalse(feature.geometry().isMultipart()) self.assertEqual(expected_lines.featureCount(), split_lines.featureCount()) # Assert for every line from split_lines # we can find the same line for feature in split_lines.getFeatures(): found = False for expected in expected_lines.getFeatures(): if (feature.attributes() == expected.attributes()) and \ (feature.geometry().isGeosEqual(expected.geometry())): found = True break self.assertTrue(found) # Split by the extent (The result is the copy of the layer) line_before.updateExtents() # Expand extent to cover the lines (add epsilon to bounds) epsilon = 0.0001 # A small number extent = line_before.extent() new_extent = QgsRectangle(extent.xMinimum() - epsilon, extent.yMinimum() - epsilon, extent.xMaximum() + epsilon, extent.yMaximum() + epsilon) new_extent = QgsGeometry().fromRect(new_extent) split_lines = split_by_polygon(line_before, new_extent) for feature in split_lines.getFeatures(): found = False for expected in line_before.getFeatures(): if (feature.attributes() == expected.attributes()) and \ (feature.geometry().isGeosEqual(expected.geometry())): found = True break self.assertTrue(found)
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 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)]) ] 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, 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 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, 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 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('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