Trajectory Pipeline

The architecture of the trajectory pipeline is illustrated below:

A schematic of the trajectory pipeline.

Fig. 48 Trajectory pipeline schematic.

Point Generating Sources

The MPointGeneratorSource generates a list of points packed into a MPoints data item, managed in memory. It requests a MPointGeneratorInterface to generate the points. The horizontal cross-section and vertical cross-section actors implement this interface and provide the necessary methods for point generation based on the actor’s current state and user interface.

To implement a custom point source, have the class inherit from MPointGeneratorInterface and implement the following abstract methods:

  • QString getPointGeneratingSourceBaseRequestKeys(): Generates a clear, identifiable string based on its current state. The string has to fully define the state required to uniquely identify the points being generated.

  • MPoints* generatePointsFromString(const QString&): Generates the data item containing the list of points based on the provided request string. This method must always return the same set of points given the same input string.

Register the interface with MPointSourceFactory in the constructor, e.g.:

MPointSourceFactory::getInstance()->registerPointGeneratingInterface<MPointGeneratorInterfaceSettings>(this);

The template argument must be a class inheriting from MPointGeneratorInterfaceSettings, containing the user interface settings.

Remember to deregister the interface upon deletion. Use MPointSourceFactory::showCreationDialog() to display a dialog allowing the user to select a point generation interface from the registered ones.

Point Filters

Point filters inherit from MPointFilter and accept a MPoints data item as input, returning an altered MPoints data item. Each filter has a MPointSource as its parent, which may be either a MPointGeneratorSource or another MPointFilter; filters are therefore chained. Adding a new point filter follows the same three-step pattern used by trajectory filters below: a filter class, a settings class, and registration to the factory.

1. The filter (inherits MPointFilter). You need to implement:

  • QString getFilterName() override – Returns the general name identifier for the filter. Each filter type needs to have a unique identifier. It is used for saving the pipeline state to configuration files, and restoring the trajectory pipeline when a session is loaded.

  • MPoints *produceData(MDataRequest request) – the filtering heuristic. Read the filter’s state from the request via its request keys and return the filtered MPoints.

  • MTask *createTaskGraph(MDataRequest request) – declare the dependency on the parent source (and any grid sources it samples) so the scheduler runs them first.

MThresholdPointFilter::MThresholdPointFilter()
    : MPointFilter({"POINT_FILTER_THRESHOLD"})   // base request key
{}

// in the header:
static QString filterName() { return "Threshold filter"; }

2. The settings (inherits MPointFilterProperties) own the filter’s user interface and translate it into the request. Implement the constructor, add your properties (and, if the filter samples a data field, the actor-variable hooks onAddActorVariable / onChangeActorVariable / onDeleteActorVariable) to the actor – and

void insertCurrentStateToRequest(MDataRequestHelper &rh,
                                 const QStringList &keys) override;

which writes the current UI state into keys (the filter’s unique request keys).

3. Register the pair in the MPointFilterFactory constructor:

registerPointFilter<MThresholdPointFilter, MThresholdPointFilterProperties>();

The “Add point filter” button uses the factory to let the user pick a registered filter, builds its settings, and inserts the filter into every pipeline. See MThresholdPointFilter (filter by an upper/lower data threshold) and MBoundingBoxPointFilter for complete examples.

Trajectory Sources

Precomputed trajectories are loaded via MTrajectoryReader, while on-the-fly computed trajectories are generated through MTrajectoryComputationSource. The computation process is encapsulated in the MTrajectoryComputation class. Trajectory sources return a MTrajectories data item, containing the vertex data of the trajectories along with some metadata.

Trajectory Filters

As shown in the schematic above, trajectory filters are connected downstream of the trajectory source. A MTrajectoryFilter reads both the trajectories (MTrajectoryDataSource) and the incoming selection (MTrajectorySelectionSource) and returns a new selection. Filters can therefore

  • discard entire trajectories, or

  • discard segments of trajectories,

without copying the (potentially large) vertex data. They only narrow the set of selected indices into it. Filters are chained; each filter’s input is the previous filter’s selection. Currently implemented filters are the ascent/descent (MPressureTimeTrajectoryFilter), bounding-box (MBoundingBoxTrajectoryFilter) and thin-out (MThinOutTrajectoryFilter) filters.

A new trajectory filter follows the same three steps as a point filter. Create a filter inheriting from MTrajectoryFilter that holds the filtering logic, a MTrajectoryFilterProperties defining the user facing properties, and register the pair to the MTrajectoryFilterFactory.

Note

Request keys are unique per instance, not per type.

