def get_geometry(self, copy=False, as_geometry_objects=False): """Return geometry for vector layer. Depending on the feature type, geometry is ============= =========== geometry type output type ============= =========== point list of 2x1 array of longitudes and latitudes) line list of arrays of coordinates polygon list of arrays of coordinates ============= =========== Optional boolean argument as_geometry_objects will change the return value to a list of geometry objects rather than a list of arrays. This currently only applies to polygon geometries """ if copy: geometry = copy_module.deepcopy(self.geometry) else: geometry = self.geometry if self.is_polygon_data: if not as_geometry_objects: geometry = [p.outer_ring for p in geometry] else: if as_geometry_objects: msg = ('Argument as_geometry_objects can currently ' 'be True only for polygon data') raise InaSAFEError(msg) return geometry
def interpolate_polygon_vector(source, target, layer_name=None, attribute_name=None): """Interpolate from polygon vector layer to vector data Args: * source: Vector data set (polygon) * target: Vector data set (points or polygons) - TBA also lines * layer_name: Optional name of returned interpolated layer. If None the name of target is used for the returned layer. * attribute_name: Name for new attribute. If None (default) the name of source is used Output I: Vector data set; points located as target with values interpolated from source Note: If target geometry is polygon, data will be interpolated to its centroids and the output is a point data set. """ # Input checks verify(source.is_vector) verify(target.is_vector) verify(source.is_polygon_data) if target.is_point_data: R = interpolate_polygon_points(source, target, layer_name=layer_name, attribute_name=attribute_name) elif target.is_line_data: R = interpolate_polygon_lines(source, target, layer_name=layer_name, attribute_name=attribute_name) elif target.is_polygon_data: # Use polygon centroids X = convert_polygons_to_centroids(target) P = interpolate_polygon_points(source, X, layer_name=layer_name, attribute_name=attribute_name) # In case of polygon data, restore the polygon geometry # Do this setting the geometry of the returned set to # that of the original polygon R = Vector(data=P.get_data(), projection=P.get_projection(), geometry=X.get_geometry(), name=P.get_name()) else: msg = ('Unknown datatype for polygon2vector interpolation: ' 'I got %s' % str(target)) raise InaSAFEError(msg) # Return interpolated vector layer return R
def interpolate_raster_vector(source, target, layer_name=None, attribute_name=None, mode='linear'): """Interpolate from raster layer to vector data Args: * source: Raster data set (grid) * target: Vector data set (points or polygons) * layer_name: Optional name of returned interpolated layer. If None the name of V is used for the returned layer. * attribute_name: Name for new attribute. If None (default) the name of R is used Returns: I: Vector data set; points located as target with values interpolated from source Note: If target geometry is polygon, data will be interpolated to its centroids and the output is a point data set. """ # Input checks verify(source.is_raster) verify(target.is_vector) if target.is_point_data: # Interpolate from raster to point data R = interpolate_raster_vector_points(source, target, layer_name=layer_name, attribute_name=attribute_name, mode=mode) # elif target.is_line_data: # TBA - issue https://github.com/AIFDR/inasafe/issues/36 # elif target.is_polygon_data: # Use centroids, in case of polygons P = convert_polygons_to_centroids(target) R = interpolate_raster_vector_points(source, P, layer_name=layer_name, attribute_name=attribute_name, mode=mode) # In case of polygon data, restore the polygon geometry # Do this setting the geometry of the returned set to # that of the original polygon R = Vector(data=R.get_data(), projection=R.get_projection(), geometry=target.get_geometry(), name=R.get_name()) else: msg = ('Unknown datatype for raster2vector interpolation: ' 'I got %s' % str(target)) raise InaSAFEError(msg) # Return interpolated vector layer return R
def interpolate_raster_vector_points(source, target, layer_name=None, attribute_name=None, mode='linear'): """Interpolate from raster layer to point data Args: * source: Raster data set (grid) * 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. * attribute_name: Name for new attribute. If None (default) the name of layer source is used * mode: 'linear' or 'constant' - determines whether interpolation from grid to points should be bilinear or piecewise constant Output I: Vector data set; points located as target with values interpolated from source """ msg = ('There are no data points to interpolate to. Perhaps zoom out ' 'and try again') verify(len(target) > 0, msg) # Input checks verify(source.is_raster) verify(target.is_vector) verify(target.is_point_data) # Get raster data and corresponding x and y axes A = source.get_data(nan=True) longitudes, latitudes = source.get_geometry() verify(len(longitudes) == A.shape[1]) verify(len(latitudes) == A.shape[0]) # Get vector point geometry as Nx2 array coordinates = numpy.array(target.get_geometry(), dtype='d', copy=False) # Get original attributes attributes = target.get_data() # Create new attribute and interpolate try: values = interpolate_raster(longitudes, latitudes, A, coordinates, mode=mode) except (BoundsError, InaSAFEError), e: msg = (tr('Could not interpolate from raster layer %(raster)s to ' 'vector layer %(vector)s. Error message: %(error)s') % { 'raster': source.get_name(), 'vector': target.get_name(), 'error': str(e) }) raise InaSAFEError(msg)
def get_extrema(self, attribute=None): """Get min and max values from specified attribute Return min, max """ if attribute is None: msg = ('Valid attribute name must be specified in get_extrema ' 'for vector layers. I got None.') raise InaSAFEError(msg) x = self.get_data(attribute) return min(x), max(x)
def interpolate_raster_raster(source, target): """Check for alignment and returns target layer as is """ if source.get_geotransform() != target.get_geotransform(): msg = ('Intergrid interpolation not implemented here. ' 'Make sure rasters are aligned and sampled to ' 'the same resolution') raise InaSAFEError(msg) else: # Rasters are aligned, no need to interpolate return target
def in_and_outside_polygon(points, polygon, closed=True, holes=None, check_input=True): """Separate a list of points into two sets inside and outside a polygon :param points: (tuple, list or array) of coordinates :param polygon: list or Nx2 array of polygon vertices :param closed: Set to True if points on boundary are considered to be 'inside' polygon :param holes: list of polygons representing holes. Points inside either of these are considered outside polygon :param check_input: Allows faster execution if set to False Output: inside: Indices of points inside the polygon outside: Indices of points outside the polygon See separate_points_by_polygon for more documentation """ # Get separation by outer_ring inside, outside = separate_points_by_polygon(points, polygon, closed=closed, check_input=check_input) # Take care of holes if holes is not None: msg = ('Argument holes must be a list of polygons, ' 'I got %s' % holes) if not isinstance(holes, list): raise InaSAFEError(msg) for hole in holes: in_hole, out_hole = separate_points_by_polygon(points[inside], hole, closed=not closed, check_input=True) in_hole = inside[in_hole] # Inside hole inside = inside[out_hole] # Inside outer_ring but outside hole # Add holde indices to outside outside = numpy.concatenate((outside, in_hole)) return inside, outside
def check_inputs(x, y, Z, points, mode, bounds_error): """Check inputs for interpolate2d function """ msg = ('Only mode "linear" and "constant" are implemented. ' 'I got "%s"' % mode) if mode not in ['linear', 'constant']: raise InaSAFEError(msg) x = numpy.array(x) try: y = numpy.array(y) except Exception, e: msg = ('Input vector y could not be converted to numpy array: ' '%s' % str(e)) raise Exception(msg)
def validate_mode(mode): """Validate that the mode is an allowable value. :param mode: Determines the interpolation order. Options are: * 'constant' - piecewise constant nearest neighbour interpolation * 'linear' - bilinear interpolation using the two nearest \ neighbours (default) :type mode: str :raises: InaSAFEError """ msg = ('Only mode "linear" and "constant" are implemented. I got "%s"' % mode) if mode not in ['linear', 'constant']: raise InaSAFEError(msg)
def get_extrema(self, attribute=None): """Get min and max values from specified attribute :param attribute: Specify an attribute name of which to return data. :type attribute: str :raises: InaSAFEError :returns: minimum and maximum attribute values :rtype: """ if attribute is None: msg = ('Valid attribute name must be specified in get_extrema ' 'for vector layers. I got None.') raise InaSAFEError(msg) x = self.get_data(attribute) return min(x), max(x)
def get_resolution(self, isotropic=False, native=False): """Get raster resolution as a 2-tuple (resx, resy) Args: * isotropic: If True, verify that dx == dy and return dx If False return 2-tuple (dx, dy) * native: Optional flag. If True, return native resolution if available. Otherwise return actual. """ # Get actual resolution first try: res = geotransform_to_resolution(self.geotransform, isotropic=isotropic) except Exception, e: msg = ('Resolution for layer %s could not be obtained: %s ' % (self.get_name(), str(e))) raise InaSAFEError(msg)
def check_geotransform(geotransform): """Check that geotransform is valid Args * geotransform: GDAL geotransform (6-tuple). (top left x, w-e pixel resolution, rotation, top left y, rotation, n-s pixel resolution). See e.g. http://www.gdal.org/gdal_tutorial.html Note This assumes that the spatial reference uses geographic coordinaties, so will not work for projected coordinate systems. """ msg = ('Supplied geotransform must be a tuple with ' '6 numbers. I got %s' % str(geotransform)) verify(len(geotransform) == 6, msg) for x in geotransform: try: float(x) except TypeError: raise InaSAFEError(msg) # Check longitude msg = ('Element in 0 (first) geotransform must be a valid ' 'longitude. I got %s' % geotransform[0]) verify(-180 <= geotransform[0] <= 180, msg) # Check latitude msg = ('Element 3 (fourth) in geotransform must be a valid ' 'latitude. I got %s' % geotransform[3]) verify(-90 <= geotransform[3] <= 90, msg) # Check cell size msg = ('Element 1 (second) in geotransform must be a positive ' 'number. I got %s' % geotransform[1]) verify(geotransform[1] > 0, msg) msg = ('Element 5 (sixth) in geotransform must be a negative ' 'number. I got %s' % geotransform[1]) verify(geotransform[5] < 0, msg)
def get_resolution(self, isotropic=False, native=False, rtol=1.0e-4, atol=1.0e-8): """Get raster resolution as a 2-tuple (resx, resy) Args: * isotropic: If True, verify that dx == dy and return dx If False return 2-tuple (dx, dy) * native: Optional flag. If True, return native resolution if available. Otherwise return actual. * rtol, atol: Tolerances as to how much difference is accepted between dx and dy if isotropic is True. """ # Get actual resolution first try: res = geotransform2resolution(self.geotransform, isotropic=isotropic, rtol=rtol, atol=atol) except Exception, e: msg = ('Resolution for layer %s could not be obtained: %s ' % (self.get_name(), str(e))) raise InaSAFEError(msg)
def assign_hazard_values_to_exposure_data(hazard, exposure, layer_name=None, attribute_name=None, mode='linear'): """Assign hazard values to exposure data. This is the high level wrapper around interpolation functions for different combinations of data types. :param hazard: Layer representing the hazard levels :param exposure: Layer representing the exposure data :param layer_name: Optional name of returned layer. If None (default) the name of the exposure layer is used for the returned layer. :param attribute_name: If hazard layer is of type raster, this will be the name for new attribute in the result containing the hazard level. If None (default) the name of hazard layer is used. If hazard layer is polygon and exposure layer raster, this will be the name of the new attribute containing the raster value at each point. If hazard and exposure layers are both of type vector, this attribute is ignored. If hazard and exposure layers are both of type raster, this attribute is ignored. :param mode: Interpolation mode for raster to point interpolation only. Permissible values are 'linear' (default) which will employ billinear interpolation and 'constant' which will employ a piecewise constant interpolation. This parameter is passed all the way down to the underlying interpolation function interpolate2d (module common/interpolation2d.py) :returns: Layer representing the exposure data with hazard levels assigned. :raises: Underlying exceptions are propagated Note: Admissible combinations of input layer types are Note: Admissible combinations of input layer types are:: Exposure Raster Polygon Line Point Hazard Polygon Y Y Y Y Raster Y Y Y Y with the following methodologies used: Polygon-Point: Clip points to polygon and assign polygon attributes to them. Polygon-Line: * Not Implemented * Polygon-Polygon: * Not Implemented * Polygon-Raster: Convert raster to points, clip to polygon, assign values and return point data Raster-Point: Bilinear (or constant) interpolation as currently implemented Raster-Line: * Not Implemented * Raster-Polygon: Calculate centroids and use Raster - Point algorithm Raster-Raster: Exposure raster is returned as is The data type of the resulting layer depends on the combination of input types as follows: Polygon-Point: Point data Polygon-Line: N/A Polygon-Polygon: N/A Polygon-Raster: Point data Raster-Point: Point data Raster-Line: N/A Raster-Polygon: Polygon data Raster-Raster: Raster data """ # Make sure attribute name can be stored in a shapefile if attribute_name is not None and len(attribute_name) > 10: msg = ('Specified attribute name "%s"\ has length = %i. ' 'To fit into a shapefile it must be at most 10 characters ' 'long. How about naming it "%s"?' % (attribute_name, len(attribute_name), attribute_name[:10])) raise InaSAFEError(msg) layer_name, attribute_name = check_inputs(hazard, exposure, layer_name, attribute_name) # Raster-Vector if hazard.is_raster and exposure.is_vector: return interpolate_raster_vector(hazard, exposure, layer_name=layer_name, attribute_name=attribute_name, mode=mode) # Raster-Raster elif hazard.is_raster and exposure.is_raster: return interpolate_raster_raster(hazard, exposure) # Vector-Vector elif hazard.is_vector and exposure.is_vector: return interpolate_polygon_vector(hazard, exposure, layer_name=layer_name) # Vector-Raster (returns tuple) # (interpolated_layer, covered exposure layer) elif hazard.is_vector and exposure.is_raster: return interpolate_polygon_raster(hazard, exposure, layer_name=layer_name, attribute_name=attribute_name) # Unknown else: msg = ('Unknown combination of types for hazard and exposure data. ' 'hazard: %s, exposure: %s' % (str(hazard), str(exposure))) raise InaSAFEError(msg)
def run(self, layers): """Risk plugin for volcano population evacuation :param layers: List of layers expected to contain where two layers should be present. * my_hazard: Vector polygon layer of volcano impact zones * my_exposure: Raster layer of population data on the same grid as my_hazard 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 """ # Identify hazard and exposure layers my_hazard = get_hazard_layer(layers) # Volcano KRB my_exposure = get_exposure_layer(layers) question = get_question(my_hazard.get_name(), my_exposure.get_name(), self) # Input checks if not my_hazard.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % my_hazard.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (my_hazard.get_name(), my_hazard.get_geometry_name())) if not (my_hazard.is_polygon_data or my_hazard.is_point_data): raise Exception(msg) if my_hazard.is_point_data: # Use concentric circles radii = self.parameters['distance [km]'] centers = my_hazard.get_geometry() attributes = my_hazard.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters my_hazard = make_circular_polygon(centers, rad_m, attributes=attributes) 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 attributes = my_hazard.get_data() # Get names of volcanos considered if name_attribute in my_hazard.get_attribute_names(): D = {} for att in my_hazard.get_data(): # Run through all polygons and get unique names D[att[name_attribute]] = None volcano_names = '' for name in D: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') if not category_title in my_hazard.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (my_hazard.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(my_hazard, my_exposure, attribute_name='population') # Initialise attributes of output dataset with all attributes # from input polygon and a population count of zero new_attributes = my_hazard.get_data() categories = {} for attr in new_attributes: attr[self.target_field] = 0 cat = attr[category_title] categories[cat] = 0 # Count affected population per polygon and total evacuated = 0 for attr in P.get_data(): # Get population at this location pop = float(attr['population']) # Update population count for associated polygon poly_id = attr['polygon_id'] new_attributes[poly_id][self.target_field] += pop # Update population count for each category cat = new_attributes[poly_id][category_title] categories[cat] += pop # Count totals total = int(numpy.sum(my_exposure.get_data(nan=0))) # Don't show digits less than a 1000 total = round_thousand(total) # Count number and cumulative for each zone cum = 0 pops = {} cums = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name # prevent key error pop = int(categories.get(key, 0)) pop = round_thousand(pop) cum += pop cum = round_thousand(cum) pops[name] = pop cums[name] = cum # Use final accumulation as total number needing evac evacuated = cum tot_needs = evacuated_population_weekly_needs(evacuated) # Generate impact report for the pdf map blank_cell = '' table_body = [ question, TableRow( [tr('Volcanos 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(pops[name]), format_int(cums[name])])) table_body.extend([ TableRow( tr('Map shows population affected in ' 'each of volcano hazard polygons.')), TableRow([tr('Needs per week'), tr('Total'), blank_cell], header=True), [tr('Rice [kg]'), format_int(tot_needs['rice']), blank_cell], [ tr('Drinking Water [l]'), format_int(tot_needs['drinking_water']), blank_cell ], [ tr('Clean Water [l]'), format_int(tot_needs['water']), blank_cell ], [ tr('Family Kits'), format_int(tot_needs['family_kits']), blank_cell ], [tr('Toilets'), format_int(tot_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_attributes] 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 V = Vector(data=new_attributes, projection=my_hazard.get_projection(), geometry=my_hazard.get_geometry(as_geometry_objects=True), name=tr('Population 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 V
def interpolate2d(x, y, z, points, mode='linear', bounds_error=False): """Fundamental 2D interpolation routine :param x: 1D array of x-coordinates of the mesh on which to interpolate :type x: numpy.ndarray :param y: 1D array of y-coordinates of the mesh on which to interpolate :type y: numpy.ndarray :param z: 2D array of values for each x, y pair :type z: numpy.ndarry :param points: Nx2 array of coordinates where interpolated values are sought :type points: numpy.narray :param mode: Determines the interpolation order. Options are: * 'constant' - piecewise constant nearest neighbour interpolation * 'linear' - bilinear interpolation using the four nearest neighbours (default) :type mode: str :param bounds_error: If True (default) a BoundsError exception will be raised when interpolated values are requested outside the domain of the input data. If False, nan is returned for those values :type bounds_error: bool :returns: 1D array with same length as points with interpolated values :raises: Exception, BoundsError (see note about bounds_error) ..notes:: Input coordinates x and y are assumed to be monotonically increasing, but need not be equidistantly spaced. No such assumption regarding ordering of points is made. z is assumed to have dimension M x N, where M = len(x) and N = len(y). In other words it is assumed that the x values follow the first (vertical) axis downwards and y values the second (horizontal) axis from left to right. If this routine is to be used for interpolation of raster grids where data is typically organised with longitudes (x) going from left to right and latitudes (y) from left to right then user interpolate_raster in this module """ # Input checks validate_mode(mode) # pylint: disable=unbalanced-tuple-unpacking x, y, z, xi, eta = validate_inputs(x=x, y=y, z=z, points=points, bounds_error=bounds_error) # Identify elements that are outside interpolation domain or NaN outside = (xi < x[0]) + (eta < y[0]) + (xi > x[-1]) + (eta > y[-1]) outside += numpy.isnan(xi) + numpy.isnan(eta) inside = -outside xi = xi[inside] eta = eta[inside] # Find upper neighbours for each interpolation point idx = numpy.searchsorted(x, xi, side='left') idy = numpy.searchsorted(y, eta, side='left') # Internal check (index == 0 is OK) if len(idx) > 0 or len(idy) > 0: if (max(idx) >= len(x)) or (max(idy) >= len(y)): msg = ('Interpolation point outside domain. ' 'This should never happen. ' 'Please email [email protected]') raise InaSAFEError(msg) # Get the four neighbours for each interpolation point x0 = x[idx - 1] x1 = x[idx] y0 = y[idy - 1] y1 = y[idy] z00 = z[idx - 1, idy - 1] z01 = z[idx - 1, idy] z10 = z[idx, idy - 1] z11 = z[idx, idy] # Coefficients for weighting between lower and upper bounds old_set = numpy.seterr(invalid='ignore') # Suppress warnings alpha = (xi - x0) / (x1 - x0) beta = (eta - y0) / (y1 - y0) numpy.seterr(**old_set) # Restore if mode == 'linear': # Bilinear interpolation formula dx = z10 - z00 dy = z01 - z00 z_interpolate = z00 + alpha * dx + beta * dy + alpha * beta * ( z11 - dx - dy - z00) else: # Piecewise constant (as verified in input_check) # Set up masks for the quadrants left = alpha < 0.5 right = -left lower = beta < 0.5 upper = -lower lower_left = lower * left lower_right = lower * right upper_left = upper * left # Initialise result array with all elements set to upper right z_interpolate = z11 # Then set the other quadrants z_interpolate[lower_left] = z00[lower_left] z_interpolate[lower_right] = z10[lower_right] z_interpolate[upper_left] = z01[upper_left] # Self test if len(z_interpolate) > 0: mz_interpolate = numpy.nanmax(z_interpolate) mz = numpy.nanmax(z) # noinspection PyStringFormat msg = ('Internal check failed. Max interpolated value %.15f ' 'exceeds max grid value %.15f ' % (mz_interpolate, mz)) if not (numpy.isnan(mz_interpolate) or numpy.isnan(mz)): if not mz_interpolate <= mz: raise InaSAFEError(msg) # Populate result with interpolated values for points inside domain # and NaN for values outside r = numpy.zeros(len(points)) r[inside] = z_interpolate r[outside] = numpy.nan return r
def run(self): """Run volcano population evacuation Impact Function. 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() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # Parameters self.hazard_class_attribute = self.hazard.keyword('field') name_attribute = self.hazard.keyword('volcano_name_field') self.hazard_class_mapping = self.hazard.keyword('value_map') if has_no_data(self.exposure.layer.get_data(nan=True)): self.no_data_warning = True # Input checks if not self.hazard.layer.is_polygon_data: message = tr( 'Input hazard must be a polygon layer. I got %s with layer ' 'type %s' % (self.hazard.layer.get_name(), self.hazard.layer.get_geometry_name())) raise Exception(message) # Check if hazard_class_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): message = tr( 'Hazard data %s did not contain expected attribute ' '%s ' % (self.hazard.layer.get_name(), self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) features = self.hazard.layer.get_data() # Get names of volcanoes considered if name_attribute in self.hazard.layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in features: volcano_name_list.append(row[name_attribute]) self.volcano_names = ', '.join(set(volcano_name_list)) # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Initialize OrderedDict of affected buildings self.affected_population = OrderedDict() # Iterate over vector hazard classes for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building self.affected_population[vector_hazard_class['name']] = 0 # Run interpolation function for polygon2raster interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # 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 hazard zone hazard_value = get_key_for_value( row[self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value self.affected_population[hazard_value] += population # Count totals self.total_population = int( numpy.nansum(self.exposure.layer.get_data())) self.unaffected_population = (self.total_population - self.total_affected_population) self.minimum_needs = [ parameter.serialize() for parameter in filter_needs_parameters( self.parameters['minimum needs']) ] impact_table = impact_summary = self.html_report() # check for zero impact if self.total_affected_population == 0: message = no_population_impact_message(self.question) 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]) style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = 0 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 Volcano Hazard Zones') legend_title = tr('Population') legend_units = tr('(people per cell)') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) extra_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': self.total_needs } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # 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 volcano hazard zones'), keywords=impact_layer_keywords, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self, layers): """Indonesian Earthquake Fatality Model Input layers: List of layers expected to contain H: Raster layer of MMI ground shaking P: Raster layer of population density """ # Establish model coefficients x = self.parameters['x'] y = self.parameters['y'] # Define percentages of people being displaced at each mmi level displacement_rate = self.parameters['displacement_rate'] # Tolerance for transparency tolerance = self.parameters['tolerance'] # Extract input layers intensity = get_hazard_layer(layers) population = get_exposure_layer(layers) question = get_question(intensity.get_name(), population.get_name(), self) # Extract data grids H = intensity.get_data() # Ground Shaking P = population.get_data(scaling=True) # Population Density # Calculate population affected by each MMI level # FIXME (Ole): this range is 2-9. Should 10 be included? mmi_range = range(2, 10) number_of_exposed = {} number_of_displaced = {} number_of_fatalities = {} # Calculate fatality rates for observed Intensity values (H # based on ITB power model R = numpy.zeros(H.shape) for mmi in mmi_range: # Identify cells where MMI is in class i mask = (H > mmi - 0.5) * (H <= mmi + 0.5) # Count population affected by this shake level I = numpy.where(mask, P, 0) # Calculate expected number of fatalities per level fatality_rate = numpy.power(10.0, x * mmi - y) F = fatality_rate * I # Calculate expected number of displaced people per level try: D = displacement_rate[mmi] * I except KeyError, e: msg = 'mmi = %i, I = %s, Error msg: %s' % (mmi, str(I), str(e)) raise InaSAFEError(msg) # Adjust displaced people to disregard fatalities. # Set to zero if there are more fatalities than displaced. D = numpy.where(D > F, D - F, 0) # Sum up numbers for map R += D # Displaced # Generate text with result for this study # This is what is used in the real time system exposure table number_of_exposed[mmi] = numpy.nansum(I.flat) number_of_displaced[mmi] = numpy.nansum(D.flat) number_of_fatalities[mmi] = numpy.nansum(F.flat)
def get_data(self, nan=True, scaling=None, copy=False): """Get raster data as numeric array Args: * nan: Optional flag controlling handling of missing values. If keyword nan has a numeric value, nodata values will be replaced by that value. E.g. to set missing values to 0, do get_data(nan=0.0) NOTE: The following behaviour is depricated, since we handle this on file load: [If nan is True (default), nodata values will be replaced with numpy.nan] * scaling: Optional flag controlling if data is to be scaled if it has been resampled. Admissible values are False: data is retrieved without modification. True: Data is rescaled based on the squared ratio between its current and native resolution. This is typically required if raster data represents a density such as population per km^2 None: The behaviour will depend on the keyword "population" associated with the layer. If it is "density", scaling will be applied otherwise not. This is the default. scalar value: If scaling takes a numerical scalar value, that will be use to scale the data * copy (optional): If present and True return copy Note: Scaling does not currently work with projected layers. See issue #123 """ if copy: A = copy_module.deepcopy(self.data) else: A = self.data verify(A.shape[0] == self.rows and A.shape[1] == self.columns) # Handle no data value # Must explicit comparison to False and True as nan can be a number # so 0 would evaluate to False and e.g. 1 to True. if type(nan) is not bool: # We are handling all non-NaN's in read_from_file and # assuming NaN's in internal numpy arrays [issue #297]. try: # Use user specified number new_nodata_value = float(nan) except (ValueError, TypeError): msg = ('Argument nan must be either True, False or a ' 'number. I got "nan=%s"' % str(nan)) raise InaSAFEError(msg) # Replace NODATA_VALUE with NaN array NoData = numpy.ones(A.shape, A.dtype) * new_nodata_value A = numpy.where(numpy.isnan(A), NoData, A) # Take care of possible scaling if scaling is None: # Redefine scaling from density keyword if possible kw = self.get_keywords() if 'datatype' in kw and kw['datatype'].lower() == 'density': scaling = True else: scaling = False if scaling is False: # No change sigma = 1 elif scaling is True: # Calculate scaling based on resolution change actual_res = self.get_resolution(isotropic=True) native_res = self.get_resolution(isotropic=True, native=True) sigma = (actual_res / native_res)**2 else: # See if scaling can work as a scalar value try: sigma = float(scaling) except ValueError, e: msg = ('Keyword scaling "%s" could not be converted to a ' 'number. It must be either True, False, None or a ' 'number: %s' % (scaling, str(e))) raise GetDataError(msg)
def run(self, layers): """Indonesian Earthquake Fatality Model Input: :param layers: List of layers expected to contain, my_hazard: Raster layer of MMI ground shaking my_exposure: Raster layer of population density """ displacement_rate = self.parameters['displacement_rate'] # Tolerance for transparency tolerance = self.parameters['tolerance'] # Extract input layers intensity = get_hazard_layer(layers) population = get_exposure_layer(layers) question = get_question(intensity.get_name(), population.get_name(), self) # Extract data grids my_hazard = intensity.get_data() # Ground Shaking my_exposure = population.get_data(scaling=True) # Population Density # Calculate population affected by each MMI level # FIXME (Ole): this range is 2-9. Should 10 be included? mmi_range = self.parameters['mmi_range'] number_of_exposed = {} number_of_displaced = {} number_of_fatalities = {} # Calculate fatality rates for observed Intensity values (my_hazard # based on ITB power model R = numpy.zeros(my_hazard.shape) for mmi in mmi_range: # Identify cells where MMI is in class i and # count population affected by this shake level I = numpy.where((my_hazard > mmi - self.parameters['step']) * (my_hazard <= mmi + self.parameters['step']), my_exposure, 0) # Calculate expected number of fatalities per level fatality_rate = self.fatality_rate(mmi) F = fatality_rate * I # Calculate expected number of displaced people per level try: D = displacement_rate[mmi] * I except KeyError, e: msg = 'mmi = %i, I = %s, Error msg: %s' % (mmi, str(I), str(e)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Adjust displaced people to disregard fatalities. # Set to zero if there are more fatalities than displaced. D = numpy.where(D > F, D - F, 0) # Sum up numbers for map R += D # Displaced # Generate text with result for this study # This is what is used in the real time system exposure table number_of_exposed[mmi] = numpy.nansum(I.flat) number_of_displaced[mmi] = numpy.nansum(D.flat) # noinspection PyUnresolvedReferences number_of_fatalities[mmi] = numpy.nansum(F.flat)
def run(self, layers): """Risk plugin for flood population evacuation Input layers: List of layers expected to contain H: Raster layer of volcano depth P: Raster layer of population data on the same grid as H Counts number of people exposed to flood levels exceeding specified threshold. Return Map of population exposed to flood levels exceeding the threshold Table with number of people evacuated and supplies required """ # Identify hazard and exposure layers H = get_hazard_layer(layers) # Flood inundation E = get_exposure_layer(layers) question = get_question(H.get_name(), E.get_name(), self) # Input checks if not H.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % H.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. ' 'I got %s with layer ' 'type %s' % (H.get_name(), H.get_geometry_name())) if not (H.is_polygon_data or H.is_point_data): raise Exception(msg) if H.is_point_data: # Use concentric circles radii = self.parameters['R [km]'] centers = H.get_geometry() attributes = H.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters H = make_circular_polygon(centers, rad_m, attributes=attributes) #H.write_to_file('Evac_zones_%s.shp' % str(radii)) # To check 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 attributes = H.get_data() # Get names of volcanos considered if name_attribute in H.get_attribute_names(): D = {} for att in H.get_data(): # Run through all polygons and get unique names D[att[name_attribute]] = None volcano_names = '' for name in D: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') if not category_title in H.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (H.get_name(), category_title)) raise InaSAFEError(msg) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(H, E, attribute_name='population') # Initialise attributes of output dataset with all attributes # from input polygon and a population count of zero new_attributes = H.get_data() categories = {} for attr in new_attributes: attr[self.target_field] = 0 cat = attr[category_title] categories[cat] = 0 # Count affected population per polygon and total evacuated = 0 for attr in P.get_data(): # Get population at this location pop = float(attr['population']) # Update population count for associated polygon poly_id = attr['polygon_id'] new_attributes[poly_id][self.target_field] += pop # Update population count for each category cat = new_attributes[poly_id][category_title] categories[cat] += pop # Count totals total = int(numpy.sum(E.get_data(nan=0))) # Don't show digits less than a 1000 if total > 1000: total = total // 1000 * 1000 # Count number and cumulative for each zone cum = 0 pops = {} cums = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name pop = int(categories[key]) if pop > 1000: pop = pop // 1000 * 1000 cum += pop if cum > 1000: cum = cum // 1000 * 1000 pops[name] = pop cums[name] = cum # Use final accumulation as total number needing evac evacuated = cum # Calculate estimated needs based on BNPB Perka # 7/2008 minimum bantuan # FIXME (Ole): Refactor into one function to be shared rice = int(evacuated * 2.8) drinking_water = int(evacuated * 17.5) water = int(evacuated * 67) family_kits = int(evacuated / 5) toilets = int(evacuated / 20) # Generate impact report for the pdf map blank_cell = '' table_body = [ question, TableRow( [tr('Volcanos 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(pops[name]), format_int(cums[name])])) table_body.extend([ TableRow( tr('Map shows population affected in ' 'each of volcano hazard polygons.')), TableRow([tr('Needs per week'), tr('Total'), blank_cell], header=True), [tr('Rice [kg]'), format_int(rice), blank_cell], [tr('Drinking Water [l]'), format_int(drinking_water), blank_cell], [tr('Clean Water [l]'), format_int(water), blank_cell], [tr('Family Kits'), format_int(family_kits), blank_cell], [tr('Toilets'), format_int(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 viewable area') % format_int(total), tr('People need evacuation if they are within the ' 'volcanic hazard zones.') ]) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('People affected by volcanic hazard zone') # Define classes for legend for flooded population counts colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] population_counts = [x[self.target_field] for x in new_attributes] cls = [0] + numpy.linspace(1, max(population_counts), len(colours)).tolist() # Define style info for output polygons showing population counts style_classes = [] for i, colour in enumerate(colours): lo = cls[i] hi = cls[i + 1] if i == 0: label = tr('0') else: label = tr('%i - %i') % (lo, hi) entry = dict(label=label, colour=colour, min=lo, max=hi, transparency=50, size=1) style_classes.append(entry) # Override style info with new classes and name style_info = dict(target_field=self.target_field, style_classes=style_classes, legend_title=tr('Population Count')) # Create vector layer and return V = Vector(data=new_attributes, projection=H.get_projection(), geometry=H.get_geometry(as_geometry_objects=True), name=tr('Population affected by volcanic hazard zone'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'target_field': self.target_field }, style_info=style_info) return V
def __eq__(self, other, rtol=1.0e-5, atol=1.0e-8): """Override '==' to allow comparison with other vector objecs Args: * other: Vector instance to compare to * rtol, atol: Relative and absolute tolerance. See numpy.allclose for details Note: The algorithm will try to falsify every aspect of equality for the two layers such as data, geometry, projection, keywords etc. Only if none of them can be falsified will it return True. """ # Check type if not isinstance(other, Vector): msg = ('Vector instance cannot be compared to %s' ' as its type is %s ' % (str(other), type(other))) raise TypeError(msg) # Check keywords if self.keywords != other.keywords: return False # Check number of features match if len(self) != len(other): return False # Check projection if self.projection != other.projection: return False # Check geometry type if self.geometry_type != other.geometry_type: return False # Check geometry if self.is_polygon_data: geom0 = self.get_geometry(as_geometry_objects=True) geom1 = other.get_geometry(as_geometry_objects=True) else: geom0 = self.get_geometry() geom1 = other.get_geometry() if len(geom0) != len(geom1): return False if self.is_point_data: if not numpy.allclose(geom0, geom1, rtol=rtol, atol=atol): return False elif self.is_line_data: # Check vertices of each line for i in range(len(geom0)): if not rings_equal(geom0[i], geom1[i], rtol=rtol, atol=atol): return False elif self.is_polygon_data: # Check vertices of outer and inner rings for i in range(len(geom0)): x = geom0[i].outer_ring y = geom1[i].outer_ring if not rings_equal(x, y, rtol=rtol, atol=atol): return False for j, ring0 in enumerate(geom0[i].inner_rings): ring1 = geom1[i].inner_rings[j] if not rings_equal(ring0, ring1, rtol=rtol, atol=atol): return False else: msg = ('== not implemented for geometry type: %s' % self.geometry_type) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Check keys for attribute values x = self.get_data() y = other.get_data() if x is None: if y is not None: return False else: for key in x[0]: for i in range(len(y)): if key not in y[i]: return False for key in y[0]: for i in range(len(x)): if key not in x[i]: return False # Check attribute values for i, a in enumerate(x): for key in a: X = a[key] Y = y[i][key] if X != Y: # Not obviously equal, try some special cases try: # Try numerical comparison with tolerances res = numpy.allclose(X, Y, rtol=rtol, atol=atol) except (NotImplementedError, TypeError): # E.g. '' (Not implemented) # or None or {} (Type error) pass else: if not res: return False # Finally cast as booleans. # This will e.g. match False with None or '' if not (bool(X) is bool(Y)): return False # Vector layers are identical up to the specified tolerance return True
def run(self, layers): """Risk plugin for volcano hazard on building/structure Input layers: List of layers expected to contain my_hazard: Hazard layer of volcano my_exposure: Vector layer of structure data on the same grid as my_hazard Counts number of building exposed to each volcano hazard zones. Return Map of building exposed to volcanic hazard zones Table with number of buildings affected """ # Identify hazard and exposure layers my_hazard = get_hazard_layer(layers) # Volcano hazard layer my_exposure = get_exposure_layer(layers) is_point_data = False question = get_question(my_hazard.get_name(), my_exposure.get_name(), self) # Input checks if not my_hazard.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % my_hazard.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s ' 'with layer type %s' % (my_hazard.get_name(), my_hazard.get_geometry_name())) if not (my_hazard.is_polygon_data or my_hazard.is_point_data): raise Exception(msg) if my_hazard.is_point_data: # Use concentric circles radii = self.parameters['distances [km]'] is_point_data = True centers = my_hazard.get_geometry() attributes = my_hazard.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters Z = make_circular_polygon(centers, rad_m, attributes=attributes) # To check category_title = 'Radius' my_hazard = Z 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 volcanos considered if name_attribute in my_hazard.get_attribute_names(): D = {} for att in my_hazard.get_data(): # Run through all polygons and get unique names D[att[name_attribute]] = None volcano_names = '' for name in D: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') if not category_title in my_hazard.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (my_hazard.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(my_hazard, my_exposure) # Initialise attributes of output dataset with all attributes # from input polygon and a building count of zero new_attributes = my_hazard.get_data() categories = {} for attr in new_attributes: attr[self.target_field] = 0 cat = attr[category_title] categories[cat] = 0 # Count impacted building per polygon and total for attr in P.get_data(): # Update building count for associated polygon poly_id = attr['polygon_id'] if poly_id is not None: new_attributes[poly_id][self.target_field] += 1 # Update building count for each category cat = new_attributes[poly_id][category_title] categories[cat] += 1 # Count totals total = len(my_exposure) # Generate simple impact report blank_cell = '' table_body = [ question, TableRow( [tr('Volcanos considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('Distance [km]'), tr('Total'), tr('Cumulative')], header=True) ] cum = 0 for name in category_names: # prevent key error count = categories.get(name, 0) cum += count if is_point_data: name = int(name) / 1000 table_body.append( TableRow([name, format_int(count), format_int(cum)])) 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_attributes] if max(building_counts) == 0 == min(building_counts): table_body = [ question, TableRow([ tr('Number of buildings affected'), '%s' % format_int(cum), 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(building_counts, len(colours)) 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: 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('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 V = Vector(data=new_attributes, projection=my_hazard.get_projection(), geometry=my_hazard.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 V
if mode not in ['linear', 'constant']: raise InaSAFEError(msg) x = numpy.array(x) try: y = numpy.array(y) except Exception, e: msg = ('Input vector y could not be converted to numpy array: ' '%s' % str(e)) raise Exception(msg) msg = ('Input vector x must be monotoneously increasing. I got ' 'min(x) == %.15f, but x[0] == %.15f' % (min(x), x[0])) if not min(x) == x[0]: raise InaSAFEError(msg) msg = ('Input vector y must be monotoneously increasing. ' 'I got min(y) == %.15f, but y[0] == %.15f' % (min(y), y[0])) if not min(y) == y[0]: raise InaSAFEError(msg) msg = ('Input vector x must be monotoneously increasing. I got ' 'max(x) == %.15f, but x[-1] == %.15f' % (max(x), x[-1])) if not max(x) == x[-1]: raise InaSAFEError(msg) msg = ('Input vector y must be monotoneously increasing. I got ' 'max(y) == %.15f, but y[-1] == %.15f' % (max(y), y[-1])) if not max(y) == y[-1]: raise InaSAFEError(msg)
def run(self): """Run classified population evacuation Impact Function. 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 """ # Value from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') # TODO: Remove check to self.validate (Ismail) # Input checks message = tr( 'Input hazard must be a polygon layer. I got %s with layer type ' '%s' % (self.hazard.name, self.hazard.layer.get_geometry_name())) if not self.hazard.layer.is_polygon_data: raise Exception(message) # Check if hazard_class_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): message = tr( 'Hazard data %s does not contain expected hazard ' 'zone attribute "%s". Please change it in the option. ' % (self.hazard.name, self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Initialize OrderedDict of affected buildings self.affected_population = OrderedDict() # Iterate over vector hazard classes for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building self.affected_population[vector_hazard_class['name']] = 0 # Interpolated layer represents grid cell that lies in the polygon interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field ) # 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_value = get_key_for_value( row[self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value else: self.affected_population[hazard_value] += population # Count total population from exposure layer self.total_population = int( numpy.nansum(self.exposure.layer.get_data())) # Count total affected population total_affected_population = self.total_affected_population self.unaffected_population = (self.total_population - total_affected_population) self.minimum_needs = [ parameter.serialize() for parameter in filter_needs_parameters( self.parameters['minimum needs']) ] # check for zero impact if total_affected_population == 0: message = no_population_impact_message(self.question) 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]) style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = 0 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') impact_data = self.generate_data() extra_keywords = { 'target_field': self.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 = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), 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): """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=None): """Indonesian Earthquake Fatality Model. Input: :param layers: List of layers expected to contain, hazard: Raster layer of MMI ground shaking exposure: Raster layer of population count """ self.validate() self.prepare(layers) displacement_rate = self.hardcoded_parameters['displacement_rate'] # Tolerance for transparency tolerance = self.hardcoded_parameters['tolerance'] # Extract input layers intensity = self.hazard population = self.exposure # Extract data grids hazard = intensity.get_data() # Ground Shaking exposure = population.get_data(scaling=True) # Population Density # Calculate people affected by each MMI level # FIXME (Ole): this range is 2-9. Should 10 be included? mmi_range = self.hardcoded_parameters['mmi_range'] number_of_exposed = {} number_of_displaced = {} number_of_fatalities = {} # Calculate fatality rates for observed Intensity values (hazard # based on ITB power model mask = numpy.zeros(hazard.shape) for mmi in mmi_range: # Identify cells where MMI is in class i and # count people affected by this shake level mmi_matches = numpy.where( (hazard > mmi - self.hardcoded_parameters['step']) * ( hazard <= mmi + self.hardcoded_parameters['step']), exposure, 0) # Calculate expected number of fatalities per level fatality_rate = self.fatality_rate(mmi) fatalities = fatality_rate * mmi_matches # Calculate expected number of displaced people per level try: displacements = displacement_rate[mmi] * mmi_matches except KeyError, e: msg = 'mmi = %i, mmi_matches = %s, Error msg: %s' % ( mmi, str(mmi_matches), str(e)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Adjust displaced people to disregard fatalities. # Set to zero if there are more fatalities than displaced. displacements = numpy.where( displacements > fatalities, displacements - fatalities, 0) # Sum up numbers for map mask += displacements # Displaced # Generate text with result for this study # This is what is used in the real time system exposure table number_of_exposed[mmi] = numpy.nansum(mmi_matches.flat) number_of_displaced[mmi] = numpy.nansum(displacements.flat) # noinspection PyUnresolvedReferences number_of_fatalities[mmi] = numpy.nansum(fatalities.flat)
def run(self): """Risk plugin for volcano hazard on building/structure. 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.') # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.name_attribute = self.hazard.keyword('volcano_name_field') self.hazard_class_mapping = self.hazard.keyword('value_map') # 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_polygon_data: message = ( 'Input hazard must be a polygon. I got %s with ' 'layer type %s' % (self.hazard.name, self.hazard.layer.get_geometry_name())) raise Exception(message) # Check if hazard_zone_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): message = ( 'Hazard data %s did not contain expected attribute %s ' % (self.hazard.name, self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Get names of volcanoes considered if self.name_attribute in self.hazard.layer.get_attribute_names(): volcano_name_list = set() for row in self.hazard.layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[self.name_attribute]) self.volcano_names = ', '.join(volcano_name_list) else: self.volcano_names = tr('Not specified in data') # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Initialize OrderedDict of affected buildings self.affected_buildings = OrderedDict() # Iterate over vector hazard classes for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building self.affected_buildings[vector_hazard_class['name']] = {} # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer) # Extract relevant exposure data attribute_names = interpolated_layer.get_attribute_names() features = interpolated_layer.get_data() self.buildings = {} for i in range(len(features)): # Get the hazard value based on the value mapping in keyword hazard_value = get_key_for_value( features[i][self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value features[i][self.target_field] = get_string(hazard_value) 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 in [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 # 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(self.affected_buildings.keys())] style_classes = [] i = 0 for category_name in self.affected_buildings.keys(): 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(self.affected_buildings.keys()): i = len(self.affected_buildings.keys()) - 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=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic hazard zone') 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': self.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 hazard zone'), keywords=impact_layer_keywords, style_info=style_info) self._impact = impact_layer return impact_layer
def get_data(self, nan=True, scaling=None, copy=False): """Get raster data as numeric array Args: * nan: Optional flag controlling handling of missing values. If nan is True (default), nodata values will be replaced with numpy.nan If keyword nan has a numeric value, nodata values will be replaced by that value. E.g. to set missing values to 0, do get_data(nan=0.0) * scaling: Optional flag controlling if data is to be scaled if it has been resampled. Admissible values are False: data is retrieved without modification. True: Data is rescaled based on the squared ratio between its current and native resolution. This is typically required if raster data represents a density such as population per km^2 None: The behaviour will depend on the keyword "population" associated with the layer. If it is "density", scaling will be applied otherwise not. This is the default. scalar value: If scaling takes a numerical scalar value, that will be use to scale the data * copy (optional): If present and True return copy Note: Scaling does not currently work with projected layers. See issue #123 """ if hasattr(self, 'data') and self.data is not None: # Return internal data grid if copy: A = copy_module.deepcopy(self.data) else: A = self.data verify(A.shape[0] == self.rows and A.shape[1] == self.columns) else: # Force garbage collection to free up any memory we can (TS) gc.collect() # Read from raster file # FIXME: This can be slow so should be moved to read_from_file A = self.band.ReadAsArray() # Convert to double precision (issue #75) A = numpy.array(A, dtype=numpy.float64) # Self check M, N = A.shape msg = ('Dimensions of raster array do not match those of ' 'raster file %s' % self.filename) verify(M == self.rows, msg) verify(N == self.columns, msg) # Handle no data value # FIXME (Ole): This only pertains to data read from file # and should be moved to read_from_file. nodata = self.get_nodata_value() # Must explicit comparison to False and True as nan can be a number # so 0 would evaluate to False and e.g. 1 to True. if nan is False: # No change pass else: # Nan value should be changed if nan is True: NAN = numpy.nan # Use numpy's nan value else: try: # Use user specified number NAN = float(nan) except (ValueError, TypeError): msg = ('Argument nan must be either True, False or a ' 'number. I got "nan=%s"' % str(nan)) raise InaSAFEError(msg) # Replace NODATA_VALUE with NaN array #print 'Replacing', nodata, 'with', NAN NaN = numpy.ones(A.shape, A.dtype) * NAN A = numpy.where(A == nodata, NaN, A) # Take care of possible scaling if scaling is None: # Redefine scaling from density keyword if possible kw = self.get_keywords() if 'datatype' in kw and kw['datatype'].lower() == 'density': scaling = True else: scaling = False if scaling is False: # No change sigma = 1 elif scaling is True: # Calculate scaling based on resolution change actual_res = self.get_resolution(isotropic=True) native_res = self.get_resolution(isotropic=True, native=True) #print #print 'Actual res', actual_res #print 'Native res', native_res sigma = (actual_res / native_res)**2 #print 'Scaling', sigma else: # See if scaling can work as a scalar value try: sigma = float(scaling) except ValueError, e: msg = ('Keyword scaling "%s" could not be converted to a ' 'number. It must be either True, False, None or a ' 'number: %s' % (scaling, str(e))) raise GetDataError(msg)
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