A line class for coordinate geometry

jrcagle 0 Tallied Votes 2K Views Share

So I needed a way to represent a line in Python. My first stab was to create a function dynamically. But classes are so much ... classier, so here's a simple implementation of a line object, with plenty of room for additional methods.

The line is created as

l = Line(slope,intercept) *or*
l = Line(slope,Q) *or*
l = Line(P,Q)

where P and Q are (x,y) pairs.

"""
line.py

Exports Line class
"""

class Line(object):
    """
A representation of a line.  Can be populated through

__init__(slope,intercept)
__init__(slope,Q)
__init__(P,Q)

where P and Q are (x,y) points.
"""

    def __init__(self, a, b):
        """
L.__init__(a,b) --> None

Populates self with information from a,b.

- If a is an int or float, it is taken to be the slope of the line.
- If b is an int or float, it is taken to be the intercept of the line.
- If a or b is a tuple, it is taken to be a point through which the line passes.
- A vertical line can be indicated by a slope of None.

An error is raised if there is incomplete information, or if a and b represent
the same point.
"""
        try:
            x0, y0 = a
            slope = None
        except:
            slope = a
            x0 = y0 = None

        try:
            x1, y1 = b
            intercept = None
        except:
            intercept = b
            x1 = y1 = None

        if slope == None:
            if x0 == None and x1 != None:
                self._m = None
                self._inv_m = 0
                self._int = None
                self._A = 1
                self._B = 0
                self._C = x1
                return
            else:
                try:
                    slope = float(y1 - y0)/float(x1 - x0)
                except ZeroDivisionError:
                    self._m = None
                    self._inv_m = 0
                    self._int = None
                    self._A = 1
                    self._B = 0
                    self._C = x1
                    return
                
        if intercept == None:
            intercept = -slope * x1 + y1
            
        self._m = slope
        if self._m == 0:
            self._inv_m = None
        else:
            self._inv_m = 1./slope
        self._int = intercept
        self._A = -slope
        self._B = 1
        self._C = intercept
                
    def __str__(self):
        if self._m != None:
            if self._m == 0:
                return "y = %0.2f" % (self._int)
            if self._int > 0:
                return "y = %0.2fx + %0.2f" % (self._m, self._int)
            elif self._int < 0:
                return "y = %0.2fx - %0.2f" % (self._m, -self._int)
            else:
                return "y = %0.2fx" % (self._m)
        else:
            return "x = %0.2f" % self._C

    

    def y(self, x):
        """
L.y(x) --> y

Return y-coordinate of point on line whose x-coordinate is x.
"""
        try:
            return self._m * x + self._int
        except:
            return None

    def x(self, y):
        """
L.x(y) --> x

Return x-coordinate of point on line whose y-coordinate is y.
"""
        if self._m == None:
            return self._C
        else:
            return self._inv_m * (y - self._int)

    def contains(self, P, tol = 0.0001):
        """
L.contains(P, tol) --> Bool

Return True if P = (x,y) is on line.  The y-coordinate of point on line whose
x-coord is x must be within tol of y.
"""
        x, y = P
        return abs(self.y(x) - y) < tol

    def slope(self):
        """
L.slope() --> m

Return slope.
"""
        return self._m

# This can be called as Line.test()
@staticmethod
def test():
        pass_count = 0
        fail_count = 0

        print """
----------------------------
Testing __init__ and __str__
----------------------------"""
        test_cases = [((2,3),(4,5)),\
                      ((2.5,3),(4.2,5.8)),\
                      ((3,4),(3,5)),\
                      ((3,4),(4,4)),\
                      (5,(1,8)),\
                      (None,(2,5)),\
                      (3,8),\
                      (None,8)]
        test_results = ['y = 1.00x + 1.00',\
                        'y = 1.65x - 1.12',\
                        'x = 3.00',\
                        'y = 4.00',\
                        'y = 5.00x + 3.00',\
                        'x = 2.00',\
                        'y = 3.00x + 8.00',\
                        'TypeError']
        tests = dict(zip(test_cases,test_results))
        test_lines = []
        for a,b in test_cases:
            try:
                print "%s , %s -->" % (str(a),str(b)),
                l = Line(a,b)
                test_lines.append(l)
                s = str(l)
            except TypeError:
                s = "TypeError"
            print s
            if s == tests[(a,b)]:
                print "test passed"
                pass_count += 1
            else:
                print "***TEST FAILED***"
                fail_count += 1

        print """
---------------
Testing y and x
---------------"""
        y_intercept_results = [1, -1.12, None, 4, 3, None, 8]
        y_int_tests = dict(zip(test_lines, y_intercept_results))
        x_intercept_results = [-1, 0.6788, 3, None, -0.6, 2, -2.667]
        x_int_tests = dict(zip(test_lines, x_intercept_results))
        print "Line\t\t\t\ty-int\t\tx-int"
        print "----\t\t\t\t-----\t\t-----"
        for line in test_lines:
            try:
                
                if line.slope() == None:
                    if abs(line.x(0) - x_int_tests[line]) < 0.01:
                        print "%s\t\t\tNone\t\t%.2f" % (line, line.x(0))
                        print "test passed"
                        pass_count += 1
                        continue
                elif line.slope() == 0:
                    if abs(line.y(0) - y_int_tests[line]) < 0.01:
                        print "%s\t\t\t%.2f\t\tNone" % (line, line.y(0))
                        print "test passed"
                        pass_count += 1
                        continue
                print "%s\t\t%.2f\t\t%.2f" % (line, line.y(0), line.x(0))
                if abs(line.y(0) - y_int_tests[line]) < 0.01 and \
                   abs(line.x(0) - x_int_tests[line]) < 0.01:
                    print "test passed"
                    pass_count += 1
                else:
                    print line
                    print "***TEST FAILED***"
                    print y_int_tests[line], x_int_tests[line]
                    fail_count += 1
            except: 
                print line
                print "***TEST FAILED***"
                fail_count += 1
        print """
----------------
Testing contains
----------------
"""
        l = Line(3,5)
        test_cases = [(1,2),(0,5),(0,5.003),(-3,18)]
        test_results = [False,True,True,False]
        tests = dict(zip(test_cases,test_results))

        for P in tests:
            print "%s contains %s: %s" % (l, str(P), l.contains(P, tol=0.01))
            if l.contains(P, tol=0.01) == tests[P]:
                print "test passed"
                pass_count += 1
            else:
                print "***TEST FAILED***"
                fail_count += 1
                
        print "\n\nTOTALS:"  
        print "%d tests passed" % pass_count
        print "%d tests failed" % fail_count
vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

Very nice Jeff!
You may want to add a few lines of testing the class.

jrcagle 77 Practically a Master Poster

Thanks for gently pointing out that I had an error in the x() method. :)

The test routine is not, unfortunately, a 'few lines' -- I'm finding that I can't do short unit tests. I'll try to check out the unittest module.

Thanks,
Jeff

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.