def layerFromPath(lineFilePath: str, rootGroup: QgsLayerTreeGroup, project: QgsLayerTreeGroup) -> None: lineFileBasename = os.path.splitext(os.path.basename(lineFilePath))[0] lineLayer = QgsVectorLayer(lineFilePath, lineFileBasename, 'ogr') # Get number of features (range of Sequence#, number of renderer color classes) driver = ogr.GetDriverByName('ESRI Shapefile') dataSource = driver.Open(lineFilePath, 0) # 0 means read-only. 1 means writeable. layer = dataSource.GetLayer() dataSource = None #Setup graduated color renderer based on year targetField = 'Year' renderer = QgsGraduatedSymbolRenderer('', [QgsRendererRange()]) renderer.setClassAttribute(targetField) lineLayer.setRenderer(renderer) #Get viridis color ramp style = QgsStyle().defaultStyle() defaultColorRampNames = style.colorRampNames() viridisIndex = defaultColorRampNames.index('Viridis') viridisColorRamp = style.colorRamp( defaultColorRampNames[viridisIndex]) #Spectral color ramp #Dynamically recalculate number of classes and colors renderer.updateColorRamp(viridisColorRamp) yearsRange = list(range(1972, 2020)) classCount = len(yearsRange) renderer.updateClasses(lineLayer, QgsGraduatedSymbolRenderer.EqualInterval, classCount) #Set graduated color renderer based on Sequence# for i in range(classCount): #[1972-2019], 2020 not included targetField = 'DateUnix' year = yearsRange[i] renderer.updateRangeLowerValue(i, year) renderer.updateRangeUpperValue(i, year) project.addMapLayer(lineLayer, False) rootGroup.insertLayer(0, lineLayer)
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()
class LayerBase(QgsVectorLayer): """QGIS layer base class (read-only memory based). :param fileName: path to input file :param storageFormat: storage format for layer (Memory or SQLite) """ def __init__(self, fileName, storageFormat): self._fileName = fileName self._layerName = os.path.splitext(os.path.basename(self._fileName))[0] self.storageFormat = storageFormat # create point layer (WGS-84, EPSG:4326) super(LayerBase, self).__init__('Point?crs=epsg:4326', self._layerName, "memory") self._aliases = [] # list of attribute aliases self._provider = self.dataProvider() # import errors self._errs = {} # layer is empty, no data loaded self._loaded = False self.metadata = None # layer type not defined self.layerType = None # style self._style = Style() self._renderer = None def load(self, reader): """Load input data by specified reader. :param reader: reader class used for reading input data """ if self._loaded: return # data already loaded # create progress bar widget progressMessageBar = iface.messageBar().createMessage( self.tr("Loading data...")) progress = QtWidgets.QProgressBar() progress.setMaximum(100) progressMessageBar.layout().addWidget(progress) iface.messageBar().pushWidget(progressMessageBar, Qgis.Info) # load items as new point features (inform user about progress) i = 0 count = reader.count() start = time.clock() prev = None # previous feature self.startEditing() for item in reader: i += 1 if i == 1 and not self._aliases: # set attributes from data item if needed self._aliases = self._setAttrbsDefs( limit=item.keys(), defs=reader.attributeDefs()) feat = self._item2feat(item) if not feat: # error appeared continue feat.setId(i) self.addFeature(feat) if i % 100 == 0: percent = i / float(count) * 100 progress.setValue(percent) # add features (attributes recalculated if requested) self.commitChanges() if self.storageFormat != "memory": self._writeToOgrDataSource() self.reload() # finish import endtime = time.clock() - start progress.setValue(100) iface.messageBar().clearWidgets() if self._errs: # report errors if any iface.messageBar().pushMessage( self.tr("Info"), self. tr("{} invalid measurement(s) skipped (see message log for details)" ).format(sum(self._errs.values())), level=Qgis.Info, duration=5) for attr in list(self._errs.keys()): QgsMessageLog.logMessage( "{}: {} measurement(s) skipped (invalid {})".format( self._fileName, self._errs[attr], attr), tag=PLUGIN_NAME) # inform user about successful import iface.messageBar().pushMessage( self.tr("Data loaded"), self.tr("{} features loaded (in {:.2f} sec).").format( self.featureCount(), endtime), level=Qgis.Success, duration=3) # data loaded (avoid multiple imports) self._loaded = True # switch to read-only mode self.setReadOnly(True) def _writeToOgrDataSource(self): fileExt = self.storageFormat.lower() filePath = os.path.splitext(self._fileName)[0] + '.{}'.format(fileExt) writer, msg = QgsVectorFileWriter.writeAsVectorFormat( self, filePath, self._provider.encoding(), self._provider.crs(), driverName=self.storageFormat) if writer != QgsVectorFileWriter.NoError: raise LoadError( self.tr("Unable to create SQLite datasource: {}").format(msg)) # set datasource to new OGR datasource self.setDataSource(filePath, self._layerName, 'ogr') self._provider = self.dataProvider() # create metadata layer if self.metadata and 'table' in self.metadata: ds = ogr.Open(filePath, True) layer_name = self.metadata['table'] layer = ds.GetLayerByName(layer_name) if layer is None: layer = ds.CreateLayer(layer_name, None, ogr.wkbNone) layer_defn = layer.GetLayerDefn() if 'columns' in self.metadata: for key in self.metadata['columns']: field = ogr.FieldDefn(key, ogr.OFTString) layer.CreateField(field) feat = ogr.Feature(layer_defn) for key, value in list(self.metadata['columns'].items()): feat.SetField(key, value) layer.CreateFeature(feat) feat = None def _addError(self, etype): """Add error message. :param etype: error type (HDOP, SAT, ...) """ if etype not in self._errs: self._errs[etype] = 0 self._errs[etype] += 1 def _item2feat(self, item): """Create QgsFeature from data item. """ raise NotImplementedError() def _setAttrbsDefs(self, limit=[], defs=None): """Set attributes definition from CSV file if available. :param limit: limit to list of attributes :returns: list of aliases """ def addAttribute(row, attrbs, aliases): attrbs.append( QgsField(row['attribute'], eval("QVariant.{}".format(row['qtype'])))) if 'alias' in row and row['alias']: aliases.append(row['alias'].replace('_', ' ')) def processAttributes(csv_attrbs, limit): attrbs = [] aliases = [] if limit: # limit attributes based on input file (first feature) - ERS/PEI format specific for name in limit: # first try full name match found = False for row in csv_attrbs: if row['attribute'] == name: addAttribute(row, attrbs, aliases) found = True break if found: continue for row in csv_attrbs: # full name match is not required see # https://gitlab.com/opengeolabs/qgis-radiation-toolbox-plugin/issues/41#note_136183930 if row['attribute'] == name[:len( row['attribute'] )] or name == row['attribute'][:len(name)]: row_modified = copy.copy(row) row_modified[ 'attribute'] = name # force (full) attribute name from input file if row_modified['alias']: row_modified['alias'] = '{} ({})'.format( name, row_modified['alias']) addAttribute(row_modified, attrbs, aliases) break else: # add all attributes for row in csv_attrbs: addAttribute(row, attrbs, aliases) return attrbs, aliases if not defs: # predefined attributes (CSV file) # Safecast, ERS csv_file = self._attributesCSVFile() if not os.path.exists(csv_file): return [] with open(csv_file) as fd: csv_attrbs = list(csv.DictReader(fd, delimiter=';')) attrbs, aliases = processAttributes(csv_attrbs, limit) else: # data-driven attributes # PEI attrbs, aliases = processAttributes(defs, limit) if limit and len(attrbs) != len(limit): raise LoadError("Number of attributes differs {} vs {}".format( len(attrbs), len(limit))) if aliases and self.storageFormat != "memory": aliases.insert(0, "FID") # set attributes self._provider.addAttributes(attrbs) self.updateFields() return aliases def setAliases(self): """Set aliases """ for i in range(0, len(self._aliases)): self.setFieldAlias(i, self._aliases[i]) def setStyle(self, idx): """Set layer style. :param int idx: style (combobox) index """ try: style = self._style[idx] except (IndexError, KeyError): return None if 'file' in style: # QML style stylePath = style['file'] if not os.path.isfile(stylePath): raise StyleError( self.tr("Style '{}' not found").format(stylePath)) self.loadNamedStyle(stylePath) elif 'colorramp' in style: if not self._renderer: # symbol (store transparent) symbol = QgsSymbol.defaultSymbol(self.geometryType()) symbol.symbolLayer(0).setStrokeColor(QColor("transparent")) # renderer self._renderer = QgsGraduatedSymbolRenderer() self._renderer.setSourceSymbol(symbol) print(self._renderer.sourceSymbol()) self._renderer.setClassAttribute(style['attribute']) self._renderer.setMode( QgsGraduatedSymbolRenderer.EqualInterval) self._renderer.updateClasses( self, QgsGraduatedSymbolRenderer.EqualInterval, style['classes']) self.setRenderer(self._renderer) self._renderer.updateColorRamp(style['colorramp']) else: raise StyleError(self.tr("Undefined style")) def style(self): """Get style. :return: style class """ return self._style def _attributesCSVFile(self): return os.path.join( os.path.dirname(__file__), os.path.splitext(inspect.getfile(self.__class__))[0] + '.csv')