From a8247087070c2ed5cfa2cb86457167ac4576d551 Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sat, 17 Sep 2022 16:38:35 +0300 Subject: [PATCH 01/11] Added Line Iterator --- .../nav2_simple_commander/line_iterator.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 nav2_simple_commander/nav2_simple_commander/line_iterator.py diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py new file mode 100644 index 0000000000..89148d6ae9 --- /dev/null +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -0,0 +1,119 @@ +#! /usr/bin/env python3 +# Copyright 2021 Samsung Research America +# Copyright 2022 Afif Swaidan +# +# 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. + +""" +This is a Python3 API for a line iterator. + +It provides the ability to iterate through the points of a line. +""" + + +class LineIterator(): + + def __init__(self, x0, y0, x1, y1): + """ + Initializer for LineIterator. + + Initialize instance variables with parameter occupancy_map. + Args: + x0: Abscissa of the initial point + y0: Ordinate of the initial point + x1: Abscissa of the final point + y1: Ordinate of the final point + Returns: + None + """ + self.x0_ = x0 + self.y0_ = x0 + self.x1_ = x1 + self.y1_ = y1 + self.x_ = x0 + self.y_ = y0 + + self.delta_x_ = abs(self.x1_ - self.x0_) + self.delta_y_ = abs(self.y1_ - self.y0_) + self.curpixel_ = 0 + self.xinc1_, self.xinc2_, self.yinc1_, self.yinc2_ = 0, 0, 0, 0 + self.num_, self.den_ = 0, 0 + self.numadd_ = 0 + + if (self.x1_ >= x0): + self.xinc1_ = 1 + self.xinc2_ = 1 + else: + self.xinc1_ = -1 + self.xinc2_ = -1 + + if (y1 >= y0): + self.yinc1_ = 1 + self.yinc2_ = 1 + else: + self.yinc1_ = -1 + self.yinc2_ = -1 + + if (self.delta_x_ >= self.delta_y_): + self.xinc1_ = 0 + self.yinc2_ = 0 + self.den_ = self.delta_x_ + self.num_ = self.delta_x_ / 2 + self.numadd_ = self.delta_y_ + self.numpixels_ = self.delta_x_ + else: + self.xinc2_ = 0 + self.yinc1_ = 0 + self.den_ = self.delta_y_ + self.num_ = self.delta_y_ / 2 + self.numadd_ = self.delta_x_ + self.numpixels_ = self.delta_y_ + + def isValid(self): + """Checks if the given line is valid""" + return self.curpixel_ <= self.numpixels_ + + def advance(self): + """Advances to the next point in the line.""" + self.num_ = self.numadd_ + if (self.num_ >= self.den_): + self.num_ -= self.den_ + self.x_ += self.xinc1_ + self.y_ += self.yinc1_ + self.x_ += self.xinc2_ + self.y_ += self.yinc2_ + self.curpixel_ += 1 + + def getX(self): + """Gets abscissa of the current point.""" + return self.x_ + + def getY(self): + """Gets ordinate of the current point.""" + return self.y_ + + def getX0(self): + """Gets abscissa of the initial point.""" + return self.x0_ + + def getY0(self): + """Gets ordinate of the initial point.""" + return self.y0_ + + def getX1(self): + """Gets abscissa of the final point.""" + return self.x1_ + + def getY1(self): + """Gets ordinate of the final point.""" + return self.y1_ From a1767f2540fdc78975d768849e10c8fd634b8c59 Mon Sep 17 00:00:00 2001 From: Afif Swaidan <53655365+afifswaidan@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:34:34 +0200 Subject: [PATCH 02/11] Updated Line Iterator to a new iteration method --- .../nav2_simple_commander/line_iterator.py | 122 +++++++++++------- 1 file changed, 72 insertions(+), 50 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 89148d6ae9..9b09ea3f5c 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -21,6 +21,9 @@ """ +from cmath import sqrt + + class LineIterator(): def __init__(self, x0, y0, x1, y1): @@ -43,77 +46,96 @@ def __init__(self, x0, y0, x1, y1): self.x_ = x0 self.y_ = y0 - self.delta_x_ = abs(self.x1_ - self.x0_) - self.delta_y_ = abs(self.y1_ - self.y0_) - self.curpixel_ = 0 - self.xinc1_, self.xinc2_, self.yinc1_, self.yinc2_ = 0, 0, 0, 0 - self.num_, self.den_ = 0, 0 - self.numadd_ = 0 - - if (self.x1_ >= x0): - self.xinc1_ = 1 - self.xinc2_ = 1 - else: - self.xinc1_ = -1 - self.xinc2_ = -1 - - if (y1 >= y0): - self.yinc1_ = 1 - self.yinc2_ = 1 - else: - self.yinc1_ = -1 - self.yinc2_ = -1 - - if (self.delta_x_ >= self.delta_y_): - self.xinc1_ = 0 - self.yinc2_ = 0 - self.den_ = self.delta_x_ - self.num_ = self.delta_x_ / 2 - self.numadd_ = self.delta_y_ - self.numpixels_ = self.delta_x_ + if x1 != x0 and y1 != y0: + self.valid_ = True + self.distance_ = abs(x1-x0) + self.m_ = (y1-y0)/(x1-x0) + self.b_ = y1 - (self.m_*x1) + if (self.b_ < 0): + self.equation_ = "y = " + str(self.m_) + "*x " + str(self.b_) + else: + self.equation_ = "y = " + str(self.m_) + "*x + " + str(self.b_) + elif x1 == x0 and y1 != y0: + self.valid_ = True + self.distance_ = abs(y1-y0) + self.equation_ = "x = " + str(x1) + elif y1 == y1 and x1 != x0: + self.valid_ = True + self.distance_ = abs(x1-x0) + self.equation_ = "y = " + str(y1) else: - self.xinc2_ = 0 - self.yinc1_ = 0 - self.den_ = self.delta_y_ - self.num_ = self.delta_y_ / 2 - self.numadd_ = self.delta_x_ - self.numpixels_ = self.delta_y_ + self.valid_ = False + self.equation_ = "Invalid" + self.step_ = self.distance_/1000 def isValid(self): - """Checks if the given line is valid""" - return self.curpixel_ <= self.numpixels_ + """Returns True if the line is valid""" + return self.valid_ def advance(self): """Advances to the next point in the line.""" - self.num_ = self.numadd_ - if (self.num_ >= self.den_): - self.num_ -= self.den_ - self.x_ += self.xinc1_ - self.y_ += self.yinc1_ - self.x_ += self.xinc2_ - self.y_ += self.yinc2_ - self.curpixel_ += 1 + if self.x1_ > self.x0_: + if self.x_ < self.x1_: + self.x_ = round(self.x_ + self.step_, 5) + self.y_ = round(self.m_ * self.x_ + self.b_, 5) + else: + self.valid_ = False + elif self.x1_ < self.x0_: + if self.x_ > self.x1_: + self.x_ = round(self.x_ - self.step_, 5) + self.y_ = round(self.m_ * self.x_ + self.b_, 5) + else: + self.valid_ = False + else: + if self.y1_ > self.y0_: + if self.y_ < self.y1_: + self.y_ = round(self.y_ + self.step_, 5) + else: + self.valid_ = False + elif self.y1_ < self.y0_: + if self.y_ > self.y1_: + self.y_ = round(self.y_ - self.step_, 5) + else: + self.valid_ = False + else: + self.valid_ = False def getX(self): - """Gets abscissa of the current point.""" + """Returns the abscissa of the current point.""" return self.x_ def getY(self): - """Gets ordinate of the current point.""" + """Returns the ordinate of the current point.""" return self.y_ def getX0(self): - """Gets abscissa of the initial point.""" + """Returns the abscissa of the initial point.""" return self.x0_ def getY0(self): - """Gets ordinate of the initial point.""" + """Returns the ordinate of the initial point.""" return self.y0_ def getX1(self): - """Gets abscissa of the final point.""" + """Returns the abscissa of the final point.""" return self.x1_ def getY1(self): - """Gets ordinate of the final point.""" + """Returns the ordinate of the final point.""" return self.y1_ + + def get_line_length(self): + """Returns the length of the line.""" + return sqrt(pow(self.x1_ - self.x0_, 2) + pow(self.y1_ - self.y0_, 2)) + + def get_line_equation(self): + """Returns the equation of the line as a string.""" + return self.equation_ + + def get_curr_point_str(self): + """Returns the coordinates of the current point as string.""" + return "X: " + str(self.x_) + " Y: " + str(self.y_) + + def get_curr_point(self): + """Returns the coordinates of the current point as list [X,Y].""" + return [self.x_, self.y_] \ No newline at end of file From a9c6d6d0bd786be52d42787e7832309e21eb7fd9 Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Mon, 3 Oct 2022 17:37:45 +0200 Subject: [PATCH 03/11] Added the resolution as a parameter/ fixed linting --- .../nav2_simple_commander/line_iterator.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 9b09ea3f5c..1c9042ab85 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -26,7 +26,7 @@ class LineIterator(): - def __init__(self, x0, y0, x1, y1): + def __init__(self, x0, y0, x1, y1, step_size=1): """ Initializer for LineIterator. @@ -36,6 +36,7 @@ def __init__(self, x0, y0, x1, y1): y0: Ordinate of the initial point x1: Abscissa of the final point y1: Ordinate of the final point + step_size: Resolution of increments, default is 1 Returns: None """ @@ -45,10 +46,10 @@ def __init__(self, x0, y0, x1, y1): self.y1_ = y1 self.x_ = x0 self.y_ = y0 + self.step_size_ = step_size if x1 != x0 and y1 != y0: self.valid_ = True - self.distance_ = abs(x1-x0) self.m_ = (y1-y0)/(x1-x0) self.b_ = y1 - (self.m_*x1) if (self.b_ < 0): @@ -57,16 +58,15 @@ def __init__(self, x0, y0, x1, y1): self.equation_ = "y = " + str(self.m_) + "*x + " + str(self.b_) elif x1 == x0 and y1 != y0: self.valid_ = True - self.distance_ = abs(y1-y0) self.equation_ = "x = " + str(x1) elif y1 == y1 and x1 != x0: self.valid_ = True - self.distance_ = abs(x1-x0) + self.m_ = (y1-y0)/(x1-x0) + self.b_ = y1 - (self.m_*x1) self.equation_ = "y = " + str(y1) else: self.valid_ = False self.equation_ = "Invalid" - self.step_ = self.distance_/1000 def isValid(self): """Returns True if the line is valid""" @@ -76,25 +76,29 @@ def advance(self): """Advances to the next point in the line.""" if self.x1_ > self.x0_: if self.x_ < self.x1_: - self.x_ = round(self.x_ + self.step_, 5) + self.x_ = round(self.clamp( + self.x_ + self.step_size_, self.x0_, self.x1_), 5) self.y_ = round(self.m_ * self.x_ + self.b_, 5) else: self.valid_ = False elif self.x1_ < self.x0_: if self.x_ > self.x1_: - self.x_ = round(self.x_ - self.step_, 5) + self.x_ = round(self.clamp( + self.x_ - self.step_size_, self.x1_, self.x0_), 5) self.y_ = round(self.m_ * self.x_ + self.b_, 5) else: self.valid_ = False else: if self.y1_ > self.y0_: if self.y_ < self.y1_: - self.y_ = round(self.y_ + self.step_, 5) + self.y_ = round(self.clamp( + self.y_ + self.step_size_, self.y0_, self.y1_), 5) else: self.valid_ = False elif self.y1_ < self.y0_: if self.y_ > self.y1_: - self.y_ = round(self.y_ - self.step_, 5) + self.y_ = round(self.clamp( + self.y_ - self.step_size_, self.y1_, self.y0_), 5) else: self.valid_ = False else: @@ -138,4 +142,13 @@ def get_curr_point_str(self): def get_curr_point(self): """Returns the coordinates of the current point as list [X,Y].""" - return [self.x_, self.y_] \ No newline at end of file + return [self.x_, self.y_] + + def clamp(self, n, min_n, max_n): + """Class Helper Function: Clamps n to be between min_n and max_n""" + if n < min_n: + return min_n + elif n > max_n: + return max_n + else: + return n From 9b7975724f48bdc92524602e0ffc8e6444604805 Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Mon, 3 Oct 2022 17:51:33 +0200 Subject: [PATCH 04/11] Added the resolution as a parameter/ fixed linting --- .../nav2_simple_commander/line_iterator.py | 99 ++++++++++++++----- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 1c9042ab85..ecae9b19f2 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -25,20 +25,15 @@ class LineIterator(): - - def __init__(self, x0, y0, x1, y1, step_size=1): - """ - Initializer for LineIterator. - - Initialize instance variables with parameter occupancy_map. - Args: - x0: Abscissa of the initial point - y0: Ordinate of the initial point - x1: Abscissa of the final point - y1: Ordinate of the final point - step_size: Resolution of increments, default is 1 - Returns: - None + def __init__(self, x0, y0, x1, y1, step_size=1.0): + """Initializer for LineIterator. + + Args: + x0 (Float): Abscissa of the initial point + y0 (Float): Ordinate of the initial point + x1 (Float): Abscissa of the final point + y1 (Float): Ordinate of the final point + step_size (Float, optional): Resolution of increments. Defaults to 1. """ self.x0_ = x0 self.y0_ = x0 @@ -69,7 +64,10 @@ def __init__(self, x0, y0, x1, y1, step_size=1): self.equation_ = "Invalid" def isValid(self): - """Returns True if the line is valid""" + """Returns True if the line is valid + Returns: + Bool: Flag if live is valid (true) or not (false) + """ return self.valid_ def advance(self): @@ -105,47 +103,96 @@ def advance(self): self.valid_ = False def getX(self): - """Returns the abscissa of the current point.""" + """Returns the abscissa of the current point. + + Returns: + Float: abscissa of current point + """ return self.x_ def getY(self): - """Returns the ordinate of the current point.""" + """Returns the ordinate of the current point. + + Returns: + Float: ordinate of current point + """ return self.y_ def getX0(self): - """Returns the abscissa of the initial point.""" + """Returns the abscissa of the initial point. + + Returns: + Float: abscissa of initial point + """ return self.x0_ def getY0(self): - """Returns the ordinate of the initial point.""" + """Returns the ordinate of the intial point. + + Returns: + Float: ordinate of intial point + """ return self.y0_ def getX1(self): - """Returns the abscissa of the final point.""" + """Returns the abscissa of the final point. + + Returns: + Float: abscissa of final point + """ return self.x1_ def getY1(self): - """Returns the ordinate of the final point.""" + """Returns the ordinate of the final point. + + Returns: + Float: ordinate of final point + """ return self.y1_ def get_line_length(self): - """Returns the length of the line.""" + """Returns the length of the line. + + Returns: + Float: Line Length + """ return sqrt(pow(self.x1_ - self.x0_, 2) + pow(self.y1_ - self.y0_, 2)) def get_line_equation(self): - """Returns the equation of the line as a string.""" + """Returns the equation of the line as a string. + + Returns: + String: Line's Equation + """ return self.equation_ def get_curr_point_str(self): - """Returns the coordinates of the current point as string.""" + """Returns the coordinates of the current point as string. + + Returns: + String: Current Coordinates + """ return "X: " + str(self.x_) + " Y: " + str(self.y_) def get_curr_point(self): - """Returns the coordinates of the current point as list [X,Y].""" + """Returns the coordinates of the current point as list [X,Y]. + + Returns: + List: Current Coordinates as float [X,Y] + """ return [self.x_, self.y_] def clamp(self, n, min_n, max_n): - """Class Helper Function: Clamps n to be between min_n and max_n""" + """Class Helper Function: Clamps n to be between min_n and max_n + + Args: + n (Float): input value + min_n (Float): minimum value + max_n (Float): maximum value + + Returns: + Float: input value clamped between given min and max + """ if n < min_n: return min_n elif n > max_n: From c050299cefa9a75c429ea2f75ab6641fdc9ba036 Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sun, 9 Oct 2022 16:05:36 +0200 Subject: [PATCH 05/11] Added unittests for the line iterator --- .../nav2_simple_commander/line_iterator.py | 25 ++++- .../test/test_line_iterator.py | 95 +++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 nav2_simple_commander/test/test_line_iterator.py diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index ecae9b19f2..403a0369ae 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -33,10 +33,29 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): y0 (Float): Ordinate of the initial point x1 (Float): Abscissa of the final point y1 (Float): Ordinate of the final point - step_size (Float, optional): Resolution of increments. Defaults to 1. + step_size (Float, optional): Increments Resolution. Defaults to 1. """ + + if type(x0) not in [int, float]: + raise TypeError("x0 must be a number (int or float)") + + if type(y0) not in [int, float]: + raise TypeError("y0 must be a number (int or float)") + + if type(x1) not in [int, float]: + raise TypeError("x1 must be a number (int or float)") + + if type(y1) not in [int, float]: + raise TypeError("y1 must be a number (int or float)") + + if type(step_size) not in [int, float]: + raise TypeError("step_size must be a number (int or float)") + + if step_size <= 0: + raise ValueError("step_size must be a positive number") + self.x0_ = x0 - self.y0_ = x0 + self.y0_ = y0 self.x1_ = x1 self.y1_ = y1 self.x_ = x0 @@ -62,6 +81,8 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): else: self.valid_ = False self.equation_ = "Invalid" + raise ValueError( + "Line has zero length (All 4 points have same coordinates)") def isValid(self): """Returns True if the line is valid diff --git a/nav2_simple_commander/test/test_line_iterator.py b/nav2_simple_commander/test/test_line_iterator.py new file mode 100644 index 0000000000..d9e8b032d3 --- /dev/null +++ b/nav2_simple_commander/test/test_line_iterator.py @@ -0,0 +1,95 @@ +# Copyright 2022 Afif Swaidan +# +# 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 pytest +from nav2_simple_commander.line_iterator import LineIterator + + +def test_type_error(): + # Test if a type error raised when passing invalid arguements types + with pytest.raises(TypeError): + LineIterator(0, 0, '10', 10, '1') + + +def test_value_error(): + # Test if a value error raised when passing negative or zero step_size + with pytest.raises(ValueError): + LineIterator(0, 0, 10, 10, -2) + # Test if a value error raised when passing zero length line + with pytest.raises(ValueError): + LineIterator(2, 2, 2, 2, 1) + + +def test_straight_line(): + # Test if the calculations are correct for y = x + lt = LineIterator(0, 0, 5, 5, 1) + i = 0 + while lt.isValid(): + assert lt.getX() == lt.getX0() + i + assert lt.getY() == lt.getY0() + i + lt.advance() + i += 1 + + # Test if the calculations are correct for y = 2x (positive slope) + lt = LineIterator(0, 0, 5, 10, 1) + i = 0 + while lt.isValid(): + assert lt.getX() == lt.getX0() + i + assert lt.getY() == lt.getY0() + (i*2) + lt.advance() + i += 1 + + # Test if the calculations are correct for y = -2x (negative slope) + lt = LineIterator(0, 0, 5, -10, 1) + i = 0 + while lt.isValid(): + assert lt.getX() == lt.getX0() + i + assert lt.getY() == lt.getY0() + (-i*2) + lt.advance() + i += 1 + + +def test_hor_line(): + # Test if the calculations are correct for y = 0x+b (horizontal line) + lt = LineIterator(0, 10, 5, 10, 1) + i = 0 + while lt.isValid(): + assert lt.getX() == lt.getX0() + i + assert lt.getY() == lt.getY0() + lt.advance() + i += 1 + + +def test_ver_line(): + # Test if the calculations are correct for x = n (vertical line) + lt = LineIterator(5, 0, 5, 10, 1) + i = 0 + while lt.isValid(): + assert lt.getX() == lt.getX0() + assert lt.getY() == lt.getY0() + i + lt.advance() + i += 1 + + +def test_clamp(): + # Test if the increments are clamped to avoid crossing the final points + # when step_size is large with respect to line length + lt = LineIterator(0, 0, 5, 5, 10) + assert lt.getX() == 0 + assert lt.getY() == 0 + lt.advance() + while lt.isValid(): + assert lt.getX() == 5 + assert lt.getY() == 5 + lt.advance() From 9df1b622d9a70627d488092d3cec2e45ff58f293 Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sat, 15 Oct 2022 12:47:41 +0200 Subject: [PATCH 06/11] Added unittests based on "unittest" package --- .../nav2_simple_commander/__init__.py | 1 + nav2_simple_commander/pytest.ini | 2 + .../test/test_line_iterator.py | 132 +++++++++--------- 3 files changed, 68 insertions(+), 67 deletions(-) create mode 100644 nav2_simple_commander/pytest.ini diff --git a/nav2_simple_commander/nav2_simple_commander/__init__.py b/nav2_simple_commander/nav2_simple_commander/__init__.py index e69de29bb2..052f63c4ab 100644 --- a/nav2_simple_commander/nav2_simple_commander/__init__.py +++ b/nav2_simple_commander/nav2_simple_commander/__init__.py @@ -0,0 +1 @@ +from .line_iterator import * \ No newline at end of file diff --git a/nav2_simple_commander/pytest.ini b/nav2_simple_commander/pytest.ini new file mode 100644 index 0000000000..50d6d01257 --- /dev/null +++ b/nav2_simple_commander/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +junit_family=xunit2 \ No newline at end of file diff --git a/nav2_simple_commander/test/test_line_iterator.py b/nav2_simple_commander/test/test_line_iterator.py index d9e8b032d3..03f7399076 100644 --- a/nav2_simple_commander/test/test_line_iterator.py +++ b/nav2_simple_commander/test/test_line_iterator.py @@ -12,84 +12,82 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import unittest from nav2_simple_commander.line_iterator import LineIterator -def test_type_error(): - # Test if a type error raised when passing invalid arguements types - with pytest.raises(TypeError): - LineIterator(0, 0, '10', 10, '1') +class TestLineIterator(unittest.TestCase): + def test_type_error(self): + # Test if a type error raised when passing invalid arguements types + self.assertRaises(TypeError, LineIterator, 0, 0, '10', 10, '1') -def test_value_error(): - # Test if a value error raised when passing negative or zero step_size - with pytest.raises(ValueError): - LineIterator(0, 0, 10, 10, -2) - # Test if a value error raised when passing zero length line - with pytest.raises(ValueError): - LineIterator(2, 2, 2, 2, 1) + def test_value_error(self): + # Test if a value error raised when passing negative or zero step_size + self.assertRaises(ValueError, LineIterator, 0, 0, 10, 10, -2) + # Test if a value error raised when passing zero length line + self.assertRaises(ValueError, LineIterator, 2, 2, 2, 2, 1) + def test_straight_line(self): + # Test if the calculations are correct for y = x + lt = LineIterator(0, 0, 5, 5, 1) + i = 0 + while lt.isValid(): + self.assertEqual(lt.getX(), lt.getX0()+i) + self.assertEqual(lt.getY(), lt.getY0()+i) + lt.advance() + i += 1 -def test_straight_line(): - # Test if the calculations are correct for y = x - lt = LineIterator(0, 0, 5, 5, 1) - i = 0 - while lt.isValid(): - assert lt.getX() == lt.getX0() + i - assert lt.getY() == lt.getY0() + i - lt.advance() - i += 1 + # Test if the calculations are correct for y = 2x (positive slope) + lt = LineIterator(0, 0, 5, 10, 1) + i = 0 + while lt.isValid(): + self.assertEqual(lt.getX(), lt.getX0()+i) + self.assertEqual(lt.getY(), lt.getY0() + (i*2)) + lt.advance() + i += 1 - # Test if the calculations are correct for y = 2x (positive slope) - lt = LineIterator(0, 0, 5, 10, 1) - i = 0 - while lt.isValid(): - assert lt.getX() == lt.getX0() + i - assert lt.getY() == lt.getY0() + (i*2) - lt.advance() - i += 1 + # Test if the calculations are correct for y = -2x (negative slope) + lt = LineIterator(0, 0, 5, -10, 1) + i = 0 + while lt.isValid(): + self.assertEqual(lt.getX(), lt.getX0()+i) + self.assertEqual(lt.getY(), lt.getY0() + (-i*2)) + lt.advance() + i += 1 - # Test if the calculations are correct for y = -2x (negative slope) - lt = LineIterator(0, 0, 5, -10, 1) - i = 0 - while lt.isValid(): - assert lt.getX() == lt.getX0() + i - assert lt.getY() == lt.getY0() + (-i*2) - lt.advance() - i += 1 + def test_hor_line(self): + # Test if the calculations are correct for y = 0x+b (horizontal line) + lt = LineIterator(0, 10, 5, 10, 1) + i = 0 + while lt.isValid(): + self.assertEqual(lt.getX(), lt.getX0() + i) + self.assertEqual(lt.getY(), lt.getY0()) + lt.advance() + i += 1 + def test_ver_line(self): + # Test if the calculations are correct for x = n (vertical line) + lt = LineIterator(5, 0, 5, 10, 1) + i = 0 + while lt.isValid(): + self.assertEqual(lt.getX(), lt.getX0()) + self.assertEqual(lt.getY(), lt.getY0() + i) + lt.advance() + i += 1 -def test_hor_line(): - # Test if the calculations are correct for y = 0x+b (horizontal line) - lt = LineIterator(0, 10, 5, 10, 1) - i = 0 - while lt.isValid(): - assert lt.getX() == lt.getX0() + i - assert lt.getY() == lt.getY0() + def test_clamp(self): + # Test if the increments are clamped to avoid crossing the final points + # when step_size is large with respect to line length + lt = LineIterator(0, 0, 5, 5, 10) + self.assertEqual(lt.getX(), 0) + self.assertEqual(lt.getY(), 0) lt.advance() - i += 1 + while lt.isValid(): + self.assertEqual(lt.getX(), 5) + self.assertEqual(lt.getY(), 5) + lt.advance() -def test_ver_line(): - # Test if the calculations are correct for x = n (vertical line) - lt = LineIterator(5, 0, 5, 10, 1) - i = 0 - while lt.isValid(): - assert lt.getX() == lt.getX0() - assert lt.getY() == lt.getY0() + i - lt.advance() - i += 1 - - -def test_clamp(): - # Test if the increments are clamped to avoid crossing the final points - # when step_size is large with respect to line length - lt = LineIterator(0, 0, 5, 5, 10) - assert lt.getX() == 0 - assert lt.getY() == 0 - lt.advance() - while lt.isValid(): - assert lt.getX() == 5 - assert lt.getY() == 5 - lt.advance() +if __name__ == '__main__': + unittest.main() From 6731c6b5f15dc58d0aab612d50ab25ffaa9ffebb Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sat, 15 Oct 2022 13:34:04 +0200 Subject: [PATCH 07/11] Fixed __init__.py and rephrased some docstrings --- .../nav2_simple_commander/__init__.py | 1 - .../nav2_simple_commander/line_iterator.py | 37 +++++++++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/__init__.py b/nav2_simple_commander/nav2_simple_commander/__init__.py index 052f63c4ab..e69de29bb2 100644 --- a/nav2_simple_commander/nav2_simple_commander/__init__.py +++ b/nav2_simple_commander/nav2_simple_commander/__init__.py @@ -1 +0,0 @@ -from .line_iterator import * \ No newline at end of file diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 403a0369ae..0c1177b761 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -26,14 +26,25 @@ class LineIterator(): def __init__(self, x0, y0, x1, y1, step_size=1.0): - """Initializer for LineIterator. - - Args: - x0 (Float): Abscissa of the initial point - y0 (Float): Ordinate of the initial point - x1 (Float): Abscissa of the final point - y1 (Float): Ordinate of the final point - step_size (Float, optional): Increments Resolution. Defaults to 1. + """Initialize the LineIterator + + Arguments: + x0 -- Abscissa of the initial point + y0 -- Ordinate of the initial point + x1 -- Abscissa of the final point + y1 -- Ordinate of the final point + + Keyword Arguments: + step_size -- Increments' Resolution (default: {1.0}) + + Raises: + TypeError: When x0 is not a number + TypeError: When y0 is not a number + TypeError: When x1 is not a number + TypeError: When y1 is not a number + TypeError: When step_size is not a number + ValueError: When x0 is not a positive number + ValueError: When line has zero length """ if type(x0) not in [int, float]: @@ -86,13 +97,15 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): def isValid(self): """Returns True if the line is valid + Returns: - Bool: Flag if live is valid (true) or not (false) + True if line is valid, false otherwise """ return self.valid_ def advance(self): - """Advances to the next point in the line.""" + """Advance to the next point in the line + """ if self.x1_ > self.x0_: if self.x_ < self.x1_: self.x_ = round(self.clamp( @@ -124,10 +137,10 @@ def advance(self): self.valid_ = False def getX(self): - """Returns the abscissa of the current point. + """Get the abscissa of the current point Returns: - Float: abscissa of current point + Abscissa of current point """ return self.x_ From d35d26dff475d8809b78da8bf6548e0b43f677ff Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sat, 22 Oct 2022 13:04:07 +0200 Subject: [PATCH 08/11] Fixed linting errors --- .../nav2_simple_commander/line_iterator.py | 127 ++++++------------ 1 file changed, 44 insertions(+), 83 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 0c1177b761..47794ed360 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -17,36 +17,38 @@ """ This is a Python3 API for a line iterator. -It provides the ability to iterate through the points of a line. +It provides the ability to iterate +through the points of a line. """ - from cmath import sqrt class LineIterator(): - def __init__(self, x0, y0, x1, y1, step_size=1.0): - """Initialize the LineIterator + """ + LineIterator. - Arguments: - x0 -- Abscissa of the initial point - y0 -- Ordinate of the initial point - x1 -- Abscissa of the final point - y1 -- Ordinate of the final point + LineIterator Python3 API for iterating along the points of a given line + """ - Keyword Arguments: - step_size -- Increments' Resolution (default: {1.0}) + def __init__(self, x0, y0, x1, y1, step_size=1.0): + """ + Initialize the LineIterator. + + Args: + ---- + x0 (float): Abscissa of the initial point + y0 (float): Ordinate of the initial point + x1 (float): Abscissa of the final point + y1 (float): Ordinate of the final point + step_size (float): Optional, Increments' resolution, defaults to 1 Raises: - TypeError: When x0 is not a number - TypeError: When y0 is not a number - TypeError: When x1 is not a number - TypeError: When y1 is not a number - TypeError: When step_size is not a number - ValueError: When x0 is not a positive number - ValueError: When line has zero length - """ + ------ + TypeError: When one (or more) of the inputs is not a number + ValueError: When step_size is not a positive number + """ if type(x0) not in [int, float]: raise TypeError("x0 must be a number (int or float)") @@ -96,16 +98,11 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): "Line has zero length (All 4 points have same coordinates)") def isValid(self): - """Returns True if the line is valid - - Returns: - True if line is valid, false otherwise - """ + """Check if line is valid.""" return self.valid_ def advance(self): - """Advance to the next point in the line - """ + """Advance to the next point in the line.""" if self.x1_ > self.x0_: if self.x_ < self.x1_: self.x_ = round(self.clamp( @@ -137,95 +134,59 @@ def advance(self): self.valid_ = False def getX(self): - """Get the abscissa of the current point - - Returns: - Abscissa of current point - """ + """Get the abscissa of the current point.""" return self.x_ def getY(self): - """Returns the ordinate of the current point. - - Returns: - Float: ordinate of current point - """ + """Get the ordinate of the current point.""" return self.y_ def getX0(self): - """Returns the abscissa of the initial point. - - Returns: - Float: abscissa of initial point - """ + """Get the abscissa of the initial point.""" return self.x0_ def getY0(self): - """Returns the ordinate of the intial point. - - Returns: - Float: ordinate of intial point - """ + """Get the ordinate of the intial point.""" return self.y0_ def getX1(self): - """Returns the abscissa of the final point. - - Returns: - Float: abscissa of final point - """ + """Get the abscissa of the final point.""" return self.x1_ def getY1(self): - """Returns the ordinate of the final point. - - Returns: - Float: ordinate of final point - """ + """Get the ordinate of the final point.""" return self.y1_ def get_line_length(self): - """Returns the length of the line. - - Returns: - Float: Line Length - """ + """Get the length of the line.""" return sqrt(pow(self.x1_ - self.x0_, 2) + pow(self.y1_ - self.y0_, 2)) def get_line_equation(self): - """Returns the equation of the line as a string. - - Returns: - String: Line's Equation - """ + """Get the equation of the line as a string.""" return self.equation_ def get_curr_point_str(self): - """Returns the coordinates of the current point as string. - - Returns: - String: Current Coordinates - """ + """Get the coordinates of the current point as string.""" return "X: " + str(self.x_) + " Y: " + str(self.y_) def get_curr_point(self): - """Returns the coordinates of the current point as list [X,Y]. - - Returns: - List: Current Coordinates as float [X,Y] - """ + """Get the coordinates of the current point as list [X,Y].""" return [self.x_, self.y_] def clamp(self, n, min_n, max_n): - """Class Helper Function: Clamps n to be between min_n and max_n + """ + Clamp n to be between min_n and max_n. - Args: - n (Float): input value - min_n (Float): minimum value - max_n (Float): maximum value + Args + ---- + n (float): input value + min_n (float): minimum value + max_n (float): maximum value + + Returns + ------- + n (float): input value clamped between given min and max - Returns: - Float: input value clamped between given min and max """ if n < min_n: return min_n From b8d0858761344b37e42c685e5a1907f68a940b9a Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sun, 23 Oct 2022 13:35:11 +0200 Subject: [PATCH 09/11] Fixed Linting Errors --- nav2_simple_commander/nav2_simple_commander/line_iterator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 47794ed360..0d4eae6c05 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -35,7 +35,7 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): """ Initialize the LineIterator. - Args: + Args ---- x0 (float): Abscissa of the initial point y0 (float): Ordinate of the initial point @@ -43,7 +43,7 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): y1 (float): Ordinate of the final point step_size (float): Optional, Increments' resolution, defaults to 1 - Raises: + Raises ------ TypeError: When one (or more) of the inputs is not a number ValueError: When step_size is not a positive number From f9d48a2065462774c8b0661957ea2efaa7f605ad Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Sun, 6 Nov 2022 17:24:06 +0100 Subject: [PATCH 10/11] Added some unittests and removed some methods --- .../nav2_simple_commander/line_iterator.py | 19 ---------------- .../test/test_line_iterator.py | 22 +++++++++++++++---- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/nav2_simple_commander/nav2_simple_commander/line_iterator.py b/nav2_simple_commander/nav2_simple_commander/line_iterator.py index 0d4eae6c05..db19d4180c 100644 --- a/nav2_simple_commander/nav2_simple_commander/line_iterator.py +++ b/nav2_simple_commander/nav2_simple_commander/line_iterator.py @@ -79,21 +79,14 @@ def __init__(self, x0, y0, x1, y1, step_size=1.0): self.valid_ = True self.m_ = (y1-y0)/(x1-x0) self.b_ = y1 - (self.m_*x1) - if (self.b_ < 0): - self.equation_ = "y = " + str(self.m_) + "*x " + str(self.b_) - else: - self.equation_ = "y = " + str(self.m_) + "*x + " + str(self.b_) elif x1 == x0 and y1 != y0: self.valid_ = True - self.equation_ = "x = " + str(x1) elif y1 == y1 and x1 != x0: self.valid_ = True self.m_ = (y1-y0)/(x1-x0) self.b_ = y1 - (self.m_*x1) - self.equation_ = "y = " + str(y1) else: self.valid_ = False - self.equation_ = "Invalid" raise ValueError( "Line has zero length (All 4 points have same coordinates)") @@ -161,18 +154,6 @@ def get_line_length(self): """Get the length of the line.""" return sqrt(pow(self.x1_ - self.x0_, 2) + pow(self.y1_ - self.y0_, 2)) - def get_line_equation(self): - """Get the equation of the line as a string.""" - return self.equation_ - - def get_curr_point_str(self): - """Get the coordinates of the current point as string.""" - return "X: " + str(self.x_) + " Y: " + str(self.y_) - - def get_curr_point(self): - """Get the coordinates of the current point as list [X,Y].""" - return [self.x_, self.y_] - def clamp(self, n, min_n, max_n): """ Clamp n to be between min_n and max_n. diff --git a/nav2_simple_commander/test/test_line_iterator.py b/nav2_simple_commander/test/test_line_iterator.py index 03f7399076..e80eda2995 100644 --- a/nav2_simple_commander/test/test_line_iterator.py +++ b/nav2_simple_commander/test/test_line_iterator.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from cmath import sqrt import unittest from nav2_simple_commander.line_iterator import LineIterator @@ -28,13 +29,26 @@ def test_value_error(self): # Test if a value error raised when passing zero length line self.assertRaises(ValueError, LineIterator, 2, 2, 2, 2, 1) + def test_get_xy(self): + # Test if the initial and final coordinates are returned correctly + lt = LineIterator(0, 0, 5, 5, 1) + self.assertEqual(lt.getX0(), 0) + self.assertEqual(lt.getY0(), 0) + self.assertEqual(lt.getX1(), 5) + self.assertEqual(lt.getY1(), 5) + + def test_line_length(self): + # Test if the line length is calculated correctly + lt = LineIterator(0, 0, 5, 5, 1) + self.assertEqual(lt.get_line_length(), sqrt(pow(5, 2) + pow(5, 2))) + def test_straight_line(self): # Test if the calculations are correct for y = x lt = LineIterator(0, 0, 5, 5, 1) i = 0 while lt.isValid(): - self.assertEqual(lt.getX(), lt.getX0()+i) - self.assertEqual(lt.getY(), lt.getY0()+i) + self.assertEqual(lt.getX(), lt.getX0() + i) + self.assertEqual(lt.getY(), lt.getY0() + i) lt.advance() i += 1 @@ -42,7 +56,7 @@ def test_straight_line(self): lt = LineIterator(0, 0, 5, 10, 1) i = 0 while lt.isValid(): - self.assertEqual(lt.getX(), lt.getX0()+i) + self.assertEqual(lt.getX(), lt.getX0() + i) self.assertEqual(lt.getY(), lt.getY0() + (i*2)) lt.advance() i += 1 @@ -51,7 +65,7 @@ def test_straight_line(self): lt = LineIterator(0, 0, 5, -10, 1) i = 0 while lt.isValid(): - self.assertEqual(lt.getX(), lt.getX0()+i) + self.assertEqual(lt.getX(), lt.getX0() + i) self.assertEqual(lt.getY(), lt.getY0() + (-i*2)) lt.advance() i += 1 From a571cded680187db964741a5822a7509802e340f Mon Sep 17 00:00:00 2001 From: Afif Swaidan Date: Mon, 7 Nov 2022 20:15:43 +0100 Subject: [PATCH 11/11] Dummy commit for CircleCI Issue --- nav2_simple_commander/test/test_line_iterator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nav2_simple_commander/test/test_line_iterator.py b/nav2_simple_commander/test/test_line_iterator.py index e80eda2995..4c5e97420a 100644 --- a/nav2_simple_commander/test/test_line_iterator.py +++ b/nav2_simple_commander/test/test_line_iterator.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cmath import sqrt import unittest +from cmath import sqrt from nav2_simple_commander.line_iterator import LineIterator