Wednesday, October 17, 2007

howto: travel maps


Who needs things like GPS-synchronized cameras and geo-tagging of photos in google maps when one can use such modern tools like gnuplot? So here's a way to create maps with points corresponding to places you've been to for holidays or whatever. Let's say you start with a file called 'travel_list' that looks like this:

#date place latitude longitude
06/04 Berlin 52.31.00 N 13.25.00 E
07/04 Strasbourg 48.35.04 N 07.44.55 E

08/05 Muenchen 48.08.00 N 11.34.00 E

07/05 Tokyo 35.41.00 N 139.46.00 E
10/05 Freiburg 48.00.00 N 07.51.00 E

11/06 Dublin 53.20.33 N 06.15.57 W

12/06 Sao_Paulo 23.30.00 S 46.37.00 W

02/06 Grenoble 45.11.24 N 05.43.12 E
03/06 Montreal 45.30.29 N 73.33.18 W

06/07 Vancouver 49.16.00 N 123.08.00 W

07/07 Vina_del_Mar 33.03.00 S 71.32.30 W

08/07 Sydney 33.52.06 S 151.12.31 E

i.e. date (not too relevant here...), place, latitude and longitude in the stupid degree-minute-second-direction format. First, you need to convert that to something useful, using for example the beautiful filter script:

#!/usr/bin/perl -w
use Math::Trig;

print "#date\tlat.\tlong.\tplace\ttime\tdist\n";
$first = 1;

$fact = 3.14159/180;
$R = 6371;

$latitude_ref = sprintf("%10.4f", 50);
$longitude_ref = sprintf("%10.4f", 8.2711);
$place_ref = "Mainz";

$month_prev = -1;
$day = 2;

while (<>) {
if (m/^#/) {
print;
next;
}
@tab = split, /\s+/;
$date=$tab[0];
$place=$tab[1];
$latitude=$tab[2]." ".$tab[3];
$longitude=$tab[4]." ".$tab[5];

$date =~ s#([0-9]{2})/([0-9]{2})#$2/01/$1#;
$month=$2;
$year=$1;


if ($month == $month_prev) {
$day += 7;
} else {
$day = 2;
}

$latitude =~ s#([0-9]+)\.([0-9]+).([0-9]+)\s+([A-Z])#$dec=sprintf("%5.4f", $2/60+$3/3600); $s=1; if ("$4" eq "S" || "$4" eq "s") {$s=-1;} $l = ($1 + $dec) * $s; $l="$l";#e;

$latitude = sprintf("%10.4f", $latitude);

$longitude =~ s#([0-9]+)\.([0-9]+).([0-9]+)\s+([A-Z])#$dec=sprintf("%5.4f", $2/60+$3/3600); $s=1; if ("$4" eq "W" || "$4" eq "w") {$s=-1;} $l = ($1 + $dec) * $s; $l="$l";#e;

$longitude = sprintf("%10.4f", $longitude);

$time = $year*365 + ($month-1)*30 + $day;

if ($first == 1) {
$day_ref = $day - 1;
$date_ref = sprintf("%02d/%02d/%02d", $month,$day_ref,$year);
$time_ref = $year*365 + ($month-1)*30 + $day_ref;
print "$date_ref\t$latitude_ref\t$longitude_ref\t$place_ref\t$time_ref\t0\n";
$first = 0;
}

$cos_alpha = cos($fact*(90. - $latitude))*cos($fact*(90. - $latitude_ref)) + sin($fact*(90. - $latitude))*sin($fact*(90. - $latitude_ref))*cos($fact*($longitude_ref - $longitude));

$dist = $R*acos($cos_alpha);


print "$date\t$latitude\t$longitude\t$place\t$time\t$dist\n";

$day_nxt = $day + 1;
$date_nxt = sprintf("%02d/%02d/%02d", $month,$day_nxt,$year);
$time_nxt = $year*365 + ($month-1)*30 + $day_nxt;
print "$date_nxt\t$latitude_ref\t$longitude_ref\t$place_ref\t$time_nxt\t0\n";
}


(The script basically reformats the latitude and longitude to the more usable signed decimal format, but also does a couple of other not-too-important things, like adding a reference point before/after each travel (Mainz here, nobody's perfect...), and modify the date format. It also calculates an estimate of the distance (in km) between the destination and the reference point.)
Then the command
./filter travel_list > travel_list_fmt
yields the following, reformatted data:
#date lat long place time dist
04/01/06 50.0000 8.2711 Mainz 2281 0

04/01/06 52.5167 13.4167 Berlin 2282 454.262703608682

04/03/06 50.0000 8.2711 Mainz 2283 0
04/01/07 48.5844 7.7486 Strasbourg 2647 161.902911382215

04/03/07 50.0000 8.2711 Mainz 2648 0

05/01/08 48.1333 11.5667 Muenchen 3042 317.323904751761

05/03/08 50.0000 8.2711 Mainz 3043 0

05/01/07 35.6833 139.7667 Tokyo 2677 9363.54420110419

05/03/07 50.0000 8.2711 Mainz 2678 0
05/01/10 48.0000 7.8500 Freiburg 3772 224.50025608341

05/03/10 50.0000 8.2711 Mainz 3773 0

06/01/11 53.3425 -6.2658 Dublin 4167 1066.81746017645

06/03/11 50.0000 8.2711 Mainz 4168 0
06/01/12 -23.5000 -46.6167 Sao_Paulo 4532 9793.4646868345

06/03/12 50.0000 8.2711 Mainz 4533 0

06/01/02 45.1900 5.7200 Grenoble 882 567.938242585956

06/03/02 50.0000 8.2711 Mainz 883 0
06/01/03 45.5081 -73.5550 Montreal 1247 5823.71530042123

06/03/03 50.0000 8.2711 Mainz 1248 0

07/01/06 49.2667 -123.1333 Vancouver 2372 8045.84376783844

07/03/06 50.0000 8.2711 Mainz 2373 0

07/01/07 -33.0500 -71.5417 Vina_del_Mar 2737 12099.4845691498

07/03/07 50.0000 8.2711 Mainz 2738 0
07/01/08 -33.8683 151.2086 Sydney 3102 16514.4120114015

07/03/08 50.0000 8.2711 Mainz 3103 0


Then the only thing to do is to put good old gnuplot to use, with a complicated script like travel_map.gnu

#!/usr/bin/gnuplot -persist
set title "Travel map"
unset key

unset border

unset xtics

unset ytics

pl 'w2.dat' t '' w l lt 3
repl 'travel_list_fmt' u 3:2 t '' w lp lt 1 pt 5


set output 'travel_map.eps'

set terminal postscript eps enhanced color solid 20

repl

set terminal X11

Here's the resulting picture (./travel_map.gnu):


A variant: Same thing on a sphere (travel_map_sphere.gnu, picture at the top).

#!/usr/bin/gnuplot -persist
set angles degrees
set title "Travel map"

set ticslevel 0


set view 50,95,1.2,1.2

set mapping spherical

set parametric

set samples 32

set isosamples 9

set urange [-90:90]

set vrange [0:360]

set noxtics

set noytics

set noztics

set border 0

unset key

spl cos(u)*cos(v),cos(u)*sin(v),sin(u) with lines lt 0

repl 'w2.dat' w l lt 3

repl 'travel_list_fmt' u 3:2 w p lt 1 pt 7


pause -1

set output 'travel_map_globe.eps'

set terminal postscript eps
enhanced color solid 20

repl

set terminal X11

Beautiful, huh?

(Inspiration: gnuplot demo)

1 comments:

pimbert said...

Exactly how do you get the day of the month as output if it wasn't in the input? :)