#!/usr/bin/perl =head1 NAME gpx2ipod - GPX to iPod Notes converter =head1 SYNOPSIS gpx2ipod [options] [ ...] =head1 OPTIONS -h|-? Help =cut # This script was modified by Jan Gosmann (http://www.hyper-world.de) # The unmodified version is the gpx2html script from the geo-* package by # Rick Richardson (http://http://geo.rkkda.com/) who has modified the script # itself whereby he modified the version from # http://www.fizzymagic.net/Geocaching/gpx2html/gpx2html.html use strict; use XML::Twig; use Math::Trig; use Time::Local; use Time::localtime; use Date::Manip; # RER mod: 11/16/06 next line requires => use use HTML::Entities; use utf8; binmode(STDOUT, ":utf8"); sub usage { system "pod2text $0"; exit 1; } # # process command line arguments # use Getopt::Std; my %Opt; use vars qw(%Opt); getopts("h\?", \%Opt); if ($Opt{h} || $Opt{'?'}) { &usage(); } my $Version = '1.0'; my $VDate = '6/30/2008'; Date::Manip::Date_Init("TZ=PST8PDT"); print "gpx2ipod GPX to iPod Notes Processor version $Version $VDate\n\n"; my %FoundTypes = ( "Didn\'t find it" => 'Not Found', 'Found it' => 'Found', 'Needs Archived' => 'Archive Request', 'Other' => 'Note', 'Unknown' => 'Unknown', 'Archive (show)' => 'Archived', 'Archive (no show)' => 'Archived', 'Write note' => 'Note', 'Attended' => 'Attended', 'Will Attend' => 'Will Attend', 'Green' => 'Green' ); my %CacheTypeIDs = ( 'Traditional' => 'C', 'Multi' => 'M', 'Virtual' => 'V', 'Event' => 'E', 'Unknown' => 'Q', 'Locationless' => 'L', 'Letterbox Hybrid' => 'LB', 'Webcam' => 'W', 'Earth' => 'G' ); my @Directions = ( 'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N' ); my @InputFiles; if ($#ARGV >= 0) { @InputFiles = (@ARGV); } else { print "Finding GPX files...\n"; GetGPXFiles(); } #default output directory name my $OutputDirectory = 'Geocaching'; MakeOutputDirectory($OutputDirectory); my $OutputLocFile = 'Caches.loc'; my %IgnoreCaches; my %CorrectedCaches; my %ReferenceLocations; my %CacheNameChanges; my %Caches; my %CacheLogs; my %CacheBugs; my %CacheNotes; my %NearestCaches; my $NumNearest = 5; my $GeneratedDate; my $ID; print "Getting ignore list...\n"; GetIgnoreCaches(); print "Getting corrections list...\n"; GetCorrectedCaches(); print "Getting reference locations...\n"; GetReferenceLocations(); print "Getting notes...\n"; GetCacheNotes(); print "Getting name changes...\n"; GetCacheNameChanges(); my $Parser = new XML::Twig( twig_handlers=>{'gpx/time' => \&GetGeneratedTime, 'gpx/wpt/groundspeak:cache' => \&GetCache}, input_filter => 'safe' ); foreach my $InputFileName (@InputFiles) { print "Cleaning input GPX file $InputFileName:\n"; CleanXML($InputFileName); print "Processing input GPX file $InputFileName:\n"; $Parser->parsefile($InputFileName); $Parser->purge; } # print "\nWriting LOC file:\n"; # MakeLocFile(); print "\nWriting iPod Notes files:\n"; foreach $ID (sort keys %Caches) { GetNearestCaches($ID, $NumNearest); MakeCacheFile($ID); } # print "\nWriting index files:\n"; # MakeIndexFile(); print "\nDone!\n"; exit; sub GetGPXFiles { opendir DIR, '.'; foreach my $FileName (reverse sort { -M "$b" cmp -M "$a" } readdir DIR) { push @InputFiles, ($FileName) if ($FileName =~ m/\.gpx$/i); } closedir DIR; } sub CleanXML { my $InputFileName = @_[0]; open (INPUTFILE, "<$InputFileName"); my @FileLines = readline(*INPUTFILE); close INPUTFILE; foreach my $Line (@FileLines) { if ($Line =~ //i) { if ($Line =~ /Cleaned/i) { print "File $InputFileName already cleaned.\n"; return; } } } open (OUTPUTFILE, ">$InputFileName"); foreach my $Line (@FileLines) { if ($Line =~ /Geocache file/i) { $Line =~ s/Geocache file/Cleaned file/i; } if ($Line =~ /\&\#/) { my $H; $Line =~ s/(\&\#((x?)\w+);)/($H=($3?hex($2):$2))>31||$H==9||$H==10||$H==13?$1:''/eig; } print OUTPUTFILE $Line; } close OUTPUTFILE; } sub MakeOutputDirectory { my $Directory = @_[0]; if (-d $Directory) { opendir DIR, $Directory; foreach my $FileName (readdir DIR) { unlink $Directory.'/'.$FileName if ($FileName =~ m/\.html/i); } } else { mkdir $Directory; } } sub GetCache { my ($t, $cache) = @_; my $wpt = $cache->parent('wpt'); my $logs = $cache->first_child('groundspeak:logs'); my $bugs = $cache->first_child('groundspeak:travelbugs'); my $ID = $wpt->first_child_text('name'); print "Parsing ID: $ID \r"; if (defined $Caches{$ID}) { print "Already parsed. \r"; return; } if ($IgnoreCaches{$ID}) { print "Ignoring... \r"; return; } my $LogString = ''; my $LatestFoundDate = 0; my $LatestDNFDate = 0; my $LatestLogDate = 0; foreach my $log ($logs->children('groundspeak:log')) { my $LogEntry = { Found => $FoundTypes{$log->first_child_text('groundspeak:type')}, FoundDate => XMLTime2Time($log->first_child_text('groundspeak:date')), FoundBy => $log->first_child_text('groundspeak:finder'), FoundNote => ProcessLogText($log->first_child_text('groundspeak:text')) }; if ($LogEntry->{FoundDate} > $LatestLogDate) { $LatestLogDate = $LogEntry->{FoundDate}; } my $foundat = $log->first_child('groundspeak:log_wpt'); if ($foundat) { $LogEntry->{FoundLat} = $foundat->att('lat'); $LogEntry->{FoundLong} = $foundat->att('lon'); } push (@{$CacheLogs{$ID}}, $LogEntry); if ($LogEntry->{Found} eq 'Not Found') { $LogString .= '-'; if ($LogEntry->{FoundDate} > $LatestDNFDate) { $LatestDNFDate = $LogEntry->{FoundDate}; } } elsif ($LogEntry->{Found} eq 'Found') { $LogString .= '+'; if ($LogEntry->{FoundDate} > $LatestFoundDate) { $LatestFoundDate = $LogEntry->{FoundDate}; } } else { $LogString .= '.'; } } foreach my $bug ($bugs->children('groundspeak:travelbug')) { my $BugEntry = { ID => $bug->{'att'}->{'ref'}, Name => $bug->first_child_text('groundspeak:name'), }; push (@{$CacheBugs{$ID}}, $BugEntry); } $Caches{$ID} = { Number => $cache->{'att'}->{'id'}, Latitude => $wpt->{'att'}->{'lat'}, Longitude => $wpt->{'att'}->{'lon'}, Name => FixCacheName($cache->first_child_text('groundspeak:name')), Type => FixCacheType($cache->first_child_text('groundspeak:type')), Size => $cache->first_child_text('groundspeak:container'), Date => XMLTime2Time($wpt->first_child_text('time')), URL => $wpt->first_child_text('url'), Symbol => $wpt->first_child_text('sym'), Placer => $cache->first_child_text('groundspeak:placed_by'), Difficulty => $cache->first_child_text('groundspeak:difficulty'), Terrain => $cache->first_child_text('groundspeak:terrain'), # rick: modification... # ShortDescription => ProcessDescriptionText($cache->first_child_text('groundspeak:short_description')), # LongDescription => ProcessDescriptionText($cache->first_child_text('groundspeak:long_description')), ShortDescription => $cache->first_child_text('groundspeak:short_description'), LongDescription => $cache->first_child_text('groundspeak:long_description'), IsHTML => $cache->first_child('groundspeak:long_description')->att('html'), # rick: end modification. Hint => $cache->first_child_text('groundspeak:encoded_hints'), DescriptiveString => $CacheTypeIDs{FixCacheType($cache->first_child_text('groundspeak:type'))}, LogString => $LogString, LastFound => $LatestFoundDate, LastLog => $LatestLogDate, LastDNF => $LatestDNFDate, LastDate => $LatestFoundDate, Exported => GetExportedDate($GeneratedDate, $cache->first_child_text('groundspeak:exported')), Corrected => 0, OldLatitude => $wpt->{'att'}->{'lat'}, OldLongitude => $wpt->{'att'}->{'lon'}, }; # rick: modification... if ($Caches{$ID}->{IsHTML} eq "True" ) { # Do not re-encode HTML data... $Caches{$ID}->{LongDescription} = decode_entities($Caches{$ID}->{LongDescription}); # Rip out any BACKGROUND= images $Caches{$ID}->{LongDescription} =~ s/ background=[^ >]*//i; } # rick: end modification. if (defined $CorrectedCaches{$ID}) { $Caches{$ID}->{Corrected} = 1; $Caches{$ID}->{Latitude} = $CorrectedCaches{$ID}->{Latitude}; $Caches{$ID}->{Longitude} = $CorrectedCaches{$ID}->{Longitude}; } if ($Caches{$ID}->{Size} eq 'Micro') { $Caches{$ID}->{DescriptiveString} .= 'm'; } elsif ($Caches{$ID}->{Size} eq 'Small') { $Caches{$ID}->{DescriptiveString} .= 's'; } if (defined $CacheBugs{$ID}) { $Caches{$ID}->{DescriptiveString} .= 'B'; } if (index($LogString, '+') < 0 && length($LogString) < 4) { $Caches{$ID}->{DescriptiveString} .= 'U'; } if ($Caches{$ID}->{LastFound} == 0) { $Caches{$ID}->{LastDate} = $Caches{$ID}->{Date}; } $Parser->purge; } sub GetGeneratedTime { my ($t, $tm) = @_; $GeneratedDate = XMLTime2Time($tm->text); } sub MakeIndexFile { my $CacheID; my $Location; my $ModLoc; open(HTMLOUT, ">$OutputDirectory/index.html"); print HTMLOUT "Caches Sorted by ID\n"; print HTMLOUT "

