Prerequisites

In this tutorial, it is assumed that the reader is familiar with the LuciadLightspeed concepts introduced in the following tutorials:

Goal

This tutorial teaches you how to use controllers to:

  • Edit existing domain objects on the map

  • Create new domain objects on the map

This tutorial creates a JFrame containing a Lightspeed view and a layer with editable way point data. We also add a JToolBar with a button to activate the controller to create a new way point on the map, and a button to switch back to the default controller.

lls controller create edit result

You can find the complete, runnable code at the end of the tutorial.

Initial setup

UI setup

This tutorial starts from an application that shows a Lightspeed view in a JFrame:

Program: Basic setup for this tutorial
public class CreateAndEditTutorial {
  final ILspAWTView fView = TLspViewBuilder.newBuilder().buildAWTView();

  public JFrame createUI() {
    JFrame frame = new JFrame("Create and edit tutorial");
    frame.getContentPane().add(fView.getHostComponent(), BorderLayout.CENTER);
    frame.getContentPane().add(new TLcdLayerTree(fView), BorderLayout.EAST);

    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    return frame;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new CreateAndEditTutorial().createUI();
      frame.setVisible(true);
    });
  }

Data format setup

If you are not familiar with TLcdDataModel and ILcdDataObject, follow the tutorial Introduction to the data modeling API first.

In this tutorial, we edit the data of a custom format. The custom format is a format containing way points with:

  • A name

  • A point geometry

Program: The way point TLcdDataModel
private static final TLcdDataModel WAYPOINT_DATA_MODEL;
private static final TLcdDataType WAYPOINT_DATA_TYPE;
private static final TLcdDataProperty NAME_PROPERTY;
private static final TLcdDataProperty GEOMETRY_PROPERTY;

static {
  TLcdDataModelBuilder builder = new TLcdDataModelBuilder("http://www.luciad.com/datamodel/createandedittutorial");

  TLcdDataTypeBuilder wayPointTypeBuilder = builder.typeBuilder("WayPointType");
  wayPointTypeBuilder.addProperty("name", TLcdCoreDataTypes.STRING_TYPE);
  wayPointTypeBuilder.addProperty("geometry", TLcdShapeDataTypes.SHAPE_TYPE);

  //Ensure that creating a new way point creates a waypoint object which has a valid geometry
  wayPointTypeBuilder.dataObjectFactory(aType -> {
    ILcdDataObject wayPoint = new TLcdDataObject(aType);
    wayPoint.setValue("geometry", new TLcdLonLatPoint());
    return wayPoint;
  });

  TLcdDataModel dataModel = builder.createDataModel();
  TLcdDataType wayPointType = dataModel.getDeclaredType("WayPointType");
  wayPointType.addAnnotation(new TLcdHasGeometryAnnotation(wayPointType.getProperty("geometry")));

  WAYPOINT_DATA_MODEL = dataModel;
  WAYPOINT_DATA_TYPE = wayPointType;
  NAME_PROPERTY = wayPointType.getProperty("name");
  GEOMETRY_PROPERTY = wayPointType.getProperty("geometry");
}

We use this TLcdDataModel to create an ILcdModel containing some way points:

Program: Creating a model with pre-defined way points
private ILcdModel createWaypointModel() {
  //Create an empty model
  TLcdVectorModel model = new TLcdVectorModel(new TLcdGeodeticReference());
  TLcdDataModelDescriptor modelDescriptor =
      new TLcdDataModelDescriptor("",
                                  WAY_POINTS_MODEL_TYPE_NAME,
                                  "Way Points",
                                  WAYPOINT_DATA_MODEL,
                                  Collections.singleton(WAYPOINT_DATA_TYPE),
                                  null);
  model.setModelDescriptor(modelDescriptor);

  //Already add some data
  model.addElement(createWayPoint("WP1", 10, 10), ILcdModel.NO_EVENT);
  model.addElement(createWayPoint("WP2", 20, 10), ILcdModel.NO_EVENT);
  model.addElement(createWayPoint("WP3", 10, 20), ILcdModel.NO_EVENT);

  return model;
}

private ILcdDataObject createWayPoint(String name, double longitude, double latitude) {
  ILcdDataObject wayPoint = WAYPOINT_DATA_TYPE.newInstance();
  wayPoint.setValue(NAME_PROPERTY, name);
  ((TLcdLonLatPoint) wayPoint.getValue(GEOMETRY_PROPERTY)).move2D(longitude, latitude);
  return wayPoint;
}

Editing data on the map

