exiftool-vendored.pl 12.78.0 → 12.82.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 (58) hide show
  1. package/bin/Changes +80 -3
  2. package/bin/MANIFEST +29 -2
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +3 -2
  6. package/bin/config_files/acdsee.config +37 -57
  7. package/bin/config_files/example.config +6 -0
  8. package/bin/exiftool +67 -25
  9. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +44 -31
  10. package/bin/lib/Image/ExifTool/CanonVRD.pm +2 -2
  11. package/bin/lib/Image/ExifTool/FujiFilm.pm +21 -5
  12. package/bin/lib/Image/ExifTool/GM.pm +543 -0
  13. package/bin/lib/Image/ExifTool/GeoLang/cs.pm +978 -0
  14. package/bin/lib/Image/ExifTool/GeoLang/de.pm +1975 -0
  15. package/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +44 -0
  16. package/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +124 -0
  17. package/bin/lib/Image/ExifTool/GeoLang/es.pm +2921 -0
  18. package/bin/lib/Image/ExifTool/GeoLang/fi.pm +1116 -0
  19. package/bin/lib/Image/ExifTool/GeoLang/fr.pm +3171 -0
  20. package/bin/lib/Image/ExifTool/GeoLang/it.pm +2750 -0
  21. package/bin/lib/Image/ExifTool/GeoLang/ja.pm +10256 -0
  22. package/bin/lib/Image/ExifTool/GeoLang/ko.pm +4499 -0
  23. package/bin/lib/Image/ExifTool/GeoLang/nl.pm +1270 -0
  24. package/bin/lib/Image/ExifTool/GeoLang/pl.pm +3019 -0
  25. package/bin/lib/Image/ExifTool/GeoLang/ru.pm +18220 -0
  26. package/bin/lib/Image/ExifTool/GeoLang/sk.pm +441 -0
  27. package/bin/lib/Image/ExifTool/GeoLang/sv.pm +714 -0
  28. package/bin/lib/Image/ExifTool/GeoLang/tr.pm +452 -0
  29. package/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +2225 -0
  30. package/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +72 -0
  31. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  32. package/bin/lib/Image/ExifTool/Geolocation.pm +867 -146
  33. package/bin/lib/Image/ExifTool/Geotag.pm +13 -1
  34. package/bin/lib/Image/ExifTool/JSON.pm +7 -2
  35. package/bin/lib/Image/ExifTool/M2TS.pm +32 -4
  36. package/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
  37. package/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
  38. package/bin/lib/Image/ExifTool/Nikon.pm +331 -22
  39. package/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
  40. package/bin/lib/Image/ExifTool/Olympus.pm +1 -0
  41. package/bin/lib/Image/ExifTool/OpenEXR.pm +37 -19
  42. package/bin/lib/Image/ExifTool/PNG.pm +3 -3
  43. package/bin/lib/Image/ExifTool/QuickTime.pm +40 -24
  44. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +61 -30
  45. package/bin/lib/Image/ExifTool/README +8 -5
  46. package/bin/lib/Image/ExifTool/Sony.pm +1 -1
  47. package/bin/lib/Image/ExifTool/TagLookup.pm +4820 -4750
  48. package/bin/lib/Image/ExifTool/TagNames.pod +1000 -615
  49. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +31 -8
  50. package/bin/lib/Image/ExifTool/WriteXMP.pl +1 -1
  51. package/bin/lib/Image/ExifTool/Writer.pl +58 -2
  52. package/bin/lib/Image/ExifTool/XMP.pm +20 -3
  53. package/bin/lib/Image/ExifTool/XMP2.pl +64 -0
  54. package/bin/lib/Image/ExifTool.pm +210 -97
  55. package/bin/lib/Image/ExifTool.pod +44 -18
  56. package/bin/perl-Image-ExifTool.spec +1 -1
  57. package/bin/pp_build_exe.args +4 -4
  58. package/package.json +3 -3
@@ -1,224 +1,943 @@
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::databaseFile to override
11
- # default database file (lib/Image/ExifTool/Geolocation.dat)
12
+ # Notes: Set $Image::ExifTool::Geolocation::geoDir to override
13
+ # default directory for the database file Geolocation.dat
14
+ # and language directory GeoLang.
12
15
  #
13
- # Based on data from geonames.org Creative Commons databases,
14
- # reformatted as follows in the Geolocation.dat file:
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
20
+ # terminated by a null byte.
15
21
  #
16
- # Header: GeolocationV.VV\tNNNN\n - V.VV=version, NNNN=num city entries
22
+ # Databases are based on data from geonames.org with a
23
+ # Creative Commons license, reformatted as follows in the
24
+ # Geolocation.dat file:
17
25
  #
26
+ # Header:
27
+ # "GeolocationV.VV\tNNNN\n" (V.VV=version, NNNN=num city entries)
28
+ # "# <comment>\n"
18
29
  # NNNN City entries:
19
- # 1. int16u[2] - longitude.latitude (converted to 0-64k range)
20
- # 2. int8u - low byte of time zone number
21
- # 3. int8u - 100's=time zone high bit, population: 10's=num zeros, 1's=sig digit
22
- # 4. UTF8 City name, terminated by tab
23
- # 5. 2-character country code
24
- # 6. Region code, terminated by newline
25
- # End of section marker - "\0\0\0\0\x01"
30
+ # Offset Format Description
31
+ # 0 int16u - latitude high 16 bits (converted to 0-0x100000 range)
32
+ # 2 int8u - latitude low 4 bits, longitude low 4 bits
33
+ # 3 int16u - longitude high 16 bits
34
+ # 5 int8u - index of country in country list
35
+ # 6 int8u - 0xf0 = population E exponent (in format "N.Fe+0E"), 0x0f = population N digit
36
+ # 7 int16u - 0xf000 = population F digit, 0x0fff = index in region list (admin1)
37
+ # 9 int16u - v1.02: 0x7fff = index in subregion (admin2), 0x8000 = high bit of time zone
38
+ # 9 int16u - v1.03: index in subregion (admin2)
39
+ # 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
+ # 13 string - UTF8 City name, terminated by newline
42
+ # "\0\0\0\0\x01"
26
43
  # Country entries:
27
44
  # 1. 2-character country code
28
45
  # 2. Country name, terminated by newline
