File Coverage

blib/lib/Image/ExifTool/GPS.pm
Criterion Covered Total %
statement 82 88 93.1
branch 49 64 76.5
condition 21 34 61.7
subroutine 7 7 100.0
pod 0 4 0.0
total 159 197 80.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: GPS.pm
3             #
4             # Description: EXIF GPS meta information tags
5             #
6             # Revisions: 12/09/2003 - P. Harvey Created
7             #------------------------------------------------------------------------------
8              
9             package Image::ExifTool::GPS;
10              
11 75     75   4571 use strict;
  75         232  
  75         3400  
12 75     75   423 use vars qw($VERSION);
  75         174  
  75         3605  
13 75     75   6178 use Image::ExifTool::Exif;
  75         168  
  75         250254  
14              
15             $VERSION = '1.58';
16              
17             my %coordConv = (
18             ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)',
19             ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val)',
20             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)',
21             );
22              
23             my %printConvLatRef = (
24             # extract N/S if written from Composite:GPSLatitude
25             # (also allow writing from a signed number)
26             OTHER => sub {
27             my ($val, $inv) = @_;
28             return undef unless $inv;
29             return uc $2 if $val =~ /(^|[^A-Z])([NS])(orth|outh)?\b/i;
30             return $1 eq '-' ? 'S' : 'N' if $val =~ /([-+]?)\d+/;
31             return undef;
32             },
33             N => 'North',
34             S => 'South',
35             );
36              
37             my %printConvLonRef = (
38             # extract E/W if written from Composite:GPSLongitude
39             # (also allow writing from a signed number)
40             OTHER => sub {
41             my ($val, $inv) = @_;
42             return undef unless $inv;
43             return uc $2 if $val =~ /(^|[^A-Z])([EW])(ast|est)?\b/i;
44             return $1 eq '-' ? 'W' : 'E' if $val =~ /([-+]?)\d+/;
45             return undef;
46             },
47             E => 'East',
48             W => 'West',
49             );
50              
51             %Image::ExifTool::GPS::Main = (
52             GROUPS => { 0 => 'EXIF', 1 => 'GPS', 2 => 'Location' },
53             WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
54             CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
55             WRITABLE => 1,
56             WRITE_GROUP => 'GPS',
57             0x0000 => {
58             Name => 'GPSVersionID',
59             Writable => 'int8u',
60             Mandatory => 1,
61             Count => 4,
62             PrintConv => '$val =~ tr/ /./; $val',
63             PrintConvInv => '$val =~ tr/./ /; $val',
64             },
65             0x0001 => {
66             Name => 'GPSLatitudeRef',
67             Writable => 'string',
68             Notes => q{
69             tags 0x0001-0x0006 used for camera location according to MWG 2.0. ExifTool
70             will also accept a number when writing GPSLatitudeRef, positive for north
71             latitudes or negative for south, or a string containing N, North, S or South
72             },
73             Count => 2,
74             PrintConv => \%printConvLatRef,
75             },
76             0x0002 => {
77             Name => 'GPSLatitude',
78             Writable => 'rational64u',
79             Count => 3,
80             %coordConv,
81             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")',
82             },
83             0x0003 => {
84             Name => 'GPSLongitudeRef',
85             Writable => 'string',
86             Count => 2,
87             Notes => q{
88             ExifTool will also accept a number when writing this tag, positive for east
89             longitudes or negative for west, or a string containing E, East, W or West
90             },
91             PrintConv => \%printConvLonRef,
92             },
93             0x0004 => {
94             Name => 'GPSLongitude',
95             Writable => 'rational64u',
96             Count => 3,
97             %coordConv,
98             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")',
99             },
100             0x0005 => {
101             Name => 'GPSAltitudeRef',
102             Writable => 'int8u',
103             Notes => q{
104             ExifTool will also accept number when writing this tag, with negative
105             numbers indicating below sea level
106             },
107             PrintConv => {
108             OTHER => sub {
109             my ($val, $inv) = @_;
110             return undef unless $inv and $val =~ /^([-+0-9])/;
111             return($1 eq '-' ? 1 : 0);
112             },
113             0 => 'Above Sea Level', # (ellipsoidal surface, Exif 3.0)
114             1 => 'Below Sea Level', # (ellipsoidal surface, Exif 3.0)
115             2 => 'Positive Sea Level (sea-level ref)', # sea-level reference, Exif 3.0
116             3 => 'Negative Sea Level (sea-level ref)', # sea-level reference, Exif 3.0
117             },
118             },
119             0x0006 => {
120             Name => 'GPSAltitude',
121             Writable => 'rational64u',
122             # extricate unsigned decimal number from string
123             ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
124             PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
125             PrintConvInv => '$val=~s/\s*m$//;$val',
126             },
127             0x0007 => {
128             Name => 'GPSTimeStamp',
129             Groups => { 2 => 'Time' },
130             Writable => 'rational64u',
131             Count => 3,
132             Shift => 'Time',
133             Notes => q{
134             UTC time of GPS fix. When writing, date is stripped off if present, and
135             time is adjusted to UTC if it includes a timezone
136             },
137             ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)',
138             ValueConvInv => '$val=~tr/:/ /;$val',
139             PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)',
140             # pull time out of any format date/time string
141             # (converting to UTC if a timezone is given)
142             PrintConvInv => sub {
143             my ($v, $et) = @_;
144             $v = $et->TimeNow() if lc($v) eq 'now';
145             my @tz;
146             if ($v =~ s/([-+])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { # remove timezone
147             my $s = $1 eq '-' ? 1 : -1; # opposite sign to convert back to UTC
148             my $t = $2;
149             @tz = ($s*$2, $s*$3);
150             }
151             # (note: we must allow '.' as a time separator, eg. '10.30.00', with is tricky due to decimal seconds)
152             # YYYYmmddHHMMSS[.ss] format
153             my @a = ($v =~ /^[^\d]*\d{4}[^\d]*\d{1,2}[^\d]*\d{1,2}[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/);
154             # HHMMSS[.ss] format
155             @a or @a = ($v =~ /^[^\d]*(\d{1,2})[^\d]*(\d{2})[^\d]*(\d{2}(?:\.\d+)?)[^\d]*$/);
156             @a or warn('Invalid time (use HH:MM:SS[.ss][+/-HH:MM|Z])'), return undef;
157             if (@tz) {
158             # adjust to UTC
159             $a[1] += $tz[1];
160             $a[0] += $tz[0];
161             while ($a[1] >= 60) { $a[1] -= 60; ++$a[0] }
162             while ($a[1] < 0) { $a[1] += 60; --$a[0] }
163             $a[0] = ($a[0] + 24) % 24;
164             }
165             return join(':', @a);
166             },
167             },
168             0x0008 => {
169             Name => 'GPSSatellites',
170             Writable => 'string',
171             },
172             0x0009 => {
173             Name => 'GPSStatus',
174             Writable => 'string',
175             Count => 2,
176             PrintConv => {
177             A => 'Measurement Active', # Exif2.2 "Measurement in progress"
178             V => 'Measurement Void', # Exif2.2 "Measurement Interoperability" (WTF?)
179             # (meaning for 'V' taken from status code in NMEA GLL and RMC sentences)
180             },
181             },
182             0x000a => {
183             Name => 'GPSMeasureMode',
184             Writable => 'string',
185             Count => 2,
186             PrintConv => {
187             2 => '2-Dimensional Measurement',
188             3 => '3-Dimensional Measurement',
189             },
190             },
191             0x000b => {
192             Name => 'GPSDOP',
193             Description => 'GPS Dilution Of Precision',
194             Writable => 'rational64u',
195             },
196             0x000c => {
197             Name => 'GPSSpeedRef',
198             Writable => 'string',
199             Count => 2,
200             PrintConv => {
201             K => 'km/h',
202             M => 'mph',
203             N => 'knots',
204             },
205             },
206             0x000d => {
207             Name => 'GPSSpeed',
208             Writable => 'rational64u',
209             },
210             0x000e => {
211             Name => 'GPSTrackRef',
212             Writable => 'string',
213             Count => 2,
214             PrintConv => {
215             M => 'Magnetic North',
216             T => 'True North',
217             },
218             },
219             0x000f => {
220             Name => 'GPSTrack',
221             Writable => 'rational64u',
222             },
223             0x0010 => {
224             Name => 'GPSImgDirectionRef',
225             Writable => 'string',
226             Count => 2,
227             PrintConv => {
228             M => 'Magnetic North',
229             T => 'True North',
230             },
231             },
232             0x0011 => {
233             Name => 'GPSImgDirection',
234             Writable => 'rational64u',
235             },
236             0x0012 => {
237             Name => 'GPSMapDatum',
238             Writable => 'string',
239             },
240             0x0013 => {
241             Name => 'GPSDestLatitudeRef',
242             Writable => 'string',
243             Notes => 'tags 0x0013-0x001a used for subject location according to MWG 2.0',
244             Count => 2,
245             PrintConv => \%printConvLatRef,
246             },
247             0x0014 => {
248             Name => 'GPSDestLatitude',
249             Writable => 'rational64u',
250             Count => 3,
251             %coordConv,
252             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lat")',
253             },
254             0x0015 => {
255             Name => 'GPSDestLongitudeRef',
256             Writable => 'string',
257             Count => 2,
258             PrintConv => \%printConvLonRef,
259             },
260             0x0016 => {
261             Name => 'GPSDestLongitude',
262             Writable => 'rational64u',
263             Count => 3,
264             %coordConv,
265             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val,undef,"lon")',
266             },
267             0x0017 => {
268             Name => 'GPSDestBearingRef',
269             Writable => 'string',
270             Count => 2,
271             PrintConv => {
272             M => 'Magnetic North',
273             T => 'True North',
274             },
275             },
276             0x0018 => {
277             Name => 'GPSDestBearing',
278             Writable => 'rational64u',
279             },
280             0x0019 => {
281             Name => 'GPSDestDistanceRef',
282             Writable => 'string',
283             Count => 2,
284             PrintConv => {
285             K => 'Kilometers',
286             M => 'Miles',
287             N => 'Nautical Miles',
288             },
289             },
290             0x001a => {
291             Name => 'GPSDestDistance',
292             Writable => 'rational64u',
293             },
294             0x001b => {
295             Name => 'GPSProcessingMethod',
296             Writable => 'undef',
297             Notes => 'values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.',
298             # (or QZZSS, GALILEO, GLONASS, BEIDOU or NAVIC in Exif 3.0)
299             RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)',
300             RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)',
301             },
302             0x001c => {
303             Name => 'GPSAreaInformation',
304             Writable => 'undef',
305             RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)',
306             RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)',
307             },
308             0x001d => {
309             Name => 'GPSDateStamp',
310             Groups => { 2 => 'Time' },
311             Writable => 'string',
312             Format => 'undef', # (Casio EX-H20G uses "\0" instead of ":" as a separator)
313             Count => 11,
314             Shift => 'Time',
315             Notes => q{
316             when writing, time is stripped off if present, after adjusting date/time to
317             UTC if time includes a timezone. Format is YYYY:mm:dd
318             },
319             RawConv => '$val =~ s/\0+$//; $val',
320             ValueConv => 'Image::ExifTool::Exif::ExifDate($val)',
321             ValueConvInv => '$val',
322             # pull date out of any format date/time string
323             # (and adjust to UTC if this is a full date/time/timezone value)
324             PrintConvInv => q{
325             my $secs;
326             $val = $self->TimeNow() if lc($val) eq 'now';
327             if ($val =~ /[-+]/ and ($secs = Image::ExifTool::GetUnixTime($val, 1))) {
328             $val = Image::ExifTool::ConvertUnixTime($secs);
329             }
330             return $val =~ /(\d{4}).*?(\d{2}).*?(\d{2})/ ? "$1:$2:$3" : undef;
331             },
332             },
333             0x001e => {
334             Name => 'GPSDifferential',
335             Writable => 'int16u',
336             PrintConv => {
337             0 => 'No Correction',
338             1 => 'Differential Corrected',
339             },
340             },
341             0x001f => {
342             Name => 'GPSHPositioningError',
343             Description => 'GPS Horizontal Positioning Error',
344             PrintConv => '"$val m"',
345             PrintConvInv => '$val=~s/\s*m$//; $val',
346             Writable => 'rational64u',
347             },
348             # 0xea1c - Nokia Lumina 1020, Samsung GT-I8750, and other Windows 8
349             # phones write this (padding) in GPS IFD - PH
350             );
351              
352             # Composite GPS tags
353             %Image::ExifTool::GPS::Composite = (
354             GROUPS => { 2 => 'Location' },
355             GPSDateTime => {
356             Description => 'GPS Date/Time',
357             Groups => { 2 => 'Time' },
358             SubDoc => 1, # generate for all sub-documents
359             Require => {
360             0 => 'GPS:GPSDateStamp',
361             1 => 'GPS:GPSTimeStamp',
362             },
363             ValueConv => '"$val[0] $val[1]Z"',
364             PrintConv => '$self->ConvertDateTime($val)',
365             },
366             # Note: The following tags are used by other modules
367             # which must therefore require this module as necessary
368             GPSLatitude => {
369             SubDoc => 1, # generate for all sub-documents
370             Writable => 1,
371             Avoid => 1,
372             Priority => 1, # (necessary because Avoid sets default Priority to 0)
373             Require => {
374             0 => 'GPS:GPSLatitude',
375             1 => 'GPS:GPSLatitudeRef',
376             },
377             WriteAlso => {
378             'GPS:GPSLatitude' => '$val',
379             'GPS:GPSLatitudeRef' => '(defined $val and $val < 0) ? "S" : "N"',
380             },
381             ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]',
382             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
383             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
384             },
385             GPSLongitude => {
386             SubDoc => 1, # generate for all sub-documents
387             Writable => 1,
388             Avoid => 1,
389             Priority => 1,
390             Require => {
391             0 => 'GPS:GPSLongitude',
392             1 => 'GPS:GPSLongitudeRef',
393             },
394             WriteAlso => {
395             'GPS:GPSLongitude' => '$val',
396             'GPS:GPSLongitudeRef' => '(defined $val and $val < 0) ? "W" : "E"',
397             },
398             Require => {
399             0 => 'GPS:GPSLongitude',
400             1 => 'GPS:GPSLongitudeRef',
401             },
402             ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]',
403             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
404             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
405             },
406             GPSAltitude => {
407             SubDoc => [1,3], # generate for sub-documents if Desire 1 or 3 has a chance to exist
408             Desire => {
409             0 => 'GPS:GPSAltitude',
410             1 => 'GPS:GPSAltitudeRef',
411             2 => 'XMP:GPSAltitude',
412             3 => 'XMP:GPSAltitudeRef',
413             },
414             # Require either GPS:GPSAltitudeRef or XMP:GPSAltitudeRef
415             RawConv => '(defined $val[1] or defined $val[3]) ? $val : undef',
416             ValueConv => q{
417             foreach (0,2) {
418             next unless defined $val[$_] and IsFloat($val[$_]) and defined $val[$_+1];
419             return $val[$_+1] ? -abs($val[$_]) : $val[$_];
420             }
421             return undef;
422             },
423             PrintConv => q{
424             foreach (0,2) {
425             next unless defined $val[$_] and IsFloat($val[$_]);
426             next unless defined $prt[$_+1] and $prt[$_+1] =~ /Sea/;
427             return((int($val[$_]*10)/10) . ' m ' . $prt[$_+1]);
428             }
429             $val = int($val * 10) / 10;
430             return(($val =~ s/^-// ? "$val m Below" : "$val m Above") . " Sea Level");
431             },
432             },
433             GPSDestLatitude => {
434             Require => {
435             0 => 'GPS:GPSDestLatitude',
436             1 => 'GPS:GPSDestLatitudeRef',
437             },
438             ValueConv => '$val[1] =~ /^S/i ? -$val[0] : $val[0]',
439             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
440             },
441             GPSDestLongitude => {
442             SubDoc => 1, # generate for all sub-documents
443             Require => {
444             0 => 'GPS:GPSDestLongitude',
445             1 => 'GPS:GPSDestLongitudeRef',
446             },
447             ValueConv => '$val[1] =~ /^W/i ? -$val[0] : $val[0]',
448             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
449             },
450             );
451              
452             # add our composite tags
453             Image::ExifTool::AddCompositeTags('Image::ExifTool::GPS');
454              
455             #------------------------------------------------------------------------------
456             # Convert GPS timestamp value
457             # Inputs: 0) raw timestamp value string
458             # Returns: EXIF-formatted time string
459             sub ConvertTimeStamp($)
460             {
461 20     20 0 69 my $val = shift;
462 20         120 my ($h,$m,$s) = split ' ', $val;
463 20   100     205 my $f = (($h || 0) * 60 + ($m || 0)) * 60 + ($s || 0);
      50        
      100        
464 20         68 $h = int($f / 3600); $f -= $h * 3600;
  20         49  
465 20         57 $m = int($f / 60); $f -= $m * 60;
  20         56  
466 20         181 my $ss = sprintf('%012.9f', $f);
467 20 50       128 if ($ss >= 60) {
468 0         0 $ss = '00';
469 0 0       0 ++$m >= 60 and $m -= 60, ++$h;
470             } else {
471 20         167 $ss =~ s/\.?0+$//; # trim trailing zeros + decimal
472             }
473 20         295 return sprintf("%.2d:%.2d:%s",$h,$m,$ss);
474             }
475              
476             #------------------------------------------------------------------------------
477             # Print GPS timestamp
478             # Inputs: 0) EXIF-formatted time string
479             # Returns: time rounded to the nearest microsecond
480             sub PrintTimeStamp($)
481             {
482 20     20 0 66 my $val = shift;
483 20 100       284 return $val unless $val =~ s/:(\d{2}\.\d+)$//;
484 3         18 my $s = int($1 * 1000000 + 0.5) / 1000000;
485 3 50       12 $s = "0$s" if $s < 10;
486 3         38 return "${val}:$s";
487             }
488              
489             #------------------------------------------------------------------------------
490             # Convert degrees to DMS, or whatever the current settings are
491             # Inputs: 0) ExifTool reference, 1) Value in degrees,
492             # 2) format code (0=no format, 1=CoordFormat, 2=XMP format, 3=signed unformatted)
493             # 3) 'N' or 'E' if sign is significant and N/S/E/W should be added
494             # Returns: DMS string
495             sub ToDMS($$;$$)
496             {
497 132     132 0 478 my ($et, $val, $doPrintConv, $ref) = @_;
498 132         331 my ($fmt, @fmt, $num, $sign, $minus, $rtnVal, $neg);
499              
500 132 100       1070 unless (length $val) {
501             # don't convert an empty value
502 7 50 33     103 return $val if $doPrintConv and $doPrintConv eq '1'; # avoid hiding existing tag when extracting
503 0         0 return undef; # avoid writing empty value
504             }
505 125 100       399 if ($ref) {
506 46 100       181 if ($val < 0) {
507 21         72 $val = -$val;
508 21         129 $ref = {N => 'S', E => 'W'}->{$ref};
509 21         68 $sign = '-';
510 21         50 $minus = '-';
511             } else {
512 25         69 $sign = '+';
513 25         59 $minus = '';
514             }
515 46 100 66     346 $ref = " $ref" unless $doPrintConv and $doPrintConv eq '2';
516             } else {
517 79 100 100     540 if ($doPrintConv and $doPrintConv eq '3') {
518 20 100       110 $neg = 1 if $val < 0;
519 20         53 $doPrintConv = 0;
520             }
521 79         187 $val = abs($val);
522 79         183 $ref = '';
523             }
524 125 100       358 if ($doPrintConv) {
525 85 100       284 if ($doPrintConv eq '1') {
526 64         328 $fmt = $et->Options('CoordFormat');
527 64 100       256 if (not $fmt) {
    100          
528 40         132 $fmt = q{%d deg %d' %.2f"} . $ref;
529             } elsif ($ref) {
530             # use signed value instead of reference direction if specified
531 13 50 33     127 $fmt =~ s/%\+/$sign%/g or $fmt =~ s/%-/$minus%/g or $fmt .= $ref;
532             } else {
533 11         38 $fmt =~ s/%\+/%/g; # don't know sign, so don't print it
534             }
535             } else {
536 21         62 $fmt = "%d,%.8f$ref"; # use XMP format with 8 decimal minutes
537             }
538             # count (and capture) the format specifiers (max 3)
539 85         701 while ($fmt =~ /(%(%|[^%]*?[diouxXDOUeEfFgGcs]))/g) {
540 228 50       645 next if $1 eq '%%';
541 228         628 push @fmt, $1;
542 228 100       1041 last if @fmt >= 3;
543             }
544 85         204 $num = scalar @fmt;
545             } else {
546 40         81 $num = 3;
547             }
548 125         243 my @c; # coordinates (D) or (D,M) or (D,M,S)
549 125         340 $c[0] = $val;
550 125 50       403 if ($num > 1) {
551 125         320 $c[0] = int($c[0]);
552 125         453 $c[1] = ($val - $c[0]) * 60;
553 125 100       396 if ($num > 2) {
554 98         268 $c[1] = int($c[1]);
555 98         284 $c[2] = ($val - $c[0] - $c[1] / 60) * 3600;
556             }
557             # handle round-off errors to ensure minutes and seconds are
558             # less than 60 (eg. convert "72 59 60.00" to "73 0 0.00")
559 125 100       1138 $c[-1] = $doPrintConv ? sprintf($fmt[-1], $c[-1]) : ($c[-1] . '');
560 125 50       789 if ($c[-1] >= 60) {
561 0         0 $c[-1] -= 60;
562 0 0 0     0 ($c[-2] += 1) >= 60 and $num > 2 and $c[-2] -= 60, $c[-3] += 1;
563             }
564             }
565 125 100       356 if ($doPrintConv) {
566 85         560 $rtnVal = sprintf($fmt, @c);
567             # trim trailing zeros in XMP
568 85 100       1442 $rtnVal =~ s/(\d)0+$ref$/$1$ref/ if $doPrintConv eq '2';
569             } else {
570 40 100       133 $neg and map { $_ *= -1 } @c;
  21         54  
571 40         203 $rtnVal = "@c$ref";
572             }
573 125         1679 return $rtnVal;
574             }
575              
576             #------------------------------------------------------------------------------
577             # Convert to decimal degrees
578             # Inputs: 0) a string containing 1-3 decimal numbers and any amount of other garbage
579             # 1) true if value should be negative if coordinate ends in 'S' or 'W',
580             # 2) 'lat' or 'lon' to extract lat or lon from GPSCoordinates string
581             # Returns: Coordinate in degrees, or '' on error
582             sub ToDegrees($;$$)
583             {
584 259     259 0 723 my ($val, $doSign, $coord) = @_;
585 259 100       1255 return '' if $val =~ /\b(inf|undef)\b/; # ignore invalid values
586             # use only lat or lon part of combined GPSCoordinates inputs
587 256 50 66     746 if ($coord and ($coord eq 'lat' or $coord eq 'lon') and
      66        
      33        
588             # (two formatted coordinate values with cardinal directions, separated by a comma)
589             $val =~ /^(.*(?:N(?:orth)?|S(?:outh)?)),\s*(.*(?:E(?:ast)?|W(?:est)?))$/i)
590             {
591 0 0       0 $val = $coord eq 'lat' ? $1 : $2;
592             }
593             # extract decimal or floating point values out of any other garbage
594 256         2037 my ($d, $m, $s) = ($val =~ /((?:[+-]?)(?=\d|\.\d)\d*(?:\.\d*)?(?:[Ee][+-]\d+)?)/g);
595 256 50       650 return '' unless defined $d;
596 256   100     2104 my $deg = $d + (($m || 0) + ($s || 0)/60) / 60;
      100        
597             # make negative if S or W coordinate
598 256 100       1585 $deg = -$deg if $doSign ? $val =~ /[^A-Z](S(outh)?|W(est)?)\s*$/i : $deg < 0;
    100          
599 256         1817 return $deg;
600             }
601              
602              
603             1; #end
604              
605             __END__