http://www.perlmonks.org?node_id=912476

Willard B. Trophy has asked for the wisdom of the Perl Monks concerning the following question:

Amateur radio operators use the Maidenhead Grid Locator Square system to identify location. For example, the location FN03ir describes a small area in Eastern Toronto, centred on 43.72917°N, 79.29167°W. It's a form of geohash that degrades gracefully in precision.

I've written a two-way converter that seems to work well. Other people have expressed interest in using it. I have a nagging suspicion, though, that the sub maidenhead2latlong could be a lot more elegant, but I've been out of everyday programming for too long to see what's up with it. Any hints, please?

Some test data:

Callsign Locator   Latitude  Longitude
======== ========= ========= ========== 
LU5VV    FE48hu    -41.14583  -71.37500
OX3WS    GP44dd     64.14583  -51.70833
AH6V     BK29jv     19.89583 -155.20833
ZL3TE    RF73ic    -36.89583  174.70833

... and now the code:

#!/usr/bin/perl -w # maidenhead - 6 character locator # created by scruss/VA3PID on 02011/04/01 # RCS/CVS: $Id: maidenhead,v 1.3 2011/04/03 11:04:38 scruss Exp $ use strict; sub latlong2maidenhead; sub maidenhead2latlong; if ( $#ARGV >= 1 ) { # two or more args # convert lat/long to grid print latlong2maidenhead( $ARGV[0], $ARGV[1] ), "\n"; } elsif ( $#ARGV == 0 ) { # one arg # convert grid to lat/long print join( ", ", maidenhead2latlong( $ARGV[0] ) ), "\n"; } else { # no args # print usage print 'Usage: ', $0, ' dd.dddddd ddd.dddddd', "\n", ' or', "\n", $0, ' grid_location', "\n"; } exit; sub maidenhead2latlong { # convert a Maidenhead Grid location (eg FN03ir) # to decimal degrees # this code could be cleaner/shorter/clearer my @locator = split( //, uc(shift) ); # convert arg to upper case array my $lat = 0; my $long = 0; my $latdiv = 0; my $longdiv = 0; my @divisors = ( 72000, 36000, 7200, 3600, 300, 150 ) ; # long,lat field size in seconds my $max = ( $#locator > $#divisors ) ? $#divisors : $#locator; for ( my $i = 0 ; $i <= $max ; $i++ ) { if ( int( $i / 2 ) % 2 ) { # numeric if ( $i % 2 ) { # lat $latdiv = $divisors[$i]; # save for later $lat += $locator[$i] * $latdiv; } else { # long $longdiv = $divisors[$i]; $long += $locator[$i] * $longdiv; } } else { # alpha my $val = ord( $locator[$i] ) - ord('A'); if ( $i % 2 ) { # lat $latdiv = $divisors[$i]; # save for later $lat += $val * $latdiv; } else { # long $longdiv = $divisors[$i]; $long += $val * $longdiv; } } } $lat += ( $latdiv / 2 ); # location of centre of square $long += ( $longdiv / 2 ); return ( ( $lat / 3600 ) - 90, ( $long / 3600 ) - 180 ); } sub latlong2maidenhead { # convert a WGS84 coordinate in decimal degrees # to a Maidenhead grid location my ( $lat, $long ) = @_; my @divisors = ( 72000, 36000, 7200, 3600, 300, 150 ); # field size in seconds my @locator = (); # add false easting and northing, convert to seconds $lat = ( $lat + 90 ) * 3600; $long = ( $long + 180 ) * 3600; for ( my $i = 0 ; $i < 3 ; $i++ ) { foreach ( $long, $lat ) { my $div = shift(@divisors); my $part = int( $_ / $div ); if ( $i == 1 ) { # do the numeric thing for 2nd pair push @locator, $part; } else { # character thing for 1st and 3rd pair push @locator, chr( ( ( $i < 1 ) ? ord('A') : ord('a') ) + $part ); } $_ -= ( $part * $div ); # leaves remainder in $long or $lat } } return join( '', @locator ); }

--
bowling trophy thieves, die!