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 a UI button to switch between 2D and 3D.

Starting point

We take the code written in the Add a layer management UI article as our starting point, and expand from there.

Switching between 2D and 3D views

LuciadLightspeed supports a two-dimensional (2D) as well as a three-dimensional (3D) visualization of the same data. You can switch between the two views using the TLspViewTransformationUtil class, which has methods to configure a view as either 2D or 3D.

Program: Creating the actions to switch between 2D and 3D
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;
}

Note that the 2D action requires us to specify what kind of projection the map needs to use. In this example, we opted for an equidistant cylindrical projection. Another example of a frequently used projection is the (Pseudo-)Mercator projection, also used by Bing Maps for instance.

To add these actions to the UI, we place them in a JToolBar:

Program: Adding the actions to the UI
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);

In JavaFX, we do exactly the same, using an FX ToolBar. Refer to the full code for the details.

lls basic app end result
Figure 1. Using the 3D button to switch the map to a 3D view

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);
  }
}