From 8ec1f008ab13e2787703f47383b794dc0743c610 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 24 Jun 2024 19:17:43 +0200 Subject: [PATCH] core: move split_dive*() functions to struct dive_table These functions have to access other dives in the list to calculate CNS, etc, so let's call them from there. Signed-off-by: Berthold Stoeger --- commands/command_divelist.cpp | 6 +- core/dive.cpp | 209 --------------------------------- core/dive.h | 3 - core/divelist.cpp | 210 ++++++++++++++++++++++++++++++++++ core/divelist.h | 5 +- 5 files changed, 217 insertions(+), 216 deletions(-) diff --git a/commands/command_divelist.cpp b/commands/command_divelist.cpp index bffa80c17..0dcf988de 100644 --- a/commands/command_divelist.cpp +++ b/commands/command_divelist.cpp @@ -833,9 +833,9 @@ static std::array, 2> doSplitDives(const dive *d, duration { // Split the dive if (time.seconds < 0) - return split_dive(*d); + return divelog.dives.split_dive(*d); else - return split_dive_at_time(*d, time); + return divelog.dives.split_dive_at_time(*d, time); } SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDives(d, time)) @@ -844,7 +844,7 @@ SplitDives::SplitDives(dive *d, duration_t time) : SplitDivesBase(d, doSplitDive } SplitDiveComputer::SplitDiveComputer(dive *d, int dc_num) : - SplitDivesBase(d, split_divecomputer(*d, dc_num)) + SplitDivesBase(d, divelog.dives.split_divecomputer(*d, dc_num)) { setText(Command::Base::tr("split dive computer")); } diff --git a/core/dive.cpp b/core/dive.cpp index 149b13067..528836cb3 100644 --- a/core/dive.cpp +++ b/core/dive.cpp @@ -2296,176 +2296,6 @@ merge_result merge_dives(const struct dive &a_in, const struct dive &b_in, int o return res; } -/* - * Split a dive that has a surface interval from samples 'a' to 'b' - * into two dives, but don't add them to the log yet. - * Returns the nr of the old dive or <0 on failure. - * Moreover, on failure both output dives are set to NULL. - * On success, the newly allocated dives are returned in out1 and out2. - */ -static std::array, 2> split_dive_at(const struct dive &dive, int a, int b) -{ - size_t nr = divelog.dives.get_idx(&dive); - - /* if we can't find the dive in the dive list, don't bother */ - if (nr == std::string::npos) - return {}; - - /* Splitting should leave at least 3 samples per dive */ - if (a < 3 || static_cast(b + 4) > dive.dcs[0].samples.size()) - return {}; - - /* We're not trying to be efficient here.. */ - auto d1 = std::make_unique(dive); - auto d2 = std::make_unique(dive); - d1->id = dive_getUniqID(); - d2->id = dive_getUniqID(); - d1->divetrip = d2->divetrip = nullptr; - - /* now unselect the first first segment so we don't keep all - * dives selected by mistake. But do keep the second one selected - * so the algorithm keeps splitting the dive further */ - d1->selected = false; - - struct divecomputer &dc1 = d1->dcs[0]; - struct divecomputer &dc2 = d2->dcs[0]; - /* - * Cut off the samples of d1 at the beginning - * of the interval. - */ - dc1.samples.resize(a); - - /* And get rid of the 'b' first samples of d2 */ - dc2.samples.erase(dc2.samples.begin(), dc2.samples.begin() + b); - - /* Now the secondary dive computers */ - int32_t t = dc2.samples[0].time.seconds; - for (auto it1 = d1->dcs.begin() + 1; it1 != d1->dcs.end(); ++it1) { - auto it = std::find_if(it1->samples.begin(), it1->samples.end(), - [t](auto &sample) { return sample.time.seconds >= t; }); - it1->samples.erase(it, it1->samples.end()); - } - for (auto it2 = d2->dcs.begin() + 1; it2 != d2->dcs.end(); ++it2) { - auto it = std::find_if(it2->samples.begin(), it2->samples.end(), - [t](auto &sample) { return sample.time.seconds >= t; }); - it2->samples.erase(it2->samples.begin(), it); - } - - /* - * This is where we cut off events from d1, - * and shift everything in d2 - */ - d2->when += t; - auto it1 = d1->dcs.begin(); - auto it2 = d2->dcs.begin(); - while (it1 != d1->dcs.end() && it2 != d2->dcs.end()) { - it2->when += t; - for (auto &sample: it2->samples) - sample.time.seconds -= t; - - /* Remove the events past 't' from d1 */ - auto it = std::lower_bound(it1->events.begin(), it1->events.end(), t, - [] (struct event &ev, int t) - { return ev.time.seconds < t; }); - it1->events.erase(it, it1->events.end()); - - /* Remove the events before 't' from d2, and shift the rest */ - it = std::lower_bound(it2->events.begin(), it2->events.end(), t, - [] (struct event &ev, int t) - { return ev.time.seconds < t; }); - it2->events.erase(it2->events.begin(), it); - for (auto &ev: it2->events) - ev.time.seconds -= t; - - ++it1; - ++it2; - } - - divelog.dives.force_fixup_dive(*d1); - divelog.dives.force_fixup_dive(*d2); - - /* - * Was the dive numbered? If it was the last dive, then we'll - * increment the dive number for the tail part that we split off. - * Otherwise the tail is unnumbered. - */ - if (d2->number) { - if (divelog.dives.size() == nr + 1) - d2->number++; - else - d2->number = 0; - } - - return { std::move(d1), std::move(d2) }; -} - -/* in freedive mode we split for as little as 10 seconds on the surface, - * otherwise we use a minute */ -static bool should_split(const struct divecomputer *dc, int t1, int t2) -{ - int threshold = dc->divemode == FREEDIVE ? 10 : 60; - - return t2 - t1 >= threshold; -} - -/* - * Try to split a dive into multiple dives at a surface interval point. - * - * NOTE! We will split when there is at least one surface event that has - * non-surface events on both sides. - * - * The surface interval points are determined using the first dive computer. - * - * In other words, this is a (simplified) reversal of the dive merging. - */ -std::array, 2> split_dive(const struct dive &dive) -{ - const struct divecomputer *dc = &dive.dcs[0]; - bool at_surface = true; - if (dc->samples.empty()) - return {}; - auto surface_start = dc->samples.begin(); - for (auto it = dc->samples.begin() + 1; it != dc->samples.end(); ++it) { - bool surface_sample = it->depth.mm < SURFACE_THRESHOLD; - - /* - * We care about the transition from and to depth 0, - * not about the depth staying similar. - */ - if (at_surface == surface_sample) - continue; - at_surface = surface_sample; - - // Did it become surface after having been non-surface? We found the start - if (at_surface) { - surface_start = it; - continue; - } - - // Going down again? We want at least a minute from - // the surface start. - if (surface_start == dc->samples.begin()) - continue; - if (!should_split(dc, surface_start->time.seconds, std::prev(it)->time.seconds)) - continue; - - return split_dive_at(dive, surface_start - dc->samples.begin(), it - dc->samples.begin() - 1); - } - return {}; -} - -std::array, 2> split_dive_at_time(const struct dive &dive, duration_t time) -{ - auto it = std::find_if(dive.dcs[0].samples.begin(), dive.dcs[0].samples.end(), - [time](auto &sample) { return sample.time.seconds >= time.seconds; }); - if (it == dive.dcs[0].samples.end()) - return {}; - size_t idx = it - dive.dcs[0].samples.begin(); - if (idx < 1) - return {}; - return split_dive_at(dive, static_cast(idx), static_cast(idx - 1)); -} - /* * "dc_maxtime()" is how much total time this dive computer * has for this dive. Note that it can differ from "duration" @@ -2606,45 +2436,6 @@ std::unique_ptr clone_delete_divecomputer(const struct dive &d, int dc_num return res; } -/* - * This splits the dive src by dive computer. The first output dive has all - * dive computers except num, the second only dive computer num. - * The dives will not be associated with a trip. - * On error, both output parameters are set to NULL. - */ -std::array, 2> split_divecomputer(const struct dive &src, int num) -{ - if (num < 0 || src.dcs.size() < 2 || static_cast(num) >= src.dcs.size()) - return {}; - - // Copy the dive with full divecomputer list - auto out1 = std::make_unique(src); - - // Remove all DCs, stash them and copy the dive again. - // Then, we have to dives without DCs and a list of DCs. - std::vector dcs; - std::swap(out1->dcs, dcs); - auto out2 = std::make_unique(*out1); - - // Give the dives new unique ids and remove them from the trip. - out1->id = dive_getUniqID(); - out2->id = dive_getUniqID(); - out1->divetrip = out2->divetrip = NULL; - - // Now copy the divecomputers - out1->dcs.reserve(src.dcs.size() - 1); - for (auto [idx, dc]: enumerated_range(dcs)) { - auto &dcs = idx == num ? out2->dcs : out1->dcs; - dcs.push_back(std::move(dc)); - } - - // Recalculate gas data, etc. - divelog.dives.fixup_dive(*out1); - divelog.dives.fixup_dive(*out2); - - return { std::move(out1), std::move(out2) }; -} - //Calculate O2 in best mix fraction_t best_o2(depth_t depth, const struct dive *dive, bool in_planner) { diff --git a/core/dive.h b/core/dive.h index fceacc68c..6289baab0 100644 --- a/core/dive.h +++ b/core/dive.h @@ -151,7 +151,6 @@ extern const struct divecomputer *get_dive_dc(const struct dive *dive, int nr); extern std::unique_ptr clone_make_first_dc(const struct dive &d, int dc_number); extern std::unique_ptr clone_delete_divecomputer(const struct dive &d, int dc_number); -extern std::array, 2> split_divecomputer(const struct dive &src, int num); extern bool dive_site_has_gps_location(const struct dive_site *ds); extern int dive_has_gps_location(const struct dive *dive); @@ -184,8 +183,6 @@ extern bool dive_less_than_ptr(const struct dive *a, const struct dive *b); extern bool dive_or_trip_less_than(struct dive_or_trip a, struct dive_or_trip b); extern int get_dive_salinity(const struct dive *dive); extern int dive_getUniqID(); -extern std::array, 2> split_dive(const struct dive &dive); -extern std::array, 2> split_dive_at_time(const struct dive &dive, duration_t time); struct merge_result { std::unique_ptr dive; diff --git a/core/divelist.cpp b/core/divelist.cpp index 27939711e..f1af46208 100644 --- a/core/divelist.cpp +++ b/core/divelist.cpp @@ -405,6 +405,7 @@ int dive_table::calculate_cns(struct dive &dive) const dive.cns = lrint(cns); return dive.cns; } + /* * Return air usage (in liters). */ @@ -939,3 +940,212 @@ bool has_dive(unsigned int deviceid, unsigned int diveid) }); }); } + +/* + * This splits the dive src by dive computer. The first output dive has all + * dive computers except num, the second only dive computer num. + * The dives will not be associated with a trip. + * On error, both output parameters are set to NULL. + */ +std::array, 2> dive_table::split_divecomputer(const struct dive &src, int num) const +{ + if (num < 0 || src.dcs.size() < 2 || static_cast(num) >= src.dcs.size()) + return {}; + + // Copy the dive with full divecomputer list + auto out1 = std::make_unique(src); + + // Remove all DCs, stash them and copy the dive again. + // Then, we have to dives without DCs and a list of DCs. + std::vector dcs; + std::swap(out1->dcs, dcs); + auto out2 = std::make_unique(*out1); + + // Give the dives new unique ids and remove them from the trip. + out1->id = dive_getUniqID(); + out2->id = dive_getUniqID(); + out1->divetrip = out2->divetrip = NULL; + + // Now copy the divecomputers + out1->dcs.reserve(src.dcs.size() - 1); + for (auto [idx, dc]: enumerated_range(dcs)) { + auto &dcs = idx == num ? out2->dcs : out1->dcs; + dcs.push_back(std::move(dc)); + } + + // Recalculate gas data, etc. + fixup_dive(*out1); + fixup_dive(*out2); + + return { std::move(out1), std::move(out2) }; +} + +/* + * Split a dive that has a surface interval from samples 'a' to 'b' + * into two dives, but don't add them to the log yet. + * Returns the nr of the old dive or <0 on failure. + * Moreover, on failure both output dives are set to NULL. + * On success, the newly allocated dives are returned in out1 and out2. + */ +std::array, 2> dive_table::split_dive_at(const struct dive &dive, int a, int b) const +{ + size_t nr = get_idx(&dive); + + /* if we can't find the dive in the dive list, don't bother */ + if (nr == std::string::npos) + return {}; + + bool is_last_dive = size() == nr + 1; + + /* Splitting should leave at least 3 samples per dive */ + if (a < 3 || static_cast(b + 4) > dive.dcs[0].samples.size()) + return {}; + + /* We're not trying to be efficient here.. */ + auto d1 = std::make_unique(dive); + auto d2 = std::make_unique(dive); + d1->id = dive_getUniqID(); + d2->id = dive_getUniqID(); + d1->divetrip = d2->divetrip = nullptr; + + /* now unselect the first first segment so we don't keep all + * dives selected by mistake. But do keep the second one selected + * so the algorithm keeps splitting the dive further */ + d1->selected = false; + + struct divecomputer &dc1 = d1->dcs[0]; + struct divecomputer &dc2 = d2->dcs[0]; + /* + * Cut off the samples of d1 at the beginning + * of the interval. + */ + dc1.samples.resize(a); + + /* And get rid of the 'b' first samples of d2 */ + dc2.samples.erase(dc2.samples.begin(), dc2.samples.begin() + b); + + /* Now the secondary dive computers */ + int32_t t = dc2.samples[0].time.seconds; + for (auto it1 = d1->dcs.begin() + 1; it1 != d1->dcs.end(); ++it1) { + auto it = std::find_if(it1->samples.begin(), it1->samples.end(), + [t](auto &sample) { return sample.time.seconds >= t; }); + it1->samples.erase(it, it1->samples.end()); + } + for (auto it2 = d2->dcs.begin() + 1; it2 != d2->dcs.end(); ++it2) { + auto it = std::find_if(it2->samples.begin(), it2->samples.end(), + [t](auto &sample) { return sample.time.seconds >= t; }); + it2->samples.erase(it2->samples.begin(), it); + } + + /* + * This is where we cut off events from d1, + * and shift everything in d2 + */ + d2->when += t; + auto it1 = d1->dcs.begin(); + auto it2 = d2->dcs.begin(); + while (it1 != d1->dcs.end() && it2 != d2->dcs.end()) { + it2->when += t; + for (auto &sample: it2->samples) + sample.time.seconds -= t; + + /* Remove the events past 't' from d1 */ + auto it = std::lower_bound(it1->events.begin(), it1->events.end(), t, + [] (struct event &ev, int t) + { return ev.time.seconds < t; }); + it1->events.erase(it, it1->events.end()); + + /* Remove the events before 't' from d2, and shift the rest */ + it = std::lower_bound(it2->events.begin(), it2->events.end(), t, + [] (struct event &ev, int t) + { return ev.time.seconds < t; }); + it2->events.erase(it2->events.begin(), it); + for (auto &ev: it2->events) + ev.time.seconds -= t; + + ++it1; + ++it2; + } + + force_fixup_dive(*d1); + force_fixup_dive(*d2); + + /* + * Was the dive numbered? If it was the last dive, then we'll + * increment the dive number for the tail part that we split off. + * Otherwise the tail is unnumbered. + */ + if (d2->number && is_last_dive) + d2->number++; + else + d2->number = 0; + + return { std::move(d1), std::move(d2) }; +} + +/* in freedive mode we split for as little as 10 seconds on the surface, + * otherwise we use a minute */ +static bool should_split(const struct divecomputer *dc, int t1, int t2) +{ + int threshold = dc->divemode == FREEDIVE ? 10 : 60; + + return t2 - t1 >= threshold; +} + +/* + * Try to split a dive into multiple dives at a surface interval point. + * + * NOTE! We will split when there is at least one surface event that has + * non-surface events on both sides. + * + * The surface interval points are determined using the first dive computer. + * + * In other words, this is a (simplified) reversal of the dive merging. + */ +std::array, 2> dive_table::split_dive(const struct dive &dive) const +{ + const struct divecomputer *dc = &dive.dcs[0]; + bool at_surface = true; + if (dc->samples.empty()) + return {}; + auto surface_start = dc->samples.begin(); + for (auto it = dc->samples.begin() + 1; it != dc->samples.end(); ++it) { + bool surface_sample = it->depth.mm < SURFACE_THRESHOLD; + + /* + * We care about the transition from and to depth 0, + * not about the depth staying similar. + */ + if (at_surface == surface_sample) + continue; + at_surface = surface_sample; + + // Did it become surface after having been non-surface? We found the start + if (at_surface) { + surface_start = it; + continue; + } + + // Going down again? We want at least a minute from + // the surface start. + if (surface_start == dc->samples.begin()) + continue; + if (!should_split(dc, surface_start->time.seconds, std::prev(it)->time.seconds)) + continue; + + return split_dive_at(dive, surface_start - dc->samples.begin(), it - dc->samples.begin() - 1); + } + return {}; +} + +std::array, 2> dive_table::split_dive_at_time(const struct dive &dive, duration_t time) const +{ + auto it = std::find_if(dive.dcs[0].samples.begin(), dive.dcs[0].samples.end(), + [time](auto &sample) { return sample.time.seconds >= time.seconds; }); + if (it == dive.dcs[0].samples.end()) + return {}; + size_t idx = it - dive.dcs[0].samples.begin(); + if (idx < 1) + return {}; + return split_dive_at(dive, static_cast(idx), static_cast(idx - 1)); +} diff --git a/core/divelist.h b/core/divelist.h index 9d68540bf..1e96c7040 100644 --- a/core/divelist.h +++ b/core/divelist.h @@ -6,7 +6,6 @@ #include "divesitetable.h" #include "units.h" #include -#include struct dive; struct divelog; @@ -33,8 +32,12 @@ struct dive_table : public sorted_owning_table { int get_dive_nr_at_idx(int idx) const; timestamp_t get_surface_interval(timestamp_t when) const; struct dive *find_next_visible_dive(timestamp_t when); + std::array, 2> split_divecomputer(const struct dive &src, int num) const; + std::array, 2> split_dive(const struct dive &dive) const; + std::array, 2> split_dive_at_time(const struct dive &dive, duration_t time) const; private: int calculate_cns(struct dive &dive) const; // Note: writes into dive->cns + std::array, 2> split_dive_at(const struct dive &dive, int a, int b) const; }; /* this is used for both git and xml format */