exiftool-vendored.pl 12.80.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 (43) hide show
  1. package/bin/Changes +81 -0
  2. package/bin/MANIFEST +6 -18
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +4 -2
  6. package/bin/build_geolocation +872 -0
  7. package/bin/config_files/example.config +2 -2
  8. package/bin/exiftool +61 -17
  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 +47 -31
  13. package/bin/lib/Image/ExifTool/CanonVRD.pm +19 -6
  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/FujiFilm.pm +20 -7
  17. package/bin/lib/Image/ExifTool/GM.pm +552 -0
  18. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  19. package/bin/lib/Image/ExifTool/Geolocation.pm +423 -178
  20. package/bin/lib/Image/ExifTool/Geotag.pm +26 -13
  21. package/bin/lib/Image/ExifTool/M2TS.pm +32 -4
  22. package/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
  23. package/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
  24. package/bin/lib/Image/ExifTool/Nikon.pm +337 -27
  25. package/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
  26. package/bin/lib/Image/ExifTool/Olympus.pm +1 -0
  27. package/bin/lib/Image/ExifTool/OpenEXR.pm +21 -3
  28. package/bin/lib/Image/ExifTool/PNG.pm +3 -3
  29. package/bin/lib/Image/ExifTool/QuickTime.pm +45 -24
  30. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +66 -30
  31. package/bin/lib/Image/ExifTool/README +2 -0
  32. package/bin/lib/Image/ExifTool/Sony.pm +16 -7
  33. package/bin/lib/Image/ExifTool/TagLookup.pm +4827 -4778
  34. package/bin/lib/Image/ExifTool/TagNames.pod +953 -620
  35. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +32 -9
  36. package/bin/lib/Image/ExifTool/Writer.pl +169 -130
  37. package/bin/lib/Image/ExifTool/XMP.pm +4 -2
  38. package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
  39. package/bin/lib/Image/ExifTool.pm +106 -48
  40. package/bin/lib/Image/ExifTool.pod +47 -25
  41. package/bin/perl-Image-ExifTool.spec +1 -1
  42. package/bin/pp_build_exe.args +4 -4
  43. package/package.json +3 -3
@@ -1,20 +1,30 @@
1
1
  #------------------------------------------------------------------------------
2
2
  # File: Geolocation.pm
3
3
  #
4
- # Description: Look up geolocation information based on a GPS position
4
+ # Description: Determine geolocation from GPS and visa-versa
5
5
  #
6
6
  # Revisions: 2024-03-03 - P. Harvey Created
7
+ # 2024-03-21 - PH Significant restructuring and addition of
8
+ # several new features.
7
9
  #
8
10
  # References: https://download.geonames.org/export/
9
11
  #
10
- # Notes: Set $Image::ExifTool::Geolocation::geoDir to override
11
- # default directory for the database file Geolocation.dat
12
- # 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
13
16
  #
14
- # Based on data from geonames.org Creative Commons databases,
15
- # reformatted as follows in the Geolocation.dat file:
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
21
+ # terminated by a null byte.
16
22
  #
17
- # Header:
23
+ # Databases are based on data from geonames.org with a
24
+ # Creative Commons license, reformatted as follows in the
25
+ # Geolocation.dat file:
26
+ #
27
+ # Header:
18
28
  # "GeolocationV.VV\tNNNN\n" (V.VV=version, NNNN=num city entries)
19
29
  # "# <comment>\n"
20
30
  # NNNN City entries:
@@ -25,9 +35,10 @@
25
35
  # 5 int8u - index of country in country list
26
36
  # 6 int8u - 0xf0 = population E exponent (in format "N.Fe+0E"), 0x0f = population N digit
27
37
  # 7 int16u - 0xf000 = population F digit, 0x0fff = index in region list (admin1)
28
- # 9 int16u - 0x7fff = index in subregion (admin2), 0x8000 = high bit of time zone
38
+ # 9 int16u - v1.02: 0x7fff = index in subregion (admin2), 0x8000 = high bit of time zone
39
+ # 9 int16u - v1.03: index in subregion (admin2)
29
40
  # 11 int8u - low byte of time zone index
30
- # 12 int8u - 0x0f - feature code index (see below)
41
+ # 12 int8u - 0x3f = feature code index (see below), v1.03: 0x80 = high bit of time zone
31
42
  # 13 string - UTF8 City name, terminated by newline
32
43
  # "\0\0\0\0\x01"
33
44
  # Country entries:
@@ -42,11 +53,14 @@
42
53
  # "\0\0\0\0\x04"
43
54
  # Time zone entries:
44
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
45
59
  # "\0\0\0\0\0"
46
60
  #
47
- # 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)
48
62
  #
49
- # 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR
63
+ # 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR 15. PPLX
50
64
  # 1. PPL 4. PPLA3 7. PPLC 10. PPLG 13. PPLS
51
65
  # 2. PPLA 5. PPLA4 8. PPLCH 11. PPLL 14. STLMT
52
66
  #------------------------------------------------------------------------------
@@ -54,28 +68,29 @@
54
68
  package Image::ExifTool::Geolocation;
55
69
 
56
70
  use strict;
57
- use vars qw($VERSION $geoDir $dbInfo);
71
+ use vars qw($VERSION $geoDir $altDir $dbInfo);
58
72
 
59
- $VERSION = '1.02';
73
+ $VERSION = '1.07'; # (this is the module version number, not the database version)
60
74
 
