Using LuciadFusion, you can set up high-performance, OGC-certified services for your data. Next to the file formats and databases that are supported out-of-the-box, you can also plug in your own data formats.

In this article, we focus on plugging in another WMS, WMTS, WCS, or WFS service as the source of the data, so that we can serve that data from a LuciadFusion OGC service. This can be useful to:

  • Boost the performance of slow WMS services, because LuciadFusion WMS services automatically use a built-in tile caching mechanism that speeds up WMS data serving.

  • Re-project WMS and WMTS maps to geographic references that the original service doesn’t support. A LuciadFusion WMS service comes with on-the-fly re-projection support to any reference system, such as EPSG, AUTO, AUTO2, or WKT.

  • Customize the styling of WMS and WMTS maps, to add transparency, for example.

  • Render WFS data with an SLD style of your choice and serve the resulting maps using a LuciadFusion WMS or WMTS service.

  • Access WCS coverage data value using WM(T)S GetFeatureInfo requests

LuciadFusion already includes API to load data from a WMS, WMTS, WCS and WFS. You can implement a decoder that loads the data from the original service.

How to do it?

Adding support to connect to a WMS service

Create a file entry point with the URL and layer of the WMS service

LuciadFusion can use files as entry points for a data source. This means that we need a custom file format in which we can specify the URL and the layer of the WMS service. For example, we can define a Java Properties file with those two properties:

  • The URL of the WMS service

  • The name of the WMS layer

To load the Black Marble layer served by https://sampleservices.luciad.com/wms, for example, we specify these properties in the entry point file:

Program: Example of a file-based entry point for a WMS layer
url=http://sampleservices.luciad.com/wms
layer=92c09725-a9c5-46fb-bffd-d9e23b4abbf2

Create a model decoder for the WMS file entry point

We must now plug in a decoder that can create a model for the WMS data specified in the Properties file:

WMSModelDecoder.java

package samples.fusion.wms;

import java.io.IOException;
import java.util.Properties;

import com.luciad.io.TLcdInputStreamFactory;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.util.service.LcdService;
import com.luciad.wms.client.model.TLcdOGCWMSProxyModelDecoder;
import com.luciad.wms.client.model.TLcdWMSDataSource;

/**
 * Implementation of {@code ILcdModelDecoder} that decodes a WMS model from a Java Properties file.
 * The file needs to have the extension .wms and specify two properties:
 * <html:ol>
 *   <html:li>url: the URL of the WMS service,</html:li>
 *   <html:li>url: the name of the WMS layer to be loaded.</html:li>
 * </html:ol>
 */
@LcdService(service = ILcdModelDecoder.class, priority = LcdService.HIGH_PRIORITY)
public class WMSModelDecoder implements ILcdModelDecoder {

  private TLcdInputStreamFactory fInputStreamFactory = new TLcdInputStreamFactory();

  // We leverage the existing TLcdOGCWMSProxyModelDecoder to create a WMS model.
  private TLcdOGCWMSProxyModelDecoder fWMSModelDecoder = new TLcdOGCWMSProxyModelDecoder();

  @Override
  public String getDisplayName() {
    return "WMSProxyModelDecoder";
  }

  @Override
  public boolean canDecodeSource(String aSourceName) {
    return aSourceName.endsWith(".wms");
  }

  @Override
  public ILcdModel decode(String aSourceName) throws IOException {
    // Load Properties file.
    Properties properties = new Properties();
    properties.load(fInputStreamFactory.createInputStream(aSourceName));

    // Retrieve the required WMS connection parameters.
    String wmsUrl = properties.getProperty("url");
    String wmsLayer = properties.getProperty("layer");

    // Decode a model for the WMS URL & layer when correctly specified.
    if (wmsUrl != null && wmsLayer != null) {
      return fWMSModelDecoder.decodeSource(TLcdWMSDataSource.newBuilder().uri(wmsUrl).addLayer(wmsLayer).build());
    } else {
      throw new IOException("Could not find WMS URL and layer in Properties file " + aSourceName);
    }
  }

  @Override
  public TLcdModelMetadata decodeModelMetadata(String aSourceName) throws IOException {
    ILcdModel model = decode(aSourceName);
    return TLcdModelMetadata.newBuilder()
                            .fromModel(model)
                            .entryPoint(new TLcdModelMetadata.Source(aSourceName, null))
                            .addDataCategory(TLcdModelMetadata.DataCategory.RASTER)
                            .build();
  }
}

