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 IRasterModel
IRasterModel
IRasterModel
. 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.
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::styledLayerDescriptor
WmsDataSource::Builder::styledLayerDescriptor
WmsDataSource::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.
// 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 WmsDataSource
WmsDataSource
WmsDataSource
. As a result, each GetMap
request refers to the SLD file at the end point for the styling of the requested layer.
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 addLayer
addLayer
addLayer
method to inject the non-S-52 layers with their style. You can then use styledLayerDescriptor
styledLayerDescriptor
styledLayerDescriptor
or styledLayerDescriptorUrl
styledLayerDescriptorUrl
styledLayerDescriptorUrl
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:
// 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::decode
WmsModelDecoder::decode
WmsModelDecoder::decode
method documentation.