61
- my $databaseVersion = '1.02';
75
+ my $debug; # set to output processing time for testing
62
76
 
63
77
  sub ReadDatabase($);
64
78
  sub SortDatabase($);
65
79
  sub AddEntry(@);
66
- sub GetEntry($;$);
67
- sub Geolocate($;$$$$$);
80
+ sub GetEntry($;$$);
81
+ sub Geolocate($;$);
68
82
 
69
83
  my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
70
- my (%countryNum, %regionNum, %subregionNum, %timezoneNum, %langLookup);
84
+ my (%countryNum, %regionNum, %subregionNum, %timezoneNum); # reverse lookups
85
+ my (@sortOrder, @altNames, %langLookup, $nCity, %featureCodes);
86
+ my ($lastArgs, %lastFound, @lastByPop, @lastByLat); # cached city matches
87
+ my $dbVer = '1.03';
71
88
  my $sortedBy = 'Latitude';
72
89
  my $pi = 3.1415926536;
73
90
  my $earthRadius = 6371; # earth radius in km
74
-
75
- my @featureCodes = qw(Other PPL PPLA PPLA2 PPLA3 PPLA4 PPLA5
76
- PPLC PPLCH PPLF PPLG PPLL PPLR PPLS STLMT ?);
77
- my $i = 0;
78
- my %featureCodes = map { lc($_) => $i++ } @featureCodes;
91
+ # hard-coded feature codes for v1.02 database
92
+ my @featureCodes = qw(Other PPL PPLA PPLA2 PPLA3 PPLA4 PPLA5 PPLC
93
+ PPLCH PPLF PPLG PPLL PPLR PPLS STLMT PPLX);
79
94
 
80
95
  # get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
81
96
  # or according to $Image::ExifTool::Geolocation::directory if specified
@@ -94,12 +109,10 @@ unless (defined $geoDir and not $geoDir) {
94
109
  }
95
110
  }
96
111
 
97
- # set directory for language files
98
- my $geoLang;
99
- if ($geoDir and -d "$geoDir/GeoLang") {
100
- $geoLang = "$geoDir/GeoLang";
101
- } elsif ($geoDir or not defined $geoDir) {
102
- $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;
103
116
  }
104
117
 
105
118
  # add user-defined entries to the database
