exiftool_vendored 12.82.0 → 12.85.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +59 -0
  3. data/bin/MANIFEST +2 -18
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +47 -46
  7. data/bin/build_geolocation +945 -0
  8. data/bin/config_files/example.config +2 -2
  9. data/bin/exiftool +75 -51
  10. data/bin/fmt_files/gpx.fmt +2 -1
  11. data/bin/fmt_files/gpx_wpt.fmt +2 -1
  12. data/bin/lib/Image/ExifTool/Apple.pm +52 -7
  13. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +5 -2
  14. data/bin/lib/Image/ExifTool/Canon.pm +11 -2
  15. data/bin/lib/Image/ExifTool/CanonVRD.pm +18 -5
  16. data/bin/lib/Image/ExifTool/DJI.pm +29 -0
  17. data/bin/lib/Image/ExifTool/DPX.pm +3 -3
  18. data/bin/lib/Image/ExifTool/Exif.pm +19 -2
  19. data/bin/lib/Image/ExifTool/GM.pm +17 -8
  20. data/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  21. data/bin/lib/Image/ExifTool/Geolocation.pm +170 -104
  22. data/bin/lib/Image/ExifTool/Geotag.pm +18 -10
  23. data/bin/lib/Image/ExifTool/ID3.pm +28 -6
  24. data/bin/lib/Image/ExifTool/Nikon.pm +25 -6
  25. data/bin/lib/Image/ExifTool/Pentax.pm +64 -13
  26. data/bin/lib/Image/ExifTool/QuickTime.pm +26 -7
  27. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +5 -0
  28. data/bin/lib/Image/ExifTool/Sony.pm +15 -6
  29. data/bin/lib/Image/ExifTool/TagLookup.pm +3557 -3540
  30. data/bin/lib/Image/ExifTool/TagNames.pod +61 -13
  31. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +4 -2
  32. data/bin/lib/Image/ExifTool/Writer.pl +166 -134
  33. data/bin/lib/Image/ExifTool/XMP.pm +2 -0
  34. data/bin/lib/Image/ExifTool/XMP2.pl +3 -0
  35. data/bin/lib/Image/ExifTool.pm +59 -27
  36. data/bin/lib/Image/ExifTool.pod +88 -71
  37. data/bin/perl-Image-ExifTool.spec +45 -45
  38. data/lib/exiftool_vendored/version.rb +1 -1
  39. metadata +3 -20
  40. data/bin/lib/Image/ExifTool/GeoLang/cs.pm +0 -978
  41. data/bin/lib/Image/ExifTool/GeoLang/de.pm +0 -1975
  42. data/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +0 -44
  43. data/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +0 -124
  44. data/bin/lib/Image/ExifTool/GeoLang/es.pm +0 -2921
  45. data/bin/lib/Image/ExifTool/GeoLang/fi.pm +0 -1116
  46. data/bin/lib/Image/ExifTool/GeoLang/fr.pm +0 -3171
  47. data/bin/lib/Image/ExifTool/GeoLang/it.pm +0 -2750
  48. data/bin/lib/Image/ExifTool/GeoLang/ja.pm +0 -10256
  49. data/bin/lib/Image/ExifTool/GeoLang/ko.pm +0 -4499
  50. data/bin/lib/Image/ExifTool/GeoLang/nl.pm +0 -1270
  51. data/bin/lib/Image/ExifTool/GeoLang/pl.pm +0 -3019
  52. data/bin/lib/Image/ExifTool/GeoLang/ru.pm +0 -18220
  53. data/bin/lib/Image/ExifTool/GeoLang/sk.pm +0 -441
  54. data/bin/lib/Image/ExifTool/GeoLang/sv.pm +0 -714
  55. data/bin/lib/Image/ExifTool/GeoLang/tr.pm +0 -452
  56. data/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +0 -2225
  57. data/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +0 -72
