Main Content

Track Space Debris Using a Keplerian Motion Model

R2026b

This example shows how to simulate space debris with trackingScenario and how to configure a fusionRadarSensor in monostatic mode to generate synthetic detections of space debris. The example also shows how to configure a task-oriented multi-object tracker to track the simulated debris.

Space Debris Scenario

There are more than 30,000 large debris objects (with diameter larger than 10 cm) and more than 1 million smaller debris objects in Low Earth Orbit (LEO) [1]. This debris poses a danger to human activities in space, damages operational satellites, and forces time-sensitive and costly avoidance maneuvers. As space activity increases, reducing and monitoring space debris becomes crucial.

You can use Sensor Fusion and Tracking Toolbox™ and Aerospace Toolbox to model the debris trajectories, generate synthetic radar detections of the debris, and track position and velocity estimates of all debris.

Create Tracking Scenario

First, create a tracking scenario and set the random seed for repeatable results. Set the IsEarthCentered property to true to model trajectories with respect to Earth.

seed = 2020;
rng(seed);
tTotal = 1800; % seconds
dt = 4; % seconds
updateRate = 1/dt; % Hz
scene = trackingScenario(IsEarthCentered=true, InitialAdvance="UpdateInterval",...
    StopTime=tTotal, UpdateRate=updateRate);

Define Space Debris

First, generate the initial states for 100 space debris objects by varying their orbital elements: semi-major axis, eccentricity, inclination, right ascension of ascending node, argument of periapsis, and true anomaly. These orbits are nearly circular low-Earth orbits with an orbital period of roughly 100 minutes.

numDebris = 100;

a = 7e6 + 1e5*randn(numDebris,1); % semi-major axis
ecc = abs(0.015 + 0.005*randn(numDebris,1)); % eccentricity
inc = 80 + 10*rand(numDebris,1); % inclination
raan = 360*rand(numDebris,1); % right ascension of ascending node
w = 360*rand(numDebris,1); % argument of periapsis
nu = 360*rand(numDebris,1); % true anomaly

Propagate the orbits of these debris by using the propagateOrbit function. Assume the initial time epoch is at 13-May-2026 08:36:36. As demonstrated in the next section, this example uses four ground-based radar stations to detect the debris. To enable this, transform the positions and velocities from the Earth-Centered Inertial (ECI) frame to geodetic coordinates (latitude, longitude, and altitude) and the corresponding velocities in the North-East-Down (NED) frame.

utc0 = datetime(2026,5,13,8,36,36);
utcs = utc0 + seconds(0:dt:tTotal);
[lla,velocitiesNED] = propagateOrbit(utcs,a,ecc,inc,raan,w,nu,OutputCoordinateFrame="geographic",PropModel="two-body-keplerian");

Now, define the debris as platforms in the tracking scenario. Assign a trajectory to each platform using geoTrajectory objects.

timeOfArrivals = seconds(utcs-utcs(1)); % Obtain time in seconds
for ii=1:numDebris
    debris(ii) = platform(scene); %#ok<SAGROW>
    debris(ii).Trajectory = geoTrajectory(lla(:,:,ii)',timeOfArrivals,Velocities=velocitiesNED(:,:,ii)'); %#ok<SAGROW>
end

Model Space Surveillance Radars

Define four antipodal stations with fan-shaped radar beams looking into space. The fans cut through the orbits of debris to maximize the number of debris detections. A pair of stations are located in the Pacific Ocean and in the Atlantic Ocean, whereas a second pair of surveillance stations is located near the poles. Having four dispersed radars allows for the re-detection of space debris to correct their position estimates and to acquire acquiring new debris detections.

% Specify four station positions in latitude, longitude, and altitude.
stationsLLA = [10 180 0;
                0 -20 0;
               65 -20 0;
              -90   0 0];
% Create a space surveillance station in the Pacific ocean.
station1 = platform(scene,Position=stationsLLA(1,:));
% Create a second surveillance station in the Atlantic ocean.
station2 = platform(scene,Position=stationsLLA(2,:));
% Near the North Pole, create a third surveillance station in Iceland.
station3 = platform(scene,Position=stationsLLA(3,:));
% Create a fourth surveillance station near the South Pole.
station4 = platform(scene,Position=stationsLLA(4,:));