@@ -115,7 +128,7 @@ sub ReadDatabase($)
115
128
  {
116
129
  my $datfile = shift;
117
130
  # open geolocation database and verify header
118
- open DATFILE, "<$datfile" or warn("Error reading $datfile\n"), return 0;
131
+ open DATFILE, "< $datfile" or warn("Error reading $datfile\n"), return 0;
119
132
  binmode DATFILE;
120
133
  my $line = <DATFILE>;
121
134
  unless ($line =~ /^Geolocation(\d+\.\d+)\t(\d+)/) {
@@ -123,20 +136,23 @@ sub ReadDatabase($)
123
136
  close(DATFILE);
124
137
  return 0;
125
138
  }
126
- if ($1 != $databaseVersion) {
127
- my $which = $1 < $databaseVersion ? 'database' : 'ExifTool';
139
+ ($dbVer, $nCity) = ($1, $2);
140
+ if ($dbVer !~ /^1\.0[23]$/) {
141
+ my $which = $dbVer < 1.03 ? 'database' : 'ExifTool';
128
142
  warn("Incompatible Geolocation database (update your $which)\n");
129
143
  close(DATFILE);
130
144
  return 0;
131
145
  }
132
- my $ncity = $2;
133
146
  my $comment = <DATFILE>;
134
- defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
135
- $dbInfo = "$datfile v$databaseVersion: $ncity cities with population > $1";
147
+ defined $comment and $comment =~ / (\d+) / or close(DATFILE), return 0;
148
+ $dbInfo = "$datfile v$dbVer: $nCity cities with population > $1";
136
149
  my $isUserDefined = @Image::ExifTool::UserDefined::Geolocation;
137
-
150
+
151
+ undef @altNames; # reset altNames
152
+
138
153
  # read city database
139
154
  undef @cityList;
155
+ my $i = 0;
140
156
  for (;;) {
141
157
  $line = <DATFILE>;
142
158
  last if length($line) == 6 and $line =~ /\0\0\0\0/;
@@ -144,7 +160,7 @@ sub ReadDatabase($)
144
160
  chomp $line;
145
161
  push @cityList, $line;
146
162
  }
147
- @cityList == $ncity or warn("Bad number of entries in Geolocation database\n"), return 0;
163
+ @cityList == $nCity or warn("Bad number of entries in Geolocation database\n"), return 0;
148
164
  # read countries
149
165
  for (;;) {
150
166
  $line = <DATFILE>;
@@ -177,10 +193,62 @@ sub ReadDatabase($)
177
193
  push @timezoneList, $line;
178
194
  $timezoneNum{lc $line} = $#timezoneList if $isUserDefined;
179
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
+ }
180
207
  close DATFILE;
208
+ # initialize featureCodes lookup
209
+ $i = 0;
210
+ %featureCodes = map { lc($_) => $i++ } @featureCodes;
181
211
  return 1;
182
212
  }
183
213
 
214
+ #------------------------------------------------------------------------------
215
+ # Read alternate-names database
216
+ # Returns: True on success
217
+ # Notes: Must be called after ReadDatabase(). Resets $altDir on exit.
218
+ sub ReadAltNames()
219
+ {
220
+ my $success;
221
+ if ($altDir and $nCity) {
222
+ if (open ALTFILE, "< $altDir/AltNames.dat") {
223
+ binmode ALTFILE;
224
+ local $/ = "\0";
225
+ my $i = 0;
226
+ while (<ALTFILE>) { chop; $altNames[$i++] = $_; }
227
+ close ALTFILE;
228
+ if ($i == $nCity) {
229
+ $success = 1;
230
+ } else {
231
+ warn("Bad number of entries in AltNames database\n");
232
+ undef @altNames;
233
+ }
234
+ } else {
235
+ warn "Error reading $altDir/AltNames.dat\n";
236
+ }
237
+ undef $altDir;
238
+ }
239
+ return $success;
240
+ }
241
+
242
+ #------------------------------------------------------------------------------
243
+ # Clear last city matches cache
244
+ sub ClearLastMatches()
245
+ {
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
250
+ }
251
+
184
252
  #------------------------------------------------------------------------------
185
253
  # Sort database by specified field
186
254
  # Inputs: 0) Field name to sort (Latitude,City,Country)
@@ -189,38 +257,47 @@ sub SortDatabase($)
189
257
  {
190
258
  my $field = shift;
191
259
  return 1 if $field eq $sortedBy; # already sorted?
260
+ undef @sortOrder;
192
261
  if ($field eq 'Latitude') {
193
- @cityList = sort { $a cmp $b } @cityList;
262
+ @sortOrder = sort { $cityList[$a] cmp $cityList[$b] } 0..$#cityList;
194
263
  } elsif ($field eq 'City') {
195
- @cityList = sort { substr($a,13) cmp substr($b,13) } @cityList;
264
+ @sortOrder = sort { substr($cityList[$a],13) cmp substr($cityList[$b],13) } 0..$#cityList;
196
265
  } elsif ($field eq 'Country') {
197
266
  my %lkup;
198
- foreach (@cityList) {
199
- my $city = substr($_,13);
200
- my $ctry = substr($countryList[ord substr($_,5,1)], 2);
267
+ foreach (0..$#cityList) {
268
+ my $city = substr($cityList[$_],13);
269
+ my $ctry = substr($countryList[ord substr($cityList[$_],5,1)], 2);
201
270
  $lkup{$_} = "$ctry $city";
202
271
  }
203
- @cityList = sort { $lkup{$a} cmp $lkup{$b} } @cityList;
272
+ @sortOrder = sort { $lkup{$a} cmp $lkup{$b} } 0..$#cityList;
204
273
  } else {
205
274
  return 0;
206
275
  }
207
276
  $sortedBy = $field;
277
+ ClearLastMatches();
208
278
  return 1;
209
279
  }
210
280
 
211
281
  #------------------------------------------------------------------------------
212
282
  # Add cities to the Geolocation database
213
- # Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon
283
+ # Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon,altNames
214
284
  # eg. AddEntry('Sinemorets','Burgas','Obshtina Tsarevo','BG','Bulgaria','Europe/Sofia','',400,42.06115,27.97833)
285
+ # Returns: true on success, otherwise issues warning
215
286
  sub AddEntry(@)
216
287
  {
217
- my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon) = @_;
218
- @_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return;
219
- if (@_ != 10 or length($cc) != 2) {
220
- warn "Country code '${cc}' in UserDefined::Geolocation is not 2 characters\n";
221
- return;
288
+ my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon, $altNames) = @_;
289
+ @_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return 0;
290
+ length($cc) != 2 and warn("Country code '${cc}' is not 2 characters\n"), return 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
+ }
222
300
  }
223
- $fc = $featureCodes{lc $fc} || 0;
224
301
  chomp $lon; # (just in case it was read from file)
225
302
  # create reverse lookups for country/region/subregion/timezone if not done already
226
303
  # (eg. if the entries are being added manually instead of via UserDefined::Geolocation)
@@ -233,6 +310,7 @@ sub AddEntry(@)
233
310
  }
234
311
  my $cn = $countryNum{lc $cc};
235
312
  unless (defined $cn) {
313
+ $#countryList >= 0xff and warn("AddEntry: Too many countries\n"), return 0;
236
314
  push @countryList, "$cc$country";
237
315
  $cn = $countryNum{lc $cc} = $#countryList;
238
316
  } elsif ($country) {
@@ -240,16 +318,20 @@ sub AddEntry(@)
240
318
  }
241
319
  my $tn = $timezoneNum{lc $timezone};
242
320
  unless (defined $tn) {
321
+ $#timezoneList >= 0x1ff and warn("AddEntry: Too many time zones\n"), return 0;
243
322
  push @timezoneList, $timezone;
244
323
  $tn = $timezoneNum{lc $timezone} = $#timezoneList;
245
324
  }
246
325
  my $rn = $regionNum{lc $region};
247
326
  unless (defined $rn) {
327
+ $#regionList >= 0xfff and warn("AddEntry: Too many regions\n"), return 0;
248
328
  push @regionList, $region;
249
329
  $rn = $regionNum{lc $region} = $#regionList;
250
330
  }
251
331
  my $sn = $subregionNum{lc $subregion};
252
332
  unless (defined $sn) {
333
+ my $max = $dbVer eq '1.02' ? 0x7fff : 0xffff;
334
+ $#subregionList >= $max and warn("AddEntry: Too many subregions\n"), return 0;
253
335
  push @subregionList, $subregion;
254
336
  $sn = $subregionNum{lc $subregion} = $#subregionList;
255
337
  }
