Raster Symbolizer support - STEMLab/geotools GitHub Wiki

Implement RasterSymbolizer support for the GeoTools rendering system.

Discussion

RasterSymbolizer defines a set of rules on how the client can instruct the renderer to create a portrayal of raster data. It introduces a set of operations that a rendering engine can perform consequently on raster data, like band selection, recolor, contrast enhancement and so on (see SE 1.1 and SLD 1.0 1.0 for reference).

The Current Situation

The actual GeoTools SLD Raster Symbolizer implementation is rather poor and takes in account a very limited set of operations. Currently, the Raster Symbolizer functionalities are provided basically by the RasterSymbolizerSupport class represented in the diagram below.

The RasterSymbolizerSupport class has basically one method (recolorCoverage()) which extracts the color-map from the provided SLD's RasterSymbolizer element and tries to create a new set of Categories for the Coverage. Obviously, this approach only takes into account the ColorMap operation, also the result is not guaranted for all the cases, due to the fact that an input coverage may have no categories at all hence the recolorCoverage method may fail.

The Proposed Implementation

We describe here the approach we have followed to improve support for the RasterSymbolizer.

The work that has been performed can be summarized as follows:

  • check the SE 1.1 documentation for any changes relative to SLD 1.1 RasterSymbolizer
  • development of piecewise1D transformation support classes for performing generic transformation on raster bands.
  • development of coverage processing nodes implementing all the functionalities of the Raster Symbolizer as a processing chain.

The benefits are:

  • the SLD Raster Symbolizer basic operations supported are:
    • Channel Selection
    • Color Map
    • Contrast Enhancement
      • algorithms actually supported:
        • Histogram Normalize
        • Histogram Equalize (ONLY ON BYTE DATA for the moment)
        • Logarithmic Enhancement
        • Exponential Enhancement
    • Opacity
    • Gamma Correction
  • this architecture allows to easily add more functionalities as needed, by simply implementing specific coverage processing nodes
  • piecewise transformations allow full control for raster classification

The diagrams below provides an overview of the proposed infrastructure for supporting piecewise transformation on raster values (click to enlarge diagram).

Here below the UML class diagram for the classes responsible for implementing SLD 1.0 rastersymbolizer (click to enlarge diagram).

Depending on the type of the CoveerageProcessingNode, it can be nested at different levels and invoked at different steps of the processing chain. The structure of the proposed API allows a developer to chain nodes as desired. The execute method produces an output GridCoverage2D used as input for the next node on the chain.

In order to improve the raster classification using categories, a new RasterClassifier JAI operation is proposed. This class inspects the raster classifing the raw values and range of values into categories, like for instance No-Data, but also prepares the image layout of the portrayal taking into account the original sample model of the raster, which usually cannot be directly represented as an image.

Note that javadocs for this work are attached.

Tests and Code Coverage

As shown by the picture below particular care has been put on testing the provided capabilities. However, given the extensive refactor that this work has been undergoing lately to incorporate suggestions from the community we cannot guarantee to be 100% bug free (click to enlarge diagram).

Usage

From the user point of view, the Raster Symbolizer intervention support is fully transparent since it is hidden within the GridCoverageRender. Let's show some example how usage.

Symbolizing Sea Bottom Data

The following code provides an example of applying raster symbolizer on a sea bottom coverage through parsing of an SLD file.

// ////////////////////////////////////////////////////////////////////
//
// Test #1: [SLD]
// - Opacity: 1.0
// - ChannelSelection: Gray {Contrast Enh: Histogram}
//
// ////////////////////////////////////////////////////////////////////
java.net.URL surl = TestData.url(this, "histogram.sld");
SLDParser stylereader = new SLDParser(sf, surl);
StyledLayerDescriptor sld = stylereader.parseSLD();
// the RasterSymbolizer Helper
SubchainStyleVisitorCoverageProcessingAdapter rsh_SLD = new RasterSymbolizerHelper(gc, null);
// build the RasterSymbolizer
final UserLayer nl = (UserLayer) sld.getStyledLayers()[0];
final Style style = nl.getUserStyles()[0];
final FeatureTypeStyle fts = style.getFeatureTypeStyles()[0];
final Rule rule = fts.getRules()[0];
final RasterSymbolizer rs_1 = (RasterSymbolizer) rule.getSymbolizers()[0];
// visit the RasterSymbolizer
rsh_SLD.visit(rs_1).show();

