def test_get_non_conflicting_attribute_name(self): """Test we can get a non conflicting attribute name.""" default_name = 'population' attribute_names = ['POPULATION', 'id', 'location', 'latitude'] non_conflicting_attribute_name = get_non_conflicting_attribute_name( default_name, attribute_names) expected_result = 'populati_1' self.assertEqual(expected_result, non_conflicting_attribute_name)
def test_get_non_conflicting_attribute_name(self): """Test we can get a non conflicting attribute name.""" default_name = 'population' attribute_names = ['POPULATION', 'id', 'location', 'latitude'] non_conflicting_attribute_name = get_non_conflicting_attribute_name( default_name, attribute_names) expected_result = 'populati_1' message = 'The expected result should be %s, but it gives %s' % ( expected_result, non_conflicting_attribute_name) self.assertEqual( expected_result, non_conflicting_attribute_name, message)
def exposure(self, layer): """Setter for exposure layer property. :param layer: exposure layer to be used for the analysis. :type layer: SafeLayer """ if isinstance(layer, SafeLayer): self._exposure = layer else: if self.function_type() == 'old-style': self._exposure = SafeLayer(convert_to_safe_layer(layer)) elif self.function_type() == 'qgis2.0': # convert for new style impact function self._exposure = SafeLayer(layer) else: message = tr('Error: Impact Function has unknown style.') raise Exception(message) # Update the target field to a non-conflicting one if self.exposure.is_qgsvectorlayer(): self._target_field = get_non_conflicting_attribute_name( self.target_field, self.exposure.layer.dataProvider().fieldNameMap().keys())
def exposure(self, layer): """Setter for exposure layer property. :param layer: exposure layer to be used for the analysis. :type layer: SafeLayer """ if isinstance(layer, SafeLayer): self._exposure = layer else: if self.function_type() == 'old-style': self._exposure = SafeLayer(convert_to_safe_layer(layer)) elif self.function_type() == 'qgis2.0': # convert for new style impact function self._exposure = SafeLayer(layer) else: message = tr('Error: Impact Function has unknown style.') raise Exception(message) # Update the target field to a non-conflicting one if self.exposure.is_qgsvectorlayer(): self._target_field = get_non_conflicting_attribute_name( self.target_field, self.exposure.layer.dataProvider().fieldNameMap().keys() )
def run(self): """Counts number of building exposed to each volcano hazard zones. :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Parameters radii = self.parameters['distances'].value # Get parameters from layer's keywords volcano_name_attribute = self.hazard.keyword('volcano_name_field') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Category names for the impact zone category_names = radii # In kilometers self._affected_categories_volcano = [ tr('Radius %.1f km') % key for key in radii[::]] # Get names of volcanoes considered if volcano_name_attribute in self.hazard.layer.get_attribute_names(): for row in self.hazard.layer.get_data(): # Run through all polygons and get unique names self.volcano_names.add(row[volcano_name_attribute]) # Find the target field name that has no conflict with the attribute # names in the hazard layer hazard_attribute_names = self.hazard.layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( self.target_field, hazard_attribute_names) # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer) # Extract relevant interpolated layer data features = interpolated_layer.get_data() self.init_report_var(radii) # Iterate the interpolated building layer for i in range(len(features)): hazard_value = features[i][self.hazard_zone_attribute] if not hazard_value: hazard_value = self._not_affected_value features[i][target_field] = hazard_value # Count affected buildings by usage type if available usage = features[i][self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.affected_buildings.keys(): affected = True self.classify_feature(hazard_value, usage, affected) self.reorder_dictionaries() # Adding 'km'. affected_building_keys = self.affected_buildings.keys() for key in affected_building_keys: self.affected_buildings[tr('Radius %.1f km' % key)] = \ self.affected_buildings.pop(key) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] for i, category_name in enumerate(category_names): style_class = dict() style_class['label'] = tr('Radius %s km') % tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': target_field, 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), 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 run(self, layers=None): """Run volcano point population evacuation Impact Function. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector point layer. * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ self.validate() self.prepare(layers) # Parameters radii = self.parameters['distance [km]'] name_attribute = self.parameters['volcano name attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure # Input checks if not hazard_layer.is_point_data: msg = ( 'Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(msg) data_table = hazard_layer.get_data() # Use concentric circles category_title = 'Radius' category_header = tr('Distance [km]') centers = hazard_layer.get_geometry() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points(centers, rad_m, category_title, data_table=data_table) # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in data_table: volcano_name_list.append(row[name_attribute]) volcano_names = '' for radius in volcano_name_list: volcano_names += '%s, ' % radius volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field ) # Initialise affected population per categories affected_population = {} for radius in rad_m: affected_population[radius] = 0 nan_warning = False if has_no_data(exposure_layer.get_data(nan=True)): nan_warning = True # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this category category = row[category_title] affected_population[category] += population # Count totals total_population = population_rounding( int(numpy.nansum(exposure_layer.get_data()))) # Count cumulative for each zone total_affected_population = 0 cumulative_affected_population = {} for radius in rad_m: population = int(affected_population.get(radius, 0)) total_affected_population += population cumulative_affected_population[radius] = total_affected_population minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map blank_cell = '' table_body = [ self.question, TableRow( [tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([ tr('People needing evacuation'), '%s' % format_int(population_rounding(total_affected_population)), blank_cell ], header=True), TableRow( [category_header, tr('Total'), tr('Cumulative')], header=True) ] for radius in rad_m: table_body.append( TableRow([ radius, format_int(population_rounding( affected_population[radius])), format_int( population_rounding( cumulative_affected_population[radius])) ])) table_body.extend([ TableRow( tr('Map shows the number of people affected in each of volcano ' 'hazard polygons.')) ]) total_needs = evacuated_population_needs(total_affected_population, 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() # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int(total_population), tr('People need evacuation if they are within the ' 'volcanic hazard zones.') ]) if nan_warning: table_body.extend([ tr('The population layer contained `no data`. This missing ' 'data was carried through to the impact layer.'), tr('`No data` values in the impact layer were treated as 0 ' 'when counting the affected or total population.') ]) impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if total_affected_population == 0: table_body = [ self.question, TableRow([ tr('People needing evacuation'), '%s' % format_int(total_affected_population), blank_cell ], header=True) ] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(covered_exposure_layer.get_data().flat[:], 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 == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People affected by the buffered point volcano') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People affected by the buffered point volcano'), 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, 'total_needs': total_needs }, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self, layers=None): """Run classified population evacuation Impact Function. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to each hazard zones. :returns: Map of population exposed to each hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer """ self.validate() self.prepare(layers) # Parameters hazard_zone_attribute = self.parameters['hazard zone attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure # Input checks if not hazard_layer.is_polygon_data: msg = ('Input hazard must be a polygon layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(msg) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute not in hazard_layer.get_attribute_names(): msg = ('Hazard data %s does not contain expected hazard ' 'zone attribute "%s". Please change it in the option. ' % (hazard_layer.get_name(), hazard_zone_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Get unique hazard zones from the layer attribute self.hazard_zones = list( set(hazard_layer.get_data(hazard_zone_attribute))) # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Interpolated layer represents grid cell that lies in the polygon interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field ) # Initialise total population affected by each hazard zone affected_population = {} for hazard_zone in self.hazard_zones: affected_population[hazard_zone] = 0 # Count total affected population per hazard zone for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this hazard zone hazard_zone = row[hazard_zone_attribute] affected_population[hazard_zone] += population # Count total population from exposure layer total_population = population_rounding( int(numpy.nansum(exposure_layer.get_data()))) # Count total affected population total_affected_population = reduce( lambda x, y: x + y, [population for population in affected_population.values()]) # check for zero impact if total_affected_population == 0: table_body = [ self.question, TableRow( [tr('People impacted'), '%s' % format_int(total_affected_population)], header=True)] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Generate impact report for the pdf map blank_cell = '' table_body = [ self.question, TableRow( [ tr('People impacted'), '%s' % format_int( population_rounding(total_affected_population)), blank_cell], header=True)] for hazard_zone in self.hazard_zones: table_body.append( TableRow( [ hazard_zone, format_int( population_rounding( affected_population[hazard_zone])) ])) table_body.extend([ TableRow(tr( 'Map shows the number of people impacted in each of the ' 'hazard zones.'))]) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population: %s in the exposure layer') % format_int( total_population), tr('"nodata" values in the exposure layer are treated as 0 ' 'when counting the affected or total population')] ) impact_summary = Table(table_body).toNewlineFreeString() # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes( covered_exposure_layer.get_data().flat[:], 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 == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People impacted by each hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People impacted by each hazard zone'), 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}, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self, layers): """Risk plugin for volcano hazard on building/structure. Counts number of building exposed to each volcano hazard zones. :param layers: List of layers expected to contain. * hazard_layer: Hazard layer of volcano * exposure_layer: Vector layer of structure data on the same grid as hazard_layer :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Volcano hazard layer exposure_layer = get_exposure_layer(layers) is_point_data = False question = get_question( hazard_layer.get_name(), exposure_layer.get_name(), self) # Input checks if not hazard_layer.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s ' 'with layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data): raise Exception(msg) if hazard_layer.is_point_data: # Use concentric circles radii = self.parameters['distances [km]'] is_point_data = True centers = hazard_layer.get_geometry() attributes = hazard_layer.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points(centers, rad_m, data_table=attributes) # To check category_title = 'Radius' category_names = rad_m name_attribute = 'NAME' # As in e.g. the Smithsonian dataset else: # Use hazard map category_title = 'KRB' # FIXME (Ole): Change to English and use translation system category_names = ['Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I'] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] for row in hazard_layer.get_data(): # Run through all polygons and get unique names volcano_name_list.append(row[name_attribute]) volcano_names = '' for name in volcano_name_list: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Check if category_title exists in hazard_layer if not category_title in hazard_layer.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (hazard_layer.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Find the target field name that has no conflict with default # target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer) # Initialise attributes of output dataset with all attributes # from input polygon and a building count of zero new_data_table = hazard_layer.get_data() categories = {} for row in new_data_table: row[self.target_field] = 0 category = row[category_title] categories[category] = 0 # Count impacted building per polygon and total for row in interpolated_layer.get_data(): # Update building count for associated polygon poly_id = row['polygon_id'] if poly_id is not None: new_data_table[poly_id][self.target_field] += 1 # Update building count for each category category = new_data_table[poly_id][category_title] categories[category] += 1 # Count totals total = len(exposure_layer) # Generate simple impact report blank_cell = '' table_body = [question, TableRow([tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('Distance [km]'), tr('Total'), tr('Cumulative')], header=True)] cumulative = 0 for name in category_names: # prevent key error count = categories.get(name, 0) cumulative += count if is_point_data: name = int(name) / 1000 table_body.append(TableRow([name, format_int(count), format_int(cumulative)])) table_body.append(TableRow(tr('Map shows buildings affected in ' 'each of volcano hazard polygons.'))) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend([TableRow(tr('Notes'), header=True), tr('Total number of buildings %s in the viewable ' 'area') % format_int(total), tr('Only buildings available in OpenStreetMap ' 'are considered.')]) impact_summary = Table(table_body).toNewlineFreeString() building_counts = [x[self.target_field] for x in new_data_table] if max(building_counts) == 0 == min(building_counts): table_body = [ question, TableRow([tr('Number of buildings affected'), '%s' % format_int(cumulative), blank_cell], header=True)] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] # Create Classes classes = create_classes(building_counts, len(colours)) # Create Interval Classes interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 0: style_class['min'] = 0 else: style_class['min'] = classes[i - 1] style_class['transparency'] = 30 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('Buildings affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return impact_layer = Vector( data=new_data_table, projection=hazard_layer.get_projection(), geometry=hazard_layer.get_geometry(as_geometry_objects=True), name=tr('Buildings affected by volcanic hazard zone'), 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}, style_info=style_info) return impact_layer
def run(self, layers): """Risk plugin for volcano population evacuation. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer of volcano impact zones * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Volcano KRB exposure_layer = get_exposure_layer(layers) question = get_question( hazard_layer.get_name(), exposure_layer.get_name(), self) # Input checks if not hazard_layer.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data): raise Exception(msg) data_table = hazard_layer.get_data() if hazard_layer.is_point_data: # Use concentric circles radii = self.parameters['distance [km]'] centers = hazard_layer.get_geometry() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points(centers, rad_m, data_table=data_table) category_title = 'Radius' category_header = tr('Distance [km]') category_names = radii name_attribute = 'NAME' # As in e.g. the Smithsonian dataset else: # Use hazard map category_title = 'KRB' category_header = tr('Category') # FIXME (Ole): Change to English and use translation system category_names = ['Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I'] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in data_table: volcano_name_list.append(row[name_attribute]) volcano_names = '' for name in volcano_name_list: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Check if category_title exists in hazard_layer if not category_title in hazard_layer.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (hazard_layer.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field) # Initialise data_table of output dataset with all data_table # from input polygon and a population count of zero new_data_table = hazard_layer.get_data() categories = {} for row in new_data_table: row[self.target_field] = 0 category = row[category_title] categories[category] = 0 # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = float(row[self.target_field]) # Update population count for associated polygon poly_id = row['polygon_id'] new_data_table[poly_id][self.target_field] += population # Update population count for each category category = new_data_table[poly_id][category_title] categories[category] += population # Count totals total = int(numpy.sum(exposure_layer.get_data(nan=0))) # Don't show digits less than a 1000 total = round_thousand(total) # Count number and cumulative for each zone cumulative = 0 all_categories_population = {} all_categories_cumulative = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name # prevent key error population = int(categories.get(key, 0)) population = round_thousand(population) cumulative += population cumulative = round_thousand(cumulative) all_categories_population[name] = population all_categories_cumulative[name] = cumulative # Use final accumulation as total number needing evacuation evacuated = cumulative # Calculate estimated minimum needs minimum_needs = self.parameters['minimum needs'] total_needs = evacuated_population_weekly_needs( evacuated, minimum_needs) # Generate impact report for the pdf map blank_cell = '' table_body = [question, TableRow([tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True), TableRow([category_header, tr('Total'), tr('Cumulative')], header=True)] for name in category_names: table_body.append( TableRow([name, format_int(all_categories_population[name]), format_int(all_categories_cumulative[name])])) table_body.extend([ TableRow(tr( 'Map shows the number of people affected in each of volcano ' 'hazard polygons.')), TableRow( [tr('Needs per week'), tr('Total'), blank_cell], header=True), [tr('Rice [kg]'), format_int(total_needs['rice']), blank_cell], [ tr('Drinking Water [l]'), format_int(total_needs['drinking_water']), blank_cell], [tr('Clean Water [l]'), format_int(total_needs['water']), blank_cell], [tr('Family Kits'), format_int(total_needs['family_kits']), blank_cell], [tr('Toilets'), format_int(total_needs['toilets']), blank_cell]]) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int( total), tr('People need evacuation if they are within the ' 'volcanic hazard zones.')]) population_counts = [x[self.target_field] for x in new_data_table] impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if numpy.nanmax(population_counts) == 0 == numpy.nanmin( population_counts): table_body = [ question, TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True)] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] 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 = 100 style_class['min'] = 0 else: transparency = 30 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 volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people)') legend_title = tr('Population count') # Create vector layer and return impact_layer = Vector( data=new_data_table, projection=hazard_layer.get_projection(), geometry=hazard_layer.get_geometry(as_geometry_objects=True), name=tr('People affected by volcanic hazard zone'), 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}, style_info=style_info) return impact_layer
def run(self): """Classified hazard impact to buildings (e.g. from Open Street Map). """ # Value from layer's keywords structure_class_field = self.exposure.keyword('structure_class_field') try: exposure_value_mapping = self.exposure.keyword('value_mapping') except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} self.hazard_class_mapping = self.hazard.keyword('value_map') keys = [x['key'] for x in generic_raster_hazard_classes['classes']] names = [x['name'] for x in generic_raster_hazard_classes['classes']] classes = OrderedDict() for i in range(len(keys)): classes[keys[i]] = names[i] # Determine attribute name for hazard class hazard_class_attribute = get_non_conflicting_attribute_name( 'haz_class', [x.keys() for x in self.exposure.layer.data][0]) interpolated_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_class_attribute, mode='constant') # Extract relevant exposure data attributes = interpolated_result.get_data() # Number of building in the interpolated layer buildings_total = len(interpolated_result) # Inverse the order from low to high self.init_report_var(classes.values()[::-1]) for i in range(buildings_total): # Get the usage of the building usage = attributes[i][structure_class_field] usage = main_type(usage, exposure_value_mapping) # Initialize value as Not affected attributes[i][self.target_field] = tr('Not affected') attributes[i][self.affected_field] = 0 # Get the hazard level of the building level = float(attributes[i][hazard_class_attribute]) level = float(numpy_round(level)) # Find the class according the building's level for k, v in self.hazard_class_mapping.items(): if level in v: impact_class = classes[k] # Set the impact level attributes[i][self.target_field] = impact_class # Set to affected attributes[i][self.affected_field] = 1 break # Count affected buildings by type self.classify_feature(attributes[i][self.target_field], usage, bool(attributes[i][self.affected_field])) self.reorder_dictionaries() # Create style # Low, Medium and High are translated in the attribute table. # "Not affected" is not translated in the attribute table. style_classes = [ dict(label=tr('Not Affected'), value='Not affected', colour='#1EFC7C', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('Low'), value=tr('Low hazard zone'), colour='#EBF442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('Medium'), value=tr('Medium hazard zone'), colour='#F4A442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('High'), value=tr('High hazard zone'), colour='#F31A1C', transparency=0, size=2, border_color='#969696', border_width=0.2), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') LOGGER.debug('target field : ' + self.target_field) impact_data = self.generate_data() extra_keywords = { 'target_field': self.affected_field, 'map_title': self.map_title(), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title'), 'buildings_total': buildings_total, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create impact layer and return impact_layer = Vector(data=attributes, projection=self.exposure.layer.get_projection(), geometry=self.exposure.layer.get_geometry(), 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 run(self, layers=None): """Counts number of building exposed to each volcano hazard zones. :param layers: List of layers expected to contain. * hazard_layer: Hazard layer of volcano * exposure_layer: Vector layer of structure data on the same grid as hazard_layer :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ self.validate() self.prepare(layers) # Target Field target_field = 'zone' # Hazard Zone Attribute hazard_zone_attribute = 'radius' # Not Affected Value not_affected_value = 'Not Affected' # Parameters radii = self.parameters['distances [km]'] volcano_name_attribute = self.parameters['volcano name attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard # Volcano hazard layer exposure_layer = self.exposure # Building exposure layer # Input checks if not hazard_layer.is_point_data: message = ( 'Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % ( hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(message) # Make hazard layer by buffering the point centers = hazard_layer.get_geometry() features = hazard_layer.get_data() radii_meter = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points( centers, radii_meter, hazard_zone_attribute, data_table=features) # Category names for the impact zone category_names = radii_meter category_names.append(not_affected_value) # Get names of volcanoes considered if volcano_name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = set() for row in hazard_layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[volcano_name_attribute]) self.volcano_names = ', '.join(volcano_name_list) # Find the target field name that has no conflict with the attribute # names in the hazard layer hazard_attribute_names = hazard_layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( target_field, hazard_attribute_names) # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=None) # Extract relevant interpolated layer data attribute_names = interpolated_layer.get_attribute_names() features = interpolated_layer.get_data() self.buildings = {} self.affected_buildings = OrderedDict() for category in radii_meter: self.affected_buildings[category] = {} # Iterate the interpolated building layer for i in range(len(features)): hazard_value = features[i][hazard_zone_attribute] if not hazard_value: hazard_value = not_affected_value features[i][target_field] = hazard_value # Count affected buildings by usage type if available usage = get_osm_building_usage(attribute_names, features[i]) if usage is [None, 'NULL', 'null', 'Null', 0]: usage = tr('Unknown') if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][ usage] = OrderedDict([ (tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 if hazard_value in self.affected_buildings.keys(): self.affected_buildings[hazard_value][usage][ tr('Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() # Generate simple impact report impact_summary = impact_table = self.generate_html_report() # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] i = 0 for category_name in category_names: style_class = dict() style_class['label'] = tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] i += 1 style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic buffered point') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=tr('Buildings affected by volcanic buffered point'), keywords={'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title}, style_info=style_info) self._impact = impact_layer return impact_layer
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 run(self, layers=None): """Run volcano point population evacuation Impact Function. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector point layer. * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ self.validate() self.prepare(layers) # Parameters radii = self.parameters['distance [km]'] name_attribute = self.parameters['volcano name attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure # Input checks if not hazard_layer.is_point_data: msg = ( 'Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(msg) data_table = hazard_layer.get_data() # Use concentric circles category_title = 'Radius' category_header = tr('Distance [km]') centers = hazard_layer.get_geometry() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points( centers, rad_m, category_title, data_table=data_table) # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in data_table: volcano_name_list.append(row[name_attribute]) volcano_names = '' for radius in volcano_name_list: volcano_names += '%s, ' % radius volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field ) # Initialise affected population per categories affected_population = {} for radius in rad_m: affected_population[radius] = 0 nan_warning = False if has_no_data(exposure_layer.get_data(nan=True)): nan_warning = True # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this category category = row[category_title] affected_population[category] += population # Count totals total_population = population_rounding( int(numpy.nansum(exposure_layer.get_data()))) # Count cumulative for each zone total_affected_population = 0 cumulative_affected_population = {} for radius in rad_m: population = int(affected_population.get(radius, 0)) total_affected_population += population cumulative_affected_population[radius] = total_affected_population minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map blank_cell = '' table_body = [ self.question, TableRow( [tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow( [tr('People needing evacuation'), '%s' % format_int( population_rounding(total_affected_population)), blank_cell], header=True), TableRow( [category_header, tr('Total'), tr('Cumulative')], header=True)] for radius in rad_m: table_body.append( TableRow( [radius, format_int( population_rounding( affected_population[radius])), format_int( population_rounding( cumulative_affected_population[radius]))])) table_body.extend([ TableRow(tr( 'Map shows the number of people affected in each of volcano ' 'hazard polygons.'))]) total_needs = evacuated_population_needs( total_affected_population, 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() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int( total_population), tr('People need evacuation if they are within the ' 'volcanic hazard zones.')]) if nan_warning: table_body.extend([ tr('The population layer contained `no data`. This missing ' 'data was carried through to the impact layer.'), tr('`No data` values in the impact layer were treated as 0 ' 'when counting the affected or total population.') ]) impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if total_affected_population == 0: table_body = [ self.question, TableRow( [tr('People needing evacuation'), '%s' % format_int(total_affected_population), blank_cell], header=True)] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes( covered_exposure_layer.get_data().flat[:], 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 == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People affected by the buffered point volcano') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People affected by the buffered point volcano'), 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, 'total_needs': total_needs}, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self): """Classified hazard impact to buildings (e.g. from Open Street Map). """ # Value from layer's keywords structure_class_field = self.exposure.keyword("structure_class_field") try: exposure_value_mapping = self.exposure.keyword("value_mapping") except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} self.hazard_class_mapping = self.hazard.keyword("value_map") keys = [x["key"] for x in generic_raster_hazard_classes["classes"]] names = [x["name"] for x in generic_raster_hazard_classes["classes"]] classes = OrderedDict() for i in range(len(keys)): classes[keys[i]] = names[i] # Determine attribute name for hazard class hazard_class_attribute = get_non_conflicting_attribute_name( "haz_class", [x.keys() for x in self.exposure.layer.data][0] ) interpolated_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_class_attribute, mode="constant" ) # Extract relevant exposure data attributes = interpolated_result.get_data() # Number of building in the interpolated layer buildings_total = len(interpolated_result) # Inverse the order from low to high self.init_report_var(classes.values()[::-1]) for i in range(buildings_total): # Get the usage of the building usage = attributes[i][structure_class_field] usage = main_type(usage, exposure_value_mapping) # Initialize value as Not affected attributes[i][self.target_field] = tr("Not affected") attributes[i][self.affected_field] = 0 # Get the hazard level of the building level = float(attributes[i][hazard_class_attribute]) level = float(numpy_round(level)) # Find the class according the building's level for k, v in self.hazard_class_mapping.items(): if level in v: impact_class = classes[k] # Set the impact level attributes[i][self.target_field] = impact_class # Set to affected attributes[i][self.affected_field] = 1 break # Count affected buildings by type self.classify_feature(attributes[i][self.target_field], usage, bool(attributes[i][self.affected_field])) self.reorder_dictionaries() # Create style # Low, Medium and High are translated in the attribute table. # "Not affected" is not translated in the attribute table. style_classes = [ dict( label=tr("Not Affected"), value="Not affected", colour="#1EFC7C", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("Low"), value=tr("Low hazard zone"), colour="#EBF442", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("Medium"), value=tr("Medium hazard zone"), colour="#F4A442", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("High"), value=tr("High hazard zone"), colour="#F31A1C", transparency=0, size=2, border_color="#969696", border_width=0.2, ), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type="categorizedSymbol") LOGGER.debug("target field : " + self.target_field) impact_data = self.generate_data() extra_keywords = { "target_field": self.affected_field, "map_title": self.map_title(), "legend_units": self.metadata().key("legend_units"), "legend_title": self.metadata().key("legend_title"), "buildings_total": buildings_total, "buildings_affected": self.total_affected_buildings, } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create impact layer and return impact_layer = Vector( data=attributes, projection=self.exposure.layer.get_projection(), geometry=self.exposure.layer.get_geometry(), 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 run(self, layers=None): """Counts number of building exposed to each volcano hazard zones. :param layers: List of layers expected to contain. * hazard_layer: Hazard layer of volcano * exposure_layer: Vector layer of structure data on the same grid as hazard_layer :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ self.validate() self.prepare(layers) # Target Field target_field = 'zone' # Hazard Zone Attribute hazard_zone_attribute = 'radius' # Not Affected Value not_affected_value = 'Not Affected' # Parameters radii = self.parameters['distances [km]'] volcano_name_attribute = self.parameters['volcano name attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard # Volcano hazard layer exposure_layer = self.exposure # Building exposure layer # Input checks if not hazard_layer.is_point_data: message = ( 'Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(message) # Make hazard layer by buffering the point centers = hazard_layer.get_geometry() features = hazard_layer.get_data() radii_meter = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points(centers, radii_meter, hazard_zone_attribute, data_table=features) # Category names for the impact zone category_names = radii_meter category_names.append(not_affected_value) # Get names of volcanoes considered if volcano_name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = set() for row in hazard_layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[volcano_name_attribute]) self.volcano_names = ', '.join(volcano_name_list) else: self.volcano_names = tr('Not specified in data') # Find the target field name that has no conflict with the attribute # names in the hazard layer hazard_attribute_names = hazard_layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( target_field, hazard_attribute_names) # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=None) # Extract relevant interpolated layer data attribute_names = interpolated_layer.get_attribute_names() features = interpolated_layer.get_data() self.buildings = {} self.affected_buildings = OrderedDict() for category in radii_meter: self.affected_buildings[category] = {} # Iterate the interpolated building layer for i in range(len(features)): hazard_value = features[i][hazard_zone_attribute] if not hazard_value: hazard_value = not_affected_value features[i][target_field] = hazard_value # Count affected buildings by usage type if available usage = get_osm_building_usage(attribute_names, features[i]) if usage is [None, 'NULL', 'null', 'Null', 0]: usage = tr('Unknown') if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][usage] = OrderedDict([ (tr('Buildings Affected'), 0) ]) self.buildings[usage] += 1 if hazard_value in self.affected_buildings.keys(): self.affected_buildings[hazard_value][usage][tr( 'Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() # Generate simple impact report impact_summary = impact_table = self.generate_html_report() # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] i = 0 for category_name in category_names: style_class = dict() style_class['label'] = tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] i += 1 style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic buffered point') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=tr('Buildings affected by volcanic buffered point'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title }, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self): """Counts number of building exposed to each volcano hazard zones. :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # Hazard Zone Attribute hazard_zone_attribute = 'radius' # Parameters radii = self.parameters['distances'].value # Get parameters from layer's keywords volcano_name_attribute = self.hazard.keyword('volcano_name_field') # Try to get the value from keyword, if not exist, it will not fail, # but use the old get_osm_building_usage try: self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') except KeywordNotFoundError: self.exposure_class_attribute = None # Input checks if not self.hazard.layer.is_point_data: message = ( 'Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % ( self.hazard.name, self.hazard.layer.get_geometry_name())) raise Exception(message) # Make hazard layer by buffering the point centers = self.hazard.layer.get_geometry() features = self.hazard.layer.get_data() hazard_layer = buffer_points( centers, radii, hazard_zone_attribute, data_table=features) # Category names for the impact zone category_names = radii # In kilometers self._affected_categories_volcano = [ tr('Radius %.1f km') % key for key in radii[::]] # Get names of volcanoes considered if volcano_name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = set() for row in hazard_layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[volcano_name_attribute]) self.volcano_names = ', '.join(volcano_name_list) # Find the target field name that has no conflict with the attribute # names in the hazard layer hazard_attribute_names = hazard_layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( self.target_field, hazard_attribute_names) # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, self.exposure.layer) # Extract relevant interpolated layer data attribute_names = interpolated_layer.get_attribute_names() features = interpolated_layer.get_data() self.buildings = {} self.affected_buildings = OrderedDict() for category in radii: self.affected_buildings[category] = {} # Iterate the interpolated building layer for i in range(len(features)): hazard_value = features[i][hazard_zone_attribute] if not hazard_value: hazard_value = self._not_affected_value features[i][target_field] = hazard_value # Count affected buildings by usage type if available if (self.exposure_class_attribute and self.exposure_class_attribute in attribute_names): usage = features[i][self.exposure_class_attribute] else: usage = get_osm_building_usage(attribute_names, features[i]) if usage is [None, 'NULL', 'null', 'Null', 0]: usage = tr('Unknown') if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][ usage] = OrderedDict([ (tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 if hazard_value in self.affected_buildings.keys(): self.affected_buildings[hazard_value][usage][ tr('Buildings Affected')] += 1 # Adding 'km' affected_building_keys = self.affected_buildings.keys() for key in affected_building_keys: self.affected_buildings[tr('Radius %.1f km' % key)] = \ self.affected_buildings.pop(key) # 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() # Generate simple impact report impact_summary = impact_table = self.html_report() # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] i = 0 for category_name in category_names: style_class = dict() style_class['label'] = tr('Radius %s km') % tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] i += 1 style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic buffered point') legend_title = tr('Building count') legend_units = tr('(building)') legend_notes = tr( 'Thousand separator is represented by %s' % get_thousand_separator()) extra_keywords = { 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=tr('Buildings affected by volcanic buffered point'), keywords=impact_layer_keywords, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self, layers=None): """Run classified population evacuation Impact Function. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to each hazard zones. :returns: Map of population exposed to each hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer """ self.validate() self.prepare(layers) # Parameters hazard_zone_attribute = self.parameters['hazard zone attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure # Input checks if not hazard_layer.is_polygon_data: msg = ('Input hazard must be a polygon layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(msg) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute not in hazard_layer.get_attribute_names(): msg = ('Hazard data %s does not contain expected hazard ' 'zone attribute "%s". Please change it in the option. ' % (hazard_layer.get_name(), hazard_zone_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Get unique hazard zones from the layer attribute self.hazard_zones = list( set(hazard_layer.get_data(hazard_zone_attribute))) # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Interpolated layer represents grid cell that lies in the polygon interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field ) # Initialise total population affected by each hazard zone affected_population = {} for hazard_zone in self.hazard_zones: affected_population[hazard_zone] = 0 # Count total affected population per hazard zone for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this hazard zone hazard_zone = row[hazard_zone_attribute] affected_population[hazard_zone] += population # Count total population from exposure layer total_population = population_rounding( int(numpy.nansum(exposure_layer.get_data()))) # Count total affected population total_affected_population = reduce( lambda x, y: x + y, [population for population in affected_population.values()]) # check for zero impact if total_affected_population == 0: table_body = [ self.question, TableRow([ tr('People impacted'), '%s' % format_int(total_affected_population) ], header=True) ] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Generate impact report for the pdf map blank_cell = '' table_body = [ self.question, TableRow([ tr('People impacted'), '%s' % format_int(population_rounding(total_affected_population)), blank_cell ], header=True) ] for hazard_zone in self.hazard_zones: table_body.append( TableRow([ hazard_zone, format_int( population_rounding(affected_population[hazard_zone])) ])) table_body.extend([ TableRow( tr('Map shows the number of people impacted in each of the ' 'hazard zones.')) ]) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population: %s in the exposure layer') % format_int(total_population), tr('"nodata" values in the exposure layer are treated as 0 ' 'when counting the affected or total population') ]) impact_summary = Table(table_body).toNewlineFreeString() # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(covered_exposure_layer.get_data().flat[:], 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 == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People impacted by each hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People impacted by each hazard zone'), 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 }, style_info=style_info) self._impact = impact_layer return impact_layer
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 run(self, layers): """Risk plugin for volcano hazard on building/structure. Counts number of building exposed to each volcano hazard zones. :param layers: List of layers expected to contain. * hazard_layer: Hazard layer of volcano * exposure_layer: Vector layer of structure data on the same grid as hazard_layer :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Parameters not_affected_value = self.parameters['Not affected value'] radii = self.parameters['distances [km]'] target_field = self.parameters['target field'] name_attribute = self.parameters['name attribute'] hazard_zone_attribute = self.parameters['hazard zone attribute'] # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Volcano hazard layer exposure_layer = get_exposure_layer(layers) # Building exposure layer # Get question question = get_question( hazard_layer.get_name(), exposure_layer.get_name(), self) # Input checks if not hazard_layer.is_vector: message = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(message) if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data): message = ( 'Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(message) if hazard_layer.is_point_data: # Use concentric circles centers = hazard_layer.get_geometry() attributes = hazard_layer.get_data() radii_meter = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points( centers, radii_meter, hazard_zone_attribute, data_table=attributes) # To check category_names = radii_meter else: # FIXME (Ole): Change to English and use translation system # FIXME (Ismail) : Or simply use the values from the hazard layer category_names = ['Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I'] category_names.append(not_affected_value) # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = set() for row in hazard_layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[name_attribute]) volcano_names = ', '.join(volcano_name_list) else: volcano_names = tr('Not specified in data') # Check if category_title exists in hazard_layer if hazard_zone_attribute not in hazard_layer.get_attribute_names(): message = ( 'Hazard data %s did not contain expected attribute %s ' % (hazard_layer.get_name(), hazard_zone_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Find the target field name that has no conflict with default # target attribute_names = hazard_layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( target_field, attribute_names) # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=None) # Extract relevant exposure data attribute_names = interpolated_layer.get_attribute_names() attribute_names_lower = [ attribute_name.lower() for attribute_name in attribute_names] attributes = interpolated_layer.get_data() interpolate_size = len(interpolated_layer) building_per_category = {} building_usages = [] other_sum = {} for category_name in category_names: building_per_category[category_name] = {} building_per_category[category_name]['total'] = 0 other_sum[category_name] = 0 # Building attribute that should be looked up to get the usage building_type_attributes = [ 'type', 'amenity', 'building_t', 'office', 'tourism', 'leisure', 'use', ] for i in range(interpolate_size): hazard_value = attributes[i][hazard_zone_attribute] if not hazard_value: hazard_value = not_affected_value attributes[i][target_field] = hazard_value if hazard_value in building_per_category.keys(): building_per_category[hazard_value]['total'] += 1 elif not hazard_value: building_per_category[not_affected_value]['total'] += 1 else: building_per_category[hazard_value] = {} building_per_category[hazard_value]['total'] = 1 # Count affected buildings by usage type if available usage = None for building_type_attribute in building_type_attributes: if ( building_type_attribute in attribute_names_lower and ( usage is None or usage == 0)): attribute_index = attribute_names_lower.index( building_type_attribute) field_name = attribute_names[attribute_index] usage = attributes[i][field_name] if ( 'building' in attribute_names_lower and ( usage is None or usage == 0)): attribute_index = attribute_names_lower.index('building') field_name = attribute_names[attribute_index] usage = attributes[i][field_name] if usage == 'yes': usage = 'building' if usage is None or usage == 0: usage = tr('unknown') if usage not in building_usages: building_usages.append(usage) for building in building_per_category.values(): building[usage] = 0 building_per_category[hazard_value][usage] += 1 # Generate simple impact report blank_cell = '' table_body = [question, TableRow([tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True)] table_headers = [tr('Building type')] table_headers += [tr(x) for x in category_names] table_headers += [tr('Total')] table_body += [TableRow(table_headers, header=True)] for building_usage in building_usages: building_usage_good = building_usage.replace('_', ' ') building_usage_good = building_usage_good.capitalize() building_sum = sum([ building_per_category[category_name][building_usage] for category_name in category_names ]) # Filter building type that has no less than 25 items if building_sum >= 25: row = [tr(building_usage_good)] building_sum = 0 for category_name in category_names: building_sub_sum = building_per_category[category_name][ building_usage] row.append(format_int(building_sub_sum)) building_sum += building_sub_sum row.append(format_int(building_sum)) table_body.append(row) else: for category_name in category_names: if category_name in other_sum.keys(): other_sum[category_name] += building_per_category[ category_name][building_usage] else: other_sum[category_name] = building_per_category[ category_name][building_usage] # Adding others building type to the report. other_row = [tr('Other')] other_building_total = 0 for category_name in category_names: other_building_sum = other_sum[category_name] other_row.append(format_int(other_building_sum)) other_building_total += other_building_sum other_row.append(format_int(other_building_total)) table_body.append(other_row) all_row = [tr('Total')] all_row += [format_int(building_per_category[category_name]['total']) for category_name in category_names] total = sum([building_per_category[category_name]['total'] for category_name in category_names]) all_row += [format_int(total)] table_body.append(TableRow(all_row, header=True)) table_body += [TableRow(tr('Map shows buildings affected in each of ' 'volcano hazard polygons.'))] impact_table = Table(table_body).toNewlineFreeString() impact_summary = impact_table # Extend impact report for on-screen display table_body.extend([TableRow(tr('Notes'), header=True), tr('Total number of buildings %s in the viewable ' 'area') % format_int(total), tr('Only buildings available in OpenStreetMap ' 'are considered.')]) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] i = 0 for category_name in category_names: style_class = dict() style_class['label'] = tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] i += 1 style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return impact_layer = Vector( data=attributes, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(as_geometry_objects=True), name=tr('Buildings affected by volcanic hazard zone'), keywords={'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title}, style_info=style_info) return impact_layer
def run(self, layers=None): """Risk plugin for classified polygon hazard on building/structure. Counts number of building exposed to each hazard zones. :param layers: List of layers expected to contain. * hazard_layer: Hazard layer * exposure_layer: Vector layer of structure data on the same grid as hazard_layer :returns: Map of building exposed to each hazard zones. Table with number of buildings affected :rtype: dict """ self.validate() self.prepare(layers) # Target Field target_field = 'zone' # Not affected string in the target field not_affected_value = 'Not Affected' # Parameters hazard_zone_attribute = self.parameters['hazard zone attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure # Input checks if not hazard_layer.is_polygon_data: message = ( 'Input hazard must be a polygon. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(message) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute not in hazard_layer.get_attribute_names(): message = ( 'Hazard data %s does not contain expected attribute %s ' % (hazard_layer.get_name(), hazard_zone_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Find the target field name that has no conflict with default # target attribute_names = hazard_layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( target_field, attribute_names) # Hazard zone categories from hazard layer self.hazard_zones = list( set(hazard_layer.get_data(hazard_zone_attribute))) self.buildings = {} self.affected_buildings = OrderedDict() for hazard_zone in self.hazard_zones: self.affected_buildings[hazard_zone] = {} # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=None) # Extract relevant interpolated data attribute_names = interpolated_layer.get_attribute_names() features = interpolated_layer.get_data() for i in range(len(features)): hazard_value = features[i][hazard_zone_attribute] if not hazard_value: hazard_value = not_affected_value features[i][target_field] = hazard_value usage = get_osm_building_usage(attribute_names, features[i]) if usage is None: usage = tr('Unknown') if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][usage] = OrderedDict( [(tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 if hazard_value in self.affected_buildings.keys(): self.affected_buildings[hazard_value][usage][ tr('Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() # Generate simple impact report impact_summary = impact_table = self.generate_html_report() # Create style categories = self.hazard_zones categories.append(not_affected_value) colours = color_ramp(len(categories)) style_classes = [] i = 0 for hazard_zone in self.hazard_zones: style_class = dict() style_class['label'] = tr(hazard_zone) style_class['transparency'] = 0 style_class['value'] = hazard_zone style_class['size'] = 1 style_class['colour'] = colours[i] style_classes.append(style_class) i += 1 # Override style info with new classes and name style_info = dict(target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by each hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=tr('Buildings affected by each hazard zone'), keywords={'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title}, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self, layers): """Risk plugin for volcano population evacuation. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer of volcano impact zones * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Volcano KRB exposure_layer = get_exposure_layer(layers) question = get_question( hazard_layer.get_name(), exposure_layer.get_name(), self) # Input checks if not hazard_layer.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data): raise Exception(msg) data_table = hazard_layer.get_data() if hazard_layer.is_point_data: # Use concentric circles radii = self.parameters['distance [km]'] category_title = 'Radius' category_header = tr('Distance [km]') category_names = radii name_attribute = 'NAME' # As in e.g. the Smithsonian dataset centers = hazard_layer.get_geometry() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points( centers, rad_m, category_title, data_table=data_table) else: # Use hazard map category_title = 'KRB' category_header = tr('Category') # FIXME (Ole): Change to English and use translation system category_names = ['Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I'] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in data_table: volcano_name_list.append(row[name_attribute]) volcano_names = '' for name in volcano_name_list: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Check if category_title exists in hazard_layer if category_title not in hazard_layer.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (hazard_layer.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field) # Initialise data_table of output dataset with all data_table # from input polygon and a population count of zero new_data_table = hazard_layer.get_data() categories = {} for row in new_data_table: row[self.target_field] = 0 category = row[category_title] categories[category] = 0 # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = float(row[self.target_field]) # Update population count for associated polygon poly_id = row['polygon_id'] new_data_table[poly_id][self.target_field] += population # Update population count for each category category = new_data_table[poly_id][category_title] categories[category] += population # Count totals total_population = population_rounding( int(numpy.sum(exposure_layer.get_data(nan=0)))) # Count number and cumulative for each zone cumulative = 0 all_categories_population = {} all_categories_cumulative = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name # prevent key error population = int(categories.get(key, 0)) cumulative += population # I'm not sure whether this is the best place to apply rounding? all_categories_population[name] = population_rounding(population) all_categories_cumulative[name] = population_rounding(cumulative) # Use final accumulation as total number needing evacuation evacuated = population_rounding(cumulative) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map blank_cell = '' table_body = [question, TableRow([tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True), TableRow([category_header, tr('Total'), tr('Cumulative')], header=True)] for name in category_names: table_body.append( TableRow([name, format_int(all_categories_population[name]), format_int(all_categories_cumulative[name])])) table_body.extend([ TableRow(tr( 'Map shows the number of people affected in each of volcano ' 'hazard polygons.'))]) 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() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int( total_population), tr('People need evacuation if they are within the ' 'volcanic hazard zones.')]) population_counts = [x[self.target_field] for x in new_data_table] impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if numpy.nanmax(population_counts) == 0 == numpy.nanmin( population_counts): table_body = [ question, TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True)] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] 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 = 100 style_class['min'] = 0 else: transparency = 30 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 volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Vector( data=new_data_table, projection=hazard_layer.get_projection(), geometry=hazard_layer.get_geometry(as_geometry_objects=True), name=tr('People affected by volcanic hazard zone'), 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, 'total_needs': total_needs}, style_info=style_info) return impact_layer