Skip to content

Commit

Permalink
Merge pull request #39 from pblottiere/singleband_pseudocolor
Browse files Browse the repository at this point in the history
Add singleband pseudocolor renderer
  • Loading branch information
pblottiere authored Jun 25, 2024
2 parents ba975c4 + 45b8b3b commit baa3057
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 23 deletions.
20 changes: 11 additions & 9 deletions docs/src/qsa-api/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,17 @@ in QGIS is very dense but for now, only Marker, Line and Fill simple symbols
are supported. The `/api/symbology` endpoint allows to dynamically retrieve the
corresponding parameters depending on QGIS Server version.

| Method | URL | Description |
|---------|---------------------------------------------------------------|----------------------------------------------|
| GET | `/api/symbology/vector/point/single_symbol/marker/properties` | Marker simple symbol properties |
| GET | `/api/symbology/vector/line/single_symbol/line/properties` | Line simple symbol properties |
| GET | `/api/symbology/vector/polygon/single_symbol/fill/properties` | Polygon simple symbol properties |
| GET | `/api/symbology/vector/rendering/properties` | Vector layer rendering properties |
| GET | `/api/symbology/raster/singlebandgray/properties` | Single band gray properties |
| GET | `/api/symbology/raster/multibandcolor/properties` | Multi band color properties |
| GET | `/api/symbology/raster/rendering/properties` | Raster layer rendering properties |
| Method | URL | Description |
|---------|---------------------------------------------------------------------------|----------------------------------------------|
| GET | `/api/symbology/vector/point/single_symbol/marker/properties` | Marker simple symbol properties |
| GET | `/api/symbology/vector/line/single_symbol/line/properties` | Line simple symbol properties |
| GET | `/api/symbology/vector/polygon/single_symbol/fill/properties` | Polygon simple symbol properties |
| GET | `/api/symbology/vector/rendering/properties` | Vector layer rendering properties |
| GET | `/api/symbology/raster/singlebandgray/properties` | Single band gray properties |
| GET | `/api/symbology/raster/multibandcolor/properties` | Multi band color properties |
| GET | `/api/symbology/raster/singlebandpseudocolor/properties` | Single band pseudocolor properties |
| GET | `/api/symbology/raster/singlebandpseudocolor/ramp/{name}/properties` | Single band pseudocolor ramp properties |
| GET | `/api/symbology/raster/rendering/properties` | Raster layer rendering properties |

Examples:

Expand Down
40 changes: 40 additions & 0 deletions qsa-api/qsa_api/api/symbology.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from flask import Blueprint, jsonify

from qgis.core import (
QgsStyle,
QgsSimpleLineSymbolLayer,
QgsSimpleFillSymbolLayer,
QgsSingleBandGrayRenderer,
QgsMultiBandColorRenderer,
QgsSimpleMarkerSymbolLayer,
QgsSingleBandPseudoColorRenderer,
)


Expand Down Expand Up @@ -74,6 +76,44 @@ def symbology_raster_multibandcolor():
return jsonify(props)


@symbology.get(
f"/raster/{QgsSingleBandPseudoColorRenderer(None, 1).type()}/properties"
)
def symbology_raster_singlebandpseudocolor():
ramps = ", ".join(QgsStyle().defaultStyle().colorRampNames())

props = {}
props["band"] = {"band": 1, "min": 0.0, "max": 1.0}
props["ramp"] = {
"name" : f"Spectral ({ramps})",
"color1": "0,0,0,255",
"color2": "255,255,255,255",
"interpolation": "Linear (Linear, Discrete, Exact)"
}
props["contrast_enhancement"] = {
"limits_min_max": "MinMax (MinMax, UserDefined)",
}
return jsonify(props)


@symbology.get(
f"/raster/{QgsSingleBandPseudoColorRenderer(None, 1).type()}/ramp/<name>/properties"
)
def symbology_raster_singlebandpseudocolor_ramp_props(name):
proper_name = ""
for n in QgsStyle().defaultStyle().colorRampNames():
if n.lower() == name:
proper_name = n

props = {}
ramp = QgsStyle().defaultStyle().colorRamp(proper_name)
if ramp:
props["color1"] = ramp.properties()["color1"].split("rgb")[0]
props["color2"] = ramp.properties()["color2"].split("rgb")[0]

return jsonify(props)


@symbology.get("/raster/rendering/properties")
def symbology_raster_rendering():
props = {}
Expand Down
148 changes: 134 additions & 14 deletions qsa-api/qsa_api/raster/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
from pathlib import Path

