#!/opt/local/bin/perl -w

#-----------------------------------------------------------------------------
# Please refer to a page I have not yet written on the Openstreetmap wiki
# written by Callum Noble
# based by hacking at:
#    osm2ozi_tah by Oliver Reimann
#
#-----------------------------------------------------------------------------
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#-----------------------------------------------------------------------------



use warnings;
use strict;
use Getopt::Long;
use Math::Trig;
use LWP::Simple;
use GD;
use POSIX qw(ceil floor);


our $help=0;

my $name;

my $bbox;

my $tilesource = "http://tile.openstreetmap.org/mapnik";
my $tilesource_osmarender = "http://dev.openstreetmap.org/~ojw/Tiles/tile.php";

my $zoom1;
my $zoom2;

Getopt::Long::Configure('no_ignore_case');
GetOptions ( 
		'h|help|x'         => \$help, 

		'mapname=s'	=> \$name,

		'bbox=s'		=> \$bbox,

		'z1=i'	=> \$zoom1,
		'z2=i'	=> \$zoom2,

		'tilesource:s'	=> \$tilesource,

		) or usage(1);


usage(1) if $help;
#-----------------------------------------------------------------------------
usage(1) unless $name;
#-----------------------------------------------------------------------------
my $rCoOrd = qr/-?\d+(?:\.\d+)/;
usage(1) unless ( $bbox =~ /^($rCoOrd),($rCoOrd),($rCoOrd),($rCoOrd)$/ ); 
my @bbox = ($1, $2, $3, $4);
#-----------------------------------------------------------------------------
usage(1) unless $zoom1;
usage(1) unless $zoom2;
usage(1) if ($zoom1 > $zoom2);
my @zooms = ($zoom1 .. $zoom2);
#-----------------------------------------------------------------------------

mkdir $name;
open (my $fh, "> $name/$name.tba");
print $fh "Atlas 1.0\n";
close $fh;

for my $zoom(@zooms) {

	print "Zoom $zoom\n";

	mkdir "$name/zoom_$zoom";
	mkdir "$name/zoom_$zoom/$zoom";
	mkdir "$name/zoom_$zoom/$zoom/set";

	my ($x1,$y1) = getTileNumber( $bbox[3], $bbox[0], $zoom);
	my ($x2,$y2) = getTileNumber( $bbox[1], $bbox[2], $zoom);

	print " Top left tile is: $zoom $x1 $y1\n";
	print " Bottom right tile is: $zoom $x2 $y2\n";

	my $x_size = (($x2-$x1+1)*256);
	my $y_size = (($y2-$y1+1)*256);


	my ($S, $W, $N, $E);
# Get the North and West edge co-ords from the top left tile
	(undef, $W, $N, undef) = Project($x1,$y1,$zoom);
# Get the South and East edge co-ords from the bot right tile
	($S, undef, undef, $E) = Project($x2,$y2,$zoom);

	print " Bounding box is $W, $S, $E, $N\n";

	GenCalibrationMap_OZI ("$name/zoom_$zoom/$zoom/$zoom.map",$zoom,$W,$S,$E,$N,$x_size,$y_size); 

	my @Tiles = GetTiles ($x1, $x2, $y1, $y2, $zoom) ;

	my $full = CombineTiles($x_size, $y_size, $zoom, @Tiles);
	
	SplitTiles($x_size, $y_size, $zoom, $full);

	system "tar -c -C $name/zoom_$zoom/$zoom/ -f $name/zoom_$zoom/$zoom/$zoom.tar $zoom.map set";
	system "tar -R -tf $name/zoom_$zoom/$zoom/$zoom.tar > $name/zoom_$zoom/$zoom/$zoom.tbi";
	system "rm -r $name/zoom_$zoom/$zoom/set";

}

my $cmd = "tar -cf $name/$name.tar -C $name --exclude=*.tar --exclude=*DS_Store $name.tba";

foreach(@zooms) {
	$cmd .= " zoom_$_";
}

system $cmd;

system "rm $name/$name.tba";

foreach(@zooms) {
	system "rm -r $name/zoom_$_/$_/$_.map";
}

1;





sub GetTiles {
	my ($xmin, $xmax, $ymin, $ymax, $zoom) = @_;

	my @Tiles = ();

	my $xcount = 0;
	for my $x($xmin .. $xmax) {
		my $ycount = 0;
		for my $y($ymin .. $ymax) {

			my $save_as = sprintf "%d_%d_%d.png", $zoom, $xcount*256, $ycount*256;
			my $save_as_fn ="$name/zoom_$zoom/$zoom/tmp/$save_as";

			my $Image;
			$Image = getTile($x,$y,$zoom,"http://tile.openstreetmap.org/mapnik");
#			$Image = getTile($x,$y,$zoom,"http://dev.openstreetmap.org/~ojw/Tiles/tile.php");

			push @Tiles, [$Image, $xcount*256, $ycount*256];

#			savePng($Image,$save_as_fn);

			$ycount++;
		}
		$xcount++;
	}

	return @Tiles;
}


