Goal

This tutorial shows you how you add support for static, non-editable business data to a Lucy application using a Lightspeed view.

The custom data format used in this tutorial is the same waypoints format as introduced in the Support a custom vector format tutorial of LuciadLightspeed.

Adding support for a custom data format to Lucy requires a number of Lucy-specific steps that are not required in a non-Lucy application. In return however, you get a lot of functionality for free. For example, if you follow these steps, you can:

  • Drag-and-drop the data on the map

  • Use the File | Open menu item to load your data

  • Open the table view for your data

  • Customize the styling through the layer properties panel

Adding support to decode the data

The recommended way to add support for decoding data is to:

  • Create an ALcyFormat class: the ALcyFormat is a class that groups everything that Lucy needs to decode and handle models, and optionally to visualize them on a GXY view.

  • Create an ALcyAddOn that registers everything the ALcyFormat creates as Lucy services: for example the File | Open action will query the services for all available model decoders and layer factories. By registering our instances as services, we ensure that they are picked up automatically.

Creating the ALcyFormat

As our data format is file-based, we start from one of the available ALcyFormat extensions for the creation of our ALcyFormat:

package com.luciad.lucy.addons.tutorial.staticlspdata.model;

class WayPointModelFormat extends ALcyFileFormat {

  WayPointModelFormat(ILcyLucyEnv aLucyEnv, String aLongPrefix, String aShortPrefix, ALcyProperties aProperties) {
    super(aLucyEnv, aLongPrefix, aShortPrefix, aProperties);
  }
}

The ALcyFileFormat allows us to configure certain items in the configuration file instead of in code. We will create the configuration file later on in this tutorial.

Create the model decoder

For our model decoder, we re-use the model decoder created in the How to support a custom vector format tutorial. This model decoder is returned from the createModelDecoders method in our ALcyFormat:

@Override
protected ILcdModelDecoder[] createModelDecoders() {
  return new ILcdModelDecoder[]{new WayPointsModelDecoder()};
}

Creating the model content type provider

Lucy tries to determine the best initial position for a layer when adding new data to a map. This requires knowledge about the kind of data the layer and model represent.

One of the interfaces used in this process is the ILcyModelContentTypeProvider interface, which indicates what kind of data a model contains. See How to influence the initial layer position in the view for more information.

The ALcyFormat has a method to create this model content type provider:

@Override
protected ILcyModelContentTypeProvider createModelContentTypeProvider() {
  //All our models only contain point data, so we can return a fixed type
  //No need to check the contents of the model
  return aModel -> ILcyModelContentType.POINT;
}

As documented in the ILcyModelContentTypeProvider interface, the provider must return ILcyModelContentType.UNKNOWN for models it does not recognize.

Our implementation currently does not do that. We will take care of that later on in this tutorial.

Disable GXY layer creation support

In this tutorial, we are only interested in supporting our data on a Lightspeed view. The ALcyFormat class has abstract methods related to GXY layer creation, however.

We need to implement these methods, but can simply return null:

@Override
protected ILcyGXYLayerTypeProvider createGXYLayerTypeProvider() {
  return null;
}

@Override
protected ILcdGXYLayerFactory createGXYLayerFactory() {
  return null;
}

Adding an ALcyAddOn to plug in the ALcyFormat

If you are coding along with this tutorial, you’ll notice that at this point our ALcyFormat implementation still requires the implementation of an extra method.

This missing method will be implemented later on.

Creating the ALcyAddOn

Because we are creating our own functionality and adding it to Lucy, we need to create our own Lucy add-on. A Lucy add-on is an extension of ALcyAddOn.

One of the benefits of using an ALcyFormat in the previous step is that our add-on can be an extension of ALcyFormatAddOn. The format add-on:

  • Uses a configuration file for configurable settings.

  • Makes it easy to register all the objects created in the ALcyFormat, such as the model decoder, as a service.

package com.luciad.lucy.addons.tutorial.staticlspdata.model;

public class WayPointModelAddOn extends ALcyFormatAddOn {

  public WayPointModelAddOn() {
    super(ALcyTool.getLongPrefix(WayPointModelAddOn.class),
          ALcyTool.getShortPrefix(WayPointModelAddOn.class));
  }
}

The prefixes we need to pass to the super constructor are:

  • A long prefix, which is used to generate unique IDs

  • A short prefix, which is the prefix used in the configuration file of the add-on

The add-on needs to create the ALcyFormat so that it can plug in everything, including the model decoder and model content type provider we created:

@Override
protected ALcyFormat createBaseFormat() {
  return new WayPointModelFormat(getLucyEnv(),
                                 getLongPrefix(),
                                 getShortPrefix(),
                                 getPreferences());
}

