Trajectory data

The Trajectory class is a data container that stores both per-point data for a trajectory and per-trajectory metadata values.

The data stored in Trajectory values is defined by a list of “field sets” using the FieldSet and FieldMetadata classes. All trajectories include the “base” field set (defined in the BASE_FIELDS value in the AEIC.trajectories.trajectory module). Trajectories may contain other field sets in addition to the base field set. Additional field sets are added to a trajectory using the add_fields method.

The Trajectory class has a flexible system for saving and accessing trajectory data: attribute access on a Trajectory value is managed with reference to the field sets included in the trajectory. This means that any field in the base field set can be accessed directly for a trajectory, as, for example, traj.rate_of_climb or traj.n_cruise. The first of these is a per-point Numpy array value, while the second is an integer metadata value. Other data types are supported to work with emissions data, and there is an “emissions” field set (defined in EMISSIONS_FIELDS values in the AEIC.emissions.emission module).

This approach is designed to work cleanly with the TrajectoryStore class for saving trajectory data to NetCDF files.

Note

Currently trajectories have to be created with a fixed number of points. This matches the way the legacy trajectory builder works, but I will extend the class to allow incremental construction of trajectories once that’s needed.

Warning

There is more documentation to come here.

AEIC.trajectories.trajectory.BASE_FIELDS = <FieldSet base: ['fuel_flow', 'aircraft_mass', 'fuel_mass', 'ground_distance', 'altitude', 'flight_level', 'rate_of_climb', 'flight_time', 'latitude', 'longitude', 'azimuth', 'heading', 'true_airspeed', 'ground_speed', 'starting_mass', 'total_fuel_mass', 'n_idle_origin', 'n_taxi_origin', 'n_takeoff', 'n_climb', 'n_cruise', 'n_descent', 'n_approach', 'n_taxi_destination', 'n_idle_destination', 'flight_id', 'name']>

Base field set included in every trajectory.

AEIC.emissions.emission.EMISSIONS_FIELDS = <FieldSet emissions: ['trajectory_emissions', 'trajectory_indices', 'lto_emissions', 'lto_indices', 'apu_emissions', 'apu_indices', 'gse_emissions', 'total_emissions', 'fuel_burn_per_segment', 'total_fuel_burn', 'lifecycle_co2']>

Field set containing emissions data.

class AEIC.trajectories.trajectory.Trajectory(npoints=None, name=None, fieldsets=None)

Class representing a 1-D trajectory with various data fields and metadata.

The “various fields” include a base set of pointwise fields, one value per trajectory point and a base set of trajectory fields, one value per trajectory, plus optional additional pointwise or per-trajectory fields added by adding field sets to the trajectory. (These additional fields may be indexed by chemical species and/or LTO thrust mode as well as trajectory and point index. This is needed to record emissions information.)

Parameters:
  • npoints (int | None)

  • name (str | None)

  • fieldsets (list[str] | None)

__init__(npoints=None, name=None, fieldsets=None)

Initialized either empty for construction be appending points or with a fixed number of points, and an optional name.

The name is used for labelling trajectories within trajectory sets (and NetCDF files).

All pointwise data and per-trajectory fields included in every trajectory by default are taken from the BASE_FIELDS dictionary above. Other fields may be added using the add_fields method.

Parameters:
  • npoints (int | None)

  • name (str | None)

  • fieldsets (list[str] | None)

append(*args, **kwargs)

Override the append method to also update the current flight phase point count.

Return type:

None

compare(other, fields=None, skip_final=None)

Compare this trajectory to another trajectory and compute comparison metrics.

Comparison metrics are computed for each pointwise field in the data dictionary and returned in a dictionary mapping field names to metric values.

Parameters:
  • other (Trajectory)

  • fields (list[str] | None)

  • skip_final (set[str] | None)

Return type:

dict[str, ComparisonMetrics | SpeciesValues[ComparisonMetrics]]

copy_point(from_idx, to_idx)

Copy data from one point to another within the trajectory.

Parameters:
  • from_idx (int)

  • to_idx (int)

interpolate_time(new_time)

Interpolate trajectory data to new time points.

This method assumes that the trajectory has a flight_time field containing the time points corresponding to the existing data. The new time points must be within the range of the existing time points. The method returns a new trajectory with all pointwise fields interpolated to the new time points.

Parameters:

new_time (ndarray)

Return type:

Trajectory

property nbytes: int

Calculate approximate memory size of the trajectory in bytes.

(This only needs to be approximate because it’s just used for sizing the TrajectoryStore LRU cache.)