Each station has a radar, modeled using a fusionRadarSensor object. To detect debris objects in the LEO range, the radar has the following requirements:

  • Detecting a 10 dBsm object up to 2000 km away

  • Resolving objects horizontally and vertically with a precision of 100 m at 2000 km range

  • Having a fan-shaped field of view of 120 degrees in azimuth and 30 degrees in elevation

  • Looking up into space based on its geo-location

  • Reporting detections in azimuth, elevation, and range

% Create fan-shaped monostatic radars to monitor space debris objects
radar1 = fusionRadarSensor(1,...
    UpdateRate=updateRate,...
    ScanMode="No scanning",...
    MountingAngles=[0 90 0],... % Look up
    FieldOfView=[120;30],... % degrees
    ReferenceRange=2e6,... % m
    RangeLimits=[0 2e6],...
    ReferenceRCS=10,... % dBsm
    HasFalseAlarms=false,...
    HasElevation=true,...
    AzimuthResolution=0.01,... % degrees
    ElevationResolution=0.01,... % degrees
    RangeResolution=100,... % m
    HasINS=true,...
    DetectionCoordinates="Sensor Spherical");
station1.Sensors = radar1;

radar2 = clone(radar1);
radar2.SensorIndex = 2;
station2.Sensors = radar2;

radar3 = clone(radar1);
radar3.SensorIndex = 3;
station3.Sensors = radar3;

radar4 = clone(radar1);
radar4.SensorIndex = 4;
station4.Sensors = radar4;

Visualize the Ground Truth with trackingGlobeViewer

You can use trackingGlobeViewer to visualize all the elements defined in the tracking scenario: individual debris objects and their trajectories, radar fans, radar detections, and tracks.

f = uifigure;
viewer = trackingGlobeViewer(f,Basemap="satellite",ShowDroppedTracks=false,NumCovarianceSigma=3);
% Add info box on top of the globe viewer
infotext = simulationInfoText(0,0,0);
infobox = uilabel(f,Text=infotext,FontColor=[1 1 1],FontSize=11,...
    Position=[10 20 300 70],Visible="on");

% Show radar beams on the globe
coverages = coverageConfig(scene);
plotCoverage(viewer, coverages, "ECEF");
scene.restart();
% Simulate the debris motion
while advance(scene)
    infobox.Text = simulationInfoText(scene.SimulationTime, 0, numDebris);
    plotPlatform(viewer, debris, "ECEF","TrajectoryMode","History");
end
% Take a snapshot of the scenario
snapshot(viewer);

Figure contains an axes object. The hidden axes object contains an object of type image.

On the virtual globe, space debris is visualized as white dots. Most of these generated objects follow orbits with high inclination angles, close to 80 degrees. The trajectories are plotted in Earth-Centered, Earth-Fixed (ECEF) coordinates, and so the entire trajectory drifts westward due to Earth’s rotation. Over several orbital periods, all debris objects will eventually pass through the surveillance beams of the radars.

Define Tracker

To track these debris, you can use a task-oriented multi-object tracker which integrates data from multiple sensors and creates a unified representation of target states. The tracker processes incoming sensor data by:

  • Evaluating existing tracks

  • Associating new data with them

  • Updating tracks using filtering techniques such as Kalman filtering

  • Adjusting track existence probabilities based on sensor field-of-view

  • Initiating new tracks for unassigned detections

Specify Target Type

In this step, you specify the type and the characteristics of the objects you intend to track, which informs the tracker about choosing appropriate models and parameters to define the target. You use the trackerTargetSpec function to create a target spec that models Keplerian motion.

debrisSpec = trackerTargetSpec("space","earth-centered","keplerian");
disp(debrisSpec)
  EarthCenteredKeplerian with properties:

                   MinAltitude: 1.2e+05    m   
                   MaxAltitude: 2e+06      m   
                      MaxSpeed: 8000       m/s 
    MaxDisturbanceAcceleration: 0.02       m/s²

The display above shows the list of properties of the Keplerian target specification, which can be modified for your application. The specification models target states as [x;vx;y;vy;z;vz] in the ECI frame. The MinAltitude and MaxAltitude properties specify the region of interest for tracking. Targets that fall outside this altitude range are assigned a low survival probability and are likely to be quickly removed by the tracker that uses this target specification. The MaxSpeed property sets the upper limit for the magnitude of a target’s initial velocity. The MaxDisturbanceAcceleration property defines the extent of motion uncertainty, accounting for unmodeled perturbations such as higher-order gravitational effects, atmospheric drag, and solar radiation pressure.

