exiftool-vendored.pl 12.82.0 → 12.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/bin/Changes +37 -0
  2. package/bin/MANIFEST +2 -18
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +3 -2
  6. package/bin/build_geolocation +872 -0
  7. package/bin/config_files/example.config +2 -2
  8. package/bin/exiftool +28 -6
  9. package/bin/fmt_files/gpx.fmt +2 -1
  10. package/bin/fmt_files/gpx_wpt.fmt +2 -1
  11. package/bin/lib/Image/ExifTool/Apple.pm +51 -7
  12. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +5 -2
  13. package/bin/lib/Image/ExifTool/CanonVRD.pm +18 -5
  14. package/bin/lib/Image/ExifTool/DJI.pm +29 -0
  15. package/bin/lib/Image/ExifTool/Exif.pm +19 -2
  16. package/bin/lib/Image/ExifTool/GM.pm +17 -8
  17. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  18. package/bin/lib/Image/ExifTool/Geolocation.pm +163 -101
  19. package/bin/lib/Image/ExifTool/Geotag.pm +18 -10
  20. package/bin/lib/Image/ExifTool/Nikon.pm +7 -6
  21. package/bin/lib/Image/ExifTool/QuickTime.pm +6 -1
  22. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +5 -0
  23. package/bin/lib/Image/ExifTool/Sony.pm +15 -6
  24. package/bin/lib/Image/ExifTool/TagLookup.pm +18 -9
  25. package/bin/lib/Image/ExifTool/TagNames.pod +24 -5
  26. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +2 -1
  27. package/bin/lib/Image/ExifTool/Writer.pl +165 -132
  28. package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
  29. package/bin/lib/Image/ExifTool.pm +45 -23
  30. package/bin/lib/Image/ExifTool.pod +23 -14
  31. package/bin/perl-Image-ExifTool.spec +1 -1
  32. package/bin/pp_build_exe.args +4 -4
  33. package/package.json +2 -2
  34. package/bin/lib/Image/ExifTool/GeoLang/cs.pm +0 -978
  35. package/bin/lib/Image/ExifTool/GeoLang/de.pm +0 -1975
  36. package/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +0 -44
  37. package/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +0 -124
  38. package/bin/lib/Image/ExifTool/GeoLang/es.pm +0 -2921
  39. package/bin/lib/Image/ExifTool/GeoLang/fi.pm +0 -1116
  40. package/bin/lib/Image/ExifTool/GeoLang/fr.pm +0 -3171
  41. package/bin/lib/Image/ExifTool/GeoLang/it.pm +0 -2750
  42. package/bin/lib/Image/ExifTool/GeoLang/ja.pm +0 -10256
  43. package/bin/lib/Image/ExifTool/GeoLang/ko.pm +0 -4499
  44. package/bin/lib/Image/ExifTool/GeoLang/nl.pm +0 -1270
  45. package/bin/lib/Image/ExifTool/GeoLang/pl.pm +0 -3019
  46. package/bin/lib/Image/ExifTool/GeoLang/ru.pm +0 -18220
  47. package/bin/lib/Image/ExifTool/GeoLang/sk.pm +0 -441
  48. package/bin/lib/Image/ExifTool/GeoLang/sv.pm +0 -714
  49. package/bin/lib/Image/ExifTool/GeoLang/tr.pm +0 -452
  50. package/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +0 -2225
  51. package/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +0 -72
@@ -9,14 +9,15 @@
9
9
  #
10
10
  # References: https://download.geonames.org/export/
11
11
  #
12
- # Notes: Set $Image::ExifTool::Geolocation::geoDir to override
13
- # default directory for the database file Geolocation.dat
14
- # and language directory GeoLang.
12
+ # Notes: Set $Image::ExifTool::Geolocation::geoDir to override the
13
+ # default directory containing the database file Geolocation.dat
14
+ # and the GeoLang directory with the alternate language files.
15
+ # If set, this directory is
15
16
  #
16
- # Set $Image::ExifTool::Geolocation::altDir to use a database
17
- # of alternate city names. The file is called AltNames.dat
18
- # with entries in the same order as Geolocation.dat. Each
19
- # entry is a newline-separated list of alternate names
17
+ # AltNames.dat may be loaded from a different directory by
18
+ # specifying $Image::ExifTool::Geolocation::altDir. This
19
+ # database and has entries in the same order as Geolocation.dat,
20
+ # and each entry is a newline-separated list of alternate names
20
21
  # terminated by a null byte.
21
22
  #
22
23
  # Databases are based on data from geonames.org with a
@@ -37,7 +38,7 @@
37
38
  # 9 int16u - v1.02: 0x7fff = index in subregion (admin2), 0x8000 = high bit of time zone
38
39
  # 9 int16u - v1.03: index in subregion (admin2)
