#!/usr/bin/perl
#
# Script available under do-whatever-you-want-with-it license,
# sometimes known as 'public domain license'
#
use strict;
use POSIX;
use XML::Parser;
use GD;

# Latitude and longitude of target rectangle
my $lat_min = 48.54;
my $lat_max = 51.07;
my $lon_min = 12.08;
my $lon_max = 18.87;

my $lat_d = $lat_max-$lat_min;
my $lon_d = $lon_max-$lon_min;

# Width of target image
my $rx=1600;
# Autocompte height based on width and min/max latitude/longitude
my $ry=ceil(($lat_d/$lon_d)*$rx*1.5);
#Actual dimensions may be slightly larger due to borders, etc ...

# Print basic info
print STDERR "Map size: $lon_d x $lat_d deg\n";
print STDERR "Map size: $rx x $ry px\n";

# Hash, keys are node ID's values are node coordinates
my %node=();

# Minimal time seen in the file (unix timestamp)
my $maxt=0;
# Maximal time seen in the file (unix timestamp)
my $mint=0xffffffff;

# Hash, key is node coordinate, values are newest timestamp of item at those coordinates
my %maxts_node=();
# Hash, key is way coordinate, values are newest timestamp of item at those coordinates
my %maxts_way=();
# Hash, key is way/node coordinate, values are newest timestamp of item at those coordinates
my %maxts=();

# Process node
# id - ID of node
# x,y - unprocessed node coordinates
# t - unprocessed timestamp
sub pt {
 my ($id,$x,$y,$t)=@_;
 if ($t=~/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/) {
  $t=mktime($6,$5,$4,$3,$2-1,$1-1900,0,0);  
  if ($t>$maxt) {$maxt=$t;}
  if ($t<$mint) {$mint=$t;}
  my $nx=floor(($x-$lon_min)*$rx/$lon_d);
  my $ny=floor(($lat_max-$y)*$ry/$lat_d);
  my $k=$nx*32768+$ny;
  $node{$id}=$k;
  if (($maxts_node{$k}+0)<$t) {
   $maxts_node{$k}=$t;
  }
  if (($maxts{$k}+0)<$t) {
   $maxts{$k}=$t;
  }
 }
}

# Timestamp of last seen way in the file
my $way_t;

# TAG handler
# $expat - reference to parser
# $elem - name of element
# %list - list of attributes
sub tag {
 my ($expat,$elem,%list)=@_;
 if ($elem eq 'node') {
  # Node
  my $id=$list{'id'};
  my $x=$list{'lon'};
  my $y=$list{'lat'};
  my $t=$list{'timestamp'};
  pt($id,$x,$y,$t);
 } elsif ($elem eq 'nd') {
  # Node reference - store timestamp from way at target coords if newer
  my $coord=$node{$list{'ref'}};
  if (($maxts_way{$coord}+0)<$way_t) {
   $maxts_way{$coord}=$way_t;
  }
  if (($maxts{$coord}+0)<$way_t) {
   $maxts{$coord}=$way_t;
  }
 } elsif ($elem eq 'way') {
  # Way - store timestamp of way
  $way_t=$list{'timestamp'};
  if ($way_t=~/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)/) {
   $way_t=mktime($6,$5,$4,$3,$2-1,$1-1900,0,0);  
  }
  if ($way_t>$maxt) {$maxt=$way_t;}
  if ($way_t<$mint) {$mint=$way_t;}
 } elsif ($elem eq 'way') {
  # Relations are ignored
  undef $way_t;
 }
}

# Check commandline arguments
if (@ARGV<2) {
 print <<EOF;
OSM activity visualization v0.1.0
Usage: $0 [filename-input.xml] [filename-output-prefix]

Script will parse input data in OSM XML format (must not be bzipped or gzipped)
and will create output images based on the given prefix.

Following suffixes will be appended:
 -<DAYS>-<WIDTH>-node.png for node-only history
 -<DAYS>-<WIDTH>-way.png for way-only history
 -<DAYS>-<WIDTH>-waynode.png for combined history

 <WIDTH> is width of image in pixels
 <DAYS> is number of days of history
EOF
 exit(-1);
}

# Common mistake with autocomplete and too quick fingers :)
if ($ARGV[0] eq $ARGV[1]) {
 die('Input and output file must be different');
}

# Name of input file
my $infile=$ARGV[0];

# Name of output file
my $outputfile=$ARGV[1];

