2011-09-20 19:40:34 +00:00
|
|
|
/* dive.c */
|
|
|
|
/* maintains the internal dive list structure */
|
2011-09-03 20:19:26 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2012-10-11 00:42:59 +00:00
|
|
|
#include <glib/gi18n.h>
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
#include "dive.h"
|
|
|
|
|
2011-09-23 01:02:54 +00:00
|
|
|
void add_event(struct dive *dive, int time, int type, int flags, int value, const char *name)
|
|
|
|
{
|
|
|
|
struct event *ev, **p;
|
|
|
|
unsigned int size, len = strlen(name);
|
|
|
|
|
|
|
|
size = sizeof(*ev) + len + 1;
|
|
|
|
ev = malloc(size);
|
|
|
|
if (!ev)
|
|
|
|
return;
|
|
|
|
memset(ev, 0, size);
|
|
|
|
memcpy(ev->name, name, len);
|
|
|
|
ev->time.seconds = time;
|
|
|
|
ev->type = type;
|
|
|
|
ev->flags = flags;
|
|
|
|
ev->value = value;
|
|
|
|
ev->next = NULL;
|
|
|
|
|
|
|
|
p = &dive->events;
|
|
|
|
while (*p)
|
|
|
|
p = &(*p)->next;
|
|
|
|
*p = ev;
|
2011-10-25 07:29:19 +00:00
|
|
|
remember_event(name);
|
2011-09-23 01:02:54 +00:00
|
|
|
}
|
|
|
|
|
2011-11-02 03:13:14 +00:00
|
|
|
int get_pressure_units(unsigned int mb, const char **units)
|
|
|
|
{
|
|
|
|
int pressure;
|
|
|
|
const char* unit;
|
|
|
|
|
|
|
|
switch (output_units.pressure) {
|
|
|
|
case PASCAL:
|
|
|
|
pressure = mb * 100;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("pascal");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
case BAR:
|
|
|
|
pressure = (mb + 500) / 1000;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("bar");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
case PSI:
|
|
|
|
pressure = mbar_to_PSI(mb);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("psi");
|
2011-11-02 03:13:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return pressure;
|
|
|
|
}
|
|
|
|
|
2011-11-01 18:39:52 +00:00
|
|
|
double get_temp_units(unsigned int mk, const char **units)
|
|
|
|
{
|
|
|
|
double deg;
|
|
|
|
const char *unit;
|
|
|
|
|
|
|
|
if (output_units.temperature == FAHRENHEIT) {
|
|
|
|
deg = mkelvin_to_F(mk);
|
|
|
|
unit = UTF8_DEGREE "F";
|
|
|
|
} else {
|
|
|
|
deg = mkelvin_to_C(mk);
|
|
|
|
unit = UTF8_DEGREE "C";
|
|
|
|
}
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return deg;
|
|
|
|
}
|
|
|
|
|
2011-11-02 02:56:14 +00:00
|
|
|
double get_volume_units(unsigned int ml, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double vol;
|
|
|
|
const char *unit;
|
|
|
|
|
|
|
|
switch (output_units.volume) {
|
|
|
|
case LITER:
|
|
|
|
vol = ml / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("l");
|
2011-11-02 02:56:14 +00:00
|
|
|
decimals = 1;
|
|
|
|
break;
|
|
|
|
case CUFT:
|
|
|
|
vol = ml_to_cuft(ml);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("cuft");
|
2011-11-02 02:56:14 +00:00
|
|
|
decimals = 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return vol;
|
|
|
|
}
|
|
|
|
|
2011-09-21 19:12:54 +00:00
|
|
|
double get_depth_units(unsigned int mm, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double d;
|
|
|
|
const char *unit;
|
|
|
|
|
|
|
|
switch (output_units.length) {
|
|
|
|
case METERS:
|
|
|
|
d = mm / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("m");
|
2011-09-21 19:12:54 +00:00
|
|
|
decimals = d < 20;
|
|
|
|
break;
|
|
|
|
case FEET:
|
|
|
|
d = mm_to_feet(mm);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("ft");
|
2011-09-21 19:12:54 +00:00
|
|
|
decimals = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2012-08-07 18:24:40 +00:00
|
|
|
double get_weight_units(unsigned int grams, int *frac, const char **units)
|
|
|
|
{
|
|
|
|
int decimals;
|
|
|
|
double value;
|
|
|
|
const char* unit;
|
|
|
|
|
|
|
|
if (output_units.weight == LBS) {
|
|
|
|
value = grams_to_lbs(grams);
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("lbs");
|
2012-08-07 18:24:40 +00:00
|
|
|
decimals = 0;
|
|
|
|
} else {
|
|
|
|
value = grams / 1000.0;
|
2012-10-17 17:42:31 +00:00
|
|
|
unit = _("kg");
|
2012-08-07 18:24:40 +00:00
|
|
|
decimals = 1;
|
|
|
|
}
|
|
|
|
if (frac)
|
|
|
|
*frac = decimals;
|
|
|
|
if (units)
|
|
|
|
*units = unit;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2011-09-12 19:56:34 +00:00
|
|
|
struct dive *alloc_dive(void)
|
|
|
|
{
|
|
|
|
const int initial_samples = 5;
|
|
|
|
unsigned int size;
|
|
|
|
struct dive *dive;
|
|
|
|
|
|
|
|
size = dive_size(initial_samples);
|
|
|
|
dive = malloc(size);
|
|
|
|
if (!dive)
|
|
|
|
exit(1);
|
|
|
|
memset(dive, 0, size);
|
|
|
|
dive->alloc_samples = initial_samples;
|
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct sample *prepare_sample(struct dive **divep)
|
|
|
|
{
|
|
|
|
struct dive *dive = *divep;
|
|
|
|
if (dive) {
|
|
|
|
int nr = dive->samples;
|
|
|
|
int alloc_samples = dive->alloc_samples;
|
|
|
|
struct sample *sample;
|
|
|
|
if (nr >= alloc_samples) {
|
|
|
|
unsigned int size;
|
|
|
|
|
|
|
|
alloc_samples = (alloc_samples * 3)/2 + 10;
|
|
|
|
size = dive_size(alloc_samples);
|
|
|
|
dive = realloc(dive, size);
|
|
|
|
if (!dive)
|
|
|
|
return NULL;
|
|
|
|
dive->alloc_samples = alloc_samples;
|
|
|
|
*divep = dive;
|
|
|
|
}
|
|
|
|
sample = dive->sample + nr;
|
|
|
|
memset(sample, 0, sizeof(*sample));
|
|
|
|
return sample;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-01-05 06:01:02 +00:00
|
|
|
void finish_sample(struct dive *dive)
|
2011-09-12 19:56:34 +00:00
|
|
|
{
|
|
|
|
dive->samples++;
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:36:25 +00:00
|
|
|
/*
|
|
|
|
* So when we re-calculate maxdepth and meandepth, we will
|
|
|
|
* not override the old numbers if they are close to the
|
|
|
|
* new ones.
|
|
|
|
*
|
|
|
|
* Why? Because a dive computer may well actually track the
|
|
|
|
* max depth and mean depth at finer granularity than the
|
|
|
|
* samples it stores. So it's possible that the max and mean
|
|
|
|
* have been reported more correctly originally.
|
|
|
|
*
|
2011-09-04 01:48:39 +00:00
|
|
|
* Only if the values calculated from the samples are clearly
|
2011-09-03 20:36:25 +00:00
|
|
|
* different do we override the normal depth values.
|
|
|
|
*
|
|
|
|
* This considers 1m to be "clearly different". That's
|
|
|
|
* a totally random number.
|
|
|
|
*/
|
|
|
|
static void update_depth(depth_t *depth, int new)
|
|
|
|
{
|
2011-09-04 20:06:47 +00:00
|
|
|
if (new) {
|
|
|
|
int old = depth->mm;
|
2011-09-03 20:36:25 +00:00
|
|
|
|
2011-09-04 20:06:47 +00:00
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
depth->mm = new;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_duration(duration_t *duration, int new)
|
|
|
|
{
|
|
|
|
if (new)
|
|
|
|
duration->seconds = new;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_temperature(temperature_t *temperature, int new)
|
|
|
|
{
|
|
|
|
if (new) {
|
|
|
|
int old = temperature->mkelvin;
|
|
|
|
|
|
|
|
if (abs(old - new) > 1000)
|
|
|
|
temperature->mkelvin = new;
|
|
|
|
}
|
2011-09-03 20:36:25 +00:00
|
|
|
}
|
|
|
|
|
2011-11-09 15:37:25 +00:00
|
|
|
static void fixup_pressure(struct dive *dive, struct sample *sample)
|
2011-09-05 16:12:54 +00:00
|
|
|
{
|
|
|
|
unsigned int pressure, index;
|
|
|
|
cylinder_t *cyl;
|
|
|
|
|
|
|
|
pressure = sample->cylinderpressure.mbar;
|
|
|
|
if (!pressure)
|
2011-11-09 15:37:25 +00:00
|
|
|
return;
|
2011-09-05 16:12:54 +00:00
|
|
|
index = sample->cylinderindex;
|
|
|
|
if (index >= MAX_CYLINDERS)
|
2011-11-09 15:37:25 +00:00
|
|
|
return;
|
2011-09-05 16:12:54 +00:00
|
|
|
cyl = dive->cylinder + index;
|
2011-11-09 15:37:25 +00:00
|
|
|
if (!cyl->sample_start.mbar)
|
|
|
|
cyl->sample_start.mbar = pressure;
|
|
|
|
cyl->sample_end.mbar = pressure;
|
2011-09-05 16:12:54 +00:00
|
|
|
}
|
|
|
|
|
2011-11-10 23:33:38 +00:00
|
|
|
/*
|
|
|
|
* If the cylinder tank pressures are within half a bar
|
|
|
|
* (about 8 PSI) of the sample pressures, we consider it
|
|
|
|
* to be a rounding error, and throw them away as redundant.
|
|
|
|
*/
|
|
|
|
static int same_rounded_pressure(pressure_t a, pressure_t b)
|
|
|
|
{
|
|
|
|
return abs(a.mbar - b.mbar) <= 500;
|
|
|
|
}
|
|
|
|
|
2011-12-30 21:09:17 +00:00
|
|
|
static void sanitize_gasmix(struct gasmix *mix)
|
|
|
|
{
|
|
|
|
unsigned int o2, he;
|
|
|
|
|
|
|
|
o2 = mix->o2.permille;
|
|
|
|
he = mix->he.permille;
|
|
|
|
|
|
|
|
/* Regular air: leave empty */
|
|
|
|
if (!he) {
|
|
|
|
if (!o2)
|
|
|
|
return;
|
|
|
|
/* 20.9% or 21% O2 is just air */
|
|
|
|
if (o2 >= 209 && o2 <= 210) {
|
|
|
|
mix->o2.permille = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sane mix? */
|
|
|
|
if (o2 <= 1000 && he <= 1000 && o2+he <= 1000)
|
|
|
|
return;
|
2012-09-18 23:51:48 +00:00
|
|
|
fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he);
|
2011-12-30 21:09:17 +00:00
|
|
|
memset(mix, 0, sizeof(*mix));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the size/workingpressure looks like some standard cylinder
|
|
|
|
* size, eg "AL80".
|
|
|
|
*/
|
|
|
|
static void match_standard_cylinder(cylinder_type_t *type)
|
|
|
|
{
|
|
|
|
double cuft;
|
|
|
|
int psi, len;
|
|
|
|
const char *fmt;
|
|
|
|
char buffer[20], *p;
|
|
|
|
|
|
|
|
/* Do we already have a cylinder description? */
|
|
|
|
if (type->description)
|
|
|
|
return;
|
|
|
|
|
|
|
|
cuft = ml_to_cuft(type->size.mliter);
|
|
|
|
cuft *= to_ATM(type->workingpressure);
|
|
|
|
psi = to_PSI(type->workingpressure);
|
|
|
|
|
|
|
|
switch (psi) {
|
|
|
|
case 2300 ... 2500: /* 2400 psi: LP tank */
|
|
|
|
fmt = "LP%d";
|
|
|
|
break;
|
|
|
|
case 2600 ... 2700: /* 2640 psi: LP+10% */
|
|
|
|
fmt = "LP%d";
|
|
|
|
break;
|
|
|
|
case 2900 ... 3100: /* 3000 psi: ALx tank */
|
|
|
|
fmt = "AL%d";
|
|
|
|
break;
|
|
|
|
case 3400 ... 3500: /* 3442 psi: HP tank */
|
|
|
|
fmt = "HP%d";
|
|
|
|
break;
|
|
|
|
case 3700 ... 3850: /* HP+10% */
|
|
|
|
fmt = "HP%d+";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5));
|
|
|
|
p = malloc(len+1);
|
|
|
|
if (!p)
|
|
|
|
return;
|
|
|
|
memcpy(p, buffer, len+1);
|
|
|
|
type->description = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* There are two ways to give cylinder size information:
|
|
|
|
* - total amount of gas in cuft (depends on working pressure and physical size)
|
|
|
|
* - physical size
|
|
|
|
*
|
|
|
|
* where "physical size" is the one that actually matters and is sane.
|
|
|
|
*
|
|
|
|
* We internally use physical size only. But we save the workingpressure
|
|
|
|
* so that we can do the conversion if required.
|
|
|
|
*/
|
|
|
|
static void sanitize_cylinder_type(cylinder_type_t *type)
|
|
|
|
{
|
|
|
|
double volume_of_air, atm, volume;
|
|
|
|
|
|
|
|
/* If we have no working pressure, it had *better* be just a physical size! */
|
|
|
|
if (!type->workingpressure.mbar)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* No size either? Nothing to go on */
|
|
|
|
if (!type->size.mliter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (input_units.volume == CUFT) {
|
|
|
|
/* confusing - we don't really start from ml but millicuft !*/
|
|
|
|
volume_of_air = cuft_to_l(type->size.mliter);
|
|
|
|
atm = to_ATM(type->workingpressure); /* working pressure in atm */
|
|
|
|
volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */
|
|
|
|
type->size.mliter = volume + 0.5;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ok, we have both size and pressure: try to match a description */
|
|
|
|
match_standard_cylinder(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sanitize_cylinder_info(struct dive *dive)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
sanitize_gasmix(&dive->cylinder[i].gasmix);
|
|
|
|
sanitize_cylinder_type(&dive->cylinder[i].type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
/* some events should never be thrown away */
|
|
|
|
static gboolean is_potentially_redundant(struct event *event)
|
|
|
|
{
|
|
|
|
if (!strcmp(event->name, "gaschange"))
|
|
|
|
return FALSE;
|
2012-11-10 19:02:21 +00:00
|
|
|
if (!strcmp(event->name, "bookmark"))
|
|
|
|
return FALSE;
|
|
|
|
if (!strcmp(event->name, "heading"))
|
|
|
|
return FALSE;
|
2012-11-10 10:40:35 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* match just by name - we compare the details in the code that uses this helper */
|
|
|
|
static struct event *find_previous_event(struct dive *dive, struct event *event)
|
|
|
|
{
|
|
|
|
struct event *ev = dive->events;
|
|
|
|
struct event *previous = NULL;
|
|
|
|
|
|
|
|
if (!event->name)
|
|
|
|
return NULL;
|
|
|
|
while (ev && ev != event) {
|
|
|
|
if(ev->name && !strcmp(ev->name, event->name))
|
|
|
|
previous = ev;
|
|
|
|
ev = ev->next;
|
|
|
|
}
|
|
|
|
return previous;
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
struct dive *fixup_dive(struct dive *dive)
|
|
|
|
{
|
2011-11-19 12:09:14 +00:00
|
|
|
int i,j;
|
2011-09-03 20:19:26 +00:00
|
|
|
double depthtime = 0;
|
|
|
|
int lasttime = 0;
|
2011-11-17 14:03:11 +00:00
|
|
|
int lastindex = -1;
|
2011-09-03 20:19:26 +00:00
|
|
|
int start = -1, end = -1;
|
|
|
|
int maxdepth = 0, mintemp = 0;
|
|
|
|
int lastdepth = 0;
|
2011-11-17 14:03:11 +00:00
|
|
|
int lasttemp = 0, lastpressure = 0;
|
2011-11-19 12:09:14 +00:00
|
|
|
int pressure_delta[MAX_CYLINDERS] = {INT_MAX, };
|
2012-11-10 10:40:35 +00:00
|
|
|
struct event *event;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-10-02 14:56:12 +00:00
|
|
|
add_people(dive->buddy);
|
|
|
|
add_people(dive->divemaster);
|
|
|
|
add_location(dive->location);
|
|
|
|
add_suit(dive->suit);
|
2011-12-30 21:09:17 +00:00
|
|
|
sanitize_cylinder_info(dive);
|
2011-09-03 20:19:26 +00:00
|
|
|
for (i = 0; i < dive->samples; i++) {
|
|
|
|
struct sample *sample = dive->sample + i;
|
|
|
|
int time = sample->time.seconds;
|
|
|
|
int depth = sample->depth.mm;
|
|
|
|
int temp = sample->temperature.mkelvin;
|
2011-11-17 14:03:11 +00:00
|
|
|
int pressure = sample->cylinderpressure.mbar;
|
|
|
|
int index = sample->cylinderindex;
|
|
|
|
|
2011-11-19 12:09:14 +00:00
|
|
|
if (index == lastindex) {
|
|
|
|
/* Remove duplicate redundant pressure information */
|
|
|
|
if (pressure == lastpressure)
|
|
|
|
sample->cylinderpressure.mbar = 0;
|
|
|
|
/* check for simply linear data in the samples
|
|
|
|
+INT_MAX means uninitialized, -INT_MAX means not linear */
|
|
|
|
if (pressure_delta[index] != -INT_MAX && lastpressure) {
|
|
|
|
if (pressure_delta[index] == INT_MAX) {
|
|
|
|
pressure_delta[index] = abs(pressure - lastpressure);
|
|
|
|
} else {
|
|
|
|
int cur_delta = abs(pressure - lastpressure);
|
|
|
|
if (cur_delta && abs(cur_delta - pressure_delta[index]) > 150) {
|
|
|
|
/* ok the samples aren't just a linearisation
|
|
|
|
* between start and end */
|
|
|
|
pressure_delta[index] = -INT_MAX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 14:03:11 +00:00
|
|
|
lastindex = index;
|
|
|
|
lastpressure = pressure;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2012-11-11 09:36:46 +00:00
|
|
|
if (lastdepth > SURFACE_THRESHOLD)
|
2011-09-03 20:19:26 +00:00
|
|
|
end = time;
|
|
|
|
|
2012-11-11 09:36:46 +00:00
|
|
|
if (depth > SURFACE_THRESHOLD) {
|
2011-09-03 20:19:26 +00:00
|
|
|
if (start < 0)
|
|
|
|
start = lasttime;
|
|
|
|
if (depth > maxdepth)
|
|
|
|
maxdepth = depth;
|
|
|
|
}
|
2011-09-05 16:12:54 +00:00
|
|
|
|
2011-11-09 15:37:25 +00:00
|
|
|
fixup_pressure(dive, sample);
|
2011-09-05 16:12:54 +00:00
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
if (temp) {
|
2011-09-04 18:20:27 +00:00
|
|
|
/*
|
|
|
|
* If we have consecutive identical
|
|
|
|
* temperature readings, throw away
|
2011-09-13 03:37:32 +00:00
|
|
|
* the redundant ones.
|
2011-09-04 18:20:27 +00:00
|
|
|
*/
|
2011-09-13 03:37:32 +00:00
|
|
|
if (lasttemp == temp)
|
|
|
|
sample->temperature.mkelvin = 0;
|
|
|
|
else
|
2011-09-04 18:20:27 +00:00
|
|
|
lasttemp = temp;
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
if (!mintemp || temp < mintemp)
|
|
|
|
mintemp = temp;
|
|
|
|
}
|
|
|
|
depthtime += (time - lasttime) * (lastdepth + depth) / 2;
|
|
|
|
lastdepth = depth;
|
|
|
|
lasttime = time;
|
|
|
|
}
|
2012-11-11 09:36:46 +00:00
|
|
|
dive->start = start;
|
|
|
|
dive->end = end;
|
2011-11-19 12:09:14 +00:00
|
|
|
/* if all the samples for a cylinder have pressure data that
|
|
|
|
* is basically equidistant throw out the sample cylinder pressure
|
|
|
|
* information but make sure we still have a valid start and end
|
|
|
|
* pressure
|
|
|
|
* this happens when DivingLog decides to linearalize the
|
|
|
|
* pressure between beginning and end and for strange reasons
|
|
|
|
* decides to put that in the sample data as if it came from
|
|
|
|
* the dive computer; we don't want that (we'll visualize with
|
|
|
|
* constant SAC rate instead)
|
|
|
|
* WARNING WARNING - I have only seen this in single tank dives
|
|
|
|
* --- maybe I should try to create a multi tank dive and see what
|
|
|
|
* --- divinglog does there - but the code right now is only tested
|
|
|
|
* --- for the single tank case */
|
|
|
|
for (j = 0; j < MAX_CYLINDERS; j++) {
|
|
|
|
if (abs(pressure_delta[j]) != INT_MAX) {
|
|
|
|
cylinder_t *cyl = dive->cylinder + j;
|
|
|
|
for (i = 0; i < dive->samples; i++)
|
|
|
|
if (dive->sample[i].cylinderindex == j)
|
|
|
|
dive->sample[i].cylinderpressure.mbar = 0;
|
|
|
|
if (! cyl->start.mbar)
|
|
|
|
cyl->start.mbar = cyl->sample_start.mbar;
|
|
|
|
if (! cyl->end.mbar)
|
|
|
|
cyl->end.mbar = cyl->sample_end.mbar;
|
|
|
|
cyl->sample_start.mbar = 0;
|
|
|
|
cyl->sample_end.mbar = 0;
|
|
|
|
}
|
|
|
|
}
|
2012-08-18 16:02:27 +00:00
|
|
|
if (end < 0) {
|
2012-07-29 09:52:51 +00:00
|
|
|
/* Assume an ascent/descent rate of 9 m/min */
|
2012-08-18 16:02:27 +00:00
|
|
|
int depth = dive->maxdepth.mm;
|
|
|
|
int asc_desc_time = depth*60/9000;
|
|
|
|
int duration = dive->duration.seconds;
|
|
|
|
|
2012-08-29 16:10:26 +00:00
|
|
|
/* Some sanity checks against insane dives */
|
|
|
|
if (duration < 2)
|
2012-08-18 16:02:27 +00:00
|
|
|
duration = 2;
|
2012-08-29 16:10:26 +00:00
|
|
|
if (asc_desc_time * 2 >= duration)
|
|
|
|
asc_desc_time = duration/2;
|
|
|
|
|
2012-08-18 16:02:27 +00:00
|
|
|
dive->meandepth.mm = depth*(duration-asc_desc_time)/duration;
|
2011-09-03 20:19:26 +00:00
|
|
|
return dive;
|
2012-07-29 09:52:51 +00:00
|
|
|
}
|
2011-09-04 20:06:47 +00:00
|
|
|
|
|
|
|
update_duration(&dive->duration, end - start);
|
2011-09-03 20:19:26 +00:00
|
|
|
if (start != end)
|
2011-09-04 20:06:47 +00:00
|
|
|
depthtime /= (end - start);
|
|
|
|
|
|
|
|
update_depth(&dive->meandepth, depthtime);
|
|
|
|
update_temperature(&dive->watertemp, mintemp);
|
|
|
|
update_depth(&dive->maxdepth, maxdepth);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2011-10-22 15:12:30 +00:00
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
2011-11-09 15:37:25 +00:00
|
|
|
cylinder_t *cyl = dive->cylinder + i;
|
|
|
|
add_cylinder_description(&cyl->type);
|
2011-11-10 23:33:38 +00:00
|
|
|
if (same_rounded_pressure(cyl->sample_start, cyl->start))
|
2011-11-09 15:37:25 +00:00
|
|
|
cyl->start.mbar = 0;
|
2011-11-10 23:33:38 +00:00
|
|
|
if (same_rounded_pressure(cyl->sample_end, cyl->end))
|
2011-11-09 15:37:25 +00:00
|
|
|
cyl->end.mbar = 0;
|
2011-10-22 15:12:30 +00:00
|
|
|
}
|
2012-08-06 21:03:24 +00:00
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) {
|
|
|
|
weightsystem_t *ws = dive->weightsystem + i;
|
|
|
|
add_weightsystem_description(ws);
|
|
|
|
}
|
2011-10-22 15:12:30 +00:00
|
|
|
|
2012-11-10 10:40:35 +00:00
|
|
|
/* events are stored as a linked list, so the concept of
|
|
|
|
* "consecutive, identical events" is somewhat hard to
|
|
|
|
* implement correctly (especially given that on some dive
|
|
|
|
* computers events are asynchronous, so they can come in
|
|
|
|
* between what would be the non-constant sample rate).
|
|
|
|
*
|
|
|
|
* So what we do is that we throw away clearly redundant
|
|
|
|
* events that are fewer than 61 seconds apart (assuming there
|
|
|
|
* is no dive computer with a sample rate of more than 60
|
|
|
|
* seconds... that would be pretty pointless to plot the
|
|
|
|
* profile with)
|
|
|
|
* We first only mark the events for deletion so that we
|
|
|
|
* still know when the previous event happened. */
|
|
|
|
event = dive->events;
|
|
|
|
while (event) {
|
|
|
|
struct event *prev;
|
|
|
|
if (is_potentially_redundant(event)) {
|
|
|
|
prev = find_previous_event(dive, event);
|
|
|
|
if (prev && prev->value == event->value &&
|
|
|
|
prev->flags == event->flags &&
|
|
|
|
event->time.seconds - prev->time.seconds < 61)
|
|
|
|
event->deleted = TRUE;
|
|
|
|
}
|
|
|
|
event = event->next;
|
|
|
|
}
|
|
|
|
event = dive->events;
|
|
|
|
while (event) {
|
|
|
|
if (event->next && event->next->deleted) {
|
|
|
|
struct event *nextnext = event->next->next;
|
|
|
|
free(event->next);
|
|
|
|
event->next = nextnext;
|
|
|
|
} else {
|
|
|
|
event = event->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Don't pick a zero for MERGE_MIN() */
|
|
|
|
#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
|
|
|
|
#define MERGE_MIN(res, a, b, n) res->n = (a->n)?(b->n)?MIN(a->n, b->n):(a->n):(b->n)
|
2011-09-23 01:01:01 +00:00
|
|
|
#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
|
2011-09-23 03:50:07 +00:00
|
|
|
#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
static struct dive *add_sample(struct sample *sample, int time, struct dive *dive)
|
|
|
|
{
|
2011-09-12 19:56:34 +00:00
|
|
|
struct sample *p = prepare_sample(&dive);
|
2011-09-03 20:19:26 +00:00
|
|
|
|
2011-09-12 19:56:34 +00:00
|
|
|
if (!p)
|
|
|
|
return NULL;
|
|
|
|
*p = *sample;
|
|
|
|
p->time.seconds = time;
|
2012-01-05 06:01:02 +00:00
|
|
|
finish_sample(dive);
|
2011-09-03 20:19:26 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge samples. Dive 'a' is "offset" seconds before Dive 'b'
|
|
|
|
*/
|
|
|
|
static struct dive *merge_samples(struct dive *res, struct dive *a, struct dive *b, int offset)
|
|
|
|
{
|
|
|
|
int asamples = a->samples;
|
|
|
|
int bsamples = b->samples;
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/*
|
|
|
|
* We want a positive sample offset, so that sample
|
|
|
|
* times are always positive. So if the samples for
|
|
|
|
* 'b' are before the samples for 'a' (so the offset
|
|
|
|
* is negative), we switch a and b around, and use
|
|
|
|
* the reverse offset.
|
|
|
|
*/
|
|
|
|
if (offset < 0) {
|
|
|
|
offset = -offset;
|
|
|
|
asamples = bsamples;
|
|
|
|
bsamples = a->samples;
|
|
|
|
as = bs;
|
|
|
|
bs = a->sample;
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
for (;;) {
|
|
|
|
int at, bt;
|
|
|
|
struct sample sample;
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
at = asamples ? as->time.seconds : -1;
|
|
|
|
bt = bsamples ? bs->time.seconds + offset : -1;
|
|
|
|
|
|
|
|
/* No samples? All done! */
|
|
|
|
if (at < 0 && bt < 0)
|
|
|
|
return fixup_dive(res);
|
|
|
|
|
|
|
|
/* Only samples from a? */
|
|
|
|
if (bt < 0) {
|
|
|
|
add_sample_a:
|
|
|
|
res = add_sample(as, at, res);
|
|
|
|
as++;
|
|
|
|
asamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only samples from b? */
|
|
|
|
if (at < 0) {
|
|
|
|
add_sample_b:
|
|
|
|
res = add_sample(bs, bt, res);
|
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at < bt)
|
|
|
|
goto add_sample_a;
|
|
|
|
if (at > bt)
|
|
|
|
goto add_sample_b;
|
|
|
|
|
|
|
|
/* same-time sample: add a merged sample. Take the non-zero ones */
|
|
|
|
sample = *bs;
|
|
|
|
if (as->depth.mm)
|
|
|
|
sample.depth = as->depth;
|
|
|
|
if (as->temperature.mkelvin)
|
|
|
|
sample.temperature = as->temperature;
|
2011-09-04 03:31:18 +00:00
|
|
|
if (as->cylinderpressure.mbar)
|
|
|
|
sample.cylinderpressure = as->cylinderpressure;
|
|
|
|
if (as->cylinderindex)
|
|
|
|
sample.cylinderindex = as->cylinderindex;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
res = add_sample(&sample, at, res);
|
|
|
|
|
|
|
|
as++;
|
|
|
|
bs++;
|
|
|
|
asamples--;
|
|
|
|
bsamples--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *merge_text(const char *a, const char *b)
|
|
|
|
{
|
|
|
|
char *res;
|
|
|
|
|
|
|
|
if (!a || !*a)
|
|
|
|
return (char *)b;
|
|
|
|
if (!b || !*b)
|
|
|
|
return (char *)a;
|
|
|
|
if (!strcmp(a,b))
|
|
|
|
return (char *)a;
|
|
|
|
res = malloc(strlen(a) + strlen(b) + 9);
|
|
|
|
if (!res)
|
|
|
|
return (char *)a;
|
2012-10-11 00:42:59 +00:00
|
|
|
sprintf(res, _("(%s) or (%s)"), a, b);
|
2011-09-03 20:19:26 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
#define SORT(a,b,field) \
|
|
|
|
if (a->field != b->field) return a->field < b->field ? -1 : 1
|
|
|
|
|
|
|
|
static int sort_event(struct event *a, struct event *b)
|
|
|
|
{
|
|
|
|
SORT(a,b,time.seconds);
|
|
|
|
SORT(a,b,type);
|
|
|
|
SORT(a,b,flags);
|
|
|
|
SORT(a,b,value);
|
|
|
|
return strcmp(a->name, b->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_events(struct dive *res, struct dive *src1, struct dive *src2, int offset)
|
|
|
|
{
|
|
|
|
struct event *a, *b;
|
|
|
|
struct event **p = &res->events;
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/* Always use positive offsets */
|
|
|
|
if (offset < 0) {
|
|
|
|
struct dive *tmp;
|
|
|
|
|
|
|
|
offset = -offset;
|
|
|
|
tmp = src1;
|
|
|
|
src1 = src2;
|
|
|
|
src2 = tmp;
|
|
|
|
}
|
|
|
|
|
2011-09-23 03:28:04 +00:00
|
|
|
a = src1->events;
|
|
|
|
b = src2->events;
|
|
|
|
while (b) {
|
|
|
|
b->time.seconds += offset;
|
|
|
|
b = b->next;
|
|
|
|
}
|
|
|
|
b = src2->events;
|
|
|
|
|
|
|
|
while (a || b) {
|
|
|
|
int s;
|
|
|
|
if (!b) {
|
|
|
|
*p = a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!a) {
|
|
|
|
*p = b;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
s = sort_event(a, b);
|
|
|
|
/* Pick b */
|
|
|
|
if (s > 0) {
|
|
|
|
*p = b;
|
|
|
|
p = &b->next;
|
|
|
|
b = b->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Pick 'a' or neither */
|
|
|
|
if (s < 0) {
|
|
|
|
*p = a;
|
|
|
|
p = &a->next;
|
|
|
|
}
|
|
|
|
a = a->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-04 03:31:18 +00:00
|
|
|
/* Pick whichever has any info (if either). Prefer 'a' */
|
|
|
|
static void merge_cylinder_type(cylinder_type_t *res, cylinder_type_t *a, cylinder_type_t *b)
|
|
|
|
{
|
|
|
|
if (a->size.mliter)
|
|
|
|
b = a;
|
|
|
|
*res = *b;
|
|
|
|
}
|
|
|
|
|
2011-09-12 16:47:55 +00:00
|
|
|
static void merge_cylinder_mix(struct gasmix *res, struct gasmix *a, struct gasmix *b)
|
2011-09-04 03:31:18 +00:00
|
|
|
{
|
|
|
|
if (a->o2.permille)
|
|
|
|
b = a;
|
|
|
|
*res = *b;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_cylinder_info(cylinder_t *res, cylinder_t *a, cylinder_t *b)
|
|
|
|
{
|
|
|
|
merge_cylinder_type(&res->type, &a->type, &b->type);
|
|
|
|
merge_cylinder_mix(&res->gasmix, &a->gasmix, &b->gasmix);
|
2011-09-05 16:12:54 +00:00
|
|
|
MERGE_MAX(res, a, b, start.mbar);
|
|
|
|
MERGE_MIN(res, a, b, end.mbar);
|
2011-09-04 03:31:18 +00:00
|
|
|
}
|
|
|
|
|
2012-10-29 18:27:14 +00:00
|
|
|
static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b)
|
|
|
|
{
|
|
|
|
if (!a->weight.grams)
|
|
|
|
a = b;
|
|
|
|
*res = *a;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void merge_equipment(struct dive *res, struct dive *a, struct dive *b)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++)
|
|
|
|
merge_cylinder_info(res->cylinder+i, a->cylinder + i, b->cylinder + i);
|
|
|
|
for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
|
|
|
|
merge_weightsystem_info(res->weightsystem+i, a->weightsystem + i, b->weightsystem + i);
|
|
|
|
}
|
|
|
|
|
2012-11-09 18:46:39 +00:00
|
|
|
/*
|
|
|
|
* When merging two dives, this picks the trip from one, and removes it
|
|
|
|
* from the other.
|
|
|
|
*
|
|
|
|
* The 'next' dive is not involved in the dive merging, but is the dive
|
|
|
|
* that will be the next dive after the merged dive.
|
|
|
|
*/
|
2012-11-11 06:20:05 +00:00
|
|
|
static void pick_and_delete_trip(struct dive *res, struct dive *pick, struct dive *remove)
|
2012-11-09 18:46:39 +00:00
|
|
|
{
|
|
|
|
tripflag_t tripflag = pick->tripflag;
|
|
|
|
dive_trip_t *trip = pick->divetrip;
|
|
|
|
|
|
|
|
res->tripflag = tripflag;
|
2012-11-10 18:51:03 +00:00
|
|
|
add_dive_to_trip(res, trip);
|
|
|
|
remove_dive_from_trip(pick);
|
|
|
|
remove_dive_from_trip(remove);
|
2012-11-09 18:46:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pick a trip for a dive
|
|
|
|
*/
|
2012-11-11 06:20:05 +00:00
|
|
|
static void merge_trip(struct dive *res, struct dive *a, struct dive *b)
|
2012-11-09 18:46:39 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* The larger tripflag is more relevant: we prefer
|
|
|
|
* take manually assigned trips over auto-generated
|
|
|
|
* ones.
|
|
|
|
*/
|
|
|
|
if (a->tripflag > b->tripflag)
|
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
if (a->tripflag < b->tripflag)
|
|
|
|
goto pick_b;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ok, so the divetrips are equally "important".
|
|
|
|
* Pick the one with the better description.
|
|
|
|
*/
|
|
|
|
if (!a->location)
|
|
|
|
goto pick_b;
|
|
|
|
if (!b->location)
|
|
|
|
goto pick_a;
|
|
|
|
if (!a->notes)
|
|
|
|
goto pick_b;
|
|
|
|
if (!b->notes)
|
|
|
|
goto pick_a;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ok, so both have location and notes.
|
|
|
|
* Pick the earlier one.
|
|
|
|
*/
|
|
|
|
if (a->when < b->when)
|
|
|
|
goto pick_a;
|
|
|
|
goto pick_b;
|
|
|
|
|
|
|
|
pick_a:
|
2012-11-11 06:20:05 +00:00
|
|
|
pick_and_delete_trip(res, a, b);
|
2012-11-09 18:46:39 +00:00
|
|
|
return;
|
|
|
|
pick_b:
|
2012-11-11 06:20:05 +00:00
|
|
|
pick_and_delete_trip(res, b, a);
|
2012-11-09 18:46:39 +00:00
|
|
|
}
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/*
|
|
|
|
* Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'.
|
|
|
|
*
|
|
|
|
* If 's' and 'a' are at the same time, offset is 0, and b is NULL.
|
|
|
|
*/
|
|
|
|
static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset)
|
|
|
|
{
|
|
|
|
unsigned int depth = a->depth.mm;
|
|
|
|
int diff;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
unsigned int interval = b->time.seconds - a->time.seconds;
|
|
|
|
unsigned int depth_a = a->depth.mm;
|
|
|
|
unsigned int depth_b = b->depth.mm;
|
|
|
|
|
|
|
|
if (offset > interval)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* pick the average depth, scaled by the offset from 'b' */
|
|
|
|
depth = (depth_a * offset) + (depth_b * (interval - offset));
|
|
|
|
depth /= interval;
|
|
|
|
}
|
|
|
|
diff = s->depth.mm - depth;
|
|
|
|
if (diff < 0)
|
|
|
|
diff = -diff;
|
|
|
|
/* cut off at one meter difference */
|
|
|
|
if (diff > 1000)
|
|
|
|
diff = 1000;
|
|
|
|
return diff*diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate a "difference" in samples between the two dives, given
|
|
|
|
* the offset in seconds between them. Use this to find the best
|
|
|
|
* match of samples between two different dive computers.
|
|
|
|
*/
|
|
|
|
static unsigned long sample_difference(struct dive *a, struct dive *b, int offset)
|
|
|
|
{
|
|
|
|
int asamples = a->samples;
|
|
|
|
int bsamples = b->samples;
|
|
|
|
struct sample *as = a->sample;
|
|
|
|
struct sample *bs = b->sample;
|
|
|
|
unsigned long error = 0;
|
|
|
|
int start = -1;
|
|
|
|
|
|
|
|
if (!asamples || !bsamples)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* skip the first sample - this way we know can always look at
|
|
|
|
* as/bs[-1] to look at the samples around it in the loop.
|
|
|
|
*/
|
|
|
|
as++; bs++;
|
|
|
|
asamples--;
|
|
|
|
bsamples--;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int at, bt, diff;
|
|
|
|
|
|
|
|
|
|
|
|
/* If we run out of samples, punt */
|
|
|
|
if (!asamples)
|
|
|
|
return INT_MAX;
|
|
|
|
if (!bsamples)
|
|
|
|
return INT_MAX;
|
|
|
|
|
|
|
|
at = as->time.seconds;
|
|
|
|
bt = bs->time.seconds + offset;
|
|
|
|
|
|
|
|
/* b hasn't started yet? Ignore it */
|
|
|
|
if (bt < 0) {
|
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at < bt) {
|
|
|
|
diff = compare_sample(as, bs-1, bs, bt - at);
|
|
|
|
as++;
|
|
|
|
asamples--;
|
|
|
|
} else if (at > bt) {
|
|
|
|
diff = compare_sample(bs, as-1, as, at - bt);
|
|
|
|
bs++;
|
|
|
|
bsamples--;
|
|
|
|
} else {
|
|
|
|
diff = compare_sample(as, bs, NULL, 0);
|
|
|
|
as++; bs++;
|
|
|
|
asamples--; bsamples--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalid comparison point? */
|
|
|
|
if (diff < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (start < 0)
|
|
|
|
start = at;
|
|
|
|
|
|
|
|
error += diff;
|
|
|
|
|
|
|
|
if (at - start > 120)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dive 'a' is 'offset' seconds before dive 'b'
|
|
|
|
*
|
|
|
|
* This is *not* because the dive computers clocks aren't in sync,
|
|
|
|
* it is because the dive computers may "start" the dive at different
|
|
|
|
* points in the dive, so the sample at time X in dive 'a' is the
|
|
|
|
* same as the sample at time X+offset in dive 'b'.
|
|
|
|
*
|
|
|
|
* For example, some dive computers take longer to "wake up" when
|
|
|
|
* they sense that you are under water (ie Uemis Zurich if it was off
|
|
|
|
* when the dive started). And other dive computers have different
|
|
|
|
* depths that they activate at, etc etc.
|
|
|
|
*
|
|
|
|
* If we cannot find a shared offset, don't try to merge.
|
|
|
|
*/
|
|
|
|
static int find_sample_offset(struct dive *a, struct dive *b)
|
|
|
|
{
|
|
|
|
int offset, best;
|
|
|
|
unsigned long max;
|
|
|
|
|
|
|
|
/* No samples? Merge at any time (0 offset) */
|
|
|
|
if (!a->samples)
|
|
|
|
return 0;
|
|
|
|
if (!b->samples)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common special-case: merging a dive that came from
|
|
|
|
* the same dive computer, so the samples are identical.
|
|
|
|
* Check this first, without wasting time trying to find
|
|
|
|
* some minimal offset case.
|
|
|
|
*/
|
|
|
|
best = 0;
|
|
|
|
max = sample_difference(a, b, 0);
|
|
|
|
if (!max)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise, look if we can find anything better within
|
|
|
|
* a thirty second window..
|
|
|
|
*/
|
|
|
|
for (offset = -30; offset <= 30; offset++) {
|
|
|
|
unsigned long diff;
|
|
|
|
|
|
|
|
diff = sample_difference(a, b, offset);
|
|
|
|
if (diff > max)
|
|
|
|
continue;
|
|
|
|
best = offset;
|
|
|
|
max = diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
2011-09-03 20:19:26 +00:00
|
|
|
/*
|
|
|
|
* This could do a lot more merging. Right now it really only
|
|
|
|
* merges almost exact duplicates - something that happens easily
|
|
|
|
* with overlapping dive downloads.
|
|
|
|
*/
|
2012-11-11 06:20:05 +00:00
|
|
|
struct dive *try_to_merge(struct dive *a, struct dive *b)
|
2011-09-03 20:19:26 +00:00
|
|
|
{
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
int offset;
|
2011-09-03 20:19:26 +00:00
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/*
|
|
|
|
* This assumes that the clocks on the dive computers are
|
|
|
|
* roughly synchronized.
|
|
|
|
*/
|
2011-10-05 03:33:17 +00:00
|
|
|
if ((a->when >= b->when + 60) || (a->when <= b->when - 60))
|
2011-09-03 20:19:26 +00:00
|
|
|
return NULL;
|
|
|
|
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
/* Dive 'a' is 'offset' seconds before dive 'b' */
|
|
|
|
offset = find_sample_offset(a, b);
|
|
|
|
if (offset > 120 || offset < -120)
|
|
|
|
return NULL;
|
|
|
|
|
2012-11-11 06:20:05 +00:00
|
|
|
return merge_dives(a, b, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dive *merge_dives(struct dive *a, struct dive *b, int offset)
|
|
|
|
{
|
|
|
|
struct dive *res = alloc_dive();
|
2011-09-03 20:19:26 +00:00
|
|
|
|
|
|
|
res->when = a->when;
|
2012-11-11 06:20:05 +00:00
|
|
|
merge_trip(res, a, b);
|
2011-09-23 03:50:07 +00:00
|
|
|
MERGE_NONZERO(res, a, b, latitude);
|
|
|
|
MERGE_NONZERO(res, a, b, longitude);
|
2011-09-23 01:01:01 +00:00
|
|
|
MERGE_TXT(res, a, b, location);
|
|
|
|
MERGE_TXT(res, a, b, notes);
|
|
|
|
MERGE_TXT(res, a, b, buddy);
|
|
|
|
MERGE_TXT(res, a, b, divemaster);
|
2011-12-07 19:58:16 +00:00
|
|
|
MERGE_MAX(res, a, b, rating);
|
2012-08-14 23:07:25 +00:00
|
|
|
MERGE_TXT(res, a, b, suit);
|
2011-09-12 19:56:34 +00:00
|
|
|
MERGE_MAX(res, a, b, number);
|
2011-09-03 20:19:26 +00:00
|
|
|
MERGE_MAX(res, a, b, maxdepth.mm);
|
|
|
|
res->meandepth.mm = 0;
|
2012-11-10 18:18:10 +00:00
|
|
|
MERGE_NONZERO(res, a, b, salinity);
|
|
|
|
MERGE_NONZERO(res, a, b, visibility);
|
|
|
|
MERGE_NONZERO(res, a, b, surface_pressure.mbar);
|
2011-09-03 20:19:26 +00:00
|
|
|
MERGE_MAX(res, a, b, duration.seconds);
|
|
|
|
MERGE_MAX(res, a, b, surfacetime.seconds);
|
|
|
|
MERGE_MAX(res, a, b, airtemp.mkelvin);
|
|
|
|
MERGE_MIN(res, a, b, watertemp.mkelvin);
|
2012-10-29 18:27:14 +00:00
|
|
|
merge_equipment(res, a, b);
|
Try to find optimal dive sample merge offset
When we merge dives where the samples have come from different dive
computers, the samples may be offset from each other due to the dive
computers not having decided that the dive starts at quite the same
time.
For example, some dive computers may take a while to wake up when
submerged, or there may be differences in exactly when the dive
computer decides that a dive has started. Different computers tend to
have different depths that they consider the start of a real dive.
So when we merge two dives, look for differences in the sample data,
and search for the sample time offset that minimizes the differences
(logic: minimize the sum-of-square of the depth differences over a
two-minute window at the start of the dive).
This still doesn't really result in perfect merges, since different
computers will give slightly different values anyway, but it improves
the dive merging noticeably. To the point that this seems to have
found a bug in our Uemis data import (it looks like the Uemis importer
does an incorrect saltwater pressure conversion, and the data is
actually in centimeter, not in pressure).
So there is room for improvement, but this is at least a reasonable
approximation and starting point.
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
2012-11-10 11:23:13 +00:00
|
|
|
merge_events(res, a, b, offset);
|
|
|
|
return merge_samples(res, a, b, offset);
|
2011-09-03 20:19:26 +00:00
|
|
|
}
|