środa, 27 września 2017

Żuławy wKoło 2017 -- podsumowanie

Podsumowanie wyników dla lat 2015--2017

z16 <- read.csv("wyniki_zulawy_2016_D.csv", sep = ';',  header=T, na.string="NA", dec=",");
aggregate (z16$time, list(Numer = z16$dist), summary)
z16$year <- 2016;

z15 <- read.csv("wyniki_zulawy_2015_D.csv", sep = ';',  header=T, na.string="NA", dec=",");
aggregate (z15$time, list(Numer = z15$dist), summary)
z15$year <- 2015;

z17 <- read.csv("wyniki_zulawy_2017_D.csv", sep = ';',  header=T, na.string="NA", dec=".");
aggregate (z17$time, list(Numer = z17$dist), summary)
z17$year <- 2017;

zz15 <- z15[, c("dist", "kmH", "time", "year")];
zz16 <- z16[, c("dist", "kmH", "time", "year")];
zz17  <- z17[, c("dist", "kmH", "time", "year")];

zz <- rbind (zz15, zz16, zz17);

## tylko dystans 140
zz140 <- subset (zz, ( dist == 140 ));
sum140 <- aggregate (zz140$kmH, list(Numer = zz140$year), summary)

boxplot (kmH ~ year, zz140, ylab = "Śr.prędkość [kmh]", col = "yellow", main="140km" )

## tylko dystans 55
zz75 <- subset (zz, ( dist > 60 & dist < 90 ));
sum75 <- aggregate (zz75$kmH, list(Numer = zz75$year), summary)
sum75
boxplot (kmH ~ year, zz75, ylab = "Śr.prędkość [kmh]", col = "yellow", main="80/75km" )

## tylko dystans 55
zz55 <- subset (zz, ( dist < 60 ));
sum55 <- aggregate (zz55$kmH, list(Numer = zz55$year), summary)
sum55
xl <- paste ("średnie 2015=", sum55$x[1,4], "kmh   2016=",
  sum55$x[2,4], "kmh   2017=", sum55$x[3,4], " kmh")

  boxplot (kmH ~ year, zz55, xlab = xl,
  ylab = "Śr.prędkość [kmh]", col = "yellow", main="55km" )

A ja (numer 418) byłem 70 w kategorii 140 km, z czasem 5:42:03 co dało 24,56 kmh przeciętną. Do pierwszego bufetu się spinałem, potem już nie...

wtorek, 26 września 2017

Obracanie wideo za pomocą ffmpeg

Raz na jakiś czas mi się zdarza źle założyć kamerę i obraz jest odwrócony do góry nogami (albo przekręcony o 90 stopni). Film z Żuławy wKoło jest do-góry-nogami. Próbuję postawić go na nogi rozpoczynając od mencodera:

time mencoder -vf flip -o plik.avi -oac copy -ovc lavc plik2.avi
## 26min ale słaba jakość

Słaba jakość. Pewnie można poprawić ale nie wiem jak i nie chce mi się zgłębiać dokumentacji. Zamiast tego zmieniam konwerter na ffmpeg

time ffmpeg -i plik.we -vf vflip -c:a copy plik.wy
## 75 minut (jedziemy lewa strona)

Prawie dobrze, tj. głowa jest na górze a nogi na dole, ale obraz jest w lustrze (jedziemy lewą stroną ulicy, napisy są w lustrzanym odbiciu itp...). W google można znaleść taki przepis:

time ffmpeg -i plik.we -vf "transpose=0" -c:a copy plik.wy

Jak poprzednio tylko przekręca lustro w pionie bez sensu. Działa (prawie) prawidłowo następujące wywołanie:

time ffmpeg -i plik.we -vf "transpose=2,transpose=2" -c:a copy plik.wy

Prawie, ponieważ:

ffmpeg -i plik.wy

Zgubiły się napisy zawierające współrzędne geograficzne, ale to też da się naprawić:

time ffmpeg -i plik.we -vf "transpose=2,transpose=2" -c:a copy -c:s copy plik.wy

poniedziałek, 25 września 2017

Chancellor Merkel victory for a visual person

Change in number of seats won by party (AfD is brown of course regardless official party colors :-):-)

library(ggplot2)

df <- read.csv("de.csv", sep = ';', header=T, na.string="NA");

ggplot(df, aes(x=party, y=diff, fill=party )) +
geom_bar(stat="identity") +
geom_text(aes(label=diff), vjust=-0.5) +
labs(x = "", y="change") +

ggtitle("German elections results (#MP change)") +

## AfD is brown regardless official party colors :-)
scale_fill_manual(values=c("#8B4513", "#56B4E9",
"yellow", "green", "red", "#ff6666") )

Współrzędne geograficzne zarejestrowane kamerą Contour+

Contour+ ma GPSa i rejestruje współrzędne geograficzne, tyle że do niedawna nie bardzo wiedziałem jak (słusznie podejrzewałem że w postaci napisów aka subtitles). Wreszcie rozkminiłem jak to działa, a zmobilizowały mnie filmy zarejestrowane podczas imprezy Żuławy wKoło 2017.

