exiftool_vendored 12.80.0 → 12.81.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.
- checksums.yaml +4 -4
- data/bin/Changes +26 -0
- data/bin/MANIFEST +2 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +3 -2
- data/bin/exiftool +25 -9
- data/bin/lib/Image/ExifTool/CanonVRD.pm +2 -2
- data/bin/lib/Image/ExifTool/FujiFilm.pm +13 -7
- data/bin/lib/Image/ExifTool/Geolocation.pm +294 -134
- data/bin/lib/Image/ExifTool/Geotag.pm +9 -4
- data/bin/lib/Image/ExifTool/M2TS.pm +32 -4
- data/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
- data/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
- data/bin/lib/Image/ExifTool/Nikon.pm +329 -21
- data/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
- data/bin/lib/Image/ExifTool/Olympus.pm +1 -0
- data/bin/lib/Image/ExifTool/OpenEXR.pm +21 -3
- data/bin/lib/Image/ExifTool/PNG.pm +3 -3
- data/bin/lib/Image/ExifTool/QuickTime.pm +12 -1
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +1 -1
- data/bin/lib/Image/ExifTool/Sony.pm +1 -1
- data/bin/lib/Image/ExifTool/TagLookup.pm +4791 -4776
- data/bin/lib/Image/ExifTool/TagNames.pod +542 -383
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +28 -8
- data/bin/lib/Image/ExifTool/Writer.pl +8 -2
- data/bin/lib/Image/ExifTool/XMP.pm +1 -0
- data/bin/lib/Image/ExifTool.pm +51 -37
- data/bin/lib/Image/ExifTool.pod +6 -0
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +2 -2
|
@@ -1,9 +1,11 @@
|
|
|
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
|
#
|
|
@@ -11,10 +13,17 @@
|
|
|
11
13
|
# default directory for the database file Geolocation.dat
|
|
12
14
|
# and language directory GeoLang.
|
|
13
15
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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.
|
|
16
21
|
#
|
|
17
|
-
#
|
|
22
|
+
# Databases are based on data from geonames.org with a
|
|
23
|
+
# Creative Commons license, reformatted as follows in the
|
|
24
|
+
# Geolocation.dat file:
|
|
25
|
+
#
|
|
26
|
+
# Header:
|
|
18
27
|
# "GeolocationV.VV\tNNNN\n" (V.VV=version, NNNN=num city entries)
|
|
19
28
|
# "# <comment>\n"
|
|
20
29
|
# NNNN City entries:
|
|
@@ -54,20 +63,24 @@
|
|
|
54
63
|
package Image::ExifTool::Geolocation;
|
|
55
64
|
|
|
56
65
|
use strict;
|
|
57
|
-
use vars qw($VERSION $geoDir $dbInfo);
|
|
66
|
+
use vars qw($VERSION $geoDir $altDir $dbInfo);
|
|
58
67
|
|
|
59
|
-
$VERSION = '1.
|
|
68
|
+
$VERSION = '1.03';
|
|
60
69
|
|
|
61
70
|
my $databaseVersion = '1.02';
|
|
62
71
|
|
|
72
|
+
my $debug; # set to output processing time for testing
|
|
73
|
+
|
|
63
74
|
sub ReadDatabase($);
|
|
64
75
|
sub SortDatabase($);
|
|
65
76
|
sub AddEntry(@);
|
|
66
|
-
sub GetEntry(
|
|
77
|
+
sub GetEntry($;$$);
|
|
67
78
|
sub Geolocate($;$$$$$);
|
|
68
79
|
|
|
69
80
|
my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
|
|
70
|
-
my (%countryNum, %regionNum, %subregionNum, %timezoneNum
|
|
81
|
+
my (%countryNum, %regionNum, %subregionNum, %timezoneNum); # reverse lookups
|
|
82
|
+
my (@sortOrder, @altNames, %langLookup, $nCity);
|
|
83
|
+
my ($lastArgs, %lastFound, @lastByPop, @lastByLat); # cached city matches
|
|
71
84
|
my $sortedBy = 'Latitude';
|
|
72
85
|
my $pi = 3.1415926536;
|
|
73
86
|
my $earthRadius = 6371; # earth radius in km
|
|
@@ -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+)/) {
|
|
@@ -129,14 +142,17 @@ sub ReadDatabase($)
|
|
|
129
142
|
close(DATFILE);
|
|
130
143
|
return 0;
|
|
131
144
|
}
|
|
132
|
-
|
|
145
|
+
$nCity = $2;
|
|
133
146
|
my $comment = <DATFILE>;
|
|
134
147
|
defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
|
|
135
|
-
$dbInfo = "$datfile v$databaseVersion: $
|
|
148
|
+
$dbInfo = "$datfile v$databaseVersion: $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>;
|
|
@@ -181,6 +197,44 @@ sub ReadDatabase($)
|
|
|
181
197
|
return 1;
|
|
182
198
|
}
|
|
183
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;
|
|
236
|
+
}
|
|
237
|
+
|
|
184
238
|
#------------------------------------------------------------------------------
|
|
185
239
|
# Sort database by specified field
|
|
186
240
|
# Inputs: 0) Field name to sort (Latitude,City,Country)
|
|
@@ -189,37 +243,36 @@ sub SortDatabase($)
|
|
|
189
243
|
{
|
|
190
244
|
my $field = shift;
|
|
191
245
|
return 1 if $field eq $sortedBy; # already sorted?
|
|
246
|
+
undef @sortOrder;
|
|
192
247
|
if ($field eq 'Latitude') {
|
|
193
|
-
@
|
|
248
|
+
@sortOrder = sort { $cityList[$a] cmp $cityList[$b] } 0..$#cityList;
|
|
194
249
|
} elsif ($field eq 'City') {
|
|
195
|
-
@
|
|
250
|
+
@sortOrder = sort { substr($cityList[$a],13) cmp substr($cityList[$b],13) } 0..$#cityList;
|
|
196
251
|
} elsif ($field eq 'Country') {
|
|
197
252
|
my %lkup;
|
|
198
|
-
foreach (
|
|
199
|
-
my $city = substr($_,13);
|
|
200
|
-
my $ctry = substr($countryList[ord substr($_,5,1)], 2);
|
|
253
|
+
foreach (0..$#cityList) {
|
|
254
|
+
my $city = substr($cityList[$_],13);
|
|
255
|
+
my $ctry = substr($countryList[ord substr($cityList[$_],5,1)], 2);
|
|
201
256
|
$lkup{$_} = "$ctry $city";
|
|
202
257
|
}
|
|
203
|
-
@
|
|
258
|
+
@sortOrder = sort { $lkup{$a} cmp $lkup{$b} } 0..$#cityList;
|
|
204
259
|
} else {
|
|
205
260
|
return 0;
|
|
206
261
|
}
|
|
207
262
|
$sortedBy = $field;
|
|
263
|
+
ClearLastMatches();
|
|
208
264
|
return 1;
|
|
209
265
|
}
|
|
210
266
|
|
|
211
267
|
#------------------------------------------------------------------------------
|
|
212
268
|
# Add cities to the Geolocation database
|
|
213
|
-
# Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon
|
|
269
|
+
# Inputs: 0-8) city,region,subregion,country_code,country,timezone,feature_code,population,lat,lon,altNames
|
|
214
270
|
# eg. AddEntry('Sinemorets','Burgas','Obshtina Tsarevo','BG','Bulgaria','Europe/Sofia','',400,42.06115,27.97833)
|
|
215
271
|
sub AddEntry(@)
|
|
216
272
|
{
|
|
217
|
-
my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon) = @_;
|
|
273
|
+
my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon, $altNames) = @_;
|
|
218
274
|
@_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return;
|
|
219
|
-
|
|
220
|
-
warn "Country code '${cc}' in UserDefined::Geolocation is not 2 characters\n";
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
275
|
+
length($cc) != 2 and warn("Country code '${cc}' is not 2 characters\n"), return;
|
|
223
276
|
$fc = $featureCodes{lc $fc} || 0;
|
|
224
277
|
chomp $lon; # (just in case it was read from file)
|
|
225
278
|
# create reverse lookups for country/region/subregion/timezone if not done already
|
|
@@ -262,17 +315,32 @@ sub AddEntry(@)
|
|
|
262
315
|
$lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
|
|
263
316
|
my $hdr = pack('nCnNnCC', $lat>>4, (($lat&0x0f)<<4)|($lon&0x0f), $lon>>4, $code, $sn, $tn, $fc);
|
|
264
317
|
push @cityList, "$hdr$city";
|
|
318
|
+
# add altNames entry if provided
|
|
319
|
+
if ($altNames) {
|
|
320
|
+
chomp $altNames; # (just in case)
|
|
321
|
+
$altNames =~ tr/,/\n/;
|
|
322
|
+
# add any more arguments in case altNames were passed separately (undocumented)
|
|
323
|
+
foreach (11..$#_) {
|
|
324
|
+
chomp $_[$_];
|
|
325
|
+
$altNames .= "\n$_[$_]";
|
|
326
|
+
}
|
|
327
|
+
$altNames[$#cityList] = $altNames;
|
|
328
|
+
}
|
|
265
329
|
$sortedBy = '';
|
|
330
|
+
undef $lastArgs; # (faster than ClearLastArgs)
|
|
266
331
|
}
|
|
267
332
|
|
|
268
333
|
#------------------------------------------------------------------------------
|
|
269
334
|
# Unpack entry in database
|
|
270
|
-
# Inputs: 0) entry number
|
|
271
|
-
#
|
|
272
|
-
|
|
335
|
+
# Inputs: 0) entry number or index into sorted database,
|
|
336
|
+
# 1) optional language code, 2) flag to use index into sorted database
|
|
337
|
+
# Returns: 0-10) city,region,subregion,country_code,country,timezone,
|
|
338
|
+
# feature_code,pop,lat,lon,altNames
|
|
339
|
+
sub GetEntry($;$$)
|
|
273
340
|
{
|
|
274
|
-
my ($entryNum, $lang) = @_;
|
|
341
|
+
my ($entryNum, $lang, $sort) = @_;
|
|
275
342
|
return() if $entryNum > $#cityList;
|
|
343
|
+
$entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
|
|
276
344
|
my ($lt,$f,$ln,$code,$sb,$tn,$fc) = unpack('nCnNnCC', $cityList[$entryNum]);
|
|
277
345
|
my $city = substr($cityList[$entryNum],13);
|
|
278
346
|
my $ctry = $countryList[$code >> 24];
|
|
@@ -325,31 +393,45 @@ sub GetEntry($;$)
|
|
|
325
393
|
return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
|
|
326
394
|
}
|
|
327
395
|
|
|
396
|
+
#------------------------------------------------------------------------------
|
|
397
|
+
# Get alternate names for specified database entry
|
|
398
|
+
# Inputs: 0) entry number or index into sorted database, 1) sort flag
|
|
399
|
+
# Returns: comma-separated list of alternate names, or empty string if no names
|
|
400
|
+
# Notes: ReadAltNames() must be called before this
|
|
401
|
+
sub GetAltNames($;$)
|
|
402
|
+
{
|
|
403
|
+
my ($entryNum, $sort) = @_;
|
|
404
|
+
$entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
|
|
405
|
+
my $alt = $altNames[$entryNum] or return '';
|
|
406
|
+
$alt =~ tr/\n/,/;
|
|
407
|
+
return $alt;
|
|
408
|
+
}
|
|
409
|
+
|
|
328
410
|
#------------------------------------------------------------------------------
|
|
329
411
|
# Look up lat,lon or city in geolocation database
|
|
330
412
|
# Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
|
|
331
|
-
# 1)
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
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)
|
|
413
|
+
# 1) options hash reference (or undef for no options)
|
|
414
|
+
# Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames
|
|
415
|
+
# Returns: 0) number of matching cities (0 if no matches),
|
|
416
|
+
# 1) index of matching city in database, or undef if no matches, or
|
|
417
|
+
# reference to list of indices if multiple matches were found and
|
|
418
|
+
# the flag to return multiple matches was set,
|
|
419
|
+
# 2) approx distance (km), 3) compass bearing to city
|
|
346
420
|
sub Geolocate($;$$$$$)
|
|
347
421
|
{
|
|
348
|
-
my ($arg, $
|
|
349
|
-
my ($city,
|
|
350
|
-
my ($minPop, $minDistU, $minDistC, @matchParms, @
|
|
422
|
+
my ($arg, $opts) = @_;
|
|
423
|
+
my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
|
|
424
|
+
my ($minPop, $minDistU, $minDistC, @matchParms, @coords, $fcmask, $both);
|
|
425
|
+
my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
|
|
426
|
+
|
|
427
|
+
$opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
|
|
428
|
+
@$opts{qw(GeolocMinPop GeolocMaxDist GeolocMulti GeolocFeature GeolocAltNames)};
|
|
351
429
|
|
|
352
|
-
|
|
430
|
+
if ($debug) {
|
|
431
|
+
require Time::HiRes;
|
|
432
|
+
@startTime = Time::HiRes::gettimeofday();
|
|
433
|
+
}
|
|
434
|
+
@cityList or warn('No Geolocation database'), return 0;
|
|
353
435
|
# make population code for comparing with 2 bytes at offset 6 in database
|
|
354
436
|
if ($pop) {
|
|
355
437
|
$pop = sprintf('%.1e', $pop);
|
|
@@ -376,23 +458,28 @@ sub Geolocate($;$$$$$)
|
|
|
376
458
|
if (m{^(-)?(\w{2})?/(.*)/(i?)$}) {
|
|
377
459
|
my $re = $4 ? qr/$3/im : qr/$3/m;
|
|
378
460
|
next if not defined($idx = $ri{$2});
|
|
461
|
+
push @cargs, $_;
|
|
379
462
|
$other = 1 if $idx < 5;
|
|
380
463
|
$idx += 10 if $1; # add 10 for negative matches
|
|
464
|
+
$regex{$idx} or $regex{$idx} = [ ];
|
|
381
465
|
push @{$regex{$idx}}, $re;
|
|
382
466
|
$city = '' unless defined $city;
|
|
383
467
|
} elsif (/^[-+]?\d+(\.\d+)?$/) { # coordinate format
|
|
384
468
|
push @coords, $_ if @coords < 2;
|
|
385
|
-
} elsif (lc eq 'both') {
|
|
469
|
+
} elsif (lc $_ eq 'both') {
|
|
386
470
|
$both = 1;
|
|
387
|
-
} elsif ($
|
|
388
|
-
push @
|
|
389
|
-
|
|
390
|
-
|
|
471
|
+
} elsif ($_) {
|
|
472
|
+
push @cargs, $_;
|
|
473
|
+
if ($city) {
|
|
474
|
+
push @exact, lc $_;
|
|
475
|
+
} else {
|
|
476
|
+
$city = lc $_;
|
|
477
|
+
}
|
|
391
478
|
}
|
|
392
479
|
}
|
|
393
480
|
unless (defined $city or @coords == 2) {
|
|
394
481
|
warn("Insufficient information to determine geolocation\n");
|
|
395
|
-
return
|
|
482
|
+
return 0;
|
|
396
483
|
}
|
|
397
484
|
# sort database by logitude if finding entry based on coordinates
|
|
398
485
|
SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
|
|
@@ -400,9 +487,26 @@ sub Geolocate($;$$$$$)
|
|
|
400
487
|
# perform reverse Geolocation lookup to determine GPS based on city, country, etc.
|
|
401
488
|
#
|
|
402
489
|
while (defined $city and (@coords != 2 or $both)) {
|
|
403
|
-
|
|
490
|
+
my $cargs = join(',', @cargs, $pop||'', $maxDist||'', $fcodes||'');
|
|
491
|
+
my $i = 0;
|
|
492
|
+
if ($lastArgs and $lastArgs eq $cargs) {
|
|
493
|
+
$i = @cityList; # bypass search
|
|
494
|
+
} else {
|
|
495
|
+
ClearLastMatches();
|
|
496
|
+
$lastArgs = $cargs;
|
|
497
|
+
}
|
|
498
|
+
# read alternate names database if an exact city match is specified
|
|
499
|
+
if ($altNames) {
|
|
500
|
+
ReadAltNames() if $city and $altDir;
|
|
501
|
+
$altNames = \@altNames;
|
|
502
|
+
} else {
|
|
503
|
+
$altNames = [ ]; # (don't search alt names)
|
|
504
|
+
}
|
|
505
|
+
Entry: for (; $i<@cityList; ++$i) {
|
|
404
506
|
my $cty = substr($cityList[$i],13);
|
|
405
|
-
|
|
507
|
+
if ($city and $city ne lc $cty) { # test exact city name first
|
|
508
|
+
next unless $$altNames[$i] and $$altNames[$i] =~ /^$city$/im;
|
|
509
|
+
}
|
|
406
510
|
# test with city-specific regexes
|
|
407
511
|
if ($regex{8}) { $cty =~ $_ or next Entry foreach @{$regex{8}} }
|
|
408
512
|
if ($regex{18}) { $cty !~ $_ or next Entry foreach @{$regex{18}} }
|
|
@@ -430,23 +534,24 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
|
|
|
430
534
|
# test feature code and population
|
|
431
535
|
next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
|
|
432
536
|
my $pc = substr($cityList[$i],6,2);
|
|
433
|
-
|
|
537
|
+
if (not defined $minPop or $pc ge $minPop) {
|
|
538
|
+
$lastFound{$i} = $pc;
|
|
539
|
+
push @lastByLat, $i if @coords == 2;
|
|
540
|
+
}
|
|
434
541
|
}
|
|
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;
|
|
542
|
+
@startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
|
|
543
|
+
if (%lastFound) {
|
|
544
|
+
@coords == 2 and $useLastFound = 1, last; # continue to use coords with last city matches
|
|
545
|
+
scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return 0;
|
|
546
|
+
unless (@lastByPop) {
|
|
547
|
+
@lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
|
|
445
548
|
}
|
|
446
|
-
|
|
549
|
+
my $n = scalar @lastByPop;
|
|
550
|
+
return($n, [ @lastByPop ]) if $n > 1 and $multi;
|
|
551
|
+
return($n, $lastByPop[0]);
|
|
447
552
|
}
|
|
448
553
|
warn "No such city in Geolocation database\n";
|
|
449
|
-
return
|
|
554
|
+
return 0;
|
|
450
555
|
}
|
|
451
556
|
#
|
|
452
557
|
# determine Geolocation based on GPS coordinates
|
|
@@ -465,12 +570,14 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
|
|
|
465
570
|
$lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
|
|
466
571
|
$lat or $lat = $coords[0] < 0 ? 1 : 0xfffff; # (zero latitude is a problem for our calculations)
|
|
467
572
|
my $coord = pack('nCn',$lat>>4,(($lat&0x0f)<<4)|($lon&0x0f),$lon>>4);;
|
|
573
|
+
# start from cached city matches if also using city information
|
|
574
|
+
my $numEntries = @lastByLat || @cityList;
|
|
468
575
|
# binary search to find closest longitude
|
|
469
|
-
my $numEntries = @matches || @cityList;
|
|
470
576
|
my ($n0, $n1) = (0, $numEntries - 1);
|
|
577
|
+
my $sorted = @lastByLat ? \@lastByLat : (@sortOrder ? \@sortOrder : undef);
|
|
471
578
|
while ($n1 - $n0 > 1) {
|
|
472
579
|
my $n = int(($n0 + $n1) / 2);
|
|
473
|
-
if ($coord lt $cityList[
|
|
580
|
+
if ($coord lt $cityList[$sorted ? $$sorted[$n] : $n]) {
|
|
474
581
|
$n1 = $n;
|
|
475
582
|
} else {
|
|
476
583
|
$n0 = $n;
|
|
@@ -485,15 +592,15 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
|
|
|
485
592
|
last if $inc == 1;
|
|
486
593
|
($inc, $end, $n) = (1, $numEntries, $n1);
|
|
487
594
|
}
|
|
488
|
-
my $
|
|
595
|
+
my $i = $sorted ? $$sorted[$n] : $n;
|
|
489
596
|
# get city latitude/longitude
|
|
490
|
-
my ($lt,$f,$ln) = unpack('nCn', $cityList[$
|
|
597
|
+
my ($lt,$f,$ln) = unpack('nCn', $cityList[$i]);
|
|
491
598
|
$lt = ($lt << 4) | ($f >> 4);
|
|
492
599
|
# searched far enough if latitude alone is further than best distance
|
|
493
600
|
abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
|
|
494
601
|
# ignore if population is below threshold
|
|
495
|
-
next if defined $minPop and $minPop ge substr($cityList[$
|
|
496
|
-
next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$
|
|
602
|
+
next if defined $minPop and $minPop ge substr($cityList[$i],6,2);
|
|
603
|
+
next if $fcmask and not $fcmask & (1 << (ord(substr($cityList[$i],12,1)) & 0x0f));
|
|
497
604
|
$ln = ($ln << 4) | ($f & 0x0f);
|
|
498
605
|
# calculate great circle distance to this city on unit sphere
|
|
499
606
|
my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
|
|
@@ -503,20 +610,18 @@ Entry: for ($i=0; $i<@cityList; ++$i) {
|
|
|
503
610
|
next if $distU > $minDistU;
|
|
504
611
|
$minDistU = $distU;
|
|
505
612
|
$minDistC = $minDistU * 0x200000 / $pi;
|
|
506
|
-
@matchParms = ($
|
|
613
|
+
@matchParms = ($i, $p1, $t1, $distU);
|
|
507
614
|
}
|
|
508
|
-
@matchParms or warn("No suitable location in Geolocation database\n"), return
|
|
615
|
+
@matchParms or warn("No suitable location in Geolocation database\n"), return 0;
|
|
509
616
|
|
|
510
617
|
# calculate distance in km and bearing to matching city
|
|
511
|
-
my ($
|
|
618
|
+
my ($ii, $p1, $t1, $distU) = @matchParms;
|
|
512
619
|
my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
|
|
513
620
|
my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
|
|
514
621
|
$be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
|
|
515
622
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
@cityInfo[10,11] = ($km, $be); # add distance and heading
|
|
519
|
-
return \@cityInfo;
|
|
623
|
+
@startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
|
|
624
|
+
return(1, $ii, $km, $be)
|
|
520
625
|
}
|
|
521
626
|
|
|
522
627
|
1; #end
|
|
@@ -525,11 +630,13 @@ __END__
|
|
|
525
630
|
|
|
526
631
|
=head1 NAME
|
|
527
632
|
|
|
528
|
-
Image::ExifTool::Geolocation -
|
|
633
|
+
Image::ExifTool::Geolocation - Determine geolocation from GPS and visa-versa
|
|
529
634
|
|
|
530
635
|
=head1 SYNOPSIS
|
|
531
636
|
|
|
532
|
-
|
|
637
|
+
Look up geolocation information (city, region, subregion, country, etc)
|
|
638
|
+
based on input GPS coordinates, or determine GPS coordinates based on city
|
|
639
|
+
name, etc.
|
|
533
640
|
|
|
534
641
|
=head1 DESCRIPTION
|
|
535
642
|
|
|
@@ -562,6 +669,29 @@ True on success.
|
|
|
562
669
|
|
|
563
670
|
=back
|
|
564
671
|
|
|
672
|
+
=head2 ReadAltNames
|
|
673
|
+
|
|
674
|
+
Load the alternate names database. Before calling this method the $altDir
|
|
675
|
+
package variable must be set to a directory containing the AltNames.dat
|
|
676
|
+
database that matches the current Geolocation.dat. This method is called
|
|
677
|
+
automatically by L</Geolocate> if $altDir is set and the GeolocAltNames
|
|
678
|
+
option is used and an input city name is provided.
|
|
679
|
+
|
|
680
|
+
Image::ExifTool::Geolocation::ReadAltNames();
|
|
681
|
+
|
|
682
|
+
=over 4
|
|
683
|
+
|
|
684
|
+
=item Inputs:
|
|
685
|
+
|
|
686
|
+
(none)
|
|
687
|
+
|
|
688
|
+
=item Return Value:
|
|
689
|
+
|
|
690
|
+
True on success. Resets the value of $altDir to prevent further attempts at
|
|
691
|
+
re-loading the same database.
|
|
692
|
+
|
|
693
|
+
=back
|
|
694
|
+
|
|
565
695
|
=head2 SortDatabase
|
|
566
696
|
|
|
567
697
|
Sort database in specified order.
|
|
@@ -585,8 +715,8 @@ Sort database in specified order.
|
|
|
585
715
|
Add entry to Geolocation database.
|
|
586
716
|
|
|
587
717
|
Image::ExifTool::Geolocation::AddEntry($city, $region,
|
|
588
|
-
$countryCode, $country, $timezone,
|
|
589
|
-
$population, $lat, $lon);
|
|
718
|
+
$subregion, $countryCode, $country, $timezone,
|
|
719
|
+
$featureCode, $population, $lat, $lon, $altNames);
|
|
590
720
|
|
|
591
721
|
=over 4
|
|
592
722
|
|
|
@@ -614,23 +744,28 @@ database, then the database entry is updated with the new country name.
|
|
|
614
744
|
|
|
615
745
|
9) GPS longitude
|
|
616
746
|
|
|
747
|
+
10) Optional comma-separated list of alternate names for the city
|
|
748
|
+
|
|
617
749
|
=back
|
|
618
750
|
|
|
619
751
|
=head2 GetEntry
|
|
620
752
|
|
|
621
753
|
Get entry from Geolocation database.
|
|
622
754
|
|
|
623
|
-
my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang);
|
|
755
|
+
my @vals = Image::ExifTool::Geolocation::GetEntry($num,$lang,$sort);
|
|
624
756
|
|
|
625
757
|
=over 4
|
|
626
758
|
|
|
627
759
|
=item Inputs:
|
|
628
760
|
|
|
629
|
-
0) Entry number in database
|
|
761
|
+
0) Entry number in database, or index into sorted database
|
|
630
762
|
|
|
631
763
|
1) Optional language code
|
|
632
764
|
|
|
633
|
-
|
|
765
|
+
2) Optional flag to treat first argument as an index into the sorted
|
|
766
|
+
database
|
|
767
|
+
|
|
768
|
+
item Return Values:
|
|
634
769
|
|
|
635
770
|
0) City name, or undef if the entry didn't exist
|
|
636
771
|
|
|
@@ -652,11 +787,31 @@ Get entry from Geolocation database.
|
|
|
652
787
|
|
|
653
788
|
9) GPS longitude
|
|
654
789
|
|
|
790
|
+
=back
|
|
791
|
+
|
|
792
|
+
=head2 GetAltNames
|
|
793
|
+
|
|
794
|
+
Get alternate names for specified city.
|
|
795
|
+
|
|
796
|
+
my $str = Image::ExifTool::Geolocation::GetAltNames($num,$sort);
|
|
797
|
+
|
|
798
|
+
=over 4
|
|
799
|
+
|
|
800
|
+
=item Inputs:
|
|
801
|
+
|
|
802
|
+
0) Entry number in database or index into the sorted database
|
|
803
|
+
|
|
804
|
+
1) Optional flag to treat first argument as an index into the sorted
|
|
805
|
+
database
|
|
806
|
+
|
|
807
|
+
=item Return value:
|
|
808
|
+
|
|
809
|
+
Comma-separated string of alternate names for this city.
|
|
810
|
+
|
|
655
811
|
=item Notes:
|
|
656
812
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
L<https://exiftool.org/geolocation.html> for more information.
|
|
813
|
+
Must set the $altDir package variable and call L</ReadAltNames> before
|
|
814
|
+
calling this routine.
|
|
660
815
|
|
|
661
816
|
=back
|
|
662
817
|
|
|
@@ -664,8 +819,7 @@ L<https://exiftool.org/geolocation.html> for more information.
|
|
|
664
819
|
|
|
665
820
|
Return geolocation information for specified GPS coordinates or city name.
|
|
666
821
|
|
|
667
|
-
my @
|
|
668
|
-
Image::ExifTool::Geolocation::Geolocate($arg,$pop,$dist,$lang,$multi,$codes);
|
|
822
|
+
my @rtnInfo = Image::ExifTool::Geolocation::Geolocate($arg,$opts);
|
|
669
823
|
|
|
670
824
|
=over 4
|
|
671
825
|
|
|
@@ -680,54 +834,67 @@ expressions in C</expr/> format are also allowed, optionally prefixed by
|
|
|
680
834
|
Subregion, CountryCode or Country name. See
|
|
681
835
|
L<https://exiftool.org/geolocation.html#Read> for details.
|
|
682
836
|
|
|
683
|
-
1)
|
|
684
|
-
|
|
685
|
-
2) Maximum distance to city (farther cities are not considered)
|
|
686
|
-
|
|
687
|
-
3) Language code
|
|
837
|
+
1) Optional reference to hash of options:
|
|
688
838
|
|
|
689
|
-
|
|
690
|
-
case the return value is a list of city information lists.
|
|
839
|
+
GeolocMinPop - minimum population of cities to consider in search
|
|
691
840
|
|
|
692
|
-
|
|
693
|
-
|
|
841
|
+
GeolocMaxDist - maximum distance (km) to search for cities when an input
|
|
842
|
+
GPS position is used
|
|
694
843
|
|
|
695
|
-
|
|
844
|
+
GeolocMulti - flag to return multiple cities if there is more than one
|
|
845
|
+
match. In this case the return value is a list of city
|
|
846
|
+
information lists.
|
|
696
847
|
|
|
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.
|
|
848
|
+
GeolocFeature - comma-separated list of feature codes to include in
|
|
849
|
+
search, or exclude if the list starts with a dash (-)
|
|
701
850
|
|
|
702
|
-
|
|
851
|
+
GeolocAltNames - flag to search alternate names database if available
|
|
852
|
+
for matching city name (see ALTERNATE DATABASES below)
|
|
703
853
|
|
|
704
|
-
|
|
854
|
+
=item Return Value:
|
|
705
855
|
|
|
706
|
-
|
|
856
|
+
0) Number of matching entries, or 0 if no matches
|
|
707
857
|
|
|
708
|
-
|
|
858
|
+
1) Entry number for matching city in database, or undef if no matches, or a
|
|
859
|
+
reference to a list of entry numbers of matching cities if multiple matches
|
|
860
|
+
were found and the flag was set to return multiple matches
|
|
709
861
|
|
|
710
|
-
|
|
862
|
+
2) Distance to closest city in km if "lat,lon" specified
|
|
711
863
|
|
|
712
|
-
|
|
864
|
+
3) Compass bearing for direction to closest city if "lat,lon" specified
|
|
713
865
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
6) Feature code
|
|
866
|
+
=back
|
|
717
867
|
|
|
718
|
-
|
|
868
|
+
=head1 ALTERNATE DATABASES
|
|
719
869
|
|
|
720
|
-
|
|
870
|
+
A different version of the cities database may be specified setting the
|
|
871
|
+
package $geoDir variable before loading this module. This directory should
|
|
872
|
+
contain the Geolocation.dat file, and optionally a GeoLang directory for the
|
|
873
|
+
language translations. The $geoDir variable may be set to an empty string
|
|
874
|
+
to disable loading of a database.
|
|
721
875
|
|
|
722
|
-
|
|
876
|
+
A database of alternate city names may be loaded by setting the package
|
|
877
|
+
$altDir variable. This directory should contain the AltNames.dat database
|
|
878
|
+
that matches the version of Geolocation.dat being used. When searching for
|
|
879
|
+
a city by name, the alternate-names database is checked to provide
|
|
880
|
+
additional possibilities for matches.
|
|
723
881
|
|
|
724
|
-
|
|
882
|
+
=head1 ADDING USER-DEFINED DATABASE ENTRIES
|
|
725
883
|
|
|
726
|
-
|
|
884
|
+
User-defined entries may be created by defining them using the following
|
|
885
|
+
technique before the Geolocation module is loaded.
|
|
727
886
|
|
|
728
|
-
|
|
887
|
+
@Image::ExifTool::UserDefined::Geolocation = (
|
|
888
|
+
# city, region, subregion, country code, country, timezone,
|
|
889
|
+
['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
|
|
890
|
+
# feature code, population, lat, lon
|
|
891
|
+
'',400,42.06115,27.97833],
|
|
892
|
+
);
|
|
729
893
|
|
|
730
|
-
|
|
894
|
+
Similarly, user-defined language translations may be defined, and will
|
|
895
|
+
override any existing translations. Translations for the default 'en'
|
|
896
|
+
language may also be specified. See
|
|
897
|
+
L<https://exiftool.org/geolocation.html#Custom> for more information.
|
|
731
898
|
|
|
732
899
|
=head1 USING A CUSTOM DATABASE
|
|
733
900
|
|
|
@@ -737,24 +904,17 @@ the input arguments of the AddEntry method.
|
|
|
737
904
|
|
|
738
905
|
$Image::ExifTool::Geolocation::geoDir = '';
|
|
739
906
|
require Image::ExifTool::Geolocation;
|
|
740
|
-
open DFILE, "
|
|
907
|
+
open DFILE, "< $filename";
|
|
741
908
|
Image::ExifTool::Geolocation::AddEntry(split /,/) foreach <DFILE>;
|
|
742
909
|
close DFILE;
|
|
743
910
|
|
|
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
911
|
=head1 AUTHOR
|
|
752
912
|
|
|
753
913
|
Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
|
|
754
914
|
|
|
755
915
|
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.
|
|
916
|
+
under the same terms as Perl itself. The associated database files are
|
|
917
|
+
based on data from geonames.org with a Creative Commons license.
|
|
758
918
|
|
|
759
919
|
=head1 REFERENCES
|
|
760
920
|
|