set_phase(phase)

Set the current flight phase for this trajectory.

This is used by trajectory builders to keep track of which phase of flight they’re currently building, so that they can update the correct phase point count field in the trajectory metadata.

Parameters:

phase (FlightPhase)

Field sets

Each field in a field set is represented by a FieldMetadata value, which records the type of each field and whether it is a per-point data field (metadata = False) or a per-trajectory metadata field (metadata = True). Additional fields provide NetCDF metadata for the field description and units, and allow fields to be marked as required, or to be provided with a default value.

A field set is a collection of FieldMetadata records, keyed by the field name. Field sets are stored by name in a registry, and use an MD5-based hash to ensure that named field sets from different sources correspond to the same sets of fields.

Note

We probably need a mechanism for versioning field sets. At the moment, adding a new field to the base field set, for example, will invalidate files that were created before the new field was added. That’s not sustainable, so we need to implement some sort of simple version control for these things.

class AEIC.storage.field_sets.FieldMetadata(dimensions=<factory>, field_type=<class 'numpy.float64'>, description='', units='', required=True, default=None)

Metadata for a single field.

This is intended to describe the properties of a field in a dataset that may be serialized to NetCDF.

Fields can indexed by a range of different dimensions, represented by the dimensions field (in the “Type” rows, “scalar” means a Numpy floating point or integer type, or Python str):

  • Dimensions: trajectory

  • Use: per-trajectory individual values

  • Examples: total fuel burn for a trajectory, number of climb segments, etc.

  • Type: scalar


  • Dimensions: trajectory, point

  • Use: pointwise values along a trajectory

  • Examples: trajectory latitude, longitude, altitude

  • Type: np.ndarray


  • Dimensions: trajectory, species

  • Use: per-species values for a whole trajectory

  • Examples: APU emissions for a species on a trajectory

  • Type: SpeciesValues[scalar]


  • Dimensions: trajectory, species, point

  • Use: pointwise values for each species along a trajectory

  • Example: CO₂ emissions at each point along a trajectory

  • Type: SpeciesValues[np.ndarray]


  • Dimensions: trajectory, thrust_mode

  • Use: per-thrust-mode values for a whole trajectory

  • Examples: LTO fuel burn per thrust mode for a trajectory

  • Type: ThrustModeValues


  • Dimensions: trajectory, species, thrust_mode

  • Use: per-species values for each thrust mode for a whole trajectory.

  • Examples: NOₓ emissions per thrust mode for a trajectory

  • Type: SpeciesValues[ThrustModeValues]

Parameters:
  • dimensions (Dimensions)

  • field_type (type)

  • description (str)

  • units (str)

  • required (bool)

  • default (Any | None)

convert_in(v, name, npoints)

Convert an incoming value to the appropriate type for this field.

Parameters:
  • v (Any)

  • name (str)

  • npoints (int)

Return type:

Any

default: Any | None = None

Default value for the field if not present in the dataset.

description: str = ''

Human-readable description of the field (used for the NetCDF “description” attribute).

property digest_info: str

Generate a string representation of the field metadata for hashing.

dimensions: Dimensions

Dimensions for this field.

empty(npoints)

Create an empty value for this field based on its type and dimensions.

Parameters:

npoints (int)

Return type:

Any

field_type

Type of the field; should be a Numpy dtype or str for variable-length strings. Note that Python int and float are not allowed because we need to have a 1-to-1 mapping from Python to Numpy types.

alias of float64

nbytes(npoints)

Estimate the number of bytes used by this field per trajectory point.

This is a rough estimate used for memory usage calculations.

Parameters:

npoints (int)

Return type:

int

required: bool = True

Is this field required to be present in the dataset?

units: str = ''

Units of the field (used for the NetCDF “units” attribute).

class AEIC.storage.field_sets.FieldSet(fieldset_name=None, registered=True, **fields)

A collection of field definitions.

Represented as a mapping from field name to metadata.

Parameters:
  • fieldset_name (str | None)

  • registered (bool)

  • fields (FieldMetadata)

REGISTRY: dict[str, FieldSet] = {'base': <FieldSet base: ['fuel_flow', 'aircraft_mass', 'fuel_mass', 'ground_distance', 'altitude', 'flight_level', 'rate_of_climb', 'flight_time', 'latitude', 'longitude', 'azimuth', 'heading', 'true_airspeed', 'ground_speed', 'starting_mass', 'total_fuel_mass', 'n_idle_origin', 'n_taxi_origin', 'n_takeoff', 'n_climb', 'n_cruise', 'n_descent', 'n_approach', 'n_taxi_destination', 'n_idle_destination', 'flight_id', 'name']>, 'emissions': <FieldSet emissions: ['trajectory_emissions', 'trajectory_indices', 'lto_emissions', 'lto_indices', 'apu_emissions', 'apu_indices', 'gse_emissions', 'total_emissions', 'fuel_burn_per_segment', 'total_fuel_burn', 'lifecycle_co2']>}

