Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Locator to Lat/Long code: works, but is inelegant

by Willard B. Trophy (Hermit)
on Jul 02, 2011 at 16:28 UTC ( #912476=perlquestion: print w/ replies, xml ) Need Help??
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.72917N, 79.29167W. 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!

Comment on Locator to Lat/Long code: works, but is inelegant
Download Code
Re: Locator to Lat/Long code: works, but is inelegant
by Anonymous Monk on Jul 02, 2011 at 17:36 UTC

    Looks adequately elegant to my eyes :) Maybe you wish to compare to these alternate implementations

    Ham::Locator - Convert between Maidenhead locators and latitude/longitude.

    Astro::Coord::ECI - Manipulate geocentric coordinates

    Small style point, there is no need for

    sub latlong2maidenhead; sub maidenhead2latlong;
    in your code. You might wish to compare your program to this template see (tye)Re: Stupid question (and see one discussion of that template Re^2: RFC: Creating unicursal stars)
      Aargh! Forgot about the zero'th rule of Perl programming: check CPAN first! I just got into the habit of sub declaration, because I remember warnings being generated if you didn't.

      -- 
      bowling trophy thieves, die!

        I just got into the habit of sub declaration, because I remember warnings being generated if you didn't.

        Its all about parens. If you use parens, no warnings. You use parens :)

        $ perl -le " f(1); sub f{warn@_} " 1 at -e line 1. $ perl -le " f 1 ; sub f{warn@_} " Number found where operator expected at -e line 1, near "f 1" (Do you need to predeclare f?) syntax error at -e line 1, near "f 1" Execution of -e aborted due to compilation errors. $ perl -le " sub f; f 1 ; sub f{warn@_} " 1 at -e line 1.
Re: Locator to Lat/Long code: works, but is inelegant
by graff (Chancellor) on Jul 03, 2011 at 02:41 UTC
    I understand your sense of something suboptimal in that first sub, because there is some repetition of code. Of course, a little repetition in a small script is not really a problem, provided that the script ends up doing the right thing.

    (Repetition is nasty when you get into 100's or 1000's of lines of code, with some things being repeated multiple times -- no programmer good enough to deserve a living wage would ever do that.)

    The only difference between the "alpha" and "numeric" blocks is the value that gets multiplied with your "div" value, so work that out just once, then handle the lat vs. long decision:

    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 = my $long = 0; my ( $latdiv, $longdiv ); my @divisors = ( 72000, 36000, 7200, 3600, 300, 150 ); # long,lat field size in seconds my $max = ( @locator > @divisors ) ? $#divisors : $#locator; for my $i ( 0 .. $max ) { my $val = ( int( $i/2 ) % 2 ) ? $locator[$i] : ord($locator[$i +]) - ord('A'); if ( $i % 2 ) { # lat $latdiv = $divisors[$i]; # save latdiv for later $lat += $val * $latdiv; } else { # long $longdiv = $divisors[$i]; # save longdiv for later $long += $val * $longdiv; } } $lat += ( $latdiv / 2 ); # location of centre of square $long += ( $longdiv / 2 ); return ( ( $lat / 3600 ) - 90, ( $long / 3600 ) - 180 ); }
    The "for" loop there is about half as many lines as the one in the OP, and seems to produce the same output.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://912476]
Approved by lidden
Front-paged by davies
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (11)
As of 2014-10-01 15:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    What is your favourite meta-syntactic variable name?














    Results (29 votes), past polls