In this article, it’s assumed that you already know how to use the LuciadCPillar API to retrieve WMS capabilities, and use that capability information to create a IRasterModelIRasterModelIRasterModel. If you want to learn more about this first, see the WMS data article.

To retrieve and display data from a WMS server with ECDIS data, you need to consider the presence of S-52 display settings. To make sure that these styling settings are passed along and rendered, you must use a Styled Layer Descriptor (SLD) to embed the settings in your request.

S-52 styling of ECDIS data

Maritime ECDIS data is typically rendered according to the International Hydrographic Organisation (IHO) S-52 specification. It provides a detailed list of icons, line styles and other styling information to use for the visualization of buoys, lightmarks, and other maritime features.

The S-52 specification defines a set of parameters that you can use to optimize the displaying of maritime data for a specific environment. These parameters include color scheme settings, ship depth settings, several object filters, and other customizations.

In a client-server setup where the data is rendered on a remote server, such as the WMS, you must pass these parameters from the client to the server. The server can then use the parameters to render its images according to the client’s preferences.

The WMS protocol doesn’t support the modeling of S-52 parameters out-of-the-box, but LuciadCPillar offers API for the exchange of S-52 settings between WMS clients and servers.

Detect which layers support S-52/SLD styling

A WMS may serve a mixed set of layers: some layers contain ECDIS data while other layers contain different types of data. To allow WMS clients to discover which layers support S-52/SLD styling, LuciadFusion WMS servers include the keyword S52-SLD in the layer description of SLD-capable layers. You can check a layer description in the WMS Capabilities returned by the server.

Program: Check if a layer is SLD-capable
bool isS52SLDLayer(const std::shared_ptr<WmsLayer>& layer) {
  auto layerKeyword = layer->getKeywords();
  return std::find(layerKeyword.cbegin(), layerKeyword.cend(), "S52-SLD") != layerKeyword.cend();
}
public bool isS52SLDLayer(WmsLayer layer)
{
    return layer.Keywords.Contains("S52-SLD");
}
public boolean isS52SLDLayer(WmsLayer layer) {
  return layer.getKeywords().contains("S52-SLD");
}

Embedding S-52 display settings in a WMS GetMap request using SLD

The WMS protocol allows you to embed an SLD Feature Type Style in a GetMap request. You can use the SLD as a mechanism to pass custom styling parameters to a WMS server, and tell the WMS server how to render its images. You can inject the SLD in a GetMap request in 2 ways:

  • Serialize the SLD XML document and embed it as a URL parameter in the request

  • Specify the URL where the XML document with the SLD description is accessible.

This article shows you both options using the LuciadCPillar API. We request a layer named 'noaa-s57' that accepts S-52 visualization style. We want to pass along an SLD style that changes the color scheme from the default color scheme 'day' to 'dusk'.

Serialize the SLD styling document

You can inject a custom style in the GetMap request through the WmsDataSource::Builder::styledLayerDescriptorWmsDataSource::Builder::styledLayerDescriptorWmsDataSource::Builder::styledLayerDescriptor method. In this approach, you pass along the SLD XML definition serialized as string. In the following snippet, we use a S-52 SLD template that sets the colorScheme to dusk to change how the data is displayed. LuciadCPillar uses the SLD XML representation as-is, and injects it in every GetMap request by the model.

Program: Styled Layer Descriptor styling using a body
  // Styled Layer Descriptor
  // Note the s52:colorScheme node where we set the 'dusk' scheme to be used.
  const auto* styledLayerDescriptor = R"eos(