@@ -15,7 +15,7 @@ use vars qw($VERSION);
15
15
  use Image::ExifTool qw(:DataAccess :Utils);
16
16
  use Image::ExifTool::GPS;
17
17
 
18
- $VERSION = '1.00';
18
+ $VERSION = '1.01';
19
19
 
20
20
  sub Process_marl($$$);
21
21
  sub Process_mrld($$$);
@@ -30,6 +30,8 @@ my %convertUnits = (
30
30
  ltr => 'L',
31
31
  );
32
32
 
33
+ my $pi = 3.141592653589793;
34
+
33
35
  # offsets and scaling factors to convert to reasonable units
34
36
  my %changeOffset = (
35
37
  C => -273.15, # K to C
@@ -37,8 +39,8 @@ my %changeOffset = (
37
39
  my %changeScale = (
38
40
  G => 1 / 9.80665, # m/s2 to G
39
41
  kph => 3.6, # m/s to km/h
40
- deg => 180 / 3.1415926536, # radians to degrees
41
- 'deg/sec' => 180 / 3.1415926536, # rad/s to deg/s
42
+ deg => 180 / $pi, # radians to degrees
43
+ 'deg/sec' => 180 / $pi, # rad/s to deg/s
42
44
  '%' => 100, # decimal to %
43
45
  kPa => 1/1000, # Pa to kPa
44
46
  rpm => 10, # ? (arbitrary factor of 10)
@@ -181,7 +183,7 @@ my %channelStruct = (
181
183
  Name => 'GPSTrack',
182
184
  Description => 'GPS Track',
183
185
  Groups => { 2 => 'Location' },
184
- PrintConv => 'sprintf("%.2f",$val)',
186
+ PrintConv => '$val > 360 ? "n/a" : sprintf("%.2f",$val)', # (seen 655.35)
185
187
  },
186
188
  ABSActive => { },
187
189
  AccelPos => { },
@@ -209,7 +211,14 @@ my %channelStruct = (
209
211
  EngineTorqureReq => { },
210
212
  FuelCapacity => { },
211
213
  FuelLevel => { },
212
- Gear => { ValueConv => { 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 13=>'N', 14=>'R' } },
214
+ Gear => {
215
+ Notes => q{
216
+ in the PrintCSV output, the value for Neutral is set to -1, and Reverse to
217
+ -100 for compatibility with RaceRender
218
+ },
219
+ CSVConv => { 13 => -1, 14 => -100 },
220
+ PrintConv => { 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 13=>'N', 14=>'R' }
221
+ },
213
222
  GPSFix => { },
214
223
  InfotainOpMode => { },
215
224
  IntakeAirTemperature => { },
@@ -261,7 +270,7 @@ my %channelStruct = (
261
270
  sub PrintCSV($;$)
262
271
  {
263
272
  my ($et, $ts) = @_;
264
- my $csv = $$et{GMCsv} or return;
273
+ my $csv = $$et{GMCsv} or return; # get the list of channels with measurements
265
274
  @$csv or return;
266
275
  my $vals = $$et{GMVals};
267
276
  my $gmDict = $$et{GMDictionary};
@@ -271,7 +280,7 @@ sub PrintCSV($;$)
271
280
  foreach (@$csv) {
272
281
  my $gmChan = $$gmDict[$_];
273
282
  $items[$_] = $$vals[$_] * $$gmChan{Mult} + $$gmChan{Off};
274
- # apply lookup conversion if applicable (ie. Gear)
283
+ # apply CSV conversion if applicable (ie. Gear)
275
284
  next unless $$gmChan{Conv} and defined $$gmChan{Conv}{$items[$_]};
276
285
  $items[$_] = $$gmChan{Conv}{$items[$_]};
277
286
  }
@@ -397,7 +406,7 @@ sub Process_mrld($$$)
397
406
  my $init = int(($a[6] + $a[7]) / 2); # initial value for difference readings
398
407
  # save information about this channel necessary for processing the marl data
399
408
  $$gmDict[$chan] = { Name => $name, Mult => $mult, Off => $off, Init => $init };
400
- $$gmDict[$chan]{Conv} = $$tagInfo{ValueConv} if ref $$tagInfo{ValueConv} eq 'HASH';
409
+ $$gmDict[$chan]{Conv} = $$tagInfo{CSVConv};
401
410
  $csv and $$csv[$chan] = $a[12] . ($a[3] ? " ($a[3])" : '');
402
411
  }
403
412
  # channel 0 must not be defined because we use it for the TimeStamp
Binary file
@@ -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, optional space-followed-by-feature-name, then 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.08'; # (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, %featureTypes);
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
+ $featureTypes{$line} = $1 if $line =~ s/ (.*)//;
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
+ $featureTypes{$fc} = $1 if $fc =~ s/ (.*)//;
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) {
@@ -349,34 +372,35 @@ sub AddEntry(@)
349
372
  # Inputs: 0) entry number or index into sorted database,
350
373
  # 1) optional language code, 2) flag to use index into sorted database
351
374
  # Returns: 0-10) city,region,subregion,country_code,country,timezone,
352
- # feature_code,pop,lat,lon,altNames
375
+ # feature_code,pop,lat,lon,feature_type
353
376
  sub GetEntry($;$$)
354
377
  {
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
- if ($lang) {
398
+ my $ft = $featureTypes{$fc};
399
+ if ($lang and $lang ne 'en') {
376
400
  my $xlat = $langLookup{$lang};
377
401
  # load language lookups if not done already
378
402
  if (not defined $xlat) {
379
- if (eval "require '$geoLang/$lang.pm'") {
403
+ if (eval "require '$geoDir/GeoLang/$lang.pm'") {
380
404
  my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
381
405
  no strict 'refs';
382
406
  $xlat = \%$trans if %$trans;
@@ -406,9 +430,10 @@ sub GetEntry($;$$)
406
430
  $sub = $$xlat{"$cc$rgn,$sub,"} || $$xlat{$sub} || $sub;
407
431
  $rgn = $$xlat{"$cc$rgn,"} || $$xlat{$rgn} || $rgn;
408
432
  $country = $$xlat{"$cc,"} || $$xlat{$country} || $country;
433
+ $ft = $$xlat{$fc} if $$xlat{$fc};
409
434
  }
410
435
  }
411
- return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
436
+ return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln,$ft);
412
437
  }
413
438
 
414
439
  #------------------------------------------------------------------------------
@@ -429,17 +454,16 @@ sub GetAltNames($;$)
429
454
  # Look up lat,lon or city in geolocation database
430
455
  # Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
431
456
  # 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($;$$$$$)
457
+ # Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames,
458
+ # GeolocNearby
459
+ # Returns: List of matching city information, empty if none found.
460
+ # Each element in the list is an array with 0=index of city in database,
461
+ # 1=distance in km (or undef if no distance), 2=compass bearing (or undef)
462
+ sub Geolocate($;$)
439
463
  {
440
464
  my ($arg, $opts) = @_;
441
465
  my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
442
- my ($minPop, $minDistU, $minDistC, @matchParms, @coords, $fcmask, $both);
466
+ my ($minPop, $minDistU, $minDistC, @matchParms, @coords, %fcOK, $both);
443
467
  my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
444
468
 
445
469
  $opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
@@ -449,7 +473,7 @@ sub Geolocate($;$$$$$)
449
473
  require Time::HiRes;
450
474
  @startTime = Time::HiRes::gettimeofday();
451
475
  }
452
- @cityList or warn('No Geolocation database'), return 0;
476
+ @cityList or warn('No Geolocation database'), return();
453
477
  # make population code for comparing with 2 bytes at offset 6 in database
454
478
  if ($pop) {
455
479
  $pop = sprintf('%.1e', $pop);
@@ -457,17 +481,18 @@ sub Geolocate($;$$$$$)
457
481
  }
458
482
  if ($fcodes) {
459
483
  my $neg = $fcodes =~ s/^-//;
460
- my @fcodes = split /\s*,\s*/, $fcodes;
484
+ my @fcodes = split /\s*,-?\s*/, lc $fcodes; # (allow leading dash on subsequent codes)
461
485
  if ($neg) {
462
- $fcmask = 0xffff;
463
- defined $featureCodes{lc $_} and $fcmask &= ~((1 << $featureCodes{lc $_})) foreach @fcodes;
486
+ $fcOK{$_} = 1 foreach 0..$#featureCodes;
487
+ defined $featureCodes{$_} and delete $fcOK{$featureCodes{$_}} foreach @fcodes;
464
488
  } else {
465
- defined $featureCodes{lc $_} and $fcmask |= (1 << $featureCodes{lc $_}) foreach @fcodes;
489
+ defined $featureCodes{$_} and $fcOK{$featureCodes{$_}} = 1 foreach @fcodes;
466
490
  }
467
491
  }
468
492
  #
469
493
  # process input argument
470
494
  #
495
+ my $num = 1;
471
496
  $arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
472
497
  my @args = split /\s*,\s*/, $arg;
473
498
  my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
@@ -486,6 +511,8 @@ sub Geolocate($;$$$$$)
486
511
  push @coords, $_ if @coords < 2;
487
512
  } elsif (lc $_ eq 'both') {
488
513
  $both = 1;
514
+ } elsif ($_ =~ /^num=(\d+)$/i) {
515
+ $num = $1;
489
516
  } elsif ($_) {
490
517
  push @cargs, $_;
491
518
  if ($city) {
@@ -497,7 +524,7 @@ sub Geolocate($;$$$$$)
497
524
  }
498
525
  unless (defined $city or @coords == 2) {
499
526
  warn("Insufficient information to determine geolocation\n");
500
- return 0;
527
+ return();
501
528
  }
502
529
  # sort database by logitude if finding entry based on coordinates
503
530
  SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
@@ -551,7 +578,7 @@ Entry: for (; $i<@cityList; ++$i) {
551
578
  $str !~ $_ or next Entry foreach @{$regex{19}};
552
579
  }
553
580
  # test feature code and population
554
- next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
581
+ next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
555
582
  my $pc = substr($cityList[$i],6,2);
556
583
  if (not defined $minPop or $pc ge $minPop) {
557
584
  $lastFound{$i} = $pc;
@@ -561,16 +588,14 @@ Entry: for (; $i<@cityList; ++$i) {
561
588
  @startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
562
589
  if (%lastFound) {
563
590
  @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;
591
+ scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return();
565
592
  unless (@lastByPop) {
566
593
  @lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
567
594
  }
568
- my $n = scalar @lastByPop;
569
- return($n, [ @lastByPop ]) if $n > 1 and $multi;
570
- return($n, $lastByPop[0]);
595
+ return(\@lastByPop);
571
596
  }
572
597
  warn "No such city in Geolocation database\n";
573
- return 0;
598
+ return();
574
599
  }
575
600
  #
576
601
  # determine Geolocation based on GPS coordinates
@@ -606,9 +631,11 @@ Entry: for (; $i<@cityList; ++$i) {
606
631
  my ($inc, $end, $n) = (-1, -1, $n0+1);
607
632
  my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
608
633
  my $cp0 = cos($p0);
634
+ my (@matches, @rtnList, @dist);
635
+
609
636
  for (;;) {
610
637
  if (($n += $inc) == $end) {
611
- last if $inc == 1;
638
+ last if $inc == 1 or $n0 == $n1;
612
639
  ($inc, $end, $n) = (1, $numEntries, $n1);
613
640
  }
614
641
  my $i = $sorted ? $$sorted[$n] : $n;
@@ -619,28 +646,56 @@ Entry: for (; $i<@cityList; ++$i) {
619
646
  abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
620
647
  # ignore if population is below threshold
621
648
  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));
649
+ next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
623
650
  $ln = ($ln << 4) | ($f & 0x0f);
624
651
  # calculate great circle distance to this city on unit sphere
625
652
  my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
626
653
  my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
627
654
  my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
628
- my $distU = atan2(sqrt($a), sqrt(1-$a));
655
+ my $distU = atan2(sqrt($a), sqrt(1-$a)); # distance on unit sphere
629
656
  next if $distU > $minDistU;
630
- $minDistU = $distU;
631
- $minDistC = $minDistU * 0x200000 / $pi;
632
657
  @matchParms = ($i, $p1, $t1, $distU);
658
+ if ($num <= 1) {
659
+ $minDistU = $distU;
660
+ } else {
661
+ my $j;
662
+ # add this entry into list of matching cities ordered by closest first
663
+ for ($j=0; $j<@matches; ++$j) {
664
+ last if $distU < $matches[$j][3];
665
+ }
666
+ if ($j < $#matches) {
667
+ splice @matches, $j, 0, [ @matchParms ];
668
+ } else {
669
+ $matches[$j] = [ @matchParms ];
670
+ }
671
+ # restrict list to the specified number of nearest cities
672
+ pop @matches if @matches > $num;
673
+ # update minimum distance with furthest match if we satisfied our quota
674
+ $minDistU = $matches[-1][3] if @matches >= $num;
675
+ }
676
+ $minDistC = $minDistU * 0x200000 / $pi; # distance in scaled coordinate units
633
677
  }
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
678
+ @matchParms or warn("No suitable location in Geolocation database\n"), return();
679
+ $num = @matches;
641
680
 
642
681
  @startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
643
- return(1, $ii, $km, $be)
682
+
683
+ for (;;) {
684
+ if ($num > 1) {
685
+ last unless @matches;
686
+ @matchParms = @{$matches[0]};
687
+ shift @matches;
688
+ }
689
+ # calculate distance in km and bearing to matching city
690
+ my ($ii, $p1, $t1, $distU) = @matchParms;
691
+ my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
692
+ my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
693
+ $be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
694
+ push @rtnList, $ii;
695
+ push @dist, [ $km, $be ];
696
+ last if $num <= 1;
697
+ }
698
+ return(\@rtnList, \@dist);
644
699
  }
645
700
 
646
701
  1; #end
@@ -691,10 +746,10 @@ True on success.
691
746
  =head2 ReadAltNames
692
747
 
693
748
  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.
749
+ package variable may be set, otherwise AltNames.dat is loaded from the same
750
+ directory as Geolocation.dat. This method is called automatically by
751
+ L</Geolocate> if the GeolocAltNames option is used and an input city name is
752
+ provided.
698
753
 
699
754
  Image::ExifTool::Geolocation::ReadAltNames();
700
755
 
@@ -706,8 +761,8 @@ option is used and an input city name is provided.
706
761
 
707
762
  =item Return Value:
708
763
 
709
- True on success. Resets the value of $altDir to prevent further attempts at
710
- re-loading the same database.
764
+ True on success. May be called repeatedly, but AltNames.dat is loaded only
765
+ on the first call.
711
766
 
712
767
  =back
713
768
 
@@ -810,6 +865,8 @@ item Return Values:
810
865
 
811
866
  9) GPS longitude
812
867
 
868
+ 10) Feature type, or undef
869
+
813
870
  =back
814
871
 
815
872
  =head2 GetAltNames
@@ -833,8 +890,7 @@ Comma-separated string of alternate names for this city.
833
890
 
834
891
  =item Notes:
835
892
 
836
- Must set the $altDir package variable and call L</ReadAltNames> before
837
- calling this routine.
893
+ L</ReadAltNames> must be called before calling this routine.
838
894
 
839
895
  =back
840
896
 
@@ -854,37 +910,46 @@ zero or more of the following in any order, separated by commas: region
854
910
  name, subregion name, country code, and/or country name. Regular
855
911
  expressions in C</expr/> format are also allowed, optionally prefixed by
856
912
  "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.
913
+ Subregion, CountryCode or Country name. Two special controls may be added
914
+ to the argument list:
859
915
 
860
- 1) Optional reference to hash of options:
916
+ 'both' - When search input includes both name and GPS coordinates, use
917
+ both to determine the closest city matching the specified
918
+ name(s) instead of using GPS only.
861
919
 
862
- GeolocMinPop - minimum population of cities to consider in search
920
+ 'num=##' - When the search includes GPS coordinates, return the nearest
921
+ ## cities instead of just the closest one. Returned cities
922
+ are in the order from nearest to farthest.
863
923
 
864
- GeolocMaxDist - maximum distance (km) to search for cities when an input
865
- GPS position is used
924
+ See L<https://exiftool.org/geolocation.html#Read> for more details.
866
925
 
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.
926
+ 1) Optional reference to hash of options:
870
927
 
871
- GeolocFeature - comma-separated list of feature codes to include in
872
- search, or exclude if the list starts with a dash (-)
928
+ GeolocMinPop - Minimum population of cities to consider in search.
929
+ Default 0.
873
930
 
874
- GeolocAltNames - flag to search alternate names database if available
875
- for matching city name (see ALTERNATE DATABASES below)
931
+ GeolocMaxDist - Maximum distance (km) to search for cities when an
932
+ input GPS position is used. Default infinity.
876
933
 
877
- =item Return Value:
934
+ GeolocMulti - Flag to return multiple cities if there is more than
935
+ one match. Used in the case where no input GPS
936
+ coordinates are provided. Default 0.
937
+
938
+ GeolocFeature - Comma-separated list of feature codes to include in
939
+ search, or exclude if the list starts with a dash (-).
940
+ Default undef.
878
941
 
879
- 0) Number of matching entries, or 0 if no matches
942
+ GeolocAltNames - Flag to search alternate names database if available
943
+ for matching city name (see ALTERNATE DATABASES below).
944
+ Default undef.
880
945
 
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
946
+ =item Return Values:
884
947
 
885
- 2) Distance to closest city in km if "lat,lon" specified
948
+ 0) Reference to list of database entry numbers for matching cities, or undef
949
+ if no matches were found.
886
950
 