@@ -257,40 +339,67 @@ sub AddEntry(@)
257
339
  # pack CC index, population and region index into a 32-bit integer
258
340
  my $code = ($cn << 24) | (substr($pop,-1,1)<<20) | (substr($pop,0,1)<<16) | (substr($pop,2,1)<<12) | $rn;
259
341
  # store high bit of timezone index
260
- $tn > 255 and $sn |= 0x8000, $tn -= 256;
342
+ if ($tn > 255) {
343
+ if ($dbVer eq '1.02') {
344
+ $sn |= 0x8000;
345
+ } else {
346
+ $fn |= 0x80;
347
+ }
348
+ $tn -= 256;
349
+ }
261
350
  $lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
262
351
  $lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
263
- 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);
264
353
  push @cityList, "$hdr$city";
354
+ # add altNames entry if provided
355
+ if ($altNames) {
356
+ chomp $altNames; # (just in case)
357
+ $altNames =~ tr/,/\n/;
358
+ # add any more arguments in case altNames were passed separately (undocumented)
359
+ foreach (11..$#_) {
360
+ chomp $_[$_];
361
+ $altNames .= "\n$_[$_]";
362
+ }
363
+ $altNames[$#cityList] = $altNames;
364
+ }
265
365
  $sortedBy = '';
366
+ undef $lastArgs; # (faster than ClearLastArgs)
367
+ return 1;
266
368
  }
267
369
 
268
370
  #------------------------------------------------------------------------------
269
371
  # Unpack entry in database
