Skip to content
Line.py 7.52 KiB
Newer Older
####################################################################################################
#
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# Patro - A Python library to make patterns for fashion design
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# Copyright (C) 2017 Fabrice Salvaire
#
# 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
"""Module to implement line.

"""

####################################################################################################

__all__ = ['Line2D']

####################################################################################################

from Patro.Common.IterTools import pairwise

from .Primitive import Primitive, Primitive2DMixin
from .Vector import Vector2D

####################################################################################################

class Line2D(Primitive2DMixin, Primitive):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to implement 2D Line."""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    @staticmethod
    def from_two_points(p0, p1):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Construct a :class:`Line2D` from two points."""

        return Line2D(p0, p1 - p0)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __init__(self, point, vector):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Construct a :class:`Line2D` from a point and a vector."""

        self.p = point
        self.v = vector

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def clone(self):
        return self.__class__(self.p, self.v)

    ##############################################

    def __str__(self):

        str_format = '''Line
  Point  {0.p}
  Vector {0.v}
    magnitude {1}
        return str_format.format(self, self.v.magnitude)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################
    @property
    def is_infinite(self):
        return True

    ##############################################

    def interpolate(self, s):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the Point corresponding to the curvilinear abscissa s"""
        return self.p + (self.v * s)

    point_at_s = interpolate
    point_at_t = interpolate

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def compute_distance_between_abscissae(self, s0, s1):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Compute distance between two abscissae"""
        return abs(s1 - s0) * self.v.magnitude()

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def compute_distance(self, s_list):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Compute distance between a set of abscissae"""

        # Fixme: ?
        #   s_list_sorted = copy.deepcopy(s_list)
        #   s_list_sorted.sort()

        return [self.compute_distance_between_abscissae(s0, s1) for s0, s1 in pairwise(s_list)]

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def get_y_from_x(self, x):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return y corresponding to x"""
        return self.v.tan * (x - self.p.x) + self.p.y
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def get_x_from_y(self, y):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return x corresponding to y"""
        return self.v.inverse_tan * (y - self.p.y) + self.p.x
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    # Fixme: is_parallel_to

    def is_parallel(self, other, return_cross=False):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Self is parallel to other"""
        return self.v.is_parallel(other.v, return_cross)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def is_orthogonal(self, other):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Self is orthogonal to other"""
        return self.v.is_orthogonal(other.v)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def shifted_parallel_line(self, shift):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the shifted parallel line"""
        n.normalise()
        point = self.p + n*shift

        return self.__class__(point, self.v)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def orthogonal_line_at_abscissa(self, s):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the orthogonal line at abscissa s"""
        point = self.interpolate(s)

        return self.__class__(point, vector)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def intersection_abscissae(l1, l2):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the intersection abscissae between l1 and l2"""

        # l1 = p1 + s1*v1
        # l2 = p2 + s2*v2
        # at intersection l1 = l2
        # p2 + s2*v2 = p1 + s1*v1
        # delta = p2 - p1 = s1*v1 - s2*v2
        # delta x v1 = - s2 * v2 x v1 = s2 * v1 x v2
        # delta x v2                  = s1 * v1 x v2

        test, cross = l1.is_parallel(l2, return_cross=True)
        if test:
            return (None, None)
        else:
            denominator = 1. / cross
            delta = l2.p - l1.p
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            s1 = delta.cross(l2.v) * denominator
            s2 = delta.cross(l1.v) * denominator
            return (s1, s2)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def intersection(self, other):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the intersection Point between self and other"""
        s1, s2 = self.intersection_abscissae(other)
        if s1 is None:
            return None
        else:
            return self.interpolate(s1)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def projected_abscissa(self, point):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the abscissa corresponding to the perpendicular projection of a point to the line

        """

        delta = point - self.p
        s = delta.projection_on(self.v)

        return s

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def distance_to_line(self, point):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the distance of a point to the line"""
        # Reference: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line

        # Line equation: a*x + b*y + c = 0
        # d = |a*x + b*y + c| / sqrt(a**2 + b**2)
        # Vx*y - Vy*x + c = 0
        # c = Vy*X0 - Vx*Y0
        # d = (vx*(y - y0) - vy*(x - x0)) / |V|
        # d = V x (P - P0) / |V|

        # x0 = self.p.x
        # y0 = self.p.y
        # vx = self.v.x
        # vy = self.v.y

        # return (self.v.x*(point.y - self.p.y) - self.v.y*(point.x - self.p.x)) / self.v.magnitude

        delta = point - self.p
        d = delta.deviation_with(self.v)
        return d

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def distance_and_abscissa_to_line(self, point):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the distance of a point to the line"""

        delta = point - self.p
        if delta.magnitude_square == 0:
            return 0, 0
        else:
            d = delta.deviation_with(self.v)
            s = delta.projection_on(self.v)
            return d, s # distance to line, abscissa
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def get_x_y_from_bounding_box(self, interval):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Return the bounding box build on the intersection of the input bounding box with the line

        """

        left, bottom, right, top = interval.bounding_box()
        vb = Vector2D(interval.size())
            x_min, y_min = self.get_x_from_y(bottom), bottom
            x_max, y_max = self.get_x_from_y(top), top
        else:
            x_min, y_min = left, self.get_y_from_x(left)
            x_max, y_max = right, self.get_y_from_x(right)

        return Vector2D(x_min, y_min), Vector2D(x_max, y_max)