Geocaches

Sorted by ID.
"; print HTMLOUT "

Reverse Sorted by ID
\n"; print HTMLOUT "Sorted by Name.
\n"; print HTMLOUT "Sorted by Log Date.
\n"; print HTMLOUT "With Travel Bugs.
\n"; print HTMLOUT "Unfound.

\n"; foreach $Location (sort keys %ReferenceLocations) { $ModLoc = lc($Location); $ModLoc =~ s/\s/_/g; print HTMLOUT "
By distance from $Location\n"; } print HTMLOUT "

\n"; foreach $CacheID (sort {$Caches{$a}->{Number} <=> $Caches{$b}->{Number}} keys %Caches) { print HTMLOUT "
$CacheID:  $Caches{$CacheID}->{Name} ($Caches{$CacheID}->{DescriptiveString})
\n"; } print HTMLOUT "
\n"; close HTMLOUT; open(HTMLOUT, ">$OutputDirectory/index_rev.html"); print HTMLOUT "Caches Reverse Sorted by ID\n"; print HTMLOUT "

Geocaches

Reverse Sorted by ID.
"; print HTMLOUT "

Sorted by ID
\n"; print HTMLOUT "Sorted by Name.
\n"; print HTMLOUT "Sorted by Log Date.
\n"; print HTMLOUT "With Travel Bugs.
\n"; print HTMLOUT "Unfound.

