Hackerdehack

Herken je dit? Je hebt een GPS track, van een tracklogger (in .gpx formaat) of van je Garmin horloge (in .tcx formaat), maar er zitten geen splits in, omdat je niet bij elke post op het knopje hebt gedrukt. En de club gebruikt niet Winsplits in combinatie met die ene specifieke server waar Quickroute mee praat, dus je kan de splits niet automatisch in Quickroute krijgen.

Herkenbaar? Je zult handmatig de tijden in moeten voeren, door op de juiste plaatsen op de kaart te klikken.

Of je schrijft een klein programmatje in Matlab (of gebruikt mijn code) om de .tcx files te voorzien van de splits die je eenvoudig van de Slitsbrowser pagina haalt (de regel in de Results Table met jouw naam er voor; copy – paste).

Heb je geen Garmin .tcx file, maar een generieke .gpx file van een tracklogger, maak er dan eerst een .tcx file van met GPSbabel.
gpsbabel -t -i gpx -f dit_track_bestand.gpx -o gtrnctr,course=0 -F het_nieuwe_bestand.tcx

En zo heb ik bijvoorbeeld deze kaart van Hamont gemaakt, met de controls direct op de juiste plek.

Er zit een slimmigheidje in de functie om te detecteren hoeveel tijdverschil er tussen de GPS gegevens zit en de start. Mocht dat niet werken, knip dan handmatig tevoren het stuk van de track tot vlak voor de start, en vlak na de finish af.

Mochten ze in QuickRoute het inladen van SplitsBrowser files ondersteuen, dan zal het wel op het QuickRoute Forum te lezen zijn. En dat is wellicht ook de plaats om om deze feature te vragen. Hoe meer mensen het hier over hebben, hoe groter de kans.

function splits2tcx(in_file,splits)
% om een extra neutralisatie-post in mee te nemen die niet in de splits zat, voeg een
% +0:17 (of andere tijd) toe op de plaats. 
 
% gpsbabel = 'c:\localdata\bin\gpsbabel\gpsbabel.exe'
 
% CHECK various splits strings
 
force_tcx_conversion = 1; % force gpsbabel to convert tcx to tcx (to fix for example Android Garmin Uploader files)
if nargin support for %s files not yet implemented.\n',fe);
    end
    if nok
        error('err-> conversion to .tcx file failed');
    else
        fprintf('done.\n'); % JHH
    end
end
 
fprintf('** acquiring splits **\n');
if nargin0) % absolute times
    if splits(1)~=splits(end)
        warning('I expected the total time (1st data point) to be equal to the finish time (last data point). Netralized leg compensation?');
    end
    splittimes = [0 splits(2:end)];
else % relative times
    splittimes = [0 cumsum(splits)];
end
 
