File Coverage

blib/lib/Astro/SkyPlot.pm
Criterion Covered Total %
statement 45 151 29.8
branch 0 32 0.0
condition 0 5 0.0
subroutine 15 24 62.5
pod 4 4 100.0
total 64 216 29.6


line stmt bran cond sub pod time code
1             package Astro::SkyPlot;
2 1     1   25606 use 5.006001;
  1         3  
  1         38  
3 1     1   5 use strict;
  1         2  
  1         34  
4 1     1   6 use warnings;
  1         7  
  1         40  
5              
6             our $VERSION = '0.03';
7 1     1   11 use Carp 'croak';
  1         2  
  1         87  
8 1     1   809 use Astro::MapProjection;
  1         718  
  1         42  
9 1     1   1008 use PostScript::Simple;
  1         13662  
  1         79  
10             use Class::XSAccessor {
11 1         9 getters => [qw/xsize ysize ps/],
12             accessors => [qw/marker/]
13 1     1   951 };
  1         3209  
14              
15             use constant {
16 1         126 HAMMER_PROJ => 0,
17             SINUSOIDAL_PROJ => 1,
18             MILLER_PROJ => 2,
19 1     1   282 };
  1         2  
20              
21 1         227 use constant PROJ_COORD_TRAFO => [
22             \&Astro::MapProjection::hammer_projection,
23             \&Astro::MapProjection::sinusoidal_projection,
24             \&Astro::MapProjection::miller_projection,
25 1     1   5 ];
  1         2  
26 0         0 use constant PROJ_CANVAS_TRAFO => [
27             sub {return( $_[0]*$_[2]/6 + $_[2]/2, $_[1]*$_[3]/6 + $_[3]/2 )},
28 0         0 sub {return( $_[0]*$_[2]/6.5 + $_[2]/2, $_[1]*$_[3]/6.5 + $_[3]/2 )},
29 0         0 sub {return( $_[0]*$_[2]/6.5 + $_[2]/2, $_[1]*$_[3]/6.5 + $_[3]/2 )},
30 1     1   6 ];
  1         2  
  1         183  
31 1         82 use constant PROJ_NAMES => {
32             'hammer' => HAMMER_PROJ,
33             'sinusoidal' => SINUSOIDAL_PROJ,
34             'miller' => MILLER_PROJ,
35 1     1   7 };
  1         1  
36              
37 1     1   6 use constant PI => atan2(1,0)*2;
  1         3  
  1         56  
38 1     1   6 use constant DEG2RAD => PI/180;
  1         3  
  1         72  
39 1     1   22 use constant RAD2DEG => 180/PI;
  1         3  
  1         63  
40              
41             use constant {
42 1         2714 MARK_CIRCLE => 0,
43             MARK_CIRCLE_FILLED => 1,
44             MARK_BOX => 2,
45             MARK_BOX_FILLED => 3,
46             MARK_TRIANGLE => 4,
47             MARK_TRIANGLE_FILLED => 5,
48             MARK_DTRIANGLE => 6,
49             MARK_DTRIANGLE_FILLED => 7,
50             MARK_CROSS => 8,
51             MARK_DIAG_CROSS => 9,
52 1     1   6 };
  1         1  
