Goal
At the end of this tutorial, you should understand the essential concepts of the data modeling API and be familiar with its basic use.
The data modeling API is a framework that allows access to the properties of domain objects, the elements in an ILcdModel
, in a uniform way.
This offers several benefits:
-
You need to learn just one API to read or modify the properties of all kinds of domain objects.
For example, to get the name of a city from a SHP file, you can use the same code as you would use to find the name of an airport in the AIXM5 format.
So, not only does this provide you with a single code path; it also allows you to write a generic UI, for example, to display the properties of domain objects of all formats.
-
Classes designed to work with this API work with data of all formats.
For example, you can use the same label painter to visualize labels on the map, next to the geometry of the domain objects. The label painters use the data modeling API to retrieve the label text from the domain object.
-
If you need to support a custom format, you give LuciadLighspeed access to the properties of your domain objects by implementing this API. That way, you ensure that all classes, like the label painter from the previous example, can deal with your data.
Overview of the main classes
We introduce the main classes with an example. Suppose we have a model with airport data.
Each airport has:
-
A name represented with a String
-
A location. To keep things simple, we model the location as a point with a longitude and latitude.
-
A collection of runways
A runway has:
-
An identifier, represented with an integer
-
A length, represented with a double
The concepts in the data modeling API are very similar to Java concepts. If we wanted to model the above in Java code, we could use:
public class Airport{
private String name;
private TLcdLonLatPoint location;//TLcdLonLatPoint is a point with a longitude and latitude
private List<Runway> runways;
//getters and setters for the properties
}
public class Runway{
private int identifier;
private double length;
//getters and setters for the properties
}
The data modeling API consists of:
-
com.luciad.datamodel.TLcdDataType
, which is the equivalent of a class in Java. -
com.luciad.datamodel.TLcdDataProperty
, which is the equivalent of a field in Java. -
com.luciad.datamodel.ILcdDataObject
, which is the equivalent of an object instance in Java.
The relationship between those 3 classes is:
-
Each
TLcdDataType
can have a number of properties, just like a Java class can have a number of fields. TheTLcdDataType
class offers API to list all the available properties. -
Each
TLcdDataProperty
is of a certainTLcdDataType
, just like a Java field is of a certain class. TheTLcdDataProperty
class offers API to discover the type of the property. -
Each
TLcdDataProperty
can indicate whether the property has a single value, or a collection of values. -
Each
ILcdDataObject
instance is of a certainTLcdDataType
, just like a Java object is of a certain class.
Therefore, we represent the airport example with this TLcdDataType
structure:
Airport: is a TLcdDataType
|_ name: is a TLcdDataProperty
|_ location: is as TLcdDataProperty
|_ runways: is as TLcdDataProperty, indicating it is a collection
Runway: is a TLcdDataType
|_ identifier: is a TLcdDataProperty
|_ length: is a TLcdDataProperty
An ILcdModel
containing airports is populated with ILcdDataObject
instances of the Airport
data type.
The ILcdDataObject
interface exposes that the object is of the Airport
data type so that you know which properties are available for the object. On top of that,
it provides access to those properties.
This is the equivalent of providing getters and setters for the properties of a Java object.
For example, obtaining the name of an airport looks like:
ILcdDataObject airport = (ILcdDataObject)model.elements().next();
String airportName = airport.getValue("name");
The usage of these getters and setters is illustrated in more detail later on in this tutorial.
Relationship between TLcdDataModel and ILcdModel
Before you can use this API to access the properties of the data in your ILcdModel
, you need to know:
-
Whether the model implemented this API
-
The object type of each domain object
To find out, you need to check whether the ILcdModelDescriptor
of the ILcdModel
implements the com.luciad.model.ILcdDataModelDescriptor
interface.
If it does, you can:
-
Use
ILcdDataModelDescriptor.getDataModel()
to obtain acom.luciad.datamodel.TLcdDataModel
. ATLcdDataModel
is a container for all usedTLcdDataType
instances.In this example, the data model would include the
Airport
andRunway
types. So, when theAirport
type declares that it has a property which is a collection ofRunway
, you know what aRunway
is, because it is declared in that sameTLcdDataModel
.If we compare this with Java code again: there could be multiple
Runway
class definitions in the code base, but by using an import in theAirport
class, you define exactly whichRunway
yourAirport
has.The same principle applies to
TLcdDataModel
. It groups all usedTLcdDataType
instances so that there can be no confusion. -
Safely cast each domain object in the model to
com.luciad.datamodel.ILcdDataObject
, which is the interface needed to know what properties the object has and to access the values of the properties.
To make this all sound a bit less abstract, we will use all these concepts to create an ILcdModel
with airports in the next section,
and then use the ILcdDataObject
API to print the properties of all the objects.
Bringing it into practice
As an exercise, we will create an ILcdModel
with two airports:
-
Chicago O’Hare airport:
-
Name: Chicago O’Hare
-
Location: 41.9742° N, 87.9073° W
-
Runways:
-
Runway 1 with length 1829 metres
-
Runway 2 with length 2438 metres
-
-
-
LaGuardia airport:
-
Name: LaGuardia
-
Location: 40.7769° N, 73.8740° W
-
Runways:
-
Runway 1 with length 1957 metres
-
Runway 2 with length 2367 metres
-
-
For the TLcdDataModel
, we use the one described in The airport data model.
This tutorial also contains the code to create the Creating your own |
Querying the data model of an ILcdModel
Querying the TLcdDataModel
of an ILcdModel
allows you to learn about:
-
What types of domain objects the
ILcdModel
contains -
What properties are available in the domain objects
-
What the type of those properties is
This is done through the ILcdDataModelDescriptor
.
You can use this descriptor to access the data model and check which types are used in the model.
These snippets illustrate a few possible usages of the API:
//Check what the type of the domain objects is
Set<TLcdDataType> modelElementTypes = aDataModelDescriptor.getModelElementTypes();
System.out.println("The model contains elements of the following types:");
modelElementTypes.stream()
.map(TLcdDataType::getDisplayName)
.sorted()
.forEach(System.out::println);
resulting for our example data model in
The model contains elements of the following types:
Airport
TLcdDataType
instances are that are defined in the TLcdDataModel
//Check what types the data model contains
Set<TLcdDataType> allDefinedTypes = dataModel.getDeclaredTypes();
System.out.println(String.format("The TLcdDataModel [%s] defines the following types", dataModel.getDisplayName()));
allDefinedTypes.stream()
.map(TLcdDataType::getDisplayName)
.sorted()
.forEach(System.out::println);
resulting in
The TLcdDataModel [http://www.luciad.com/datamodel/tutorial/Airport] defines the following types
Airport
Runway
TLcdDataType
//Check which properties the Airport type contains
TLcdDataType airportType = dataModel.getDeclaredType("Airport");
System.out.println("The Airport type has the following properties:");
airportType.getProperties().stream()
.map(property -> String.format("Property [%s] of type [%s] (collection: [%s])",
property.getDisplayName(),
property.getType().getDisplayName(),
property.isCollection()))
.sorted()
.forEach(System.out::println);
resulting in
The Airport type has the following properties:
Property [location] of type [Shape] (collection: [false])
Property [name] of type [String] (collection: [false])
Property [runways] of type [Runway] (collection: [true])
Accessing the property values of a domain object
All domain objects in our model are ILcdDataObject
instances.
This interface allows you to:
-
Determine the type of domain object, using
ILcdDataObject.getDataType()
-
Read the value of a property using
ILcdDataObject.getValue
-
Change the value of a property using
ILcdDataObject.setValue
Combine that with what we learned in the previous section about looping over the properties of a TLcdDataType
and we can now print all the properties of our domain objects in a generic way.
public static void printDomainObjectProperties(ILcdModel aModel) {
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(aModel)) {
Enumeration elements = aModel.elements();
while (elements.hasMoreElements()) {
ILcdDataObject airport = (ILcdDataObject) elements.nextElement();
String name = (String) airport.getValue("name");//if you know the property names, you can access them directly
System.out.println(name);
//We can also use the information contained in TLcdDataType
//to loop over the properties
//
//This can for example be used to create a tree UI
//displaying all properties of an object
//
//In this example, we limit ourself to printing
//out some information about the airport
printProperties(airport);
System.out.println();
}
}
}
private static void printProperties(ILcdDataObject aDataObject) {
TLcdDataType dataType = aDataObject.getDataType();
List<TLcdDataProperty> properties = dataType.getProperties();
for (TLcdDataProperty property : properties) {
TLcdDataType propertyType = property.getType();
//When the property is a primitive (e.g. a string, number, ...) we can
//print it directly
if (propertyType.isPrimitive()) {
System.out.println(String.format("%s: %s", property.getDisplayName(), aDataObject.getValue(property)));
}
//If the property is an ILcdDataObject itself, we use recursion
else if (propertyType.isDataObjectType()) {
System.out.println(String.format("%s (Type: %s)", property.getDisplayName(), propertyType.getDisplayName()));
if (!property.isCollection()) {
printProperties((ILcdDataObject) aDataObject.getValue(property));
} else {
Collection<ILcdDataObject> collection = (Collection<ILcdDataObject>) aDataObject.getValue(property);
int counter = 0;
for (ILcdDataObject dataObject : collection) {
System.out.println("Index: [" + counter++ + "]");
printProperties(dataObject);
}
}
}
}
}
resulting in
Chicago O'Hare
name: Chicago O'Hare
location: com.luciad.shape.shape2D.TLcdLonLatPoint(87.9073, 41.9742)
runways (Type: Runway)
Index: [0]
identifier: 1
length: 1829.0
Index: [1]
identifier: 2
length: 2438.0
LaGuardia
name: LaGuardia
location: com.luciad.shape.shape2D.TLcdLonLatPoint(73.874, 40.7769)
runways (Type: Runway)
Index: [0]
identifier: 1
length: 1957.0
Index: [1]
identifier: 2
length: 2367.0
Creating the TLcdDataModel and ILcdModel
This section is only relevant if you need to create your own |
We start by defining the TLcdDataModel
including a TLcdDataType
for the airports and runways.
Creating a data model requires the use of the TLcdDataModelBuilder
class:
TLcdDataModel
//Use the builder to create the TLcdDataModel
//The builder is created with a unique name for the TLcdDataModel
//You can compare this with a Java package name
TLcdDataModelBuilder builder =
new TLcdDataModelBuilder("http://www.luciad.com/datamodel/tutorial/Airport");
//We start with defining the runway type
TLcdDataTypeBuilder runwayTypeBuilder = builder.typeBuilder("Runway");
//Add the properties and specify their type
//Primitive types like strings, integers, doubles, ... are available in TLcdCoreDataTypes
runwayTypeBuilder.addProperty("identifier", TLcdCoreDataTypes.INTEGER_TYPE);
runwayTypeBuilder.addProperty("length", TLcdCoreDataTypes.DOUBLE_TYPE);
//Define the Airport Type
TLcdDataTypeBuilder airportTypeBuilder = builder.typeBuilder("Airport");
airportTypeBuilder.addProperty("name", TLcdCoreDataTypes.STRING_TYPE);
airportTypeBuilder.addProperty("location", TLcdShapeDataTypes.SHAPE_TYPE);//indicate location is an ILcdShape
airportTypeBuilder.addProperty("runways", runwayTypeBuilder)//same for runways
.collectionType(TLcdDataProperty.CollectionType.LIST);//indicate this property is actually a List of runways
//Create the TLcdDataModel instance
TLcdDataModel dataModel = builder.createDataModel();
Once we have the TLcdDataModel
, we need to:
-
Create the
ILcdModel
. -
Create and install the
ILcdModelDescriptor
. Because we want to indicate to LuciadLightspeed that our model uses the data modeling API, our model descriptor must be an implementation ofILcdDataModelDescriptor
. -
Create the two airports, and add them to the model.
ILcdModel
//Retrieve the types from the created data model
//These are needed to create the actual domain objects
TLcdDataType airportType = dataModel.getDeclaredType("Airport");
TLcdDataType runwayType = dataModel.getDeclaredType("Runway");
//Create the two airports domain objects
//The domain objects must be ILcdDataObject instances of the correct type,
//so we use the available method on the data type.
//Afterwards, we use the ILcdDataObject API to populate the properties
ILcdDataObject chicagoAirport = airportType.newInstance();//this creates an airport ILcdDataObject
chicagoAirport.setValue("name", "Chicago O'Hare");//fill in the values for each of the properties
chicagoAirport.setValue("location", new TLcdLonLatPoint(87.9073, 41.9742));
ILcdDataObject newYorkAirport = airportType.newInstance();
newYorkAirport.setValue("name", "LaGuardia");
newYorkAirport.setValue("location", new TLcdLonLatPoint(73.8740, 40.7769));
//The runways are ILcdDataObjects themselfs, but of the runwayType
//Create them, and add them to the airports
ILcdDataObject chicagoRunway1 = runwayType.newInstance();
chicagoRunway1.setValue("identifier", 1);
chicagoRunway1.setValue("length", 1829d);
ILcdDataObject chicagoRunway2 = runwayType.newInstance();
chicagoRunway2.setValue("identifier", 2);
chicagoRunway2.setValue("length", 2438d);
ILcdDataObject newYorkRunway1 = runwayType.newInstance();
newYorkRunway1.setValue("identifier", 1);
newYorkRunway1.setValue("length", 1957d);
ILcdDataObject newYorkRunway2 = runwayType.newInstance();
newYorkRunway2.setValue("identifier", 2);
newYorkRunway2.setValue("length", 2367d);
chicagoAirport.setValue("runways", Arrays.asList(chicagoRunway1, chicagoRunway2));
newYorkAirport.setValue("runways", Arrays.asList(newYorkRunway1, newYorkRunway2));
//Create the ILcdModel with the two airports
TLcdVectorModel model = new TLcdVectorModel(new TLcdGeodeticReference());
model.addElement(chicagoAirport, ILcdModel.NO_EVENT);
model.addElement(newYorkAirport, ILcdModel.NO_EVENT);
//Create the model descriptor and install it on the model
ILcdDataModelDescriptor modelDescriptor =
new TLcdDataModelDescriptor(null,//No source name, as the model is created in code
"AIRPORT_TYPE", //Invent a type name
"Airports", //A nice display name
dataModel,//Specify what the datamodel is of the model
Collections.singleton(airportType),//specify that all domain objects are airports
null);
model.setModelDescriptor(modelDescriptor);
Our ILcdModel
is as good as done.
We just need to tweak it slightly to show the data on the map.
If we want to show the data on a map, we need to tell LuciadLightspeed where it can find the geometry of the domain object.
In this example, we need to point it to the location
property of the airport.
We do so by adding a com.luciad.datamodel.ILcdAnnotation
to the airport TLcdDataType
, telling LuciadLightspeed that it can find the geometry for the airport type in the location property:
//If we want to visualize our model on a map,
//we need to indicate to LuciadLightspeed what the main geometry is of our domain object
//This is done by adding an annotation on the airport type (=the type of our domain objects)
//that specifies that the location property holds the geometry of the airport that should
//be used when visualizing the data on the map
TLcdDataProperty locationProperty = airportType.getProperty("location");
airportType.addAnnotation(new TLcdHasGeometryAnnotation(locationProperty));
The TLcdHasGeometryAnnotation
is the most commonly used annotation.
To find out the other available annotations, see the Javadoc with the implementations of the
ILcdAnnotation
interface.
If necessary, you can also create and use custom annotations, as explained in the reference guide.
Full code
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import com.luciad.datamodel.ILcdDataObject;
import com.luciad.datamodel.TLcdCoreDataTypes;
import com.luciad.datamodel.TLcdDataModel;
import com.luciad.datamodel.TLcdDataModelBuilder;
import com.luciad.datamodel.TLcdDataProperty;
import com.luciad.datamodel.TLcdDataType;
import com.luciad.datamodel.TLcdDataTypeBuilder;
import com.luciad.model.ILcdDataModelDescriptor;
import com.luciad.model.ILcdModel;
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.util.concurrent.TLcdLockUtil;
public class DataModelTutorial {
public static ILcdModel createModel() {
//Use the builder to create the TLcdDataModel
//The builder is created with a unique name for the TLcdDataModel
//You can compare this with a Java package name
TLcdDataModelBuilder builder =
new TLcdDataModelBuilder("http://www.luciad.com/datamodel/tutorial/Airport");
//We start with defining the runway type
TLcdDataTypeBuilder runwayTypeBuilder = builder.typeBuilder("Runway");
//Add the properties and specify their type
//Primitive types like strings, integers, doubles, ... are available in TLcdCoreDataTypes
runwayTypeBuilder.addProperty("identifier", TLcdCoreDataTypes.INTEGER_TYPE);
runwayTypeBuilder.addProperty("length", TLcdCoreDataTypes.DOUBLE_TYPE);
//Define the Airport Type
TLcdDataTypeBuilder airportTypeBuilder = builder.typeBuilder("Airport");
airportTypeBuilder.addProperty("name", TLcdCoreDataTypes.STRING_TYPE);
airportTypeBuilder.addProperty("location", TLcdShapeDataTypes.SHAPE_TYPE);//indicate location is an ILcdShape
airportTypeBuilder.addProperty("runways", runwayTypeBuilder)//same for runways
.collectionType(TLcdDataProperty.CollectionType.LIST);//indicate this property is actually a List of runways
//Create the TLcdDataModel instance
TLcdDataModel dataModel = builder.createDataModel();
//Retrieve the types from the created data model
//These are needed to create the actual domain objects
TLcdDataType airportType = dataModel.getDeclaredType("Airport");
TLcdDataType runwayType = dataModel.getDeclaredType("Runway");
//Create the two airports domain objects
//The domain objects must be ILcdDataObject instances of the correct type,
//so we use the available method on the data type.
//Afterwards, we use the ILcdDataObject API to populate the properties
ILcdDataObject chicagoAirport = airportType.newInstance();//this creates an airport ILcdDataObject
chicagoAirport.setValue("name", "Chicago O'Hare");//fill in the values for each of the properties
chicagoAirport.setValue("location", new TLcdLonLatPoint(87.9073, 41.9742));
ILcdDataObject newYorkAirport = airportType.newInstance();
newYorkAirport.setValue("name", "LaGuardia");
newYorkAirport.setValue("location", new TLcdLonLatPoint(73.8740, 40.7769));
//The runways are ILcdDataObjects themselfs, but of the runwayType
//Create them, and add them to the airports
ILcdDataObject chicagoRunway1 = runwayType.newInstance();
chicagoRunway1.setValue("identifier", 1);
chicagoRunway1.setValue("length", 1829d);
ILcdDataObject chicagoRunway2 = runwayType.newInstance();
chicagoRunway2.setValue("identifier", 2);
chicagoRunway2.setValue("length", 2438d);
ILcdDataObject newYorkRunway1 = runwayType.newInstance();
newYorkRunway1.setValue("identifier", 1);
newYorkRunway1.setValue("length", 1957d);
ILcdDataObject newYorkRunway2 = runwayType.newInstance();
newYorkRunway2.setValue("identifier", 2);
newYorkRunway2.setValue("length", 2367d);
chicagoAirport.setValue("runways", Arrays.asList(chicagoRunway1, chicagoRunway2));
newYorkAirport.setValue("runways", Arrays.asList(newYorkRunway1, newYorkRunway2));
//Create the ILcdModel with the two airports
TLcdVectorModel model = new TLcdVectorModel(new TLcdGeodeticReference());
model.addElement(chicagoAirport, ILcdModel.NO_EVENT);
model.addElement(newYorkAirport, ILcdModel.NO_EVENT);
//Create the model descriptor and install it on the model
ILcdDataModelDescriptor modelDescriptor =
new TLcdDataModelDescriptor(null,//No source name, as the model is created in code
"AIRPORT_TYPE", //Invent a type name
"Airports", //A nice display name
dataModel,//Specify what the datamodel is of the model
Collections.singleton(airportType),//specify that all domain objects are airports
null);
model.setModelDescriptor(modelDescriptor);
//If we want to visualize our model on a map,
//we need to indicate to LuciadLightspeed what the main geometry is of our domain object
//This is done by adding an annotation on the airport type (=the type of our domain objects)
//that specifies that the location property holds the geometry of the airport that should
//be used when visualizing the data on the map
TLcdDataProperty locationProperty = airportType.getProperty("location");
airportType.addAnnotation(new TLcdHasGeometryAnnotation(locationProperty));
return model;
}
public static void printTypes(ILcdDataModelDescriptor aDataModelDescriptor) {
TLcdDataModel dataModel = aDataModelDescriptor.getDataModel();
//Check what the type of the domain objects is
Set<TLcdDataType> modelElementTypes = aDataModelDescriptor.getModelElementTypes();
System.out.println("The model contains elements of the following types:");
modelElementTypes.stream()
.map(TLcdDataType::getDisplayName)
.sorted()
.forEach(System.out::println);
System.out.println();
//Check what types the data model contains
Set<TLcdDataType> allDefinedTypes = dataModel.getDeclaredTypes();
System.out.println(String.format("The TLcdDataModel [%s] defines the following types", dataModel.getDisplayName()));
allDefinedTypes.stream()
.map(TLcdDataType::getDisplayName)
.sorted()
.forEach(System.out::println);
System.out.println();
//Check which properties the Airport type contains
TLcdDataType airportType = dataModel.getDeclaredType("Airport");
System.out.println("The Airport type has the following properties:");
airportType.getProperties().stream()
.map(property -> String.format("Property [%s] of type [%s] (collection: [%s])",
property.getDisplayName(),
property.getType().getDisplayName(),
property.isCollection()))
.sorted()
.forEach(System.out::println);
System.out.println();
}
public static void printDomainObjectProperties(ILcdModel aModel) {
try (TLcdLockUtil.Lock lock = TLcdLockUtil.readLock(aModel)) {
Enumeration elements = aModel.elements();
while (elements.hasMoreElements()) {
ILcdDataObject airport = (ILcdDataObject) elements.nextElement();
String name = (String) airport.getValue("name");//if you know the property names, you can access them directly
System.out.println(name);
//We can also use the information contained in TLcdDataType
//to loop over the properties
//
//This can for example be used to create a tree UI
//displaying all properties of an object
//
//In this example, we limit ourself to printing
//out some information about the airport
printProperties(airport);
System.out.println();
}
}
}
private static void printProperties(ILcdDataObject aDataObject) {
TLcdDataType dataType = aDataObject.getDataType();
List<TLcdDataProperty> properties = dataType.getProperties();
for (TLcdDataProperty property : properties) {
TLcdDataType propertyType = property.getType();
//When the property is a primitive (e.g. a string, number, ...) we can
//print it directly
if (propertyType.isPrimitive()) {
System.out.println(String.format("%s: %s", property.getDisplayName(), aDataObject.getValue(property)));
}
//If the property is an ILcdDataObject itself, we use recursion
else if (propertyType.isDataObjectType()) {
System.out.println(String.format("%s (Type: %s)", property.getDisplayName(), propertyType.getDisplayName()));
if (!property.isCollection()) {
printProperties((ILcdDataObject) aDataObject.getValue(property));
} else {
Collection<ILcdDataObject> collection = (Collection<ILcdDataObject>) aDataObject.getValue(property);
int counter = 0;
for (ILcdDataObject dataObject : collection) {
System.out.println("Index: [" + counter++ + "]");
printProperties(dataObject);
}
}
}
}
}
public static void main(String[] args) {
ILcdModel model = createModel();
printTypes((ILcdDataModelDescriptor) model.getModelDescriptor());
printDomainObjectProperties(model);
}
}