Адаптивное отслеживание маневрирования целей с управляемым радаром

В этом примере показано, как использовать радарное управление ресурсами, чтобы эффективно отследить несколько маневрирующих целей. Отслеживание маневрирующих целей требует, чтобы радар пересматривал цели более часто, чем отслеживание неманеврирующих целей. Фильтр взаимодействующей многоуровневой модели (IMM) оценивает, когда цель маневрирует. Эта оценка помогает справиться, радар пересматривают время, и поэтому улучшает отслеживание. Этот пример использует Radar Toolbox™ для радарной модели и Sensor Fusion and Tracking Toolbox™ для отслеживания.

Введение

Многофункциональные радары могут искать цели, подтвердить новые треки и пересмотреть дорожки, чтобы обновить состояние. Чтобы выполнить эти функции, многофункциональный радар часто управляем менеджером ресурсов, который создает радарные задачи для поиска, подтверждения и отслеживания. Эти задачи планируются согласно приоритету и время так, чтобы на каждом временном шаге многофункциональный радар мог указать свой луч в желаемом направлении. Планирование Поиска и Дорожки для Многофункционального Радара Фазированной решетки (Radar Toolbox) пример показывает многофункциональный радар фазированной решетки, управляемый с менеджером ресурсов.

В этом примере мы расширяем Планирование Поиска и Дорожки для Многофункционального Радара Фазированной решетки (Radar Toolbox) пример к случаю нескольких маневрирующих целей. Существует два конфликтных требования для радара, используемого, чтобы отследить маневрирующие цели:

  1. Количество целей и их начального местоположения не обычно известно заранее. Поэтому радар должен постоянно искать сферу интересов, чтобы найти цели. Кроме того, радар должен обнаружить и установить дорожку на каждой цели, как только это вводит радарную зону охвата.

  2. Период времени целевого маневрирования неизвестен заранее. Если известно, что цели не маневрируют, радар может нечасто пересматривать цели. Однако, поскольку времена начала и конца маневра неизвестны, радар должен пересмотреть каждую дорожку достаточно часто, чтобы смочь распознать когда начала и концы маневра.

Радар должен балансироваться между обеспечением достаточного количества лучей, сосредоточенных на отслеженных целях и отъездом достаточного количества времени, чтобы искать новые цели. Один подход должен просто задать пересмотреть уровень на каждой отслеженной цели независимо от ее состояния маневрирования и оставить остающееся время для нового целевого поиска. Эта радарная схема управления иногда упоминается как Активное Отслеживание [1]. Когда больше целей становится отслеженным, радар может или выполнить меньше поисковых задач, или он может отслеживать каждую цель менее часто. Безусловно, если количество целей является большим, радар может быть разбит.

Активное отслеживание обрабатывает все дорожки таким же образом, который делает его основанным на режиме алгоритмом управления ресурсами. Более сложный способ управлять радаром основан на свойствах каждой дорожки. Например, используйте свойства дорожки, такие как размер ковариации неопределенности состояния, маневрирует ли дорожка, и как быстро это двигает актив, который защищает радарный сайт. Когда такие свойства используются, радарное управление ресурсами упоминается как Адаптивное Отслеживание [1].

В этом примере вы сравниваете результаты Активного Отслеживания и Адаптивного Отслеживания, когда радар адаптируется на основе предполагаемого маневра дорожки.

Задайте модель сценария и радара

Вы задаете сценарий и радар с частотой обновления 20 Гц, что означает, что радар имеет 20 лучей, в секунду выделенных или для поиска, подтверждения или для отслеживания. Вы загружаете траектории сравнительного теста, используемые в Траекториях Сравнительного теста для примера Отслеживания Мультиобъекта. Существует шесть траекторий сравнительного теста, и вы задаете траекторию для каждого. Эти шесть платформ на рисунке следуют за неманеврирующими участками, вкрапленными маневрирующими участками. Можно просмотреть траектории на рисунке.

