package Astro::DSS::JPEG;

use 5.006;
use strict;
use warnings;
use utf8;

use LWP::UserAgent;
use List::Util 'min';

=encoding utf8

=head1 NAME

Astro::DSS::JPEG - Download color JPEG images from the Digitized Sky Survey

=head1 VERSION

Version 0.01_0

=cut

our $VERSION = '0.01_0';


=head1 SYNOPSIS

    use Astro::DSS::JPEG;

    my $dss = Astro::DSS::JPEG->new();

    #get an object (Triangulum galaxy M33) by name at default 1000x1000,
    #getting coordinates and frame size automatically from SIMBAD
    my $image = $dss->get_image(Target => 'M33');

    #Save an image directly to file manually specifying coordinates, frame of 
    #90x90 arcmin, 2048x2048 pixels
    $dss->get_image(
        RA           => '01 33 50.904',
        Dec          => '+30 39 35.79',
        angular_size => 90,
        pixel_size   => 2048,
        filename     => $filename
    );


=head1 DESCRIPTION

Astro::DSS::JPEG downloads JPEG images for any location in the sky from the L<Digitized Sky Survey (DSS)|https://archive.stsci.edu/dss/>.
It is meant to be a simple stand alone module to access a fast JPEG-only API that
provides color composites made from the blue and red DSS surveys.
In comparison, there is an old/not updated L<Astro::DSS> module that would provide
access to the slow FITS/GIF interface of the separate DSS1/DSS2 surveys.

Optionally, L<SIMBAD|http://simbad.u-strasbg.fr/simbad/> is used if you'd like to use
an object name/id instead of coordinates.

=head1 CONSTRUCTOR METHODS

=head2 C<new>

    my $dss = Astro::DSS::JPEG->new(
        dss_url    => 'http://gsss.stsci.edu/webservices/dssjpg/dss.svc/GetImage', # Optional
        simbad_url => 'http://simbad.u-strasbg.fr/simbad/sim-id',                  # Optional
        ua         => $ua                                                          # Optional
    );
  
All parameters are optional. The constructor by default creates an L<LWP::UserAgent>,
but you can pass your own with C<ua>, while the URL to the STScI JPEG endpoint
and the L<SIMBAD|http://simbad.u-strasbg.fr/simbad/> simple id interface can be redefined
(e.g. if they change in the future) from the above shown defaults.

=head1 METHODS

=head2 C<get_image>

    my $res = $dss->get_image(
        Target       => $target,            # Not used if RA, Dec provided
        RA           => $RA,                # Right Ascension (in hours of angle)
        Dec          => $Dec,               # Declination (in degrees of angle)
        angular_size => 30,                 # Optional. Frame angular size in arcmin
        pixel_size   => 1000,               # Optional. Frame size in pixels
        filename     => $filename           # Optional. Filename to save image to
    );

Fetches an image either resolving the name through L<SIMBAD|http://simbad.u-strasbg.fr/simbad/>,
or using RA & Dec when they are defined. If you pass a C<filename>, the JPEG image will
be saved to that and the function will return an L<HTTP::Response> object, otherwise the
image data will be returned as a response. 

The parameters to pass:

=over 4
 
=item * C<RA>

Right ascension, from 0-24h of angle. The option is flexible, it will accept a single
decimal number - e.g. C<2.5> or a string with hours, minutes, secs like C<'2 30 00'> or
C<'2h30m30s'> etc. Single/double quotes and single/double prime symbols are accepted
for denoting minute, second in place of a single space which also works.

=item * C<Dec>

Declination, from -90 to 90 degrees of angle. The option is flexible, it will accept
a single decimal number - e.g. C<54.5> or a string with degrees, minutes, secs like
C<'+54 30 00'> or C<'54°30m30s'> etc. Single/double quotes and single/double prime symbols
are accepted for denoting minute, second in place of a single space which also works.

=item * C<Target>

Do a L<SIMBAD|http://simbad.u-strasbg.fr/simbad/> lookup to get the C<Target> by name
(e.g. 'NGC598'). Will be disregarded if C<RA> and C<Dec> are defined. If you have not
defined an C<angular_size> and L<SIMBAD|http://simbad.u-strasbg.fr/simbad/> has one
defined, it will be used for the image request with an extra 50% for padding.

=item * C<angular_size>

Define the frame angular size in arcminutes, either as a single number (square) or
a comma separated string for a rectangle. Default is 0.5 degrees (equivalent to passing
C<30> or C<'30,30'> or even C<'30x30'>). The aspect ratio will be overriden by
C<pixel_size>.

=item * C<pixel_size>

Define the frame size in pixels, either as a single number (square) or a comma separated
string for a rectangle. Default is 1000 pixels (equivalent to passing C<1000> or
C<'1000,1000'> or even C<'1000x1000'>). Max possible is C<'4096,4096'> total size,
or 1 pixel / arcsecond resolution (e.g. 30*60 = 1800 pixels for 30 arcminutes), which
is the full resolution DSS plates were scanned at.

=back

=head1 NOTES

Some artifacts can be seen at the borders of separate "stripes" of the survey and
also the particular JPEG endpoint used sometimes can leave the corners as plain black
squares (depending on the selected frame size, as it is to do with the way it does
segmentation), so if you want to make sure you have a frame with no corner gaps,
request some more angular size than you want and crop. 