We also pass the parsed version of the configuration file to the format with getPreferences(), and we pass the prefix used in the configuration file to the format with getShortPrefix. This allows the format to read settings from the configuration file.

The last method we need to implement is the createFormatWrapper method.

The Lucy API contains a number of decorators or wrappers for an ALcyFormat, which makes it easy to re-use functionality. You can find all available decorators in the Javadoc by looking for the available extensions of ALcyFormatWrapper.

In this tutorial, we are going to use the TLcySafeGuardFormatWrapper: this wrapper ensures that the instances created in the base ALcyFormat are called only with the correct models and layers.

For example, the ILcyModelContentTypeProvider we created currently violates the contract because it indicates that all models contain point data. Instead, it should indicate that our waypoint models contain point data, and that it does not know what kind of data other models contain.

By decorating our format with a TLcySafeGuardFormatWrapper, our ILcyModelContentTypeProvider will only be called for waypoint models.

For this to work, the TLcySafeGuardFormatWrapper needs to know which models belong to our format, and which do not. This information is provided by the last method we still need to implement in our ALcyFormat:

@Override
public boolean isModelOfFormat(ILcdModel aModel) {
  //All the waypoint models created by our model decoder have CWP as type name
  //We assume here that this typename is unique over all supported formats
  return "CWP".equals(aModel.getModelDescriptor().getTypeName());
}

We add the safeguard wrapper in the add-on:

@Override
protected ALcyFormat createFormatWrapper(ALcyFormat aBaseFormat) {
  return new TLcySafeGuardFormatWrapper(aBaseFormat);
}

Creating the configuration file for the add-on

Our add-on requires a configuration file. The ALcyFileFormat we used reads some properties from that configuration, as documented in the class Javadoc.

WayPointModelAddOn.fileTypeDescriptor.displayName=Waypoint files
WayPointModelAddOn.fileTypeDescriptor.defaultExtension=cwp
WayPointModelAddOn.fileTypeDescriptor.filters=*.cwp
WayPointModelAddOn.fileTypeDescriptor.groupIDs=All Vector Files

Adding support to visualize the data

In the previous section, we used an ALcyFormat instance to add support for the model part of adding data to a Lightspeed view. The ALcyFormat class offers no methods for the creation of an ILspLayerFactory however. We need a layer factory for the visualization on a Lightspeed view.

For that, we use an ALcyLspFormat instance. The ALcyLspFormat is conceptually the same as an ALcyFormat, but deals with Lightspeed layers only.

Similar to what we did with the ALcyFormat, we will:

package com.luciad.lucy.addons.tutorial.staticlspdata;

import com.luciad.lucy.addons.lightspeed.ALcyLspFormatAddOn;
import com.luciad.lucy.format.lightspeed.ALcyLspFormat;
import com.luciad.lucy.format.lightspeed.TLcyLspSafeGuardFormatWrapper;
import com.luciad.lucy.format.lightspeed.TLcyLspVectorFormat;
import com.luciad.lucy.util.ALcyTool;
import com.luciad.model.ILcdModel;
import com.luciad.util.ILcdFilter;

public class WayPointAddOn extends ALcyLspFormatAddOn {

  public WayPointAddOn() {
    super(ALcyTool.getLongPrefix(WayPointAddOn.class),
          ALcyTool.getShortPrefix(WayPointAddOn.class));
  }

  @Override
  protected ALcyLspFormat createBaseFormat() {
    ILcdFilter<ILcdModel> modelFilter =
        (ILcdFilter<ILcdModel>) aModel -> "CWP".equals(aModel.getModelDescriptor().getTypeName());

    return new TLcyLspVectorFormat(getLucyEnv(),
                                   getLongPrefix(),
                                   getShortPrefix(),
                                   getPreferences(),
                                   modelFilter);
  }

  @Override
  protected ALcyLspFormat createFormatWrapper(ALcyLspFormat aBaseFormat) {
    return new TLcyLspSafeGuardFormatWrapper(aBaseFormat);
  }
}

This add-on also requires a configuration file. Consult the class Javadoc of ALcyLspStyleFormat, the parent class of TLcyLspVectorFormat, for more information about the available configuration options.

WayPointAddOn.style.fileTypeDescriptor.displayName=Style Files
WayPointAddOn.style.fileTypeDescriptor.defaultExtension=sty
WayPointAddOn.style.fileTypeDescriptor.filters=*.sty
WayPointAddOn.style.fileTypeDescriptor.groupIDs=All Style Files

