#!/usr/bin/perl
#
# (C) Copyright 2001-2004 Diomidis Spinellis
#
# This file is part of GTWeb.
#
# GTWeb 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, or (at your option) any later
# version.
#
# GTWeb 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 groff; see the file COPYING.  If not, write to the Free Software
# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
# Scan a track log with appended location information (see program near)
# and a set of image files
#
# Produce meta-information for the images
# and a timeline
#
# $Id: timeline.pl 1.21 2004/02/07 08:35:19 dds Exp $
#


# Local time offset
# Set TZ EET-2 (winter) or (EST-3) summer

use POSIX;
use Time::Local;

$starttime = time();

if ($#ARGV < 0 || $#ARGV > 1) {
	print STDERR "Usage: $0 track-log [picture-metadata]\n";
	exit 1;
}

# Configuration variables
$legdiff = 15 * 60;	# After this long a new leg begins
$partdist = 30000;	# After so many meters a new part begins
$stoptime = 5 * 60;	# After this long we consider to have stopped
$makemap = 1;		# Set to true to re-create maps
$makethumb = 1;		# Set to true to re-create thumbs
$mapinfo = 1;		# Set to true to create hyperlinks to web maps
$map_xsize = 640;
$map_ysize = 480;
$datpath = "$ENV{PROGDIR}/";
$plan9_map = 0;		# Set 1 to use the Plan-9 map program instead of GMT

$PI = 3.14159265358979;

# Global variables
$sec = 1;		# Section number
$mapnum = '';		# Map number
$photonum = 0;
undef($map_lasturl);

init_arrays();
read_auxfiles();
read_track_log();
read_photos();

$overview = create_map(1e38, 1e38, 0, 0, "Trip Overview Map") . "<p>\n";

open(INDEX, ">index.html") || die;
print INDEX htmlhead("Trip Log Presentation");

print INDEX
'<table border="0">
<tr>
<td>
';

print INDEX 
"<h2>$sec. Trip Overview</h2>
$overview<p>
";
$sec++;

htmlpage('tdfeat.html', "Geographical Features and Photographs", create_timeline(1));
htmlpage('tafeat.html', "Geographical Features and Photographs", create_timeline(0));

print INDEX 
"<h2>$sec. Timelines</h2>
$sec.1 <a href=\"tdfeat.html\">Geographical features and photographs by date</a><br>
$sec.2 <a href=\"tafeat.html\">Geographical features and photographs (all, big - for searching)</a><br>
";
$sec++;

htmlpage('legs.html', "Trip Legs",
"Each leg represents a break of over " . time_form($legdiff) . ".<p>\n" .
create_map(1e38, $legdiff, 0, 1, "Trip Leg Map") . "<p>\n");

htmlpage('parts.html', "Detailed Maps of Trip Parts",
sprintf("Each part covers a linear distance of %d km.<p>\n", $partdist / 1000) .
# The map name is used to create photo back-links.  Search for it.
create_map($partdist, $legdiff, 1, 1, "Detailed Trip Part Map") . "<p>\n");

print INDEX 
"<h2>$sec. Maps</h2>
$sec.1 <a href=\"map.html\">Overview</a><br>
$sec.2 <a href=\"legs.html\">Trip legs</a><br>
$sec.3 <a href=\"parts.html\">Detailed maps of trip parts</a><br>
";
$sec++;

htmlpage('dphoto.html', "Photographs by Date", create_photos(1, 1));
htmlpage('allphoto.html', "All Photographs", create_photos(0, 1));
htmlpage('cphoto.html', "All Photographs", create_photos(0, 0));

print INDEX 
"<h2>$sec. Photographs</h2>
$sec.1 <a href=\"dphoto.html\">Ordered by date</a><br>
$sec.2 <a href=\"allphoto.html\">All photographs</a><br>
$sec.3 <a href=\"cphoto.html\">Compact index (no captions)</a><br>
";
$sec++;

print INDEX 
"<h2>$sec. Processing Details</h2>
Track log points processed: $#tracklog<br>
Photos processed: $photonum <br>
Maps generated: $mapnum<br>
Local time setting: $ENV{TZ}<br>
Generated on: " . local_datetime(time()) . "<br>
Processing time: " . time_form(time() - $starttime) . '<br>
Program version: $Id: timeline.pl 1.21 2004/02/07 08:35:19 dds Exp $
';
$sec++;

print INDEX
'</td>
<td>
<img src="small.gif" hspace="40" border=$plan9_map><p>
<img src="persp.gif" hspace="40" border="0">
</td>
</tr>
</table>
';

# Sort hash references based on their time value
sub
bytime
{
	return $a->{time} <=> $b->{time};
}