% Create scenario
updateRate = 20;
scenario = trackingScenario('UpdateRate',updateRate);
% Add the benchmark trajectories
load('BenchmarkTrajectories.mat','-mat');
platform(scenario,'Trajectory',v1Trajectory);
platform(scenario,'Trajectory',v2Trajectory);
platform(scenario,'Trajectory',v3Trajectory);
platform(scenario,'Trajectory',v4Trajectory);
platform(scenario,'Trajectory',v5Trajectory);
platform(scenario,'Trajectory',v6Trajectory);

% Create visualization
f = figure;
mp = uipanel('Parent',f,'Title','Theater Plot','FontSize',12,...
    'BackgroundColor','white','Position',[.01 .25 .98 .73]);
tax = axes(mp,'ZDir','reverse');

% Visualize scenario
thp = theaterPlot('Parent',tax,'AxesUnits',["km","km","km"],'XLimits',[0 85000],'YLimits',[-45000 70000],'ZLimits',[-10000 1000]);
plp = platformPlotter(thp, 'DisplayName', 'Platforms');
pap = trajectoryPlotter(thp, 'DisplayName', 'Trajectories', 'LineWidth', 1);
dtp = detectionPlotter(thp, 'DisplayName', 'Detections');
cvp = coveragePlotter(thp, 'DisplayName', 'Radar Coverage');
trp = trackPlotter(thp, 'DisplayName', 'Tracks', 'ConnectHistory', 'on', 'ColorizeHistory', 'on');
numPlatforms = numel(scenario.Platforms);
trajectoryPositions = cell(1,numPlatforms);
for i = 1:numPlatforms
    trajectoryPositions{i} = lookupPose(scenario.Platforms{i}.Trajectory,(0:0.1:185));
end
plotTrajectory(pap, trajectoryPositions);
view(tax,3)

Вероятностная радарная модель задана с помощью radarDataGenerator Система object™. Установка 'ScanMode' свойство этого объекта к 'Custom' позволяет менеджеру ресурсов управлять углом вида радара. Это позволяет планировать радара для поиска, подтверждения и отслеживания целей. Радар смонтирован на новой платформе в сценарии.

radar = radarDataGenerator(1, ...
    'ScanMode', 'Custom', ...
    'UpdateRate', updateRate, ...
    'MountingLocation', [0 0 -15], ...
    'FieldOfView', [3;10], ...
    'AzimuthResolution', 1.5, ...
    'HasElevation', true, ...
    'DetectionCoordinates', 'Sensor spherical');
platform(scenario,'Position',[0 0 0],'Sensors',radar);

Задайте средство отслеживания

После того, как радар обнаруживает объекты, он кормит обнаружениями средство отслеживания, которое выполняет несколько операций. Средство отслеживания ведет список дорожек, которые являются оценками целевых состояний в сфере интересов. Если обнаружение не может быть присвоено никакой дорожке, уже обеспеченной средством отслеживания, средство отслеживания инициирует новый трек. В большинстве случаев, ли новый трек представляет истинную целевую или ложную цель, неясно. Сначала, дорожка создается с предварительным состоянием. Если достаточно обнаружений получено, дорожка становится подтвержденной. Точно так же, если никакие обнаружения не присвоены дорожке, дорожка курсируется (предсказанный без коррекции). Если дорожка имеет несколько пропущенных обновлений, средство отслеживания удаляет дорожку.

В этом примере вы используете средство отслеживания, которое сопоставляет обнаружения к дорожкам с помощью алгоритма глобального самого близкого соседа (GNN). Чтобы отследить маневрирующие цели, вы задаете FilterInitializationFcn функция, которая инициализирует фильтр IMM. initMPARIMM функционируйте использует две модели движения: модель постоянной скорости и модель постоянной угловой скорости вращения. trackingIMM фильтр ответственен за оценку вероятности каждой модели, к которой можно получить доступ от ее ModelProbabilities свойство. В этом примере вы классифицируете цель как маневрирование, когда вероятность модели постоянной угловой скорости вращения выше, чем 0,6.