270
- # Inputs: 0) entry number, 1) optional language code
271
- # Returns: 0-9) city,region,subregion,country_code,country,timezone,feature_code,pop,lat,lon
272
- sub GetEntry($;$)
372
+ # Inputs: 0) entry number or index into sorted database,
373
+ # 1) optional language code, 2) flag to use index into sorted database
374
+ # Returns: 0-10) city,region,subregion,country_code,country,timezone,
375
+ # feature_code,pop,lat,lon,altNames
376
+ sub GetEntry($;$$)
273
377
  {
274
- my ($entryNum, $lang) = @_;
378
+ my ($entryNum, $lang, $sort) = @_;
275
379
  return() if $entryNum > $#cityList;
276
- my ($lt,$f,$ln,$code,$sb,$tn,$fc) = unpack('nCnNnCC', $cityList[$entryNum]);
380
+ $entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
381
+ my ($lt,$f,$ln,$code,$sn,$tn,$fn) = unpack('nCnNnCC', $cityList[$entryNum]);
277
382
  my $city = substr($cityList[$entryNum],13);
278
383
  my $ctry = $countryList[$code >> 24];
279
384
  my $rgn = $regionList[$code & 0x0fff];
280
- my $sub = $subregionList[$sb & 0x7fff];
385
+ if ($dbVer eq '1.02') {
386
+ $sn & 0x8000 and $tn += 256, $sn &= 0x7fff;
387
+ } else {
388
+ $fn & 0x80 and $tn += 256;
389
+ }
390
+ my $sub = $subregionList[$sn];
281
391
  # convert population digits back into exponent format
282
392
  my $pop = (($code>>16 & 0x0f) . '.' . ($code>>12 & 0x0f) . 'e+' . ($code>>20 & 0x0f)) + 0;
283
- $tn += 256 if $sb & 0x8000;
284
393
  $lt = sprintf('%.4f', (($lt<<4)|($f >> 4)) * 180 / 0x100000 - 90);
285
394
  $ln = sprintf('%.4f', (($ln<<4)|($f & 0x0f))* 360 / 0x100000 - 180);
286
- $fc = $featureCodes[$fc & 0x0f];
395
+ my $fc = $featureCodes[$fn & 0x3f] || 'Other';
287
396
  my $cc = substr($ctry, 0, 2);
288
397
  my $country = substr($ctry, 2);
289
398
  if ($lang) {
290
399
  my $xlat = $langLookup{$lang};
291
400
  # load language lookups if not done already
292
401
  if (not defined $xlat) {
293
- if (eval "require '$geoLang/$lang.pm'") {
402
+ if (eval "require '$geoDir/GeoLang/$lang.pm'") {
294
403
  my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
295
404
  no strict 'refs';
296
405
  $xlat = \%$trans if %$trans;
@@ -325,30 +434,43 @@ sub GetEntry($;$)
325
434
  return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
326
435
  }
327
436
 
437
+ #------------------------------------------------------------------------------
438
+ # Get alternate names for specified database entry
439
+ # Inputs: 0) entry number or index into sorted database, 1) sort flag
440
+ # Returns: comma-separated list of alternate names, or empty string if no names
441
+ # Notes: ReadAltNames() must be called before this
442
+ sub GetAltNames($;$)
443
+ {
444
+ my ($entryNum, $sort) = @_;
445
+ $entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
446
+ my $alt = $altNames[$entryNum] or return '';
447
+ $alt =~ tr/\n/,/;
448
+ return $alt;
449
+ }
450
+
328
451
  #------------------------------------------------------------------------------
329
452
  # Look up lat,lon or city in geolocation database
330
453
  # Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
331
- # 1) optional min population, 2) optional max distance (km)
332
- # 3) optional language code, 4) flag to return multiple cities
333
- # 5) comma-separated list of feature codes to match (or ignore if leading "-")
334
- # Returns: Reference to list of city information, or list of city information
335
- # lists if returning multiple cities.
336
- # City information list elements:
337
- # 0) UTF8 city name (or undef if geolocation is unsuccessful),
338
- # 1) UTF8 state, province or region (or empty),
339
- # 2) UTF8 subregion (or empty)
340
- # 3) country code, 4) country name,
341
- # 5) time zone name (empty string possible), 6) feature code, 7) population,
342
- # 8/9) approx lat/lon (or undef if geolocation is unsuccessful,
343
- # 10) approx distance (km), 11) compass bearing to city,
344
- # 12) non-zero if multiple matches were possible (and city with
345
- # the largest population is returned)
346
- sub Geolocate($;$$$$$)
454
+ # 1) options hash reference (or undef for no options)
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($;$)
347
461
  {
348
- my ($arg, $pop, $maxDist, $lang, $multi, $fcodes) = @_;
349
- my ($city, $i, %found, @exact, %regex, @multiCity, $other, $idx);
350
- my ($minPop, $minDistU, $minDistC, @matchParms, @matches, @coords, $fcmask, $both);
462
+ my ($arg, $opts) = @_;
463
+ my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
464
+ my ($minPop, $minDistU, $minDistC, @matchParms, @coords, %fcOK, $both);
465
+ my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
351
466
 
467
+ $opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
468
+ @$opts{qw(GeolocMinPop GeolocMaxDist GeolocMulti GeolocFeature GeolocAltNames)};
469
+
470
+ if ($debug) {
471
+ require Time::HiRes;
472
+ @startTime = Time::HiRes::gettimeofday();
473
+ }
352
474
  @cityList or warn('No Geolocation database'), return();
353
475
  # make population code for comparing with 2 bytes at offset 6 in database
354
476
  if ($pop) {
@@ -357,17 +479,18 @@ sub Geolocate($;$$$$$)
357
479
  }
358
480
  if ($fcodes) {
359
481
  my $neg = $fcodes =~ s/^-//;
360
- my @fcodes = split /\s*,\s*/, $fcodes;
482
+ my @fcodes = split /\s*,-?\s*/, lc $fcodes; # (allow leading dash on subsequent codes)
361
483
  if ($neg) {
362
- $fcmask = 0xffff;
363
- 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;
364
486
  } else {
365
- defined $featureCodes{lc $_} and $fcmask |= (1 << $featureCodes{lc $_}) foreach @fcodes;
487
+ defined $featureCodes{$_} and $fcOK{$featureCodes{$_}} = 1 foreach @fcodes;
366
488
  }
367
489
  }
368
490
  #
369
491
  # process input argument
370
492
  #
493
+ my $num = 1;
371
494
  $arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
372
495
  my @args = split /\s*,\s*/, $arg;
373
496
  my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
@@ -376,18 +499,25 @@ sub Geolocate($;$$$$$)
376
499
  if (m{^(-)?(\w{2})?/(.*)/(i?)$}) {
377
500
  my $re = $4 ? qr/$3/im : qr/$3/m;
378
501
  next if not defined($idx = $ri{$2});
502
+ push @cargs, $_;
379
503
  $other = 1 if $idx < 5;
380
504
  $idx += 10 if $1; # add 10 for negative matches
505
+ $regex{$idx} or $regex{$idx} = [ ];
381
506
  push @{$regex{$idx}}, $re;
382
507
  $city = '' unless defined $city;
383
508
  } elsif (/^[-+]?\d+(\.\d+)?$/) { # coordinate format
384
509
  push @coords, $_ if @coords < 2;
385
- } elsif (lc eq 'both') {
510
+ } elsif (lc $_ eq 'both') {
386
511
  $both = 1;
387
- } elsif ($city) {
388
- push @exact, lc $_;
389
- } else {
390
- $city = lc $_;
512
+ } elsif ($_ =~ /^num=(\d+)$/i) {
513
+ $num = $1;
514
+ } elsif ($_) {
515
+ push @cargs, $_;
516
+ if ($city) {
517
+ push @exact, lc $_;
518
+ } else {
519
+ $city = lc $_;
520
+ }
391
521
  }
392
522
  }
393
523
  unless (defined $city or @coords == 2) {
@@ -400,16 +530,34 @@ sub Geolocate($;$$$$$)
400
530
  # perform reverse Geolocation lookup to determine GPS based on city, country, etc.
401
531
  #
402
532
  while (defined $city and (@coords != 2 or $both)) {
403
- Entry: for ($i=0; $i<@cityList; ++$i) {
533
+ my $cargs = join(',', @cargs, $pop||'', $maxDist||'', $fcodes||'');
534
+ my $i = 0;
535
+ if ($lastArgs and $lastArgs eq $cargs) {
536
+ $i = @cityList; # bypass search
537
+ } else {
538
+ ClearLastMatches();
539
+ $lastArgs = $cargs;
540
+ }
541
+ # read alternate names database if an exact city match is specified
542
+ if ($altNames) {
543
+ ReadAltNames() if $city and $altDir;
544
+ $altNames = \@altNames;
545
+ } else {
546
+ $altNames = [ ]; # (don't search alt names)
547
+ }
548
+ Entry: for (; $i<@cityList; ++$i) {
404
549
  my $cty = substr($cityList[$i],13);
405
- next if $city and $city ne lc $cty; # test exact city name first
550
+ if ($city and $city ne lc $cty) { # test exact city name first
551
+ next unless $$altNames[$i] and $$altNames[$i] =~ /^$city$/im;
552
+ }
406
553
  # test with city-specific regexes
407
554
  if ($regex{8}) { $cty =~ $_ or next Entry foreach @{$regex{8}} }
408
555
  if ($regex{18}) { $cty !~ $_ or next Entry foreach @{$regex{18}} }
409
556
  # test other arguments
410
- my ($cd,$sb) = unpack('x5Nn', $cityList[$i]);
557
+ my ($cd,$sn) = unpack('x5Nn', $cityList[$i]);
411
558
  my $ct = $countryList[$cd >> 24];
412
- my @geo = (substr($ct,0,2), substr($ct,2), $regionList[$cd & 0x0fff], $subregionList[$sb & 0x7fff]);
559
+ $sn &= 0x7fff if $dbVer eq '1.02';
560
+ my @geo = (substr($ct,0,2), substr($ct,2), $regionList[$cd & 0x0fff], $subregionList[$sn]);
413
561
  if (@exact) {
414
562
  # make quick lookup for all names at this location
415
563
  my %geoLkup;
@@ -428,22 +576,21 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
428
576
  $str !~ $_ or next Entry foreach @{$regex{19}};
429
577
  }
430
578
  # test feature code and population
431
- 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};
432
580
  my $pc = substr($cityList[$i],6,2);
433
- $found{$i} = $pc if not defined $minPop or $pc ge $minPop;
581
+ if (not defined $minPop or $pc ge $minPop) {
582
+ $lastFound{$i} = $pc;
583
+ push @lastByLat, $i if @coords == 2;
584
+ }
434
585
  }
435
- if (%found) {
436
- @matches = keys %found;
437
- @coords == 2 and @matches = sort({ $a <=> $b } @matches), last; # continue to use coords
438
- scalar(keys %found) > 200 and warn("Too many matching cities\n"), return();
439
- @matches = sort { $found{$b} cmp $found{$a} or $cityList[$a] cmp $cityList[$b] } @matches if @matches > 1;
440
- foreach (@matches) {
441
- my @cityInfo = GetEntry($_, $lang);
442
- $cityInfo[12] = @matches if @matches > 1;
443
- return \@cityInfo unless @matches > 1 and $multi;
444
- push @multiCity, \@cityInfo;
586
+ @startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
587
+ if (%lastFound) {
588
+ @coords == 2 and $useLastFound = 1, last; # continue to use coords with last city matches
589
+ scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return();
590
+ unless (@lastByPop) {
591
+ @lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
445
592
  }
446
- return \@multiCity;
593
+ return(\@lastByPop);
447
594
  }
448
595
  warn "No such city in Geolocation database\n";
449
596
  return();
@@ -465,12 +612,14 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
465
612
  $lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
466
613
  $lat or $lat = $coords[0] < 0 ? 1 : 0xfffff; # (zero latitude is a problem for our calculations)
467
614
  my $coord = pack('nCn',$lat>>4,(($lat&0x0f)<<4)|($lon&0x0f),$lon>>4);;
615
+ # start from cached city matches if also using city information
616
+ my $numEntries = @lastByLat || @cityList;
468
617
  # binary search to find closest longitude
469
- my $numEntries = @matches || @cityList;
470
618
  my ($n0, $n1) = (0, $numEntries - 1);
619
+ my $sorted = @lastByLat ? \@lastByLat : (@sortOrder ? \@sortOrder : undef);
471
620
  while ($n1 - $n0 > 1) {
472
621
  my $n = int(($n0 + $n1) / 2);
473
- if ($coord lt $cityList[@matches ? $matches[$n] : $n]) {
622
+ if ($coord lt $cityList[$sorted ? $$sorted[$n] : $n]) {
474
623
  $n1 = $n;
475
624
  } else {
476
625
  $n0 = $n;
@@ -480,43 +629,71 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
480
629
  my ($inc, $end, $n) = (-1, -1, $n0+1);
481
630
  my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
482
631
  my $cp0 = cos($p0);
632
+ my (@matches, @rtnList, @dist);
633
+
483
634
  for (;;) {
484
635
  if (($n += $inc) == $end) {
485
- last if $inc == 1;
636
+ last if $inc == 1 or $n0 == $n1;
486
637
  ($inc, $end, $n) = (1, $numEntries, $n1);
487
638
  }
488
- my $entryNum = @matches ? $matches[$n] : $n;
639
+ my $i = $sorted ? $$sorted[$n] : $n;
489
640
  # get city latitude/longitude
490
- my ($lt,$f,$ln) = unpack('nCn', $cityList[$entryNum]);
641
+ my ($lt,$f,$ln) = unpack('nCn', $cityList[$i]);
491
642
  $lt = ($lt << 4) | ($f >> 4);
492
643
  # searched far enough if latitude alone is further than best distance
493
644
  abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
494
645
  # ignore if population is below threshold
495
- next if defined $minPop and $minPop ge substr($cityList[$entryNum],6,2);
496
- next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$entryNum],12,1)) & 0x0f));
646
+ next if defined $minPop and $minPop ge substr($cityList[$i],6,2);
647
+ next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
497
648
  $ln = ($ln << 4) | ($f & 0x0f);
498
649
  # calculate great circle distance to this city on unit sphere
499
650
  my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
500
651
  my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
501
652
  my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
502
- my $distU = atan2(sqrt($a), sqrt(1-$a));
653
+ my $distU = atan2(sqrt($a), sqrt(1-$a)); # distance on unit sphere
503
654
  next if $distU > $minDistU;
504
- $minDistU = $distU;
505
- $minDistC = $minDistU * 0x200000 / $pi;
506
- @matchParms = ($entryNum, $p1, $t1, $distU);
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
507
675
  }
508
676
  @matchParms or warn("No suitable location in Geolocation database\n"), return();
677
+ $num = @matches;
509
678
 
510
- # calculate distance in km and bearing to matching city
511
- my ($en, $p1, $t1, $distU) = @matchParms;
512
- my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
513
- my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
514
- $be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
679
+ @startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
515
680
 
516
- # unpack return values from database entry
517
- my @cityInfo = GetEntry($en, $lang);
518
- @cityInfo[10,11] = ($km, $be); # add distance and heading
519
- return \@cityInfo;
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);
520
697
  }
521
698
 
522
699
  1; #end
@@ -525,11 +702,13 @@ __END__
525
702
 
526
703
  =head1 NAME
527
704
 
528
- Image::ExifTool::Geolocation - Look up geolocation based on GPS position
705
+ Image::ExifTool::Geolocation - Determine geolocation from GPS and visa-versa
529
706
 
530
707
  =head1 SYNOPSIS
531
708
 
532
- This module is used by the Image::ExifTool Geolocation feature.
709
+ Look up geolocation information (city, region, subregion, country, etc)
710
+ based on input GPS coordinates, or determine GPS coordinates based on city
711
+ name, etc.
533
712
 
534
713
  =head1 DESCRIPTION
535
714
 
@@ -562,6 +741,29 @@ True on success.
562
741
 
563
742
  =back
564
743
 
744
+ =head2 ReadAltNames
745
+
746
+ Load the alternate names database. Before calling this method the $altDir
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.
751
+
752
+ Image::ExifTool::Geolocation::ReadAltNames();
753
+
754
+ =over 4
755
+
756
+ =item Inputs:
757
+
758
+ (none)
759
+
760
+ =item Return Value:
761
+
762
+ True on success. May be called repeatedly, but AltNames.dat is loaded only
763
+ on the first call.
764
+
765
+ =back
766
+
565
767
  =head2 SortDatabase
566
768
 
567
769
  Sort database in specified order.
@@ -585,8 +787,8 @@ Sort database in specified order.
585
787
  Add entry to Geolocation database.
586
788
 
587
789
  Image::ExifTool::Geolocation::AddEntry($city, $region,
588
- $countryCode, $country, $timezone, $featureCode
589
- $population, $lat, $lon);
790
+ $subregion, $countryCode, $country, $timezone,
791
+ $featureCode, $population, $lat, $lon, $altNames);
590
792
 
591
793
  =over 4
592
794
 
@@ -614,23 +816,32 @@ database, then the database entry is updated with the new country name.
614
816
 
615
817
  9) GPS longitude
616
818
 
819
+ 10) Optional comma-separated list of alternate names for the city
820
+
821
+ =item Return Value:
822
+
823
+ 1 on success, otherwise sends a warning message to stderr
824
+
617
825
  =back
618
826
 
619
827
  =head2 GetEntry
620
828
 
621
829
  Get entry from Geolocation database.
622
830
 
623
- my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang);
831
+ my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang,$sort);
624
832
 
