def test_sqlite_writing(self): """Test that writing a dataset to sqlite works.""" keywords = read_keywords(SHP_BASE + '.keywords') layer = Vector(data=SHP_BASE + '.shp', keywords=keywords) test_dir = temp_dir(sub_dir='test') test_file = unique_filename(suffix='.sqlite', dir=test_dir) layer.write_to_file(test_file, sublayer='foo') self.assertTrue(os.path.exists(test_file))
def minimum_needs(self, input_layer, population_name): """Compute minimum needs given a layer and a column containing pop. :param input_layer: InaSAFE layer object assumed to contain population counts :type input_layer: read_layer :param population_name: Attribute name that holds population count :type population_name: str :returns: Layer with attributes for minimum needs as per Perka 7 :rtype: read_layer """ all_attributes = [] for attributes in input_layer.get_data(): # Get population count population = attributes[population_name] # Clean up and turn into integer if population in ['-', None]: displaced = 0 else: if isinstance(population, basestring): population = str(population).replace(',', '') try: displaced = int(population) except ValueError: # noinspection PyTypeChecker,PyArgumentList QtGui.QMessageBox.information( None, self.tr('Format error'), self.tr( 'Please change the value of %1 in attribute ' '%1 to integer format').arg(population).arg( population_name)) raise ValueError # Calculate estimated needs based on BNPB Perka 7/2008 # minimum needs # weekly_needs = { # 'rice': int(ceil(population * min_rice)), # 'drinking_water': int(ceil(population * min_drinking_water)), # 'water': int(ceil(population * min_water)), # 'family_kits': int(ceil(population * min_family_kits)), # 'toilets': int(ceil(population * min_toilets))} # Add to attributes weekly_needs = evacuated_population_weekly_needs(displaced) # Record attributes for this feature all_attributes.append(weekly_needs) output_layer = Vector( geometry=input_layer.get_geometry(), data=all_attributes, projection=input_layer.get_projection()) return output_layer
def interpolate_raster_vector(source, target, layer_name=None, attribute_name=None, mode='linear'): """Interpolate from raster layer to vector data Args: * source: Raster data set (grid) * target: Vector data set (points or polygons) * layer_name: Optional name of returned interpolated layer. If None the name of V is used for the returned layer. * attribute_name: Name for new attribute. If None (default) the name of R is used Returns: I: Vector data set; points located as target with values interpolated from source Note: If target geometry is polygon, data will be interpolated to its centroids and the output is a point data set. """ # Input checks verify(source.is_raster) verify(target.is_vector) if target.is_point_data: # Interpolate from raster to point data R = interpolate_raster_vector_points(source, target, layer_name=layer_name, attribute_name=attribute_name, mode=mode) #elif target.is_line_data: # TBA - issue https://github.com/AIFDR/inasafe/issues/36 # elif target.is_polygon_data: # Use centroids, in case of polygons P = convert_polygons_to_centroids(target) R = interpolate_raster_vector_points(source, P, layer_name=layer_name, attribute_name=attribute_name) # In case of polygon data, restore the polygon geometry # Do this setting the geometry of the returned set to # that of the original polygon R = Vector(data=R.get_data(), projection=R.get_projection(), geometry=target.get_geometry(), name=R.get_name()) else: msg = ('Unknown datatype for raster2vector interpolation: ' 'I got %s' % str(target)) raise InaSAFEError(msg) # Return interpolated vector layer return R
def test_shapefile_loading(self): """Test that loading a dataset with no sublayers works.""" keywords = read_keywords(SHP_BASE + '.keywords') layer = Vector(data=SHP_BASE + '.shp', keywords=keywords) msg = ('Expected layer to be a polygon layer, got a %s' % layer.geometry_type) self.assertTrue(layer.is_polygon_data, msg) count = len(layer) self.assertEqual(count, 250, 'Expected 250 features, got %s' % count)
def testShpLoading(self): """Test that loading a dataset with no sublayers works.""" keywords = read_keywords(SHP_BASE + '.keywords') layer = Vector(data=SHP_BASE + '.shp', keywords=keywords) msg = ('Expected layer to be a polygon layer, got a %s' % layer.geometry_type) assert layer.is_polygon_data, msg count = len(layer) assert count == 250, 'Expected 250 features, got %s' % count
def testSublayerLoading(self): keywords = read_keywords(KEYWORD_PATH, EXPOSURE_SUBLAYER_NAME) layer = Vector(data=SQLITE_PATH, keywords=keywords, sublayer=EXPOSURE_SUBLAYER_NAME) msg = ('Expected layer to be a polygon layer, got a %s' % layer.geometry_type) assert layer.is_polygon_data, msg count = len(layer) assert count == 250, 'Expected 250 features, got %s' % count
def test_sublayer_loading(self): """Test if we can load sublayers.""" keywords = read_keywords(KEYWORD_PATH, EXPOSURE_SUBLAYER_NAME) layer = Vector(data=SQLITE_PATH, keywords=keywords, sublayer=EXPOSURE_SUBLAYER_NAME) msg = ('Expected layer to be a polygon layer, got a %s' % layer.geometry_type) self.assertTrue(layer.is_polygon_data, msg) count = len(layer) self.assertEqual(count, 250, 'Expected 250 features, got %s' % count)
def interpolate_polygon_raster(source, target, layer_name=None, attribute_name=None): """Interpolate from polygon layer to raster data Args * source: Polygon data set * target: Raster data set * layer_name: Optional name of returned interpolated layer. If None the name of source is used for the returned layer. * attribute_name: Name for new attribute. If None (default) the name of layer target is used Output I: Vector data set; points located as target with values interpolated from source Note: Each point in the resulting dataset will have an attribute 'polygon_id' which refers to the polygon it belongs to. """ # Input checks verify(target.is_raster) verify(source.is_vector) verify(source.is_polygon_data) # Run underlying clipping algorithm polygon_geometry = source.get_geometry(as_geometry_objects=True) polygon_attributes = source.get_data() res = clip_grid_by_polygons(target.get_data(scaling=False), target.get_geotransform(), polygon_geometry) # Create one new point layer with interpolated attributes new_geometry = [] new_attributes = [] for i, (geometry, values) in enumerate(res): # For each polygon assign attributes to points that fall inside it for j, geom in enumerate(geometry): attr = polygon_attributes[i].copy() # Attributes for this polygon attr[attribute_name] = values[j] # Attribute value from grid cell attr['polygon_id'] = i # Store id for associated polygon new_attributes.append(attr) new_geometry.append(geom) R = Vector(data=new_attributes, projection=source.get_projection(), geometry=new_geometry, name=layer_name) return R
def test_line_aggregation(self): """Test if line aggregation works """ data_path = standard_data_path('impact', 'aggregation_test_roads.shp') impact_layer = Vector(data=data_path, name='test vector impact') expected_results = [[u'JAKARTA BARAT', 0], [u'JAKARTA PUSAT', 4356], [u'JAKARTA SELATAN', 0], [u'JAKARTA UTARA', 4986], [u'JAKARTA TIMUR', 5809]] impact_layer_attributes = [[{ 'KAB_NAME': u'JAKARTA BARAT', 'flooded': 0.0, 'length': 7230.864654, 'id': 2, 'aggr_sum': 7230.864654 }], [{ 'KAB_NAME': u'JAKARTA PUSAT', 'flooded': 4356.161093, 'length': 4356.161093, 'id': 3, 'aggr_sum': 4356.161093 }], [{ 'KAB_NAME': u'JAKARTA SELATAN', 'flooded': 0.0, 'length': 3633.317287, 'id': 4, 'aggr_sum': 3633.317287 }], [{ 'KAB_NAME': u'JAKARTA UTARA', 'flooded': 4985.831677, 'length': 4985.831677, 'id': 1, 'aggr_sum': 4985.831677 }], [{ 'KAB_NAME': u'JAKARTA TIMUR', 'flooded': 0.0, 'length': 4503.033629, 'id': 4, 'aggr_sum': 4503.033629 }, { 'KAB_NAME': u'JAKARTA TIMUR', 'flooded': 5809.142247, 'length': 5809.142247, 'id': 1, 'aggr_sum': 5809.142247 }]] self._aggregate(impact_layer, expected_results, impact_layer_attributes=impact_layer_attributes)
def tag_polygons_by_grid(polygons, grid, threshold=0, tag='affected'): """Tag polygons by raster values Args: * polygons: Polygon layer * grid: Raster layer * threshold: Threshold for grid value to tag polygon * tag: Name of new tag Returns: Polygon layer: Same as input polygon but with extra attribute tag set according to grid values """ verify(polygons.is_polygon_data) verify(grid.is_raster) polygon_attributes = polygons.get_data() polygon_geometry = polygons.get_geometry(as_geometry_objects=True) # Separate grid points by polygon res = clip_grid_by_polygons(grid.get_data(), grid.get_geotransform(), polygon_geometry) # Create new polygon layer with tag set according to grid values # and threshold new_attributes = [] for i, (_, values) in enumerate(res): # For each polygon check if any grid value in it exceeds the threshold affected = False for val in values: # Check each grid value in this polygon if val > threshold: affected = True # Existing attributes for this polygon attr = polygon_attributes[i].copy() # Create tagged polygon feature if affected: attr[tag] = True else: attr[tag] = False new_attributes.append(attr) R = Vector(data=new_attributes, projection=polygons.get_projection(), geometry=polygon_geometry, name='%s_tagged_by_%s' % (polygons.name, grid.name)) return R
def sigab2bnpb(E, target_attribute='VCLASS'): """Map SIGAB point data to BNPB vulnerability classes Input E: Vector object representing the OSM data target_attribute: Optional name of the attribute containing the mapped vulnerability class. Default value is 'VCLASS' Output: Vector object like E, but with one new attribute (e.g. 'VCLASS') representing the vulnerability class used in the guidelines """ # Input check required = ['Struktur_B', 'Lantai', 'Atap', 'Dinding', 'Tingkat'] actual = E.get_attribute_names() msg = ('Input data to sigab2bnpb must have attributes %s. ' 'It has %s' % (str(required), str(actual))) for attribute in required: verify(attribute in actual, msg) # Start mapping N = len(E) attributes = E.get_data() for i in range(N): levels = E.get_data('Tingkat', i).lower() structure = E.get_data('Struktur_B', i).lower() # roof_type = E.get_data('Atap', i).lower() # wall_type = E.get_data('Dinding', i).lower() # floor_type = E.get_data('Lantai', i).lower() if levels == 'none' or structure == 'none': vulnerability_class = 'URM' elif structure.startswith('beton') or structure.startswith('kayu'): vulnerability_class = 'RM' else: if int(levels) >= 2: vulnerability_class = 'RM' else: vulnerability_class = 'URM' # Store new attribute value attributes[i][target_attribute] = vulnerability_class # Create new vector instance and return V = Vector(data=attributes, projection=E.get_projection(), geometry=E.get_geometry(), name=E.get_name() + ' mapped to BNPB vulnerability classes', keywords=E.get_keywords()) return V
def test_convert_to_qgis_vector_layer(self): """Test that converting to QgsVectorLayer works.""" if QGIS_IS_AVAILABLE: # Create vector layer keywords = read_keywords(SHP_BASE + '.keywords') layer = Vector(data=SHP_BASE + '.shp', keywords=keywords) # Convert to QgsVectorLayer qgis_layer = layer.as_qgis_native() provider = qgis_layer.dataProvider() count = provider.featureCount() self.assertEqual(count, 250, 'Expected 250 features, got %s' % count)
def test_qgis_vector_layer_loading(self): """Test that reading from QgsVectorLayer works.""" keywords = read_keywords(KEYWORD_PATH, EXPOSURE_SUBLAYER_NAME) if QGIS_IS_AVAILABLE: qgis_layer = QgsVectorLayer(SHP_BASE + '.shp', 'test', 'ogr') layer = Vector(data=qgis_layer, keywords=keywords) msg = ('Expected layer to be a polygon layer, got a %s' % layer.geometry_type) self.assertTrue(layer.is_polygon_data, msg) count = len(layer) self.assertEqual(count, 250, 'Expected 250 features, got %s' % count)
def make_circular_polygon(centers, radii, attributes=None): """Create circular polygon in geographic coordinates Args: centers: list of (longitude, latitude) radii: desired approximate radii in meters (must be monotonically ascending). Can be either one number or list of numbers attributes (optional): Attributes for each center Returns: Vector polygon layer representing circle in WGS84 """ if not isinstance(radii, list): radii = [radii] # FIXME (Ole): Check that radii are monotonically increasing circles = [] new_attributes = [] for i, center in enumerate(centers): p = Point(longitude=center[0], latitude=center[1]) inner_rings = None for radius in radii: # Generate circle polygon C = p.generate_circle(radius) circles.append(Polygon(outer_ring=C, inner_rings=inner_rings)) # Store current circle and inner ring for next poly inner_rings = [C] # Carry attributes for center forward attr = {} if attributes is not None: for key in attributes[i]: attr[key] = attributes[i][key] # Add radius to this ring attr['Radius'] = radius new_attributes.append(attr) Z = Vector( geometry=circles, # List with circular polygons data=new_attributes, # Associated attributes geometry_type='polygon') return Z
def interpolate_polygon_vector(source, target, layer_name=None): """Interpolate from polygon vector layer to vector data Args: * source: Vector data set (polygon) * target: Vector data set (points or polygons) - TBA also lines * layer_name: Optional name of returned interpolated layer. If None the name of target is used for the returned layer. Output I: Vector data set; points located as target with values interpolated from source Note: If target geometry is polygon, data will be interpolated to its centroids and the output is a point data set. """ # Input checks verify(source.is_vector) verify(target.is_vector) verify(source.is_polygon_data) if target.is_point_data: R = interpolate_polygon_points(source, target, layer_name=layer_name) elif target.is_line_data: R = interpolate_polygon_lines(source, target, layer_name=layer_name) elif target.is_polygon_data: # Use polygon centroids X = convert_polygons_to_centroids(target) P = interpolate_polygon_points(source, X, layer_name=layer_name) # In case of polygon data, restore the polygon geometry # Do this setting the geometry of the returned set to # that of the original polygon R = Vector(data=P.get_data(), projection=P.get_projection(), geometry=target.get_geometry(as_geometry_objects=True), name=P.get_name()) else: msg = ('Unknown datatype for polygon2vector interpolation: ' 'I got %s' % str(target)) raise InaSAFEError(msg) # Return interpolated vector layer return R
def interpolate_raster_vector_points(R, V, name=None): """Interpolate from raster layer to point data Input R: Raster data set (grid) V: Vector data set (points) name: Name for new attribute. If None (default) the name of R is used Output I: Vector data set; points located as V with values interpolated from R """ msg = ('There are no data points to interpolate to. Perhaps zoom out ' 'and try again') verify(len(V) > 0, msg) # Input checks verify(R.is_raster) verify(V.is_vector) verify(V.is_point_data) # Get raster data and corresponding x and y axes A = R.get_data(nan=True) longitudes, latitudes = R.get_geometry() verify(len(longitudes) == A.shape[1]) verify(len(latitudes) == A.shape[0]) # Get vector point geometry as Nx2 array coordinates = numpy.array(V.get_geometry(), dtype='d', copy=False) # Interpolate and create new attribute N = len(V) attributes = [] if name is None: name = R.get_name() values = interpolate_raster(longitudes, latitudes, A, coordinates, mode='linear') # Create list of dictionaries for this attribute and return for i in range(N): attributes.append({name: values[i]}) return Vector(data=attributes, projection=V.get_projection(), geometry=coordinates)
def run(layers): """Risk plugin for earthquake school damage """ # Extract data H = get_hazard_layer(layers) # Ground shaking E = get_exposure_layer(layers) # Building locations # Interpolate hazard level to building locations H = H.interpolate(E) # Extract relevant numerical data coordinates = E.get_geometry() shaking = H.get_data() # Calculate building damage building_damage = [] for i in range(len(shaking)): x = float(shaking[i].values()[0]) if x < 6.0 or (x != x): # x != x -> check for nan pre python 2.6 value = 0.0 else: print x value = (0.692 * (x**4) - 15.82 * (x**3) + 135.0 * (x**2) - 509.0 * x + 714.4) building_damage.append({'DAMAGE': value, 'MMI': x}) # FIXME (Ole): Need helper to generate new layer using # correct spatial reference # (i.e. sensibly wrap the following lines) projection = E.get_projection() V = Vector(data=building_damage, projection=E.get_projection(), geometry=coordinates) return V
def run(self, layers=None): """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 """ self.validate() self.prepare(layers) target_field = self.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 = self.hazard # Flood E = self.exposure # Roads 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.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 = 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.requested_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.requested_extent[0], self.requested_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 = 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 osm2bnpb(E, target_attribute='VCLASS'): """ Map OSM attributes to BNPB vulnerability classes This maps attributes collected in the OpenStreetMap exposure data (data.kompetisiosm.org) to 2 vulnerability classes identified by BNPB in Kajian Risiko Gempabumi VERS 1.0, 2011. They are URM: Unreinforced Masonry and RM: Reinforced Masonry Input E: Vector object representing the OSM data target_attribute: Optional name of the attribute containing the mapped vulnerability class. Default value is 'VCLASS' Output: Vector object like E, but with one new attribute (e.g. 'VCLASS') representing the vulnerability class used in the guidelines """ # Input check required = ['building_l', 'building_s'] actual = E.get_attribute_names() msg = ('Input data to osm2bnpb must have attributes %s. ' 'It has %s' % (str(required), str(actual))) for attribute in required: verify(attribute in actual, msg) # Start mapping N = len(E) attributes = E.get_data() # FIXME (Ole): Pylint says variable count is unused. Why? # pylint: disable=W0612 count = 0 # pylint: enable=W0612 for i in range(N): levels = E.get_data('building_l', i) structure = E.get_data('building_s', i) if levels is None or structure is None: vulnerability_class = 'URM' count += 1 else: # Map string variable levels to integer if levels.endswith('+'): levels = 100 try: levels = int(levels) except ValueError: # E.g. 'ILP jalan' vulnerability_class = 'URM' count += 1 else: # Start mapping depending on levels if levels >= 4: # High vulnerability_class = 'RM' elif 1 <= levels < 4: # Low if structure in ['reinforced_masonry', 'confined_masonry']: vulnerability_class = 'RM' elif 'kayu' in structure or 'wood' in structure: vulnerability_class = 'RM' else: vulnerability_class = 'URM' elif numpy.allclose(levels, 0): # A few buildings exist with 0 levels. # In general, we should be assigning here the most # frequent building in the area which could be defined # by admin boundaries. vulnerability_class = 'URM' else: msg = 'Unknown number of levels: %s' % levels raise Exception(msg) # Store new attribute value attributes[i][target_attribute] = vulnerability_class # print 'Got %i without levels or structure (out of %i total)' % (count, N) # Create new vector instance and return V = Vector(data=attributes, projection=E.get_projection(), geometry=E.get_geometry(), name=E.get_name() + ' mapped to BNPB vulnerability classes', keywords=E.get_keywords()) return V
def osm2padang(E): """Map OSM attributes to Padang vulnerability classes This maps attributes collected in the OpenStreetMap exposure data (data.kompetisiosm.org) to 9 vulnerability classes identified by Geoscience Australia and ITB in the post 2009 Padang earthquake survey (http://trove.nla.gov.au/work/38470066). The mapping was developed by Abigail Baca, World Bank-GFDRR. Input E: Vector object representing the OSM data Output: Vector object like E, but with one new attribute ('VCLASS') representing the vulnerability class used in the Padang dataset Algorithm 1. Class the "levels" field into height bands where 1-3 = low, 4-10 = mid, >10 = high 2. Where height band = mid then building type = 4 "RC medium rise Frame with Masonry in-fill walls" 3. Where height band = high then building type = 6 "Concrete Shear wall high rise* Hazus C2H" 4. Where height band = low and structure = (plastered or reinforced_masonry) then building type = 7 "RC low rise Frame with Masonry in-fill walls" 5. Where height band = low and structure = confined_masonry then building type = 8 "Confined Masonry" 6. Where height band = low and structure = unreinforced_masonry then building type = 2 "URM with Metal Roof" """ # Input check required = ['building_l', 'building_s'] actual = E.get_attribute_names() msg = ('Input data to osm2padang must have attributes %s. ' 'It has %s' % (str(required), str(actual))) for attribute in required: verify(attribute in actual, msg) # Start mapping N = len(E) attributes = E.get_data() # FIXME (Ole): Pylint says variable count is unused. Why? # pylint: disable=W0612 count = 0 # pylint: enable=W0612 for i in range(N): levels = E.get_data('building_l', i) structure = E.get_data('building_s', i) if levels is None or structure is None: vulnerability_class = 2 count += 1 else: # Map string variable levels to integer if levels.endswith('+'): levels = 100 try: levels = int(levels) except ValueError: # E.g. 'ILP jalan' vulnerability_class = 2 count += 1 else: # Start mapping depending on levels if levels >= 10: # High vulnerability_class = 6 # Concrete shear elif 4 <= levels < 10: # Mid vulnerability_class = 4 # RC mid elif 1 <= levels < 4: # Low if structure in [ 'plastered', 'reinforced masonry', 'reinforced_masonry' ]: vulnerability_class = 7 # RC low elif structure == 'confined_masonry': vulnerability_class = 8 # Confined elif 'kayu' in structure or 'wood' in structure: vulnerability_class = 9 # Wood else: vulnerability_class = 2 # URM elif numpy.allclose(levels, 0): # A few buildings exist with 0 levels. # In general, we should be assigning here the most # frequent building in the area which could be defined # by admin boundaries. vulnerability_class = 2 else: msg = 'Unknown number of levels: %s' % levels raise Exception(msg) # Store new attribute value attributes[i]['VCLASS'] = vulnerability_class # Selfcheck for use with osm_080811.shp if E.get_name() == 'osm_080811': if levels > 0: msg = ('Got %s expected %s. levels = %f, structure = %s' % (vulnerability_class, attributes[i]['TestBLDGCl'], levels, structure)) verify( numpy.allclose(attributes[i]['TestBLDGCl'], vulnerability_class), msg) # print 'Got %i without levels or structure (out of %i total)' % (count, N) # Create new vector instance and return V = Vector(data=attributes, projection=E.get_projection(), geometry=E.get_geometry(), name=E.get_name() + ' mapped to Padang vulnerability classes', keywords=E.get_keywords()) return V
def sigab2padang(E): """Map SIGAB attributes to Padang vulnerability classes Input E: Vector object representing the SIGAB data Output: Vector object like E, but with one new attribute ('VCLASS') representing the vulnerability class used in the Padang dataset """ # Input check required = ['Struktur_B', 'Lantai', 'Atap', 'Dinding', 'Tingkat'] actual = E.get_attribute_names() msg = ('Input data to sigab2bnpb must have attributes %s. ' 'It has %s' % (str(required), str(actual))) for attribute in required: verify(attribute in actual, msg) # Start mapping N = len(E) attributes = E.get_data() for i in range(N): levels = E.get_data('Tingkat', i).lower() structure = E.get_data('Struktur_B', i).lower() # roof_type = E.get_data('Atap', i).lower() # wall_type = E.get_data('Dinding', i).lower() # floor_type = E.get_data('Lantai', i).lower() if levels == 'none' or structure == 'none': vulnerability_class = 2 else: if int(levels) >= 2: vulnerability_class = 7 # RC low else: # Low if structure in ['beton bertulang']: vulnerability_class = 6 # Concrete shear elif structure.startswith('rangka'): vulnerability_class = 8 # Confined elif 'kayu' in structure or 'wood' in structure: vulnerability_class = 9 # Wood else: vulnerability_class = 2 # URM # Store new attribute value attributes[i]['VCLASS'] = vulnerability_class # Selfcheck for use with osm_080811.shp if E.get_name() == 'osm_080811': if levels > 0: msg = ('Got %s expected %s. levels = %f, structure = %s' % (vulnerability_class, attributes[i]['TestBLDGCl'], levels, structure)) verify( numpy.allclose(attributes[i]['TestBLDGCl'], vulnerability_class), msg) # Create new vector instance and return V = Vector(data=attributes, projection=E.get_projection(), geometry=E.get_geometry(), name=E.get_name() + ' mapped to Padang vulnerability classes', keywords=E.get_keywords()) return V
def interpolate_polygon_lines(source, target, layer_name=None): """Interpolate from polygon vector layer to line vector data Args: * source: Vector data set (polygon) * target: Vector data set (lines) * layer_name: Optional name of returned interpolated layer. If None the name of target is used for the returned layer. Returns: Vector data set of lines inside polygons Attributes are combined from polygon they fall into and line that was clipped. Lines not in any polygon are ignored. """ # Extract line features lines = target.get_geometry() line_attributes = target.get_data() N = len(target) verify(len(lines) == N) verify(len(line_attributes) == N) # Extract polygon features polygons = source.get_geometry() polygon_attributes = source.get_data() verify(len(polygons) == len(polygon_attributes)) # Data structure for resulting line segments # clipped_geometry = [] # clipped_attributes = [] # Clip line lines to polygons lines_covered = clip_lines_by_polygons(lines, polygons) # Create one new line data layer with joined attributes # from polygons and lines new_geometry = [] new_attributes = [] for i in range(len(polygons)): # Loop over polygons for j in lines_covered[i]: # Loop over parent lines lines = lines_covered[i][j] for line in lines: # Loop over lines clipped from line j by polygon i # Associated polygon and line attributes attr = polygon_attributes[i].copy() attr.update(line_attributes[j].copy()) attr['polygon_id'] = i # Store id for associated polygon attr['parent_line_id'] = j # Store id for parent line attr[DEFAULT_ATTRIBUTE] = True # Store new line feature new_geometry.append(line) new_attributes.append(attr) R = Vector(data=new_attributes, projection=source.get_projection(), geometry=new_geometry, geometry_type='line', name=layer_name) return R
def interpolate_polygon_points(source, target, layer_name=None): """Interpolate from polygon vector layer to point vector data Args: * source: Vector data set (polygon) * target: Vector data set (points) * layer_name: Optional name of returned interpolated layer. If None the name of target is used for the returned layer. Output I: Vector data set; points located as target with values interpolated from source Note All attribute names from polygons are transferred to the points that are inside them. """ msg = ('Vector layer to interpolate to must be point geometry. ' 'I got OGR geometry type %s' % geometry_type_to_string(target.geometry_type)) verify(target.is_point_data, msg) msg = ('Name must be either a string or None. I got %s' % (str(type(target)))[1:-1]) verify(layer_name is None or isinstance(layer_name, basestring), msg) attribute_names = source.get_attribute_names() target_attribute_names = target.get_attribute_names() # Include polygon_id as attribute attribute_names.append('polygon_id') attribute_names.append(DEFAULT_ATTRIBUTE) # Let's ensure that we don't shadow attribute names #2090. # Also this ensures shp file character length compliance. safe_attribute_name = {} used_names = [_ for _ in target_attribute_names] for name in attribute_names: safe_name = get_non_conflicting_attribute_name(name, used_names) used_names.append(safe_name) safe_attribute_name[name] = safe_name # ---------------- # Start algorithm # ---------------- # Extract point features points = ensure_numeric(target.get_geometry()) attributes = target.get_data() original_geometry = target.get_geometry() # Geometry for returned data # Extract polygon features geom = source.get_geometry(as_geometry_objects=True) data = source.get_data() verify(len(geom) == len(data)) # Augment point features with empty attributes from polygon for a in attributes: # Create all attributes that exist in source for key in attribute_names: safe_key = safe_attribute_name[key] a[safe_key] = None # Traverse polygons and assign attributes to points that fall inside for i, polygon in enumerate(geom): # Carry all attributes across from source poly_attr = data[i] # Assign default attribute to indicate points inside poly_attr[DEFAULT_ATTRIBUTE] = True # Clip data points by polygons and add polygon attributes indices = inside_polygon(points, polygon.outer_ring, holes=polygon.inner_rings) for k in indices: for key in poly_attr: # Assign attributes from polygon to points safe_key = safe_attribute_name[key] attributes[k][safe_key] = poly_attr[key] attributes[k]['polygon_id'] = i # Store id for associated polygon # Create new Vector instance and return V = Vector(data=attributes, projection=target.get_projection(), geometry=original_geometry, name=layer_name) return V
def interpolate_polygon_raster(source, target, layer_name=None, attribute_name=None): """Interpolate from polygon layer to raster data. .. note: Each point in the resulting dataset will have an attribute 'polygon_id' which refers to the polygon it belongs to and 'grid_point' which refers to the grid point of the target. :param source: Polygon data set. :type source: Vector :param target: Raster data set. :type target: Raster :param layer_name: Optional name of returned interpolated layer. If None the name of source is used for the returned layer. :type layer_name: basestring :param attribute_name: Name for new attribute. If None (default) the name of layer target is used :type attribute_name: basestring :returns: Tuple of Vector (points located as target with values interpolated from source) and Raster (raster data that are coincide with the source) :rtype: Vector """ # Input checks verify(source.is_polygon_data) verify(target.is_raster) # Run underlying clipping algorithm polygon_geometry = source.get_geometry(as_geometry_objects=True) polygon_attributes = source.get_data() covered_source, covered_target = clip_grid_by_polygons( target.get_data(scaling=False), target.get_geotransform(), polygon_geometry) # Create one new point layer with interpolated attributes new_geometry = [] new_attributes = [] for i, (geometry, values) in enumerate(covered_source): # For each polygon assign attributes to points that fall inside it for j, geom in enumerate(geometry): attr = polygon_attributes[i].copy() # Attributes for this polygon attr[attribute_name] = values[j] # Attribute value from grid cell attr['polygon_id'] = i # Store id for associated polygon attr['grid_point'] = geom # Store grid point for associated grid new_attributes.append(attr) new_geometry.append(geom) interpolated_layer = Vector(data=new_attributes, projection=source.get_projection(), geometry=new_geometry, name=layer_name) covered_target = Raster(data=covered_target, projection=target.get_projection(), geotransform=target.get_geotransform(), name=layer_name) return interpolated_layer, covered_target
except (BoundsError, InaSAFEError), e: msg = (tr('Could not interpolate from raster layer %(raster)s to ' 'vector layer %(vector)s. Error message: %(error)s') % { 'raster': source.get_name(), 'vector': target.get_name(), 'error': str(e) }) raise InaSAFEError(msg) # Add interpolated attribute to existing attributes and return N = len(target) for i in range(N): attributes[i][attribute_name] = values[i] return Vector(data=attributes, projection=target.get_projection(), geometry=coordinates, name=layer_name) def interpolate_polygon_points(source, target, layer_name=None): """Interpolate from polygon vector layer to point vector data Args: * source: Vector data set (polygon) * target: Vector data set (points) * layer_name: Optional name of returned interpolated layer. If None the name of target is used for the returned layer. Output I: Vector data set; points located as target with values interpolated from source
def run(self, layers): """Risk plugin for flood population evacuation. :param layers: List of layers expected to contain * hazard_layer : Vector polygon layer of flood depth * exposure_layer : Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to areas identified as flood prone :returns: Map of population exposed to flooding Table with number of people evacuated and supplies required. :rtype: tuple """ # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Flood inundation exposure_layer = get_exposure_layer(layers) question = get_question(hazard_layer.get_name(), exposure_layer.get_name(), self) # Check that hazard is polygon type if not hazard_layer.is_vector: message = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(message) message = ( 'Input hazard must be a polygon layer. I got %s with layer type ' '%s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) if not hazard_layer.is_polygon_data: raise Exception(message) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(hazard_layer, exposure_layer, attribute_name='population') # Initialise attributes of output dataset with all attributes # from input polygon and a population count of zero new_attributes = hazard_layer.get_data() category_title = 'affected' # FIXME: Should come from keywords deprecated_category_title = 'FLOODPRONE' categories = {} for attr in new_attributes: attr[self.target_field] = 0 try: cat = attr[category_title] except KeyError: try: cat = attr['FLOODPRONE'] categories[cat] = 0 except KeyError: pass # Count affected population per polygon, per category and total affected_population = 0 for attr in P.get_data(): affected = False if 'affected' in attr: res = attr['affected'] if res is None: x = False else: x = bool(res) affected = x elif 'FLOODPRONE' in attr: # If there isn't an 'affected' attribute, res = attr['FLOODPRONE'] if res is not None: affected = res.lower() == 'yes' elif 'Affected' in attr: # Check the default attribute assigned for points # covered by a polygon res = attr['Affected'] if res is None: x = False else: x = res affected = x else: # assume that every polygon is affected (see #816) affected = True # there is no flood related attribute # message = ('No flood related attribute found in %s. ' # 'I was looking for either "Flooded", "FLOODPRONE" ' # 'or "Affected". The latter should have been ' # 'automatically set by call to ' # 'assign_hazard_values_to_exposure_data(). ' # 'Sorry I can\'t help more.') # raise Exception(message) if affected: # Get population at this location pop = float(attr['population']) # Update population count for associated polygon poly_id = attr['polygon_id'] new_attributes[poly_id][self.target_field] += pop # Update population count for each category if len(categories) > 0: try: cat = new_attributes[poly_id][category_title] except KeyError: cat = new_attributes[poly_id][ deprecated_category_title] categories[cat] += pop # Update total affected_population += pop # Estimate number of people in need of evacuation evacuated = (affected_population * self.parameters['evacuation_percentage'] / 100.0) affected_population, rounding = population_rounding_full( affected_population) total = int(numpy.sum(exposure_layer.get_data(nan=0, scaling=False))) # Don't show digits less than a 1000 total = population_rounding(total) evacuated, rounding_evacuated = population_rounding_full(evacuated) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map table_body = [ question, TableRow([ tr('People affected'), '%s*' % (format_int(int(affected_population))) ], header=True), TableRow([ TableCell(tr('* Number is rounded up to the nearest %s') % (rounding), col_span=2) ]), TableRow([ tr('People needing evacuation'), '%s*' % (format_int(int(evacuated))) ], header=True), TableRow([ TableCell(tr('* Number is rounded up to the nearest %s') % (rounding_evacuated), col_span=2) ]), TableRow([ tr('Evacuation threshold'), '%s%%' % format_int(self.parameters['evacuation_percentage']) ], header=True), TableRow( tr('Map shows the number of people affected in each flood prone ' 'area')), TableRow( tr('Table below shows the weekly minimum needs for all ' 'evacuated people')) ] total_needs = evacuated_population_needs(evacuated, minimum_needs) for frequency, needs in total_needs.items(): table_body.append( TableRow([ tr('Needs should be provided %s' % frequency), tr('Total') ], header=True)) for resource in needs: table_body.append( TableRow([ tr(resource['table name']), format_int(resource['amount']) ])) impact_table = Table(table_body).toNewlineFreeString() table_body.append(TableRow(tr('Action Checklist:'), header=True)) table_body.append(TableRow(tr('How will warnings be disseminated?'))) table_body.append(TableRow(tr('How will we reach stranded people?'))) table_body.append(TableRow(tr('Do we have enough relief items?'))) table_body.append( TableRow( tr('If yes, where are they located and how will we distribute ' 'them?'))) table_body.append( TableRow( tr('If no, where can we obtain additional relief items from and ' 'how will we transport them to here?'))) # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population: %s') % format_int(total), tr('People need evacuation if in the area identified as ' '"Flood Prone"'), tr('Minimum needs are defined in BNPB regulation 7/2008') ]) impact_summary = Table(table_body).toNewlineFreeString() # Create style # Define classes for legend for flooded population counts colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] population_counts = [x['population'] for x in new_attributes] classes = create_classes(population_counts, len(colours)) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 0: transparency = 0 style_class['min'] = 0 else: transparency = 0 style_class['min'] = classes[i - 1] style_class['transparency'] = transparency style_class['colour'] = colours[i] style_class['max'] = classes[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='graduatedSymbol') # For printing map purpose map_title = tr('People affected by flood prone areas') legend_notes = tr('Thousand separator is represented by \'.\'') legend_units = tr('(people per polygon)') legend_title = tr('Population Count') # Create vector layer and return vector_layer = Vector(data=new_attributes, projection=hazard_layer.get_projection(), geometry=hazard_layer.get_geometry(), name=tr('People affected by flood prone areas'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'affected_population': affected_population, 'total_population': total, 'total_needs': total_needs }, style_info=style_info) return vector_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ self.validate() self.prepare() 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') # 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, 'legend_title': legend_title, 'target_field': target_field }, style_info=style_info) self._impact = line_layer 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
def run(self, layers): """Earthquake impact to buildings (e.g. from Open Street Map) """ # Thresholds for mmi breakdown t0 = 6 t1 = 7 t2 = 8 class_1 = 'Low' class_2 = 'Medium' class_3 = 'High' # Extract data H = get_hazard_layer(layers) # Depth E = get_exposure_layer(layers) # Building locations question = get_question(H.get_name(), E.get_name(), self) # Define attribute name for hazard levels hazard_attribute = 'mmi' # Interpolate hazard level to building locations I = assign_hazard_values_to_exposure_data( H, E, attribute_name=hazard_attribute) # Extract relevant exposure data #attribute_names = I.get_attribute_names() attributes = I.get_data() N = len(I) # Calculate building impact lo = 0 me = 0 hi = 0 building_values = {} contents_values = {} for key in range(4): building_values[key] = 0 contents_values[key] = 0 for i in range(N): # Classify building according to shake level x = float(attributes[i][hazard_attribute]) # Interpolated MMI val if t0 <= x < t1: lo += 1 cls = 1 elif t1 <= x < t2: me += 1 cls = 2 elif t2 <= x: hi += 1 cls = 3 else: # Buildings not reported for MMI levels < t0 cls = 0 attributes[i][self.target_field] = cls # Generate simple impact report for unspecific buildings table_body = [ question, TableRow(['Hazard Level', 'Buildings Affected'], header=True), TableRow([class_1, lo]), TableRow([class_2, me]), TableRow([class_3, hi]) ] table_body.append(TableRow('Notes', header=True)) table_body.append('High hazard is defined as shake levels greater ' 'than %i on the MMI scale.' % t2) table_body.append('Medium hazard is defined as shake levels ' 'between %i and %i on the MMI scale.' % (t1, t2)) table_body.append('Low hazard is defined as shake levels ' 'between %i and %i on the MMI scale.' % (t0, t1)) impact_summary = Table(table_body).toNewlineFreeString() impact_table = impact_summary map_title = 'Buildings affected' # Create style style_classes = [ dict(label=class_1, value=1, colour='#ffff00', transparency=1), dict(label=class_2, value=2, colour='#ffaa00', transparency=1), dict(label=class_3, value=3, colour='#ff0000', transparency=1) ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Create vector layer and return V = Vector(data=attributes, projection=I.get_projection(), geometry=I.get_geometry(), name='Estimated buildings affected', keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'target_field': self.target_field, 'statistics_type': self.statistics_type, 'statistics_classes': self.statistics_classes }, style_info=style_info) return V
def run(self, layers=None): """Earthquake impact to buildings (e.g. from OpenStreetMap). :param layers: All the input layers (Hazard Layer and Exposure Layer) """ self.validate() self.prepare(layers) LOGGER.debug('Running earthquake building impact') # merely initialize building_value = 0 contents_value = 0 # Thresholds for mmi breakdown. t0 = self.parameters['low_threshold'] t1 = self.parameters['medium_threshold'] t2 = self.parameters['high_threshold'] # Class Attribute and Label. class_1 = {'label': tr('Low'), 'class': 1} class_2 = {'label': tr('Medium'), 'class': 2} class_3 = {'label': tr('High'), 'class': 3} # Extract data hazard_layer = self.hazard # Depth exposure_layer = self.exposure # Building locations # Define attribute name for hazard levels. hazard_attribute = 'mmi' # Determine if exposure data have NEXIS attributes. attribute_names = exposure_layer.get_attribute_names() if ( 'FLOOR_AREA' in attribute_names and 'BUILDING_C' in attribute_names and 'CONTENTS_C' in attribute_names): self.is_nexis = True else: self.is_nexis = False # Interpolate hazard level to building locations. interpolate_result = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=hazard_attribute ) # Extract relevant exposure data # attribute_names = interpolate_result.get_attribute_names() attributes = interpolate_result.get_data() interpolate_size = len(interpolate_result) # Building breakdown self.buildings = {} # Impacted building breakdown self.affected_buildings = OrderedDict([ (tr('High'), {}), (tr('Medium'), {}), (tr('Low'), {}) ]) for i in range(interpolate_size): # Classify building according to shake level # and calculate dollar losses if self.is_nexis: try: area = float(attributes[i]['FLOOR_AREA']) except (ValueError, KeyError): # print 'Got area', attributes[i]['FLOOR_AREA'] area = 0.0 try: building_value_density = float(attributes[i]['BUILDING_C']) except (ValueError, KeyError): # print 'Got bld value', attributes[i]['BUILDING_C'] building_value_density = 0.0 try: contents_value_density = float(attributes[i]['CONTENTS_C']) except (ValueError, KeyError): # print 'Got cont value', attributes[i]['CONTENTS_C'] contents_value_density = 0.0 building_value = building_value_density * area contents_value = contents_value_density * area usage = get_osm_building_usage(attribute_names, attributes[i]) if usage is None or usage == 0: usage = 'unknown' if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): if self.is_nexis: self.affected_buildings[category][usage] = OrderedDict( [ (tr('Buildings Affected'), 0), (tr('Buildings value ($M)'), 0), (tr('Contents value ($M)'), 0)]) else: self.affected_buildings[category][usage] = OrderedDict( [ (tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 try: mmi = float(attributes[i][hazard_attribute]) # MMI except TypeError: mmi = 0.0 if t0 <= mmi < t1: cls = 1 category = tr('Low') elif t1 <= mmi < t2: cls = 2 category = tr('Medium') elif t2 <= mmi: cls = 3 category = tr('High') else: # Not reported for less than level t0 continue attributes[i][self.target_field] = cls self.affected_buildings[ category][usage][tr('Buildings Affected')] += 1 if self.is_nexis: self.affected_buildings[category][usage][ tr('Buildings value ($M)')] += building_value / 1000000.0 self.affected_buildings[category][usage][ tr('Contents value ($M)')] += contents_value / 1000000.0 # Consolidate the small building usage groups < 25 to other self._consolidate_to_other() impact_table = impact_summary = self.generate_html_report() # Create style style_classes = [dict(label=class_1['label'], value=class_1['class'], colour='#ffff00', transparency=1), dict(label=class_2['label'], value=class_2['class'], colour='#ffaa00', transparency=1), dict(label=class_3['label'], value=class_3['class'], colour='#ff0000', transparency=1)] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Building affected by earthquake') legend_notes = tr('The level of the impact is according to the ' 'threshold the user input.') legend_units = tr('(mmi)') legend_title = tr('Impact level') # Create vector layer and return result_layer = Vector( data=attributes, projection=interpolate_result.get_projection(), geometry=interpolate_result.get_geometry(), name=tr('Estimated buildings affected'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'target_field': self.target_field, 'statistics_type': self.statistics_type, 'statistics_classes': self.statistics_classes}, style_info=style_info) msg = 'Created vector layer %s' % str(result_layer) LOGGER.debug(msg) self._impact = result_layer return result_layer