File Coverage

blib/lib/Location/GeoTool.pm
Criterion Covered Total %
statement 232 348 66.6
branch 64 94 68.0
condition 33 56 58.9
subroutine 40 47 85.1
pod 8 22 36.3
total 377 567 66.4


line stmt bran cond sub pod time code
1             package Location::GeoTool;
2              
3             ################################################################
4             #
5             # Geometric Functions
6             # Location::GeoTool
7             #
8              
9 8     8   1420433 use 5.008;
  8         32  
  8         289  
10 8     8   43 use strict;
  8         18  
  8         277  
11 8     8   39 use warnings;
  8         17  
  8         354  
12 8     8   44 use vars qw($VERSION @ISA $AUTOLOAD %engines);
  8         13  
  8         851  
13             $VERSION = 2.000000;
14              
15             ################################################################
16             # Dependency #
17             ################################
18              
19 8     8   8009 use Math::Trig qw(asin tan);
  8         505655  
  8         862  
20 8     8   96 use Carp;
  8         16  
  8         17431  
21             require Location::GeoTool::Direction;
22             require XSLoader;
23              
24             my $enginename = 'pp';
25             eval {
26             XSLoader::load('Location::GeoTool', $VERSION);
27             $enginename = 'xs';
28             };
29              
30             ################################################################
31             # Initialize #
32             ################################
33              
34             __PACKAGE__->_make_accessors(
35             qw(
36             def_datum def_format out_datum out_format cache_lat cache_long alt
37             changeMyself enableWantarray defaultDatum defaultFormat
38             )
39             );
40              
41             my $pi = 4 * atan2(1,1); # PI
42             my $rd = $pi / 180; # [radian/degree]
43              
44             ################################
45             # Use pureperl? or XS?
46              
47             $engines{'v2p'} = \&{"v2p_$enginename"};
48             $engines{'p2v'} = \&{"p2v_$enginename"};
49             $engines{'mol'} = \&{"molodensky_$enginename"};
50              
51             ################################
52             # Default data
53              
54             # Direction string
55             my %dirstr_master = (
56             'jp' => [
57             'ËÌ','ËÌÈùÅì','ËÌËÌÅì','ËÌÅìÈùËÌ','ËÌÅì','ËÌÅìÈùÅì','ÅìËÌÅì','ÅìÈùËÌ',
58             'Åì','ÅìÈùÆî','ÅìÆîÅì','ÆîÅìÈùÅì','ÆîÅì','ÆîÅìÈùÆî','ÆîÆîÅì','ÆîÈùÅì',
59             'Æî','ÆîÈùÀ¾','ÆîÆîÀ¾','ÆîÀ¾ÈùÆî','ÆîÀ¾','ÆîÀ¾ÈùÀ¾','À¾ÆîÀ¾','À¾ÈùÆî',
60             'À¾','À¾ÈùËÌ','À¾ËÌÀ¾','ËÌÀ¾ÈùËÌ','ËÌÀ¾','ËÌÀ¾ÈùÀ¾','ËÌËÌÀ¾','ËÌÈùÀ¾'
61             ],
62             'en' => [
63             'N','NbE','NNE','NEbN','NE','NEbE','ENE','EbN',
64             'E','EbS','ESE','SEbE','SE','SEbS','SSE','SbE',
65             'S','SbW','SSW','SWbS','SW','SWbW','WSW','WbS',
66             'W','WbN','WNW','NWbN','NW','NWbW','NNW','NbW'
67             ]
68             );
69             my $def_dirstr = 'jp';
70              
71             # Format
72             my %format_sub = map { $_ => [\&_def_fmt_in,\&_def_fmt_out]} ('mapion','dmsn','second','degree','radian','gpsone','spacetag');
73              
74             # Code
75             my %code_sub;
76              
77             # Datum
78             my %ellip = (
79             'wgs84' => [6378137,(1 / 298.257223),0,0,0],
80             'tokyo' => [6377397.155,(1 / 299.152813),148,-507,-681]
81             );
82              
83             # Constants
84             my $changeMyself = 0;
85             my $enableWantarray = 0;
86             my $defaultDatum = 'wgs84';
87             my $defaultFormat = 'gpsone';
88              
89             ################################################################
90             # Class Methods #
91             ################################
92              
93             # Import : set plugin
94             sub import {
95 19     19   97 my $class = shift;
96 19         34 my $piname = '';
97 19         333 foreach my $plugin (@_) {
98 3         7 $piname = '';
99 3         8 foreach my $type ('','Plugin') {
100 6 100       33 my $name = __PACKAGE__.($type eq "" ? "" : "::$type")."::$plugin";
101 6         396 eval "require $name;";
102 6 100       49 unless ($@) {
103 3         6 $piname = $name;
104 3         9 last;
105             }
106             }
107 3 50       14 croak "Can't find plugin named $plugin" unless ($piname);
108 3         24 $piname->setup;
109             }
110             }
111              
112             # Set new format you like
113             sub set_original_format
114             {
115 0     0 0 0 my $class = shift;
116 0         0 $format_sub{$_[0]} = $_[1];
117             }
118              
119             # Set new code you like
120             sub set_original_code
121             {
122 3     3 0 7 my $class = shift;
123 3         5413 $code_sub{$_[0]} = $_[1];
124             }
125              
126             # Set new datum you like
127             sub set_original_datum
128             {
129 0     0 0 0 my $class = shift;
130 0         0 $ellip{$_[0]} = $_[1];
131             }
132              
133             # Set new direction string you like
134             sub set_original_dirstr
135             {
136 0     0 0 0 my $arg = shift;
137 0 0       0 if (ref($arg) eq "ARRAY")
138             {
139 0         0 $dirstr_master{'def_original'} = $arg;
140 0         0 $def_dirstr = 'def_original';
141             }
142             else
143             {
144 0         0 $dirstr_master{$_[0]} = $_[1];
145             }
146             }
147              
148             ################################################################
149             # Object Methods #
150             ################################
151              
152             ################################
153             # Constructor by 2D coordinates
154             sub create_coord
155             {
156 78     78 1 27354 shift->set_coord(@_);
157             }
158              
159             sub set_coord
160             {
161 78     78 1 117 my $self = shift;
162 78         345 return $self->create_coord3d(@_[0..1],"+000000",@_[2..$#_]);
163             }
164              
165             # Constructor by 3D coordinates ... it has no effect in this version
166             sub create_coord3d
167             {
168 74     74 0 104 my $self = shift;
169 74 50       365 $self = $self->new() unless (ref($self));
170 74         169 @{$self}{qw(lat long alt def_datum def_format source)} = @_;
  74         414  
171 74   50     223 $self->{def_datum} ||= 'wgs84';
172 74   50     1403 $self->{def_format} ||= 'spacetag';
173 74         107 $self->_set_outsetting(@{$self}{qw(def_datum def_format)});
  74         198  
174 74         246 return $self;
175             }
176              
177             ################################
178             # Accessor to 2D coordinates
179             sub array
180             {
181 275     275 1 314 my $self = shift;
182 275 100 100     510 return ($self->{lat},$self->{long}) if (($self->def_datum eq $self->out_datum) && ($self->def_format eq $self->out_format));
183 229 50 33     698 ($self->{cache_lat},$self->{cache_long}) = $self->_exec_array($self->out_datum,$self->out_format) unless ($self->cache_lat && $self->cache_long);
184 229         530 return ($self->cache_lat,$self->cache_long);
185             }
186              
187             # Accessor to Latitude
188             sub lat
189             {
190 2     2 1 21 my $self = shift;
191 2         6 my @cache = $self->array;
192 2         13 return $cache[0];
193             }
194              
195             # Accessor to Longitude
196             sub long
197             {
198 2     2 1 5 my $self = shift;
199 2         6 my @cache = $self->array;
200 2         11 return $cache[1];
201             }
202              
203             # Accessor to 3D coordinates ... it has no effect in this version
204             sub array3d
205             {
206 0     0 0 0 my $self = shift;
207 0         0 return ($self->array,$self->alt);
208             }
209              
210             ################################
211             # Create Location::GeoTool::Direction object start from here, giving end point
212             sub direction_point
213             {
214 12     12 1 6572 my $self = shift;
215 12         49 return Location::GeoTool::Direction->new($self)->set_topoint(@_);
216             }
217              
218             ################################
219             # Create Location::GeoTool::Direction object start from here, giving direction and distance
220             sub direction_vector
221             {
222 16     16 1 11703 my $self = shift;
223 16         73 return Location::GeoTool::Direction->new($self)->set_vector(@_);
224             }
225              
226             ################################
227             # For... changing datum or format method
228             sub AUTOLOAD
229             {
230 21     21   8734 my $self = shift;
231 21         36 my $method = $AUTOLOAD;
232 21         120 $method =~ s/.+://;
233 21 50       65 return if ($method eq "DESTROY");
234              
235 21 100 66     142 if ($method =~ /^format_(\w+)$/) # changing format
    100          
    100          
    50          
236             {
237 13 50       57 croak qq{Not support this format : $1 at this version of Location::GeoTool}
238             unless ($format_sub{$1});
239 13         29 my $out_format = $1;
240 8     8   59 no strict 'refs';
  8         17  
  8         1171  
241 13         50 *{$method} = sub {
242 261     261   128079 my $self = shift;
243 261         548 $self->_exec_change($self->out_datum,$out_format,@_);
244 13         77 };
245 13         49 return $self->$method(@_);
246             }
247             elsif ($method =~ /^datum_(\w+)$/) # changing datum
248             {
249 5 50       24 croak qq{Not support this datum : $1 at this version of Location::GeoTool}
250             unless ($ellip{$1});
251 5         14 my $out_datum = $1;
252 8     8   43 no strict 'refs';
  8         21  
  8         1366  
253 5         16 *{$method} = sub {
254 32     32   3712 my $self = shift;
255 32         75 $self->_exec_change($out_datum,$self->out_format,@_);
256 5         27 };
257 5         19 return $self->$method(@_);
258             }
259             elsif (($method =~ /^create_(\w+)$/) || ($method =~ /^set_(\w+)$/))
260             {
261 1 50       9 croak qq{Cannot create from this format : $1 at this version of Location::GeoTool}
262             unless ($code_sub{$1});
263 1         13 my $from_code = $code_sub{$1}->[0];
264 8     8   67 no strict 'refs';
  8         21  
  8         1032  
265 1         5 *{$method} = sub {
266 2     2   539 $from_code->(@_);
267 1         7 };
268 1         6 return $self->$method(@_);
269             }
270             elsif ($method =~ /^get_(\w+)$/)
271             {
272 2 50       12 croak qq{Cannot get from this format : $1 at this version of Location::GeoTool}
273             unless ($code_sub{$1});
274 2         7 my $to_code = $code_sub{$1}->[1];
275 8     8   126 no strict 'refs';
  8         16  
  8         2228  
276 2         8 *{$method} = sub {
277 7     7   32 $to_code->(@_);
278 2         11 };
279 2         10 return $self->$method(@_);
280             }
281             else
282             {
283 0         0 croak qq{Can't locate object method "$method" via package "Location::GeoTool"};
284             }
285             }
286              
287             ################################################################
288             # Internal Methods for OO #
289             ################################
290              
291             # Internal Constructor
292             sub new
293             {
294 78     78 1 110 my $class = shift;
295 78 50 33     287 my $opt = $_[0] && ref($_[0]) eq 'HASH' ? shift : {} ;
296 78         507 return bless {
297             changeMyself => $changeMyself,
298             enableWantarray => $enableWantarray,
299             defaultDatum => $defaultDatum,
300             defaultFormat => $defaultFormat,
301 78         127 %{$opt},
302             },$class;
303             }
304              
305             # Construct accessor methods
306             sub _make_accessors
307             {
308 10     10   59 my($class, @attr) = @_;
309 10         27 for my $attr (@attr) {
310 8     8   47 no strict 'refs';
  8         12  
  8         31792  
311 90         394 *{"$class\::$attr"} = sub {
312 4711 50   4711   8628 $_[0]->{$attr} = $_[1] if (defined($_[1]));
313 4711         14161 $_[0]->{$attr}
314 90         240 };
315             }
316             }
317              
318             # Clone myself
319             sub _clone
320             {
321 321     321   367 my $self = shift;
322 321         3419 return bless {%$self},ref($self);
323             }
324              
325             # Set datum and format
326             sub _set_outsetting
327             {
328 371     371   443 my $self = shift;
329 371         500 @{$self}{qw(out_datum out_format cache_lat cache_long)} = @_;
  371         1275  
330             }
331              
332             # Create child object or return lat/long value
333             # They are in same function for future extension
334             sub _exec_change
335             {
336 293     293   367 my $self = shift;
337 293         432 my ($outdatum,$outformat) = @_;
338              
339 293 50       499 my $copy = $self->changeMyself ? $self : $self->_clone;
340 293         879 $copy->_set_outsetting($outdatum,$outformat);
341 293 50 33     814 return $copy->array if (wantarray && $self->enableWantarray);
342 293         1385 return $copy;
343             }
344              
345             sub _exec_array {
346 229     229   273 my $self = shift;
347              
348 229         272 my ($lat,$long) = @{$self}{qw(lat long)};
  229         499  
349 229 50 33     1143 return unless (defined($lat) && defined($long));
350 229 100       412 if ($self->def_datum eq $self->out_datum)
351             {
352 221 50       429 ($lat,$long) = map { coordformat($_,$self->def_format,$self->out_format) } ($lat,$long) if ($self->def_format ne $self->out_format);
  442         863  
353             }
354             else
355             {
356 8 50       22 ($lat,$long) = map { coordformat($_,$self->def_format,'degree') } ($lat,$long) unless ($self->def_format eq 'degree');
  16         33  
357 8         21 ($lat,$long) = datumchange_degree($lat,$long,"+000000",$self->def_datum,$self->out_datum);
358 8 50       27 ($lat,$long) = map { coordformat($_,'degree',$self->out_format) } ($lat,$long) unless ($self->out_format eq 'degree');
  16         33  
359             }
360 229         761 return ($lat,$long);
361             }
362              
363             ################################################################
364             # Basic functions #
365             ###############################
366              
367             ################################
368             # Get the name of direction from degree
369              
370             sub direction_string
371             {
372 32     32 0 49 my ($degree,$mother,$lang) = @_;
373 32   33     34 my @master = @{$dirstr_master{ $lang || $def_dirstr }};
  32         170  
374              
375 32         58 my $masternum = @master;
376 32         33 my @mothermaster;
377 32         65 for (my $i = 0;$i < $masternum;$i += int($masternum/$mother))
378             {
379 480         947 push(@mothermaster,$master[$i]);
380             }
381              
382             # Turn 45 degree
383 32         53 $degree += int(360/($mother*2));
384 32   66     127 while (($degree < 0) || ($degree >= 360))
385             {
386 2 50       4 $degree -= 360 if ($degree >= 360);
387 2 50       11 $degree += 360 if ($degree < 0);
388             }
389 32         184 return $mothermaster[int($degree/360*$mother)];
390             }
391              
392             ################################
393             # Change the format of coordinate
394              
395             sub coordformat
396             {
397 474     474 0 951 my ($coord,$farg,$targ,$pon) = @_[0..3];
398 474 100       2110 my $s = ($coord =~ s/^(\+|-)//) ? $1 : "+";
399 474         551 my ($dd, $mm, $ss) = &{$format_sub{$farg}->[0]}($coord,$farg);
  474         1092  
400 474         626 my $ret;
401 474         534 ($ret,$pon) = &{$format_sub{$targ}->[1]}($dd, $mm, $ss, $targ, $pon);
  474         1124  
402              
403             # Set Plus/Minus sign if wanted
404 474 100 66     2363 if ((($pon) && ($pon == 1)) || ($s eq '-'))
      100        
405             {
406 52         115 $ret = $s.$ret;
407             }
408              
409 474         1427 return $ret;
410             }
411              
412             # Default engine for decode each format to degree,minute and second.
413             # You can add any original format by using set_original_format
414             sub _def_fmt_in
415             {
416 474     474   606 my ($coord,$farg) = @_;
417 474         475 my ($dd, $mm, $ss);
418              
419 474 100 100     2289 if ($farg eq 'spacetag')
    100 100        
    100          
    100          
    50          
420             {
421 48         149 $coord =~ /(\d{3})(\d{2})(\d{2})(\d{3})$/;
422 48         73 $dd = $1;
423 48         71 $mm = $2;
424 48         130 $ss = $3+$4/1000;
425             }
426             elsif ($farg eq 'mapion')
427             {
428 48         189 $coord =~ /(\d+)\/(\d+)\/(\d+(\.\d+)?)$/;
429 48         84 $dd = $1;
430 48         61 $mm = $2;
431 48         71 $ss = $3;
432             }
433             elsif ($farg eq 'dmsn')
434             {
435 130         626 $coord =~ /(\d+)(\d{2})(\d{2}(\.\d+)?)$/;
436 130         235 $dd = $1;
437 130         182 $mm = $2;
438 130         193 $ss = $3;
439             }
440             elsif (($farg eq 'second') || ($farg eq 'degree') || ($farg eq 'radian'))
441             {
442 200 100       590 if ($farg eq 'second')
    100          
443             {
444 64         85 $ss = $coord;
445             }
446             elsif ($farg eq 'degree')
447             {
448 88         129 $ss = $coord * 3600;
449             }
450             else
451             {
452 48         66 $ss = $coord * 3600 / $rd;
453             }
454 200         280 $dd = int($ss/3600);
455 200         249 $ss = $ss-$dd*3600;
456 200         211 $mm = int($ss/60);
457 200         235 $ss = $ss-$mm*60;
458             }
459             elsif ($farg eq 'gpsone')
460             {
461 48         189 $coord =~ /(\d+)\.(\d+)\.(\d+(\.\d+)?)$/;
462 48         86 $dd = $1;
463 48         55 $mm = $2;
464 48         74 $ss = $3;
465             }
466 474         721 $dd += 0;
467              
468 474         1348 return ($dd,$mm,$ss);
469             }
470              
471             # Default engine for encode degree,minute and second to each format.
472             # You can add any original format by using set_original_format
473             sub _def_fmt_out
474             {
475 474     474   746 my ($dd, $mm, $ss, $targ, $pon) = @_;
476 474         458 my $ret;
477 474 100 100     2354 if ($targ eq 'spacetag')
    100 100        
    100          
    100          
    50          
478             {
479 48         50 $pon = 1;
480 48         282 $ret = sprintf("%03d%02d%06.3f",$dd,$mm,$ss);
481 48         154 $ret =~ s/\.//;
482             }
483             elsif ($targ eq 'mapion')
484             {
485 48         308 $ret = sprintf("%d/%02d/%06.3f",$dd,$mm,$ss);
486             }
487             elsif ($targ eq 'dmsn')
488             {
489 72         471 $ret = sprintf("%d%02d%06.3f",$dd,$mm,$ss);
490             }
491             elsif (($targ eq 'second') || ($targ eq 'degree') || ($targ eq 'radian'))
492             {
493 258         590 $ret = $dd * 3600 + $mm * 60 + $ss;
494 258 100       498 if ($targ eq 'degree')
495             {
496 146         189 $ret = $ret/3600;
497             }
498 258 100       473 if ($targ eq 'radian')
499             {
500 48         71 $ret = $ret*$rd/3600;
501             }
502             }
503             elsif ($targ eq 'gpsone')
504             {
505 48         284 $ret = sprintf("%d.%02d.%06.3f",$dd,$mm,$ss);
506             }
507              
508 474         1141 return ($ret,$pon);
509             }
510              
511             ################################
512             # Calcurate a point that has the direction
513             # and the distance from other point
514              
515             sub vector2point_degree
516             {
517 16     16 0 39 my ($lat,$lon,$dir,$dis) = @_[0..3];
518 16   50     40 my $datum = $_[4] || 'wgs84';
519              
520 16         26 my $ellip = $ellip{$datum};
521 16         26 my $a = $ellip->[0]; # Equatorial Radius
522 16         22 my $f = $ellip->[1]; # Flattening
523              
524 16         22 return &{$engines{'v2p'}}($f,$a,$rd,$lat,$lon,$dir,$dis);
  16         259  
525             }
526              
527             ################################
528             # Calcurate distance and direction of points
529              
530             sub point2vector_degree
531             {
532 12     12 0 31 my ($lat,$lon,$tlat,$tlon) = map { $_ * $rd } @_[0..3];
  48         75  
533 12   50     31 my $datum = $_[4] || 'wgs84';
534              
535 12         25 my $ellip = $ellip{$datum};
536 12         16 my $a = $ellip->[0]; # Equatorial Radius
537 12         25 my $f = $ellip->[1]; # Flattening
538              
539 12         15 return &{$engines{'p2v'}}($f,$a,$rd,$lat,$lon,$tlat,$tlon);
  12         204  
540             }
541              
542             ################################
543             # Change the coordinate to different datum
544              
545             sub datumchange_degree
546             {
547 8     8 0 13 my ($b,$l,$h,$from,$to) = @_;
548 8         475 $h = eval($h)/100;
549 8 50       24 ($from,$to) = map { $_ || 'wgs84' } ($from,$to);
  16         54  
550              
551 8         13 my $fellip = $ellip{$from};
552 8         14 my $tellip = $ellip{$to};
553 8         18 my @a = ($fellip->[0],$tellip->[0]); # Equatorial Radius
554 8         13 my @f = ($fellip->[1],$tellip->[1]); # Flattening
555              
556 8         14 my $dx = $tellip->[2] - $fellip->[2];
557 8         11 my $dy = $tellip->[3] - $fellip->[3];
558 8         45 my $dz = $tellip->[4] - $fellip->[4];
559              
560 8         18 return ($b, $l, $h) = &{$engines{'mol'}}($b, $l, $h, $a[0], $f[0], $a[1], $f[1],$dx,$dy,$dz,$rd);
  8         93  
561             }
562              
563             ################################################################
564             # Pureperl Engine #
565             ################################
566              
567             # Engine for vector2point
568             sub v2p_pp
569             {
570 0     0 0   my ($f,$a,$rd,$lat,$lon,$dir,$dis) = @_;
571 0           ($lat,$lon,$dir) = map{ $_ * $rd } ($lat,$lon,$dir); # Change to radian
  0            
572              
573 0           my $r = 1 - $f;
574 0           my $tu = $r * tan($lat);
575 0           my $sf = sin($dir);
576 0           my $cf = cos($dir);
577 0 0         my $b = ($cf == 0) ? 0.0 : 2.0 * atan2($tu,$cf);
578              
579 0           my $cu = 1.0 / sqrt(1 + $tu**2);
580 0           my $su = $tu * $cu;
581 0           my $sa = $cu * $sf;
582 0           my $c2a = 1 - $sa**2;
583 0           my $x = 1.0 + sqrt(1.0 + $c2a * (1.0/($r**2)-1.0));
584 0           $x = ($x - 2.0) / $x;
585              
586 0           my $c = 1.0 - $x;
587 0           $c = ($x**2 / 4.0 + 1.0) / $c;
588 0           my $d = (0.375 * $x**2 - 1.0)* $x;
589 0           $tu = $dis / ($r * $a * $c);
590 0           my $y = $tu;
591 0           $c = $y + 1;
592              
593 0           my ($sy,$cy,$cz,$e) = ();
594 0           while (abs($y - $c) > 0.00000000005)
595             {
596 0           $sy = sin($y);
597 0           $cy = cos($y);
598 0           $cz = cos($b + $y);
599 0           $e = 2.0 * $cz**2 -1.0;
600 0           $c = $y;
601 0           $x = $e * $cy;
602 0           $y = $e + $e - 1;
603 0           $y = ((($sy**2 * 4.0 - 3.0) * $y * $cz * $d / 6.0 + $x) * $d / 4.0 - $cz) * $sy * $d + $tu;
604             }
605            
606 0           $b = $cu * $cy * $cf - $su * $sy;
607 0           $c = $r * sqrt($sa**2 + $b**2);
608 0           $d = $su * $cy + $cu * $sy * $cf;
609 0           my $rlat = atan2($d,$c);
610              
611 0           $c = $cu * $cy - $su * $sy * $cf;
612 0           $x = atan2($sy * $sf, $c);
613 0           $c = ((-3.0 * $c2a + 4.0) * $f + 4.0) * $c2a * $f / 16.0;
614 0           $d = (($e * $cy * $c + $cz) * $sy * $c + $y) * $sa;
615 0           my $rlon = $lon + $x - (1.0 - $c) * $d * $f;
616              
617 0           return map { $_/$rd } ($rlat,$rlon);
  0            
618             }
619              
620             # Engine for point2vector
621             sub p2v_pp
622             {
623 0     0 0   my ($f,$a,$rd,$lat,$lon,$tlat,$tlon) = @_;
624              
625 0 0 0       return (180,0) if (($lat == $tlat) && ($lon == $tlon));
626              
627 0           my $e2 = 2*$f - $f*$f; # Square of Eccentricity
628 0           my $r = 1 - $f;
629              
630 0           my $tu1 = $r * tan($lat);
631 0           my $tu2 = $r * tan($tlat);
632              
633 0           my $cu1 = 1.0 / sqrt(1.0 + $tu1**2);
634 0           my $su1 = $cu1 * $tu1;
635 0           my $cu2 = 1.0 / sqrt(1.0 + $tu2**2);
636 0           my $s1 = $cu1 * $cu2;
637 0           my $b1 = $s1 * $tu2;
638 0           my $f1 = $b1 * $tu1;
639 0           my $x = $tlon - $lon;
640 0           my $d = $x + 1; # Force one pass
641              
642 0           my $iter =1;
643 0           my ($sx,$cx,$sy,$cy,$y,$sa,$c2a,$cz,$e,$c)=();
644              
645 0   0       while ((abs($d - $x) > 0.00000000005) && ($iter < 100))
646             {
647 0           $iter++;
648 0           $sx = sin($x);
649 0           $cx = cos($x);
650 0           $tu1 = $cu2 * $sx;
651 0           $tu2 = $b1 - $su1 * $cu2 * $cx;
652 0           $sy = sqrt($tu1**2 + $tu2**2);
653 0           $cy = $s1 * $cx + $f1;
654 0           $y = atan2($sy,$cy);
655 0           $sa = $s1 * $sx / $sy;
656 0           $c2a = 1 - $sa**2;
657 0           $cz = $f1 + $f1;
658 0 0         if ($c2a > 0.0)
659             {
660 0           $cz = $cy - $cz / $c2a;
661             }
662 0           $e = $cz**2 * 2.0 - 1.0;
663 0           $c = ((-3.0 * $c2a + 4.0) * $f + 4.0) * $c2a * $f / 16.0;
664 0           $d = $x;
665 0           $x = (($e * $cy * $c + $cz) * $sy * $c + $y) * $sa;
666 0           $x = (1.0 - $c) * $x * $f + $tlon - $lon;
667             }
668              
669 0           my $dir = atan2($tu1,$tu2) / $rd;
670 0           $x = sqrt((1 / ($r**2) -1) * $c2a +1);
671 0           $x += 1;
672 0           $x = ($x - 2.0) / $x;
673 0           $c = 1.0 - $x;
674 0           $c = ($x**2 / 4.0 + 1.0) / $c;
675 0           $d = (0.375 * $x**2 - 1.0) * $x;
676 0           $x = $e * $cy;
677 0           my $dis = (((($sy**2 * 4.0 - 3.0) * (1.0 - $e - $e) * $cz * $d / 6.0 - $x) * $d / 4.0 + $cz) * $sy * $d + $y) * $c * $a * $r;
678            
679 0           return ($dir,$dis);
680             }
681              
682             # Engine for datumchange: Main procedure of Molodensky method
683             sub molodensky_pp
684             {
685 0     0 0   my($b, $l, $h, $a, $f, $a_, $f_,$dx,$dy,$dz,$rd) = @_;
686 0           my($bda, $e2, $da, $df, $db, $dl, $dh);
687 0           my($sb, $cb, $sl, $cl, $rn, $rm);
688              
689 0           $b *= $rd;
690 0           $l *= $rd;
691              
692 0           $e2 = 2*$f - $f*$f; # Square of Eccentricity
693 0           $bda = 1- $f; # Polar Radius / Equatorial Radius
694 0           ($da, $df) = ($a_-$a, $f_-$f);
695 0           ($sb, $cb, $sl, $cl) = (sin($b), cos($b), sin($l), cos($l));
696              
697 0           $rn = 1 / sqrt(1 - $e2*$sb*$sb);
698 0           $rm = $a * (1 - $e2) * $rn * $rn * $rn;
699 0           $rn *= $a;
700              
701             # Calcurating Delta Value
702 0           $db = -$dx*$sb*$cl - $dy*$sb*$sl + $dz*$cb
703             + $da*$rn*$e2*$sb*$cb/$a + $df*($rm/$bda+$rn*$bda)*$sb*$cb;
704 0           $db /= $rm + $h;
705 0           $dl = -$dx*$sl + $dy*$cl;
706 0           $dl /= ($rn+$h) * $cb;
707 0           $dh = $dx*$cb*$cl + $dy*$cb*$sl + $dz*$sb
708             - $da*$a/$rn + $df*$bda*$rn*$sb*$sb;
709              
710 0           return (($b+$db)/$rd, ($l+$dl)/$rd, $h+$dh);
711             }
712              
713              
714             1;
715             __END__