Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Line2, Line3, SignalStats, Temperature python interface #220

Merged
merged 5 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions include/ignition/math/Line2.hh
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ namespace ignition
double _epsilon = 1e-6) const
{
return math::equal(this->CrossProduct(_pt),
static_cast<T>(0), _epsilon);
0., _epsilon);
}

/// \brief Check if the given line is parallel with this line.
Expand All @@ -124,7 +124,7 @@ namespace ignition
double _epsilon = 1e-6) const
{
return math::equal(this->CrossProduct(_line),
static_cast<T>(0), _epsilon);
0., _epsilon);
}

/// \brief Check if the given line is collinear with this line. This
Expand Down
10 changes: 7 additions & 3 deletions src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ if (PYTHONLIBS_FOUND)

# Suppress warnings on SWIG-generated files
target_compile_options(${SWIG_PY_LIB} PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wno-pedantic -Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
$<$<CXX_COMPILER_ID:Clang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers>
$<$<CXX_COMPILER_ID:GNU>:-Wno-pedantic -Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers -Wno-class-memaccess>
$<$<CXX_COMPILER_ID:Clang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers -Wno-class-memaccess>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-shadow -Wno-maybe-uninitialized -Wno-unused-parameter -Wno-cast-function-type -Wno-missing-field-initializers -Wno-class-memaccess>
)
install(TARGETS ${SWIG_PY_LIB} DESTINATION ${IGN_LIB_INSTALL_DIR}/python/ignition)
install(FILES ${CMAKE_BINARY_DIR}/lib/python/math.py DESTINATION ${IGN_LIB_INSTALL_DIR}/python/ignition)
Expand All @@ -70,11 +70,15 @@ if (PYTHONLIBS_FOUND)
set(python_tests
Angle_TEST
GaussMarkovProcess_TEST
Line2_TEST
Line3_TEST
python_TEST
Rand_TEST
SignalStats_TEST
Vector2_TEST
Vector3_TEST
Vector4_TEST
Temperature_TEST
)

foreach (test ${python_tests})
Expand Down
83 changes: 83 additions & 0 deletions src/python/Line2.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2021 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

%module line2
%{
#include <ignition/math/Line2.hh>
#include <ignition/math/Helpers.hh>
#include <ignition/math/Vector2.hh>
%}

%include "std_string.i"

namespace ignition
{
namespace math
{
template<typename T>
class Line2
{
%rename("%(undercase)s", %$isfunction, %$ismember, %$not %$isconstructor) "";
public: Line2(const math::Vector2<T> &_ptA, const math::Vector2<T> &_ptB);
public: Line2(double _x1, double _y1, double _x2, double _y2);
public: void Set(const math::Vector2<T> &_ptA,
const math::Vector2<T> &_ptB);
public: void Set(double _x1, double _y1, double _x2, double _y2);
public: double CrossProduct(const Line2<T> &_line) const;
public: double CrossProduct(const Vector2<T> &_pt) const;
public: bool Collinear(const math::Vector2<T> &_pt,
double _epsilon = 1e-6) const;
public: bool Parallel(const math::Line2<T> &_line,
double _epsilon = 1e-6) const;
public: bool Collinear(const math::Line2<T> &_line,
double _epsilon = 1e-6) const;
public: bool OnSegment(const math::Vector2<T> &_pt,
double _epsilon = 1e-6) const;
public: bool Within(const math::Vector2<T> &_pt,
double _epsilon = 1e-6) const;
public: bool Intersect(const Line2<T> &_line,
double _epsilon = 1e-6) const;
public: bool Intersect(const Line2<T> &_line, math::Vector2<T> &_pt,
double _epsilon = 1e-6) const;
public: T Length() const;
public: double Slope() const;
public: bool operator==(const Line2<T> &_line) const;
public: bool operator!=(const Line2<T> &_line) const;
};

%extend Line2
{
ignition::math::Vector2<T> __getitem__(unsigned int i) const
{
return (*$self)[i];
}
}

%extend Line2
{
std::string __str__() const {
std::ostringstream out;
out << *$self;
return out.str();
}
}

%template(Line2i) Line2<int>;
%template(Line2d) Line2<double>;
%template(Line2f) Line2<float>;
}
}
215 changes: 215 additions & 0 deletions src/python/Line2_TEST.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# Copyright (C) 2021 Open Source Robotics Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License")
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import unittest
from ignition.math import Line2d
from ignition.math import Vector2d


