User guide

Updated at:

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:

  1. Create a trajectory table — configure a point model, build a line model, add a secondary index, and call createSchema.

  2. Write trajectory data — parse raw points, apply a segmentation policy, and batch-write trajectory segments.

  3. 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 DataStore connection — 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);
Important

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