A filter passes base keys (e.g. "TRAJ_FILTER_BBOX") to its constructor. The base class appends the filter’s runtime id (key + "_" + getID()) to build the actual keys returned by locallyRequiredKeys(). This is why several instances of the same filter type can coexist (and be chained) in one pipeline without their request keys colliding.

Persisting configured filters

Point and trajectory filters are configured at runtime, so the trajectory actor must be able to recreate them when a session is loaded. On save (MTrajectoryActor::saveTrajectoryPipelineConfig), the actor writes the number of configured filters and, for each, its getFilterName() under an indexed prefix (trajectoryFilter0, trajectoryFilter1, …); the filter’s own property group is assigned that prefix, so the property framework serializes its settings there. On load, the actor reads the name back, recreates the settings through the factory (MTrajectoryFilterFactory::createProperties(name, actor)) and calls addTrajectoryFilter again.

Single Point Filter

The MSingleTimeTrajectoryFilter extracts single time position data from the trajectories, used for rendering spheres at points in time selected in the user interface. It returns a MWritableTrajectorySelection, but specifically includes only subsets of trajectories with length 1.

Normals Source

The MTrajectoryNormalsSource computes normal directions along all trajectories, essential for accurate rendering and shading. The returned data item is a MTrajectoryNormals.

Note

Normal directions depend on the vertical scale of the scene. Thus, normals must be computed separately for each scene, resulting in an additional layer of lists within the MTrajectoryNormals data item: normals per scene view.

Auxiliary Data Sources

Beyond the selection, a pipeline can attach supplemental data to the trajectory collection, computed either per vertex or per whole trajectory. All such sources inherit from MTrajectorySupplementDataSource (which holds a trajectory source and a selection source) and produce a subclass of MSupplementalTrajectoryData tagged with a MSupplementalTrajectoryDataType (e.g. DATA_PER_VERTEX, NORMALS). Existing supplements:

  • Per vertex – MFloatPerTrajectoryVertexSource provides one float per vertex. MAuxiliaryVariableTrajectorySource uses it to sample an auxiliary data field along each trajectory (used to colour trajectories by that field).

  • Per trajectory – MFloatPerTrajectorySource provides one float per trajectory. MDeltaPressurePerTrajectorySource uses it to compute the maximum pressure change along a trajectory within a time interval.

  • The normals source (see below) is also a supplement, tagged NORMALS.

To add a supplement, subclass MTrajectorySupplementDataSource, implement produceData / createTaskGraph to compute your MSupplementalTrajectoryData subclass, and give it the correct MSupplementalTrajectoryDataType. Register the source with the pipeline manager, which keeps a list of supplement sources: its produceData then routes NORMALS and DATA_PER_VERTEX into their dedicated collection slots (dynamic_lease_cast to the concrete type) and gathers every other type into the generic supplement list, from which a consumer can retrieve it via the MTrajectoryDataCollection.

The Trajectory Pipeline Barrier

The MTrajectoryPipelineManager has dual responsibilities: it owns the pipeline structure – the trajectory source, the filter chain, the single-time filter, the normals source and the supplement sources – and it acts as the barrier that gathers their outputs.

Pipeline integration. Filters are added and removed at runtime via addTrajectoryFilter / removeTrajectoryFilter (and the point-filter equivalents). After any change, updatePipeline() updates the chain so that each enabled filter’s input is the previous enabled filter’s output; disabled filters are skipped. Because the dispatcher owns one pipeline per point source, adding a filter adds a corresponding instance to every pipeline. Having one dedicated pipeline per point source allows us to re-use cached trajectories when only one of many point sources is changed (e.g., a pole is moved).

The barrier. In produceData() the manager performs no computation itself. It requests data from each upstream source – the trajectories, the selection from the last enabled filter, the single-time selection, the normals and every supplement – and packs them into a single MTrajectoryDataCollection, so a requester is notified only once all parts are available.

Note

Lifetime of the upstream trajectory data.

MTrajectoryDataCollection holds no copies of the trajectory data; instead it stores an MDataLease for each upstream item (trajectories, selection, normals, supplements, …). A lease pins its item in the memory manager for as long as the lease exists (RAII, similar to shared_ptr), so the whole set of upstream items stays alive exactly as long as the collection does. When the last lease on the collection is released and the collection is evicted, its member leases are destroyed and the upstream items become evictable again.

The Trajectory Dispatcher

The MTrajectoryRequestDispatcher manages the trajectory pipelines. Each point source is linked to a dedicated pipeline, allowing efficient caching of computation results. When a point source changes, only the points for that source are used to recompute the corresponding trajectories.

Similar to the pipeline, it serves as a barrier, holding references to both the pipeline and the associated data item references to collections returned by the pipelines.