class TestLine2d(unittest.TestCase):

def test_construction(self):
line_a = Line2d(0, 0, 10, 10)
self.assertAlmostEqual(line_a[0].x(), 0.0)
self.assertAlmostEqual(line_a[0].y(), 0.0)
self.assertAlmostEqual(line_a[1].x(), 10.0)
self.assertAlmostEqual(line_a[1].y(), 10.0)

line_b = Line2d(Vector2d(1, 2), Vector2d(3, 4))
self.assertAlmostEqual(line_b[0].x(), 1.0)
self.assertAlmostEqual(line_b[0].y(), 2.0)
self.assertAlmostEqual(line_b[1].x(), 3.0)
self.assertAlmostEqual(line_b[1].y(), 4.0)

self.assertAlmostEqual(line_b[2].x(), line_b[1].x())

def test_length(self):
line_a = Line2d(0, 0, 10, 10)
self.assertAlmostEqual(line_a.length(), math.sqrt(200), delta=1e-10)

def test_slope(self):
line = Line2d(0, 0, 10, 10)
self.assertAlmostEqual(line.slope(), 1.0, delta=1e-10)

line = Line2d(0, 0, 0, 10)
self.assertTrue(math.isnan(line.slope()))

line = Line2d(-10, 0, 100, 0)
self.assertAlmostEqual(line.slope(), 0.0)

def test_parallel_line(self):
# Line is always parallel with itself
line = Line2d(0, 0, 10, 0)
self.assertTrue(line.parallel(line, 1e-10))

# Degenerate line segment
# Still expect Line is parallel with itself
line = Line2d(0, 0, 0, 0)
self.assertTrue(line.parallel(line, 1e-10))

line_a = Line2d(0, 0, 10, 0)
line_b = Line2d(0, 0, 10, 0)
self.assertTrue(line_a.parallel(line_b, 1e-10))

line_b.set(0, 0, 0, 10)
self.assertFalse(line_a.parallel(line_b))

line_b.set(0, 10, 10, 10)
self.assertTrue(line_a.parallel(line_b))

line_b.set(0, 10, 10, 10.00001)
self.assertFalse(line_a.parallel(line_b, 1e-10))
self.assertFalse(line_a.parallel(line_b))
self.assertTrue(line_a.parallel(line_b, 1e-3))

def test_collinear_line(self):
# Line is always collinear with itself
line = Line2d(0, 0, 10, 0)
self.assertTrue(line.collinear(line, 1e-10))

line_a = Line2d(0, 0, 10, 0)
line_b = Line2d(0, 0, 10, 0)
self.assertTrue(line_a.collinear(line_b, 1e-10))

line_b.set(0, 10, 10, 10)
self.assertFalse(line_a.collinear(line_b))

line_b.set(9, 0, 10, 0.00001)
self.assertFalse(line_a.collinear(line_b, 1e-10))
self.assertFalse(line_a.collinear(line_b))
self.assertTrue(line_a.collinear(line_b, 1e-3))

def test_collinear_point(self):
line_a = Line2d(0, 0, 10, 0)
pt = Vector2d(0, 0)
self.assertTrue(line_a.collinear(pt))

pt_line = Line2d(pt, pt)
self.assertTrue(line_a.collinear(pt_line))

pt.set(1000, 0)
self.assertTrue(line_a.collinear(pt, 1e-10))

pt_line = Line2d(pt, pt)
self.assertTrue(line_a.parallel(pt_line))
self.assertFalse(line_a.intersect(pt_line))
self.assertFalse(line_a.collinear(pt_line, 1e-10))