625
833
  =over 4
626
834
 
627
835
  =item Inputs:
628
836
 
629
- 0) Entry number in database
837
+ 0) Entry number in database, or index into sorted database
630
838
 
631
839
  1) Optional language code
632
840
 
633
- =item Return Values:
841
+ 2) Optional flag to treat first argument as an index into the sorted
842
+ database
843
+
844
+ item Return Values:
634
845
 
635
846
  0) City name, or undef if the entry didn't exist
636
847
 
@@ -652,11 +863,30 @@ Get entry from Geolocation database.
652
863
 
653
864
  9) GPS longitude
654
865
 
866
+ =back
867
+
868
+ =head2 GetAltNames
869
+
870
+ Get alternate names for specified city.
871
+
872
+ my $str = Image::ExifTool::Geolocation::GetAltNames($num,$sort);
873
+
874
+ =over 4
875
+
876
+ =item Inputs:
877
+
878
+ 0) Entry number in database or index into the sorted database
879
+
880
+ 1) Optional flag to treat first argument as an index into the sorted
881
+ database
882
+
883
+ =item Return value:
884
+
885
+ Comma-separated string of alternate names for this city.
886
+
655
887
  =item Notes:
656
888
 
657
- The alternate-language feature of this method (and of L</Geolocate>)
658
- requires the installation of optional GeoLang modules. See
659
- L<https://exiftool.org/geolocation.html> for more information.
889
+ L</ReadAltNames> must be called before calling this routine.
660
890
 
