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.
- package/bin/Changes +81 -0
- package/bin/MANIFEST +6 -18
- package/bin/META.json +1 -1
- package/bin/META.yml +1 -1
- package/bin/README +4 -2
- package/bin/build_geolocation +872 -0
- package/bin/config_files/example.config +2 -2
- package/bin/exiftool +61 -17
- package/bin/fmt_files/gpx.fmt +2 -1
- package/bin/fmt_files/gpx_wpt.fmt +2 -1
- package/bin/lib/Image/ExifTool/Apple.pm +51 -7
- package/bin/lib/Image/ExifTool/BuildTagLookup.pm +47 -31
- package/bin/lib/Image/ExifTool/CanonVRD.pm +19 -6
- package/bin/lib/Image/ExifTool/DJI.pm +29 -0
- package/bin/lib/Image/ExifTool/Exif.pm +19 -2
- package/bin/lib/Image/ExifTool/FujiFilm.pm +20 -7
- package/bin/lib/Image/ExifTool/GM.pm +552 -0
- package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
- package/bin/lib/Image/ExifTool/Geolocation.pm +423 -178
- package/bin/lib/Image/ExifTool/Geotag.pm +26 -13
- package/bin/lib/Image/ExifTool/M2TS.pm +32 -4
- package/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
- package/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
- package/bin/lib/Image/ExifTool/Nikon.pm +337 -27
- package/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
- package/bin/lib/Image/ExifTool/Olympus.pm +1 -0
- package/bin/lib/Image/ExifTool/OpenEXR.pm +21 -3
- package/bin/lib/Image/ExifTool/PNG.pm +3 -3
- package/bin/lib/Image/ExifTool/QuickTime.pm +45 -24
- package/bin/lib/Image/ExifTool/QuickTimeStream.pl +66 -30
- package/bin/lib/Image/ExifTool/README +2 -0
- package/bin/lib/Image/ExifTool/Sony.pm +16 -7
- package/bin/lib/Image/ExifTool/TagLookup.pm +4827 -4778
- package/bin/lib/Image/ExifTool/TagNames.pod +953 -620
- package/bin/lib/Image/ExifTool/WriteQuickTime.pl +32 -9
- package/bin/lib/Image/ExifTool/Writer.pl +169 -130
- package/bin/lib/Image/ExifTool/XMP.pm +4 -2
- package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
- package/bin/lib/Image/ExifTool.pm +106 -48
- package/bin/lib/Image/ExifTool.pod +47 -25
- package/bin/perl-Image-ExifTool.spec +1 -1
- package/bin/pp_build_exe.args +4 -4
- package/package.json +3 -3
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
#------------------------------------------------------------------------------
|
|
2
2
|
# File: Geolocation.pm
|
|
3
3
|
#
|
|
4
|
-
# Description:
|
|
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
|
|
12
|
-
# and
|
|
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
|
-
#
|
|
15
|
-
#
|
|
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
|
-
#
|
|
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 -
|
|
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.
|
|
73
|
+
$VERSION = '1.07'; # (this is the module version number, not the database version)
|
|
60
74
|
|
|
61
|
-
my $
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
if ($geoDir and -
|
|
100
|
-
$
|
|
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, "
|
|
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
|
-
|
|
127
|
-
|
|
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$
|
|
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 == $
|
|
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
|
-
@
|
|
262
|
+
@sortOrder = sort { $cityList[$a] cmp $cityList[$b] } 0..$#cityList;
|
|
194
263
|
} elsif ($field eq 'City') {
|
|
195
|
-
@
|
|
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 (
|
|
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
|
-
@
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
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, $
|
|
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
|
|
271
|
-
#
|
|
272
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[$
|
|
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 '$
|
|
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)
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
# Returns:
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
|
|
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, $
|
|
349
|
-
my ($city,
|
|
350
|
-
my ($minPop, $minDistU, $minDistC, @matchParms, @
|
|
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
|
|
482
|
+
my @fcodes = split /\s*,-?\s*/, lc $fcodes; # (allow leading dash on subsequent codes)
|
|
361
483
|
if ($neg) {
|
|
362
|
-
$
|
|
363
|
-
defined $featureCodes{
|
|
484
|
+
$fcOK{$_} = 1 foreach 0..$#featureCodes;
|
|
485
|
+
defined $featureCodes{$_} and delete $fcOK{$featureCodes{$_}} foreach @fcodes;
|
|
364
486
|
} else {
|
|
365
|
-
defined $featureCodes{
|
|
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 ($
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,$
|
|
557
|
+
my ($cd,$sn) = unpack('x5Nn', $cityList[$i]);
|
|
411
558
|
my $ct = $countryList[$cd >> 24];
|
|
412
|
-
|
|
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 $
|
|
579
|
+
next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
|
|
432
580
|
my $pc = substr($cityList[$i],6,2);
|
|
433
|
-
|
|
581
|
+
if (not defined $minPop or $pc ge $minPop) {
|
|
582
|
+
$lastFound{$i} = $pc;
|
|
583
|
+
push @lastByLat, $i if @coords == 2;
|
|
584
|
+
}
|
|
434
585
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
@coords == 2 and
|
|
438
|
-
scalar(keys %
|
|
439
|
-
@
|
|
440
|
-
|
|
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
|
|
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[
|
|
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 $
|
|
639
|
+
my $i = $sorted ? $$sorted[$n] : $n;
|
|
489
640
|
# get city latitude/longitude
|
|
490
|
-
my ($lt,$f,$ln) = unpack('nCn', $cityList[$
|
|
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[$
|
|
496
|
-
next if $
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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 -
|
|
705
|
+
Image::ExifTool::Geolocation - Determine geolocation from GPS and visa-versa
|
|
529
706
|
|
|
530
707
|
=head1 SYNOPSIS
|
|
531
708
|
|
|
532
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 @
|
|
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.
|
|
681
|
-
|
|
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
|
-
|
|
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
|
-
|
|
690
|
-
|
|
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
|
-
|
|
693
|
-
if the list starts with a dash (-).
|
|
920
|
+
See L<https://exiftool.org/geolocation.html#Read> for more details.
|
|
694
921
|
|
|
695
|
-
|
|
922
|
+
1) Optional reference to hash of options:
|
|
696
923
|
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
927
|
+
GeolocMaxDist - Maximum distance (km) to search for cities when an
|
|
928
|
+
input GPS position is used. Default infinity.
|
|
703
929
|
|
|
704
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
942
|
+
=item Return Values:
|
|
711
943
|
|
|
712
|
-
|
|
944
|
+
0) Reference to list of database entry numbers for matching cities, or undef
|
|
945
|
+
if no matches were found.
|
|
713
946
|
|
|
714
|
-
|
|
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
|
-
|
|
950
|
+
=back
|
|
717
951
|
|
|
718
|
-
|
|
952
|
+
=head1 ALTERNATE DATABASES
|
|
719
953
|
|
|
720
|
-
|
|
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
|
-
|
|
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
|
-
|
|
967
|
+
=head1 ADDING USER-DEFINED DATABASE ENTRIES
|
|
725
968
|
|
|
726
|
-
|
|
969
|
+
User-defined entries may be created by defining them using the following
|
|
970
|
+
technique before the Geolocation module is loaded.
|
|
727
971
|
|
|
728
|
-
|
|
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
|
-
|
|
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, "
|
|
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.
|
|
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
|
|