def has_same_projection_as(self, other_layer): """ Check if the layer uses the same projection as another layer :param other_layer: layer to compare with :type other_layer: QgsVectorLayer :returns: (bool, str): (True, msg) if the layers use the same coordinates, specifying the projection in the message (False, msg) if they use different projections, specifying the projections in the message """ this_layer_projection = self.layer.crs().geographicCrsAuthId() other_layer_projection = other_layer.crs().geographicCrsAuthId() if (this_layer_projection != other_layer_projection): msg = tr("The two layers use different coordinate" " reference systems (%s vs %s)" % (this_layer_projection, other_layer_projection)) return False, msg else: msg = tr("Both layers use the following coordinate" " reference system: %s" % this_layer_projection) return True, msg
def on_clone_btn_clicked(self): title = (self.selected_proj_def['title'] + ' (copy)' if 'title' in self.selected_proj_def else '(copy)') title, ok = QInputDialog().getText(self, tr('Assign a title'), tr('Project definition title'), text=title) if ok: self.add_proj_def(title, self.selected_proj_def)
def work(self): """ :returns: (fname, msg), where fname is the name of the target csv file that will store the downloaded data, and msg is a message describing if the download is performed successfully :raises: SvNetworkError """ self.set_message.emit( tr('Waiting for the OpenQuake Platform to export the data...')) self.toggle_show_progress.emit(False) page = self.downloader.host + PLATFORM_EXPORT_VARIABLES_DATA data = dict(sv_variables_ids=self.sv_variables_ids, export_geometries=self.load_geometries, country_iso_codes=self.country_iso_codes) result = self.downloader.sess.post(page, data=data, stream=True) self.set_message.emit(tr('Downloading data from platform')) self.toggle_show_progress.emit(True) if result.status_code == 200: # save csv on a temporary file fd, fname = tempfile.mkstemp(suffix='.csv') os.close(fd) # All the fields of the csv file will be considered as text fields # unless a .csvt file with the same name as the .csv file is used # to specify the field types. # For the type descriptor, use the same name as the csv file fname_types = fname.split('.')[0] + '.csvt' # We expect iso, country_name, v1, v2, ... vn # Count variables ids sv_variables_count = len(self.sv_variables_ids.split(',')) # build the string that describes data types for the csv types_string = '"String","String"' + ',"Real"' * sv_variables_count if self.load_geometries: types_string += ',"String"' with open(fname_types, 'w', newline='') as csvt: csvt.write(types_string) with open(fname, 'w', newline='') as csv_file: n_countries_to_download = len(self.country_iso_codes) n_downloaded_countries = 0 for line in result.iter_lines(): if self.is_killed: raise UserAbortedNotification( 'Download aborted by user') csv_file.write(line.decode('utf8') + os.linesep) n_downloaded_countries += 1 progress = (1.0 * n_downloaded_countries / n_countries_to_download * 100) self.progress.emit(progress) msg = 'The socioeconomic data has been saved into %s' % fname return fname, msg else: raise SvNetworkError(result.content)
def load_loss_layer(self, loss_layer_path): # Load loss layer if self.loss_layer_is_vector: loss_layer = QgsVectorLayer(loss_layer_path, tr('Loss map'), 'ogr') if not loss_layer.geometryType() == QGis.Point: msg = 'Loss map must contain points' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return False else: loss_layer = QgsRasterLayer(loss_layer_path, tr('Loss map')) # Add loss layer to registry if loss_layer.isValid(): QgsMapLayerRegistry.instance().addMapLayer(loss_layer) else: msg = 'Invalid loss map' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return None # Zoom depending on the zonal layer's extent return loss_layer
def calculate_raster_stats(loss_layer, zonal_layer, iface): """ In case the layer containing loss data is raster, use QgsZonalStatistics to calculate NUM_POINTS, SUM and AVG values for each zone """ zonal_statistics = QgsZonalStatistics( zonal_layer, loss_layer.dataProvider().dataSourceUri()) progress_dialog = QProgressDialog(tr('Calculating zonal statistics'), tr('Abort...'), 0, 0) zonal_statistics.calculateStatistics(progress_dialog) # TODO: This is not giving any warning in case no loss points are # contained by any of the zones if progress_dialog.wasCanceled(): msg = ("You aborted aggregation, so there are" " no data for analysis. Exiting...") log_msg(msg, level='C', message_bar=iface.messageBar()) # FIXME: We probably need to return something more return (loss_layer, zonal_layer)
def set_labels(self): self.situation_lbl.setVisible(self.exists_on_platform) self.question_lbl.setVisible(self.exists_on_platform) for button in self.upload_action.buttons(): button.setVisible(self.exists_on_platform) if self.update_radio.isChecked(): explaination_lbl = tr( 'The current project definition will be added to the ' 'OpenQuake Platform project\nidentified as "%s"' % self.suppl_info['platform_layer_id']) title_lbl = tr('Project title') description_lbl = tr('Project description') self.title_le.setEnabled(False) self.description_te.setEnabled(False) self.zone_label_field_cbx.setEnabled(False) self.license_cbx.setEnabled(False) self.set_zone_label_field() self.set_license() else: explaination_lbl = tr( 'A new layer will be created on the OpenQuake Platform.') title_lbl = tr('New layer title') description_lbl = tr('New layer abstract') self.title_le.setEnabled(True) self.description_te.setEnabled(True) self.zone_label_field_cbx.setEnabled(True) self.license_cbx.setEnabled(True) self.title_lbl.setText(title_lbl) self.description_lbl.setText(description_lbl) self.explaination_lbl.setText(explaination_lbl)
def load_zonal_layer(self, zonal_layer_path): # Load zonal layer zonal_layer = QgsVectorLayer(zonal_layer_path, tr('Zonal data'), 'ogr') if not zonal_layer.geometryType() == QGis.Polygon: msg = 'Zonal layer must contain zone polygons' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return False # Add zonal layer to registry if zonal_layer.isValid(): QgsMapLayerRegistry.instance().addMapLayer(zonal_layer) else: msg = 'Invalid zonal layer' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return None return zonal_layer
def __init__(self, loss_layer, zonal_layer): QDialog.__init__(self) # Set up the user interface from Designer. self.setupUi(self) self.ok_button = self.buttonBox.button(QDialogButtonBox.Ok) self.set_ok_button() # if the loss layer does not contain an attribute specifying the ids of # zones, the user must not be forced to select such attribute, so we # add an "empty" option to the combobox self.zone_id_attr_name_loss_cbox.addItem(tr("Use zonal geometries")) # if the zonal_layer doesn't have a field containing a unique zone id, # the user can choose to add such unique id self.zone_id_attr_name_zone_cbox.addItem( tr("Add field with unique zone id")) # Load in the comboboxes only the names of the attributes compatible # with the following analyses for field in loss_layer.fields(): # for the zone id accept both numeric or textual fields self.zone_id_attr_name_loss_cbox.addItem(field.name()) # Accept only numeric fields to contain loss data if field.typeName() in NUMERIC_FIELD_TYPES: self.loss_attrs_multisel.add_unselected_items([field.name()]) self.zone_id_attr_name_loss_cbox.setCurrentIndex(0) for field in zonal_layer.fields(): # for the zone id accept both numeric or textual fields self.zone_id_attr_name_zone_cbox.addItem(field.name()) # by default, set the selection to the first textual field self.zone_id_attr_name_zone_cbox.setCurrentIndex(0) self.loss_attrs_multisel.selection_changed.connect(self.set_ok_button)
def downloadContent(self, reply): self.stop() home = os.path.expanduser('~') downloads = os.path.join(home, 'Downloads') download = os.path.join(home, 'Download') if os.path.isdir(downloads): dest_dir = downloads if os.path.isdir(download): dest_dir = download if not dest_dir: dest_dir = QFileDialog.getExistingDirectory( self, tr("Select the destination folder"), home, QFileDialog.ShowDirsOnly) if not dest_dir: return try: file_name = reply.rawHeader('Content-Disposition').split('=')[1] file_name = str(file_name).strip('"') except Exception as exc: header_pairs = reply.rawHeaderPairs() self.gem_api.common.error( 'Unable to get the file name from headers: %s\n' 'Exception: %s' % (header_pairs, str(exc))) return file_content = str(reply.readAll()) # From # http://doc.qt.io/archives/qt-4.8/qwebpage.html#unsupportedContent # "The receiving slot is responsible for deleting the reply" reply.close() reply.deleteLater() file_fullpath = os.path.join(dest_dir, file_name) if os.path.exists(file_fullpath): name, ext = os.path.splitext(file_name) i = 1 while True: file_fullpath = os.path.join(dest_dir, '%s_%s%s' % (name, i, ext)) if not os.path.exists(file_fullpath): break i += 1 with open(file_fullpath, "w", newline='') as f: f.write(file_content) self.gem_api.common.info('File downloaded as: %s' % file_fullpath)
def load_zonal_layer(self, zonal_layer_path, make_a_copy=False): # Load zonal layer zonal_layer = QgsVectorLayer(zonal_layer_path, tr('Zonal data'), 'ogr') if not zonal_layer.geometryType() == QGis.Polygon: msg = 'Zonal layer must contain zone polygons' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return False if make_a_copy: # Make a copy, where stats will be added zonal_layer_plus_stats = ProcessLayer( zonal_layer).duplicate_in_memory() else: zonal_layer_plus_stats = zonal_layer # Add zonal layer to registry if zonal_layer_plus_stats.isValid(): QgsMapLayerRegistry.instance().addMapLayer(zonal_layer_plus_stats) else: msg = 'Invalid zonal layer' log_msg(msg, level='C', message_bar=self.iface.messageBar()) return None return zonal_layer_plus_stats
def progress_cb(self, param, current, total): if self.is_killed: raise UserAbortedNotification('Upload aborted by user') if not param: return # check out # http://tcd.netinf.eu/doc/classnilib_1_1encode_1_1MultipartParam.html # for a complete list of the properties param provides to you progress = float(current) / float(total) * 100 self.progress.emit(progress) # here we remove the progress bar and the cancel button since the # server side processing kicks in at 100% but we allow for some # rounding. if progress > 99: self.toggle_show_progress.emit(False) self.toggle_show_cancel.emit(False) self.set_message.emit(self.upload_size_msg + ' ' + tr('(processing on Platform)')) if DEBUG: print("PROGRESS: {0} ({1}) - {2:d}/{3:d} - {4:.2f}%").format( param.name, param.filename, current, total, progress)
def purge_zones_without_loss_points(zonal_layer, loss_attrs_dict, iface): """ Delete from the zonal layer the zones that contain no loss points """ tot_zones = zonal_layer.featureCount() msg = tr("Purging zones containing no loss points...") msg_bar_item, progress = create_progress_message_bar( iface.messageBar(), msg) with edit(zonal_layer): for current_zone, zone_feature in enumerate(zonal_layer.getFeatures()): progress_percent = current_zone / float(tot_zones) * 100 progress.setValue(progress_percent) # save the ids of the zones to purge (which contain no loss # points) if zone_feature[loss_attrs_dict['count']] == 0: zonal_layer.deleteFeature(zone_feature.id()) clear_progress_message_bar(iface.messageBar(), msg_bar_item) msg = "Zones containing no loss points were deleted" log_msg(msg, level='W', message_bar=iface.messageBar()) return zonal_layer
def work(self): # To upload the layer to the platform, we need it to be a shapefile. # So we need to check if the active layer is stored as a shapefile and, # if it isn't, save it as a shapefile data_file = '%s%s' % (self.file_stem, '.shp') projection = self.current_layer.crs().geographicCRSAuthId() if (self.current_layer.storageType() == 'ESRI Shapefile' and projection == 'EPSG:4326'): # copy the shapefile (with all its files) into the temporary # directory, using self.file_stem as name self.set_message.emit(tr('Preparing the files to be uploaded...')) layer_source = self.current_layer.publicSource() layer_source_stem = layer_source[:-4] # remove '.shp' for ext in ['shp', 'dbf', 'shx', 'prj']: src = "%s.%s" % (layer_source_stem, ext) dst = "%s.%s" % (self.file_stem, ext) shutil.copyfile(src, dst) else: # if it's not a shapefile or it is in a bad projection, # we need to build a shapefile from it self.set_message.emit( tr('Writing the shapefile to be uploaded...')) result = save_layer_as_shapefile( self.current_layer, data_file, crs=QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId)) if result != QgsVectorFileWriter.NoError: raise RuntimeError('Could not save shapefile') file_size_mb = os.path.getsize(data_file) file_size_mb += os.path.getsize(self.file_stem + '.shx') file_size_mb += os.path.getsize(self.file_stem + '.dbf') # convert bytes to MB file_size_mb = file_size_mb / 1024 / 1024 self.upload_size_msg = tr('Uploading ~%s MB...' % file_size_mb) self.set_message.emit(self.upload_size_msg) permissions = { "authenticated": "_none", "anonymous": "_none", "users": [[self.username, "layer_readwrite"], [self.username, "layer_admin"]] } data = { 'layer_title': os.path.basename(self.file_stem), 'base_file': open('%s.shp' % self.file_stem, 'rb'), 'dbf_file': open('%s.dbf' % self.file_stem, 'rb'), 'shx_file': open('%s.shx' % self.file_stem, 'rb'), 'prj_file': open('%s.prj' % self.file_stem, 'rb'), 'xml_file': open('%s.xml' % self.file_stem, 'rb'), 'charset': 'UTF-8', 'permissions': json.dumps(permissions) } # generate headers and data-generator an a requests-compatible format # and provide our progress-callback data_generator, headers = multipart_encode_for_requests( data, cb=self.progress_cb) # use the requests-lib to issue a post-request with out data attached r = self.session.post(self.hostname + '/layers/upload', data=data_generator, headers=headers) try: response = json.loads(r.text) return self.hostname + response['url'], True except KeyError: if 'errors' in response: raise KeyError(response['errors']) else: raise KeyError("The server did not provide error messages") except ValueError: raise RuntimeError(r.text)
def accept(self): self.suppl_info['title'] = self.title_le.text() if 'title' not in self.project_definition: self.project_definition['title'] = self.suppl_info['title'] self.suppl_info['abstract'] = self.description_te.toPlainText() if 'description' not in self.project_definition: self.project_definition['description'] = self.suppl_info[ 'abstract'] zone_label_field = self.zone_label_field_cbx.currentText() self.suppl_info['zone_label_field'] = zone_label_field license_name = self.license_cbx.currentText() license_idx = self.license_cbx.currentIndex() license_url = self.license_cbx.itemData(license_idx) license_txt = '%s (%s)' % (license_name, license_url) self.suppl_info['license'] = license_txt self.suppl_info['irmt_plugin_version'] = IRMT_PLUGIN_VERSION self.suppl_info['supplemental_information_version'] = \ SUPPLEMENTAL_INFORMATION_VERSION self.suppl_info['vertices_count'] = self.vertices_count self.suppl_info['project_definitions'][self.selected_idx] = \ self.project_definition active_layer_id = self.iface.activeLayer().id() write_layer_suppl_info_to_qgs(active_layer_id, self.suppl_info) if self.do_update: with WaitCursorManager( 'Updating project on the OpenQuake Platform', self.iface.messageBar()): hostname, username, password = get_credentials('platform') session = Session() try: platform_login(hostname, username, password, session) except SvNetworkError as e: error_msg = ( 'Unable to login to the platform: ' + e.message) log_msg(error_msg, level='C', message_bar=self.iface.messageBar(), exception=e) return if 'platform_layer_id' not in self.suppl_info: error_msg = ('Unable to retrieve the id of' 'the layer on the Platform') log_msg(error_msg, level='C', message_bar=self.iface.messageBar()) return response = update_platform_project( hostname, session, self.project_definition, self.suppl_info['platform_layer_id']) if response.ok: log_msg(tr(response.text), level='S', message_bar=self.iface.messageBar()) else: error_msg = response.text # example of response text: # "The title 'No incomplete data' was already assigned to # another project definition. Please provide a new unique # one." # NOTE: if the api response message changes, this might # not work properly if 'Please provide a new unique' in response.text: error_msg += (' Please consider using the "Manage' ' project definitions" functionality' ' to save the current project definition' ' as a new one having a unique title.') log_msg(error_msg, level='C', message_bar=self.iface.messageBar()) else: if DEBUG: log_msg('xml_file: %s' % self.xml_file) # do not upload the selected_project_definition_idx self.suppl_info.pop('selected_project_definition_idx', None) write_iso_metadata_file(self.xml_file, self.suppl_info) metadata_dialog = UploadDialog( self.iface, self.file_stem) metadata_dialog.upload_successful.connect( lambda layer_url: insert_platform_layer_id( layer_url, active_layer_id, self.suppl_info)) if metadata_dialog.exec_(): QDesktopServices.openUrl(QUrl(metadata_dialog.layer_url)) elif DEBUG: log_msg("metadata_dialog cancelled") super(UploadSettingsDialog, self).accept()
def on_add_proj_def_btn_clicked(self): title, ok = QInputDialog().getText(self, tr('Assign a title'), tr('Project definition title')) if ok: self.add_proj_def(title)
def style_maps(layer, style_by, iface, output_type='damages-rlzs', perils=None, add_null_class=False, render_higher_on_top=False, repaint=True, use_sgc_style=False): symbol = QgsSymbol.defaultSymbol(layer.geometryType()) # see properties at: # https://qgis.org/api/qgsmarkersymbollayerv2_8cpp_source.html#l01073 symbol.setOpacity(1) if isinstance(symbol, QgsMarkerSymbol): # do it only for the layer with points symbol.symbolLayer(0).setStrokeStyle(Qt.PenStyle(Qt.NoPen)) style = get_style(layer, iface.messageBar()) # this is the default, as specified in the user settings ramp = QgsGradientColorRamp(style['color_from'], style['color_to']) style_mode = style['style_mode'] # in most cases, we override the user-specified setting, and use # instead a setting that was required by scientists if output_type in OQ_TO_LAYER_TYPES: default_qgs_style = QgsStyle().defaultStyle() default_color_ramp_names = default_qgs_style.colorRampNames() if output_type in ( 'damages-rlzs', 'avg_losses-rlzs', 'avg_losses-stats', ): # options are EqualInterval, Quantile, Jenks, StdDev, Pretty # jenks = natural breaks if Qgis.QGIS_VERSION_INT < 31000: style_mode = QgsGraduatedSymbolRenderer.Jenks else: style_mode = 'Jenks' ramp_type_idx = default_color_ramp_names.index('Reds') symbol.setColor(QColor(RAMP_EXTREME_COLORS['Reds']['top'])) inverted = False elif (output_type in ('gmf_data', 'ruptures') or (output_type == 'hmaps' and not use_sgc_style)): # options are EqualInterval, Quantile, Jenks, StdDev, Pretty # jenks = natural breaks if output_type == 'ruptures': if Qgis.QGIS_VERSION_INT < 31000: style_mode = QgsGraduatedSymbolRenderer.Pretty else: style_mode = 'PrettyBreaks' else: if Qgis.QGIS_VERSION_INT < 31000: style_mode = QgsGraduatedSymbolRenderer.EqualInterval else: style_mode = 'EqualInterval' ramp_type_idx = default_color_ramp_names.index('Spectral') inverted = True symbol.setColor(QColor(RAMP_EXTREME_COLORS['Reds']['top'])) elif output_type == 'hmaps' and use_sgc_style: # FIXME: for SGC they were using size 10000 map units # options are EqualInterval, Quantile, Jenks, StdDev, Pretty # jenks = natural breaks if Qgis.QGIS_VERSION_INT < 31000: style_mode = QgsGraduatedSymbolRenderer.Pretty else: style_mode = 'PrettyBreaks' try: ramp_type_idx = default_color_ramp_names.index( 'SGC_Green2Red_Hmap_Color_Ramp') except ValueError: raise ValueError( 'Color ramp SGC_Green2Red_Hmap_Color_Ramp was ' 'not found. Please import it from ' 'Settings -> Style Manager, loading ' 'svir/resources/sgc_green2red_hmap_color_ramp.xml') inverted = False registry = QgsApplication.symbolLayerRegistry() symbol_props = { 'name': 'square', 'color': '0,0,0', 'color_border': '0,0,0', 'offset': '0,0', 'size': '1.5', # FIXME 'angle': '0', } square = registry.symbolLayerMetadata( "SimpleMarker").createSymbolLayer(symbol_props) symbol = QgsSymbol.defaultSymbol(layer.geometryType()).clone() symbol.deleteSymbolLayer(0) symbol.appendSymbolLayer(square) symbol.symbolLayer(0).setStrokeStyle(Qt.PenStyle(Qt.NoPen)) elif output_type in ['asset_risk', 'input']: # options are EqualInterval, Quantile, Jenks, StdDev, Pretty # jenks = natural breaks if Qgis.QGIS_VERSION_INT < 31000: style_mode = QgsGraduatedSymbolRenderer.EqualInterval else: style_mode = 'EqualInterval' # exposure_strings = ['number', 'occupants', 'value'] # setting exposure colors by default colors = { 'single': RAMP_EXTREME_COLORS['Blues']['top'], 'ramp_name': 'Blues' } inverted = False if output_type == 'asset_risk': damage_strings = perils for damage_string in damage_strings: if damage_string in style_by: colors = { 'single': RAMP_EXTREME_COLORS['Spectral']['top'], 'ramp_name': 'Spectral' } inverted = True break else: # 'input' colors = { 'single': RAMP_EXTREME_COLORS['Greens']['top'], 'ramp_name': 'Greens' } symbol.symbolLayer(0).setShape( QgsSimpleMarkerSymbolLayerBase.Square) single_color = colors['single'] ramp_name = colors['ramp_name'] ramp_type_idx = default_color_ramp_names.index(ramp_name) symbol.setColor(QColor(single_color)) else: raise NotImplementedError( 'Undefined color ramp for output type %s' % output_type) ramp = default_qgs_style.colorRamp( default_color_ramp_names[ramp_type_idx]) if inverted: ramp.invert() # get unique values fni = layer.fields().indexOf(style_by) unique_values = layer.dataProvider().uniqueValues(fni) num_unique_values = len(unique_values - {NULL}) if num_unique_values > 2: if Qgis.QGIS_VERSION_INT < 31000: renderer = QgsGraduatedSymbolRenderer.createRenderer( layer, style_by, min(num_unique_values, style['classes']), style_mode, symbol.clone(), ramp) else: renderer = QgsGraduatedSymbolRenderer(style_by, []) # NOTE: the following returns an instance of one of the # subclasses of QgsClassificationMethod classification_method = \ QgsApplication.classificationMethodRegistry().method( style_mode) renderer.setClassificationMethod(classification_method) renderer.updateColorRamp(ramp) renderer.updateSymbols(symbol.clone()) renderer.updateClasses( layer, min(num_unique_values, style['classes'])) if not use_sgc_style: if Qgis.QGIS_VERSION_INT < 31000: label_format = renderer.labelFormat() # NOTE: the following line might be useful # label_format.setTrimTrailingZeroes(True) label_format.setPrecision(2) renderer.setLabelFormat(label_format, updateRanges=True) else: renderer.classificationMethod().setLabelPrecision(2) renderer.calculateLabelPrecision() elif num_unique_values == 2: categories = [] for unique_value in unique_values: symbol = symbol.clone() try: symbol.setColor( QColor(RAMP_EXTREME_COLORS[ramp_name] ['bottom' if unique_value == min(unique_values) else 'top'])) except Exception: symbol.setColor( QColor(style['color_from'] if unique_value == min(unique_values) else style['color_to'])) category = QgsRendererCategory(unique_value, symbol, str(unique_value)) # entry for the list of category items categories.append(category) renderer = QgsCategorizedSymbolRenderer(style_by, categories) else: renderer = QgsSingleSymbolRenderer(symbol.clone()) if add_null_class and NULL in unique_values: # add a class for NULL values rule_renderer = QgsRuleBasedRenderer(symbol.clone()) root_rule = rule_renderer.rootRule() not_null_rule = root_rule.children()[0].clone() # strip parentheses from stringified color HSL not_null_rule.setFilterExpression( '%s IS NOT NULL' % QgsExpression.quotedColumnRef(style_by)) not_null_rule.setLabel('%s:' % style_by) root_rule.appendChild(not_null_rule) null_rule = root_rule.children()[0].clone() null_rule.setSymbol( QgsFillSymbol.createSimple({ 'color': '160,160,160', 'style': 'diagonal_x' })) null_rule.setFilterExpression( '%s IS NULL' % QgsExpression.quotedColumnRef(style_by)) null_rule.setLabel(tr('No points')) root_rule.appendChild(null_rule) if isinstance(renderer, QgsGraduatedSymbolRenderer): # create value ranges rule_renderer.refineRuleRanges(not_null_rule, renderer) # remove default rule elif isinstance(renderer, QgsCategorizedSymbolRenderer): rule_renderer.refineRuleCategoris(not_null_rule, renderer) for rule in rule_renderer.rootRule().children()[1].children(): label = rule.label() # by default, labels are like: # ('"collapse-structural-ASH_DRY_sum" >= 0.0000 AND # "collapse-structural-ASH_DRY_sum" <= 2.3949') first, second = label.split(" AND ") bottom = first.rsplit(" ", 1)[1] top = second.rsplit(" ", 1)[1] simplified = "%s - %s" % (bottom, top) rule.setLabel(simplified) root_rule.removeChildAt(0) renderer = rule_renderer if render_higher_on_top: renderer.setUsingSymbolLevels(True) symbol_items = [item for item in renderer.legendSymbolItems()] for i in range(len(symbol_items)): sym = symbol_items[i].symbol().clone() key = symbol_items[i].ruleKey() for lay in range(sym.symbolLayerCount()): sym.symbolLayer(lay).setRenderingPass(i) renderer.setLegendSymbolItem(key, sym) layer.setRenderer(renderer) if not use_sgc_style: layer.setOpacity(0.7) if repaint: layer.triggerRepaint() iface.setActiveLayer(layer) iface.zoomToActiveLayer() # NOTE QGIS3: probably not needed # iface.layerTreeView().refreshLayerSymbology(layer.id()) iface.mapCanvas().refresh()
def kill(self): self.is_killed = True self.set_message.emit(tr('Aborting...')) self.toggle_show_progress.emit(False)
def work(self): # To upload the layer to the platform, we need it to be a shapefile. # So we need to check if the active layer is stored as a shapefile and, # if it isn't, save it as a shapefile data_file = '%s%s' % (self.file_stem, '.shp') projection = self.current_layer.crs().geographicCrsAuthId() if (self.current_layer.storageType() == 'ESRI Shapefile' and projection == 'EPSG:4326'): # copy the shapefile (with all its files) into the temporary # directory, using self.file_stem as name self.set_message.emit(tr( 'Preparing the files to be uploaded...')) layer_source = self.current_layer.publicSource() layer_source_stem = layer_source[:-4] # remove '.shp' for ext in ['shp', 'dbf', 'shx', 'prj']: src = "%s.%s" % (layer_source_stem, ext) dst = "%s.%s" % (self.file_stem, ext) shutil.copyfile(src, dst) else: # if it's not a shapefile or it is in a bad projection, # we need to build a shapefile from it self.set_message.emit(tr( 'Writing the shapefile to be uploaded...')) writer_error, error_msg = save_layer_as( self.current_layer, data_file, 'ESRI Shapefile', crs=QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem.EpsgCrsId)) if writer_error: raise RuntimeError( 'Could not save shapefile. %s: %s' % (writer_error, error_msg)) file_size_mb = os.path.getsize(data_file) file_size_mb += os.path.getsize(self.file_stem + '.shx') file_size_mb += os.path.getsize(self.file_stem + '.dbf') # convert bytes to MB file_size_mb = file_size_mb / 1024 / 1024 self.upload_size_msg = tr('Uploading ~%s MB...' % file_size_mb) self.set_message.emit(self.upload_size_msg) user_permissions = [ 'change_layer_style', 'add_layer', 'change_layer', 'delete_layer', 'view_resourcebase', 'download_resourcebase', 'publish_resourcebase', ] admin_permissions = [ 'change_layer_data', 'change_resourcebase_metadata', 'change_resourcebase', 'delete_resourcebase', 'change_resourcebase_permissions', ] admin_permissions.extend(user_permissions) permissions = { "admin": { self.username: admin_permissions }, "users": { self.username: user_permissions } } files = {'base_file': open('%s.shp' % self.file_stem, 'rb'), 'dbf_file': open('%s.dbf' % self.file_stem, 'rb'), 'shx_file': open('%s.shx' % self.file_stem, 'rb'), 'prj_file': open('%s.prj' % self.file_stem, 'rb'), 'xml_file': open('%s.xml' % self.file_stem, 'rb')} data = {'layer_title': os.path.basename(self.file_stem), 'charset': 'UTF-8', 'permissions': json.dumps(permissions), 'metadata_uploaded_preserve': True} self.progress.emit(-1) r = self.session.post( self.hostname + '/layers/upload', data=data, files=files) self.toggle_show_progress.emit(False) self.toggle_show_cancel.emit(False) self.set_message.emit( self.upload_size_msg + ' ' + tr('(processing on Platform)')) try: response = json.loads(r.text) return self.hostname + response['url'], True except KeyError: if 'errors' in response: raise KeyError(response['errors']) else: raise KeyError("The server did not provide error messages") except ValueError: raise RuntimeError(r.text)
def _add_zone_id_to_points_internal(iface, loss_layer, zonal_layer, zone_id_in_zones_attr_name): """ On the hypothesis that we don't know what is the zone in which each point was collected we use an alternative implementation of what SAGA does, i.e., we add a field to the loss layer, containing the id of the zone to which it belongs. In order to achieve that: * we create a spatial index of the loss points * for each zone (in the layer containing zonally-aggregated SVI * we identify points that are inside the zone's bounding box * we check if each of these points is actually inside the zone's geometry; if it is: * copy the zone id into the new field of the loss point Notes: * loss_layer contains the not aggregated loss points * zonal_layer contains the zone geometries """ # make a copy of the loss layer and use that from now on add_to_registry = True if DEBUG else False loss_layer_plus_zones = \ ProcessLayer(loss_layer).duplicate_in_memory( new_name='Loss plus zone labels', add_to_registry=add_to_registry) # add to it the new attribute that will contain the zone id # and to do that we need to know the type of the zone id field zonal_layer_fields = zonal_layer.fields() zone_id_field_variant, zone_id_field_type_name = [ (field.type(), field.typeName()) for field in zonal_layer_fields if field.name() == zone_id_in_zones_attr_name ][0] zone_id_field = QgsField(zone_id_in_zones_attr_name, zone_id_field_variant) zone_id_field.setTypeName(zone_id_field_type_name) assigned_attr_names_dict = \ ProcessLayer(loss_layer_plus_zones).add_attributes( [zone_id_field]) zone_id_in_losses_attr_name = assigned_attr_names_dict.values()[0] # get the index of the new attribute, to be used to update its values zone_id_attr_idx = loss_layer_plus_zones.fieldNameIndex( zone_id_in_losses_attr_name) # to show the overall progress, cycling through points tot_points = loss_layer_plus_zones.featureCount() msg = tr("Step 2 of 3: creating spatial index for loss points...") msg_bar_item, progress = create_progress_message_bar( iface.messageBar(), msg) # create spatial index with TraceTimeManager(tr("Creating spatial index for loss points..."), DEBUG): spatial_index = QgsSpatialIndex() for current_point, loss_feature in enumerate( loss_layer_plus_zones.getFeatures()): progress_perc = current_point / float(tot_points) * 100 progress.setValue(progress_perc) spatial_index.insertFeature(loss_feature) clear_progress_message_bar(iface.messageBar(), msg_bar_item) with edit(loss_layer_plus_zones): # to show the overall progress, cycling through zones tot_zones = zonal_layer.featureCount() msg = tr("Step 3 of 3: labeling points by zone id...") msg_bar_item, progress = create_progress_message_bar( iface.messageBar(), msg) for current_zone, zone_feature in enumerate(zonal_layer.getFeatures()): progress_perc = current_zone / float(tot_zones) * 100 progress.setValue(progress_perc) msg = "{0}% - Zone: {1} on {2}".format(progress_perc, zone_feature.id(), tot_zones) with TraceTimeManager(msg, DEBUG): zone_geometry = zone_feature.geometry() # Find ids of points within the bounding box of the zone point_ids = spatial_index.intersects( zone_geometry.boundingBox()) # check if the points inside the bounding box of the zone # are actually inside the zone's geometry for point_id in point_ids: msg = "Checking if point {0} is actually inside " \ "the zone".format(point_id) with TraceTimeManager(msg, DEBUG): # Get the point feature by the point's id request = QgsFeatureRequest().setFilterFid(point_id) point_feature = loss_layer_plus_zones.getFeatures( request).next() point_geometry = QgsGeometry(point_feature.geometry()) # check if the point is actually inside the zone # and it is not only contained by its bounding box if zone_geometry.contains(point_geometry): zone_id = zone_feature[zone_id_in_zones_attr_name] loss_layer_plus_zones.changeAttributeValue( point_id, zone_id_attr_idx, zone_id) # for consistency with the SAGA algorithm, remove points that don't # belong to any zone for point_feature in loss_layer_plus_zones.getFeatures(): if not point_feature[zone_id_in_losses_attr_name]: loss_layer_plus_zones.deleteFeature(point_feature.id()) clear_progress_message_bar(iface.messageBar(), msg_bar_item) return loss_layer_plus_zones, zone_id_in_losses_attr_name
def calculate_vector_stats_aggregating_by_zone_id(loss_layer, zonal_layer, zone_id_in_losses_attr_name, zone_id_in_zones_attr_name, loss_attr_names, loss_attrs_dict, iface, old_field_to_new_field=None, extra=True): """ Once we know the zone id of each point in the loss map, we can count how many points are in each zone, sum and average their values """ tot_points = loss_layer.featureCount() msg = tr("Step 2 of 3: aggregating losses by zone id...") msg_bar_item, progress = create_progress_message_bar( iface.messageBar(), msg) # if the user picked an attribute from the loss layer, to be # used as zone id, use that; otherwise, use the attribute # copied from the zonal layer if not zone_id_in_losses_attr_name: zone_id_in_losses_attr_name = zone_id_in_zones_attr_name with TraceTimeManager(msg, DEBUG): zone_stats = {} for current_point, point_feat in enumerate(loss_layer.getFeatures()): progress_perc = current_point / float(tot_points) * 100 progress.setValue(progress_perc) zone_id = point_feat[zone_id_in_losses_attr_name] if zone_id not in zone_stats: zone_stats[zone_id] = {} for loss_attr_name in loss_attr_names: if loss_attr_name not in zone_stats[zone_id]: zone_stats[zone_id][loss_attr_name] = { 'count': 0, 'sum': 0.0 } if old_field_to_new_field: loss_value = point_feat[ old_field_to_new_field[loss_attr_name]] else: loss_value = point_feat[loss_attr_name] zone_stats[zone_id][loss_attr_name]['count'] += 1 zone_stats[zone_id][loss_attr_name]['sum'] += loss_value clear_progress_message_bar(iface.messageBar(), msg_bar_item) if extra: msg = tr("Step 3 of 3: writing point counts, loss sums and averages" " into the zonal layer...") else: msg = tr("Step 3 of 3: writing sums into the zonal layer...") with TraceTimeManager(msg, DEBUG): tot_zones = zonal_layer.featureCount() msg_bar_item, progress = create_progress_message_bar( iface.messageBar(), msg) with edit(zonal_layer): if extra: count_idx = zonal_layer.fieldNameIndex( loss_attrs_dict['count']) avg_idx = {} sum_idx = {} for loss_attr_name in loss_attr_names: sum_idx[loss_attr_name] = zonal_layer.fieldNameIndex( loss_attrs_dict[loss_attr_name]['sum']) if extra: avg_idx[loss_attr_name] = zonal_layer.fieldNameIndex( loss_attrs_dict[loss_attr_name]['avg']) for current_zone, zone_feat in enumerate( zonal_layer.getFeatures()): progress_perc = current_zone / float(tot_zones) * 100 progress.setValue(progress_perc) # get the id of the current zone zone_id = zone_feat[zone_id_in_zones_attr_name] # initialize points_count, loss_sum and loss_avg # to zero, and update them afterwards only if the zone # contains at least one loss point points_count = 0 if extra: loss_avg = {} loss_sum = {} for loss_attr_name in loss_attr_names: loss_sum[loss_attr_name] = 0.0 if extra: loss_avg[loss_attr_name] = 0.0 # retrieve count and sum from the dictionary, using # the zone id as key to get the values from the # corresponding dict (otherwise, keep zero values) if zone_id in zone_stats: for loss_attr_name in loss_attr_names: loss_sum[loss_attr_name] = \ zone_stats[zone_id][loss_attr_name]['sum'] points_count = \ zone_stats[zone_id][loss_attr_name]['count'] if extra: # division by zero should be impossible, because # we are computing this only for zones containing # at least one point (otherwise we keep all zeros) loss_avg[loss_attr_name] = ( loss_sum[loss_attr_name] / points_count) # NOTE: The following line looks redundant zone_stats[zone_id][loss_attr_name]['avg'] = ( loss_avg[loss_attr_name]) # without casting to int and to float, it wouldn't work fid = zone_feat.id() if extra: zonal_layer.changeAttributeValue(fid, count_idx, int(points_count)) for loss_attr_name in loss_attr_names: if points_count: zonal_layer.changeAttributeValue( fid, sum_idx[loss_attr_name], float(loss_sum[loss_attr_name])) if extra: zonal_layer.changeAttributeValue( fid, avg_idx[loss_attr_name], float(loss_avg[loss_attr_name])) else: # if no points were found in that region, let both # sum and average be NULL instead of 0 zonal_layer.changeAttributeValue( fid, sum_idx[loss_attr_name], QPyNullVariant(float)) if extra: zonal_layer.changeAttributeValue( fid, avg_idx[loss_attr_name], QPyNullVariant(float)) clear_progress_message_bar(iface.messageBar(), msg_bar_item) notify_loss_aggregation_by_zone_complete(loss_attrs_dict, loss_attr_names, iface, extra=extra) return (loss_layer, zonal_layer, loss_attrs_dict)