887
- 3) Compass bearing for direction to closest city if "lat,lon" specified
951
+ 1) Reference to list of distance/bearing pairs for each matching city, or
952
+ undef if the search didn't provide GPS coordinates.
888
953
 
889
954
  =back
890
955
 
@@ -896,11 +961,12 @@ contain the Geolocation.dat file, and optionally a GeoLang directory for the
896
961
  language translations. The $geoDir variable may be set to an empty string
897
962
  to disable loading of a database.
898
963
 
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.
964
+ When searching for a city by name, AltNames.dat is checked to provide
965
+ additional possibilities for matches if the GeolocAltNames option is set and
966
+ an AltNames.dat database exists. The package $altDir variable may be set to
967
+ specify a different directory for AltNames.dat, otherwise the
968
+ Geolocation.dat directory is assumed. The entries in AltNames.dat must
969
+ match those in the currently loaded version of Geolocation.dat.
904
970
 
905
971
  =head1 ADDING USER-DEFINED DATABASE ENTRIES
906
972
 
@@ -911,7 +977,7 @@ technique before the Geolocation module is loaded.
911
977
  # city, region, subregion, country code, country, timezone,
912
978
  ['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
913
979
  # feature code, population, lat, lon
914
- '',400,42.06115,27.97833],
980
+ 'PPL',400,42.06115,27.97833],
915
981
  );
916
982
 
917
983
  Similarly, user-defined language translations may be defined, and will