tracker = trackerGNN('FilterInitializationFcn',@initMPARIMM,...
    'ConfirmationThreshold',[2 3], 'DeletionThreshold',[5 5],...
    'HasDetectableTrackIDsInput',true,'AssignmentThreshold',150,...
    'MaxNumTracks',10,'MaxNumSensors',1);
posSelector = [1 0 0 0 0 0; 0 0 1 0 0 0; 0 0 0 0 1 0];

Радарное управление ресурсами

Этот раздел только кратко обрисовывает в общих чертах радарное управление ресурсами. Для получения дополнительной информации смотрите, что Адаптивное Отслеживает Маневрирования Целей с Управляемым Радаром (Radar Toolbox) пример.

Поисковые задачи

В этом примере вы присваиваете поисковые задачи детерминировано. Развертка растра используется, чтобы покрыть желаемое воздушное пространство. Пределы сканирования азимута устанавливаются к [-90 60] степени и пределы вертикального изменения [-9.9 0] степени. Если никакие другие задачи не существуют, радиолокационные обзоры пробел одна угловая ячейка за один раз. Размер угловой ячейки определяется FieldOfView радара свойство. Отрицательные углы возвышения означают, что радар указывает луч от горизонта.

AzimuthLimits = [-90 60];
ElevationLimits = [-9.9 0];
azscanspan   = diff(AzimuthLimits);
numazscan    = floor(azscanspan/radar.FieldOfView(1))+1;
azscanangles = linspace(AzimuthLimits(1),AzimuthLimits(2),numazscan)+radar.MountingAngles(1);
elscanspan   = diff(ElevationLimits);
numelscan    = floor(elscanspan/radar.FieldOfView(2))+1;
elscanangles = linspace(ElevationLimits(1),ElevationLimits(2),numelscan)+radar.MountingAngles(2);
[elscangrid,azscangrid] = meshgrid(elscanangles,azscanangles);  
scanangles   = [azscangrid(:) elscangrid(:)].';
searchq = struct('JobType','Search','BeamDirection',num2cell(scanangles,1),...
    'Priority',1000,'WaveformIndex',1);
current_search_idx = 1;

Отследите задачи

В отличие от поисковых задач, задачи дорожки не могут быть запланированы заранее. Вместо этого менеджер ресурсов создает задачи подтверждения и отслеживания на основе изменяющегося сценария. Основное различие в этом примере от Адаптивного Отслеживания Маневрирования Целей с Управляемым Радаром (Radar Toolbox) пример то, что JobType для каждой дорожки задачей может быть любой "TrackNonManeuvering" или "TrackManeuvering". Различие между двумя типами отслеживания задач включает, вы, чтобы запланировать задачи для каждого типа дорожки в различном пересматриваете уровни, делая его адаптивным алгоритмом отслеживания. Подобно поисковым задачам отслеживающие задачи также управляемы в очереди заданий.

trackq = repmat(struct('JobType',[],'BeamDirection',[],'Priority',3000,'WaveformIndex',[],...
    'Time',[],'Range',[],'TrackID',[]), 10, 1);
num_trackq_items = 0;

Соберите в группу очереди поиска и отслеживания в структуре для более легкой ссылки в цикле симуляции.

jobq.SearchQueue  = searchq;
jobq.SearchIndex  = current_search_idx;
jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;
jobq.PositionSelector = posSelector;
% Keep a reset state of jobq
resetJobQ = jobq;

Планирование задач

