Prerequisites
This tutorial requires no prior LuciadLightspeed knowledge, but you do need some basic familiarity with Java Swing.
Goal
This tutorial provides a step-by-step guide to building a basic LuciadLightspeed application with a 2D, non-hardware-accelerated map. The application built in this tutorial allows users to view major USA cities on a raster image of the world. The tutorial discusses the setup of the basic View, Model and Controller components of the application, before expanding this basic configuration with additional functions.
More specifically, the application contains the following functionality:
-
A map displayed inside a
JFrame
-
A raster image of the world used as background data
-
The major cities of the USA, read from a .SHP file
-
The ability to navigate through the view
-
Check boxes to toggle the visibility of the data on the map

You can find the complete, runnable code at the end of the tutorial.
This tutorial uses the non-hardware-accelerated GXY view. A similar tutorial for the hardware-accelerated Lightspeed view is available here. Read High-level overview of the different technologies if you are unsure which view is most suitable for you. |
Overview of the main API concepts
A standard LuciadLightspeed application follows the M(odel)-V(iew)-C(ontroller) architectural pattern. The idea behind the MVC architecture is to separate the data (model), the representation of the data (view), and the user interaction (controller) from each other. That separation results in a simpler design of the application and a higher flexibility and re-usability of code.
The MVC parts of the LuciadLightspeed API are defined as follows:
-
A LuciadLightspeed model stores and describes geographical data regardless of how the data is visualized and interacted with. For example: a model contains the location of a number of hospitals and additional hospital information such as capacity.
In the LuciadLightspeed API, the model is an
ILcdModel
instance. -
A LuciadLightspeed view contains all information needed for the visual representation of the data in the LuciadLightspeed models. A view does not contain data. For example, in the case of the hospitals, the view uses a red cross to represent the location of a hospital.
In the LuciadLightspeed API, an
ILcdGXYLayer
contains the information about how to visualize a singleILcdModel
. All those layers are added to aILcdGXYView
, which can be visualized in a Swing container. -
A LuciadLightspeed controller interprets user interaction and performs the required action on LuciadLightspeed models and views regardless of the type of model and view. For example: in the case of the hospitals, a mouse right-click on a red cross results in an information pop-up with the hospital location and capacity..
In the LuciadLightspeed API, these are
ILcdGXYController
instances.
Separating the different parts of the application allows you to re-use objects for different purposes, and to re-define objects without changing other objects. You can, for example, change a view without making changes to the models represented in the view. You can also re-define the user interaction with a view without changing the view itself. Object re-use shortens the time for writing an application. In addition, it promotes a consistent design and functionality for all your applications.
Creating the application UI
Before we can turn to the LuciadLightspeed API to create our map, we need a JFrame
to display the map in,
and a main
method to start the application:
public class FirstGXYApplicationTutorial {
public JFrame createUI() {
JFrame frame = new JFrame("First GXY application");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
public static void main(String[] args) {
//Swing components must be created on the Event Dispatch Thread
EventQueue.invokeLater(() -> {
JFrame frame = new FirstGXYApplicationTutorial().createUI();
frame.setVisible(true);
});
}
}
Creating the map
Now that we have a frame, we need to create the map and add it to the UI.
In LuciadLightspeed terminology, a map is known as a view. It is defined by the interface
ILcdGXYView
.
Because we are going to add our view to a Swing hierarchy, we use a Swing-based implementation of the ILcdGXYView
interface:
the TLcdMapJPanel
class.
private TLcdMapJPanel createView() {
TLcdMapJPanel view = new TLcdMapJPanel();
return view;
}
Once the view is created, we can add it to our UI.
JFrame
in the createUI
method
TLcdMapJPanel view = createView();
frame.add(view, BorderLayout.CENTER);
Running the program at this point results in a view with a grid layer.
This grid layer was automatically created when we constructed a new TLcdMapJPanel
instance.
Adding data to the view
Now that we have set up the view, we can add some data to the view. Adding the data is a two-step process:
-
First we create an
ILcdModel
that contains the data. -
Then we create an
ILcdGXYLayer
for the model, which we can add to the view. TheILcdModel
contains the data only. TheILcdGXYLayer
defines how that data is visualized on the view.
Creating a model from a file
The LuciadLightspeed API supports a number of geospatial formats out-of-the-box.
It offers ILcdModelDecoder
implementations to create an ILcdModel
for data of those formats.
In this tutorial, we use a TLcdCompositeModelDecoder
to decode the data.
As the class name suggests, this is a composite version of the ILcdModelDecoder
interface.
This composite implementation is populated with model decoders for all supported formats.
ILcdModelDecoder decoder =
new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
The mechanism that makes this work is the Java ServiceLoader
.
LuciadLightspeed registers ILcdModelDecoder
implementations for each of the supported formats in the service registry.
We request all those implementations using the TLcdServiceLoader
utility class, and pass them all to the constructor of the TLcdCompositeModelDecoder
.
The resulting model decoder is capable of decoding data of all supported formats.
Once we have the decoder, we pass the path to the data file to the decode
method of the model decoder.
The model decoder reads the data in the file and creates an ILcdModel
for it.
ILcdModel
for a SHP file
private 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;
}
We can do the same for some raster data:
ILcdModel
for raster data
private 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;
}
These snippets only work from your IDE if you activate its annotation processing settings. See the Installation documentation for more details on how to set up your IDE, and the Fixing service registry errors article for specific tips related to annotation processing. |
Creating a layer using a layer factory
The layer defines how the data from the ILcdModel
is visualized on the view.
The LuciadLightspeed API uses the concept of an ILcdGXYLayerFactory
to create layers for an ILcdModel
.
Just like we did for the model decoder, we use a composite ILcdGXYLayerFactory
implementation.
We make use of the TLcdServiceLoader
class once more to populate the composite instance with layer factories for all supported formats.
TLcdCompositeGXYLayerFactory layerFactory =
new TLcdCompositeGXYLayerFactory(TLcdServiceLoader.getInstance(ILcdGXYLayerFactory.class));
TLcdCompositeGXYLayerFactory layerFactory =
new TLcdCompositeGXYLayerFactory(TLcdServiceLoader.getInstance(ILcdGXYLayerFactory.class));
We can use the createGXYLayer
method of the layer factory to create our layer.
private ILcdGXYLayer createLayer(ILcdModel aModel) {
TLcdCompositeGXYLayerFactory layerFactory =
new TLcdCompositeGXYLayerFactory(TLcdServiceLoader.getInstance(ILcdGXYLayerFactory.class));
ILcdGXYLayer layer = layerFactory.createGXYLayer(aModel);
if (layer != null) {
return layer;
}
throw new RuntimeException("Could not create a layer for " + aModel.getModelDescriptor().getDisplayName());
}
Now we can add the model to the view using ILcdGXYView.addGXYLayer
:
ILcdGXYLayer backgroundLayer = createLayer(createRasterModel());
view.addGXYLayer(backgroundLayer);
ILcdGXYLayer citiesLayer = createLayer(createSHPModel());
view.addGXYLayer(citiesLayer);
Moving the grid layer to the top
The TLcdMapJPanel
constructor added a grid layer to the map.
Now that the raster and SHP data have been added to the map, we want the grid layer to appear on top of all other data.
We move the grid layer to the top of the view:
//Move the grid layer to the top of the view,
//so that it is rendered on top of the other layers
view.moveLayerAt(view.layerCount() - 1, view.getGridLayer());
Using a controller to make the view interactive
For the creation of the view, we used the TLcdMapJPanel
constructor.
The constructor already installed an ILcdGXYController
on the view, allowing users to use the mouse for navigatiion in the view.
Essentially, a controller interprets input events and translates them into actions that are performed on the view, or on the models it contains.
The default controller offers view navigation, selection and editing with the following navigation and selection configuration:
-
Dragging the left mouse button pans the view, unless a selected editable object is under the cursor.
-
Scrolling the mouse wheel zooms in and out on the cursor location.
-
Clicking the left mouse button selects a selectable object.
-
Dragging the left mouse button over one or more selectable objects while holding the Shift key selects the objects that are fully covered by the resulting selection rectangle.
-
Clicking the left mouse button over one or more selectable objects while holding the Alt key displays a pop-up menu. The menu allows you to indicate which object you want to select.
-
Clicking the left mouse button over a selectable object while holding the Shift key inverts the selection state of the object. An unselected object is selected, a selected object is deselected.
-
Dragging the left mouse button while a selected object is under the cursor, moves the selected object.
-
Dragging the left mouse button on a handle of a selected object edits the object.
For this tutorial, we stick to the default controller. See Interacting with the view for more information about working with controllers.
Adding a UI widget with available layers
To keep the user of our application informed about the layers that are available on the view,
we will add a widget showing the available layers: a TLcdLayerTree
.
The widget also includes check boxes to toggle the visibility of the individual layers.
TLcdLayerTree
widget
private JComponent createLayerControl(ILcdGXYView aView) {
return new TLcdLayerTree(aView);
}
Once the widget is created, we can add it to our JFrame
:
TLcdLayerTree
widget
JComponent layerControl = createLayerControl(view);
frame.add(layerControl, BorderLayout.EAST);
The full code
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JFrame;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.TLcdCompositeModelDecoder;
import com.luciad.util.service.TLcdServiceLoader;
import com.luciad.view.gxy.ILcdGXYLayer;
import com.luciad.view.gxy.ILcdGXYLayerFactory;
import com.luciad.view.gxy.ILcdGXYView;
import com.luciad.view.gxy.TLcdCompositeGXYLayerFactory;
import com.luciad.view.map.TLcdMapJPanel;
import com.luciad.view.swing.TLcdLayerTree;
public class FirstGXYApplicationTutorial {
public JFrame createUI() {
JFrame frame = new JFrame("First GXY application");
TLcdMapJPanel view = createView();
frame.add(view, BorderLayout.CENTER);
try {
ILcdGXYLayer backgroundLayer = createLayer(createRasterModel());
view.addGXYLayer(backgroundLayer);
ILcdGXYLayer citiesLayer = createLayer(createSHPModel());
view.addGXYLayer(citiesLayer);
} catch (IOException e) {
throw new RuntimeException("Problem during data decoding", e);
}
//Move the grid layer to the top of the view,
//so that it is rendered on top of the other layers
view.moveLayerAt(view.layerCount() - 1, view.getGridLayer());
JComponent layerControl = createLayerControl(view);
frame.add(layerControl, BorderLayout.EAST);
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
private TLcdMapJPanel createView() {
TLcdMapJPanel view = new TLcdMapJPanel();
TLcdCompositeGXYLayerFactory layerFactory =
new TLcdCompositeGXYLayerFactory(TLcdServiceLoader.getInstance(ILcdGXYLayerFactory.class));
//Install the layer factory on the view
//When adding models to the view, this factory is used to create layers for those models
view.setGXYLayerFactory(layerFactory);
return view;
}
private 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 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 ILcdGXYLayer createLayer(ILcdModel aModel) {
TLcdCompositeGXYLayerFactory layerFactory =
new TLcdCompositeGXYLayerFactory(TLcdServiceLoader.getInstance(ILcdGXYLayerFactory.class));
ILcdGXYLayer layer = layerFactory.createGXYLayer(aModel);
if (layer != null) {
return layer;
}
throw new RuntimeException("Could not create a layer for " + aModel.getModelDescriptor().getDisplayName());
}
private JComponent createLayerControl(ILcdGXYView aView) {
return new TLcdLayerTree(aView);
}
public static void main(String[] args) {
//Swing components must be created on the Event Dispatch Thread
EventQueue.invokeLater(() -> {
JFrame frame = new FirstGXYApplicationTutorial().createUI();
frame.setVisible(true);
});
}
}