Registry of named field sets for reuse.

static calc_hash(name, fields)

Calculate a hash from a FieldSet name and field data.

Parameters:
Return type:

int

property digest

Generate persistent hash for FieldSet.

This MD5-based hash is used for identifying field sets within NetCDF files and is used to check the integrity of the link between associated NetCDF files and base trajectory NetCDF files in the TrajectoryStore class.

property dimensions: set[Dimension]

Combined dimensions of all fields in the field set.

property fields

Return an immutable view of the field definitions.

classmethod from_netcdf_group(group)

Construct a FieldSet from a NetCDF group.

Parameters:

group (Group)

Return type:

FieldSet

classmethod from_registry(name)

Retrieve a FieldSet from the registry by name.

Parameters:

name (str)

Return type:

FieldSet

classmethod known(name)

Check if a FieldSet with the given name exists in the registry.

Parameters:

name (str)

Return type:

bool

merge(other)

Combine field sets, ensuring unique field names.

single_point()

Return a new FieldSet containing only fields that have a point dimension, with the point dimension removed. This makes it possible to derive the equivalent “single point” field set for any field set containing whatever kinds of fields.

Return type:

FieldSet

enum AEIC.storage.dimensions.Dimension(value)

Standard dimension names for NetCDF serialization.

The sort order here is important: when serializing to NetCDF, dimensions should be written in the order defined here.

Member Type:

int

Valid values are as follows:

TRAJECTORY = <Dimension.TRAJECTORY: 1>
SPECIES = <Dimension.SPECIES: 2>
POINT = <Dimension.POINT: 3>
THRUST_MODE = <Dimension.THRUST_MODE: 4>

The Enum and its members also have the following methods:

property dim_name: str

Dimension name for use in NetCDF files.

classmethod from_dim_name(name)

Convert from names used in NetCDF files.

Parameters:

name (str)

Return type:

Dimension

class AEIC.storage.dimensions.Dimensions(*dims)
Parameters:

dims (Dimension)

property abbrev: str

Return an abbreviated string representation of the dimensions.

(Mostly for convenience when testing.)

add(dim)

Add a dimension.

Parameters:

dim (Dimension)

Return type:

Dimensions

classmethod from_abbrev(abbrev)

Create a Dimensions object from an abbreviated string representation.

(Mostly for convenience when testing.)

Parameters:

abbrev (str)

Return type:

Dimensions

classmethod from_dim_names(*names)

Create a Dimensions object from a list of dimension names used in NetCDF files.

Parameters:

names (str)

Return type:

Dimensions

property netcdf: tuple[str, ...]

Return dimension for use in NetCDF files.

property ordered: list[Dimension]

Return dimensions in standard order.

remove(dim)

Remove a dimension.

Parameters:

dim (Dimension)

Return type:

Dimensions

Flight phases

Trajectories are divided into a sequence of “phases” (e.g., climb, cruise, descent). Each phase has a corresponding field in the Trajectory class that records the number of points in that phase for each trajectory.

Some phases are expected to be simulated by all performance models (climb, cruise, descent) while others are optional (taxi, takeoff, approach, idle) and may only be simulated by more detailed models or may be populated from time-in-mode values. For uniformity of representation, these optional phases are included as “normal” points in the trajectory.

Each Trajectory records the number of points in each phase using fields with names prefixed by n_ (e.g., n_climb, n_cruise, n_descent).

enum AEIC.storage.phase.FlightPhase(value)

Flight phases known to AEIC.

Member Type:

int

Valid values are as follows:

IDLE_ORIGIN = <FlightPhase.IDLE_ORIGIN: 1>
TAXI_ORIGIN = <FlightPhase.TAXI_ORIGIN: 2>
TAKEOFF = <FlightPhase.TAKEOFF: 3>
CLIMB = <FlightPhase.CLIMB: 4>
CRUISE = <FlightPhase.CRUISE: 5>
DESCENT = <FlightPhase.DESCENT: 6>
APPROACH = <FlightPhase.APPROACH: 7>
TAXI_DESTINATION = <FlightPhase.TAXI_DESTINATION: 8>
IDLE_DESTINATION = <FlightPhase.IDLE_DESTINATION: 9>

