Prerequisites

In this tutorial, it is assumed that you are familiar with the LuciadLightspeed concepts introduced in these 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 GXY 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.

gxy controller create edit result

The complete, runnable code of this tutorial can be found at the end.

Initial setup

UI setup

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

Program: Basic setup for this tutorial
class CreateAndEditTutorial {
  final TLcdMapJPanel fView = new TLcdMapJPanel();

  public JFrame createUI() {
    JFrame frame = new JFrame("Create and edit tutorial");
    frame.getContentPane().add(fView, 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 Introduction to the data modeling API tutorial 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");
}

and we use a TLcdDataModel to create an ILcdModel containing some way points:

Program: Creating a model with some predefined 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 constructing the layer. We call the TLcdGXYLayer.setEditable method to make the layer editable.

  • Installing an ILcdGXYEditorProvider on the layer. An ILcdGXYEditorProvider instance is responsible for returning an ILcdGXYEditor. The user can use the ILcdGXYEditor to edit an object. Typically, the editor paints shape handles on the map to allow the user to make modifications to the shape. Each time the user interacts with a handle, the editor will update the geometry of the domain object accordingly.

    In this tutorial, we use the TLcdGXYShapePainter class. This ILcdGXYEditorProvider is the standard editor in the LuciadLightspeed API which can handle all standard shapes.

    The ILcdGXYEditorProvider is installed on the layer during construction.

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

  //Create a layer and make it editable
  TLcdGXYLayer editableLayer = TLcdGXYLayer.create(model);
  editableLayer.setEditable(true);
  //Install an editor provider on the layer
  //The TLcdGXYShapePainter can paint and edit all standard shapes
  TLcdGXYShapePainter painterEditor = new TLcdGXYShapePainter();
  editableLayer.setGXYPainterProvider(painterEditor);
  editableLayer.setGXYEditorProvider(painterEditor);

  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 us to edit objects. If you install your own controller on the view, include a TLcdGXYEditController2 in your controller chain to add support for object editing.

Creating new objects

Creating new objects is done by using a TLcdGXYNewController2. When creating a new domain object, the controller must know:

  • What kind of domain object it must create, and

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

This knowledge is provided by an ALcdGXYNewControllerModel2:

Program: Creating the controller model
private ALcdGXYNewControllerModel2 createNewPointControllerModel() {
  return new ALcdGXYNewControllerModel2() {
    @Override
    public ILcdGXYLayer getGXYLayer(Graphics aGraphics, MouseEvent aMouseEvent, ILcdGXYLayerSubsetList aSnappables, ILcdGXYContext aContext) {
      if (aContext.getGXYLayer() != null) {
        return aContext.getGXYLayer();
      }
      //Find the first layer with a waypoint model in the view
      ILcdGXYView gxyView = aContext.getGXYView();
      return gxyView.getLayers()
                    .stream()
                    .filter(this::isWayPointLayer)
                    .findFirst()
                    .orElse(null);
    }

    @Override
    public Object create(int aEditCount, Graphics aGraphics, MouseEvent aMouseEvent, ILcdGXYLayerSubsetList aSnappables, ILcdGXYContext aContext) {
      return WAYPOINT_DATA_TYPE.newInstance();
    }

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

and that controller model is passed to the create controller during construction:

Program: Creating the TLcdGXYNewController2
ALcdGXYNewControllerModel2 createControllerModel = createNewPointControllerModel();
TLcdGXYNewController2 createController = new TLcdGXYNewController2(createControllerModel);

Adding a tool bar with buttons to activate the controller

We want the user to have the option to choose between the two available controllers: the default controller for navigation, selecting and editing, and the controller for creating a new way point. For this reason, we create a JToolBar with a button for each controller.

To create the buttons, we:

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
ILcdGXYController defaultController = fView.getGXYController();
ILcdAction activateDefaultControllerAction = new TLcdGXYSetControllerAction(fView, defaultController);
activateDefaultControllerAction.putValue(ILcdAction.NAME, "Edit");
toolBar.add(new JToggleButton(new TLcdSWAction(activateDefaultControllerAction)));

//Create a button to create a new waypoint
TLcdGXYNewController2 newWayPointController = createNewWayPointController();
ILcdAction activateNewWayPointController = new TLcdGXYSetControllerAction(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 some text for the button, so we configure those on the controller. The TLcdGXYSetControllerAction 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 edit it immediately afterwards, select a new way point, navigate around, and so on, we activate the default controller once the way point is created. We call the setActionToTriggerAfterCommit on the TLcdGXYNewController2 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.awt.Graphics;
import java.awt.event.MouseEvent;
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.gxy.ILcdGXYContext;
import com.luciad.view.gxy.ILcdGXYController;
import com.luciad.view.gxy.ILcdGXYLayer;
import com.luciad.view.gxy.ILcdGXYLayerSubsetList;
import com.luciad.view.gxy.ILcdGXYView;
import com.luciad.view.gxy.TLcdGXYLayer;
import com.luciad.view.gxy.TLcdGXYSetControllerAction;
import com.luciad.view.gxy.TLcdGXYShapePainter;
import com.luciad.view.gxy.controller.ALcdGXYNewControllerModel2;
import com.luciad.view.gxy.controller.TLcdGXYNewController2;
import com.luciad.view.map.TLcdMapJPanel;
import com.luciad.view.swing.TLcdLayerTree;

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 TLcdMapJPanel fView = new TLcdMapJPanel();

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

    fView.addGXYLayer(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
    ILcdGXYController defaultController = fView.getGXYController();
    ILcdAction activateDefaultControllerAction = new TLcdGXYSetControllerAction(fView, defaultController);
    activateDefaultControllerAction.putValue(ILcdAction.NAME, "Edit");
    toolBar.add(new JToggleButton(new TLcdSWAction(activateDefaultControllerAction)));

    //Create a button to create a new waypoint
    TLcdGXYNewController2 newWayPointController = createNewWayPointController();
    ILcdAction activateNewWayPointController = new TLcdGXYSetControllerAction(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, 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 ILcdGXYLayer createEditableLayer() {
    ILcdModel model = createWaypointModel();

    //Create a layer and make it editable
    TLcdGXYLayer editableLayer = TLcdGXYLayer.create(model);
    editableLayer.setEditable(true);
    //Install an editor provider on the layer
    //The TLcdGXYShapePainter can paint and edit all standard shapes
    TLcdGXYShapePainter painterEditor = new TLcdGXYShapePainter();
    editableLayer.setGXYPainterProvider(painterEditor);
    editableLayer.setGXYEditorProvider(painterEditor);

    return editableLayer;
  }

  private TLcdGXYNewController2 createNewWayPointController() {
    ALcdGXYNewControllerModel2 createControllerModel = createNewPointControllerModel();
    TLcdGXYNewController2 createController = new TLcdGXYNewController2(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 ALcdGXYNewControllerModel2 createNewPointControllerModel() {
    return new ALcdGXYNewControllerModel2() {
      @Override
      public ILcdGXYLayer getGXYLayer(Graphics aGraphics, MouseEvent aMouseEvent, ILcdGXYLayerSubsetList aSnappables, ILcdGXYContext aContext) {
        if (aContext.getGXYLayer() != null) {
          return aContext.getGXYLayer();
        }
        //Find the first layer with a waypoint model in the view
        ILcdGXYView gxyView = aContext.getGXYView();
        return gxyView.getLayers()
                      .stream()
                      .filter(this::isWayPointLayer)
                      .findFirst()
                      .orElse(null);
      }

      @Override
      public Object create(int aEditCount, Graphics aGraphics, MouseEvent aMouseEvent, ILcdGXYLayerSubsetList aSnappables, ILcdGXYContext aContext) {
        return WAYPOINT_DATA_TYPE.newInstance();
      }

      private boolean isWayPointLayer(ILcdGXYLayer 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);
    });
  }

}