\n"; print HTMLOUT "

\n"; foreach $CacheID (reverse sort {$Caches{$a}->{Number} <=> $Caches{$b}->{Number}} keys %Caches) { print HTMLOUT "
$CacheID:  $Caches{$CacheID}->{Name} ($Caches{$CacheID}->{DescriptiveString})
\n"; } print HTMLOUT "
\n"; close HTMLOUT; open(HTMLOUT, ">$OutputDirectory/index_names.html"); print HTMLOUT "Caches Sorted by Name\n"; print HTMLOUT "

Geocaches

Sorted by Name.
"; print HTMLOUT "

Sorted by ID.
\n"; print HTMLOUT "Reverse Sorted by ID

\n"; print HTMLOUT "
\n"; print HTMLOUT "
"; my @Alphabet = ('A' .. 'Z'); # rick: modification 4/12/08 if ($Opt{a}) { # all in one file... foreach my $i (@Alphabet) { print HTMLOUT "$i "; } print HTMLOUT "
\n"; my $CurrentFirstLetter = '!'; foreach $CacheID (sort {uc($Caches{$a}->{Name}) cmp uc($Caches{$b}->{Name})} keys %Caches) { if ($Caches{$CacheID}->{Name} =~ /^(\w)/) { if (!($1 =~ /\d/) && (uc($1) ne $CurrentFirstLetter)) { $CurrentFirstLetter = uc($1); print HTMLOUT "
$CurrentFirstLetter
\n"; } } print HTMLOUT "
$CacheID $Caches{$CacheID}->{Name} ($Caches{$CacheID}->{DescriptiveString})
\n"; } print HTMLOUT "
\n"; close HTMLOUT; } else { # separate files... my $CurrentFirstLetter = '!'; open(LETOUT, ">$OutputDirectory/index_names_$CurrentFirstLetter.html"); print LETOUT "Caches Sorted by letter $CurrentFirstLetter\n"; print HTMLOUT "$CurrentFirstLetter "; foreach $CacheID (sort {uc($Caches{$a}->{Name}) cmp uc($Caches{$b}->{Name})} keys %Caches) { if ($Caches{$CacheID}->{Name} =~ /^(\w)/) { if (!($1 =~ /\d/) && (uc($1) ne $CurrentFirstLetter)) { $CurrentFirstLetter = uc($1); close LETOUT; open(LETOUT, ">$OutputDirectory/index_names_$CurrentFirstLetter.html"); print LETOUT "Caches Sorted by letter $CurrentFirstLetter\n"; print HTMLOUT "$CurrentFirstLetter "; } } print LETOUT "
$CacheID $Caches{$CacheID}->{Name} ($Caches{$CacheID}->{DescriptiveString})
\n"; } print LETOUT "\n"; close LETOUT; print HTMLOUT "\n"; close HTMLOUT; } # rick: end modification 4/12/08 open(HTMLOUT, ">$OutputDirectory/index_bugs.html"); print HTMLOUT "Caches With Bugs\n"; print HTMLOUT "