39
40
  # 11 int8u - low byte of time zone index
40
- # 12 int8u - 0x0f = feature code index (see below), v1.03: 0x80 = high bit of time zone
41
+ # 12 int8u - 0x3f = feature code index (see below), v1.03: 0x80 = high bit of time zone
41
42
  # 13 string - UTF8 City name, terminated by newline
42
43
  # "\0\0\0\0\x01"
43
44
  # Country entries:
@@ -52,9 +53,12 @@
52
53
  # "\0\0\0\0\x04"
53
54
  # Time zone entries:
54
55
  # 1. Time zone name, terminated by newline
56
+ # "\0\0\0\0\x05" (feature codes added in v1.03)
57
+ # Feature codes:
58
+ # 1. Feature code, terminated by newline
55
59
  # "\0\0\0\0\0"
56
60
  #
57
- # Feature Codes: (see http://www.geonames.org/export/codes.html#P for descriptions)
61
+ # Feature Codes v1.02: (see http://www.geonames.org/export/codes.html#P for descriptions)
58
62
  #
59
63
  # 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR 15. PPLX
60
64
  # 1. PPL 4. PPLA3 7. PPLC 10. PPLG 13. PPLS
@@ -66,7 +70,7 @@ package Image::ExifTool::Geolocation;
66
70
  use strict;
67
71
  use vars qw($VERSION $geoDir $altDir $dbInfo);
68
72
 
69
- $VERSION = '1.04'; # (this is the module version number, not the database version)
73
+ $VERSION = '1.07'; # (this is the module version number, not the database version)
70
74
 
71
75
  my $debug; # set to output processing time for testing
72
76
 
@@ -74,21 +78,19 @@ sub ReadDatabase($);
74
78
  sub SortDatabase($);
75
79
  sub AddEntry(@);
76
80
  sub GetEntry($;$$);
77
- sub Geolocate($;$$$$$);
81
+ sub Geolocate($;$);
78
82
 
79
83
  my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
80
84
  my (%countryNum, %regionNum, %subregionNum, %timezoneNum); # reverse lookups
81
- my (@sortOrder, @altNames, %langLookup, $nCity);
85
+ my (@sortOrder, @altNames, %langLookup, $nCity, %featureCodes);
82
86
  my ($lastArgs, %lastFound, @lastByPop, @lastByLat); # cached city matches
83
87
  my $dbVer = '1.03';
84
88
  my $sortedBy = 'Latitude';
85
89
  my $pi = 3.1415926536;
86
90
  my $earthRadius = 6371; # earth radius in km
87
-
91
+ # hard-coded feature codes for v1.02 database
88
92
  my @featureCodes = qw(Other PPL PPLA PPLA2 PPLA3 PPLA4 PPLA5 PPLC
89
93
  PPLCH PPLF PPLG PPLL PPLR PPLS STLMT PPLX);
90
- my $i = 0;
91
- my %featureCodes = map { lc($_) => $i++ } @featureCodes;
92
94
 
93
95
  # get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
94
96
  # or according to $Image::ExifTool::Geolocation::directory if specified
@@ -107,12 +109,10 @@ unless (defined $geoDir and not $geoDir) {
107
109
  }
108
110
  }
109
111
 
110
- # set directory for language files
111
- my $geoLang;
112
- if ($geoDir and -d "$geoDir/GeoLang") {
113
- $geoLang = "$geoDir/GeoLang";
114
- } elsif ($geoDir or not defined $geoDir) {
115
- $geoLang = "$defaultDir/GeoLang";
112
+ # set directory for language files and alternate names
113
+ $geoDir = $defaultDir unless defined $geoDir;
114
+ if (not defined $altDir and $geoDir and -e "$geoDir/AltNames.dat") {
115
+ $altDir = $geoDir;
116
116
  }
117
117
 
118
118
  # add user-defined entries to the database
@@ -144,7 +144,7 @@ sub ReadDatabase($)
144
144
  return 0;
145
145
  }
146
146
  my $comment = <DATFILE>;
147
- defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
147
+ defined $comment and $comment =~ / (\d+) / or close(DATFILE), return 0;
148
148
  $dbInfo = "$datfile v$dbVer: $nCity cities with population > $1";
149
149
  my $isUserDefined = @Image::ExifTool::UserDefined::Geolocation;
150
150
 
@@ -193,7 +193,21 @@ sub ReadDatabase($)
193
193
  push @timezoneList, $line;
194
194
  $timezoneNum{lc $line} = $#timezoneList if $isUserDefined;
195
195
  }