53              
54             require Exporter;
55             our @ISA = qw(Exporter);
56             our @EXPORT;
57             our %EXPORT_TAGS = ( 'all' => [ qw(
58             MARK_CIRCLE
59             MARK_CIRCLE_FILLED
60             MARK_BOX
61             MARK_BOX_FILLED
62             MARK_TRIANGLE
63             MARK_TRIANGLE_FILLED
64             MARK_DTRIANGLE
65             MARK_DTRIANGLE_FILLED
66             MARK_CROSS
67             MARK_DIAG_CROSS
68             ) ] );
69             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
70              
71             =head1 NAME
72              
73             Astro::SkyPlot - Create very basic sky plots
74              
75             =head1 SYNOPSIS
76              
77             use Astro::SkyPlot qw/:all/; # export the markers
78             my $plot = Astro::SkyPlot->new(); # use defaults (see below)
79            
80             # specify options yourself:
81             $plot = Astro::SkyPlot->new(
82             xsize => 200, # mm
83             ysize => 200,
84             bgcolor => [0, 0, 0], # RGB => black
85             projection => 'hammer',
86             axiscolor => [100, 100, 100], # RGB => grey
87             );
88            
89             $plot->setcolor(255, 0, 0); # RGB => red
90             $plot->plot_lat_long(1, 1); # units: radians
91             $plot->plot_lat_long(1, 1, size => 0.2, marker => MARK_CIRCLE); # units: radians, radians, mm
92             $plot->write(file => "skyplot.eps");
93              
94             =head1 DESCRIPTION
95              
96             A module to create very basic sky plots as EPS documents.
97              
98             =head1 MARKERS
99              
100             There are multiple types of markers that can be plotted into the sky plot.
101             These are defined through constants that can be exported from the module:
102              
103             MARK_CIRCLE => circular markers
104             MARK_CIRCLE_FILLED => filled circular markers
105             MARK_BOX => square markers
106             MARK_BOX_FILLED => filled square markers
107             MARK_TRIANGLE => triangularmarkers
108             MARK_TRIANGLE_FILLED => filled triangular markers
109             MARK_DTRIANGLE => downward triangular markers
110             MARK_DTRIANGLE_FILLED => filled downward triangular markers
111             MARK_CROSS => cross shaped markers
112             MARK_DIAGCROSS => diagonal cross shaped markers
113              
114             =head1 PROJECTIONS
115              
116             You can use the Hammer projection (C<"hammer">), the Sinusoidal projection
117             (C<"sinusoidal">), and the Miller projection (C<"miller">). Default is C<"hammer">.
118             You can use the C argument to the constructor to change this.
119              
120             Cf. L for details on these projections and have a look at
121             the default axes by running the F example.
122              
123             =head1 METHODS
124              
125             =head2 new
126              
127             Constructor. Without arguments, uses the default settings
128             (cf. SYNOPSIS). Supports the following options:
129              
130             xsize => Plot x-size in mm (def: 200mm)
131             ysize => Plot y-size in mm (def: 200mm)
132             bgcolor => Background color as array reference
133             (RGB value 0-255 per component)
134             (def: black, [0, 0, 0])
135             projection => Projection type. Default: Hammer projection ('hammer')
136             axiscolor => Color for the axes. (def: grey, [100, 100, 100])
137              
138             =cut
139              
140             sub new {
141 0     0 1   my $class = shift;
142             # todo: arg checking
143 0           my $self = bless {
144             xsize => 200, # mm
145             ysize => 200,
146             bgcolor => [0, 0, 0], # RGB => black
147             projection => 'hammer',
148             axiscolor => [100, 100, 100], # RGB => grey
149             marker => MARK_CIRCLE_FILLED,
150             @_,
151             } => $class;
152              
153 0           my $ps = PostScript::Simple->new(
154             eps => 1,
155             units => "mm",
156             xsize => $self->xsize,
157             ysize => $self->ysize,
158             colour => 1,
159             );
160 0           $self->{ps} = $ps;
161              
162 0           $self->setcolor(255, 255, 255);
163              
164 0           my $proj_name = $self->{projection};
165 0           $self->{projection} = PROJ_NAMES->{$proj_name};
166 0 0         croak("Unknown projection '$proj_name'")
167             if not defined $self->{projection};
168              
169 0           $self->_draw_bg();
170 0           $self->_plot_axis();
171              
172 0           return $self;
173             }
174              
175             =head2 setcolor
176              
177             Set a new drawing color. Takes three numbers corresponding to red,
178             green and blue values between 0 and 255.
179              
180             =cut
181              
182             sub setcolor {
183 0     0 1   my $self = shift;
184 0 0         croak("Need three numbers (RGB) for the drawing color")
185             if @_ != 3;
186 0           $self->{color} = [@_];
187 0           $self->{ps}->setcolour(@_);
188 0           return $self;
189             }
190              
191             =head2 plot_lat_long
192              
193             Draw a new latitude/longitude point.
194              
195             These may be followed by key/value pairs of options.
196             Supported options:
197              
198             C: the size (radius) of the point (default: 0.1mm)
199              
200             C: The type of marker to use (see L).
201              
202             =cut
203              
204             sub plot_lat_long {
205 0     0 1   my $self = shift;
206 0 0         croak("Need latitude/longitude")
207             if @_ < 2;
208 0           my $lat = shift;
209 0           my $long = shift;
210 0           my %opt = @_;
211 0   0       my $size = $opt{size}||0.1;
212 0           my $ps = $self->{ps};
213 0           my ($x, $y) = $self->_project($lat, $long);
214 0 0         my $marker = exists($opt{marker}) ? $opt{marker} : $self->{marker};
215 0           $self->_draw_marker($x, $y, $marker, $size);
216             }
217              
218             =head2 write
219              
220             Write the plot to the specified EPS file.
221              
222             =cut
223              
224             sub write {
225 0     0 1   my $self = shift;
226 0           my $file = shift;
227 0 0         croak("Need file name as argument")
228             if not defined $_[0];
229 0           my $ps = $self->{ps};
230 0           $ps->output($_[0]);
231 0           return $self;
232             }
233              
234             =head1 ACCESSOR METHODS
235              
236             The following are read only accessors unless otherwise noted.
237              
238             =head2 marker
239              
240             Get/Set the default marker type. The marker type for a single
241             plot operation can be specified as an option to C.
242              
243             =head2 ps
244              
245             Returns the internals C object.
246              
247             =head2 xsize
248              
249             Returns the image's width (in mm).
250              
251             =head2 ysize
252              
253             Returns the image's height (in mm).
254              
255             =cut
256              
257             =head1 PRIVATE METHODS
258              
259             =head2 _draw_bg
260              
261             Draws the plot's background.
262              
263             =cut
264              
265             sub _draw_bg {
266 0     0     my $self = shift;
267 0           my $ps = $self->{ps};
268 0           $ps->setcolour(0, 0, 0);
269 0           $ps->box({filled=>1}, 0, 0, $self->xsize, $self->ysize);
270 0           $ps->setcolour(255, 255, 255);
271 0           return $self->_restore_color();
272             }
273              
274             =head2 _restore_color
275              
276             Restores the previously saved color.
277              
278             =cut
279              
280             sub _restore_color {
281 0     0     my $self = shift;
282 0           my $ps = $self->{ps};
283 0           $ps->setcolour(@{$self->{color}});
  0            
284 0           return $self;
285             }
286              
287             =head2 _plot_axis
288              
289             Plot the sky-plot axis.
290              
291             =cut
292              
293             sub _plot_axis {
294 0     0     my $self = shift;
295 0           my $ps = $self->{ps};
296 0           my $projection = $self->{projection};
297 0           my $old_color = $self->{color};
298 0           $self->setcolor(@{$self->{axiscolor}});
  0            
299 0           my $xsize = $self->{xsize};
300 0           my $ysize = $self->{ysize};
301              
302 0           my $ps_trafo = PROJ_CANVAS_TRAFO->[$projection];
303 0           my $projector = PROJ_COORD_TRAFO->[$projection];
304              
305 0           $ps->setlinewidth(0.05);
306            
307 0 0 0       if ($projection == HAMMER_PROJ || $projection == SINUSOIDAL_PROJ) {
    0          
308             # plot longitude axes
309 0           for (my $long = -180*DEG2RAD; $long <= 180.001*DEG2RAD; $long += 45*DEG2RAD) {
310 0           my $first = 1;
311 0           for (my $lat = -90*DEG2RAD; $lat <= 90.001*DEG2RAD; $lat += 3.0*DEG2RAD) {
312 0           my ($x, $y) = $ps_trafo->( $projector->($lat, $long), $xsize, $ysize );
313 0 0         if ($first) {
314 0           $ps->line($x, $y, $x, $y);
315 0           $first = 0;
316             }
317             else {
318 0           $ps->linextend($x, $y);
319             }
320             }
321             }
322              
323             # plot lat axes
324 0           for (my $lat = -90*DEG2RAD; $lat <= 90.001*DEG2RAD; $lat += 10*DEG2RAD) {
325 0           my $first = 1;
326 0           for (my $long = -180*DEG2RAD; $long <= 180.001*DEG2RAD; $long += 5.0*DEG2RAD) {
327 0           my ($x, $y) = $ps_trafo->( $projector->($lat, $long), $xsize, $ysize );
328 0 0         if ($first) {
329 0           $ps->line($x, $y, $x, $y);
330 0           $first = 0;
331             }
332             else {
333 0           $ps->linextend($x, $y);
334             }
335             }
336             }
337             }
338             elsif ($projection == MILLER_PROJ) {
339             # much, much less steps required ==> special case
340             # plot longitude axes
341 0           for (my $long = -180*DEG2RAD; $long <= 180.001*DEG2RAD; $long += 45*DEG2RAD) {
342 0           my ($xs, $ys) = $ps_trafo->( $projector->(-90*DEG2RAD, $long), $xsize, $ysize );
343 0           my ($xe, $ye) = $ps_trafo->( $projector->(90*DEG2RAD, $long), $xsize, $ysize );
344 0           $ps->line($xs, $ys, $xe, $ye);
345             }
346              
347             # plot lat axes
348 0           for (my $lat = -90*DEG2RAD; $lat <= 90.001*DEG2RAD; $lat += 10*DEG2RAD) {
349 0           my ($xs, $ys) = $ps_trafo->( $projector->($lat, -180*DEG2RAD), $xsize, $ysize );
350 0           my ($xe, $ye) = $ps_trafo->( $projector->($lat, 180*DEG2RAD), $xsize, $ysize );
351 0           $ps->line($xs, $ys, $xe, $ye);
352             }
353             }
354             else {
355 0           die "Invalid projection type $projection";
356             }
357              
358 0           $self->{color} = $old_color;
359 0           return $self->_restore_color();
360             }
361              
362             =head2 _project
363              
364             Projects given lat/long to x/y to plot coordinates.
365              
366             =cut
367              
368             sub _project {
369 0     0     my $self = shift;
370 0           my $projection = $self->{projection};
371 0           my $ps_trafo = PROJ_CANVAS_TRAFO->[$projection];
372 0           my $projector = PROJ_COORD_TRAFO->[$projection];
373 0           return $ps_trafo->( $projector->(@_), $self->{xsize}, $self->{ysize} );
374             }
375              
376             =head2 _draw_marker
377              
378             Draws a marker at the given plot coordinates. Arguments C<$x, $y, $markerno, $size>.
379              
380             =cut
381              
382             sub _draw_marker {
383 0     0     my $self = shift;
384 0 0         die('Need $x, $y, $marker, $size')
385             if not @_ == 4;
386 0           my ($x, $y, $marker, $size) = @_;
387 0           my $ps = $self->{ps};
388 0 0         if ($marker <= MARK_CIRCLE_FILLED) {
    0          
    0          
    0          
    0          
    0          
389 0           $ps->circle(
390             {filled => ($marker == MARK_CIRCLE_FILLED)},
391             $x, $y, $size
392             );
393             }
394             elsif ($marker <= MARK_BOX_FILLED) {
395 0           $ps->box(
396             {filled => ($marker == MARK_BOX_FILLED)},
397             $x-$size, $y-$size, $x+$size, $y+$size
398             );
399             }
400             elsif ($marker <= MARK_TRIANGLE_FILLED) {
401 0           my $lowy = $y-$size;
402 0           $ps->polygon(
403             {filled => ($marker == MARK_TRIANGLE_FILLED)},
404             $x-$size, $lowy,
405             $x+$size, $lowy,
406             $x, $y+$size,
407             $x-$size, $lowy,
408             );
409             }
410             elsif ($marker <= MARK_DTRIANGLE_FILLED) {
411 0           my $highy = $y+$size;
412 0           $ps->polygon(
413             {filled => ($marker == MARK_DTRIANGLE_FILLED)},
414             $x-$size, $highy,
415             $x+$size, $highy,
416             $x, $y-$size,
417             $x-$size, $highy,
418             );
419             }
420             elsif ($marker == MARK_CROSS) {
421 0           $ps->line(
422             $x-$size, $y, $x+$size, $y
423             );
424 0           $ps->line(
425             $x, $y-$size, $x, $y+$size
426             );
427             }
428             elsif ($marker == MARK_DIAG_CROSS) {
429 0           $ps->line(
430             $x-$size, $y-$size, $x+$size, $y+$size
431             );
432 0           $ps->line(
433             $x-$size, $y+$size, $x+$size, $y-$size
434             );
435             }
436             else {
437 0           die('Invalid marker no. ' . $marker);
438             }
439             }
440              
441             1;
442             __END__