Geocaches

With Travel Bugs.
"; print HTMLOUT "

Sorted by ID\n"; print HTMLOUT "

\n"; foreach $CacheID (reverse sort {$Caches{$a}->{LastDate} <=> $Caches{$b}->{LastDate}} keys %Caches) { if (defined $CacheBugs{$CacheID}) { print HTMLOUT "
$CacheID:  $Caches{$CacheID}->{Name} (".MakeDate($Caches{$CacheID}->{LastDate}).")
\n"; } } print HTMLOUT "
\n"; close HTMLOUT; open(HTMLOUT, ">$OutputDirectory/index_unfound.html"); print HTMLOUT "Unfound Caches\n"; print HTMLOUT "

Geocaches

That Haven't Been Found.
"; print HTMLOUT "

Sorted by ID\n"; print HTMLOUT "

\n"; foreach $CacheID (reverse sort {$Caches{$a}->{LastDate} <=> $Caches{$b}->{LastDate}} keys %Caches) { if (index($Caches{$CacheID}->{LogString}, '+') < 0 && length($Caches{$CacheID}->{LogString}) < 4 ) { print HTMLOUT "
$CacheID:  $Caches{$CacheID}->{Name} (".MakeDate($Caches{$CacheID}->{LastDate}).")
\n"; } } print HTMLOUT "
\n"; close HTMLOUT; # rick: modification... open(HTMLOUT, ">$OutputDirectory/index_date.html"); print HTMLOUT "Caches By Log Date\n"; print HTMLOUT "

Geocaches

by Log Date.
"; print HTMLOUT "

Sorted by ID\n"; print HTMLOUT "

\n"; my $lastdate = ""; my $thiscnt = 0; foreach $CacheID (reverse sort {$Caches{$a}->{LastLog} <=> $Caches{$b}->{LastLog}} keys %Caches) { my $thisdate = MakeDate($Caches{$CacheID}->{LastLog}); if ($lastdate ne "" && ($thisdate ne $lastdate && $thiscnt >= 5) # day has changed || ($thisdate != $lastdate) # month has changed ) { print HTMLOUT "\n"; $thiscnt = 0; } print HTMLOUT ""; print HTMLOUT "" . "" . "" . "\n"; ++$thiscnt; $lastdate = $thisdate; } print HTMLOUT "

