# A line class for coordinate geometry

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

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

jrcagle 77

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, learning, and sharing knowledge.