# Return direction from point1 to point2 i.e. how point2 relates to point1
# Used as in: point2 is distance() direction() from point1
sub
direction
{
	my($lat1, $lon1, $lat2, $lon2) = @_;
	my($angle, $check);

	$lat1 /= 180 * $PI;
	$lat2 /= 180 * $PI;
	$lon1 /= 180 * $PI;
	$lon2 /= 180 * $PI;
	$angle = atan2($lat2 - $lat1, $lon2 - $lon1);
	#           N
	#	   PI/2
	#
	#   W PI  point1   0 E
	#
	#	  -PI/2
	#            S
	$check = -$PI + $PI / 8;
	return 'W'  if ($angle < $check); $check += $PI / 4;
	return 'SW' if ($angle < $check); $check += $PI / 4;
	return 'S'  if ($angle < $check); $check += $PI / 4;
	return 'SE' if ($angle < $check); $check += $PI / 4;
	return 'E'  if ($angle < $check); $check += $PI / 4;
	return 'NE' if ($angle < $check); $check += $PI / 4;
	return 'N'  if ($angle < $check); $check += $PI / 4;
	return 'NW' if ($angle < $check);
	return 'W';
}

sub
time_form
{
	my($t) = @_;

	$t = abs($t);
	if ($t < 60) {
		return "$t seconds";
	} elsif ($t < 60 * 60) {
		return int($t / 60) . " minute(s)";
	} elsif ($t < 60 * 60 * 24) {
		return sprintf("%02d:%02d", $t / 60 / 60, int($t / 60) % 60);
	} else {
		return int($t / 60 / 60 / 24) . " day(s)";
	}
}

# Return distance (in km) between two points
sub
dist
{
	my($lat1, $lon1, $lat2, $lon2) = @_;
	my($dist);

	return 0 if ($lat1 == $lat2 && $lon1 == $lon2);
	$lat1 /= 180 / $PI;
	$lat2 /= 180 / $PI;
	$lon1 /= 180 / $PI;
	$lon2 /= 180 / $PI;
	$dist = 60 * acos(sin($lat1)*sin($lat2) + cos($lat1)*cos($lat2)*cos($lon1-$lon2)) / $PI * 180 * 1852;
	return ($dist);
}

# Return HTML with URLs for map info
sub
mapinfo
{
	my($lat, $lon, $name) = @_;
	my($lat2, $lon2);

	$lat2 = "$lat";
	$lon2 = "$lon";
	$lat2 = sprintf("%02.3f0", $lat);
	$lon2 = sprintf("%02.3f0", $lon);
	return "(" .
	"<a target=\"new\" href=\"http://maps.fallingrain.com/perl/map.cgi?kind=topo&lat=$lat&long=$lon&name=$name&scale=0&x=320&y=200\">" .
	"topological</a>, " .
	"<a target=\"new\" href=\"http://www.mapquest.com/maps/map.adp?latlongtype=decimal&latitude=$lat2&longitude=$lon2\">" . 
	"street</a> map) ";
	#"<a target=\"new\" href=\"http://pubweb.parc.xerox.com/map/ht=0.70/lat=$lat/lon=$lon/mark=$lat,$lon/wd=1.41\">" .
	#"world</a>) ";
}

sub
max
{
	my ($a, $b) = @_;
	return ($a > $b ? $a : $b);
}

# Return a name for a track point based on its relative position to its
# nearest geographical feature
sub
track_point_name
{
	my($s) = @_;
	my($ret);

	$ret = relpos($s->{tlat}, $s->{tlon}, $s->{flat}, $s->{flon}, $s->{fname});
	$ret .= " ($region{$s->{fadm}}) " if (substr($s->{fadm}, 2, 2) ne '00' && $s->{fadm} ne '');
	$ret .= " ($feature_name{$s->{ftype}}) " if ($s->{ftype} ne '');
	$ret .= mapinfo($s->{flat}, $s->{flon}, $s->{fname});
	return ($ret);
}

# Return a string representing a point's relative position to a reference point
sub
relpos
{
	my($plat, $plon, $reflat, $reflon, $refname) = @_;

	return sprintf("%.2f km ", dist($plat, $plon, $reflat, $reflon) / 1000) .
		direction($reflat, $reflon, $plat, $plon) .
		" of $refname";
}

# Return the speed based on the distance (in m) and
# time difference (in s)
# The speed is returned as a string in human readable form
sub
speed
{
	my($dist, $difftime) = @_;
	my($kmspeed) = $dist / $difftime / 1000 * 60 * 60;
	if ($kmspeed > 10) {
		return sprintf("%d km/h ", $kmspeed);
	} else {
		return sprintf("%.1f km/h ", $kmspeed);
	}
}