sub CombineTiles {
	my ( $x_size, $y_size, $zoom, @Tiles) = @_; 

	print " Combine to Image (X,Y) = (", $x_size-1, ",", $y_size-1, ")\n";

	my $full = GD::Image->new($x_size-1,$y_size-1);

	foreach my $image(@Tiles) {
		my $tmppng = GD::Image->new($image->[0]);
		$full->copy($tmppng, $image->[1], $image->[2], 0, 0, $tmppng->getBounds() );
	}

	return $full;
}


sub SplitTiles {
	my ( $x_size, $y_size, $zoom, $full) = @_; 
	my $outx=0;
	while ($outx < $x_size-1) {
		my $outy=0;
		while ($outy < $y_size-1) {
			my $tiled = GD::Image->new(260,340);
			my $fn = "$zoom"."_"."$outx"."_"."$outy.png";
			print " ...  Split to: $fn\n";

			$tiled->copy($full, 0, 0, $outx, $outy, 260, 340);

			open (my $fh, "> $name/zoom_$zoom/$zoom/set/$fn");
			print $fh $tiled->png;
			close $fh;

			$outy += 340;
		}

		$outx += 260;
	}
}


##############################################################################
# transform the latitude and logitude to the OSM tile number -> needed for tile-download 
##############################################################################
sub getTileNumber {
		my ($lat, $lon, $zoom) = @_;

  my $xtile = int( ($lon+180)/360 *2**$zoom ) ;
  my $ytile = int( (1 - log(tan($lat*pi/180) + sec($lat*pi/180))/pi)/2 *2**$zoom ) ;
  return(($xtile, $ytile));
}
##############################################################################
# helper sub for getsaveTileWithCache and getsaveTileWithoutCache
##############################################################################
sub getTile {
  my ($x,$y,$z,$tilesource) = @_;
  my $URL = sprintf $tilesource . "/%d/%d/%d.png",$z,$x,$y;
  print " ...  Fetching $URL\n";
  my $Data = get($URL);
  return $Data;
}
##############################################################################
# helper sub for getsaveTileWithCache() and getsaveTileWithoutCache()
##############################################################################
sub savePng {
  my ($Image, $Filename) = @_;
  open(my $fp , '>', $Filename) || return;
  binmode $fp;
  print $fp $Image;
  close $fp;
}
##############################################################################
# transform the x,y tilenumbers to the S,W,N,E edge ccordinates
##############################################################################
sub Project{
  my ($X,$Y, $Zoom) = @_;

  my $Unit = 1 / (2 ** $Zoom);
  my $relY1 = $Y * $Unit;
  my $relY2 = $relY1 + $Unit;

  my $LimitY = ProjectF(85.0511);
  my $RangeY = 2 * $LimitY;

  $relY1 = $LimitY - $RangeY * $relY1;
  $relY2 = $LimitY - $RangeY * $relY2;

  my $Lat1 = ProjectMercToLat($relY1);
  my $Lat2 = ProjectMercToLat($relY2);

  $Unit = 360 / (2 ** $Zoom);
  my $Long1 = -180 + $X * $Unit;

  return(($Lat2, $Long1, $Lat1, $Long1 + $Unit)); # S,W,N,E
}

##############################################################################
# helper sub for Project()
##############################################################################
sub ProjectMercToLat($){
  my $MercY = shift();
  return( 180/pi* atan(sinh($MercY)));
}