Create a layer factory for the created models

Next, we plug in a customized layer factory to create a GXY layer for a WMS model:

WMSLayerFactory.java

package samples.fusion.wms;

import com.luciad.model.ILcdModel;
import com.luciad.util.service.LcdService;
import com.luciad.view.gxy.ILcdGXYLayer;
import com.luciad.wms.client.gxy.TLcdWMSProxyGXYLayerFactory;
import com.luciad.wms.client.model.TLcdWMSProxyModelDescriptor;
import com.luciad.wms.server.ILcdWMSGXYLayerFactory;
import com.luciad.wms.server.TLcdWMSRequestContext;
import com.luciad.wms.server.model.ALcdWMSLayer;

/**
 * Implementation of {@code ILcdWMSGXYLayerFactory} that can generate a GXY layer for a model
 * representing data from a WMS service.
 */
@LcdService(service = ILcdWMSGXYLayerFactory.class, priority = LcdService.HIGH_PRIORITY)
public class WMSLayerFactory implements ILcdWMSGXYLayerFactory {

  // We leverage the existing TLcdWMSProxyGXYLayerFactory to create a GXY layer for a WMS model.
  // Through the @LcdService annotation, we make it available to LuciadFusion.
  private TLcdWMSProxyGXYLayerFactory fWMSLayerFactory = new TLcdWMSProxyGXYLayerFactory();

  public WMSLayerFactory() {
    fWMSLayerFactory.setTiled(false);
  }

  @Override
  public ILcdGXYLayer createGXYLayer(ILcdModel aModel, ALcdWMSLayer aWMSLayer, String aStyleID, TLcdWMSRequestContext aContext) {
    if (aModel.getModelDescriptor() instanceof TLcdWMSProxyModelDescriptor) {
      return fWMSLayerFactory.createGXYLayer(aModel);
    } else {
      return null;
    }
  }
}

Adding support to connect to a WMTS service

Create a file entry point with the URL and layer of the WMTS service

Similar to the use case of plugging in a WMS service, we can define a Java Properties file that contains this information:

  • The URL of the WMTS service

  • The name of the WMTS layer

To load the global imagery layer served by https://sampleservices.luciad.com/wmts, for example, we specify these properties in the entry point file:

Program: Example of a file-based entry point for a WMTS layer
url=https://sampleservices.luciad.com/wmts
layer=4ceea49c-3e7c-4e2d-973d-c608fb2fb07e

Create a model decoder for the WMTS file entry point

We must now plug in a decoder that can create a model for the WMTS data specified in the Properties file:

WMTSModelDecoder.java

package samples.fusion.wmts;

import java.io.IOException;
import java.util.Properties;

import com.luciad.io.TLcdInputStreamFactory;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.ogc.wmts.client.TLcdWMTSDataSource;
import com.luciad.ogc.wmts.client.TLcdWMTSModelDecoder;
import com.luciad.util.service.LcdService;

/**
 * Implementation of {@code ILcdModelDecoder} that decodes a WMTS model from a Java Properties file.
 * The file needs to have the extension .wmts and specify two properties:
 * <html:ol>
 *   <html:li>url: the URL of the WMTS service,</html:li>
 *   <html:li>url: the name of the WMTS layer to be loaded.</html:li>
 * </html:ol>
 */
@LcdService(service = ILcdModelDecoder.class, priority = LcdService.HIGH_PRIORITY)
public class WMTSModelDecoder implements ILcdModelDecoder {

  private TLcdInputStreamFactory fInputStreamFactory = new TLcdInputStreamFactory();

  // We leverage the existing TLcdWMTSModelDecoder to create a WMTS model.
  private TLcdWMTSModelDecoder fWMTSModelDecoder = new TLcdWMTSModelDecoder();

  @Override
  public String getDisplayName() {
    return "WMTSModelDecoder";
  }

  @Override
  public boolean canDecodeSource(String aSourceName) {
    return aSourceName.endsWith(".wmts");
  }