Note that the module test suite won't actually fetch data from either DSS or SIMBAD.
This is mainly to ensure it will not fail even if the DSS & SIMBAD endpoints change,
as you can still use the module by passing the updated urls to the constructor. It
also avoids unneeded strain to those free services.

=cut


sub new {
    my ($class, %opts) = @_;

    my $self = {};
    bless($self, $class);

    $self->{dss_url} = $opts{dss_url}
      || 'http://gsss.stsci.edu/webservices/dssjpg/dss.svc/GetImage';
    $self->{simbad_url} = $opts{simbad_url}
      || 'http://simbad.u-strasbg.fr/simbad/sim-id';
    $self->{ua} = $opts{ua}
      || LWP::UserAgent->new(
        agent => "perl/Astro::DSS::JPEG/$VERSION",
      );

    return $self;
}

sub get_image {
    my ($self, %opts) = @_;
    if ($opts{Target} && (! defined $opts{RA} || ! defined $opts{Dec})) {
        my $simbad = $self->_get_simbad(%opts);
        %opts = (
            %opts,
            %$simbad
        );
    }

    my $res = $self->_getDSS(_process_options(%opts));
    if ($res->is_success) {
        return $res if $opts{filename};
        return $res->decoded_content;
    } else {
        die "*ERROR* Could not access DSS:\n".$res->status_line;
    }
}

sub _getDSS {
    my ($self, %opts) = @_;

    my @request = (
        $self->{dss_url} . sprintf(
            "?POS=%f,%f&SIZE=%f,%f&ISIZE=%.0f,%.0f",
            $opts{RA} * 15,             # Convert to degrees
            $opts{Dec},
            $opts{angular_size} / 60,   # Convert to degrees
            $opts{angular_size_y} / 60, # Convert to degrees
            $opts{pixel_size},
            $opts{pixel_size_y}
        )
    );
    push @request, (':content_file' => $opts{filename}) if $opts{filename};
    return $self->{ua}->get(@request);
}

sub _convert_coordinates {
    my %opts = @_;
    if (defined $opts{RA} && $opts{RA} =~ /([+-]?[0-9.]+)[ h]+([0-9.]+)[ m′']+([0-9.]+)/) {
        $opts{RA} = $1+$2/60+$3/3600;
    }
    if (defined $opts{Dec} && $opts{Dec} =~ /([+-]?[0-9.]+)[ d°]+([0-9.]+)[ m′']+([0-9.]+)/) {
        my $sign = $1 < 0 ? -1 : 1;
        $opts{Dec} = (abs($1)+$2/60+$3/3600)*$sign;
    }
    return %opts;
}

sub _process_options {
    my %opts = _convert_coordinates(@_);
    $opts{angular_size} ||= 30; # 30' Default angular size
    $opts{pixel_size}   ||= 1000;

    if ($opts{angular_size} =~ /(\S+)\s*[,xX]\s*(\S+)/) {
        $opts{angular_size}   = $1 || 30;
        $opts{angular_size_y} = $2;
    }
    if ($opts{pixel_size} =~ /(\S+)\s*[,xX]\s*(\S+)/) {
        $opts{pixel_size}   = $1 || 1000;
        $opts{pixel_size_y} = $2;
    }

    $opts{angular_size_y} = $opts{angular_size} unless $opts{angular_size_y};
    $opts{pixel_size_y}   = $opts{pixel_size}*$opts{angular_size_y}/$opts{angular_size}
        unless $opts{pixel_size_y};

    $opts{"pixel$_"} = min 4096, $opts{"pixel$_"}, $opts{"angular$_"}*60
        for qw/_size _size_y/;

    return %opts;
}

sub _get_simbad {
    my ($self, %opts) = @_;

    my $res = $self->{ua}->get(
        $self->{simbad_url}."?output.format=ASCII&Ident=".$opts{Target}
    );
    if ($res->is_success) {
        my $simbad = $res->decoded_content;
        if ($simbad =~ /\(ICRS,ep=J2000,eq=2000\): (\S+ \S+ \S+)\s+(\S+ \S+ \S+)/) {
            $opts{RA}  = $1;
            $opts{Dec} = $2;
        } else {
            die "*ERROR* Could not parse SIMBAD output:\n$simbad";
        }
        if ($simbad =~ /Angular size:\s*([0-9.]+)/) { # Get only largest dimension
            $opts{angular_size} = $1 * 1.5; # Get 50% extra for framing
        }
    } else {
        die "*ERROR* Could not access SIMBAD:\n".$res->status_line;
    }

    return \%opts;
}

=head1 AUTHOR

Dimitrios Kechagias, C<< <dkechag at cpan.org> >>


=head1 BUGS

Please report any bugs or feature requests to C<bug-astro-dss-jpeg at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Astro-DSS-JPEG>.
I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

You could also submit issues or even pull requests to the
github repo (see below).


=head1 GIT

L<https://github.com/dkechag/Astro-DSS-JPEG>


=head1 ACKNOWLEDGEMENTS

The DSS images are downloaded using a public api of the L<Digitized Sky Survey|https://archive.stsci.edu/dss/>
provided by the L<Space Telescope Science Institute|http://www.stsci.edu>.

Targets by name/id are resolved using the L<SIMBAD Astronomical Database|http://simbad.u-strasbg.fr/simbad/>.


=head1 LICENSE AND COPYRIGHT

This software is copyright (c) 2020 by Dimitrios Kechagias.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

For the images you download with this module, please see the L<STScI site|https://archive.stsci.edu/dss/copyright.html>
for the full copyright info.


=cut

1;