<sld:StyledLayerDescriptor  xmlns:sld="http://www.opengis.net/sld" xmlns:se="http://www.opengis.net/se"
xmlns:s52="http://www.luciad.com/ecdis/s52/1.0" xmlns:s52sld="http://www.luciad.com/ecdis/s52-sld/1.0" version="1.1.0">
  <sld:NamedLayer>
    <se:Name>noaa-s57</se:Name>
    <sld:UserStyle>
      <se:Name>default</se:Name>
      <sld:IsDefault>true</sld:IsDefault>
      <se:FeatureTypeStyle>
        <se:Rule>
          <se:MinScaleDenominator>0.0</se:MinScaleDenominator>
          <se:MaxScaleDenominator>INF</se:MaxScaleDenominator>
          <s52sld:S52Symbolizer>
            <s52:displaySettings s52:beam="10.0" s52:airDraft="10.0">
              <s52:colorScheme>dusk</s52:colorScheme>
              <s52:pointSymbolType>simplified</s52:pointSymbolType>
              <s52:areaBoundarySymbolType>plain</s52:areaBoundarySymbolType>
              <s52:displayFullLengthLightSectorLines>false</s52:displayFullLengthLightSectorLines>
              <s52:displayCategory>standard</s52:displayCategory>
              <s52:displaySoundings>false</s52:displaySoundings>
              <s52:displayLandAreas>true</s52:displayLandAreas>
              <s52:displayMetadata>false</s52:displayMetadata>
              <s52:displayText>false</s52:displayText>
              <s52:useAbbreviations>false</s52:useAbbreviations>
              <s52:useNationalLanguage>false</s52:useNationalLanguage>
              <s52:safetyDepth>7.0</s52:safetyDepth>
              <s52:shallowContour>2.0</s52:shallowContour>
              <s52:safetyContour>30.0</s52:safetyContour>
              <s52:deepContour>30.0</s52:deepContour>
              <s52:useTwoShades>true</s52:useTwoShades>
              <s52:displayShallowPattern>false</s52:displayShallowPattern>
              <s52:displayIsolatedDangersInShallowWater>false</s52:displayIsolatedDangersInShallowWater>
              <s52:displayChartBoundaries>true</s52:displayChartBoundaries>
              <s52:displayOverscaleIndication>true</s52:displayOverscaleIndication>
              <s52:displayUnderscaleIndication>true</s52:displayUnderscaleIndication>
            </s52:displaySettings>
          </s52sld:S52Symbolizer>
        </se:Rule>
      </se:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>
)eos";

  WmsDataSource::Builder wmsDataSource = WmsDataSource::newBuilder().url(url).addLayer("noaa-s57").styledLayerDescriptor(styledLayerDescriptor);

  auto expWmsModel = WmsModelDecoder::decode(wmsDataSource.build(), capabilities);
  if (expWmsModel.has_value()) {
    // create a layer and add it to the map's layer list.
  } else {
    ErrorInfo errorInfo = expWmsModel.error();
    std::cerr << "could not be decoded: " << errorInfo.getMessage();
  }
            // Styled Layer Descriptor
            // Note the s52:colorScheme node where we set the 'dusk' scheme to be used.
            string styledLayerDescriptor = @"
<sld:StyledLayerDescriptor xmlns:sld=""http://www.opengis.net/sld"" xmlns:se=""http://www.opengis.net/se""
xmlns:s52=""http://www.luciad.com/ecdis/s52/1.0"" xmlns:s52sld =""http://www.luciad.com/ecdis/s52-sld/1.0"" version=""1.1.0"" >
  <sld:NamedLayer>
    <se:Name>noaa-s57</se:Name>
    <sld:UserStyle>
      <se:Name>default</se:Name>
      <sld:IsDefault>true</sld:IsDefault>
      <se:FeatureTypeStyle>
        <se:Rule>
          <se:MinScaleDenominator>0.0</se:MinScaleDenominator>
          <se:MaxScaleDenominator>INF</se:MaxScaleDenominator>
          <s52sld:S52Symbolizer>
            <s52:displaySettings s52:beam=""10.0"" s52:airDraft=""10.0"">
              <s52:colorScheme>dusk</s52:colorScheme>
              <s52:pointSymbolType>simplified</s52:pointSymbolType>
              <s52:areaBoundarySymbolType>plain</s52:areaBoundarySymbolType>
              <s52:displayFullLengthLightSectorLines>false</s52:displayFullLengthLightSectorLines>
              <s52:displayCategory>standard</s52:displayCategory>
              <s52:displaySoundings>false</s52:displaySoundings>
              <s52:displayLandAreas>true</s52:displayLandAreas>
              <s52:displayMetadata>false</s52:displayMetadata>
              <s52:displayText>false</s52:displayText>
              <s52:useAbbreviations>false</s52:useAbbreviations>
              <s52:useNationalLanguage>false</s52:useNationalLanguage>
              <s52:safetyDepth>7.0</s52:safetyDepth>
              <s52:shallowContour>2.0</s52:shallowContour>
              <s52:safetyContour>30.0</s52:safetyContour>
              <s52:deepContour>30.0</s52:deepContour>
              <s52:useTwoShades>true</s52:useTwoShades>
              <s52:displayShallowPattern>false</s52:displayShallowPattern>
              <s52:displayIsolatedDangersInShallowWater>false</s52:displayIsolatedDangersInShallowWater>
              <s52:displayChartBoundaries>true</s52:displayChartBoundaries>
              <s52:displayOverscaleIndication>true</s52:displayOverscaleIndication>
              <s52:displayUnderscaleIndication>true</s52:displayUnderscaleIndication>
            </s52:displaySettings>
          </s52sld:S52Symbolizer>
        </se:Rule>
      </se:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>";

            var wmsDataSource = WmsDataSource.NewBuilder().Url(url)
                .AddLayer("noaa-s57")
                .StyledLayerDescriptor(styledLayerDescriptor);

            try
            {
                var model = WmsModelDecoder.Decode(wmsDataSource.Build(), capabilities);
                if (model != null)
                {
                    // create a layer and add it to the map's layer list.
                }
            }
            catch (Exception exception)
            {
                Console.Error.WriteLine("Failed to open WMS data source for '" + url + "': " + exception.Message);
            }