# Return H:M:S of local time of UTC parameter
sub
local_hms
{
	my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
		localtime($_[0]);

	return sprintf("%02d:%02d:%02d", $hour, $min, $sec);
}

# Return date and time of local time of UTC parameter
sub
local_datetime
{
	my($t) = @_;

	return local_date($t) . ' ' . local_hms($t);
}

# Return date of local time of UTC parameter
sub
local_date
{
	my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
		localtime($_[0]);

	$year += 1900;
	return "$day_name[$wday] $month_name[$mon] $mday, $year";
}

# Initialize values of global arrays
sub
init_arrays
{
	%month_num = (
		'Jan' => 0,
		'Feb' => 1,
		'Mar' => 2,
		'Apr' => 3,
		'May' => 4,
		'Jun' => 5,
		'Jul' => 6,
		'Aug' => 7,
		'Sep' => 8,
		'Oct' => 9,
		'Nov' => 10,
		'Dec' => 11
	);

	@month_name = (
		'Jan',
		'Feb',
		'Mar',
		'Apr',
		'May',
		'Jun',
		'Jul',
		'Aug',
		'Sep',
		'Oct',
		'Nov',
		'Dec'
	);
	@day_name = (
		'Sun',
		'Mon',
		'Tue',
		'Wed',
		'Thu',
		'Fri',
		'Sat'
	);
}

# Read auxilliary data files setting the appropriate associative arrays
sub
read_auxfiles
{
	my($code, $reagio, $feature);
	my($fname);

	open(IN, $fname = $datpath . 'regioncode.dat') || die "Unable to open $fname:$!\n";
	while (<IN>) {
		chop;
		($code, $region) = split(/\t/, $_);
		$region{$code} = $region;
	}

	open(IN, $fname = $datpath . 'feature.dat') || die "Unable to open $fname:$!\n";
	while (<IN>) {
		chop;
		($code, $feature) = split(/\t/, $_);
		$feature_name{$code} = lc($feature);
	}
}

# Return a suitable HTML head given a title
sub
htmlhead
{
	my($title) = @_;

	return "
<html>
<head>
<title>$title</title>
</head>
<body>
<h1>$title</h1>
";
}

#Return an HTML page ending
sub
htmltail
{
	my($name) = @_;
	my($previous, $next);

	#print "name=$name\nnext=$next{$name}\n";
	$previous = " <a href=\"$previous{$name}\">Previous</a> " if (defined($previous{$name}));
	$next = " <a href=\"$next{$name}\">Next</a> " if (defined($next{$name}));
	return "
<hr>
<a href=\"index.html\">Trip index</a>$previous$next
</body>
</head>
";
}

# Read augmented track log creating @tracklog and %feature
sub
read_track_log
{
	my($i, $lat, $lon, $mon, $mday, $hour, $min, $sec, $year);
	my($ftype, $fadm, $fdist, $flat, $flon, $fname, $time);

	open(IN, $ARGV[0]) || die "Unable to open $ARGV[0]: $!\n";
	$i = 0;
	while (<IN>) {
		chop;
		# T  N38 04.8777 E023 49.9539 Sun Aug 19 08:48:55 2001 										HLL  GR00 0.0187192 38.1 23.8333 Kastraki
		if (/^T  ([NS])(\d\d) (\d\d\.\d\d\d\d) ([EW])(\d\d\d) (\d\d\.\d\d\d\d) \w\w\w (\w\w\w) (\d\d) (\d\d)\:(\d\d)\:(\d\d) (\d\d\d\d) ([^\t]+)\t\s*([^\t]+)\t\s*([-0-9.eE]+) ([0-9.]+) ([0-9.]+) (.*)$/) {
			# Normalize coordinates
			$lat = ($1 eq 'N' ? 1 : -1) * $2 + $3 / 60;
			$lon = ($4 eq 'E' ? 1 : -1) * $5 + $6 / 60;
			# Human readable versions
			$mon = $month_num{$7};
			$mday = $8;
			$hour = $9;
			$min = $10;
			$sec = $11;
			$year = $12;
			$ftype = $13;
			$fadm = $14;
			$fdist = $15;
			$flat = $16;
			$flon = $17;
			$fname = $18;
			$ftype =~ s/\s+$//;	# World data contains spaces
			$fadm =~ s/\s+$//;
			# print "year = $year, ftype=[$ftype], fadm=[$fadm] fdist=[$fdist]\n";
			# Track-log time is UTC
			$time = timegm($sec, $min, $hour, $mday, $mon, $year);
			push(@tracklog, {
				tlat => $lat,
				tlon => $lon,
				time => $time,		# Time (Unix)
				fname => $fname,
				fdist => $fdist,
				fadm => $fadm,
				ftype => $ftype,
				idx => $i,
				flat => $flat,
				flon => $flon
			});
			if (!defined($feature{$fname}) || $feature{$fname}->{dist} > $fdist) {
				$feature{$fname} = {
					fname => $fname,
					fdist => $fdist,
					flat => $flat,		# Feature coord
					flon => $flon,
					tlat => $lat,		# Track coord
					tlon => $lon,
					time => $time,
					fadm => $fadm,
					ftype => $ftype,
					idx => $i,
				};
			}
			$i++;
		} else {
			print STDERR "Ignoring $_\n";
		}
	}
}

