This tutorial series show you how to build your first LuciadLightspeed application that loads data and allows you to navigate on a 2D/3D map:

Goal

In this tutorial, we add imagery data from a GeoTIFF file.

Starting point

We take the code written in the First application article as our starting point, and expand from there.

Adding raster data to the map

Now that we have set up the map, we can add some data to it. Adding the data is a two-step process:

  1. First we create an ILcdModel that contains the data.

  2. Then we create an ILspLayer for the model, which we can add to the map. The ILcdModel contains the data only. The ILspLayer defines how that data is visualized on the map.

Creating a model from a file

The LuciadLightspeed API supports a number of geospatial formats out-of-the-box. It offers ILcdModelDecoder implementations to create an ILcdModel for data of those formats.

In this tutorial, we use a TLcdCompositeModelDecoder to decode the data. As the class name suggests, this is a composite version of the ILcdModelDecoder interface.

This composite implementation is populated with model decoders for all supported formats.

Program: The creation of the composite model decoder
ILcdModelDecoder decoder =
    new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));

The mechanism that makes this work is the Java ServiceLoader: LuciadLightspeed registers ILcdModelDecoder implementations for each of the supported formats with the service registry. We request all those implementations using the TLcdServiceLoader utility class, and pass them all to the constructor of the TLcdCompositeModelDecoder.

The resulting model decoder is capable of decoding data of all supported formats.

Once we have the decoder, we pass the path to the data file to the decode method of the model decoder. The model decoder reads the data in the file and creates an ILcdModel for it.

Program: Creating an ILcdModel for raster data
private static ILcdModel createRasterModel() throws IOException {
  // This composite decoder can decode all supported formats
  ILcdModelDecoder decoder =
      new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));

  // Decode a sample data set (imagery data)
  ILcdModel geopackageModel = decoder.decode("Data/GeoPackage/bluemarble.gpkg");

  return geopackageModel;
}

These snippets only work from your IDE if you activate its annotation processing settings.

See the Installation documentation for more details on how to set up your IDE, and the Fixing service registry errors article for specific tips related to annotation processing.

Creating a layer using a layer factory

The layer defines how the data from the ILcdModel is visualized on the map. The LuciadLightspeed API uses the concept of an ILspLayerFactory to create layers for an ILcdModel.

Just like we did for the model decoder, we use a composite ILspLayerFactory implementation. We make use of the TLcdServiceLoader class once more to populate the composite instance with layer factories for all supported formats.

Program: Creating the composite layer factory
TLspCompositeLayerFactory layerFactory =
    new TLspCompositeLayerFactory(TLcdServiceLoader.getInstance(ILspLayerFactory.class));

We can use the createLayers method of the layer factory to create our layer.

Program Creating a layer using the composite layer factory
private static ILspLayer createLayer(ILcdModel aModel) {
  TLspCompositeLayerFactory layerFactory =
      new TLspCompositeLayerFactory(TLcdServiceLoader.getInstance(ILspLayerFactory.class));

  if (layerFactory.canCreateLayers(aModel)) {
    Collection<ILspLayer> layers = layerFactory.createLayers(aModel);
    //We only expect a single layer for our data
    return layers.iterator().next();
  }
  throw new RuntimeException("Could not create a layer for " + aModel.getModelDescriptor().getDisplayName());
}

Now we can add the model to the map using ILspView.addLayer:

Program: Adding the created layers to the map
ILcdModel shpModel = createSHPModel();
view.addLayer(createLayer(shpModel));

ILcdModel rasterModel = createRasterModel();
view.addLayer(createLayer(rasterModel));

Creating layers directly

The usage of an ILspLayerFactory is optional. For the map, it is irrelevant how the layer was constructed.

To illustrate this, we create a grid layer directly:

Program: Creating the grid layer using the TLspLonLatGridLayerBuilder
static ILspLayer createGridLayer() {
  return TLspLonLatGridLayerBuilder.newBuilder().build();
}

Then, we add the grid layer to the map:

Program: Adding the grid layer to the map
view.addLayer(createGridLayer());

After following the Add raster data to the map and Add vector data to the map tutorials, our application looks like this:

lls basic app with layers
Figure 1. The map displaying raster imagery, a SHP model and a longitude/latitude grid

The full code

Program: The full Swing code
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.Collection;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.JToolBar;

import com.luciad.geodesy.TLcdGeodeticDatum;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdCompositeModelDecoder;
import com.luciad.projection.TLcdEquidistantCylindrical;
import com.luciad.reference.TLcdGridReference;
import com.luciad.util.service.TLcdServiceLoader;
import com.luciad.view.lightspeed.ILspAWTView;
import com.luciad.view.lightspeed.ILspView;
import com.luciad.view.lightspeed.TLspViewBuilder;
import com.luciad.view.lightspeed.layer.ILspLayer;
import com.luciad.view.lightspeed.layer.ILspLayerFactory;
import com.luciad.view.lightspeed.layer.TLspCompositeLayerFactory;
import com.luciad.view.lightspeed.painter.grid.TLspLonLatGridLayerBuilder;
import com.luciad.view.lightspeed.util.TLspViewTransformationUtil;
import com.luciad.view.swing.TLcdLayerTree;

public class FirstApplicationTutorial {