To make a layer editable, we need to:

  • Specify that the layer is editable when we construct the layer. We can do that by calling the TLspShapeLayerBuilder#bodyEditable method.

  • Installing an ILspEditor on the layer. An ILspEditor instance provides handles for the user to manipulate to edit an object. Each time the user interacts with a handle, the editor updates the geometry of the domain object accordingly.

    In this tutorial we use the TLspShapeEditor class. This ILspEditor is the standard editor in the LuciadLightspeed API. It can handle all standard shapes.

    We install the ILspEditor on the layer during construction time, using the bodyEditor method.

Program: Creating an editable way point layer
private ILspLayer createEditableLayer() {
  ILcdModel model = createWaypointModel();

  //Create an editor capable of editing all standard shapes
  TLspShapeEditor editor = new TLspShapeEditor();

  //Configure the editor on the layer,
  //and specify that the layer should be editable
  TLspLayer editableLayer =
      TLspShapeLayerBuilder.newBuilder()
                           .model(model)
                           .bodyEditable(true)//indicate the layer should be editable
                           .bodyEditor(editor)//install the editor
                           .build();

  return editableLayer;
}

When we add this layer to the map and select a way point, the mouse cursor changes, indicating that we can move the way point to a new location.

This works because the default controller of a Lightspeed view allows object editing. If you install your own controller on the view, include a TLspEditController in your controller chain if you want support for editing objects.

Creating new objects

You create new objects by using a TLspCreateController. When creating a new domain object, the controller must know:

  • What kind of domain object it must create.

  • To which layer the created domain object must be added.

ALspCreateControllerModel provides that information:

Program: Creating the controller model
private ALspCreateControllerModel createNewPointControllerModel() {
  return new ALspCreateControllerModel() {
    @Override
    public ILspInteractivePaintableLayer getLayer(ILspView aView) {
      //Find the first layer with a waypoint model in the view
      return aView.getLayers()
                  .stream()
                  .filter(ILspInteractivePaintableLayer.class::isInstance)
                  .map(ILspInteractivePaintableLayer.class::cast)
                  .filter(this::isWayPointLayer)
                  .findFirst()
                  .orElse(null);
    }

    @Override
    public Object create(ILspView aView, ILspLayer aLayer) {
      return WAYPOINT_DATA_TYPE.newInstance();
    }

    private boolean isWayPointLayer(ILspLayer aLayer) {
      ILcdModelDescriptor modelDescriptor = aLayer.getModel().getModelDescriptor();
      String typeName = modelDescriptor.getTypeName();
      return WAY_POINTS_MODEL_TYPE_NAME.equals(typeName);
    }
  };
}

The controller model is passed to the create controller during construction:

Program: Creating the TLspCreateController
ALspCreateControllerModel createControllerModel = createNewPointControllerModel();
TLspCreateController createController = new TLspCreateController(createControllerModel);

Adding a tool bar with buttons to activate the controller

We want to offer the user a choice between the default controller to navigate, select and edit, and the controller to create a new way point. We will create a JToolBar with a button for each controller.

To create the buttons, we:

  1. Create an TLspSetControllerAction: this ILcdAction implementation is designed to install a controller on the view.

  2. Wrap it with a TLcdSWAction: the TLcdSWAction class converts an ILcdAction to a javax.swing.Action, which can be added to a JToolBar.

For the buttons, we use JToggleButton instances so that the user can see which controller is active.

Program: Creating the tool bar
//Create a toolbar with buttons to switch between the default controller
//and the controller to create new domain objects
JToolBar toolBar = new JToolBar();

//Create a button to activate the default controller of the view
ILspController defaultController = fView.getController();
ILcdAction activateDefaultControllerAction = new TLspSetControllerAction(fView, defaultController);
toolBar.add(new JToggleButton(new TLcdSWAction(activateDefaultControllerAction)));

//Create a button to create a new waypoint
TLspCreateController newWayPointController = createNewWayPointController();
ILcdAction activateNewWayPointController = new TLspSetControllerAction(fView, newWayPointController);
toolBar.add(new JToggleButton(new TLcdSWAction(activateNewWayPointController)));

Once the tool bar is created, we add it to the UI:

Program: Adding the tool bar to the UI
frame.getContentPane().add(toolBar, BorderLayout.NORTH);

Our create controller button needs a few more tweaks.

We want a nice icon and text for the button. We can configure those on the controller. The TLspSetControllerAction will pick those up, and pass them on to the JToggleButton:

Program: Providing an icon and text for the controller button
//Set a name and icon which will be shown by the button to activate the controller
createController.setName("Create new way point");
createController.setIcon(TLcdIconFactory.create(TLcdIconFactory.DRAW_POINT_ICON));