from qgis.core import (
QgsStyle,
QgsRasterLayer,
QgsRasterShader,
QgsColorRampShader,
QgsRasterBandStats,
QgsGradientColorRamp,
QgsRasterMinMaxOrigin,
QgsContrastEnhancement,
QgsSingleBandGrayRenderer,
QgsMultiBandColorRenderer,
QgsSingleBandPseudoColorRenderer,
)

ContrastEnhancementAlgorithm = (
Expand All @@ -20,6 +25,7 @@
class RasterSymbologyRenderer:
class Type(Enum):
SINGLE_BAND_GRAY = QgsSingleBandGrayRenderer(None, 1).type()
SINGLE_BAND_PSEUDOCOLOR = QgsSingleBandPseudoColorRenderer(None, 1).type()
MULTI_BAND_COLOR = QgsMultiBandColorRenderer(None, 1, 1, 1).type()

def __init__(self, name: str) -> None:
Expand All @@ -40,6 +46,8 @@ def __init__(self, name: str) -> None:
self.renderer = QgsSingleBandGrayRenderer(None, 1)
elif name == RasterSymbologyRenderer.Type.MULTI_BAND_COLOR.value:
self.renderer = QgsMultiBandColorRenderer(None, 1, 1, 1)
elif name == RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR.value:
self.renderer = QgsSingleBandPseudoColorRenderer(None, 1)

@property
def type(self):
Expand All @@ -53,6 +61,11 @@ def type(self):
== RasterSymbologyRenderer.Type.MULTI_BAND_COLOR.value
):
return RasterSymbologyRenderer.Type.MULTI_BAND_COLOR
elif (
self.renderer.type()
== RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR.value
):
return RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR

return None

Expand All @@ -67,6 +80,8 @@ def load(self, properties: dict) -> (bool, str):
self._load_multibandcolor_properties(properties)
elif self.type == RasterSymbologyRenderer.Type.SINGLE_BAND_GRAY:
self._load_singlebandgray_properties(properties)
elif self.type == RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR:
self._load_singlebandpseudocolor_properties(properties)

return True, ""

Expand All @@ -85,6 +100,8 @@ def refresh_min_max(self, layer: QgsRasterLayer) -> None:
self._refresh_min_max_singlebandgray(layer)
elif self.type == RasterSymbologyRenderer.Type.MULTI_BAND_COLOR:
self._refresh_min_max_multibandcolor(layer)
elif self.type == RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR:
self._refresh_min_max_singlebandpseudocolor(layer)

@staticmethod
def style_to_json(path: Path) -> (dict, str):
Expand All @@ -110,6 +127,10 @@ def style_to_json(path: Path) -> (dict, str):
props = RasterSymbologyRenderer._multibandcolor_properties(
renderer
)
elif renderer_type == RasterSymbologyRenderer.Type.SINGLE_BAND_PSEUDOCOLOR:
props = RasterSymbologyRenderer._singlebandpseudocolor_properties(
renderer
)

m["symbology"]["properties"] = props

Expand Down Expand Up @@ -220,6 +241,40 @@ def _singlebandgray_properties(renderer) -> dict:

return props

@staticmethod
def _singlebandpseudocolor_properties(renderer) -> dict:
props = {}

props["band"] = {}
props["band"]["band"] = renderer.band()

props["band"]["min"] = renderer.classificationMin()
props["band"]["max"] = renderer.classificationMax()

props["ramp"] = {}
shader_fct = renderer.shader().rasterShaderFunction()
color_1 = shader_fct.sourceColorRamp().properties()["color1"].split("rgb")[0]
color_2 = shader_fct.sourceColorRamp().properties()["color2"].split("rgb")[0]
props["ramp"]["color1"] = color_1
props["ramp"]["color2"] = color_2

ramp_type = shader_fct.colorRampType()
if ramp_type == QgsColorRampShader.Discrete:
props["ramp"]["interpolation"] = "Discrete"
elif ramp_type == QgsColorRampShader.Exact:
props["ramp"]["interpolation"] = "Exact"
elif ramp_type == QgsColorRampShader.Interpolated:
props["ramp"]["interpolation"] = "Interpolated"

props["contrast_enhancement"] = {}

limits = renderer.minMaxOrigin().limits()
props["contrast_enhancement"]["limits_min_max"] = "UserDefined"
if limits == QgsRasterMinMaxOrigin.Limits.MinMax:
props["contrast_enhancement"]["limits_min_max"] = "MinMax"

return props