pt.set(10, 0)
pt_line.set(pt, pt)
self.assertTrue(line_a.collinear(pt_line, 1e-10))

pt.set(0, 0.00001)
self.assertFalse(line_a.collinear(pt))
self.assertTrue(line_a.collinear(pt, 1e-3))

pt_line = Line2d(pt, pt)
self.assertFalse(line_a.collinear(pt_line))
self.assertTrue(line_a.parallel(pt_line))
self.assertFalse(line_a.intersect(pt_line))
self.assertTrue(line_a.intersect(pt_line, 1e-2))
self.assertTrue(line_a.collinear(pt_line, 1e-3))

pt.set(0, -0.00001)
self.assertFalse(line_a.collinear(pt))
self.assertTrue(line_a.collinear(pt, 1e-3))

pt_line = Line2d(pt, pt)
self.assertFalse(line_a.collinear(pt_line))
self.assertTrue(line_a.collinear(pt_line, 1e-4))

def test_intersect(self):
pt = Vector2d()

# parallel horizontal lines
line_a = Line2d(1, 1, 2, 1)
line_b = Line2d(1, 2, 2, 2)
self.assertFalse(line_a.intersect(line_b, pt))

# parallel vertical lines
line_a.set(1, 1, 1, 10)
line_b.set(2, 1, 2, 10)
self.assertFalse(line_a.intersect(line_b, pt))

# Two lines that form an inverted T with a gap
line_a.set(1, 1, 1, 10)
line_b.set(0, 0, 2, 0)
self.assertFalse(line_a.intersect(line_b, pt))

# Two lines that form a T with a gap
line_a.set(1, 1, 1, 10)
line_b.set(0, 10.1, 2, 10.1)
self.assertFalse(line_a.intersect(line_b, pt))

# Two lines that form an inverted T with a gap
line_a.set(0, -10, 0, 10)
line_b.set(1, 0, 10, 0)
self.assertFalse(line_a.intersect(line_b, pt))

# Two lines that form a T with a gap
line_a.set(0, -10, 0, 10)
line_b.set(-1, 0, -10, 0)
self.assertFalse(line_a.intersect(line_b, pt))

# Two collinear lines, one starts where the other stopped
line_a.set(1, 1, 1, 10)
line_b.set(1, 10, 1, 11)
self.assertTrue(line_a.intersect(line_b, pt))
self.assertEqual(pt, Vector2d(1, 10))

# Two collinear lines, one overlaps the other
line_a.set(0, 0, 0, 10)
line_b.set(0, 9, 0, 11)
self.assertTrue(line_a.intersect(line_b, pt))
self.assertEqual(pt, Vector2d(0, 9))

# Two collinear lines, one overlaps the other
line_a.set(0, 0, 0, 10)
line_b.set(0, -10, 0, 1)
self.assertTrue(line_a.intersect(line_b, pt))
self.assertEqual(pt, Vector2d(0, 1))

# Two intersecting lines
line_a.set(0, 0, 10, 10)
line_b.set(0, 10, 10, 0)
self.assertTrue(line_a.intersect(line_b, pt))
self.assertEqual(pt, Vector2d(5, 5))

def test_equality(self):
line_a = Line2d(1, 1, 2, 1)
line_b = Line2d(1, 2, 2, 2)

self.assertTrue(line_a != line_b)
self.assertTrue(line_a == line_a)

line_b.set(1, 1, 2, 1.1)
self.assertFalse(line_a == line_b)

line_b.set(1, 1, 2.1, 1)
self.assertFalse(line_a == line_b)

line_b.set(1, 1.1, 2, 1)
self.assertFalse(line_a == line_b)

line_b.set(1.1, 1, 2, 1)
self.assertFalse(line_a == line_b)

def test_serialization(self):
line = Line2d(0, 1, 2, 3)
self.assertEqual(str(line), "0 1 2 3")


if __name__ == '__main__':
unittest.main()
Loading