The Enum and its members also have the following methods:

property field_name

Trajectory point count field name for this flight phase.

property method_name

Method name used in trajectory builders for this flight phase.

property field_label

Human-readable label for this flight phase.

classmethod from_field_name(field_name)

Parse trajectory point count field name to get flight phase.

Parameters:

field_name (str)

AEIC.storage.phase.FlightPhases

Type alias for trajectory point counts in each flight phase.

alias of dict[FlightPhase, int]

AEIC.storage.phase.PHASE_FIELDS = {'n_approach': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in approach phase', units='count', required=False, default=0), 'n_climb': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in climb phase', units='count', required=True, default=0), 'n_cruise': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in cruise phase', units='count', required=True, default=0), 'n_descent': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in descent phase', units='count', required=True, default=0), 'n_idle_destination': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in idle at destination phase', units='count', required=False, default=0), 'n_idle_origin': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in idle at origin phase', units='count', required=False, default=0), 'n_takeoff': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in takeoff phase', units='count', required=False, default=0), 'n_taxi_destination': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in taxi at destination phase', units='count', required=False, default=0), 'n_taxi_origin': FieldMetadata(dimensions=Dimensions(T), field_type=<class 'numpy.int32'>, description='Number of points in taxi at origin phase', units='count', required=False, default=0)}

Convenience dictionary of field metadata for all flight phases.

This is used in the “base” field set for trajectory datasets to add the phase point count metadata variables to every trajectory.

AEIC.storage.phase.REQUIRED_PHASES = {FlightPhase.CLIMB, FlightPhase.CRUISE, FlightPhase.DESCENT}

Flight phases we expect all performance models to simulate.

Ground tracks

Warning

Ground tracks currently only really work for great circle routes between an origin and a destination. Trajectory builders that need more complex ground tracks with intermediate waypoints will need to take account of the exceptions that GroundTrack.step raises when an attempt is made to step past a waypoint. There should probably be an option on creation of a GroundTrack to determine whether these exceptions are generated: for a “dense” ground track, maybe we don’t care about stepping past waypoints; for a “flight plan” ground track defined by a sequence of navigation aid positions, maybe we would like our trajectory to hit each waypoint exactly, so we do need to care about the “stepped over a waypoint” exceptions.

Ground tracks are used to represent the path of an aircraft over the Earth’s surface, defined by a series of waypoints. They support interpolation along great circle paths between waypoints. Trajectory builders can use ground tracks to determine the aircraft’s position as they simulate flight along its route.

class AEIC.trajectories.ground_track.GroundTrack(waypoints, allow_overstep=False)

Great circle interpolator along a set of waypoints.

A GroundTrack defines a path in longitude/latitude by an ordered set of waypoints. Between waypoints the ground track follows great circle paths.

This abstraction supports both great circle paths (one start point, one end point, no intermediate waypoints) and paths with multiple waypoints (for example, paths defined by ADS-B data).

The fundamental operation on a GroundTrack is to find the location of a point based on its distance from the ground track’s start location. It may sometimes be advantageous to allow steps beyond the end of the ground track; this can be enabled by setting the allow_overstep parameter to True when creating a GroundTrack instance. If allow_overstep is not enabled, attempts to look up locations beyond the end of the ground track will raise an exception.

Parameters:
  • waypoints (list[Location])

  • allow_overstep (bool)

exception Exception

Exception raised for errors in the GroundTrack class.

class Point(location, azimuth)

A point along the ground track, defined by a location and azimuth.

Parameters:
classmethod great_circle(start_loc, end_loc, allow_overstep=False)

Create a great circle ground track from a start and end location.

Parameters:
Return type:

GroundTrack

location(distance)

Calculate location at a given distance from start of ground track.

Parameters:

distance (float)

Return type:

Point

lookup_waypoint(distance)

Find index of waypoint immediately after or at given distance.

Parameters:

distance (float)

Return type:

int

step(from_distance, distance_step)

Calculate location a given distance step from a starting distance.

For normal steps, this is a convenience method that calls location with the sum of from_distance and distance_step. However, if allow_overstep is True, it also supports steps that go beyond the end of the ground track by continuing along the final great circle path beyond the last waypoint.

Parameters:
  • from_distance (float)

  • distance_step (float)

Return type:

Point

property total_distance: float

Get total distance of ground track.

waypoint_distance(idx)

Get cumulative distance to waypoint at given index.

Parameters:

idx (int)

Return type:

float