Najpierw trzeba ustalić co jest w środku pliku .mov:

ffmpeg -i FILE0037.MOV
## ## ##  
Stream #0:2(eng): Subtitle: mov_text (text / 0x74786574), 1 kb/s (default)

Teraz można wyciągnąć napis znajdujący się w strumieniu (stream) 2:

ffmpeg -i FILE0037.MOV -vn -an -codec:s:0.2 srt file0037_2.srt

W pliku file0037_2.srt jest coś takiego:

692
00:11:31,000 --> 00:11:32,000
$GPRMC,061159.00,V,,,,,,,240917,,,N*7E
$GPGGA,061159.00,,,,,0,04,2.18,,,,,,*53

693
00:11:32,000 --> 00:11:33,000
$GPRMC,061200.00,A,5412.74161,N,01906.66188,E,18.465,202.51,240917,,,A*50
$GPGGA,061200.00,5412.74161,N,01906.66188,E,1,04,2.18,6.5,M,32.4,M,,*58

Czyli jest to zwykły plik napisów w formacie SRT, tj. sekwencja rekordów składających się z wierszy tekstu. Pierwszy wiersz zawiera numeru napisu (692 na przykład). Drugi wiersz określa czas wyświetlania napisu (początek --> koniec). Kolejne wiersze to tekst napisu. W przykładzie powyżej napis 692 jeszcze nie złapał fiksa, a napis 693 już tak. Współrzędne są zarejestrowane w postaci par zdań (sentences) GPRMC/GPGGA w standardzie NMEA. Do konwersji czegoś takiego na format GPX na przykład można zastosować gpsbabela

gpsbabel -i nmea -f file.srt -o GPX -F file.gpx

Ale wtedy gubi się informację z pierwszych dwóch wierszy rekordu, a jest ona niezbędna do synchronizacji obrazu ze współrzędnymi w programach nie potrafiących wykorzystać napisów wbudowanych. Chciał-nie-chciał musiałem rozpoznać NMEA i dokonać konwersji po swojemu:

$GPRMC,time,###,dd.mm,N/S,dd.mm,E/W,speed,###,date,###,###,###
$GPGGA,time,dd.mm,N/S,dd.mm,E/W,q,s,###,ele,M,###,M,###,### 

Gdzie: speed -- prędkość w węzłach czyli milach/godzinę; date -- data w formacie ddmmyy; time -- czas w formacie hhmmss.ss; dd.mm -- współrzędne geograficzne w formacie stopnieminuty.minuty tj 5412.74161 oznacza 54 stopnie 12.74161 minut a 01906.66188 oznacza 19 stopni 6.66188 minut (uwaga: szerokość/długość ma różną liczbę cyfr przed kropką dziesiętną); N/S/E/W -- kierunki geograficzne (north, south itp); q -- jakość sygnału (niezerowa wartość jest OK); s -- liczba satelitów; ele -- wysokość npm. (w metrach na szczęście w przypadku Contoura+). Zawartość pól oznaczona jako ### nas nie interesuje. Symbol M oznacza jednostkę miary (metry), z czego by wynikało, że różne odbiorniki GPS mogą zapisywać informacje o wysokości z wykorzystaniem innych jednostek miary.

Teraz banalny skrypt Perlowy zamienia SRT na format GPX dodając informacje o numerze napisu i czasie wyświetlania w postaci stosownego elementu cmt

  <trkpt lat="54.212360" lon="19.111031">
    <ele>6.500000</ele>
    <time>2017-09-24T06:12:00Z</time>
    <speed>9.499208</speed>
    <cmt>693 00:11:32,000 --> 00:11:33,000</cmt>
  </trkpt>

BTW nie ma elementu speed w specyfikacji schematu GPX, ale na przykład gpsbabel taki element wstawia i jakoś to działa. Sprawa wymaga zbadania.

Uwaga: Garmin Virb Edit nie czyta dokumentów GPX w wersji 1.0 -- musi być wersja 1.1. W praktyce oznacza to, że element gpx powinien posiadać atrybuty version oraz xmlns o następujących wartościach

<gpx version="1.1" xmlns="http://www.topografix.com/GPX/1/1">
 

Skrypt pn. cc2gpx.pl do konwersji SRT→GPX jest tutaj.

sobota, 23 września 2017

Przed Żuławy w Koło 2017

Jutro planuję przejechać 140km biorąc udział w imprezie pn. Żuławy wKoło 2017. Niby Żuławy a profil trasy sugeruje jakieś istotne wzniesienie w okolicach 25--40km:

Uważnie przyjrzenie się liczbom (zwłaszcza na osi OY) pozwala stwierdzić, że jest to złudzenie, wynikające z różnicy w jednostkach miary obu osi (kilometry vs metry). W rzeczywistości góra tam jest symboliczna o czym można się przekonać robiąc wykres nachyleń. Żeby pozbyć się przypadkowych błędów związanych z niedokładnością pomiaru oryginalne 673 punktowe dane zostały zmienione na 111 punktowe (uśrednienie minimum 1 km) lub 62 punktowe (uśrednienie minimum 2 km).

