[scripts] Add OwlPS::{CSV,TimeInterpolation}

Add Perl modules OwlPS::CSV and OwlPS::TimeInterpolation.
This commit is contained in:
Matteo Cypriani 2013-07-26 15:38:36 -04:00
parent 8ef5a55b07
commit 9073013997
2 changed files with 537 additions and 0 deletions

203
scripts/OwlPS/CSV.pm Normal file
View File

@ -0,0 +1,203 @@
# 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
# http://code.lm7.fr/p/owlps/source/tree/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::CSV - manipulate OwlPS CSV strings
=head1 SYNOPSIS
use OwlPS::CSV;
get_field("a;b;c;d", 1); # returns "b"
update_field("a;b;c", 0, "z"); # returns "z;b;c"
split_timestamp("42.123456789"); # returns (42, 123456789)
split_point_2d("4.3;8.1"); # returns (4.3, 8.1)
split_point_2d("3;8;7"); # returns (3, 8, 7)
round2(3.14159); # returns "3.14"
round2(0); # returns "0.00"
=head1 DESCRIPTION
The OwlPS::CSV Perl module provides functions to deal with the CSV format used
in OwlPS. Upon error, all these functions will kill the program. This may change
in future versions.
The following functions are provided and exported by default:
=over
=cut
package OwlPS::CSV;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(
get_field
update_field
split_timestamp
split_point_2d
split_point_3d
round2
);
our $VERSION = 1.00;
use strict;
use Carp;
use Scalar::Util qw(looks_like_number);
=item get_field($string, $field_idx)
This function returns the field number $field_idx of the CSV string $string.
The first field of the string has number 0.
=cut
sub get_field($$) {
my ($string, $field_idx) = @_;
# Split the line
my @fields = split(';', $string);
if (@fields <= $field_idx) {
croak "Bad number of fields in CSV string \"$string\"";
}
return $fields[$field_idx];
}
=item update_field($string, $field_idx, $value)
This function updates the field number $field_idx the CSV string $string with
$value.
It returns the modified string ($string is left untouched).
The first field of the string has number 0.
=cut
sub update_field($$$) {
my ($string, $field_idx, $value) = @_;
# Split the line
my @fields = split(';', $string);
if (@fields <= $field_idx) {
croak "Bad number of fields in CSV string \"$string\"";
}
# Update the corresponding field
$fields[$field_idx] = $value;
# Reconstitute the line
my $newstring = shift @fields;
foreach my $field (@fields) { $newstring .= ";$field" }
return $newstring;
}
=item split_timestamp($timestamp)
This function splits a timestamp $timestamp which is a string
"seconds.nanoseconds".
It returns an array ($seconds, $nanoseconds).
=cut
sub split_timestamp($) {
my $timestamp = $_[0];
my ($sec, $nsec) = split('\.', $timestamp, 2);
foreach ($sec, $nsec) {
if (!(defined && looks_like_number($_))) {
croak "Malformed timestamp string \"$timestamp\"";
}
}
if (length($nsec) != 9) {
croak "Malformed timestamp string \"$timestamp\": the nanosecond field"
. " must have exactly 9 digits";
}
return ($sec, $nsec);
}
=item split_point_2d($point)
This funtion splits the 2-D point $point (string format: "X;Y").
It returns an array ($x, $y).
=cut
sub split_point_2d($) {
my $point = $_[0];
my ($x, $y) = split(';', $point, 2);
foreach ($x, $y) {
if (!(defined && looks_like_number($_))) {
croak "Malformed 2-D point string \"$point\"";
}
}
return ($x, $y);
}
=item split_point_3d($point)
This funtion splits the 3-D point $point (string format: "X;Y;Z").
It returns an array ($x, $y, $z).
=cut
sub split_point_3d($) {
my $point = $_[0];
my ($x, $y, $z) = split(';', $point, 3);
foreach ($x, $y) {
if (!(defined && looks_like_number($_))) {
croak "Malformed 3-D point string \"$point\"";
}
}
return ($x, $y, $z);
}
=item round2($f)
This function rounds a floating point number $f to two decimals.
It returns the rounded value of $f as a string.
=cut
sub round2($) {
my $f = $_[0];
if (!(defined($f) && looks_like_number($f))) {
croak "Not a valid number \"$f\"";
}
return sprintf("%.2f", $f);
}
=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 http://code.lm7.fr/p/owlps/source/tree/master/COPYRIGHT.t2t
=head1 SEE ALSO
owlps(7)
=cut
# vim: tabstop=4:shiftwidth=4:expandtab:textwidth=80

View File

@ -0,0 +1,334 @@
# 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
# http://code.lm7.fr/p/owlps/source/tree/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</"Main functions"> 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 http://code.lm7.fr/p/owlps/source/tree/master/COPYRIGHT.t2t
=head1 SEE ALSO
owlps(7)
=cut
# vim: tabstop=4:shiftwidth=4:expandtab:textwidth=80