196
+ # read feature codes if available
197
+ if ($line eq "\0\0\0\0\x05\n") {
198
+ undef @featureCodes;
199
+ for (;;) {
200
+ $line = <DATFILE>;
201
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
202
+ chomp $line;
203
+ $line =~ s/ .*//; # (hook to be able to add feature descriptions later)
204
+ push @featureCodes, $line;
205
+ }
206
+ }
196
207
  close DATFILE;
208
+ # initialize featureCodes lookup
209
+ $i = 0;
210
+ %featureCodes = map { lc($_) => $i++ } @featureCodes;
197
211
  return 1;
198
212
  }
199
213
 
@@ -229,10 +243,10 @@ sub ReadAltNames()
229
243
  # Clear last city matches cache
230
244
  sub ClearLastMatches()
231
245
  {
232
- undef $lastArgs;
233
- undef %lastFound;
234
- undef @lastByPop;
235
- undef @lastByLat;
246
+ undef $lastArgs; # arguments in last call to Geolocate
247
+ undef %lastFound; # keys are last matching city numbers, values are population codes
248
+ undef @lastByPop; # last matching city numbers ordered by population
249
+ undef @lastByLat; # last matching city numbers ordered by latitude
236
250
  }
237
251
 
238
252
  #------------------------------------------------------------------------------
@@ -274,7 +288,16 @@ sub AddEntry(@)
274
288
  my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon, $altNames) = @_;
275
289
  @_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return 0;
276
290
  length($cc) != 2 and warn("Country code '${cc}' is not 2 characters\n"), return 0;
277
- $fc = $featureCodes{lc $fc} || 0;
291
+ $fc =~ s/ .*//; # (allow for descriptions to be added after a space -- future feature?)
292
+ my $fn = $featureCodes{lc $fc};
293
+ unless (defined $fn) {
294
+ if ($dbVer eq '1.02' or @featureCodes > 0x3f or not length $fc) {
295
+ $fn = 0;
296
+ } else {
297
+ push @featureCodes, uc($fc);
298
+ $featureCodes{lc $fc} = $fn = $#featureCodes;
299
+ }
300
+ }
278
301
  chomp $lon; # (just in case it was read from file)
279
302
  # create reverse lookups for country/region/subregion/timezone if not done already
280
303
  # (eg. if the entries are being added manually instead of via UserDefined::Geolocation)
@@ -307,7 +330,7 @@ sub AddEntry(@)
307
330
  }
308
331
  my $sn = $subregionNum{lc $subregion};
309
332
  unless (defined $sn) {
310
- my $max = $dbVer eq '1.02' ? 0x0fff : 0xffff;
333
+ my $max = $dbVer eq '1.02' ? 0x7fff : 0xffff;
311
334
  $#subregionList >= $max and warn("AddEntry: Too many subregions\n"), return 0;
312
335
  push @subregionList, $subregion;
313
336
  $sn = $subregionNum{lc $subregion} = $#subregionList;
@@ -320,13 +343,13 @@ sub AddEntry(@)
320
343
  if ($dbVer eq '1.02') {
321
344
  $sn |= 0x8000;
322
345
  } else {
323
- $fc |= 0x80;
346
+ $fn |= 0x80;
324
347
  }
325
348
  $tn -= 256;
326
349
  }
327
350
  $lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
328
351
  $lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
329
- my $hdr = pack('nCnNnCC', $lat>>4, (($lat&0x0f)<<4)|($lon&0x0f), $lon>>4, $code, $sn, $tn, $fc);
352
+ my $hdr = pack('nCnNnCC', $lat>>4, (($lat&0x0f)<<4)|($lon&0x0f), $lon>>4, $code, $sn, $tn, $fn);
330
353
  push @cityList, "$hdr$city";
331
354
  # add altNames entry if provided