StyleBuilder can also be used to manually build the Raster Symboliser element, see below.

// ////////////////////////////////////////////////////////////////////
//
// Test #1: [StyleBuilder]
// - Opacity: 1.0
// - ChannelSelection: Gray {Contrast Enh: Histogram}
//
// ////////////////////////////////////////////////////////////////////

// the RasterSymbolizer Helper
SubchainStyleVisitorCoverageProcessingAdapter rsh_StyleBuilder = new RasterSymbolizerHelper(gc, null);
// build the RasterSymbolizer
StyleBuilder sldBuilder = new StyleBuilder();
// the RasterSymbolizer Helper
rsh_StyleBuilder = new RasterSymbolizerHelper(gc, null);

final RasterSymbolizer rsb_1 = sldBuilder.createRasterSymbolizer();
final ChannelSelection chSel = new ChannelSelectionImpl();
final SelectedChannelType chTypeGray = new SelectedChannelTypeImpl();
final ContrastEnhancement cntEnh = new ContrastEnhancementImpl();
cntEnh.setHistogram();

chTypeGray.setChannelName("1");
chTypeGray.setContrastEnhancement(cntEnh);
chSel.setGrayChannel(chTypeGray);

rsb_1.setChannelSelection(chSel);
rsb_1.setOpacity(sldBuilder.literalExpression(1.0));
rsb_1.setOverlap(sldBuilder.literalExpression("AVERAGE"));

// visit the RasterSymbolizer
rsh_StyleBuilder.visit(rsb_1).show();

Here below the results of the code above with an input sonar image at which an Histogram Contrast Enhancement has been applied

Original Image

Symbolized Image

Applying SLD to DEM data

The SLD used:

<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld"
    xmlns:ogc="http://www.opengis.net/ogc"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.opengis.net/sld
http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd?" version="1.0.0">
<UserLayer>
 <Name>raster_layer</Name>
        <LayerFeatureConstraints>
            <FeatureTypeConstraint/>
        </LayerFeatureConstraints>
 <UserStyle>
  <Name>raster</Name>
  <Title>A boring default style</Title>
  <Abstract>A sample style for rasters</Abstract>
  <FeatureTypeStyle>
         <FeatureTypeName>Feature</FeatureTypeName>
   <Rule>
    <RasterSymbolizer>
     <!-- ColorMap type="ramp" extended="true">
      <ColorMapEntry color="#000000" quantity="0.0"  opacity="1.0"/>
      <ColorMapEntry color="#ff0000" quantity="2.0"  opacity="1.0"/>
      <ColorMapEntry color="#ffff00" quantity="10.0" opacity="1.0"/>
      <ColorMapEntry color="#00ff00" quantity="15.0" opacity="1.0"/>
      <ColorMapEntry color="#00ffff" quantity="20.0" opacity="1.0"/>
      <ColorMapEntry color="#0000ff" quantity="25.0" opacity="1.0"/>
      <ColorMapEntry color="#ff00ff" quantity="30.0" opacity="1.0"/>
      <ColorMapEntry color="#ffffff" quantity="100.0" opacity="1.0"/>
     </ColorMap -->
        <Opacity>1.0</Opacity>
     <ChannelSelection>
      <GrayChannel>
       <SourceChannelName>1</SourceChannelName>
        <ContrastEnhancement>
         <Normalize/>
        </ContrastEnhancement>
      </GrayChannel>
     </ChannelSelection>
     <ContrastEnhancement>
      <GammaValue>1</GammaValue>
            </ContrastEnhancement>
    </RasterSymbolizer>
   </Rule>
  </FeatureTypeStyle>
 </UserStyle>
</UserLayer>
</StyledLayerDescriptor>

Original Image

Gray Channel

Symbolized Gray Channel

An example using an input ASCII-Grid file produced by the SWAN processing

A snippet of the original file:

NCOLS 278
NROWS 144
XLLCENTER 8.118000030517578
YLLCENTER 43.191001892089844
CELLSIZE 0.008999999478566561
NODATA_VALUE -9.0
-9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 ...
...
-9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 -9.0 0.36357998847961426 0.45179998874664307 0.5112000107765198 ...

