def __init__(self, iface, template, layer, extra_layers=[]): """Constructor for the Composition Report class. :param iface: Reference to the QGIS iface object. :type iface: QgsAppInterface :param template: The QGIS template path. :type template: str """ LOGGER.debug('InaSAFE Impact Report class initialised') self._iface = iface self._template = None self.template = template self._layer = layer self._extra_layers = extra_layers self._extent = self._iface.mapCanvas().extent() self._page_dpi = 300.0 self._black_inasafe_logo = black_inasafe_logo_path() self._white_inasafe_logo = white_inasafe_logo_path() # User can change this path in preferences self._organisation_logo = supporters_logo_path() self._supporters_logo = supporters_logo_path() self._north_arrow = default_north_arrow_path() self._disclaimer = disclaimer() # For QGIS < 2.4 compatibility # QgsMapSettings is added in 2.4 if qgis_version() < 20400: map_settings = self._iface.mapCanvas().mapRenderer() else: map_settings = self._iface.mapCanvas().mapSettings() self._template_composition = TemplateComposition( template_path=self.template, map_settings=map_settings) self._keyword_io = KeywordIO()
def test_clip_vector_with_unicode(self): """Test clipping vector layer with unicode attribute in feature. This issue is described at Github #2262 and #2233 TODO: FIXME: This is a hacky fix. See above ticket for further explanation. To fix this, we should be able to specify UTF-8 encoding for QgsVectorFileWriter """ # this layer contains unicode values in the layer_path = test_data_path('boundaries', 'district_osm_jakarta.shp') vector_layer = QgsVectorLayer(layer_path, 'District Jakarta', 'ogr') keyword_io = KeywordIO() aggregation_keyword = get_defaults()['AGGR_ATTR_KEY'] aggregation_attribute = keyword_io.read_keywords( vector_layer, keyword=aggregation_keyword) source_extent = vector_layer.extent() extent = [source_extent.xMinimum(), source_extent.yMinimum(), source_extent.xMaximum(), source_extent.yMaximum()] clipped_layer = clip_layer( layer=vector_layer, extent=extent, explode_flag=True, explode_attribute=aggregation_attribute) # cross check vector layer attribute in clipped layer vector_values = [] for f in vector_layer.getFeatures(): vector_values.append(f.attributes()) clipped_values = [] for f in clipped_layer.getFeatures(): clipped_values.append(f.attributes()) for val in clipped_values: self.assertIn(val, vector_values)
def _clip_vector_layer( layer, extent, extra_keywords=None, explode_flag=True, hard_clip_flag=False, explode_attribute=None): """Clip a Hazard or Exposure layer to the extents provided. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS vector or raster layer :type layer: :param extent: Either an array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. or: A QgsGeometry of type polygon. **Polygon clipping is currently only supported for vector datasets.** :type extent: list(float, float, float, float) :param extra_keywords: Optional keywords dictionary to be added to output layer. :type extra_keywords: dict :param explode_flag: A bool specifying whether multipart features should be 'exploded' into singleparts. **This parameter is ignored for raster layer clipping.** :type explode_flag: bool :param hard_clip_flag: A bool specifying whether line and polygon features that extend beyond the extents should be clipped such that they are reduced in size to the part of the geometry that intersects the extent only. Default is False. **This parameter is ignored for raster layer clipping.** :type hard_clip_flag: bool :param explode_attribute: A str specifying to which attribute #1, #2 and so on will be added in case of explode_flag being true. The attribute is modified only if there are at least 2 parts. :type explode_attribute: str :returns: Clipped layer (placed in the system temp dir). The output layer will be reprojected to EPSG:4326 if needed. :rtype: QgsVectorLayer """ if not layer or not extent: message = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(message) if layer.type() != QgsMapLayer.VectorLayer: message = tr( 'Expected a vector layer but received a %s.' % str(layer.type())) raise InvalidParameterError(message) # handle, file_name = tempfile.mkstemp('.sqlite', 'clip_', # temp_dir()) handle, file_name = tempfile.mkstemp( '.shp', 'clip_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(file_name) # Get the clip extents in the layer's native CRS geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) transform = QgsCoordinateTransform(geo_crs, layer.crs()) allowed_clip_values = [QGis.WKBPolygon, QGis.WKBPolygon25D] if isinstance(extent, list): rectangle = QgsRectangle( extent[0], extent[1], extent[2], extent[3]) # noinspection PyCallByClass # noinspection PyTypeChecker polygon = QgsGeometry.fromRect(rectangle) elif (isinstance(extent, QgsGeometry) and extent.wkbType in allowed_clip_values): rectangle = extent.boundingBox().toRectF() polygon = extent else: raise InvalidClipGeometryError( tr( 'Clip geometry must be an extent or a single part' 'polygon based geometry.')) projected_extent = transform.transformBoundingBox(rectangle) # Get vector layer provider = layer.dataProvider() if provider is None: message = tr( 'Could not obtain data provider from ' 'layer "%s"' % layer.source()) raise Exception(message) # Get the layer field list, select by our extent then write to disk # .. todo:: FIXME - for different geometry types we should implement # different clipping behaviour e.g. reject polygons that # intersect the edge of the bbox. Tim request = QgsFeatureRequest() if not projected_extent.isEmpty(): request.setFilterRect(projected_extent) request.setFlags(QgsFeatureRequest.ExactIntersect) field_list = provider.fields() writer = QgsVectorFileWriter( file_name, 'UTF-8', field_list, layer.wkbType(), geo_crs, # 'SQLite') # FIXME (Ole): This works but is far too slow 'ESRI Shapefile') if writer.hasError() != QgsVectorFileWriter.NoError: message = tr( 'Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (file_name, writer.hasError())) raise Exception(message) # Reverse the coordinate xform now so that we can convert # geometries from layer crs to geocrs. transform = QgsCoordinateTransform(layer.crs(), geo_crs) # Retrieve every feature with its geometry and attributes count = 0 has_multipart = False for feature in provider.getFeatures(request): geometry = feature.geometry() # Loop through the parts adding them to the output file # we write out single part features unless explode_flag is False if explode_flag: geometry_list = explode_multipart_geometry(geometry) else: geometry_list = [geometry] for part_index, part in enumerate(geometry_list): part.transform(transform) if hard_clip_flag: # Remove any dangling bits so only intersecting area is # kept. part = clip_geometry(polygon, part) if part is None: continue feature.setGeometry(part) # There are multiple parts and we want to show it in the # explode_attribute if part_index > 0 and explode_attribute is not None: has_multipart = True writer.addFeature(feature) count += 1 del writer # Flush to disk if count < 1: message = tr( 'No features fall within the clip extents. Try panning / zooming ' 'to an area containing data and then try to run your analysis ' 'again. If hazard and exposure data doesn\'t overlap at all, it ' 'is not possible to do an analysis. Another possibility is that ' 'the layers do overlap but because they may have different ' 'spatial references, they appear to be disjointed. If this is the ' 'case, try to turn on reproject on-the-fly in QGIS.') raise NoFeaturesInExtentError(message) keyword_io = KeywordIO() if extra_keywords is None: extra_keywords = {} extra_keywords['had multipart polygon'] = has_multipart keyword_io.copy_keywords( layer, file_name, extra_keywords=extra_keywords) base_name = '%s clipped' % layer.name() layer = QgsVectorLayer(file_name, base_name, 'ogr') return layer
def __init__(self, parent=None, iface=None): """Constructor.""" QDialog.__init__(self, parent) self.setupUi(self) icon = resources_path('img', 'icons', 'show-metadata-converter.svg') self.setWindowIcon(QIcon(icon)) self.setWindowTitle(self.tr('InaSAFE Metadata Converter')) self.parent = parent self.iface = iface self.keyword_io = KeywordIO() self.layer = None # Setup header label self.header_label.setText( tr('In this tool, you can convert a metadata 4.x of a layer to ' 'metadata 3.5. You will get a directory contains all the files of ' 'the layer and the new 3.5 metadata. If you want to convert ' 'hazard layer, you need to choose what exposure that you want to ' 'work with.')) # Setup input layer combo box # Filter our layers excepted_layers = [] for i in range(self.input_layer_combo_box.count()): layer = self.input_layer_combo_box.layer(i) try: keywords = self.keyword_io.read_keywords(layer) except (KeywordNotFoundError, NoKeywordsFoundError): # Filter out if no keywords excepted_layers.append(layer) continue layer_purpose = keywords.get('layer_purpose') if not layer_purpose: # Filter out if no layer purpose excepted_layers.append(layer) continue if layer_purpose not in accepted_layer_purposes: # Filter out if not aggregation, hazard, or exposure layer excepted_layers.append(layer) continue keyword_version = keywords.get('keyword_version') if not keyword_version: # Filter out if no keyword version excepted_layers.append(layer) continue if not is_keyword_version_supported(keyword_version): # Filter out if the version is not supported (4.x) excepted_layers.append(layer) continue self.input_layer_combo_box.setExceptedLayerList(excepted_layers) # Select the active layer. if self.iface.activeLayer(): found = self.input_layer_combo_box.findText( self.iface.activeLayer().name()) if found > -1: self.input_layer_combo_box.setLayer(self.iface.activeLayer()) # Set current layer as the active layer if self.input_layer_combo_box.currentLayer(): self.set_layer(self.input_layer_combo_box.currentLayer()) # Signals self.input_layer_combo_box.layerChanged.connect(self.set_layer) self.output_path_tool.clicked.connect(self.select_output_directory) # Set widget to show the main page (not help page) self.main_stacked_widget.setCurrentIndex(1) # Set up things for context help self.help_button = self.button_box.button(QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) # Set up things for ok button self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up things for cancel button self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.clicked.connect(self.reject) # The bug is fixed in QT 5.4 if QT_VERSION < 0x050400: self.resized.connect(self.after_resize)
def __init__(self, parent=None, iface=None, setting=None): """Constructor.""" QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Field Mapping Tool')) icon = resources_path('img', 'icons', 'show-mapping-tool.svg') self.setWindowIcon(QIcon(icon)) self.parent = parent self.iface = iface if setting is None: setting = QSettings() self.setting = setting self.keyword_io = KeywordIO() self.layer = None self.metadata = {} self.layer_input_layout = QHBoxLayout() self.layer_label = QLabel(tr('Layer')) self.layer_combo_box = QgsMapLayerComboBox() # Filter only for Polygon and Point self.layer_combo_box.setFilters( QgsMapLayerProxyModel.PolygonLayer | QgsMapLayerProxyModel.PointLayer) # Filter out a layer that don't have layer groups excepted_layers = [] for i in range(self.layer_combo_box.count()): layer = self.layer_combo_box.layer(i) try: keywords = self.keyword_io.read_keywords(layer) except (KeywordNotFoundError, NoKeywordsFoundError): excepted_layers.append(layer) continue layer_purpose = keywords.get('layer_purpose') if not layer_purpose: excepted_layers.append(layer) continue if layer_purpose == layer_purpose_exposure['key']: layer_subcategory = keywords.get('exposure') elif layer_purpose == layer_purpose_hazard['key']: layer_subcategory = keywords.get('hazard') else: layer_subcategory = None field_groups = get_field_groups(layer_purpose, layer_subcategory) if len(field_groups) == 0: excepted_layers.append(layer) continue self.layer_combo_box.setExceptedLayerList(excepted_layers) # Select the active layer. if self.iface.activeLayer(): found = self.layer_combo_box.findText( self.iface.activeLayer().name()) if found > -1: self.layer_combo_box.setLayer(self.iface.activeLayer()) self.field_mapping_widget = None self.main_stacked_widget.setCurrentIndex(1) # Input self.layer_input_layout.addWidget(self.layer_label) self.layer_input_layout.addWidget(self.layer_combo_box) self.header_label = QLabel() self.header_label.setWordWrap(True) self.main_layout.addWidget(self.header_label) self.main_layout.addLayout(self.layer_input_layout) # Signal self.layer_combo_box.layerChanged.connect(self.set_layer) if self.layer_combo_box.currentLayer(): self.set_layer(self.layer_combo_box.currentLayer()) # Set up things for context help self.help_button = self.button_box.button(QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) # Set up things for ok button self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up things for cancel button self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.clicked.connect(self.reject)
def __init__(self, parent=None, iface=None): """Constructor for dialog. :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ QDialog.__init__(self, parent) self.parent = parent self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Impact Layer Merge Tool')) self.iface = iface self.keyword_io = KeywordIO() # Template Path for composer self.template_path = resources_path('qgis-composer-templates', 'merged_report.qpt') # Safe Logo Path self.safe_logo_path = white_inasafe_logo_path() # Organisation Logo Path - defaults to supporters logo, will be # updated to user defined organisation logo path in read_settings in # user has specified a custom logo. self.organisation_logo_path = supporters_logo_path() # Disclaimer text self.disclaimer = disclaimer() # The output directory self.out_dir = None # Stored information from first impact layer self.first_impact = { 'layer': None, 'map_title': None, 'hazard_title': None, 'exposure_title': None, 'postprocessing_report': None, } # Stored information from second impact layer self.second_impact = { 'layer': None, 'map_title': None, 'hazard_title': None, 'exposure_title': None, 'postprocessing_report': None, } # Stored information from aggregation layer self.aggregation = {'layer': None, 'aggregation_attribute': None} # Available aggregation layer self.available_aggregation = [] # The summary report, contains report for each aggregation area self.summary_report = OrderedDict() # The html reports and its file path self.html_reports = OrderedDict() # A boolean flag whether to merge entire area or aggregated self.entire_area_mode = False # Get the global settings and override some variable if exist self.read_settings() # Get all current project layers for combo box self.get_project_layers() # Set up things for context help self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # Show usage info self.restore_state()
def save_hazard_data(self): hazard_geojson = PetaJakartaAPI.get_aggregate_report( self.duration, self.level) if not hazard_geojson: raise PetaJakartaAPIError("Can't access PetaJakarta REST API") with open(self.hazard_path, 'w+') as f: f.write(hazard_geojson) # Save the layer as shp file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr', False) target_name = 'flood_data.shp' self.hazard_path = os.path.join(self.report_path, target_name) QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path, 'CP1250', None, 'ESRI Shapefile') file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr') hazard_layer.startEditing() field = QgsField('flooded', QVariant.Int) hazard_layer.dataProvider().addAttributes([field]) hazard_layer.commitChanges() idx = hazard_layer.fieldNameIndex('flooded') expression = QgsExpression('count > 0') expression.prepare(hazard_layer.pendingFields()) hazard_layer.startEditing() for feature in hazard_layer.getFeatures(): feature[idx] = expression.evaluate(feature) hazard_layer.updateFeature(feature) hazard_layer.commitChanges() # writing keywords keyword_io = KeywordIO() keywords = { 'field': 'flooded', 'hazard': 'flood', 'hazard_category': 'single_event', 'keyword_version': '3.3', 'layer_geometry': 'polygon', 'layer_mode': 'classified', 'layer_purpose': 'hazard', 'title': 'Flood', 'value_map': '{"wet": [1], "dry": [0]}', 'vector_hazard_classification': 'flood_vector_hazard_classes' } keyword_io.write_keywords(hazard_layer, keywords) # archiving hazard layer with ZipFile(self.hazard_zip_path, 'w') as zf: for root, dirs, files in os.walk(self.report_path): for f in files: _, ext = os.path.splitext(f) if 'flood_data' in f: filename = os.path.join(root, f) zf.write(filename, arcname=f)
def get_analysis_dir(exposure_key=None): """Retrieve an output directory of an analysis/ImpactFunction from a multi exposure analysis/ImpactFunction based on exposure type. :param exposure_key: An exposure keyword. :type exposure_key: str :return: A directory contains analysis outputs. :rtype: str """ keyword_io = KeywordIO() layer_tree_root = QgsProject.instance().layerTreeRoot() all_groups = [ child for child in layer_tree_root.children() if (isinstance(child, QgsLayerTreeGroup)) ] multi_exposure_group = None for group in all_groups: if group.customProperty(MULTI_EXPOSURE_ANALYSIS_FLAG): multi_exposure_group = group break if multi_exposure_group: multi_exposure_tree_layers = [ child for child in multi_exposure_group.children() if (isinstance(child, QgsLayerTreeLayer)) ] exposure_groups = [ child for child in multi_exposure_group.children() if (isinstance(child, QgsLayerTreeGroup)) ] def get_report_ready_layer(tree_layers): """Get a layer which has a report inn its directory. :param tree_layers: A list of tree layer nodes (QgsLayerTreeLayer) :type tree_layers: list :return: A vector layer :rtype: QgsMapLayer """ for tree_layer in tree_layers: layer = tree_layer.layer() keywords = keyword_io.read_keywords(layer) extra_keywords_found = keywords.get('extra_keywords') provenance = keywords.get('provenance_data') if provenance: exposure_keywords = provenance.get('exposure_keywords', {}) exposure_key_found = exposure_keywords.get('exposure') if exposure_key_found and (exposure_key == exposure_key_found): return layer if not exposure_key and extra_keywords_found and ( extra_keywords_found[ extra_keyword_analysis_type['key']] == (MULTI_EXPOSURE_ANALYSIS_FLAG)): return layer return None layer = get_report_ready_layer(multi_exposure_tree_layers) if not layer: for exposure_group in exposure_groups: tree_layers = [ child for child in exposure_group.children() if (isinstance(child, QgsLayerTreeLayer)) ] layer = get_report_ready_layer(tree_layers) if layer: break if layer: return dirname(layer.source()) return None
def buffer_points(point_layer, radii, hazard_zone_attribute, output_crs): """Buffer points for each point with defined radii. This function is used for making buffer of volcano point hazard. :param point_layer: A point layer to buffer. :type point_layer: QgsVectorLayer :param radii: Desired approximate radii in kilometers (must be monotonically ascending). Can be either one number or list of numbers :type radii: int, list :param hazard_zone_attribute: The name of the attributes representing hazard zone. :type hazard_zone_attribute: str :param output_crs: The output CRS. :type output_crs: QgsCoordinateReferenceSystem :return: Vector polygon layer representing circle in point layer CRS. :rtype: QgsVectorLayer """ if not isinstance(radii, list): radii = [radii] if not is_point_layer(point_layer): message = ('Input hazard must be a vector point layer. I got %s ' 'with layer type %s' % (point_layer.name(), point_layer.type())) raise Exception(message) # Check that radii are monotonically increasing monotonically_increasing_flag = all(x < y for x, y in zip(radii, radii[1:])) if not monotonically_increasing_flag: raise RadiiException(RadiiException.suggestion) hazard_file_path = unique_filename(suffix='-polygon-volcano.shp') fields = point_layer.pendingFields() fields.append(QgsField(hazard_zone_attribute, QVariant.Double)) writer = QgsVectorFileWriter(hazard_file_path, 'utf-8', fields, QGis.WKBPolygon, output_crs, 'ESRI Shapefile') input_crs = point_layer.crs() center = point_layer.extent().center() utm = None if output_crs.authid() == 'EPSG:4326': utm = QgsCoordinateReferenceSystem( get_utm_epsg(center.x(), center.y(), input_crs)) transform = QgsCoordinateTransform(point_layer.crs(), utm) else: transform = QgsCoordinateTransform(point_layer.crs(), output_crs) for point in point_layer.getFeatures(): geom = point.geometry() geom.transform(transform) inner_rings = None for radius in radii: attributes = point.attributes() # Generate circle polygon circle = geom.buffer(radius * 1000.0, 30) if inner_rings: circle.addRing(inner_rings) inner_rings = circle.asPolygon()[0] new_buffer = QgsFeature() if output_crs.authid() == 'EPSG:4326': circle.transform(QgsCoordinateTransform(utm, output_crs)) new_buffer.setGeometry(circle) attributes.append(radius) new_buffer.setAttributes(attributes) writer.addFeature(new_buffer) del writer vector_layer = QgsVectorLayer(hazard_file_path, 'Polygons', 'ogr') keyword_io = KeywordIO() try: keywords = keyword_io.read_keywords(point_layer) keyword_io.write_keywords(vector_layer, keywords) except NoKeywordsFoundError: pass return vector_layer
def set_style(self): # get requested style of impact qml_path = self.flood_fixtures_dir('impact-template.qml') target_style_path = os.path.join(self.report_path, 'population_aggregate.qml') keyword_io = KeywordIO() qgis_exposure_layer = self.exposure_layer.as_qgis_native() attribute_field = keyword_io.read_keywords(qgis_exposure_layer, 'field') with open(qml_path) as f_template: str_template = f_template.read() # search max_population in an RW # use generator for memory efficiency maximum_population = max( f[attribute_field] for f in qgis_exposure_layer.getFeatures()) range_increment = maximum_population / 5 legend_expressions = {} for i in range(5): if i == 0: marker_label = '< %s' % format_int(range_increment) elif i < 4: marker_label = '%s - %s' % (format_int( range_increment * i), format_int(range_increment * (i + 1))) else: marker_label = '> %s' % format_int(range_increment * 4) marker_size = \ 'scale_linear(' \ 'attribute($currentfeature, "%s") ' \ ', %d, %d, %d, %d)' % ( self.affect_field, int(range_increment * i), int(range_increment * (i + 1)), i * 2, (i + 1) * 2, ) marker_color = \ "set_color_part(" \ "set_color_part('0,0,255','saturation', %d ), 'alpha', " \ "if(attribute($currentfeature, '%s') = 0, " \ "0, 255))" % (i * 20, self.affect_field) marker_border = \ "set_color_part(" \ "'0,0,0','alpha', " \ "if (to_int(attribute(" \ "$currentfeature, '%s')) = 0, 0, 255))" % \ self.affect_field legend_expressions['marker-min-range-%d' % i] = \ '%s' % (range_increment * i) legend_expressions['marker-max-range-%d' % i] = \ '%s' % (range_increment * (i + 1)) legend_expressions['marker-label-%d' % i] = marker_label legend_expressions['marker-size-%d' % i] = marker_size legend_expressions['marker-color-%d' % i] = marker_color legend_expressions['marker-border-%d' % i] = marker_border for k, v in legend_expressions.iteritems(): str_template = str_template.replace('[%s]' % k, v) with open(target_style_path, mode='w') as target_f: target_f.write(str_template) # Get requested style for impact layer of either kind impact = self.impact_layer style = impact.get_style_info() style_type = impact.get_style_type() # Determine styling for QGIS layer qgis_impact_layer = impact.as_qgis_native() if impact.is_vector: LOGGER.debug('myEngineImpactLayer.is_vector') if not style: # Set default style if possible pass elif style_type == 'categorizedSymbol': LOGGER.debug('use categorized') set_vector_categorized_style(qgis_impact_layer, style) elif style_type == 'graduatedSymbol': LOGGER.debug('use graduated') set_vector_graduated_style(qgis_impact_layer, style) elif impact.is_raster: LOGGER.debug('myEngineImpactLayer.is_raster') if not style: qgis_impact_layer.setDrawingStyle("SingleBandPseudoColor") else: setRasterStyle(qgis_impact_layer, style)
def calculate_impact(self): if_manager = ImpactFunctionManager() function_id = self.function_id impact_function = if_manager.get_instance(function_id) impact_function.hazard = self.hazard_layer.as_qgis_native() impact_function.exposure = self.exposure_layer.as_qgis_native() extent = impact_function.hazard.extent() impact_function.requested_extent = [ extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum() ] impact_function.requested_extent_crs = impact_function.hazard.crs() # impact_function.setup_aggregator() # impact_function.aggregator.validate_keywords() try: # skip process if hazard not contain significant flood skip_process = True qgis_hazard_layer = self.hazard_layer.as_qgis_native() keyword_io = KeywordIO() hazard_attribute_key = keyword_io.read_keywords( qgis_hazard_layer, 'field') # Do not skip if there are significant hazard class (2,3,4) for f in qgis_hazard_layer.getFeatures(): try: # try cast to int hazard_state = f[hazard_attribute_key] hazard_state = int(str(hazard_state)) if hazard_state >= 2: skip_process = False break except ValueError: # this is expected pass if skip_process: return impact_function.run_analysis() self.impact_layer = impact_function.impact # impact_function.aggregator.set_layers( # self.hazard_layer.as_qgis_native(), # self.exposure_layer.as_qgis_native()) self.calculate_aggregate_impact(impact_function) # impact_function.run_aggregator() # impact_function.run_post_processor() # self.generate_aggregation(impact_function) self.generate_population_aggregation() self.set_style() except ZeroImpactException as e: # in case zero impact, just return LOGGER.info('No impact detected') LOGGER.info(e.message) return # copy results of impact to report_path directory base_name, _ = os.path.splitext(self.impact_layer.filename) dir_name = os.path.dirname(self.impact_layer.filename) for (root, dirs, files) in os.walk(dir_name): for f in files: source_filename = os.path.join(root, f) if source_filename.find(base_name) >= 0: extensions = source_filename.replace(base_name, '') new_path = os.path.join(self.report_path, 'impact' + extensions) shutil.copy(source_filename, new_path) self.impact_layer = read_layer(self.impact_path)
def save_hazard_data(self): if self.dummy_report_folder: filename = os.path.join(self.working_dir, self.dummy_report_folder, 'flood_data.json') hazard_geojson = DummySourceAPI.get_aggregate_report(filename) else: hazard_geojson = PetaJakartaAPI.get_aggregate_report( self.duration, self.level) if not hazard_geojson: raise PetaJakartaAPIError("Can't access PetaJakarta REST API") with open(self.hazard_path, 'w+') as f: f.write(hazard_geojson) # Save the layer as shp file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr', False) target_name = 'flood_data.shp' self.hazard_path = os.path.join(self.report_path, target_name) QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path, 'CP1250', None, 'ESRI Shapefile') file_info = QFileInfo(self.hazard_path) hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(), 'ogr') # hazard_layer.startEditing() # field = QgsField('flooded', QVariant.Int) # hazard_layer.dataProvider().addAttributes([field]) # hazard_layer.commitChanges() # idx = hazard_layer.fieldNameIndex('flooded') # expression = QgsExpression('count > 0') # expression.prepare(hazard_layer.pendingFields()) # # hazard_layer.startEditing() # for feature in hazard_layer.getFeatures(): # feature[idx] = expression.evaluate(feature) # hazard_layer.updateFeature(feature) # # hazard_layer.commitChanges() # writing keywords keyword_io = KeywordIO() keywords = { 'field': 'state', 'hazard': 'generic', 'hazard_category': 'single_event', 'keyword_version': '3.5', 'layer_geometry': 'polygon', 'layer_mode': 'classified', 'layer_purpose': 'hazard', 'title': 'Flood', 'value_map': '{"high": [4], "medium": [3], ' '"low": [2], ' '"unaffected": ["None","","NULL",0,1]}', 'vector_hazard_classification': 'generic_vector_hazard_classes' } keyword_io.write_keywords(hazard_layer, keywords) # copy layer styles style_path = self.flood_fixtures_dir('flood_data_classified_state.qml') target_style_path = os.path.join(self.report_path, 'flood_data.qml') shutil.copy(style_path, target_style_path) # archiving hazard layer with ZipFile(self.hazard_zip_path, 'w') as zf: for root, dirs, files in os.walk(self.report_path): for f in files: _, ext = os.path.splitext(f) if 'flood_data' in f: filename = os.path.join(root, f) zf.write(filename, arcname=f)
def test_regression_2553_no_resample(self): """Test for regression 2553 (no resampling). see : https://github.com/inasafe/inasafe/issues/2553 We want to verify that population with resampling should produce a result within a reasonable range of the same analysis but doing population with no resampling. """ hazard_path = test_data_path( 'hazard', 'continuous_flood_unaligned_big_size.tif') exposure_path = test_data_path( 'exposure', 'people_allow_resampling_false.tif') hazard_layer, hazard_layer_purpose = load_layer(hazard_path) # Check if there is a regression about keywords being updated from # another layer - see #2605 keywords = KeywordIO(hazard_layer) self.assertIn('flood unaligned', keywords.to_message().to_text()) exposure_layer, exposure_layer_purpose = load_layer( exposure_path) keywords = KeywordIO(exposure_layer) self.assertIn( '*Allow resampling*, false------', keywords.to_message().to_text()) QgsMapLayerRegistry.instance().addMapLayers( [hazard_layer, exposure_layer]) # Count the total value of all exposure pixels # this is arse about face but width is actually giving height height = exposure_layer.width() # this is arse about face but height is actually giving width width = exposure_layer.height() provider = exposure_layer.dataProvider() # Bands count from 1! block = provider.block(1, provider.extent(), height, width) # Enable on-the-fly reprojection set_canvas_crs(GEOCRS, True) # This is the nicer way but wierdly it gets nan for every cell total_population = 0.0 cell_count = 0 row = 0 # Iterate down each column to match the layout produced by r.stats while row < width: column = 0 while column < height: cell_count += 1 value = block.value(row, column) if value > 0: total_population += value column += 1 row += 1 print "Total value of all cells is: %d" % total_population print "Number of cells counted: %d" % cell_count # 131 computed using r.sum self.assertAlmostEqual(total_population, 131.0177006121) result, message = setup_scenario( self.dock, hazard='flood unaligned', exposure='People never resample', function='Need evacuation', function_id='FloodEvacuationRasterHazardFunction') self.assertTrue(result, message) # Press RUN self.dock.accept() safe_layer = self.dock.analysis.impact_layer keywords = safe_layer.get_keywords() evacuated = float(keywords['evacuated']) self.assertLess(evacuated, total_population) expected_evacuated = 131.0 self.assertEqual(evacuated, expected_evacuated)
def __init__(self, parent=None, iface=None): """Constructor for Raster Reclassification to Vector Polygon. .. versionadded: 3.4 :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ QDialog.__init__(self, parent) self.parent = parent self.setupUi(self) self.setWindowTitle(self.tr('Raster Reclassification')) self.iface = iface self.if_registry = Registry() self.keyword_io = KeywordIO() # populate raster input self.cbo_raster_input.clear() registry = QgsMapLayerRegistry.instance() # MapLayers returns a QMap<QString id, QgsMapLayer layer> layers = registry.mapLayers().values() for layer in layers: try: name = layer.name() source = layer.id() layer_purpose = self.keyword_io.read_keywords( layer, 'layer_purpose') if (isinstance(layer, QgsRasterLayer) and layer_purpose == 'hazard'): add_ordered_combo_item( self.cbo_raster_input, self.tr(name), source) except Exception as e: raise e # self.input_list_parameter = InputListParameter() # self.input_list_parameter.name = 'Thresholds' # self.input_list_parameter.description = ( # 'List of thresholds of values used in reclassification.') # self.input_list_parameter.help_text = 'list of thresholds used' # self.input_list_parameter.maximum_item_count = 100 # self.input_list_parameter.minimum_item_count = 1 # self.input_list_parameter.element_type = float # self.input_list_parameter.value = [0.0, 1.0] # self.input_list_parameter.ordering = \ # InputListParameter.AscendingOrder # self.thresholds_widget = InputListParameterWidget( # self.input_list_parameter) # self.threshold_editor.layout().addWidget(self.thresholds_widget) # Set up context help self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help) self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok) # self.cancel_button = self.button_box.button( # QtGui.QDialogButtonBox.Cancel) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # adapt layer changed self.cbo_raster_input.currentIndexChanged.connect(self.raster_changed) self.raster_changed(self.cbo_raster_input.currentIndex())
def render_nearby_table(self): hazard_mapping = { 0: 'Very Low', 1: 'Low', 2: 'Moderate', 3: 'High', 4: 'Very High' } # load PLACES keyword_io = KeywordIO() try: cities_impact = read_qgis_layer( self.working_dir_path('cities_impact.shp'), 'Cities') hazard = keyword_io.read_keywords(cities_impact, 'target_field') hazard_field_index = cities_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.cities_layer, 'name_field') name_field_index = cities_impact.fieldNameIndex(name_field) try: population_field = keyword_io.read_keywords( self.cities_layer, 'population_field') population_field_index = cities_impact.fieldNameIndex( population_field) except KeywordNotFoundError: population_field = None population_field_index = None table_places = [] for f in cities_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] city_name = f.attributes()[name_field_index] if population_field_index >= 0: city_pop = f.attributes()[population_field_index] else: city_pop = 1 # format: # [ # 'hazard class', # 'city's population', # 'city's name', # 'the type' # ] haz = hazard_mapping[haz_class] item = { 'class': haz_class, 'hazard': haz, 'css': haz.lower().replace(' ', '-'), 'population': format_int(population_rounding(city_pop / 1000)), 'name': city_name.title(), 'type': 'places' } table_places.append(item) # sort table by hazard zone, then population table_places = sorted(table_places, key=lambda x: (-x['class'], -x['population'])) except Exception as e: LOGGER.exception(e) table_places = [] # load AIRPORTS try: airport_impact = read_qgis_layer( self.working_dir_path('airport_impact.shp'), 'Airport') hazard = keyword_io.read_keywords(airport_impact, 'target_field') hazard_field_index = airport_impact.fieldNameIndex(hazard) name_field = keyword_io.read_keywords(self.airport_layer, 'name_field') name_field_index = airport_impact.fieldNameIndex(name_field) # airport doesnt have population, so enter 0 for population table_airports = [] for f in airport_impact.getFeatures(): haz_class = f.attributes()[hazard_field_index] airport_name = f.attributes()[name_field_index] haz = hazard_mapping[haz_class] item = { 'class': haz_class, 'hazard': haz, 'css': haz.lower().replace(' ', '-'), 'population': 0, 'name': airport_name.title(), 'type': 'airport' } table_airports.append(item) # Sort by hazard class table_airports = sorted(table_airports, key=lambda x: -x['class']) except Exception as e: LOGGER.exception(e) table_airports = [] # decide which to show # maximum 2 airport max_airports = 2 airport_count = min(max_airports, len(table_airports)) # maximum total 7 entries to show max_rows = 6 places_count = min(len(table_places), max_rows - airport_count) # get top airport table_airports = table_airports[:airport_count] # get top places table_places = table_places[:places_count] item_list = table_places + table_airports # sort entry by hazard level item_list = sorted(item_list, key=lambda x: (-x['class'], -x['population'])) nearby_template = self.ash_fixtures_dir('nearby-table.template.html') with open(nearby_template) as f: template = Template(f.read()) # generate table here html_string = template.render(item_list=item_list) with open(self.nearby_html_path, 'w') as f: f.write(html_string) # copy airport logo shutil.copy(self.ash_fixtures_dir('logo/airport.jpg'), self.working_dir_path('airport.jpg'))
def _clip_raster_layer( layer, extent, cell_size=None, extra_keywords=None): """Clip a Hazard or Exposure raster layer to the extents provided. The layer must be a raster layer or an exception will be thrown. .. note:: The extent *must* be in EPSG:4326. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS raster layer in EPSG:4326 :type layer: QgsRasterLayer :param extent: An array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. or: A QgsGeometry of type polygon. **Polygon clipping currently only supported for vector datasets.** :type extent: list(float), QgsGeometry :param cell_size: Cell size (in GeoCRS) which the layer should be resampled to. If not provided for a raster layer (i.e. theCellSize=None), the native raster cell size will be used. :type cell_size: float :returns: Output clipped layer (placed in the system temp dir). :rtype: QgsRasterLayer :raises: InvalidProjectionError - if input layer is a density layer in projected coordinates. See issue #123. """ if not layer or not extent: message = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(message) if layer.type() != QgsMapLayer.RasterLayer: message = tr( 'Expected a raster layer but received a %s.' % str(layer.type())) raise InvalidParameterError(message) working_layer = layer.source() # Check for existence of keywords file base, _ = os.path.splitext(working_layer) keywords_path = base + '.keywords' message = tr( 'Input file to be clipped "%s" does not have the ' 'expected keywords file %s' % ( working_layer, keywords_path )) verify(os.path.isfile(keywords_path), message) # Raise exception if layer is projected and refers to density (issue #123) # FIXME (Ole): Need to deal with it - e.g. by automatically reprojecting # the layer at this point and setting the native resolution accordingly # in its keywords. keywords = read_file_keywords(keywords_path) if 'datatype' in keywords and keywords['datatype'] == 'count': if str(layer.crs().authid()) != 'EPSG:4326': # This layer is not WGS84 geographic message = ( 'Layer %s represents count but has spatial reference "%s". ' 'Count layers must be given in WGS84 geographic coordinates, ' 'so please reproject and try again. For more information, see ' 'issue https://github.com/AIFDR/inasafe/issues/123' % ( working_layer, layer.crs().toProj4() )) raise InvalidProjectionError(message) # We need to provide gdalwarp with a dataset for the clip # because unlike gdal_translate, it does not take projwin. clip_kml = extent_to_kml(extent) # Create a filename for the clipped, resampled and reprojected layer handle, filename = tempfile.mkstemp('.tif', 'clip_', temp_dir()) os.close(handle) os.remove(filename) # If no cell size is specified, we need to run gdalwarp without # specifying the output pixel size to ensure the raster dims # remain consistent. binary_list = which('gdalwarp') LOGGER.debug('Path for gdalwarp: %s' % binary_list) if len(binary_list) < 1: raise CallGDALError( tr('gdalwarp could not be found on your computer')) # Use the first matching gdalwarp found binary = binary_list[0] if cell_size is None: command = ( '"%s" -q -t_srs EPSG:4326 -r near -cutline %s -crop_to_cutline ' '-ot Float64 -of GTiff "%s" "%s"' % ( binary, clip_kml, working_layer, filename)) else: command = ( '"%s" -q -t_srs EPSG:4326 -r near -tr %s %s -cutline %s ' '-crop_to_cutline -ot Float64 -of GTiff "%s" "%s"' % ( binary, repr(cell_size), repr(cell_size), clip_kml, working_layer, filename)) LOGGER.debug(command) result = QProcess().execute(command) # For QProcess exit codes see # http://qt-project.org/doc/qt-4.8/qprocess.html#execute if result == -2: # cannot be started message_detail = tr('Process could not be started.') message = tr( '<p>Error while executing the following shell command:' '</p><pre>%s</pre><p>Error message: %s' % (command, message_detail)) raise CallGDALError(message) elif result == -1: # process crashed message_detail = tr('Process crashed.') message = tr( '<p>Error while executing the following shell command:</p>' '<pre>%s</pre><p>Error message: %s' % (command, message_detail)) raise CallGDALError(message) # .. todo:: Check the result of the shell call is ok keyword_io = KeywordIO() keyword_io.copy_keywords(layer, filename, extra_keywords=extra_keywords) base_name = '%s clipped' % layer.name() layer = QgsRasterLayer(filename, base_name) return layer
def create_keyword_file(self, algorithm): """Create keyword file for the raster file created. Basically copy a template from keyword file in converter data and add extra keyword (usually a title) :param algorithm: Which re-sampling algorithm to use. valid options are 'nearest' (for nearest neighbour), 'invdist' (for inverse distance), 'average' (for moving average). Defaults to 'nearest' if not specified. Note that passing re-sampling alg parameters is currently not supported. If None is passed it will be replaced with 'nearest'. :type algorithm: str """ keyword_io = KeywordIO() # Set thresholds for each exposure mmi_default_classes = default_classification_thresholds( earthquake_mmi_scale) mmi_default_threshold = { earthquake_mmi_scale['key']: { 'active': True, 'classes': mmi_default_classes } } generic_default_classes = default_classification_thresholds( generic_hazard_classes) generic_default_threshold = { generic_hazard_classes['key']: { 'active': True, 'classes': generic_default_classes } } threshold_keyword = {} for exposure in exposure_all: # Not all exposure is supported by earthquake_mmi_scale if exposure in earthquake_mmi_scale['exposures']: threshold_keyword[exposure['key']] = mmi_default_threshold else: threshold_keyword[exposure['key']] = generic_default_threshold extra_keywords = { extra_keyword_earthquake_latitude['key']: self.latitude, extra_keyword_earthquake_longitude['key']: self.longitude, extra_keyword_earthquake_magnitude['key']: self.magnitude, extra_keyword_earthquake_depth['key']: self.depth, extra_keyword_earthquake_description['key']: self.description, extra_keyword_earthquake_location['key']: self.location, extra_keyword_earthquake_event_time['key']: self.time.strftime('%Y-%m-%dT%H:%M:%S'), extra_keyword_time_zone['key']: self.time_zone, extra_keyword_earthquake_x_minimum['key']: self.x_minimum, extra_keyword_earthquake_x_maximum['key']: self.x_maximum, extra_keyword_earthquake_y_minimum['key']: self.y_minimum, extra_keyword_earthquake_y_maximum['key']: self.y_maximum, extra_keyword_earthquake_event_id['key']: self.event_id } for key, value in self.extra_keywords.items(): extra_keywords[key] = value # Delete empty element. empty_keys = [] for key, value in extra_keywords.items(): if value is None: empty_keys.append(key) for empty_key in empty_keys: extra_keywords.pop(empty_key) keywords = { 'hazard': hazard_earthquake['key'], 'hazard_category': hazard_category_single_event['key'], 'keyword_version': inasafe_keyword_version, 'layer_geometry': layer_geometry_raster['key'], 'layer_mode': layer_mode_continuous['key'], 'layer_purpose': layer_purpose_hazard['key'], 'continuous_hazard_unit': unit_mmi['key'], 'classification': earthquake_mmi_scale['key'], 'thresholds': threshold_keyword, 'extra_keywords': extra_keywords, 'active_band': 1 } if self.algorithm_name: layer_path = os.path.join( self.output_dir, '%s-%s.tif' % (self.output_basename, algorithm)) else: layer_path = os.path.join(self.output_dir, '%s.tif' % self.output_basename) # append title and source to the keywords file if len(self.title.strip()) == 0: keyword_title = self.output_basename else: keyword_title = self.title keywords['title'] = keyword_title hazard_layer = QgsRasterLayer(layer_path, keyword_title) if not hazard_layer.isValid(): raise InvalidLayerError() keyword_io.write_keywords(hazard_layer, keywords)
def __init__(self, iface, template_metadata, impact_function=None, hazard=None, exposure=None, impact=None, analysis=None, exposure_summary_table=None, aggregation_summary=None, extra_layers=None, ordered_layers=None, legend_layers=None, minimum_needs_profile=None, multi_exposure_impact_function=None, use_template_extent=False): """Constructor for the Composition Report class. :param iface: Reference to the QGIS iface object. :type iface: QgsAppInterface :param template_metadata: InaSAFE template metadata. :type template_metadata: ReportMetadata :param impact_function: Impact function instance for the report :type impact_function: safe.impact_function.impact_function.ImpactFunction .. versionadded:: 4.0 """ LOGGER.debug('InaSAFE Impact Report class initialised') self._iface = iface self._metadata = template_metadata self._output_folder = None self._impact_function = impact_function or ( multi_exposure_impact_function) self._hazard = hazard or self._impact_function.hazard self._analysis = (analysis or self._impact_function.analysis_impacted) if impact_function: self._exposure = (exposure or self._impact_function.exposure) self._impact = (impact or self._impact_function.impact) self._exposure_summary_table = ( exposure_summary_table or self._impact_function.exposure_summary_table) self._aggregation_summary = ( aggregation_summary or self._impact_function.aggregation_summary) if extra_layers is None: extra_layers = [] self._extra_layers = extra_layers self._ordered_layers = ordered_layers self._legend_layers = legend_layers self._minimum_needs = minimum_needs_profile self._multi_exposure_impact_function = multi_exposure_impact_function self._use_template_extent = use_template_extent self._inasafe_context = InaSAFEReportContext() # QgsMapSettings is added in 2.4 if self._iface: map_settings = self._iface.mapCanvas().mapSettings() else: map_settings = QgsMapSettings() self._qgis_composition_context = QgsLayoutContext( None, map_settings, ImpactReport.DEFAULT_PAGE_DPI) self._keyword_io = KeywordIO()
def __init__(self, parent, iface, dock=None, layer=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: Quantum GIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle( self.tr('InaSAFE %s Keywords Editor' % get_version())) # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.defaults = None # string constants self.global_default_string = definitions.global_default_attribute[ 'name'] self.do_not_use_string = definitions.do_not_use_attribute['name'] self.global_default_data = definitions.global_default_attribute['id'] self.do_not_use_data = definitions.do_not_use_attribute['id'] if layer is None: self.layer = self.iface.activeLayer() else: self.layer = layer self.keyword_io = KeywordIO() # note the keys should remain untranslated as we need to write # english to the keywords file. The keys will be written as user data # in the combo entries. # .. seealso:: http://www.voidspace.org.uk/python/odict.html self.standard_exposure_list = OrderedDict([ ('population', self.tr('population')), ('structure', self.tr('structure')), ('road', self.tr('road')), ('Not Set', self.tr('Not Set')) ]) self.standard_hazard_list = OrderedDict([ ('earthquake [MMI]', self.tr('earthquake [MMI]')), ('tsunami [m]', self.tr('tsunami [m]')), ('tsunami [wet/dry]', self.tr('tsunami [wet/dry]')), ('tsunami [feet]', self.tr('tsunami [feet]')), ('flood [m]', self.tr('flood [m]')), ('flood [wet/dry]', self.tr('flood [wet/dry]')), ('flood [feet]', self.tr('flood [feet]')), ('tephra [kg2/m2]', self.tr('tephra [kg2/m2]')), ('volcano', self.tr('volcano')), ('generic [categorised]', self.tr('generic [categorised]')), ('Not Set', self.tr('Not Set')) ]) # noinspection PyUnresolvedReferences self.lstKeywords.itemClicked.connect(self.edit_key_value_pair) # Set up help dialog showing logic. help_button = self.buttonBox.button(QtGui.QDialogButtonBox.Help) help_button.clicked.connect(self.show_help) if self.layer is not None and is_polygon_layer(self.layer): # set some initial ui state: self.defaults = get_defaults() self.radPredefined.setChecked(True) self.dsbFemaleRatioDefault.blockSignals(True) self.dsbFemaleRatioDefault.setValue(self.defaults['FEMALE_RATIO']) self.dsbFemaleRatioDefault.blockSignals(False) self.dsbYouthRatioDefault.blockSignals(True) self.dsbYouthRatioDefault.setValue(self.defaults['YOUTH_RATIO']) self.dsbYouthRatioDefault.blockSignals(False) self.dsbAdultRatioDefault.blockSignals(True) self.dsbAdultRatioDefault.setValue(self.defaults['ADULT_RATIO']) self.dsbAdultRatioDefault.blockSignals(False) self.dsbElderlyRatioDefault.blockSignals(True) self.dsbElderlyRatioDefault.setValue( self.defaults['ELDERLY_RATIO']) self.dsbElderlyRatioDefault.blockSignals(False) else: self.radPostprocessing.hide() self.tab_widget.removeTab(1) if self.layer: self.load_state_from_keywords() # add a reload from keywords button reload_button = self.buttonBox.addButton( self.tr('Reload'), QtGui.QDialogButtonBox.ActionRole) reload_button.clicked.connect(self.load_state_from_keywords) self.resize_dialog() self.tab_widget.setCurrentIndex(0) # TODO No we should not have test related stuff in prod code. TS self.test = False
def check_input_layer(layer, purpose): """Function to check if the layer is valid. The function will also set the monkey patching if needed. :param layer: The layer to test. :type layer: QgsMapLayer :param purpose: The expected purpose of the layer. :type purpose: basestring :return: A tuple with the status of the layer and an error message if needed. The status is 0 if everything was fine. The status is 1 if the client should fix something. :rtype: (int, m.Message) """ if not layer.isValid(): title = tr('The {purpose} layer is invalid').format(purpose=purpose) content = tr('The impact function needs a {exposure} layer to run. ' 'You must provide a valid {exposure} layer.').format( purpose=purpose) message = generate_input_error_message(title, m.Paragraph(content)) return PREPARE_FAILED_BAD_INPUT, message # We should read it using KeywordIO for the very beginning. To avoid # get the modified keywords in the patching. try: keywords = KeywordIO().read_keywords(layer) except NoKeywordsFoundError: title = tr('The {purpose} layer does not have keywords.').format( purpose=purpose) content = tr( 'The {purpose} layer does not have keywords. Use the wizard ' 'to assign keywords to the layer.').format(purpose=purpose) message = generate_input_error_message(title, m.Paragraph(content)) return PREPARE_FAILED_BAD_INPUT, message if keywords.get('layer_purpose') != purpose: title = tr('The expected {purpose} layer is not an {purpose}.') \ .format(purpose=purpose) content = tr('The expected {purpose} layer is not an {purpose}.') \ .format(purpose=purpose) message = generate_input_error_message(title, m.Paragraph(content)) return PREPARE_FAILED_BAD_INPUT, message version = keywords.get(inasafe_keyword_version_key) supported = is_keyword_version_supported(version) if not supported: parameters = { 'version': inasafe_keyword_version, 'source': layer.publicSource() } title = tr('The {purpose} layer is not up to date.').format( purpose=purpose) content = tr('The layer {source} must be updated to {version}.' ).format(**parameters) message = generate_input_error_message(title, m.Paragraph(content)) return PREPARE_FAILED_BAD_INPUT, message layer.keywords = keywords if is_vector_layer(layer): try: check_inasafe_fields(layer, keywords_only=True) except InvalidLayerError: title = tr('The {purpose} layer is not up to date.').format( purpose=purpose) content = tr( 'The layer {source} must be updated with the keyword ' 'wizard. Your fields which have been set in the keywords ' 'previously are not matching your layer.').format( source=layer.publicSource()) message = generate_input_error_message(title, m.Paragraph(content)) del layer.keywords return PREPARE_FAILED_BAD_INPUT, message return PREPARE_SUCCESS, None
def __init__(self, parent=None, iface=None, dock=None): """Constructor for the dialog. .. note:: In QtDesigner the advanced editor's predefined keywords list should be shown in english always, so when adding entries to cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick the :safe_qgis:`translatable` property. :param parent: Parent widget of this dialog. :type parent: QWidget :param iface: QGIS QGisAppInterface instance. :type iface: QGisAppInterface :param dock: Dock widget instance that we can notify of changes to the keywords. Optional. :type dock: Dock """ QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('InaSAFE') # Constants self.keyword_creation_wizard_name = tr( 'InaSAFE Keywords Creation Wizard') self.ifcw_name = tr('InaSAFE Impact Function Centric Wizard') # Note the keys should remain untranslated as we need to write # english to the keywords file. # Save reference to the QGIS interface and parent self.iface = iface self.parent = parent self.dock = dock self.suppress_warning_dialog = False self.lblStep.clear() # Set icons self.lblMainIcon.setPixmap( QPixmap(resources_path('img', 'icons', 'icon-white.svg'))) self.keyword_io = KeywordIO() self.is_selected_layer_keywordless = False self.parent_step = None self.pbnBack.setEnabled(False) self.pbnNext.setEnabled(False) self.pbnCancel.released.connect(self.reject) # Initialize attributes self.existing_keywords = None self.layer = None self.hazard_layer = None self.exposure_layer = None self.aggregation_layer = None self.step_kw_purpose = StepKwPurpose(self) self.step_kw_subcategory = StepKwSubcategory(self) self.step_kw_hazard_category = StepKwHazardCategory(self) self.step_kw_band_selector = StepKwBandSelector(self) self.step_kw_layermode = StepKwLayerMode(self) self.step_kw_unit = StepKwUnit(self) self.step_kw_classification = StepKwClassification(self) self.step_kw_field = StepKwField(self) self.step_kw_multi_classifications = StepKwMultiClassifications(self) self.step_kw_classify = StepKwClassify(self) self.step_kw_threshold = StepKwThreshold(self) self.step_kw_fields_mapping = StepKwFieldsMapping(self) self.step_kw_inasafe_fields = StepKwInaSAFEFields(self) self.step_kw_default_inasafe_fields = StepKwDefaultInaSAFEFields(self) self.step_kw_inasafe_raster_default_values = \ StepKwInaSAFERasterDefaultValues(self) self.step_kw_source = StepKwSource(self) self.step_kw_title = StepKwTitle(self) self.step_kw_summary = StepKwSummary(self) self.step_fc_functions1 = StepFcFunctions1(self) self.step_fc_functions2 = StepFcFunctions2(self) self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self) self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self) self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self) self.step_fc_explayer_origin = StepFcExpLayerOrigin(self) self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self) self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self) self.step_fc_disjoint_layers = StepFcDisjointLayers(self) self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self) self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self) self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self) self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self) self.step_fc_extent = StepFcExtent(self) self.step_fc_extent_disjoint = StepFcExtentDisjoint(self) self.step_fc_summary = StepFcSummary(self) self.step_fc_analysis = StepFcAnalysis(self) self.wizard_help = WizardHelp(self) self.stackedWidget.addWidget(self.step_kw_purpose) self.stackedWidget.addWidget(self.step_kw_subcategory) self.stackedWidget.addWidget(self.step_kw_hazard_category) self.stackedWidget.addWidget(self.step_kw_band_selector) self.stackedWidget.addWidget(self.step_kw_layermode) self.stackedWidget.addWidget(self.step_kw_unit) self.stackedWidget.addWidget(self.step_kw_classification) self.stackedWidget.addWidget(self.step_kw_field) self.stackedWidget.addWidget(self.step_kw_multi_classifications) self.stackedWidget.addWidget(self.step_kw_classify) self.stackedWidget.addWidget(self.step_kw_threshold) self.stackedWidget.addWidget(self.step_kw_fields_mapping) self.stackedWidget.addWidget(self.step_kw_inasafe_fields) self.stackedWidget.addWidget(self.step_kw_default_inasafe_fields) self.stackedWidget.addWidget( self.step_kw_inasafe_raster_default_values) self.stackedWidget.addWidget(self.step_kw_source) self.stackedWidget.addWidget(self.step_kw_title) self.stackedWidget.addWidget(self.step_kw_summary) self.stackedWidget.addWidget(self.step_fc_functions1) self.stackedWidget.addWidget(self.step_fc_functions2) self.stackedWidget.addWidget(self.step_fc_hazlayer_origin) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser) self.stackedWidget.addWidget(self.step_fc_explayer_origin) self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_explayer_from_browser) self.stackedWidget.addWidget(self.step_fc_disjoint_layers) self.stackedWidget.addWidget(self.step_fc_agglayer_origin) self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas) self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser) self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint) self.stackedWidget.addWidget(self.step_fc_extent) self.stackedWidget.addWidget(self.step_fc_extent_disjoint) self.stackedWidget.addWidget(self.step_fc_summary) self.stackedWidget.addWidget(self.step_fc_analysis) self.stackedWidget.addWidget(self.wizard_help) # QSetting self.setting = QSettings() # Wizard Steps self.impact_function_steps = [] self.keyword_steps = [] self.on_help = False self.resized.connect(self.after_resize)
def __init__(self, parent=None, iface=None): """Constructor for dialog. :param parent: Optional widget to use as parent :type parent: QWidget :param iface: An instance of QGisInterface :type iface: QGisInterface """ QDialog.__init__(self, parent) self.parent = parent self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Impact Layer Merge Tool')) self.iface = iface self.keyword_io = KeywordIO() # Template Path for composer self.template_path = resources_path('qgis-composer-templates', 'merged_report.qpt') # Safe Logo Path self.safe_logo_path = resources_path('img', 'logos', 'inasafe-logo-url.png') # Organisation Logo Path self.organisation_logo_path = resources_path('img', 'logos', 'supporters.png') # Disclaimer text self.disclaimer = disclaimer() # The output directory self.out_dir = None # Stored information from first impact layer self.first_impact = { 'layer': None, 'map_title': None, 'hazard_title': None, 'exposure_title': None, 'postprocessing_report': None, } # Stored information from second impact layer self.second_impact = { 'layer': None, 'map_title': None, 'hazard_title': None, 'exposure_title': None, 'postprocessing_report': None, } # Stored information from aggregation layer self.aggregation = {'layer': None, 'aggregation_attribute': None} # The summary report, contains report for each aggregation area self.summary_report = {} # The html reports and its file path self.html_reports = {} # A boolean flag whether to merge entire area or aggregated self.entire_area_mode = False # Get the global settings and override some variable if exist self.read_settings() # Get all current project layers for combo box self.get_project_layers() # Set up context help help_button = self.button_box.button(QtGui.QDialogButtonBox.Help) help_button.clicked.connect(self.show_help) # Show usage info self.show_info() self.restore_state()