User guide
Lindorm GanosBase provides a Java API compatible with GeoMesa for storing and querying spatiotemporal trajectory data. This tutorial walks through three tasks using a trajectory line segmentation model:
-
Create a trajectory table — configure a point model, build a line model, add a secondary index, and call
createSchema. -
Write trajectory data — parse raw points, apply a segmentation policy, and batch-write trajectory segments.
-
Query trajectories — build Common Query Language (CQL) filters and retrieve either matching segments or complete trajectories.
Prerequisites
Before you begin, ensure that you have:
-
A Lindorm GanosBase instance
-
A
DataStoreconnection — see Getting started for setup instructions
Key concepts
Trajectory point model — Each trajectory point is a spatiotemporal object with attributes. Define the point model first; the line model is built on top of it. In GeoTools terms, SimpleFeatureType defines the schema (similar to a table definition in a relational database), and SimpleFeature is a single data record (equivalent to a row).
Trajectory line segmentation model — A derived model that groups trajectory points into segments. TrajectoryFeatureType includes default attribute fields that cannot be removed; add extra fields with addTrajProperties.
Create a table
The steps below create a trajectory table with a primary index on id and a secondary spatiotemporal index on xz2t.
Step 1: Define the trajectory point model.
String pointSpec = "id:String,dtg:Date,*geom:Point:srid=4326";
SimpleFeatureType pointSFT = SimpleFeatureTypes.createType("point", pointSpec);
Each trajectory point has an ID, a timestamp (dtg), and a geometry (geom). The asterisk (*) marks geom as the default geometry column.
Step 2: Build the trajectory line model.
TrajectoryBuilder trajBuilder = new TrajectoryBuilder("trajectory", pointSFT, "dtg", "id", true);
TrajectoryFeatureType trajFeatureType = trajBuilder.getTrajFeatureType();
TrajectoryBuilder takes the point model (pointSFT), the timestamp field (dtg), and the ID field (id). To add custom attribute fields to each trajectory segment, call addTrajProperties before calling getTrajFeatureType:
TrajectoryBuilder trajBuilder = new TrajectoryBuilder("trajectory", pointSFT, "dtg", "id", true);
trajBuilder.addTrajProperties("speed:Double,traj_end_time:Date");
TrajectoryFeatureType trajFeatureType = trajBuilder.getTrajFeatureType();
For the full list of default fields, see Terms.
Step 3: Configure the secondary index.
trajFeatureType.getUserData().put("geomesa.secondary.enabled", "true");
trajFeatureType.getUserData().put("geomesa.primary.index", "id");
trajFeatureType.getUserData().put("geomesa.indices.enabled", "id,xz2t");
The following table describes the index configuration keys:
| Key | Value in this example | Effect |
|---|---|---|
geomesa.secondary.enabled |
true |
Enables secondary index creation |
geomesa.primary.index |
id |
Sets id as the index key column of the primary table |
geomesa.indices.enabled |
id,xz2t |
Creates an id index and an xz2t spatiotemporal index |
The following table describes the supported index types:
| Index | Algorithm | Best for |
|---|---|---|
id |
Feature ID as primary key | Point-in-time lookups by object ID |
xz2t |
Three-dimensional space-filling curve (latitude, longitude, time) for non-point geometries | Spatiotemporal range queries on trajectory segments |
Step 4: Create the table.
ds.createSchema(trajFeatureType);
This creates both the primary table and the secondary index table. The secondary index table is managed internally by Lindorm and is not visible to users.
Write trajectory data
Step 1: Parse trajectory points from raw data.
public SimpleFeature createFeature(SimpleFeatureType sft, String id, Object... values) {
SimpleFeatureBuilder sfBuilder = new SimpleFeatureBuilder(sft);
SimpleFeature simpleFeature = sfBuilder.buildFeature(id, values);
return simpleFeature;
}
List<SimpleFeature> points = new ArrayList<>();
for (String pointData : rawData) {
// Parse id and point values from pointData
String pointID = ...;
Object[] values = ...;
points.add(createFeature(pointSFT, pointID, values));
}
Step 2: Define a segmentation policy.
SplitConfigs splitConfigs = new SplitConfigs(SplitConfigs.BY_COUNT(), new Object[]{50});
This policy groups every 50 trajectory points into one trajectory segment.
Step 3: Create trajectory segments.
String objectID = "001";
List<SimpleFeature> trajFeatures = trajBuilder.createTrajSegments(objectID, features, splitConfigs);
If you called addTrajProperties when building the model, pass an extraTrajProperties array to createTrajSegments with values for every added field.
Object[] extraTrajProperties = new Object[]{speed, trajEndTime};
List<SimpleFeature> trajFeatures = trajBuilder.createTrajSegments(objectID, features, splitConfigs, extraTrajProperties);
Step 4: Batch-write trajectory segments.
SimpleFeatureStore featureStore = (SimpleFeatureStore) ds.getFeatureSource(trajFeatureType.getTypeName());
featureStore.addFeatures(new ListFeatureCollection(trajFeatureType, trajFeatures));
Query trajectories
Lindorm GanosBase accepts CQL predicates compatible with GeoMesa. Build a filter with ECQL.toFilter, wrap it in a Query, and call the appropriate method on LindormDataStore. For the full CQL syntax reference, see GanosQuery spatiotemporal objects.
The following table summarizes the supported query types:
| Query type | Example predicate |
|---|---|
| ID and point in time | object_id in ('001') and seg_start_time > 1549522511 |
| Attribute query | speed > 2.0 — for a trajectory point model, queries point attributes; for a trajectory line model, queries trajectory-level and segment-level attributes only |
| Spatial range query | INTERSECTS(geom, POLYGON((-78 26,-73 26,-73 31,-78 31,-78 26))) |
| Spatiotemporal range query | INTERSECTS(geom, POLYGON((-78 26,-73 26,-73 31,-78 31,-78 26))) AND traj_start_time >= 1549510271 AND traj_start_time <= 1549582040 |
ID and point-in-time query
Use getFeatures with an ID and timestamp predicate to retrieve matching segments:
String ecqlPredicate = "object_id in ('001') and seg_start_time > 1549522511";
Query query = new Query("trajectory", ECQL.toFilter(ecqlPredicate));
SimpleFeatureCollection featureCollection =
((LindormDataStore) ds).getFeatureSource("trajectory").getFeatures(query);
SimpleFeatureIterator iterator = featureCollection.features();
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
// Process each matching segment
}
Attribute query
Use getFeatures with an attribute predicate. For a trajectory point model, this queries point attributes. For a trajectory line model, this queries trajectory-level and segment-level attributes only.
String ecqlPredicate = "speed > 2.0";
Query query = new Query("trajectory", ECQL.toFilter(ecqlPredicate));
SimpleFeatureCollection featureCollection =
((LindormDataStore) ds).getFeatureSource("trajectory").getFeatures(query);
SimpleFeatureIterator iterator = featureCollection.features();
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
// Process each matching segment
}
Spatial range query
Use getFeatures with an INTERSECTS predicate to retrieve segments whose geometry intersects the specified polygon:
String ecqlPredicate = "INTERSECTS(geom, POLYGON((-78 26,-73 26,-73 31,-78 31,-78 26)))";
Query query = new Query("trajectory", ECQL.toFilter(ecqlPredicate));
SimpleFeatureCollection featureCollection =
((LindormDataStore) ds).getFeatureSource("trajectory").getFeatures(query);
SimpleFeatureIterator iterator = featureCollection.features();
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
// Process each matching segment
}
Spatiotemporal range query
Combine a spatial predicate with a time range to filter by both location and time window.
Return matching segments only — use getFeatures to retrieve only the segments that match the filter:
String ecqlPredicate = "INTERSECTS(geom, POLYGON((-78 26,-73 26,-73 31,-78 31,-78 26)))" +
" AND traj_start_time >= 1549510271 AND traj_start_time <= 1549582040";
Query query = new Query("trajectory", ECQL.toFilter(ecqlPredicate));
SimpleFeatureCollection featureCollection =
((LindormDataStore) ds).getFeatureSource("trajectory").getFeatures(query);
SimpleFeatureIterator iterator = featureCollection.features();
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
// Process each matching segment
}
Return complete trajectories — use getWholeTrajectoies to reconstruct and return entire trajectories that contain matching segments:
String ecqlPredicate = "INTERSECTS(geom, POLYGON((-78 26,-73 26,-73 31,-78 31,-78 26)))" +
" AND traj_start_time >= 1549510271 AND traj_start_time <= 1549582040";
Query query = new Query("trajectory", ECQL.toFilter(ecqlPredicate));
List<TrajectoryFeature> trajectories =
((LindormDataStore) ds).getFeatureSource("trajectory").getWholeTrajectoies(query);
What's next
-
Terms — Definitions of trajectory storage model fields and supported index types
-
Query spatiotemporal objects — Full CQL syntax and advanced query options