mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
profile: implement adaptive depth markings
The old code used the maximum / minimum values of nine-minute intervals to indicate maximum / minimum depths. This does not work well when zooming, since the labels will get sparse. Instead implement a primitive peak finding algorithm, that searches for the deepest peak in the whole plot and then repeats the procedure for the right and left sides, leaving out a certain distance to the origninal peak. This is repeated until there are no more peaks found. Only peaks of a certain prominence are considered, which conveniently gives us the valleys. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
efc89b9d9c
commit
7ef0ae02a8
2 changed files with 83 additions and 22 deletions
|
@ -130,6 +130,11 @@ void DiveProfileItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o
|
||||||
painter->restore();
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool comp_depth(const struct plot_data &p1, const struct plot_data &p2)
|
||||||
|
{
|
||||||
|
return p1.depth < p2.depth;
|
||||||
|
}
|
||||||
|
|
||||||
void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner)
|
void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner)
|
||||||
{
|
{
|
||||||
makePolygon(from, to);
|
makePolygon(from, to);
|
||||||
|
@ -163,37 +168,93 @@ void DiveProfileItem::replot(const dive *d, int from, int to, bool in_planner)
|
||||||
pat.setColorAt(0, getColor(DEPTH_TOP));
|
pat.setColorAt(0, getColor(DEPTH_TOP));
|
||||||
setBrush(QBrush(pat));
|
setBrush(QBrush(pat));
|
||||||
|
|
||||||
int last = -1;
|
// No point in searching peaks with less than three samples
|
||||||
for (int i = from; i < to; i++) {
|
if (to - from < 3)
|
||||||
struct plot_data *pd = dataModel.data().entry;
|
return;
|
||||||
struct plot_data *entry = pd + i;
|
|
||||||
// "min/max" are the 9-minute window min/max indices
|
|
||||||
struct plot_data *min_entry = pd + entry->min;
|
|
||||||
struct plot_data *max_entry = pd + entry->max;
|
|
||||||
|
|
||||||
if (entry->depth < 2000)
|
const int half_interval = vAxis.getMinLabelDistance(hAxis);
|
||||||
continue;
|
const int min_depth = 2000; // in mm
|
||||||
|
const int min_prominence = 2000; // in mm (should this adapt to depth range?)
|
||||||
|
const plot_data *data = dataModel.data().entry;
|
||||||
|
const int max_peaks = (data[to - 1].sec - data[from].sec) / half_interval + 1;
|
||||||
|
struct Peak {
|
||||||
|
int range_from;
|
||||||
|
int range_to;
|
||||||
|
int peak;
|
||||||
|
};
|
||||||
|
std::vector<Peak> stack;
|
||||||
|
stack.reserve(max_peaks);
|
||||||
|
int highest_peak = std::max_element(data + from, data + to, comp_depth) - data;
|
||||||
|
if (data[highest_peak].depth < min_depth)
|
||||||
|
return;
|
||||||
|
stack.push_back(Peak{ from, to, highest_peak });
|
||||||
|
while (!stack.empty()) {
|
||||||
|
Peak act_peak = stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
plot_depth_sample(data[act_peak.peak], Qt::AlignHCenter | Qt::AlignTop, getColor(SAMPLE_DEEP));
|
||||||
|
|
||||||
if ((entry == max_entry) && entry->depth / 100 != last) {
|
// Skip half_interval seconds to the left and right of peak
|
||||||
plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_DEEP));
|
// and add new peaks if there is enough place.
|
||||||
last = entry->depth / 100;
|
const plot_data &act_sample = data[act_peak.peak];
|
||||||
|
int valley = act_peak.peak;
|
||||||
|
|
||||||
|
// Search for first sample outside minimum range to the right.
|
||||||
|
int new_from;
|
||||||
|
for (new_from = act_peak.peak + 1; new_from + 3 < act_peak.range_to; ++new_from) {
|
||||||
|
if (data[new_from].sec > act_sample.sec + half_interval)
|
||||||
|
break;
|
||||||
|
if (data[new_from].depth < data[valley].depth)
|
||||||
|
valley = new_from;
|
||||||
|
}
|
||||||
|
// Continue search until peaks reach the minimum prominence (height from valley).
|
||||||
|
for ( ; new_from + 3 < act_peak.range_to; ++new_from) {
|
||||||
|
if (data[new_from].depth >= data[valley].depth + min_prominence) {
|
||||||
|
int new_peak = std::max_element(data + new_from, data + act_peak.range_to, comp_depth) - data;
|
||||||
|
if (data[new_peak].depth < min_depth)
|
||||||
|
break;
|
||||||
|
stack.push_back(Peak{ new_from, act_peak.range_to, new_peak });
|
||||||
|
|
||||||
|
if (data[valley].depth >= min_depth)
|
||||||
|
plot_depth_sample(data[valley], Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_SHALLOW));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (data[new_from].depth < data[valley].depth)
|
||||||
|
valley = new_from;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((entry == min_entry) && entry->depth / 100 != last) {
|
valley = act_peak.peak;
|
||||||
plot_depth_sample(entry, Qt::AlignHCenter | Qt::AlignTop, getColor(SAMPLE_SHALLOW));
|
|
||||||
last = entry->depth / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry->depth != last)
|
// Search for first sample outside minimum range to the left.
|
||||||
last = -1;
|
int new_to;
|
||||||
|
for (new_to = act_peak.peak - 1; new_to >= act_peak.range_from + 3; --new_to) {
|
||||||
|
if (data[new_to].sec + half_interval < act_sample.sec)
|
||||||
|
break;
|
||||||
|
if (data[new_to].depth < data[valley].depth)
|
||||||
|
valley = new_to;
|
||||||
|
}
|
||||||
|
// Continue search until peaks reach the minimum prominence (height from valley).
|
||||||
|
for ( ; new_to >= act_peak.range_from + 3; --new_to) {
|
||||||
|
if (data[new_to].depth >= data[valley].depth + min_prominence) {
|
||||||
|
int new_peak = std::max_element(data + act_peak.range_from, data + new_to, comp_depth) - data;
|
||||||
|
if (data[new_peak].depth < min_depth)
|
||||||
|
break;
|
||||||
|
stack.push_back(Peak{ act_peak.range_from, new_to, new_peak });
|
||||||
|
|
||||||
|
if (data[valley].depth >= min_depth)
|
||||||
|
plot_depth_sample(data[valley], Qt::AlignHCenter | Qt::AlignBottom, getColor(SAMPLE_SHALLOW));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (data[new_to].depth < data[valley].depth)
|
||||||
|
valley = new_to;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiveProfileItem::plot_depth_sample(struct plot_data *entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color)
|
void DiveProfileItem::plot_depth_sample(const struct plot_data &entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color)
|
||||||
{
|
{
|
||||||
DiveTextItem *item = new DiveTextItem(dpr, 1.0, flags, this);
|
DiveTextItem *item = new DiveTextItem(dpr, 1.0, flags, this);
|
||||||
item->set(get_depth_string(entry->depth, true), color);
|
item->set(get_depth_string(entry.depth, true), color);
|
||||||
item->setPos(hAxis.posAtValue(entry->sec), vAxis.posAtValue(entry->depth));
|
item->setPos(hAxis.posAtValue(entry.sec), vAxis.posAtValue(entry.depth));
|
||||||
texts.append(item);
|
texts.append(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ public:
|
||||||
DiveProfileItem(const DivePlotDataModel &model, const DiveCartesianAxis &hAxis, int hColumn, const DiveCartesianAxis &vAxis, int vColumn, double dpr);
|
DiveProfileItem(const DivePlotDataModel &model, const DiveCartesianAxis &hAxis, int hColumn, const DiveCartesianAxis &vAxis, int vColumn, double dpr);
|
||||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
|
||||||
void replot(const dive *d, int from, int to, bool in_planner) override;
|
void replot(const dive *d, int from, int to, bool in_planner) override;
|
||||||
void plot_depth_sample(struct plot_data *entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color);
|
void plot_depth_sample(const struct plot_data &entry, QFlags<Qt::AlignmentFlag> flags, const QColor &color);
|
||||||
int maxCeiling(int row);
|
int maxCeiling(int row);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Add table
Reference in a new issue