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 updateRenderer(self, layer, attribute_vals, settings): """ Creates a renderer for the layer based on this, and applies it The renderer uses GradientColourRamp to calculate the symbol colours @param layer: the selected QgsVectorLayer object """ geometry = layer.geometryType() # create a colour ramp based on colour range type, inverting symbols if required ramp_type = int(settings['colour_range']) invert = int(settings['invert_colour']) ramp = self.getColourRamp(ramp_type, invert) line_width = float(settings['line_width']) # calculate ranges: EqualInterval = 0; Quantile = 1; Jenks = 2; StdDev = 3; Pretty = 4; Custom = 5 intervals = int(settings['intervals']) mode = int(settings['interval_type']) attribute = attribute_vals['name'] renderer = None if mode < 3: # set symbol type and line width symbol = QgsSymbol.defaultSymbol(geometry) if symbol: if symbol.type() == 1: # line symbol.setWidth(line_width) elif symbol.type() == 2: # line symbol = QgsFillSymbol.createSimple({ 'style': 'solid', 'color': 'black', 'width_border': '%s' % line_width }) elif symbol.type() == 0: # point symbol.setSize(line_width) renderer = QgsGraduatedSymbolRenderer.createRenderer( layer, attribute, intervals, mode, symbol, ramp) renderer.setMode(mode) renderer.setSourceColorRamp(ramp) else: # calculate range values individually based on custom settings ranges = [] max_value = float(attribute_vals['max']) min_value = float(attribute_vals['min']) top_value = float(settings['top_value']) bottom_value = float(settings['bottom_value']) # calculate number of ranges depending on top/bottom difference from max/min: # is there really a range there? Otherwise this will calculate 1 or even 2 ranges less calc_intervals = intervals + 1 if top_value != max_value: calc_intervals -= 1 if bottom_value != min_value: calc_intervals -= 1 range_steps = [ r for r in np.linspace(bottom_value, top_value, calc_intervals) ] if top_value != max_value: range_steps.append(max_value) if bottom_value != min_value: range_steps.insert(0, min_value) for i in range(0, len(range_steps) - 1): symbol = QgsSymbol.defaultSymbol(geometry) if symbol: new_colour = ramp.color( i / (float(len(range_steps)) - 2)).getRgb() symbol.setColor(QColor(*new_colour)) symbol.setWidth(line_width) label = "%s - %s" % (range_steps[i], range_steps[i + 1]) this_range = QgsRendererRange(range_steps[i], range_steps[i + 1], symbol, label) ranges.append(this_range) if ranges: renderer = QgsGraduatedSymbolRenderer(attribute, ranges) # renderer.setMode(5) renderer.setSourceColorRamp(ramp) # configure symbol levels to display in specific order # the classic "reds on top" from space syntax, or the reverse if renderer: display_order = int(settings['display_order']) renderer.setUsingSymbolLevels(True) render_pass = 0 if display_order == 0: for symbol in renderer.symbols(QgsRenderContext()): for i in range(0, symbol.symbolLayerCount()): symbol.symbolLayer(i).setRenderingPass(render_pass) render_pass += 1 else: for symbol in reversed(renderer.symbols(QgsRenderContext())): for i in range(0, symbol.symbolLayerCount()): symbol.symbolLayer(i).setRenderingPass(render_pass) render_pass += 1 # set the symbols for monochrome ramp # varying line width, point size or polygon pattern density # doesn't use data column because it's not scaled according to line width values # the width is calculated linearly between min and given value if renderer: if ramp_type == 3: new_width = np.linspace(0.1, line_width, intervals) step = intervals / 8.0 # this is usd for fill patterns # color = QColor(ramp.color(0).getRgb()) # same as above for i in range(0, intervals): symbol = renderer.symbols(QgsRenderContext())[i] if invert: if symbol.type() == 1: # line symbol.setWidth(new_width[(intervals - 1) - i]) elif symbol.type() == 0: # point symbol.setSize(new_width[(intervals - 1) - i]) elif symbol.type() == 2: # polygon dense = int(i / step) if dense == 0: style = 'solid' else: style = 'dense%s' % dense symbol = QgsFillSymbol.createSimple({ 'style': style, 'color': 'black', 'width_border': '%s' % new_width[(intervals - 1) - i] }) else: if symbol.type() == 1: # line symbol.setWidth(new_width[i]) elif symbol.type() == 0: # point symbol.setSize(new_width[i]) elif symbol.type() == 2: # polygon dense = int(i / step) if dense == 7: style = 'solid' else: style = 'dense%s' % (7 - dense) symbol = QgsFillSymbol.createSimple({ 'style': style, 'color': 'black', 'width_border': '%s' % new_width[i] }) renderer.updateRangeSymbol(i, symbol) return renderer