diff --git a/pyresample/future/spherical/__init__.py b/pyresample/future/spherical/__init__.py index 817b63fce..108158df7 100644 --- a/pyresample/future/spherical/__init__.py +++ b/pyresample/future/spherical/__init__.py @@ -17,4 +17,5 @@ # with this program. If not, see . """Future features that are backwards incompatible with current functionality.""" +from .arc import SArc # noqa from .point import SMultiPoint, SPoint # noqa diff --git a/pyresample/future/spherical/arc.py b/pyresample/future/spherical/arc.py new file mode 100644 index 000000000..9696552c8 --- /dev/null +++ b/pyresample/future/spherical/arc.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022 Pyresample developers +# +# 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 . +"""Define a great-circle arc between two points on the sphere through the SArc class.""" + +import numpy as np +import pyproj +from shapely.geometry import LineString + +from pyresample.spherical import Arc + +from .point import SPoint, create_spherical_point + +EPSILON = 0.0000001 + + +def _check_valid_arc(start_point, end_point): + """Check arc validity.""" + if start_point == end_point: + raise ValueError("An SArc can not be represented by the same start and end SPoint.") + if start_point.is_pole() and end_point.is_pole(): + raise ValueError("An SArc can not be uniquely defined between the two poles.") + if start_point.is_on_equator() and end_point.is_on_equator() and abs(start_point.lon - end_point.lon) == np.pi: + raise ValueError( + "An SArc can not be uniquely defined on the equator if start and end points are 180 degrees apart.") + if start_point.antipode == end_point: + raise ValueError("An SArc can not be uniquely defined between antipodal points.") + + +def _is_point_on_arc(point, arc): + """Check if the point is on the arc.""" + # Define arc start and end points + start = arc.start + end = arc.end + + # Compute arc length + arc_length = start.hdistance(end) + + # Distance from arc start & end points to the input point + start_to_point_dist = start.hdistance(point) + end_to_point_dist = end.hdistance(point) + + # Check if point is a start or end point of the arc + point_is_on_arc_extremities = point in (start, end) + + # Check if point is on the arc segment + point_is_on_arc_segment = abs(start_to_point_dist + end_to_point_dist - arc_length) < EPSILON + + # Assess if point is on the arc + point_is_on_arc = point_is_on_arc_extremities or point_is_on_arc_segment + return point_is_on_arc + + +class SArc(Arc): + """Object representing a great-circle arc between two points on a sphere. + + The ``start_point`` and ``end_point`` must be SPoint objects. + The great-circle arc is defined as the shortest track(s) between the two points. + Between the north and south pole there are an infinite number of great-circle arcs. + """ + + def __init__(self, start_point, end_point): + _check_valid_arc(start_point, end_point) + super().__init__(start_point, end_point) + + def __hash__(self): + """Define SArc hash.""" + return hash((float(self.start.lon), float(self.start.lat), + float(self.end.lon), float(self.end.lat))) + + def is_on_equator(self): + """Check if the SArc lies on the equator.""" + return self.start.lat == 0 and self.end.lat == 0 + + def __eq__(self, other): + """Check equality.""" + return self.start == other.start and self.end == other.end + + def __contains__(self, point): + """Check if a point lies on the SArc.""" + if isinstance(point, SPoint): + return _is_point_on_arc(point, arc=self) + else: + raise NotImplementedError("SArc.__contains__ currently accept only SPoint objects.") + + def reverse_direction(self): + """Reverse SArc direction.""" + return SArc(self.end, self.start) + + @property + def vertices(self): + """Get start SPoint and end SPoint (radians) vertices array.""" + return self.start.vertices, self.end.vertices + + @property + def vertices_in_degrees(self): + """Get start SPoint and end SPoint (degrees) vertices array.""" + return self.start.vertices_in_degrees, self.end.vertices_in_degrees + + def get_next_intersection(self, other_arc): + """Overwrite a Arc deprecated function. Inherited from Arc class.""" + raise ValueError("This function is deprecated.") + + def intersections(self, other_arc): + """Overwritea Arc deprecated function. Inherited from Arc class.""" + raise ValueError("'SArc.intersections' is deprecated. Use '_great_circle_intersections' instead.") + + def _great_circle_intersections(self, other_arc): + """Compute the intersections points of the greats circles over which the arcs lies. + + A great circle divides the sphere in two equal hemispheres. + """ + end_lon = self.end.lon + other_end_lon = other_arc.end.lon + + if self.end.lon - self.start.lon > np.pi: + end_lon -= 2 * np.pi + if other_arc.end.lon - other_arc.start.lon > np.pi: + other_end_lon -= 2 * np.pi + if self.end.lon - self.start.lon < -np.pi: + end_lon += 2 * np.pi + if other_arc.end.lon - other_arc.start.lon < -np.pi: + other_end_lon += 2 * np.pi + + end_point = SPoint(end_lon, self.end.lat) + other_end_point = SPoint(other_end_lon, other_arc.end.lat) + + ea_ = self.start.cross2cart(end_point).normalize() + eb_ = other_arc.start.cross2cart(other_end_point).normalize() + + cross = ea_.cross(eb_) + lat = np.arctan2(cross.cart[2], + np.sqrt(cross.cart[0] ** 2 + cross.cart[1] ** 2)) + lon = np.arctan2(cross.cart[1], cross.cart[0]) + + return (SPoint(lon, lat), SPoint(lon + np.pi, -lat)) + + def intersection(self, other_arc): + """Overwrite a Arc deprecated function. Inherited from Arc class.""" + raise ValueError("'SArc.intersection' is deprecated. Use 'intersection_point' instead.") + + def intersection_point(self, other_arc): + """Compute the intersection point between two great circle arcs. + + If arc and *other_arc* does not intersect, it returns None. + If arc and *other_arc* are the same (whatever direction), it returns None. + If arc and *other_arc* overlaps, within or contain, it returns None. + If arc and *other_arc* intersect, it returns the intersection SPoint. + If arc and *other_arc* touches in 1 point, it returns the intersection SPoint. + + """ + # If same arc (same direction), return None + if self == other_arc: + return None + # Compute the great circle intersection points + great_circles_intersection_spoints = self._great_circle_intersections(other_arc) + # If a great circle intersection point lies on the arcs, it is the intersection point + for point in great_circles_intersection_spoints: + if point in self and point in other_arc: + return point + return None + + def intersects(self, other_arc): + """Check if the current Sarc and another SArc intersect.""" + return bool(self.intersection_point(other_arc)) + + # def midpoint(self): + # """Return the SArc midpoint SPoint.""" + # # Retrieve start and end point in Cartesian coordinates + # start_xyz = self.start.to_cart().cart + # end_xyz = self.end.to_cart().cart + # # Find midpoint + # midpoint_xyz = (start_xyz + end_xyz) / 2.0 + # # Normalize + # midpoint_xyz = CCoordinate(midpoint_xyz).normalize() + # # Convert back to SPoint(s) + # midpoint = midpoint_xyz.to_spherical(future=True) + # return midpoint + + def midpoint(self, ellips='sphere'): + """Return the SArc midpoint SPoint.""" + geod = pyproj.Geod(ellps=ellips) + lon_start = self.start.lon + lon_end = self.end.lon + lat_start = self.start.lat + lat_end = self.end.lat + lon_mid, lat_mid = geod.npts(lon_start, lat_start, lon_end, lat_end, npts=1, radians=True)[0] + return SPoint(lon_mid, lat_mid) + + def to_shapely(self): + """Convert to Shapely LineString.""" + start_coord, end_coord = self.vertices_in_degrees + return LineString((start_coord.tolist()[0], end_coord.tolist()[0])) + + def forward_points(self, distance, ellps="sphere"): + """Get points at given distance(s) from SArc end point in the forward direction. + + The distance value(s) must be provided in meters. + If the distance is positive, the point will be located outside the SArc. + If the distance is negative, the point will be located inside the SArc. + The function returns an SPoint or SMultiPoint. + """ + # Define geoid + geod = pyproj.Geod(ellps=ellps) + # Retrieve forward and back azimuth + fwd_az, back_az, _ = geod.inv(self.start.lon, self.start.lat, + self.end.lon, self.end.lat, radians=True) + # Retreve forward points + distance = np.array(distance) + lon, lat, back_az = geod.fwd(np.broadcast_to(np.array(self.end.lon), distance.shape), + np.broadcast_to(np.array(self.end.lat), distance.shape), + az=np.broadcast_to(fwd_az, distance.shape), + dist=distance, radians=True) + p = create_spherical_point(lon, lat) + return p + + def backward_points(self, distance, ellps="sphere"): + """Get points at given distance(s) from SArc start point in the backward direction. + + The distance value(s) must be provided in meters. + If the distance is positive, the point will be located outside the SArc. + If the distance is negative, the point will be located inside the SArc. + The function returns an SPoint or SMultiPoint. + """ + reverse_arc = self.reverse_direction() + return reverse_arc.forward_points(distance=distance, ellps=ellps) + + def extend(self, distance, direction="both", ellps="sphere"): + """Extend the SArc of a given distance in both, forward or backward directions. + + If the distance is positive, it extends the SArc. + If the distance is negative, it shortens the SArc. + """ + valid_direction = ["both", "forward", "backward"] + if direction not in valid_direction: + raise ValueError(f"Valid direction values are: {valid_direction}") + if direction in ["both", "forward"]: + end_point = self.forward_points(distance=distance, ellps="sphere") + else: + end_point = self.end + + if direction in ["both", "backward"]: + start_point = self.backward_points(distance=distance, ellps="sphere") + else: + start_point = self.start + arc = create_spherical_arcs(start_point, end_point) + return arc + + def shorten(self, distance, direction="both", ellps="sphere"): + """Short the SArc of a given distance in both, forward or backward directions. + + If the distance is positive, it shortens the SArc. + If the distance is negative, it extends the SArc. + """ + return self.extend(distance=-distance, direction=direction, ellps="sphere") + + # def segmentize(self, npts=0, max_distance=0, ellips='sphere'): + # """Segmentize the great-circle arc. + + # It returns an SLine. + # npts or max_distance are mutually exclusively. Specify one of them. + # max_distance must be provided in meters. + # """ + # if npts != 0: + # npts = npts + 2 # + 2 to account for initial and terminus points + # geod = pyproj.Geod(ellps=ellips) + # lon_start = self.start.lon + # lon_end = self.end.lon + # lat_start = self.start.lat + # lat_end = self.end.lat + # points = geod.inv_intermediate(lon_start, lat_start, lon_end, lat_end, + # del_s=max_distance, + # npts=npts, + # radians=True, + # initial_idx=0, terminus_idx=0) + # lons, lats = (points.lons, points.lats) + # lons = np.asarray(lons) + # lats = np.asarray(lats) + # vertices = np.stack((lons, lats)).T + # return SLine(vertices) + + # def to_line(self): + # """Convert to SLine.""" + # vertices = np.vstack(self.vertices) + # return SLine(vertices) + + # def plot(self, *args, **kwargs): + # """Convert to SLine.""" + # self.to_line.plot(*args, **kwargs) + + +def create_spherical_arcs(start_point, end_point): + """Create a SArc or SArcs class depending on the number of points. + + If a Spoint is provided, it returns an SArc. + If a SMultiPoint is provided, it returns an SArcs. + """ + if isinstance(start_point, SPoint) and isinstance(end_point, SPoint): + arc = SArc(start_point, end_point) + else: + raise NotImplementedError("SArcs class is not yet available.") + return arc diff --git a/pyresample/future/spherical/point.py b/pyresample/future/spherical/point.py index 33389cf05..a652429bd 100644 --- a/pyresample/future/spherical/point.py +++ b/pyresample/future/spherical/point.py @@ -39,13 +39,35 @@ def from_degrees(cls, lon, lat): """Create SPoint from lon/lat coordinates in degrees.""" return cls(np.deg2rad(lon), np.deg2rad(lat)) + def is_pole(self): + """Test if the point is on a pole.""" + return self.lat in (-np.pi / 2, np.pi / 2) + + def is_on_equator(self): + """Test if the point is on the equator.""" + return self.lat == 0 + + @property + def antipode(self): + """Return the antipode point.""" + lat = - self.lat + lon = self.lon - np.pi + return SPoint(lon, lat) + def __str__(self): """Get simplified representation of lon/lat arrays in radians.""" return str((float(self.lon), float(self.lat))) def __repr__(self): """Get simplified representation of lon/lat arrays in radians.""" - return str((float(self.lon), float(self.lat))) + coords = str((float(self.lon), float(self.lat))) + return f"{self.__class__.__name__}: {coords}" + + def __eq__(self, other): + """Check equality.""" + if self.lat == other.lat and self.is_pole(): + return True + return np.allclose((self.lon, self.lat), (other.lon, other.lat)) def to_shapely(self): """Convert the SPoint to a shapely Point (in lon/lat degrees).""" @@ -69,9 +91,23 @@ def from_degrees(cls, lon, lat): """Create SMultiPoint from lon/lat coordinates in degrees.""" return cls(np.deg2rad(lon), np.deg2rad(lat)) + @property + def antipodes(self): + """Return the antipodal points.""" + lat = - self.lat + lon = self.lon - np.pi + return SMultiPoint(lon, lat) + def __eq__(self, other): """Check equality.""" - return np.allclose(self.lon, other.lon) and np.allclose(self.lat, other.lat) + lon1 = self.lon.copy() + lon2 = other.lon.copy() + lat1 = self.lat + lat2 = other.lat + # Set longitude 0 at the pole for array comparison + lon1[np.isin(lat1, [-np.pi / 2, np.pi / 2])] = 0 + lon2[np.isin(lat2, [-np.pi / 2, np.pi / 2])] = 0 + return np.allclose(lon1, lon2) and np.allclose(lat1, lat2) def __str__(self): """Get simplified representation of lon/lat arrays in radians.""" @@ -79,10 +115,25 @@ def __str__(self): def __repr__(self): """Get simplified representation of lon/lat arrays in radians.""" - return str(self.vertices) + return f"{self.__class__.__name__}:\n {self.vertices}" def to_shapely(self): """Convert the SMultiPoint to a shapely MultiPoint (in lon/lat degrees).""" from shapely.geometry import MultiPoint point = MultiPoint(self.vertices_in_degrees) return point + + +def create_spherical_point(lon, lat): + """Create a SPoint or SMultiPoint class depending on the number of point coordinates. + + If a single point coordinate is provided, it returns an SPoint. + If multiple point coordinates are provided, it returns an SMultiPoint. + """ + lon = np.asarray(lon) + lat = np.asarray(lat) + if lon.ndim == 0: + p = SPoint(lon, lat) + else: + p = SMultiPoint(lon, lat) + return p diff --git a/pyresample/spherical.py b/pyresample/spherical.py index 9dca27904..cfa8b637e 100644 --- a/pyresample/spherical.py +++ b/pyresample/spherical.py @@ -349,12 +349,17 @@ def __rmul__(self, other): """Multiply.""" return self.__mul__(other) - def to_spherical(self): + def to_spherical(self, future=False): """Convert to SPoint/SMultiPoint object.""" # TODO: this in future should point to SPoint or SMultiPoint + from pyresample.future.spherical.point import create_spherical_point + lon = np.arctan2(self.cart[..., 1], self.cart[..., 0]) lat = np.arcsin(self.cart[..., 2]) - return SCoordinate(lon, lat) + if not future: + return SCoordinate(lon, lat) + else: + return create_spherical_point(lon, lat) class Arc(object): @@ -365,20 +370,18 @@ def __init__(self, start, end): def __eq__(self, other): """Check equality.""" - if self.start == other.start and self.end == other.end: - return 1 - return 0 + return self.start == other.start and self.end == other.end def __ne__(self, other): """Check not equal comparison.""" return not self.__eq__(other) def __str__(self): - """Get simplified representation.""" + """Get simplified string representation.""" return str(self.start) + " -> " + str(self.end) def __repr__(self): - """Get simplified representation.""" + """Get simplified repr representation.""" return str(self.start) + " -> " + str(self.end) def angle(self, other_arc): @@ -427,9 +430,9 @@ def angle(self, other_arc): return angle def intersections(self, other_arc): - """Give the two intersections of the greats circles defined by the current arc and *other_arc*. + """Compute the intersections points of the great circles over which the arcs lies. - From http://williams.best.vwh.net/intersect.htm + A great circle divides the sphere in two equal hemispheres. """ end_lon = self.end.lon other_end_lon = other_arc.end.lon @@ -465,23 +468,23 @@ def intersects(self, other_arc): return bool(self.intersection(other_arc)) def intersection(self, other_arc): - """Return where, if the current arc and the *other_arc* intersect. + """Compute the intersection point between two arcs. - None is returned if there is not intersection. An arc is defined - as the shortest tracks between two points. + If arc and *other_arc* intersect, it returns the intersection SPoint. + If arc and *other_arc* does not intersect, it returns None. """ if self == other_arc: return None - for i in self.intersections(other_arc): - a__ = self.start - b__ = self.end - c__ = other_arc.start - d__ = other_arc.end + a__ = self.start + b__ = self.end + c__ = other_arc.start + d__ = other_arc.end - ab_ = a__.hdistance(b__) - cd_ = c__.hdistance(d__) + ab_ = a__.hdistance(b__) + cd_ = c__.hdistance(d__) + for i in self.intersections(other_arc): if (((i in (a__, b__)) or (abs(a__.hdistance(i) + b__.hdistance(i) - ab_) < EPSILON)) and ((i in (c__, d__)) or @@ -490,7 +493,11 @@ def intersection(self, other_arc): return None def get_next_intersection(self, arcs, known_inter=None): - """Get the next intersection between the current arc and *arcs*.""" + """Get the next intersection between the current arc and *arcs*. + + It return a tuple with the intersecting point and the arc within *arcs* + that intersect the self arc. + """ res = [] for arc in arcs: inter = self.intersection(arc) diff --git a/pyresample/test/test_sgeom/test_arc.py b/pyresample/test/test_sgeom/test_arc.py new file mode 100644 index 000000000..64bc48063 --- /dev/null +++ b/pyresample/test/test_sgeom/test_arc.py @@ -0,0 +1,1032 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022 Pyresample developers +# +# 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 . +"""Define tests for the SArc class.""" + +import copy + +import numpy as np +import pytest +from shapely.geometry import LineString + +from pyresample.future.spherical.arc import SArc +from pyresample.future.spherical.point import SMultiPoint, SPoint + + +class TestSArc: + """Test SArc class.""" + + # TODO in future + # - Fixtures defined here or outside the class + # - function that test intersection_point in all direction + + equator_arc1 = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(50, 0.0)) + equator_arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(0.0, 0.0)) + + # pole_to_pole_arc1 = SArc(SPoint.from_degrees(0.0, -90.0), SPoint.from_degrees(0.0, 90.0)) + # pole_to_pole_arc2 = SArc(SPoint.from_degrees(60.0, -90.0), SPoint.from_degrees(60.0, 90.0)) + + def test_unvalid_point_arc(self): + """Check raise error if start and end point are equal.""" + p = SPoint.from_degrees(0.0, 80.0) + with pytest.raises(ValueError): + SArc(p, p) + + def test_unvalid_180degree_equator_arc(self): + """Check raise error if the points lies on the equator and are 180° apart.""" + p1 = SPoint.from_degrees(0, 0) + p2 = SPoint.from_degrees(180, 0) + with pytest.raises(ValueError): + SArc(p1, p2) + + p1 = SPoint.from_degrees(-10, 0) + p2 = SPoint.from_degrees(170, 0) + with pytest.raises(ValueError): + SArc(p1, p2) + + p1 = SPoint.from_degrees(10, 0) + p2 = SPoint.from_degrees(-170, 0) + with pytest.raises(ValueError): + SArc(p1, p2) + + def test_unvalid_pole_to_pole_arc(self): + """Check raise error if the points defines a pole to pole arc.""" + p1 = SPoint.from_degrees(0.0, -90.0) + p2 = SPoint.from_degrees(0.0, 90.0) + with pytest.raises(ValueError): + SArc(p1, p2) + + def test_antipode_points_arcs(self): + """Check raise error if the points are the antipodes.""" + p1 = SPoint.from_degrees(45, 45.0) + p2 = SPoint.from_degrees(-135.0, -45) + with pytest.raises(ValueError): + SArc(p1, p2) + + def test_is_on_equator_arc(self): + """Check if the arc lies on the equator.""" + equator_arc1 = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(50, 0.0)) + arc = SArc(SPoint.from_degrees(0, 10), SPoint.from_degrees(10, 0)) + assert equator_arc1.is_on_equator() + assert not arc.is_on_equator() + + def test_eq(self): + """Test SArc equality.""" + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + arc2 = SArc(SPoint.from_degrees(0, 10), SPoint.from_degrees(10, 0)) + assert not arc1.__eq__(arc2) + assert arc1 == arc1 + + def test_ne(self): + """Test SArc disequality.""" + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + arc2 = SArc(SPoint.from_degrees(0, 10), SPoint.from_degrees(10, 0)) + assert arc1 != arc2 + assert not arc1.__ne__(arc1) + + def test_str(self): + """Test SArc __str__ representation.""" + arc = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + expected_str = str(arc.start) + " -> " + str(arc.end) + assert str(arc) == expected_str + + def test_repr(self): + """Test SArc __repr__ representation.""" + arc = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + expected_repr = str(arc.start) + " -> " + str(arc.end) + assert repr(arc) == expected_repr + + def test_vertices(self): + """Test vertices property.""" + start_point_vertices = np.deg2rad((-180.0, 0.0)) + end_point_vertices = np.deg2rad((-180.0, 90.0)) + arc = SArc(SPoint(*start_point_vertices), SPoint(*end_point_vertices)) + start_vertices, end_vertices = arc.vertices + assert np.allclose(start_vertices, start_point_vertices) + assert np.allclose(end_vertices, end_point_vertices) + + def test_vertices_in_degrees(self): + """Test vertices_in_degrees property.""" + start_point_vertices = np.deg2rad((-180.0, 0.0)) + end_point_vertices = np.deg2rad((-180.0, 90.0)) + arc = SArc(SPoint.from_degrees(* start_point_vertices), + SPoint.from_degrees(* end_point_vertices)) + start_vertices, end_vertices = arc.vertices_in_degrees + assert np.allclose(start_vertices, start_point_vertices) + assert np.allclose(end_vertices, end_point_vertices) + + def test_to_shapely(self): + """Test conversion to shapely.""" + start_point_vertices = np.deg2rad((-180.0, 0.0)) + end_point_vertices = np.deg2rad((-180.0, 90.0)) + arc = SArc(SPoint.from_degrees(* start_point_vertices), + SPoint.from_degrees(* end_point_vertices)) + shapely_line = arc.to_shapely() + expected_line = LineString((start_point_vertices, end_point_vertices)) + assert shapely_line.equals_exact(expected_line, tolerance=1e-10) + + def test_hash(self): + """Test arc hash.""" + arc = SArc(SPoint.from_degrees(-180.0, -90.0), SPoint.from_degrees(-180.0, 0.0)) + assert hash(arc) == -3096892178517935054 + + # ----------------------------------------------------------------------. + # Test SArc manipulations + + def test_midpoint(self): + """Test arc midpoint.""" + start_point_vertices = np.deg2rad((-180.0, 0.0)) + end_point_vertices = np.deg2rad((-180.0, 90.0)) + arc = SArc(SPoint(*start_point_vertices), SPoint(*end_point_vertices)) + midpoint = arc.midpoint() + assert isinstance(midpoint, SPoint) + assert np.allclose(midpoint.vertices_in_degrees[0], (-180, 45)) + + def test_arc_forward_points(self): + """Test SArc forward points.""" + arc = SArc(SPoint.from_degrees(10, 10), SPoint.from_degrees(20, 20)) + + # forward outside arc SPoint + p = arc.forward_points(distance=100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (20.00065043, 20.00065971)) + + # forward inside arc SPoint + p = arc.forward_points(distance=-100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (19.99934958, 19.99934029)) + + # Forward outside arc SMultiPoint + p = arc.forward_points(distance=[100 * 100, 1000 * 1000]) + expected_coords = np.array([[20.06506968, 20.06595905], + [26.81520629, 26.45975315]]) + assert isinstance(p, SMultiPoint) + assert np.allclose(p.vertices_in_degrees, expected_coords) + + # Forward inside arc SPoint + p = arc.forward_points(distance=[-100 * 100, -1000 * 1000]) + expected_coords = np.array([[19.93498483, 19.93401721], + [13.73281401, 13.30073419]]) + assert isinstance(p, SMultiPoint) + assert np.allclose(p.vertices_in_degrees, expected_coords) + + def test_arc_backward_points(self): + """Test SArc backward points.""" + arc = SArc(SPoint.from_degrees(10, 10), SPoint.from_degrees(20, 20)) + + # Backward outside arc SPoint + p = arc.backward_points(distance=100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (9.99934958, 9.99936874)) + + # Backward inside arc SPoint + p = arc.backward_points(distance=-100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (10.00065043, 10.00063126)) + + def test_arc_forward_points_at_equator(self): + """Test SArc forward points at equator.""" + arc = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(0.0, 0.0)) + + # Forward outside arc SPoint + p = arc.forward_points(distance=100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (0.00089932, 0.0)) + + # Forward inside arc SPoint + p = arc.forward_points(distance=-100) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (-0.00089932, 0.0)) + + def test_arc_forward_points_at_pole(self): + """Test SArc forward points at pole.""" + # North pole + arc = SArc(SPoint.from_degrees(0.0, 50.0), SPoint.from_degrees(0.0, 90)) + + # Forward outside arc SPoint + p = arc.forward_points(distance=1000 * 1000) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (-180, 81.00677971)) + + # Forward inside arc SPoint + p = arc.forward_points(distance=-1000 * 1000) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (0, 81.00677971)) + + # South pole + arc = SArc(SPoint.from_degrees(0.0, 50.0), SPoint.from_degrees(0.0, -90)) + + # Forward outside arc SPoint + p = arc.forward_points(distance=1000 * 1000) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (-180, -81.00677971)) + + # Forward inside arc SPoint + p = arc.forward_points(distance=-1000 * 1000) + assert isinstance(p, SPoint) + assert np.allclose(p.vertices_in_degrees, (0, -81.00677971)) + + def test_arc_extend(self): + """Test SArc extend method.""" + arc = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(20.0, 0.0)) + + # Test unvalid direction + with pytest.raises(ValueError): + arc.extend(distance=0, direction="bot") + + # Test backward + extended_arc = arc.extend(distance=1000 * 1000, direction="backward") + assert extended_arc.start == SPoint.from_degrees(-8.99322029, 0) + assert extended_arc.end == arc.end + + # Test forward + extended_arc = arc.extend(distance=1000 * 1000, direction="forward") + assert extended_arc.start == arc.start + assert extended_arc.end == SPoint.from_degrees(28.99322029, 0) + + # Test both + extended_arc = arc.extend(distance=1000 * 1000, direction="both") + assert extended_arc.start == SPoint.from_degrees(-8.99322029, 0) + assert extended_arc.end == SPoint.from_degrees(28.99322029, 0) + + def test_arc_shorten(self): + """Test SArc shorten method.""" + arc = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(20.0, 0.0)) + + # Test unvalid direction + with pytest.raises(ValueError): + arc.shorten(distance=0, direction="bot") + + # Test backward + shortened_arc = arc.shorten(distance=1000 * 1000, direction="backward") + assert shortened_arc.start == SPoint.from_degrees(8.99322029, 0) + assert shortened_arc.end == arc.end + + # Test forward + shortened_arc = arc.shorten(distance=1000 * 1000, direction="forward") + assert shortened_arc.start == arc.start + assert shortened_arc.end == SPoint.from_degrees(11.00677971, 0) + + # Test both + shortened_arc = arc.shorten(distance=1000 * 1000, direction="both") + assert shortened_arc.start == SPoint.from_degrees(8.99322029, 0) + assert shortened_arc.end == SPoint.from_degrees(11.00677971, 0) + # ----------------------------------------------------------------------. + # Test great circle intersections method + + def test_great_circles_intersections_of_crossing_arcs(self): + """Test behaviour when the 2 crossing arcs are not on the same great-circle.""" + # If the two arcs cross along the arc + # - one intersection point is the crossing point + # - the other intersection point is at the antipodes + + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + arc2 = SArc(SPoint.from_degrees(0, 10), SPoint.from_degrees(10, 0)) + crossing_point = (5., 5.0575149) + + # - Changing the arc used to call the method changes the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (-175., -5.0575149)) + assert np.allclose(p2.vertices_in_degrees, crossing_point) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, crossing_point) + assert np.allclose(p2.vertices_in_degrees, (-175., -5.0575149)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-175., -5.0575149)) + assert np.allclose(p2.vertices_in_degrees, crossing_point) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, crossing_point) + assert np.allclose(p2.vertices_in_degrees, (-175., -5.0575149)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, crossing_point) + assert np.allclose(p2.vertices_in_degrees, (-175., -5.0575149)) + + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-175., -5.0575149)) + assert np.allclose(p2.vertices_in_degrees, crossing_point) + + def test_great_circles_intersections_on_touching_arcs(self): + """Test behaviour when the 2 arcs not on the same great-circle but touch at the extremes.""" + # If the two arcs are touching at the arc extremes + # - one intersection point is the touching point + # - the other intersection point is at the antipodes + + touching_point = (10.0, 20.0) + arc1 = SArc(SPoint.from_degrees(10.0, 10.0), SPoint.from_degrees(*touching_point)) + arc2 = SArc(SPoint.from_degrees(50.0, 60.0), SPoint.from_degrees(*touching_point)) + + # - Changing the arc used to call the method changes the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, touching_point) + assert np.allclose(p2.vertices_in_degrees, (-170, -20)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (-170, -20)) + assert np.allclose(p2.vertices_in_degrees, touching_point) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, touching_point) + assert np.allclose(p2.vertices_in_degrees, (-170, -20)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-170, -20)) + assert np.allclose(p2.vertices_in_degrees, touching_point) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-170, -20)) + assert np.allclose(p2.vertices_in_degrees, touching_point) + + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, touching_point) + assert np.allclose(p2.vertices_in_degrees, (-170, -20)) + + def test_great_circles_intersections_of_disjoint_arcs(self): + """Test behaviour when the 2 disjoint arcs are not on the same great-circle.""" + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) # north hemisphere + arc2 = SArc(SPoint.from_degrees(-10, -10), SPoint.from_degrees(-10, -20)) # south hemisphere + + # - Changing the arc used to call the method changes the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (170., 10.)) + assert np.allclose(p2.vertices_in_degrees, (-10., -10.)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (-10., -10.)) + assert np.allclose(p2.vertices_in_degrees, (170., 10.)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (170., 10.)) + assert np.allclose(p2.vertices_in_degrees, (-10., -10.)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-10., -10.)) + assert np.allclose(p2.vertices_in_degrees, (170., 10.)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-10., -10.)) + assert np.allclose(p2.vertices_in_degrees, (170., 10.)) + + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (170., 10.)) + assert np.allclose(p2.vertices_in_degrees, (-10., -10.)) + + # - with arcs on the same great circle cases + def test_great_circles_intersections_of_disjoint_arcs_on_same_great_circle(self): + """Test behaviour when the 2 disjoint arcs (same great-circle case).""" + arc1 = SArc(SPoint.from_degrees(5, 1.0), SPoint.from_degrees(5.0, 20.0)) + start_point = arc1.forward_points(distance=1000 * 1000) + end_point = arc1.forward_points(distance=3000 * 1000) + arc2 = SArc(start_point, end_point) + + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + def test_great_circles_intersections_of_touching_equator_arcs(self): + """Test behaviour of touching equatorial arcs (same great-circle case).""" + arc1 = SArc(SPoint.from_degrees(1.0, 0.0), SPoint.from_degrees(50, 0.0)) + arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(1.0, 0.0)) + + # - Changing the arc used to call the method does not change the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + def test_great_circles_intersections_on_overlapping_arcs(self): + """Test behaviour with 2 overlapping arcs (same great-circle case).""" + arc1 = SArc(SPoint.from_degrees(5, 1.0), SPoint.from_degrees(5.0, 20.0)) + arc2 = arc1.extend(distance=1000 * 1000, direction="forward") + + # - Changing the arc used to call the method does not change the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + def test_great_circles_intersections_on_contain_within_arcs(self): + """Test behaviour with arcs contain/within each other (same great-circle case).""" + arc1 = SArc(SPoint.from_degrees(5, 1.0), SPoint.from_degrees(5.0, 20.0)) + arc2 = arc1.extend(distance=1000 * 1000, direction="both") + + # - Changing the arc used to call the method does not change the results order + p1, p2 = arc1._great_circle_intersections(arc2) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2._great_circle_intersections(arc1) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - If both arc are reversed, the results are the same + p1, p2 = arc1.reverse_direction()._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc2.reverse_direction()._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing the direction of one arc changes the order of the points + p1, p2 = arc2._great_circle_intersections(arc1.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + p1, p2 = arc1._great_circle_intersections(arc2.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (-180, 0)) + assert np.allclose(p2.vertices_in_degrees, (0, 0)) + + def test_great_circles_intersections_with_equal_arc(self): + """Test great_circles intersection points with equal arcs.""" + arc = SArc(SPoint.from_degrees(1.0, 20.0), SPoint.from_degrees(10.0, 40.0)) + + p1, p2 = arc._great_circle_intersections(arc) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing both arcs does not change the results + p1, p2 = arc.reverse_direction()._great_circle_intersections(arc.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # - Reversing the direction of one arc does not change the results + p1, p2 = arc._great_circle_intersections(arc.reverse_direction()) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + p1, p2 = arc.reverse_direction()._great_circle_intersections(arc) + assert np.allclose(p1.vertices_in_degrees, (0, 0)) + assert np.allclose(p2.vertices_in_degrees, (-180, 0)) + + # ----------------------------------------------------------------------. + # Test intersection_point method + def test_disjoint_arcs(self): + """Test disjoint arcs behaviour.""" + arc1 = SArc(SPoint.from_degrees(-2.4982818108326734, 48.596644847869655), + SPoint.from_degrees(-2.9571441235622835, 49.165688435261394)) + arc2 = SArc(SPoint.from_degrees(-3.4976667413531688, 48.562704872921373), + SPoint.from_degrees(-5.893976312685715, 48.445795283217116)) + assert arc1.intersection_point(arc2) is None + assert not arc1.intersects(arc2) + assert not bool(None) # this occurs within the intersects method + + def test_disjoint_equator_arcs(self): + """Test behaviour with disjoint arc along the equator.""" + equator_arc1 = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(50, 0.0)) + equator_arc3 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(-20, 0.0)) + p = equator_arc3.intersection_point(equator_arc1) + assert isinstance(p, type(None)) + + def test_disjoint_arcs_crossing_the_antimeridian(self): + """Test SArc(s) intersection point when disjoint arcs cross the antimeridian.""" + arc1 = SArc(SPoint.from_degrees(143.76, 0), + SPoint.from_degrees(143.95, 7.33)) + arc2 = SArc(SPoint.from_degrees(170.34, 71.36), + SPoint.from_degrees(-171.03, 76.75)) + arc1_orig = copy.deepcopy(arc1) + arc2_orig = copy.deepcopy(arc2) + point = arc1.intersection_point(arc2) + + # Assert original arcs are unaffected + assert arc1_orig.end.lon == arc1.end.lon + assert arc2_orig.end.lon == arc2.end.lon + + # Assert disjoint arcs returns None + assert isinstance(point, type(None)) + + def test_crossing_arcs(self): + """Test crossing arcs behaviour.""" + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(10, 10)) + arc2 = SArc(SPoint.from_degrees(0, 10), SPoint.from_degrees(10, 0)) + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (5, 5.0575148968282093)) + + arc1 = SArc(SPoint.from_degrees(24.341215776575297, 44.987819588259327), + SPoint.from_degrees(18.842727517611817, 46.512483610284178)) + arc2 = SArc(SPoint.from_degrees(20.165961750361905, 46.177305385810541), + SPoint.from_degrees(20.253297585831707, 50.935830837274324)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + assert arc1.intersects(arc2) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (20.165957021925202, 46.177022633103398)) + + def test_crossing_arcs_at_equator(self): + """Test arcs crossing at the equator.""" + arc1 = SArc(SPoint.from_degrees(0, 0), SPoint.from_degrees(50, 0)) + arc2 = SArc(SPoint.from_degrees(0, -10), SPoint.from_degrees(10, 10)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (5, 0)) + + def test_crossing_arcs_intersecting_at_the_antimeridian(self): + """Test SArc(s) intersection point when intersecting at the antimeridian.""" + arc1 = SArc(SPoint.from_degrees(-180.0, -89.0), + SPoint.from_degrees(-180.0, 89.0)) + arc2 = SArc(SPoint.from_degrees(-171.03, -76.75), + SPoint.from_degrees(170.34, -71.36)) + arc1_orig = copy.deepcopy(arc1) + arc2_orig = copy.deepcopy(arc2) + point = arc1.intersection_point(arc2) + # Assert original arcs are unaffected + assert arc1_orig.end.lon == arc1.end.lon + assert arc2_orig.end.lon == arc2.end.lon + # Assert intersection result + assert point == SPoint.from_degrees(-180.0, -74.78847163) + + def test_touching_arcs(self): + """Test behaviour when touches not at the arc extremes.""" + # Touching point at (15, 0) + arc1 = SArc(SPoint.from_degrees(0.0, 10.0), SPoint.from_degrees(0.0, 20.0)) + midpoint = arc1.midpoint() # (0, 15) + arc2 = SArc(midpoint, SPoint(-np.pi / 2, midpoint.lat)) + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (0, 15)) + + def test_touching_arcs_at_the_equator(self): + """Test arcs touching at the equator in the mid of one arc.""" + # Touching point at (25, 0) + arc1 = SArc(SPoint.from_degrees(0.0, 0.0), SPoint.from_degrees(50, 0.0)) + arc2 = SArc(SPoint.from_degrees(25, 0.0), SPoint.from_degrees(0.0, 30)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (25.0, 0)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (25.0, 0)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (25.0, 0)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (25.0, 0)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (25.0, 0)) + + def test_touching_arcs_at_extremes(self): + """Test arcs touching at the arc extremes (not on the same great-circle).""" + # Touching point at (10.0, 20.0) + arc1 = SArc(SPoint.from_degrees(10.0, 10.0), SPoint.from_degrees(10.0, 20.0)) + arc2 = SArc(SPoint.from_degrees(50.0, 60.0), SPoint.from_degrees(10.0, 20.0)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (10.0, 20.0)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (10.0, 20.0)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (10.0, 20.0)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (10.0, 20.0)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (10.0, 20.0)) + + def test_touching_arcs_at_extremes_which_cross_antimeridian(self): + """Test arcs crossing the antimeridian, touching at the arc extremes.""" + # Touching point at (-175., 45) + arc1 = SArc(SPoint.from_degrees(-150, 10.0), SPoint.from_degrees(-175, 45)) + arc2 = SArc(SPoint.from_degrees(150, 10.0), SPoint.from_degrees(-175, 45)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (-175, 45)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (-175, 45)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-175, 45)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-175, 45)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-175, 45)) + + def test_touching_arcs_at_extremes_at_the_pole(self): + """Test arcs touching at the arc extremes at the pole.""" + # Touching point at (xxx, 90) + arc1 = SArc(SPoint.from_degrees(0.0, 10.0), SPoint.from_degrees(0.0, 90.0)) + arc2 = SArc(SPoint.from_degrees(60.0, 10.0), SPoint.from_degrees(10.0, 90.0)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (0., 90)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (0., 90)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (0., 90)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (0., 90)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (0., 90)) + + def test_touching_arcs_at_extremes_at_the_antimeridian(self): + """Test arcs touching at the arc extremes at the antimeridian.""" + # Touching point at (-180., 45) + arc1 = SArc(SPoint.from_degrees(-150, 10.0), SPoint.from_degrees(-180, 45)) + arc2 = SArc(SPoint.from_degrees(150, 10.0), SPoint.from_degrees(180, 45)) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (-180., 45)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (-180., 45)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-180., 45)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-180., 45)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (-180., 45)) + + def test_touching_arcs_at_extremes_at_the_equator(self): + """Test arcs touching at the equator at the arc extremes.""" + # Touching point at the equator (5, 0) + arc1 = SArc(SPoint.from_degrees(5.0, 0.0), SPoint.from_degrees(0.0, 20.0)) + arc2 = SArc(SPoint.from_degrees(5.0, 0.0), SPoint.from_degrees(0.0, -20.0)) + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, (5.0, 0)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, (5.0, 0)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (5.0, 0)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, (5.0, 0)) + + # - with arcs on the same great circle + def test_touching_arcs_at_extremes_on_same_great_circle(self): + """Test arcs touching at the arc extremes with arc on the same great circle.""" + touching_point = 5.0, 20.0 + start_point = SPoint.from_degrees(6.0, 1.0) + end_point = SPoint.from_degrees(*touching_point) + arc1 = SArc(start_point, end_point) + forward_point = arc1.forward_points(distance=3000 * 1000) + arc2 = SArc(end_point, forward_point) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + # Define another touching arc on the same great circle + # BUG [TO SOLVE] + start_point = SPoint.from_degrees(5.0, 1.0) + end_point = SPoint.from_degrees(5.0, 20.0) + arc1 = SArc(start_point, end_point) + forward_point = arc1.forward_points(distance=3000 * 1000) + arc2 = SArc(end_point, forward_point) + + # arc1._great_circle_intersections(arc2) # (0,0) , (-180,0) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + assert not arc1.intersects(arc2) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + def test_touching_arcs_at_extremes_with_equator_arcs(self): + """Test touching behaviour with arc lying around the equator.""" + touching_point = (0.0, 0.0) + arc1 = SArc(SPoint.from_degrees(*touching_point), SPoint.from_degrees(50, 0.0)) + arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(*touching_point)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + touching_point = (-180.0, 0.0) + arc1 = SArc(SPoint.from_degrees(*touching_point), SPoint.from_degrees(50, 0.0)) + arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(*touching_point)) + + p = arc2.intersection_point(arc1) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc1.intersection_point(arc2) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert np.allclose(p.vertices_in_degrees, touching_point) + + # BUG [TO SOLVE] (WHATEVER LON OUTSIDE 0/180 return None) + touching_point = (-90.0, 0.0) + arc1 = SArc(SPoint.from_degrees(*touching_point), SPoint.from_degrees(50, 0.0)) + arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(*touching_point)) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + assert not arc1.intersects(arc2) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + # p = arc2.intersection_point(arc1) + # assert np.allclose(p.vertices_in_degrees, touching_point) + + # p = arc1.intersection_point(arc2) + # assert np.allclose(p.vertices_in_degrees, touching_point) + + # p = arc2.intersection_point(arc1.reverse_direction()) + # assert np.allclose(p.vertices_in_degrees, touching_point) + + # p = arc1.intersection_point(arc2.reverse_direction()) + # assert np.allclose(p.vertices_in_degrees, touching_point) + + # p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + # assert np.allclose(p.vertices_in_degrees, touching_point) + + def test_touching_arcs_at_extremes_with_meridian_arcs(self): + """Test arcs touching at the arc extremes with arcs pointing vertically to the pole.""" + # BUG [TO SOLVE] + + # Touching at point at (1, 20) + arc1 = SArc(SPoint.from_degrees(1.0, 10.0), SPoint.from_degrees(1.0, 20.0)) + arc2 = SArc(SPoint.from_degrees(1.0, 20.0), SPoint.from_degrees(1.0, 30.0)) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + assert not arc1.intersects(arc2) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + # Touching at point at (1, -20) + arc1 = SArc(SPoint.from_degrees(1.0, -10.0), SPoint.from_degrees(1.0, -20.0)) + arc2 = SArc(SPoint.from_degrees(1.0, -20.0), SPoint.from_degrees(1.0, -30.0)) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + assert not arc1.intersects(arc2) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + def test_contain_within_arcs(self): + """Test behaviour with arc contained/within each other.""" + arc1 = SArc(SPoint.from_degrees(5, 1.0), SPoint.from_degrees(5.0, 20.0)) + arc2 = arc1.extend(distance=1000 * 1000, direction="both") + + # Arc2 contain arc1 + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + + # Arc1 within arc1 + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + def test_overlapping_arcs(self): + """Test behaviour with arc partially overlapping.""" + arc1 = SArc(SPoint.from_degrees(5, 1.0), SPoint.from_degrees(5.0, 20.0)) + arc2 = arc1.extend(distance=1000 * 1000, direction="forward") + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + def test_overlapping_with_meridian_arcs(self): + """Test behaviour with arc overlapping along a meridian.""" + # Overlapping between (5, 10) and (5, 20) + arc1 = SArc(SPoint.from_degrees(5, 0.0), SPoint.from_degrees(5.0, 20.0)) + arc2 = SArc(SPoint.from_degrees(5, 10.0), SPoint.from_degrees(5.0, 30.0)) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + def test_overlapping_equator_arcs(self): + """Test behaviour with arc overlapping along the equator.""" + arc1 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(0.0, 0.0)) + arc2 = SArc(SPoint.from_degrees(-50, 0.0), SPoint.from_degrees(-20, 0.0)) + + p = arc1.intersection_point(arc2) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1) + assert isinstance(p, type(None)) + + p = arc2.intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc1.intersection_point(arc2.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc2.reverse_direction().intersection_point(arc1.reverse_direction()) + assert isinstance(p, type(None)) + + def test_equal_arcs(self): + """Test intersection behaviour with equal arcs.""" + arc = SArc(SPoint.from_degrees(1.0, 20.0), SPoint.from_degrees(10.0, 40.0)) + + p = arc.intersection_point(arc) + assert isinstance(p, type(None)) + + p = arc.intersection_point(arc.reverse_direction()) + assert isinstance(p, type(None)) + + p = arc.reverse_direction().intersection_point(arc) + assert isinstance(p, type(None)) diff --git a/pyresample/test/test_sgeom/test_point.py b/pyresample/test/test_sgeom/test_point.py index 4a6075e45..9f6343f41 100644 --- a/pyresample/test/test_sgeom/test_point.py +++ b/pyresample/test/test_sgeom/test_point.py @@ -15,15 +15,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """Test cases for SPoint and SMultiPoint.""" -import unittest import numpy as np import pytest from pyresample.future.spherical import SMultiPoint, SPoint +from pyresample.future.spherical.point import create_spherical_point -class TestSPoint(unittest.TestCase): +class TestSPoint: """Test SPoint.""" def test_latitude_validity(self): @@ -55,6 +55,53 @@ def test_creation_from_degrees(self): p2 = SPoint(np.deg2rad(lon), np.deg2rad(lat)) assert p1 == p2 + def test_spherical_point_creation(self): + """Test SPoint or SMultiPoint factory.""" + # SPoint + p = create_spherical_point(0, np.pi / 2) + assert isinstance(p, SPoint) + + # SMultiPoint + p = create_spherical_point([0, 0], [np.pi / 2, 0]) + assert isinstance(p, SMultiPoint) + + def test_is_pole(self): + """Test is pole method.""" + assert SPoint.from_degrees(0, -90).is_pole() + assert SPoint.from_degrees(0, 90).is_pole() + assert not SPoint.from_degrees(0, 89.99).is_pole() + + def test_is_on_equator(self): + """Test is on equator method.""" + assert SPoint.from_degrees(0, 0).is_on_equator() + assert SPoint.from_degrees(10, -0).is_on_equator() + assert not SPoint.from_degrees(0, 0.001).is_on_equator() + + def test_antipodal_point(self): + """Test antipode property.""" + antipode = SPoint.from_degrees(0, 0).antipode + assert np.allclose(antipode.vertices_in_degrees, (-180, 0)) + + antipode = SPoint.from_degrees(90, 0).antipode + assert np.allclose(antipode.vertices_in_degrees, (-90, 0)) + + antipode = SPoint.from_degrees(-90, 0).antipode + assert np.allclose(antipode.vertices_in_degrees, (90, 0)) + + antipode = SPoint.from_degrees(180, 0).antipode + assert np.allclose(antipode.vertices_in_degrees, (0, 0)) + + antipode = SPoint.from_degrees(0, 90).antipode + assert antipode == SPoint.from_degrees(-5, -90) + assert np.allclose(antipode.vertices_in_degrees, (-180, -90)) + + antipode = SPoint.from_degrees(0, -90).antipode + assert antipode == SPoint.from_degrees(-5, 90) + assert np.allclose(antipode.vertices_in_degrees, (-180, 90)) + + antipode = SPoint.from_degrees(45, 45).antipode + assert np.allclose(antipode.vertices_in_degrees, (-135., -45.)) + def test_vertices(self): """Test vertices property.""" lons = 0 @@ -78,15 +125,42 @@ def test_raise_error_if_multi_point(self): with pytest.raises(ValueError): SPoint(lons, lats) + def test_eq(self): + """Check the equality.""" + p1 = SPoint.from_degrees(0, 10) + p2 = SPoint.from_degrees(0, 10) + assert p1 == p2 + + def test_pole_equality(self): + """Check pole point equality.""" + p1 = SPoint.from_degrees(0, 90) + p2 = SPoint.from_degrees(10, 90) + assert p1 == p2 + + p1 = SPoint.from_degrees(0, -90) + p2 = SPoint.from_degrees(10, -90) + assert p1 == p2 + + p1 = SPoint.from_degrees(0, -90) + p2 = SPoint.from_degrees(0, 90) + assert not p1 == p2 + assert p1 != p2 + + def test_eq_antimeridian(self): + """Check the equality of a point on the antimeridian.""" + p = SPoint.from_degrees(-180, 0) + p1 = SPoint.from_degrees(180, 0) + assert p == p1 + def test_str(self): """Check the string representation.""" d = SPoint(1.0, 0.5) - self.assertEqual(str(d), "(1.0, 0.5)") + assert str(d) == "(1.0, 0.5)" def test_repr(self): """Check the representation.""" d = SPoint(1.0, 0.5) - self.assertEqual(repr(d), "(1.0, 0.5)") + assert repr(d) == 'SPoint: (1.0, 0.5)' def test_to_shapely(self): """Test conversion to shapely.""" @@ -98,7 +172,7 @@ def test_to_shapely(self): assert shapely_point.equals_exact(spherical_point.to_shapely(), tolerance=1e-10) -class TestSMultiPoint(unittest.TestCase): +class TestSMultiPoint: """Test SMultiPoint.""" def test_single_point(self): @@ -120,6 +194,16 @@ def test_creation_from_degrees(self): p2 = SMultiPoint(np.deg2rad(lon), np.deg2rad(lat)) assert p1 == p2 + def test_antipodal_points(self): + """Test antipodes property.""" + lon = [0, 90, -90, 180, 0, 0, 45] + lat = [0, 0, 0, 0, 90, -90, 45] + antipodal_lon = [-180, -90, 90, 0, -5, -5, -135] + antipodal_lat = [0, 0, 0, 0, -90, 90, -45] + p = SMultiPoint.from_degrees(lon, lat) + antipodal_p = SMultiPoint.from_degrees(antipodal_lon, antipodal_lat) + assert p.antipodes == antipodal_p + def test_vertices(self): """Test vertices property.""" lons = np.array([0, np.pi]) @@ -148,8 +232,8 @@ def test_distance(self): p2 = SMultiPoint(lons, lats) d12 = p1.distance(p2) d21 = p2.distance(p1) - self.assertEqual(d12.shape, (2, 3)) - self.assertEqual(d21.shape, (3, 2)) + assert d12.shape == (2, 3) + assert d21.shape == (3, 2) res = np.array([[0., 1.57079633, 3.14159265], [3.14159265, 1.57079633, 0.]]) assert np.allclose(d12, res) @@ -169,19 +253,22 @@ def test_hdistance(self): p2 = SMultiPoint(lons, lats) d12 = p1.hdistance(p2) d21 = p2.hdistance(p1) - self.assertEqual(d12.shape, (2, 3)) - self.assertEqual(d21.shape, (3, 2)) + assert d12.shape == (2, 3) + assert d21.shape == (3, 2) res = np.array([[0., 1.57079633, 3.14159265], [3.14159265, 1.57079633, 0.]]) assert np.allclose(d12, res) - def test_eq(self): - """Check the equality.""" - lons = [0, np.pi] - lats = [-np.pi / 2, np.pi / 2] - p = SMultiPoint(lons, lats) - p1 = SMultiPoint(lons, lats) - assert p == p1 + def test_pole_equality(self): + """Check pole point equality.""" + p1 = SMultiPoint.from_degrees([0, 0, 1], [0, 0, 90]) + p2 = SMultiPoint.from_degrees([0, 0, 50], [0, 0, 90]) + assert p1 == p2 + + p1 = SMultiPoint.from_degrees([0, 0, 1], [0, 0, 90]) + p2 = SMultiPoint.from_degrees([0, 0, 1], [0, 0, -90]) + assert not p1 == p2 + assert p1 != p2 def test_eq_antimeridian(self): """Check the equality with longitudes at -180/180 degrees.""" @@ -192,29 +279,44 @@ def test_eq_antimeridian(self): p1 = SMultiPoint(lons1, lats) assert p == p1 + def test_eq(self): + """Check the equality.""" + lons = [0, np.pi] + lats = [-np.pi / 2, np.pi / 2] + p = SMultiPoint(lons, lats) + p1 = SMultiPoint(lons, lats) + assert p == p1 + def test_neq(self): """Check the equality.""" lons = np.array([0, np.pi]) - lats = [-np.pi / 2, np.pi / 2] + lats = [0, np.pi / 2] p = SMultiPoint(lons, lats) p1 = SMultiPoint(lons + 0.1, lats) assert p != p1 + # Equality at the pole + lons = np.array([0, np.pi]) + lats = [-np.pi / 2, np.pi / 2] + p = SMultiPoint(lons, lats) + p1 = SMultiPoint(lons + 0.1, lats) + assert not p != p1 + def test_str(self): """Check the string representation.""" lons = [0, np.pi] lats = [-np.pi / 2, np.pi / 2] p = SMultiPoint(lons, lats) expected_str = '[[ 0. -1.57079633]\n [-3.14159265 1.57079633]]' - self.assertEqual(str(p), expected_str) + assert str(p) == expected_str def test_repr(self): """Check the representation.""" lons = [0, np.pi] lats = [-np.pi / 2, np.pi / 2] p = SMultiPoint(lons, lats) - expected_repr = '[[ 0. -1.57079633]\n [-3.14159265 1.57079633]]' - self.assertEqual(repr(p), expected_repr) + expected_repr = 'SMultiPoint:\n [[ 0. -1.57079633]\n [-3.14159265 1.57079633]]' + assert repr(p) == expected_repr def test_to_shapely(self): """Test conversion to shapely."""