| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Acme::Geo::Whitwell::Name; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 6 |  |  | 6 |  | 157831 | use strict; | 
|  | 6 |  |  |  |  | 14 |  | 
|  | 6 |  |  |  |  | 221 |  | 
| 4 | 6 |  |  | 6 |  | 33 | use warnings; | 
|  | 6 |  |  |  |  | 11 |  | 
|  | 6 |  |  |  |  | 173 |  | 
| 5 | 6 |  |  | 6 |  | 32 | use Carp qw(croak); | 
|  | 6 |  |  |  |  | 15 |  | 
|  | 6 |  |  |  |  | 444 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 6 |  |  | 6 |  | 33 | use Exporter; | 
|  | 6 |  |  |  |  | 11 |  | 
|  | 6 |  |  |  |  | 431 |  | 
| 8 |  |  |  |  |  |  | @Acme::Geo::Whitwell::Name::ISA       = qw(Exporter); | 
| 9 |  |  |  |  |  |  | @Acme::Geo::Whitwell::Name::EXPORT_OK = qw(to_whitwell from_whitwell); | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 6 |  |  | 6 |  | 90 | use Scalar::Util qw(looks_like_number); | 
|  | 6 |  |  |  |  | 11 |  | 
|  | 6 |  |  |  |  | 8834 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | =head1 NAME | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | Acme::Geo::Whitwell::Name - Steadman Whitwell's "rational geographic nomenclature" | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | =head1 VERSION | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | Version 0.04 | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | =cut | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | our $VERSION = '0.04'; | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | use Acme::Geo::Whitwell::Name qw(to_whitwell from_whitwell); | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | # Convert Sunnyvale, CA's lat and lon to a Whitwell name pair. | 
| 30 |  |  |  |  |  |  | my @names = to_whitwell("37.37N", "122.03"); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | # Same conversion, using signed latitude and longitude instead. | 
| 33 |  |  |  |  |  |  | my @names = to_whitwell(37.37, -122.03); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | # Convert a Whitwell name to a latitude and longitude. | 
| 36 |  |  |  |  |  |  | # (Washington DC's "rational" name to N/S lat and E/W long.) | 
| 37 |  |  |  |  |  |  | my($lat_string, $lon_string) = from_whitwell("Feiro Nyvout"); | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | # If you want signed values, add signed => some true value. | 
| 40 |  |  |  |  |  |  | my($lat, $long) = from_whitwell("Feiro Nyvout", signed=>1); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | This module implements Steadman Whitwell's "rational system of geographic | 
| 45 |  |  |  |  |  |  | nomenclature", in which place names are generated by converting the latitude | 
| 46 |  |  |  |  |  |  | and longitude of the location into a two-part name by means of a | 
| 47 |  |  |  |  |  |  | transliteration scheme. | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | Whitwell devised this scheme in an attempt to provide an alternative to | 
| 50 |  |  |  |  |  |  | the proliferation of similarly-named towns in the early US. However, people | 
| 51 |  |  |  |  |  |  | seemed to prefer creating many Springfields and Washingtons in preference to | 
| 52 |  |  |  |  |  |  | using Whitwell's uniquely quirky names. | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | =head2 THE SCHEME | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | Two tables of number-to-letter(s) are used to translate latitudes and | 
| 57 |  |  |  |  |  |  | longitudes of two-decimal precision, digit-by-digit, into | 
| 58 |  |  |  |  |  |  | vaguely-pronounceable two-part names. | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | 1 2 3 4 5 6 7  8  9  0 | 
| 61 |  |  |  |  |  |  | latitude  a e i o u y ee ei ie ou  vowels | 
| 62 |  |  |  |  |  |  | longitude  b d f k l m n  p  r  t   consonants | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | Transliteration is done by looking up the apropriate digit in the tables above, | 
| 65 |  |  |  |  |  |  | switching rows until all the digits are consumed. If the coordinate is negative, | 
| 66 |  |  |  |  |  |  | a special 'sign consonant' is inserted into the (partial) name after the first | 
| 67 |  |  |  |  |  |  | vowel is added, and the transliteration continues by choosing again from the | 
| 68 |  |  |  |  |  |  | vowel table, then continuing to alternate again. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | This is very orderly, but confusing to generate by hand (putting aside the | 
| 71 |  |  |  |  |  |  | fact that no one in their right mind really wants to live in "Isilu Buban" | 
| 72 |  |  |  |  |  |  | instead of Sydney, AU, or "Feiro Nyvout" instead of Washington, DC). | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | The generated names are guaranteed to have alternating consonants and vowels, | 
| 75 |  |  |  |  |  |  | and should be pronounceable (though most likely bizarre). I have not been able | 
| 76 |  |  |  |  |  |  | to locate the original documentation of the scheme, so I am unable to determine | 
| 77 |  |  |  |  |  |  | why some example names are built in "reverse": with the first letter for the | 
| 78 |  |  |  |  |  |  | latitude selected from the longitude table, and vice versa for the longitude. I | 
| 79 |  |  |  |  |  |  | can only guess that the alternate construction was deemed more pronounceable or | 
| 80 |  |  |  |  |  |  | "interesting". Since this is the case, I generate both alternatives so you can | 
| 81 |  |  |  |  |  |  | choose the one that seems "better". In the cases of places like McMurdo Base | 
| 82 |  |  |  |  |  |  | ("Eeseepu Bymeem" or "Neeveil Amyny"), I'm not sure there I a "better". | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | However, solely for the purposes of amusement, it can be interesting to find | 
| 85 |  |  |  |  |  |  | out what a given location would have been called in the alternate universe | 
| 86 |  |  |  |  |  |  | where Whitwell's scheme caught on. | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | It would be lovely to use this module to change all the place names on | 
| 89 |  |  |  |  |  |  | online maps, wouldn't it? | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | =head2 SOURCES | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | =over | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =item * I, William E. | 
| 96 |  |  |  |  |  |  | Wilson, Indiana University Press, 1984, p. 154 | 
| 97 |  |  |  |  |  |  | =item * Search books.google.com for '"new harmony gazette" whitwell' | 
| 98 |  |  |  |  |  |  | =item * http://www.kirchersociety.org/blog/2007/05/15/whitwells-system-for-a-rational-geographical-nomenclature/ | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | =back | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | =cut | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | # These tables define the letters that the numbers will be transliterated into. | 
| 105 |  |  |  |  |  |  | #                   0  1 2 3 4 5 6 7  8  9 | 
| 106 |  |  |  |  |  |  | my @vowels     = qw(ou a e i o u y ee ei ie); | 
| 107 |  |  |  |  |  |  | my @consonants = qw(t  b d f k l m n  p  r); | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | # Allows us to detect when to insert the "sign consonant" for negative | 
| 110 |  |  |  |  |  |  | # lats and lons. | 
| 111 |  |  |  |  |  |  | my %vowel; | 
| 112 |  |  |  |  |  |  | @vowel{@vowels} = (); | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | =head1 EXPORT | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | =head2 to_whitwell($lat, $lon) | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | Generates a properly-capitalized Whitwell name from a latitude-longitude pair. | 
| 121 |  |  |  |  |  |  | Latitude and longitude are truncated to the two digits after the decimal point, | 
| 122 |  |  |  |  |  |  | in keeping with Whitwell's original scheme. Zeroes are added after the decimal | 
| 123 |  |  |  |  |  |  | point as necessary. | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | North latitudes are positve, and south latitudes are negative. East longitudes | 
| 126 |  |  |  |  |  |  | are positive, west longitudes are negative. Trailing E/W and N/S are converted | 
| 127 |  |  |  |  |  |  | into the appropriate sign. If you supply both for some reason, trailing | 
| 128 |  |  |  |  |  |  | sign indicators override signs. | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | Returns both alternatives for the name (see L). | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | =cut | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub to_whitwell { | 
| 135 | 6 |  |  | 6 | 1 | 13287 | my($lat, $lon) = @_; | 
| 136 | 6 |  |  |  |  | 12 | return ( _vowel_build($lat)     . ' ' . _consonant_build($lon), | 
| 137 |  |  |  |  |  |  | _consonant_build($lat) . ' ' . _vowel_build($lon) | 
| 138 |  |  |  |  |  |  | ); | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 23 |  |  | 23 |  | 11607 | sub _vowel_build     { _gen(shift, [\@vowels, \@consonants], 's') } | 
| 142 | 18 |  |  | 18 |  | 65 | sub _consonant_build { _gen(shift, [\@consonants, \@vowels], 'v') } | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub _gen { | 
| 145 |  |  |  |  |  |  | # The coordinate, the letter lists, and the appropriate sign consonant. | 
| 146 | 41 |  |  | 41 |  | 73 | my($coord, $lists, $neg) = @_; | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | # Turn the floating-point number into a list of digits. | 
| 149 |  |  |  |  |  |  | # Note that _two_decimal does NOT CARE about sign or sign indicators. | 
| 150 | 41 |  |  |  |  | 79 | $coord = uc(my $orig_coord = $coord); | 
| 151 | 41 |  |  |  |  | 87 | my @coord = grep {/(\d)/} (split //, _two_decimal($coord)); | 
|  | 206 |  |  |  |  | 507 |  | 
| 152 |  |  |  |  |  |  |  | 
| 153 | 41 |  |  |  |  | 75 | my $word = ''; | 
| 154 | 41 |  |  |  |  | 79 | my $list = 0; | 
| 155 | 41 |  |  |  |  | 47 | my $signed = 0; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 41 |  |  |  |  | 133 | my ($is_negative) = ($coord =~ s/[SW]//g); | 
| 158 | 41 |  |  |  |  | 102 | my ($is_positive) = ($coord =~ s/[NE]//g); | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 41 | 100 |  |  |  | 166 | croak | 
| 161 |  |  |  |  |  |  | "Coordinate '$orig_coord' does not look like a proper coordinate" | 
| 162 |  |  |  |  |  |  | if !looks_like_number($coord); | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 40 | 100 |  |  |  | 94 | $is_negative = ($coord < 0) unless $is_negative; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 40 | 100 | 100 |  |  | 150 | my $conflicting = ($is_negative and $is_positive) ? 'conflicting ' : ''; | 
| 167 | 40 | 100 | 100 |  |  | 271 | croak "Multiple ${conflicting}sign indicators detected in '$orig_coord'" | 
|  |  |  | 100 |  |  |  |  | 
| 168 |  |  |  |  |  |  | if  $conflicting or $is_negative > 1 or $is_positive > 1; | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 36 |  |  |  |  | 61 | foreach my $digit (@coord) { | 
| 171 |  |  |  |  |  |  | # Convert the next digit into a letter from the proper table. | 
| 172 | 146 |  |  |  |  | 252 | my $letter = $lists->[$list]->[$digit]; | 
| 173 |  |  |  |  |  |  | ### "$letter -> $digit" | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | # Decide whether to insert a sign consonant. | 
| 176 | 146 | 100 | 100 |  |  | 556 | if (exists $vowel{$letter} and $is_negative and not $signed) { | 
|  |  |  | 100 |  |  |  |  | 
| 177 |  |  |  |  |  |  | # If negative, we have a vowel, and we haven't inserted the sign | 
| 178 |  |  |  |  |  |  | # consonant yet, insert it. | 
| 179 | 16 |  |  |  |  | 18 | $letter .= $neg; | 
| 180 |  |  |  |  |  |  | # Now signed. | 
| 181 | 16 |  |  |  |  | 27 | $signed = 1; | 
| 182 | 16 |  |  |  |  | 19 | $list = !$list; | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  | # Add new letter(s) to word and continue; | 
| 185 | 146 |  |  |  |  | 153 | $word .= $letter; | 
| 186 | 146 |  |  |  |  | 231 | $list = !$list; | 
| 187 |  |  |  |  |  |  | } | 
| 188 | 36 |  |  |  |  | 226 | return ucfirst $word; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub _two_decimal { | 
| 192 | 78 |  |  | 78 |  | 29546 | my ($coord) = @_; | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | # Discard non-digits except for a decimal point. | 
| 195 | 78 |  |  |  |  | 296 | $coord =~ s/[^\d\.]//g; | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | # Drop leading zeros. | 
| 198 | 78 |  |  |  |  | 272 | $coord =~ s/^0*//g; | 
| 199 | 78 | 100 |  |  |  | 202 | $coord = 0 unless $coord; | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 78 | 100 |  |  |  | 247 | if (abs($coord) > 180) { | 
| 202 | 2 |  |  |  |  | 41 | croak "$coord must be between -180 and +180\n"; | 
| 203 |  |  |  |  |  |  | } | 
| 204 | 76 | 100 |  |  |  | 225 | unless ($coord =~ /\./) { | 
| 205 |  |  |  |  |  |  | # add decimals | 
| 206 | 24 |  |  |  |  | 282 | $coord .= "."; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  | # Add two more zeroes; we'll discard them if we don't need them. | 
| 209 | 76 |  |  |  |  | 109 | $coord .= "00"; | 
| 210 | 76 |  |  |  |  | 301 | ($coord) = ($coord =~ /^(\d{0,3}\.\d\d)/); | 
| 211 | 76 |  |  |  |  | 241 | return $coord; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | =head2 from_whitwell($whitwell_name, signed => $yes_or_no) | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | Converts a Whitwell name back into a lat/lon pair, in trailing indicator | 
| 217 |  |  |  |  |  |  | format.  Results will be undefined if the string does not match the Whitwell | 
| 218 |  |  |  |  |  |  | scheme; if the strings I Whitwell-compatible, but includes extra letters, | 
| 219 |  |  |  |  |  |  | these will be assumed to be further digits after the decimal point. | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | If you supply the 'signed' option with a true value, the returned values are | 
| 222 |  |  |  |  |  |  | signed numbers rather than numbers with trailing sign indicators. | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =cut | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | sub from_whitwell { | 
| 227 | 10 |  |  | 10 | 1 | 5873 | my($name, %opts) = @_; | 
| 228 | 10 |  |  |  |  | 28 | my ($lat_name, $lon_name) = split(/\s+/, $name); | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 10 |  |  |  |  | 13 | my ($value, $negative); | 
| 231 | 10 |  |  |  |  | 24 | ($value, $negative) = _coord_for(lc($lat_name)); | 
| 232 | 10 | 100 |  |  |  | 22 | if ($negative) { | 
| 233 | 5 | 100 |  |  |  | 12 | if ($opts{signed}) { | 
| 234 | 2 |  |  |  |  | 4 | $value = -$value; | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  | else { | 
| 237 | 3 |  |  |  |  | 13 | $value .= "S"; | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  | else { | 
| 241 | 5 | 100 |  |  |  | 11 | unless ($opts{signed}) { | 
| 242 | 3 |  |  |  |  | 22 | $value .= "N"; | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  | } | 
| 245 | 10 |  |  |  |  | 14 | my $lat = $value; | 
| 246 |  |  |  |  |  |  |  | 
| 247 | 10 |  |  |  |  | 22 | ($value, $negative) = _coord_for(lc($lon_name)); | 
| 248 | 10 | 100 |  |  |  | 22 | if ($negative) { | 
| 249 | 4 | 100 |  |  |  | 9 | if ($opts{signed}) { | 
| 250 | 2 |  |  |  |  | 4 | $value = -$value; | 
| 251 |  |  |  |  |  |  | } | 
| 252 |  |  |  |  |  |  | else { | 
| 253 | 2 |  |  |  |  | 7 | $value .= "W"; | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  | else { | 
| 257 | 6 | 100 |  |  |  | 11 | unless ($opts{signed}) { | 
| 258 | 4 |  |  |  |  | 17 | $value .= "E"; | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  | } | 
| 261 | 10 |  |  |  |  | 14 | my $lon = $value; | 
| 262 |  |  |  |  |  |  |  | 
| 263 | 10 |  |  |  |  | 32 | return ($lat, $lon); | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | sub _coord_for { | 
| 268 | 27 |  |  | 27 |  | 6285 | my($original) = my($string) = @_; | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | # Determine if the string starts in the vowel table or the consonant table. | 
| 271 | 27 |  |  |  |  | 47 | my @tables  = (\@consonants, \@vowels); | 
| 272 | 27 |  |  |  |  | 32 | my $vowel_found; | 
| 273 | 27 |  | 100 |  |  | 105 | my $current = ($string =~ /^[aeiouy]/) || 0; | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | # Decompose and look up the character(s). | 
| 276 | 27 |  |  |  |  | 26 | my $coord_string; | 
| 277 | 27 |  |  |  |  | 29 | my $try_sign = 0; | 
| 278 | 27 |  |  |  |  | 32 | my $is_negative = 0; | 
| 279 | 27 |  |  |  |  | 31 | my $sign_checked = 0; | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | PARSE: | 
| 282 | 27 |  |  |  |  | 49 | while ($string) { | 
| 283 |  |  |  |  |  |  | # If we need to look for the sign character, | 
| 284 |  |  |  |  |  |  | # do so. Since we've allowed names to start in either table | 
| 285 |  |  |  |  |  |  | # as seems to have been the historical precedent (yes, someone | 
| 286 |  |  |  |  |  |  | # actually did use this at least once for a real placename), | 
| 287 |  |  |  |  |  |  | # we check for both sign characters and record whether or not | 
| 288 |  |  |  |  |  |  | # we found one. | 
| 289 | 115 | 100 |  |  |  | 199 | if ($try_sign) { | 
| 290 |  |  |  |  |  |  | # Don't try more than once. | 
| 291 | 35 |  |  |  |  | 37 | $try_sign = 0; | 
| 292 | 35 | 100 |  |  |  | 83 | if ($string =~ s/^[vs]//) { | 
| 293 | 11 |  |  |  |  | 12 | $is_negative = 1; | 
| 294 |  |  |  |  |  |  | # Return to the vowel table again. | 
| 295 | 11 |  |  |  |  | 10 | $current = 1; | 
| 296 | 11 |  |  |  |  | 24 | next PARSE; | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  | # Note we've looked for the sign once, so we shouldn't look | 
| 299 |  |  |  |  |  |  | # again. This wil trap badly-placed sign characters. | 
| 300 | 24 |  |  |  |  | 26 | $sign_checked = 1; | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  | # Longer entries occur at the end of the vowel table, so | 
| 303 |  |  |  |  |  |  | # to avoid parsing 'ee' as 'e' and 'e', we try the longer | 
| 304 |  |  |  |  |  |  | # strings first. However: complicating this process is the '0' | 
| 305 |  |  |  |  |  |  | # entry, which is also a longer one, so it has to be checked first. | 
| 306 | 104 |  |  |  |  | 149 | for my $i (0, reverse 1..9) { | 
| 307 | 658 |  |  |  |  | 815 | my $char = $tables[$current]->[$i]; | 
| 308 | 658 | 100 |  |  |  | 4834 | if ($string =~ s/^$char//) { | 
| 309 |  |  |  |  |  |  | # Found it. Tack the number onto the coordinate string, | 
| 310 |  |  |  |  |  |  | # swap tables, and see if we need to check the sign. | 
| 311 | 102 |  |  |  |  | 172 | $coord_string .= $i; | 
| 312 | 102 |  | 100 |  |  | 814 | $try_sign = ($current == 1 and !$sign_checked); | 
| 313 | 102 |  |  |  |  | 108 | $current = !$current; | 
| 314 | 102 |  |  |  |  | 290 | next PARSE; | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  | # The current table should have matched, so the input string is bad. | 
| 318 | 2 |  |  |  |  | 32 | croak "Bad character or sequencing found in '$original' at '$string'"; | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  | # Insert the decimal point such that the resulting number is < 180. | 
| 321 |  |  |  |  |  |  | # This allows "high-precision" Whitwell names (constructed in some | 
| 322 |  |  |  |  |  |  | # manner other than via to_whitwell) to be converted back correctly. | 
| 323 | 25 | 100 |  |  |  | 49 | if (length($coord_string) >= 3) { | 
| 324 |  |  |  |  |  |  | # Need to insert a decimal point. The final value must be < 180, | 
| 325 |  |  |  |  |  |  | # and we asssume at least two decimal places. | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | # Let's try the easy case first, and insert a decimal point | 
| 328 |  |  |  |  |  |  | # right before the last two digits. All names generated via | 
| 329 |  |  |  |  |  |  | # to_whitwell() will work with this case. Since we know the | 
| 330 |  |  |  |  |  |  | # coordinate string only has numbers in it, we can just divide | 
| 331 |  |  |  |  |  |  | # by 100. | 
| 332 | 24 |  |  |  |  | 70 | my $trial_value = $coord_string/100; | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | # Manufactured by some other means. Move the decimal left one | 
| 335 |  |  |  |  |  |  | # character at a time until the number is < 180. We never do this | 
| 336 |  |  |  |  |  |  | # at all if our initial guess worked. | 
| 337 | 24 |  |  |  |  | 54 | $trial_value /= 10 while $trial_value > 180; | 
| 338 | 24 |  |  |  |  | 32 | $coord_string = $trial_value; | 
| 339 |  |  |  |  |  |  | } | 
| 340 |  |  |  |  |  |  | else { | 
| 341 |  |  |  |  |  |  | # < 3, so can't be > 180. Just add decimals. | 
| 342 | 1 |  |  |  |  | 3 | $coord_string .= ".00"; | 
| 343 |  |  |  |  |  |  | } | 
| 344 | 25 |  |  |  |  | 71 | return ($coord_string, $is_negative); | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | =head1 AUTHOR | 
| 348 |  |  |  |  |  |  |  | 
| 349 |  |  |  |  |  |  | Joe McMahon, C<<  >> | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | =head1 BUGS | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | Please report any bugs or feature requests to C | 
| 354 |  |  |  |  |  |  | rt.cpan.org>, or through the web interface at | 
| 355 |  |  |  |  |  |  | L.  I | 
| 356 |  |  |  |  |  |  | will be notified, and then you'll automatically be notified of progress on your | 
| 357 |  |  |  |  |  |  | bug as I make changes. | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | =head2 KNOWN BUGS | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | =over | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | =item * (0,0) isn't handled correctly; however, since there's nothing there | 
| 364 |  |  |  |  |  |  | but water, this is not a practical limitation. | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | =back | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =head1 SUPPORT | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | perldoc Acme::Geo::Whitwell::Name | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | You can also look for information at: | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | =over 4 | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | =item * RT: CPAN's request tracker | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | L | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | =item * AnnoCPAN: Annotated CPAN documentation | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | L | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | L | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | =item * Search CPAN | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | L | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | =back | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | Copyright 2008 Joe McMahon, all rights reserved. | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 406 |  |  |  |  |  |  | under the same terms as Perl itself. | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | =cut | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | 1; # End of Acme::Geo::Whitwell::Name |