Przy czym uśrednienie minimum $x$ oznacza obliczenie nachylenia dla najkrótszego odcinka kolejnych $n$ punktów z oryginalnego śladu GPX, który będzie dłuższy niż $x$.

Skrypty R/dane są tutaj. Oryginale ślady GPX/TCX skopiowane ze strony ŻwK są tutaj.

poniedziałek, 18 września 2017

Duńskie crime story z równością płci w tle

W Danii doszło do morderstwa. Ponieważ ofiarą jest kobieta i dziennikarka, do tego znana w pewnych kręgach sprawa jest komentowana (w tychże kręgach). Niejaka i bliżej mi nieznana pani Sruthi Gottipati z Guardiana w rubryce Gender zresztą czyli bynajmniej nie kryminalnej stwierdza: After traveling and reporting in Africa, Asia and the Caribbean, it was in her native Scandinavia, a supposed bastion of gender parity, in which Kim has disappeared. Zatem wg. pani Gottipati nastąpił paradoks: zamordowana uszła z życiem w krajach znanych z męskiej opresji wzg. kobiet, a nie dała rady w państwie, który jest bastionem równości płci.

Po pierwsze w swoim ptasim móżdżku pani Gottipati nie potrafi zauważyć, że jak równość to równość. Co to za równość jak ofiarą morderstwa może paść wyłącznie facet, a jak kobieta to paradoks i skandal. W bastionie równości rozkład ofiar (i sprawców) powinien być fifty-fifty, a jest zaledwie 30--70 (na niekorzyść kobiet, w sensie że tylko 30% ofiar to kobiety). Dania ma tutaj jeszcze sporo do nadrobienia zanim będzie można ją nazwać bastionem równości.

Po drugie i to mnie zastanawia: pani Gottipati używa terminu gender parity, który jest dużo mniej popularny niż gender equality. Wg eige.europa.eu/ Gender parity is a numerical concept related to gender equality, czyli jest to miara równości płci. Zatem Dania jest bastionem pod względem poprawności wskaźników mierzących równość płci. Nie wiem czy pani Gottipati chciała to faktycznie powiedzieć (być może nie rozróżnia po prostu obu pojęć), ale wygląda że coś takiego chlapnęła--idiotyzm do kwadatu.

No i po trzecie paradoks Gottipati ma się jak pięść do nosa w konfrontacji nie tylko z zwykłą logiką ale z danymi. Bo jeżeli jednak przyjmiemy za pozytywną sytuację, w której współczynnik zabójstw nie jest jednakowy wg. płci, tylko dla kobiet jest mniejszy, to w wybranych krajach kształtuje się on (ten współczynnik) następująco:

https://en.wikipedia.org/wiki/Homicide_statistics_by_gender
Kraj   Liczba zabójstw  WspZ       %M      %K    Rok
Czech Republic	   105	 1.0	54.3%	45.7%	2011
Denmark	            47	 0.8	66.0%	34.0%	2010
Finland	            89	 1.6	53.9%	46.1%   2010
Norway	           111	 2.2	53.2%	46.8%   2010
Poland	           449	 1.2	73.2%	26.8%	2010
Colombia	14,670	30.8	91.6%	 8.4%   2012
Brazil	        50,108	25.2	89.8%	10.2%	2010
Argentina	 2,237	 5.5	83.6%	16.4%   2011
Pakistan	13,846	 7.7	76.7%	23.3%   2010
Indonesia	 1,456	 0.6	80.3%	19.7%   2010
Dominican Rep.   2,268	22.1	91.1%	 8.9%	2010
Honduras	 7,172	90.4	93.2%	 6.8%	2010
gdzie: %M/%K -- odsetek ofiar mężczyźni/kobiety;
WspZ -- współczynnik zabójstw
czyli liczba zabójstw / 100 tys mieszkańców

Kilka interesujących stron przy okazji United Nations Office on Drugs and Crime oraz ourworldindata.org

PS: lokalna tuba lewactwa na PL czyli politruka.pl in-extenso relacjonowała całą ,,aferę'' łącznie ze bezrefleksyjnym skopiowaniem stosownego fragmentu z Guardiana (dowód w załączeniu)

poniedziałek, 11 września 2017

Czytelnictwo prasy

Punktem wyjścia są dane ze strony ZKDP (w formacie Excel.) Ponieważ pobieram je od pewnego czasu mam tego więcej niż jest na ww. stronie bo od stycznia 2015 do lipca 2017, czyli 31 plików. Ręczna konwersja byłaby zatem ciut za bardzo czasochłonna.

  for i in *.xls do
    oocalc --headless --convert-to csv $i ;
    # albo ssconvert -v $i `basename $i .xls`.csv ;
    done
  # Wyciągam dane dotyczące sprzedaży ogółem dla SE
  grep 'Super Ex' *.csv | awk -F ',' '{print $7} ' > se_sales.csv
  # Analogicznie dla innych tytułów