fprintf('** using split times: "%s" **\n',reshape(strvcat(m2ms(splittimes'/60,1)',' '),1,[]));
 
fprintf('** reading tcx file: "%s" **\n',tcx_file);
t = []; tp = [];
d = []; dp = [];
ll = []; lp = [];
 
[fid,err] = fopen(tcx_file,'r');
if fid failed opening file "%s" for reading, with error: %s',tcx_file,err);
end
 
arm = 0;
while ~feof(fid)
    l = fgetl(fid);
    hms = sscanf(l,'%*[^&lt;]<time>%*[^T]T%d:%d:%dZ</time>');
    if ~isempty(hms)
        t(max(length(d),size(ll,1))+1) = [60*60 60 1]*hms;
        tp(max(length(d),size(ll,1))+1) = ftell(fid);
        arm = 1;
    else
        dm = sscanf(l,'%*[^&lt;]%f');
        if arm &amp;&amp; ~isempty(dm)
            d(length(t)) = dm;
            dp(length(t)) = ftell(fid);
            if length(d)&gt;length(t)
                l
            elseif length(d)&lt;length(t)
                l
            end
            arm = 0;
        else
            la = sscanf(l,'%*[^&lt;]%f');
            if arm &amp;&amp; ~isempty(la)
                ll(length(t),1) = la;
                lp(length(t)) = ftell(fid);
            else
                lo = sscanf(l,'%*[^&lt;]%f');
                if arm &amp;&amp; ~isempty(lo)
                    ll(length(t),2) = lo;
                else
                    ls = sscanf(l,'%*[^&lt;]');
                    if ~isempty(ls)
                        arm = 0;
                    end
                end
            end
        end
    end
end
if arm % last timepoint has no location or distance
    if ~isempty(d)
        t = t(1:length(d));
    elseif ~isempty(ll)
        t = t(1:size(ll,1));
    end
end
 
findmin = inline('min(find(a(:)==min(a(:))))','a');
if ~isempty(d)
    fprintf('%d timestamps; %d distances; %d splits; %d gaps\n',length(t),length(d),length(find(diff(t)==0)),length(find(diff(t)&gt;1)));
else
    fprintf('%d timestamps; %d distances; %d splits; %d gaps\n',length(t),length(ll),length(find(diff(t)==0)),length(find(diff(t)&gt;1)));
end
lapsplit = find(diff(t)==0);
t(lapsplit) = [];
ll(lapsplit,:) = [];
if ~isempty(d)
    d(lapsplit) = [];
    dd = diff(d);
else
    [x,y] = deg2utm(ll(:,1),ll(:,2));
    dd = sqrt(sum(diff([x y]).^2,2));
end
 
dd = dd(:).';
 
if 1
    %%
    dt = diff(t);
    if any(dt&gt;1)
        fprintf('missing track-points at: \n%s',sprintf('%d:%02d : %d [s]\n',[round(m2ms((t(find(diff(t)&gt;1))-t(1))/60)).';dt(dt&gt;1)]));
        %         if any(diff(t)&gt;2)
        %             error('only solves single missing points');
        %         end
        for ii = fliplr(find(dt&gt;1))
            dd(1:end+dt(ii)-1) = [dd(1:ii-1) repmat(dd(ii)/dt(ii),1,dt(ii)) dd(ii+1:end)];
        end
        t = t(1):t(end);
        %         t1(cumsum([1 (diff(t)&gt;1)+1])) = t;
        %         d1(cumsum([1 (diff(t)&gt;1)+1])) = dd;
        %         d1(find(~t1))   = d1(find(~t1)-1)/2;
        %         d1(find(~t1)-1) = d1(find(~t1));
        %         t1(~t1) = t1(find(~t1)-1);
    end
 
%     comb = repmat(round(splittimes),2,1)+repmat([1:2]',1,length(splittimes));
%     window(comb(:)) = 1;
%     xc = xcorr(window,dd);
%     those = length(xc)-length(dd)-[0:length(dd)-splittimes(end)];
%     xcdt = detrend(xc(those),'linear',ceil(length(those)/2));
%     %plot(xcdt)
%     offset = findmin(xcdt);
%     figure(12345);
%     s1=subplot(211);plot(dd);line(offset,0,'marker','o','color','r');line(offset+splittimes,0,'marker','s','color','g');
%     s2=subplot(212);plot(xc(those));line(offset,xc(those(offset)),'marker','o','color','r');
%     set(s2,'xlim',get(s1,'xlim'));
    %%
    m = 2;
    comb = repmat(round(splittimes),m,1)+repmat([1:m]',1,length(splittimes));
    window = [];
    window(comb(:)) = 1;
    if length(window)&gt;length(dd)
        dd = [zeros(1,2*(length(window)-length(dd))) dd zeros(1,2*(length(window)-length(dd)))];
    end
    xc = xcorr(window,dd);
    those = length(xc)-length(dd)-[0:length(dd)-splittimes(end)];
    xcdt = detrend(xc(those),'linear',ceil(length(those)/2));
    %plot(xcdt)
    offset = findmin(xcdt)+floor(m/2)
    figure(12345);
    s1=subplot(2,4,1:4);plot(dd*3600/1000);line(offset,0,'marker','o','color','r');line(offset+splittimes,0,'marker','s','color','g');
    s2=subplot(2,4,5:6);plot([1:length(those)],xc(those));line(offset-floor(m/2),xc(those(offset-floor(m/2))),'marker','o','color','r');
    n = 20;
    x=reshape(offset+repmat(0*splittimes(:)',2*n+2,1)+repmat([-n:n nan]',1,length(splittimes)),1,[]);
    y=reshape(offset+repmat(splittimes(:)',2*n+2,1)+repmat([-n:n nan]',1,length(splittimes)),1,[]);
    s3=subplot(2,4,[7 8]);plot(x,dd(min(max(y,1),length(dd))));line(offset,0,'marker','s','color','r');
    %%
else
    %%
    [b,a] = butter(2,0.9);
    dd = filtfilt(b,a,dd);
    for ii = 1:(t(end)-t(1))-splittimes(end)+1
        td(ii) = 0;
        ti = ii+(1:splittimes(end))-1;
        for jj = 1:length(splittimes)
            deze(jj) = findmin(abs((t(ti)-t(1))-splittimes(jj)));
            td(ii) = td(ii)+dd(deze(jj));
        end
    end
    offset = t(max(find(abs(td)==min(abs(td)))))-t(1);
    %%
end
 
s = sprintf('Estimated offset of GPS (nr of seconds the GPS was started before the splittimer): %0.1f [s]',offset);
fprintf('%s\n',s);
 
new_offset = inputdlg(strvcat(s,'Keep this value, or enter a different offset (based on the graphs):'),'Corect estimated offset',1,{sprintf('%0.1f',offset)});
drawnow;
if ~isempty(new_offset{1}), 
    if isempty(str2num(new_offset{1}))
        error('err-&gt; invalid value: "%s"',new_offset{1});
    end
    offset = str2num(new_offset{1});
    s3=subplot(2,4,[7 8]);
    line(offset,0,'marker','s','color','m');
end
 
 
t_sync = t-t(1)-offset;
for jj = 1:length(splittimes)
    splittimes_sync(jj) = t_sync(findmin(abs(t_sync-splittimes(jj))));
end
splittimes_abs = splittimes_sync+t(1)+offset;
 
fprintf('** finished reading tcx file **\n');
 
frewind(fid);
[fp,fn,fe] = fileparts(in_file);
outfile = fullfile(fp,[fn '_splits.tcx']);
fprintf('** writing output file: "%s" **\n',outfile);
fod = fopen(outfile,'w');
[fod,err] = fopen(outfile,'w');
if fod failed opening file "%s" for writing, with error: %s',outfile,err);
end
 
distance_offset = 0;
 
phase = 0;
% phases:
% 0 header
% 1 first track header, until first point to be included
% 2 first trackpoints being included
% 3 other trackpoints being included
% 4 buffering last point of current lap
% 5 obsolete lap header
% 6 last trackpoint writing
% 7 trackpoints after last trackpoint
% 8 trailer
 
trackpointbuffer = {};
lap = 0;
while ~feof(fid)
    l = fgets(fid);
 
    if phase==0
        lp = sscanf(l,'%*[^&lt;]&lt;Lap%s'); % StartTime=%s');
        if ~isempty(lp) &amp;&amp; phase==0
            phase = 1;
            % do not parse from here
        end
    else
        lp = sscanf(l,'%*[^&lt;]%c');
        if ~isempty(lp) &amp;&amp; phase&gt;0
            phase = 8;
            % do parse trailer from here
        else
            lp = sscanf(l,'%*[^&lt;]&lt;Creator%s'); if ~isempty(lp) &amp;&amp; phase&gt;0
                phase = 8;
                % do parse trailer from here
            end
        end
    end
 
    if any(phase==[2 3 4])
        lp = sscanf(l,'%*[^&lt;]%c');
        if ~isempty(lp)
            if any(phase==[2 3]) % not if phase is 4, since then already a lap was included here
                phase = 5;
                % do not parse from here, until next lap started
            elseif phase==4 % buffered, but continue including lap
                phase = 3; % continue writing; lap is OK here
                trackpointbuffer = {};
                lap = lap+1;
            end
        end
    end
 
    laptext = [0 0];
 
    if any(phase==[4 5 6])
        lp = sscanf(l,'%*[^&lt;]%c');
        if ~isempty(lp)
            if phase==6
                % last point passed, no output anymore except trailer
                phase = 7;
                fprintf(fod,l);
                l = '';
                laptext = [0 1];
            elseif phase==4
                % copying last trackpoint for new track
                % inlude lap split
                fprintf(fod,l);
                laptext = [1 1]; % implicitly include trackpointbuffer
            elseif phase==5
                % skip first trackpoint after lap, since it is identical
                phase = 3;
                l = '';
            end
        end
    end
 
    hms = sscanf(l,'%*[^&lt;]<time>%*[^T]T%d:%d:%dZ</time>');
    if ~isempty(hms)
        timestr = sscanf(l,'%*[^&lt;]<time>%[^Z]Z</time>');
        if [60*60 60 1]*hms&gt;=splittimes_abs(1) &amp;&amp; phase=splittimes_abs(end) &amp;&amp; phase=splittimes_abs(lap+1) &amp;&amp; phase%f');
        if ~isempty(lp)
            if phase==2
                distance_offset = lp;
            end
            l = sprintf('            %0.7f\n',lp-distance_offset);
        end
    end
 
    if laptext(2)
        fprintf(fod,'         \n');
        fprintf(fod,'       \n');
    end
    if laptext(1)
        lap = lap+1;
        fprintf(fod,'       \n',timestr);
        fprintf(fod,'         %0.7f\n',diff(splittimes_abs(lap+[0:1])));
        fprintf(fod,'         Active\n');
        fprintf(fod,'         Manual\n');
        fprintf(fod,'         \n');
        fprintf(fod,'           \n');
    end
    if phase==4
        if all(laptext)
            fprintf(fod,'%s',trackpointbuffer{:});
            trackpointbuffer = {};
            phase = 3;
        else
            trackpointbuffer{end+1} = l;
        end
    end
 
    if any(phase==[0 2 3 4 6 8])
        fprintf(fod,'%s',l);
    end
 
end
fclose(fod);
fclose(fid);
fclose('all');
 
fprintf('** finished successfully: splits were included in tcx file **\n');
 
 
 
 
function times = readtimes(in)
times = [];
offset = 0;
for ii = 1:length(in)
    this = sscanf(in{ii},'%d%*[.:]%d%*[.:]%d');
    if length(this)==2
        if in{ii}(1)=='+'
            offset = offset+[60 1]*this;
            times(end+1) = times(end)+offset;
        else
            times(end+1) = [60 1]*this+offset;
        end
    elseif length(this)==3
        if in{ii}(1)=='+'
            offset = offset+[60*60 60 1]*this;
            times(end+1) = times(end)+offset;
        else
            times(end+1) = [60*60 60 1]*this+offset;
        end
    end
end
 
 
% function strs = split(str,sep,include,num)
% % SPLIT - split a string in substrings by separator
% matches = [1 strfind(str,sep)+1 length(str)+2];
% for ii = 1:length(matches)-1
%     strs{ii} = str(matches(ii)+(ii&gt;1)*(size(sep,2)-1):matches(ii+1)-2);
% end
 
 
 
function  [x,y,utmzone] = deg2utm(Lat,Lon)
% -------------------------------------------------------------------------
% [x,y,utmzone] = deg2utm(Lat,Lon)
%
% Description: Function to convert lat/lon vectors into UTM coordinates (WGS84).
% Some code has been extracted from UTM.m function by Gabriel Ruiz Martinez.
%
% Inputs:
%    Lat: Latitude vector.   Degrees.  +ddd.ddddd  WGS84
%    Lon: Longitude vector.  Degrees.  +ddd.ddddd  WGS84
%
% Outputs:
%    x, y , utmzone.   See example
%
% Example 1:
%    Lat=[40.3154333; 46.283900; 37.577833; 28.645650; 38.855550; 25.061783];
%    Lon=[-3.4857166; 7.8012333; -119.95525; -17.759533; -94.7990166; 121.640266];
%    [x,y,utmzone] = deg2utm(Lat,Lon);
%    fprintf('%7.0f ',x)
%       458731  407653  239027  230253  343898  362850
%    fprintf('%7.0f ',y)
%      4462881 5126290 4163083 3171843 4302285 2772478
%    utmzone =
%       30 T
%       32 T
%       11 S
%       28 R
%       15 S
%       51 R
%
% Example 2: If you have Lat/Lon coordinates in Degrees, Minutes and Seconds
%    LatDMS=[40 18 55.56; 46 17 2.04];
%    LonDMS=[-3 29  8.58;  7 48 4.44];
%    Lat=dms2deg(mat2dms(LatDMS)); %convert into degrees
%    Lon=dms2deg(mat2dms(LonDMS)); %convert into degrees
%    [x,y,utmzone] = deg2utm(Lat,Lon)
%
% Author: 
%   Rafael Palacios
%   Universidad Pontificia Comillas
%   Madrid, Spain
% Version: Apr/06, Jun/06, Aug/06, Aug/06
% Aug/06: fixed a problem (found by Rodolphe Dewarrat) related to southern 
%    hemisphere coordinates. 
% Aug/06: corrected m-Lint warnings
%-------------------------------------------------------------------------
 
% Argument checking
%
error(nargchk(2, 2, nargin));  %2 arguments required
n1=length(Lat);
n2=length(Lon);
if (n1~=n2)
   error('Lat and Lon vectors should have the same length');
end
 
 
% Memory pre-allocation
%
x=zeros(n1,1);
y=zeros(n1,1);
utmzone(n1,:)='60 X';
 
% Main Loop
%
for i=1:n1
   la=Lat(i);
   lo=Lon(i);
 
   sa = 6378137.000000 ; sb = 6356752.314245;
 
   %e = ( ( ( sa ^ 2 ) - ( sb ^ 2 ) ) ^ 0.5 ) / sa;
   e2 = ( ( ( sa ^ 2 ) - ( sb ^ 2 ) ) ^ 0.5 ) / sb;
   e2cuadrada = e2 ^ 2;
   c = ( sa ^ 2 ) / sb;
   %alpha = ( sa - sb ) / sa;             %f
   %ablandamiento = 1 / alpha;   % 1/f
 
   lat = la * ( pi / 180 );
   lon = lo * ( pi / 180 );
 
   Huso = fix( ( lo / 6 ) + 31);
   S = ( ( Huso * 6 ) - 183 );
   deltaS = lon - ( S * ( pi / 180 ) );
 
   if (la&lt;-72), Letra='C';
   elseif (la&lt;-64), Letra='D';
   elseif (la&lt;-56), Letra='E';
   elseif (la&lt;-48), Letra='F';
   elseif (la&lt;-40), Letra='G';
   elseif (la&lt;-32), Letra='H';
   elseif (la&lt;-24), Letra='J';
   elseif (la&lt;-16), Letra='K';
   elseif (la&lt;-8), Letra='L';
   elseif (la&lt;0), Letra='M';
   elseif (la&lt;8), Letra='N';
   elseif (la&lt;16), Letra='P';
   elseif (la&lt;24), Letra='Q';
   elseif (la&lt;32), Letra='R';
   elseif (la&lt;40), Letra='S';
   elseif (la&lt;48), Letra='T';
   elseif (la&lt;56), Letra='U';
   elseif (la&lt;64), Letra='V';
   elseif (la&lt;72), Letra='W';
   else Letra='X';
   end
 
   a = cos(lat) * sin(deltaS);
   epsilon = 0.5 * log( ( 1 +  a) / ( 1 - a ) );
   nu = atan( tan(lat) / cos(deltaS) ) - lat;
   v = ( c / ( ( 1 + ( e2cuadrada * ( cos(lat) ) ^ 2 ) ) ) ^ 0.5 ) * 0.9996;
   ta = ( e2cuadrada / 2 ) * epsilon ^ 2 * ( cos(lat) ) ^ 2;
   a1 = sin( 2 * lat );
   a2 = a1 * ( cos(lat) ) ^ 2;
   j2 = lat + ( a1 / 2 );
   j4 = ( ( 3 * j2 ) + a2 ) / 4;
   j6 = ( ( 5 * j4 ) + ( a2 * ( cos(lat) ) ^ 2) ) / 3;
   alfa = ( 3 / 4 ) * e2cuadrada;
   beta = ( 5 / 3 ) * alfa ^ 2;
   gama = ( 35 / 27 ) * alfa ^ 3;
   Bm = 0.9996 * c * ( lat - alfa * j2 + beta * j4 - gama * j6 );
   xx = epsilon * v * ( 1 + ( ta / 3 ) ) + 500000;
   yy = nu * v * ( 1 + ta ) + Bm;
 
   if (yy&lt;0)
       yy=9999999+yy;
   end
 
   x(i)=xx;
   y(i)=yy;
   utmzone(i,:)=sprintf('%02d %c',Huso,Letra);
end
 
 
 
 
function ms = m2ms(m,textout)
% min to min:sec
% if second argument is true, then text is output
 
if nargin1 &amp;&amp; ~~textout)
    if any(ms(:,1)&gt;=60)
        f = '%d:%02d';
        hms = [floor(ms(:,1)/60) mod(ms,60)];
    else
        f = '%02d';
        hms = ms;
    end
    if any(abs(m*60-round(m*60))&gt;1e-3)
        f = [f ':%06.3f'];
    else
        f = [f ':%02.0f'];
    end
    if ~nargout
        fprintf([f '\n'],hms.');
        clear ms;
    else
        ms = num2str(hms,f);
    end
end

Wie weet maak in nog een keer een .exe file hier van, of een online tool zodat je op mijn website jouw gpx/tcx files van splits kan voorzien. Laat hier onder, als commentaar, opmerkingen, wensen of suggesties achter. Mocht je handig zijn met Perl, PHP, Python, of zo, dan kan je wellicht deze, op zich redelijke simpele, code herschrijven.

7 thoughts on “Hackerdehack

  1. Lijkt me reuze handig programmaatje, en wilde ‘m eens gaan proberen.
    Helaas een Matlab-foutmelding: je gebruikt ‘explode’, en die functie is bij mij niet bekend.
    Any suggestions…?

    1. Ik had de functie explode.m inderdaad elders vandaan: Matlab Central. Ik heb mijn script hier boven nu aangepast zodat die externe functie niet meer nodig is.

  2. Let op! Als een wedstrijd een NEUTRALISATIE post heeft, klopt de bewerkte track niet meer. Je raakt dan aan het eind een stukje kwijt, omdat de splits en stukje missen. Dit los je op door het verschil handmatig in de splits toe te voegen, maar dat vergt wel wat gepuzzel.

    Intussen heb ik ook het script zelf aangepast (niet om dit op te lossen), en de laatste versie hier boven gezet.

  3. Hallo, könnten Sie mir bitte erklären, wie ich aus dem Quelltext ein lauffähiges Programm machen kann?
    Vielen Dank

    1. It’s executable in Matlab. So typically I do not compile it; just start Matlab and call this function.
      There are ways to make a stand-alone executable out of it, though. But that requires to have the MCR (Matlab Component Runtime) installed on the user’s PC.
      I think the nicest solution would be to make a web-app, a site where you upload your gpx/tcx file, and provide a link to the splitsbrowser page and your name on that page; then the web function executes, retrieves the splits, finds the best match in the track file, modifies it and returns the gps track with the correct splits included. At the moment, I don’t have time to make that, however. Needs rewriting this function in php or python.

      1. Vielen Dank für die schnelle Antwort.
        Muß ich Matlab auf meinem PC installiert haben, um das Programm in Matlab zu starten?
        Viele Grüße aus Bayern

Leave a Reply

Your email address will not be published. Required fields are marked *