mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 13:10:19 +00:00
0089dd8819
Keep the sample pressure start/end data separate from the overall cylinder start/end data - and clean the overall cylinder start/end data if it matches the samples exactly to avoid the redundancy. This breaks all the SAC calculations etc, which expect the cylinder pressures to always be in the cylinder data. I'll fix that up separately. The reason for this is that we really want to keep the manually entered data separate: the pressure plotting doesn't need the confusion, and considers end-point data (with interpolation) very different from sample data. Also, we do not want to pollute the xml save-file with data that is computed. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
510 lines
10 KiB
C
510 lines
10 KiB
C
/* dive.c */
|
|
/* maintains the internal dive list structure */
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "dive.h"
|
|
|
|
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;
|
|
remember_event(name);
|
|
}
|
|
|
|
int get_pressure_units(unsigned int mb, const char **units)
|
|
{
|
|
int pressure;
|
|
const char* unit;
|
|
|
|
switch (output_units.pressure) {
|
|
case PASCAL:
|
|
pressure = mb * 100;
|
|
unit = "pascal";
|
|
break;
|
|
case BAR:
|
|
pressure = (mb + 500) / 1000;
|
|
unit = "bar";
|
|
break;
|
|
case PSI:
|
|
pressure = mbar_to_PSI(mb);
|
|
unit = "psi";
|
|
break;
|
|
}
|
|
if (units)
|
|
*units = unit;
|
|
return pressure;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
unit = "l";
|
|
decimals = 1;
|
|
break;
|
|
case CUFT:
|
|
vol = ml_to_cuft(ml);
|
|
unit = "cuft";
|
|
decimals = 2;
|
|
break;
|
|
}
|
|
if (frac)
|
|
*frac = decimals;
|
|
if (units)
|
|
*units = unit;
|
|
return vol;
|
|
}
|
|
|
|
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;
|
|
unit = "m";
|
|
decimals = d < 20;
|
|
break;
|
|
case FEET:
|
|
d = mm_to_feet(mm);
|
|
unit = "ft";
|
|
decimals = 0;
|
|
break;
|
|
}
|
|
if (frac)
|
|
*frac = decimals;
|
|
if (units)
|
|
*units = unit;
|
|
return d;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void finish_sample(struct dive *dive, struct sample *sample)
|
|
{
|
|
dive->samples++;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* Only if the values calculated from the samples are clearly
|
|
* 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)
|
|
{
|
|
if (new) {
|
|
int old = depth->mm;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static void fixup_pressure(struct dive *dive, struct sample *sample)
|
|
{
|
|
unsigned int pressure, index;
|
|
cylinder_t *cyl;
|
|
|
|
pressure = sample->cylinderpressure.mbar;
|
|
if (!pressure)
|
|
return;
|
|
index = sample->cylinderindex;
|
|
if (index >= MAX_CYLINDERS)
|
|
return;
|
|
cyl = dive->cylinder + index;
|
|
if (!cyl->sample_start.mbar)
|
|
cyl->sample_start.mbar = pressure;
|
|
cyl->sample_end.mbar = pressure;
|
|
}
|
|
|
|
struct dive *fixup_dive(struct dive *dive)
|
|
{
|
|
int i;
|
|
double depthtime = 0;
|
|
int lasttime = 0;
|
|
int start = -1, end = -1;
|
|
int maxdepth = 0, mintemp = 0;
|
|
int lastdepth = 0;
|
|
int lasttemp = 0;
|
|
|
|
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;
|
|
|
|
if (lastdepth)
|
|
end = time;
|
|
|
|
if (depth) {
|
|
if (start < 0)
|
|
start = lasttime;
|
|
if (depth > maxdepth)
|
|
maxdepth = depth;
|
|
}
|
|
|
|
fixup_pressure(dive, sample);
|
|
|
|
if (temp) {
|
|
/*
|
|
* If we have consecutive identical
|
|
* temperature readings, throw away
|
|
* the redundant ones.
|
|
*/
|
|
if (lasttemp == temp)
|
|
sample->temperature.mkelvin = 0;
|
|
else
|
|
lasttemp = temp;
|
|
|
|
if (!mintemp || temp < mintemp)
|
|
mintemp = temp;
|
|
}
|
|
depthtime += (time - lasttime) * (lastdepth + depth) / 2;
|
|
lastdepth = depth;
|
|
lasttime = time;
|
|
}
|
|
if (end < 0)
|
|
return dive;
|
|
|
|
update_duration(&dive->duration, end - start);
|
|
if (start != end)
|
|
depthtime /= (end - start);
|
|
|
|
update_depth(&dive->meandepth, depthtime);
|
|
update_temperature(&dive->watertemp, mintemp);
|
|
update_depth(&dive->maxdepth, maxdepth);
|
|
|
|
add_people(dive->buddy);
|
|
add_people(dive->divemaster);
|
|
add_location(dive->location);
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
cylinder_t *cyl = dive->cylinder + i;
|
|
add_cylinder_description(&cyl->type);
|
|
if (cyl->sample_start.mbar == cyl->start.mbar)
|
|
cyl->start.mbar = 0;
|
|
if (cyl->sample_end.mbar == cyl->end.mbar)
|
|
cyl->end.mbar = 0;
|
|
}
|
|
|
|
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)
|
|
#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
|
|
#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
|
|
|
|
static struct dive *add_sample(struct sample *sample, int time, struct dive *dive)
|
|
{
|
|
struct sample *p = prepare_sample(&dive);
|
|
|
|
if (!p)
|
|
return NULL;
|
|
*p = *sample;
|
|
p->time.seconds = time;
|
|
finish_sample(dive, p);
|
|
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;
|
|
|
|
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;
|
|
if (as->cylinderpressure.mbar)
|
|
sample.cylinderpressure = as->cylinderpressure;
|
|
if (as->cylinderindex)
|
|
sample.cylinderindex = as->cylinderindex;
|
|
|
|
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;
|
|
sprintf(res, "(%s) or (%s)", a, b);
|
|
return res;
|
|
}
|
|
|
|
#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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static void merge_cylinder_mix(struct gasmix *res, struct gasmix *a, struct gasmix *b)
|
|
{
|
|
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);
|
|
MERGE_MAX(res, a, b, start.mbar);
|
|
MERGE_MIN(res, a, b, end.mbar);
|
|
}
|
|
|
|
/*
|
|
* This could do a lot more merging. Right now it really only
|
|
* merges almost exact duplicates - something that happens easily
|
|
* with overlapping dive downloads.
|
|
*/
|
|
struct dive *try_to_merge(struct dive *a, struct dive *b)
|
|
{
|
|
int i;
|
|
struct dive *res;
|
|
|
|
if ((a->when >= b->when + 60) || (a->when <= b->when - 60))
|
|
return NULL;
|
|
|
|
res = alloc_dive();
|
|
|
|
res->when = a->when;
|
|
MERGE_NONZERO(res, a, b, latitude);
|
|
MERGE_NONZERO(res, a, b, longitude);
|
|
MERGE_TXT(res, a, b, location);
|
|
MERGE_TXT(res, a, b, notes);
|
|
MERGE_TXT(res, a, b, buddy);
|
|
MERGE_TXT(res, a, b, divemaster);
|
|
MERGE_MAX(res, a, b, number);
|
|
MERGE_MAX(res, a, b, maxdepth.mm);
|
|
res->meandepth.mm = 0;
|
|
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);
|
|
for (i = 0; i < MAX_CYLINDERS; i++)
|
|
merge_cylinder_info(res->cylinder+i, a->cylinder + i, b->cylinder + i);
|
|
merge_events(res, a, b, 0);
|
|
return merge_samples(res, a, b, 0);
|
|
}
|