// Styled Layer Descriptor
// Note the s52:colorScheme node where we set the 'dusk' scheme to be used.
String styledLayerDescriptor =
    "<sld:StyledLayerDescriptor xmlns:sld=\"http://www.opengis.net/sld\" xmlns:se=\"http://www.opengis.net/se\" " +
    "xmlns:s52=\"http://www.luciad.com/ecdis/s52/1.0\" xmlns:s52sld =\"http://www.luciad.com/ecdis/s52-sld/1.0\" version=\"1.1.0\">\n" +
    "<sld:NamedLayer>\n" +
    "<se:Name>noaa-s57</se:Name>\n" +
    "<sld:UserStyle>\n" +
    "<se:Name>default</se:Name>\n" +
    "<sld:IsDefault>true</sld:IsDefault>\n" +
    "<se:FeatureTypeStyle>\n" +
    "<se:Rule>\n" +
    "<se:MinScaleDenominator>0.0</se:MinScaleDenominator>\n" +
    "<se:MaxScaleDenominator>INF</se:MaxScaleDenominator>\n" +
    "<s52sld:S52Symbolizer>\n" +
    "<s52:displaySettings s52:beam=\"10.0\" s52:airDraft=\"10.0\">\n" +
    "<s52:colorScheme>dusk</s52:colorScheme>\n" +
    "<s52:pointSymbolType>simplified</s52:pointSymbolType>\n" +
    "<s52:areaBoundarySymbolType>plain</s52:areaBoundarySymbolType>\n" +
    "<s52:displayFullLengthLightSectorLines>false</s52:displayFullLengthLightSectorLines>\n" +
    "<s52:displayCategory>standard</s52:displayCategory>\n" +
    "<s52:displaySoundings>false</s52:displaySoundings>\n" +
    "<s52:displayLandAreas>true</s52:displayLandAreas>\n" +
    "<s52:displayMetadata>false</s52:displayMetadata>\n" +
    "<s52:displayText>false</s52:displayText>\n" +
    "<s52:useAbbreviations>false</s52:useAbbreviations>\n" +
    "<s52:useNationalLanguage>false</s52:useNationalLanguage>\n" +
    "<s52:safetyDepth>7.0</s52:safetyDepth>\n" +
    "<s52:shallowContour>2.0</s52:shallowContour>\n" +
    "<s52:safetyContour>30.0</s52:safetyContour>\n" +
    "<s52:deepContour>30.0</s52:deepContour>\n" +
    "<s52:useTwoShades>true</s52:useTwoShades>\n" +
    "<s52:displayShallowPattern>false</s52:displayShallowPattern>\n" +
    "<s52:displayIsolatedDangersInShallowWater>false</s52:displayIsolatedDangersInShallowWater>\n" +
    "<s52:displayChartBoundaries>true</s52:displayChartBoundaries>\n" +
    "<s52:displayOverscaleIndication>true</s52:displayOverscaleIndication>\n" +
    "<s52:displayUnderscaleIndication>true</s52:displayUnderscaleIndication>\n" +
    "</s52:displaySettings>\n" +
    "</s52sld:S52Symbolizer>\n" +
    "</se:Rule>\n" +
    "</se:FeatureTypeStyle>\n" +
    "</sld:UserStyle>\n" +
    "</sld:NamedLayer>\n" +
    "</sld:StyledLayerDescriptor>";

