File Coverage

blib/lib/Image/ExifTool/GPS.pm
Criterion Covered Total %
statement 80 86 93.0
branch 49 64 76.5
condition 20 31 64.5
subroutine 7 7 100.0
pod 0 4 0.0
total 156 192 81.2


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