# Read the photo meta-file (created by pmeta.pl and hand editing)
# and update %features with the photos
sub
read_photos
{
	my($p, $best, $nearest, $time, $i);
	my($lasturl);

	open(IN, $ARGV[1]) || return;
	line: while (<IN>) {
		$p = $1 if (/\<name\>([^<]+)\<\/name\>/);
		$time = $1 if (/\<time\>([^<]+)\<\/time\>/);
		$caption = $1 if (/\<caption\>([^<]+)\<\/caption\>/);
		if(/\<photo\>/) {
			undef($p);
			undef($time);
			undef($caption);
		}
		next line unless(/\<\/photo\>/);
		next line unless(-f $p);

		$photonum++;
		# Find nearest feature point
		$nearest = 1e38;
		for $i (@tracklog) {
			if (abs($i->{time} - $time) < $nearest) {
				$nearest = abs($i->{time} - $time);
				$best = $i;
			}
		}
		$feature{$p} = {
			fname => $best->{fname},
			fdist => $best->{fdist},
			fadm => $best->{fadm},
			ftype => $best->{ftype},
			flat => $best->{flat},		# Feature coord
			flon => $best->{flon},
			tlat => $best->{tlat},		# Track coord
			tlon => $best->{tlon},
			time => $time,
			idx => $best->{idx},
			photo => $p,
			url => "p$photonum.html",	# URL to use
			caption => $caption,
			# Difference between the photo and track time
			timediff => $best->{time} - $time,
		};
		if (defined($lasturl)) {
			$previous{"p$photonum.html"} = $lasturl;
			$next{$lasturl} = "p$photonum.html";
		}
		$lasturl = "p$photonum.html";
	}
	close(IN);
}

