# -*- coding: utf-8 -*-
"""
============================================================================
2D geometric regions (:mod:`sknano.core.geometric_regions._2D_regions`)
============================================================================
.. currentmodule:: sknano.core.geometric_regions._2D_regions
"""
from __future__ import absolute_import, division, print_function, \
unicode_literals
__docformat__ = 'restructuredtext en'
from abc import ABCMeta, abstractmethod
# from functools import reduce
import numpy as np
from sknano.core.math import Point, Vector
from .base import GeometricRegion, GeometricTransformsMixin, ndim_errmsg
__all__ = ['Geometric2DRegion', 'Parallelogram', 'Rectangle', 'Square',
'Ellipse', 'Circle', 'Triangle', 'geometric_2D_regions']
[docs]class Geometric2DRegion(GeometricRegion, GeometricTransformsMixin,
metaclass=ABCMeta):
"""Abstract base class for representing 2D geometric regions."""
@property
def ndim(self):
"""Return the dimensions."""
return 2
@property
@abstractmethod
def area(self):
"""Area of 2D geometric region."""
raise NotImplementedError
@property
def measure(self):
"""Alias for :attr:`~Geometric2DRegion.area`, which is the measure \
of a 2D geometric region."""
return self.area
@property
def bounding_box(self):
"""Bounding :class:`Cuboid`."""
return Rectangle(pmin=self.pmin.tolist(), pmax=self.pmax.tolist())
[docs]class Parallelogram(Geometric2DRegion):
"""`Geometric2DRegion` for a parallelogram.
.. versionadded:: 0.3.0
Represents a parallelogram with origin :math:`o=(o_x, o_y)` and
direction vectors :math:`\\mathbf{u}=(u_x, u_y)` and
:math:`\\mathbf{v}=(v_x, v_y)`.
Parameters
----------
o : array_like, optional
Parallelogram origin. If `None`, it defaults to `o=[0, 0]`.
u, v : array_like, optional
Parallelogram direction vectors stemming from origin `o`.
If `None`, then the default values are `u=[1, 0]` and `v=[1, 1]`.
Notes
-----
:class:`Parallelogram` represents the bounded region
:math:`\\left \\{o+\\lambda_1\\mathbf{u}+\\lambda_2\\mathbf{v}\\in R^2
|0\\le\\lambda_i\\le 1\\right\\}`, where :math:`\\mathbf{u}` and
:math:`\\mathbf{v}` have to be linearly independent.
Calling :class:`Paralleogram` with no parameters is equivalent to
:class:`Parallelogram`\ `(o=[0, 0], u=[1, 0], v=[1, 1])`
"""
def __init__(self, o=None, u=None, v=None):
super().__init__()
if o is None:
o = [0, 0]
self._o = Point(o)
if u is None:
u = [1, 0]
self._u = Vector(u, p0=self.o)
if v is None:
v = [1, 1]
self._v = Vector(v, p0=self.o)
self.points.append(self.o)
self.vectors.extend([self.u, self.v])
self.fmtstr = "o={o!r}, u={u!r}, v={v!r}"
@property
def o(self):
"""2D point coordinates :math:`(o_x, o_y)` of origin.
Returns
-------
:class:`Point`
2D :class:`Point` coordinates :math:`(o_x, o_y)` of origin.
"""
return self._o
@o.setter
def o(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
# self._o[:] = Point(value)
self.translate(Vector(p0=self.o, p=Point(value)))
@property
def u(self):
"""2D direction vector :math:`\\mathbf{u}=(u_x, u_y)`, with origin \
:attr:`~Paralleogram.o`"""
return self._u
@u.setter
def u(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._u[:] = Vector(value)
self._u.p0 = self.o
@property
def v(self):
"""2D direction vector :math:`\\mathbf{v}=(v_x, v_y)`, with origin \
:attr:`~Paralleogram.o`"""
return self._v
@v.setter
def v(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._v[:] = Vector(value)
self._v.p0 = self.o
@property
def area(self):
""":class:`Paralleogram` area, \
:math:`A=|\\mathbf{u}\\times\\mathbf{v}|`.
Computed as:
.. math::
A = |\\mathbf{u}\\times\\mathbf{v}|
"""
u = self.u
v = self.v
return np.abs(np.cross(u, v))
@property
def centroid(self):
"""Paralleogram centroid, :math:`(c_x, c_y)`.
Computed as the 2D point :math:`(c_x, c_y)` with coordinates:
.. math::
c_x = o_x + \\frac{u_x + v_x}{2}
c_y = o_y + \\frac{u_y + v_y}{2}
where :math:`(o_x, o_y)`, :math:`(u_x, u_y)`, and :math:`(v_x, v_y)`
are the :math:`(x, y)` coordinates of the origin :math:`o`
and :math:`(x, y)` components of the direction vectors
:math:`\\mathbf{u}` and :math:`\\mathbf{v}`, respectively.
Returns
-------
:class:`Point`
2D :class:`Point` of centroid.
"""
ox, oy = self.o
ux, uy = self.u
vx, vy = self.v
cx = ox + (ux + vx) / 2
cy = oy + (uy + vy) / 2
return Point([cx, cy])
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Parallelogram`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Paralleogram`,
`False` otherwise
Notes
-----
A `point` :math:`(p_x, p_y)` is within the bounded region of
a parallelogram with origin :math:`(o_x, o_y)` and direction
vectors :math:`\\mathbf{u}=(u_x, u_y)` and
:math:`\\mathbf{v}=(v_x, v_y)` if the following is true:
.. math::
0\\le\\frac{(p_y - o_y) v_x + (o_x - p_x) v_y}{u_y v_x - u_x v_y}
\\le 1 \\land
0\\le\\frac{(p_y - o_y) u_x + (o_x - p_x) u_y}{u_x v_y - u_y v_x}
\\le 1
"""
px, py = Point(point)
ox, oy = self.o
ux, uy = self.u
vx, vy = self.v
q1 = ((py - oy) * vx + (ox - px) * vy) / (uy * vx - ux * vy)
q2 = ((py - oy) * ux + (ox - px) * uy) / (ux * vy - uy * vx)
return q1 >= 0 and q1 <= 1 and q2 >= 0 and q2 <= 1
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Paralleogram` \
constructor parameters."""
return dict(o=self.o, u=self.u, v=self.v)
[docs]class Rectangle(Geometric2DRegion):
"""`Geometric2DRegion` for a rectangle.
.. versionadded:: 0.3.0
Represents an axis-aligned bounded region from
:math:`p_{\\mathrm{min}}=(x_{\\mathrm{min}},y_{\\mathrm{min}})` to
:math:`p_{\\mathrm{max}}=(x_{\\mathrm{max}},y_{\\mathrm{max}})`.
Parameters
----------
pmin, pmax : array_like, optional
The minimum and maximum 2D point coordinates of the axis-aligned
rectangle from `pmin=[xmin, ymin]` to `pmax=[xmax, ymax]`.
xmin, ymin : float, optional
The minimum :math:`(x, y)` point of the axis-aligned rectangle.
xmax, ymax : float, optional
The maximum :math:`(x, y)` point of the axis-aligned rectangle.
Notes
-----
:class:`Rectangle` represents the region
:math:`\\left\\{\\{x, y\\}|x_{\\mathrm{min}}\\le x\\le x_{\\mathrm{max}}
\\land y_{\\mathrm{min}}\\le y\\le y_{\\mathrm{max}}\\right\\}`
Calling :class:`Rectangle` with no parameters is equivalent to
:class:`Rectangle`\ `(pmin=[0, 0], pmax=[1, 1])`.
"""
def __init__(self, pmin=None, pmax=None, xmin=0, ymin=0, xmax=1, ymax=1):
super().__init__()
if pmin is None:
pmin = [xmin, ymin]
self._pmin = Point(pmin)
if pmax is None:
pmax = [xmax, ymax]
self._pmax = Point(pmax)
self.points.extend([self._pmin, self._pmax])
assert np.all(np.less_equal(self.pmin, self.pmax))
self.fmtstr = "pmin={pmin!r}, pmax={pmax!r}"
def _update_points(self):
pmin = self.pmin.copy()
pmax = self.pmax.copy()
self._pmin = np.minimum(pmin, pmax)
self._pmax = np.maximum(pmin, pmax)
assert np.all(np.less_equal(self._pmin, self._pmax))
self.points.clear()
self.points.extend([self.pmin, self.pmax])
@property
def pmin(self):
"""2D :class:`Point` at \
(:attr:`~Rectangle.xmin`, :attr:`~Rectangle.ymin`)."""
# return self._pmin
return super().pmin
@pmin.setter
def pmin(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or \
len(value) != self.ndim:
raise TypeError(ndim_errmsg.format(self.ndim))
self._pmin[:] = Point(value)
self._update_points()
@property
def pmax(self):
"""2D :class:`Point` at \
(:attr:`~Rectangle.xmax`, :attr:`~Rectangle.ymax`)."""
# return self._pmax
return super().pmax
@pmax.setter
def pmax(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or \
len(value) != self.ndim:
raise TypeError(ndim_errmsg.format(self.ndim))
self._pmax[:] = Point(value)
self._update_points()
@property
def xmin(self):
""":math:`x_{\\mathrm{min}}` coordinate."""
return self.pmin.x
@xmin.setter
def xmin(self, value):
self._pmin.x = float(value)
self._update_points()
@property
def xmax(self):
""":math:`x_{\\mathrm{max}}` coordinate."""
return self.pmax.x
@xmax.setter
def xmax(self, value):
self._pmax.x = float(value)
self._update_points()
@property
def ymin(self):
""":math:`y_{\\mathrm{min}}` coordinate."""
return self.pmin.y
@ymin.setter
def ymin(self, value):
self._pmin.y = float(value)
self._update_points()
@property
def ymax(self):
""":math:`y_{\\mathrm{max}}` coordinate."""
return self.pmax.y
@ymax.setter
def ymax(self, value):
self._pmax.y = float(value)
self._update_points()
@property
def lengths(self):
""":class:`~python:tuple` of side lengths"""
return self.lx, self.ly
@property
def lx(self):
"""Distance between :math:`x_{\\mathrm{max}}-x_{\\mathrm{min}}`."""
return self.xmax - self.xmin
@property
def ly(self):
"""Distance between :math:`y_{\\mathrm{max}}-y_{\\mathrm{min}}`."""
return self.ymax - self.ymin
@property
def a(self):
"""Alias for :attr:`Rectangle.lx`."""
return self.lx
@property
def b(self):
"""Alias for :attr:`Rectangle.ly`."""
return self.ly
@property
def area(self):
""":class:`Rectangle` area, :math:`A=\\ell_x\\ell_y`"""
return self.lx * self.ly
@property
def centroid(self):
""":class:`Rectangle` centroid, :math:`(c_x, c_y)`.
Computed as the 2D :class:`Point` :math:`(c_x, c_y)` with
coordinates:
.. math::
c_x = \\frac{x_{\\mathrm{min}}+x_{\\mathrm{max}}}{2}
c_y = \\frac{y_{\\mathrm{min}}+y_{\\mathrm{max}}}{2}
Returns
-------
:class:`Point`
2D :class:`Point` of centroid.
"""
cx = (self.xmax + self.xmin) / 2
cy = (self.ymax + self.ymin) / 2
return Point([cx, cy])
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Rectangle`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Rectangle`,
`False`, otherwise.
Notes
-----
A point :math:`(p_x, p_y)` is within the bounded region of a
rectangle with lower corner at
:math:`p_{\\mathrm{min}}=
(x_{\\mathrm{min}}, y_{\\mathrm{min}})` and
upper corner at
:math:`p_{\\mathrm{max}}=
(x_{\\mathrm{max}}, y_{\\mathrm{max}})` if the
following is true:
.. math::
x_{\mathrm{min}}\\le x\\le x_{\\mathrm{max}}\\land
y_{\mathrm{min}}\\le y\\le y_{\\mathrm{max}}
"""
px, py = Point(point)
xmin = self.xmin
xmax = self.xmax
ymin = self.ymin
ymax = self.ymax
return (px >= xmin) and (px <= xmax) and (py >= ymin) and (py <= ymax)
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Rectangle` \
constructor parameters."""
return dict(pmin=self.pmin, pmax=self.pmax)
[docs]class Square(Geometric2DRegion):
"""`Geometric2DRegion` for a square.
.. versionadded:: 0.3.0
Represents the axis-aligned bounded region with center
:math:`(c_x, c_y)` and side length :math:`a`.
Parameters
----------
center : array_like, optional
The center point coordinate :math:`(c_x, c_y)`
of the axis-aligned square.
a : float, optional
The side length :math:`a` of the axis-aligned square.
Notes
-----
:class:`Square` represents the region
:math:`\\left\\{\\{c_i\\pm\\frac{a}{2}\\}|a>0\\forall
i\\in\\{x,y\\}\\right\\}`.
Calling :class:`Square` with no parameters is equivalent to
:class:`Square`\ `(center=[0, 0], a=1)`.
"""
def __init__(self, center=None, a=1):
super().__init__()
if center is None:
center = [0, 0]
self._center = Point(center)
self.a = a
self.points.append(self.center)
self.fmtstr = "center={center!r}, a={a:.2f}"
@property
def center(self):
"""Center point :math:`(c_x, c_y)` of axis-aligned square."""
return self._center
@center.setter
def center(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._center[:] = Point(value)
@property
def a(self):
"""Side length :math:`a` of the axis-aligned square."""
return self._a
@a.setter
def a(self, value):
self._a = float(value)
@property
def area(self):
""":class:`Square` area, :math:`A=a^2`."""
return self.a ** 2
@property
def centroid(self):
"""Alias for :attr:`~Square.center`."""
return self.center
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Square`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Square`,
`False` otherwise.
Notes
-----
A `point` :math:`(p_x, p_y)` is within the bounded region of a
square with center :math:`(c_x, c_y)` and side length :math:`a`
if the following is true:
.. math::
c_i - \\frac{a}{2}\\le p_i\\le c_i + \\frac{a}{2}\\forall
i\\in \\{x, y\\}
"""
px, py = Point(point)
cx, cy = self.center
a = self.a
xmin = cx - a / 2
xmax = cx + a / 2
ymin = cy - a / 2
ymax = cy + a / 2
return (px >= xmin) and (px <= xmax) and (py >= ymin) and (py <= ymax)
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Square` \
constructor parameters."""
return dict(center=self.center, a=self.a)
[docs]class Triangle(Geometric2DRegion):
"""`Geometric2DRegion` for a triangle.
.. versionadded:: 0.3.10
Represents the bounded region with corner points
:math:`p_1=(x_1,y_1)`, :math:`p_2=(x_2,y_2)`, and
:math:`p_3=(x_3,y_3)`.
Parameters
----------
p1, p2, p3 : array_like, optional
2-tuples or :class:`~sknano.core.Point` class instances
specifying the `Triangle` corner points :math:`p_1=(x_1,y_1)`,
:math:`p_2=(x_2,y_2)`, and :math:`p_3=(x_3, y_3)`.
Notes
-----
:class:`Triangle` represents a 2D geometric region consisting of all
combinations of corner points :math:`p_i`,
:math:`\\left\\{\\lambda_1 p_1+\\lambda_2 p_2 + \\lambda_3 p_3|
\\lambda_i\\ge0\\land\\lambda_1+\\lambda_2+\\lambda_3=1\\right\\}`.
Calling :class:`Triangle` with no parameters is equivalent to
:class:`Triangle`\ `(p1=[0, 0], p2=[0, 1], p3=[1, 0])`.
"""
def __init__(self, p1=None, p2=None, p3=None):
super().__init__()
if p1 is None:
p1 = [0, 0]
self._p1 = Point(p1)
if p2 is None:
p2 = [0, 1]
self._p2 = Point(p2)
if p3 is None:
p3 = [1, 0]
self._p3 = Point(p3)
self.points.extend([self.p1, self.p2, self.p3])
self.fmtstr = "p1={p1!r}, p2={p2!r}, p3={p3!r}"
@property
def p1(self):
"""Corner point :math:`p_1=(x_1, y_1)`."""
return self._p1
@p1.setter
def p1(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._p1[:] = Point(value)
@property
def p2(self):
"""Corner point :math:`p_2=(x_2, y_2)`."""
return self._p2
@p2.setter
def p2(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._p2[:] = Point(value)
@property
def p3(self):
"""Corner point :math:`p_3=(x_3, y_3)`."""
return self._p3
@p3.setter
def p3(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._p3[:] = Point(value)
@property
def area(self):
""":class:`Triangle` area.
Computed as:
.. math::
A = \\frac{1}{2}|-x_2 y_1 + x_3 y_1 + x_1 y_2 - x_3 y_2 - x_1 y_3
+ x_2 y_3|
"""
x1, y1 = self.p1
x2, y2 = self.p2
x3, y3 = self.p3
return np.abs(-x2 * y1 + x3 * y1 + x1 * y2 - x3 * y2 - x1 * y3 +
x2 * y3) / 2
@property
def centroid(self):
""":class:`Triangle` centroid, :math:`(c_x, c_y)`.
Computed as 2D :class:`Point` :math:`(c_x, c_y)` with
coordinates:
.. math::
c_x = \\frac{x_1 + x_2 + x_3}{3}
c_y = \\frac{y_1 + y_2 + y_3}{3}
Returns
-------
:class:`Point`
2D :class:`Point` of centroid.
"""
x1, y1 = self.p1
x2, y2 = self.p2
x3, y3 = self.p3
cx = (x1 + x2 + x3) / 3
cy = (y1 + y2 + y3) / 3
return Point([cx, cy])
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Triangle`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Triangle`,
`False`, otherwise.
Notes
-----
A point :math:`(p_x, p_y)` is within the bounded region of a
triangle with corner points :math:`p_1=(x_1, y_1)`,
:math:`p_2=(x_2, y_2)`, and :math:`p_3=(x_3, y_3)`, if the
following is true:
.. math::
\\frac{(x_1 - x_3) p_y + (x_3 - p_x) y_1 + (p_x - x_1) y_3}{
(y_1-y_2) x_3 + (y_2 - y_3) x_1 + (y_3 - y_1) x_2}\\ge 0\\land
\\frac{(x_2 - x_1) p_y + (p_x - x_2) y_1 + (x_1 - p_x) y_2}{
(y_1 - y_2) x_3 + (y_2 - y_3) x_1 + (y_3 - y_1) x_2}\\ge 0\\land
\\frac{(x_2 - x_3) p_y + (x_3 - p_x) y_2 + (p_x - x_2) y_3}{
(y_1 - y_2) x_3 + (y_2 - y_3) x_1 + (y_3 - y_1) x_2}\\le 0
"""
px, py = Point(point)
x1, y1 = self.p1
x2, y2 = self.p2
x3, y3 = self.p3
q1 = ((x1 - x3) * py + (x3 - px) * y1 + (px - x1) * y3) / \
((y1 - y2) * x3 + (y2 - y3) * x1 + (y3 - y1) * x2)
q2 = ((x2 - x1) * py + (px - x2) * y1 + (x1 - px) * y2) / \
((y1 - y2) * x3 + (y2 - y3) * x1 + (y3 - y1) * x2)
q3 = ((x2 - x3) * py + (x3 - px) * y2 + (px - x2) * y3) / \
((y1 - y2) * x3 + (y2 - y3) * x1 + (y3 - y1) * x2)
return q1 >= 0 and q2 >= 0 and q3 <= 0
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Triangle` \
constructor parameters."""
return dict(p1=self.p1, p2=self.p2, p3=self.p3)
[docs]class Ellipse(Geometric2DRegion):
"""`Geometric2DRegion` for an ellipse.
.. versionadded:: 0.3.0
Represents an axis-aligned ellipse centered at :math:`(c_x, c_y)`
with semi-axes lengths :math:`r_x, r_y`.
Parameters
----------
center : array_like, optional
Center of axis-aligned ellipse with semi-axes lengths :math:`r_x, r_y`
rx, ry : float
Lengths of semi-axes :math:`r_x, r_y`
Notes
-----
:class:`Ellipse` represents the axis-aligned ellipsoid:
.. math::
\\left\\{\\{x, y, z\\}\\in R^3|
\\left(\\frac{x-c_x}{r_x}\\right)^2 +
\\left(\\frac{y-c_y}{r_y}\\right)^2 +
\\left(\\frac{z-c_z}{r_z}\\right)^2\\le 1\\right\\}
Calling :class:`Ellipse` with no parameters is equivalent to
:class:`Ellipse`\ `(center=[0, 0], rx=1, ry=1)`.
"""
def __init__(self, center=None, rx=1, ry=1):
super().__init__()
if center is None:
center = [0, 0]
self._center = Point(center)
self.rx = rx
self.ry = ry
self.points.append(self.center)
self.fmtstr = "center={center!r}, rx={rx:.3f}, ry={ry:.3f}"
@property
def center(self):
""":class:`Ellipse` center point :math:`(c_x, c_y)`."""
return self._center
@center.setter
def center(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._center[:] = Point(value)
@property
def rx(self):
"""Length of semi-axis :math:`r_x`."""
return self._rx
@rx.setter
def rx(self, value):
self._rx = float(value)
@property
def ry(self):
"""Length of semi-axis :math:`r_y`."""
return self._ry
@ry.setter
def ry(self, value):
self._ry = float(value)
@property
def area(self):
""":class:`Ellipse` area, :math:`A=\\pi r_x r_y`."""
return np.pi * self.rx * self.ry
@property
def centroid(self):
"""Alias for :attr:`~Ellipse.center`."""
return self.center
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Ellipse`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Ellipse`,
`False` otherwise
Notes
-----
A `point` :math:`(p_x, p_y)` is within the bounded region of
an ellipse with center :math:`(c_x, c_y)` and semi-axes lengths
:math:`r_x, r_y` if the following is true:
.. math::
\\left(\\frac{p_x - c_x}{r_x}\\right)^2 +
\\left(\\frac{p_y - c_y}{r_y}\\right)^2\\le 1
"""
px, py = Point(point)
cx, cy = self.center
rx, ry = self.rx, self.ry
q1 = (px - cx) ** 2 / rx ** 2
q2 = (py - cy) ** 2 / ry ** 2
return q1 + q2 <= 1
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Ellipse` \
constructor parameters."""
return dict(center=self.center, rx=self.rx, ry=self.ry)
[docs]class Circle(Geometric2DRegion):
"""`Geometric2DRegion` for a circle.
.. versionadded:: 0.3.0
Represents the bounded region with center :math:`(h, k)` and
radius :math:`r`.
Parameters
----------
center : array_like, optional
Center point :math:`(h, k)` of circle.
r : float, optional
Radius :math:`r` of circle.
Notes
-----
Calling :class:`Circle` with no parameters is equivalent to
:class:`Circle`\ `(center=[0, 0], r=1.0)`.
"""
def __init__(self, center=None, r=1.0):
super().__init__()
if center is None:
center = [0, 0]
self._center = Point(center)
self.r = r
self.points.append(self.center)
self.fmtstr = "center={center!r}, r={r:.3f}"
@property
def center(self):
"""Center point :math:`(h, k)` of circle."""
return self._center
@center.setter
def center(self, value):
if not isinstance(value, (tuple, list, np.ndarray)) or len(value) != 2:
raise TypeError('Expected a 2-element array_like object')
self._center[:] = Point(value)
@property
def r(self):
""":class:`Circle` radius, :math:`r`."""
return self._r
@r.setter
def r(self, value):
self._r = float(value)
@property
def area(self):
""":class:`Circle` area, :math:`A=\\pi r^2`."""
return np.pi * self.r ** 2
@property
def centroid(self):
"""Alias for :attr:`~Circle.center`."""
return self.center
[docs] def contains(self, point):
"""Test region membership of `point` in :class:`Circle`.
Parameters
----------
point : array_like
Returns
-------
:class:`~python:bool`
`True` if `point` is within :class:`Circle`,
`False` otherwise.
Notes
-----
A `point` :math:`(p_x, p_y)` is within the bounded region
of a circle with center :math:`(h, k)` and radius :math:`r`
if the following is true:
.. math::
(p_x - h)^2 + (p_y - k)^2 \\le r^2
"""
x, y = Point(point)
h, k = self.center
r = self.r
return (x - h) ** 2 + (y - k) ** 2 <= r ** 2
[docs] def todict(self):
"""Returns a :class:`~python:dict` of the :class:`Circle` \
constructor parameters."""
return dict(center=self.center, r=self.r)
geometric_2D_regions = \
[Parallelogram, Rectangle, Square, Ellipse, Circle, Triangle]