332
355
  if ($altNames) {
@@ -355,28 +378,28 @@ sub GetEntry($;$$)
355
378
  my ($entryNum, $lang, $sort) = @_;
356
379
  return() if $entryNum > $#cityList;
357
380
  $entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
358
- my ($lt,$f,$ln,$code,$sn,$tn,$fc) = unpack('nCnNnCC', $cityList[$entryNum]);
381
+ my ($lt,$f,$ln,$code,$sn,$tn,$fn) = unpack('nCnNnCC', $cityList[$entryNum]);
359
382
  my $city = substr($cityList[$entryNum],13);
360
383
  my $ctry = $countryList[$code >> 24];
361
384
  my $rgn = $regionList[$code & 0x0fff];
362
385
  if ($dbVer eq '1.02') {
363
386
  $sn & 0x8000 and $tn += 256, $sn &= 0x7fff;
364
387
  } else {
365
- $fc & 0x80 and $tn += 256;
388
+ $fn & 0x80 and $tn += 256;
366
389
  }
367
390
  my $sub = $subregionList[$sn];
368
391
  # convert population digits back into exponent format
369
392
  my $pop = (($code>>16 & 0x0f) . '.' . ($code>>12 & 0x0f) . 'e+' . ($code>>20 & 0x0f)) + 0;
370
393
  $lt = sprintf('%.4f', (($lt<<4)|($f >> 4)) * 180 / 0x100000 - 90);
371
394
  $ln = sprintf('%.4f', (($ln<<4)|($f & 0x0f))* 360 / 0x100000 - 180);
372
- $fc = $featureCodes[$fc & 0x0f];
395
+ my $fc = $featureCodes[$fn & 0x3f] || 'Other';
373
396
  my $cc = substr($ctry, 0, 2);
374
397
  my $country = substr($ctry, 2);
375
398
  if ($lang) {
376
399
  my $xlat = $langLookup{$lang};
377
400
  # load language lookups if not done already
378
401
  if (not defined $xlat) {
379
- if (eval "require '$geoLang/$lang.pm'") {
402
+ if (eval "require '$geoDir/GeoLang/$lang.pm'") {
380
403
  my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
381
404
  no strict 'refs';
382
405
  $xlat = \%$trans if %$trans;
@@ -429,17 +452,16 @@ sub GetAltNames($;$)
429
452
  # Look up lat,lon or city in geolocation database
430
453
  # Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
431
454
  # 1) options hash reference (or undef for no options)
432
- # Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames
433
- # Returns: 0) number of matching cities (0 if no matches),
434
- # 1) index of matching city in database, or undef if no matches, or
435
- # reference to list of indices if multiple matches were found and
436
- # the flag to return multiple matches was set,
437
- # 2) approx distance (km), 3) compass bearing to city
438
- sub Geolocate($;$$$$$)
455
+ # Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames,
456
+ # GeolocNearby
457
+ # Returns: List of matching city information, empty if none found.
458
+ # Each element in the list is an array with 0=index of city in database,
459
+ # 1=distance in km (or undef if no distance), 2=compass bearing (or undef)
460
+ sub Geolocate($;$)
439
461
  {
440
462
  my ($arg, $opts) = @_;
441
463
  my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
442
- my ($minPop, $minDistU, $minDistC, @matchParms, @coords, $fcmask, $both);
464
+ my ($minPop, $minDistU, $minDistC, @matchParms, @coords, %fcOK, $both);
443
465
  my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
444
466
 
445
467
  $opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
@@ -449,7 +471,7 @@ sub Geolocate($;$$$$$)
449
471
  require Time::HiRes;
450
472
  @startTime = Time::HiRes::gettimeofday();
451
473
  }
452
- @cityList or warn('No Geolocation database'), return 0;
474
+ @cityList or warn('No Geolocation database'), return();
453
475
  # make population code for comparing with 2 bytes at offset 6 in database
454
476
  if ($pop) {
455
477
  $pop = sprintf('%.1e', $pop);
@@ -457,17 +479,18 @@ sub Geolocate($;$$$$$)
457
479
  }
458
480
  if ($fcodes) {
459
481
  my $neg = $fcodes =~ s/^-//;
460
- my @fcodes = split /\s*,\s*/, $fcodes;
482
+ my @fcodes = split /\s*,-?\s*/, lc $fcodes; # (allow leading dash on subsequent codes)
461
483
  if ($neg) {
462
- $fcmask = 0xffff;
463
- defined $featureCodes{lc $_} and $fcmask &= ~((1 << $featureCodes{lc $_})) foreach @fcodes;
484
+ $fcOK{$_} = 1 foreach 0..$#featureCodes;
485
+ defined $featureCodes{$_} and delete $fcOK{$featureCodes{$_}} foreach @fcodes;
464
486
  } else {
465
- defined $featureCodes{lc $_} and $fcmask |= (1 << $featureCodes{lc $_}) foreach @fcodes;
487
+ defined $featureCodes{$_} and $fcOK{$featureCodes{$_}} = 1 foreach @fcodes;
466
488
  }
467
489
  }
468
490
  #
469
491
  # process input argument
470
492
  #
493
+ my $num = 1;
471
494
  $arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
472
495
  my @args = split /\s*,\s*/, $arg;
473
496
  my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
@@ -486,6 +509,8 @@ sub Geolocate($;$$$$$)
486
509
  push @coords, $_ if @coords < 2;
487
510
  } elsif (lc $_ eq 'both') {
488
511
  $both = 1;
512
+ } elsif ($_ =~ /^num=(\d+)$/i) {
513
+ $num = $1;
489
514
  } elsif ($_) {
490
515
  push @cargs, $_;
491
516
  if ($city) {
@@ -497,7 +522,7 @@ sub Geolocate($;$$$$$)
497
522
  }
498
523
  unless (defined $city or @coords == 2) {
499
524
  warn("Insufficient information to determine geolocation\n");
500
- return 0;
525
+ return();
501
526
  }
502
527
  # sort database by logitude if finding entry based on coordinates
503
528
  SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
@@ -551,7 +576,7 @@ Entry: for (; $i<@cityList; ++$i) {
551
576
  $str !~ $_ or next Entry foreach @{$regex{19}};
552
577
  }
553
578
  # test feature code and population
554
- next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
579
+ next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
555
580
  my $pc = substr($cityList[$i],6,2);
556
581
  if (not defined $minPop or $pc ge $minPop) {
557
582
  $lastFound{$i} = $pc;
@@ -561,16 +586,14 @@ Entry: for (; $i<@cityList; ++$i) {
561
586
  @startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
562
587
  if (%lastFound) {
563
588
  @coords == 2 and $useLastFound = 1, last; # continue to use coords with last city matches
564
- scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return 0;
589
+ scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return();
565
590
  unless (@lastByPop) {
566
591
  @lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
567
592
  }
568
- my $n = scalar @lastByPop;
569
- return($n, [ @lastByPop ]) if $n > 1 and $multi;
570
- return($n, $lastByPop[0]);
593
+ return(\@lastByPop);
571
594
  }
572
595
  warn "No such city in Geolocation database\n";
573
- return 0;
596
+ return();
574
597
  }
575
598
  #
576
599
  # determine Geolocation based on GPS coordinates
@@ -606,9 +629,11 @@ Entry: for (; $i<@cityList; ++$i) {
606
629
  my ($inc, $end, $n) = (-1, -1, $n0+1);
607
630
  my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
608
631
  my $cp0 = cos($p0);
632
+ my (@matches, @rtnList, @dist);
633
+
609
634
  for (;;) {
610
635
  if (($n += $inc) == $end) {
611
- last if $inc == 1;
636
+ last if $inc == 1 or $n0 == $n1;
612
637
  ($inc, $end, $n) = (1, $numEntries, $n1);
613
638
  }
614
639
  my $i = $sorted ? $$sorted[$n] : $n;
@@ -619,28 +644,56 @@ Entry: for (; $i<@cityList; ++$i) {
619
644
  abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
620
645
  # ignore if population is below threshold
621
646
  next if defined $minPop and $minPop ge substr($cityList[$i],6,2);
622
- next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
647
+ next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
623
648
  $ln = ($ln << 4) | ($f & 0x0f);
624
649
  # calculate great circle distance to this city on unit sphere
625
650
  my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
626
651
  my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
627
652
  my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
628
- my $distU = atan2(sqrt($a), sqrt(1-$a));
653
+ my $distU = atan2(sqrt($a), sqrt(1-$a)); # distance on unit sphere
629
654
  next if $distU > $minDistU;
630
- $minDistU = $distU;
631
- $minDistC = $minDistU * 0x200000 / $pi;
632
655
  @matchParms = ($i, $p1, $t1, $distU);
656
+ if ($num <= 1) {
657
+ $minDistU = $distU;
658
+ } else {
659
+ my $j;
660
+ # add this entry into list of matching cities ordered by closest first
661
+ for ($j=0; $j<@matches; ++$j) {
662
+ last if $distU < $matches[$j][3];
663
+ }
664
+ if ($j < $#matches) {
665
+ splice @matches, $j, 0, [ @matchParms ];
666
+ } else {
667
+ $matches[$j] = [ @matchParms ];
668
+ }
669
+ # restrict list to the specified number of nearest cities
670
+ pop @matches if @matches > $num;
671
+ # update minimum distance with furthest match if we satisfied our quota
672
+ $minDistU = $matches[-1][3] if @matches >= $num;
673
+ }
674
+ $minDistC = $minDistU * 0x200000 / $pi; # distance in scaled coordinate units
633
675
  }
634
- @matchParms or warn("No suitable location in Geolocation database\n"), return 0;
635
-
636
- # calculate distance in km and bearing to matching city
637
- my ($ii, $p1, $t1, $distU) = @matchParms;
638
- my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
639
- my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
640
- $be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
676
+ @matchParms or warn("No suitable location in Geolocation database\n"), return();
677
+ $num = @matches;
641
678
 
642
679
  @startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
643
- return(1, $ii, $km, $be)
680
+
681
+ for (;;) {
682
+ if ($num > 1) {
683
+ last unless @matches;
684
+ @matchParms = @{$matches[0]};
685
+ shift @matches;
686
+ }
687
+ # calculate distance in km and bearing to matching city
688
+ my ($ii, $p1, $t1, $distU) = @matchParms;
689
+ my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
690
+ my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
691
+ $be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
692
+ push @rtnList, $ii;
693
+ push @dist, [ $km, $be ];
694
+ last if $num <= 1;
695
+ }
696
+ return(\@rtnList, \@dist);
644
697
  }
645
698
 
646
699
  1; #end
@@ -691,10 +744,10 @@ True on success.
691
744
  =head2 ReadAltNames
692
745
 
693
746
  Load the alternate names database. Before calling this method the $altDir
694
- package variable must be set to a directory containing the AltNames.dat
695
- database that matches the current Geolocation.dat. This method is called
696
- automatically by L</Geolocate> if $altDir is set and the GeolocAltNames
697
- option is used and an input city name is provided.
747
+ package variable may be set, otherwise AltNames.dat is loaded from the same
748
+ directory as Geolocation.dat. This method is called automatically by
749
+ L</Geolocate> if the GeolocAltNames option is used and an input city name is
750
+ provided.
698
751
 
699
752
  Image::ExifTool::Geolocation::ReadAltNames();
700
753
 
@@ -706,8 +759,8 @@ option is used and an input city name is provided.
706
759
 
707
760
  =item Return Value:
708
761
 
709
- True on success. Resets the value of $altDir to prevent further attempts at
710
- re-loading the same database.
762
+ True on success. May be called repeatedly, but AltNames.dat is loaded only
763
+ on the first call.
711
764
 
712
765
  =back
713
766
 
@@ -833,8 +886,7 @@ Comma-separated string of alternate names for this city.
833
886
 
834
887
  =item Notes:
835
888
 
836
- Must set the $altDir package variable and call L</ReadAltNames> before
837
- calling this routine.
889
+ L</ReadAltNames> must be called before calling this routine.
838
890
 
839
891
  =back
840
892
 
@@ -854,37 +906,46 @@ zero or more of the following in any order, separated by commas: region
854
906
  name, subregion name, country code, and/or country name. Regular
855
907
  expressions in C</expr/> format are also allowed, optionally prefixed by
856
908
  "ci", "re", "sr", "cc" or "co" to specifically match City, Region,
857
- Subregion, CountryCode or Country name. See
858
- L<https://exiftool.org/geolocation.html#Read> for details.
909
+ Subregion, CountryCode or Country name. Two special controls may be added
910
+ to the argument list:
859
911
 
860
- 1) Optional reference to hash of options:
912
+ 'both' - When search input includes both name and GPS coordinates, use
913
+ both to determine the closest city matching the specified
914
+ name(s) instead of using GPS only.
861
915
 
862
- GeolocMinPop - minimum population of cities to consider in search
916
+ 'num=##' - When the search includes GPS coordinates, return the nearest
917
+ ## cities instead of just the closest one. Returned cities
918
+ are in the order from nearest to farthest.
863
919
 
864
- GeolocMaxDist - maximum distance (km) to search for cities when an input
865
- GPS position is used
920
+ See L<https://exiftool.org/geolocation.html#Read> for more details.
866
921
 
867
- GeolocMulti - flag to return multiple cities if there is more than one
868
- match. In this case the return value is a list of city
869
- information lists.
922
+ 1) Optional reference to hash of options:
870
923
 
871
- GeolocFeature - comma-separated list of feature codes to include in
872
- search, or exclude if the list starts with a dash (-)
924
+ GeolocMinPop - Minimum population of cities to consider in search.
925
+ Default 0.
873
926
 
874
- GeolocAltNames - flag to search alternate names database if available
875
- for matching city name (see ALTERNATE DATABASES below)
927
+ GeolocMaxDist - Maximum distance (km) to search for cities when an
928
+ input GPS position is used. Default infinity.
876
929
 
877
- =item Return Value:
930
+ GeolocMulti - Flag to return multiple cities if there is more than
931
+ one match. Used in the case where no input GPS
932
+ coordinates are provided. Default 0.
933
+
934
+ GeolocFeature - Comma-separated list of feature codes to include in
935
+ search, or exclude if the list starts with a dash (-).
936
+ Default undef.
878
937
 
879
- 0) Number of matching entries, or 0 if no matches
938
+ GeolocAltNames - Flag to search alternate names database if available
939
+ for matching city name (see ALTERNATE DATABASES below).
940
+ Default undef.
880
941
 
881
- 1) Entry number for matching city in database, or undef if no matches, or a
882
- reference to a list of entry numbers of matching cities if multiple matches
883
- were found and the flag was set to return multiple matches
942
+ =item Return Values:
884
943
 
885
- 2) Distance to closest city in km if "lat,lon" specified
944
+ 0) Reference to list of database entry numbers for matching cities, or undef
945
+ if no matches were found.
886
946
 
887
- 3) Compass bearing for direction to closest city if "lat,lon" specified
947
+ 1) Reference to list of distance/bearing pairs for each matching city, or
948
+ undef if the search didn't provide GPS coordinates.
888
949
 