WmsDataSource wmsDataSource = WmsDataSource.newBuilder().url(url)
                                           .addLayer("noaa-s57")
                                           .styledLayerDescriptor(styledLayerDescriptor)
                                           .build();
try {
  IRasterModel model = WmsModelDecoder.decode(wmsDataSource, capabilities);
  if (model != null) {
    // create a layer and add it to the map's layer list.
  }
} catch (Exception exception) {
  Log.e("LUCIAD", "Failed to open WMS data source for '" + url + "': " + exception.getMessage());
}

Use a URL pointing to an SLD document

If you have a reachable end point with an SLD file, you can directly configure it in the WmsDataSourceWmsDataSourceWmsDataSource. As a result, each GetMap request refers to the SLD file at the end point for the styling of the requested layer.

Program: SLD styling using a URL
std::string url = "http://sampleservices.luciad.com/wms";
WmsDataSource::Builder
    wmsDataSource = WmsDataSource::newBuilder().url(url).addLayer("noaa-s57").styledLayerDescriptorUrl("http://a.path.to.your.xml.file/SLD.xml");

auto expWmsModel = WmsModelDecoder::decode(wmsDataSource.build(), capabilities);
if (expWmsModel.has_value()) {
  // create a layer and add it to the map's layer list.
} else {
  ErrorInfo errorInfo = expWmsModel.error();
  std::cerr << "could not be decoded: " << errorInfo.getMessage();
}
string url = "http://sampleservices.luciad.com/wms";
// sld_dusk.xml contains the layer we want to request and the associated style.
var wmsDataSource = WmsDataSource.NewBuilder().Url(url)
    .AddLayer("noaa-s57")
    .StyledLayerDescriptorUrl("http://a.path.to.your.xml.file/SLD.xml");

try
{
    var model = WmsModelDecoder.Decode(wmsDataSource.Build(), capabilities);
    if (model != null)
    {
        // create a layer and add it to the map's layer list.
    }
}
catch (Exception exception)
{
    Console.Error.WriteLine("Failed to open WMS data source for '" + url + "': " + exception.Message);
}
String url = "http://sampleservices.luciad.com/wms";
// sld_dusk.xml contains the layer we want to request and the associated style.
var wmsDataSource = WmsDataSource.newBuilder()
                                 .url(url)
                                 .addLayer("noaa-s57")
                                 .styledLayerDescriptorUrl("http://a.path.to.your.xml.file/SLD.xml");

try {
  var model = WmsModelDecoder.decode(wmsDataSource.build(), capabilities);
  if (model != null) {
    // create a layer and add it to the map's layer list.
  }
} catch (Exception exception) {
  Log.e("LUCIAD", "Failed to open WMS data source for '" + url + "': " + exception.getMessage());
}

Mixing ECDIS data with non-ECDIS data

LuciadCPillar allows you to query multiple layers at the same time. Some of those layers may be ECDIS data layers that support S-52 styling, while others contain data that’s styled differently.

To query multiple layers, each with its specific styling, use a single WmsDataSource::Builder and call the addLayeraddLayeraddLayer method to inject the non-S-52 layers with their style. You can then use styledLayerDescriptorstyledLayerDescriptorstyledLayerDescriptor or styledLayerDescriptorUrlstyledLayerDescriptorUrlstyledLayerDescriptorUrl to inject the ECDIS data layers with S-52 styling.

There is one limitation, though: if you have an S-52 layer in your list of added layers and you specify a style name for it, you must also specify that style name in the SLD definition. Otherwise, the layer gets the default style instead of the SLD style.

These snippets illustrate how you can mix ECDIS data with non-ECDIS data:

Program: Mix ECDIS data with non-ECDIS data
  // Styled Layer Descriptor
  // Note the se:Name used and the sld:IsDefault flag
  const auto* styledLayerDescriptor = R"eos(