661
891
  =back
662
892
 
@@ -664,8 +894,7 @@ L<https://exiftool.org/geolocation.html> for more information.
664
894
 
665
895
  Return geolocation information for specified GPS coordinates or city name.
666
896
 
667
- my @cityInfo =
668
- Image::ExifTool::Geolocation::Geolocate($arg,$pop,$dist,$lang,$multi,$codes);
897
+ my @rtnInfo = Image::ExifTool::Geolocation::Geolocate($arg,$opts);
669
898
 
670
899
  =over 4
671
900
 
@@ -677,57 +906,80 @@ zero or more of the following in any order, separated by commas: region
677
906
  name, subregion name, country code, and/or country name. Regular
678
907
  expressions in C</expr/> format are also allowed, optionally prefixed by
679
908
  "ci", "re", "sr", "cc" or "co" to specifically match City, Region,
680
- Subregion, CountryCode or Country name. See
681
- L<https://exiftool.org/geolocation.html#Read> for details.
682
-
683
- 1) Minimum city population (cities smaller than this are ignored)
684
-
685
- 2) Maximum distance to city (farther cities are not considered)
909
+ Subregion, CountryCode or Country name. Two special controls may be added
910
+ to the argument list:
686
911
 
687
- 3) Language code
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.
688
915
 