889
950
  =back
890
951
 
@@ -896,11 +957,12 @@ contain the Geolocation.dat file, and optionally a GeoLang directory for the
896
957
  language translations. The $geoDir variable may be set to an empty string
897
958
  to disable loading of a database.
898
959
 
899
- A database of alternate city names may be loaded by setting the package
900
- $altDir variable. This directory should contain the AltNames.dat database
901
- that matches the version of Geolocation.dat being used. When searching for
902
- a city by name, the alternate-names database is checked to provide
903
- additional possibilities for matches.
960
+ When searching for a city by name, AltNames.dat is checked to provide
961
+ additional possibilities for matches if the GeolocAltNames option is set and
962
+ an AltNames.dat database exists. The package $altDir variable may be set to
963
+ specify a different directory for AltNames.dat, otherwise the
964
+ Geolocation.dat directory is assumed. The entries in AltNames.dat must
965
+ match those in the currently loaded version of Geolocation.dat.
904
966
 
905
967
  =head1 ADDING USER-DEFINED DATABASE ENTRIES
906
968
 
@@ -911,7 +973,7 @@ technique before the Geolocation module is loaded.
911
973
  # city, region, subregion, country code, country, timezone,
912
974
  ['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
913
975
  # feature code, population, lat, lon
914
- '',400,42.06115,27.97833],
976
+ 'PPL',400,42.06115,27.97833],
915
977
  );
