The LuciadFusion Platform allows you to customize the legend encoding for the WMS and WMTS services.

By default, the LuciadFusion Platform responds with an icon graphic if it receives a GetLegendGraphic request from a WMS or WMTS service. The icon depends on the type of model linked to the server layer: elevation, raster or vector. For more information about these default implementations, see TLcdDefaultWMSGetLegendGraphicEncoder for WMS and TLcdDefaultWMTSGetLegendGraphicEncoder for WMTS.

If you want to offer something other than an icon as a legend, you can customize the response of the LuciadFusion Platform by implementing your own legend encoder.

This only affects WMS and WMTS services.

Customizing the legend encoding

You can customize the encoding by plugging in your own legend encoder.

To plug in a custom legend decoder, you register it with the service loader using the @LcdService annotation. You don’t have to specify a priority in the LcdService. By default, your custom legend encoder already has a higher priority than the pre-configured legend encoder. For more information about the services mechanism, see Working with the services mechanism.

The sample code includes three examples of custom legend encoders for WMS and WMTS.

Using OGC Symbology Encoding to encode a legend for a raster style with a color map

The first example illustrates the legend encoding of an SLD feature type style with a TLcdSLDRasterSymbolizer. Figure 1, “Example raster style legend generated by the Custom WMTS legend encoder” shows you a possible result.

GetLegendGraphic raster
Figure 1. Example raster style legend generated by the Custom WMTS legend encoder
Program: Custom WMS raster legend encoder
package samples.wms.server;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Optional;

import javax.imageio.ImageIO;

import com.luciad.util.service.LcdService;
import com.luciad.model.ILcdModel;
import com.luciad.ogc.sld.model.TLcdSLDColorMap;
import com.luciad.wms.server.ILcdWMSGetLegendGraphicRequestEncoder;
import com.luciad.wms.server.TLcdWMSGetLegendGraphicRequestContext;
import com.luciad.wms.server.TLcdWMSRequestContext;

/**
 * This WMS legend encoder creates a legend based upon the SLD color map if one is part of the SLD Feature Type Style.
 */
@LcdService(service = ILcdWMSGetLegendGraphicRequestEncoder.class)
public class SLDRasterWMSGetLegendGraphicEncoder implements ILcdWMSGetLegendGraphicRequestEncoder {

  private final SLDColorMapLegendProvider fSLDColorMapLegendProvider = new SLDColorMapLegendProvider();

  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMSGetLegendGraphicRequestContext aWMSGetLegendGraphicRequestContext,
                        TLcdWMSRequestContext aWMSRequestContext, OutputStream aOutputStream) throws IOException {
    if (aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles() == null ||
        aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles().length != 1) {
      return false;
    }
    Optional<TLcdSLDColorMap> colorMap =
        fSLDColorMapLegendProvider.getColorMap(aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0]);
    if (!colorMap.isPresent()) {
      return false;
    }
    ILcdModel model = aWMSGetLegendGraphicRequestContext.getModelSupplier().get().length > 0 ?
                      aWMSGetLegendGraphicRequestContext.getModelSupplier().get()[0] : null;
    ImageIO.write(fSLDColorMapLegendProvider.getLegend(colorMap.get(), model), "PNG", aOutputStream);
    return true;
  }
}
Program: Custom WMTS raster legend encoder
package samples.wmts.server;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Optional;

import javax.imageio.ImageIO;

import samples.wms.server.SLDColorMapLegendProvider;
import com.luciad.util.service.LcdService;
import com.luciad.model.ILcdModel;
import com.luciad.ogc.sld.model.TLcdSLDColorMap;
import com.luciad.wmts.server.ILcdWMTSGetLegendGraphicRequestEncoder;
import com.luciad.wmts.server.TLcdWMTSGetLegendGraphicRequestContext;

/**
 * This WMTS legend encoder creates a legend based upon the SLD color map if one is part of the SLD Feature Type Style.
 */
@LcdService(service = ILcdWMTSGetLegendGraphicRequestEncoder.class)
public class SLDRasterWMTSGetLegendGraphicEncoder implements ILcdWMTSGetLegendGraphicRequestEncoder {

