Goal
In this tutorial, you will learn how to:
-
Allow the user to select objects on the map
-
Receive notifications when the user selects or deselects an object on the map
-
Change the selection through the LuciadLightspeed API
Initial setup
This tutorial starts from an application which shows a Lightspeed view in a JFrame
, and adds a SHP layer to it.
public class SelectionTutorial { final ILspAWTView fView = TLspViewBuilder.newBuilder().buildAWTView(); private ILspLayer fSelectableLayer; public JFrame createUI() throws IOException { JFrame frame = new JFrame("Selection tutorial"); fSelectableLayer = createSelectableLayer(); fView.addLayer(fSelectableLayer); 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 ILspLayer createSelectableLayer() throws IOException { ILcdModelDecoder decoder = new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class)); String countriesFile = "Data/Shp/NaturalEarth/50m_cultural/ne_50m_admin_0_countries_lakes.shp"; ILcdModel model = decoder.decode(countriesFile); TLspLayer layer = TLspShapeLayerBuilder.newBuilder() .model(model) .label("Countries") .build(); return layer; } public static void main(String[] args) { EventQueue.invokeLater(() -> { try { JFrame frame = new SelectionTutorial().createUI(); frame.setVisible(true); } catch (IOException aE) { //In a real application, we would need proper error handling throw new RuntimeException(aE); } }); } }
Manually selecting objects on the map
Like all user interaction with the map, selecting objects happens through a controller.
For Lightspeed maps, the selection controller is the
TLspSelectController
.
This controller allows for the selection of an object on the map when the object is in an ILcdLayer
which:
-
Supports selection through
ILcdLayer.isSelectableSupported
-
Is configured to be selectable, through
ILcdLayer.isSelectable
You can use the controllers in combination with modifier keys, such as the shift key to select multiple objects.
When wou create a Lightspeed view using the TLspViewBuilder
, the default controller installed on the view allows the user to select objects.
If you run the initial code of this tutorial, you can:
-
Left-click on a country to select it
-
Left-click again outside a selected country to de-select it
-
Hold the shift button and drag your mouse to select multiple countries
-
Hold the shift button and left-click on a country to add it to the selection
Listening to changes in the selection
LuciadLightspeed has the ILcdSelection
interface to work with selection.
This interface allows you to:
-
Retrieve the selected objects
-
Checking if a certain object is selected
-
Register
ILcdSelectionListener
instances that will be informed when the selection changes
The ILcdSelectionListener
receives a TLcdSelectionChangedEvent
each time the selection changes.
The event identifies the objects that were selected and de-selected, and provides access to the ILcdSelection
, which is the source of the event.
In this tutorial, we write a listener which prints out some information about the selected elements and the event.
ILcdSelectionListener<Object> selectionListener = new ILcdSelectionListener<Object>() { @Override public void selectionChanged(TLcdSelectionChangedEvent<Object> aSelectionEvent) { ILcdSelection<Object> layer = aSelectionEvent.getSelection(); //Print out some general information about the currently selected elements System.out.println(String.format("Detected selection change in layer %s", ((ILcdLayer) layer).getLabel())); System.out.println(String.format("There are currently %d elements selected in the layer:", layer.getSelectionCount())); List<Object> selectedObjects = layer.getSelectedObjects(); selectedObjects.stream() .map(ILcdDataObject.class::cast)//a SHP layer only contains ILcdDataObject instances .map(dataObject -> " " + dataObject.getValue("ADMIN")) .forEach(System.out::println); //Print out how many elements got selected and deselected in this event long selectedElementCount = aSelectionEvent.getChangedObjects() .stream() .filter(aSelectionEvent::retrieveChange) .count(); long deselectedElementCount = aSelectionEvent.getChangedObjects() .stream() .filter(object -> !aSelectionEvent.retrieveChange(object)) .count(); System.out.println(String.format("This selection change selected %d elements and de-selected %d elements.", selectedElementCount, deselectedElementCount)); } };
Once we have the listener, we attach it to the ILspLayer
, which implements ILcdSelection
, using the ILcdSelection#addSelectionListener
method.
fSelectableLayer.addSelectionListener(selectionListener);
As a result, the listener will print out what we changed after a selection change on the map. For example, if we select Zimbabwe and Zambia in one go, the output is:
Detected selection change in layer Countries There are currently 2 elements selected in the layer: Zimbabwe Zambia This selection change selected 2 elements and de-selected 0 elements.
In this example, we attached the listener directly to the An |
Changing the selection with code
It is also possible to alter the selection programmatically.
This functionality is not provided by the ILcdSelection
interface itself.
That interface provides read-only access to the selection.
However, most implementations of the ILcdSelection
interface expose extra methods that allow you to adjust the selection.
For example, all layers have the com.luciad.view.ILcdLayer.selectObject
method.
We will use this method to select 2 elements in the layer:
private void selectObjects() { ILcdModel model = fSelectableLayer.getModel(); //Accessing the model elements requires a read lock try (TLcdLockUtil.Lock readLock = TLcdLockUtil.readLock(model)) { //Query 2 random elements from the model try (Stream<Object> elementsToSelect = model.query(ILcdModel.all().limit(2))) { //For each of those elements, use the selectObject method to select it //Do not fire events yet elementsToSelect.forEach(element -> fSelectableLayer.selectObject(element, true, ILcdFireEventMode.FIRE_LATER)); } finally { //Now that we have adjusted the selection, fire the event which will inform the listeners about this change fSelectableLayer.fireCollectedSelectionChanges(); } } }
Full code
import java.awt.BorderLayout; import java.awt.EventQueue; import java.io.IOException; import java.util.List; import java.util.stream.Stream; import javax.swing.JFrame; import javax.swing.WindowConstants; import com.luciad.datamodel.ILcdDataObject; import com.luciad.model.ILcdModel; import com.luciad.model.ILcdModelDecoder; import com.luciad.model.TLcdCompositeModelDecoder; import com.luciad.util.ILcdFireEventMode; import com.luciad.util.ILcdSelection; import com.luciad.util.ILcdSelectionListener; import com.luciad.util.TLcdSelectionChangedEvent; import com.luciad.util.concurrent.TLcdLockUtil; import com.luciad.util.service.TLcdServiceLoader; import com.luciad.view.ILcdLayer; import com.luciad.view.lightspeed.ILspAWTView; import com.luciad.view.lightspeed.TLspViewBuilder; 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 SelectionTutorial { final ILspAWTView fView = TLspViewBuilder.newBuilder().buildAWTView(); private ILspLayer fSelectableLayer; public JFrame createUI() throws IOException { JFrame frame = new JFrame("Selection tutorial"); fSelectableLayer = createSelectableLayer(); fView.addLayer(fSelectableLayer); listenForSelectionChanges(); selectObjects(); 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 ILspLayer createSelectableLayer() throws IOException { ILcdModelDecoder decoder = new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class)); String countriesFile = "Data/Shp/NaturalEarth/50m_cultural/ne_50m_admin_0_countries_lakes.shp"; ILcdModel model = decoder.decode(countriesFile); TLspLayer layer = TLspShapeLayerBuilder.newBuilder() .model(model) .label("Countries") .build(); return layer; } private void listenForSelectionChanges() { ILcdSelectionListener<Object> selectionListener = new ILcdSelectionListener<Object>() { @Override public void selectionChanged(TLcdSelectionChangedEvent<Object> aSelectionEvent) { ILcdSelection<Object> layer = aSelectionEvent.getSelection(); //Print out some general information about the currently selected elements System.out.println(String.format("Detected selection change in layer %s", ((ILcdLayer) layer).getLabel())); System.out.println(String.format("There are currently %d elements selected in the layer:", layer.getSelectionCount())); List<Object> selectedObjects = layer.getSelectedObjects(); selectedObjects.stream() .map(ILcdDataObject.class::cast)//a SHP layer only contains ILcdDataObject instances .map(dataObject -> " " + dataObject.getValue("ADMIN")) .forEach(System.out::println); //Print out how many elements got selected and deselected in this event long selectedElementCount = aSelectionEvent.getChangedObjects() .stream() .filter(aSelectionEvent::retrieveChange) .count(); long deselectedElementCount = aSelectionEvent.getChangedObjects() .stream() .filter(object -> !aSelectionEvent.retrieveChange(object)) .count(); System.out.println(String.format("This selection change selected %d elements and de-selected %d elements.", selectedElementCount, deselectedElementCount)); } }; fSelectableLayer.addSelectionListener(selectionListener); } private void selectObjects() { ILcdModel model = fSelectableLayer.getModel(); //Accessing the model elements requires a read lock try (TLcdLockUtil.Lock readLock = TLcdLockUtil.readLock(model)) { //Query 2 random elements from the model try (Stream<Object> elementsToSelect = model.query(ILcdModel.all().limit(2))) { //For each of those elements, use the selectObject method to select it //Do not fire events yet elementsToSelect.forEach(element -> fSelectableLayer.selectObject(element, true, ILcdFireEventMode.FIRE_LATER)); } finally { //Now that we have adjusted the selection, fire the event which will inform the listeners about this change fSelectableLayer.fireCollectedSelectionChanges(); } } } public static void main(String[] args) { EventQueue.invokeLater(() -> { try { JFrame frame = new SelectionTutorial().createUI(); frame.setVisible(true); } catch (IOException aE) { //In a real application, we would need proper error handling throw new RuntimeException(aE); } }); } }