Image-ExifTool
view release on metacpan or search on metacpan
lib/Image/ExifTool/Geotag.pm view on Meta::CPAN
# - the fix information hash may contain:
# lat - signed latitude (required)
# lon - signed longitude (required)
# alt - signed altitude
# time - fix time in UTC as XML string
# fixtype- type of fix ('none'|'2d'|'3d'|'dgps'|'pps')
# pdop - dilution of precision
# hdop - horizontal DOP
# vdop - vertical DOP
# sats - comma-separated list of active satellites
# nsats - number of active satellites
# track - track heading (deg true)
# dir - image direction (deg true)
# pitch - pitch angle (deg)
# roll - roll angle (deg)
# speed - speed (knots)
# first - flag set for first fix of track
# - concatenates new data with existing track data stored in ExifTool NEW_VALUE
# for the Geotag tag
sub LoadTrackLog($$;$)
{
local ($_, $/, *EXIFTOOL_TRKFILE);
my ($et, $val) = @_;
my ($raf, $from, $time, $isDate, $noDate, $noDateChanged, $lastDate, $dateFlarm);
my ($nmeaStart, $fixSecs, @fixTimes, $lastFix, %nmea, @csvHeadings, $sortFixes);
my ($canCut, $cutPDOP, $cutHDOP, $cutSats, $e0, $e1, @tmp, $trackFile, $trackTime);
my ($scaleSpeed, $startTime);
unless (eval { require Time::Local }) {
return 'Geotag feature requires Time::Local installed';
}
InitUserTags($et);
# add data to existing track
my $geotag = $et->GetNewValue('Geotag') || { };
# initialize track points lookup
my $points = $$geotag{Points};
$points or $points = $$geotag{Points} = { };
# get lookup for available information types
my $has = $$geotag{Has};
$has or $has = $$geotag{Has} = { 'pos' => 1 };
my $format = '';
# is $val track log data?
if ($val =~ /^(\xef\xbb\xbf)?<(\?xml|gpx)[\s>]/) {
$format = 'XML';
$/ = '>'; # set input record separator to '>' for XML/GPX data
} elsif ($val =~ /(\x0d\x0a|\x0d|\x0a)/) {
$/ = $1;
} else {
# $val is track file name
if ($et->Open(\*EXIFTOOL_TRKFILE, $val)) {
$trackFile = $val;
$raf = File::RandomAccess->new(\*EXIFTOOL_TRKFILE);
unless ($raf->Read($_, 256)) {
close EXIFTOOL_TRKFILE;
return "Empty track file '${val}'";
}
# look for XML or GPX header (might as well allow UTF-8 BOM)
if (/^(\xef\xbb\xbf)?<(\?xml|gpx)[\s>]/) {
$format = 'XML';
$/ = '>'; # set input record separator to '>' for XML/GPX data
} elsif (/(\x0d\x0a|\x0d|\x0a)/) {
$/ = $1;
} else {
close EXIFTOOL_TRKFILE;
return "Invalid track file '${val}'";
}
$raf->Seek(0,0);
$from = "file '${val}'";
} elsif ($val eq 'DATETIMEONLY') {
$$geotag{DateTimeOnly} = 1;
$$geotag{IsDate} = 1;
$et->VPrint(0, 'Geotagging date/time only');
return $geotag;
} else {
return "Error opening GPS file '${val}'";
}
}
unless ($from) {
# set up RAF for reading log file in memory
$raf = File::RandomAccess->new(\$val);
$from = 'data';
}
# initialize cuts
my $maxHDOP = $et->Options('GeoMaxHDOP');
my $maxPDOP = $et->Options('GeoMaxPDOP');
my $minSats = $et->Options('GeoMinSats');
my $isCut = $maxHDOP || $maxPDOP || $minSats;
my $numPoints = 0;
my $skipped = 0;
my $lastSecs = 0;
my $fix = { };
my $csvDelim = $et->Options('CSVDelim');
$csvDelim = ',' unless defined $csvDelim;
my (@saveFix, @saveTime, $timeSpan);
for (;;) {
$raf->ReadLine($_) or last;
# determine file format
if (not $format) {
s/^\xef\xbb\xbf//; # remove leading BOM if it exists
if (/^\xff\xfe|\xfe\xff/) {
return "ExifTool doesn't yet read UTF16-format track logs";
}
if (/^<(\?xml|gpx)[\s>]/) { # look for XML or GPX header
$format = 'XML';
# check for NMEA sentence
# (must ONLY start with ones that have timestamps! eg. not GSA or PTNTHPR!)
} elsif (/^.*\$([A-Z]{2}(RMC|GGA|GLL|ZDA)|PMGNTRK),/) {
$format = 'NMEA';
$nmeaStart = $2 || $1; # save type of first sentence
} elsif (/^A(FLA|XSY|FIL)/) {
# (don't set format yet because we want to read HFDTE first)
$nmeaStart = 'B' ;
next;
} elsif (/^HFDTE(?:DATE:)?(\d{2})(\d{2})(\d{2})/) {
my $year = $3 + ($3 >= 70 ? 1900 : 2000);
$dateFlarm = Time::Local::timegm(0,0,0,$1,$2-1,$year);
$nmeaStart = 'B' ;
$format = 'IGC';
next;
} elsif ($nmeaStart and /^B/) {
# parse IGC fixes without a date
$format = 'IGC';
} elsif (/^TP,D,/) {
$format = 'Winplus';
} elsif (/^\s*\d+\s+.*\sypr\s*$/ and (@tmp=split) == 12) {
$format = 'Bramor';
} elsif (((/\b(GPS)?Date/i and /\b(GPS)?(Date)?Time/i) or /\bTime\(seconds\)/i) and /\Q$csvDelim/) {
chomp;
@csvHeadings = SplitCSV($_, $csvDelim);
my $isColumbus = ($csvHeadings[0] and $csvHeadings[0] eq 'INDEX'); # (Columbus GPS logger)
$format = 'CSV';
# convert recognized headings to our parameter names
foreach (@csvHeadings) {
my $head = $_;
my $param;
my $xtra = '';
s/^GPS ?//; # remove leading "GPS" to simplify regex patterns
if (/^Time ?\(seconds\)$/i) { # DJI
# DJI CSV log files have a column "Time(seconds)" which is seconds since
# the start of the flight. The date/time is obtained from the file name.
$param = 'runtime';
if ($trackFile and $trackFile =~ /(\d{4})-(\d{2})-(\d{2})[^\/]+(\d{2})-(\d{2})-(\d{2})[^\/]*$/) {
$trackTime = Image::ExifTool::TimeLocal($6,$5,$4,$3,$2-1,$1);
my $utc = PrintFixTime($trackTime);
my $tzs = Image::ExifTool::TimeZoneString([$6,$5,$4,$3,$2-1,$1-1900],$trackTime);
$et->VPrint(2, " DJI start time: $utc (local timezone is $tzs)\n");
} else {
return 'Error getting start time from file name for DJI CSV track file';
}
} elsif (/^Date ?Time/i) { # ExifTool addition
$param = 'datetime';
} elsif (/^Date/i) {
$param = 'date';
} elsif (/^Time(?! ?\(text\))/i) { # (ignore DJI "Time(text)" column)
$param = 'time';
} elsif (/^(Pos)?Lat/i) {
$param = 'lat';
/ref$/i and $param .= 'ref';
} elsif (/^(Pos)?Lon/i) {
( run in 1.699 second using v1.01-cache-2.11-cpan-39bf76dae61 )