  private final SLDColorMapLegendProvider fSLDColorMapLegendProvider = new SLDColorMapLegendProvider();

  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMTSGetLegendGraphicRequestContext aWMTSGetLegendGraphicRequestContext,
                        OutputStream aOutputStream) throws IOException {
    if (aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles() == null ||
        aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles().length != 1) {
      return false;
    }
    Optional<TLcdSLDColorMap> colorMap =
        fSLDColorMapLegendProvider.getColorMap(aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0]);
    if (!colorMap.isPresent()) {
      return false;
    }
    ILcdModel model = aWMTSGetLegendGraphicRequestContext.getModelSupplier().get();
    ImageIO.write(fSLDColorMapLegendProvider.getLegend(colorMap.get(), model), "PNG", aOutputStream);
    return true;
  }
}

For a closer look at this legend code, see the implementations samples.wms.server.SLDRasterWMSGetLegendGraphicEncoder for WMS and samples.wmts.server.SLDRasterWMTSGetLegendGraphicEncoder for WMTS.

Using OGC Symbology Encoding to encode a legend for a vector style

The second example illustrates the legend encoding of a vector SLD feature type style, containing one or more TLcdSLDPointSymbolizer, TLcdSLDLineSymbolizer and/or TLcdSLDPolygonSymbolizer elements. Figure 2, “Example vector style legend generated by the Custom WMTS legend encoder” shows you a possible result.

GetLegendGraphic vector
Figure 2. Example vector style legend generated by the Custom WMTS legend encoder
Program: Custom WMS vector legend encoder
package samples.wms.server;

import java.io.IOException;
import java.io.OutputStream;

import javax.imageio.ImageIO;

import com.luciad.util.service.LcdService;
import com.luciad.wms.server.ILcdWMSGetLegendGraphicRequestEncoder;
import com.luciad.wms.server.TLcdWMSGetLegendGraphicRequestContext;
import com.luciad.wms.server.TLcdWMSRequestContext;

/**
 * This WMS legend encoder creates a legend based upon the SLD vector symbolizers (point, line, polygon) if
 * they are part of the SLD feature type style.
 */
@LcdService(service = ILcdWMSGetLegendGraphicRequestEncoder.class)
public class SLDVectorWMSGetLegendGraphicEncoder implements ILcdWMSGetLegendGraphicRequestEncoder {

  private final SLDVectorLegendProvider fSLDVectorLegendProvider = new SLDVectorLegendProvider();

  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMSGetLegendGraphicRequestContext aWMSGetLegendGraphicRequestContext,
                        TLcdWMSRequestContext aWMSRequestContext, OutputStream aOutputStream) throws IOException {
    if (aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles() == null ||
        aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles().length != 1) {
      return false;
    }
    if (!fSLDVectorLegendProvider.hasVectorSymbolizer(aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0])) {
      return false;
    }
    ImageIO.write(fSLDVectorLegendProvider.getLegend(aWMSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0]), "PNG", aOutputStream);
    return true;
  }
}
Program: Custom WMTS vector legend encoder
package samples.wmts.server;

import java.io.IOException;
import java.io.OutputStream;

import javax.imageio.ImageIO;

import samples.wms.server.SLDVectorLegendProvider;
import com.luciad.util.service.LcdService;
import com.luciad.wmts.server.ILcdWMTSGetLegendGraphicRequestEncoder;
import com.luciad.wmts.server.TLcdWMTSGetLegendGraphicRequestContext;

/**
 * This WMTS legend encoder creates a legend based upon the SLD vector symbolizers (point, line, polygon) if
 * they are part of the SLD feature type style.
 */
@LcdService(service = ILcdWMTSGetLegendGraphicRequestEncoder.class)
public class SLDVectorWMTSGetLegendGraphicEncoder implements ILcdWMTSGetLegendGraphicRequestEncoder {

  private final SLDVectorLegendProvider fSLDVectorLegendProvider = new SLDVectorLegendProvider();

  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMTSGetLegendGraphicRequestContext aWMTSGetLegendGraphicRequestContext, OutputStream aOutputStream) throws IOException {
    if (aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles() == null ||
        aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles().length != 1) {
      return false;
    }
    if (!fSLDVectorLegendProvider.hasVectorSymbolizer(aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0])) {
      return false;
    }
    ImageIO.write(fSLDVectorLegendProvider.getLegend(aWMTSGetLegendGraphicRequestContext.getSLDFeatureTypeStyles()[0]), "PNG", aOutputStream);
    return true;
  }
}