916
978
 
917
979
  Similarly, user-defined language translations may be defined, and will
@@ -15,6 +15,7 @@
15
15
  # 2019/11/10 - PH Also write pitch to CameraElevationAngle
16
16
  # 2020/12/01 - PH Added ability to read DJI CSV log files
17
17
  # 2022/06/21 - PH Added ability to read Google Takeout JSON files
18
+ # 2024/04/23 - PH Added ability to read more OpenTracks GPS tags
18
19
  #
19
20
  # References: 1) http://www.topografix.com/GPX/1/1/
20
21
  # 2) http://www.gpsinformation.org/dale/nmea.htm#GSA
@@ -29,7 +30,7 @@ use vars qw($VERSION);
29
30
  use Image::ExifTool qw(:Public);
30
31
  use Image::ExifTool::GPS;
31
32
 
32
- $VERSION = '1.75';
33
+ $VERSION = '1.76';
33
34
 
34
35
  sub JITTER() { return 2 } # maximum time jitter
35
36
 
@@ -66,6 +67,8 @@ my %xmlTag = (
66
67
  course => 'dir', # (written by Arduino)
67
68
  pitch => 'pitch', # (written by Arduino)
68
69
  roll => 'roll', # (written by Arduino)
70
+ speed => 'speed', # (OpenTrack gpx)
71
+ accuracy_horizontal => 'err',#(OpenTrack gpx)
69
72
  # XML containers (fix is reset at the opening tag of these properties)
70
73
  wpt => '', # GPX
71
74
  trkpt => '', # GPX
@@ -85,6 +88,7 @@ my %fixInfoKeys = (
85
88
  alt => [ 'alt' ],
86
89
  orient => [ 'dir', 'pitch', 'roll' ],
87
90
  atemp => [ 'atemp' ],
91
+ err => [ 'err' ],
88
92
  );
89
93
 
90
94
  my %isOrient = ( dir => 1, pitch => 1, roll => 1 ); # test for orientation key
@@ -346,8 +350,8 @@ sub LoadTrackLog($$;$)
346
350
  # validate altitude
347
351
  undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/;
348
352
  $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate
349
- } elsif ($tag eq 'atemp') {
350
- $$has{atemp} = 1;
353
+ } elsif ($tag eq 'atemp' or $tag eq 'speed' or $tag eq 'err') {
354
+ $$has{$tag} = 1;
351
355
  }
