Skip to content

Commit

Permalink
Adding initial implementation of raster styles:
Browse files Browse the repository at this point in the history
Adding models, objects and renderer factories for a proof of concept
  • Loading branch information
constantinius committed Sep 8, 2023
1 parent c7f709c commit b6752d6
Show file tree
Hide file tree
Showing 10 changed files with 514 additions and 14 deletions.
36 changes: 36 additions & 0 deletions autotest/autotest/data/SCL/scl.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:gml="http://www.opengis.net/gml" xmlns:sld="http://www.opengis.net/sld" version="1.0.0" xmlns:ogc="http://www.opengis.net/ogc">
<UserLayer>
<sld:LayerFeatureConstraints>
<sld:FeatureTypeConstraint/>
</sld:LayerFeatureConstraints>
<sld:UserStyle>
<sld:Name>S2B_30UUG_20221226_0_L2A_scl</sld:Name>
<sld:FeatureTypeStyle>
<sld:Rule>
<sld:RasterSymbolizer>
<sld:ChannelSelection>
<sld:GrayChannel>
<sld:SourceChannelName>1</sld:SourceChannelName>
</sld:GrayChannel>
</sld:ChannelSelection>
<sld:ColorMap type="values">
<sld:ColorMapEntry color="#000000" quantity="0" label="NO_DATA"/>
<sld:ColorMapEntry color="#ff0000" quantity="1" label="SATURATED_OR_DEFECTIVE"/>
<sld:ColorMapEntry color="#2e2e2e" quantity="2" label="DARK_AREA_PIXELS"/>
<sld:ColorMapEntry color="#541800" quantity="3" label="CLOUD_SHADOWS"/>
<sld:ColorMapEntry color="#46e800" quantity="4" label="VEGETATION"/>
<sld:ColorMapEntry color="#ffff00" quantity="5" label="NOT_VEGETATED"/>
<sld:ColorMapEntry color="#0000ff" quantity="6" label="WATER"/>
<sld:ColorMapEntry color="#525252" quantity="7" label="UNCLASSIFIED"/>
<sld:ColorMapEntry color="#787878" quantity="8" label="CLOUD_MEDIUM_PROBABILITY"/>
<sld:ColorMapEntry color="#b5b5b5" quantity="9" label="CLOUD_HIGH_PROBABILITY"/>
<sld:ColorMapEntry color="#00b6bf" quantity="10" label="THIN_CIRRUS"/>
<sld:ColorMapEntry color="#da00f2" quantity="11" label="SNOW"/>
</sld:ColorMap>
</sld:RasterSymbolizer>
</sld:Rule>
</sld:FeatureTypeStyle>
</sld:UserStyle>
</UserLayer>
</StyledLayerDescriptor>
24 changes: 24 additions & 0 deletions autotest/autotest/data/SCL/scl_coverage_type.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[{
"bands": [
{
"definition": "http://www.opengis.net/def/property/OGC/0/Radiance",
"description": "SCL",
"gdal_interpretation": "gray",
"identifier": "scl",
"name": "scl",
"nil_values": [
{
"reason": "http://www.opengis.net/def/nil/OGC/0/unknown",
"value": 0
}
],
"uom": "nil",
"significant_figures": 2,
"allowed_value_ranges": [
[0, 11]
]
}
],
"data_type": "Byte",
"name": "SCL"
}]
14 changes: 14 additions & 0 deletions eoxserver/render/browse/defaultstyles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from eoxserver.render.colors import COLOR_SCALES
from eoxserver.render.browse.objects import RasterStyle, RasterStyleColorEntry

DEFAULT_RASTER_STYLES = {}

for name, entries in COLOR_SCALES.items():
DEFAULT_RASTER_STYLES[name] = RasterStyle(
name,
"ramp",
[
RasterStyleColorEntry(i, color)
for i, color in entries
]
)
85 changes: 81 additions & 4 deletions eoxserver/render/browse/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@


class Browse(object):
def __init__(self, name, filename, env, size, extent, crs, mode, footprint):
def __init__(self, name, filename, env, size, extent, crs, mode, footprint,
raster_styles):
self._name = name
self._filename = filename
self._env = env
Expand All @@ -55,6 +56,7 @@ def __init__(self, name, filename, env, size, extent, crs, mode, footprint):
self._crs = crs
self._mode = mode
self._footprint = footprint
self._raster_styles = raster_styles

@property
def name(self):
Expand Down Expand Up @@ -146,8 +148,8 @@ def from_file(cls, filename, env=None):

class GeneratedBrowse(Browse):
def __init__(self, name, band_expressions, ranges, nodata_values,
fields_and_coverages, field_list, footprint, variables,
show_out_of_bounds_data=False,
fields_and_coverages, field_list, footprint, raster_styles,
variables, show_out_of_bounds_data=False,
):
self._name = name
self._band_expressions = band_expressions
Expand All @@ -156,6 +158,7 @@ def __init__(self, name, band_expressions, ranges, nodata_values,
self._fields_and_coverages = fields_and_coverages
self._field_list = field_list
self._footprint = footprint
self._raster_styles = raster_styles
self._variables = variables
self._show_out_of_bounds_data = show_out_of_bounds_data

Expand Down Expand Up @@ -217,14 +220,19 @@ def field_list(self):
def variables(self):
return self._variables

@property
def raster_styles(self):
return self._raster_styles

@property
def show_out_of_bounds_data(self) -> bool:
return self._show_out_of_bounds_data

@classmethod
def from_coverage_models(cls, band_expressions, ranges, nodata_values,
fields_and_coverage_models,
product_model, variables, show_out_of_bounds_data):
product_model, variables, raster_styles,
show_out_of_bounds_data):