For a closer look at this legend code, see the implementations samples.wms.server.SLDVectorWMSGetLegendGraphicEncoder for WMS and samples.wmts.server.SLDVectorWMTSGetLegendGraphicEncoder for WMTS.

Using a static image file to encode a legend

This encoder looks for a static legend image that is placed next to the original model source. With this encoder, you can author an appropriate legend image yourself from scratch, or re-use existing legend images.

Program: WMS legend encoder that picks up an image file
package samples.wms.server;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelTreeNode;
import com.luciad.util.service.LcdService;
import com.luciad.wms.server.ILcdWMSGetLegendGraphicRequestEncoder;
import com.luciad.wms.server.TLcdWMSGetLegendGraphicRequestContext;
import com.luciad.wms.server.TLcdWMSRequestContext;

/**
 * This legend encoder picks up a {@code .legend.png} image that's in the same directory as the original data source.
 * For example, if the original data source is {@code C:\Data\SHP\world.shp}, the encoder looks for
 * {@code C:\Data\SHP\world.legend.png}.
 */
@LcdService(service = ILcdWMSGetLegendGraphicRequestEncoder.class)
public class FileBasedWMSGetLegendGraphicRequestEncoder implements ILcdWMSGetLegendGraphicRequestEncoder {
  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMSGetLegendGraphicRequestContext aWMSGetLegendGraphicRequestContext, TLcdWMSRequestContext aWMSRequestContext, OutputStream aOutputStream) throws IOException {
    ILcdModel[] models = aWMSGetLegendGraphicRequestContext.getModelSupplier().get();
    for (ILcdModel model : models) {
      if (encode(aOutputStream, model)) {
        return true;
      }
    }
    return false;
  }

  public static boolean encode(OutputStream aOutputStream, ILcdModel aModel) throws IOException {
    String dataSource = aModel.getModelDescriptor().getSourceName();
    String legendSource = getFileNameNoExtension(dataSource) + ".legend.png";
    if (Files.exists(Paths.get(legendSource))) {
      Files.copy(Paths.get(legendSource), aOutputStream);
      return true;
    } else if (aModel instanceof ILcdModelTreeNode) {
      for (ILcdModel child : ((ILcdModelTreeNode) aModel).getModels()) {
        if (encode(aOutputStream, child)) {
          return true;
        }
      }
    }
    return false;
  }

  private static String getFileNameNoExtension(String aFileName) {
    String fileName = null;
    int pointIndex = aFileName.lastIndexOf('.');
    if (pointIndex > 0 && pointIndex < aFileName.length() - 1) {
      fileName = aFileName.substring(0, pointIndex);
    }
    return fileName;
  }
}
Program: WMTS legend encoder that picks up an image file
package samples.wmts.server;

import java.io.IOException;
import java.io.OutputStream;

import com.luciad.model.ILcdModel;
import com.luciad.util.service.LcdService;
import com.luciad.wmts.server.ILcdWMTSGetLegendGraphicRequestEncoder;
import com.luciad.wmts.server.TLcdWMTSGetLegendGraphicRequestContext;

import samples.wms.server.FileBasedWMSGetLegendGraphicRequestEncoder;

/**
 * This legend encoder picks up a {@code .legend.png} image that's in the same directory as the original data source.
 * For example, if the original data source would be {@code C:\Data\SHP\world.shp}, the encoder would look for
 * {@code C:\Data\SHP\world.legend.png}.
 */
@LcdService(service = ILcdWMTSGetLegendGraphicRequestEncoder.class)
public class FileBasedWMTSGetLegendGraphicRequestEncoder implements ILcdWMTSGetLegendGraphicRequestEncoder {

  @Override
  public String getContentType() {
    return "image/png";
  }

  @Override
  public boolean encode(TLcdWMTSGetLegendGraphicRequestContext aWMTSGetLegendGraphicRequestContext, OutputStream aOutputStream) throws IOException {
    ILcdModel model = aWMTSGetLegendGraphicRequestContext.getModelSupplier().get();
    return FileBasedWMSGetLegendGraphicRequestEncoder.encode(aOutputStream, model);
  }
}

See the implementations samples.wms.server.FileBasedWMSGetLegendGraphicRequestEncoder for WMS and samples.wmts.server.FileBasedWMTSGetLegendGraphicRequestEncoder for WMTS.