Uwaga: program ssconvert znajduje się w pakiecie gnumeric, oocalc to oczywiście składni Libre/OpenOffice.

Wielkości sprzedaży dla trzech najpoczytniejszych tytułów pokazują wykresy liniowe (pierwszy w tys egz. a drugi w procentach nakładu ze stycznia 2015 r.)


Sprzedaż w tys egz.

Sprzedaż w % poziomu ze stycznia 2015
library(ggplot2)
library(reshape2)

df <- read.csv("newspaper_sales_2015-17.csv", sep = ';',
               header=T, na.string="NA");

meltdf <- melt(df,id="month")

ggplot(meltdf,aes(x=month, y=value, colour=variable, group=variable)) +
  geom_line() +
  ylab(label="sales [ths]") +
  theme(legend.title=element_blank()) +
  scale_x_discrete (breaks=c("2015-01-01", "2015-06-01",
     "2016-01-01", "2016-06-01", "2017-01-01",  "2017-06-01"),
  labels=c("2015-01", "2015-06", "2016-01", "2016-06",
     "2017-01", "2017-06")  )

# https://stackoverflow.com/questions/10085806/extracting-specific-columns-from-a-data-frame
obs <- df[,c("month")]

normalize <- function(x) { return (x /x[1] * 100 )  }
dfN <- as.data.frame(lapply(df[-1], normalize))

# https://stackoverflow.com/questions/10150579/adding-a-column-to-a-data-frame
dfN["month"] <- obs

str(dfN)

meltdf <- melt(dfN,id="month")

# https://www.r-bloggers.com/what-is-a-linear-trend-by-the-way/
pN <- ggplot(meltdf,
 aes(x=month, y=value, colour=variable, group=variable)) + geom_line() +
 ylab(label="sales [ths]") +
 theme(legend.title=element_blank()) +
 stat_smooth(method = "lm", se=F) +
  scale_x_discrete (breaks=c("2015-01-01", "2015-06-01",
     "2016-01-01", "2016-06-01", "2017-01-01",  "2017-06-01"),
  labels=c("2015-01", "2015-06", "2016-01",
  "2016-06", "2017-01", "2017-06")  )

  pN

Spadek widoczny na wykresach można określić liczbowo na przykład szacując linię trendu:

# Trend liniowy
# http://t-redactyl.io/blog/2016/05/creating-plots-in-r-using-ggplot2-part-11-linear-regression-plots.html

# http://r-statistics.co/Time-Series-Analysis-With-R.html
seq = c (1:nrow(dfN))
dfN["trend"] <- seq

trendL.gw <- lm(data=dfN, gw ~ trend )
trendL.fakt <- lm(data=dfN, fakt ~ trend )
trendL.se <- lm(data=dfN, se ~ trend )

trendL.gw
trendL.fakt
trendL.se

Współczynniki trendu dla GW, Faktu i SE są odpowiednio równe -1.114 -0.6415 -0.4301, co należy interpretować następująco: przeciętnie z miesiąca na miesiąc nakład spada o 1,11%, 0,64% oraz 0,43% nakładu ze stycznia 2015 r., odpowiednio dla GW, Faktu i SuperExpresu.

Dane i wyniki są tutaj

niedziela, 10 września 2017

Żuławy w koło 2016

Żuławy w koło to maraton rowerowy (czyli przejazd rowerem na dłuższym dystansie -- nie mylić z wyścigiem) organizowany od paru lat na Żuławach jak nazwa wskazuje. Sprawdziłem jak ta impreza wyglądała pod kątem prędkości w roku 2016. W tym celu ze strony Wyniki żUŁAWY wKOŁO 2016 ściągnąłem stosowny plik PDF z danymi, który następnie skonwertowałem do pliku w formacie XLS (Excel) wykorzystując konwerter on-line tajemniczej firmy convertio.pl. Tajemniczej w tym sensie, że nie znalazłem informacji kto i po co tą usługę świadczy.

Konwersja (do formatu CSV) -- jak to zwykle konwersja -- nie poszła na 100% poprawnie i wymagała jeszcze circa 30 minutowej ręcznej obróbki. Być może zresztą są lepsze konwertery, ale problem był z gatunku banalnych i wolałem stracić 30 minut na poprawianiu wyników konwersji niż 2 godziny na ustalaniu, który z konwerterów on-line konwertuje ten konkretny plik PDF (w miarę) bezbłędnie.

Po konwersji wypadało by sprawdzić (chociaż zgrubnie) czy wszystko jest OK.

## Czy każdy wiersz zawieraja 9 pól (powinien)
$ awk -F ';' 'NF != 9 {print NR, NF}' wyniki_zulawy_2016S.csv

## Ilu było uczestników na dystansie 140km?
$ awk -F ';' '$7 ==140 {print $0}' wyniki_zulawy_2016S.csv | wc -l
133

## Ilu było wszystkich (winno być 567 + 1 nagłówek)
$ cat wyniki_zulawy_2016S.csv | wc -l
568 # ok!

