1 from sympy
.core
.basic
import Basic
, S
, C
2 from sympy
.simplify
import simplify
3 from sympy
.geometry
.exceptions
import GeometryError
4 from entity
import GeometryEntity
5 from point
import Point
7 class LinearEntity(GeometryEntity
):
9 A linear entity (line, ray, segment, etc) in space.
11 This is an abstract class and is not meant to be instantiated.
12 Subclasses should implement the following methods:
16 def __new__(cls
, p1
, p2
, **kwargs
):
17 if not isinstance(p1
, Point
) or not isinstance(p2
, Point
):
18 raise TypeError("%s.__new__ requires Point instances" % cls
.__name
__)
20 raise RuntimeError("%s.__new__ requires two distinct points" % cls
.__name
__)
22 return GeometryEntity
.__new
__(cls
, p1
, p2
, **kwargs
)
26 """One of the defining points of a linear entity."""
27 return self
.__getitem
__(0)
31 """One of the defining points of a linear entity."""
32 return self
.__getitem
__(1)
35 def coefficients(self
):
36 """The coefficients (a,b,c) for equation ax+by+c=0"""
37 return (self
.p1
[1]-self
.p2
[1],
38 self
.p2
[0]-self
.p1
[0],
39 self
.p1
[0]*self
.p2
[1] - self
.p1
[1]*self
.p2
[0])
41 def is_concurrent(*lines
):
43 Returns True if the set of linear entities are concurrent, False
44 otherwise. Two or more linear entities are concurrent if they all
45 intersect at a single point.
47 Description of Method Used:
48 ===========================
49 Simply take the first two lines and find their intersection.
50 If there is no intersection, then the first two lines were
51 parallel and had no intersection so concurrenecy is impossible
52 amongst the whole set. Otherwise, check to see if the
53 intersection point of the first two lines is a member on
54 the rest of the lines. If so, the lines are concurrent.
57 lines
= GeometryEntity
.extract_entities(lines
)
59 # Concurrency requires intersection at a single point; One linear
60 # entity cannot be concurrent.
65 # Get the intersection (if parallel)
66 p
= GeometryEntity
.do_intersection(lines
[0], lines
[1])
67 if len(p
) == 0: return False
69 # Make sure the intersection is on every linear entity
70 for line
in lines
[2:]:
74 except AttributeError:
77 def is_parallel(l1
, l2
):
78 """Returns True if l1 and l2 are parallel, False otherwise"""
80 a1
,b1
,c1
= l1
.coefficients
81 a2
,b2
,c2
= l2
.coefficients
82 return bool(simplify(a1
*b2
- b1
*a2
) == 0)
83 except AttributeError:
86 def is_perpendicular(l1
, l2
):
87 """Returns True if l1 and l2 are perpendicular, False otherwise"""
89 a1
,b1
,c1
= l1
.coefficients
90 a2
,b2
,c2
= l2
.coefficients
91 return bool(simplify(a1
*a2
+ b1
*b2
) == 0)
92 except AttributeError:
95 def angle_between(l1
, l2
):
97 Returns an angle formed between the two linear entities.
99 Description of Method Used:
100 ===========================
101 From the dot product of vectors v1 and v2 it is known that:
102 dot(v1, v2) = |v1|*|v2|*cos(A)
103 where A is the angle formed between the two vectors. We can
104 get the directional vectors of the two lines and readily
105 find the angle between the two using the above formula.
109 return C
.acos( (v1
[0]*v2
[0]+v1
[1]*v2
[1]) / (abs(v1
)*abs(v2
)) )
111 def parallel_line(self
, p
):
113 Returns a new Line which is parallel to this linear entity and passes
114 through the specified point.
116 d
= self
.p1
- self
.p2
117 return Line(p
, p
+ d
)
119 def perpendicular_line(self
, p
):
121 Returns a new Line which is perpendicular to this linear entity and
122 passes through the specified point.
124 d1
,d2
= self
.p1
- self
.p2
125 if d2
== 0: # If an horizontal line
126 if p
[1] == self
.p1
[1]: # if p is on this linear entity
127 p2
= Point(p
[0], p
[1] + 1)
130 p2
= Point(p
[0], self
.p1
[1])
133 p2
= Point(p
[0] - d2
, p
[1] + d1
)
136 def perpendicular_segment(self
, p
):
138 Returns a new Segment which connects p to a point on this linear
139 entity and is also perpendicular to this line. Returns p itself
140 if p is on this linear entity.
144 pl
= self
.perpendicular_line(p
)
145 p2
= GeometryEntity
.do_intersection(self
, pl
)[0]
146 return Segment(p
, p2
)
151 The slope of this linear entity, or infinity if vertical.
153 d1
,d2
= self
.p1
- self
.p2
156 return simplify(d2
/d1
)
160 """The two points used to define this linear entity."""
161 return (self
.p1
, self
.p2
)
163 def projection(self
, o
):
165 Project a point, line, ray, or segment onto this linear entity.
166 If projection cannot be performed then a GeometryError is raised.
170 - A projection involves taking the two points that define
171 the linear entity and projecting those points onto a
172 Line and then reforming the linear entity using these
174 - A point P is projected onto a line L by finding the point
175 on L that is closest to P. This is done by creating a
176 perpendicular line through P and L and finding its
179 tline
= Line(self
.p1
, self
.p2
)
182 """Project a point onto the line representing self."""
183 if p
in tline
: return p
184 l1
= tline
.perpendicular_line(p
)
185 return tline
.intersection(l1
)[0]
188 if isinstance(o
, Point
):
190 elif isinstance(o
, LinearEntity
):
196 projected
= o
.__class
__(n_p1
, n_p2
)
198 # Didn't know how to project so raise an error
199 if projected
is None:
200 n1
= self
.__class
__.__name
__
201 n2
= o
.__class
__.__name
__
202 raise GeometryError("Do not know how to project %s onto %s" % (n2
, n1
))
204 return GeometryEntity
.do_intersection(self
, projected
)[0]
206 def intersection(self
, o
):
207 if isinstance(o
, Point
):
212 elif isinstance(o
, LinearEntity
):
213 a1
,b1
,c1
= self
.coefficients
214 a2
,b2
,c2
= o
.coefficients
215 t
= simplify(a1
*b2
- a2
*b1
)
216 if t
== 0: # are parallel?
217 if isinstance(self
, Line
):
221 elif isinstance(o
, Line
):
225 elif isinstance(self
, Ray
):
226 if isinstance(o
, Ray
):
227 # case 1, rays in the same direction
228 if self
.xdirection
== o
.xdirection
:
229 if self
.source
[0] < o
.source
[0]:
232 # case 2, rays in the opposite directions
235 if self
.source
== o
.source
:
237 return [Segment(o
.source
, self
.source
)]
239 elif isinstance(o
, Segment
):
243 return [Segment(o
.p1
, self
.source
)]
245 return [Segment(o
.p2
, self
.source
)]
247 elif isinstance(self
, Segment
):
248 if isinstance(o
, Ray
):
249 return o
.intersection(self
)
250 elif isinstance(o
, Segment
):
251 # A reminder that the points of Segments are ordered
252 # in such a way that the following works. See
253 # Segment.__new__ for details on the ordering.
256 # Neither of the endpoints are in o so either
257 # o is contained in this segment or it isn't
262 # p1 not in o but p2 is. Either there is a
263 # segment as an intersection, or they only
264 # intersect at an endpoint
267 return [Segment(o
.p1
, self
.p2
)]
268 elif self
.p2
not in o
:
269 # p2 not in o but p1 is. Either there is a
270 # segment as an intersection, or they only
271 # intersect at an endpoint
274 return [Segment(o
.p2
, self
.p1
)]
276 # Both points of self in o so the whole segment
280 # Unknown linear entity
283 # Not parallel, so find the point of intersection
284 px
= simplify((b1
*c2
- c1
*b2
) / t
)
285 py
= simplify((a2
*c1
- a1
*c2
) / t
)
286 inter
= Point(px
, py
)
287 if inter
in self
and inter
in o
:
290 raise NotImplementedError()
292 def random_point(self
):
293 """Returns a random point on this Ray."""
294 from random
import randint
295 from sys
import maxint
297 # The lower and upper
298 lower
, upper
= -maxint
-1, maxint
300 if self
.slope
is S
.Infinity
:
301 if isinstance(self
, Ray
):
302 if self
.ydirection
is S
.Infinity
:
306 elif isinstance(self
, Segment
):
311 y
= randint(lower
, upper
)
313 if isinstance(self
, Ray
):
314 if self
.xdirection
is S
.Infinity
:
318 elif isinstance(self
, Segment
):
322 a
,b
,c
= self
.coefficients
323 x
= randint(lower
, upper
)
324 y
= simplify( (-c
- a
*x
) / b
)
327 def __eq__(self
, other
):
328 raise NotImplementedError()
330 def __contains__(self
, other
):
331 raise NotImplementedError()
334 class Line(LinearEntity
):
335 """A line in space."""
337 def arbitrary_point(self
, parameter_name
='t'):
338 """Returns a symbolic point that is on this line."""
339 t
= C
.Symbol(parameter_name
, real
=True)
340 x
= simplify(self
.p1
[0] + t
*(self
.p2
[0] - self
.p1
[0]))
341 y
= simplify(self
.p1
[1] + t
*(self
.p2
[1] - self
.p1
[1]))
344 def plot_interval(self
, parameter_name
='t'):
345 """Returns the plot interval for the default geometric plot of line"""
346 t
= C
.Symbol(parameter_name
, real
=True)
349 def equation(self
, xaxis_name
='x', yaxis_name
='y'):
351 Returns the equation for this line. Optional parameters xaxis_name
352 and yaxis_name can be used to specify the names of the symbols used
355 x
= C
.Symbol(xaxis_name
, real
=True)
356 y
= C
.Symbol(yaxis_name
, real
=True)
357 a
,b
,c
= self
.coefficients
358 return simplify(a
*x
+ b
*y
+ c
)
360 def __contains__(self
, o
):
361 """Return True if o is on this Line, or False otherwise."""
362 if isinstance(o
, Line
):
363 return self
.__eq
__(o
)
364 elif isinstance(o
, Point
):
365 x
= C
.Symbol('x', real
=True)
366 y
= C
.Symbol('y', real
=True)
367 r
= self
.equation().subs({x
: o
[0], y
: o
[1]})
369 return simplify(x
) == 0
373 def __eq__(self
, other
):
374 """Return True if other is equal to this Line, or False otherwise."""
375 if not isinstance(other
, Line
): return False
376 return Point
.is_collinear(self
.p1
, self
.p2
, other
.p1
, other
.p2
)
379 class Ray(LinearEntity
):
380 """A ray in space."""
384 """The point from which the ray eminates."""
388 def xdirection(self
):
390 The x direction of the ray. Positive infinity if the ray points in
391 the positive x direction, negative infinity if the ray points
392 in the negative x direction, or 0 if the ray is vertical.
394 if self
.p1
[0] < self
.p2
[0]:
396 elif self
.p1
[0] == self
.p2
[0]:
399 return S
.NegativeInfinity
402 def ydirection(self
):
404 The y direction of the ray. Positive infinity if the ray points in
405 the positive y direction, negative infinity if the ray points
406 in the negative y direction, or 0 if the ray is horizontal.
408 if self
.p1
[1] < self
.p2
[1]:
410 elif self
.p1
[1] == self
.p2
[1]:
413 return S
.NegativeInfinity
415 def __eq__(self
, other
):
416 """Return True if other is equal to this Ray, or False otherwise."""
417 if not isinstance(other
, Ray
):
419 return ((self
.source
== other
.source
) and (other
.p2
in self
))
421 def __contains__(self
, o
):
422 """Return True if o is on this Ray, or False otherwise."""
423 if isinstance(o
, Ray
):
425 return Point
.is_collinear(self
.p1
, self
.p2
, o
.p1
, o
.p2
) \
426 and (self
.xdirection
== o
.xdirection
) \
427 and (self
.ydirection
== o
.ydirection
)
428 elif isinstance(o
, Segment
):
429 return ((o
.p1
in self
) and (o
.p2
in self
))
430 elif isinstance(o
, Point
):
431 if Point
.is_collinear(self
.p1
, self
.p2
, o
):
432 if (not self
.p1
[0].atoms(C
.Symbol
)) and (not self
.p1
[1].atoms(C
.Symbol
)) \
433 and (not self
.p2
[0].atoms(C
.Symbol
)) and (not self
.p2
[1].atoms(C
.Symbol
)):
434 if self
.xdirection
is S
.Infinity
:
435 return o
[0] >= self
.source
[0]
436 elif self
.xdirection
is S
.NegativeInfinity
:
437 return o
[0] <= self
.source
[0]
438 elif self
.ydirection
is S
.Infinity
:
439 return o
[1] >= self
.source
[1]
440 return o
[1] <= self
.source
[1]
442 # There are symbols lying around, so assume that o
443 # is contained in this ray (for now)
446 # Points are not collinear, so the rays are not parallel
447 # and hence it isimpossible for self to contain o
450 # No other known entity can be contained in a Ray
454 class Segment(LinearEntity
):
455 """An undirected line segment in space."""
457 def __new__(cls
, p1
, p2
, **kwargs
):
458 # Reorder the two points under the following ordering:
459 # if p1[0] != p2[0] then p1[0] < p2[0]
460 # if p1[0] == p2[0] then p1[1] < p2[1]
463 elif p1
[0] == p2
[0] and p1
[1] > p2
[0]:
465 return LinearEntity
.__new
__(cls
, p1
, p2
, **kwargs
)
467 def arbitrary_point(self
, parameter_name
='t'):
468 """Returns a symbolic point that is on this line segment."""
469 t
= C
.Symbol(parameter_name
, real
=True)
470 x
= simplify(self
.p1
[0] + t
*(self
.p2
[0] - self
.p1
[0]))
471 y
= simplify(self
.p1
[1] + t
*(self
.p2
[1] - self
.p1
[1]))
474 def plot_interval(self
, parameter_name
='t'):
475 t
= C
.Symbol(parameter_name
, real
=True)
478 def perpendicular_bisector(self
, p
=None):
480 Returns the perpendicular bisector of this segment. If no point is
481 specified or the point specified is not on the bisector then the
482 bisector is returned as a Line. Otherwise a Segment is returned
483 that joins the point specified and the intersection of the bisector
486 l
= LinearEntity
.perpendicular_line(self
, self
.midpoint
)
487 if p
is None or p
not in l
:
490 return Segment(self
.midpoint
, p
)
494 """The length of the segment."""
495 return Point
.distance(self
.p1
, self
.p2
)
499 """The midpoint of the segment."""
500 return Point
.midpoint(self
.p1
, self
.p2
)
502 def __eq__(self
, other
):
503 """Return True if other is equal to this Line, or False otherwise."""
504 if not isinstance(other
, Segment
):
506 return ((self
.p1
== other
.p1
) and (self
.p2
== other
.p2
))
508 def __contains__(self
, o
):
509 """Return True if o is on this Segment, or False otherwise."""
510 if isinstance(o
, Segment
):
511 return ((o
.p1
in self
) and (o
.p2
in self
))
512 elif isinstance(o
, Point
):
513 if Point
.is_collinear(self
.p1
, self
.p2
, o
):
514 x1
,x2
= self
.p1
[0], self
.p2
[0]
515 if not (x1
.atoms(C
.Symbol
)) or (x2
.atoms(C
.Symbol
)):
516 return (min(x1
,x2
) <= o
[0]) and (o
[0] <= max(x1
,x2
))
522 # No other known entity can be contained in a Ray