To ensure that the user can create a new way point and instantly edit it, select a new way point, and navigate around, we want to activate the default controller once the way point has been created. We call the setActionToTriggerAfterCommit on the TLspCreateController with an action that re-activates the default controller.

Program: Re-activating the default controller after creation
//Once the waypoint has been created, revert back to the original controller
//This allows the user to place the waypoint and immediately start editing it
newWayPointController.setActionToTriggerAfterCommit(activateDefaultControllerAction);

Full code

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Collections;

import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.WindowConstants;

import com.luciad.datamodel.ILcdDataObject;
import com.luciad.datamodel.TLcdCoreDataTypes;
import com.luciad.datamodel.TLcdDataModel;
import com.luciad.datamodel.TLcdDataModelBuilder;
import com.luciad.datamodel.TLcdDataObject;
import com.luciad.datamodel.TLcdDataProperty;
import com.luciad.datamodel.TLcdDataType;
import com.luciad.datamodel.TLcdDataTypeBuilder;
import com.luciad.gui.ILcdAction;
import com.luciad.gui.TLcdIconFactory;
import com.luciad.gui.swing.TLcdSWAction;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDescriptor;
import com.luciad.model.TLcdDataModelDescriptor;
import com.luciad.model.TLcdVectorModel;
import com.luciad.reference.TLcdGeodeticReference;
import com.luciad.shape.TLcdShapeDataTypes;
import com.luciad.shape.shape2D.TLcdLonLatPoint;
import com.luciad.util.TLcdHasGeometryAnnotation;
import com.luciad.view.lightspeed.ILspAWTView;
import com.luciad.view.lightspeed.ILspView;
import com.luciad.view.lightspeed.TLspViewBuilder;
import com.luciad.view.lightspeed.action.TLspSetControllerAction;
import com.luciad.view.lightspeed.controller.ILspController;
import com.luciad.view.lightspeed.controller.manipulation.ALspCreateControllerModel;
import com.luciad.view.lightspeed.controller.manipulation.TLspCreateController;
import com.luciad.view.lightspeed.editor.TLspShapeEditor;
import com.luciad.view.lightspeed.layer.ILspInteractivePaintableLayer;
import com.luciad.view.lightspeed.layer.ILspLayer;
import com.luciad.view.lightspeed.layer.TLspLayer;
import com.luciad.view.lightspeed.layer.shape.TLspShapeLayerBuilder;
import com.luciad.view.swing.TLcdLayerTree;

public class CreateAndEditTutorial {

  private static final String WAY_POINTS_MODEL_TYPE_NAME = "WayPoints";

  private static final TLcdDataModel WAYPOINT_DATA_MODEL;
  private static final TLcdDataType WAYPOINT_DATA_TYPE;
  private static final TLcdDataProperty NAME_PROPERTY;
  private static final TLcdDataProperty GEOMETRY_PROPERTY;

  static {
    TLcdDataModelBuilder builder = new TLcdDataModelBuilder("http://www.luciad.com/datamodel/createandedittutorial");

    TLcdDataTypeBuilder wayPointTypeBuilder = builder.typeBuilder("WayPointType");
    wayPointTypeBuilder.addProperty("name", TLcdCoreDataTypes.STRING_TYPE);
    wayPointTypeBuilder.addProperty("geometry", TLcdShapeDataTypes.SHAPE_TYPE);

    //Ensure that creating a new way point creates a waypoint object which has a valid geometry
    wayPointTypeBuilder.dataObjectFactory(aType -> {
      ILcdDataObject wayPoint = new TLcdDataObject(aType);
      wayPoint.setValue("geometry", new TLcdLonLatPoint());
      return wayPoint;
    });

    TLcdDataModel dataModel = builder.createDataModel();
    TLcdDataType wayPointType = dataModel.getDeclaredType("WayPointType");
    wayPointType.addAnnotation(new TLcdHasGeometryAnnotation(wayPointType.getProperty("geometry")));

    WAYPOINT_DATA_MODEL = dataModel;
    WAYPOINT_DATA_TYPE = wayPointType;
    NAME_PROPERTY = wayPointType.getProperty("name");
    GEOMETRY_PROPERTY = wayPointType.getProperty("geometry");
  }

  final ILspAWTView fView = TLspViewBuilder.newBuilder().buildAWTView();