Przykładowy wykres pudełkowy

Do analizy statystycznej wykorzystano wykres pudełkowy (porównanie wyników na różnych dystansach) oraz histogram (rozkład średnich prędkości na dystansie 140km). BTW gdyby ktoś nie wiedział co to jest wykres pudełkowy to wyjaśnienie jest na rysunku obok. Objaśnienie: Me, $Q_1$, $Q_3$ to odpowiednio mediana i kwartyle. Dolna/górna krawędź prostokąta wyznacza zatem rozstęp kwartylny (IQR). Wąsy ($W_L$/$W_U$)są wyznaczane jako 150% wartości rozstępu kwartylnego. Wartości leżące poza ,,wąsami'' (nietypowe) są oznaczane kółkami.

Ww. wykresy wygenerowano następującym skryptem:

#
co <- "Żuławy wKoło 2016"
#
z <- read.csv("wyniki_zulawy_2016_C.csv", sep = ';',
  header=T, na.string="NA", dec=",");

aggregate (z$meanv, list(Numer = z$dist), fivenum)

boxplot (meanv ~ dist, z, xlab = "Dystans [km]",
    ylab = "Śr.prędkość [kmh]", col = "yellow", main=co )

## tylko dystans 140
z140 <- subset (z, ( dist == 140 ));

## statystyki zbiorcze
s140 <- summary(z140$meanv)
names(s140)

summary_label <- paste (sep='', "Średnia = ", s140[["Mean"]], 
  "\nMediana = ", s140[["Median"]],
  "\nQ1 = ", s140[["1st Qu."]],  "\nQ3 = ", s140[["3rd Qu."]],
  "\n\nMax = ", s140[["Max."]] )
# drukuje wartości kolumny meanv
# z140$meanv
# drukuje wartości statystyk zbiorczych
s140

# wykres słupkowy
h <- hist(z140$meanv, breaks=c(14,18,22,26,30,34,38), freq=TRUE, 
   col="orange", 
   main=paste (co, "[140km]"), # tytuł
   xlab="Prędkość [kmh]",ylab="L.kolarzy", labels=T, xaxt='n' )
# xaxt usuwa domyślną oś 
# axis definiuje lepiej oś OX
axis(side=1, at=c(14,18,22,26,30,34,38))
text(38, 37, summary_label, cex = .8, adj=c(1,1) )

Dane i wyniki są tutaj

środa, 6 września 2017

O lepsze liczenie kadencji

Informacja o kadencji jest w pliku .tcx rejestrowana (przez GarminaEdge 500) w następujący sposób:

  <Activities>
    <Activity Sport="Biking">
      <Lap StartTime="2017-08-31T14:51:16Z">
 <Cadence>77</Cadence>
 ...
 <Track>
     <Trackpoint>
             <Time>2017-08-31T14:52:02Z</Time>
             <Cadence>0</Cadence>
          </Trackpoint>
   <Trackpoint>
      <Time>2017-08-31T14:52:06Z</Time>
      <Cadence>46</Cadence>
   </Trackpoint>
   <Trackpoint>
            <Time>2017-08-31T14:52:07Z</Time>
     <Cadence>53</Cadence>
   </Trackpoint>

Lap-ów może być wiele. Każdy zawiera element Track, w którym zapisana jest informacja o fragmencie trasy. Pomiędzy LapTrack znajduje się nagłówek zawierający różne obliczone/zbiorcze informacje, m.in. średnią kadencję. Co by oznaczało, że w powyższym przykładzie średnia kadencja wynosiła 77 obrotów/min. Na odcinku od 14:52:02Z do 14:52:06Z (4 sekundy) średnia kadencja wyniosła 46 o/min, zaś na odcinku 14:52:06Z--07Z (1s) 53 o/min.

Można też policzyć kadencję samodzielnie, co pozwoli na uzyskanie dodatkowej informacji. Liczenie kadencji jest proste. Mnożąc średnią kadencję odcinka razy czas w sekundach otrzymamy liczbę obrotów korby na tym odcinku (kadencję należy podzielić przez 60 bo jest podana w minutach). Odcinki o zerowej kadencji pomija się (Garmin też tak oblicza BTW). Dzieląc łączną liczbę obrotów przez czas otrzymamy średnią kadencję. Ja dodałem opcję pomijania odcinków o pewnej minimalnej prędkości (np. 8 kmh) -- takie odcinki to zwykle jakieś nietypowe fragmenty, zaniżające tylko średnią.

#!/usr/bin/perl
# Cadence calculator for GarminEdge tcx files (or converted fits)
# tprzechlewski@gmail.com
use Getopt::Long;

my $prev_Time = -1;
my $parsingTrack ='N';
my $regCadence = 'N';
my $min_speed = -1; # minimum speed
my $trackNo=1;

GetOptions( "s=f"  => \$min_speed, );
$min_speedMS = ($min_speed *1000)/3600.0;

if ($min_speed > 0) {
  print "*** Segments with speed below $min_speed kmh skipped ***\n";
}

