#!/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") . "

\n"; open(INDEX, ">index.html") || die; print INDEX htmlhead("Trip Log Presentation"); print INDEX '
'; print INDEX "

$sec. Trip Overview

$overview

"; $sec++; htmlpage('tdfeat.html', "Geographical Features and Photographs", create_timeline(1)); htmlpage('tafeat.html', "Geographical Features and Photographs", create_timeline(0)); print INDEX "

$sec. Timelines

$sec.1 Geographical features and photographs by date
$sec.2 Geographical features and photographs (all, big - for searching)
"; $sec++; htmlpage('legs.html', "Trip Legs", "Each leg represents a break of over " . time_form($legdiff) . ".

\n" . create_map(1e38, $legdiff, 0, 1, "Trip Leg Map") . "

\n"); htmlpage('parts.html', "Detailed Maps of Trip Parts", sprintf("Each part covers a linear distance of %d km.

\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") . "

\n"); print INDEX "

$sec. Maps

$sec.1 Overview
$sec.2 Trip legs
$sec.3 Detailed maps of trip parts
"; $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 "

$sec. Photographs

$sec.1 Ordered by date
$sec.2 All photographs
$sec.3 Compact index (no captions)
"; $sec++; print INDEX "

$sec. Processing Details

Track log points processed: $#tracklog
Photos processed: $photonum
Maps generated: $mapnum
Local time setting: $ENV{TZ}
Generated on: " . local_datetime(time()) . "
Processing time: " . time_form(time() - $starttime) . '
Program version: $Id: timeline.pl 1.21 2004/02/07 08:35:19 dds Exp $ '; $sec++; print INDEX '

'; # 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 "(" . "" . "topological, " . "" . "street map) "; #"" . #"world) "; } 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 () { chop; ($code, $region) = split(/\t/, $_); $region{$code} = $region; } open(IN, $fname = $datpath . 'feature.dat') || die "Unable to open $fname:$!\n"; while () { chop; ($code, $feature) = split(/\t/, $_); $feature_name{$code} = lc($feature); } } # Return a suitable HTML head given a title sub htmlhead { my($title) = @_; return " $title

$title

"; } #Return an HTML page ending sub htmltail { my($name) = @_; my($previous, $next); #print "name=$name\nnext=$next{$name}\n"; $previous = " Previous " if (defined($previous{$name})); $next = " Next " if (defined($next{$name})); return "
Trip index$previous$next "; } # 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 () { 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 () { $p = $1 if (/\([^<]+)\<\/name\>/); $time = $1 if (/\([^<]+)\<\/time\>/); $caption = $1 if (/\([^<]+)\<\/caption\>/); if(/\/) { 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 .= '

' . ($date = local_date($s->{time})) . '

'; } $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 .= "{url}\">$count "; $p->{map} .= "$title "; } } $photolist = "
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 . " (travel map).

"; 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 . ".

$photolist

"); $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 = "

    \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 .= ''; htmlpage("tld$dnum.html", "Geographical Features and Photographs", $html); $dnum++; } $html = '

    ' . ($date = local_date($x->{time})) . '

    '; $html2 .= "
  • $date"; } $html .= "
    " . ($date_break ? local_hms($x->{time}) : local_datetime($x->{time})) . "
    "; if ($x->{photo}) { $html .= " {url}\">Photograph."; $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 .= '
  • '; htmlpage("tld$dnum.html", "Geographical Features and Photographs", $html); } $html2 .= "
\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°%.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 = "
    \n"; $html = '' 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 .= '
    '; htmlpage("pd$dnum.html", "Photographs", $html); $dnum++; } $html = '

    ' . ($date = local_date($x->{time})) . '

    '; $html .= '' if ($make_captions); $html2 .= "
  • $date"; } $html .= "
  • \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}, "{photo}\">

    $caption"); } $html .= '

    " if ($make_captions); $html .= "{url}\">{photo}\">\n"; $caption = "$x->{caption}
    " . ($date_break ? local_hms($x->{time}) : local_datetime($x->{time})) . "
    About (most recent fix taken " . time_form($x->{timediff}) . " from the picture time) " . mapinfo($x->{tlat}, $x->{tlon}, "Approach") . " " . track_point_name($x) . "\n" . "
    $x->{map}\n"; if ($make_captions) { $html .= "
    $caption
    '; if (defined($date)) { # Unfortunately the are two copies of this code in the subroutine htmlpage("pd$dnum.html", "Photographs", $html); } $html2 .= "
\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 () { 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); } }