Files
pylib/pylib/geometry.py

512 lines
15 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""2D geometry objects.
:Date: 2019-08-28
.. module:: geometry
:platform: *nix, Windows
:synopsis: Geometry objects.
.. moduleauthor:: Daniel Weschke <daniel.weschke@directbox.de>
"""
import math
import numpy as np
def distance(point1, point2):
"""Distance between two points (or length of a straight line).
:param point1: first point (first end point of straight line)
:type point1: tuple
:param point2: second point (second end point of straight line)
:type point2: tuple
:returns: distance between the two points
:rtype: float
"""
return math.sqrt((point2[0]-point1[0])**2 + (point2[1]-point1[1])**2)
def angle(point1, point2=None):
"""Angle of point or between two points.
:param point1: (first) point
:type point1: tuple
:param point2: second point (default = None)
:type point2: tuple
:returns: angle of point or between two points
:rtype: float
"""
if point2 is None:
return math.atan2(point1[1], point1[0])
return math.atan2(point2[1]-point1[1], point2[0]-point1[0])
def translate(vec, *pts):
"""Translate a point or polygon by a given vector.
:param vec: translation vector
:type vec: tuple
:param `*pts`: points to translate
:returns: (point_x, point_y) or (point1, point2, ...)
:rtype: tuple
.. seealso::
:meth:`translate_xy`
"""
vx, vy = vec
return tuple([(x+vx, y+vy) for (x, y) in pts])
def translate_xy(vec, x, y):
"""Translate a point or polygon by a given vector.
:param vec: translation vector
:type vec: tuple
:param x: points to translate
:type x: int or float or list
:param y: points to translate
:type y: int or float or list
:returns: (x', y')
:rtype: tuple
.. seealso::
:meth:`translate`
"""
vx, vy = vec
if not hasattr(x, "__len__"):
x = [x]
if not hasattr(y, "__len__"):
y = [y]
xp = [xi+vx for xi in x]
yp = [yi+vy for yi in y]
# no list if it is only one value
if len(xp) == 1:
return xp[0], yp[0]
return xp, yp
def rotate(origin, angle, *pts, **kwargs):
"""Rotate a point or polygon counterclockwise by a given angle
around a given origin. The angle should be given in radians.
:param origin: the center of rotation
:type origin: tuple
:param angle: the rotation angle
:type angle: int or float
:param `*pts`: points to rotate
:param `**kwargs`: options
:returns: (point_x, point_y) or (point1, point2, ...)
:rtype: tuple
.. seealso::
:meth:`rotate_xy`
"""
ox, oy = origin
# add first point to the end
if "closed" in kwargs and kwargs["closed"] is True:
pts += (pts[0],)
result = tuple([(ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy),
oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy))
for (px, py) in pts])
# no tuple in tuple if it is only one point
if len(pts) == 1:
return result[0][0], result[0][1]
return result
def rotate_deg(origin, angle, *pts, **kwargs):
"""Rotate a point or polygon counterclockwise by a given angle
around a given origin. The angle should be given in degrees.
:param origin: the center of rotation
:type origin: tuple
:param angle: the rotation angle
:type angle: int or float
:param `*pts`: points to rotate
:param `**kwargs`: options
:returns: (point_x, point_y) or (point1, point2, ...)
:rtype: tuple
.. seealso::
:meth:`rotate`
"""
return rotate(origin, angle*math.pi/180, *pts, **kwargs)
def rotate_xy(origin, angle, x, y, **kwargs):
"""Rotate x and y coordinates counterclockwise by a given angle
around a given origin. The angle should be given in radians.
:param origin: the center of rotation
:type origin: tuple
:param angle: the rotation angle
:type angle: int or float
:param x: x coordinates
:type x: int or float or list
:param y: y coordinates
:type y: int or float or list
:param `**kwargs`: options
.. seealso::
:meth:`rotate`
"""
ox, oy = origin
if not hasattr(x, "__len__"):
x = [x]
if not hasattr(y, "__len__"):
y = [y]
# add first point to the end
if "closed" in kwargs and kwargs["closed"] is True:
x.append(x[0])
y.append(y[0])
x_result = [ox + math.cos(angle) * (xi - ox) - math.sin(angle) * (yi - oy)
for xi, yi in zip(x, y)]
y_result = [oy + math.sin(angle) * (xi - ox) + math.cos(angle) * (yi - oy)
for xi, yi in zip(x, y)]
# no list if it is only one value
if len(x_result) == 1:
return x_result[0], y_result[0]
return x_result, y_result
def rectangle(width, height):
"""\
:param width: the width of the rectangle
:type width: int or float
:param height: the height of the rectangle
:type height: int or float
:returns: (point1, point2, point3, point4)
:rtype: tuple
"""
pt1 = (-width/2, -height/2)
pt2 = (width/2, -height/2)
pt3 = (width/2, height/2)
pt4 = (-width/2, height/2)
return pt1, pt2, pt3, pt4, pt1
def square(width):
"""\
:param width: the edge size of the square
:type width: int or float
:returns: (point1, point2, point3, point4)
:rtype: tuple
.. seealso::
:meth:`rectangle`
"""
return rectangle(width, width)
def lines(pts, **kwargs):
"""Lines defined by a list of end points.
:param pts: list of points in absolute global coordinate system. If
keyword inc is given than the inc decides what the left and the
right end point of the line is, otherwise it is assumed that the
points build a solid line, that is lines between the given points
in given order.
:type pts: list
:param `**kwargs`: options:
* deformation -- list of points. Additional deformation
(translation) at point.
* factor -- factor of the deformation (default = 1).
* inc -- the incidence table, a list of 2 element lists. The inc
decides what the left and the right end point of the line is.
* index_offset -- starting index of lists (default = 0).
:returns: list of endpoints for each line;
[((point1_x, point1_y), (point2_x, point2_y)),
(p1, p2),
...]
:rtype: list
.. seealso::
:meth:`~geometry_plot.plot_lines` of the :mod:`geometry_plot`
module to plot the lines
"""
if 'index_offset' not in kwargs:
kwargs['index_offset'] = 0
if 'deformation' in kwargs:
if 'factor' not in kwargs:
kwargs['factor'] = 1
pts = [(p[0]+d[0]*kwargs['factor'], p[1]+d[1]*kwargs['factor']) for
p, d in zip(pts, kwargs['deformation'])]
if 'inc' in kwargs:
return [(pts[l-kwargs['index_offset']],
pts[r-kwargs['index_offset']]) for l, r in kwargs['inc']]
return list(zip(pts[:-1], pts[1:]))
def cubics(pts, **kwargs):
"""Cubic lines defined by a list of two end points. The deformation
as displacement and rotation (radians) is defined element wise as
keyword argument deformation or global node wise as
global_deformation. The global coordinate system is xy. x in the
right direction and y in the top direction.
:param pts: list of points in absolute global coordinate system. If
keyword inc is given than the inc decides what the left and the
right end point of the line is, otherwise it is assumed that the
points build a solid line, that is lines between the given points
in given order.
:type pts_rot: list
:param `**kwargs`: options:
* deformation -- list of deformation element wise. Additional
deformation (translation and rotation in radians) at element
left and right node.
* rotation_plane -- rotation plane of the element wise
deformation defined by a string; either 'xy' or 'xz' (default
= 'xy'). x in the right direction and y in the top direction
or z in the bottom direction.
* global_deformation -- list of deformation global node wise.
Additional deformation (horizontal translation, vertical
translation and rotation in radians) at node.
* factor -- factor of the derformation (default = 1).
* inc -- the incidence table, a list of 2 element lists. The inc
decides what the left and the right end point of the line is.
* index_offset -- starting index of lists (default = 0).
:returns: list of endpoints for each line;
[(((point1_x, point1_y) angle1), ((point2_x, point2_y), angle2),
(p1, angle1, p2, angle2),
...]
:rtype: list
"""
if 'index_offset' not in kwargs:
kwargs['index_offset'] = 0
if 'deformation' in kwargs or 'global_deformation' in kwargs:
if 'factor' not in kwargs:
kwargs['factor'] = 1
if 'inc' in kwargs:
if 'global_deformation' in kwargs:
lr = [(pts[l-kwargs['index_offset']],
pts[r-kwargs['index_offset']]) for l, r in kwargs['inc']]
ang = [angle(l, r) for l, r in lr]
# system deformation
U = kwargs['global_deformation']
if 'rotation_plane' in kwargs and 'xz' == kwargs['rotation_plane']:
# system deformation left X Z RY right X Z RY element wise
# Z downwards -> convert to X Y RZ
Ue = [[ U[(l-kwargs['index_offset'])][0],
-U[(l-kwargs['index_offset'])][1],
U[(l-kwargs['index_offset'])][2],
U[(r-kwargs['index_offset'])][0],
-U[(r-kwargs['index_offset'])][1],
U[(r-kwargs['index_offset'])][2]] for l, r in kwargs['inc']]
else:
# system deformation left X Y RZ right X Y RZ element wise
Ue = [[U[(l-kwargs['index_offset'])][0],
U[(l-kwargs['index_offset'])][1],
U[(l-kwargs['index_offset'])][2],
U[(r-kwargs['index_offset'])][0],
U[(r-kwargs['index_offset'])][1],
U[(r-kwargs['index_offset'])][2]] for l, r in kwargs['inc']]
# element deformation, X Y RZ to x r rz
# back transformation T^T = [[c, s, 0], [-s, c, 0], [0, 0, 1]]
u = [[( math.cos(angi)*Uei[0]+math.sin(angi)*Uei[1])*kwargs['factor'],
(-math.sin(angi)*Uei[0]+math.cos(angi)*Uei[1])*kwargs['factor'],
Uei[2] *kwargs['factor'],
( math.cos(angi)*Uei[3]+math.sin(angi)*Uei[4])*kwargs['factor'],
(-math.sin(angi)*Uei[3]+math.cos(angi)*Uei[4])*kwargs['factor'],
Uei[5] *kwargs['factor']]
for Uei, angi in zip(Ue, ang)]
else: # deformation
# the deformation is in element coordinate system, therefore the angle is needed
if 'rotation_plane' in kwargs and 'xz' == kwargs['rotation_plane']:
u = [[ ue[0]*kwargs['factor'],
-ue[1]*kwargs['factor'],
ue[2]*kwargs['factor'],
ue[3]*kwargs['factor'],
-ue[4]*kwargs['factor'],
ue[5]*kwargs['factor']] for ue in kwargs['deformation']]
else:
u = [[ui*kwargs['factor'] for ui in ue] for ue in kwargs['deformation']]
return [(pts[l-kwargs['index_offset']],
pts[r-kwargs['index_offset']],
d) for (l, r), d in zip(kwargs['inc'], u)]
return list(zip(pts[:-1], pts[1:]))
def interpolate_hermite(lvd, lr, rvd, rr, lhd=0, rhd=0, scale_x=1, scale_y=1, samples=10):
r"""Interpolate cubic line with hermite boundary conditions.
:param lvd: left vertcal deflection
:type lvd: int or float
:param lr: left rotation
:type lr: int or float
:param rvd: right vertical deflection
:type rvd: int or float
:param rr: right rotation
:type rr: int or float
:param lhd: left horizontal deformation (default = 0)
:type lhd: int or float
:param rhd: right horizontal deformation (default = 0)
:type rhd: int or float
:param scale_x: length of element (default = 1)
:type scale_x: int or float
:param scale_y: factor of the deformation (default = 1).
This does not change the length.
:type scale_y: int or float
:param samples: number of sampling points (default = 10)
:type samples: int
.. math::
s = \frac{x - x_1}{L} \\
x = s\,L + x_1
"""
L = scale_x
Lp = L + rhd - lhd
# x=[0,1] in non-dimensional coordinates
x = np.linspace(0, 1, num=samples)
N1 = 1 - 3*x**2 + 2*x**3
N2 = ( x - 2*x**2 + x**3)*Lp
N3 = 3*x**2 - 2*x**3
N4 = ( - x**2 + x**3)*Lp
x = x*Lp
# x=[0,L] in global coordinates
x = np.linspace(0, Lp, num=samples)
N1 = 1 - 3*x**2/Lp**2 + 2*x**3/Lp**3
N2 = x - 2*x**2/Lp + x**3/Lp**2
N3 = 3*x**2/Lp**2 - 2*x**3/Lp**3
N4 = - x**2/Lp + x**3/Lp**2
v = N1*lvd + N2*lr + N3*rvd + N4*rr
x = x + lhd
y = v*scale_y
return x, y
#
# matplotlib format, return lists for x and y
#
def line(point1, point2, samples=2):
"""Line defined by two end points.
.. math::
y = \\frac{y_2-y_1}{x_2-x_1}(x-x_1) + y_1
:param point1: one end point
:type point1: tuple
:param point2: other end point
:type point2: tuple
:param samples: number of sampling points (default = 2)
:type samples: int
:returns: ((point1_x, point2_x), (points1_y, point2_y)) or
([sample_point1_x, sample_point2_x, ...],
[sample_points1_y, sample_point2_y, ...])
:rtype: tuple
:Example:
>>> x, y = line((0, 0), (1, 0))
>>> print(x, y)
((0, 1), (0, 0))
"""
p1x, p1y = point1
p2x, p2y = point2
denominator = (p1x - p2x)
if samples > 2 and denominator > 0:
x = np.linspace(p1x, p2x, samples)
a = (p1y - p2y) / denominator
b = (p1x*p2y - p2x*p1y) / denominator
y = a*x + b
return x, y
return (p1x, p2x), (p1y, p2y) # matplotlib format
def cubic(point1, angle1, point2, angle2, samples=10):
"""Cubic line defined by two end points and the rotation in radians
at the points.
:param point1: one end point
:type point1: tuple
:param angle1: the slope at the one end point
:type angle1: int or float
:param point2: other end point
:type point2: tuple
:param angle2: the slope at the other end point
:type angle2: int or float
:param samples: number of sampling points (default = 10)
:type samples: int
:returns: ([sample_point1_x, sample_point2_x, ...],
[sample_points1_y, sample_point2_y, ...])
:rtype: tuple
"""
p1x, p1y = point1
p2x, p2y = point2
x = np.linspace(p1x, p2x, num=samples)
p1ys = math.tan(angle1)
p2ys = math.tan(angle2)
a = (p1x*p1ys + p1x*p2ys - p2x*p1ys - p2x*p2ys - 2*p1y + 2*p2y)/(p1x**3 - 3*p1x**2*p2x + 3*p1x*p2x**2 - p2x**3)
b = (- p1x**2*p1ys - 2*p1x**2*p2ys - p1x*p2x*p1ys + p1x*p2x*p2ys + 3*p1x*p1y - 3*p1x*p2y + 2*p2x**2*p1ys + p2x**2*p2ys + 3*p2x*p1y - 3*p2x*p2y)/(p1x**3 - 3*p1x**2*p2x + 3*p1x*p2x**2 - p2x**3)
c = (p1x**3*p2ys + 2*p1x**2*p2x*p1ys + p1x**2*p2x*p2ys - p1x*p2x**2*p1ys - 2*p1x*p2x**2*p2ys - 6*p1x*p2x*p1y + 6*p1x*p2x*p2y - p2x**3*p1ys)/(p1x**3 - 3*p1x**2*p2x + 3*p1x*p2x**2 - p2x**3)
d = (- p1x**3*p2x*p2ys + p1x**3*p2y - p1x**2*p2x**2*p1ys + p1x**2*p2x**2*p2ys - 3*p1x**2*p2x*p2y + p1x*p2x**3*p1ys + 3*p1x*p2x**2*p1y - p2x**3*p1y)/(p1x**3 - 3*p1x**2*p2x + 3*p1x*p2x**2 - p2x**3)
y = a*x**3 + b*x**2 + c*x + d
return x, y
def cubic_deg(point1, angle1, point2, angle2):
"""Cubic line defined by two end points and the roation in degree
at the points.
:param point1: one end point
:type point1: tuple
:param angle1: the slope at the one end point
:type angle1: int or float
:param point2: other end point
:type point2: tuple
:param angle2: the slope at the other end point
:type angle2: int or float
:returns: ([sample_point1_x, sample_point2_x, ...],
[sample_points1_y, sample_point2_y, ...])
:rtype: tuple
.. seealso::
:meth:`cubic`
"""
return cubic(point1, angle1 * math.pi/180, point2, angle2 * math.pi/180)