while (<>) {
  chomp();

  # Order is importany (first check for <Track>:
  if ( /<Track>/ ) {
    if ( $regCadence eq 'N' ) {
      ## Check if Cadence is registered
      print STDERR "*** No cadence registered! ***\n";
      exit 1;
    } else {
      $parsingTrack = 'Y' ; print STDERR "### Parsing track #$trackNo...\n";
      $trackNo++;
    }
  }

  # Parsing header:
  if ($parsingTrack eq 'N' ) {
    if ( /<TotalTimeSeconds>/) {
      $edge_LapTime = xmlEleValue('TotalTimeSeconds', $_) ; }
    elsif (/<Cadence>/) {
      $edge_LapCadence = xmlEleValue('Cadence', $_) ; $regCadence = 'Y' }
    elsif (/<DistanceMeters>/) {
      $edge_LapDist = xmlEleValue('DistanceMeters', $_) ;  }
    ##print STDERR "## Parsing track info: $_\n";
    next;
  }


  ## start parsing Track now:
  if (/Speed>/)  { $speed = xmlEleValue ('Speed', $_);
  } elsif ($_ =~ /<Time>(.*)T(.*)Z<\/Time>/ ) {
    $time = $2;
    ($h, $m, $s )  = split /:/, $time;
    $current_time = $h * 60 * 60  + $m * 60 + $s ;
    ##print STDERR "$current_time\n";
  } elsif (/<Cadence>/ ) {
    $cadence = xmlEleValue ('Cadence', $_);
  } elsif (/<DistanceMeters>/ ) { 
    $distance = xmlEleValue ('DistanceMeters', $_);
  } elsif ($_ =~ /<\/Trackpoint>/ ) {

    if ( $prevTime > 0 ) {## pomija pierwsze
      $lastTime = $current_time ;
      if ($cadence > 0 && $speed > $min_speedMS ) {
 $timeDiff = $current_time - $prevTime; 

 $total_time_cycled += $timeDiff ;
 $total_cycles += $cadence * $timeDiff / 60;

 $prevTime = $current_time ;
      }
      else {
 $timeDiff = $current_time - $prevTime; 

 $total_time_idle += $timeDiff ;

 $prevTime = $current_time ;
      }

    } else {
     $prevTime = $current_time ;
     $firstTime = $current_time ;
  }

 }

if ( /<\/Track>/ ) {
   $total_Time = $total_time_idle + $total_time_cycled;

   printf "Time = Idle: %d s Spinning: %d s Total: %d s\n", $total_time_idle,
      $total_time_cycled, $total_Time;
   printf "Time = Idle: %.2f%% Spinning: %.2f%% Total: %.2f%%\n",
      $total_time_idle/$total_Time *100,
      $total_time_cycled/$total_Time * 100, $total_Time/$total_Time *100;

   print "Rotations (total) = $total_cycles\n";
   print "Mean cadence (computed) = " . $total_cycles/$total_time_cycled * 60 . "\n";

   $totalTimeTime = $lastTime - $firstTime;

   print "Total time (last - first) = $totalTimeTime s\n";

  print "Registered (header) values: lap time: $edge_LapTime "
    . "cadence: $edge_LapCadence lap distance (m): $edge_LapDist\n";
  
  ## reset values ## ## ## 
  $grand_total_time_idle += $total_time_idle; 
  $grand_total_time_cycled  += $total_time_cycled ;
  $grand_total_cycles += $total_cycles ;
  $grand_total_Dist +=  $edge_LapDist;

  $total_time_idle = $total_time_cycled = $total_cycles = 0 ;
  $prev_Time = -1; 
  $parsingTrack ='N';
  $regCadence = 'N';
}

} ##/while

## Grand Totals:
$trackNo--;
$grand_total_Time = $grand_total_time_idle + $grand_total_time_cycled;

print "====== Totals/means for $trackNo tracks =====\n";
printf "Time = Idle: %d s Spinning: %d s Total: %d s\n", $grand_total_time_idle,
      $grand_total_time_cycled, $grand_total_Time;
   printf "Time = Idle: %.2f%% Spinning: %.2f%% Total: %.2f%%\n",
      $grand_total_time_idle/$grand_total_Time *100,
      $grand_total_time_cycled/$grand_total_Time * 100, 
      $grand_total_Time/$grand_total_Time *100;
print "Rotations (total) = $grand_total_cycles\n";
print "Mean cadence (computed) = " . $grand_total_cycles/$grand_total_time_cycled * 60 .  "\n";
print "Total distance (registered): $grand_total_Dist (m)\n";

## ### ### ### ### ###

sub xmlEleValue {
  my $en = shift; # element name
  my $el = shift; # line

  $el =~ /<$en>(.*)<\/$en>/;

  return "$1";

}

Przykładowy wydruk dla pliku fit/tcx zawierającego trzy segmenty:

