exiftool-vendored.pl 12.82.0 → 12.85.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 +59 -0
- package/bin/MANIFEST +2 -18
- package/bin/META.json +1 -1
- package/bin/META.yml +1 -1
- package/bin/README +47 -46
- package/bin/build_geolocation +945 -0
- package/bin/config_files/example.config +2 -2
- package/bin/exiftool +75 -51
- package/bin/fmt_files/gpx.fmt +2 -1
- package/bin/fmt_files/gpx_wpt.fmt +2 -1
- package/bin/lib/Image/ExifTool/Apple.pm +52 -7
- package/bin/lib/Image/ExifTool/BuildTagLookup.pm +5 -2
- package/bin/lib/Image/ExifTool/Canon.pm +11 -2
- package/bin/lib/Image/ExifTool/CanonVRD.pm +18 -5
- package/bin/lib/Image/ExifTool/DJI.pm +29 -0
- package/bin/lib/Image/ExifTool/DPX.pm +3 -3
- package/bin/lib/Image/ExifTool/Exif.pm +19 -2
- package/bin/lib/Image/ExifTool/GM.pm +17 -8
- package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
- package/bin/lib/Image/ExifTool/Geolocation.pm +170 -104
- package/bin/lib/Image/ExifTool/Geotag.pm +18 -10
- package/bin/lib/Image/ExifTool/ID3.pm +28 -6
- package/bin/lib/Image/ExifTool/Nikon.pm +25 -6
- package/bin/lib/Image/ExifTool/Pentax.pm +64 -13
- package/bin/lib/Image/ExifTool/QuickTime.pm +26 -7
- package/bin/lib/Image/ExifTool/QuickTimeStream.pl +5 -0
- package/bin/lib/Image/ExifTool/Sony.pm +15 -6
- package/bin/lib/Image/ExifTool/TagLookup.pm +3557 -3540
- package/bin/lib/Image/ExifTool/TagNames.pod +61 -13
- package/bin/lib/Image/ExifTool/WriteQuickTime.pl +4 -2
- package/bin/lib/Image/ExifTool/Writer.pl +166 -134
- package/bin/lib/Image/ExifTool/XMP.pm +2 -0
- package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
- package/bin/lib/Image/ExifTool.pm +59 -27
- package/bin/lib/Image/ExifTool.pod +88 -71
- package/bin/perl-Image-ExifTool.spec +45 -45
- package/bin/pp_build_exe.args +4 -4
- package/package.json +2 -2
- package/bin/lib/Image/ExifTool/GeoLang/cs.pm +0 -978
- package/bin/lib/Image/ExifTool/GeoLang/de.pm +0 -1975
- package/bin/lib/Image/ExifTool/GeoLang/en_ca.pm +0 -44
- package/bin/lib/Image/ExifTool/GeoLang/en_gb.pm +0 -124
- package/bin/lib/Image/ExifTool/GeoLang/es.pm +0 -2921
- package/bin/lib/Image/ExifTool/GeoLang/fi.pm +0 -1116
- package/bin/lib/Image/ExifTool/GeoLang/fr.pm +0 -3171
- package/bin/lib/Image/ExifTool/GeoLang/it.pm +0 -2750
- package/bin/lib/Image/ExifTool/GeoLang/ja.pm +0 -10256
- package/bin/lib/Image/ExifTool/GeoLang/ko.pm +0 -4499
- package/bin/lib/Image/ExifTool/GeoLang/nl.pm +0 -1270
- package/bin/lib/Image/ExifTool/GeoLang/pl.pm +0 -3019
- package/bin/lib/Image/ExifTool/GeoLang/ru.pm +0 -18220
- package/bin/lib/Image/ExifTool/GeoLang/sk.pm +0 -441
- package/bin/lib/Image/ExifTool/GeoLang/sv.pm +0 -714
- package/bin/lib/Image/ExifTool/GeoLang/tr.pm +0 -452
- package/bin/lib/Image/ExifTool/GeoLang/zh_cn.pm +0 -2225
- package/bin/lib/Image/ExifTool/GeoLang/zh_tw.pm +0 -72
|
@@ -15,7 +15,7 @@ use vars qw($VERSION);
|
|
|
15
15
|
use Image::ExifTool qw(:DataAccess :Utils);
|
|
16
16
|
use Image::ExifTool::GPS;
|
|
17
17
|
|
|
18
|
-
$VERSION = '1.
|
|
18
|
+
$VERSION = '1.01';
|
|
19
19
|
|
|
20
20
|
sub Process_marl($$$);
|
|
21
21
|
sub Process_mrld($$$);
|
|
@@ -30,6 +30,8 @@ my %convertUnits = (
|
|
|
30
30
|
ltr => 'L',
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
my $pi = 3.141592653589793;
|
|
34
|
+
|
|
33
35
|
# offsets and scaling factors to convert to reasonable units
|
|
34
36
|
my %changeOffset = (
|
|
35
37
|
C => -273.15, # K to C
|
|
@@ -37,8 +39,8 @@ my %changeOffset = (
|
|
|
37
39
|
my %changeScale = (
|
|
38
40
|
G => 1 / 9.80665, # m/s2 to G
|
|
39
41
|
kph => 3.6, # m/s to km/h
|
|
40
|
-
deg => 180 /
|
|
41
|
-
'deg/sec' => 180 /
|
|
42
|
+
deg => 180 / $pi, # radians to degrees
|
|
43
|
+
'deg/sec' => 180 / $pi, # rad/s to deg/s
|
|
42
44
|
'%' => 100, # decimal to %
|
|
43
45
|
kPa => 1/1000, # Pa to kPa
|
|
44
46
|
rpm => 10, # ? (arbitrary factor of 10)
|
|
@@ -181,7 +183,7 @@ my %channelStruct = (
|
|
|
181
183
|
Name => 'GPSTrack',
|
|
182
184
|
Description => 'GPS Track',
|
|
183
185
|
Groups => { 2 => 'Location' },
|
|
184
|
-
PrintConv => 'sprintf("%.2f",$val)',
|
|
186
|
+
PrintConv => '$val > 360 ? "n/a" : sprintf("%.2f",$val)', # (seen 655.35)
|
|
185
187
|
},
|
|
186
188
|
ABSActive => { },
|
|
187
189
|
AccelPos => { },
|
|
@@ -209,7 +211,14 @@ my %channelStruct = (
|
|
|
209
211
|
EngineTorqureReq => { },
|
|
210
212
|
FuelCapacity => { },
|
|
211
213
|
FuelLevel => { },
|
|
212
|
-
Gear => {
|
|
214
|
+
Gear => {
|
|
215
|
+
Notes => q{
|
|
216
|
+
in the PrintCSV output, the value for Neutral is set to -1, and Reverse to
|
|
217
|
+
-100 for compatibility with RaceRender
|
|
218
|
+
},
|
|
219
|
+
CSVConv => { 13 => -1, 14 => -100 },
|
|
220
|
+
PrintConv => { 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 13=>'N', 14=>'R' }
|
|
221
|
+
},
|
|
213
222
|
GPSFix => { },
|
|
214
223
|
InfotainOpMode => { },
|
|
215
224
|
IntakeAirTemperature => { },
|
|
@@ -261,7 +270,7 @@ my %channelStruct = (
|
|
|
261
270
|
sub PrintCSV($;$)
|
|
262
271
|
{
|
|
263
272
|
my ($et, $ts) = @_;
|
|
264
|
-
my $csv = $$et{GMCsv} or return;
|
|
273
|
+
my $csv = $$et{GMCsv} or return; # get the list of channels with measurements
|
|
265
274
|
@$csv or return;
|
|
266
275
|
my $vals = $$et{GMVals};
|
|
267
276
|
my $gmDict = $$et{GMDictionary};
|
|
@@ -271,7 +280,7 @@ sub PrintCSV($;$)
|
|
|
271
280
|
foreach (@$csv) {
|
|
272
281
|
my $gmChan = $$gmDict[$_];
|
|
273
282
|
$items[$_] = $$vals[$_] * $$gmChan{Mult} + $$gmChan{Off};
|
|
274
|
-
# apply
|
|
283
|
+
# apply CSV conversion if applicable (ie. Gear)
|
|
275
284
|
next unless $$gmChan{Conv} and defined $$gmChan{Conv}{$items[$_]};
|
|
276
285
|
$items[$_] = $$gmChan{Conv}{$items[$_]};
|
|
277
286
|
}
|
|
@@ -397,7 +406,7 @@ sub Process_mrld($$$)
|
|
|
397
406
|
my $init = int(($a[6] + $a[7]) / 2); # initial value for difference readings
|
|
398
407
|
# save information about this channel necessary for processing the marl data
|
|
399
408
|
$$gmDict[$chan] = { Name => $name, Mult => $mult, Off => $off, Init => $init };
|
|
400
|
-
$$gmDict[$chan]{Conv} = $$tagInfo{
|
|
409
|
+
$$gmDict[$chan]{Conv} = $$tagInfo{CSVConv};
|
|
401
410
|
$csv and $$csv[$chan] = $a[12] . ($a[3] ? " ($a[3])" : '');
|
|
402
411
|
}
|
|
403
412
|
# channel 0 must not be defined because we use it for the TimeStamp
|
|
Binary file
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
#
|
|
10
10
|
# References: https://download.geonames.org/export/
|
|
11
11
|
#
|
|
12
|
-
# Notes: Set $Image::ExifTool::Geolocation::geoDir to override
|
|
13
|
-
# default directory
|
|
14
|
-
# 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
|
|
15
16
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# entry is a newline-separated list of alternate names
|
|
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
|
|
20
21
|
# terminated by a null byte.
|
|
21
22
|
#
|
|
22
23
|
# Databases are based on data from geonames.org with a
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
# 9 int16u - v1.02: 0x7fff = index in subregion (admin2), 0x8000 = high bit of time zone
|
|
38
39
|
# 9 int16u - v1.03: index in subregion (admin2)
|
|
39
40
|
# 11 int8u - low byte of time zone index
|
|
40
|
-
# 12 int8u -
|
|
41
|
+
# 12 int8u - 0x3f = feature code index (see below), v1.03: 0x80 = high bit of time zone
|
|
41
42
|
# 13 string - UTF8 City name, terminated by newline
|
|
42
43
|
# "\0\0\0\0\x01"
|
|
43
44
|
# Country entries:
|
|
@@ -52,9 +53,12 @@
|
|
|
52
53
|
# "\0\0\0\0\x04"
|
|
53
54
|
# Time zone entries:
|
|
54
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, optional space-followed-by-feature-name, then newline
|
|
55
59
|
# "\0\0\0\0\0"
|
|
56
60
|
#
|
|
57
|
-
# 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)
|
|
58
62
|
#
|
|
59
63
|
# 0. Other 3. PPLA2 6. PPLA5 9. PPLF 12. PPLR 15. PPLX
|
|
60
64
|
# 1. PPL 4. PPLA3 7. PPLC 10. PPLG 13. PPLS
|
|
@@ -66,7 +70,7 @@ package Image::ExifTool::Geolocation;
|
|
|
66
70
|
use strict;
|
|
67
71
|
use vars qw($VERSION $geoDir $altDir $dbInfo);
|
|
68
72
|
|
|
69
|
-
$VERSION = '1.
|
|
73
|
+
$VERSION = '1.08'; # (this is the module version number, not the database version)
|
|
70
74
|
|
|
71
75
|
my $debug; # set to output processing time for testing
|
|
72
76
|
|
|
@@ -74,21 +78,19 @@ sub ReadDatabase($);
|
|
|
74
78
|
sub SortDatabase($);
|
|
75
79
|
sub AddEntry(@);
|
|
76
80
|
sub GetEntry($;$$);
|
|
77
|
-
sub Geolocate(
|
|
81
|
+
sub Geolocate($;$);
|
|
78
82
|
|
|
79
83
|
my (@cityList, @countryList, @regionList, @subregionList, @timezoneList);
|
|
80
84
|
my (%countryNum, %regionNum, %subregionNum, %timezoneNum); # reverse lookups
|
|
81
|
-
my (@sortOrder, @altNames, %langLookup, $nCity);
|
|
85
|
+
my (@sortOrder, @altNames, %langLookup, $nCity, %featureCodes, %featureTypes);
|
|
82
86
|
my ($lastArgs, %lastFound, @lastByPop, @lastByLat); # cached city matches
|
|
83
87
|
my $dbVer = '1.03';
|
|
84
88
|
my $sortedBy = 'Latitude';
|
|
85
89
|
my $pi = 3.1415926536;
|
|
86
90
|
my $earthRadius = 6371; # earth radius in km
|
|
87
|
-
|
|
91
|
+
# hard-coded feature codes for v1.02 database
|
|
88
92
|
my @featureCodes = qw(Other PPL PPLA PPLA2 PPLA3 PPLA4 PPLA5 PPLC
|
|
89
93
|
PPLCH PPLF PPLG PPLL PPLR PPLS STLMT PPLX);
|
|
90
|
-
my $i = 0;
|
|
91
|
-
my %featureCodes = map { lc($_) => $i++ } @featureCodes;
|
|
92
94
|
|
|
93
95
|
# get path name for database file from lib/Image/ExifTool/Geolocation.dat by default,
|
|
94
96
|
# or according to $Image::ExifTool::Geolocation::directory if specified
|
|
@@ -107,12 +109,10 @@ unless (defined $geoDir and not $geoDir) {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
# set directory for language files
|
|
111
|
-
|
|
112
|
-
if ($geoDir and -
|
|
113
|
-
$
|
|
114
|
-
} elsif ($geoDir or not defined $geoDir) {
|
|
115
|
-
$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;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
# add user-defined entries to the database
|
|
@@ -144,7 +144,7 @@ sub ReadDatabase($)
|
|
|
144
144
|
return 0;
|
|
145
145
|
}
|
|
146
146
|
my $comment = <DATFILE>;
|
|
147
|
-
defined $comment and $comment =~ /(\d+)/ or close(DATFILE), return 0;
|
|
147
|
+
defined $comment and $comment =~ / (\d+) / or close(DATFILE), return 0;
|
|
148
148
|
$dbInfo = "$datfile v$dbVer: $nCity cities with population > $1";
|
|
149
149
|
my $isUserDefined = @Image::ExifTool::UserDefined::Geolocation;
|
|
150
150
|
|
|
@@ -193,7 +193,21 @@ sub ReadDatabase($)
|
|
|
193
193
|
push @timezoneList, $line;
|
|
194
194
|
$timezoneNum{lc $line} = $#timezoneList if $isUserDefined;
|
|
195
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
|
+
$featureTypes{$line} = $1 if $line =~ s/ (.*)//;
|
|
204
|
+
push @featureCodes, $line;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
196
207
|
close DATFILE;
|
|
208
|
+
# initialize featureCodes lookup
|
|
209
|
+
$i = 0;
|
|
210
|
+
%featureCodes = map { lc($_) => $i++ } @featureCodes;
|
|
197
211
|
return 1;
|
|
198
212
|
}
|
|
199
213
|
|
|
@@ -229,10 +243,10 @@ sub ReadAltNames()
|
|
|
229
243
|
# Clear last city matches cache
|
|
230
244
|
sub ClearLastMatches()
|
|
231
245
|
{
|
|
232
|
-
undef $lastArgs;
|
|
233
|
-
undef %lastFound;
|
|
234
|
-
undef @lastByPop;
|
|
235
|
-
undef @lastByLat;
|
|
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
|
|
236
250
|
}
|
|
237
251
|
|
|
238
252
|
#------------------------------------------------------------------------------
|
|
@@ -274,7 +288,16 @@ sub AddEntry(@)
|
|
|
274
288
|
my ($city, $region, $subregion, $cc, $country, $timezone, $fc, $pop, $lat, $lon, $altNames) = @_;
|
|
275
289
|
@_ < 10 and warn("Too few arguments in $city definition (check for updated format)\n"), return 0;
|
|
276
290
|
length($cc) != 2 and warn("Country code '${cc}' is not 2 characters\n"), return 0;
|
|
277
|
-
$fc = $
|
|
291
|
+
$featureTypes{$fc} = $1 if $fc =~ s/ (.*)//;
|
|
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
|
+
}
|
|
300
|
+
}
|
|
278
301
|
chomp $lon; # (just in case it was read from file)
|
|
279
302
|
# create reverse lookups for country/region/subregion/timezone if not done already
|
|
280
303
|
# (eg. if the entries are being added manually instead of via UserDefined::Geolocation)
|
|
@@ -307,7 +330,7 @@ sub AddEntry(@)
|
|
|
307
330
|
}
|
|
308
331
|
my $sn = $subregionNum{lc $subregion};
|
|
309
332
|
unless (defined $sn) {
|
|
310
|
-
my $max = $dbVer eq '1.02' ?
|
|
333
|
+
my $max = $dbVer eq '1.02' ? 0x7fff : 0xffff;
|
|
311
334
|
$#subregionList >= $max and warn("AddEntry: Too many subregions\n"), return 0;
|
|
312
335
|
push @subregionList, $subregion;
|
|
313
336
|
$sn = $subregionNum{lc $subregion} = $#subregionList;
|
|
@@ -320,13 +343,13 @@ sub AddEntry(@)
|
|
|
320
343
|
if ($dbVer eq '1.02') {
|
|
321
344
|
$sn |= 0x8000;
|
|
322
345
|
} else {
|
|
323
|
-
$
|
|
346
|
+
$fn |= 0x80;
|
|
324
347
|
}
|
|
325
348
|
$tn -= 256;
|
|
326
349
|
}
|
|
327
350
|
$lat = int(($lat + 90) / 180 * 0x100000 + 0.5) & 0xfffff;
|
|
328
351
|
$lon = int(($lon + 180) / 360 * 0x100000 + 0.5) & 0xfffff;
|
|
329
|
-
my $hdr = pack('nCnNnCC', $lat>>4, (($lat&0x0f)<<4)|($lon&0x0f), $lon>>4, $code, $sn, $tn, $
|
|
352
|
+
my $hdr = pack('nCnNnCC', $lat>>4, (($lat&0x0f)<<4)|($lon&0x0f), $lon>>4, $code, $sn, $tn, $fn);
|
|
330
353
|
push @cityList, "$hdr$city";
|
|
331
354
|
# add altNames entry if provided
|
|
332
355
|
if ($altNames) {
|
|
@@ -349,34 +372,35 @@ sub AddEntry(@)
|
|
|
349
372
|
# Inputs: 0) entry number or index into sorted database,
|
|
350
373
|
# 1) optional language code, 2) flag to use index into sorted database
|
|
351
374
|
# Returns: 0-10) city,region,subregion,country_code,country,timezone,
|
|
352
|
-
# feature_code,pop,lat,lon,
|
|
375
|
+
# feature_code,pop,lat,lon,feature_type
|
|
353
376
|
sub GetEntry($;$$)
|
|
354
377
|
{
|
|
355
378
|
my ($entryNum, $lang, $sort) = @_;
|
|
356
379
|
return() if $entryNum > $#cityList;
|
|
357
380
|
$entryNum = $sortOrder[$entryNum] if $sort and @sortOrder > $entryNum;
|
|
358
|
-
my ($lt,$f,$ln,$code,$sn,$tn,$
|
|
381
|
+
my ($lt,$f,$ln,$code,$sn,$tn,$fn) = unpack('nCnNnCC', $cityList[$entryNum]);
|
|
359
382
|
my $city = substr($cityList[$entryNum],13);
|
|
360
383
|
my $ctry = $countryList[$code >> 24];
|
|
361
384
|
my $rgn = $regionList[$code & 0x0fff];
|
|
362
385
|
if ($dbVer eq '1.02') {
|
|
363
386
|
$sn & 0x8000 and $tn += 256, $sn &= 0x7fff;
|
|
364
387
|
} else {
|
|
365
|
-
$
|
|
388
|
+
$fn & 0x80 and $tn += 256;
|
|
366
389
|
}
|
|
367
390
|
my $sub = $subregionList[$sn];
|
|
368
391
|
# convert population digits back into exponent format
|
|
369
392
|
my $pop = (($code>>16 & 0x0f) . '.' . ($code>>12 & 0x0f) . 'e+' . ($code>>20 & 0x0f)) + 0;
|
|
370
393
|
$lt = sprintf('%.4f', (($lt<<4)|($f >> 4)) * 180 / 0x100000 - 90);
|
|
371
394
|
$ln = sprintf('%.4f', (($ln<<4)|($f & 0x0f))* 360 / 0x100000 - 180);
|
|
372
|
-
$fc = $featureCodes[$
|
|
395
|
+
my $fc = $featureCodes[$fn & 0x3f] || 'Other';
|
|
373
396
|
my $cc = substr($ctry, 0, 2);
|
|
374
397
|
my $country = substr($ctry, 2);
|
|
375
|
-
|
|
398
|
+
my $ft = $featureTypes{$fc};
|
|
399
|
+
if ($lang and $lang ne 'en') {
|
|
376
400
|
my $xlat = $langLookup{$lang};
|
|
377
401
|
# load language lookups if not done already
|
|
378
402
|
if (not defined $xlat) {
|
|
379
|
-
if (eval "require '$
|
|
403
|
+
if (eval "require '$geoDir/GeoLang/$lang.pm'") {
|
|
380
404
|
my $trans = "Image::ExifTool::GeoLang::${lang}::Translate";
|
|
381
405
|
no strict 'refs';
|
|
382
406
|
$xlat = \%$trans if %$trans;
|
|
@@ -406,9 +430,10 @@ sub GetEntry($;$$)
|
|
|
406
430
|
$sub = $$xlat{"$cc$rgn,$sub,"} || $$xlat{$sub} || $sub;
|
|
407
431
|
$rgn = $$xlat{"$cc$rgn,"} || $$xlat{$rgn} || $rgn;
|
|
408
432
|
$country = $$xlat{"$cc,"} || $$xlat{$country} || $country;
|
|
433
|
+
$ft = $$xlat{$fc} if $$xlat{$fc};
|
|
409
434
|
}
|
|
410
435
|
}
|
|
411
|
-
return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln);
|
|
436
|
+
return($city,$rgn,$sub,$cc,$country,$timezoneList[$tn],$fc,$pop,$lt,$ln,$ft);
|
|
412
437
|
}
|
|
413
438
|
|
|
414
439
|
#------------------------------------------------------------------------------
|
|
@@ -429,17 +454,16 @@ sub GetAltNames($;$)
|
|
|
429
454
|
# Look up lat,lon or city in geolocation database
|
|
430
455
|
# Inputs: 0) "lat,lon", "city,region,country", etc, (city must be first)
|
|
431
456
|
# 1) options hash reference (or undef for no options)
|
|
432
|
-
# Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames
|
|
433
|
-
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
|
|
438
|
-
sub Geolocate($;$$$$$)
|
|
457
|
+
# Options: GeolocMinPop, GeolocMaxDist, GeolocMulti, GeolocFeature, GeolocAltNames,
|
|
458
|
+
# GeolocNearby
|
|
459
|
+
# Returns: List of matching city information, empty if none found.
|
|
460
|
+
# Each element in the list is an array with 0=index of city in database,
|
|
461
|
+
# 1=distance in km (or undef if no distance), 2=compass bearing (or undef)
|
|
462
|
+
sub Geolocate($;$)
|
|
439
463
|
{
|
|
440
464
|
my ($arg, $opts) = @_;
|
|
441
465
|
my ($city, @exact, %regex, @multiCity, $other, $idx, @cargs, $useLastFound);
|
|
442
|
-
my ($minPop, $minDistU, $minDistC, @matchParms, @coords,
|
|
466
|
+
my ($minPop, $minDistU, $minDistC, @matchParms, @coords, %fcOK, $both);
|
|
443
467
|
my ($pop, $maxDist, $multi, $fcodes, $altNames, @startTime);
|
|
444
468
|
|
|
445
469
|
$opts and ($pop, $maxDist, $multi, $fcodes, $altNames) =
|
|
@@ -449,7 +473,7 @@ sub Geolocate($;$$$$$)
|
|
|
449
473
|
require Time::HiRes;
|
|
450
474
|
@startTime = Time::HiRes::gettimeofday();
|
|
451
475
|
}
|
|
452
|
-
@cityList or warn('No Geolocation database'), return
|
|
476
|
+
@cityList or warn('No Geolocation database'), return();
|
|
453
477
|
# make population code for comparing with 2 bytes at offset 6 in database
|
|
454
478
|
if ($pop) {
|
|
455
479
|
$pop = sprintf('%.1e', $pop);
|
|
@@ -457,17 +481,18 @@ sub Geolocate($;$$$$$)
|
|
|
457
481
|
}
|
|
458
482
|
if ($fcodes) {
|
|
459
483
|
my $neg = $fcodes =~ s/^-//;
|
|
460
|
-
my @fcodes = split /\s
|
|
484
|
+
my @fcodes = split /\s*,-?\s*/, lc $fcodes; # (allow leading dash on subsequent codes)
|
|
461
485
|
if ($neg) {
|
|
462
|
-
$
|
|
463
|
-
defined $featureCodes{
|
|
486
|
+
$fcOK{$_} = 1 foreach 0..$#featureCodes;
|
|
487
|
+
defined $featureCodes{$_} and delete $fcOK{$featureCodes{$_}} foreach @fcodes;
|
|
464
488
|
} else {
|
|
465
|
-
defined $featureCodes{
|
|
489
|
+
defined $featureCodes{$_} and $fcOK{$featureCodes{$_}} = 1 foreach @fcodes;
|
|
466
490
|
}
|
|
467
491
|
}
|
|
468
492
|
#
|
|
469
493
|
# process input argument
|
|
470
494
|
#
|
|
495
|
+
my $num = 1;
|
|
471
496
|
$arg =~ s/^\s+//; $arg =~ s/\s+$//; # remove leading/trailing spaces
|
|
472
497
|
my @args = split /\s*,\s*/, $arg;
|
|
473
498
|
my %ri = ( cc => 0, co => 1, re => 2, sr => 3, ci => 8, '' => 9 );
|
|
@@ -486,6 +511,8 @@ sub Geolocate($;$$$$$)
|
|
|
486
511
|
push @coords, $_ if @coords < 2;
|
|
487
512
|
} elsif (lc $_ eq 'both') {
|
|
488
513
|
$both = 1;
|
|
514
|
+
} elsif ($_ =~ /^num=(\d+)$/i) {
|
|
515
|
+
$num = $1;
|
|
489
516
|
} elsif ($_) {
|
|
490
517
|
push @cargs, $_;
|
|
491
518
|
if ($city) {
|
|
@@ -497,7 +524,7 @@ sub Geolocate($;$$$$$)
|
|
|
497
524
|
}
|
|
498
525
|
unless (defined $city or @coords == 2) {
|
|
499
526
|
warn("Insufficient information to determine geolocation\n");
|
|
500
|
-
return
|
|
527
|
+
return();
|
|
501
528
|
}
|
|
502
529
|
# sort database by logitude if finding entry based on coordinates
|
|
503
530
|
SortDatabase('Latitude') if @coords == 2 and ($both or not defined $city);
|
|
@@ -551,7 +578,7 @@ Entry: for (; $i<@cityList; ++$i) {
|
|
|
551
578
|
$str !~ $_ or next Entry foreach @{$regex{19}};
|
|
552
579
|
}
|
|
553
580
|
# test feature code and population
|
|
554
|
-
next if $
|
|
581
|
+
next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
|
|
555
582
|
my $pc = substr($cityList[$i],6,2);
|
|
556
583
|
if (not defined $minPop or $pc ge $minPop) {
|
|
557
584
|
$lastFound{$i} = $pc;
|
|
@@ -561,16 +588,14 @@ Entry: for (; $i<@cityList; ++$i) {
|
|
|
561
588
|
@startTime and printf("= Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
|
|
562
589
|
if (%lastFound) {
|
|
563
590
|
@coords == 2 and $useLastFound = 1, last; # continue to use coords with last city matches
|
|
564
|
-
scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return
|
|
591
|
+
scalar(keys %lastFound) > 200 and warn("Too many matching cities\n"), return();
|
|
565
592
|
unless (@lastByPop) {
|
|
566
593
|
@lastByPop = sort { $lastFound{$b} cmp $lastFound{$a} or $cityList[$a] cmp $cityList[$b] } keys %lastFound;
|
|
567
594
|
}
|
|
568
|
-
|
|
569
|
-
return($n, [ @lastByPop ]) if $n > 1 and $multi;
|
|
570
|
-
return($n, $lastByPop[0]);
|
|
595
|
+
return(\@lastByPop);
|
|
571
596
|
}
|
|
572
597
|
warn "No such city in Geolocation database\n";
|
|
573
|
-
return
|
|
598
|
+
return();
|
|
574
599
|
}
|
|
575
600
|
#
|
|
576
601
|
# determine Geolocation based on GPS coordinates
|
|
@@ -606,9 +631,11 @@ Entry: for (; $i<@cityList; ++$i) {
|
|
|
606
631
|
my ($inc, $end, $n) = (-1, -1, $n0+1);
|
|
607
632
|
my ($p0, $t0) = ($lat*$pi/0x100000 - $pi/2, $lon*$pi/0x080000 - $pi);
|
|
608
633
|
my $cp0 = cos($p0);
|
|
634
|
+
my (@matches, @rtnList, @dist);
|
|
635
|
+
|
|
609
636
|
for (;;) {
|
|
610
637
|
if (($n += $inc) == $end) {
|
|
611
|
-
last if $inc == 1;
|
|
638
|
+
last if $inc == 1 or $n0 == $n1;
|
|
612
639
|
($inc, $end, $n) = (1, $numEntries, $n1);
|
|
613
640
|
}
|
|
614
641
|
my $i = $sorted ? $$sorted[$n] : $n;
|
|
@@ -619,28 +646,56 @@ Entry: for (; $i<@cityList; ++$i) {
|
|
|
619
646
|
abs($lt - $lat) > $minDistC and $n = $end - $inc, next;
|
|
620
647
|
# ignore if population is below threshold
|
|
621
648
|
next if defined $minPop and $minPop ge substr($cityList[$i],6,2);
|
|
622
|
-
next if $
|
|
649
|
+
next if $fcodes and not $fcOK{ord(substr($cityList[$i],12,1)) & 0x3f};
|
|
623
650
|
$ln = ($ln << 4) | ($f & 0x0f);
|
|
624
651
|
# calculate great circle distance to this city on unit sphere
|
|
625
652
|
my ($p1, $t1) = ($lt*$pi/0x100000 - $pi/2, $ln*$pi/0x080000 - $pi);
|
|
626
653
|
my ($sp, $st) = (sin(($p1-$p0)/2), sin(($t1-$t0)/2));
|
|
627
654
|
my $a = $sp * $sp + $cp0 * cos($p1) * $st * $st;
|
|
628
|
-
my $distU = atan2(sqrt($a), sqrt(1-$a));
|
|
655
|
+
my $distU = atan2(sqrt($a), sqrt(1-$a)); # distance on unit sphere
|
|
629
656
|
next if $distU > $minDistU;
|
|
630
|
-
$minDistU = $distU;
|
|
631
|
-
$minDistC = $minDistU * 0x200000 / $pi;
|
|
632
657
|
@matchParms = ($i, $p1, $t1, $distU);
|
|
658
|
+
if ($num <= 1) {
|
|
659
|
+
$minDistU = $distU;
|
|
660
|
+
} else {
|
|
661
|
+
my $j;
|
|
662
|
+
# add this entry into list of matching cities ordered by closest first
|
|
663
|
+
for ($j=0; $j<@matches; ++$j) {
|
|
664
|
+
last if $distU < $matches[$j][3];
|
|
665
|
+
}
|
|
666
|
+
if ($j < $#matches) {
|
|
667
|
+
splice @matches, $j, 0, [ @matchParms ];
|
|
668
|
+
} else {
|
|
669
|
+
$matches[$j] = [ @matchParms ];
|
|
670
|
+
}
|
|
671
|
+
# restrict list to the specified number of nearest cities
|
|
672
|
+
pop @matches if @matches > $num;
|
|
673
|
+
# update minimum distance with furthest match if we satisfied our quota
|
|
674
|
+
$minDistU = $matches[-1][3] if @matches >= $num;
|
|
675
|
+
}
|
|
676
|
+
$minDistC = $minDistU * 0x200000 / $pi; # distance in scaled coordinate units
|
|
633
677
|
}
|
|
634
|
-
@matchParms or warn("No suitable location in Geolocation database\n"), return
|
|
635
|
-
|
|
636
|
-
# calculate distance in km and bearing to matching city
|
|
637
|
-
my ($ii, $p1, $t1, $distU) = @matchParms;
|
|
638
|
-
my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
|
|
639
|
-
my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
|
|
640
|
-
$be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
|
|
678
|
+
@matchParms or warn("No suitable location in Geolocation database\n"), return();
|
|
679
|
+
$num = @matches;
|
|
641
680
|
|
|
642
681
|
@startTime and printf("- Processing time: %.3f sec\n", Time::HiRes::tv_interval(\@startTime));
|
|
643
|
-
|
|
682
|
+
|
|
683
|
+
for (;;) {
|
|
684
|
+
if ($num > 1) {
|
|
685
|
+
last unless @matches;
|
|
686
|
+
@matchParms = @{$matches[0]};
|
|
687
|
+
shift @matches;
|
|
688
|
+
}
|
|
689
|
+
# calculate distance in km and bearing to matching city
|
|
690
|
+
my ($ii, $p1, $t1, $distU) = @matchParms;
|
|
691
|
+
my $km = sprintf('%.2f', 2 * $earthRadius * $distU);
|
|
692
|
+
my $be = atan2(sin($t1-$t0)*cos($p1-$p0), $cp0*sin($p1)-sin($p0)*cos($p1)*cos($t1-$t0));
|
|
693
|
+
$be = int($be * 180 / $pi + 360.5) % 360; # convert from radians to integer degrees
|
|
694
|
+
push @rtnList, $ii;
|
|
695
|
+
push @dist, [ $km, $be ];
|
|
696
|
+
last if $num <= 1;
|
|
697
|
+
}
|
|
698
|
+
return(\@rtnList, \@dist);
|
|
644
699
|
}
|
|
645
700
|
|
|
646
701
|
1; #end
|
|
@@ -691,10 +746,10 @@ True on success.
|
|
|
691
746
|
=head2 ReadAltNames
|
|
692
747
|
|
|
693
748
|
Load the alternate names database. Before calling this method the $altDir
|
|
694
|
-
package variable
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
749
|
+
package variable may be set, otherwise AltNames.dat is loaded from the same
|
|
750
|
+
directory as Geolocation.dat. This method is called automatically by
|
|
751
|
+
L</Geolocate> if the GeolocAltNames option is used and an input city name is
|
|
752
|
+
provided.
|
|
698
753
|
|
|
699
754
|
Image::ExifTool::Geolocation::ReadAltNames();
|
|
700
755
|
|
|
@@ -706,8 +761,8 @@ option is used and an input city name is provided.
|
|
|
706
761
|
|
|
707
762
|
=item Return Value:
|
|
708
763
|
|
|
709
|
-
True on success.
|
|
710
|
-
|
|
764
|
+
True on success. May be called repeatedly, but AltNames.dat is loaded only
|
|
765
|
+
on the first call.
|
|
711
766
|
|
|
712
767
|
=back
|
|
713
768
|
|
|
@@ -810,6 +865,8 @@ item Return Values:
|
|
|
810
865
|
|
|
811
866
|
9) GPS longitude
|
|
812
867
|
|
|
868
|
+
10) Feature type, or undef
|
|
869
|
+
|
|
813
870
|
=back
|
|
814
871
|
|
|
815
872
|
=head2 GetAltNames
|
|
@@ -833,8 +890,7 @@ Comma-separated string of alternate names for this city.
|
|
|
833
890
|
|
|
834
891
|
=item Notes:
|
|
835
892
|
|
|
836
|
-
|
|
837
|
-
calling this routine.
|
|
893
|
+
L</ReadAltNames> must be called before calling this routine.
|
|
838
894
|
|
|
839
895
|
=back
|
|
840
896
|
|
|
@@ -854,37 +910,46 @@ zero or more of the following in any order, separated by commas: region
|
|
|
854
910
|
name, subregion name, country code, and/or country name. Regular
|
|
855
911
|
expressions in C</expr/> format are also allowed, optionally prefixed by
|
|
856
912
|
"ci", "re", "sr", "cc" or "co" to specifically match City, Region,
|
|
857
|
-
Subregion, CountryCode or Country name.
|
|
858
|
-
|
|
913
|
+
Subregion, CountryCode or Country name. Two special controls may be added
|
|
914
|
+
to the argument list:
|
|
859
915
|
|
|
860
|
-
|
|
916
|
+
'both' - When search input includes both name and GPS coordinates, use
|
|
917
|
+
both to determine the closest city matching the specified
|
|
918
|
+
name(s) instead of using GPS only.
|
|
861
919
|
|
|
862
|
-
|
|
920
|
+
'num=##' - When the search includes GPS coordinates, return the nearest
|
|
921
|
+
## cities instead of just the closest one. Returned cities
|
|
922
|
+
are in the order from nearest to farthest.
|
|
863
923
|
|
|
864
|
-
|
|
865
|
-
GPS position is used
|
|
924
|
+
See L<https://exiftool.org/geolocation.html#Read> for more details.
|
|
866
925
|
|
|
867
|
-
|
|
868
|
-
match. In this case the return value is a list of city
|
|
869
|
-
information lists.
|
|
926
|
+
1) Optional reference to hash of options:
|
|
870
927
|
|
|
871
|
-
|
|
872
|
-
|
|
928
|
+
GeolocMinPop - Minimum population of cities to consider in search.
|
|
929
|
+
Default 0.
|
|
873
930
|
|
|
874
|
-
|
|
875
|
-
|
|
931
|
+
GeolocMaxDist - Maximum distance (km) to search for cities when an
|
|
932
|
+
input GPS position is used. Default infinity.
|
|
876
933
|
|
|
877
|
-
|
|
934
|
+
GeolocMulti - Flag to return multiple cities if there is more than
|
|
935
|
+
one match. Used in the case where no input GPS
|
|
936
|
+
coordinates are provided. Default 0.
|
|
937
|
+
|
|
938
|
+
GeolocFeature - Comma-separated list of feature codes to include in
|
|
939
|
+
search, or exclude if the list starts with a dash (-).
|
|
940
|
+
Default undef.
|
|
878
941
|
|
|
879
|
-
|
|
942
|
+
GeolocAltNames - Flag to search alternate names database if available
|
|
943
|
+
for matching city name (see ALTERNATE DATABASES below).
|
|
944
|
+
Default undef.
|
|
880
945
|
|
|
881
|
-
|
|
882
|
-
reference to a list of entry numbers of matching cities if multiple matches
|
|
883
|
-
were found and the flag was set to return multiple matches
|
|
946
|
+
=item Return Values:
|
|
884
947
|
|
|
885
|
-
|
|
948
|
+
0) Reference to list of database entry numbers for matching cities, or undef
|
|
949
|
+
if no matches were found.
|
|
886
950
|
|
|
887
|
-
|
|
951
|
+
1) Reference to list of distance/bearing pairs for each matching city, or
|
|
952
|
+
undef if the search didn't provide GPS coordinates.
|
|
888
953
|
|
|
889
954
|
=back
|
|
890
955
|
|
|
@@ -896,11 +961,12 @@ contain the Geolocation.dat file, and optionally a GeoLang directory for the
|
|
|
896
961
|
language translations. The $geoDir variable may be set to an empty string
|
|
897
962
|
to disable loading of a database.
|
|
898
963
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
a
|
|
903
|
-
|
|
964
|
+
When searching for a city by name, AltNames.dat is checked to provide
|
|
965
|
+
additional possibilities for matches if the GeolocAltNames option is set and
|
|
966
|
+
an AltNames.dat database exists. The package $altDir variable may be set to
|
|
967
|
+
specify a different directory for AltNames.dat, otherwise the
|
|
968
|
+
Geolocation.dat directory is assumed. The entries in AltNames.dat must
|
|
969
|
+
match those in the currently loaded version of Geolocation.dat.
|
|
904
970
|
|
|
905
971
|
=head1 ADDING USER-DEFINED DATABASE ENTRIES
|
|
906
972
|
|
|
@@ -911,7 +977,7 @@ technique before the Geolocation module is loaded.
|
|
|
911
977
|
# city, region, subregion, country code, country, timezone,
|
|
912
978
|
['Sinemorets','burgas','Obshtina Tsarevo','BG','','Europe/Sofia',
|
|
913
979
|
# feature code, population, lat, lon
|
|
914
|
-
'',400,42.06115,27.97833],
|
|
980
|
+
'PPL',400,42.06115,27.97833],
|
|
915
981
|
);
|
|
916
982
|
|
|
917
983
|
Similarly, user-defined language translations may be defined, and will
|