  public JFrame createUI() {
    JFrame frame = new JFrame("First Lightspeed application");

    ILspAWTView view = createView();
    frame.add(view.getHostComponent(), BorderLayout.CENTER);

    addData(view);

    JComponent layerControl = createLayerControl(view);
    frame.add(layerControl, BorderLayout.EAST);

    view.addLayer(createGridLayer());

    JToolBar toolBar = new JToolBar();

    JRadioButton b2d = new JRadioButton(createSwitchTo2DAction(view));
    b2d.setSelected(true);//start with a 2D view
    JRadioButton b3d = new JRadioButton(createSwitchTo3DAction(view));

    //Place the buttons in a ButtonGroup.
    //This ensures that only one of them can be selected at the same time
    ButtonGroup group = new ButtonGroup();
    group.add(b2d);
    group.add(b3d);

    toolBar.add(b2d);
    toolBar.add(b3d);

    frame.add(toolBar, BorderLayout.NORTH);

    frame.setSize(2000, 1500);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    return frame;
  }

  ILspAWTView createView() {
    return TLspViewBuilder.newBuilder().buildAWTView();
  }

  static void addData(ILspView view) {
    try {
      ILcdModel shpModel = createSHPModel();
      view.addLayer(createLayer(shpModel));

      ILcdModel rasterModel = createRasterModel();
      view.addLayer(createLayer(rasterModel));
    } catch (IOException e) {
      throw new RuntimeException("Problem during data decoding", e);
    }
  }

  private static ILcdModel createSHPModel() throws IOException {
    // This composite decoder can decode all supported formats
    ILcdModelDecoder decoder =
        new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));

    // Decode city_125.shp to create an ILcdModel
    ILcdModel shpModel = decoder.decode("Data/Shp/Usa/city_125.shp");

    return shpModel;
  }

  private static ILcdModel createRasterModel() throws IOException {
    // This composite decoder can decode all supported formats
    ILcdModelDecoder decoder =
        new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));

    // Decode a sample data set (imagery data)
    ILcdModel geopackageModel = decoder.decode("Data/GeoPackage/bluemarble.gpkg");

    return geopackageModel;
  }

  private static ILspLayer createLayer(ILcdModel aModel) {
    TLspCompositeLayerFactory layerFactory =
        new TLspCompositeLayerFactory(TLcdServiceLoader.getInstance(ILspLayerFactory.class));

    if (layerFactory.canCreateLayers(aModel)) {
      Collection<ILspLayer> layers = layerFactory.createLayers(aModel);
      //We only expect a single layer for our data
      return layers.iterator().next();
    }
    throw new RuntimeException("Could not create a layer for " + aModel.getModelDescriptor().getDisplayName());
  }

  static ILspLayer createGridLayer() {
    return TLspLonLatGridLayerBuilder.newBuilder().build();
  }

  private JComponent createLayerControl(ILspView aView) {
    return new TLcdLayerTree(aView);
  }

  static Action createSwitchTo2DAction(ILspView aView) {
    AbstractAction action = new AbstractAction("2D") {
      @Override
      public void actionPerformed(ActionEvent e) {
        TLspViewTransformationUtil.setup2DView(
            aView,
            new TLcdGridReference(new TLcdGeodeticDatum(),
                                  new TLcdEquidistantCylindrical()),
            true
        );
      }
    };
    action.putValue(Action.SHORT_DESCRIPTION, "Switch the view to 2D");
    return action;
  }

  private Action createSwitchTo3DAction(ILspView aView) {
    AbstractAction action = new AbstractAction("3D") {
      @Override
      public void actionPerformed(ActionEvent e) {
        TLspViewTransformationUtil.setup3DView(aView, true);
      }
    };
    action.putValue(Action.SHORT_DESCRIPTION, "Switch the view to 3D");
    return action;
  }

  public static void main(String[] args) {
    //Swing components must be created on the Event Dispatch Thread
    EventQueue.invokeLater(() -> {
      JFrame frame = new FirstApplicationTutorial().createUI();
      frame.setVisible(true);
    });
  }
}
Program: The JavaFX code
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import com.luciad.geodesy.TLcdGeodeticDatum;
import com.luciad.projection.TLcdEquidistantCylindrical;
import com.luciad.reference.TLcdGridReference;
import com.luciad.view.lightspeed.ILspView;
import com.luciad.view.lightspeed.TLspFXView;
import com.luciad.view.lightspeed.TLspViewBuilder;
import com.luciad.view.lightspeed.util.TLspViewTransformationUtil;

import samples.lightspeed.javafx.common.layercontrols.FXLayerControl;

public class FirstApplicationFXTutorial extends Application {


  @Override
  public void start(Stage primaryStage) {
    primaryStage.setTitle("First Lightspeed application");
    BorderPane borderPane = new BorderPane();

    TLspFXView view = createView();
    borderPane.setCenter(view.getHostNode());


    FirstApplicationTutorial.addData(view);

    Node layerControl = createLayerControl(view);
    borderPane.setRight(layerControl);

    view.addLayer(FirstApplicationTutorial.createGridLayer());

    ToolBar toolBar = new ToolBar();
    borderPane.setTop(toolBar);
    RadioButton b2d = new RadioButton("2D");
    RadioButton b3d = new RadioButton("3D");

    toolBar.getItems().add(b2d);
    toolBar.getItems().add(b3d);

    //Place the buttons in a group.
    //This ensures that only one of them can be selected at the same time
    ToggleGroup group = new ToggleGroup();
    b2d.setToggleGroup(group);
    b3d.setToggleGroup(group);

    group.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
      if (newValue == b2d) {
        TLspViewTransformationUtil.setup2DView(
            view,
            new TLcdGridReference(new TLcdGeodeticDatum(), new TLcdEquidistantCylindrical()), true);
      } else {
        TLspViewTransformationUtil.setup3DView(view, true);
      }
    });

    b2d.setSelected(true);//start with a 2D view


    Scene scene = new Scene(borderPane, 800, 600);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

  private TLspFXView createView() {
    return TLspViewBuilder.newBuilder().buildFXView();
  }

  private Node createLayerControl(ILspView aView) {
    return new FXLayerControl(aView);
  }
}