Source code for geoptics.elements.line

# -*- coding: utf-8 -*-

# Copyright (C) 2014 ederag <edera@gmx.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GeOptics; see the file LICENSE.txt.  If not, see
# <http://www.gnu.org/licenses/>.


"""Define :term:`Lines`."""


from typing import NamedTuple
from math import cos, pi, sin

from geoptics.elements.vector import Point, Vector


[docs]class Intersection(NamedTuple): """Intersection between a :class:`Line` and another element""" #: point of intersection p: Point #: `s` multiplier of the :class:`Line` (p0, u0), such that ``p = p0 + u0 * s`` s: float #: unit vector normal to the other element, at the intersection eN: Vector #: unit vector tangent to the other element, at the intersection eT: Vector
[docs]class Line(object): """Line defined by a point `p` and a direction vector `u`. Args: p (Point): Reference point belonging to the line u (Vector): Direction vector """ def __init__(self, p=None, u=None): if p is None: self.p = Point(x=0, y=0) else: self.p = p.copy() if u is None: self.u = Vector(x=1, y=0) else: self.u = u.copy() @property def config(self): # noqa: D401 """Configuration dictionary.""" return {'p': self.p.config, 'u': self.u.config, }
[docs] def copy(self): """Return an independent copy.""" return Line(self.p, self.u)
[docs] @classmethod def from_config(cls, config): """Alternate constructor. Args: config (dict): Configuration dictionary Returns: Line: new Line instance Examples: >>> from geoptics.elements.vector import Point, Vector >>> p = Point(10, 20) >>> u = Vector(30, 60) >>> line1 = Line(p, u) >>> config = line1.config >>> line2 = Line.from_config(config) >>> line2.config == config True """ p = Point.from_config(config['p']) u = Vector.from_config(config['u']) return cls(p, u)
[docs] def normal(self, normalized=False): """Return a vector orthogonal to the line. Args: normalized (bool): if True, return a unit vector. Returns: :class:`.Vector` """ normal = self.u.normal(normalized) return normal
[docs] def tangent(self, normalized=False): """Return a tangent vector to the line. For a line, this is basically `u` itself. Args: normalized (bool): if True, return a unit vector. Returns: :class:`.Vector` """ if normalized: tangent = self.u.copy() tangent.normalize() return tangent else: return self.u.copy()
[docs] def intersection(self, other, sign_of_s=0): """Intersections of the line with another line. Args: other (Line): another line. sign_of_s (float): if ``sign_of_s !=0``, consider other as a half line, and search for intersections with `s` having the same sign as `sign_of_s`. Returns: :obj:`list` of :class:`Intersection`: list either empty (no intersection), or holding a single intersection. This is done for consistency with other elements that can have multiple intersections with a line. """ if isinstance(other, Line): if other.u.colinear(self.u): result = [] else: s = ( ( (self.p.x - other.p.x) * self.u.y - (self.p.y - other.p.y) * self.u.x ) / (other.u.x * self.u.y - other.u.y * self.u.x) ) if (sign_of_s == 0 and s != 0) or (s * sign_of_s > 0): Mi = Point(other.p.x + s * other.u.x, other.p.y + s * other.u.y) eN = self.normal(normalized=True) eT = self.tangent(normalized=True) result = [Intersection(Mi, s, eN, eT)] else: result = [] else: raise NotImplementedError( "intersection between Line and {}".format(type(other))) return result
[docs] @staticmethod def interpolate(line_start, line_end, x): """Interpolated line. The returned line is interpolated between two given lines, going ccw from `line_start` to `line_end`. The interpolation is done on `Line.p` and on the `Line.u` angle. Args: line_start (Line): The starting line. line_end (Line): The ending line. x (float): the fraction, between 0.0 (starting line) and 1.0 (ending line). Returns: Line: Interpolated line. """ p = (1 - x) * line_start.p + x * line_end.p angle_start = line_start.u.theta_x() angle_end = line_end.u.theta_x() if angle_end < angle_start: # we want to go ccw from start to end angle_end += 2 * pi angle = (1 - x) * angle_start + x * angle_end u = Vector(x=cos(angle), y=sin(angle)) return Line(p=p, u=u)
[docs] def point(self, s): """Return the :class:`.Point` at the position ``p + s * u``.""" return Point(self.p.x + s * self.u.x, self.p.y + s * self.u.y)
def __repr__(self): return "Line({p}, {u})".format(**vars(self))
[docs] def translate(self, **kwargs): """Translate the starting point. Same syntax and same side effects as :py:func:`geoptics.elements.vector.Point.translate` Likewise, it is possible to insert a ``.copy()`` to avoid side-effects. return `self` for convenience """ self.p.translate(**kwargs) return self