# This file is part of the Owl Positioning System (OwlPS) project. It is # subject to the copyright notice and license terms in the COPYRIGHT.t2t file # found in the top-level directory of this distribution and at # https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t # No part of the OwlPS Project, including this file, may be copied, modified, # propagated, or distributed except according to the terms contained in the # COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be distributed along with this # file, either separately or by replacing this notice by the COPYRIGHT.t2t # file's contents. =head1 NAME OwlPS::TimeInterpolation - interpolate timestamped coordinates =head1 SYNOPSIS use OwlPS::TimeInterpolation; interpolate_1d(1, "1368637546.331881794", 10, "1368637879.566242145", "1368637726.677787231"); interpolate_2d("1;2", "1368637546.331881794", "10;5", "1368637879.566242145", "1368637726.677787231"); interpolate_3d("1;2;1", "1368637546.331881794", "10;5;2", "1368637879.566242145", "1368637726.677787231"); =head1 DESCRIPTION The OwlPS::TimeInterpolation Perl module offers function to interpolate timestamped coordinates. Its main functions are interpolate_1d(), interpolate_2d() and interpolate_3d() (cf. section L below). =head2 Helper functions (not exported by default) The following functions are provided but not exported by default. =cut package OwlPS::TimeInterpolation; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw( interpolate_1d interpolate_2d interpolate_3d ); our @EXPORT_OK = qw( timestamp_cmp elapsed elapsed_sec travel_speed travel_speed_1d distance_travelled linear_interpolation ); our $VERSION = 1.00; use strict; use Carp; use OwlPS::CSV; use Scalar::Util qw(looks_like_number); =head3 Time-related functions =over =cut =item timestamp_cmp($t0, $t1) This function compares two timestamps $t0 and $t1, which are strings under the format "seconds.nanoseconds". It returns: =over =item * 1 if $t0 is before $t1 =item * -1 if $t0 is after $t1 =item * 0 if $t0 and $t1 are equal =back =cut sub timestamp_cmp($$) { my ($t0, $t1) = @_; my ($t0_sec, $t0_nsec) = split_timestamp($t0); my ($t1_sec, $t1_nsec) = split_timestamp($t1); # First test the seconds if ($t1_sec > $t0_sec) { return 1 } if ($t1_sec < $t0_sec) { return -1 } # Seconds are equal, test the nanoseconds if ($t1_nsec > $t0_nsec) { return 1 } if ($t1_nsec < $t0_nsec) { return -1 } # Seconds and nanoseconds are equal return 0; } =item elapsed($t0, $t1) This function calculates the elapsed time between two timestamps $t0 and $t1 (string format: "seconds.nanoseconds"). It returns an array ($seconds, $nanoseconds). =cut sub elapsed($$) { my ($t0, $t1) = @_; my ($t0_sec, $t0_nsec) = split_timestamp($t0); my ($t1_sec, $t1_nsec) = split_timestamp($t1); my $sec = $t1_sec - $t0_sec; my $nsec = $t1_nsec - $t0_nsec; if ($sec == 0) { return (0, abs($nsec)) } if ($sec > 0) { if ($nsec >= 0) { return ($sec, $nsec) } # $nsec < 0 return ($sec - 1, $nsec + 1000000000); } # $sec < 0 if ($nsec > 0) { return (abs($sec) - 1, 1000000000 - $nsec) } # $nsec <= 0 return (abs($sec), abs($nsec)); } =item elapsed_sec($t0, $t1) This function calculates the elapsed time between two timestamps $t0 and $t1, in seconds. It returns the number of seconds (floating point value). =cut sub elapsed_sec($$) { my ($t0, $t1) = @_; my ($t0t1_sec, $t0t1_nsec) = elapsed($t0, $t1); my $total_nsec = $t0t1_sec * 1000000000 + $t0t1_nsec; return $total_nsec / 1000000000; } =back =head3 Distance and speed-related functions =over =cut =item travel_speed($start_time, $stop_time, $distance) This function returns the movement speed, given two timestamp $start_time and $stop_time and a travelled distance $distance. =cut sub travel_speed($$$) { my ($start_time, $stop_time, $distance) = @_; my $time_elapsed = elapsed_sec($start_time, $stop_time); return $distance / $time_elapsed; } =item travel_speed_1d($start_point, $start_time, $stop_point, $stop_time) This function returns the movement speed between two coordinates (1-D points), given the start position $start_point at a timestamp $start_time, and the stop position $stop_point at a timestamp $stop_time. =cut sub travel_speed_1d($$$$) { my ($start_point, $start_time, $stop_point, $stop_time) = @_; if (!looks_like_number($start_point) || !looks_like_number($stop_point)) { croak "1-D coordinates must be numbers"; } my $distance_travelled = abs($stop_point - $start_point); return travel_speed($start_time, $stop_time, $distance_travelled); } =item distance_travelled($start_time, $stop_time, $speed) This function returns the distance travelled between two timestamps $start_time and $stop_time, given the movement speed $speed. =cut sub distance_travelled($$$) { my ($start_time, $stop_time, $speed) = @_; my $time_travelled = elapsed_sec($start_time, $stop_time); return $time_travelled * $speed; } =back =head3 Interpolation-related functions =over =cut =item linear_interpolation($start_point, $stop_point, $distance) Given two 1-D coordinates $start_point and $stop_point, this function computes the interpolated coordinate at $distance from $start_point in the direction of $stop_point. It returns the interpolated 1-D coordinate. =cut sub linear_interpolation($$$) { my ($start_point, $stop_point, $distance) = @_; if (!looks_like_number($start_point) || !looks_like_number($stop_point)) { croak "1-D coordinates must be numbers"; } if ($start_point <= $stop_point) { return $start_point + $distance; } return $start_point - $distance; } =back =head2 Main functions The following functions are exported by default. =over =cut =item interpolate_1d($start_point, $start_time, $stop_point, $stop_time, $time) Given two 1-D coordinates $start_point and $stop_point with their associated timestamps $start_time and $stop_time, and an additional timestamp $time, this function computes the coordinates associated with this additional timestamp. A constant pace is assumed between the two timestamped coordinates. $time should be after the $start_time, and can also be after $stop_time. The interpolated coordinate is returned. =cut sub interpolate_1d($$$$$) { my ($start_point, $start_time, $stop_point, $stop_time, $time) = @_; # Check the timestamps if (timestamp_cmp($start_time, $stop_time) <= 0) { croak "\$start_time must be before \$stop_time"; } if (timestamp_cmp($start_time, $time) < 0) { croak "\$start_time must be equal or before \$time"; } # Compute the travel speed between the two known points my $speed = travel_speed_1d($start_point, $start_time, $stop_point, $stop_time); # Compute the distance travelled between `start_point` and the point # to interpolate my $distance = distance_travelled($start_time, $time, $speed); # Compute and return the interpolated coordinates return linear_interpolation($start_point, $stop_point, $distance); } =item interpolate_2d($start_point, $start_time, $stop_point, $stop_time, $time) This function is similar to interpolate_1d(), but works with 2-D coordinates: $start_point and $stop_point must be strings "X;Y". It returns an array ($x, $y) representing the interpolated point. =cut sub interpolate_2d($$$$$) { my ($start_point, $start_time, $stop_point, $stop_time, $time) = @_; my ($start_x, $start_y) = split_point_2d($start_point); my ($stop_x, $stop_y) = split_point_2d($stop_point); return ( interpolate_1d($start_x, $start_time, $stop_x, $stop_time, $time), interpolate_1d($start_y, $start_time, $stop_y, $stop_time, $time) ); } =item interpolate_3d($start_point, $start_time, $stop_point, $stop_time, $time) This function is similar to interpolate_1d() and interpolate_2d(), but works with 3-D coordinates: $start_point and $stop_point must be strings "X;Y;Z". It returns an array ($x, $y, $z) representing the interpolated point. =cut sub interpolate_3d($$$$$) { my ($start_point, $start_time, $stop_point, $stop_time, $time) = @_; my ($start_x, $start_y, $start_z) = split_point_3d($start_point); my ($stop_x, $stop_y, $stop_z) = split_point_3d($stop_point); return ( interpolate_1d($start_x, $start_time, $stop_x, $stop_time, $time), interpolate_1d($start_y, $start_time, $stop_y, $stop_time, $time), interpolate_1d($start_z, $start_time, $stop_z, $stop_time, $time) ); } =back =head1 COPYING This module and its documentation are part of the Owl Positioning System (OwlPS) project. They are subject to the copyright notice and license terms in the COPYRIGHT.t2t file found in the top-level directory of the OwlPS distribution and at https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t =head1 SEE ALSO owlps(7) =cut # vim: tabstop=4:shiftwidth=4:expandtab:textwidth=80