def _refresh_min_max_multibandcolor(self, layer: QgsRasterLayer) -> None:
renderer = layer.renderer()
red_ce = QgsContrastEnhancement(renderer.redContrastEnhancement())
Expand Down Expand Up @@ -286,6 +341,19 @@ def _refresh_min_max_singlebandgray(self, layer: QgsRasterLayer) -> None:

layer.renderer().setContrastEnhancement(ce)

def _refresh_min_max_singlebandpseudocolor(self, layer: QgsRasterLayer) -> None:
# compute min/max
min_max_origin = layer.renderer().minMaxOrigin().limits()
if min_max_origin == QgsRasterMinMaxOrigin.Limits.MinMax:
# Accuracy : estimate
stats = layer.dataProvider().bandStatistics(
1, QgsRasterBandStats.Min | QgsRasterBandStats.Max, layer.extent(), 250000
)

layer.renderer().setClassificationMin(stats.minimumValue)
layer.renderer().setClassificationMax(stats.maximumValue)
layer.renderer().shader().rasterShaderFunction().classifyColorRamp()

def _load_multibandcolor_properties(self, properties: dict) -> None:
if "red" in properties:
red = properties["red"]
Expand Down Expand Up @@ -343,19 +411,71 @@ def _load_singlebandgray_properties(self, properties: dict) -> None:
QgsSingleBandGrayRenderer.Gradient.WhiteToBlack
)

def _load_singlebandpseudocolor_properties(self, properties: dict) -> None:
# always stretch to min/max in case of the singlepseudocolor renderer
self.contrast_algorithm = (
ContrastEnhancementAlgorithm.StretchToMinimumMaximum
)

band_min = None
band_max = None
if "band" in properties:
band = properties["band"]
self.renderer.setBand(int(band["band"]))

if self.contrast_limits == QgsRasterMinMaxOrigin.Limits.None_:
if "min" in band:
band_min = float(band["min"])

if "max" in band:
band_max = float(band["max"])

if "ramp" in properties:
ramp = properties["ramp"]
shader_type = QgsColorRampShader.Type.Interpolated
if "interpolation" in ramp:
interpolation = ramp["interpolation"]
if interpolation == "Discrete":
shader_type = QgsColorRampShader.Type.Discrete
elif interpolation == "Exact":
shader_type = QgsColorRampShader.Type.Exact

color_ramp = QgsStyle().defaultStyle().colorRamp("Spectral")
if "name" in ramp:
color_ramp = QgsStyle().defaultStyle().colorRamp(ramp["name"])
elif "color1" in ramp and "color2" in ramp:
color_ramp = QgsGradientColorRamp.create(ramp)

ramp_shader = QgsColorRampShader()
ramp_shader.setSourceColorRamp(color_ramp)
ramp_shader.setColorRampType(shader_type)

shader = QgsRasterShader()
shader.setRasterShaderFunction(ramp_shader)
self.renderer.setShader(shader)

if band_min is not None:
self.renderer.setClassificationMin(band_min)
if band_max is not None:
self.renderer.setClassificationMax(band_max)

self.renderer.shader().rasterShaderFunction().classifyColorRamp()

def _load_contrast_enhancement(self, properties: dict) -> None:
alg = properties["algorithm"]
if alg == "StretchToMinimumMaximum":
self.contrast_algorithm = (
ContrastEnhancementAlgorithm.StretchToMinimumMaximum
)
elif alg == "NoEnhancement":
self.contrast_algorithm = (
ContrastEnhancementAlgorithm.NoEnhancement
)
if "algorithm" in properties:
alg = properties["algorithm"]
if alg == "StretchToMinimumMaximum":
self.contrast_algorithm = (
ContrastEnhancementAlgorithm.StretchToMinimumMaximum
)
elif alg == "NoEnhancement":
self.contrast_algorithm = (
ContrastEnhancementAlgorithm.NoEnhancement
)

limits = properties["limits_min_max"]
if limits == "UserDefined":
self.contrast_limits = QgsRasterMinMaxOrigin.Limits.None_
elif limits == "MinMax":
self.contrast_limits = QgsRasterMinMaxOrigin.Limits.MinMax
if "limits_min_max" in properties:
limits = properties["limits_min_max"]
if limits == "UserDefined":
self.contrast_limits = QgsRasterMinMaxOrigin.Limits.None_
elif limits == "MinMax":
self.contrast_limits = QgsRasterMinMaxOrigin.Limits.MinMax

0 comments on commit baa3057

Please sign in to comment.