=head1 NAME V4 -- a 4-vector handling package =head1 DESCRIPTION Implements an object called a "4-vector" that keeps track of a location in spacetime. 4-vectors can be converted between a variety of known coordinate systems. The system is designed to be easily extensible -- you can add a new coordinate system to the soup by writing a single pair of interconversion routines to/from any known system and the new one. The package automatically finds the shortest conversion route from that system to any of the desired ones. In keeping with the PDL philosophy, an individual V4 object can hold a whole list of points -- just add extra dimensions for threading. =head1 SYNOPSIS use V4; $a = v4('Solar coords',['time','arcsec','arcsec','solar-radii'],$coordslist) $b = $a->convert('Carrington'); $c = $a->convert('Carrington',['time','radian','radian','furlong']) Conversions are handled behind-the-scenes. You can get the default units or specify your own (provided that they're interconvertible). =head1 DATA STRUCTURE V4's are implemented as PDLs, but usage of crossways slicing is deprecated, because that may lead to difficulties later. In particular, I reserve the right to store times as strings and not numbers, which will require that the single PDL inside the V4 be broken out into separate variables. That will break any code that uses PDL::slice() instead of V4::x() to access the raw structures. Ancillary information about the coordinate system is stored in the V4 -- currently in the PDL header. The values of interest are: coords - the name of the coordinate system used for the V4. units - an array ref with the names of the units for each coord (0-3). sys_params - An optional hash ref containing ancillary information that's required for some of the coordinate systems, in particular derived systems. You can pass this in through the v4() constructor method and out via the parms() method. Sys_params is used by the offset (derived) coordinate systems to store the required derivation information: REF_SYSTEM The name of the reference system (unused at present, as heliographic coords are the only supported offset-reference system just now) ORIGIN The origin of the offset system, in HC coordinates (this is a string to be eval'ed to get the V4 back -- a workaround to the PDL-in-header problem). Z_ORIG If set, then the reference angle for the ROT keyword points directly away from the origin of the original system (to match Heliographic Cartesian -- Z toward the viewer!). If it exists, the ROT matrix is applied to the origin-facing system. ROT A 3-list containing the three rotation angles about X, Y, Z (in that order) to get from orig. to offset coords. =head1 Shortcomings & caveats This code was written long before the PDL::Transform module. It reallyl needs to be overhauled to take advantage of the newer functionality. For now, time handling is severely limited for the scientific world. Everything is converted to UNIX time using Time::ParseDate. More effort should be given to time handling -- in particular, a routine is needed that understands the difference between UT and TAI. It would be terrific to augment Math::Units::convert to handle TAI, UT, Carrington second, Carrington hour, Julian second, Julian day, and other numerical time formats. Needless to say, parsedate and strftime are a little underpowered for this stuff. All of the existing conversions are non-relativistic. More support is needed for relatively specified coordinate systems: that is to say, for a general spherical system at some arbitrary location in one of the other named systems. Currently, only 1-D lists of vectors are supported. This makes some of the slicing and converting easier, at the cost of some hassle in the interface if you want 2-D lists of points. Currently, the only derived systems are referred to the Heliographic system. This is a port of the SSW/IDL code V4.pro, which offers much the same functionality. =head1 Author, license, no warranty Copyright 2000, Craig DeForest. This code may be modified and distributed under the same terms as PDL itself. The code comes with NO WARRANTY. =head1 FUNCTIONS =cut $PDL::V4::VERSION = 0.6; ###################################################################### package PDL::V4; BEGIN { package PDL::V4; $VERSION = 0.3; use Exporter; @ISA = ("PDL"); @EXPORT = qw ( v4 xform canonicalize copy append replicate); @EXPORT_OK = qw ( v4 xform canonicalize resolve unit copy append ); %EXPORT_TAGS = (Func=>[ qw ( v4 xform canonicalize copy appnd ) ]); sub PDL::V4::append (;@); sub PDL::V4::index (;@): lvalue; sub PDL::V4::xform; sub PDL::V4::x : lvalue; sub PDL::V4::x0 : lvalue; sub PDL::V4::x1 : lvalue; sub PDL::V4::x2 : lvalue; sub PDL::V4::x3 : lvalue; } ###################################################################### ## Initialize constants! # BEGIN { *debug = \0; use PDL; print __PACKAGE__,"loading...\n" if($debug); use PDL::V4::Xforms; use Math::Units; use Time::CTime; use Time::ParseDate; *convert = \(&{Math::Units:convert}); *PI = \3.141592653589793238462643383279502; *NaN = \PDL::asin(pdl(2)); # E Pluribus unum -- non-equality doesn't # guarantee non-nanness! # (use (($a * 0) != 0) to test for NaNness) } ###################################################################### =pod =head2 v4 =for ref 4-vector constructor - takes a PDL and some other info, returns a V4. =for usage $a = v4(, [], [], ) =for example $a = v4( "Coord-system-name", {hdr}, [], ) $a = v4( "coord-system-name", [], {hdr}, ($x0,$x1,$x2,[$x3]) ) $a = v4( "coord-system-name", [], {hdr}, [1],[p2],[p3],...) $a = v4( , [p1],[p2],[p3],...) $a = v4( , {hdr}, ) You feed in the name of a coordinate system, optional ancillary header information, an optional array of unit names, and the data to V4ize. You get back a V4 of undef. Note that when supplying data points as array refs, a units array ref is REQUIRED to avoid ambiguity! If the hash syntax is used, then the hash ref should contain keys "coords" (with the coord system name) and "units" (an array ref with the units to be used for each coordinate). If the "units" key is missing, default units are extracted from the coordinate system. If you pass in a PDL, it must be at most two-dimensional. The first coordinate loops across dimensions and the second coordinate loops across points. =cut sub v4 { local $_; if($_[0] eq 'PDL::V4') { Carp::confess("V4 Funky constructor flag tripped... someone's been using V4s as PDLs...\n"); } ### Parse out unit names and coordinate system from the leading args. my($units); my($coords) = shift; if(ref $coords eq "HASH") { ($coords,$units) = ($coords->{'coords'},$coords->{'units'}); } else { $units = shift if(ref $_[0] eq 'ARRAY'); } ### Check that the coordinate system really exists and that the ## units are compatible with the defined ones in the coordinate system. my($type) = $systems{$coords}; Carp::confess "Couldn't find coordinate system named '$coords'\n" unless ($type); if(! defined($units)) { $units = $type->{'units'}; } elsif( ((ref $units) ne 'ARRAY') || (@{$units} != 4)) { barf "Mangled units sent to V4..."; } my($sys_params) = (ref$_[0] eq 'HASH') ? shift : {}; ## Kludge until we handle timestamps right - skip units containing "time". ## Check that units agree with the defaults! for(my ($i)=0;$i<4;$i++) { if(!($systems{$coords}->{'units'}->[$i]) =~ m/time/) { barf "Unit $units[$i] don't agree with canonical $units[$i] ". "(coord $i)\n" unless (defined convert(1, $units[$i], $systems{$coords}->{'units'}->[$i] )); } } ## Parse the remaining arguments and stuff 'em into a PDL. my $a; while(scalar(@_)) { $a = shift; if('' eq ref $a) { $a = [$a,@_]; @_ = (); } if('ARRAY' eq ref $a) { ## Kludge for timestamp handling... zeroth coord is always time ## (so far). parsedate will ignore all timezone specifiers after ## the first, so we append a "UT" before converting, to make sure ## we default to the right zone. ## This is complex as we might have been passed a list of ## times 4-vectors and not just a single 4-vector. if('ARRAY' eq ref($a->[0])) { # Got a list of 4-vectors... for(@{$a}) { $_->[0] = Time::ParseDate::parsedate(($_->[0]).' UT') if(($_->[0]).0 ne ($_->[0])); } } elsif((($a->[0]+1)-1) ne $a->[0]) { # If $a->[0] is a string... $a->[0] = Time::ParseDate::parsedate($a->[0].' UT'); } $a = pdl($a); }elsif('PDL' eq ref $a) { barf "v4: v4's can only have up to 2 dimensions" if($a->dims > 2); if($a->dims <= 1) { ## Assume one PDL per coordinate my($out) = zeroes(4,$a->nelem); my($foo) = $out->slice('(0)'); $foo .= $a; $foo = $out->slice('(1)'); $foo .= shift; $foo = $out->slice('(2)'); $foo .= shift; $foo = $out->slice('(3)'); $foo .= shift; $a = $out; } } if((($a->dims)[0]) < 4) { $a = PDL::append($a,zeroes(4-(($a->dims)[0]))); } } my $self = {'coords'=> $coords ,'units'=> ((defined $units) ? $units : $systems{$coords}->{'units'} ) ,'sys_params' => $sys_params ,'PDL' => $a }; bless $self,__PACKAGE__; } #*initialize = \&v4; *main::v4 = \&v4; ###################################################################### =pod =head2 append =for ref Like PDL::append, but for V4's -- appends in the 2nd dimension, after making sure that all the coords are the same. =for usage $a = $v->append($v2); =cut sub append(;@) { my($a,@victims) = @_; local(@_); $a = $a->copy; foreach $_(@victims) { my($foo) = $_->xform($a->{coords},$a->{sys_params}); my($z); if($a->{PDL}->dims == 1) { $a->{PDL} = $a->{PDL}->dummy(1,1); } if($_->{PDL}->dims == 1) { $a->{PDL} = (PDL::append ($a->{PDL}->xchg(0,1),$_->{PDL}->dummy(1,1)->xchg(0,1)))->xchg(0,1); } else { $a->{PDL} = (PDL::append ($a->{PDL}->xchg(0,1),$_->{PDL}->xchg(0,1)))->xchg(0,1); } } return $a; } ###################################################################### =pod =head2 replicate =for ref Given a V4, makes copies of it in a nice array =cut sub replicate(;@) { my($a,$n) = @_; if($a->{PDL}->is_inplace) {$b=$a;} else {$b=$a->copy;} $b->{PDL} = $b->{PDL}->dummy(1,1) unless ($b->{PDL}->dims>1); $b->{PDL} = $b->{PDL}->dummy(2,$n)->copy->xchg(2,0)->clump(2)->xchg(1,0); $b->set_inplace(0); $b; } ###################################################################### =pod =head2 index =for ref Like PDL::index, but for V4's -- index out individual points. =for usage $a = $v->index(pdl(3,4,5)); =cut sub index (;@) : lvalue { my($a,$index) = @_; $index = pdl($index) unless (ref $index eq 'PDL'); my($out) = {PDL=>1}; my $foo; foreach $foo(keys %{$a}){ $out->{$foo} = main::deep_copy($a->{$foo}) unless($foo eq 'PDL'); } print "V4 index: pdl dims: ",$a->{PDL}->dims if($debug); print "\nindex dim is ",pdl($index)->nelem,"; max is ",max($index),"; min is ",min($index),"\n" if($debug); $out->{PDL} = $a->{PDL}->dice((xvals zeroes 4),$index); print "v4::index returning" if($debug); bless($out,'V4'); return $out; } ###################################################################### # =pod =head2 x, x0, x1, x2, x3 =for ref Access to individual coordinate slices of a V4 =for usage $v = v4('Heliographic',0,1,2,3) $r = $v->x3; Provide a way to get out individual coordinates of a system. These are implemented as direct calls to slice() but are preferred over them for extensibility reasons. These return slice-like connected references -- you can .= them like you could PDLs (because, after all, that's what they are! x0...x3 are syntactical sugar for x(0), x(1), etc. Just now the x0...x3 work by calling x -- that's because of the extra hassle involved in the ->dummy(0,1) workaround for the glitches with 0-dimensional piddles in PDL 2.005. =cut sub x0 : lvalue { return (my $a = x($_[0],0)) } sub x1 : lvalue { return (my $a = x($_[0],1)) } sub x2 : lvalue { return (my $a = x($_[0],2)) } sub x3 : lvalue { return (my $a = x($_[0],3)) } sub xlist { my $self = shift; local $_; my @out; if(!@_) { @_ = (0,1,2,3); } while(defined ($_=shift)) { my($a) = $self->{PDL}->slice("($_)"); bless($a,"PDL"); if($a->dims) { push @out,$a; } else { push @out,$a->dummy(0,1); } } return @out if(@out > 1); return $out[0]; } sub x : lvalue { my $self = shift; local $_; my @out; if(!@_) { @_ = (0,1,2,3); } while(defined ($_=shift)) { my($a) = $self->{PDL}->slice("($_)"); bless($a,"PDL"); if($a->dims) { push @out,$a; } else { push @out,$a->dummy(0,1); } } my $out = (@out > 1 ? cat(@out) : $out[0]); return $out; } ###################################################################### # =pod =head2 system =for ref Returns the canonical name of the coordinate system associated with a v4. Add additional parameters to set the name. =for usage $a = system($name)->{'coords'}; =cut sub system { if(@_>1) { $_[0]->{'coords'} = $_[1]; } return $systems{$_[0]->{'coords'}}->{'coords'}->[0]; } ###################################################################### # =pod =head2 coords =for ref Return the standard name of the coordinate system in a V4 =for usage = $v4->coords; =for example $a = v4('HS',undef,0,0,1); print $a->coords; Pass in a list of coords to retrieve the names of. If you pass in just one, you get back a scalar, otherwise a list. =cut sub coords { my $self = shift; if(!@_) { push(@_,0,1,2,3); } my @out; local $_; while($_=shift) { push(@out,systems->{$self->system}->{udesc}->[$_]); } return @out if(@out > 1); return $out[0]; } ###################################################################### =pod =head2 units =for ref Access to the units strings in a V4 =for usage $a = $v->units; =for example @a = units($v4) - Retrieve all units @a = units($v4,1,2) - Retrieve units for x1 & x2 units($v4,[undef,undef,'arcsec','minutes']); - Set units 2 & 3 Pass in an array ref to set units; a list of numbers to retrieve specific ones. =cut sub units { my $self = shift; @_=(0,1,2,3) if(!@_); my @out; local $_; foreach $_(@_) { if(ref $_ eq 'ARRAY') { print "UNIT SETTING NOT READY\n"; } push @out,$self->{'units'}->[$_]; } return @out if(@out > 1); return $out[0]; } ###################################################################### # =pod =head2 PDL::V4::string =for ref Stringify a V4 This routine takes a V4 and returns a string. It overlays the PDL stringify-overlay method, in order to return the V4 header and unit information. =cut sub string { my @out; my($self,$format)=@_; if($PDL::_STRINGIZING) { return "ALREADY_STRINGIZING_NO_LOOPS_V4"; } local $PDL::_STRINGIZING = 1; my $ndims = $self->getndims; if($self->nelem > 500) { return "Too many vectors to print"; } if($ndims == 0) { my @x = $self->at(); return ($format ? sprintf($format,$x[0]) : "$x[0]"); } return "Null" if $self->isnull; return "Empty" if $self->isempty; local $PDL::Core::sep = $PDL::use_commas ? "," : " "; local $PDL::Core::sep2 = $PDL::use_commas ? "," : ""; my $coord = $systems{$self->{'coords'}} || {}; push(@out,sprintf("4-vector: %s\n", ( (defined $coord) ? ($coord->{'coords'}->[0]) : ("invalid coordinate system '$self->{'coords'}'") ))); local($i); for($i=0;$i{'units'}});$i++){ push(@out,sprintf(" x$i: %15s (%s)\n" ,$coord->{'udesc'}->[$i] ,$self->{'units'}->[$i] )); } if($ndims == 1){ return join("",@out,PDL::Core::str1D($self,$format)); } else { return join("",@out,PDL::Core::strND($self,$format)); } } use overload ( ( "\"\"" => \&string ) ); ###################################################################### # =pod =head2 V4::resolve =for ref Find the best known conversion path between two coordinate systems. =for usage @chain = &resolve($from,$to); $from and $to are names of coordinate systems. @chain gets a list of coordinate system names separated by "atomic" transitions. It's returned as an array so that callers have a convenient way of getting the number of steps (with $chain = &resolve($from,$to)). Meanwhile, the list of subroutines gets stored in the V4 global data structures. =for description This is an internal method used by xform to find the proper sequence of atomic conversions to call for a particular conversion. The code works by traversing the existing @conversions array, looking for the conversion in question. If it's not there then a treewalker explores possible lines of conversion. The first path found is the shortest possible, and that sequence is returned. If there's no such path, then the empty list is returned. resolve starts by testing the trivial chain (an atomic conversion). Then it starts building a list of all non-repeating conversion chains that start from A. The central loop goes from L -> L+1 by scanning the entire conversions database for conversions from the last item in each existing chain. The possible chain extensions are filtered -- no circular chain is allowed. The process repeats until there are NO chains of the current length that start with A; or a chain is found that reaches B. The speed of the algorithm is something like O(N^2.5). The fastest method is faster, but more of a hassle to implement. TIMTOWTDI. History: Ported from V4_resolve_xform.pro, CED 1-Apr-2000 =cut sub resolve { my ($from,$to) = @_; # Regularize coordinate systems and barf if nonexistent if(ref $from =~ m/V4/) { $from = $from->{'coords'}; } $from = ($systems{$from}->{'coords'}->[0] || barf "V4::resolve: Unknown system `$from'"); $to = ($systems{$to} ->{'coords'}->[0] || barf "V4::resolve: Unknown system `$to'"); ############################## # Check for the trivial cases # return $from if($from eq $to); my $conv = $systems{$from}->{conversions}; if(defined($conv->{$to})) { my $steps = (scalar(@{$conv->{$to}})); barf "V4::resolve: Impossible to convert from `$from' to `$to'!" unless ($steps); return ($to) if($steps == 1); my @route = @{$systems{$from}->{conv_route}->{$to}}; barf "V4::resolve: Inconsistency in the databases! Help! (route has ".@route." steps; conv has $steps)" if(scalar(@route) != $steps); return @route; } ###################################################################### # Build chains of conversions that start from $from and fan out until # either we find no new ones or we reach maximal length. We keep track # of where we've been with %beenthere to avoid loops. The chains # consist of linked lists back from the endpoint to the source. # They're hashes with "val" and "back" tags. # # We deliberately avoid taking advantage of previously discovered # routes -- this ensures we'll always find the shortest route through # the graph. my (%beenthere,$root,$current,$prev,@paths,$i); $beenthere{$from} = 1; $root = {val=>$from}; $current = [$root]; for($i=0; !$beenthere{$to} && scalar(@{$current}); $i++) { $prev=$current; $current = []; my $newfrom_ref, $newto; foreach $newfrom_ref(@{$prev}) { foreach $newto(keys %{$systems{$newfrom_ref->{'val'}}->{conversions}}) { next if($beenthere{$newto}); my $newto_ref = $systems{$newfrom_ref->{'val'}}->{conversions}->{$newto}; if(scalar(@{$newto_ref}) == 1) { # Simple conversion found push (@{$current},{ 'val' => $newto ,'back'=> $newfrom_ref } ); $beenthere{$newto} = 1; } } } push(@paths,@{$current}); # Cache discovered paths -- may come in handy. } ## Now we've discovered all paths FROM the originating system up to ## the length of the first path that contains the desired system. ## Cache 'em all in the data structures! (saves another trip here.) my $to_ref,@route,@conv; my $begin_to_ref; foreach $begin_to_ref(@paths) { $to_ref = $begin_to_ref; for(@route=@conv=();defined $to_ref->{'back'};$to_ref=$to_ref->{'back'}) { unshift(@route,$to_ref->{'val'}); unshift(@conv, @{ $systems{ $to_ref->{'back'}->{'val'} } ->{conversions}->{ $to_ref->{'val'} } } ); } barf "V4::resolve: HELLoo -- conv has ".@conv." elements; route has ".@route unless( @route == @conv ); # scalar comparison $systems{$from}->{conversions}->{$begin_to_ref->{'val'}} = [@conv]; $systems{$from}->{conv_route} ->{$begin_to_ref->{'val'}} = [@route]; } my $out = $systems{$from}->{conv_route}->{$to}; if(!defined $out) { $systems{$from}->{conversions}->{$to}=[]; $systems{$from}->{conv_route}->{$to}=[]; barf "V4::resolve: No route found from `$from' to `$to'!\n\t(Marked it impossible.)\n"; } return @{$out}; } ###################################################################### =pod =head2 copy =for ref Copies a 4-vector. Overlay for the PDL ->copy. It uses deep_copy to copy the structure. =cut sub copy { print "V4 copy...\n" if($debug); my(%out); my($key); my($in) = shift; foreach $key(keys %{$in}) { if(ref($in->{$key})) { $out{$key} = main::deep_copy($in->{$key}); } else{ $out{$key} = $in->{$key}; } } return bless( \ %out, 'V4'); } ###################################################################### =pod =head2 xform =for ref Convert a 4-vector to another coordinate system. =for usage $a = xform( $b, 'SRS', [{Coord-system-parms}] ); =for description xform just walks the conversion tables given to it by resolve(), calling the conversion routines listed in the main %V4::systems data structure. Note that because only one set of system_args is currently supported -- and that is passed to each of the conversion routines, whether it wants 'em or not! -- only one transition (out of the whole chain) that supports arbitrary parameters is currently allowed -- or, I suppose, multiple ones may be allowed, provided that their parameters are appropriately selected to avoid collision! =cut sub xform { my($vect,$to,$system_args) = @_; # Make sure it's not the identity transform! if($systems{$vect->{'coords'}} == $systems{$to}) { return $vect; } # Make sure path exists if(!scalar(resolve($vect->{coords},$to))) { barf "You can't get here! (resolve should've barfed)"; } $out = $vect->copy unless($vect->{PDL}->is_inplace); local $conv_sub; print "Converting: " if($debug); foreach $conv_sub(@{$systems{$vect->{coords}}->{conversions}-> {$systems{$to}->{coords}->[0]}}) { $out = &{$conv_sub}((inplace $out),$system_args); } $out->{coords} = $to; $out->{sys_params} = $system_args if (defined($system_args)); bless $out,'V4'; return $out; } ###################################################################### =pod =head2 canonicalize =for ref Put a 4-vector into "standardized" units for that coordinate system =for usage $a = $a->canonicalize(); =cut sub canonicalize { my $a = @_[0]; my ($type) = $systems{$a->{coords}}; barf "canonicalize: unknown system!" unless($type); $a->{'coords'} = $type->{coords}->[0]; for(my $i=0;$i<4;$i++) { my $foo; $foo = $a->{PDL}->slice("($i)"); $foo .= PDL::V4::Xforms::unit($foo,$a->{'units'}->[$i] , $type->{'units'}->[$i] ); $a->{'units'}->[$i] = $type->{'units'}->[$i]; } bless $a; return $a; } ###################################################################### # Data structures follow. # =pod =head2 @V4::systems, %V4::systems. Array containing system definition data structures. The array is defined in the source code; but the hash is filled by the package constructor, so that all the official synonyms can be used to access a package's information. Each element is a hash ref. Tags are: coords A list of names for each system. The 0th element is a verbose name; the 1st is the canonical abbreviation. Alternate names follow. units A list of four unit-names containing the canonical scientific units for the system (e.g. "km"). udesc A list of strings describing (briefly!) the meaning of each coordinate. desc A string containing a (one-line!) description of the coordinate system. metric A string containing the name of the subroutine used to measure distance in the coordinate system. The metric returns values in the canonical units for the coordinate system. conversions A hash of other coordinate systems to which we know the conversions. The value of each of these tags is an array ref containing a sequence of subroutines to be called in sequence to get to the target. An empty array ref signifies that it's impossible to get there from here! conv_route A hash of routes to other coordinate systems to which we know the conversions. Each of these tags points to an array ref containing the names of the coordinate systems in the conversion. Only nontrivial routes are listed. (The conv_route tag is written to by the resolve routine and is not present in the static data structure declared here.) cartesian A string containing the name of the associated Cartesian coordinate system, if any. This is used for conversions (such as derived or offset systems) that are generic but only work between cartesian systems. If the string doesn't exist, there is no known associated Cartesian system; and if it exists but is the empty string, then this system *is* Cartesian. =head1 COORDINATE SYSTEMS =cut BEGIN { =pod =head2 Earth Range Solar ("ERS","Solar","S") (Spherical) Earth-centered "observing coordinates". These are used for historical reasons, though observing coordinates are probably best treated as an EQA projection rather than true spherical. This system is centered on Earth, with argument along the Earth-Sun line and pole in the plane defined by the E-S line and the Earth's north pole. It is a LEFT HANDED system -- longitude gets POSITIVE to the west. Again, this is for historical reasons -- the longitude is like the traditional "solar-X" and latitude is like "solar-Y". This is the coordinate system used at the focal plane of near-Earth solar observations. =head2 Solar Range Solar ("SRS") (Modified spherical) These are Earth Range Solar coordinates, with the radius replaced by radius from Sun center. This is not a complete coordinate system, as *two* points satisfy any given 4-tuple (near side and far side of the Sun). These coordinates are used primarily as an intermediate step for heliographic conversion, and the conversion routines take the closer branch of the ambiguity (because you can't see the far side of the surface of the Sun). =cut push(@systems,( {coords=> ["Earth Range Solar","ERS","Solar","S"] ,units=> ["unixtime", "arcsec", "arcsec", "solar-radii"] ,udesc=> ["time", "W angle", "Solar-N angle","distance"] ,desc=> "Spherical coords; 2-D origin at Disk Center; P=0; arg. thru Sun; LEFT-HANDED!" ,metric=>"V4MSPH", cartesian=>"Solar Cartesian" ,conversions => { "Solar Cartesian" => [ \&PDL::V4::Xforms::ERS2SCart ] ,"Heliographic Cartesian" => [ sub { &PDL::V4::Xforms::ERS2SCart( $_[0], $_[1], 1); } ] }} ,{coords=> ["Solar Range Solar","SRS"] ,units=> ["unixtime", "arcsec", "arcsec", "solar-radii"] ,udesc=> ["time", "W angle", "Solar-N angle","altitude"] ,desc=> "Observing coords: Earth-cent. sph.; radius rel. to Sun center; P=0" ,metric=>"V4MSRS" ,conversions => { }} )); =pod =head2 Solar Cartesian ("SC", "Earth Solar Cartesian", "ESC") This is the cartesian coordinate system that corresponds best to the Earth-Range Solar and Solar-Range Solar coordinates. It is a cartesian system centered on the Sun's center and oriented to match ERS: "Z" is directly away from the Earth on the E-S line; "X" is positive-west, and "Y" lies in the plane defined by the E-S line and the (Earth's) north pole. =cut push(@systems,( {coords=> ["Solar Cartesian","SC","Earth Solar Cartesian","ESC"] ,units=> ["unixtime", "solar-radii", "solar-radii", "solar-radii"] ,udesc=> ["time", "W fr. Sun ctr","up fr. Sun ctr","toward Earth"] ,desc=> "Sun-centered Earth-N-oriented cartesian (approx. SRS)" ,metric=>"V4MCART", cartesian=>"" ,conversions => { "Earth Range Solar" => [ \&SCart2ERS ] ,"Heliographic Cartesian" => [ sub { SCart2HC($_[0],$_[1],1); } ] }} )); =pod =head2 Heliographic Cartesian ("HC") This is the cartesian system matching Heliographic coordinates. It is centered on the Solar center, +Y is true Solar north, +Z is approximately away from the Earth, and +X is west. =cut push(@systems,( {coords=> ["Heliographic Cartesian","HC"] ,units=> ["unixtime", "solar-radii", "solar-radii", "solar-radii"] ,udesc=> ["time", "W fr. Sun ctr.", "Solar-N fr. Sun ctr.", "toward Earth"] ,desc=> "Sun-centered solar-N-oriented cartesian" ,metric=>"V4MCART", cartesian=>"" ,conversions => { "Heliographic" => [sub { Cart2Sph($_[0],{ix=>[0,3,1,2],%{$_[1]}}); }] ,"Offset Cartesian" => [ \&PDL::V4::Xforms::offset_cartesian ] ,"Solar Cartesian" => [ sub {SCart2HC($_[0],$_[1],-1); } ] ,"Earth Range Solar" => [ sub { PDL::V4::Xforms::SCart2ERS($_[0],$_[1],1); } ] }} )); =pod =head2 Heliographic ("H") These are Solar spherical coordinates -- *Solar* latitude and longitude, with the prime meridian intersecting the Earth-Sun line. =cut push(@systems,( {coords=> ["Heliographic","H"] ,units=> ["unixtime", "deg", "deg", "solar-radii"] ,udesc=> ["time", "H-Longitude", "Latitude", "above Sun ctr"] ,desc=> "Spherical, with pole at Solar North and prime meridian at disk ctr" ,metric=>"V4MSPH", cartesian=>"HC" ,conversions => { "Heliographic Cartesian"=> [ sub { Sph2Cart($_[0],{ox=>[0,3,1,2],coords=>'Heliographic Cartesian'}); } ] ,"Carrington" => [ sub { barf "H -> C not here yet"; } ] }} )); =pod =head2 Carrington (C) The traditional Carrington solar spherical coordinates, in the frame of the approximate overall solar rotation. North is true Solar north; longitude is the traditional Carrington longitude. =head2 Carrington Cartesian (CC) Cartesian system matching the Carrington system. +Z rolls around with the Carrington prime meridian. =cut push(@systems,( {coords=> ["Carrington", "C"] ,units=> ["unixtime", "deg", "deg", "solar-radii"] ,udesc=> ["time", "C-Longitude", "Latiude", "above Sun ctr"] ,desc=> "Pole at Solar North; prime meridian spinning with average Sun" ,metric=> "V4MSPH", cartesian=>"CC" ,conversions => { "Heliographic" => [ sub {barf "C -> H not here yet"; } ] ,"Carrington Cartesian" => [ sub {Sph2Cart($_[0],{ox=>[0,3,1,2],coords=>'Carrington Cartesian'}); } ] }} ,{coords=> ["Carrington Cartesian", "CC"] ,units=> ["unixtime", "km", "km", "km"] ,udesc=> ["time", "Solar-E", "Solar-N", "Out pr. meridian"] ,desc=> "Cartesian coords matching the Carrington system" ,metric=> "V4MCART", cartesian=>"" ,conversions => { "Carrington" => [ sub {Cart2Sph($_[0],{ix=>[0,3,1,2],coords=>"Carrington"}); } ] }} )); =pod =head2 Earth Solar Polar ("ESP", "zunwrap") Spherical coordinates based at Earth; but the pole goes through the Earth-Sun line, and the argument is North. The "longitude" is azimuth (CW) around the Sun, and the colatitude measures impact parameter of each line of sight. (x2 is in fact colatitude rather than latitude.) Useful for interpreting radially-symmetric objects in solar observations. Called "zunwrap" after an earlier coordinate transform routine. =cut push(@systems,( {coords=> ["Earth Solar Polar", "ESP", "zunwrap"] ,units=> ["unixtime", "deg", "solar-radii", "solar-radii"] ,udesc=> ["time", "Azimuth", "degrees", "Dist along E-S line"] ,desc=> "Zunwrapped solar coords -- azimuth and impact parameter of L.O.S." ,metric=> "V4MCYL", ,conversions => { }} ,{coords=>["Heliocentric Ecliptic Cartesian", "HEC", "HE", "Heliocentric Ecliptic"] ,units => ["unixtime", "km", "km", "km"] ,udesc => ["time", "toward Aries", "perpl", "Ecliptic North"] ,desc => "Cartesian coords centered on Sun, aligned with Ecliptic" ,metric => "V4MCART", cartesian=>"" ,conversions => { }} )); =pod =head2 Offset Cartesian ("OC", "Perspective Cartesian", "PC") Cartesian coordinates with parametrized origin and Euler angle rotation from another coordinate system. =head2 Offset Spherical ("Perspective Spherical", "PS", "OS") Spherical coordinates with parametrized origin and Euler angle rotation from another coordinate system (currently, the only parent system that is supported is the Heliographic one). [This system is derived from OC] =head2 Perspective ("Offset Equi-Angular Azimuthal", "P", "OEQA") Equi-Angular Azimuthal Perspective coordinates with parametrized origin and Euler angle rotation from another coordinate system. x1 and x2 define points in an image plane such that the distance to the origin in the image plane is proportional to the angle between that line of sight and the "Z" axis, and azimuth is preserved. x3 is radius from the origin. [This system is derived from OC] =cut push(@systems,( {coords=>["Offset Spherical", "Perspective Spherical", "PS", "OS"] ,units=> ["unixtime","deg","deg","solar-radii"] ,udesc=>["time","horiz. angle","vert. angle","range"] ,desc=>"Perspective: Sph. coords w/ (lat,lon) origin pointed at major origin" ,metric => "V4MSPH", cartesian=>"Offset Cartesian" ,conversions => { "Offset Cartesian" => [ sub {Sph2Cart($_[0],{ox=>[0,3,1,2],coords=>"Offset Cartesian",%{$_[1]}});} ] } } ,{ coords=>[ "Offset Cartesian", "OC", "Perspective Cartesian", "PC" ] ,units=>["unixtime","solar-radii","solar-radii","solar-radii"] ,udesc=>["time","arb. X","arb. Y","arb. Z"] ,desc=>"Cartesian system offset at arb. angle from Heliographic Cartesian" ,metric=> "V4MCART", cartesian=>"" ,conversions => { "Heliographic Cartesian" => [ \&PDL::V4::Xforms::unoffset_cartesian ] ,"Offset Spherical" => [ sub{Cart2Sph($_[0],{ix=>[0,3,1,2],coords=>"Offset Spherical"});} ] ,"Perspective" => [ \&PDL::V4::Xforms::Cart2EQA ] } } ,{ coords=>[ "Perspective", "Offset Equi-Angular Azimuthal", "P","OEQA" ] ,units=>["unixtime","deg","deg","solar-radii"] ,desc =>["Spherical projection preserving angular dist. fr. origin line"] ,udesc=>["time","proj. X", "proj. Y", "range"] ,metric=>"to OC", cartesian=>"Offset Cartesian" ,conversions=> { "Offset Cartesian" => [ \&PDL::V4::Xforms::EQA2Cart ] }} )); } BEGIN { local ($a); print "V4 initializing systems list...\n" if ($debug); foreach $a(@systems) { foreach $b(@{$a -> {'coords'}}) { $systems{$b}=$a; } } } 1;