WayPointAddOn.defaultStyleFile=docs/articles/tutorial/lucy/customdata/defaultWaypointStyle.sty

This configuration file points to a default style file. The TLcyLspVectorFormat allows you to:

  • Customize the styling of your data through the layer properties panel, and save that styling

  • Use a saved style as the default style for new layers

Plugging in the add-ons

Plugging in the add-ons requires the creation of a custom add-ons file where we include our add-ons:

The WayPointAddOns.xml file
<?xml version="1.0" encoding="UTF-8"?>
<addonConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:noNamespaceSchemaLocation="config/lucy/addons.xsd">
  <addons>
    <!-- Include all the original add-ons -->
    <include>lucy/addons.xml</include>

    <!-- Add our own add-ons for waypoint data -->
    <addon>
      <priority>data_producer</priority>
      <name>Waypoint model format</name>
      <class>com.luciad.lucy.addons.tutorial.staticlspdata.model.WayPointModelAddOn</class>
      <configFile>docs/articles/tutorial/lucy/customdata/WayPointModelAddOn.cfg</configFile>
    </addon>
    <addon>
      <priority>data_producer</priority>
      <name>Waypoint format</name>
      <class>com.luciad.lucy.addons.tutorial.staticlspdata.WayPointAddOn</class>
      <configFile>docs/articles/tutorial/lucy/customdata/WayPointAddOn.cfg</configFile>
    </addon>
  </addons>
</addonConfiguration>

See How to customize the add-ons used by your application for more information about using custom add-ons.

Taking advantage of other functionality that just works

Lucy comes bundled with a lot of default functionality. Now that we added support for the waypoint data files, we get a lot of extra functionality for free:

  • Integration with the table view: you can view the waypoint data in a tabular view. Just right-click on the layer and choose the Table view option. This functionality is provided by the TLcyLspToteAddOn.

  • Object properties: double-click on a waypoint to open an object properties view. It displays properties that are based on the data model of the waypoint.

  • Adjustable layer styling: you can use TLcyLspVectorFormat and load the TLcyLspLayerCustomizerAddOn to adjust the layer styling through the UI. To see such a styling UI, right-click a layer in the layer control panel, and choose the Properties action.

  • Workspace support: a Lucy workspace is a file containing all the information about a state of your Lucy application: which files were loaded, which panels were open, how your map was fitted, and so on. You can create such a workspace file from File | Save workspace as. To load the file afterwards, go to File | Load workspace.

    Because we used ALcyFileFormat and TLcyLspVectorFormat, we automatically gained workspace support for our own data. You can see workspace support in action by saving a workspace containing waypoint data, and loading the workspace file again. As a result, the flight plan data is restored, including any modifications made to the styling.

In this tutorial, it is assumed that the model data is loaded from a file. See the ""Generated data" sample samples.lucy.format.generated.Main for an illustration of handling models created in code.

Examples of such models are models that are the result of some computation, the result of some web service request, or a connection to a live feed of tracks.

Full code

The WayPointModelAddOn code

package com.luciad.lucy.addons.tutorial.staticlspdata.model;


import com.luciad.lucy.addons.ALcyFormatAddOn;
import com.luciad.lucy.format.ALcyFormat;
import com.luciad.lucy.format.TLcySafeGuardFormatWrapper;
import com.luciad.lucy.util.ALcyTool;

public class WayPointModelAddOn extends ALcyFormatAddOn {

  public WayPointModelAddOn() {
    super(ALcyTool.getLongPrefix(WayPointModelAddOn.class),
          ALcyTool.getShortPrefix(WayPointModelAddOn.class));
  }

  @Override
  protected ALcyFormat createBaseFormat() {
    return new WayPointModelFormat(getLucyEnv(),
                                   getLongPrefix(),
                                   getShortPrefix(),
                                   getPreferences());
  }

  @Override
  protected ALcyFormat createFormatWrapper(ALcyFormat aBaseFormat) {
    return new TLcySafeGuardFormatWrapper(aBaseFormat);
  }

}

The WayPointModelFormat code

package com.luciad.lucy.addons.tutorial.staticlspdata.model;

import com.luciad.lucy.ILcyLucyEnv;
import com.luciad.lucy.format.ALcyFileFormat;
import com.luciad.lucy.map.ILcyGXYLayerTypeProvider;
import com.luciad.lucy.model.ILcyModelContentType;
import com.luciad.lucy.model.ILcyModelContentTypeProvider;
import com.luciad.lucy.util.properties.ALcyProperties;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.tutorial.customvector.WayPointsModelDecoder;
import com.luciad.view.gxy.ILcdGXYLayerFactory;


