niedziela, 29 stycznia 2012

Trzecie ćwiczenie z Google Fusion Tables - sejm RP siódmej kadencji

Wprawdzie Sejm RP szybkimi krokami zmierza w stronę wybranego w 1936 r. Reichstagu określanego jako ,,najlepiej opłacany męski chór ma świecie'' (The best paid male chorus in the world -- określenie użyte podobno w amerykańskim czasopiśmie The Literary Digest) i być może już w kadencji ósmej ten stan ideału zostanie osiągnięty. Ale zanim to nastąpi zbierzmy dane dotyczące posłów wybranych do Sejmu 7 kadencji.

Zaczynam od ściągnięcia listy stron bibliograficznych posłów:


## Najpierw zestawienie posłów:
wget -U XX http://www.sejm.gov.pl/sejm7.nsf/poslowie.xsp?type=A -O lista-poslow.html
## Potem dla każdego posła z zestawiena:
cat lista-poslow.html | perl -e '
undef $/;
$_ = <>;
s/\n/ /g;

while ($_ =~ m@href="/sejm7.nsf/posel.xsp\?id=([0-9]+)\&amp;type=A@g ) {
$id = $1; ## id posła
$pos = "http://www.sejm.gov.pl//sejm7.nsf/posel.xsp?id=$id&amp;type=A";
print STDERR $pos, "\n";
system ('wget', '-U', 'XXX', "-O", "7_$id.html", $pos);
sleep 3; ## --let's be a little polite--
}

Powyższe pobiera 460 stron, każda zawierająca biografię jakiegoś posła.

Z kolei poniższy skrypt wydłubuje co trzeba (data scraping) ze strony zawierających biografię pojedynczego posła (np. tego).


#!/usr/bin/perl
#
use Storable;
use Google::GeoCoder::Smart;
$geo = Google::GeoCoder::Smart->new();

# Domyślny kraj
my $GeoCodeCacheName = 'geocode.cache';
my $NewCoordinatesFetched=0; # global flag
my $kraj = 'Polska';
my $SLEEP_TIME = 3 ;
# Rok wyborów
my $baseyr = 2011;
my ($data_day, $data_mc, $data_yr);

undef $/;
$_ = <>;
s/\n/ /g;

# Retrieve geocode cash (if exists)
# http://curiousprogrammer.wordpress.com/2011/05/11/faking-image-based-programming-in-perl/
my %hash = %{ retrieve("$GeoCodeCacheName") } if ( -f "$GeoCodeCacheName" ) ;

if (m@<h2>([^<>]+)</h2>@) { $posel = recode($1); }

# zgadnij płeć patrząc na imię (jeżeli kończy się na a->kobieta):
my @tmp_posel = split " ", $posel;
if ( $tmp_posel[0] =~ m/a$/ ) { $sex='K' } else { $sex='M' }

