def test_empty_layer(self): """Test if we import an empty layer.""" layer = create_memory_layer( 'test', QgsWkbTypes.PolygonGeometry, QgsCoordinateReferenceSystem(3857), [ QgsField('my_field_1', QVariant.Int), QgsField('my_field_2', QVariant.Double), QgsField('my_field_3', QVariant.String) ]) self.assertTrue(layer.isValid()) self.assertEqual(len(layer.fields()), 3) # These following tests doesn't work if we add 'geosjon' in the formats # list. https://issues.qgis.org/issues/18370 formats = ['shp'] for extension in formats: path = QDir(mkdtemp()) data_store = Folder(path) data_store.default_vector_format = extension name = 'test' result, message = data_store.add_layer(layer, name) self.assertTrue(result, message) # Fetch the layer imported_layer = data_store.layer(name) self.assertTrue(imported_layer.isValid()) self.assertListEqual([f.name() for f in imported_layer.fields()], ['my_field_1', 'my_field_2', 'my_field_3'])
def accept(self): """Process the layer for multi buffering and generate a new layer. .. note:: This is called on OK click. """ # set parameter from dialog input_layer = self.layer.currentLayer() output_path = self.output_form.text() radius = self.get_classification() # monkey patch keywords so layer works on multi buffering function input_layer.keywords = {'inasafe_fields': {}} # run multi buffering self.output_layer = multi_buffering(input_layer, radius) # save output layer to data store and check whether user # provide the output path. if output_path: self.output_directory, self.output_filename = ( os.path.split(output_path)) self.output_filename, self.output_extension = ( os.path.splitext(self.output_filename)) # if user do not provide the output path, create a temporary file. else: self.output_directory = temp_dir(sub_dir='work') self.output_filename = ( unique_filename( prefix='hazard_layer', suffix='.geojson', dir=self.output_directory)) self.output_filename = os.path.split(self.output_filename)[1] self.output_filename, self.output_extension = ( os.path.splitext(self.output_filename)) self.data_store = Folder(self.output_directory) if self.output_extension == '.shp': self.data_store.default_vector_format = 'shp' elif self.output_extension == '.geojson': self.data_store.default_vector_format = 'geojson' self.data_store.add_layer(self.output_layer, self.output_filename) # add output layer to map canvas self.output_layer = self.data_store.layer(self.output_filename) QgsMapLayerRegistry.instance().addMapLayers( [self.output_layer]) self.iface.setActiveLayer(self.output_layer) self.iface.zoomToActiveLayer() self.done(QtGui.QDialog.Accepted) if self.keyword_wizard_checkbox.isChecked(): self.launch_keyword_wizard()
def save_layer_to_file(layer): """Save a QGIS layer to disk. :param layer: The layer to save. :type layer: QgsMapLayer :return: The path to the file. :rtype: str """ path = mkdtemp() data_store = Folder(path) data_store.default_vector_format = 'geojson' result = data_store.add_layer(layer, 'debug_layer') return data_store.layer_uri(result[1])
def accept(self): """Process the layer and field and generate a new layer. .. note:: This is called on OK click. """ # run minimum needs calculator try: success, self.result_layer = (self.minimum_needs( self.layer.currentLayer())) if not success: return except Exception as e: error_name, traceback = humanise_exception(e) message = ('Problem(s) occured. \n%s \nDiagnosis: \n%s' % (error_name, traceback)) display_critical_message_box( title=self.tr('Error while calculating minimum needs'), message=message) return # remove monkey patching keywords del self.result_layer.keywords # write memory layer to file system settings = QSettings() default_user_directory = settings.value('inasafe/defaultUserDirectory', defaultValue='') if default_user_directory: path = os.path.join(default_user_directory, self.result_layer.name()) if not os.path.exists(path): os.makedirs(path) data_store = Folder(path) else: data_store = Folder(temp_dir(sub_dir=self.result_layer.name())) data_store.default_vector_format = 'geojson' data_store.add_layer(self.result_layer, self.result_layer.name()) self.result_layer = data_store.layer(self.result_layer.name()) # noinspection PyArgumentList QgsMapLayerRegistry.instance().addMapLayers( [data_store.layer(self.result_layer.name())]) self.done(QtGui.QDialog.Accepted)
def accept(self): """Process the layer and field and generate a new layer. .. note:: This is called on OK click. """ # run minimum needs calculator try: success, self.result_layer = ( self.minimum_needs(self.layer.currentLayer())) if not success: return except Exception as e: error_name, traceback = humanise_exception(e) message = ( 'Problem(s) occured. \n%s \nDiagnosis: \n%s' % ( error_name, traceback)) display_critical_message_box( title=self.tr('Error while calculating minimum needs'), message=message) return # remove monkey patching keywords del self.result_layer.keywords # write memory layer to file system settings = QSettings() default_user_directory = settings.value( 'inasafe/defaultUserDirectory', defaultValue='') if default_user_directory: output_directory = os.path.join( default_user_directory, 'minimum_needs_calculator') if not os.path.exists(output_directory): os.makedirs(output_directory) else: output_directory = temp_dir(sub_dir='minimum_needs_calculator') output_layer_name = os.path.split(self.result_layer.name())[1] # If normal filename doesn't exist, then use normal filename random_string_length = len(output_layer_name.split('_')[-1]) normal_filename = output_layer_name[:-(random_string_length + 1)] if not os.path.exists(os.path.join(output_directory, normal_filename)): output_layer_name = normal_filename data_store = Folder(output_directory) data_store.default_vector_format = 'geojson' data_store.add_layer(self.result_layer, output_layer_name) self.result_layer = data_store.layer(output_layer_name) # noinspection PyArgumentList QgsProject.instance().addMapLayers( [data_store.layer(self.result_layer.name())]) self.done(QtWidgets.QDialog.Accepted)
def run_impact_function(cli_arguments): """Runs an analysis and delegates producing pdf and .geojson output layers. .. versionadded:: 3.2 :param cli_arguments: User inputs. :type cli_arguments: CommandLineArguments """ hazard = get_layer(cli_arguments.hazard, 'Hazard Layer') exposure = get_layer(cli_arguments.exposure, 'Exposure Layer') aggregation = None if cli_arguments.aggregation: aggregation = get_layer(cli_arguments.aggregation, 'Aggregation Layer') # Set up impact function impact_function = ImpactFunction() impact_function.hazard = hazard impact_function.exposure = exposure impact_function.aggregation = aggregation # Set the datastore impact_function.datastore = Folder(cli_arguments.output_dir) impact_function.datastore.default_vector_format = 'geojson' # Set the extent if cli_arguments.extent: impact_function.requested_extent_crs = \ QgsCoordinateReferenceSystem(4326) try: impact_function.requested_extent = QgsRectangle( float(cli_arguments.extent[0]), float(cli_arguments.extent[1]), float(cli_arguments.extent[2]), float(cli_arguments.extent[3]) ) except AttributeError: print "Extent is not valid..." pass # Prepare impact function status, message = impact_function.prepare() if status != PREPARE_SUCCESS: print message.to_text() return status, message, None status, message = impact_function.run() if status != ANALYSIS_SUCCESS: print message.to_text() return status, message, None return status, message, impact_function
def accept(self): """Process the layer and field and generate a new layer. .. note:: This is called on OK click. """ # run minimum needs calculator try: success, self.result_layer = ( self.minimum_needs(self.layer.currentLayer())) if not success: return except Exception as e: error_name, traceback = humanise_exception(e) message = ( 'Problem(s) occured. \n%s \nDiagnosis: \n%s' % ( error_name, traceback)) display_critical_message_box( title=self.tr('Error while calculating minimum needs'), message=message) return # remove monkey patching keywords del self.result_layer.keywords # write memory layer to file system settings = QSettings() default_user_directory = settings.value( 'inasafe/defaultUserDirectory', defaultValue='') if default_user_directory: path = os.path.join( default_user_directory, self.result_layer.name()) if not os.path.exists(path): os.makedirs(path) data_store = Folder(path) else: data_store = Folder(temp_dir(sub_dir=self.result_layer.name())) data_store.default_vector_format = 'geojson' data_store.add_layer(self.result_layer, self.result_layer.name()) self.result_layer = data_store.layer(self.result_layer.name()) # noinspection PyArgumentList QgsMapLayerRegistry.instance().addMapLayers( [data_store.layer(self.result_layer.name())]) self.done(QtGui.QDialog.Accepted)
def rasterize_vector_layer(layer, width, height, extent): """Rasterize a vector layer to the grid given by extent and width/height. :param layer: The vector layer. :type layer: QgsVectorLayer :param width: The width of the output. :type width: int :param height: The height of the output. :type height: int :param extent: The extent to use. :type extent: QgsRectangle :return: The new raster layer. :rtype: QgsRasterLayer """ name = rasterize_steps['gdal_layer_name'] output_filename = unique_filename(prefix=name, suffix='.tif') extent_str = '%f,%f,%f,%f' % (extent.xMinimum(), extent.xMaximum(), extent.yMinimum(), extent.yMaximum()) keywords = dict(layer.keywords) # The layer is in memory, we need to save it to a file for Processing. data_store = Folder(mkdtemp()) data_store.default_vector_format = 'geojson' result = data_store.add_layer(layer, 'vector_layer') layer = data_store.layer(result[1]) assert layer.isValid() field = layer.keywords['inasafe_fields'][aggregation_id_field['key']] # ET 21/02/17. I got some issues using rasterize algorithm from Processing. # I keep it in case of we need it later. Let's use gdal command line. use_gdal_command_line = True if use_gdal_command_line: startupinfo = None if sys.platform == 'win32': # On windows, we don't want to display the bash shell. # https://github.com/inasafe/inasafe/issues/3980 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW commands = [which('gdal_rasterize')[0]] commands += ['-a', field] commands += ['-ts', str(width), str(height)] commands += ['-ot', 'Int16'] commands += ['-a_nodata', "'-1'"] commands += [layer.source(), output_filename] LOGGER.info(' '.join(commands)) result = subprocess.check_call(commands, startupinfo=startupinfo) LOGGER.info('Result : %s' % result) else: parameters = dict() parameters['INPUT'] = layer parameters['FIELD'] = field parameters['DIMENSIONS'] = 0 # output size is given in pixels parameters['WIDTH'] = width parameters['HEIGHT'] = height parameters['RASTER_EXT'] = extent_str parameters['TFW'] = False # force generation of ESRI TFW parameters['RTYPE'] = 1 # raster type: Int16 parameters['NO_DATA'] = '-1' # nodata value parameters['COMPRESS'] = 4 # GeoTIFF compression: DEFLATE parameters['JPEGCOMPRESSION'] = 75 # JPEG compression level: 75 parameters['ZLEVEL'] = 6 # DEFLATE compression level parameters['PREDICTOR'] = 1 # predictor for JPEG/DEFLATE parameters['TILED'] = False # Tiled GeoTIFF? parameters['BIGTIFF'] = 0 # whether to make big TIFF parameters['EXTRA'] = '' # additional creation parameters parameters['OUTPUT'] = output_filename result = runalg('gdalogr:rasterize', parameters) if result is None: # Let's try be removing a new parameter added between 2.14 and 2.16 del parameters['RASTER_EXT'] result = runalg('gdalogr:rasterize', parameters) assert result is not None layer_aligned = QgsRasterLayer(output_filename, name, 'gdal') assert layer_aligned.isValid() layer_aligned.keywords = keywords layer_aligned.keywords['title'] = (rasterize_steps['output_layer_name'] % 'aggregation') layer_aligned.keywords['layer_purpose'] = ( layer_purpose_aggregation_summary['key']) del layer_aligned.keywords['inasafe_fields'] check_layer(layer_aligned) return layer_aligned
class MultiBufferDialog(QtWidgets.QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE multi buffer tool.""" def __init__(self, parent=None, iface=None, dock_widget=None): """Constructor for the multi buffer dialog. :param parent: Parent widget of this dialog. :type parent: QWidget """ QtWidgets.QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Multi Buffer Tool')) icon = resources_path('img', 'icons', 'show-multi-buffer.svg') self.setWindowIcon(QtGui.QIcon(icon)) self.parent = parent self.iface = iface self.dock_widget = dock_widget self.keyword_wizard = None # output file properties initialisation self.data_store = None self.output_directory = None self.output_filename = None self.output_extension = None self.output_layer = None self.classification = [] # set icon self.add_class_button.setIcon( QIcon(resources_path('img', 'icons', 'add.svg'))) self.remove_class_button.setIcon( QIcon(resources_path('img', 'icons', 'remove.svg'))) # prepare dialog initialisation self.layer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.directory_button_status() self.add_class_button_status() self.ok_button_status() self.output_form.setPlaceholderText( self.tr('[Create a temporary layer]')) self.keyword_wizard_checkbox.setChecked(True) # set signal self.layer.layerChanged.connect(self.directory_button_status) self.layer.layerChanged.connect(self.ok_button_status) self.output_form.textChanged.connect(self.ok_button_status) self.directory_button.clicked.connect( self.on_directory_button_tool_clicked) self.radius_form.valueChanged.connect(self.add_class_button_status) self.class_form.textChanged.connect(self.add_class_button_status) self.add_class_button.clicked.connect( self.populate_hazard_classification) self.add_class_button.clicked.connect(self.ok_button_status) self.remove_class_button.clicked.connect( self.remove_selected_classification) self.remove_class_button.clicked.connect(self.ok_button_status) # Set up things for context help self.help_button = self.button_box.button( QtWidgets.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) # Fix for issue 1699 - cancel button does nothing cancel_button = self.button_box.button( QtWidgets.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Fix ends ok_button = self.button_box.button(QtWidgets.QDialogButtonBox.Ok) ok_button.clicked.connect(self.accept) def accept(self): """Process the layer for multi buffering and generate a new layer. .. note:: This is called on OK click. """ # set parameter from dialog input_layer = self.layer.currentLayer() output_path = self.output_form.text() radius = self.get_classification() # monkey patch keywords so layer works on multi buffering function input_layer.keywords = {'inasafe_fields': {}} # run multi buffering self.output_layer = multi_buffering(input_layer, radius) # save output layer to data store and check whether user # provide the output path. if output_path: self.output_directory, self.output_filename = ( os.path.split(output_path)) self.output_filename, self.output_extension = (os.path.splitext( self.output_filename)) # if user do not provide the output path, create a temporary file. else: self.output_directory = temp_dir(sub_dir='work') self.output_filename = (unique_filename(prefix='hazard_layer', suffix='.geojson', dir=self.output_directory)) self.output_filename = os.path.split(self.output_filename)[1] self.output_filename, self.output_extension = (os.path.splitext( self.output_filename)) self.data_store = Folder(self.output_directory) if self.output_extension == '.shp': self.data_store.default_vector_format = 'shp' elif self.output_extension == '.geojson': self.data_store.default_vector_format = 'geojson' self.data_store.add_layer(self.output_layer, self.output_filename) # add output layer to map canvas self.output_layer = self.data_store.layer(self.output_filename) QgsProject.instance().addMapLayers([self.output_layer]) self.iface.setActiveLayer(self.output_layer) self.iface.zoomToActiveLayer() self.done(QtWidgets.QDialog.Accepted) if self.keyword_wizard_checkbox.isChecked(): self.launch_keyword_wizard() @pyqtSlot() # prevents actions being handled twice def on_directory_button_tool_clicked(self): """Autoconnect slot activated when directory button is clicked.""" # noinspection PyCallByClass,PyTypeChecker # set up parameter from dialog input_path = self.layer.currentLayer().source() input_directory, self.output_filename = os.path.split(input_path) file_extension = os.path.splitext(self.output_filename)[1] self.output_filename = os.path.splitext(self.output_filename)[0] # show Qt file directory dialog output_path, __ = QtWidgets.QFileDialog.getSaveFileName( self, self.tr('Output file'), '%s_multi_buffer%s' % (os.path.join( input_directory, self.output_filename), file_extension), 'GeoJSON (*.geojson);;Shapefile (*.shp)') # set selected path to the dialog self.output_form.setText(output_path) def get_output_from_input(self): """Populate output form with default output path based on input layer. """ input_path = self.layer.currentLayer().source() output_path = (os.path.splitext(input_path)[0] + '_multi_buffer' + os.path.splitext(input_path)[1]) self.output_form.setText(output_path) def populate_hazard_classification(self): """Populate hazard classification on hazard class form.""" new_class = { 'value': self.radius_form.value(), 'name': self.class_form.text() } self.classification.append(new_class) self.classification = sorted(self.classification, key=itemgetter('value')) self.hazard_class_form.clear() for item in self.classification: new_item = '{value} - {name}'.format(value=item['value'], name=item['name']) self.hazard_class_form.addItem(new_item) self.radius_form.setValue(0) self.class_form.clear() self.ok_button_status() def remove_selected_classification(self): """Remove selected item on hazard class form.""" removed_classes = self.hazard_class_form.selectedItems() current_item = self.hazard_class_form.currentItem() removed_index = self.hazard_class_form.indexFromItem(current_item) del self.classification[removed_index.row()] for item in removed_classes: self.hazard_class_form.takeItem(self.hazard_class_form.row(item)) def get_classification(self): """Get all hazard class created by user. :return: Hazard class definition created by user. :rtype: OrderedDict """ classification_dictionary = {} for item in self.classification: classification_dictionary[item['value']] = item['name'] classification_dictionary = OrderedDict( sorted(classification_dictionary.items())) return classification_dictionary def directory_button_status(self): """Function to enable or disable directory button.""" if self.layer.currentLayer(): self.directory_button.setEnabled(True) else: self.directory_button.setEnabled(False) def add_class_button_status(self): """Function to enable or disable add class button.""" if self.class_form.text() and self.radius_form.value() >= 0: self.add_class_button.setEnabled(True) else: self.add_class_button.setEnabled(False) def ok_button_status(self): """Function to enable or disable OK button.""" if not self.layer.currentLayer(): self.button_box.button( QtWidgets.QDialogButtonBox.Ok).setEnabled(False) elif (self.hazard_class_form.count() > 0 and self.layer.currentLayer().name() and len(self.output_form.text()) >= 0): self.button_box.button( QtWidgets.QDialogButtonBox.Ok).setEnabled(True) else: self.button_box.button( QtWidgets.QDialogButtonBox.Ok).setEnabled(False) @pyqtSlot(bool) # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the stacked widget. :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user.""" self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user.""" # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = multi_buffer_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def launch_keyword_wizard(self): """Launch keyword creation wizard.""" # make sure selected layer is the output layer if self.iface.activeLayer() != self.output_layer: return # launch wizard dialog self.keyword_wizard = WizardDialog(self.iface.mainWindow(), self.iface, self.dock_widget) self.keyword_wizard.set_keywords_creation_mode(self.output_layer) self.keyword_wizard.exec_() # modal
def run_task(self, task_item, status_item, count=0, index=''): """Run a single task. :param task_item: Table task_item containing task name / details. :type task_item: QTableWidgetItem :param status_item: Table task_item that holds the task status. :type status_item: QTableWidgetItem :param count: Count of scenarios that have been run already. :type count: :param index: The index for the table item that will be run. :type index: int :returns: Flag indicating if the task succeeded or not. :rtype: bool """ self.enable_busy_cursor() for layer_group in self.layer_group_container: layer_group.setItemVisibilityChecked(False) # set status to 'running' status_item.setText(self.tr('Running')) # .. see also:: :func:`appendRow` to understand the next 2 lines variant = task_item.data(QtCore.Qt.UserRole) value = variant[0] result = True if isinstance(value, str): filename = value # run script try: self.run_script(filename) # set status to 'OK' status_item.setText(self.tr('Script OK')) except Exception as e: # pylint: disable=W0703 # set status to 'fail' status_item.setText(self.tr('Script Fail')) LOGGER.exception( 'Running macro failed. The exception: ' + str(e)) result = False elif isinstance(value, dict): # start in new project if toggle is active if self.start_in_new_project: self.iface.newProject() # create layer group group_name = value['scenario_name'] self.layer_group = self.root.addGroup(group_name) self.layer_group_container.append(self.layer_group) # Its a dict containing files for a scenario success, parameters = self.prepare_task(value) if not success: # set status to 'running' status_item.setText(self.tr('Please update scenario')) self.disable_busy_cursor() return False directory = self.output_directory.text() if self.scenario_directory_radio.isChecked(): directory = self.source_directory.text() output_directory = os.path.join(directory, group_name) if not os.path.exists(output_directory): os.makedirs(output_directory) # If impact function parameters loaded successfully, initiate IF. impact_function = ImpactFunction() impact_function.datastore = Folder(output_directory) impact_function.datastore.default_vector_format = "geojson" impact_function.hazard = parameters[layer_purpose_hazard['key']] impact_function.exposure = ( parameters[layer_purpose_exposure['key']]) if parameters[layer_purpose_aggregation['key']]: impact_function.aggregation = ( parameters[layer_purpose_aggregation['key']]) elif parameters['extent']: impact_function.requested_extent = parameters['extent'] impact_function.crs = parameters['crs'] prepare_status, prepare_message = impact_function.prepare() if prepare_status == PREPARE_SUCCESS: LOGGER.info('Impact function ready') status, message = impact_function.run() if status == ANALYSIS_SUCCESS: status_item.setText(self.tr('Analysis Success')) impact_layer = impact_function.impact if impact_layer.isValid(): layer_list = [ impact_layer, impact_function.analysis_impacted, parameters[layer_purpose_hazard['key']], parameters[layer_purpose_exposure['key']], parameters[layer_purpose_aggregation['key']]] QgsProject.instance().addMapLayers( layer_list, False) for layer in layer_list: self.layer_group.addLayer(layer) map_canvas = QgsProject.instance().mapLayers() for layer in map_canvas: # turn of layer visibility if not impact layer if map_canvas[layer].id() == impact_layer.id(): self.set_layer_visible( map_canvas[layer], True) else: self.set_layer_visible( map_canvas[layer], False) # we need to set analysis_impacted as an active layer # because we need to get all qgis variables that we # need from this layer for infographic. if self.iface: self.iface.setActiveLayer( impact_function.analysis_impacted) report_directory = os.path.join( output_directory, 'output') # generate map report and impact report try: error_code, message = ( impact_function.generate_report( all_default_report_components, report_directory)) except BaseException: status_item.setText( self.tr('Report failed to generate.')) else: LOGGER.info('Impact layer is invalid') elif status == ANALYSIS_FAILED_BAD_INPUT: LOGGER.info('Bad input detected') elif status == ANALYSIS_FAILED_BAD_CODE: LOGGER.info( 'Impact function encountered a bug: %s' % message) else: LOGGER.warning('Impact function not ready') send_error_message(self, prepare_message) else: LOGGER.exception('Data type not supported: "%s"' % value) result = False self.disable_busy_cursor() return result
class MultiBufferDialog(QtGui.QDialog, FORM_CLASS): """Dialog implementation class for the InaSAFE multi buffer tool.""" def __init__(self, parent=None, iface=None, dock_widget=None): """Constructor for the multi buffer dialog. :param parent: Parent widget of this dialog. :type parent: QWidget """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(self.tr('InaSAFE Multi Buffer Tool')) self.parent = parent self.iface = iface self.dock_widget = dock_widget self.keyword_wizard = None # output file properties initialisation self.data_store = None self.output_directory = None self.output_filename = None self.output_extension = None self.output_layer = None self.classification = [] # set icon self.add_class_button.setIcon( QIcon(resources_path('img', 'icons', 'add.svg'))) self.remove_class_button.setIcon( QIcon(resources_path('img', 'icons', 'remove.svg'))) # prepare dialog initialisation self.layer.setFilters(QgsMapLayerProxyModel.VectorLayer) self.directory_button_status() self.add_class_button_status() self.ok_button_status() self.output_form.setPlaceholderText( self.tr('[Create a temporary layer]')) self.keyword_wizard_checkbox.setChecked(True) # set signal self.layer.layerChanged.connect(self.directory_button_status) self.layer.layerChanged.connect(self.ok_button_status) self.output_form.textChanged.connect(self.ok_button_status) self.directory_button.clicked.connect( self.on_directory_button_tool_clicked) self.radius_form.valueChanged.connect(self.add_class_button_status) self.class_form.textChanged.connect(self.add_class_button_status) self.add_class_button.clicked.connect( self.populate_hazard_classification) self.add_class_button.clicked.connect(self.ok_button_status) self.remove_class_button.clicked.connect( self.remove_selected_classification) self.remove_class_button.clicked.connect(self.ok_button_status) # 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) # Fix for issue 1699 - cancel button does nothing cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Fix ends ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok) ok_button.clicked.connect(self.accept) def accept(self): """Process the layer for multi buffering and generate a new layer. .. note:: This is called on OK click. """ # set parameter from dialog input_layer = self.layer.currentLayer() output_path = self.output_form.text() radius = self.get_classification() # monkey patch keywords so layer works on multi buffering function input_layer.keywords = {'inasafe_fields': {}} # run multi buffering self.output_layer = multi_buffering(input_layer, radius) # save output layer to data store and check whether user # provide the output path. if output_path: self.output_directory, self.output_filename = ( os.path.split(output_path)) self.output_filename, self.output_extension = ( os.path.splitext(self.output_filename)) # if user do not provide the output path, create a temporary file. else: self.output_directory = temp_dir(sub_dir='work') self.output_filename = ( unique_filename( prefix='hazard_layer', suffix='.geojson', dir=self.output_directory)) self.output_filename = os.path.split(self.output_filename)[1] self.output_filename, self.output_extension = ( os.path.splitext(self.output_filename)) self.data_store = Folder(self.output_directory) if self.output_extension == '.shp': self.data_store.default_vector_format = 'shp' elif self.output_extension == '.geojson': self.data_store.default_vector_format = 'geojson' self.data_store.add_layer(self.output_layer, self.output_filename) # add output layer to map canvas self.output_layer = self.data_store.layer(self.output_filename) QgsMapLayerRegistry.instance().addMapLayers( [self.output_layer]) self.iface.setActiveLayer(self.output_layer) self.iface.zoomToActiveLayer() self.done(QtGui.QDialog.Accepted) if self.keyword_wizard_checkbox.isChecked(): self.launch_keyword_wizard() @pyqtSignature('') # prevents actions being handled twice def on_directory_button_tool_clicked(self): """Autoconnect slot activated when directory button is clicked.""" # noinspection PyCallByClass,PyTypeChecker # set up parameter from dialog input_path = self.layer.currentLayer().source() input_directory, self.output_filename = os.path.split(input_path) file_extension = os.path.splitext(self.output_filename)[1] self.output_filename = os.path.splitext(self.output_filename)[0] # show Qt file directory dialog output_path = QFileDialog.getSaveFileName( self, self.tr('Output file'), '%s_multi_buffer%s' % ( os.path.join(input_directory, self.output_filename), file_extension), 'GeoJSON (*.geojson);;Shapefile (*.shp)') # set selected path to the dialog self.output_form.setText(output_path) def get_output_from_input(self): """Populate output form with default output path based on input layer. """ input_path = self.layer.currentLayer().source() output_path = ( os.path.splitext(input_path)[0] + '_multi_buffer' + os.path.splitext(input_path)[1]) self.output_form.setText(output_path) def populate_hazard_classification(self): """Populate hazard classification on hazard class form.""" new_class = { 'value': self.radius_form.value(), 'name': self.class_form.text()} self.classification.append(new_class) self.classification = sorted( self.classification, key=itemgetter('value')) self.hazard_class_form.clear() for item in self.classification: new_item = '{value} - {name}'.format( value=item['value'], name=item['name']) self.hazard_class_form.addItem(new_item) self.radius_form.setValue(0) self.class_form.clear() self.ok_button_status() def remove_selected_classification(self): """Remove selected item on hazard class form.""" removed_classes = self.hazard_class_form.selectedItems() current_item = self.hazard_class_form.currentItem() removed_index = self.hazard_class_form.indexFromItem(current_item) del self.classification[removed_index.row()] for item in removed_classes: self.hazard_class_form.takeItem( self.hazard_class_form.row(item)) def get_classification(self): """Get all hazard class created by user. :return: Hazard class definition created by user. :rtype: OrderedDict """ classification_dictionary = {} for item in self.classification: classification_dictionary[item['value']] = item['name'] classification_dictionary = OrderedDict( sorted(classification_dictionary.items())) return classification_dictionary def directory_button_status(self): """Function to enable or disable directory button.""" if self.layer.currentLayer(): self.directory_button.setEnabled(True) else: self.directory_button.setEnabled(False) def add_class_button_status(self): """Function to enable or disable add class button.""" if self.class_form.text() and self.radius_form >= 0: self.add_class_button.setEnabled(True) else: self.add_class_button.setEnabled(False) def ok_button_status(self): """Function to enable or disable OK button.""" if not self.layer.currentLayer(): self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(False) elif (self.hazard_class_form.count() > 0 and self.layer.currentLayer().name() and len(self.output_form.text()) >= 0): self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(True) else: self.button_box.button(QtGui.QDialogButtonBox.Ok).setEnabled(False) @pyqtSlot() @pyqtSignature('bool') # prevents actions being handled twice def help_toggled(self, flag): """Show or hide the help tab in the stacked widget. :param flag: Flag indicating whether help should be shown or hidden. :type flag: bool """ if flag: self.help_button.setText(self.tr('Hide Help')) self.show_help() else: self.help_button.setText(self.tr('Show Help')) self.hide_help() def hide_help(self): """Hide the usage info from the user.""" self.main_stacked_widget.setCurrentIndex(1) def show_help(self): """Show usage info to the user.""" # Read the header and footer html snippets self.main_stacked_widget.setCurrentIndex(0) header = html_header() footer = html_footer() string = header message = multi_buffer_help() string += message.to_html() string += footer self.help_web_view.setHtml(string) def launch_keyword_wizard(self): """Launch keyword creation wizard.""" # make sure selected layer is the output layer if self.iface.activeLayer() != self.output_layer: return # launch wizard dialog self.keyword_wizard = WizardDialog( self.iface.mainWindow(), self.iface, self.dock_widget) self.keyword_wizard.set_keywords_creation_mode(self.output_layer) self.keyword_wizard.exec_() # modal
def test_folder_datastore(self): """Test if we can store shapefiles.""" path = QDir(mkdtemp()) data_store = Folder(path) self.assertTrue(data_store.is_writable()) path = mkdtemp() data_store = Folder(path) # We do not have any layer yet. self.assertEqual(len(data_store.layers()), 0) # Let's add a vector layer. layer = load_test_vector_layer('hazard', 'flood_multipart_polygons.shp') vector_layer_name = 'flood_test' result = data_store.add_layer(layer, vector_layer_name, True) self.assertTrue(result[0]) self.assertEqual(result[1], vector_layer_name) # We try to add the layer twice with the same name. result = data_store.add_layer(layer, vector_layer_name) self.assertFalse(result[0]) # We have imported one layer. self.assertEqual(len(data_store.layers()), 1) # Check if we have the correct URI. # self.assertIsNone(data_store.layer_uri(layer_name)) expected = str( normcase(normpath(join(path, vector_layer_name + '.shp')))) self.assertEqual( normcase(normpath(data_store.layer_uri(vector_layer_name))), expected) # The style must be there expected = unicode( normcase(normpath(join(path, vector_layer_name + '.qml')))) self.assertTrue(exists(expected)) self.assertTrue(isfile(expected)) # This layer do not exist self.assertIsNone(data_store.layer_uri('fake_layer')) # Let's add a raster layer. layer = load_test_raster_layer('hazard', 'classified_hazard.tif') result = data_store.add_layer(layer, vector_layer_name) self.assertFalse(result[0]) raster_layer_name = 'flood_raster' result = data_store.add_layer(layer, raster_layer_name, False) self.assertTrue(result[0]) # The style must not be there expected = unicode( normcase(normpath(join(path, raster_layer_name + '.qml')))) self.assertFalse(exists(expected)) self.assertFalse(isfile(expected)) # The datastore should have two layers. self.assertEqual(len(data_store.layers()), 2) # Check the URI for the raster layer. expected = normpath(normpath(join(path, raster_layer_name))) self.assertEqual( normcase(normpath(data_store.layer_uri(raster_layer_name))), expected + '.tif') # Check keywords files data_store.uri.setNameFilters(['*.xml']) files = data_store.uri.entryList() data_store.uri.setNameFilters([]) self.assertIn(raster_layer_name + '.xml', files) self.assertIn(vector_layer_name + '.xml', files) # Test layer without geometry layer = load_test_vector_layer('gisv4', 'impacts', 'exposure_summary_table.csv') tabular_layer_name = 'breakdown' result = data_store.add_layer(layer, tabular_layer_name) self.assertTrue(result[0]) self.assertIsNotNone( data_store.layer_keyword('layer_purpose', 'hazard'))
def test_folder_datastore(self): """Test if we can store shapefiles.""" path = QDir(mkdtemp()) data_store = Folder(path) self.assertTrue(data_store.is_writable()) path = mkdtemp() data_store = Folder(path) # We do not have any layer yet. self.assertEqual(len(data_store.layers()), 0) # Let's add a vector layer. layer = load_test_vector_layer( 'hazard', 'flood_multipart_polygons.shp') vector_layer_name = 'flood_test' result = data_store.add_layer(layer, vector_layer_name) self.assertTrue(result[0]) self.assertEqual(result[1], vector_layer_name) # We try to add the layer twice with the same name. result = data_store.add_layer(layer, vector_layer_name) self.assertFalse(result[0]) # We have imported one layer. self.assertEqual(len(data_store.layers()), 1) # Check if we have the correct URI. # self.assertIsNone(data_store.layer_uri(layer_name)) expected = unicode( normcase(normpath(join(path, vector_layer_name + '.shp')))) self.assertEquals( normcase(normpath( data_store.layer_uri(vector_layer_name))), expected) # This layer do not exist self.assertIsNone(data_store.layer_uri('fake_layer')) # Let's add a raster layer. layer = load_test_raster_layer('hazard', 'classified_hazard.tif') result = data_store.add_layer(layer, vector_layer_name) self.assertFalse(result[0]) raster_layer_name = 'flood_raster' result = data_store.add_layer(layer, raster_layer_name) self.assertTrue(result[0]) # The datastore should have two layers. self.assertEqual(len(data_store.layers()), 2) # Check the URI for the raster layer. expected = normpath(normpath(join(path, raster_layer_name))) self.assertEqual( normcase(normpath(data_store.layer_uri(raster_layer_name))), expected + '.tif') # Check keywords files data_store.uri.setNameFilters('*.xml') files = data_store.uri.entryList() data_store.uri.setNameFilters('') self.assertIn(raster_layer_name + '.xml', files) self.assertIn(vector_layer_name + '.xml', files) # Test layer without geometry layer = load_test_vector_layer( 'gisv4', 'impacts', 'exposure_summary_table.csv') tabular_layer_name = 'breakdown' result = data_store.add_layer(layer, tabular_layer_name) self.assertTrue(result[0]) self.assertIsNotNone( data_store.layer_keyword('layer_purpose', 'hazard') )
def rasterize_vector_layer(layer, width, height, extent): """Rasterize a vector layer to the grid given by extent and width/height. :param layer: The vector layer. :type layer: QgsVectorLayer :param width: The width of the output. :type width: int :param height: The height of the output. :type height: int :param extent: The extent to use. :type extent: QgsRectangle :return: The new raster layer. :rtype: QgsRasterLayer """ name = rasterize_steps['gdal_layer_name'] output_filename = unique_filename(prefix=name, suffix='.tif') extent_str = '%f,%f,%f,%f' % ( extent.xMinimum(), extent.xMaximum(), extent.yMinimum(), extent.yMaximum()) keywords = dict(layer.keywords) # The layer is in memory, we need to save it to a file for Processing. data_store = Folder(mkdtemp()) data_store.default_vector_format = 'geojson' result = data_store.add_layer(layer, 'vector_layer') layer = data_store.layer(result[1]) assert layer.isValid() field = layer.keywords['inasafe_fields'][aggregation_id_field['key']] # ET 21/02/17. I got some issues using rasterize algorithm from Processing. # I keep it in case of we need it later. Let's use gdal command line. use_gdal_command_line = True if use_gdal_command_line: startupinfo = None if sys.platform == 'win32': # On windows, we don't want to display the bash shell. # https://github.com/inasafe/inasafe/issues/3980 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW commands = [which('gdal_rasterize')[0]] commands += ['-a', field] commands += ['-ts', str(width), str(height)] commands += ['-ot', 'Int16'] commands += ['-a_nodata', "'-1'"] commands += [layer.source(), output_filename] LOGGER.info(' '.join(commands)) result = subprocess.check_call(commands, startupinfo=startupinfo) LOGGER.info('Result : %s' % result) else: parameters = dict() parameters['INPUT'] = layer parameters['FIELD'] = field parameters['DIMENSIONS'] = 0 # output size is given in pixels parameters['WIDTH'] = width parameters['HEIGHT'] = height parameters['RASTER_EXT'] = extent_str parameters['TFW'] = False # force generation of ESRI TFW parameters['RTYPE'] = 1 # raster type: Int16 parameters['NO_DATA'] = '-1' # nodata value parameters['COMPRESS'] = 4 # GeoTIFF compression: DEFLATE parameters['JPEGCOMPRESSION'] = 75 # JPEG compression level: 75 parameters['ZLEVEL'] = 6 # DEFLATE compression level parameters['PREDICTOR'] = 1 # predictor for JPEG/DEFLATE parameters['TILED'] = False # Tiled GeoTIFF? parameters['BIGTIFF'] = 0 # whether to make big TIFF parameters['EXTRA'] = '' # additional creation parameters parameters['OUTPUT'] = output_filename result = runalg('gdalogr:rasterize', parameters) if result is None: # Let's try be removing a new parameter added between 2.14 and 2.16 del parameters['RASTER_EXT'] result = runalg('gdalogr:rasterize', parameters) assert result is not None layer_aligned = QgsRasterLayer(output_filename, name, 'gdal') assert layer_aligned.isValid() layer_aligned.keywords = keywords layer_aligned.keywords['title'] = ( rasterize_steps['output_layer_name'] % 'aggregation') layer_aligned.keywords['layer_purpose'] = ( layer_purpose_aggregation_summary['key']) del layer_aligned.keywords['inasafe_fields'] check_layer(layer_aligned) return layer_aligned