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.
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
:
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 |
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"); }
We use this 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 we construct the layer. We can do that by calling the
TLspShapeLayerBuilder#bodyEditable
method. -
Installing an
ILspEditor
on the layer. AnILspEditor
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. ThisILspEditor
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 thebodyEditor
method.
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:
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:
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:
-
Create an
TLspSetControllerAction
: 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 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:
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
:
//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.
//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); }); } }