# Create a series of maps returning the HTML with the links
# If max_dist is specified, a new map is created every time the covered
# area exceeds it.
# If leg_break_interval is specified, a new leg is created every time
# the time difference between two track logs exceeds it
# If draw_features is true, features passed on the way are drawn
# If date_break is true, dates are inserted on every date change
# Title is the descriptive title given to the map
sub
create_map
{
	my($max_dist, $leg_break_interval, $draw_features, $date_break, $title) = @_;
	my($begin, $i, $p, $x);
	my($latmin, $latmax, $lonmin, $lonmax);
	my($timemin, $timemax);
	my($s);				# Start point
	my(%visited_features);
	my($date, $cmd, $photolist);
	my($html, $mapdesc, $mapname, $human_coord);
	my($tot_distance, $tot_time, $area);
	my($nextnum);

	$begin = 1;
	trackpoint: for ($i = 0; $i <= $#tracklog; $i++) {
		$p = $tracklog[$i];
		if ($begin) {
			open(TRACK, ">map.tra");
			open(FEAT, ">map.txt");
			$timemin = $latmin = $lonmin = 1e38;
			$timemax = $latmax = $lonmax = -1e38;
			$begin = 0;
			$s = $p;
			$tot_distance = $tot_time = 0;
			undef %visited_features;
			undef $photolist;
		}
		$latmin = $p->{tlat} if ($p->{tlat} < $latmin);
		$latmax = $p->{tlat} if ($p->{tlat} > $latmax);
		$lonmin = $p->{tlon} if ($p->{tlon} < $lonmin);
		$lonmax = $p->{tlon} if ($p->{tlon} > $lonmax);
		$timemin = $p->{time} if ($p->{time} < $timemin);
		$timemax = $p->{time} if ($p->{time} > $timemax);
		$visited_features{$p->{fname}} = 1;
		track_point($p->{tlat}, $p->{tlon});
			
		# Stop discontinuities
		if ($i < $#tracklog && $tracklog[$i + 1]->{time} - $p->{time} > $stoptime) {
			track_break();
		}
		if (dist($p->{tlat}, $p->{tlon}, $s->{tlat}, $s->{tlon}) > $max_dist ||
		    ($i < $#tracklog && $tracklog[$i + 1]->{time} - $p->{time} > $leg_break_interval) ||
		    $i == $#tracklog) {
			$begin = 1;
			$area = dist($latmin, $lonmin, $latmax, $lonmin) / 1000 *
				dist($latmin, $lonmin, $latmin, $lonmax) / 1000;
			next trackpoint if ($tot_time == 0 || $area < 1);
			if ($date_break && local_date($s->{time}) ne $date) {
				$html .= '<h3>' . ($date = local_date($s->{time})) . '</h3>';
			}
			$mapdesc = "From " . track_point_name($s) . ' (' .
			($date_break ? local_hms($s->{time}) : local_datetime($s->{time})) .
			') to ' . track_point_name($p) . ' (' .
			($date_break ? local_hms($p->{time}) : local_datetime($p->{time})) .
			')' . 
			sprintf(' covering a travel distance of %.2f km at an average speed of %s over an area of %d sq km.',
				$tot_distance / 1000,
				speed($tot_distance, $tot_time), $area) .
				' Duration ' . time_form($p->{time} - $s->{time}) . 
				', travel time ' . time_form($tot_time);
			if ($mapnum ne '') {
				my($p, $count);
				# Create a list of photos in the map
				# and ammend the photo map url
				feature: for $p (sort bytime values %feature) {
					next feature unless ($p->{photo});
					next feature unless ($p->{time} > $timemin);
					next feature unless ($p->{time} < $timemax);
					$count++;
					$photolist .= "<a href=\"$p->{url}\">$count</a> ";
					$p->{map} .= "<a href=\"map$mapnum.html\">$title</a> ";
				}
			}
			$photolist = "<br>Photos: $photolist" if (defined($photolist));
			if ($draw_features) {
				track_break();
				for $x (keys %visited_features) {
					$p = $feature{$x};
					feature($p->{flat}, $p->{flon}, $p->{fname} . ' (' . substr(local_hms($p->{time}), 0, 5) . ')');
				}
			}
			$human_coord = human_coord($latmin, $lonmin) . ' - ' . human_coord($latmax, $lonmax);
			close(TRACK);
			close(FEAT);
			# $cmd = sprintf(mercator -m cil -W $map_xsize $map_ysize -p %.2f %.2f %.2f -l %.0f %.0f %.0f %.0f -g 0 -t map.tra",
			mercator($mapname = "map$mapnum.gif", $latmin, $latmax, $lonmin, $lonmax, $map_xsize, $map_ysize, 1);
			if ($makemap && $mapnum eq '') {
				# Create small map
				# $cmd = sprintf("map mercator -m cil -W 300 260 -p %.2f %.2f %.2f -l %.0f %.0f %.0f %.0f -g 0 -t map.tra",
				mercator('small.gif', $latmin, $latmax, $lonmin, $lonmax, 300, 260, 0);
				earthmap($latmin, $latmax, $lonmin, $lonmax);
			}
			$html .= $mapdesc . " (<a href=\"map$mapnum.html\">travel map</a>).<p>";
			if (defined($map_lasturl)) {
				$previous{"map$mapnum.html"} = $map_lasturl;
			}
			$nextnum = $mapnum + 1;
			$next{"map$mapnum.html"} = "map$nextnum.html";
			$map_lasturl = "map$mapnum.html";
			htmlpage("map$mapnum.html", $title . ' ' . $human_coord,
				($date_break ? local_date($s->{time}) . '. ' : '') .
				$mapdesc . ".<p><img src=\"$mapname\" border=\"$plan9_map\">$photolist<p>");
			$mapnum++;
		}
		if ($i < $#tracklog && $tracklog[$i + 1]->{time} - $p->{time} < $stoptime) {
			$tot_distance += dist($p->{tlat}, $p->{tlon}, $tracklog[$i + 1]->{tlat}, $tracklog[$i + 1]->{tlon});
			$tot_time += $tracklog[$i + 1]->{time} - $p->{time};
		}
	}
	# Too difficult to pre-guess the last map.
	htmlpage("map$mapnum.html", "The End", "No more maps.");
	return ($html);
}

# Create a list of times and places/photos
# If date_break is true, dates are inserted on every date change
sub
create_timeline
{
	my($date_break) = @_;
	my($date, $x, $pt1, $pt2);
	my($html, $html2, $dnum);

	undef($date);
	$html2 = "<ul>\n";
	for $x (sort bytime values %feature) {
		if ($date_break && local_date($x->{time}) ne $date) {
			if (defined($date)) {
				# Unfortunately the are two copies of this code in the subroutine
				$html .= '</dl>';
				htmlpage("tld$dnum.html", "Geographical Features and Photographs", $html);
				$dnum++;
			}
			$html = '<h3>' . ($date = local_date($x->{time})) . '</h3><dl>';
			$html2 .= "<li> <a href=\"tld$dnum.html\">$date</a>";
		}
		$html .= "<dt>" . ($date_break ? local_hms($x->{time}) : local_datetime($x->{time})) . "<dd>";
		if ($x->{photo}) {
			$html .= " <a href=\"$x->{url}\">Photograph</a>.";
			$html .= " About (most recent fix taken " . time_form($x->{timediff}) . " from the picture time) ";
		} else {
			$html .= " Approached ", 
		}
		$html .= mapinfo($x->{tlat}, $x->{tlon}, "Approach") . " " .
		track_point_name($x);
		# Is speed relevant to current point?
		if ($x->{timediff} < 60) {
			# Avoid indexing errors
			if ($x->{idx} != 0) {
				$pt1 = $tracklog[$x->{idx} - 1];
				$pt2 = $tracklog[$x->{idx}];
				if (($difftime = $pt2->{time} - $pt1->{time}) < 90 && $difftime > 0 && ($dist = dist($pt1->{tlat}, $pt1->{tlon}, $pt2->{tlat}, $pt2->{tlon})) > 60) {
					# We are actually travelling
					$html .= " travelling at a speed of " . speed($dist, $difftime);
				}
			}
		}
		$html .= ".\n";
	}
	if (defined($date)) {
		# Unfortunately the are two copies of this code in the subroutine
		$html .= '</dl>';
		htmlpage("tld$dnum.html", "Geographical Features and Photographs", $html);
	}
	$html2 .= "</ul>\n";
	return ($date_break ? $html2 : $html);
}

# Create an HTML page in the specified file, with the given title and body
sub
htmlpage
{
	my($fname, $title, $body) = @_;
	open(OUT, ">$fname") || die "Unable to open $fname: $!\n";
	print OUT htmlhead($title), $body, htmltail($fname);
	close(OUT);
}

# Convert a decimal number into degrees minutes and decimal minutes
sub
dm
{
	my($d) = @_;

	return sprintf("%02d&deg;%.0f'", int($d), ($d - int($d)) * 60);
}

# Convert a coordinate pair into human-reable form
sub
human_coord
{
	my($lat, $lon) = @_;

	return  dm($lat) . ($lat > 0 ? 'N' : 'S') .  ', ' . 
		dm($lon) . ($lon > 0 ? 'E' : 'W');
}

# Create the GIF file specified out of the temporary map output file
sub
gifmap
{
	my($name, $xsize, $ysize) = @_;
	my($postproc);
	if (!$plan9_map) {
		# Add a couple of inches for the the border
		$ysize += 300;
		$xsize += 400;
		system($cmd = 'gs -r100 ' . "-g${xsize}x${ysize} " . '  -dBATCH -dNOPAUSE -sDEVICE=png16m -sOutputFile=map.png map.ps');
		$postproc = '| pnmcrop -white ';
		if ($name eq 'small.gif' ) {
			$postproc .= '| ppmquant 256 ';
		}
	}
	system('pngtopnm map.png ' . $postproc . '| ppmtogif >tmpmap.gif');
	unlink($name);
	rename('tmpmap.gif', $name);
}

# Create an index of photographs
sub
create_photos
{
	my($date_break, $make_captions) = @_;
	my($date, $x);
	my($html, $dnum);
	my($caption);

	undef($date);
	mkdir('thumb', 0777);
	$html2 = "<ul>\n";
	$html = '<table border=0>' if ($make_captions);
	feature: for $x (sort bytime values %feature) {
		next feature unless ($x->{photo});
		if ($date_break && local_date($x->{time}) ne $date) {
			if (defined($date)) {
				# Unfortunately the are two copies of this code in the subroutine
				$html .= '</table>';
				htmlpage("pd$dnum.html", "Photographs", $html);
				$dnum++;
			}
			$html = '<h3>' . ($date = local_date($x->{time})) . '</h3>';
			$html .= '<table border=0>' if ($make_captions);
			$html2 .= "<li> <a href=\"pd$dnum.html\">$date</a>";
		}
		$html .= "<tr><td>" if ($make_captions);
		$html .= "<a href=\"$x->{url}\"><img border=0 src=\"thumb/$x->{photo}\"></a>\n";
		$caption = "$x->{caption}<br>" .
		($date_break ? local_hms($x->{time}) : local_datetime($x->{time})) .
		"<br>About (most recent fix taken " . time_form($x->{timediff}) . " from the picture time) " .
		mapinfo($x->{tlat}, $x->{tlon}, "Approach") . " " .
		track_point_name($x) . "\n" .
		"<br>$x->{map}\n";
		if ($make_captions) {
			$html .= "</td><td>$caption</td></tr>\n";
		}
		if ($makethumb && !-r "thumb/$x->{photo}") {
			if (-r 'nul') {
				# Windows
				system("djpeg -scale 1/8 $x->{photo} out.tmp");
				system("cjpeg out.tmp thumb/$x->{photo}");
				unlink("out.tmp");
			} else {
				system("djpeg -scale 1/8 $f | cjpeg >thumb/$x->{photo}");
			}
		}
		htmlpage($x->{url}, $x->{caption}, "<img border=0 src=\"$x->{photo}\"><p>$caption");
	}
	$html .= '</table>';
	if (defined($date)) {
		# Unfortunately the are two copies of this code in the subroutine
		htmlpage("pd$dnum.html", "Photographs", $html);
	}
	$html2 .= "</ul>\n";
	return ($date_break ? $html2 : $html);
}

sub
mapmake
{
	print "@_[0]\n";
}

# Draw a Mercator-projection map
# Map type (for GMT maps):
# 0 xsize * ysize map w/o annotation
# 1 longitude * latitude map (xsize wide) with annotation
sub
mercator
{
	my($name, $latmin, $latmax, $lonmin, $lonmax, $xsize, $ysize, $maptype) = @_;
	my(@cmd);

	if ($plan9_map) {
		# Adjust for map(7) conventions
		$lonmin = -$lonmin;
		$lonmax = -$lonmax;
		push(@cmd, sprintf("map mercator -m cil -W $xsize $ysize -p %.2f %.2f %.2f -l %.0f %.0f %.0f %.0f -g 0 -t map.tra",
			($latmax + $latmin) / 2,
			($lonmax + $lonmin) / 2,
			max(($latmax - $latmin), ($lonmax - $lonmin)),
			$latmin - 20,
			$latmax + 20,
			$lonmin - 20,
			$lonmax + 30));
	} else {
		my($latext, $lonext);
		$latext = abs(merclat($latmax) - merclat($latmin));
		$lonext = abs($lonmax - $lonmin);
		if ($maptype == 0) {
			# Adjust longitude or latitude
			if ($latext > $lonext) {
				print "Adjusting longitude\n";
				$lonmin -= ($latext - $lonext) / 2;
				$lonmax += ($latext - $lonext) / 2;
				$lonext = $latext;
			} else {
				$latmin -= ($lonext - $latext) / 2;
				$latmax += ($lonext - $latext) / 2;
				$latext = $lonext;
			}
		}
		# Calculate ysize
		$ysize = $latext / $lonext * $xsize;
		# Set an appropriate border parameter
		# Specify annotation grid and frame every 1'
		my($border, $mindiff);
		if ($maptype == 1) {
			# Difference in minutes; we try to have about 5 annotations at the top
			$mindiff = $lonext * 60;
			if ($mindiff < 2) {
				$border = '-Ba15cf5cg5c';
			} elsif ($mindiff < 6) {
				$border = '-Ba1mf15cg15c';
			} elsif ($mindiff < 30) {
				$border = '-Ba5mf1mg1m';
			} elsif ($mindiff < 90) {
				$border = '-Ba15mf5mg5m';
			} elsif ($mindiff < 200) {
				$border = '-Ba30mf10mg10m';
			} else {
				$border = '-Ba60mf10mg10m';
			}
		}
		# Inflate the extend by 10%
		$lonmin -= $lonext * .1;
		$lonmax += $lonext * .1;
		$latmin -= $latext * .1;
		$latmax += $latext * .1;
		my($parms);
		# We give ghostscript -r 100 dpi resolution, hence size / 100 scale
		$lonmin = sprintf("%.4g", $lonmin);
		$lonmax = sprintf("%.4g", $lonmax);
		$latmin = sprintf("%.4g", $latmin);
		$latmax = sprintf("%.4g", $latmax);
		my($range) = ("-R$lonmin/$lonmax/$latmin/$latmax");
		$parms = "$range -P -JM" . $xsize / 100 . 'i';

		# Draw map
		if ($maptype == 0) {
			push(@cmd, "grdraster 2 -V $range -Gmap.grd");


			push(@cmd, 'makecpt -T-6400/6000/1 -Z -Ctopo | sed "/^N/d" >t.cpt');
			push(@cmd, "echo N	128	128	255 >>t.cpt");
			push(@cmd, "grdimage map.grd -K -V -G0 -P $parms -Ct.cpt >map.ps");
			push(@cmd, "unlink map.grd");
		} else {
			push(@cmd, "pscoast -K $parms $border -Ia -Na W0.25p/255/255/255 -G0/255/0 -S0/0/255 -Df >map.ps");
		}
		# Draw track
		push(@cmd, "psxy -O -K -M $parms -W2/255/0/0 map.tra >>map.ps");

		# Eliminate features falling on each other
		open(INFEAT, 'map.txt') || die;
		open(OUTFEAT, '>nmap.txt') || die;
		# Degrees per inch
		my($deg_pi) = 1 / ($xsize / 100 / $lonext);
		my(@rect);
		line: while (<INFEAT>) {
			my($lon, $lat, $fontsize, $font, $angle, $al, @text) = split;
			my($text) = join(' ', @text);
			$lat = merclat($lat);
			my($left) = $lon;
			my($right) = $lon + length($text) * $fontsize / 72 * $deg_pi;
			my($bot) = $lat;
			my($top) = $lat + $fontsize / 72 * $deg_pi;
			my($r);
			for $r (@rect) {
				next line if ($bot < $r->{top} &&
				    $top > $r->{bot} &&
				    $left < $r->{right} &&
				    $right > $r->{left});
			}
			$r->{right} = $right;
			$r->{top} = $top;
			$r->{bot} = $bot;
			$r->{left} = $left;
			push(@rect, $r);
			print OUTFEAT;
		}
		close(INFEAT); unlink('map.txt');
		close(OUTFEAT);
		rename('nmap.txt', 'map.txt');

		# Draw a circle for every feature
		push(@cmd, "psxy -O -K -Sc.05i -G0/0/0 $parms map.txt >>map.ps");
		# Draw feature names
		push(@cmd, "pstext -O $parms map.txt >>map.ps");

	}
	if ($makemap) {
		my($cmd);
		for $cmd (@cmd) {
			print "$cmd\n";
			system($cmd);
		}
		gifmap($name, $xsize, $ysize);
		# system("copy map.ps $name.ps") if ($maptype == 1);
	}
}

# Add a point to the track file
sub
track_point
{
	my($lat, $lon) = @_;

	if ($plan9_map) {
		print TRACK $lat, " ", -$lon, "\n";
	} else {
		print TRACK $lon, " ", $lat, "\n";
	}
}

# Break a line segment in the track file
sub
track_break
{
	if ($plan9_map) {
		print TRACK "\"\n";
	} else {
		print TRACK ">\n";
	}
}

# Draw a feature name on the map
sub
feature
{
	my($lat, $lon, $name) = @_;

	if ($plan9_map) {
		print TRACK $lat, " ", -$lon, "\n";
		print TRACK "\"$name\n";
	} else {
		# 12 point Helvetica Angle Center Middle
		print FEAT "$lon $lat 12 0 0 LB $name\n";
	}
}

# Return the latitude angle of a mercator projection
sub
merclat
{
	my($lat) = @_;
	my($slat);

	$slat = sin($lat / 180 * $PI);
	$lat = log((1 + $slat) / (1 - $slat)) / 2;
	$lat = $lat / $PI * 180;
}


# Create earth perspective map
sub
earthmap
{
	my($latmin, $latmax, $lonmin, $lonmax) = @_;
	my(@cmd);

	open(TRACK, ">map.tra");
	track_point($latmin - 2, $lonmin - 2);
	track_point($latmin - 2, $lonmax + 2);
	track_point($latmax + 2, $lonmax + 2);
	track_point($latmax + 2, $lonmin - 2);
	track_point($latmin - 2, $lonmin - 2);
	close(TRACK);
	if ($plan9_map) {
		# Adjust for map(7) conventions
		$lonmin = -$lonmin;
		$lonmax = -$lonmax;
	}
	my($latcen, $loncen) = (($latmax + $latmin) / 2, ($lonmax + $lonmin) / 2);
	if ($plan9_map) {
		push(@cmd, sprintf("map perspective 1.5 -o %g %g -W 300 300 -t map.tra",
			$latcen, $loncen));
	} else {
		my($range) = ('-R0/359/-89/89');
		my($map) = ("$range -JG$latcen/$loncen/3i -P");
		push(@cmd, "grdraster 1 -I30m -V $range -Gmap.grd");
		push(@cmd, "makecpt -Ctopo >t.cpt");
		push(@cmd, "grdimage map.grd -K -T -G0 -P $map -Ct.cpt >map.ps");
		# Draw Box
		#push(@cmd, "psxy -O -Ss.05i -G255/0/0 $map map.tra >>map.ps");
		push(@cmd, "psxy -O $map -W6/255/0/0 map.tra >>map.ps");
	}
	if ($makemap) {
		my($cmd);
		for $cmd (@cmd) {
			print "$cmd\n";
			system($cmd);
		}
		gifmap('persp.gif', 300, 300);
	}
}
