[docs]
class Coordinate2D(Coordinate):
"""
The Coordinate2D is a specialized extension of the `Coordinate` class
for 2-dimensional (x, y) data.
"""
default_dimensions = 2 # Used when setting up empty templates
[docs]
def empty_copy(self):
"""
Return an unpopulated instance of the coordinates.
Returns
-------
Coordinate2D
"""
return super().empty_copy()
[docs]
def copy(self):
"""
Return a copy of the Coordinate2D.
Returns
-------
Coordinate2D
"""
return super().copy()
@property
def ndim(self):
"""
Return the number of dimensions in the coordinate.
Returns
-------
dimensions : int
"""
return 2
@property
def x(self):
"""
Return the x coordinate.
Returns
-------
float or numpy.ndarray (float)
"""
if self.coordinates is None:
return None
return self.coordinates[0]
@x.setter
def x(self, value):
"""
Set the x coordinate.
Parameters
----------
value : float or numpy.ndarray
Returns
-------
None
"""
self.set_x(value, copy=True)
@property
def y(self):
"""
Return the y coordinate.
Returns
-------
float or numpy.ndarray (float)
"""
if self.coordinates is None:
return None
return self.coordinates[1]
@y.setter
def y(self, value):
"""
Set the y coordinate.
Parameters
----------
value : float or numpy.ndarray
Returns
-------
None
"""
self.set_y(value, copy=True)
@property
def max(self):
"""
Return the (x, y) maximum values.
Returns
-------
Coordinate2D
"""
new = self.empty_copy()
max_x = np.nanmax(self.x)
max_y = np.nanmax(self.y)
new.set([max_x, max_y])
return new
@property
def min(self):
"""
Return the (x, y) minimum values.
Returns
-------
Coordinate2D
"""
new = self.empty_copy()
max_x = np.nanmin(self.x)
max_y = np.nanmin(self.y)
new.set([max_x, max_y])
return new
@property
def span(self):
"""
Return the range of x and y values.
Returns
-------
Coordinate2D
"""
c_range = self.max
c_range.subtract(self.min)
return c_range
@property
def length(self):
"""
Return the distance of the coordinate from (0, 0).
Returns
-------
distance : float or numpy.ndarray or astropy.units.Quantity
"""
return np.hypot(self.x, self.y)
@property
def singular(self):
"""
Return if the coordinates are scalar in nature (not an array).
Returns
-------
bool
"""
if self.coordinates is None:
return True
return self.coordinates.ndim == 1
def __str__(self):
"""
Create a string representation of the equatorial coordinates.
Returns
-------
str
"""
if self.coordinates is None:
return 'Empty coordinates'
if self.singular:
return f'x={self.x} y={self.y}'
else:
with warnings.catch_warnings():
warnings.simplefilter('ignore', RuntimeWarning)
return (f'x={np.nanmin(self.x)}->{np.nanmax(self.x)} '
f'y={np.nanmin(self.y)}->{np.nanmax(self.y)}')
def __repr__(self):
"""
Return a string representation of the Coordinate.
Returns
-------
str
"""
return f'{self.__str__()} {object.__repr__(self)}'
def __getitem__(self, indices):
"""
Return a section of the coordinates
Parameters
----------
indices : int or numpy.ndarray or slice
Returns
-------
Coordinate2D
"""
return super().__getitem__(indices)
[docs]
def get_indices(self, indices):
"""
Return selected data for given indices.
Parameters
----------
indices : slice or list or int or numpy.ndarray (int)
The indices to extract.
Returns
-------
Coordinate2D
"""
return super().get_indices(indices)
[docs]
def set_singular(self, empty=False):
"""
Create a single coordinate.
Parameters
----------
empty : bool, optional
If `True`, create an empty coordinate array. Otherwise, create a
zeroed array.
Returns
-------
None
"""
if empty:
coordinates = np.empty(self.ndim, dtype=float)
else:
coordinates = np.zeros(self.ndim, dtype=float)
if self.unit is not None:
coordinates = coordinates * self.unit
self.coordinates = coordinates
[docs]
def copy_coordinates(self, coordinates):
"""
Copy the coordinates from another system to this system.
Parameters
----------
coordinates : Coordinate2D
Returns
-------
None
"""
if coordinates.coordinates is None:
self.coordinates = None
else:
self.set_x(coordinates.x)
self.set_y(coordinates.y)
[docs]
def set_x(self, coordinates, copy=True):
"""
Set the x coordinates.
Parameters
----------
coordinates : float or numpy.ndarray
copy : bool, optional
If `True`, copy the coordinates. Otherwise do a reference.
Returns
-------
None
"""
coordinates, original = self.check_coordinate_units(coordinates)
self.set_shape_from_coordinates(coordinates, single_dimension=True)
copy &= original
if copy and isinstance(coordinates, np.ndarray):
self.coordinates[0] = coordinates.copy()
else:
self.coordinates[0] = coordinates
[docs]
def set_y(self, coordinates, copy=True):
"""
Set the y coordinates.
Parameters
----------
coordinates : numpy.ndarray
copy : bool, optional
If `True`, copy the coordinates. Otherwise do a reference.
Returns
-------
None
"""
coordinates, original = self.check_coordinate_units(coordinates)
self.set_shape_from_coordinates(coordinates, single_dimension=True)
copy &= original
if copy and isinstance(coordinates, np.ndarray):
self.coordinates[1] = coordinates.copy()
else:
self.coordinates[1] = coordinates
[docs]
def set(self, coordinates, copy=True):
"""
Set the (x, y) coordinates.
Parameters
----------
coordinates : numpy.ndarray or list
copy : bool, optional
If `True`, copy the coordinates. Otherwise do a reference.
Returns
-------
None
"""
coordinates, original = self.check_coordinate_units(coordinates)
copy &= original
self.set_shape_from_coordinates(coordinates, empty=True)
self.set_x(coordinates[0], copy=copy)
self.set_y(coordinates[1], copy=copy)
[docs]
def add_x(self, x):
"""
Add x to coordinates.
Parameters
----------
x : float or numpy.ndarray
The value(s) to add.
Returns
-------
None
"""
self.broadcast_to(x)
self.coordinates[0] += x
[docs]
def subtract_x(self, x):
"""
Subtract x from coordinates.
Parameters
----------
x : float or numpy.ndarray
The value(s) to subtract.
Returns
-------
None
"""
self.broadcast_to(x)
self.coordinates[0] -= x
[docs]
def add_y(self, y):
"""
Add y to coordinates.
Parameters
----------
y : float or numpy.ndarray
The value(s) to add.
Returns
-------
None
"""
self.broadcast_to(y)
self.coordinates[1] += y
[docs]
def subtract_y(self, y):
"""
Subtract y from coordinates.
Parameters
----------
y : float or numpy.ndarray or astropy.units.Quantity
The value(s) to subtract.
Returns
-------
None
"""
self.broadcast_to(y)
self.coordinates[1] -= y
[docs]
def scale(self, factor):
"""
Scale the coordinates by a factor.
Parameters
----------
factor : int or float or Coordinate2D
Returns
-------
None
"""
if not isinstance(factor, Coordinate2D):
factor = self.convert_factor(factor)
self.scale_x(factor)
self.scale_y(factor)
else:
self.scale_x(factor.x)
self.scale_y(factor.y)
[docs]
def scale_x(self, factor):
"""
Scale the x coordinates by a factor.
Parameters
----------
factor : float or units.Quantity
The factor by which to scale the x-coordinates.
Returns
-------
None
"""
factor = self.convert_factor(factor)
self.coordinates[0] *= factor
[docs]
def scale_y(self, factor):
"""
Scale the y coordinates by a factor.
Parameters
----------
factor : float
The factor by which to scale the y-coordinates.
Returns
-------
None
"""
factor = self.convert_factor(factor)
self.coordinates[1] *= factor
[docs]
def invert_x(self):
"""
Scale the x-coordinates by -1.
Returns
-------
None
"""
self.coordinates[0] *= -1
[docs]
def invert_y(self):
"""
Scale the y-coordinates by -1.
Returns
-------
None
"""
self.coordinates[1] *= -1
[docs]
def invert(self):
"""
Scale the x and y coordinates by -1.
Returns
-------
None
"""
self.invert_x()
self.invert_y()
[docs]
def add(self, coordinates):
"""
Add other coordinates to these.
Parameters
----------
coordinates : Coordinate2D
Returns
-------
None
"""
self.add_x(coordinates.x)
self.add_y(coordinates.y)
[docs]
def subtract(self, coordinates):
"""
Subtract other coordinates from these.
Parameters
----------
coordinates : Coordinate2D
Returns
-------
None
"""
self.subtract_x(coordinates.x)
self.subtract_y(coordinates.y)
[docs]
def rotate(self, angle):
"""
Rotate the coordinates by a given angle.
Internal coordinates are rotated anti-clockwise about zero.
Parameters
----------
angle : units.Quantity
Returns
-------
None
"""
self.rotate_offsets(self, angle)
[docs]
@classmethod
def rotate_offsets(cls, offsets, angle):
"""
Rotate zero-centered offsets in-place by an angle.
Offsets are rotated anti-clockwise.
Parameters
----------
offsets : Coordinate2D or numpy.ndarray or units.Quantity or None
The (x, y) offset coordinates to rotate.
angle : astropy.units.Quantity or float
The angle by which to rotate the offsets. If a float value is
provided, it should be in radians.
Returns
-------
None
"""
if isinstance(offsets, np.ndarray):
if offsets.ndim > 1 and offsets.shape[0] > 0:
angle = cls.correct_factor_dimensions(angle, offsets[0])
elif isinstance(offsets, Coordinate2D):
if offsets.coordinates is None:
return
if offsets.coordinates.ndim > 1 and offsets.coordinates.size > 1:
angle = cls.correct_factor_dimensions(
angle, offsets.coordinates[0])
sin_a = np.sin(angle)
cos_a = np.cos(angle)
if isinstance(offsets, Coordinate2D):
x, y = offsets.x.copy(), offsets.y
offsets.x = (x * cos_a) - (y * sin_a)
offsets.y = (x * sin_a) + (y * cos_a)
else:
x, y = offsets
x = x.copy()
offsets[0] = (x * cos_a) - (y * sin_a)
offsets[1] = (x * sin_a) + (y * cos_a)
[docs]
def angle(self, center=None):
"""
Return the angle between the x and y coordinates w.r.t a center.
Parameters
----------
center : Coordinate2D, optional
The center about which to find an origin.
Returns
-------
units.Quantity
"""
if center is None:
coordinates = self
else:
coordinates = self.copy()
coordinates.subtract(center)
angle = np.arctan2(coordinates.y, coordinates.x)
if not isinstance(angle, units.Quantity):
angle = angle * units.Unit('radian')
return angle
[docs]
def plot(self, *args, **kwargs): # pragma: no cover
"""
Plot the coordinates.
Parameters
----------
args : values
Optional positional parameters to pass into pyplot.plot.
kwargs : dict, optional
Optional keyword arguments.
Returns
-------
None
"""
if self.coordinates is None:
return
import matplotlib.pyplot as plt
plt.ion()
c_args = self.coordinates[0].ravel(), self.coordinates[1].ravel()
if args is not None:
c_args += args
plt.plot(*c_args, **kwargs)
x_label = 'X'
y_label = 'Y'
if self.unit is not None:
x_label += f' ({self.unit})'
y_label += f' ({self.unit})'
plt.xlabel(x_label)
plt.ylabel(y_label)