Specify Sensor Type

After specifying the objects to track, you specify the sensors you use for tracking. First, use the trackerSensorSpec function to create the monostatic radar specification for the first radar.

radarSpec1 = trackerSensorSpec("space","ground-based","radar");
disp(radarSpec1)
  SpaceGroundBasedRadar with properties:

           MaxNumLooksPerUpdate: 30    
    MaxNumMeasurementsPerUpdate: 10    

              ReferenceFrame: 'NED'                      
       GroundStationPosition: [0 0 0]         [deg deg m]
    GroundStationOrientation: [3⨯3 double]               
            MountingLocation: [0 0 0]         m          
              MountingAngles: [0 0 0]         deg        

       HasElevation: 1                    
       HasRangeRate: 1                    
        FieldOfView: [60 20]           deg
        RangeLimits: [1e+05 2e+06]     m  
    RangeRateLimits: [-10000 10000]    m/s

      AzimuthResolution: 1      deg
        RangeResolution: 100    m  
    ElevationResolution: 5      deg
    RangeRateResolution: 10     m/s

    DetectionProbability: 0.9      
          FalseAlarmRate: 1e-06    

    Terrain: 'none'    

Then, specify the characteristics of the sensor spec based on the sensors you simulated in the last section.

radarSpec1.GroundStationPosition = stationsLLA(1,:);
radarSpec1.MountingAngles = [0 90 0];
radarSpec1.HasRangeRate = false;
radarSpec1.FieldOfView = [120;30];
radarSpec1.RangeLimits = [0 2e6];
radarSpec1.AzimuthResolution = 0.01;
radarSpec1.ElevationResolution = 0.01;
radarSpec1.RangeResolution = 100;
radarSpec1.BirthRate = 1e-10;
radarSpec1.FalseAlarmRate = 1e-10;

All the other sensors only differ from the first sensor by ground station position. So, you create copies of the first sensor spec and modify their ground station positions.

radarSpec2 = radarSpec1;
radarSpec2.GroundStationPosition = stationsLLA(2,:);

radarSpec3 = radarSpec1;
radarSpec3.GroundStationPosition = stationsLLA(3,:);

radarSpec4 = radarSpec1;
radarSpec4.GroundStationPosition = stationsLLA(4,:);

Configure Tracker

In this step, you create a tracker based on the created target spec and sensor specs. Also, you choose to use the joint integrated probabilistic data association (JIPDA) algorithm.

tracker = multiSensorTargetTracker(debrisSpec,{radarSpec1,radarSpec2,radarSpec3,radarSpec4},"jipda");

Using the target specifications, the tracker configures the right motion and survival models. Also, the sensor specifications provide the right observability, measurement, clutter, birth, and state initialization models to the tracker.

disp(tracker)
  fusion.tracker.JIPDATracker with properties:

                TargetSpecifications: {[1×1 EarthCenteredKeplerian]}
                SensorSpecifications: {[1×1 SpaceGroundBasedRadar]  [1×1 SpaceGroundBasedRadar]  [1×1 SpaceGroundBasedRadar]  [1×1 SpaceGroundBasedRadar]}
              MaxMahalanobisDistance: 5
    ConfirmationExistenceProbability: 0.9000
        DeletionExistenceProbability: 0.1000

In addition to the target and sensor specifications, the tracker exposes tuning parameters to help control data association, track confirmation, and track deletion. The MaxMahalanobisDistance property controls the size of the association gate. Generally, this distance should be small to limit the association gate, but if you observe that the tracker fails to associate a measurement to the track, you can increase this value slightly. The ConfirmationExistenceProbability property defines the probability threshold to confirm a track. The DeletionExistenceProbability property defines the probability threshold to remove a track.

Sensor Data Format

The sensor specifications determine the format of sensor data that the tracker expects. You can display the data format by using the dataFormat() object function of the sensor specification.

format = dataFormat(radarSpec1)
format = struct with fields:
             LookTime: [01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    …    ] (1×30 datetime)
          LookAzimuth: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
        LookElevation: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
        DetectionTime: [01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970    01-Jan-1970]
              Azimuth: [0 0 0 0 0 0 0 0 0 0]
            Elevation: [0 0 0 0 0 0 0 0 0 0]
                Range: [0 0 0 0 0 0 0 0 0 0]
      AzimuthAccuracy: [0 0 0 0 0 0 0 0 0 0]
    ElevationAccuracy: [0 0 0 0 0 0 0 0 0 0]
        RangeAccuracy: [0 0 0 0 0 0 0 0 0 0]