В этом примере, для простоты, многофункциональный радар выполняет только один тип задания в маленьком периоде времени, часто называемом тем, чтобы жить, но может переключиться, задачи в начале каждого живут. Поскольку каждый живет, радар смотрит на все задачи, которые подлежат выполнению, и выбирает подтверждение или задачу дорожки, если ее время, чтобы запуститься настало. В противном случае радар выбирает поисковую задачу. Чтобы управлять временем, чтобы запустить задачи, вы устанавливаете managerPreferences структура, заданная ниже. Самые высокие пересматривают уровень, который равен радарной частоте обновления, дан confirm задача гарантировать, что луч подтверждения следует за каждой новой предварительной дорожкой, которая существует. Точно так же можно управлять пересмотреть уровнем для неманеврирования и маневрирования целей. В этом случае вы выбираете значения 0,8 Гц и 4 Гц для неманеврирования и маневрирования целей, соответственно. С тех пор существует 6 целей, менеджер ресурсов будет использовать 60.85 обновления в секунду при отслеживании предназначаются, если все они не будут маневрировать. Учитывая радар частота обновления составляет 20 Гц, менеджер по радару выполнит приблизительно одно целевое обновление в каждых четырех обновлениях. Таким образом радар будет тратить приблизительно 75% времени в режиме поиска и 25% времени в режиме отслеживания. Когда новые треки инициализируются и когда средство отслеживания полагает, что цели маневрируют, менеджер ресурсов выделяет больше лучей отслеживания за счет поисковых лучей.

Раздел Analyze Results показывает результаты других опций.

managerPreferences = struct(...
    'Type', {"Search","Confirm","TrackNonManeuvering","TrackManeuvering"}, ...
    'RevisitRate', {0, updateRate, 0.8, 4}, ...
    'Priority', {1000, 3000, 1500, 2500});

Запустите сценарий

Во время симуляции радарный луч изображен синими или фиолетовыми цветами, представляющими поиск и связанные с дорожкой лучи, соответственно. Можно также видеть распределение задач между поиском и определенными дорожками в течение прошлой секунды симуляции с помощью Распределения ресурсов в Последней Второй панели в нижней части фигуры. В Театральной части Графика фигуры вы видите историю каждой дорожки и сравниваете его с траекторией.

% Create a radar resource allocation display
rp = uipanel('Parent',f,'Title','Resource Allocation in the Last Second','FontSize',12,...
    'BackgroundColor','white','Position',[.01 0.01 0.98 0.23]);
rax = axes(rp);

% Run the scenario
allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences);

Анализ результатов

Анализируйте радарную загрузку задачи и ее деление между поиском, подтверждением и заданиями отслеживания. График показывает, что наиболее часто радар выделяет приблизительно 75%-е поисковые задания и 25%, отслеживающих задания, который является как ожидалось, когда цели не маневрируют. Когда цели маневрируют, менеджер ресурсов адаптируется, чтобы выделить больше заданий отслеживания. Когда больше дорожек маневрирует одновременно, там больше отслеживают задания, как замечено около 700-го временного шага. Задания подтверждения занимают очень мало радарного времени, потому что средство отслеживания сконфигурировано, чтобы подтвердить дорожки после двух ассоциаций обнаружения в трех попытках. Поэтому подтверждение или отклонение предварительных дорожек быстры.

numSteps = numel(allocationType);
allocationSummary = zeros(3,numSteps);
for i = 1:numSteps
    for jobType = 1:3
        allocationSummary(jobType,i) = sum(allocationType(1,max(1,i-2*updateRate+1):i)==jobType)/min(i-1,2*updateRate);
    end
end
figure;
plot(1:numSteps,allocationSummary(:,1:numSteps))
title('Radar Allocation vs. Time Step');
xlabel('Time Step');
ylabel('Fraction of step in the last two seconds in each mode');
legend('Search','Confirmation','Tracking');
grid on

Сравните этот результат результатом Активного Отслеживания в дорожке на 0,8 Гц пересматривают уровень. Следующие рисунки показывают результаты отслеживания и радарный график выделения для Активного случая Отслеживания. Результаты отслеживания показывают, что некоторые следы были потеряны и повреждены, но радарный график распределения ресурсов показывает подобный 75%-й поиск и 25%, отслеживающих деление задачи как в случае Адаптивного Отслеживания. Можно получить эти результаты путем выполнения примера кода ниже.

clearData(plp);
clearData(dtp);
clearData(trp);
reset(tracker);
restart(scenario);