# id posła:
if (m@http://www.sejm.gov.pl/sejm7.nsf/posel.xsp\?id=([0-9]+)@) {$id = $1; }

# liczba oddanych na posła głosów:
if (m@<p class="left">Liczba g\&#322;os\&oacute;w:</p>[ \n\t]*<p class="right">([^<>]+)</p>@) {
$glosy = $1 ; }

# Data i miejsce urodzenia:</p><p class="right">29-06-1960<span>,&nbsp;</span>Gda&#324;sk</p>
if (m@Data i miejsce urodzenia:[ \n\t]*</p>[ \n\t]*<p class="right">([0-9\-]+)<span>,[ \n\t]*\&nbsp;[ \n\t]*</span>([^<>]+)</p>@ ) {
$data = recode ($1);
($data_day, $data_mc, $data_yr) = split "-", $data;
##print STDERR "R/M/D: $data_day, $data_mc, $data_yr\n";
$mce = recode ($2);
}

# Zawód:
if (m@<p class="left">Zaw\&oacute;d:</p>[ \nt]*<p class="right">([^<>]+)</p>@ ) { $zawod = recode($1) ; }

if (m@klub.xsp\?klub=([^"<>]+)@ ) { $klub = recode($1); }

# Klub poselski:
if (m@Okr\&#281;g wyborczy:</p>[ \t\n]*<p class="right">([^<>]+)</p>@) {
$okr = recode($1);
## Pierwszy wyraz to numer okręgu, reszta nazwa (może być wielowyrazowa)
($okrnr, $okrnz) = $okr =~ m/([^ \n\t]+)[ \n\t]+(.+)/; ## -- sprawdzić czy działa --
}

# Poprawienie błędów
if ($mce =~ /Ostrowiec Świetokrzyski/) {$mce = 'Ostrowiec Świętokrzyski' }
elsif ($mce =~ /Stargard Szaczeciński/) {$mce = 'Stargard Szczeciński' ; }
elsif ($mce =~ /Białograd/ ) {$mce = 'Białogard'; }
elsif ($mce=~ /Szwajcaria/) {$mce = 'Szwajcaria,Suwałki' }
elsif ($mce=~ /Kocierz Rydzwałdzki/) { $mce= "Kocierz Rychwałdzki" }
elsif ($mce=~ /Stąporów/) { $mce= "Stąporków" }
elsif ($mce=~ /Szmotuły/) { $mce= "Szamotuły" }

# Wyjątki:
if ($posel=~ /Arkady Fiedler/ ) {$kraj = 'Wielka Brytania' }
elsif ($posel=~ /Vincent-Rostowski/) {$kraj = 'Wielka Brytania' }
elsif ($mce=~ /Umuahia/) {$kraj = 'Nigeria' }
elsif ($posel=~ /Munyama/) {$kraj ='Zambia'; }
## Imię kończy się na `A' ale to facet:
elsif ($posel=~ /Kosma Złotowski/ ) {$sex = "M"}

# Sprawdź czy data jest zawsze w formacie dd-mm-yyyy:
if ($data_yr < 1900) { print STDERR "*** $data_yr: $data for $posel ($id)\n"; }

# geokodowanie (uwaga na limit)
my $coords = addr2coords($mce);
my $wiek = $baseyr - $data_yr;
my $coords_okr = addr2coords($okrnz, 'r'); ($tmp_lat, $tmp_lng) = split " ", $coords;

my $kml_line = "<LineString><coordinates>$tmp_lng,$tmp_lat $coords_okr</coordinates></LineString>";

print STDERR "$id : $sex : $posel : $wiek\n";
print "$id;$sex;$posel;$data;$wiek;$mce,$kraj;$coords;$klub;$glosy;$zawod;$okrnr;$okrnz;$kml_line\n";

## Retrieve geocode cash
if ($NewCoordinatesFetched) { store(\%hash, "$GeoCodeCacheName"); }

## ## ## ## ## ##
sub recode {
my $s = shift;

$s =~ s/\&nbsp;/ /g; $s =~ s/\&#322;/ł/g; $s =~ s/\&oacute;/ó/g;
$s =~ s/\&#324;/ń/g; $s =~ s/\&#321;/Ł/g; $s =~ s/\&#378;/ź/g;
$s =~ s/\&#380;/ż/g; $s =~ s/\&#346;/Ś/g; $s =~ s/\&#379;/Ż/g;
$s =~ s/\&#281;/ę/g; $s =~ s/\&#261;/ą/g; $s =~ s/\&#347;/ś/g;
$s =~ s/\&#263;/ć/g; $s =~ s/[ \t\n]+/ /g;

return $s;
}

## ## ## ## ## ##
sub addr2coords {
my $a = shift ;
my $r = shift || 'n';
my ($lat, $lng) ;

##consult cache first
if (exists $GeoCodeCache{"$a"} ) {
($lat,$lng) = split (" ", $GeoCodeCache{"$a"} );
}
else {
my ($resultnum, $error, @results, $returncontent) = $geo->geocode("address" => "$a");
$resultnum--;
$resultNo=$resultnum ;

if (resultNo > 0) { print STDERR "** Location $a occured more than once! **" }
if ($error eq 'OK') {
$NewCoordinatesFetched=1;
for $num(0 .. $resultnum) {
$lat = $results[$num]{geometry}{location}{lat};
$lng = $results[$num]{geometry}{location}{lng};
##print "*** LAT/LNG:$lat $lng ERROR: $error RES: $resultNo ***\n";
}
} else { print STDERR "** Location $a not found! due to $error **" }
}

$GeoCodeCache{"$a"} = "$lat $lng"; ## store in cache
sleep $SLEEP_TIME;

if ($r eq 'r' ) { return "$lng,$lat"; } # w formacie KML
else { return "$lat $lng"; }
}

Skrypt wydłubuje id posła (id), imię i nazwisko (imnz), datę urodzenia (data_ur), miejsce urodzenia (mce_ur), skrót nazwy partii do której należy (partia), liczbę oddanych na niego głosów (log), zawód (zawod), numer okręgu wyborczego (nrokregu) i nazwę okręgu (nzokregu). Do tego zgadywana jest płeć posłanki/posła oraz obliczany jest wiek w chwili wyboru jako $baseyr - $data_yr, gdzie $data_yr to rok urodzenia.

Miejsce urodzenia oraz nazwa okręgu są geokodowane. Współrzędne miejsca urodzenia są zapisywane jako zmienna wsp. Jako zmienna odleglosc zapisywana jest linia definiowana (w formacie KML) jako: współrzędne miejsce urodzenia--współrzędne okręgu wyborczego.


<LineString><coordinates>lng1,lat1 lng2,lat2</coordinates></LineString>

Zamiana kupy śmiecia (tj. wszystkich 460 plików HTML) na plik CSV zawierający wyżej opisane dane sprowadza się teraz do wykonania:


for i in 7_*html ; do perl extract.pl $i >> lista_poslow_7_kadencji.csv ; done

Jako ciekawostkę można zauważyć, że strony sejmowe zawierają 6 oczywistych błędów: Ostrowiec Świetokrzyski, Stargard Szaczeciński, Białograd, Kocierz Rydzwałdzki, Stąporów oraz Szmotuły. Ponadto wieś Szwajcaria k. Suwałk wymagała doprecyzowania. Czterech posłów urodziło się za granicą a pan Kosma jest mężczyzną i prosta reguła: jeżeli pierwsze imię kończy się na ,,a'' to poseł jest kobietą zawiodła.

Ostatecznie wynik konwersji wygląda jakoś tak (cały plik jest tutaj):


id;plec;imnz;dat_ur;wiek;mce_ur;wsp;partia;log;zawod;nrokregu;nzokregu;odleglosc
001;M;Adam Abramowicz;10-03-1961;50;Biała Podlaska,Polska;52.0324265 23.1164689;PiS;\
12708;przedsiębiorca;7;Chełm;<LineString><coordinates>23.1164689,52.0324265 23.4711986,51.1431232</coordinates></LineString>
...

Teraz importuję plik jako arkusz kalkulacyjny do GoogleDocs, a następnie, na podstawie tego arkusza tworzę dokument typu FusionTable. Po wizualizacji na mapie widać parę problemów -- ktoś się urodził niespodziewanie w USA a ktoś inny za Moskwą. Na szczęście takich omyłek jest tylko kilka...

Geocoder nie poradził sobie z miejscowościami: Model/prem. Pawlak, Jarosław/posłowie: Kulesza/Golba/Kasprzak, Orla/Eugeniusz Czykwin, Koło/Roman Kotliński, Lipnica/J. Borkowski, Tarnów/A. Grad. We wszystkich przypadkach odnajdywane były miejsca poza Polską -- przykładowo Orly/k Paryża zamiast Orli. Ponadto pani poseł Józefa Hrynkiewicz została prawidłowo odnaleziona w Daniuszewie ale się okazało, że ta miejscowość leży na Białorusi a nie w Polsce. Też ręcznie poprawiłem...

Ewidentnie moduł Google::GeoCoder::Smart ustawia geocoder w trybie partial match, w którym to trybie Google intelligently handles incomplete input aka stara się być mądrzejszym od pytającego. Może w trybie full match byłoby lepiej, ale to by wymagało doczytania. Na dziś, po prostu poprawiłem ręcznie te kilka ewidentnie błędnych przypadków.

Po poprawkach wszystko -- przynajmniej na pierwszy rzut -- oka wygląda OK

Link do tabeli jest zaś tutaj.

Nawiasem mówiąc kodowanie dokumentów na stronach www.sejm.gov.pl jest co najmniej dziwaczne.

sobota, 28 stycznia 2012

Korzystanie z GoogleMaps API z użyciem Google::GeoCoder::Smart

Geokodowanie to zamiana adresu lub nazwy miejsca na parę współrzędnych. Perlowy moduł Google::GeoCoder::Smart wykorzystany w poniższym skrypcie używa geolokalizatora Google'a:

#!/usr/bin/perl
use Google::GeoCoder::Smart;

my %GeoCodeCache; # cache

$geo = Google::GeoCoder::Smart->new();

$location = $ARGV[0];

my $coords = addr2coords( $location );

## ## ## ## ## ##
sub addr2coords {
my $a = shift ; ## address for example "Sopot,Polska"
my $r = shift || 'n'; ## flag--order of coordinates lat/lng or lng/lat
my ($lat, $lng) ;

## ## consult cache first ; $GeoCodeCache is a global hash ## ##
if (exists $GeoCodeCache{"$a"} ) { ($lat,$lng) = split (" ", $GeoCodeCache{"$a"} );  }
else {

my ($resultnum, $error, @results, $returncontent) = $geo->geocode("address" => "$a");
$resultnum--;

if ($resultnum > 0) { print STDERR "** Location $a occured more than once! **" }

if ( $error eq 'OK' ) {
for $num(0 .. $resultnum) {
$lat = $results[$num]{geometry}{location}{lat};
$lng = $results[$num]{geometry}{location}{lng};
##print "*** LAT/LNG:$lat $lng ERROR: $error RES: $resultNo ***\n";
}
} else { print STDERR "** Location $a not found! due to $error **"  }
}

$GeoCodeCache{"$a"} = "$lat $lng"; ## store in cache
##sleep $SLEEP_TIME;

if ($r eq 'r' ) { return "$lng,$lat"; } # KML order lng/lat
else { return "$lat $lng"; ## GPX order lat/lng }
}

Jest limit 2500 żądań/dzień (24 godziny, przy czym nie jest dokładnie opisane kiedy następuje `reset' licznika, tj. rozpoczyna się następna doba). Jeżeli się przekroczy limit to:

perl ./coordinates.pl Wrocław
** Location Wrocław not found! due to OVER_QUERY_LIMIT **** 

Ponieważ w bibliotekach Perla jest wszystko są także moduły Geo::Coder::ManyGeo::Coder::Multiple, który potrafią korzystać z wielu Geokoderów na raz (Google, Yahoo, Bing), zwiększając w ten sposób dzienny limit. Nie używałem...

Dopisane 29 stycznia 2012: W sieci via Google można znaleźć informacje, że reset ma miejsce ,,at midnight 12:00 PST.'' Ale w tym przypadku coś nie bardzo się zgadza, bo exact midnight PST byłoby o 9:00 rano (8:00 GMT), a blokę na mój IP zdjęli około 16.00. (A kwota wyczerpała się o jakieś 18--19 dnia poprzedniego--dokładnie nie pamiętam.)

czwartek, 26 stycznia 2012

MathJax: math in HTML documents

To process inline math formulas delimited by single ($) or (\() sequence one have to add the following into HTML document preamble:


<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ['\\(','\\)'] ],
processEscapes: true
}
});
</script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=Accessible" type="text/javascript">
</script>

Now ``for all \$n > 2\$ there not exist integeres \$x\$, \$y\$, \$z\$ that \$ x^n + y^n = z^n\$'' will be rendered as: ``for all $n > 2$ there not exist integeres $x$, $y$, $z$ that $ x^n + y^n = z^n$''. (Cf. Fermat's Last Theorem.)

To insert verbatim $ use \$ (processEscapes: true). Single dollar seems to be ignored, only pairs of dollars are processed---for example \$.

.

poniedziałek, 23 stycznia 2012

Bidirectional synchronisation with rsync

I would like to copy Elka's home directory (/home/eros/) from the laptop (asteroid) to the server (jupiter) and back from jupiter to asteroid as well as from/to jupiter to/from PC machine (darkstar). So the problem is to sync bidirectionally /home/eros/ on three machines.

On darkstar I run the following script (and similar one on asteroid):


#!/bin/bash
# http://wiki.archlinux.org/index.php/Full_System_Backup_with_rsync
#
SOURCEDIR=/home/eros/
DESTDIR=root@jupiter:/public/DELL/backup/rootfs/home/eros

INCLUDED=' --include="/.emacs-local/" --include="/.ssh/" --include="/.emacs" --include="/.bash_profile" --include="/.bashrc" '
EXCLUDED=' --exclude="/.*"'

rsync -av -e ssh $INCLUDED $EXCLUDED ${SOURCEDIR} ${DESTDIR}

# the other way round now
# Cf. http://faculty.wiu.edu/CB-Dilger/s07/rsync-howto.shtml
# Extra slash appended to ${DESTDIR}/ is of uttermost importance!

rsync -av -e ssh $INCLUDED $EXCLUDED ${DESTDIR}/ ${SOURCEDIR}

Adding an exclude switch, --exclude="/.*" excludes all top-level hidden files/directories. Those few which should not be excluded are specified with series of --include switches. Order is important---first --include switches then --exclude switches.

niedziela, 22 stycznia 2012

Making Fedora 15 installation work

Due to disc failure I had to install Fedora for the second time. As a third attempt is very likely in the near future, below short recipe (in the form of bash script with manual interventions included as comments), how to fix fresh installed Fedora 15.


#!/bin/bash
#
# ** Manual fix (see other manual fixes below) **
# /etc/profile.d/local_profile.sh
# /etc/hosts
# Fix udev rules to allow non-root access to GPS device for GPSbabel
# /etc/udev/rules.d/51-garmin.rules
#
yum install yum-plugin-fastestmirror
rpm -ivh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm
rpm -ivh http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm
#
# Update the whole system
yum update
#
yum install wget fuse-sshfs system-config-printer curl fdupes rtorrent wammu svn git
yum install xmms xmms-mp3 xmms-faad2 xmms-pulse xmms-skins
yum install audacious audacious-plugins-freeworld*
yum install rhythmbox gstreamer-plugins-ugly gstreamer-plugins-bad gstreamer-ffmpeg
yum install mplayer mplayer-gui gecko-mediaplayer mencoder
yum install xine xine-lib-extras xine-lib-extras-freeworld
yum install vlc
yum install gphoto2 gvfs-gphoto2 gtkam digikam gthumb geeqie gpsbabel cheese

# ** Manual fix **
# MS Fonts (personally don't use them / perhaps needed by some appilations)
# Cf. http://www.mjmwired.net/resources/files/msttcore-fonts-2.0-3.noarch.rpm
# wget http://www.mjmwired.net/resources/files/msttcore-fonts-2.0-3.noarch.rpm
# rpm -ivh msttcore-fonts-2.0-3.noarch.rpm

# 32bits Adobe Flash plugin
rpm -ivh http://linuxdownload.adobe.com/adobe-release/adobe-release-i386-1.0-1.noarch.rpm
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-adobe-linux
yum install flash-plugin

#
yum install rubygems rubygem-rake
yum install firefox thunderbird mozilla-adblockplus
yum install emacs emacs-git emacs-git-el emacs-magit emacs-magit-el emacs-common-ess emacs-ess* emacs-auctex*
yum install gimp inkscape sane dia k3b grip xpdf
yum install googlecl html2ps xhtml2ps
yum install ImageMagick
yum install gnuplot graphviz*
yum install gretl R weka
# Stix package provides Times New Roman like fonts
yum install stix-*
yum install soprano
yum install ffmpeg

# Gnome3 is unusable for me:
yum -y groupinstall XFCE

# groupinstall "Office/Productivity" is a shortcut to install LibreOffice (former OpenOffice)
yum groupinstall "Office/Productivity"
# Small utilities to process jpeg files:
yum install jhead
# Epub reader:
yum install fbreader

# Install TeXlive just to fulfill dependencies (I install recent TeXlive for everday use)
yum install texlive*
# XML related tools (fop installs xercesa/xalan as dependent packages)
yum fop trang jing

# Perl related stuff
# Warning: perl-Test* returns error...
yum install perl-Authen-* perl-Crypt* perl-DBD* perl-Data* perl-DateTime* perl-Digest* \
perl-File* perl-Flickr* perl-GD* perl-GDGraph* perl-Geo* perl-HTML* perl-IO* \
perl-Net* perl-PerlIO* perl-String* \
perl-Text* perl-XML* perl-YAML* \
perl-libwww*

# Private scripts (details omitted for obvious reason)
SOURCE_DIR="/backup/system_kopia"
cp -R $SOURCE_DIR/usr/local/share/emacs /usr/local/share
## etc ...

# gpsd interferes with GPSbabel
yum erase gpsd
## the above removes following dependencies (strange, but unharmful):
# kdeutils-libs-4.6.5-2.fc15.i686 kdeutils-4.6.5-2.fc15.i686
# kde-settings-kdm-4.6-10.fc15.noarch kdm-4.6.5-2.fc15.i686
# digikam-1.9.0-2.fc15.i686 digikam-libs-1.9.0-2.fc15.i686
# kdeedu-marble-4.6.5-1.fc15.i686 kdeedu-marble-libs-4.6.5-1.fc15.i686
# kdeplasma-addons-4.6.5-1.fc15.i686
# kdeplasma-addons-libs-4.6.5-1.fc15.i686 ktorrent-4.1.1-1.fc15.i686
# ktorrent-libs-4.1.1-1.fc15.i686 kpackagekit-0.6.3.3-2.fc15.i686
# kde-plasma-networkmanagement-vpnc-0.9-0.53.20110616git.nm09.fc15.i686
# kde-plasma-networkmanagement-openvpn-0.9-0.53.20110616git.nm09.fc15.i686
# plasma-scriptengine-python-4.6.5-2.fc15.i686
# kdenetwork-4.6.5-1.fc15.i686 kdenetwork-libs-4.6.5-1.fc15.i686
# kde-plasma-networkmanagement-pptp-0.9-0.53.20110616git.nm09.fc15.i686
# kde-plasma-networkmanagement-0.9-0.53.20110616git.nm09.fc15.i686
# kde-plasma-networkmanagement-libs-0.9-0.53.20110616git.nm09.fc15.i686
# kdebase-workspace-4.6.5-2.fc15.i686
# kdebase-workspace-libs-4.6.5-2.fc15.i686 gpsd-2.95-6.fc15.i686
## and disables the following udev rules:
# ostrzezenie: /etc/udev/rules.d/99-gpsd.rules zapisano jako
# /etc/udev/rules.d/99-gpsd.rules.rpmsave

# ** Manual fix **
# ** Perl
# perl -MCPAN -e shell
# install Geo::Distance
# install Geo::Coordinates::DecimalDegrees
#
# ** Google Earth
yum install redhat-lsb redhat-lsb-graphics
# download google-earth-stable_current_i386.rpm from Google
# rpm -Uvf google-earth-stable_current_i386.rpm
#
# ** Printer (HP6P):
# ./system-config-printer
# set driver for HP LaserJet 6P = Foomatic/ljet4 (recommended one do not work)
#
# ** Vim configuration:
# ~/.vimrc
# syntax off
#
# ** Turn on sound
# Programy->Multimedia->Mikser dzwieku
# Playback: wewnetrzny dzwiek analogowe stereo (PulseAudio Mixer)
#
# ** Thunderbird
# Compose mails in plain text (completely plain emails)
# Edit -> Preferences -> Advance-> Config Editor
# mail.html_compose = false
# mail.identity.default.compose_html = false
#
# Plugins to install: Easy YouTube Video Downloader
#
# ** TeX
# Install from http://www.tug.org/texlive/
# ln -s /usr/local/texlive/2011/bin/i386-linux/ /opt/tex/bin
# add /opt/tex/bin to /etc/profile.d/local_profile.sh
#
# ** Firefox
# Plugins to install: https://addons.mozilla.org/en-US/firefox/addon/addthis/
#
# ** Xine
# Set default window size
# ~/.xine/xinerc
# -G990x650
#

A few more strictly personal adjustments were omitted.

środa, 11 stycznia 2012

Drugie ćwiczenie z Google Fusion Tables - mapa zatopionych Ubootów

Klasyczny zbiór danych pn. niemieckie łodzie podwodne z 2WWŚ wg. typu, liczby patroli, zatopionych statków i okrętów oraz -- dodane dziś -- miejsca zatopienia (źródło: www.uboat.net). W zbiorze są 1152 okręty (pomięto 14, które były eksploatowane przez Kriegsmarine ale nie były konstrukcjami niemieckimi), z tego 778 ma określoną pozycję, na której zostały zatopione (z czego kilkadziesiąt w ramach operacji DeadLight). Można zatem powiedzieć, że dane dotyczące miejsca zatopienia/zniszczenia są w miarę kompletne, bo np. Kemp (1997) podaje liczbę 784 U-bootów zniszczonych w czasie 2WWŚ (dane są różne w zależności od źródła.)

Jeżeli U-boot zaginął/przepadł bez wieści/został zniszczony, ale nie wiadomo dokładnie gdzie, to nie występuje na mapie. Operacja DeadLight to ta wielka kupa kropek ,,nad'' Irlandią....

Link do danych/mapy na serwerze Google jest tutaj.

Dane były tylko zgrubie weryfikowane, więc mogą zawierać błędy (ale ,,na oko'' jest ich niewiele)...

Literatura

Kemp Paul, U-Boats Destroyed: German Submarine Losses in World Wars, US Naval Institute Press, 1997 (isbn: 1557508593)

poniedziałek, 9 stycznia 2012

Pierwsze próby z Google Fusion Tables

Pod tym adresem umieściłem moje ślady GPS (głównie rowerowe) z lat 2010--2012. W formacie KML bo to jedyny -- z tego co mi się wydaje -- obsługiwany przez Google Fusion Tables (GFT).

Moja procedura skopiowania danych z urządzenia GPS (zwykle jest to Garmin Legend) do GFT jest następująca:

  1. Skryptem obsługującym program gpsbabel pobieram dane z urządzenia GPS do pliku w formacie GPX.

  2. Skryptem kml2kml.sh zamieniam plik GPX otrzymany w pierwszym kroku na plik w formacie KML. Ponieważ plik ten jest ogromy (przykładowo z 60 kilowego pliku GPX gpsbabel wygenerował plik o wielkości ponad 500 kb) zostaje on za pomocą trywialnego arkusza XSLT zamieniony na znacznie mniejszą wersję uproszczoną:


    #!/bin/bash
    #
    KMLSTYLE=~/share/xml/kml2kml.xsl
    #
    # Domyslna liczba punktow na sladzie
    COUNT=99
    ## Domyslna nazwa Placemarka
    NAME=`date +%Y%m%d_%H`;

    while test $# -gt 0; do
    case "$1" in

    -name) shift; NAME="$1";;
    -name*) NAME="`echo :$1 | sed 's/^:-name//'`";;

    -max) shift; COUNT="$1";;
    -max*) COUNT="`echo :$1 | sed 's/^:-max//'`";;
    *) FILE="$1";;
    esac
    shift
    done

    ## Usun rozszerzenie z nazwy pliku wejsciowego
    OUT_FILE="${FILE%.*}"

    TMP_FILE=/tmp/${OUT_FILE}.kml.tmp

    echo "Converting $FILE to ${TMP_FILE}..."

    if [ -f $FILE ] ; then
    ## Konwersja GPX->KML
    gpsbabel -i gpx -f $FILE -x simplify,count=$COUNT -o kml -F $TMP_FILE
    else
    echo "*** ERROR *** File $FILE not found.... ***" ; exit
    fi

    ## Konwersja do uproszczonego KMLa (KML->KML)
    echo "Converting $TMP_FILE to ${OUT_FILE}.kml with maximum $COUNT points..."

    echo "KML' Placemark name is: $NAME..."
    xsltproc --stringparam FileName "$NAME" -o ${OUT_FILE}.kml $KMLSTYLE $TMP_FILE

    echo "Done..."

    Zawartość arkusza XSLT zamieszczono na końcu tej notki.

  3. Do interakcji z Fusion Tables wykorzystam zmodyfikowany skrypt (nazwany ftquery.sh) znaleziony na stronach code.google.com:


    #!/bin/bash
    #
    # Copyright 2011 Google Inc. All Rights Reserved.
    # Author: arl@google.com (Anno Langen)
    # http://code.google.com/intl/pl/apis/fusiontables/docs/samples/curl.html
    # -- Modified by TP --
    MY_QUERY=$*

    function ClientLogin() {
    password='?1234567890?'
    email='looseheadprop1@gmail.com'
    local service=$1
    curl -s -d Email=$email -d Passwd=$password -d \
    service=$service https://www.google.com/accounts/ClientLogin | tr ' ' \n | grep Auth= | sed -e 's/Auth=//'
    }

    function FusionTableQuery() {
    local sql=$1
    curl -L -s -H "Authorization: GoogleLogin auth=$(ClientLogin fusiontables)" \
    --data-urlencode sql="$sql" https://www.google.com/fusiontables/api/query
    }

    FusionTableQuery "$MY_QUERY"
  4. Teraz tworzę nową tabelę (wyklikowując co trzeba w przeglądarce) MyTracks o następującej strukturze:


    ## Korzystamy z skryptu ftquery.sh
    ./ftquery.sh SHOW TABLES
    table id,name
    2590817,MyTracks
    ## Tabela MyTracks ma zatem id=2590817
    ./ftquery.sh DESCRIBE 2590817
    column id,name,type
    col4,Date,string
    col0,Start,string
    col1,Stop,string
    col2,Location,location
    col3,Description,string

    Kolumny zawierają odpowiednio: datę (Date), czas pierwszego wpisu na śladzie GPX (Start), czas ostatniego wpisu na śladzie GPX (Stop), ślad (Location) oraz opis (Description).

    Kolumny Tabeli w Google Fusin Tables mogą być albo napisami (STRING), liczbami (NUMBER), zawierać dane przestrzenne (LOCATION) albo czas (DATETIME). Wstępne eksperymenty z typem DATETIME wskazują, że jest z nim jakiś problem. Primo format czasu jest dość dziwaczny (np. gpsbabelowy zapis: 2012-01-07T09:19:47Z nie jest rozpoznawany). Także późniejsze filtrowanie w oparciu o kolumnę DATETIME też jakoś nie wychodzi, nawet jak zapisałem datę w formacie YYY.MM.DD, który wg. dokumentacji powinien być rozpoznawany. Nie badając sprawy dogłębnie, zmieniłem po prostu typ kolumn Date, Start, Stop na STRING.

  5. Cytując za dokumentacją GFT: In a column of type LOCATION, the value can be a string containing an address, city name, country name, or latitude/longitude pair. The string can also use KML code to specify a point, line, or polygon... (cf. SQL API: Reference ). Zatem żeby wstawić linię śladu wystarczy przesłać napis zawierający współrzędne opakowane w następujący sposób:


    <lineString><coordinates>lng,lat[,alt] lng,lat[,alt]...<coordinates></lineString>

    Do tego służy następujący skrypt:


    #!/bin/bash
    #
    STYLE=~/share/xml/gpxtimestamp.xsl
    MYTRACKS_TABLE_ID="2590817"
    GPX_FILE=$1
    KML_FILE="${GPX_FILE%.*}".kml

    ## Ustal czas od--do sladu
    STARTTIME=`xsltproc --param Position '"First"' $STYLE $GPX_FILE`
    STOPTIME=`xsltproc --param Position '"Last"' $STYLE $GPX_FILE`
    DATE=`xsltproc --param Position '"First"' --param Mode '"Date"' $STYLE $GPX_FILE`

    if [ ! -f $GPX_FILE ] ; then echo "*** Error *** No GPX file: $GPX_FILE" ; exit ; fi
    if [ ! -f $KML_FILE ] ; then echo "*** Error *** No KML file: $KML_FILE" ; exit ; fi

    ## ## Wycinamy nastepujacym skryptem z uproszczonego pliku KML:
    TRACK=`perl -e 'undef $/; $t = <>; $t =~ m/(<coordinates>.*<\/coordinates>)/s; \
    $t = $1; $t =~ s/[ \t\n]+/ /gm; print $t;' $KML_FILE`
    TRACK="<LineString>$TRACK</LineString>"

    ftquery.sh "INSERT INTO $MYTRACKS_TABLE_ID (Date, Start, Stop, Location, Description)
    VALUES ('$DATE', '$STARTTIME', '$STOPTIME', '$TRACK', '') "

    I działa, tj. przesyła co trzeba na moje konto Google.

Google Fusion Table map view in Chrome
GFT map view in Chrome

Google się chwali, że GFT radzi sobie z ogromnymi zbiorami danych. Mój ma -- na tem chwilem -- ponad 150 wierszy, a w każdym 99 punktów na śladzie, co daje łącznie approx 15,000 punktów do wykreślenia. Firefox/Opera coś tam wyświetla ale niewiele widać. Chrome radzi sobie najlepiej -- faktycznie da się to obejrzeć, przesuwać, powiększać... Wprawdzie nic z tych danych nie wynika, ale to już nie jest pytanie do Google.

Także filtr w połączeniu z mapą działa w Firefoksie i Operze tak sobie. Wybieram zbiór tras, np. za pomocą warunku Date < 20100901 (na tem chwilem jest to 7 wierszy) następnie klikam Visualise Map i guzik -- nic nie widać. Ale znowu w Chrome jest znacznie lepiej. Dowód w postaci zrzutu ekranu obok. Także wklejony poniżej link do mapy (pobrany przez kliknięcie w guzik Get embeddable link) daje pozytywny wynik:

Przynajmniej w mojej wersji FF (8.0).

Arkusze XSLT wykorzystane w skryptach

Zamiana pliku KML na uproszczony plik KML:


<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:kml="http://www.opengis.net/kml/2.2" >

<xsl:output method="xml"/>
<xsl:param name='FileName' select="'??????'"/>

<xsl:template match="/">

<kml xmlns="http://www.opengis.net/kml/2.2"
xmlns:gx="http://www.google.com/kml/ext/2.2">
<Document>
<Placemark>

<name><xsl:value-of select="$FileName"/></name>

<MultiGeometry><LineString>
<tessellate>1</tessellate>
<coordinates>

<xsl:for-each select="//kml:Placemark//kml:LineString">

<xsl:value-of select="kml:coordinates"/>

</xsl:for-each>

</coordinates></LineString></MultiGeometry></Placemark>
</Document></kml>

</xsl:template>
</xsl:stylesheet>

Wydrukowanie daty/czasu z pliku w formacie GPX:


<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:gpx="http://www.topografix.com/GPX/1/0">

<xsl:output method="text"/>
<xsl:param name='Position' select="'First'"/>
<xsl:param name='Mode' select="'Time'"/>

<xsl:template match="/" >

<xsl:for-each select='//*[local-name()="trkpt"]'>

<xsl:choose>
<xsl:when test="position()=1 and $Position='First' and $Mode='Time'">
<xsl:value-of select='substring(*[local-name()="time"], 12, 8)'/>
<xsl:text> </xsl:text>
</xsl:when>
<xsl:when test="position()=1 and $Position='First' and $Mode='Date'">
<xsl:value-of select='translate(substring(*[local-name()="time"], 1, 10), "-", "")'/>
<xsl:text> </xsl:text>
</xsl:when>
<xsl:when test="position()=last() and $Position='Last'">
<xsl:value-of select='substring(*[local-name()="time"], 12, 8)'/>
<xsl:text> </xsl:text>
</xsl:when>
</xsl:choose>

</xsl:for-each>

</xsl:template>
</xsl:stylesheet>

Dokumentacja

  1. SQL API: Developer's Guide.

  2. SQL API: Reference .

  3. Google fusion tables cheat sheet.

SELinux clashes with Google Chrome

Cannot start Google Chrome:


$ google-chrome
/opt/google/chrome/chrome: error while loading shared libraries: cannot restore segment prot after reloc: Permission denied

SE Linux is supposed to cause the problem. The following solution is suggested here and works:


# ## execute as root the following two magic commands:
semanage fcontext -a -s system_u -t usr_t /opt/google/chrome/chrome-sandbox
restorecon -v /opt/google/chrome/chrome-sandbox

niedziela, 8 stycznia 2012

Ćwiczenie z XSLT

Zadanie polega na wydłubaniu z pliku GPX pierwszego i ostatniego elementu <time>, który jest dzieckiem elementu <trkpt>. (Nie może być po prostu <time>, bo element ten występuje także w innym kontekście.)

Coś takiego wymyśliłem:


<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:gpx="http://www.topografix.com/GPX/1/0">

<xsl:output method="text"/>

<xsl:param name='Position' select="'First'"/>

<xsl:template match="/" >

<xsl:for-each select='//gpx:trkpt'>

<xsl:if test="position()=1 and $Position='First'">
<xsl:value-of select='gpx:time'/>
<xsl:text>&#10;</xsl:text>
</xsl:if>

<xsl:if test="position()=last() and $Position='Last'">
<xsl:value-of select='gpx:time'/>
<xsl:text>&#10;</xsl:text>
</xsl:if>

</xsl:for-each>

</xsl:template>

</xsl:stylesheet>

W praniu się okazało, że jest jeden drobny, komplikujący życie feler-nie-feler. Struktura plików GPX (w zależności od tego czym są one generowane) jest identyfikowana albo z przestrzenią nazw http://www.topografix.com/GPX/1/0 albo z http://www.topografix.com/GPX/1/1. Praktycznie żadna różnica, ale szablon przestaje działać. Aby uodpornić szablon na wersje formatu można zamienić:


<xsl:for-each select='//gpx:trkpt'>

na


<xsl:for-each select='//*[local-name()="trkpt"]'>

Teraz szablon `reaguje' na element <trkpt> z dowolnej przestrzeni nazw. Oczywiście reguła wyboru węzłów stała się teraz `mniej specyficzna', co w przypadku bardziej skomplikowanych transformacji może nie być najlepszym pomysłem, ale w przypadku tak prostego schematu jak GPX i banalnej transformacji, jak powyższa nie powinno być problemów.

Podobnie można zamienić:


<xsl:value-of select='gpx:time'/>

na:


<xsl:value-of select='*[local-name()="time"]'/>

Teraz, przykładowo uruchomienie xsltproc (opisany wyżej szablon jest zawartością pliku gpxtimestamp.xsl):


xsltproc --param Position '"First"' gpxtimestamp.xsl 20120107.xml
xsltproc --param Position '"Last"' gpxtimestamp.xsl 20120107.xml

Wypisuje odpowiednio zawartość pierwszego i ostatniego elementu <time>.