JobCrossingChecker: new checker for crossings and passings
Initial implementation
This commit is contained in:
parent
3b969aafce
commit
6dacc44880
|
@ -1,4 +1,5 @@
|
|||
add_subdirectory(jobeditor)
|
||||
add_subdirectory(jobs_checker)
|
||||
add_subdirectory(jobsmanager)
|
||||
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
add_subdirectory(crossing)
|
||||
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
|
||||
jobs/jobs_checker/crossing/job_crossing_data.cpp
|
||||
jobs/jobs_checker/crossing/job_crossing_data.h
|
||||
|
||||
jobs/jobs_checker/crossing/jobcrossingchecker.cpp
|
||||
jobs/jobs_checker/crossing/jobcrossingchecker.h
|
||||
|
||||
jobs/jobs_checker/crossing/jobcrossingmodel.cpp
|
||||
jobs/jobs_checker/crossing/jobcrossingmodel.h
|
||||
|
||||
jobs/jobs_checker/crossing/jobcrossingtask.cpp
|
||||
jobs/jobs_checker/crossing/jobcrossingtask.h
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
|
@ -0,0 +1,51 @@
|
|||
#include "job_crossing_data.h"
|
||||
|
||||
JobCrossingErrorMap::JobCrossingErrorMap()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void JobCrossingErrorMap::removeJob(db_id jobId)
|
||||
{
|
||||
auto job = map.find(jobId);
|
||||
if(job == map.end())
|
||||
return; //Not contained in map
|
||||
|
||||
//Remove all errors referencing to us
|
||||
for(const JobCrossingErrorData& err : qAsConst(job->errors))
|
||||
{
|
||||
auto otherJob = map.find(err.otherJob.jobId);
|
||||
if(otherJob == map.end())
|
||||
continue; //Maybe already remove, skip
|
||||
|
||||
//Remove all errors regarding job in otherJob
|
||||
std::remove_if(otherJob->errors.begin(),
|
||||
otherJob->errors.end(),
|
||||
[jobId](const JobCrossingErrorData& otherErr) -> bool
|
||||
{
|
||||
return otherErr.otherJob.jobId == jobId;
|
||||
});
|
||||
|
||||
if(otherJob->errors.isEmpty())
|
||||
{
|
||||
//otherJob has no errors, remove it
|
||||
map.erase(otherJob);
|
||||
}
|
||||
}
|
||||
|
||||
//Remove job
|
||||
map.erase(job);
|
||||
}
|
||||
|
||||
void JobCrossingErrorMap::merge(const ErrorMap &results)
|
||||
{
|
||||
for(const JobCrossingErrorList& list : results)
|
||||
{
|
||||
//First clear Job
|
||||
removeJob(list.job.jobId);
|
||||
|
||||
//Then add new errors if not empty (already duplicated)
|
||||
if(!list.errors.isEmpty())
|
||||
map.insert(list.job.jobId, list);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#ifndef JOB_CROSS_DATA_H
|
||||
#define JOB_CROSS_DATA_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include <QMap>
|
||||
#include <QTime>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
struct JobCrossingErrorData
|
||||
{
|
||||
db_id jobId;
|
||||
|
||||
db_id stopId;
|
||||
db_id stationId;
|
||||
|
||||
JobStopEntry otherJob;
|
||||
|
||||
QTime arrival, departure;
|
||||
QTime otherArr, otherDep;
|
||||
|
||||
QString stationName;
|
||||
|
||||
enum Type
|
||||
{
|
||||
NoError = 0,
|
||||
JobCrossing, //NOTE: arrival refers to next job stop so it comes after departure (same for otherArr/Dep)
|
||||
JobPassing
|
||||
};
|
||||
|
||||
Type type;
|
||||
};
|
||||
|
||||
struct JobCrossingErrorList
|
||||
{
|
||||
JobEntry job;
|
||||
QVector<JobCrossingErrorData> errors;
|
||||
|
||||
inline int childCount() const { return errors.size(); }
|
||||
inline const JobCrossingErrorData *ptrForRow(int row) const { return &errors.at(row); }
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The JobCrossingErrorMap class
|
||||
*
|
||||
* Each job crossing or passing involves 2 jobs.
|
||||
* This map contains duplicated errors from the point of view
|
||||
* of both jobs.
|
||||
*/
|
||||
class JobCrossingErrorMap
|
||||
{
|
||||
public:
|
||||
typedef QMap<db_id, JobCrossingErrorList> ErrorMap;
|
||||
|
||||
JobCrossingErrorMap();
|
||||
|
||||
inline int topLevelCount() const { return map.size(); }
|
||||
|
||||
inline const JobCrossingErrorList *getTopLevelAtRow(int row) const
|
||||
{
|
||||
if(row >= topLevelCount())
|
||||
return nullptr;
|
||||
return &(map.constBegin() + row).value();
|
||||
}
|
||||
|
||||
inline const JobCrossingErrorList *getParent(JobCrossingErrorData *child) const
|
||||
{
|
||||
auto it = map.constFind(child->jobId);
|
||||
if(it == map.constEnd())
|
||||
return nullptr;
|
||||
return &it.value();
|
||||
}
|
||||
|
||||
inline int getParentRow(JobCrossingErrorData *child) const
|
||||
{
|
||||
auto it = map.constFind(child->jobId);
|
||||
if(it == map.constEnd())
|
||||
return -1;
|
||||
return std::distance(map.constBegin(), it);
|
||||
}
|
||||
|
||||
void removeJob(db_id jobId);
|
||||
|
||||
void merge(const ErrorMap& results);
|
||||
|
||||
public:
|
||||
ErrorMap map;
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // JOB_CROSS_DATA_H
|
|
@ -0,0 +1,46 @@
|
|||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include "jobcrossingchecker.h"
|
||||
|
||||
#include "jobcrossingtask.h"
|
||||
#include "jobcrossingmodel.h"
|
||||
|
||||
JobCrossingChecker::JobCrossingChecker(sqlite3pp::database &db, QObject *parent) :
|
||||
IBackgroundChecker(db, parent)
|
||||
{
|
||||
eventType = int(JobCrossingResultEvent::_Type);
|
||||
|
||||
errorsModel = new JobCrossingModel(this);
|
||||
}
|
||||
|
||||
QString JobCrossingChecker::getName() const
|
||||
{
|
||||
return tr("Job Crossings");
|
||||
}
|
||||
|
||||
void JobCrossingChecker::clearModel()
|
||||
{
|
||||
static_cast<JobCrossingModel *>(errorsModel)->clear();
|
||||
}
|
||||
|
||||
void JobCrossingChecker::showContextMenu(QWidget *panel, const QPoint &pos, const QModelIndex &idx) const
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
IQuittableTask *JobCrossingChecker::createMainWorker()
|
||||
{
|
||||
return new JobCrossingTask(mDb, this, {});
|
||||
}
|
||||
|
||||
void JobCrossingChecker::setErrors(QEvent *e, bool merge)
|
||||
{
|
||||
auto model = static_cast<JobCrossingModel *>(errorsModel);
|
||||
auto ev = static_cast<JobCrossingResultEvent *>(e);
|
||||
if(merge)
|
||||
model->mergeErrors(ev->results);
|
||||
else
|
||||
model->setErrors(ev->results);
|
||||
}
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef JOBCROSSINGCHECKER_H
|
||||
#define JOBCROSSINGCHECKER_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include "backgroundmanager/ibackgroundchecker.h"
|
||||
|
||||
class JobCrossingChecker : public IBackgroundChecker
|
||||
{
|
||||
public:
|
||||
JobCrossingChecker(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
QString getName() const override;
|
||||
void clearModel() override;
|
||||
void showContextMenu(QWidget *panel, const QPoint& pos, const QModelIndex& idx) const override;
|
||||
|
||||
protected:
|
||||
IQuittableTask *createMainWorker() override;
|
||||
void setErrors(QEvent *e, bool merge) override;
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // JOBCROSSINGCHECKER_H
|
|
@ -0,0 +1,90 @@
|
|||
#include "jobcrossingmodel.h"
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
||||
JobCrossingModel::JobCrossingModel(QObject *parent) : JobCrossingModelBase(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariant JobCrossingModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
case JobName:
|
||||
return tr("Job");
|
||||
case StationName:
|
||||
return tr("Station");
|
||||
case Arrival:
|
||||
return tr("Arrival");
|
||||
case Departure:
|
||||
return tr("Departure");
|
||||
case Description:
|
||||
return tr("Description");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return JobCrossingModelBase::headerData(section, orientation, role);
|
||||
}
|
||||
|
||||
QVariant JobCrossingModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if(!idx.isValid() || role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
const JobCrossingErrorData *item = getItem(idx);
|
||||
if(item)
|
||||
{
|
||||
switch (idx.column())
|
||||
{
|
||||
case JobName:
|
||||
return JobCategoryName::jobName(item->otherJob.jobId, item->otherJob.category);
|
||||
case StationName:
|
||||
return item->stationName;
|
||||
case Arrival:
|
||||
return item->arrival;
|
||||
case Departure:
|
||||
return item->departure;
|
||||
case Description:
|
||||
break; //TODO
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Caption
|
||||
if(idx.row() >= m_data.topLevelCount() || idx.column() != 0)
|
||||
return QVariant();
|
||||
|
||||
auto topLevel = m_data.getTopLevelAtRow(idx.row());
|
||||
return JobCategoryName::jobName(topLevel->job.jobId, topLevel->job.category);
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void JobCrossingModel::setErrors(const JobCrossingErrorMap::ErrorMap &errMap)
|
||||
{
|
||||
beginResetModel();
|
||||
m_data.map = errMap;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void JobCrossingModel::mergeErrors(const JobCrossingErrorMap::ErrorMap &errMap)
|
||||
{
|
||||
beginResetModel();
|
||||
m_data.merge(errMap);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void JobCrossingModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_data.map.clear();
|
||||
endResetModel();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef JOBCROSSINGMODEL_H
|
||||
#define JOBCROSSINGMODEL_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include "utils/singledepthtreemodelhelper.h"
|
||||
|
||||
#include "job_crossing_data.h"
|
||||
|
||||
class JobCrossingModel;
|
||||
typedef SingleDepthTreeModelHelper<JobCrossingModel, JobCrossingErrorMap, JobCrossingErrorData> JobCrossingModelBase;
|
||||
|
||||
//TODO: make on-demand
|
||||
class JobCrossingModel : public JobCrossingModelBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
JobName = 0,
|
||||
StationName,
|
||||
Arrival,
|
||||
Departure,
|
||||
Description,
|
||||
NCols
|
||||
};
|
||||
|
||||
JobCrossingModel(QObject *parent = nullptr);
|
||||
|
||||
// Header:
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setErrors(const QMap<db_id, JobCrossingErrorList> &data);
|
||||
|
||||
void mergeErrors(const JobCrossingErrorMap::ErrorMap &errMap);
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // JOBCROSSINGMODEL_H
|
|
@ -0,0 +1,137 @@
|
|||
#include "jobcrossingtask.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
||||
JobCrossingResultEvent::JobCrossingResultEvent(JobCrossingTask *worker, const JobCrossingErrorMap::ErrorMap &data, bool merge) :
|
||||
GenericTaskEvent(_Type, worker),
|
||||
results(data),
|
||||
mergeErrors(merge)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
JobCrossingTask::JobCrossingTask(sqlite3pp::database &db, QObject *receiver, const QVector<db_id>& jobs) :
|
||||
IQuittableTask(receiver),
|
||||
mDb(db),
|
||||
jobsToCheck(jobs)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void JobCrossingTask::run()
|
||||
{
|
||||
//TODO: allow checking a single job
|
||||
|
||||
//Look for passing or crossings on same segment
|
||||
query q(mDb, "SELECT s1.id, s2.id, s1.job_id, j1.category, s2.job_id, j2.category,"
|
||||
"s1.departure, MIN(s1_next.arrival),"
|
||||
"s2.departure, MIN(s2_next.arrival),"
|
||||
"g1.gate_id=g2.gate_id," //1 = passing, 0 = crossing (opposite direction)
|
||||
"s1.station_id, stations.name"
|
||||
" FROM stops s1"
|
||||
" JOIN stops s1_next ON s1_next.job_id=s1.job_id AND s1_next.arrival>s1.arrival"
|
||||
" JOIN stops s2 ON s2.next_segment_conn_id=s1.next_segment_conn_id AND s2.id<>s1.id"
|
||||
" JOIN stops s2_next ON s2_next.job_id=s2.job_id AND s2_next.arrival>s2.arrival"
|
||||
" JOIN jobs j1 ON j1.id=s1.job_id"
|
||||
" JOIN jobs j2 ON j2.id=s2.job_id"
|
||||
" JOIN station_gate_connections g1 ON g1.id=s1.out_gate_conn"
|
||||
" JOIN station_gate_connections g2 ON g2.id=s2.out_gate_conn"
|
||||
" JOIN stations ON stations.id=s1.station_id"
|
||||
" GROUP BY s1.id,s2.id"
|
||||
" HAVING s1.departure<=s2_next.arrival AND s1_next.arrival>=s2.departure");
|
||||
|
||||
QMap<db_id, JobCrossingErrorList> errorMap;
|
||||
checkCrossAndPassSegments(errorMap, q);
|
||||
|
||||
sendEvent(new JobCrossingResultEvent(this, errorMap, false), true);
|
||||
|
||||
// if(jobsToCheck.isEmpty())
|
||||
// {
|
||||
// //Select all jobs
|
||||
// query q_getAll(mDb, "SELECT id,category FROM jobs");
|
||||
// for(auto job : q_getAll)
|
||||
// {
|
||||
// JobErrorList list;
|
||||
// list.jobId = job.get<db_id>(0);
|
||||
// list.jobCategory = JobCategory(job.get<int>(1));
|
||||
|
||||
// checkCrossAndPassSegments(list, &q);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// query q_getCat(mDb, "SELECT category FROM jobs WHERE id=?");
|
||||
// for(const db_id jobId : qAsConst(jobsToCheck))
|
||||
// {
|
||||
// JobErrorList list;
|
||||
// list.jobId = jobId;
|
||||
// list.jobCategory = JobCategory::NCategories;
|
||||
|
||||
// q_getCat.bind(1, jobId);
|
||||
// if(q_getCat.step() == SQLITE_ROW)
|
||||
// {
|
||||
// list.jobCategory = JobCategory(q_getCat.getRows().get<int>(0));
|
||||
// }
|
||||
// q_getCat.reset();
|
||||
|
||||
// if(list.jobCategory == JobCategory::NCategories)
|
||||
// continue; //Job doesn't exist, skip TODO: remove from error widget
|
||||
|
||||
// checkCrossAndPassSegments(list, &q);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void JobCrossingTask::checkCrossAndPassSegments(JobCrossingErrorMap::ErrorMap &errMap, sqlite3pp::query &q)
|
||||
{
|
||||
for(auto job : q)
|
||||
{
|
||||
JobCrossingErrorData err;
|
||||
|
||||
err.stopId = job.get<db_id>(0);
|
||||
err.otherJob.stopId = job.get<db_id>(1);
|
||||
err.jobId = job.get<db_id>(2);
|
||||
JobCategory category = JobCategory(job.get<int>(3));
|
||||
err.otherJob.jobId = job.get<db_id>(4);
|
||||
err.otherJob.category = JobCategory(job.get<int>(5));
|
||||
err.departure = job.get<QTime>(6);
|
||||
err.arrival = job.get<QTime>(7);
|
||||
err.otherDep = job.get<QTime>(8);
|
||||
err.otherArr = job.get<QTime>(9);
|
||||
bool passing = job.get<int>(10) == 1;
|
||||
err.stationId = job.get<db_id>(11);
|
||||
err.stationName = job.get<QString>(12);
|
||||
|
||||
if(passing)
|
||||
{
|
||||
//In passings:
|
||||
//job A starts before B but gets passed and ends after B
|
||||
//job B starts after A but ends before
|
||||
//B travel period is contained in A travel period
|
||||
|
||||
//We need stricter checking of time, one travel must be contained in the other
|
||||
if(err.departure < err.otherDep && err.arrival < err.otherArr)
|
||||
continue; //A travels before B, no passing
|
||||
if(err.departure > err.otherDep && err.arrival > err.otherArr)
|
||||
continue; //A travels after B, no passing
|
||||
}
|
||||
|
||||
err.type = passing ? JobCrossingErrorData::JobPassing : JobCrossingErrorData::JobCrossing;
|
||||
|
||||
auto it = errMap.find(err.jobId);
|
||||
if(it == errMap.end())
|
||||
{
|
||||
//Insert Job into map for first time
|
||||
JobCrossingErrorList list;
|
||||
list.job.jobId = err.jobId;
|
||||
list.job.category = category;
|
||||
|
||||
it = errMap.insert(list.job.jobId, list);
|
||||
}
|
||||
|
||||
it.value().errors.append(err);
|
||||
}
|
||||
|
||||
q.reset();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef JOBCROSSINGTASK_H
|
||||
#define JOBCROSSINGTASK_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QEvent>
|
||||
|
||||
#include "utils/thread/iquittabletask.h"
|
||||
#include "utils/thread/taskprogressevent.h"
|
||||
|
||||
#include "job_crossing_data.h"
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
class query;
|
||||
}
|
||||
|
||||
class JobCrossingTask;
|
||||
|
||||
class JobCrossingResultEvent : public GenericTaskEvent
|
||||
{
|
||||
public:
|
||||
static const Type _Type = Type(CustomEvents::JobsCrossingCheckResult);
|
||||
|
||||
JobCrossingResultEvent(JobCrossingTask *worker, const JobCrossingErrorMap::ErrorMap &data, bool merge);
|
||||
|
||||
QMap<db_id, JobCrossingErrorList> results;
|
||||
bool mergeErrors;
|
||||
};
|
||||
|
||||
class JobCrossingTask : public IQuittableTask
|
||||
{
|
||||
public:
|
||||
JobCrossingTask(sqlite3pp::database &db, QObject *receiver, const QVector<db_id>& jobs);
|
||||
|
||||
void run() override;
|
||||
|
||||
void checkCrossAndPassSegments(JobCrossingErrorMap::ErrorMap& errMap, sqlite3pp::query &q);
|
||||
|
||||
private:
|
||||
sqlite3pp::database &mDb;
|
||||
|
||||
QVector<db_id> jobsToCheck;
|
||||
};
|
||||
|
||||
#endif // JOBCROSSINGTASK_H
|
|
@ -47,6 +47,9 @@ enum class CustomEvents
|
|||
//Jobs
|
||||
JobsModelResult,
|
||||
|
||||
//Jobs Checker
|
||||
JobsCrossingCheckResult,
|
||||
|
||||
//Printing
|
||||
PrintProgress,
|
||||
|
||||
|
|
Loading…
Reference in New Issue