exiftool-vendored.pl 12.82.0 → 12.85.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 (56) hide show
  1. package/bin/Changes +59 -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 +47 -46
  6. package/bin/build_geolocation +945 -0
  7. package/bin/config_files/example.config +2 -2
  8. package/bin/exiftool +75 -51
  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 +52 -7
  12. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +5 -2
  13. package/bin/lib/Image/ExifTool/Canon.pm +11 -2
  14. package/bin/lib/Image/ExifTool/CanonVRD.pm +18 -5
  15. package/bin/lib/Image/ExifTool/DJI.pm +29 -0
  16. package/bin/lib/Image/ExifTool/DPX.pm +3 -3
  17. package/bin/lib/Image/ExifTool/Exif.pm +19 -2
  18. package/bin/lib/Image/ExifTool/GM.pm +17 -8
  19. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  20. package/bin/lib/Image/ExifTool/Geolocation.pm +170 -104
  21. package/bin/lib/Image/ExifTool/Geotag.pm +18 -10
  22. package/bin/lib/Image/ExifTool/ID3.pm +28 -6
  23. package/bin/lib/Image/ExifTool/Nikon.pm +25 -6
  24. package/bin/lib/Image/ExifTool/Pentax.pm +64 -13
  25. package/bin/lib/Image/ExifTool/QuickTime.pm +26 -7
  26. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +5 -0
  27. package/bin/lib/Image/ExifTool/Sony.pm +15 -6
  28. package/bin/lib/Image/ExifTool/TagLookup.pm +3557 -3540
  29. package/bin/lib/Image/ExifTool/TagNames.pod +61 -13
  30. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +4 -2
  31. package/bin/lib/Image/ExifTool/Writer.pl +166 -134
  32. package/bin/lib/Image/ExifTool/XMP.pm +2 -0
  33. package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
  34. package/bin/lib/Image/ExifTool.pm +59 -27
  35. package/bin/lib/Image/ExifTool.pod +88 -71
  36. package/bin/perl-Image-ExifTool.spec +45 -45
  37. package/bin/pp_build_exe.args +4 -4
  38. package/package.json +2 -2
  39. package/bin/lib/Image/ExifTool/GeoLang/cs.pm +0 -978
  40. package/bin/lib/Image/ExifTool/GeoLang/de.pm +0 -1975
  41. package/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +0 -44
  42. package/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +0 -124
  43. package/bin/lib/Image/ExifTool/GeoLang/es.pm +0 -2921
  44. package/bin/lib/Image/ExifTool/GeoLang/fi.pm +0 -1116
  45. package/bin/lib/Image/ExifTool/GeoLang/fr.pm +0 -3171
  46. package/bin/lib/Image/ExifTool/GeoLang/it.pm +0 -2750
  47. package/bin/lib/Image/ExifTool/GeoLang/ja.pm +0 -10256
  48. package/bin/lib/Image/ExifTool/GeoLang/ko.pm +0 -4499
  49. package/bin/lib/Image/ExifTool/GeoLang/nl.pm +0 -1270
  50. package/bin/lib/Image/ExifTool/GeoLang/pl.pm +0 -3019
  51. package/bin/lib/Image/ExifTool/GeoLang/ru.pm +0 -18220
  52. package/bin/lib/Image/ExifTool/GeoLang/sk.pm +0 -441
  53. package/bin/lib/Image/ExifTool/GeoLang/sv.pm +0 -714
  54. package/bin/lib/Image/ExifTool/GeoLang/tr.pm +0 -452
  55. package/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +0 -2225
  56. package/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
@@ -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