The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#! /usr/bin/perl -w

# Toby Thurston -- 20 Jan 2016 
# Parse a National Grid ref and show it as LL coordinates

use strict;
use warnings;

use Geo::Coordinates::OSGB qw/grid_to_ll grid_to_ll_helmert/;
use Geo::Coordinates::OSGB::Grid qw/parse_grid format_grid_landranger/;

use Getopt::Long;
use Pod::Usage;

our $VERSION = '2.17';

=pod

=head1 NAME

bng_to_ll - parse a grid reference and show it as Latitude and Longitude

This programme shows off some features of L<Geo::Coordinates::OSGB>.

=head1 SYNOPSIS

  perl bng_to_ll.pl [--filter] grid ref string

=head1 ARGUMENTS and OPTIONS

The argument should be a string that represents a grid reference, 
like 'TQ 123 456' or '314159 271828'.  No need to use quotes.

=over 4 

=item --filter

Just send the WGS84 result to STDOUT instead of dressing it all up 
in a three line message.

=item --usage, --help, --man

Show increasing amounts of help text, and exit.

=item --version

Print version and exit.

=back

=head1 DESCRIPTION

This section describes how L<Geo::Coordinates::OSGB> functions are used.

=head2 Parsing a grid reference

C<parse_grid> takes more or less any possible string as a grid reference.
Or just a pair of numbers representing metres from the grid origin.
See L<Geo::Coordinates::OSGB::Grid> for full details.  You'll get 
error messages from C<parse_grid> if it fails to understand your input.

=head2 Using C<grid_to_ll>

The example shows how to get a regular WGS84 result as well as an OSGB36
result.  The OSGB36 model is used for the latitude and longitude shown
on the edges of OS maps.

=head2 Formatting a grid reference

C<format_grid_landranger> is used to re-present the grid reference parsed
from the input.  In the scalar form you get a string instead of just the 
GR and a list of maps.

=head2 Formatting a latitude / longitude pair

A simple routine that converts decimal-degrees to degrees-minutes-seconds
is included.  This might be useful in other applications.  

=head1 AUTHOR

Toby Thurston -- 04 Feb 2016 
toby@cpan.org

=cut

my $want_filter = 0;
my $test_me = 0;

my $options_ok = GetOptions(
    'filter!'     => \$want_filter, 
    'test!'       => \$test_me,
    
    'version'     => sub { warn "$0, version: $VERSION\n"; exit 0; }, 
    'usage'       => sub { pod2usage(-verbose => 0, -exitstatus => 0) },                         
    'help'        => sub { pod2usage(-verbose => 1, -exitstatus => 0) },                         
    'man'         => sub { pod2usage(-verbose => 2, -exitstatus => 0) },

) or die pod2usage();
die pod2usage unless @ARGV or $test_me;

# This routine shows how to convert from decimal degrees to DMS format
# Note that it's important to round the number of seconds **BEFORE**
# calculating the integer degrees and minutes, otherwise you can end up
# with a number of seconds that rounds to 60..., and then `use bignum` 
# ensures we keep the same number of decimal places for seconds when subtracting
# the hours and minutes
sub dms {
    use bignum;
    my ($degrees, $places, $hemisphere_labels ) = @_;
    my $sign = substr $hemisphere_labels, $degrees < 0, 1;
    my $seconds = sprintf "%.${places}f", abs($degrees)*3600;
    my $dd = sprintf "%d", $seconds / 3600; $seconds -= 3600 * $dd;
    my $mm = sprintf "%d", $seconds / 60;   $seconds -= 60 * $mm;
    return qq{$sign $dd° $mm′ $seconds″};
}

sub format_ll_nicely {
    my ($model, $lat, $lon) = @_;
    return sprintf "In $model, this is %.7g %.7g = %s %s", 
                   $lat, 
                   $lon, 
                   dms($lat, 4, 'NS'), 
                   dms($lon, 4, 'EW');
}

my $gr = "@ARGV";

my ($e, $n) = $gr eq 'test' || $test_me ? (651409.903, 313177.270) : parse_grid($gr);
my ($lat, $lon)   = grid_to_ll($e, $n);

if ($want_filter) {
    print "$lat $lon\n";
}
else {
    my ($olat, $olon) = grid_to_ll($e, $n, { shape => 'OSGB36' } );
    my ($hlat, $hlon) = grid_to_ll_helmert($e, $n);
    printf "Your input: $gr == $e $n == %s\n", scalar format_grid_landranger($e, $n);
    print format_ll_nicely(' WGS84', $lat, $lon), "\n";
    print format_ll_nicely('~WGS84', $hlat, $hlon), "\n";
    print format_ll_nicely('OSGB36', $olat, $olon), "\n";
}
exit 0;