$CacheID" . $thisdate . "$Caches{$CacheID}->{Name}
\n"; close HTMLOUT; # rick: end modification. foreach $Location (sort keys %ReferenceLocations) { $ModLoc = lc($Location); $ModLoc =~ s/\s/_/g; open(HTMLOUT, ">$OutputDirectory/index_$ModLoc.html"); print HTMLOUT "Caches Sorted by Distance to $Location\n"; print HTMLOUT "

Geocaches

Sorted by Distance to $Location.
"; print HTMLOUT "

Sorted by ID.
\n"; print HTMLOUT "Reverse Sorted by ID
\n"; print HTMLOUT "Sorted by Name.
\n"; print HTMLOUT "

\n"; my @Dists = GetDistances($Location); my $MaxDistance = $ReferenceLocations{$Location}->{MaxDistance}; foreach my $RefLocation (sort {$a->[1] <=> $b->[1]} @Dists) { $CacheID = $RefLocation->[0]; my $Distance = $RefLocation->[1]; my $Bearing = $Directions[int(($RefLocation->[2] + 11.25) / 22.5)]; if ($Distance >= $MaxDistance) { last; } print HTMLOUT "
"; printf HTMLOUT "%.2f mi %s:", ($Distance, $Bearing); print HTMLOUT " $CacheID $Caches{$CacheID}->{Name} ($Caches{$CacheID}->{DescriptiveString})
\n"; } print HTMLOUT "
\n"; close HTMLOUT; } } sub MakeCacheFile { my $ID = @_[0]; my $Cache = $Caches{$ID}; my $cacheDir = $ID; utf8::upgrade($cacheDir); $cacheDir =~ s#/#;#g; $cacheDir = "$OutputDirectory/" . $cacheDir . " - " . $Cache->{Name}; $cacheDir =~ s/&#(\d+);/chr($1)/eg; $cacheDir =~ s/ä/ae/g; $cacheDir =~ s/ö/oe/g; $cacheDir =~ s/ü/ue/g; $cacheDir =~ s/Ä/Ae/g; $cacheDir =~ s/Ö/Oe/g; $cacheDir =~ s/Ü/Ue/g; $cacheDir =~ s/ß/ss/g; $cacheDir =~ s#:|\\# #g; $cacheDir = substr( $cacheDir, 0, 45 ); $cacheDir =~ s#[\s\.]*$##g; MakeOutputDirectory( $cacheDir ); my $OutputFile = "$cacheDir/description.txt"; open(IPODOUT, ">$OutputFile"); if ($] ge 5.008) { # Version 5.8, RER 03/19/06 binmode(IPODOUT, ":utf8"); } print "Generating file $OutputFile \n"; print IPODOUT ""; print IPODOUT "Description\n"; print IPODOUT "

$ID: $Cache->{Name}    ($Cache->{Difficulty}/$Cache->{Terrain})
\n"; print IPODOUT Deg2DMMLat($Cache->{Latitude}).'   '.Deg2DMMLong($Cache->{Longitude})."
\n"; if ($Cache->{Corrected}) { print IPODOUT "Was: ".Deg2DMMLat($Cache->{OldLatitude}).'   '.Deg2DMMLong($Cache->{OldLongitude})."
\n"; } print IPODOUT "Type: $Cache->{Type}     Size: $Cache->{Size}
\n"; print IPODOUT "Placed: ".MakeDate($Cache->{Date})." by $Cache->{Placer}
\n"; if ($Cache->{LastFound} > 0) { print IPODOUT "$Cache->{LogString}"; print IPODOUT "    Last Found: ".MakeDate($Cache->{LastFound}); } print IPODOUT "

\n"; print IPODOUT ProcessDescriptionText($Cache->{ShortDescription}); print IPODOUT ProcessDescriptionText($Cache->{LongDescription}); if (defined($CacheNotes{$ID})) { print IPODOUT '

Notes:

'.$CacheNotes{$ID}."
\n"; } print IPODOUT "

Cache ".$Cache->{Number}." Exported ".MakeDate($Cache->{Exported})."

