Skip to content

Commit

Permalink
Fixed summary (using proper initial altitude)
Browse files Browse the repository at this point in the history
Fixed processing .FIT header (for Zwift .fit files), issue #14
  • Loading branch information
mrihtar committed Nov 5, 2017
1 parent d1f9dbf commit c19798e
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 142 deletions.
171 changes: 101 additions & 70 deletions fit2gpx.pl
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
my $g_maxAlt = -10000;
my $alts = [];
my $g_minHr = 10000;
my $g_maxHr = -10000; # already in lap data
my $hrs = []; # already in lap data
my $g_maxHr = -10000; # already in session/lap data
my $hrs = []; # already in session/lap data
my $g_minTemp = 10000;
my $g_maxTemp = -10000;
my $temps = [];
Expand Down Expand Up @@ -111,7 +111,7 @@
my $g_kcf = 0.65;

# Totals/summary info (from lap/session)
my $g_startTime = time(); # use current time if start_time not found
my $g_startTime;
my $f_startTime = ""; # start time in proper format
my $g_startLon; my $g_startLat;
# endLon and endLat are not present in session!
Expand Down Expand Up @@ -214,7 +214,7 @@ sub Usage {
my $ver_only = shift;

if ($ver_only) {
printf STDERR "fit2gpx 2.10 Copyright (c) 2016-2017 Matjaz Rihtar (Aug 21, 2017)\n";
printf STDERR "fit2gpx 2.11 Copyright (c) 2016-2017 Matjaz Rihtar (Nov 5, 2017)\n";
printf STDERR "Garmin::FIT Copyright (c) 2010-2017 Kiyokazu Suto\n";
printf STDERR "FIT protocol ver: %s, profile ver: %s\n",
Garmin::FIT->protocol_version_string, Garmin::FIT->profile_version_string;
Expand Down Expand Up @@ -268,7 +268,11 @@ sub ReadFitFile {

# Open and read fit file
$fit->open() or die $fit->error()."\n";
$fit->fetch_header() or die $fit->error()."\n";
my ($f_size) = $fit->fetch_header();
if (!defined $f_size) {
if (!defined $fit->error()) { $fit->error("can't read FIT header"); }
die $fit->error()."\n";
}
1 while $fit->fetch();
} # ReadFitFile

Expand Down Expand Up @@ -432,18 +436,16 @@ sub FillGlobalVars {
$g_pZone7s = $g_funcThresPower * $pc_pZone7s;
$g_pZone7e = $g_pZoneMax;

# Find all needed general info in first lap
$m = @{$laps}[0];
# Find all needed general info in first session
$m = @{$sessions}[0];
if (defined $m) {
%mh = %$m;
while (($k, $v) = each %mh) {
if ($k eq "start_time") { $g_startTime = $v; } # + $timeoffs; }
if ($k eq "start_time") { $g_startTime = $v; } # + $timeoffs;
elsif ($k eq "sport") { $g_sport = $v; }
elsif ($k eq "sub_sport") { $g_subSport = $v; }
elsif ($k eq "start_position_lat") { $g_startLat = $v; }
elsif ($k eq "start_position_long") { $g_startLon = $v; }
elsif ($k eq "end_position_lat") { $g_endLat = $v; }
elsif ($k eq "end_position_long") { $g_endLon = $v; }
elsif ($k eq "total_elapsed_time") { $g_totElapsTime = $v; }
elsif ($k eq "total_timer_time") { $g_totTimerTime = $v; }
elsif ($k eq "time_standing") { $g_totTimeStand = $v; } # invalid
Expand All @@ -464,55 +466,61 @@ sub FillGlobalVars {
}
}

# Find additional/missing general info in first session
$m = @{$sessions}[0];
if (defined $m) {
%mh = %$m;
while (($k, $v) = each %mh) {
if ($k eq "start_time" && !defined $g_startTime)
{ $g_startTime = $v; } # + $timeoffs; }
elsif ($k eq "sport" && !defined $g_sport)
{ $g_sport = $v; }
elsif ($k eq "sub_sport" && !defined $g_subSport)
{ $g_subSport = $v; }
elsif ($k eq "start_position_lat" && !defined $g_startLat)
{ $g_startLat = $v; }
elsif ($k eq "start_position_long" && !defined $g_startLon)
{ $g_startLon = $v; }
elsif ($k eq "total_elapsed_time" && !defined $g_totElapsTime)
{ $g_totElapsTime = $v; }
elsif ($k eq "total_timer_time" && !defined $g_totTimerTime)
{ $g_totTimerTime = $v; }
elsif ($k eq "time_standing" && !defined $g_totTimeStand)
{ $g_totTimeStand = $v; } # invalid
elsif ($k eq "total_distance" && !defined $g_totDistance)
{ $g_totDistance = $v; } # m
elsif ($k eq "total_cycles" && !defined $g_totCycles)
{ $g_totCycles = $v; }
elsif ($k eq "total_calories" && !defined $g_totCal)
{ $g_totCal = $v; }
elsif ($k eq "time_in_hr_zone" && !defined $g_timeHrZone)
{ $g_timeHrZone = $v; } # array
elsif ($k eq "avg_speed" && !defined $g_avgSpeed)
{ $g_avgSpeed = $v; }
elsif ($k eq "max_speed" && !defined $g_maxSpeed)
{ $g_maxSpeed = $v; }
elsif ($k eq "total_ascent" && !defined $g_totAscent)
{ $g_totAscent = $v; }
elsif ($k eq "total_descent" && !defined $g_totDescent)
{ $g_totDescent = $v; }
elsif ($k eq "avg_heart_rate" && !defined $g_avgHr)
{ $g_avgHr = $v; }
elsif ($k eq "max_heart_rate" && !defined $g_maxHr)
{ $g_maxHr = $v; }
elsif ($k eq "avg_cadence" && !defined $g_avgCad)
{ $g_avgCad = $v; }
elsif ($k eq "max_cadence" && !defined $g_maxCad)
{ $g_maxCad = $v; }
elsif ($k eq "avg_power" && !defined $g_avgPower)
{ $g_avgPower = $v; } # invalid
elsif ($k eq "max_power" && !defined $g_maxPower)
{ $g_maxPower = $v; } # invalid
if (scalar @$laps <= 1) {
# Find additional/missing general info in first (and only) lap
$m = @{$laps}[0];
if (defined $m) {
%mh = %$m;
while (($k, $v) = each %mh) {
if ($k eq "start_time" && !defined $g_startTime)
{ $g_startTime = $v; } # + $timeoffs;
elsif ($k eq "sport" && !defined $g_sport)
{ $g_sport = $v; }
elsif ($k eq "sub_sport" && !defined $g_subSport)
{ $g_subSport = $v; }
elsif ($k eq "start_position_lat" && !defined $g_startLat)
{ $g_startLat = $v; }
elsif ($k eq "start_position_long" && !defined $g_startLon)
{ $g_startLon = $v; }
elsif ($k eq "end_position_lat" && !defined $g_endLat)
{ $g_endLat = $v; }
elsif ($k eq "end_position_long" && !defined $g_endLon)
{ $g_endLon = $v; }
elsif ($k eq "total_elapsed_time" && !defined $g_totElapsTime)
{ $g_totElapsTime = $v; }
elsif ($k eq "total_timer_time" && !defined $g_totTimerTime)
{ $g_totTimerTime = $v; }
elsif ($k eq "time_standing" && !defined $g_totTimeStand)
{ $g_totTimeStand = $v; } # invalid
elsif ($k eq "total_distance" && !defined $g_totDistance)
{ $g_totDistance = $v; } # m
elsif ($k eq "total_cycles" && !defined $g_totCycles)
{ $g_totCycles = $v; }
elsif ($k eq "total_calories" && !defined $g_totCal)
{ $g_totCal = $v; }
elsif ($k eq "time_in_hr_zone" && !defined $g_timeHrZone)
{ $g_timeHrZone = $v; } # array
elsif ($k eq "avg_speed" && !defined $g_avgSpeed)
{ $g_avgSpeed = $v; }
elsif ($k eq "max_speed" && !defined $g_maxSpeed)
{ $g_maxSpeed = $v; }
elsif ($k eq "total_ascent" && !defined $g_totAscent)
{ $g_totAscent = $v; }
elsif ($k eq "total_descent" && !defined $g_totDescent)
{ $g_totDescent = $v; }
elsif ($k eq "avg_heart_rate" && !defined $g_avgHr)
{ $g_avgHr = $v; }
elsif ($k eq "max_heart_rate" && !defined $g_maxHr)
{ $g_maxHr = $v; }
elsif ($k eq "avg_cadence" && !defined $g_avgCad)
{ $g_avgCad = $v; }
elsif ($k eq "max_cadence" && !defined $g_maxCad)
{ $g_maxCad = $v; }
elsif ($k eq "avg_power" && !defined $g_avgPower)
{ $g_avgPower = $v; } # invalid
elsif ($k eq "max_power" && !defined $g_maxPower)
{ $g_maxPower = $v; } # invalid
}
}
}

Expand All @@ -522,6 +530,7 @@ sub FillGlobalVars {
$g_totCal = 0 if !defined $g_totCal;
$g_avgHr = 0 if !defined $g_avgHr;

$g_startTime = time() if !defined $g_startTime;
my @lt = localtime($g_startTime);
$f_startTime = POSIX::strftime("%d-%b-%y %H:%M", @lt);

Expand Down Expand Up @@ -824,6 +833,25 @@ sub PrintGpxMetadata {
printf "%s</metadata>\n", $indent;
} # PrintGpxMetadata

#==============================================================================
# Get 1st altitude from records. If not found, return home altitude.
sub Get1stAlt {
my $timestamp = undef;
my $alt = undef;

foreach (@$records) {
my $k; my $v;
while (($k, $v) = each %$_) {
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs;
elsif ($k eq "altitude") { $alt = $v; } # can be invalid
}
last; # check only 1st record
}
if (!defined $alt) { $alt = $g_homeAlt; }

return $alt;
} # Get1stAlt

#==============================================================================
# Simple Kalman filtering routines (used as low-pass filter) for smoothing data
sub kalman_init {
Expand Down Expand Up @@ -864,6 +892,7 @@ sub kalman_update {
# 2 positions.
# Displaying the comparison graph is possible, if code at the end is uncommented.
sub FilterAlt {
my $priv_alt = shift; # initial/home altitude
my $k; my $v;

# q = bigger (>4) => more precisely follows the original curve
Expand All @@ -872,22 +901,21 @@ sub FilterAlt {
# smaller (0.1) => exactly follows original curve
# q & r stay the same, p & x are overwritten in each step
# p = same as r
# initial_value = home altitude
kalman_init(\%alt_state, 1, 15, 1, $g_homeAlt);
# initial_value = initial/home altitude
kalman_init(\%alt_state, 1, 15, 1, $priv_alt);

my $timestamp;
my $alt; my $altf;

my $times = [];
$orig_alt = [];
$filt_alt = [];
my $priv_alt = $g_homeAlt;
foreach (@$records) {
$timestamp = undef;
$alt = undef;

while (($k, $v) = each %$_) {
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs; }
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs;
elsif ($k eq "altitude") { $alt = $v; } # can be invalid
}
if (!defined $alt) { $alt = $priv_alt; }
Expand Down Expand Up @@ -955,14 +983,15 @@ sub ProcessRecords {
$prev_lat = 0; $prev_lon = 0; # undef
@prev_dist = (0) x $histSize;
@prev_distDiff = (0) x $histSize;
@prev_alt = ($g_homeAlt) x $histSize; # home altitude
my $alt0 = Get1stAlt(); # initial/home altitude
@prev_alt = ($alt0) x $histSize;
@prev_altDiff = (0) x $histSize;
$prev_speed = 0; $prev_hr = 0; # undef
$prev_cad = 0; $prev_temp = 0; # undef
$tot_time = 0;

FilterAlt();
# kalman_init(\%pwr_state, 2, 25, 1, 0);
FilterAlt($alt0);
# kalman_init(\%pwr_state, 2, 25, 1, $alt0);

my $fai = 0; # filtered alt index
foreach (@$records) {
Expand All @@ -987,7 +1016,7 @@ sub ProcessRecord {

my $k; my $v;
while (($k, $v) = each %mh) {
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs; }
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs;
elsif ($k eq "position_lat") { $lat = $v; } # can be missing
elsif ($k eq "position_long") { $lon = $v; } # can be missing
elsif ($k eq "distance") { $dist = $v; }
Expand Down Expand Up @@ -1421,7 +1450,7 @@ sub PrintGpxTrkpt {
my $k; my $v;
while (($k, $v) = each %mh) {
# mandatory
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs; }
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs;
elsif ($k eq "position_long") { $lon = $v; } # can be missing
elsif ($k eq "position_lat") { $lat = $v; } # can be missing
# optional
Expand Down Expand Up @@ -1549,8 +1578,8 @@ sub PrintGpxLap {

my $k; my $v;
while (($k, $v) = each %mh) {
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs; }
elsif ($k eq "start_time") { $stime = $v; } # + $timeoffs; }
if ($k eq "timestamp") { $timestamp = $v; } # + $timeoffs;
elsif ($k eq "start_time") { $stime = $v; } # + $timeoffs;
elsif ($k eq "start_position_lat") { $startlat = $v; }
elsif ($k eq "start_position_long") { $startlon = $v; }
elsif ($k eq "end_position_lat") { $endlat = $v; }
Expand Down Expand Up @@ -1726,6 +1755,8 @@ sub PrintGpxLap {
printf "%s<gpxdata:summary name=\"%s\" kind=\"%s\">%d</gpxdata:summary>\n",
$indent x 3, "max_cadence", "max", $maxcad;

$g_minTemp = 0 if $g_minTemp == 10000;
$g_maxTemp = 0 if $g_maxTemp == -10000;
printf "%s<gpxdata:summary name=\"%s\" kind=\"%s\">%.1f</gpxdata:summary>\n",
$indent x 3, "min_temperature", "min", $g_minTemp;
printf "%s<gpxdata:summary name=\"%s\" kind=\"%s\">%.1f</gpxdata:summary>\n",
Expand Down
Loading

0 comments on commit c19798e

Please sign in to comment.