  @Override
  public ILcdModel decode(String aSourceName) throws IOException {
    // Load Properties file.
    Properties properties = new Properties();
    properties.load(fInputStreamFactory.createInputStream(aSourceName));

    // Retrieve the required WMS connection parameters.
    String wmtsUrl = properties.getProperty("url");
    String wmtsLayer = properties.getProperty("layer");

    // Decode a model for the WMS URL & layer when correctly specified.
    if (wmtsUrl != null && wmtsLayer != null) {
      return fWMTSModelDecoder.decodeSource(TLcdWMTSDataSource.newBuilder().uri(wmtsUrl).layer(wmtsLayer).build());
    } else {
      throw new IOException("Could not find WMTS URL and layer in Properties file " + aSourceName);
    }
  }

  @Override
  public TLcdModelMetadata decodeModelMetadata(String aSourceName) throws IOException {
    ILcdModel model = decode(aSourceName);
    return TLcdModelMetadata.newBuilder()
                            .fromModel(model)
                            .entryPoint(new TLcdModelMetadata.Source(aSourceName, null))
                            .addDataCategory(TLcdModelMetadata.DataCategory.RASTER)
                            .build();
  }
}

For WMTS data, we don’t need to plug in a custom layer factory: the data is modeled using the ALcdImage API, which is supported out-the-box by the default layer factories available in LuciadFusion.

Adding support to connect to a WCS service

Create a file entry point with the URL and coverage of the WCS service

Similar to the use case of plugging in a WMS or WMTS service, we can define a Java Properties file that contains this information:

  • The URL of the WCS service

  • The name of the WCS coverage

To load the global elevation layer served by https://sampleservices.luciad.com/wcs, for example, we specify these properties in the entry point file:

Program: Example of a file-based entry point for a WCS coverage
url=https://sampleservices.luciad.com/wcs
coverage=e8f28a35-0e8c-4210-b2e8-e5d4333824ec

Create a model decoder for the WCS file entry point

We must now plug in a decoder that can create a model for the WCS data specified in the Properties file:

WCSModelDecoder.java

package samples.fusion.wcs;

import java.io.IOException;
import java.util.Properties;

import com.luciad.io.TLcdInputStreamFactory;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.ogc.wcs.client.TLcdWCSCoverageModelDecoder;
import com.luciad.ogc.wcs.client.TLcdWCSDataSource;
import com.luciad.util.service.LcdService;

/**
 * Implementation of {@code ILcdModelDecoder} that decodes a WCS model from a Java Properties file.
 * The file needs to have the extension .wcs and specify two properties:
 * <html:ol>
 *   <html:li>url: the URL of the WCS service,</html:li>
 *   <html:li>url: the name of the WCS coverage to be loaded.</html:li>
 * </html:ol>
 */
@LcdService(service = ILcdModelDecoder.class, priority = LcdService.HIGH_PRIORITY)
public class WCSModelDecoder implements ILcdModelDecoder {

  private TLcdInputStreamFactory fInputStreamFactory = new TLcdInputStreamFactory();

  // We leverage the existing TLcdWCSCoverageModelDecoder to create a WCS model.
  private TLcdWCSCoverageModelDecoder fWCSModelDecoder = new TLcdWCSCoverageModelDecoder();

  @Override
  public String getDisplayName() {
    return "WCSModelDecoder";
  }

  @Override
  public boolean canDecodeSource(String aSourceName) {
    return aSourceName.endsWith(".wcs");
  }

  @Override
  public ILcdModel decode(String aSourceName) throws IOException {
    // Load Properties file.
    Properties properties = new Properties();
    properties.load(fInputStreamFactory.createInputStream(aSourceName));

    // Retrieve the required WCS connection parameters.
    String wcsUrl = properties.getProperty("url");
    String wcsCoverage = properties.getProperty("coverage");

    // Decode a model for the WCS URL & coverage when correctly specified.
    if (wcsUrl != null && wcsCoverage != null) {
      return fWCSModelDecoder.decodeSource(TLcdWCSDataSource.newBuilder().uri(wcsUrl).coverageName(wcsCoverage).build());
    } else {
      throw new IOException("Could not find WCS URL and coverage in Properties file " + aSourceName);
    }
  }

  @Override
  public TLcdModelMetadata decodeModelMetadata(String aSourceName) throws IOException {
    ILcdModel model = decode(aSourceName);
    return TLcdModelMetadata.newBuilder()
                            .fromModel(model)
                            .entryPoint(new TLcdModelMetadata.Source(aSourceName, null))
                            .addDataCategory(TLcdModelMetadata.DataCategory.RASTER)
                            .build();
  }
}