<sld:StyledLayerDescriptor  xmlns:sld="http://www.opengis.net/sld" xmlns:se="http://www.opengis.net/se"
xmlns:s52="http://www.luciad.com/ecdis/s52/1.0" xmlns:s52sld="http://www.luciad.com/ecdis/s52-sld/1.0" version="1.1.0">
  <sld:NamedLayer>
    <se:Name>noaa-s57</se:Name>
    <sld:UserStyle>
      <se:Name>customStyle</se:Name>
       <sld:IsDefault>true</sld:IsDefault>
       <se:FeatureTypeStyle>
         ...
       </se:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>
)eos";

  WmsDataSource::Builder wmsDataSource = WmsDataSource::newBuilder()
                                             .url(url)
                                             .addLayer("noaa-s57", "customStyle") // OK as the XML is defining the "customStyle" style
                                             .addLayer("noaa-s57")                // OK as well, the style in the XML has the 'IsDefault' flag
                                             .addLayer("noaa-s57", "default")     // KO, the "default" style is used , the XML content is discarded
                                             .addLayer("rivers")                  // non SLD layer
                                             .styledLayerDescriptor(styledLayerDescriptor);
            // Styled Layer Descriptor
            // Note the se:Name used and the sld:IsDefault flag.
            string styledLayerDescriptor = @"
<sld:StyledLayerDescriptor xmlns:sld=""http://www.opengis.net/sld"" xmlns:se=""http://www.opengis.net/se""
xmlns:s52=""http://www.luciad.com/ecdis/s52/1.0"" xmlns:s52sld =""http://www.luciad.com/ecdis/s52-sld/1.0"" version=""1.1.0"" >
  <sld:NamedLayer>
    <se:Name>noaa-s57</se:Name>
    <sld:UserStyle>
      <se:Name>customStyle</se:Name>
      <sld:IsDefault>true</sld:IsDefault>
      <se:FeatureTypeStyle>
        ...
      </se:FeatureTypeStyle>
    </sld:UserStyle>
  </sld:NamedLayer>
</sld:StyledLayerDescriptor>";

            var wmsDataSource = WmsDataSource.NewBuilder().Url(url)
                .AddLayer("noaa-s57", "customStyle") // OK as the XML is defining the "customStyle" style
                .AddLayer("noaa-s57") // OK as well, the style in the XML has the 'IsDefault' flag
                .AddLayer("noaa-s57", "default") // OK, the "default" style is used , the XML content is discarded
                .AddLayer("rivers") // non SLD layer
                .StyledLayerDescriptor(styledLayerDescriptor);
// Styled Layer Descriptor
// Note the se:Name used and the sld:IsDefault flag.
String styledLayerDescriptor =
    "<sld:StyledLayerDescriptor xmlns:sld=\"http://www.opengis.net/sld\" xmlns:se=\"http://www.opengis.net/se\"" +
    "xmlns:s52=\"http://www.luciad.com/ecdis/s52/1.0\" xmlns:s52sld =\"http://www.luciad.com/ecdis/s52-sld/1.0\" version=\"1.1.0\" >\n" +
    "<sld:NamedLayer>\n" +
    "<se:Name>noaa-s57</se:Name>\n" +
    "<sld:UserStyle>\n" +
    "<se:Name>customStyle</se:Name>\n" +
    "<sld:IsDefault>true</sld:IsDefault>\n" +
    "<se:FeatureTypeStyle>\n" +
    //...
    "</se:FeatureTypeStyle>\n" +
    "</sld:UserStyle>\n" +
    "</sld:NamedLayer>\n" +
    "</sld:StyledLayerDescriptor>";

WmsDataSource wmsDataSource = WmsDataSource.newBuilder()
                                           .url(url)
                                           .addLayer("noaa-s57", "customStyle") // OK as the XML is defining the "customStyle" style
                                           .addLayer("noaa-s57") // OK as well, the style in the XML has the 'IsDefault' flag
                                           .addLayer("noaa-s57", "default") // OK, the "default" style is used , the XML content is discarded
                                           .addLayer("rivers") // non SLD layer
                                           .styledLayerDescriptor(styledLayerDescriptor)
                                           .build();

Limitations

The support for the OGC WMS protocol has some limitations. For details, see the WmsModelDecoder::decodeWmsModelDecoder::decodeWmsModelDecoder::decode method documentation.