GPS.pm 17 KB

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