For WCS data, we don’t need to plug in a custom layer factory: the data is modeled using the ALcdImage API, which is supported out-the-box by the default layer factories available in LuciadFusion.

Adding support to connect to a WFS service

Create a file entry point with the URL and feature type of the WFS service

Similar to the use case of plugging in a WMS, WMTS or WCS service, we can define a Java Properties file that contains the following information:

  • The URL of the WFS service

  • The name of the WFS feature type

To load the US cities feature type served by https://sampleservices.luciad.com/wfs, for example, we specify these properties in the entry point file:

Program: Example of a file-based entry point for a WFS feature type
url=https://sampleservices.luciad.com/wfs
featureType=cities

Create a model decoder for the WFS file entry point

We must now plug in a decoder that can create a model for the WFS data specified in the Properties file:

WFSModelDecoder.java

package samples.fusion.wfs;

import java.io.IOException;
import java.util.Properties;

import com.luciad.io.TLcdInputStreamFactory;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdModelMetadata;
import com.luciad.ogc.wfs.client.TLcdWFSDataSource;
import com.luciad.ogc.wfs.client.TLcdWFSModelDecoderDecorator;
import com.luciad.util.service.LcdService;

/**
 * Implementation of {@code ILcdModelDecoder} that decodes a WFS model from a Java Properties file.
 * The file needs to have the extension .wfs and specify two properties:
 * <html:ol>
 *   <html:li>url: the URL of the WFS service,</html:li>
 *   <html:li>url: the name of the WFS feature type to be loaded.</html:li>
 * </html:ol>
 */
@LcdService(service = ILcdModelDecoder.class, priority = LcdService.HIGH_PRIORITY)
public class WFSModelDecoder implements ILcdModelDecoder {

  private TLcdInputStreamFactory fInputStreamFactory = new TLcdInputStreamFactory();

  // We leverage the existing TLcdWFSModelDecoderDecorator to create a WFS model.
  private TLcdWFSModelDecoderDecorator fWFSModelDecoder = new TLcdWFSModelDecoderDecorator();

  @Override
  public String getDisplayName() {
    return "WFSProxyModelDecoder";
  }

  @Override
  public boolean canDecodeSource(String aSourceName) {
    return aSourceName.endsWith(".wfs");
  }

  @Override
  public ILcdModel decode(String aSourceName) throws IOException {
    // Load Properties file.
    Properties properties = new Properties();
    properties.load(fInputStreamFactory.createInputStream(aSourceName));

    // Retrieve the required WFS connection parameters.
    String wfsUrl = properties.getProperty("url");
    String wfsFeatureType = properties.getProperty("featureType");

    // Decode a model for the WFS URL & feature type when correctly specified.
    if (wfsUrl != null && wfsFeatureType != null) {
      return fWFSModelDecoder.decodeSource(TLcdWFSDataSource.newBuilder().uri(wfsUrl).featureTypeName(wfsFeatureType).build());
    } else {
      throw new IOException("Could not find WFS URL and feature type in Properties file " + aSourceName);
    }
  }

  @Override
  public TLcdModelMetadata decodeModelMetadata(String aSourceName) throws IOException {
    ILcdModel model = decode(aSourceName);
    return TLcdModelMetadata.newBuilder()
                            .fromModel(model)
                            .entryPoint(new TLcdModelMetadata.Source(aSourceName, null))
                            .addDataCategory(TLcdModelMetadata.DataCategory.VECTOR)
                            .build();
  }
}

For WFS data, we don’t need to plug in a custom layer factory: the data is modeled using the ILcdShape API, which is supported out-the-box by the default layer factories available in LuciadFusion.

Using Studio to connect to OGC services

The annotations in both the decoder and the WMS layer factory make sure that LuciadFusion picks them up, and recognizes .wms, .wmts, .wcs and .wfs files. You can create products and services for WMS, WMTS, WCS and WFS data, like you can for data from any other source.

Users of LuciadFusion Studio can add WMS, WMTS, WCS and WFS data directly by providing the file path to a .wms, .wmts, .wcs or .wfs file when they click the Add data button.

Users of the Studio REST API can also specify a file path to a .wms, .wmts, .wcs or .wfs file to add the data directly.