Goal
This tutorial introduces you to the basic operations you can perform on an ILcdModel
.
We will:
-
Create an
ILcdModel
containing the major US cities by decoding a SHP file -
Query that model for some cities, and print out the properties of those cities
-
Modify the properties of a city
-
Make sure we are informed about changes to the elements of a model
Most operations on a model require you to take a lock. This tutorial illustrates how to take the right type of lock, but does not go into detail. Consult the Threading and locking documentation for more information about threads and locks in a LuciadLightspeed application. |
Creating models by decoding data
Most often, an ILcdModel
is created by asking an ILcdModelDecoder
to decode a source of data.
In this tutorial, we create a model by decoding a SHP file through an ILcdModelDecoder
instance.
ILcdModelDecoder modelDecoder =
new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
ILcdModel model = modelDecoder.decode("Data/Shp/Usa/city_125.shp");
The model decoder in this example is a composite instance that can handle all formats supported by our product.
If your data is stored in a custom format, you can always write your own model decoder to decode the data. See Supporting custom formats for more information and tutorials. |
Reading data from a model
We want to find the first 5 cities in the model, and print the city name and population of each of those cities.
Accessing the data of a model requires you to take a read lock on the model. All model reading code should therefore be surrounded by:
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(model)) {
}
You can read a model from any thread. See Threading and locking in LuciadLightspeed for more information about the threading rules in a LuciadLightspeed application.
To access the first 5 cities, we use the query
method.
For our query, we specify that we want the first 5 elements, which results in a java.util.Stream
with those 5 domain objects.
Because our model is a SHP model, we know that the domain objects are ILcdDataObject
instances, and that we can use
the ILcdDataObject
API to access the city name and population:
//Accessing the model data requires a read lock
//Reading, and taking the read lock, can be done on any thread
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(model)) {
//Loop over the domain objects.
//Here we limit us to 5 elements
try (Stream<ILcdDataObject> domainObjects = model.query(ILcdModel.all().limit(5))) {
domainObjects
.forEach(city -> {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
System.out.println(String.format("%s: population %d",
cityName,
population));
});
}
}
Running this results in the following output:
Akron: population 223019 Albuquerque: population 384736 Amarillo: population 157615 Anaheim: population 266406 Anchorage: population 226338
Operations that require a read lock include:
-
Model operations like
elements
,query
orapplyOnInteract2DBounds
-
Element operations such as looping over points of a polyline when the polyline is part of an
ILcdModel
Simply looping over all the elements of a model might trigger the loading of a lot of data. For example, in the case of a database model, the request might start a loop over the whole database. Therefore, it is recommended never to request all elements from a model, unless you know that the number of elements is limited. |
Changing data in a model
The ILcdModel
interface allows you to add and remove elements to and from a model using the addElement
and removeElement
methods.
The interface also provides access to the domain objects, allowing you to modify the domain objects.
In this case, we plan to change the population of the first city. To do so, we are going to access the model data,
and use the ILcdDataObject
API.
When you are modifying the contents of a model, you need to take a write lock on the model. All such code must be surrounded by:
try (TLcdLockUtil.Lock lock = TLcdLockUtil.writeLock(model)) {
} finally {
model.fireCollectedModelChanges();
}
It is also required to make the model inform all interested parties about the changes to its content. That is why there is
a call to fireCollectedModelChanges
in the finally
block. See the next section in this tutorial for more details.
You can take read locks on any thread, but you can take write locks on one thread only. In most applications, that thread is the toolkit thread: the Event Dispatch Thread (EDT) for Swing or the JavaFX Application Thread. See Threading and locking in LuciadLightspeed for more information about the threading rules in a LuciadLightspeed application.
To modify the population of the first city, we:
-
Take the write lock
-
Access the first city, using the
query
method -
Update the population using the
ILcdDatObject
API -
Inform the model that we have changed that particular domain object by calling the
elementChanged
method. We do that so that the model can inform its listeners whenfireCollectedModelChanged
is called in thefinally
block.
//Modifying a model requires a write lock.
//In a real application this can only be done on a specific thread.
//See the threading guidelines for more information
try (TLcdLockUtil.Lock lock = TLcdLockUtil.writeLock(model)) {
//Dummy operation for illustration purposes:
//Take the first city from the model, and increase the population by one
try (Stream<ILcdDataObject> cities = model.query(ILcdModel.all().limit(1))) {
cities.forEach(city -> {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
city.setValue("TOT_POP", population + 1);
System.out.println(String.format("Population of city %s has been updated in the model",
cityName));
//We need to inform the ILcdModel that the element has been changed
//Do not yet fire an event to the listener as we are still locking the model
//Firing the event from inside the lock will result in deadlocks
model.elementChanged(city, ILcdModel.FIRE_LATER);
});
}
} finally {
System.out.println("Informing the listeners about the changes in the model");
//Right after releasing the lock, let the model inform its listeners about the changes
model.fireCollectedModelChanges();
}
Operations that require a write lock include:
-
Model operations, such as
addElement
orelementChanged
-
Element operations, such as adding points to a polyline
Always call the |
Listening for changes in a model
Each time the data in an ILcdModel
is changed, the ILcdModel
informs its ILcdModelListener
instances about the changes.
This can be useful when you have a UI that shows the properties of a domain object, for example, and the user changes the
position of that domain object on the map.
You need to update the UI with the new position, and you can do this in an ILcdModelListener
implementation that you register with the ILcdModel
.
In this tutorial, we implement an ILcdModelListener
that listens for changes made to the cities in our model.
When a city is changed, we print the city name and population.
Our implementation could look like:
//Creating an ILcdModelListener which will be informed when changes are made
ILcdModelListener modelListener = new ILcdModelListener() {
@Override
public void modelChanged(TLcdModelChangedEvent aEvent) {
//The TLcdModelChangedEvent is a container object describing which elements were added/removed/changed
//
//Here we are only interested in changes to an element
//See the class javadoc of TLcdModelChangedEvent for code which handles all cases
//
//As we are going to access properties of the domain objects, we need to take a lock
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(aEvent.getModel())) {
Enumeration elements = aEvent.elements();
while (elements.hasMoreElements()) {
ILcdDataObject city = (ILcdDataObject) elements.nextElement();
int change = aEvent.retrieveChange(city);
if (change == TLcdModelChangedEvent.OBJECT_CHANGED) {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
System.out.println(String.format("Listener has been informed about change to city %s. Population: %d",
cityName,
population));
}
}
}
}
};
and you register it with the ILcdModel
using the addModelListener
method:
model.addModelListener(modelListener);
If we now make a change to the model, as described in the Changing data in a model section, the output is:
Population of city Akron has been updated in the model Informing the listeners about the changes in the model Listener has been informed about change to city Akron. Population: 223020
Full code
import java.io.IOException;
import java.util.Enumeration;
import java.util.stream.Stream;
import com.luciad.datamodel.ILcdDataObject;
import com.luciad.model.ILcdModel;
import com.luciad.model.ILcdModelDecoder;
import com.luciad.model.ILcdModelListener;
import com.luciad.model.TLcdCompositeModelDecoder;
import com.luciad.model.TLcdModelChangedEvent;
import com.luciad.util.concurrent.TLcdLockUtil;
import com.luciad.util.service.TLcdServiceLoader;
public class WorkingWithModelsTutorial {
public static void main(String[] args) throws IOException {
ILcdModelDecoder modelDecoder =
new TLcdCompositeModelDecoder(TLcdServiceLoader.getInstance(ILcdModelDecoder.class));
ILcdModel model = modelDecoder.decode("Data/Shp/Usa/city_125.shp");
//Accessing the model data requires a read lock
//Reading, and taking the read lock, can be done on any thread
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(model)) {
//Loop over the domain objects.
//Here we limit us to 5 elements
try (Stream<ILcdDataObject> domainObjects = model.query(ILcdModel.all().limit(5))) {
domainObjects
.forEach(city -> {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
System.out.println(String.format("%s: population %d",
cityName,
population));
});
}
}
System.out.println();
//Creating an ILcdModelListener which will be informed when changes are made
ILcdModelListener modelListener = new ILcdModelListener() {
@Override
public void modelChanged(TLcdModelChangedEvent aEvent) {
//The TLcdModelChangedEvent is a container object describing which elements were added/removed/changed
//
//Here we are only interested in changes to an element
//See the class javadoc of TLcdModelChangedEvent for code which handles all cases
//
//As we are going to access properties of the domain objects, we need to take a lock
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(aEvent.getModel())) {
Enumeration elements = aEvent.elements();
while (elements.hasMoreElements()) {
ILcdDataObject city = (ILcdDataObject) elements.nextElement();
int change = aEvent.retrieveChange(city);
if (change == TLcdModelChangedEvent.OBJECT_CHANGED) {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
System.out.println(String.format("Listener has been informed about change to city %s. Population: %d",
cityName,
population));
}
}
}
}
};
model.addModelListener(modelListener);
//Modifying a model requires a write lock.
//In a real application this can only be done on a specific thread.
//See the threading guidelines for more information
try (TLcdLockUtil.Lock lock = TLcdLockUtil.writeLock(model)) {
//Dummy operation for illustration purposes:
//Take the first city from the model, and increase the population by one
try (Stream<ILcdDataObject> cities = model.query(ILcdModel.all().limit(1))) {
cities.forEach(city -> {
String cityName = (String) city.getValue("CITY");
Integer population = (Integer) city.getValue("TOT_POP");
city.setValue("TOT_POP", population + 1);
System.out.println(String.format("Population of city %s has been updated in the model",
cityName));
//We need to inform the ILcdModel that the element has been changed
//Do not yet fire an event to the listener as we are still locking the model
//Firing the event from inside the lock will result in deadlocks
model.elementChanged(city, ILcdModel.FIRE_LATER);
});
}
} finally {
System.out.println("Informing the listeners about the changes in the model");
//Right after releasing the lock, let the model inform its listeners about the changes
model.fireCollectedModelChanges();
}
}
}