The data format captures everything you need to provide for sensor scanning and measurements. It includes fields to specify the sensor's scanning information and measurement information. Each of these fields is populated with nominal values to match the MaxNumLooksPerUpdate and MaxNumMeasurementsPerUpdate values set in the sensor specification.

Next, you generate a recording of the scenario used in this example.

tsr = record(scene,IncludeSensors=true,RecordingFormat="Recording",InitialSeed=seed);

Then, you organize the recorded sensor data by using the supporting function, helperOrganizeSensorData. The returned values, data1, data2, data3, and data4, follow the required data format.

[data1, data2, data3, data4] = helperOrganizeSensorData(tsr,format,utc0);

Run Tracker

Before running the tracker, you can tune these tracker properties.

release(tracker);
tracker.DeletionExistenceProbability = 0.1;
tracker.ConfirmationExistenceProbability = 0.9;
tracker.MaxMahalanobisDistance = 5;

Next, update the tracker using the sensor data and update the globe display.

reset(tsr);
clear(viewer);
snapshots = {};
plotCoverage(viewer,coverages,"ECEF");
for ii = 1:numel(data1)
    % Update tracker with sensor data
    confTracks = tracker(data1(ii),data2(ii),data3(ii),data4(ii));

    % Update Visualization
    % Read recording and plot debris poses
    [simulationTime,poses,~,detections] = read(tsr);
    plotPlatform(viewer, poses, "ECEF");
    % Plot sensor data
    plotSensorData(viewer,{data1(ii),data2(ii),data3(ii),data4(ii)},{radarSpec1,radarSpec2,radarSpec3,radarSpec4});
    % Plot confirmed tracks
    if ~isempty(confTracks)
        plotTrack(viewer,confTracks,debrisSpec,LabelStyle="Custom",CustomLabel="T" + string([confTracks.TrackID]));
    end
    % Update the info
    infobox.Text = simulationInfoText(simulationTime, numel(confTracks), numDebris);
    % Take and save snapshots at a few epochs
    snapshots = takeSnapshots(snapshots, viewer, simulationTime);
end
imshow(snapshots{1});

Figure contains an axes object. The hidden axes object contains an object of type image.

On the first snapshot, three debris passed through the surveillance region of the radar near Iceland. The tracker successfully created and maintained three tracks.

imshow(snapshots{2});

Figure contains an axes object. The hidden axes object contains an object of type image.

At approximately 20 minutes, the tracker tracks a total of 22 tracks.

imshow(snapshots{3});

Figure contains an axes object. The hidden axes object contains an object of type image.

At approximately 26 minutes, track T10 was approaching the surveillance region of the Iceland radar. T10 was originally initialized in the coverage area of the Atlantic radar. After leaving the Atlantic radar’s coverage, the tracker continued to maintain and predict T10’s state. However, because T10 was outside the range of all sensors during this prediction period, its covariance ellipse expanded, indicating increased motion uncertainty.

imshow(snapshots{4});

Figure contains an axes object. The hidden axes object contains an object of type image.

After 24 seconds, track T10 entered the surveillance region of the Iceland radar. The tracker successfully associated T10 with a new detection, resulting in a reduction of its motion uncertainty as expected.

imshow(snapshots{5});

Figure contains an axes object. The hidden axes object contains an object of type image.

At the end of the simulation, the tracker maintains a total of 30 confirmed tracks out of 100 debris with the sparse radar station configuration. If you increase the simulation time, the radars cover more debris. Explore different radar station locations and configurations to increase the number of tracked objects.

The exported tracks include the actual track times in POSIX format, which can be easily converted to standard datetime values.

datetimeFinal = datetime(confTracks(1).UpdateTime,ConvertFrom="posixtime")
datetimeFinal = datetime
   13-May-2026 09:06:36

Supporting Functions

helperOrganizeSensorData Organize sensor data from the scenario to format required by sensor spec

function varargout = helperOrganizeSensorData(tsr, format, initialUTC)
% Extract recorded data from the tracking sensor report
data = tsr.RecordedData;

% Get field names from the format structure
fields = fieldnames(format);

% Initialize each field in the format structure with an empty array
for i = 1:length(fields)
    className = class(format.(fields{i}));
    format.(fields{i}) = feval([className,'.empty'],1,0);