class WayPointModelFormat extends ALcyFileFormat {

  WayPointModelFormat(ILcyLucyEnv aLucyEnv, String aLongPrefix, String aShortPrefix, ALcyProperties aProperties) {
    super(aLucyEnv, aLongPrefix, aShortPrefix, aProperties);
  }

  @Override
  protected ILcdModelDecoder[] createModelDecoders() {
    return new ILcdModelDecoder[]{new WayPointsModelDecoder()};
  }

  @Override
  protected ILcyModelContentTypeProvider createModelContentTypeProvider() {
    //All our models only contain point data, so we can return a fixed type
    //No need to check the contents of the model
    return aModel -> ILcyModelContentType.POINT;
  }

  @Override
  protected ILcyGXYLayerTypeProvider createGXYLayerTypeProvider() {
    return null;
  }

  @Override
  protected ILcdGXYLayerFactory createGXYLayerFactory() {
    return null;
  }

  @Override
  public boolean isModelOfFormat(ILcdModel aModel) {
    //All the waypoint models created by our model decoder have CWP as type name
    //We assume here that this typename is unique over all supported formats
    return "CWP".equals(aModel.getModelDescriptor().getTypeName());
  }

}

The configuration file for the WayPointModelAddOn

WayPointModelAddOn.fileTypeDescriptor.displayName=Waypoint files
WayPointModelAddOn.fileTypeDescriptor.defaultExtension=cwp
WayPointModelAddOn.fileTypeDescriptor.filters=*.cwp
WayPointModelAddOn.fileTypeDescriptor.groupIDs=All Vector Files

The WayPointAddOn code

package com.luciad.lucy.addons.tutorial.staticlspdata;

import com.luciad.lucy.addons.lightspeed.ALcyLspFormatAddOn;
import com.luciad.lucy.format.lightspeed.ALcyLspFormat;
import com.luciad.lucy.format.lightspeed.TLcyLspSafeGuardFormatWrapper;
import com.luciad.lucy.format.lightspeed.TLcyLspVectorFormat;
import com.luciad.lucy.util.ALcyTool;
import com.luciad.model.ILcdModel;
import com.luciad.util.ILcdFilter;

public class WayPointAddOn extends ALcyLspFormatAddOn {

  public WayPointAddOn() {
    super(ALcyTool.getLongPrefix(WayPointAddOn.class),
          ALcyTool.getShortPrefix(WayPointAddOn.class));
  }

  @Override
  protected ALcyLspFormat createBaseFormat() {
    ILcdFilter<ILcdModel> modelFilter =
        (ILcdFilter<ILcdModel>) aModel -> "CWP".equals(aModel.getModelDescriptor().getTypeName());

    return new TLcyLspVectorFormat(getLucyEnv(),
                                   getLongPrefix(),
                                   getShortPrefix(),
                                   getPreferences(),
                                   modelFilter);
  }

  @Override
  protected ALcyLspFormat createFormatWrapper(ALcyLspFormat aBaseFormat) {
    return new TLcyLspSafeGuardFormatWrapper(aBaseFormat);
  }
}

The configuration file for the WayPointAddOn

WayPointAddOn.style.fileTypeDescriptor.displayName=Style Files
WayPointAddOn.style.fileTypeDescriptor.defaultExtension=sty
WayPointAddOn.style.fileTypeDescriptor.filters=*.sty
WayPointAddOn.style.fileTypeDescriptor.groupIDs=All Style Files

WayPointAddOn.defaultStyleFile=docs/articles/tutorial/lucy/customdata/defaultWaypointStyle.sty

The custom addons.xml file

<?xml version="1.0" encoding="UTF-8"?>
<addonConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:noNamespaceSchemaLocation="config/lucy/addons.xsd">
  <addons>
    <!-- Include all the original add-ons -->
    <include>lucy/addons.xml</include>

    <!-- Add our own add-ons for waypoint data -->
    <addon>
      <priority>data_producer</priority>
      <name>Waypoint model format</name>
      <class>com.luciad.lucy.addons.tutorial.staticlspdata.model.WayPointModelAddOn</class>
      <configFile>docs/articles/tutorial/lucy/customdata/WayPointModelAddOn.cfg</configFile>
    </addon>
    <addon>
      <priority>data_producer</priority>
      <name>Waypoint format</name>
      <class>com.luciad.lucy.addons.tutorial.staticlspdata.WayPointAddOn</class>
      <configFile>docs/articles/tutorial/lucy/customdata/WayPointAddOn.cfg</configFile>
    </addon>
  </addons>
</addonConfiguration>