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 )