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 -- 30 Jul 2017 

use strict;
use warnings;

use Geo::Coordinates::OSGB qw/grid_to_ll ll_to_grid/;
use Geo::Coordinates::OSGB::Grid qw/random_grid parse_grid format_grid_landranger/;
use Browser::Open qw(open_browser);

use Scalar::Util qw(looks_like_number);
use Getopt::Long;
use Pod::Usage;

our $VERSION = '2.19';

=pod

=head1 NAME

gbguessr.pl -- compare a random spot in the UK on Streetmap and Googlemaps

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

=head1 SYNOPSIS

  perl bngl.pl [--filter] [--streetmap] [--googlemap] [--osgb] [ lat lon | grid reference ] 

=head1 ARGUMENTS and OPTIONS

The argument should be 

- either something that represents a grid reference, like 'TQ 123 456' or '314159 271828' 

- or a pair of decimal numbers in the range -20 to 70 representing a latitude
and longitude pair in the UK like: '51.3 -0.01'. Latitude and longitude can be
given in either order. You can leave an optional comma between them if you
like. This allows you to cut and paste from a web page for example. 

The argument will be converted appropriately. So if you supply a grid reference
you will get back a lat/lon pair, and vice versa.

If you supply no argument, then a random grid reference is supplied.

=over 4 

=item --filter 

Just send the converted coordinates to STDOUT instead of dressing them up in a message.
This suppresses the "--streetmap" and/or "--googlemap" options.

=item --streetmap

Open a webpage on Streetmap.co.uk showing the grid point on an online OS map.

=item --googlemap

Open a webpage showing the grid point on Google maps.

=item --osgb

Use the OSGB36 latitude and longitude model instead of the normal WGS84 model

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

Show increasing amounts of help text, and exit.

=item --version

Print version and exit.

=back

=head1 DESCRIPTION

This script shows off the conversions in this module.  

The argument can be either a grid ref or a lat/lon pair.  A simple heurisistic is used to guess 
which is which.  If the argument is missing, then a random grid point is supplied.

Optionally you can show the chosen point on a map using the Streetmap and/or Googlemap options.
You can combine both so that you can compare the OS and the Google maps side by side for the same
area.  

The Google maps parameters are the result of experimentation rather than any API documentation
so I've no idea what the data parameter does, but it seems to be necessary.  The "14z" controls the 
level of zoom, and makes it roughly 1:50,000 corresponding to the Streetmap display with "&z=3".

=head1 DEPENDENCIES

You may need to install L<Browser::Open>.

=head1 AUTHOR

Toby Thurston -- 30 Jul 2017

toby@cpan.org

=cut

sub format_grid_streetmap {
    my $e = shift;
    my $n = shift;
    return sprintf 'http://www.streetmap.co.uk/map.srf?x=%06d&y=%06d&z=3', $e, $n;
}

sub format_ll_googlemaps {
    my ($lat, $lon) = @_;
    return sprintf 'http://www.google.com/maps/place/@%f,%f,14z/data=!4m2!3m1!1s0x0:0x0', $lat, $lon;
}

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 ($lat, $lon) = @_;
    return sprintf "Lat/Lon: %.7g %.7g = %s %s", 
                   $lat, 
                   $lon, 
                   dms($lat, 4, 'NS'), 
                   dms($lon, 4, 'EW');
}

sub clean_args {
    my $input = "@_";
    $input =~ s/,/ /g;
    return split ' ', $input;
}

my $show_streetmap = 0;
my $show_googlemap = 0;
my $want_filter = 0;
my $use_osgb = 0;

Getopt::Long::Configure("pass_through");

my $options_ok = GetOptions(
    
    '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) },
    'filter!'    => \$want_filter,
    'streetmap'  => \$show_streetmap,
    'googlemap'  => \$show_googlemap,
    'osgb'       => \$use_osgb,

) or die pod2usage();

my ($e, $n, $lat, $lon, @out);

my $model = $use_osgb ? 'OSGB36' : 'WGS84';

if (!@ARGV) {
    ($e, $n) = random_grid();
    @out = ($lat, $lon) = grid_to_ll($e, $n, {shape => $model});
}
else {
    @ARGV = clean_args(@ARGV);
    if ( scalar @ARGV == 2 && looks_like_number($ARGV[0]) 
                           && looks_like_number($ARGV[1]) 
                           && $ARGV[0] < 72 
                           && $ARGV[1] < 72 ) {
        ($lat, $lon) = @ARGV;    
        @out = ($e, $n) = ll_to_grid($lat, $lon, {shape => $model});
    }
    else {
        ($e, $n) = parse_grid("@ARGV");
        @out = ($lat, $lon) = grid_to_ll($e, $n, {shape => $model});
    } 
}

if ($want_filter) {
    print "@out\n";
    exit;
}

if ($show_googlemap) {
    open_browser(format_ll_googlemaps($lat,$lon));
}
if ($show_streetmap) {
    open_browser(format_grid_streetmap($e, $n));
}

printf "Your input: %s, model: $model\n", @ARGV ? "@ARGV" : "(random)";
printf "Gridref: %s %s == %s\n", $e, $n, scalar format_grid_landranger($e, $n);
print format_ll_nicely($lat, $lon), "\n";