exiftool-vendored.pl 12.78.0 → 12.80.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.
- package/bin/Changes +36 -3
- package/bin/MANIFEST +25 -2
- package/bin/META.json +1 -1
- package/bin/META.yml +1 -1
- package/bin/README +2 -2
- package/bin/config_files/acdsee.config +37 -57
- package/bin/config_files/example.config +6 -0
- package/bin/exiftool +39 -19
- package/bin/lib/Image/ExifTool/FujiFilm.pm +3 -0
- package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
- package/bin/lib/Image/ExifTool/Geolocation.pm +678 -140
- package/bin/lib/Image/ExifTool/Geotag.pm +7 -0
- package/bin/lib/Image/ExifTool/JSON.pm +7 -2
- package/bin/lib/Image/ExifTool/OpenEXR.pm +17 -17
- package/bin/lib/Image/ExifTool/README +6 -5
- package/bin/lib/Image/ExifTool/TagLookup.pm +2547 -2517
- package/bin/lib/Image/ExifTool/TagNames.pod +72 -1
- package/bin/lib/Image/ExifTool/WriteQuickTime.pl +1 -0
- package/bin/lib/Image/ExifTool/WriteXMP.pl +1 -1
- package/bin/lib/Image/ExifTool/Writer.pl +50 -0
- package/bin/lib/Image/ExifTool/XMP.pm +17 -2
- package/bin/lib/Image/ExifTool/XMP2.pl +64 -0
- package/bin/lib/Image/ExifTool.pm +165 -88
- package/bin/lib/Image/ExifTool.pod +20 -7
- package/bin/perl-Image-ExifTool.spec +1 -1
- package/bin/pp_build_exe.args +4 -4
- package/package.json +2 -2
|
@@ -7,195 +7,520 @@
|
|
|
7
7
|
#
|
|
8
8
|
# References: https://download.geonames.org/export/
|
|
9
9
|
#
|
|
10
|
-
# Notes: Set $Image::ExifTool::Geolocation::
|
|
11
|
-
# default database file
|
|
10
|
+
# Notes: Set $Image::ExifTool::Geolocation::geoDir to override
|
|
11
|
+
# default directory for the database file Geolocation.dat
|
|
12
|
+
# and language directory GeoLang.
|
|
12
13
|
#
|
|
13
14
|
# Based on data from geonames.org Creative Commons databases,
|
|
14
15
|
# reformatted as follows in the Geolocation.dat file:
|
|
15
16
|
#
|
|
16
|
-
# Header:
|
|
17
|
-
#
|
|
17
|
+
# Header:
|
|
18
|
+
# "GeolocationV.VV\tNNNN\n" (V.VV=version, NNNN=num city entries)
|
|
19
|
+
# "# <comment>\n"
|
|
18
20
|
# NNNN City entries:
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
21
|
+
# Offset Format Description
|
|
22
|
+
# 0 int16u - latitude high 16 bits (converted to 0-0x100000 range)
|
|
23
|
+
# 2 int8u - latitude low 4 bits, longitude low 4 bits
|
|
24
|
+
# 3 int16u - longitude high 16 bits
|
|
25
|
+
# 5 int8u - index of country in country list
|
|
26
|
+
# 6 int8u - 0xf0 = population E exponent (in format "N.Fe+0E"), 0x0f = population N digit
|
|
27
|
+
# 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
|
|
29
|
+
# 11 int8u - low byte of time zone index
|
|
30
|
+
# 12 int8u - 0x0f - feature code index (see below)
|
|
31
|
+
# 13 string - UTF8 City name, terminated by newline
|
|
32
|
+
# "\0\0\0\0\x01"
|
|
26
33
|
# Country entries:
|
|
27
34
|
# 1. 2-character country code
|
|
28
35
|
# 2. Country name, terminated by newline
|
|
29
|
-
#
|
|
36
|
+
# "\0\0\0\0\x02"
|
|
30
37
|
# Region entries:
|
|
31
|
-
# 1.
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
38
|
+
# 1. Region name, terminated by newline
|
|
39
|
+
# "\0\0\0\0\x03"
|
|
40
|
+
# Subregion entries:
|
|
41
|
+
# 1. Subregion name, terminated by newline
|
|
42
|
+
# "\0\0\0\0\x04"
|
|
35
43
|
# Time zone entries:
|
|
36
44
|
# 1. Time zone name, terminated by newline
|
|
37
|
-
#
|
|
45
|
+
# "\0\0\0\0\0"
|
|
46
|
+
#
|
|
47
|
+
# Feature Codes: (see http://www.geonames.org/export/codes.html#P for descriptions)
|
|
48
|
+
#
|
|
49
|
+
# 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR
|
|
50
|
+
# 1. PPL 4. PPLA3 7. PPLC 10. PPLG 13. PPLS
|
|
51
|
+
# 2. PPLA 5. PPLA4 8. PPLCH 11. PPLL 14. STLMT
|
|
38
52
|
#------------------------------------------------------------------------------
|
|
39
53
|
|
|
40
54
|
package Image::ExifTool::Geolocation;
|
|
41
55
|
|
|
42
56
|
use strict;
|
|
43
|
-
use vars qw($VERSION $
|
|
57
|
+
use vars qw($VERSION $geoDir $dbInfo);
|
|
58
|
+
|
|
59
|
+
$VERSION = '1.02';
|
|
60
|
+
|
|
61
|
+
my $databaseVersion = '1.02';
|
|
44
62
|
|
|
45
|
-
$
|
|
63
|
+
sub ReadDatabase($);
|
|
64
|
+
sub SortDatabase($);
|
|
65
|
+
sub AddEntry(@);
|
|
66
|
+
sub GetEntry($;$);
|
|
67
|
+
sub Geolocate($;$$$$$);
|
|
46
68
|
|
|
47
|
-
my (@
|
|
69
|
+
my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
|
|
70
|
+
my (%countryNum, %regionNum, %subregionNum, %timezoneNum, %langLookup);
|
|
71
|
+
my $sortedBy = 'Latitude';
|
|
72
|
+
my $pi = 3.1415926536;
|
|
73
|
+
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;
|
|
48
79
|
|
|
49
80
|
# get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
|
|
50
|
-
# or according to $Image::ExifTool::Geolocation::
|
|
51
|
-
my $
|
|
52
|
-
|
|
53
|
-
$
|
|
54
|
-
|
|
55
|
-
$
|
|
81
|
+
# or according to $Image::ExifTool::Geolocation::directory if specified
|
|
82
|
+
my $defaultDir = $INC{'Image/ExifTool/Geolocation.pm'};
|
|
83
|
+
if ($defaultDir) {
|
|
84
|
+
$defaultDir =~ s(/Geolocation\.pm$)();
|
|
85
|
+
} else {
|
|
86
|
+
$defaultDir = '.';
|
|
87
|
+
warn("Error getting Geolocation.pm directory\n");
|
|
56
88
|
}
|
|
57
89
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
warn("Bad format Geolocation database\n");
|
|
64
|
-
close(DATFILE);
|
|
65
|
-
return 0;
|
|
90
|
+
# read the Geolocation database unless $geoDir set to empty string
|
|
91
|
+
unless (defined $geoDir and not $geoDir) {
|
|
92
|
+
unless ($geoDir and ReadDatabase("$geoDir/Geolocation.dat")) {
|
|
93
|
+
ReadDatabase("$defaultDir/Geolocation.dat");
|
|
94
|
+
}
|
|
66
95
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
$
|
|
72
|
-
|
|
73
|
-
$
|
|
74
|
-
chomp $line;
|
|
75
|
-
push @cityLookup, $line;
|
|
96
|
+
|
|
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";
|
|
76
103
|
}
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
82
|
-
chomp $line;
|
|
83
|
-
$countryLookup{substr($line,0,2)} = substr($line,2);
|
|
104
|
+
|
|
105
|
+
# add user-defined entries to the database
|
|
106
|
+
if (@Image::ExifTool::UserDefined::Geolocation) {
|
|
107
|
+
AddEntry(@$_) foreach @Image::ExifTool::UserDefined::Geolocation;
|
|
84
108
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
|
|
110
|
+
#------------------------------------------------------------------------------
|
|
111
|
+
# Read Geolocation database
|
|
112
|
+
# Inputs: 0) database file name
|
|
113
|
+
# Returns: true on success
|
|
114
|
+
sub ReadDatabase($)
|
|
115
|
+
{
|
|
116
|
+
my $datfile = shift;
|
|
117
|
+
# open geolocation database and verify header
|
|
118
|
+
open DATFILE, "<$datfile" or warn("Error reading $datfile\n"), return 0;
|
|
119
|
+
binmode DATFILE;
|
|
120
|
+
my $line = <DATFILE>;
|
|
121
|
+
unless ($line =~ /^Geolocation(\d+\.\d+)\t(\d+)/) {
|
|
122
|
+
warn("Bad format Geolocation database\n");
|
|
123
|
+
close(DATFILE);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
if ($1 != $databaseVersion) {
|
|
127
|
+
my $which = $1 < $databaseVersion ? 'database' : 'ExifTool';
|
|
128
|
+
warn("Incompatible Geolocation database (update your $which)\n");
|
|
129
|
+
close(DATFILE);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
my $ncity = $2;
|
|
133
|
+
my $comment = <DATFILE>;
|
|
134
|
+
defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
|
|
135
|
+
$dbInfo = "$datfile v$databaseVersion: $ncity cities with population > $1";
|
|
136
|
+
my $isUserDefined = @Image::ExifTool::UserDefined::Geolocation;
|
|
137
|
+
|
|
138
|
+
# read city database
|
|
139
|
+
undef @cityList;
|
|
140
|
+
for (;;) {
|
|
141
|
+
$line = <DATFILE>;
|
|
142
|
+
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
143
|
+
$line .= <DATFILE> while length($line) < 14;
|
|
144
|
+
chomp $line;
|
|
145
|
+
push @cityList, $line;
|
|
146
|
+
}
|
|
147
|
+
@cityList == $ncity or warn("Bad number of entries in Geolocation database\n"), return 0;
|
|
148
|
+
# read countries
|
|
149
|
+
for (;;) {
|
|
150
|
+
$line = <DATFILE>;
|
|
151
|
+
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
152
|
+
chomp $line;
|
|
153
|
+
push @countryList, $line;
|
|
154
|
+
$countryNum{lc substr($line,0,2)} = $#countryList if $isUserDefined;
|
|
155
|
+
}
|
|
156
|
+
# read regions
|
|
157
|
+
for (;;) {
|
|
158
|
+
$line = <DATFILE>;
|
|
159
|
+
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
160
|
+
chomp $line;
|
|
161
|
+
push @regionList, $line;
|
|
162
|
+
$regionNum{lc $line} = $#regionList if $isUserDefined;
|
|
163
|
+
}
|
|
164
|
+
# read subregions
|
|
165
|
+
for (;;) {
|
|
166
|
+
$line = <DATFILE>;
|
|
167
|
+
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
168
|
+
chomp $line;
|
|
169
|
+
push @subregionList, $line;
|
|
170
|
+
$subregionNum{lc $line} = $#subregionList if $isUserDefined;
|
|
171
|
+
}
|
|
172
|
+
# read time zones
|
|
173
|
+
for (;;) {
|
|
174
|
+
$line = <DATFILE>;
|
|
175
|
+
last if length($line) == 6 and $line =~ /\0\0\0\0/;
|
|
176
|
+
chomp $line;
|
|
177
|
+
push @timezoneList, $line;
|
|
178
|
+
$timezoneNum{lc $line} = $#timezoneList if $isUserDefined;
|
|
179
|
+
}
|
|
180
|
+
close DATFILE;
|
|
181
|
+
return 1;
|
|
92
182
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
183
|
+
|
|
184
|
+
#------------------------------------------------------------------------------
|
|
185
|
+
# Sort database by specified field
|
|
186
|
+
# Inputs: 0) Field name to sort (Latitude,City,Country)
|
|
187
|
+
# Returns: 1 on success
|
|
188
|
+
sub SortDatabase($)
|
|
189
|
+
{
|
|
190
|
+
my $field = shift;
|
|
191
|
+
return 1 if $field eq $sortedBy; # already sorted?
|
|
192
|
+
if ($field eq 'Latitude') {
|
|
193
|
+
@cityList = sort { $a cmp $b } @cityList;
|
|
194
|
+
} elsif ($field eq 'City') {
|
|
195
|
+
@cityList = sort { substr($a,13) cmp substr($b,13) } @cityList;
|
|
196
|
+
} elsif ($field eq 'Country') {
|
|
197
|
+
my %lkup;
|
|
198
|
+
foreach (@cityList) {
|
|
199
|
+
my $city = substr($_,13);
|
|
200
|
+
my $ctry = substr($countryList[ord substr($_,5,1)], 2);
|
|
201
|
+
$lkup{$_} = "$ctry $city";
|
|
202
|
+
}
|
|
203
|
+
@cityList = sort { $lkup{$a} cmp $lkup{$b} } @cityList;
|
|
204
|
+
} else {
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
$sortedBy = $field;
|
|
208
|
+
return 1;
|
|
99
209
|
}
|
|
100
|
-
close DATFILE;
|
|
101
210
|
|
|
102
211
|
#------------------------------------------------------------------------------
|
|
103
|
-
#
|
|
104
|
-
# Inputs: 0)
|
|
105
|
-
#
|
|
106
|
-
|
|
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($$;$$)
|
|
212
|
+
# Add cities to the Geolocation database
|
|
213
|
+
# Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon
|
|
214
|
+
# eg. AddEntry('Sinemorets','Burgas','Obshtina Tsarevo','BG','Bulgaria','Europe/Sofia','',400,42.06115,27.97833)
|
|
215
|
+
sub AddEntry(@)
|
|
113
216
|
{
|
|
114
|
-
my ($
|
|
115
|
-
|
|
116
|
-
|
|
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;
|
|
222
|
+
}
|
|
223
|
+
$fc = $featureCodes{lc $fc} || 0;
|
|
224
|
+
chomp $lon; # (just in case it was read from file)
|
|
225
|
+
# create reverse lookups for country/region/subregion/timezone if not done already
|
|
226
|
+
# (eg. if the entries are being added manually instead of via UserDefined::Geolocation)
|
|
227
|
+
unless (%countryNum) {
|
|
228
|
+
my $i;
|
|
229
|
+
$i = 0; $countryNum{lc substr($_,0,2)} = $i++ foreach @countryList;
|
|
230
|
+
$i = 0; $regionNum{lc $_} = $i++ foreach @regionList;
|
|
231
|
+
$i = 0; $subregionNum{lc $_} = $i++ foreach @subregionList;
|
|
232
|
+
$i = 0; $timezoneNum{lc $_} = $i++ foreach @timezoneList;
|
|
233
|
+
}
|
|
234
|
+
my $cn = $countryNum{lc $cc};
|
|
235
|
+
unless (defined $cn) {
|
|
236
|
+
push @countryList, "$cc$country";
|
|
237
|
+
$cn = $countryNum{lc $cc} = $#countryList;
|
|
238
|
+
} elsif ($country) {
|
|
239
|
+
$countryList[$cn] = "$cc$country"; # (override existing country name)
|
|
240
|
+
}
|
|
241
|
+
my $tn = $timezoneNum{lc $timezone};
|
|
242
|
+
unless (defined $tn) {
|
|
243
|
+
push @timezoneList, $timezone;
|
|
244
|
+
$tn = $timezoneNum{lc $timezone} = $#timezoneList;
|
|
245
|
+
}
|
|
246
|
+
my $rn = $regionNum{lc $region};
|
|
247
|
+
unless (defined $rn) {
|
|
248
|
+
push @regionList, $region;
|
|
249
|
+
$rn = $regionNum{lc $region} = $#regionList;
|
|
250
|
+
}
|
|
251
|
+
my $sn = $subregionNum{lc $subregion};
|
|
252
|
+
unless (defined $sn) {
|
|
253
|
+
push @subregionList, $subregion;
|
|
254
|
+
$sn = $subregionNum{lc $subregion} = $#subregionList;
|
|
255
|
+
}
|
|
256
|
+
$pop = sprintf('%.1e',$pop); # format: "3.1e+04" or "3.1e+004"
|
|
257
|
+
# pack CC index, population and region index into a 32-bit integer
|
|
258
|
+
my $code = ($cn << 24) | (substr($pop,-1,1)<<20) | (substr($pop,0,1)<<16) | (substr($pop,2,1)<<12) | $rn;
|
|
259
|
+
# store high bit of timezone index
|
|
260
|
+
$tn > 255 and $sn |= 0x8000, $tn -= 256;
|
|
261
|
+
$lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
|
|
262
|
+
$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);
|
|
264
|
+
push @cityList, "$hdr$city";
|
|
265
|
+
$sortedBy = '';
|
|
266
|
+
}
|
|
117
267
|
|
|
268
|
+
#------------------------------------------------------------------------------
|
|
269
|
+
# 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($;$)
|
|
273
|
+
{
|
|
274
|
+
my ($entryNum, $lang) = @_;
|
|
275
|
+
return() if $entryNum > $#cityList;
|
|
276
|
+
my ($lt,$f,$ln,$code,$sb,$tn,$fc) = unpack('nCnNnCC', $cityList[$entryNum]);
|
|
277
|
+
my $city = substr($cityList[$entryNum],13);
|
|
278
|
+
my $ctry = $countryList[$code >> 24];
|
|
279
|
+
my $rgn = $regionList[$code & 0x0fff];
|
|
280
|
+
my $sub = $subregionList[$sb & 0x7fff];
|
|
281
|
+
# convert population digits back into exponent format
|
|
282
|
+
my $pop = (($code>>16 & 0x0f) . '.' . ($code>>12 & 0x0f) . 'e+' . ($code>>20 & 0x0f)) + 0;
|
|
283
|
+
$tn += 256 if $sb & 0x8000;
|
|
284
|
+
$lt = sprintf('%.4f', (($lt<<4)|($f >> 4)) * 180 / 0x100000 - 90);
|
|
285
|
+
$ln = sprintf('%.4f', (($ln<<4)|($f & 0x0f))* 360 / 0x100000 - 180);
|
|
286
|
+
$fc = $featureCodes[$fc & 0x0f];
|
|
287
|
+
my $cc = substr($ctry, 0, 2);
|
|
288
|
+
my $country = substr($ctry, 2);
|
|
289
|
+
if ($lang) {
|
|
290
|
+
my $xlat = $langLookup{$lang};
|
|
291
|
+
# load language lookups if not done already
|
|
292
|
+
if (not defined $xlat) {
|
|
293
|
+
if (eval "require '$geoLang/$lang.pm'") {
|
|
294
|
+
my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
|
|
295
|
+
no strict 'refs';
|
|
296
|
+
$xlat = \%$trans if %$trans;
|
|
297
|
+
}
|
|
298
|
+
# read user-defined language translations
|
|
299
|
+
if (%Image::ExifTool::Geolocation::geoLang) {
|
|
300
|
+
my $userLang = $Image::ExifTool::Geolocation::geoLang{$lang};
|
|
301
|
+
if ($userLang and ref($userLang) eq 'HASH') {
|
|
302
|
+
if ($xlat) {
|
|
303
|
+
# add user-defined entries to main lookup
|
|
304
|
+
$$xlat{$_} = $$userLang{$_} foreach keys %$userLang;
|
|
305
|
+
} else {
|
|
306
|
+
$xlat = $userLang;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
$langLookup{$lang} = $xlat || 0;
|
|
311
|
+
}
|
|
312
|
+
if ($xlat) {
|
|
313
|
+
my $r2 = $rgn;
|
|
314
|
+
# City-specific: "CCRgn,Sub,City", "CCRgn,City", "CC,City", ",City"
|
|
315
|
+
# Subregion-specific: "CCRgn,Sub,"
|
|
316
|
+
# Region-specific: "CCRgn,"
|
|
317
|
+
# Country-specific: "CC,"
|
|
318
|
+
$city = $$xlat{"$cc$r2,$sub,$city"} || $$xlat{"$cc$r2,$city"} ||
|
|
319
|
+
$$xlat{"$cc,$city"} || $$xlat{",$city"} || $$xlat{$city} || $city;
|
|
320
|
+
$sub = $$xlat{"$cc$rgn,$sub,"} || $$xlat{$sub} || $sub;
|
|
321
|
+
$rgn = $$xlat{"$cc$rgn,"} || $$xlat{$rgn} || $rgn;
|
|
322
|
+
$country = $$xlat{"$cc,"} || $$xlat{$country} || $country;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#------------------------------------------------------------------------------
|
|
329
|
+
# Look up lat,lon or city in geolocation database
|
|
330
|
+
# 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($;$$$$$)
|
|
347
|
+
{
|
|
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);
|
|
351
|
+
|
|
352
|
+
@cityList or warn('No Geolocation database'), return();
|
|
353
|
+
# make population code for comparing with 2 bytes at offset 6 in database
|
|
118
354
|
if ($pop) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
355
|
+
$pop = sprintf('%.1e', $pop);
|
|
356
|
+
$minPop = chr((substr($pop,-1,1)<<4) | (substr($pop,0,1))) . chr(substr($pop,2,1)<<4);
|
|
357
|
+
}
|
|
358
|
+
if ($fcodes) {
|
|
359
|
+
my $neg = $fcodes =~ s/^-//;
|
|
360
|
+
my @fcodes = split /\s*,\s*/, $fcodes;
|
|
361
|
+
if ($neg) {
|
|
362
|
+
$fcmask = 0xffff;
|
|
363
|
+
defined $featureCodes{lc $_} and $fcmask &= ~((1 << $featureCodes{lc $_})) foreach @fcodes;
|
|
364
|
+
} else {
|
|
365
|
+
defined $featureCodes{lc $_} and $fcmask |= (1 << $featureCodes{lc $_}) foreach @fcodes;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
#
|
|
369
|
+
# process input argument
|
|
370
|
+
#
|
|
371
|
+
$arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
|
|
372
|
+
my @args = split /\s*,\s*/, $arg;
|
|
373
|
+
my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
|
|
374
|
+
foreach (@args) {
|
|
375
|
+
# allow regular expressions optionally prefixed by "ci", "cc", "co", "re" or "sr"
|
|
376
|
+
if (m{^(-)?(\w{2})?/(.*)/(i?)$}) {
|
|
377
|
+
my $re = $4 ? qr/$3/im : qr/$3/m;
|
|
378
|
+
next if not defined($idx = $ri{$2});
|
|
379
|
+
$other = 1 if $idx < 5;
|
|
380
|
+
$idx += 10 if $1; # add 10 for negative matches
|
|
381
|
+
push @{$regex{$idx}}, $re;
|
|
382
|
+
$city = '' unless defined $city;
|
|
383
|
+
} elsif (/^[-+]?\d+(\.\d+)?$/) { # coordinate format
|
|
384
|
+
push @coords, $_ if @coords < 2;
|
|
385
|
+
} elsif (lc eq 'both') {
|
|
386
|
+
$both = 1;
|
|
387
|
+
} elsif ($city) {
|
|
388
|
+
push @exact, lc $_;
|
|
389
|
+
} else {
|
|
390
|
+
$city = lc $_;
|
|
125
391
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
392
|
+
}
|
|
393
|
+
unless (defined $city or @coords == 2) {
|
|
394
|
+
warn("Insufficient information to determine geolocation\n");
|
|
395
|
+
return();
|
|
396
|
+
}
|
|
397
|
+
# sort database by logitude if finding entry based on coordinates
|
|
398
|
+
SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
|
|
399
|
+
#
|
|
400
|
+
# perform reverse Geolocation lookup to determine GPS based on city, country, etc.
|
|
401
|
+
#
|
|
402
|
+
while (defined $city and (@coords != 2 or $both)) {
|
|
403
|
+
Entry: for ($i=0; $i<@cityList; ++$i) {
|
|
404
|
+
my $cty = substr($cityList[$i],13);
|
|
405
|
+
next if $city and $city ne lc $cty; # test exact city name first
|
|
406
|
+
# test with city-specific regexes
|
|
407
|
+
if ($regex{8}) { $cty =~ $_ or next Entry foreach @{$regex{8}} }
|
|
408
|
+
if ($regex{18}) { $cty !~ $_ or next Entry foreach @{$regex{18}} }
|
|
409
|
+
# test other arguments
|
|
410
|
+
my ($cd,$sb) = unpack('x5Nn', $cityList[$i]);
|
|
411
|
+
my $ct = $countryList[$cd >> 24];
|
|
412
|
+
my @geo = (substr($ct,0,2), substr($ct,2), $regionList[$cd & 0x0fff], $subregionList[$sb & 0x7fff]);
|
|
413
|
+
if (@exact) {
|
|
414
|
+
# make quick lookup for all names at this location
|
|
415
|
+
my %geoLkup;
|
|
416
|
+
$_ and $geoLkup{lc $_} = 1 foreach @geo;
|
|
417
|
+
$geoLkup{$_} or next Entry foreach @exact;
|
|
418
|
+
}
|
|
419
|
+
# test with cc, co, re and sr regexes
|
|
420
|
+
if ($other) { foreach $idx (0..3) {
|
|
421
|
+
if ($regex{$idx}) { $geo[$idx] =~ $_ or next Entry foreach @{$regex{$idx}} }
|
|
422
|
+
if ($regex{$idx+10}) { $geo[$idx] !~ $_ or next Entry foreach @{$regex{$idx+10}} }
|
|
423
|
+
} }
|
|
424
|
+
# test regexes for any place name
|
|
425
|
+
if ($regex{9} or $regex{19}) {
|
|
426
|
+
my $str = join "\n", $cty, @geo;
|
|
427
|
+
$str =~ $_ or next Entry foreach @{$regex{9}};
|
|
428
|
+
$str !~ $_ or next Entry foreach @{$regex{19}};
|
|
429
|
+
}
|
|
430
|
+
# test feature code and population
|
|
431
|
+
next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
|
|
432
|
+
my $pc = substr($cityList[$i],6,2);
|
|
433
|
+
$found{$i} = $pc if not defined $minPop or $pc ge $minPop;
|
|
434
|
+
}
|
|
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;
|
|
445
|
+
}
|
|
446
|
+
return \@multiCity;
|
|
447
|
+
}
|
|
448
|
+
warn "No such city in Geolocation database\n";
|
|
449
|
+
return();
|
|
450
|
+
}
|
|
451
|
+
#
|
|
452
|
+
# determine Geolocation based on GPS coordinates
|
|
453
|
+
#
|
|
454
|
+
my ($lat, $lon) = @coords;
|
|
455
|
+
if ($maxDist) {
|
|
456
|
+
$minDistU = $maxDist / (2 * $earthRadius); # min distance on unit sphere
|
|
457
|
+
$minDistC = $maxDist * 0x100000 / ($pi * $earthRadius); # min distance in coordinate units
|
|
458
|
+
} else {
|
|
459
|
+
$minDistU = $pi;
|
|
460
|
+
$minDistC = 0x200000;
|
|
461
|
+
}
|
|
462
|
+
my $cos = cos($lat * $pi / 180); # cosine factor for longitude distances
|
|
463
|
+
# reduce lat/lon to the range 0-0x100000
|
|
464
|
+
$lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
|
|
465
|
+
$lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
|
|
466
|
+
$lat or $lat = $coords[0] < 0 ? 1 : 0xfffff; # (zero latitude is a problem for our calculations)
|
|
467
|
+
my $coord = pack('nCn',$lat>>4,(($lat&0x0f)<<4)|($lon&0x0f),$lon>>4);;
|
|
138
468
|
# binary search to find closest longitude
|
|
139
|
-
my
|
|
469
|
+
my $numEntries = @matches || @cityList;
|
|
470
|
+
my ($n0, $n1) = (0, $numEntries - 1);
|
|
140
471
|
while ($n1 - $n0 > 1) {
|
|
141
472
|
my $n = int(($n0 + $n1) / 2);
|
|
142
|
-
if ($coord lt $
|
|
473
|
+
if ($coord lt $cityList[@matches ? $matches[$n] : $n]) {
|
|
143
474
|
$n1 = $n;
|
|
144
475
|
} else {
|
|
145
476
|
$n0 = $n;
|
|
146
477
|
}
|
|
147
478
|
}
|
|
148
479
|
# step backward then forward through database to find nearest city
|
|
149
|
-
my ($minDist2, $minN, @dxy);
|
|
150
480
|
my ($inc, $end, $n) = (-1, -1, $n0+1);
|
|
481
|
+
my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
|
|
482
|
+
my $cp0 = cos($p0);
|
|
151
483
|
for (;;) {
|
|
152
484
|
if (($n += $inc) == $end) {
|
|
153
485
|
last if $inc == 1;
|
|
154
|
-
($inc, $end, $n) = (1,
|
|
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
|
|
486
|
+
($inc, $end, $n) = (1, $numEntries, $n1);
|
|
169
487
|
}
|
|
488
|
+
my $entryNum = @matches ? $matches[$n] : $n;
|
|
489
|
+
# get city latitude/longitude
|
|
490
|
+
my ($lt,$f,$ln) = unpack('nCn', $cityList[$entryNum]);
|
|
491
|
+
$lt = ($lt << 4) | ($f >> 4);
|
|
492
|
+
# searched far enough if latitude alone is further than best distance
|
|
493
|
+
abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
|
|
170
494
|
# ignore if population is below threshold
|
|
171
|
-
next if $minPop and $minPop
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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));
|
|
497
|
+
$ln = ($ln << 4) | ($f & 0x0f);
|
|
498
|
+
# calculate great circle distance to this city on unit sphere
|
|
499
|
+
my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
|
|
500
|
+
my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
|
|
501
|
+
my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
|
|
502
|
+
my $distU = atan2(sqrt($a), sqrt(1-$a));
|
|
503
|
+
next if $distU > $minDistU;
|
|
504
|
+
$minDistU = $distU;
|
|
505
|
+
$minDistC = $minDistU * 0x200000 / $pi;
|
|
506
|
+
@matchParms = ($entryNum, $p1, $t1, $distU);
|
|
177
507
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
my ($
|
|
182
|
-
my $
|
|
183
|
-
my $
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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);
|
|
508
|
+
@matchParms or warn("No suitable location in Geolocation database\n"), return();
|
|
509
|
+
|
|
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
|
|
515
|
+
|
|
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;
|
|
197
520
|
}
|
|
198
521
|
|
|
522
|
+
1; #end
|
|
523
|
+
|
|
199
524
|
__END__
|
|
200
525
|
|
|
201
526
|
=head1 NAME
|
|
@@ -209,8 +534,219 @@ This module is used by the Image::ExifTool Geolocation feature.
|
|
|
209
534
|
=head1 DESCRIPTION
|
|
210
535
|
|
|
211
536
|
This module contains the code to convert GPS coordinates to city, region,
|
|
212
|
-
country, time zone, etc. It uses a database derived from
|
|
213
|
-
modified to reduce the size as much as possible.
|
|
537
|
+
subregion, country, time zone, etc. It uses a database derived from
|
|
538
|
+
geonames.org, modified to reduce the size as much as possible.
|
|
539
|
+
|
|
540
|
+
=head1 METHODS
|
|
541
|
+
|
|
542
|
+
=head2 ReadDatabase
|
|
543
|
+
|
|
544
|
+
Load Geolocation database from file. This method is called automatically
|
|
545
|
+
when this module is loaded. By default, the database is loaded from
|
|
546
|
+
"Geolocation.dat" in the same directory as this module, but a different
|
|
547
|
+
directory may be used by setting $Image::ExifTool::Geolocation::geoDir
|
|
548
|
+
before loading this module. Setting this to an empty string avoids loading
|
|
549
|
+
any database. A warning is generated if the file can't be read.
|
|
550
|
+
|
|
551
|
+
Image::ExifTool::Geolocation::ReadDatabase($filename);
|
|
552
|
+
|
|
553
|
+
=over 4
|
|
554
|
+
|
|
555
|
+
=item Inputs:
|
|
556
|
+
|
|
557
|
+
0) Database file name
|
|
558
|
+
|
|
559
|
+
=item Return Value:
|
|
560
|
+
|
|
561
|
+
True on success.
|
|
562
|
+
|
|
563
|
+
=back
|
|
564
|
+
|
|
565
|
+
=head2 SortDatabase
|
|
566
|
+
|
|
567
|
+
Sort database in specified order.
|
|
568
|
+
|
|
569
|
+
Image::ExifTool::Geolocation::ReadDatabase('City');
|
|
570
|
+
|
|
571
|
+
=over 4
|
|
572
|
+
|
|
573
|
+
=item Inputs:
|
|
574
|
+
|
|
575
|
+
0) Sort order: 'Latitude', 'City' or 'Country'
|
|
576
|
+
|
|
577
|
+
=item Return Value:
|
|
578
|
+
|
|
579
|
+
1 on success, 0 on failure (bad sort order specified).
|
|
580
|
+
|
|
581
|
+
=back
|
|
582
|
+
|
|
583
|
+
=head2 AddEntry
|
|
584
|
+
|
|
585
|
+
Add entry to Geolocation database.
|
|
586
|
+
|
|
587
|
+
Image::ExifTool::Geolocation::AddEntry($city, $region,
|
|
588
|
+
$countryCode, $country, $timezone, $featureCode
|
|
589
|
+
$population, $lat, $lon);
|
|
590
|
+
|
|
591
|
+
=over 4
|
|
592
|
+
|
|
593
|
+
=item Inputs:
|
|
594
|
+
|
|
595
|
+
0) City name (UTF8)
|
|
596
|
+
|
|
597
|
+
1) Region, state or province name (UTF8), or empty string if unknown
|
|
598
|
+
|
|
599
|
+
2) Subregion name (UTF8), or empty string if unknown
|
|
600
|
+
|
|
601
|
+
3) 2-character ISO 3166 country code
|
|
602
|
+
|
|
603
|
+
4) Country name (UTF8), or empty string to use existing definition. If the
|
|
604
|
+
country name is provided for a country code that already exists in the
|
|
605
|
+
database, then the database entry is updated with the new country name.
|
|
606
|
+
|
|
607
|
+
5) Time zone identifier (eg. "America/New_York")
|
|
608
|
+
|
|
609
|
+
6) Feature code (eg. "PPL"), or empty if not known
|
|
610
|
+
|
|
611
|
+
7) City population
|
|
612
|
+
|
|
613
|
+
8) GPS latitude (signed floating point degrees)
|
|
614
|
+
|
|
615
|
+
9) GPS longitude
|
|
616
|
+
|
|
617
|
+
=back
|
|
618
|
+
|
|
619
|
+
=head2 GetEntry
|
|
620
|
+
|
|
621
|
+
Get entry from Geolocation database.
|
|
622
|
+
|
|
623
|
+
my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang);
|
|
624
|
+
|
|
625
|
+
=over 4
|
|
626
|
+
|
|
627
|
+
=item Inputs:
|
|
628
|
+
|
|
629
|
+
0) Entry number in database
|
|
630
|
+
|
|
631
|
+
1) Optional language code
|
|
632
|
+
|
|
633
|
+
=item Return Values:
|
|
634
|
+
|
|
635
|
+
0) City name, or undef if the entry didn't exist
|
|
636
|
+
|
|
637
|
+
1) Region name, or "" if no region
|
|
638
|
+
|
|
639
|
+
2) Subregion name, or "" if no subregion
|
|
640
|
+
|
|
641
|
+
3) Country code
|
|
642
|
+
|
|
643
|
+
4) Country name
|
|
644
|
+
|
|
645
|
+
5) Time zone
|
|
646
|
+
|
|
647
|
+
6) Feature code
|
|
648
|
+
|
|
649
|
+
7) City population
|
|
650
|
+
|
|
651
|
+
8) GPS latitude
|
|
652
|
+
|
|
653
|
+
9) GPS longitude
|
|
654
|
+
|
|
655
|
+
=item Notes:
|
|
656
|
+
|
|
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.
|
|
660
|
+
|
|
661
|
+
=back
|
|
662
|
+
|
|
663
|
+
=head2 Geolocate
|
|
664
|
+
|
|
665
|
+
Return geolocation information for specified GPS coordinates or city name.
|
|
666
|
+
|
|
667
|
+
my @cityInfo =
|
|
668
|
+
Image::ExifTool::Geolocation::Geolocate($arg,$pop,$dist,$lang,$multi,$codes);
|
|
669
|
+
|
|
670
|
+
=over 4
|
|
671
|
+
|
|
672
|
+
=item Inputs:
|
|
673
|
+
|
|
674
|
+
0) Input argument ("lat,lon", "city", "city,country", "city,region,country",
|
|
675
|
+
etc). When specifying a city, the city name must come first, followed by
|
|
676
|
+
zero or more of the following in any order, separated by commas: region
|
|
677
|
+
name, subregion name, country code, and/or country name. Regular
|
|
678
|
+
expressions in C</expr/> format are also allowed, optionally prefixed by
|
|
679
|
+
"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)
|
|
686
|
+
|
|
687
|
+
3) Language code
|
|
688
|
+
|
|
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.
|
|
691
|
+
|
|
692
|
+
5) Comma-separated list of feature codes to include in search, or to exclude
|
|
693
|
+
if the list starts with a dash (-).
|
|
694
|
+
|
|
695
|
+
=item Return Value:
|
|
696
|
+
|
|
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.
|
|
701
|
+
|
|
702
|
+
The city information list contains the following entries:
|
|
703
|
+
|
|
704
|
+
0) Name of matching city (UTF8), or undef if no match
|
|
705
|
+
|
|
706
|
+
1) Region, state or province name (UTF8), or "" if no region
|
|
707
|
+
|
|
708
|
+
2) Subregion name (UTF8), or "" if no region
|
|
709
|
+
|
|
710
|
+
3) Country code
|
|
711
|
+
|
|
712
|
+
4) Country name (UTF8)
|
|
713
|
+
|
|
714
|
+
5) Standard time zone identifier name
|
|
715
|
+
|
|
716
|
+
6) Feature code
|
|
717
|
+
|
|
718
|
+
7) City population rounded to 2 significant figures
|
|
719
|
+
|
|
720
|
+
8) Approximate city latitude (signed degrees)
|
|
721
|
+
|
|
722
|
+
9) Approximate city longitude
|
|
723
|
+
|
|
724
|
+
10) Distance to city in km if "lat,lon" specified
|
|
725
|
+
|
|
726
|
+
11) Compass bearing for direction to city if "lat,lon" specified
|
|
727
|
+
|
|
728
|
+
12) Flag set if multiple matches were found
|
|
729
|
+
|
|
730
|
+
=back
|
|
731
|
+
|
|
732
|
+
=head1 USING A CUSTOM DATABASE
|
|
733
|
+
|
|
734
|
+
This example shows how to use a custom database. In this example, the input
|
|
735
|
+
database file is a comma-separated text file with columns corresponding to
|
|
736
|
+
the input arguments of the AddEntry method.
|
|
737
|
+
|
|
738
|
+
$Image::ExifTool::Geolocation::geoDir = '';
|
|
739
|
+
require Image::ExifTool::Geolocation;
|
|
740
|
+
open DFILE, "<$filename";
|
|
741
|
+
Image::ExifTool::Geolocation::AddEntry(split /,/) foreach <DFILE>;
|
|
742
|
+
close DFILE;
|
|
743
|
+
|
|
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.
|
|
214
750
|
|
|
215
751
|
=head1 AUTHOR
|
|
216
752
|
|
|
@@ -226,6 +762,8 @@ from geonames.org with a Creative Commons license.
|
|
|
226
762
|
|
|
227
763
|
=item L<https://download.geonames.org/export/>
|
|
228
764
|
|
|
765
|
+
=item L<https://exiftool.org/geolocation.html>
|
|
766
|
+
|
|
229
767
|
=back
|
|
230
768
|
|
|
231
769
|
=head1 SEE ALSO
|