Portrayal using an SLD with no Color Map, Gray Channel selection and Histogram Contrast Enhancement:

<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns=http://www.opengis.net/sld
  xmlns:ogc="http://www.opengis.net/ogc"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" version="1.0.0">
<UserLayer>
 <Name>raster_layer</Name>
 <LayerFeatureConstraints>
     <FeatureTypeConstraint/>
 </LayerFeatureConstraints>
  <UserStyle>
      <Name>raster</Name>
      <Title>A boring default style</Title>
      <Abstract>A sample style for rasters</Abstract>
      <FeatureTypeStyle>
           <FeatureTypeName>Feature</FeatureTypeName>
           <Rule>
               <RasterSymbolizer>
                   <Opacity>1.0</Opacity>
                   <ChannelSelection>
                       <GrayChannel>
                           <SourceChannelName>1</SourceChannelName>
                           <ContrastEnhancement>
                                <Histogram/>
                           </ContrastEnhancement>
                       </GrayChannel>
                   </ChannelSelection>
              </RasterSymbolizer>
           </Rule>
       </FeatureTypeStyle>
    </UserStyle>
  </UserLayer>
</StyledLayerDescriptor>

Sample output:

Portrayal using an& SLD with Color Map and Gray Channel selection

<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor
  xmlns="http://www.opengis.net/sld"
  xmlns:ogc="http://www.opengis.net/ogc"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" version="1.0.0">
   <UserLayer>
          <Name>raster_layer</Name>
          <LayerFeatureConstraints>
               <FeatureTypeConstraint/>
          </LayerFeatureConstraints>
         <UserStyle>
         <Name>raster</Name>
         <Title>A boring default style</Title>
         <Abstract>A sample style for rasters</Abstract>
         <FeatureTypeStyle>
             <FeatureTypeName>Feature</FeatureTypeName>
             <Rule>
                 <RasterSymbolizer>
                      <ColorMap type="ramp" extended="true">
                           <ColorMapEntry color="#000000" quantity="-9.0" opacity="1.0"/>
                           <ColorMapEntry color="#cc0000" quantity="0.0"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ff0000" quantity="0."&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ffcc00" quantity="0.2"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ccff00" quantity="0.3"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ccffcc" quantity="0.4"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#00ffff" quantity="0.5"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#00ffcc" quantity="0.6"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#00ff00" quantity="0.7"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ccccff" quantity="0.8"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ccffff" quantity="0.9"&nbsp; opacity="1.0"/>
                           <ColorMapEntry color="#ffffff" quantity="1.0"&nbsp; opacity="1.0"/>
                      </ColorMap>
                      <Opacity>1.0</Opacity>
                      <ChannelSelection>
                            <GrayChannel>
                                 <SourceChannelName>1</SourceChannelName>
                            </GrayChannel>
                      </ChannelSelection>
                 </RasterSymbolizer>
             </Rule>
         </FeatureTypeStyle>
     </UserStyle>
  </UserLayer>
</StyledLayerDescriptor>

Sample output:

Final Remarks

The proposed approach should be backward compatible, since it does not require any kind of intrusive intervent to the GridCoverageRenderer since it completely replace the old RasterSymbolizerSupport class.The diagram depicted at the beginning of the document will change as shown below

Status

Actually the Raster Symbolizer stuff presented here, is implemented on the GeoTools 2.4.x-RS branch. In order to complete the proposal the code should be ported to eoTools trunk.

Voting has not started yet:

Tasks

        | :white_check_mark: | :no_entry: | :warning:               | :negative_squared_cross_mark: |

------------|--------------------|------------|-------------------------|-------------------------------| no progress | done | impeded | lack mandate/funds/time | volunteer needed |

  1. Do a quick check to make sure the classes mentioned in the proposal are non public
  2. ✅ Implement the Raster Symbolizer support
  3. ❎ Porting of the RS stuff on GeoTools 2.4.x
  4. Porting of the RS stuff on GeoTools trunk
  5. Copy the examples on this page over ot the user guide
⚠️ **GitHub.com Fallback** ⚠️