\n"; close IPODOUT; if (defined $CacheBugs{$ID}) { $OutputFile = "$cacheDir/tbugs.txt"; open(IPODOUT, ">$OutputFile"); print IPODOUT ""; print IPODOUT "Travel Bugs\n"; print IPODOUT "
Travel Bugs:\n"; my @Bugs = (@{$CacheBugs{$ID}}); foreach my $Bug (@Bugs) { print IPODOUT "
$Bug->{Name}\n"; } close IPODOUT; } if (length($Cache->{Hint}) > 0) { $OutputFile = "$cacheDir/hints.txt"; open(IPODOUT, ">$OutputFile"); print IPODOUT ""; print IPODOUT "Hints\n"; my $Hint = ProcessHintText($Cache->{Hint}); print IPODOUT "$Hint\n"; close IPODOUT; } $OutputFile = "$cacheDir/nearest.txt"; open(IPODOUT, ">$OutputFile"); binmode(IPODOUT, ":utf8"); print IPODOUT ""; print IPODOUT "Nearest Caches\n"; my $i; my @NearCaches = (@{$NearestCaches{$ID}}); foreach my $Near (@NearCaches) { my $TempID = $Near->[0]; my $Distance = $Near->[1]; my $Bearing = $Directions[int(($Near->[2] + 11.25) / 22.5)]; printf IPODOUT "%.2f mi %s:", ($Distance, $Bearing); my $tcacheDir = $TempID; utf8::upgrade($tcacheDir); $tcacheDir =~ s#/#;#g; $tcacheDir = "/$OutputDirectory/" . $tcacheDir . " - " . $Caches{$TempID}->{Name}; $tcacheDir =~ s/&#(\d+);/chr($1)/eg; $tcacheDir =~ s/ä/ae/g; $tcacheDir =~ s/ö/oe/g; $tcacheDir =~ s/ü/ue/g; $tcacheDir =~ s/Ä/Ae/g; $tcacheDir =~ s/Ö/Oe/g; $tcacheDir =~ s/Ü/Ue/g; $tcacheDir =~ s/ß/ss/g; $tcacheDir =~ s#:|\\# #g; $tcacheDir = substr( $tcacheDir, 0, 46 ); $tcacheDir =~ s#[\s\.]*$##g; print IPODOUT " $TempID   $Caches{$TempID}->{Name} ($Caches{$TempID}->{DescriptiveString})
"; } if (defined $CacheLogs{$ID}) { $OutputFile = "$cacheDir/logs.txt"; open(IPODOUT, ">$OutputFile"); print IPODOUT ""; print IPODOUT "Logs\n"; my @Logs = (@{$CacheLogs{$ID}}); foreach my $Log (@Logs) { print IPODOUT "

$Log->{Found} on ".MakeDate($Log->{FoundDate})." by $Log->{FoundBy}:\n"; if ($Log->{FoundLat} != 0) { print IPODOUT "
".Deg2DMMLat($Log->{FoundLat}).'   '.Deg2DMMLong($Log->{FoundLong})."

\n"; } else { print IPODOUT "