$ cadencecalc.pl 2017-08-31.tcx
### Parsing track #1...
Time = Idle: 552 s Spinning: 2082 s Total: 2634 s
Time = Idle: 20.96% Spinning: 79.04% Total: 100.00%
Rotations (total) = 2684.95
Mean cadence (computed) = 77.3760806916427
Total time (last - first) = 2634 s
Registered (header) values: lap time: 2438.06 cadence: 77 lap distance (m): 16339.2
### Parsing track #2...
Time = Idle: 80 s Spinning: 652 s Total: 732 s
Time = Idle: 10.93% Spinning: 89.07% Total: 100.00%
Rotations (total) = 860.716666666666
Mean cadence (computed) = 79.2070552147239
Total time (last - first) = 3366 s
Registered (header) values: lap time: 731.05 cadence: 79 lap distance (m): 4994.82
### Parsing track #3...
Time = Idle: 29 s Spinning: 105 s Total: 134 s
Time = Idle: 21.64% Spinning: 78.36% Total: 100.00%
Rotations (total) = 120.716666666667
Mean cadence (computed) = 68.9809523809524
Total time (last - first) = 3500 s
Registered (header) values: lap time: 134.378 cadence: 69 lap distance (m): 761.78
====== Totals/means for 3 tracks =====
Time = Idle: 661 s Spinning: 2839 s Total: 3500 s
Time = Idle: 18.89% Spinning: 81.11% Total: 100.00%
Rotations (total) = 3666.38333333333
Mean cadence (computed) = 77.4860866502289
Total distance (registered): 22095.8 (m)

Podając jako minimalną prędkość 9 km/h otrzymamy:

$ cadencecalc.pl -s 9 2017-08-31.tcx
*** Segments with speed below 9 kmh skipped ***
### Parsing track #1...
Time = Idle: 654 s Spinning: 1980 s Total: 2634 s
Time = Idle: 24.83% Spinning: 75.17% Total: 100.00%
Rotations (total) = 2582.11666666667
Mean cadence (computed) = 78.2459595959596
Total time (last - first) = 2634 s
Registered (header) values: lap time: 2438.06 cadence: 77 lap distance (m): 16339.2
### Parsing track #2...
Time = Idle: 80 s Spinning: 652 s Total: 732 s
Time = Idle: 10.93% Spinning: 89.07% Total: 100.00%
Rotations (total) = 860.716666666666
Mean cadence (computed) = 79.2070552147239
Total time (last - first) = 3366 s
Registered (header) values: lap time: 731.05 cadence: 79 lap distance (m): 4994.82
### Parsing track #3...
Time = Idle: 29 s Spinning: 105 s Total: 134 s
Time = Idle: 21.64% Spinning: 78.36% Total: 100.00%
Rotations (total) = 120.716666666667
Mean cadence (computed) = 68.9809523809524
Total time (last - first) = 3500 s
Registered (header) values: lap time: 134.378 cadence: 69 lap distance (m): 761.78
====== Totals/means for 3 tracks =====
Time = Idle: 763 s Spinning: 2737 s Total: 3500 s
Time = Idle: 21.80% Spinning: 78.20% Total: 100.00%
Rotations (total) = 3563.55
Mean cadence (computed) = 78.1194738765071
Total distance (registered): 22095.8 (m)

Idle oznacza czas w którym nie kręcimy i/lub jedziemy poniżej prędkości minimalnej (podawany jest czas w sekundach i udział w całości). Rotations to liczba obrotów. Obliczone wartości wyglądają na prawidłowe na co wskazywałoby, że są bliskie wartościom liczonym przez Garmina.

poniedziałek, 4 września 2017

Wycieczka na Słowację i do Budapesztu

Pojechaliśmy na wycieczkę na Słowację i na Węgry. Dwa dni zwiedzania okolic Starej Lubowli (Stará Ľubovňa) potem 2 dni w Budapeszcie. Z dojazdami wyszło od poniedziałku do niedzieli 21--27 sierpnia 2017, tj. przyjechaliśmy do Lubowli w poniedziałek około 20:00, a w niedzielę z kolei byliśmy w domu około 18:00 (wyjeżdżając z Budapesztu przed 7:00 rano).

Udało mi się mimo niechętnej postawy małżonki zabrać rower. Żeby było więcej miejsca w bagażniku na bagaże, to z kół zdjąłem zaciski i wsadziłem je do zrobionych z kartonu pokrowców. Ponadto odkręciłem pedały. Po tych zabiegach rower był bardziej płaski i stwarzał lepsze wrażenie (na małżonce, bo pewnie i bez odkręcania cały bagaż też by się zmieścił). Czy ten rower w ogóle się przyda nie był całkiem pewne -- zależało to m.in. od zakwaterowania.