% Modify manager preferences to 1 Hz revisit rate for both non-maneuvering and maneuvering targets
managerPreferences = struct(...
    'Type', {"Search","Confirm","TrackNonManeuvering","TrackManeuvering"}, ...
    'RevisitRate', {0, updateRate, 0.8, 0.8}, ...
    'Priority', {1000, 3000, 1500, 2500});
% Run the scenario
allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences);

Безусловно, более высокое отслеживание пересматривает уровень, необходим для Активного Отслеживания. Следующие два графика показывают, что увеличение дорожки пересматривает уровень к 2 Гц, улучшает отслеживание маневрирования целей. Однако стоимость - то, что радар выделяет больше чем 50% своего времени к отслеживанию задач, даже когда дорожки не маневрируют. Если бы количество целей было больше, радар стал бы разбитым.

Предыдущие результаты показывают, что достаточно пересмотреть маневрирующие цели на уровне 2 Гц. Однако Адаптивное Отслеживание может использоваться, чтобы понизить пересмотреть уровень неманеврирования целей вне 0,8 Гц? Следующие графики представляют результаты для 0,6 Гц и 4 Гц для неманеврирования и маневрирования целей, соответственно. С этой установкой радарное распределение ресурсов допускает 80%-85% времени в режиме поиска, оставляя радар с возможностью искать и отследить еще больше целей.

Сводные данные

В этом примере показано, как использовать комбинацию отслеживания и радарного управления ресурсами, чтобы адаптировать пересмотреть уровень к маневрированию дорожек. Адаптивное отслеживание позволяет, вы, чтобы выбрать пересматриваете уровень, который подходит для каждого целевого типа и маневрирующего состояния. В результате радар становится более эффективным и может отследить большее число маневрирования целей.

Ссылки

[1] Charlish, Александр, Фолкер Хоффман, Кристоф Деген и Изабель Шлэнджен. “Разработка От Адаптивного до Познавательного Радарного управления ресурсами”. Космос IEEE и Журнал 35 Электронных систем, № 6 (1 июня 2020): 8–19. https://doi.org/10.1109/MAES.2019.2957847.

Вспомогательные Функции

getCurrentRadarTask

Возвращает радарную задачу, которая используется для обращения радарного луча.

type('getCurrentRadarTask.m')
function [currentjob,jobq] = getCurrentRadarTask(jobq,current_time)

searchq   = jobq.SearchQueue;
trackq    = jobq.TrackQueue;
searchidx = jobq.SearchIndex;
num_trackq_items = jobq.NumTrackJobs;

% Update search queue index
searchqidx = mod(searchidx-1,numel(searchq))+1;

% Find the track job that is due and has the highest priority
readyidx = find([trackq(1:num_trackq_items).Time]<=current_time);
[~,maxpidx] = max([trackq(readyidx).Priority]);
taskqidx = readyidx(maxpidx);

% If the track job found has a higher priority, use that as the current job
% and increase the next search job priority since it gets postponed.
% Otherwise, the next search job due is the current job.
if ~isempty(taskqidx) % && trackq(taskqidx).Priority >= searchq(searchqidx).Priority
    currentjob = trackq(taskqidx);
    for m = taskqidx+1:num_trackq_items
        trackq(m-1) = trackq(m);
    end
    num_trackq_items = num_trackq_items-1;
    searchq(searchqidx).Priority = searchq(searchqidx).Priority+100;
else
    currentjob = searchq(searchqidx);
    searchidx = searchqidx+1;

end

jobq.SearchQueue  = searchq;
jobq.SearchIndex  = searchidx;
jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;

helperAdaptiveRadarSim

Запускает симуляцию

type('helperAdaptiveTrackingSim.m')
function allocationType = helperAdaptiveTrackingSim(scenario, thp, rax, tracker, resetJobQ, managerPreferences)
% Initialize variables
radar = scenario.Platforms{end}.Sensors{1};
updateRate = radar.UpdateRate;
resourceAllocation = nan(1,updateRate);
h = histogram(rax,resourceAllocation,'BinMethod','integers');
xlabel(h.Parent,'TrackID')
ylabel(h.Parent,'Num beams');
numSteps = updateRate * 185;
allocationType = nan(1,numSteps);
currentStep = 1;
restart(scenario);
reset(tracker);
% Return to a reset state of jobq
jobq = resetJobQ;