# Convert seconds into "human readable time amount"
sub t {
 my ($s)=@_;
 if ($s>=365*3600*24) {
  return floor($s/(3600*24*365))." year(s)";
 }
 if ($s>=30*3600*24) {
  return floor($s/(3600*24*30))." month(s)";
 }
 if ($s>=20*3600*24) {
  return floor($s/(3600*24*7))." week(s)";
 }
 if ($s>=3600*24) {
  return floor($s/(3600*24))." day(s)";
 }
 if ($s>=3600) {
  return floor($s/(3600))." hour(s)";
 }
 if ($s==0) {
  return $s;
 }
 return "$s secs";
}

# Generate image from given hash and time data
sub col {
 my ($im,$hashx,$timer,$type)=@_;

 #color scale bar dimensions
 my $scalebar_w=500;
 my $scalebar_h=10;

 # Create color scale bar in topleft corner of image
 for (my $x=0;$x<=$scalebar_w;$x++) {
  for (my $y=0;$y<$scalebar_h;$y++) {
   my $k=$x*32768+$y;
   $hashx->{$k}=(($maxt-$mint)*$x/$scalebar_w)+$mint;
  }
 }

 # Color bar legend
 my $green=$im->colorAllocate(0,192,0);
 $im->string(gdSmallFont,1,$scalebar_h,t($timer),$green);
 $im->string(gdSmallFont,$scalebar_w/3,$scalebar_h,t($timer*2/3),$green);
 $im->string(gdSmallFont,$scalebar_w*2/3,$scalebar_h,t($timer/3),$green);
 $im->string(gdSmallFont,$scalebar_w-8,$scalebar_h,t(0),$green);

 # Information at bottom of image
 my $dkgreen=$im->colorAllocate(0,128,0);
 $im->string(gdSmallFont,1,$ry,"$type - Created from Openstreetmap data ($infile), available under CC-BY-SA license",$dkgreen);

 # Process image
 for (my $x=0;$x<$rx;$x++) {
  for (my $y=0;$y<$ry;$y++) {
   my $k=$x*32768+$y;
   my $xt=$hashx->{$k}+0;

   if ($xt==0) {
    # No data for pixel
    next;
   }
   if ($xt<$mint) {
    # Too old data for pixel
    $xt=$mint;
   }
   # Calculate color based on age
   my $c;
   my $r=383-floor(($maxt-$xt)*255/$timer*1.5);
   if ($r<=255) {
    $c=$im->colorAllocate($r,$r,64+$r*3/4)
   } else {
    $c=$im->colorAllocate(255,766-$r*2,766-$r*2);
   }
   # Set color in pixel
   $im->setPixel($x,$y,$c);

  }
 }
}

#Tick count
my $starttick;

# Start measuring time
sub t_start {
 $starttick=time();
}

# Stop measuring time, print message and result
sub t_stop {
 my ($msg)=@_;
 my $endtick=time();
 my $secs=$endtick-$starttick;
 print STDERR "$msg: $secs seconds\n";
}

# Generate images with history for given number of days using given filename prefix
sub generate {
 my ($days,$filename)=@_;

 # Timespan of palette
 $mint=$maxt-24*3600*$days;

 t_start();

 print STDERR "Shown time range: $mint - $maxt\n";

 # Time range
 my $timer=$maxt-$mint;
 my $c;

 # $rx x $ry pixels image
 my $im=new GD::Image($rx,$ry+16);
 col($im,\%maxts,$timer,'NODES+WAYS');
 open OUT, ">${filename}-waynode.png";
 binmode OUT;
 print OUT $im->png;
 close OUT;
 undef $im;

 my $im_node=new GD::Image($rx,$ry+16);
 col($im_node,\%maxts_node,$timer,'NODES');
 open OUT, ">${filename}-node.png";
 binmode OUT;
 print OUT $im_node->png;
 close OUT;
 undef $im_node;

 my $im_way=new GD::Image($rx,$ry+16);
 col($im_way,\%maxts_way,$timer,'WAYS');
 open OUT, ">${filename}-way.png";
 binmode OUT;
 print OUT $im_way->png;
 close OUT;
 undef $im_way;

 t_stop("Generating image set $filename");

}

# Finalize - output desired image(s)
sub final {
 my ($expat)=@_;
 t_stop("Reading XML");
 # Use truecolor images
 GD::Image->trueColor(1);

 generate(   3,$outputfile."-0003-$rx");
 generate(  21,$outputfile."-0021-$rx");
 generate(  90,$outputfile."-0090-$rx");
 generate( 365,$outputfile."-0365-$rx");
 generate(1095,$outputfile."-1065-$rx");

}

t_start();
#Parse input
my $p=new XML::Parser();
$p->setHandlers('Final'=>\&final,'Start'=>\&tag);
$p->parsefile($infile);