##############################################################################
# helper sub for Project()
##############################################################################
sub ProjectF($){
  my $Lat = pi/180 * shift();
  my $Y = log(tan($Lat) + sec($Lat));
  return($Y);
}
##############################################################################
# generates the calibration file for OZI
##############################################################################
sub GenCalibrationMap_OZI{
  my ($outFilename,$mapFilename,$W,$S,$E,$N,$Width,$Height) = @_;

my $Data = << "ENDE1" ; 
OziExplorer Map Data File Version 2.2
--Filename--.png
--Filename--.ozf2
1 ,Map Code,
WGS 84,WGS 84,   0.0000,   0.0000,WGS 84
Reserved 1
Reserved 2
Magnetic Variation,,,E
Map Projection,Mercator,PolyCal,No,AutoCalOnly,No,BSBUseWPX,No
Point01,xy,    0,    0,                  in, deg,  --N1--,--N2--,--NN--,  --W1--,--W2--,--WW--, grid,   , , ,N
Point02,xy, --Width_m1--, --Height_m1--, in, deg,  --S1--,--S2--,--SS--,  --E1--,--E2--,--EE--, grid,   , , ,N
Point03,xy, --Width_m1--,0,              in, deg,  --N1--,--N2--,--NN--,  --E1--,--E2--,--EE--, grid,   , , ,N
Point04,xy, 0, --Height_m1--,            in, deg,  --S1--,--S2--,--SS--,  --W1--,--W2--,--WW--, grid,   , , ,N
Projection Setup,,,,,,,,,,
Map Feature = MF ; Map Comment = MC     These follow if they exist
Track File = TF      These follow if they exist
Moving Map Parameters = MM?    These follow if they exist
MM0,Yes
MMPNUM,4
MMPXY,1,0,0
MMPXY,2,--Width--,0
MMPXY,3,--Width--,--Height--
MMPXY,4,0,--Height--
MMPLL,1,  --W--,  --N--
MMPLL,2,  --E--,  --N--
MMPLL,3,  --E--,  --S--
MMPLL,4,  --W--,  --S--
MM1B,--MM1B--
MOP,Map Open Position,0,0
IWH,Map Image Width/Height,--Width--,--Height--

ENDE1

  # Change some stuff
#  my$aW=abs($W); $Data =~ s/--W--/$aW/g;
#  my$aS=abs($S); $Data =~ s/--S--/$aS/g;
#  my$aE=abs($E); $Data =~ s/--E--/$aE/g;
#  my$aN=abs($N); $Data =~ s/--N--/$aN/g;

  $Data =~ s/--W--/$W/g;
  $Data =~ s/--S--/$S/g;
  $Data =~ s/--E--/$E/g;
  $Data =~ s/--N--/$N/g;

  my ($W1, $W2) = toDec($W); $Data =~ s/--W1--/$W1/g; $Data =~ s/--W2--/$W2/g;
  my ($S1, $S2) = toDec($S); $Data =~ s/--S1--/$S1/g; $Data =~ s/--S2--/$S2/g;
  my ($E1, $E2) = toDec($E); $Data =~ s/--E1--/$E1/g; $Data =~ s/--E2--/$E2/g;
  my ($N1, $N2) = toDec($N); $Data =~ s/--N1--/$N1/g; $Data =~ s/--N2--/$N2/g;

  $Data =~ s/--Filename--/$mapFilename/g;

  $Data =~ s/--Width--/$Width/g;
  my $Width_m1 = $Width-1;
  $Data =~ s/--Width_m1--/$Width_m1/g;

  $Data =~ s/--Height--/$Height/g;
  my $Height_m1 = $Height-1;
  $Data =~ s/--Height_m1--/$Height_m1/g;
#$Data =~ s/--ext--/$extension/g;

  if ($W>0)  {$Data =~ s/--WW--/E/g} else {$Data =~ s/--WW--/W/g};
  if ($E>0)  {$Data =~ s/--EE--/E/g} else {$Data =~ s/--EE--/W/g};
  if ($N>0)  {$Data =~ s/--NN--/N/g} else {$Data =~ s/--NN--/S/g};
  if ($S>0)  {$Data =~ s/--SS--/N/g} else {$Data =~ s/--SS--/S/g};

  # calcualate meter per pixel
  my $MM1B = 36000/360*1000*($N-$S)/$Height  ;
  $Data =~ s/--MM1B--/$MM1B/g;

  # convert CR to CRLF
  $Data =~ s/\n/\r\n/g;  

  # Save back to the same location
  open(my $fpOut, ">$outFilename");
#  binmode $fpOut;
  print $fpOut $Data;
  close $fpOut;
}




#-----------------------------------------------------------------------------

sub round {
	my $number = shift;
    return int($number + .5 * ($number <=> 0));
}


sub toDec {
	my $n = shift;
	$n = abs($n);

	return floor($n), round(($n-floor($n))*60);
}

##############################################################################
# usage, help
##############################################################################
sub usage {
	print "usage: $0 -mapname name -bbox left,bottom,right,top -z1 fromzoom -z2 tozoom [-tilesource=source]\n";
	print "\n";
	print "Script for creating trekbuddy atlases.\n";
	print "Downloads the tiles from the openstreetmap server, glues them together, and splits into 240x320s.\n";
	print "Generates calibration files and zips into altas.\n";
  print "\n";
  print "mapname: name for the atlas\n";
  print "bbox: a bounding box\n";
  print "z1: downlaod from zoom\n";
  print "z2: downlaod to zoom\n";
  print "source: \n";
  print "      - http://tile.openstreetmap.org/mapnik [DEFAULT]\n";
  print "      - http://tah.openstreetmap.org/Tiles/tile\n";
  print "      - http://dev.openstreetmap.org/~ojw/Tiles/maplint.php\n";
  print "\n";
  print "for more help shout at callum\n";
  exit;
}
##############################################################################