\n"; } print IPODOUT "$Log->{FoundNote}\n"; } close IPODOUT; } } sub GetExportedDate { my ($FileDate, $RecordDate) = @_; if (length($RecordDate) > 0) { return XMLTime2Time($RecordDate); } else { return $FileDate; } } sub MakeLocFile { print "Generating file $OutputLocFile\n"; open(OUTPUT, ">$OutputLocFile"); print OUTPUT "\n"; print OUTPUT "\n"; foreach my $ID (keys %Caches) { my $Cache = $Caches{$ID}; my $Name = $Cache->{Name}; my $Diff; my $ModID = $ID; $Name =~ s/[,|\'|\"]//g; $Name =~ s/[\x80-\xFF]+//ig; $Name =~ s/\&\#\d+?\;//ig; $Name =~ s/\&\w+?\;//ig; foreach my $NewName (keys %CacheNameChanges) { my $InputName = $CacheNameChanges{$NewName}->{InputName}; my $OutputName = $CacheNameChanges{$NewName}->{OutputName}; $InputName =~ s/(\W)/\\$1/g; $OutputName =~ s/(\W)/\\$1/g; $Name =~ s/$InputName/$OutputName/i; } $Diff = " ($Cache->{Difficulty}-$Cache->{Terrain})"; $Name = uc($Name).uc($Diff); my $Lat = $Cache->{Latitude}; my $Lon = $Cache->{Longitude}; my $URL = $Cache->{URL}; my $Symbol = $Cache->{Symbol}; substr($ModID,1,1) = substr($Cache->{DescriptiveString},0,1); if (substr($Cache->{DescriptiveString},1,1) eq "m") { substr($ModID,1,1) = lc(substr($ModID,1,1)); } print OUTPUT "\n"; print OUTPUT " \n"; print OUTPUT " \n"; print OUTPUT " geocache$Symbol\n"; print OUTPUT " $URL\n"; print OUTPUT "\n"; } print OUTPUT "\n"; close OUTPUT; } sub XMLTime2Time { my $XMLTime = @_[0]; $XMLTime =~ s/[+-]\d\d:\d\d$//; $XMLTime =~ s/[a-zA-Z]*$//; my $RetTime = ParseDate($XMLTime); return $RetTime; } sub MakeDate { my $Time = @_[0]; my $Text = UnixDate($Time, "%m\/%d\/%Y"); return $Text; } sub GetNearestCaches { my ($ID, $NumToStore) = @_; my $DegToRad = 3.1415926535 / 180.; my @Dists = (); my @Nearest = (); my $Cache = $Caches{$ID}; my $RefLat = $Cache->{Latitude} * $DegToRad; my $RefLong = $Cache->{Longitude} * $DegToRad; my ($Lat, $Long, $DeltaLong); foreach my $CacheID (keys %Caches) { $Lat = $Caches{$CacheID}->{Latitude} * $DegToRad; $Long = $Caches{$CacheID}->{Longitude} * $DegToRad; $DeltaLong = $Long - $RefLong; my $Distance = 3956.088331 * acos(sin($RefLat) * sin($Lat) + cos($RefLat) * cos($Lat) * cos($DeltaLong)); my $Bearing = atan2(sin($DeltaLong)*cos($Lat), cos($RefLat)*sin($Lat)-sin($RefLat)*cos($Lat)*cos($DeltaLong)) / $DegToRad; $Bearing += 360. if $Bearing < 0.; push @Dists, [ $CacheID, $Distance, $Bearing ]; } @Nearest = sort {$a->[1] <=> $b->[1]} @Dists; shift @Nearest; $NearestCaches{$ID} = [ (@Nearest)[0 .. $NumToStore-1] ]; } sub GetDistances { my $Location = @_[0]; my @Dists = (); my $DegToRad = 3.1415926535 / 180.; my $RefLat = $ReferenceLocations{$Location}->{Latitude} * $DegToRad; my $RefLong = $ReferenceLocations{$Location}->{Longitude} * $DegToRad; my ($Lat, $Long); foreach my $CacheID (keys %Caches) { $Lat = $Caches{$CacheID}->{Latitude} * $DegToRad; $Long = $Caches{$CacheID}->{Longitude} * $DegToRad; my $DeltaLong = $Long - $RefLong; my $Distance = 3956.088331 * acos(sin($RefLat) * sin($Lat) + cos($RefLat) * cos($Lat) * cos($DeltaLong)); my $Bearing = atan2(sin($DeltaLong)*cos($Lat), cos($RefLat)*sin($Lat)-sin($RefLat)*cos($Lat)*cos($DeltaLong)) / $DegToRad; $Bearing += 360. if $Bearing < 0.; push @Dists, [ $CacheID, $Distance, $Bearing ]; } return @Dists; } sub ProcessLogText { my $LogText = @_[0]; $LogText =~ s/\[(\/*[i|b|u])\]/<\1>/gi; $LogText =~ s/\[br\]/
/gi; return $LogText; } sub Deg2DMMLat { my $Latitude = @_[0]; my $Ret = sprintf "%s %d %.3f", (($Latitude > 0.)?'N':'S', int(abs($Latitude)), (abs($Latitude) - int(abs($Latitude)))*60.); return $Ret; } sub Deg2DMMLong { my $Longitude = @_[0]; my $Ret = sprintf "%s %d %.3f", (($Longitude > 0.)?'E':'W', int(abs($Longitude)), (abs($Longitude) - int(abs($Longitude))) * 60.); return $Ret; } sub FixCacheType { my $Type = @_[0]; $Type =~ s/[ |-]*Cache//gi; $Type =~ s/ \(Reverse\)//gi; return $Type; } sub FixCacheName { my $Name = @_[0]; if (length $Name == 0) { $Name = 'Generic Cache'; } $Name =~ s/\"//g; $Name =~ s/^\s+//g; # Next two lines RER 06/18/06 $Name =~ s/^The //g; $Name =~ s/^A //g; return $Name; } sub EncodeHint { my $Hint = Rot13(@_[0]); $Hint =~ s/(\[.+?\])/Rot13($&)/eg; $Hint =~ s/<.+?>//g; return $Hint; } sub Rot13 { my $Text = @_[0]; $Text =~ tr/A-Za-z/N-ZA-Mn-za-m/; return $Text; } sub ProcessDescriptionText { my $Text = @_[0]; $Text =~ s/<(p|P)(\s.*?|)>/[[!p!]]/gi; $Text =~ s/<\/(p|P)(\s.*?|)>/[[!\/p!]]/gi; $Text =~ s/<(b|B)(r|R)(\s.*?|\/|)>/[[!br!]]/gi; $Text =~ s/<\/(b|B)(r|R)(\s.*?|)>/[[!\/br!]]/gi; $Text =~ s/<.*?>//gi; $Text =~ s/\[\[!//gi; return $Text; } sub ProcessHintText { my $Hint = @_[0]; $Hint =~ s/<(\/*[i|b|u])>/\[\1\]/gi; # $Hint =~ s/
/\[br\]/gi; $Hint =~ s/<.+?>//g; $Hint =~ s/\[(\/*[i|b|u])\]/<\1>/gi; $Hint =~ s/\[br\]/
/gi; return $Hint; } sub GetIgnoreCaches { if (-r 'IgnoreCaches.txt') { open(IGNOREFILE, " GetCoord($Fields[1]), Longitude => GetCoord($Fields[2]) }; } } close CORRECTFILE; } } sub GetReferenceLocations { if (-r 'RefLocations.txt') { open(REFFILE, "= 3) { $MaxDistance = $Fields[3]; } if ($#Fields >= 2) { $ReferenceLocations{$Fields[0]} = { Latitude => GetCoord($Fields[1]), Longitude => GetCoord($Fields[2]), MaxDistance => $MaxDistance }; } } close REFFILE; } } sub SplitInputLine { my $Line = @_[0]; my @Fields = (); chomp($Line); if ($Line =~ /^\s*\#/) { return @Fields; } else { $Line =~ s/\s*\#.*//; } my @Fields = split(/\s*,\s*/, $Line); return @Fields; } sub GetCoord { my $CoordString = @_[0]; my $Result = 0; if ($CoordString =~ /\s*([N|S|E|W])\D*(\d+)\D*([\d|.]+)/i) { my $Direction = uc($1); $Result = $2 + $3 / 60.; if ($Direction eq 'S' || $Direction eq 'W') { $Result = -$Result; } } elsif ($CoordString =~ /\s*-{0,1}\d+\.\d+/) { $Result = $CoordString; } return $Result; } sub GetCacheNotes { if (-r 'Notes.xml') { my $Parser = new XML::Twig( twig_handlers=>{'note' => \&GetCacheNote}, input_filter => 'safe'); $Parser->parsefile('Notes.xml'); $Parser->purge; } } sub GetCacheNote { my ($t, $note) = @_; my $CacheNumber = $note->{'att'}->{'id'}; my $ID = GetCacheWaypoint($CacheNumber); # print "Got note for cache $ID\n"; my $CacheNote = HTML::Entities::encode_entities($note->text); $CacheNotes{$ID} = $CacheNote; } sub GetCacheWaypoint { my $Base31 = '0123456789ABCDEFGHJKMNPQRTVWXYZ'; my $ID; my $CacheNumber = @_[0]; if ($CacheNumber < 65536) { $ID = sprintf("GC%X", $CacheNumber); } else { my $TmpNumber = ($CacheNumber - 65536) / 31; my $i; for ($i = 0; $i < 3; $i++) { my $IntVal = int(($TmpNumber - int($TmpNumber))*31 + 0.01); $ID .= substr($Base31, $IntVal, 1); $TmpNumber /= 31; } my $IntVal = int(($TmpNumber - int($TmpNumber))*31 + 0.01) + index($Base31, 'G'); $ID .= substr($Base31, $IntVal, 1); $ID = 'GC'.reverse $ID; } return $ID; } sub GetCacheNameChanges { if (-r 'CacheNameChanges.txt') { open(NAMEFILE, " $Fields[0], OutputName => $Fields[1] }; } } close NAMEFILE; } }