  public JFrame createUI() {
    JFrame frame = new JFrame("Create and edit tutorial");

    fView.addLayer(createEditableLayer());

    //Create a toolbar with buttons to switch between the default controller
    //and the controller to create new domain objects
    JToolBar toolBar = new JToolBar();

    //Create a button to activate the default controller of the view
    ILspController defaultController = fView.getController();
    ILcdAction activateDefaultControllerAction = new TLspSetControllerAction(fView, defaultController);
    toolBar.add(new JToggleButton(new TLcdSWAction(activateDefaultControllerAction)));

    //Create a button to create a new waypoint
    TLspCreateController newWayPointController = createNewWayPointController();
    ILcdAction activateNewWayPointController = new TLspSetControllerAction(fView, newWayPointController);
    toolBar.add(new JToggleButton(new TLcdSWAction(activateNewWayPointController)));

    //Once the waypoint has been created, revert back to the original controller
    //This allows the user to place the waypoint and immediately start editing it
    newWayPointController.setActionToTriggerAfterCommit(activateDefaultControllerAction);

    frame.getContentPane().add(toolBar, BorderLayout.NORTH);

    frame.getContentPane().add(fView.getHostComponent(), BorderLayout.CENTER);
    frame.getContentPane().add(new TLcdLayerTree(fView), BorderLayout.EAST);

    frame.setSize(800, 600);
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

    return frame;
  }

  private ILcdModel createWaypointModel() {
    //Create an empty model
    TLcdVectorModel model = new TLcdVectorModel(new TLcdGeodeticReference());
    TLcdDataModelDescriptor modelDescriptor =
        new TLcdDataModelDescriptor("",
                                    WAY_POINTS_MODEL_TYPE_NAME,
                                    "Way Points",
                                    WAYPOINT_DATA_MODEL,
                                    Collections.singleton(WAYPOINT_DATA_TYPE),
                                    null);
    model.setModelDescriptor(modelDescriptor);

    //Already add some data
    model.addElement(createWayPoint("WP1", 10, 10), ILcdModel.NO_EVENT);
    model.addElement(createWayPoint("WP2", 20, 10), ILcdModel.NO_EVENT);
    model.addElement(createWayPoint("WP3", 10, 20), ILcdModel.NO_EVENT);

    return model;
  }

  private ILcdDataObject createWayPoint(String name, double longitude, double latitude) {
    ILcdDataObject wayPoint = WAYPOINT_DATA_TYPE.newInstance();
    wayPoint.setValue(NAME_PROPERTY, name);
    ((TLcdLonLatPoint) wayPoint.getValue(GEOMETRY_PROPERTY)).move2D(longitude, latitude);
    return wayPoint;
  }

  private ILspLayer createEditableLayer() {
    ILcdModel model = createWaypointModel();

    //Create an editor capable of editing all standard shapes
    TLspShapeEditor editor = new TLspShapeEditor();

    //Configure the editor on the layer,
    //and specify that the layer should be editable
    TLspLayer editableLayer =
        TLspShapeLayerBuilder.newBuilder()
                             .model(model)
                             .bodyEditable(true)//indicate the layer should be editable
                             .bodyEditor(editor)//install the editor
                             .build();

    return editableLayer;
  }

  private TLspCreateController createNewWayPointController() {
    ALspCreateControllerModel createControllerModel = createNewPointControllerModel();
    TLspCreateController createController = new TLspCreateController(createControllerModel);
    //Set a name and icon which will be shown by the button to activate the controller
    createController.setName("Create new way point");
    createController.setIcon(TLcdIconFactory.create(TLcdIconFactory.DRAW_POINT_ICON));
    return createController;
  }

  private ALspCreateControllerModel createNewPointControllerModel() {
    return new ALspCreateControllerModel() {
      @Override
      public ILspInteractivePaintableLayer getLayer(ILspView aView) {
        //Find the first layer with a waypoint model in the view
        return aView.getLayers()
                    .stream()
                    .filter(ILspInteractivePaintableLayer.class::isInstance)
                    .map(ILspInteractivePaintableLayer.class::cast)
                    .filter(this::isWayPointLayer)
                    .findFirst()
                    .orElse(null);
      }

      @Override
      public Object create(ILspView aView, ILspLayer aLayer) {
        return WAYPOINT_DATA_TYPE.newInstance();
      }

      private boolean isWayPointLayer(ILspLayer aLayer) {
        ILcdModelDescriptor modelDescriptor = aLayer.getModel().getModelDescriptor();
        String typeName = modelDescriptor.getTypeName();
        return WAY_POINTS_MODEL_TYPE_NAME.equals(typeName);
      }
    };
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new CreateAndEditTutorial().createUI();
      frame.setVisible(true);
    });
  }
}