29
- # End of section marker - "\0\0\0\0\x02"
46
+ # "\0\0\0\0\x02"
30
47
  # Region entries:
31
- # 1. 2-character country code
32
- # 2. Region code, terminated by tab
33
- # 3. Region name, terminated by newline
34
- # End of section marker - "\0\0\0\0\x03"
48
+ # 1. Region name, terminated by newline
49
+ # "\0\0\0\0\x03"
50
+ # Subregion entries:
51
+ # 1. Subregion name, terminated by newline
52
+ # "\0\0\0\0\x04"
35
53
  # Time zone entries:
36
54
  # 1. Time zone name, terminated by newline
37
- # End of file marker - "\0\0\0\0\0"
55
+ # "\0\0\0\0\0"
56
+ #
57
+ # Feature Codes: (see http://www.geonames.org/export/codes.html#P for descriptions)
58
+ #
59
+ # 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR 15. PPLX
60
+ # 1. PPL 4. PPLA3 7. PPLC 10. PPLG 13. PPLS
61
+ # 2. PPLA 5. PPLA4 8. PPLCH 11. PPLL 14. STLMT
38
62
  #------------------------------------------------------------------------------
39
63
 
40
64
  package Image::ExifTool::Geolocation;
41
65
 
42
66
  use strict;
43
- use vars qw($VERSION $databaseFile);
67
+ use vars qw($VERSION $geoDir $altDir $dbInfo);
68
+
69
+ $VERSION = '1.04'; # (this is the module version number, not the database version)
44
70
 
45
- $VERSION = '1.00';
71
+ my $debug; # set to output processing time for testing
46
72
 
47
- my (@cityLookup, %countryLookup, %adminLookup, @timezoneLookup);
73
+ sub ReadDatabase($);
74
+ sub SortDatabase($);
75
+ sub AddEntry(@);
76
+ sub GetEntry($;$$);
77
+ sub Geolocate($;$$$$$);
78
+
79
+ my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
80
+ my (%countryNum, %regionNum, %subregionNum, %timezoneNum); # reverse lookups
81
+ my (@sortOrder, @altNames, %langLookup, $nCity);
82
+ my ($lastArgs, %lastFound, @lastByPop, @lastByLat); # cached city matches
83
+ my $dbVer = '1.03';
84
+ my $sortedBy = 'Latitude';
85
+ my $pi = 3.1415926536;
86
+ my $earthRadius = 6371; # earth radius in km
87
+
88
+ my @featureCodes = qw(Other PPL PPLA PPLA2 PPLA3 PPLA4 PPLA5 PPLC
89
+ PPLCH PPLF PPLG PPLL PPLR PPLS STLMT PPLX);
90
+ my $i = 0;
91
+ my %featureCodes = map { lc($_) => $i++ } @featureCodes;
48
92
 
49
93
  # get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