end

% Determine the number of simulation steps and sensors
numSteps = numel(data);
numSensors = numel(data(1).SensorConfigurations);

% Initialize output cell array for each sensor
sensorData = cell(1, numSensors);

% Create a template structure for each sensor's data across all steps
for ii = 1:numSensors
    sensorData{ii} = repmat(format, numSteps, 1);
end

% Process data for each simulation step
for jj = 1:numSteps
    % Extract data for the current simulation step
    stepData = data(jj);
    simulationTime = stepData.SimulationTime;

    % Process coverage configuration data for each sensor
    for kk = 1:numel(stepData.CoverageConfig)
        configData = stepData.CoverageConfig(kk);
        index = configData.Index;

        % Assign look time and angles to the corresponding sensor's data
        sensorData{index}(jj).LookTime = initialUTC + seconds(simulationTime);
        sensorData{index}(jj).LookAzimuth = configData.LookAngle(1);
        sensorData{index}(jj).LookElevation = configData.LookAngle(2);

    end

    % Process detection data for the current step
    numDetections = numel(stepData.Detections);
    for kk = 1:numDetections
        detection = stepData.Detections{kk};
        index = detection.SensorIndex;

        % Append detection measurements and accuracies to the sensor's data
        sensorData{index}(jj).DetectionTime = [sensorData{index}(jj).DetectionTime ...
            initialUTC + seconds(detection.Time)];
        sensorData{index}(jj).Azimuth = [sensorData{index}(jj).Azimuth detection.Measurement(1)];
        sensorData{index}(jj).Elevation = [sensorData{index}(jj).Elevation detection.Measurement(2)];
        sensorData{index}(jj).Range = [sensorData{index}(jj).Range detection.Measurement(3)];

        factor = 1;
        azimuthAccuray = sqrt(detection.MeasurementNoise(1,1))*factor;
        elevationAccuracy = sqrt(detection.MeasurementNoise(2,2))*factor;
        rangeAccuracy = sqrt(detection.MeasurementNoise(3,3))*factor;

        sensorData{index}(jj).AzimuthAccuracy = [sensorData{index}(jj).AzimuthAccuracy azimuthAccuray];
        sensorData{index}(jj).ElevationAccuracy = [sensorData{index}(jj).ElevationAccuracy elevationAccuracy];
        sensorData{index}(jj).RangeAccuracy = [sensorData{index}(jj).RangeAccuracy rangeAccuracy];
    end
end
varargout = sensorData;
end

simulationInfoText Display the simulation time, the number of debris, and the current number of tracks in the scenario.

function text = simulationInfoText(time,numTracks, numDebris)
text = vertcat(string(['Elapsed simulation time: ' num2str(round(time/60)) ' (min)']),...
    string(['Current number of tracks: ' num2str(numTracks)]),...
    string(['Total number of debris: ' num2str(numDebris)]));
drawnow limitrate
end

takeSnapshots Move camera and take snapshots at a few time epochs

function snapshots = takeSnapshots(snapshots, viewer, simulationTime)
% Move camera during simulation and take snapshots
switch simulationTime
    case 272 % Good to keep
        campos(viewer,[60 -120 2.6e6]);
        camorient(viewer, [20 -45 20]);
        drawnow
        snap = snapshot(viewer);
        snapshots = [snapshots {snap}];
    case 1200
        campos(viewer,[54 2.3 6.09e6]);
        camorient(viewer, [0 -73 348]);
        drawnow
        snap = snapshot(viewer);
        snapshots = [snapshots {snap}];
    case 1544
        campos(viewer,[61.9142 -15.26258 7.614006e5]);
        camorient(viewer,[342.97078 -71.944895 0]);
        drawnow
        snap = snapshot(viewer);
        snapshots = [snapshots {snap}];
    case 1568
        campos(viewer,[63.380 -15.3023 7.614006e5]);
        camorient(viewer,[340.2416 -79.6413 0]);
        drawnow
        snap = snapshot(viewer);
        snapshots = [snapshots {snap}];
    case 1800
        campos(viewer,[0000044.75562659	125.72492074	1.5699123*1e7]);
        camorient(viewer,[360.0000  -89.9854833  0]);
        drawnow
        snap = snapshot(viewer);
        snapshots = [snapshots {snap}];
end
end

References

[1] https://www.esa.int/Space_Safety/Space_Debris/Space_debris_by_the_numbers