#!/usr/bin/perl # Copyright James Mastros (theorbtwo) 2004. Released under your # choice of the Perl Artistic License and the GNU General Public # License. # Currently maintained/updated by Bart Lateur (c) 2010 use warnings; use strict; use LWP::Simple 'mirror', 'get', '$ua', 'RC_OK', 'RC_NOT_MODIFIED'; use XML::Simple; # use Data::Dump::Streamer qw(Dump Dumper); use IO::File; use Storable; use charnames ':full'; use POSIX 'strftime'; use Carp; use List::Util 'shuffle'; use Getopt::Long; # use HTML::Entities 'decode_entities'; $|++; my $dir; GetOptions( 'dir=s' => \$dir, # directory to read/write all the files ) or die "Command line options error, aborting"; my $searchdiroption = ''; if($dir) { require File::Spec; require Cwd; my $cwd = Cwd::cwd(); chdir $dir or die "Cannot chdir to '$dir': $!"; $searchdiroption = "-searchdir " . File::Spec->abs2rel($cwd); } use constant PI => 4*atan2(1,1); my $locations = [ # Cities { re => qr/\bLittle Rock\b/i, lat => 34.7333, long => -92.2833, qual => 'city', }, { re => qr/\bSimferopol\b/i, lat => 44.9500, long => 34.1000, qual => 'city', }, { re => qr/\bfort lauderdale\b/i, lat => 26.1167, long => -80.1333, qual => 'city', }, { re => qr/\bMoscow\b/i, lat => 55.75, long => 37.7, qual => 'city', }, { re => qr/\blublin\b/i, # Poland lat => 51.3, long => 22.5167, qual => 'city', }, { re => qr/\bIstanbul\b/i, # Turkye lat => 41.0333, long => 28.9500, qual => 'city', warn => 1, }, { # prasadbabu: [theorbtwo] pondicherry includes mahe, # karaikal, yanam and pudhuchery.(the above # mentioned 4 areas located in different parts of # india), but i am residing in puchuchery which is # capital of pondichery. re => qr/\bpondicherry\b|\bpudhuchery\b|\bpondichery\b/i, lat => 11.56, long => 79.53, qual => 'city', }, { re => qr/\bMo[jg]i Mirim/i, # Brazil lat => -(22+29/60), long => -(49+55/60), qual => 'city', }, { re => qr/\bBangalore\b/i, lat => 12+58/60, long => 77+35/60, qual => 'city', }, { re => qr/\bKamloops\b/i, lat => 50+39/60, long => -(120+24/60), qual => 'city', }, { re => qr/\bManila\b/i, lat => 14+37/60, long => 120+58/60, qual => 'city', }, { re => qr/\bSeattle\b/i, lat => 47.6, long => -(122+19/60), qual => 'city', }, { re => qr/\bCincinnati\b/i, lat => 39+9/60, long => 84+27/60, qual => 'city', }, { re => qr/\bPondicherry\b/i, lat => 11+59/60, long => 79+50/60, qual => 'city', }, { re => qr/\bAugsburg\b/i, lat => 48+21/60, long => 10+54/60, qual => 'city', }, { re => qr/\bBielefeld\b/i, lat => 52+2/60, long => 8+32/60, qual => 'city', }, { re => qr/\bBedford\b/i, lat => 52+8/60, long => -29/60, qual => 'city', }, { re => qr/\bCairns\b/i, lat => -(16+51/60), long => 145+43/60, qual => 'city', }, { re => qr/\bPorto Alegre\b/i, lat => -30.05, long => -(51+10/60), qual => 'city', }, { re => qr/\bBoise\b/i, lat => 43+36/60, long => -(116+12/60), qual => 'city', }, { re => qr/\bHouston\b/i, lat => 29.75, long => -(95+21/60), qual => 'city', }, { # Russia re => qr/\bYekaterinburg\b/i, lat => 56+52/60, long => 60+35/60, qual => 'city', }, { # Brazil # http://hamcall.net/call/pp5jd re => qr/\bFloripa\b/i, lat => -(27+35/60+45/3600), long => 48+35/60+20/3600, qual => 'city', }, { # Indonesia re => qr/\bBandung\b/i, lat => -(6+57/60), long => 107+34/60, qual => 'city', }, { re => qr/\bWeert\b/i, lat => 51+15/60, long => 5+42/60, qual => 'city', }, { re => qr/\bVienna\b|\bWien\b/i, lat => 48+13/60, long => 16+22/60, qual => 'city', }, { re => qr/\bPortland\b|\bPDX\b/i, lat => 45+31/60, long => -(122+40/60), qual => 'city', }, { re => qr/\bMadrid\b/i, lat => 40+30/60, long => -(3+40/60), qual => 'city', }, { re => qr/\bKarlsruhe\b/i, lat => 49+3/60, long => 8+24/60, qual => 'city', }, { re => qr/\bBuffalo\b/i, lat => 42+53/60, long => -(78+52/60), qual => 'city', }, { re => qr/\bPasadena\b/i, lat => 34+8/60, long => -(118+8/60), qual => 'city', }, { re => qr/\bDublin\b/i, lat => 53+20/60, long => -6.25, qual => 'city', }, { re => qr/\bCoventry\b/i, lat => 52+25/60, long => -1.5, qual => 'city', }, { # In the UK, not Alabama re => qr/\bBirmingham\b/i, lat => 52.5, long => -(1+50/60), qual => 'city', }, { re => qr/\bRiedstadt\b/i, lat => 49+50/60, long => 8.5, qual => 'city', }, { re => qr/\bSaarbr(ue|\x{00fc})cken\b/i, lat => 49+15/60, long => 5+58/60, qual => 'city', }, { re => qr/\bM(ue|\x{00fc})nchen\b|\bMunich\b/i, lat => 48+8/60, long => 11+35/60, qual => 'city', }, { re => qr/\bHuddersfield\b/i, lat => 53+38/60+7/60/60, long => -(1+46/60+9/60/60), qual => 'city', }, { re => qr/\bSan Ramon\b/i, lat => 37+46/60, long => -(121+58/60), qual => 'city', }, { # Some people mention the fault, and this will match it -- # Lets hope it's close enough. re => qr/\bSan Andreas\b/i, lat => 38+11/60, long => -120+40/60, qual => 'city', }, { re => qr/\bHeidelberg\b/i, lat => 49+25/60, long => 8.7, qual => 'city', }, { re => qr/\bBerlin\b/i, lat => 52+32/60, long => 13+25/60, qual => 'city', }, { re => qr/\bLeopoldsh(\x{00f6}|oe)ne\b/i, lat => 52+1/60, long => 8.7, qual => 'city', }, { re => qr/\bLiverpool\b/i, lat => 53+25/60, long => -3, qual => 'city', }, { re => qr/\bRegensburg\b/i, lat => 49+1/60, long => 12.1, qual => 'city', }, { # Ha! He said Brest, Bevis! re => qr/\bBrest\b/i, lat => 48+23/60, long => -4.5, qual => 'city', }, { re => qr/\bKent\b/i, lat => 51+13/60, long => 39/60 }, { re => qr/\bAmsterdam\b/i, lat => 50+21/60, long => 4+54/60, qual => 'city', }, { re => qr/\bBudapest\b/i, lat => 47.5, long => 19+3/60, qual => 'city', }, { re => qr/\bNew Orleans\b/i, lat => 29+57/60, long => -(90+4/60), qual => 'city', }, { re => qr/\bParis\b/i, lat => 48+52/60, long => 2+20/60, qual => 'city', }, { re => qr/\bBucharest\b/i, lat => 44+26/60, long => 25+6/60, qual => 'city', }, { re => qr/\bBoston\b/i, lat => 42+21/60, long => -(71+3/60), qual => 'city', }, { re => qr/\bSan Antonio\b/i, lat => 29.4167, long => -98.4833, qual => 'city', warn => 1, }, { re => qr/\bTocumwal\b/i, lat => -35.8500, long => 145.5667, qual => 'city', warn => 1 }, { re => qr/\bOdessa\b/i, lat => 46.5000, long => 30.7667, qual => 'city', warn => 1 }, { re => qr/\bIndianapolis\b/i, lat => 39+46/60, long => -(86+9/60), qual => 'city', }, { re => qr/\bCambridge\b/i, lat => 52+12/60, long => 7/60, qual => 'city', }, { # No, not where Jed Bartlet lives; the one in England re => qr/\bManchester\b/i, lat => 53.5, long => -(2+13/60), qual => 'city', }, { re => qr/\bCape Town\b/i, lat => -(33+56/60), long => 18+28/60, qual => 'city', }, { re => qr/\bScarborough\b/i, lat => 54+17/60, long => -24/60, qual => 'city', }, { re => qr/\bOxford\b/i, lat => 51+46/60, long => 1+15/60, qual => 'city', }, { re => qr/\bCopenhagen\b/i, lat => 55+43/60, long => 12+34/60, qual => 'city', }, { re => qr/\bFalls Church\b/i, lat => 38+52/60, long => -(77+10/60), qual => 'city', }, { re => qr/\bSan Jose\b/i, lat => 47+20/60, long => -(121+53/60), qual => 'city', }, { re => qr/\bEdinburgh\b/i, lat => 55+57/60, long => -(3+13/60), qual => 'city', }, { re => qr/\bLondon\b/i, lat => 51+31/60, long => -5/60, qual => 'city', warn => 1, }, { re => qr/\bFrankfurt\b/i, lat => 50+6/60, long => 8+41/60, qual => 'city', }, { re => qr/\bAuckland\b/i, lat => 36+55/60, long => 174+47/60, qual => 'city', }, { re => qr/\bGroningen\b/i, lat => 53+13/60, long => 6+35/60, qual => 'city', }, { re => qr/Fort Collins/i, lat => 40+35/60, long => -(105+5/60), qual => 'city', }, { re => qr/Santa Monica/i, lat => 34+1/60, long => -(118+29/60), qual => 'city', }, { re => qr/\bSan Fran[sc]isco\b/i, lat => 37+46/60, long => -(122+25/60), qual => 'city', }, { re => qr/\bToronto\b/i, lat => 43+42/60, long => -(79+25/60), qual => 'city', }, { re => qr/\bPerth\b/i, lat => -(31+58/60), long => 115+79/60, qual => 'city', }, { re => qr/\bLos Angeles\b/i, lat => 34+3/60, long => -(118+14/60), qual => 'city', }, { re => qr/\bBangalore\b/i, lat => 12+85/60, long => 77+35/60, qual => 'city', }, { re => qr/\bChicago\b/i, lat => 41+51/60, long => -(87+39/60), qual => 'city', }, { re => qr/\bDenver\b.*\bColorado\b/si, warn => 1, lat => 39.7333, long => -104.9833, qual => 'city', }, { re => qr/\bPhoenix\b.*(?:\bAZ\b|\bArizona\b)/si, warn => 1, lat => 33.4333, long => -112.0667, qual => 'city', }, { re => qr/\bOttawa\b/i, lat => 45+25/60, long => -(75+43/60), qual => 'city', }, { # FIXME: This produces warnings about invalid utf8. # Why? Is it a known bug, or does it bear reporting? # Or is it notabug. re => qr/\bZ(\x{00FC}|ue|u)rich\b/i, lat => 47+23/60, long => 8+33/60, qual => 'city', }, { re => qr/\b(Saint|St\.?) Louis\b/i, lat => 38+37/60, long => -(90+11/60), qual => 'city', }, { re => qr/\bPhiladelphia\b/i, lat => 39+57/60, long => -(75+9/60), qual => 'city', }, { re => qr/\bSanta Fe\b/i, lat => 35+41/60, long => -(105+56/60), qual => 'city', }, { re => qr/\bSydney\b/i, lat => -(33+55/60), long => 151+(10/60), qual => 'city', }, { # 244776 spells it melb re => qr/\bMelb(ourne)?\b/i, lat => -(37+45/60), long => 144+58/60, qual => 'city', }, { re => qr/\bVenice\b/i, lat => 45+26/60, long => 12+20/60, qual => 'city', warn => 1, }, { re => qr/\bCarlsbad\b/i, lat => 33+9/60, long => -(117+20/60), qual => 'city', }, { re => qr/^(?=.*\b(Bordeaux)\b)(?=.*\bFrance\b)/si, lat => 44.8333, long => -0.5667, qual => 'city', warn => 1, }, { re => qr/\bPrague\b/i, lat => 50.1000, long => 14.4333, qual => 'city', warn => 1, }, { # Delhi, New Delhi: at almost identical coordinates re => qr/\bDelhi\b/i, lat => 28.6667, long => 77.2167, qual => 'city', warn => 1, }, { re => qr/\bBeijing\b/i, lat => 39.9167, long => 116.4333, qual => 'city', warn => 1, }, { # A unitary authority, whatever that is. re => qr/\bFife\b/i, lat => 56.25, long => -(3+2/60), warn => 1 }, # Should stay below all other cities in NY. { re => qr/\bnyc\b|\bnew york city\b/i, lat => 40.7, long => -74, qual => 'city', warn => 1 }, # States { re => qr/\bUtah\b|\bUT\b/i, lat => 40, long => -110, qual => 'state', }, { re => qr/bVermont\b|\bVT\b/i, lat => 44, long => -73, qual => 'state', }, { re => qr/\bAlberta\b/i, lat => 55, long => -115, qual => 'state', }, { re => qr/\bManitoba\b/i, lat => 55, long => -97, warn => 1, qual => 'region', }, { re => qr/\bGloucestershire\b/i, lat => 51+47/60, long => -(2+15/60), warn => 1, qual => 'region', }, { re => qr/\bHessen?\b/i, lat => 50.5, long => 9.25, warn => 1, qual => 'region', }, { re => qr/\bAlabama\b|\bAL\n/i, lat => 30, long => -87, warn => 1, qual => 'state', }, { re => qr/\bMaine\b|\bME\b/i, lat => 45, long => -69, warn => 1, qual => 'state', }, { re => qr/\bConnecticut\b|\bCT\b/i, lat => 41+50/60, long => -(71+50/60), warn => 1, qual => 'state', }, { re => qr/\bTennessee\b|\bTN\b/i, lat => 35.5, long => -85, warn => 1, qual => 'state', }, { # Low, Lower, with a dash or a space. re => qr/\bLow(er)?.Saxony\b/i, lat => 52+40/60, long => 9, warn => 1, qual => 'region', }, { re => qr/\bKY\b|\bKentucky\b/i, warn => 1, lat => 37, long => -85, qual => 'state', }, { re => qr/\bKS\b|\bKansas\b/i, warn => 1, lat => 38+40/60, long => -98, qual => 'state', }, { re => qr/\bWest Yorkshire\b/i, warn => 1, lat => 53+42/60, long => -(1+35/60), qual => 'state', }, { re => qr/\bNE\b|\bNebraska\b/i, warn => 1, lat => 42.25, long => -(71+50/60), qual => 'state', }, { re => qr/\bMA\b|\bMass(achusetts)?\b/i, warn => 1, lat => 42.25, long => -(71+50/60), qual => 'state', }, { re => qr/\bMD\b|\bMaryland\b/i, warn => 1, lat => 49, long => -(76+50/60), qual => 'state', }, { # Case-sensitive to avoid matching .co.uk and similar. re => qr/\bCO\b/, warn => 1, lat => 39, long => -105, qual => 'state', }, { re => qr/\bColorado\b/i, warn => 1, lat => 39, long => -105, qual => 'state', }, { re => qr/\bGA\b|\bGeorgia\b/i, warn => 1, lat => 32, long => -84, qual => 'state', }, { re => qr/\bWest Yorkshire\n/i, warn => 1, lat => 53+42/60, long => -(1+35/60), qual => 'state', }, { re => qr/\bNY\b|\bNew York\b/i, warn => 1, lat => 43, long => -75, qual => 'state', }, { re => qr/\bNJ\b|\bNew Jersey\b/i, warn => 1, lat => 40, long => -75, qual => 'state', }, { re => qr/\bIA\b|\bIowa\b/i, warn => 1, lat => 42, long => -92, qual => 'state', }, { re => qr/\bNC\b|\bNorth Carolina\b/i, warn => 1, lat => 35.5, long => -80, qual => 'state', }, { re => qr/\bAR\b|\bArkansas\b/i, warn => 1, lat => 35, long => -93, qual => 'state', }, { re => qr/\bIL\b|\bIllinois\b/i, warn => 1, lat => 40, long => -89, qual => 'state', }, { re => qr/\bAZ\b|\bArizona\b/i, warn => 1, lat => 34, long => -112, qual => 'state', }, { re => qr/\bBavaria\b/i, warn => 1, lat => 48.5, long => 11.5, qual => 'region', size => 2 }, { re => qr/\bB\.?C\.?\b|\bBritish Columbia\b/, warn => 1, lat => 55, long => -125, qual => 'region', }, { re => qr/\bChihuahua\b/i, warn => 1, lat => 28+30/60, long => -106, qual => 'state', }, { re => qr/\bMissouri\b|\bMO\b/i, warn => 1, lat => 38, long => -98, qual => 'state', }, { re => qr/\bVirginia\b|\bVA\b/i, warn => 1, lat => 37, long => -80, qual => 'state', }, { re => qr/\bBremen\b/i, warn => 1, lat => 53+3/60, long => 8+50/60, qual => 'city', }, { re => qr/\bTexas\b|\bTX\b/i, warn => 1, lat => 30, long => -100, qual => 'state', }, { re => qr/\bOregon\b|\bOR\b/i, warn => 1, lat => 44, long => -120, qual => 'state', }, { re => qr/(?<=, )WI\b/i, warn => 1, lat => 45, long => -90, qual => 'state', }, { re => qr/\bwisconsin\b/i, warn => 1, lat => 45, long => -90, qual => 'state', }, { re => qr/\bmichigan\b/i, warn => 1, lat => 44, long => -85, qual => 'state', }, { re => qr/, MI\b/i, warn => 1, lat => 44, long => -85, qual => 'state', }, { re => qr/, PA\b/i, warn => 1, lat => 40+60/60, long => -76, qual => 'state', }, { re => qr/\bPennsylvania\b/i, warn => 1, lat => 40+60/60, long => -76, qual => 'state', }, { re => qr/Calif(ornia|\.)\b|\bCA\b/i, warn => 1, lat => 37, long => -119, qual => 'state', warn => 1 }, { re => qr/\bIdaho\b|\bID\b/i, warn => 1, lat => 45, long => -115, qual => 'state', }, { re => qr/\bFlorida\b|\bFL\b/i, warn => 1, lat => 28, long => -82, qual => 'state', }, { re => qr/\bWashington\b|\bWA\b/i, warn => 1, lat => 47, long => -120, qual => 'state', }, { re => qr/\bMinnesota\b|\bMN\b/i, warn => 1, lat => 46, long => -94, qual => 'state', }, { # 405804 re => qr/\bOnt(?:\b|ario)\b/i, warn => 1, lat => 50, long => -86, qual => 'region', size => 3, }, { re => qr/\bOH\b|\bOhio\b/i, warn => 1, lat => 40, long => -(80+50/60), qual => 'state', }, { re => qr/\bSouth Carolina\b|\bSC\b/i, lat => 34, warn => 1, long => -80, qual => 'state', }, { re => qr/\bDevon\b/i, lat => 50+44/60, warn => 1, long => -(3+49/60), qual => 'region', size => 2, }, { # Case-sensistive -- "in" is too useful as a word re => qr/\bIndiana\b|\bIN\b/, warn => 1, lat => 40, long => -86, qual => 'state', }, { # General reagion, not listed in Getty, guess at city # closest to center as Melton Mowbray re => qr/\bEast Midlands\b/i, lat => 52+46/60, long => -53/60, warn => 1, qual => 'region', size => 2, }, { re => qr/\b(? 53, long => -2, warn => 1, qual => 'region', size => 2, }, # Is in the Getty Thesaurus, but doesn't give a lat/long. # Lat/Long given here is for Weser-Ems, which is larger. { re => qr/\b(East |Ost)friesland\b/i, warn => 1, lat => 52+45/60, long => 8, qual => 'region', size => 1, }, { re => qr/\bScotland\b/i, warn => 1, lat => 57, long => -4, qual => 'region', size => 2, }, { re => qr/\bMiddlesex\b/i, warn => 1, lat => 51+26/60, long => -0.5, qual => 'region', size => 2, }, # Countries { re => qr/\bHungary\b/i, lat => 47, long => 20, qual => 'country', size => 2, warn => 1, }, { re => qr/\bFinland\b/i, lat => 64, long => 26, qual => 'country', size => 3, }, { re => qr/\bMexico\b/i, lat => 23, long => -102, qual => 'country', size => 4, }, { re => qr/bIceland\b/i, lat => 65, long => -18, qual => 'country', size => 2, }, { re => qr/\bVenezuela\b/i, warn => 1, lat => 8, long => -66, qual => 'country', size => 3, }, { re => qr/\bSpain\b/i, warn => 1, lat => 40, long => -4, qual => 'country', size => 3, }, { re => qr/\bCosta Rica\b/i, warn => 1, lat => 10, long => -84, qual => 'country', size => 2, }, { re => qr/\bNew Zealand\b/i, warn => 1, lat => -42, long => 174, qual => 'country', size => 3, }, { re => qr/\bIndia\b/i, warn => 1, lat => 20, long => 77, qual => 'country', size => 4, }, { re => qr/\bBelgium\b/i, warn => 1, lat => 50+50/60, long => 4, qual => 'country', size => 2, }, { re => qr/\bFrance\b/i, warn => 1, lat => 46, long => 2, qual => 'country', size => 3, }, { # n? because it's technically the Russian Federation re => qr/\bRussian?\b/i, warn => 1, lat => 60, long => 47, qual => 'country', size => 5, warn => 1 }, { re => qr/\bBelarus\b|\bBelorussia\b/i, warn => 1, lat => 53, long => 28, qual => 'country', size => 2, }, { re => qr/\bMalta\b/i, warn => 1, lat => 35+55/60, long => 14+25/60, qual => 'country', size => 1, }, { re => qr/\bPortugal\b/, warn => 1, lat => 39.5, long => -8, qual => 'country', size => 2, }, { re => qr/\bItaly\b/i, warn => 1, lat => 42+60/60, long => 12+50/60, qual => 'country', size => 3, }, { re => qr/\bSwitzerland\b/i, warn => 1, lat => 47, long => 8, qual => 'country', size => 2, }, { re => qr/\bSweden\b/i, warn => 1, lat => 62, long => 15, qual => 'country', size => 3, }, { re => qr/\bGreece\b/i, warn => 1, lat => 39, long => 22, qual => 'country', size => 3, }, { re => qr/\bBrazil\b/i, warn => 1, lat => -10, long => -55, qual => 'country', size => 5, warn => 1 }, { re => qr/\bUkraine\b/i, warn => 1, lat => 49, long => 32, qual => 'country', size => 3, }, { re => qr/\bCroatia\b/i, warn => 1, lat => 45+10/60, long => 15.5, qual => 'country', size => 2, }, { re => qr/\bBelize\b/i, warn => 1, lat => 17+15/60, long => -(88+45/60), qual => 'country', size => 2, }, { warn => 1, re => qr/\bAustria\b/i, lat => 48+13/60, long => 16+22/60, qual => 'country', size => 2, }, # Could list canada, but TZ guessing is probably better. { re => qr/\bIreland\b|\bIRL\b/i, warn => 1, lat => 53, long => -8, qual => 'country', size => 2, }, { re => qr/\b(?:The )?Netherlands?\b|\.nl\b/i, warn => 1, lat => 52.5, long => 5.75, qual => 'country', size => 2, }, { re => qr/\bSingapore\b/, warn => 1, lat => 1+22/60, long => 103+48/60, qual => 'country', size => 2, }, { re => qr/\bGermany\b/, lat => 51.5, long => 10.5, qual => 'country', size => 3, warn => 1 }, { re => qr/\bAustralia\b/, warn => 1, lat => -25, long => 135, qual => 'country', size => 5, }, { re => qr/\bIsrael\b/i, lat => 31.5, warn => 1, long => 34.75, qual => 'country', size => 2, }, { re => qr/\b(?:The )?Gambia\b/i, warn => 1, lat => 13.5, long => -15.5, qual => 'country', size => 3, }, { re => qr/\bU\.K\.\b|\bUK\b|\bUnited Kingdom\b/i, warn => 1, lat => 54, long => -4.5, qual => 'country', size => 3, warn => 1 }, { re => qr/\bNorway\b/i, warn => 1, lat => 62, long => 10, qual => 'country', size => 3, }, { re => qr/\bBermuda\b/i, lat => 32+20/60, long => -(64+45/60), qual => 'country', size => 2, warn => 1, }, { re => qr/\bCzech Republic\b/i, lat => 49.7500, long => 15.0000, qual => 'country', size => 2, warn => 1 }, { re => qr/\bT[\xEEi]rgu[\s\-]Mure(?:[s\x{0219}\x{015F}]|&#(?:351|537);)(?!\w).*\bRomania\b/i, lat => 46.55, long => 24.5667, qual => 'region', size => 1, warn => 1, }, { re => qr/\bTransilvania\b/i, # Romania lat => 46.5, long => 25, qual => 'region', size => 2, }, { re => qr/\bFargo, ND\b/, lat => 46.8667, long => -96.7833, qual => 'city', }, ]; $ua->timeout(30); my $xml; while (!$xml) { print "Fetching Other Users..."; $xml = get('http://perlmonks.org/?node_id=15851'); if (!$xml) { print "Couldn't fetch other users, trying again...\n"; } } print "done\n"; $xml = XMLin($xml); mkdir 'work'; my $markerfile = IO::File->new('>monks.marker'); binmode($markerfile, ':utf8'); if (0) { for (-12..12) { my $attr = {timezones => $_, fontsize => 9}; my $attrstr = join ' ', map {"$_=$attr->{$_}"} keys %$attr; my $str = strftime "%l %p", gmtime time+60*60*$_; my $minute = (gmtime)[1]; # add_marker($markerfile, 0, ($_+$minute/60)*(360/24), # $str, $attr, "timestamp $_"); } } { my $attr = {'position' => 'pixel', 'image' => 'none'}; add_marker($markerfile, 10, -10, (scalar gmtime time). " GMT", $attr, "timestamp"); } # Note -- !-s is true when file does not exist, but -z is false. # Check this first so stat doesn't die. # (Thanks, [bart].) if ( !-s 'clouds_2048.jpg' || -M 'clouds_2048.jpg' > 3/24 ) { my @mirrors = (# primary mirror, see # http://xplanet.sourceforge.net/FUDforum2/index.php?t=msg&th=243&start=0&S=2e8dd28608a23261d3ea0aae99b00b76 "http://xplanet.sourceforge.net/clouds/clouds_2048.jpg", shuffle ( #"ftp://mirror.pacific.net.au/pub2/xplanet/clouds_2048.jpg", "http://www.ruwenzori.net/earth/clouds_2048.jpg", #"http://xplanet.arculeo.com/clouds_2048.jpg", #"http://xplanet.dyndns.org/clouds/clouds_2048.jpg", "http://userpage.fu-berlin.de/~jml/clouds_2048.jpg", #"http://rcswww.urz.tu-dresden.de/~es179238/clouds_2048.jpg", #"http://home.megapass.co.kr/~ysy00/cloud_data/clouds_2048.jpg", "http://home.megapass.co.kr/~holywatr/cloud_data/clouds_2048.jpg", #"http://user.chol.com/~winxplanet/cloud_data/clouds_2048.jpg", "http://home.megapass.co.kr/~gitto88/cloud_data/clouds_2048.jpg", #"http://www.wizabit.eclipse.co.uk/xplanet/files/mirror/clouds_2048.jpg", #"http://www.wizabit.eclipse.co.uk/xplanet/files/local/clouds_2048.jpg", "ftp://ftp.iastate.edu/pub/xplanet/clouds_2048.jpg", "http://xplanet.explore-the-world.net/clouds_2048.jpg" )); while(@mirrors) { my $mirror = shift @mirrors; print "Updating cloud map from $mirror..."; my $status = mirror($mirror, "clouds_2048.jpg"); print " $status\n"; if($status == RC_OK || $status == RC_NOT_MODIFIED) { last; } print "Fetching the cloud map was problematic, response code $status, for this mirror:\n" . $mirror . "\nYou ought to reconsider removing this mirror from the list.\n"; } } my ($count, $totalx, $totaly, $totalz); my @users = ('dummy'); my %usersbyname; my $n=0; # print $markerfile do {local(@ARGV, $/) = 'quake.marker'; <>}; my @unknown; foreach (reverse @{$xml->{user}}) { $n++; my $user = {n=>$n}; $user->{id} = $_->{user_id}; $user->{name} = $_->{username}; # next if $name eq 'tye'; next if $_->{username} eq 'NodeReaper'; my ($lat, $long, $attr, $props) = get_lat_long($user->{id}) or unshift @unknown, $user and next; $attr ||= {}; $attr->{fontsize} = 8; if (!$attr->{position}) { # Math from [mazem]'s [the center of perlmonk mass] my $latrad = $lat * PI/180; my $longrad = $long * PI/180; my ($x, $y, $z); $x = cos($latrad) * sin($longrad); $y = -cos($latrad) * cos($longrad); $z = sin($latrad) ; if (rand() < .001) { print "Focus on: $user->{name}\n"; $totalx += $x*100; $totaly += $y*100; $totalz += $z*100; $count += 100; $attr->{fontsize}+=2; } else { $totalx += $x*1; $totaly += $y*1; $totalz += $z*1; $count += 1; } $user->{latrad} = $latrad; $user->{longrad} = $longrad; } $user->{lat}=$lat; $user->{long}=$long; $user->{attr}=$attr; if($props) { $user->{$_} = $props->{$_} foreach keys %$props; } push @users, $user; $usersbyname{$user->{name}}=$user; add_marker($markerfile, $lat, $long, $user->{name}, $attr, $user->{id}); } { my $y = 2; my %attr = (position=>'pixel', image=>'none',fontsize => 8); foreach my $user (@unknown) { add_marker($markerfile, $y += 18, 3, $user->{name}, \%attr, $user->{id}); push @users, $user; $usersbyname{$user->{name}}=$user; } } # print "Known locations: $count\n"; my ($avgx, $avgy, $avgz) = ($totalx/$count , $totaly/$count , $totalz/$count ); my ($centerlatrad, $centerlat, $centerlongrad, $centerlong); # print "Average (x,y,z): $avgx, $avgy, $avgz\n"; $centerlatrad = atan2( $avgz, sqrt( $avgx*$avgx + $avgy*$avgy ) ); $centerlat = 180/PI * $centerlatrad; $centerlongrad = atan2( $avgx, -$avgy ); $centerlong = 180/PI * $centerlongrad; # print "Center of gravity: $centerlat $centerlong\n"; # print "Center of gravity: $centerlatrad $centerlongrad rad\n"; # print "Antipodes: $antilatrad $antilongrad (rad)\n"; # print "Latitude sum: ", $centerlatrad +$antilatrad , "\n"; # print "Longitude sum: ", $centerlongrad+$antilongrad, "\n"; # my $antipodians=10; # foreach my $user (@users) { # print "[id://$user->{id}|$user->{name}] -- lat: $user->{latrad} long: $user->{longrad}\n"; # my $gcd_front = great_circle_distance($centerlatrad, PI/2 - $centerlongrad, # $user->{latrad}, PI/2 - $user->{longrad}); # my $gcd_back = great_circle_distance($antilatrad, PI/2 - $antilongrad, # $user->{latrad}, PI/2 - $user->{longrad}); # print "B+F = ", $gcd_back+$gcd_front, "\n"; # if ($gcd_front > $gcd_back) { # print "[id://$user->{id}|$user->{name}] is an antipodian.\n"; # print " Front distance: ", $gcd_front, "\n"; # print " Back distance: ", $gcd_back , "\n"; # $user->{attr}{position}='pixel'; # $user->{attr}{color}='0xFF8888'; # $antipodians++; # $user->{lat} =-30*$antipodians; # $user->{long}=3; # my $attrstr = join ' ', map {"$_=$user->{attr}{$_}"} keys %{$user->{attr}}; # printf $markerfile "%6.4f %6.4f \"%s\" %s # %d\n", # $user->{lat}, $user->{long}, $user->{name}, $attrstr, $user->{id}; # } # } # Now we check if we have so few monks online that we'll look lonely, and figure out how to give us some space-fill. # [bart]: I don't like this, it's confusing for visitors, the connection to Perlmonks is nonexistent, so I disabled it. #if ($count < 15) { ## if (1) { # my @fillers = ( # \&parse_monger_groups, # #\&parse_earthquakes, # ); # # my $filler = $fillers[rand @fillers]; # # $filler->($markerfile); #} $markerfile->close; # Try forcing a certian viewpoint -- avoid the great bald spots (which # are there because my source for cloud data doesn't orbit over the # poles. $centerlat/=2; print "Rendering...\n"; `xplanet $searchdiroption -config xplanet.config -latitude $centerlat -longitude $centerlong -output pmplanet.jpg -num_times 1 -geometry 900x800 -quality 90 -starmap BSC -markerbounds monks.markerbounds -pango -radius 50`; print "Rendering EU...\n"; `xplanet $searchdiroption -config xplanet.config -latitude 45 -longitude 15 -output pmplanet_eu.jpg -num_times 1 -geometry 900x700 -quality 90 -starmap BSC -markerbounds monks_eu.markerbounds -pango -radius 140`; print "Making HTML...\n"; # `xplanet -config xplanet.config -latitude $antilat -longitude $antilong -output pmantiplanet.jpg -num_times 1 -geometry 800x800 -quality 90 -starmap BSC -pango`; # print $pngfile; my $markerboundsfile = IO::File->new('new('>pmplanet.html') or die "Can't open pmplanet.html for writing: $!"; binmode($htmlfile, ":utf8"); $htmlfile->print(<<'__END_TOP'); PMPlanet __END_TOP # Doing this in reverse means that we have to slurp, but puts things # in the right order into the HTML. foreach (reverse <$markerboundsfile>) { chomp; my ($x1,$y1,$x2,$y2, $text) = m/^(-?\d+),(-?\d+)\s+(-?\d+),(-?\d+)\s+(.*)$/; if ($text =~ /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) /) { # Timestamps don't get links next; } if ($text =~ / [AP]M$/) { # Timestamps don't get links next; } my $user = $usersbyname{$text}; # Assume this is something inserted by the space-filler, which doesn't get a link. next if not $user; # if ($text =~ /\D/) { # warn "Skipping non-numeric text $text\n"; # next; # } # my $user = $users[$text]; my $name = $user->{name}; # Don't output dupes into the HTML # (print "Dupe of $name\n"), next if $user->{markerbounds}; $user->{markerbounds} = [$x1, $y1, $x2, $y2]; my $url = $name; $url =~ s/([^a-zA-Z0-9_])/sprintf '%%%02x', ord($1)/ge; $url = 'http://perlmonks.org/?type=user;node=' . $url; # my $url = 'http://perlmonks.org/?node_id=' . $user->{id}; my $title = $name; if(defined $user->{lat} && defined $user->{long}) { my($lat, $long) = ($user->{lat}, $user->{long}); for ($lat, $long) { # format into degree, minutes, seconds my $s = sprintf '%.0f', abs($_ * 3600); my $m = int($s / 60); $s %= 60; my $d = int($m / 60); $m %= 60; $_ = sprintf "%s%d\xB0%d'%d\"", ($_ < 0 && '-'), $d, $m, $s; } $title .= " (lat:$lat, long:$long)"; if($user->{location}) { $title .= " $user->{qual}:" if $user->{qual}; $title .= " $user->{location}"; } elsif($user->{tz}) { $title .= " TZ=$user->{tz}"; } } foreach($name, $title) { s/&/&/g; s/'/'/g; } print $htmlfile " $name\n"; } print $htmlfile (<<'__END_MIDDLE');
__END_MIDDLE my $printedhead=0; foreach my $user (values %usersbyname) { next if $user->{markerbounds}; if (!$printedhead) { print $htmlfile " Not visible:
\n"; $printedhead++; } my $name = $user->{name}; my $url = $name; $url =~ s/([^a-zA-Z0-9_])/sprintf '%%%02x', ord($1)/ge; $url = 'http://perlmonks.org/?node=' . $url; my $title = $name; if(defined $user->{lat} && defined $user->{long}) { my($lat, $long) = ($user->{lat}, $user->{long}); for ($lat, $long) { # format into degree, minutes, seconds my $s = sprintf '%.0f', abs($_ * 3600); my $m = int($s / 60); $s %= 60; my $d = int($m / 60); $m %= 60; $_ = sprintf "%s%d\xB0%d'%d\"", ($_ < 0 && '-'), $d, $m, $s; } $title .= " (lat:$lat, long:$long)"; if($user->{location}) { $title .= " $user->{qual}:" if $user->{qual}; $title .= " $user->{location}"; } elsif($user->{tz}) { $title .= " TZ=$user->{tz}"; } } foreach($name, $title) { s/&/&/g; s/'/'/g; } print $htmlfile " $name
\n"; } # print $htmlfile "
    \n"; # foreach my $user (@users) { # next if $user eq 'dummy'; # print $htmlfile "
  1. $user->{name}
  2. \n"; # } # print $htmlfile "
\n"; $htmlfile->print(<<'__END_BOTTOM');
Names in cyan: From HTML comment.
Names in pink: Guess from location field.
Names in grey: Guess from timezone.
Names on the left: No idea of their location.
Names on right: Not visible given the current orientation of the earth.
Zoom of Europe also available.
Format for HTML comments: <!-- location:latitude:DD.MM.SS,longitude:-DD.MM.SS --> -- use - only for S latitude or W longitude. Try multimap.
By theorbtwo, using xplanet. Geographical data from The Getty Thesaurus of Geographic Names and other sources. Cloud layer from the University of Dundee, via the xplanet project. Open (ugly) Source (you'll need the config file, and the used daytime earth map too). Special thanks to jc, masem, bart, and vroom. Currently maintained by bart. __END_BOTTOM print "Done.\n"; my $storable; sub get_node_xml { my $id=shift; if (!$storable) { # Eval to catch die if it doesn't exist. eval {$storable = Storable::retrieve('cache.storable');}; $storable ||= {}; foreach (keys %$storable) { if ($storable->{$_}{time} < time-6*60*60) { print("Throwing out cached data for [id://$_]: from ", (scalar localtime $storable->{$_}{time}), "\n"); delete $storable->{$_}; } } } if ($storable->{$id}) { return $storable->{$id}{xml}; } print "Downloading XML for $id..."; my $content = LWP::Simple::get('http://perlmonks.org/?node_id='.$id.';displaytype=xml') or warn "Couldn't get XML displaytype for node id $id: $!" and return; my $xml = XMLin($content, NormaliseSpace => 2) or die "Error parsing XML for id://$id"; print "done.\n"; $storable->{$id}{xml}=$xml; $storable->{$id}{time}=time; return $xml; } sub save_storable { Storable::store($storable, 'cache.storable'); } END {save_storable} my ($DEG, $LAT, $LONG); BEGIN { $DEG = qr/[+\-]?\b\d+[\xB0.:]\s*\d\d[:.\']?(?:\d\d(?!\d)(?:\.\d*)?[\"]?|(?!\d))/; $LAT = qr/\blat(?:itude)?[=:]\s*($DEG\s*[NS]?\b)/i; $LONG = qr/\blon(?:g(?:itude)?)?[=:]\s*($DEG\s*[WE]?\b)/i; } sub deg2decimal { my($deg) = @_; my($sign, $d, $m, $s) = $deg =~ /(-?)(\d+)\W\s*(\d\d)\W?(\d\d(?:\.\d*)?)?(?!\d)/ or return undef; $m += $s / 60 if $s; $d += $m / 60; $d = -$d if $sign; $d = -$d if $deg =~ /[SW]$/i; return $d; } sub get_lat_long { my $id = shift; my $xml = get_node_xml($id) or return; # unknown my $bio = $xml->{data}{field}{doctext}{content}; my $tz = $xml->{data}{field}{timezone}{content}; my $location = $xml->{data}{field}{location}{content}; # save some memory undef $xml; $bio ||= ''; $tz ||= ''; $location ||= ''; $tz =~ s/^\n//; $tz = 'America/Denver' if $tz eq 'MST'; $tz = 'America/New_York' if $tz eq 'EST'; $tz = 'Asia/Kolkata' if $tz eq 'Asia/Calcutta'; $tz = 'Asia/Ho_Chi_Minh' if $tz eq 'Asia/Saigon'; $location =~ s/^\n//; $location =~ s|.*||; # Note that the bio has precedence, so the tag comes first. $bio = $bio . $location; $bio =~ s/°\b;?/\xB0/g; # probably harmless: $bio =~ s/&#(?:(x)([a-f\d]+)|( \d+))\b;?/chr($1 ? hex $2 : $3)/ge; # if ($bio =~ /