fields_and_coverages = {
field_name: [
Expand All @@ -246,6 +254,7 @@ def from_coverage_models(cls, band_expressions, ranges, nodata_values,
for field_name in fields_and_coverages.keys()
],
product_model.footprint,
raster_styles,
variables,
show_out_of_bounds_data,
)
Expand Down Expand Up @@ -320,6 +329,74 @@ def from_models(cls, product_model, browse_model, mask_model,
)


class RasterStyle(object):
def __init__(self, name, type, entries):
self._name = name
self._type = type
self._entries = entries

@property
def name(self):
return self._name

@property
def type(self):
return self._type

@property
def entries(self):
return self._entries

@classmethod
def from_model(cls, raster_style_model):
return cls(
raster_style_model.name,
raster_style_model.type,
[
RasterStyleColorEntry.from_model(entry_model)
for entry_model in raster_style_model.color_entries.all()
]
)


def hex_to_rgb(hexa):
hexa = hexa.lstrip("#")
return tuple(int(hexa[i:i + 2], 16) for i in (0, 2, 4))


class RasterStyleColorEntry(object):
def __init__(self, value, color, opacity=1.0, label=None):
self._value = value
self._color = color
self._opacity = opacity
self._label = label

@property
def value(self):
return self._value

@property
def color(self):
return self._color

@property
def opacity(self):
return self._opacity

@property
def label(self):
return self._label

@classmethod
def from_model(cls, raster_style_color_entry_model):
return cls(
raster_style_color_entry_model.value,
hex_to_rgb(raster_style_color_entry_model.color),
raster_style_color_entry_model.opacity,
raster_style_color_entry_model.label,
)


def _get_ds_mode(ds):
first = ds.GetRasterBand(1)

Expand Down
6 changes: 3 additions & 3 deletions eoxserver/render/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,9 @@ def linear(colors):
]),

"brylgn" : linear([
(130,67,0),
(255,200,110),
(255,255,179),
(130, 67, 0),
(255, 200, 110),
(255, 255, 179),
(116, 234, 118),
(0, 109, 0),
]),
Expand Down
47 changes: 44 additions & 3 deletions eoxserver/render/mapserver/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from eoxserver.render.browse.generate import (
generate_browse, FilenameGenerator
)
from eoxserver.render.browse.defaultstyles import DEFAULT_RASTER_STYLES
from eoxserver.render.map.objects import (
CoverageLayer, CoveragesLayer, MosaicLayer, OutlinedCoveragesLayer,
BrowseLayer, OutlinedBrowseLayer,
Expand Down Expand Up @@ -392,8 +393,9 @@ def make_browse_layer_generator(self, map_obj, browses, map_,
browse_range = _get_range(field)

for layer_obj in layer_objs:
raster_style = browse.raster_styles.get(style or "blackwhite") or DEFAULT_RASTER_STYLES[style or "blackwhite"]
_create_raster_style(
style or "blackwhite", layer_obj,
raster_style, layer_obj,
browse_range[0], browse_range[1],
browse.nodata_values
)
Expand Down Expand Up @@ -784,10 +786,49 @@ def _build_vrt(size, field_locations):
return path


def _create_raster_style(name, layer, minvalue=0, maxvalue=255,
def _create_raster_style(raster_style, layer, minvalue=0, maxvalue=255,
nil_values=None):
colors = COLOR_SCALES[name]
if raster_style.type == "ramp":
return _create_raster_style_ramp(
raster_style, layer, minvalue, maxvalue, nil_values
)
elif raster_style.type == "values":
for entry in raster_style.entries:
value = entry.value
if int(value) == value:
value = int(value)
cls = ms.classObj()
cls.setExpression("([pixel] = %s)" % value)
cls.group = entry.label

style = ms.styleObj()
style.color = ms.colorObj(*entry.color)
style.opacity = int(entry.opacity * 255)
cls.insertStyle(style)
layer.insertClass(cls)

cls = ms.classObj()
style = ms.styleObj()
style.color = ms.colorObj(0, 0, 0, 0)
style.opacity = 0
cls.insertStyle(style)
layer.insertClass(cls)
return

elif raster_style.type == "intervals":
# TODO
return
raise ValueError("Invalid raster style type %r" % raster_style.type)


def _create_raster_style_ramp(raster_style, layer, minvalue=0, maxvalue=255,
nil_values=None):
name = raster_style.name

colors = [
(entry.value, entry.color)
for entry in raster_style.entries
]
if nil_values and all(v is not None for v in nil_values):
nil_values = [float(nil_value) for nil_value in nil_values]
else:
Expand Down
Loading

0 comments on commit b6752d6

Please sign in to comment.