2013-04-15 03:10:25 +00:00
|
|
|
/* statistics.c
|
2011-11-02 16:10:57 +00:00
|
|
|
*
|
2013-04-15 03:10:25 +00:00
|
|
|
* core logic for the Info & Stats page -
|
|
|
|
* char *get_time_string(int seconds, int maxdays);
|
|
|
|
* char *get_minutes(int seconds);
|
|
|
|
* void process_all_dives(struct dive *dive, struct dive **prev_dive);
|
|
|
|
* void get_selected_dives_text(char *buffer, int size);
|
2011-11-02 16:10:57 +00:00
|
|
|
*/
|
2012-10-11 00:42:59 +00:00
|
|
|
#include <glib/gi18n.h>
|
2012-12-20 23:46:47 +00:00
|
|
|
#include <ctype.h>
|
2011-11-02 16:10:57 +00:00
|
|
|
|
|
|
|
#include "dive.h"
|
|
|
|
#include "display.h"
|
|
|
|
#include "divelist.h"
|
2013-04-15 03:10:25 +00:00
|
|
|
#include "statistics.h"
|
2011-11-02 16:10:57 +00:00
|
|
|
|
2013-04-09 20:06:30 +00:00
|
|
|
/* mark for translation but don't translate here as these terms are used
|
|
|
|
* in save-xml.c */
|
|
|
|
char *dtag_names[DTAG_NR] = {
|
|
|
|
N_("invalid"), N_("boat"), N_("shore"), N_("drift"), N_("deep"), N_("cavern"),
|
2013-04-09 20:40:16 +00:00
|
|
|
N_("ice"), N_("wreck"), N_("cave"), N_("altitude"), N_("pool"), N_("lake"),
|
2013-04-10 15:49:11 +00:00
|
|
|
N_("river"), N_("night"), N_("freshwater"), N_("training"), N_("teaching"),
|
2013-05-15 01:52:55 +00:00
|
|
|
N_("photo"), N_("video"), N_("deco")
|
2013-04-09 20:06:30 +00:00
|
|
|
};
|
|
|
|
|
2012-01-15 21:19:39 +00:00
|
|
|
static stats_t stats;
|
2013-04-15 03:10:25 +00:00
|
|
|
stats_t stats_selection;
|
|
|
|
stats_t *stats_monthly = NULL;
|
|
|
|
stats_t *stats_yearly = NULL;
|
2012-09-10 19:17:28 +00:00
|
|
|
|
2013-01-24 21:08:19 +00:00
|
|
|
static void process_temperatures(struct dive *dp, stats_t *stats)
|
2013-01-24 18:58:59 +00:00
|
|
|
{
|
|
|
|
int min_temp, mean_temp, max_temp = 0;
|
|
|
|
|
2013-02-09 04:10:47 +00:00
|
|
|
max_temp = dp->maxtemp.mkelvin;
|
2013-01-24 18:58:59 +00:00
|
|
|
if (max_temp && (!stats->max_temp || max_temp > stats->max_temp))
|
|
|
|
stats->max_temp = max_temp;
|
|
|
|
|
2013-02-09 04:10:47 +00:00
|
|
|
min_temp = dp->mintemp.mkelvin;
|
2013-01-24 18:58:59 +00:00
|
|
|
if (min_temp && (!stats->min_temp || min_temp < stats->min_temp))
|
|
|
|
stats->min_temp = min_temp;
|
|
|
|
|
|
|
|
if (min_temp || max_temp) {
|
|
|
|
mean_temp = min_temp;
|
|
|
|
if (mean_temp)
|
|
|
|
mean_temp = (mean_temp + max_temp) / 2;
|
|
|
|
else
|
|
|
|
mean_temp = max_temp;
|
2013-01-24 21:08:19 +00:00
|
|
|
stats->combined_temp += get_temp_units(mean_temp, NULL);
|
2013-01-24 18:58:59 +00:00
|
|
|
stats->combined_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-14 17:01:34 +00:00
|
|
|
static void process_dive(struct dive *dp, stats_t *stats)
|
|
|
|
{
|
|
|
|
int old_tt, sac_time = 0;
|
2013-02-09 15:12:30 +00:00
|
|
|
int duration = dp->duration.seconds;
|
2012-03-14 17:01:34 +00:00
|
|
|
|
|
|
|
old_tt = stats->total_time.seconds;
|
2013-02-08 06:48:07 +00:00
|
|
|
stats->total_time.seconds += duration;
|
|
|
|
if (duration > stats->longest_time.seconds)
|
|
|
|
stats->longest_time.seconds = duration;
|
|
|
|
if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds)
|
|
|
|
stats->shortest_time.seconds = duration;
|
2013-02-09 04:44:04 +00:00
|
|
|
if (dp->maxdepth.mm > stats->max_depth.mm)
|
|
|
|
stats->max_depth.mm = dp->maxdepth.mm;
|
|
|
|
if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm)
|
|
|
|
stats->min_depth.mm = dp->maxdepth.mm;
|
2013-01-24 18:58:59 +00:00
|
|
|
|
2013-01-24 21:08:19 +00:00
|
|
|
process_temperatures(dp, stats);
|
2012-07-01 03:12:11 +00:00
|
|
|
|
|
|
|
/* Maybe we should drop zero-duration dives */
|
2013-02-08 06:48:07 +00:00
|
|
|
if (!duration)
|
2012-07-01 03:12:11 +00:00
|
|
|
return;
|
2012-03-14 17:01:34 +00:00
|
|
|
stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm +
|
2013-02-09 14:50:53 +00:00
|
|
|
duration * dp->meandepth.mm) / stats->total_time.seconds;
|
2012-03-14 17:01:34 +00:00
|
|
|
if (dp->sac > 2800) { /* less than .1 cuft/min (2800ml/min) is bogus */
|
2013-02-08 06:48:07 +00:00
|
|
|
sac_time = stats->total_sac_time + duration;
|
2012-03-30 05:10:05 +00:00
|
|
|
stats->avg_sac.mliter = (1.0 * stats->total_sac_time * stats->avg_sac.mliter +
|
2013-02-08 06:48:07 +00:00
|
|
|
duration * dp->sac) / sac_time ;
|
2012-03-14 17:01:34 +00:00
|
|
|
if (dp->sac > stats->max_sac.mliter)
|
|
|
|
stats->max_sac.mliter = dp->sac;
|
|
|
|
if (stats->min_sac.mliter == 0 || dp->sac < stats->min_sac.mliter)
|
|
|
|
stats->min_sac.mliter = dp->sac;
|
2012-03-30 05:10:05 +00:00
|
|
|
stats->total_sac_time = sac_time;
|
2012-03-14 17:01:34 +00:00
|
|
|
}
|
|
|
|
}
|
2011-11-02 16:10:57 +00:00
|
|
|
|
2013-04-15 03:10:25 +00:00
|
|
|
char *get_minutes(int seconds)
|
2012-09-10 19:17:28 +00:00
|
|
|
{
|
|
|
|
static char buf[80];
|
|
|
|
snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60));
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2013-04-15 03:10:25 +00:00
|
|
|
void process_all_dives(struct dive *dive, struct dive **prev_dive)
|
2011-11-02 16:10:57 +00:00
|
|
|
{
|
|
|
|
int idx;
|
|
|
|
struct dive *dp;
|
2012-09-20 00:35:52 +00:00
|
|
|
struct tm tm;
|
2012-09-10 19:17:28 +00:00
|
|
|
int current_year = 0;
|
|
|
|
int current_month = 0;
|
|
|
|
int year_iter = 0;
|
|
|
|
int month_iter = 0;
|
|
|
|
int prev_month = 0, prev_year = 0;
|
|
|
|
unsigned int size;
|
2011-11-02 16:10:57 +00:00
|
|
|
|
|
|
|
*prev_dive = NULL;
|
2012-01-15 21:19:39 +00:00
|
|
|
memset(&stats, 0, sizeof(stats));
|
2012-01-15 23:21:56 +00:00
|
|
|
if (dive_table.nr > 0) {
|
2013-02-09 15:12:30 +00:00
|
|
|
stats.shortest_time.seconds = dive_table.dives[0]->duration.seconds;
|
2013-02-09 04:44:04 +00:00
|
|
|
stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm;
|
2012-03-14 17:01:34 +00:00
|
|
|
stats.selection_size = dive_table.nr;
|
2012-01-15 23:21:56 +00:00
|
|
|
}
|
2012-09-10 19:17:28 +00:00
|
|
|
|
|
|
|
/* allocate sufficient space to hold the worst
|
|
|
|
* case (one dive per year or all dives during
|
|
|
|
* one month) for yearly and monthly statistics*/
|
|
|
|
|
|
|
|
if (stats_yearly != NULL) {
|
|
|
|
free(stats_yearly);
|
|
|
|
free(stats_monthly);
|
|
|
|
}
|
|
|
|
size = sizeof(stats_t) * (dive_table.nr + 1);
|
|
|
|
stats_yearly = malloc(size);
|
|
|
|
stats_monthly = malloc(size);
|
|
|
|
if (!stats_yearly || !stats_monthly)
|
|
|
|
return;
|
|
|
|
memset(stats_yearly, 0, size);
|
|
|
|
memset(stats_monthly, 0, size);
|
2013-06-19 17:30:36 +00:00
|
|
|
stats_yearly[0].is_year = TRUE;
|
2012-09-10 19:17:28 +00:00
|
|
|
|
2011-11-02 16:10:57 +00:00
|
|
|
/* this relies on the fact that the dives in the dive_table
|
|
|
|
* are in chronological order */
|
|
|
|
for (idx = 0; idx < dive_table.nr; idx++) {
|
|
|
|
dp = dive_table.dives[idx];
|
2013-02-19 00:56:28 +00:00
|
|
|
if (dive && dp->when == dive->when) {
|
2011-11-02 16:10:57 +00:00
|
|
|
/* that's the one we are showing */
|
|
|
|
if (idx > 0)
|
|
|
|
*prev_dive = dive_table.dives[idx-1];
|
|
|
|
}
|
2012-03-14 17:01:34 +00:00
|
|
|
process_dive(dp, &stats);
|
2012-09-10 19:17:28 +00:00
|
|
|
|
|
|
|
/* yearly statistics */
|
2012-09-20 00:35:52 +00:00
|
|
|
utc_mkdate(dp->when, &tm);
|
2012-09-10 19:17:28 +00:00
|
|
|
if (current_year == 0)
|
2012-09-20 00:35:52 +00:00
|
|
|
current_year = tm.tm_year + 1900;
|
2012-09-10 19:17:28 +00:00
|
|
|
|
2012-09-20 19:13:55 +00:00
|
|
|
if (current_year != tm.tm_year + 1900) {
|
2012-09-20 00:35:52 +00:00
|
|
|
current_year = tm.tm_year + 1900;
|
2012-09-10 19:17:28 +00:00
|
|
|
process_dive(dp, &(stats_yearly[++year_iter]));
|
2013-06-19 17:30:36 +00:00
|
|
|
stats_yearly[year_iter].is_year = TRUE;
|
2013-01-29 21:10:46 +00:00
|
|
|
} else {
|
2012-09-10 19:17:28 +00:00
|
|
|
process_dive(dp, &(stats_yearly[year_iter]));
|
2013-01-29 21:10:46 +00:00
|
|
|
}
|
2012-09-10 19:17:28 +00:00
|
|
|
stats_yearly[year_iter].selection_size++;
|
|
|
|
stats_yearly[year_iter].period = current_year;
|
|
|
|
|
|
|
|
/* monthly statistics */
|
|
|
|
if (current_month == 0) {
|
2012-09-20 00:35:52 +00:00
|
|
|
current_month = tm.tm_mon + 1;
|
2012-09-10 19:17:28 +00:00
|
|
|
} else {
|
2012-09-20 00:35:52 +00:00
|
|
|
if (current_month != tm.tm_mon + 1)
|
|
|
|
current_month = tm.tm_mon + 1;
|
2012-09-10 19:17:28 +00:00
|
|
|
if (prev_month != current_month || prev_year != current_year)
|
|
|
|
month_iter++;
|
|
|
|
}
|
|
|
|
process_dive(dp, &(stats_monthly[month_iter]));
|
|
|
|
stats_monthly[month_iter].selection_size++;
|
|
|
|
stats_monthly[month_iter].period = current_month;
|
|
|
|
prev_month = current_month;
|
|
|
|
prev_year = current_year;
|
2012-03-14 17:01:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-14 04:11:09 +00:00
|
|
|
/* make sure we skip the selected summary entries */
|
2012-08-20 12:48:07 +00:00
|
|
|
void process_selected_dives(void)
|
2012-03-14 17:01:34 +00:00
|
|
|
{
|
2012-08-20 12:48:07 +00:00
|
|
|
struct dive *dive;
|
|
|
|
unsigned int i, nr;
|
2012-03-14 17:01:34 +00:00
|
|
|
|
|
|
|
memset(&stats_selection, 0, sizeof(stats_selection));
|
|
|
|
|
2012-08-20 12:48:07 +00:00
|
|
|
nr = 0;
|
2012-08-21 22:51:34 +00:00
|
|
|
for_each_dive(i, dive) {
|
2012-08-20 12:48:07 +00:00
|
|
|
if (dive->selected) {
|
|
|
|
process_dive(dive, &stats_selection);
|
|
|
|
nr++;
|
2011-11-02 16:10:57 +00:00
|
|
|
}
|
|
|
|
}
|
2012-08-20 12:48:07 +00:00
|
|
|
stats_selection.selection_size = nr;
|
2011-11-02 16:10:57 +00:00
|
|
|
}
|
|
|
|
|
2013-04-15 03:10:25 +00:00
|
|
|
char *get_time_string(int seconds, int maxdays)
|
2011-11-02 16:10:57 +00:00
|
|
|
{
|
|
|
|
static char buf[80];
|
2013-01-29 21:10:46 +00:00
|
|
|
if (maxdays && seconds > 3600 * 24 * maxdays) {
|
2012-10-11 00:42:59 +00:00
|
|
|
snprintf(buf, sizeof(buf), _("more than %d days"), maxdays);
|
2013-01-29 21:10:46 +00:00
|
|
|
} else {
|
2011-11-02 16:10:57 +00:00
|
|
|
int days = seconds / 3600 / 24;
|
|
|
|
int hours = (seconds - days * 3600 * 24) / 3600;
|
|
|
|
int minutes = (seconds - days * 3600 * 24 - hours * 3600) / 60;
|
|
|
|
if (days > 0)
|
2012-10-11 00:42:59 +00:00
|
|
|
snprintf(buf, sizeof(buf), _("%dd %dh %dmin"), days, hours, minutes);
|
2011-11-02 16:10:57 +00:00
|
|
|
else
|
2012-10-11 00:42:59 +00:00
|
|
|
snprintf(buf, sizeof(buf), _("%dh %dmin"), hours, minutes);
|
2011-11-02 16:10:57 +00:00
|
|
|
}
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2012-12-20 23:46:47 +00:00
|
|
|
/* this gets called when at least two but not all dives are selected */
|
|
|
|
static void get_ranges(char *buffer, int size)
|
|
|
|
{
|
|
|
|
int i, len;
|
|
|
|
int first, last = -1;
|
|
|
|
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("for dives #"));
|
2012-12-20 23:46:47 +00:00
|
|
|
for (i = 0; i < dive_table.nr; i++) {
|
|
|
|
struct dive *dive = get_dive(i);
|
|
|
|
if (! dive->selected)
|
|
|
|
continue;
|
|
|
|
if (dive->number < 1) {
|
|
|
|
/* uhh - weird numbers - bail */
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("for selected dives"));
|
2012-12-20 23:46:47 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
len = strlen(buffer);
|
|
|
|
if (last == -1) {
|
|
|
|
snprintf(buffer + len, size - len, "%d", dive->number);
|
|
|
|
first = last = dive->number;
|
|
|
|
} else {
|
|
|
|
if (dive->number == last + 1) {
|
|
|
|
last++;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
if (first == last)
|
|
|
|
snprintf(buffer + len, size - len, ", %d", dive->number);
|
|
|
|
else if (first + 1 == last)
|
|
|
|
snprintf(buffer + len, size - len, ", %d, %d", last, dive->number);
|
|
|
|
else
|
|
|
|
snprintf(buffer + len, size - len, "-%d, %d", last, dive->number);
|
|
|
|
first = last = dive->number;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-04-08 02:50:26 +00:00
|
|
|
len = strlen(buffer);
|
2012-12-20 23:46:47 +00:00
|
|
|
if (first != last) {
|
|
|
|
if (first + 1 == last)
|
|
|
|
snprintf(buffer + len, size - len, ", %d", last);
|
|
|
|
else
|
|
|
|
snprintf(buffer + len, size - len, "-%d", last);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-15 03:10:25 +00:00
|
|
|
void get_selected_dives_text(char *buffer, int size)
|
2012-12-20 23:46:47 +00:00
|
|
|
{
|
|
|
|
if (amount_selected == 1) {
|
|
|
|
if (current_dive)
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("for dive #%d"), current_dive->number);
|
2012-12-20 23:46:47 +00:00
|
|
|
else
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("for selected dive"));
|
2012-12-20 23:46:47 +00:00
|
|
|
} else if (amount_selected == dive_table.nr) {
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("for all dives"));
|
2012-12-20 23:46:47 +00:00
|
|
|
} else if (amount_selected == 0) {
|
2012-12-22 21:34:22 +00:00
|
|
|
snprintf(buffer, size, _("(no dives)"));
|
2012-12-20 23:46:47 +00:00
|
|
|
} else {
|
|
|
|
get_ranges(buffer, size);
|
|
|
|
if (strlen(buffer) == size -1) {
|
|
|
|
/* add our own ellipse... the way Pango does this is ugly
|
|
|
|
* as it will leave partial numbers there which I don't like */
|
|
|
|
int offset = 4;
|
|
|
|
while (offset < size && isdigit(buffer[size - offset]))
|
|
|
|
offset++;
|
|
|
|
strcpy(buffer + size - offset, "...");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-07 03:36:37 +00:00
|
|
|
volume_t get_gas_used(struct dive *dive)
|
|
|
|
{
|
|
|
|
int idx;
|
|
|
|
volume_t gas_used = { 0 };
|
|
|
|
for (idx = 0; idx < MAX_CYLINDERS; idx++) {
|
|
|
|
cylinder_t *cyl = &dive->cylinder[idx];
|
|
|
|
pressure_t start, end;
|
|
|
|
|
|
|
|
start = cyl->start.mbar ? cyl->start : cyl->sample_start;
|
2013-10-03 13:54:09 +00:00
|
|
|
end = cyl->end.mbar ? cyl->end : cyl->sample_end;
|
2013-05-07 03:36:37 +00:00
|
|
|
if (start.mbar && end.mbar)
|
|
|
|
gas_used.mliter += gas_volume(cyl, start) - gas_volume(cyl, end);
|
|
|
|
}
|
|
|
|
return gas_used;
|
|
|
|
}
|
2013-05-19 15:08:29 +00:00
|
|
|
|
2013-05-31 04:40:46 +00:00
|
|
|
bool is_gas_used(struct dive *dive, int idx)
|
|
|
|
{
|
|
|
|
cylinder_t *cyl = &dive->cylinder[idx];
|
|
|
|
int o2, he;
|
|
|
|
struct divecomputer *dc;
|
|
|
|
bool used = FALSE;
|
|
|
|
bool firstGasExplicit = FALSE;
|
|
|
|
if (cylinder_none(cyl))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
o2 = get_o2(&cyl->gasmix);
|
|
|
|
he = get_he(&cyl->gasmix);
|
|
|
|
dc = &dive->dc;
|
|
|
|
while (dc) {
|
|
|
|
struct event *event = dc->events;
|
|
|
|
while (event) {
|
|
|
|
if (event->value) {
|
|
|
|
if (event->name && !strcmp(event->name, "gaschange")) {
|
|
|
|
unsigned int event_he = event->value >> 16;
|
|
|
|
unsigned int event_o2 = event->value & 0xffff;
|
|
|
|
if (event->time.seconds < 30)
|
|
|
|
firstGasExplicit = TRUE;
|
|
|
|
if (is_air(o2, he)) {
|
|
|
|
if (is_air(event_o2 * 10, event_he * 10))
|
|
|
|
used = TRUE;
|
|
|
|
} else if (he == event_he * 10 && o2 == event_o2 * 10) {
|
|
|
|
used = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (used)
|
|
|
|
break;
|
|
|
|
event = event->next;
|
|
|
|
}
|
|
|
|
if (used)
|
|
|
|
break;
|
|
|
|
dc = dc->next;
|
|
|
|
}
|
|
|
|
if (idx == 0 && !firstGasExplicit)
|
|
|
|
used = TRUE;
|
|
|
|
return used;
|
|
|
|
}
|
|
|
|
|
2013-05-19 15:08:29 +00:00
|
|
|
#define MAXBUF 80
|
|
|
|
/* for the O2/He readings just create a list of them */
|
|
|
|
char *get_gaslist(struct dive *dive)
|
|
|
|
{
|
|
|
|
int idx, offset = 0;
|
|
|
|
static char buf[MAXBUF];
|
2013-05-31 04:40:46 +00:00
|
|
|
int o2, he;
|
2013-05-19 15:08:29 +00:00
|
|
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
for (idx = 0; idx < MAX_CYLINDERS; idx++) {
|
2013-05-31 04:40:46 +00:00
|
|
|
cylinder_t *cyl;
|
|
|
|
if (!is_gas_used(dive, idx))
|
|
|
|
continue;
|
|
|
|
cyl = &dive->cylinder[idx];
|
|
|
|
o2 = get_o2(&cyl->gasmix);
|
|
|
|
he = get_he(&cyl->gasmix);
|
|
|
|
if (is_air(o2, he))
|
|
|
|
snprintf(buf + offset, MAXBUF - offset, (offset > 0) ? ", %s" : "%s", _("air"));
|
|
|
|
else
|
2013-06-12 16:07:47 +00:00
|
|
|
if (he == 0)
|
|
|
|
snprintf(buf + offset, MAXBUF - offset, (offset > 0) ? _(", EAN%d") : _("EAN%d"),
|
|
|
|
(o2 + 5) / 10);
|
|
|
|
else
|
|
|
|
snprintf(buf + offset, MAXBUF - offset, (offset > 0) ? ", %d/%d" : "%d/%d",
|
2013-05-19 15:08:29 +00:00
|
|
|
(o2 + 5) / 10, (he + 5) / 10);
|
2013-05-31 04:40:46 +00:00
|
|
|
offset = strlen(buf);
|
2013-05-19 15:08:29 +00:00
|
|
|
}
|
2013-05-31 04:40:46 +00:00
|
|
|
if (*buf == '\0')
|
|
|
|
strncpy(buf, _("air"), MAXBUF);
|
2013-05-19 15:08:29 +00:00
|
|
|
return buf;
|
|
|
|
}
|