352
356
  }
353
357
  }
@@ -392,8 +396,8 @@ sub LoadTrackLog($$;$)
392
396
  # validate altitude
393
397
  undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/;
394
398
  $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate
395
- } elsif ($tag eq 'atemp') {
396
- $$has{atemp} = 1;
399
+ } elsif ($tag eq 'atemp' or $tag eq 'speed' or $tag eq 'err') {
400
+ $$has{$tag} = 1;
397
401
  }
398
402
  }
399
403
  }
@@ -1126,7 +1130,7 @@ sub SetGeoValues($$;$)
1126
1130
  # loop through available fix information categories
1127
1131
  # (pos, track, alt, orient)
1128
1132
  my ($category, $key);
1129
- Category: foreach $category (qw{pos track alt orient atemp}) {
1133
+ Category: foreach $category (qw{pos track alt orient atemp err}) {
1130
1134
  next unless $$has{$category};
1131
1135
  my ($f, $p0b, $p1b, $f0b);
1132
1136
  # loop through specific fix information keys
@@ -1236,10 +1240,11 @@ Category: foreach $category (qw{pos track alt orient atemp}) {
1236
1240
  @r = $et->SetNewValue(GPSLongitude => $$fix{lon}, %opts);
1237
1241
  @r = $et->SetNewValue(GPSAltitude => $gpsAlt, %opts);
1238
1242
  @r = $et->SetNewValue(GPSAltitudeRef => $gpsAltRef, %opts);
1239
- if ($$has{track}) {
1243
+ if ($$has{track} or $$has{speed}) {
1244
+ my $type = $$has{track} ? 'track' : 'speed';
1240
1245
  my $tFix = $fix;
1241
- if (not defined $$fix{track} and defined $iExt) {
1242
- my $p = FindFix($et,'track',$times,$points,$iExt,$iDir,$geoMaxExtSecs);
1246
+ if (not defined $$fix{$type} and defined $iExt) {
1247
+ my $p = FindFix($et,$type,$times,$points,$iExt,$iDir,$geoMaxExtSecs);
1243
1248
  $tFix = $p if $p;
1244
1249
  }
1245
1250
  @r = $et->SetNewValue(GPSTrack => $$tFix{track}, %opts);
@@ -1280,6 +1285,9 @@ Category: foreach $category (qw{pos track alt orient atemp}) {
1280
1285
  }
1281
1286
  @r = $et->SetNewValue(AmbientTemperature => $$tFix{atemp}, %opts);
1282
1287
  }
1288
+ if ($$has{err}) {
1289
+ @r = $et->SetNewValue(GPSHPositioningError => $$fix{err}, %opts);
1290
+ }
1283
1291
  unless ($xmp) {
1284
1292
  my ($latRef, $lonRef);
1285
1293
  $latRef = ($$fix{lat} > 0 ? 'N' : 'S') if defined $$fix{lat};
@@ -1305,7 +1313,7 @@ Category: foreach $category (qw{pos track alt orient atemp}) {
1305
1313
  GPSAltitude GPSAltitudeRef GPSDateStamp GPSTimeStamp GPSDateTime
1306
1314
  GPSTrack GPSTrackRef GPSSpeed GPSSpeedRef GPSImgDirection
1307
1315
  GPSImgDirectionRef GPSPitch GPSRoll CameraElevationAngle
1308
- AmbientTemperature GPSCoordinates))
1316
+ AmbientTemperature GPSHPositioningError GPSCoordinates))
1309
1317
  {
1310
1318
  my @r = $et->SetNewValue($_, undef, %opts);
1311
1319
  }
@@ -65,7 +65,7 @@ use Image::ExifTool::Exif;
65
65
  use Image::ExifTool::GPS;
66
66
  use Image::ExifTool::XMP;
67
67
 
68
- $VERSION = '4.32';
68
+ $VERSION = '4.33';
69
69
 
70
70
  sub LensIDConv($$$);
71
71
  sub ProcessNikonAVI($$$);
@@ -5506,13 +5506,14 @@ my %nikonFocalConversions = (
5506
5506
  37 => 'Nikkor Z 600mm f/4 TC VR S', #28
5507
5507
  38 => 'Nikkor Z 85mm f/1.2 S', #28
5508
5508
  39 => 'Nikkor Z 17-28mm f/2.8', #IB
5509
- 40 => 'NIKKOR Z 26mm f/2.8', #28
5510
- 41 => 'NIKKOR Z DX 12-28mm f/3.5-5.6 PZ VR', #28
5509
+ 40 => 'Nikkor Z 26mm f/2.8', #28
5510
+ 41 => 'Nikkor Z DX 12-28mm f/3.5-5.6 PZ VR', #28
5511
5511
  42 => 'Nikkor Z 180-600mm f/5.6-6.3 VR', #30
5512
- 43 => 'NIKKOR Z DX 24mm f/1.7', #28
5513
- 44 => 'NIKKOR Z 70-180mm f/2.8', #28
5514
- 45 => 'NIKKOR Z 600mm f/6.3 VR S', #28
5512
+ 43 => 'Nikkor Z DX 24mm f/1.7', #28
5513
+ 44 => 'Nikkor Z 70-180mm f/2.8', #28
5514
+ 45 => 'Nikkor Z 600mm f/6.3 VR S', #28
5515
5515
  46 => 'Nikkor Z 135mm f/1.8 S Plena', #28
5516
+ 48 => 'Nikkor Z 28-400mm f/4-8 VR', #30
5516
5517
  32768 => 'Nikkor Z 400mm f/2.8 TC VR S TC-1.4x', #28
5517
5518
  32769 => 'Nikkor Z 600mm f/4 TC VR S TC-1.4x', #28
5518
5519
  },