689
- 4) Flag to return multiple cities if there is more than one match. In this
690
- case the return value is a list of city information lists.
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.
691
919
 
692
- 5) Comma-separated list of feature codes to include in search, or to exclude
693
- if the list starts with a dash (-).
920
+ See L<https://exiftool.org/geolocation.html#Read> for more details.
694
921
 
695
- =item Return Value:
922
+ 1) Optional reference to hash of options:
696
923
 
697
- Reference to list of information about the matching city. If multiple
698
- matches were found, the city with the highest population is returned unless
699
- the flag is set to allow multiple cities to be returned, in which case all
700
- cities are turned as a list of city lists in order of decreasing population.
924
+ GeolocMinPop - Minimum population of cities to consider in search.
925
+ Default 0.
701
926
 
702
- The city information list contains the following entries:
927
+ GeolocMaxDist - Maximum distance (km) to search for cities when an
928
+ input GPS position is used. Default infinity.
703
929
 
704
- 0) Name of matching city (UTF8), or undef if no match
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.
705
933
 
706
- 1) Region, state or province name (UTF8), or "" if no region
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.
707
937
 
708
- 2) Subregion name (UTF8), or "" if no region
938
+ GeolocAltNames - Flag to search alternate names database if available
939
+ for matching city name (see ALTERNATE DATABASES below).
940
+ Default undef.
709
941
 
710
- 3) Country code
942
+ =item Return Values:
711
943
 
712
- 4) Country name (UTF8)
944
+ 0) Reference to list of database entry numbers for matching cities, or undef
945
+ if no matches were found.
713
946
 
714
- 5) Standard time zone identifier name
947
+ 1) Reference to list of distance/bearing pairs for each matching city, or
948
+ undef if the search didn't provide GPS coordinates.
715
949
 
716
- 6) Feature code
950
+ =back
717
951
 
718
- 7) City population rounded to 2 significant figures
952
+ =head1 ALTERNATE DATABASES
719
953
 
720
- 8) Approximate city latitude (signed degrees)
954
+ A different version of the cities database may be specified setting the
955
+ package $geoDir variable before loading this module. This directory should
956
+ contain the Geolocation.dat file, and optionally a GeoLang directory for the
957
+ language translations. The $geoDir variable may be set to an empty string
958
+ to disable loading of a database.
721
959
 
722
- 9) Approximate city longitude
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.
723
966
 
724
- 10) Distance to city in km if "lat,lon" specified
967
+ =head1 ADDING USER-DEFINED DATABASE ENTRIES
725
968
 
726
- 11) Compass bearing for direction to city if "lat,lon" specified
969
+ User-defined entries may be created by defining them using the following
970
+ technique before the Geolocation module is loaded.
727
971
 
728
- 12) Flag set if multiple matches were found
972
+ @Image::ExifTool::UserDefined::Geolocation = (
973
+ # city, region, subregion, country code, country, timezone,
974
+ ['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
975
+ # feature code, population, lat, lon
976
+ 'PPL',400,42.06115,27.97833],
977
+ );
729
978
 
730
- =back
979
+ Similarly, user-defined language translations may be defined, and will
980
+ override any existing translations. Translations for the default 'en'
981
+ language may also be specified. See
982
+ L<https://exiftool.org/geolocation.html#Custom> for more information.
731
983
 
732
984
  =head1 USING A CUSTOM DATABASE
733
985
 
@@ -737,24 +989,17 @@ the input arguments of the AddEntry method.
737
989
 
738
990
  $Image::ExifTool::Geolocation::geoDir = '';
739
991
  require Image::ExifTool::Geolocation;
740
- open DFILE, "<$filename";
992
+ open DFILE, "< $filename";
741
993
  Image::ExifTool::Geolocation::AddEntry(split /,/) foreach <DFILE>;
742
994
  close DFILE;
743
995
 
744
- =head1 CUSTOM LANGUAGE TRANSLATIONS
745
-
746
- User-defined language translations may be added by defining
747
- %Image::ExifTool::Geolocation::geoLang before calling GetEntry() or
748
- Geolocate(). See L<http://exiftool.org/geolocation.html#Custom> for
749
- details.
750
-
751
996
  =head1 AUTHOR
752
997
 
753
998
  Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
754
999
 
755
1000
  This library is free software; you can redistribute it and/or modify it
756
- under the same terms as Perl itself. Geolocation.dat is based on data
757
- from geonames.org with a Creative Commons license.
1001
+ under the same terms as Perl itself. The associated database files are
1002
+ based on data from geonames.org with a Creative Commons license.
758
1003
 
759
1004
  =head1 REFERENCES
760
1005