% Plotters and axes
plp = thp.Plotters(1);
dtp = thp.Plotters(3);
cvp = thp.Plotters(4);
trp = thp.Plotters(5);

% For repeatable results, set the random seed and revert it when done
s = rng(2020);
oc = onCleanup(@() rng(s));

% Main loop
tracks = {};

while advance(scenario)
    time = scenario.SimulationTime;
    
    % Update ground truth display
    poses = platformPoses(scenario);
    plotPlatform(plp, reshape([poses.Position],3,[])');
    
    % Point the radar based on the scheduler current job
    [currentJob,jobq] = getCurrentRadarTask(jobq,time);
    currentStep = currentStep + 1;
    if currentStep > updateRate
        resourceAllocation(1:end-1) = resourceAllocation(2:updateRate);
    end
    if strcmpi(currentJob.JobType,'Search')
        detectableTracks = zeros(0,1,'uint32');
        resourceAllocation(min([currentStep,updateRate])) = 0;
        allocationType(currentStep) = 1;
        cvp.Color = [0 0 1]; 
    else
        detectableTracks = currentJob.TrackID;
        resourceAllocation(min([currentStep,updateRate])) = currentJob.TrackID;
        cvp.Color = [1 0 1];
        if strcmpi(currentJob.JobType,'Confirm')
            allocationType(currentStep) = 2;
        else
            allocationType(currentStep) = 3;
        end
    end
    ra = resourceAllocation(~isnan(resourceAllocation));
    h.Data = ra;
    h.Parent.YLim = [0 updateRate];
    h.Parent.XTick = 0:max(ra);
    radar.LookAngle = currentJob.BeamDirection;
    plotCoverage(cvp, coverageConfig(scenario));
    
    % Collect detections and plot them
    detections = detect(scenario);
    if isempty(detections)
        meas = zeros(0,3);
    else
        dets = [detections{:}];
        meassph = reshape([dets.Measurement],3,[])';
        [x,y,z] = sph2cart(deg2rad(meassph(1)),deg2rad(meassph(2)),meassph(3));
        meas = (detections{1}.MeasurementParameters.Orientation*[x;y;z]+detections{1}.MeasurementParameters.OriginPosition)';
    end
    plotDetection(dtp, meas);
    
    % Track and plot tracks
    if isLocked(tracker) || ~isempty(detections)
        [confirmedTracks,tentativeTracks,~,analysisInformation] = tracker(detections, time, detectableTracks);
        pos = getTrackPositions(confirmedTracks,jobq.PositionSelector);
        plotTrack(trp,pos,string([confirmedTracks.TrackID]));
        
        tracks.confirmedTracks = confirmedTracks;
        tracks.tentativeTracks = tentativeTracks;
        tracks.analysisInformation = analysisInformation;
        tracks.PositionSelector = jobq.PositionSelector;
    end
    

    % Manage resources for next jobs
    jobq = manageResource(detections,jobq,tracker,tracks,currentJob,time,managerPreferences);
end

initMPARIMM

Инициализирует фильтр IMM, используемый средством отслеживания.

type('initMPARIMM.m')
function imm = initMPARIMM(detection)

cvekf = initcvekf(detection);
cvekf.StateCovariance(2,2) = 1e6;
cvekf.StateCovariance(4,4) = 1e6;
cvekf.StateCovariance(6,6) = 1e6;

ctekf = initctekf(detection);
ctekf.StateCovariance(2,2) = 1e6;
ctekf.StateCovariance(4,4) = 1e6;
ctekf.StateCovariance(7,7) = 1e6;
ctekf.ProcessNoise(3,3) = 1e6; % Large noise for unknown angular acceleration

imm = trackingIMM('TrackingFilters', {cvekf;ctekf}, 'TransitionProbabilities', [0.99, 0.99]);

manageResources

Управляет очередью заданий и создает новые задачи на основе результатов отслеживания.

type('manageResource.m')
function jobq = manageResource(detections,jobq,tracker,tracks,current_job,current_time,managerPreferences)

trackq           = jobq.TrackQueue;
num_trackq_items = jobq.NumTrackJobs;
if ~isempty(detections)
    detection = detections{1};
else
    detection = [];
end

% Execute current job
switch current_job.JobType
    case 'Search'
        % For search job, if there is a detection, establish tentative
        % track and schedule a confirmation job
        if ~isempty(detection)           
            % A search task can still find a track we already have. Define
            % a confirmation task only if it's a tentative track. There
            % could be more than one if there are false alarms. Create
            % confirm jobs for tentative tracks created at this update.

            numTentative = numel(tracks.tentativeTracks);
            for i = 1:numTentative
                if tracks.tentativeTracks(i).Age == 1 && tracks.tentativeTracks(i).IsCoasted == 0
                    trackid = tracks.tentativeTracks(i).TrackID;
                    job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'Confirm', tracks.PositionSelector);
                    num_trackq_items = num_trackq_items+1;
                    trackq(num_trackq_items) = job;
                end
            end
        end

    case 'Confirm'
        % For a confirm job, if the track ID is within the tentative
        % tracks, it means that we need to run another confirmation job
        % regardless of having a detection. If the track ID is within the
        % confirmed tracks, it means that we must have gotten a detection,
        % and the track passed the confirmation logic test. In this case we
        % need to schedule a track revisit job.
        trackid = current_job.TrackID;
        tentativeTrackIDs = [tracks.tentativeTracks.TrackID];        
        confirmedTrackIDs = [tracks.confirmedTracks.TrackID];
        if any(trackid == tentativeTrackIDs)
            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'Confirm', tracks.PositionSelector);
            num_trackq_items = num_trackq_items+1;
            trackq(num_trackq_items) = job;                
        elseif any(trackid == confirmedTrackIDs)
            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, 'TrackNonManeuvering', tracks.PositionSelector);
            num_trackq_items = num_trackq_items+1;
            trackq(num_trackq_items) = job;                
        end

    otherwise % Covers both types of track jobs
        % For track job, if the track hasn't been dropped, update the track
        % and schedule a track job corresponding to the revisit time
        % regardless of having a detection. In the case when there is no
        % detection, we could also predict and schedule a track job sooner
        % so the target is not lost. This would require defining another
        % job type to control the revisit rate for this case.
        
        trackid = current_job.TrackID;
        confirmedTrackIDs = [tracks.confirmedTracks.TrackID];
        if any(trackid == confirmedTrackIDs)           
            jobType = 'TrackNonManeuvering';
            mdlProbs = getTrackFilterProperties(tracker, trackid, 'ModelProbabilities');
            if mdlProbs{1}(2) > 0.6
                jobType = 'TrackManeuvering';
            end

            job = revisitTrackJob(tracker, trackid, current_time, managerPreferences, jobType, tracks.PositionSelector);
            num_trackq_items = num_trackq_items+1;
            trackq(num_trackq_items) = job;
        end
end
    
jobq.TrackQueue   = trackq;
jobq.NumTrackJobs = num_trackq_items;
end

function job = revisitTrackJob(tracker, trackID, currentTime, managerPreferences, jobType, positionSelector)
    types = [managerPreferences.Type];
    inTypes = strcmpi(jobType,types);
    revisitTime = 1/managerPreferences(inTypes).RevisitRate + currentTime;
    predictedTrack = predictTracksToTime(tracker,trackID,revisitTime);

    xpred = getTrackPositions(predictedTrack,positionSelector); 
    
    [phipred,thetapred,rpred] = cart2sph(xpred(1),xpred(2),xpred(3));
    job = struct('JobType',jobType,'Priority',managerPreferences(inTypes).Priority,...
        'BeamDirection',rad2deg([phipred thetapred]),'WaveformIndex',1,'Time',revisitTime,...
        'Range',rpred,'TrackID',trackID);
    
end