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.

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
:
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 |
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
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:
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. AnILcdGXYEditorProvider
instance is responsible for returning anILcdGXYEditor
. The user can use theILcdGXYEditor
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. ThisILcdGXYEditorProvider
is the standard editor in the LuciadLightspeed API which can handle all standard shapes.The
ILcdGXYEditorProvider
is installed on the layer during construction.
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
:
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:
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:
-
Create a
TLcdGXYSetControllerAction
: thisILcdAction
implementation is designed to install a controller on the view. -
Wrap it with a
TLcdSWAction
: theTLcdSWAction
class converts anILcdAction
to ajavax.swing.Action
, which can be added to aJToolBar
.
For the buttons, we use JToggleButton
instances so that the user can see which controller is active.
//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:
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
:
//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.
//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);
});
}
}