Pierwszego dnia w Lubowli zwiedzaliśmy głównie zamek. Interesujące miejsce, dużo poloników (tj. przedmiotów związanych z Polską) i ciekawe ekspozycje, w szczególności rekonstrukcje warsztatów rzemieślniczych, browaru i gorzelni. Pokaz ptaków drapieżnych używanych do polowań -- dla mnie taka sobie atrakcja, ale publiczność była liczna i zachwycona. Po zamku pora na zwiedzanie skansenu, który jest w pobliżu (można kupić bilet od razu uprawniający do wejścia do zamku i do skansenu.) Też miejsce warte zwiedzania, w przeciwieństwie do następnej atrakcji pn. średniowieczny obóz wojskowy (Stredoveký vojenský tábor -- w skrócie tabor), którą spokojnie można sobie odpuścić -- oszczędzając 2 EUR. Niewiele jest tu ciekawych rzeczy do oglądania; konkretnie mówiąc są cztery: replika wozu husyckiego (wóz taborowy) z okresu wojen husyckich i trzy rodzaje katapult. Nawet interesujące te katapulty, ale zwiedzanie wszystkiego zajęło nam góra 10 minut, czyli nie za dużo. Podobno jest to miejsce imprez plenerowych typu turnieje rycerskie czy łucznicze, ale my nie mieliśmy widocznie szczęścia bo nic takiego nie miało miejsca, a gdyby nawet to są to raczej atrakcje dla dzieci. Jest też `stylizowana karczma', pytanie tylko na co stylizowana? Wygląd nie zachęcał do wejścia.

Wieczorem pojechaliśmy na basen do Wyżnych Rużbachów (Vyšné Ružbachy). Kąpielisko termalne Izabela (chodzi o hrabiankę Zamojską BTW, która w pełnym brzmieniu nazywała się Izabela Alfonsina Maria Teresa Antonina Krystyna Mercedes Karolina Adelajda Rafaela de Burbon -- tutaj zwana w skrócie grófka Isabela) Basen otwarty był zamknięty, bo cośtam, ale czynny był ten całoroczny (czyli znajdujący się w budynku -- wejście pod recepcją hotelu Strand). Ja nie miałem slipów, to nie wszedłem, Elka z Jankiem pływali a ja poszedłem na kawę latte i ciasto do Hotelu (b. dobre i w przystępnej cenie). Elka wykąpała się też odważnie w słynnym (jeziorku kraterowym (trawertynowym) -- drugiej obok basenów termalnych atrakcji (ignorując zakaz kąpieli).

Drugiego dnia zwiedzanie okolicy: Czerwony klasztor (Červený kláštor), potem spacer wzdłuż Dunajca do kładki do Sromowic. Dunajcem nie płynęliśmy, podobno drogo i ciężko się dopchać takie kolejki. Po obiedzie zameczek w Strážkach koło Białej Spiskiej. Znajduje się tam kolekcja obrazów Ladislava Medňanskiego, co akurat dla mnie nie jest wielką atrakcją -- no nie znam się na malarstwie i zwiedzanie galerii zwykle mnie nudzi. Po Strážkach zwiedzanie miejscowości Kieżmark (Kežmarok)--zamek i spacer po starym mieście.

Zakwaterowani byliśmy komfortowo w czymś w rodzaju internatu (dzięki znajomej małżonki, na zaproszenie której zresztą pojechaliśmy). Mieliśmy do wyłącznej dyspozycji 5 pokoi, kuchnię z jadalnią i łazienkę. Dyrektorski apartament!

W związku z tym rower też nikomu nie przeszkadzał -- parkował w jadalni -- i dwa razy się przejechałem po okolicy. Żeby się nie alienować od reszty rodziny rowerowałem przed śniadaniem, tj. zaczynałem około 6 rano. Się okazało, że rano to tu całkiem zimno jest, np. drugiego dnia było 8C (pierwszego było ciut cieplej), a ja w krótkich spodniach, bez rękawic i czapki -- bo tak sobie wymyśliłem, że przecież jedziemy na południe. Ale dałem radę...

Budapeszt

W czwartek rano pojechaliśmy do Budapesztu, po drodze odwiedzając termalne baseny jaskiniowe w Miszkolcu-Tapolcy. Do Budapesztu dotarliśmy przed 19:00. Tym razem kwaterą było w miarę małe ale wygodne, czyste i nowocześnie wyposażone mieszkanie w bloku, przy ulicy Törökugrató (wyszukane przez AirBNB.) Ulica Törökugrató BTW to Buda a nie Peszt, co też się okazało szczęśliwe z punktu widzenia rowerowania. Buda bowiem jest górzysta, a ulica Törökugrató jest już poza centrum. Dało radę zatem całkiem serio pojeździć, co by w centrum zupełnie nieznanego miasta było raczej niemożliwe. W szczególności pierwszego dnia wjechałem na górkę pn Széchenyi-hegy, z nachyleniami sięgającymi 15%.

Część oficjalna obejmowała zwiedzanie tego co należy zwiedzać w Budapeszcie: dwa zamki, dwa kościoły (Stefana i Macieja), targ, operę, mosty, no i synagogę. W niedzielę przed siódmą rozpoczęliśmy odwrót, w domu byliśmy około 17:30. Warunki do jazdy były prawie że idealne--zero ruchu, tylko trochę popadało w okolicach Kujaw.

Zdjęcia z wycieczki są tutaj.