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 3 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/Vector2.hh>
#include <ignition/math/Helpers.hh>
francocipollone marked this conversation as resolved.
Show resolved Hide resolved
%}

%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 unittest
import math
francocipollone marked this conversation as resolved.
Show resolved Hide resolved
from ignition.math import Line2d
from ignition.math import Vector2d


class TestLine2d(unittest.TestCase):

def test_construction(self):
lineA = Line2d(0, 0, 10, 10)
francocipollone marked this conversation as resolved.
Show resolved Hide resolved
self.assertAlmostEqual(lineA[0].x(), 0.0)
self.assertAlmostEqual(lineA[0].y(), 0.0)
self.assertAlmostEqual(lineA[1].x(), 10.0)
self.assertAlmostEqual(lineA[1].y(), 10.0)

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

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

def test_length(self):
lineA = Line2d(0, 0, 10, 10)
self.assertAlmostEqual(lineA.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))

lineA = Line2d(0, 0, 10, 0)
lineB = Line2d(0, 0, 10, 0)
self.assertTrue(lineA.parallel(lineB, 1e-10))

lineB.set(0, 0, 0, 10)
self.assertFalse(lineA.parallel(lineB))

lineB.set(0, 10, 10, 10)
self.assertTrue(lineA.parallel(lineB))

lineB.set(0, 10, 10, 10.00001)
self.assertFalse(lineA.parallel(lineB, 1e-10))
self.assertFalse(lineA.parallel(lineB))
self.assertTrue(lineA.parallel(lineB, 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))

lineA = Line2d(0, 0, 10, 0)
lineB = Line2d(0, 0, 10, 0)
self.assertTrue(lineA.collinear(lineB, 1e-10))

lineB.set(0, 10, 10, 10)
self.assertFalse(lineA.collinear(lineB))

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

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

ptLine = Line2d(pt, pt)
self.assertTrue(lineA.collinear(ptLine))

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

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

pt.set(10, 0)
ptLine.set(pt, pt)
self.assertTrue(lineA.collinear(ptLine, 1e-10))

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

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

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

ptLine = Line2d(pt, pt)
self.assertFalse(lineA.collinear(ptLine))
self.assertTrue(lineA.collinear(ptLine, 1e-4))

def test_intersect(self):
pt = Vector2d()

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

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

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

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

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

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

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

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

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

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

def test_equality(self):
lineA = Line2d(1, 1, 2, 1)
lineB = Line2d(1, 2, 2, 2)

self.assertTrue(lineA != lineB)
self.assertTrue(lineA == lineA)

lineB.set(1, 1, 2, 1.1)
self.assertFalse(lineA == lineB)

lineB.set(1, 1, 2.1, 1)
self.assertFalse(lineA == lineB)

lineB.set(1, 1.1, 2, 1)
self.assertFalse(lineA == lineB)

lineB.set(1.1, 1, 2, 1)
self.assertFalse(lineA == lineB)

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


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