50
- # or according to $Image::ExifTool::Geolocation::databaseFile if specified
51
- my $datfile = $databaseFile;
52
- unless ($datfile) {
53
- $datfile = $INC{'Image/ExifTool/Geolocation.pm'};
54
- $datfile or $datfile = 'Geolocation.pm', warn("Error getting Geolocation directory\n");
55
- $datfile =~ s/\.pm$/\.dat/;
94
+ # or according to $Image::ExifTool::Geolocation::directory if specified
95
+ my $defaultDir = $INC{'Image/ExifTool/Geolocation.pm'};
96
+ if ($defaultDir) {
97
+ $defaultDir =~ s(/Geolocation\.pm$)();
98
+ } else {
99
+ $defaultDir = '.';
100
+ warn("Error getting Geolocation.pm directory\n");
56
101
  }
57
102
 
58
- # open geolocation database and verify header
59
- open DATFILE, "<$datfile" or warn("Error reading $datfile\n"), return 0;
60
- binmode DATFILE;
61
- my $line = <DATFILE>;
62
- unless ($line =~ /^Geolocation(\d+\.\d+)\t(\d+)/) {
63
- warn("Bad format Geolocation database\n");
64
- close(DATFILE);
65
- return 0;
103
+ # read the Geolocation database unless $geoDir set to empty string
104
+ unless (defined $geoDir and not $geoDir) {
105
+ unless ($geoDir and ReadDatabase("$geoDir/Geolocation.dat")) {
106
+ ReadDatabase("$defaultDir/Geolocation.dat");
107
+ }
66
108
  }
67
- my $ncity = $2;
68
-
69
- # read city database
70
- for (;;) {
71
- $line = <DATFILE>;
72
- last if length($line) == 6 and $line =~ /\0\0\0\0/;
73
- $line .= <DATFILE> while length($line) < 7;
74
- chomp $line;
75
- push @cityLookup, $line;
109
+
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";
76
116
  }
77
- @cityLookup == $ncity or warn("Bad number of entries in Geolocation database\n"), return 0;
78
- # read countries
79
- for (;;) {
80
- $line = <DATFILE>;
81
- last if length($line) == 6 and $line =~ /\0\0\0\0/;
82
- chomp $line;
83
- $countryLookup{substr($line,0,2)} = substr($line,2);
117
+
118
+ # add user-defined entries to the database
119
+ if (@Image::ExifTool::UserDefined::Geolocation) {
120
+ AddEntry(@$_) foreach @Image::ExifTool::UserDefined::Geolocation;
84
121
  }
85
- # read regions
86
- for (;;) {
87
- $line = <DATFILE>;
88
- last if length($line) == 6 and $line =~ /\0\0\0\0/;
89
- chomp $line;
90
- my ($code, $region) = split /\t/, $line;
91
- $adminLookup{$code} = $region;
122
+
123
+ #------------------------------------------------------------------------------
124
+ # Read Geolocation database
125
+ # Inputs: 0) database file name
126
+ # Returns: true on success
127
+ sub ReadDatabase($)
128
+ {
129
+ my $datfile = shift;
130
+ # open geolocation database and verify header
131
+ open DATFILE, "< $datfile" or warn("Error reading $datfile\n"), return 0;
132
+ binmode DATFILE;
133
+ my $line = <DATFILE>;
134
+ unless ($line =~ /^Geolocation(\d+\.\d+)\t(\d+)/) {
135
+ warn("Bad format Geolocation database\n");
136
+ close(DATFILE);
137
+ return 0;
138
+ }
139
+ ($dbVer, $nCity) = ($1, $2);
140
+ if ($dbVer !~ /^1\.0[23]$/) {
141
+ my $which = $dbVer < 1.03 ? 'database' : 'ExifTool';
142
+ warn("Incompatible Geolocation database (update your $which)\n");
143
+ close(DATFILE);
144
+ return 0;
145
+ }
146
+ my $comment = <DATFILE>;
147
+ defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
148
+ $dbInfo = "$datfile v$dbVer: $nCity cities with population > $1";
149
+ my $isUserDefined = @Image::ExifTool::UserDefined::Geolocation;
150
+
151
+ undef @altNames; # reset altNames
152
+
153
+ # read city database
154
+ undef @cityList;
155
+ my $i = 0;
156
+ for (;;) {
157
+ $line = <DATFILE>;
158
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
159
+ $line .= <DATFILE> while length($line) < 14;
160
+ chomp $line;
161
+ push @cityList, $line;
162
+ }
163
+ @cityList == $nCity or warn("Bad number of entries in Geolocation database\n"), return 0;
164
+ # read countries
165
+ for (;;) {
166
+ $line = <DATFILE>;
167
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
168
+ chomp $line;
169
+ push @countryList, $line;
170
+ $countryNum{lc substr($line,0,2)} = $#countryList if $isUserDefined;
171
+ }
172
+ # read regions
173
+ for (;;) {
174
+ $line = <DATFILE>;
175
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
176
+ chomp $line;
177
+ push @regionList, $line;
178
+ $regionNum{lc $line} = $#regionList if $isUserDefined;
179
+ }
180
+ # read subregions
181
+ for (;;) {
182
+ $line = <DATFILE>;
183
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
184
+ chomp $line;
185
+ push @subregionList, $line;
186
+ $subregionNum{lc $line} = $#subregionList if $isUserDefined;
187
+ }
188
+ # read time zones
189
+ for (;;) {
190
+ $line = <DATFILE>;
191
+ last if length($line) == 6 and $line =~ /\0\0\0\0/;
192
+ chomp $line;
193
+ push @timezoneList, $line;
194
+ $timezoneNum{lc $line} = $#timezoneList if $isUserDefined;
195
+ }
196
+ close DATFILE;
197
+ return 1;
92
198
  }
93
- # read time zones
94
- for (;;) {
95
- $line = <DATFILE>;
96
- last if length($line) == 6 and $line =~ /\0\0\0\0/;
97
- chomp $line;
98
- push @timezoneLookup, $line;
199
+
200
+ #------------------------------------------------------------------------------
201
+ # Read alternate-names database
202
+ # Returns: True on success
203
+ # Notes: Must be called after ReadDatabase(). Resets $altDir on exit.
204
+ sub ReadAltNames()
205
+ {
206
+ my $success;
207
+ if ($altDir and $nCity) {
208
+ if (open ALTFILE, "< $altDir/AltNames.dat") {
209
+ binmode ALTFILE;
210
+ local $/ = "\0";
211
+ my $i = 0;
212
+ while (<ALTFILE>) { chop; $altNames[$i++] = $_; }
213
+ close ALTFILE;
214
+ if ($i == $nCity) {
215
+ $success = 1;
216
+ } else {
217
+ warn("Bad number of entries in AltNames database\n");
218
+ undef @altNames;
219
+ }
220
+ } else {
221
+ warn "Error reading $altDir/AltNames.dat\n";
222
+ }
223
+ undef $altDir;
224
+ }
225
+ return $success;
226
+ }
227
+
228
+ #------------------------------------------------------------------------------
229
+ # Clear last city matches cache
230
+ sub ClearLastMatches()
231
+ {
232
+ undef $lastArgs;
233
+ undef %lastFound;
234
+ undef @lastByPop;
235
+ undef @lastByLat;
99
236
  }
100
- close DATFILE;
101
237
 
102
238
  #------------------------------------------------------------------------------
103
- # Look up lat/lon in geolocation database
104
- # Inputs: 0) Latitude, 1) longitude, 2) optional min population,
105
- # 3) optional max distance (km)
106
- # Returns: 0) UTF8 city name (or undef if geolocation is unsuccessful),
107
- # 1) UTF8 state, province or region (or undef),
108
- # 2) country code, 3) country name (undef is possible),
109
- # 4) time zone name (empty string possible), 5) approx population,
110
- # 6) approx distance (km), 7) approximate compass bearing (or undef),
111
- # 8/9) approx lat/lon
112
- sub Geolocate($$;$$)
239
+ # Sort database by specified field
240
+ # Inputs: 0) Field name to sort (Latitude,City,Country)
241
+ # Returns: 1 on success
242
+ sub SortDatabase($)
113
243
  {
114
- my ($lat, $lon, $pop, $km) = @_;
115
- my ($minPop, $maxDist2);
116
- my $earthCirc = 40000; # earth circumference in km
244
+ my $field = shift;
245
+ return 1 if $field eq $sortedBy; # already sorted?
246
+ undef @sortOrder;
247
+ if ($field eq 'Latitude') {
248
+ @sortOrder = sort { $cityList[$a] cmp $cityList[$b] } 0..$#cityList;
249
+ } elsif ($field eq 'City') {
250
+ @sortOrder = sort { substr($cityList[$a],13) cmp substr($cityList[$b],13) } 0..$#cityList;
251
+ } elsif ($field eq 'Country') {
252
+ my %lkup;
253
+ foreach (0..$#cityList) {
254
+ my $city = substr($cityList[$_],13);
255
+ my $ctry = substr($countryList[ord substr($cityList[$_],5,1)], 2);
256
+ $lkup{$_} = "$ctry $city";
257
+ }
258
+ @sortOrder = sort { $lkup{$a} cmp $lkup{$b} } 0..$#cityList;
259
+ } else {
260
+ return 0;
261
+ }
262
+ $sortedBy = $field;
263
+ ClearLastMatches();
264
+ return 1;
265
+ }
117
266
 
267
+ #------------------------------------------------------------------------------
268
+ # Add cities to the Geolocation database
269
+ # Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon,altNames
270
+ # eg. AddEntry('Sinemorets','Burgas','Obshtina Tsarevo','BG','Bulgaria','Europe/Sofia','',400,42.06115,27.97833)
271
+ # Returns: true on success, otherwise issues warning
272
+ sub AddEntry(@)
273
+ {
274
+ my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon, $altNames) = @_;
275
+ @_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return 0;
276
+ length($cc) != 2 and warn("Country code '${cc}' is not 2 characters\n"), return 0;
277
+ $fc = $featureCodes{lc $fc} || 0;
278
+ chomp $lon; # (just in case it was read from file)
279
+ # create reverse lookups for country/region/subregion/timezone if not done already
280
+ # (eg. if the entries are being added manually instead of via UserDefined::Geolocation)
281
+ unless (%countryNum) {
282
+ my $i;
283
+ $i = 0; $countryNum{lc substr($_,0,2)} = $i++ foreach @countryList;
284
+ $i = 0; $regionNum{lc $_} = $i++ foreach @regionList;
285
+ $i = 0; $subregionNum{lc $_} = $i++ foreach @subregionList;
286
+ $i = 0; $timezoneNum{lc $_} = $i++ foreach @timezoneList;
287
+ }
288
+ my $cn = $countryNum{lc $cc};
289
+ unless (defined $cn) {
290
+ $#countryList >= 0xff and warn("AddEntry: Too many countries\n"), return 0;
291
+ push @countryList, "$cc$country";
292
+ $cn = $countryNum{lc $cc} = $#countryList;
293
+ } elsif ($country) {
294
+ $countryList[$cn] = "$cc$country"; # (override existing country name)
295
+ }
296
+ my $tn = $timezoneNum{lc $timezone};
297
+ unless (defined $tn) {
298
+ $#timezoneList >= 0x1ff and warn("AddEntry: Too many time zones\n"), return 0;
299
+ push @timezoneList, $timezone;
300
+ $tn = $timezoneNum{lc $timezone} = $#timezoneList;
301
+ }
302
+ my $rn = $regionNum{lc $region};
303
+ unless (defined $rn) {
304
+ $#regionList >= 0xfff and warn("AddEntry: Too many regions\n"), return 0;
305
+ push @regionList, $region;
306
+ $rn = $regionNum{lc $region} = $#regionList;
307
+ }
308
+ my $sn = $subregionNum{lc $subregion};
309
+ unless (defined $sn) {
310
+ my $max = $dbVer eq '1.02' ? 0x0fff : 0xffff;
311
+ $#subregionList >= $max and warn("AddEntry: Too many subregions\n"), return 0;
312
+ push @subregionList, $subregion;
313
+ $sn = $subregionNum{lc $subregion} = $#subregionList;
314
+ }
315
+ $pop = sprintf('%.1e',$pop); # format: "3.1e+04" or "3.1e+004"
316
+ # pack CC index, population and region index into a 32-bit integer
317
+ my $code = ($cn << 24) | (substr($pop,-1,1)<<20) | (substr($pop,0,1)<<16) | (substr($pop,2,1)<<12) | $rn;
318
+ # store high bit of timezone index
319
+ if ($tn > 255) {
320
+ if ($dbVer eq '1.02') {
321
+ $sn |= 0x8000;
322
+ } else {
323
+ $fc |= 0x80;
324
+ }
325
+ $tn -= 256;
326
+ }
327
+ $lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
328
+ $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);
330
+ push @cityList, "$hdr$city";
331
+ # add altNames entry if provided
332
+ if ($altNames) {
333
+ chomp $altNames; # (just in case)
334
+ $altNames =~ tr/,/\n/;
335
+ # add any more arguments in case altNames were passed separately (undocumented)
336
+ foreach (11..$#_) {
337
+ chomp $_[$_];
338
+ $altNames .= "\n$_[$_]";
339
+ }
340
+ $altNames[$#cityList] = $altNames;
341
+ }
342
+ $sortedBy = '';
343
+ undef $lastArgs; # (faster than ClearLastArgs)
344
+ return 1;
345
+ }
346
+
347
+ #------------------------------------------------------------------------------
348
+ # Unpack entry in database
349
+ # Inputs: 0) entry number or index into sorted database,
350
+ # 1) optional language code, 2) flag to use index into sorted database
351
+ # Returns: 0-10) city,region,subregion,country_code,country,timezone,
352
+ # feature_code,pop,lat,lon,altNames
353
+ sub GetEntry($;$$)
354
+ {
355
+ my ($entryNum, $lang, $sort) = @_;
356
+ return() if $entryNum > $#cityList;
357
+ $entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
358
+ my ($lt,$f,$ln,$code,$sn,$tn,$fc) = unpack('nCnNnCC', $cityList[$entryNum]);
359
+ my $city = substr($cityList[$entryNum],13);
360
+ my $ctry = $countryList[$code >> 24];
361
+ my $rgn = $regionList[$code & 0x0fff];
362
+ if ($dbVer eq '1.02') {
363
+ $sn & 0x8000 and $tn += 256, $sn &= 0x7fff;
364
+ } else {
365
+ $fc & 0x80 and $tn += 256;
366
+ }
367
+ my $sub = $subregionList[$sn];
368
+ # convert population digits back into exponent format
369
+ my $pop = (($code>>16 & 0x0f) . '.' . ($code>>12 & 0x0f) . 'e+' . ($code>>20 & 0x0f)) + 0;
370
+ $lt = sprintf('%.4f', (($lt<<4)|($f >> 4)) * 180 / 0x100000 - 90);
371
+ $ln = sprintf('%.4f', (($ln<<4)|($f & 0x0f))* 360 / 0x100000 - 180);
372
+ $fc = $featureCodes[$fc & 0x0f];
373
+ my $cc = substr($ctry, 0, 2);
374
+ my $country = substr($ctry, 2);
375
+ if ($lang) {
376
+ my $xlat = $langLookup{$lang};
377
+ # load language lookups if not done already
378
+ if (not defined $xlat) {
379
+ if (eval "require '$geoLang/$lang.pm'") {
380
+ my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
381
+ no strict 'refs';
382
+ $xlat = \%$trans if %$trans;
383
+ }
384
+ # read user-defined language translations
385
+ if (%Image::ExifTool::Geolocation::geoLang) {
386
+ my $userLang = $Image::ExifTool::Geolocation::geoLang{$lang};
387
+ if ($userLang and ref($userLang) eq 'HASH') {
388
+ if ($xlat) {
389
+ # add user-defined entries to main lookup
390
+ $$xlat{$_} = $$userLang{$_} foreach keys %$userLang;
391
+ } else {
392
+ $xlat = $userLang;
393
+ }
394
+ }
395
+ }
396
+ $langLookup{$lang} = $xlat || 0;
397
+ }
398
+ if ($xlat) {
399
+ my $r2 = $rgn;
400
+ # City-specific: "CCRgn,Sub,City", "CCRgn,City", "CC,City", ",City"
401
+ # Subregion-specific: "CCRgn,Sub,"
402
+ # Region-specific: "CCRgn,"
403
+ # Country-specific: "CC,"
404
+ $city = $$xlat{"$cc$r2,$sub,$city"} || $$xlat{"$cc$r2,$city"} ||
405
+ $$xlat{"$cc,$city"} || $$xlat{",$city"} || $$xlat{$city} || $city;
406
+ $sub = $$xlat{"$cc$rgn,$sub,"} || $$xlat{$sub} || $sub;
407
+ $rgn = $$xlat{"$cc$rgn,"} || $$xlat{$rgn} || $rgn;
408
+ $country = $$xlat{"$cc,"} || $$xlat{$country} || $country;
409
+ }
410
+ }
411
+ return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
412
+ }
413
+
414
+ #------------------------------------------------------------------------------
415
+ # Get alternate names for specified database entry
416
+ # Inputs: 0) entry number or index into sorted database, 1) sort flag
417
+ # Returns: comma-separated list of alternate names, or empty string if no names
418
+ # Notes: ReadAltNames() must be called before this
419
+ sub GetAltNames($;$)
420
+ {
421
+ my ($entryNum, $sort) = @_;
422
+ $entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
423
+ my $alt = $altNames[$entryNum] or return '';
424
+ $alt =~ tr/\n/,/;
425
+ return $alt;
426
+ }
427
+
428
+ #------------------------------------------------------------------------------
429
+ # Look up lat,lon or city in geolocation database
430
+ # Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
431
+ # 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($;$$$$$)
439
+ {
440
+ my ($arg, $opts) = @_;
441
+ my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
442
+ my ($minPop, $minDistU, $minDistC, @matchParms, @coords, $fcmask, $both);
443
+ my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
444
+
445
+ $opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
446
+ @$opts{qw(GeolocMinPop GeolocMaxDist GeolocMulti GeolocFeature GeolocAltNames)};
447
+
448
+ if ($debug) {
449
+ require Time::HiRes;
450
+ @startTime = Time::HiRes::gettimeofday();
451
+ }
452
+ @cityList or warn('No Geolocation database'), return 0;
453
+ # make population code for comparing with 2 bytes at offset 6 in database
118
454
  if ($pop) {
119
- # convert population minimum to a 2-digit code
120
- my $dig = substr($pop, 0, 1);
121
- my $zer = length($pop) - 1;
122
- # round up if necessary
123
- if (length($pop) > 1 and substr($pop, 1, 1) >= 5) {
124
- ++$dig > 9 and $dig = 1, ++$zer;
455
+ $pop = sprintf('%.1e', $pop);
456
+ $minPop = chr((substr($pop,-1,1)<<4) | (substr($pop,0,1))) . chr(substr($pop,2,1)<<4);
457
+ }
458
+ if ($fcodes) {
459
+ my $neg = $fcodes =~ s/^-//;
460
+ my @fcodes = split /\s*,\s*/, $fcodes;
461
+ if ($neg) {
462
+ $fcmask = 0xffff;
463
+ defined $featureCodes{lc $_} and $fcmask &= ~((1 << $featureCodes{lc $_})) foreach @fcodes;
464
+ } else {
465
+ defined $featureCodes{lc $_} and $fcmask |= (1 << $featureCodes{lc $_}) foreach @fcodes;
466
+ }
467
+ }
468
+ #
469
+ # process input argument
470
+ #
471
+ $arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
472
+ my @args = split /\s*,\s*/, $arg;
473
+ my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
474
+ foreach (@args) {
475
+ # allow regular expressions optionally prefixed by "ci", "cc", "co", "re" or "sr"
476
+ if (m{^(-)?(\w{2})?/(.*)/(i?)$}) {
477
+ my $re = $4 ? qr/$3/im : qr/$3/m;
478
+ next if not defined($idx = $ri{$2});
479
+ push @cargs, $_;
480
+ $other = 1 if $idx < 5;
481
+ $idx += 10 if $1; # add 10 for negative matches
482
+ $regex{$idx} or $regex{$idx} = [ ];
483
+ push @{$regex{$idx}}, $re;
484
+ $city = '' unless defined $city;
485
+ } elsif (/^[-+]?\d+(\.\d+)?$/) { # coordinate format
486
+ push @coords, $_ if @coords < 2;
487
+ } elsif (lc $_ eq 'both') {
488
+ $both = 1;
489
+ } elsif ($_) {
490
+ push @cargs, $_;
491
+ if ($city) {
492
+ push @exact, lc $_;
493
+ } else {
494
+ $city = lc $_;
495
+ }
496
+ }
497
+ }
498
+ unless (defined $city or @coords == 2) {
499
+ warn("Insufficient information to determine geolocation\n");
500
+ return 0;
501
+ }
502
+ # sort database by logitude if finding entry based on coordinates
503
+ SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
504
+ #
505
+ # perform reverse Geolocation lookup to determine GPS based on city, country, etc.
506
+ #
507
+ while (defined $city and (@coords != 2 or $both)) {
508
+ my $cargs = join(',', @cargs, $pop||'', $maxDist||'', $fcodes||'');
509
+ my $i = 0;
510
+ if ($lastArgs and $lastArgs eq $cargs) {
511
+ $i = @cityList; # bypass search
512
+ } else {
513
+ ClearLastMatches();
514
+ $lastArgs = $cargs;
125
515
  }
126
- $minPop = $zer.$dig;
127
- }
128
- if ($km) {
129
- # convert max distance to reduced coordinate units
130
- my $tmp = $km * 2 * 65536 / $earthCirc;
131
- $maxDist2 = $tmp * $tmp;
132
- }
133
- my $cos = cos($lat * 3.14159 / 180); # cosine factor for longitude distances
134
- # reduce lat/lon to the range 0-65536
135
- $lat = int(($lat + 90) / 180 * 65536 + 0.5) & 0xffff;
136
- $lon = int(($lon + 180) / 360 * 65536 + 0.5) & 0xffff;
137
- my $coord = pack('n2',$lon,$lat); # pack for comparison with binary database values
516
+ # read alternate names database if an exact city match is specified
517
+ if ($altNames) {
518
+ ReadAltNames() if $city and $altDir;
519
+ $altNames = \@altNames;
520
+ } else {
521
+ $altNames = [ ]; # (don't search alt names)
522
+ }
523
+ Entry: for (; $i<@cityList; ++$i) {
524
+ my $cty = substr($cityList[$i],13);
525
+ if ($city and $city ne lc $cty) { # test exact city name first
526
+ next unless $$altNames[$i] and $$altNames[$i] =~ /^$city$/im;
527
+ }
528
+ # test with city-specific regexes
529
+ if ($regex{8}) { $cty =~ $_ or next Entry foreach @{$regex{8}} }
530
+ if ($regex{18}) { $cty !~ $_ or next Entry foreach @{$regex{18}} }
531
+ # test other arguments
532
+ my ($cd,$sn) = unpack('x5Nn', $cityList[$i]);
533
+ my $ct = $countryList[$cd >> 24];
534
+ $sn &= 0x7fff if $dbVer eq '1.02';
535
+ my @geo = (substr($ct,0,2), substr($ct,2), $regionList[$cd & 0x0fff], $subregionList[$sn]);
536
+ if (@exact) {
537
+ # make quick lookup for all names at this location
538
+ my %geoLkup;
539
+ $_ and $geoLkup{lc $_} = 1 foreach @geo;
540
+ $geoLkup{$_} or next Entry foreach @exact;
541
+ }
542
+ # test with cc, co, re and sr regexes
543
+ if ($other) { foreach $idx (0..3) {
544
+ if ($regex{$idx}) { $geo[$idx] =~ $_ or next Entry foreach @{$regex{$idx}} }
545
+ if ($regex{$idx+10}) { $geo[$idx] !~ $_ or next Entry foreach @{$regex{$idx+10}} }
546
+ } }
547
+ # test regexes for any place name
548
+ if ($regex{9} or $regex{19}) {
549
+ my $str = join "\n", $cty, @geo;
550
+ $str =~ $_ or next Entry foreach @{$regex{9}};
551
+ $str !~ $_ or next Entry foreach @{$regex{19}};
552
+ }
553
+ # test feature code and population
554
+ next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
555
+ my $pc = substr($cityList[$i],6,2);
556
+ if (not defined $minPop or $pc ge $minPop) {
557
+ $lastFound{$i} = $pc;
558
+ push @lastByLat, $i if @coords == 2;
559
+ }
560
+ }
561
+ @startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
562
+ if (%lastFound) {
563
+ @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;
565
+ unless (@lastByPop) {
566
+ @lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
567
+ }
568
+ my $n = scalar @lastByPop;
569
+ return($n, [ @lastByPop ]) if $n > 1 and $multi;
570
+ return($n, $lastByPop[0]);
571
+ }
572
+ warn "No such city in Geolocation database\n";
573
+ return 0;
574
+ }
575
+ #
576
+ # determine Geolocation based on GPS coordinates
577
+ #
578
+ my ($lat, $lon) = @coords;
579
+ if ($maxDist) {
580
+ $minDistU = $maxDist / (2 * $earthRadius); # min distance on unit sphere
581
+ $minDistC = $maxDist * 0x100000 / ($pi * $earthRadius); # min distance in coordinate units
582
+ } else {
583
+ $minDistU = $pi;
584
+ $minDistC = 0x200000;
585
+ }
586
+ my $cos = cos($lat * $pi / 180); # cosine factor for longitude distances
587
+ # reduce lat/lon to the range 0-0x100000
588
+ $lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
589
+ $lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
590
+ $lat or $lat = $coords[0] < 0 ? 1 : 0xfffff; # (zero latitude is a problem for our calculations)
591
+ my $coord = pack('nCn',$lat>>4,(($lat&0x0f)<<4)|($lon&0x0f),$lon>>4);;
592
+ # start from cached city matches if also using city information
593
+ my $numEntries = @lastByLat || @cityList;
138
594
  # binary search to find closest longitude
139
- my ($n0, $n1) = (0, scalar(@cityLookup)-1);
595
+ my ($n0, $n1) = (0, $numEntries - 1);
596
+ my $sorted = @lastByLat ? \@lastByLat : (@sortOrder ? \@sortOrder : undef);
140
597
  while ($n1 - $n0 > 1) {
141
598
  my $n = int(($n0 + $n1) / 2);
142
- if ($coord lt $cityLookup[$n]) {
599
+ if ($coord lt $cityList[$sorted ? $$sorted[$n] : $n]) {
143
600
  $n1 = $n;
144
601
  } else {
145
602
  $n0 = $n;
146
603
  }
147
604
  }
148
605
  # step backward then forward through database to find nearest city
149
- my ($minDist2, $minN, @dxy);
150
606
  my ($inc, $end, $n) = (-1, -1, $n0+1);
607
+ my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
608
+ my $cp0 = cos($p0);
151
609
  for (;;) {
152
610
  if (($n += $inc) == $end) {
153
611
  last if $inc == 1;
154
- ($inc, $end, $n) = (1, scalar(@cityLookup), $n1);
155
- }
156
- my ($x,$y) = unpack('n2', $cityLookup[$n]);
157
- my ($dy,$dx) = ($y-$lat, $x-$lon);
158
- $dx += 65536 if $dx < -32768; # measure the short way around the world
159
- $dx -= 65536 if $dx > 32768;
160
- $dx = 2 * $cos * $dx; # adjust for longitude spacing
161
- my $dx2 = $dx * $dx;
162
- my $dist2 = $dy * $dy + $dx2;
163
- if (defined $minDist2) {
164
- # searched far enough if longitude alone is further than best distance
165
- $dx2 > $minDist2 and $n = $end - $inc, next;
166
- } elsif (defined $maxDist2) {
167
- $dx2 > $maxDist2 and $n = $end - $inc, next;
168
- next if $dist2 > $maxDist2; # ignore if distance is too great
612
+ ($inc, $end, $n) = (1, $numEntries, $n1);
169
613
  }
614
+ my $i = $sorted ? $$sorted[$n] : $n;
615
+ # get city latitude/longitude
616
+ my ($lt,$f,$ln) = unpack('nCn', $cityList[$i]);
617
+ $lt = ($lt << 4) | ($f >> 4);
618
+ # searched far enough if latitude alone is further than best distance
619
+ abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
170
620
  # ignore if population is below threshold
171
- next if $minPop and $minPop > unpack('x5C', $cityLookup[$n]) % 100;
172
- if (not defined $minDist2 or $minDist2 > $dist2) {
173
- $minDist2 = $dist2;
174
- @dxy = ($dx, $dy);
175
- $minN = $n;
176
- }
621
+ 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));
623
+ $ln = ($ln << 4) | ($f & 0x0f);
624
+ # calculate great circle distance to this city on unit sphere
625
+ my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
626
+ my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
627
+ my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
628
+ my $distU = atan2(sqrt($a), sqrt(1-$a));
629
+ next if $distU > $minDistU;
630
+ $minDistU = $distU;
631
+ $minDistC = $minDistU * 0x200000 / $pi;
632
+ @matchParms = ($i, $p1, $t1, $distU);
177
633
  }
178
- return () unless defined $minN;
179
-
180
- my ($ln,$lt,$tn,$pc) = unpack('n2C2', $cityLookup[$minN]);
181
- my ($city, $code) = split /\t/, substr($cityLookup[$minN],6);
182
- my $ctry = substr($code,0,2);
183
- my $rgn = $adminLookup{$code};
184
- my $po2 = substr($pc, -1) . (length($pc) > 1 ? '0' x substr($pc, -2, 1) : '');
185
- $tn += 256 if $pc > 99;
186
- my $be; # calculate bearing to geolocated city
187
- if ($dxy[0] or $dxy[1]) {
188
- $be = atan2($dxy[0],$dxy[1]) * 180 / 3.14159;
189
- $be += 360 if $be < 0;
190
- $be = int($be + 0.5);
191
- }
192
- $lt = sprintf('%.3f', $lt * 180 / 65536 - 90);
193
- $ln = sprintf('%.3f', $ln * 360 / 65536 - 180);
194
- $km = sprintf('%.1f', sqrt($minDist2) * $earthCirc / (2 * 65536));
195
-
196
- return($city,$rgn,$ctry,$countryLookup{$ctry},$timezoneLookup[$tn],$po2,$km,$be,$lt,$ln);
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
641
+
642
+ @startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
643
+ return(1, $ii, $km, $be)
197
644
  }
198
645
 
646
+ 1; #end
647
+
199
648
  __END__
200
649
 
201
650
  =head1 NAME
202
651
 
203
- Image::ExifTool::Geolocation - Look up geolocation based on GPS position
652
+ Image::ExifTool::Geolocation - Determine geolocation from GPS and visa-versa
204
653
 
205
654
  =head1 SYNOPSIS
206
655
 
207
- This module is used by the Image::ExifTool Geolocation feature.
656
+ Look up geolocation information (city, region, subregion, country, etc)
657
+ based on input GPS coordinates, or determine GPS coordinates based on city
658
+ name, etc.
208
659
 
209
660
  =head1 DESCRIPTION
210
661
 
211
662
  This module contains the code to convert GPS coordinates to city, region,
212
- country, time zone, etc. It uses a database derived from geonames.org,
213
- modified to reduce the size as much as possible.
663
+ subregion, country, time zone, etc. It uses a database derived from
664
+ geonames.org, modified to reduce the size as much as possible.
665
+
666
+ =head1 METHODS
667
+
668
+ =head2 ReadDatabase
669
+
670
+ Load Geolocation database from file. This method is called automatically
671
+ when this module is loaded. By default, the database is loaded from
672
+ "Geolocation.dat" in the same directory as this module, but a different
673
+ directory may be used by setting $Image::ExifTool::Geolocation::geoDir
674
+ before loading this module. Setting this to an empty string avoids loading
675
+ any database. A warning is generated if the file can't be read.
676
+
677
+ Image::ExifTool::Geolocation::ReadDatabase($filename);
678
+
679
+ =over 4
680
+
681
+ =item Inputs:
682
+
683
+ 0) Database file name
684
+
685
+ =item Return Value:
686
+
687
+ True on success.
688
+
689
+ =back
690
+
691
+ =head2 ReadAltNames
692
+
693
+ 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.
698
+
699
+ Image::ExifTool::Geolocation::ReadAltNames();
700
+
701
+ =over 4
702
+
703
+ =item Inputs:
704
+
705
+ (none)
706
+
707
+ =item Return Value:
708
+
709
+ True on success. Resets the value of $altDir to prevent further attempts at
710
+ re-loading the same database.
711
+
712
+ =back
713
+
714
+ =head2 SortDatabase
715
+
716
+ Sort database in specified order.
717
+
718
+ Image::ExifTool::Geolocation::ReadDatabase('City');
719
+
720
+ =over 4
721
+
722
+ =item Inputs:
723
+
724
+ 0) Sort order: 'Latitude', 'City' or 'Country'
725
+
726
+ =item Return Value:
727
+
728
+ 1 on success, 0 on failure (bad sort order specified).
729
+
730
+ =back
731
+
732
+ =head2 AddEntry
733
+
734
+ Add entry to Geolocation database.
735
+
736
+ Image::ExifTool::Geolocation::AddEntry($city, $region,
737
+ $subregion, $countryCode, $country, $timezone,
738
+ $featureCode, $population, $lat, $lon, $altNames);
739
+
740
+ =over 4
741
+
742
+ =item Inputs:
743
+
744
+ 0) City name (UTF8)
745
+
746
+ 1) Region, state or province name (UTF8), or empty string if unknown
747
+
748
+ 2) Subregion name (UTF8), or empty string if unknown
749
+
750
+ 3) 2-character ISO 3166 country code
751
+
752
+ 4) Country name (UTF8), or empty string to use existing definition. If the
753
+ country name is provided for a country code that already exists in the
754
+ database, then the database entry is updated with the new country name.
755
+
756
+ 5) Time zone identifier (eg. "America/New_York")
757
+
758
+ 6) Feature code (eg. "PPL"), or empty if not known
759
+
760
+ 7) City population
761
+
762
+ 8) GPS latitude (signed floating point degrees)
763
+
764
+ 9) GPS longitude
765
+
766
+ 10) Optional comma-separated list of alternate names for the city
767
+
768
+ =item Return Value:
769
+
770
+ 1 on success, otherwise sends a warning message to stderr
771
+
772
+ =back
773
+
774
+ =head2 GetEntry
775
+
776
+ Get entry from Geolocation database.
777
+
778
+ my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang,$sort);
779
+
780
+ =over 4
781
+
782
+ =item Inputs:
783
+
784
+ 0) Entry number in database, or index into sorted database
785
+
786
+ 1) Optional language code
787
+
788
+ 2) Optional flag to treat first argument as an index into the sorted
789
+ database
790
+
791
+ item Return Values:
792
+
793
+ 0) City name, or undef if the entry didn't exist
794
+
795
+ 1) Region name, or "" if no region
796
+
797
+ 2) Subregion name, or "" if no subregion
798
+
799
+ 3) Country code
800
+
801
+ 4) Country name
802
+
803
+ 5) Time zone
804
+
805
+ 6) Feature code
806
+
807
+ 7) City population
808
+
809
+ 8) GPS latitude
810
+
811
+ 9) GPS longitude
812
+
813
+ =back
814
+
815
+ =head2 GetAltNames
816
+
817
+ Get alternate names for specified city.
818
+
819
+ my $str = Image::ExifTool::Geolocation::GetAltNames($num,$sort);
820
+
821
+ =over 4
822
+
823
+ =item Inputs:
824
+
825
+ 0) Entry number in database or index into the sorted database
826
+
827
+ 1) Optional flag to treat first argument as an index into the sorted
828
+ database
829
+
830
+ =item Return value:
831
+
832
+ Comma-separated string of alternate names for this city.
833
+
834
+ =item Notes:
835
+
836
+ Must set the $altDir package variable and call L</ReadAltNames> before
837
+ calling this routine.
838
+
839
+ =back
840
+
841
+ =head2 Geolocate
842
+
843
+ Return geolocation information for specified GPS coordinates or city name.
844
+
845
+ my @rtnInfo = Image::ExifTool::Geolocation::Geolocate($arg,$opts);
846
+
847
+ =over 4
848
+
849
+ =item Inputs:
850
+
851
+ 0) Input argument ("lat,lon", "city", "city,country", "city,region,country",
852
+ etc). When specifying a city, the city name must come first, followed by
853
+ zero or more of the following in any order, separated by commas: region
854
+ name, subregion name, country code, and/or country name. Regular
855
+ expressions in C</expr/> format are also allowed, optionally prefixed by
856
+ "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.
859
+
860
+ 1) Optional reference to hash of options:
861
+
862
+ GeolocMinPop - minimum population of cities to consider in search
863
+
864
+ GeolocMaxDist - maximum distance (km) to search for cities when an input
865
+ GPS position is used
866
+
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.
870
+
871
+ GeolocFeature - comma-separated list of feature codes to include in
872
+ search, or exclude if the list starts with a dash (-)
873
+
874
+ GeolocAltNames - flag to search alternate names database if available
875
+ for matching city name (see ALTERNATE DATABASES below)
876
+
877
+ =item Return Value:
878
+
879
+ 0) Number of matching entries, or 0 if no matches
880
+
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
884
+
885
+ 2) Distance to closest city in km if "lat,lon" specified
886
+
887
+ 3) Compass bearing for direction to closest city if "lat,lon" specified
888
+
889
+ =back
890
+
891
+ =head1 ALTERNATE DATABASES
892
+
893
+ A different version of the cities database may be specified setting the
894
+ package $geoDir variable before loading this module. This directory should
895
+ contain the Geolocation.dat file, and optionally a GeoLang directory for the
896
+ language translations. The $geoDir variable may be set to an empty string
897
+ to disable loading of a database.
898
+
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.
904
+
905
+ =head1 ADDING USER-DEFINED DATABASE ENTRIES
906
+
907
+ User-defined entries may be created by defining them using the following
908
+ technique before the Geolocation module is loaded.
909
+
910
+ @Image::ExifTool::UserDefined::Geolocation = (
911
+ # city, region, subregion, country code, country, timezone,
912
+ ['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
913
+ # feature code, population, lat, lon
914
+ '',400,42.06115,27.97833],
915
+ );
916
+
917
+ Similarly, user-defined language translations may be defined, and will
918
+ override any existing translations. Translations for the default 'en'
919
+ language may also be specified. See
920
+ L<https://exiftool.org/geolocation.html#Custom> for more information.
921
+
922
+ =head1 USING A CUSTOM DATABASE
923
+
924
+ This example shows how to use a custom database. In this example, the input
925
+ database file is a comma-separated text file with columns corresponding to
926
+ the input arguments of the AddEntry method.
927
+
928
+ $Image::ExifTool::Geolocation::geoDir = '';
929
+ require Image::ExifTool::Geolocation;
930
+ open DFILE, "< $filename";
931
+ Image::ExifTool::Geolocation::AddEntry(split /,/) foreach <DFILE>;
932
+ close DFILE;
214
933
 
215
934
  =head1 AUTHOR
216
935
 
217
936
  Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
218
937
 
219
938
  This library is free software; you can redistribute it and/or modify it
220
- under the same terms as Perl itself. Geolocation.dat is based on data
221
- from geonames.org with a Creative Commons license.
939
+ under the same terms as Perl itself. The associated database files are
940
+ based on data from geonames.org with a Creative Commons license.
222
941
 
223
942
  =head1 REFERENCES
224
943
 
@@ -226,6 +945,8 @@ from geonames.org with a Creative Commons license.
226
945
 
227
946
  =item L<https://download.geonames.org/export/>
228
947
 
948
+ =item L<https://exiftool.org/geolocation.html>
949
+
229
950
  =back
230
951
 
231
952
  =head1 SEE ALSO