#!/usr/bin/perl -w
#
# composes a number of maps into one big map image
#
# usage: compose-maps.pl <map-info-file-1> [...]

use Image::Magick;

# retrieve map data from the map info file given as the function
# argument; returns map info in an associative array with the
# following keys:
#  - lat0
#  - lon0   (latitude and longitude of the upper-left of the map)
#  - lat1
#  - lon1   (latitude and longitude of the lower-right of the map)
#  - width
#  - height (image width & height)
#  - file   (filename of the map image)
sub get_map_data {
    my $line;
    my @coords;
    my %rv = ();
    my $mapf;
    
    if(!open(F, "$_[0]")) {
        die "Unable to get map data for $_[0]\n";
    }
    $line = <F>;
    chomp $line;
    @coords = split(",", $line);
    $rv{'lat0'} = $coords[0];
    $rv{'lon0'} = $coords[1];
    $line = <F>;
    chomp $line;
    @coords = split(",", $line);
    $rv{'lat1'} = $coords[0];
    $rv{'lon1'} = $coords[1];
    $line = <F>;
    chomp $line;
    @coords = split(",", $line);
    $rv{'width'} = $coords[0];
    $rv{'height'} = $coords[1];
    ($rv{'file'} = $_[0]) =~ s/.txt/.png/;
    return %rv;
}

# compares two maps by their lat0 values
sub cmp_maps {
    my %map1 = %{$a};
    my %map2 = %{$b};
    
    if($map1{'lat0'} > $map2{'lat0'}) {
        return 1;
    }
    elsif($map1{'lat0'} < $map2{'lat0'}) {
        return -1;
    }
    else {
        return 0;
    }
}

# calculates max and min longitude and latitude on all maps: an array
# of map info hashes is taken as the only argument; returns a hash with
# the following keys: maxlat, minlat, maxlon, minlon
sub get_max_min {
    my ($i, $maxlat, $maxlon, $minlat, $minlon);
    my @maps = @{$_[0]};
    my %map;
    
    $maxlat = $maxlon = -1000;
    $minlat = $minlon = 1000;
    for($i = 0; $i < @maps; $i++) {
        %map = %{$maps[$i]};
        if($maxlat < $map{'lat0'}) {
            $maxlat = $map{'lat0'};
        }
        if($maxlon < $map{'lon1'}) {
            $maxlon = $map{'lon1'};
        }
        if($minlat > $map{'lat1'}) {
            $minlat = $map{'lat1'};
        }
        if($minlon > $map{'lon0'}) {
            $minlon = $map{'lon0'};
        }
    }
    
    return (maxlat=>$maxlat, minlat=>$minlat, maxlon=>$maxlon, minlon=>$minlon);
}

my $i;
my @maps = ();
my %map;
my ($dx, $dy, $pw, $ph, $px, $py);
my %maxmin;
my ($total, $part, $x);

# get map data for all maps on the command line
for($i = 0; $i < @ARGV; $i++) {
    %{$maps[$i]} = get_map_data($ARGV[$i]);
}

# a printout never hurts...
for($i = 0; $i < @maps; $i++) {
    %map = %{$maps[$i]};
    print "Map $map{file}: $map{width}x$map{height}\n";
}

# sort the maps
@maps = sort cmp_maps @maps;

# now calculate how much lat and lon is one pixel in appropriate direction
%map = %{$maps[0]};
$dy = ($map{'lat0'} - $map{'lat1'})/$map{'height'};
$dx = ($map{'lon1'} - $map{'lon0'})/$map{'width'};

# get max and min latitude and longitude
%maxmin = get_max_min(\@maps);

print "dx = $dx, dy = $dy\n";
print "Latitude ($maxmin{'minlat'}, $maxmin{'maxlat'})\n";
print "Longitude ($maxmin{'minlon'}, $maxmin{'maxlon'})\n";

# calculated width and height for the composed map
$pw = int(($maxmin{'maxlon'} - $maxmin{'minlon'})/$dx);
$ph = int(($maxmin{'maxlat'} - $maxmin{'minlat'})/$dy);

print "Total map dimensions $pw x $ph pixels\n";

# create new map image
$total = Image::Magick->new;
$total->Set(size=>$pw . "x" . $ph);
# make it a white background
$total->ReadImage("NULL:white");

# now, for each map
for($i = 0; $i < @maps; $i++) {
    %map = %{$maps[$i]};
    $part = Image::Magick->new;
    # read the image
    $x = $part->ReadImage($map{'file'});
    warn "$x" if "$x";
    print "Map $map{'file'}: $map{'lat1'}, $map{'lon0'}\n";
    # calculate the position of this map on the composed one
    $py = $ph - ($map{'lat1'} - $maxmin{'minlat'})/$dy - $map{'height'};
    $px = ($map{'lon0'} - $maxmin{'minlon'})/$dx;
    $py = int($py);
    $px = int($px);
    print "Putting map $map{'file'} at ($px, $py)\n";
    # make white transparent
    $part->Transparent(color=>'white');
    # add the small map on the composed one
    $total->Composite(image=>$part, compose=>"Over", x=>$px, y=>$py);
    undef $part;
}
# write composed map
$total->WriteImage("complete-map.png");

# create map description file
if( !open(F, ">complete-map.txt") ) {
    die "Unable to create map description file.\n";
}

print F "$maxmin{'maxlon'},$maxmin{'minlon'}\n";
print F "$maxmin{'maxlat'},$maxmin{'minlat'}\n";
print F "$pw,$ph\n";
close(F);
