def testSeparationValueGeneric(self): """Test if separation() returns the correct value. """ # This should cover arcs over the meridian, across the pole, etc. # Do not use sphgeom as an oracle, in case SpherePoint uses it # internally. for lon1, lat1 in self._dataset: point1 = SpherePoint(lon1, lat1) x1, y1, z1 = SpherePointTestSuite.toVector(lon1, lat1) for lon2, lat2 in self._dataset: point2 = SpherePoint(lon2, lat2) if lon1 != lon2 or lat1 != lat2: # Numerically unstable at small angles, but that's ok. x2, y2, z2 = SpherePointTestSuite.toVector(lon2, lat2) expected = math.acos(x1*x2 + y1*y2 + z1*z2) else: expected = 0.0 sep = point1.separation(point2) self.assertIsInstance(sep, geom.Angle) if point1.isFinite() and point2.isFinite(): self.assertGreaterEqual(sep.asDegrees(), 0.0) self.assertLessEqual(sep.asDegrees(), 180.0) self.assertAlmostEqual(expected, sep.asRadians()) self.assertAnglesAlmostEqual( sep, point2.separation(point1)) else: self.assertTrue(math.isnan(sep.asRadians())) self.assertTrue(math.isnan( point2.separation(point1).asRadians()))
def testFieldAngleToVector(self): sp00 = SpherePoint(0, 0, degrees) degList = (-90, -89.9, -20, 0, 10, 89.9, 90) for xdeg, ydeg, flipX in itertools.product(degList, degList, (False, True)): with self.subTest(xdeg=xdeg, ydeg=ydeg, flipX=flipX): xrad = xdeg * RAD_PER_DEG signx = -1 if flipX else 1 testOrientation = xdeg != 0 or ydeg != 0 yrad = ydeg * RAD_PER_DEG fieldAngle = (xrad, yrad) vector = coordUtils.fieldAngleToVector(fieldAngle, flipX) self.assertAlmostEqual(np.linalg.norm(vector), 1) if testOrientation: # Orientation should match. orientationFromFieldAngle = math.atan2(yrad, signx*xrad)*radians # Field angle x = vector y, field angle y = vector z. orientationFromVector = math.atan2(vector[2], vector[1])*radians self.assertAnglesAlmostEqual(orientationFromVector, orientationFromFieldAngle) # Now test as spherical geometry. sp = SpherePoint(Vector3d(*vector)) separation = sp00.separation(sp) predictedSeparation = math.hypot(xrad, yrad)*radians self.assertAnglesAlmostEqual(predictedSeparation, separation) if testOrientation: bearing = sp00.bearingTo(sp) self.assertAnglesAlmostEqual(orientationFromFieldAngle, bearing) # Test round trip through vectorToFieldAngle. fieldAngleFromVector = coordUtils.vectorToFieldAngle(vector, flipX) np.testing.assert_allclose(fieldAngleFromVector, fieldAngle, atol=1e-15)
def testSeparationPoles(self): """White-box test: all representations of a pole should have the same distance to another point. """ southPole1 = SpherePoint(-30.0, -90.0, degrees) southPole2 = SpherePoint(183.0, -90.0, degrees) regularPoint = SpherePoint(42.0, 45.0, degrees) expectedSep = (45.0 + 90.0)*degrees self.assertAnglesAlmostEqual( expectedSep, southPole1.separation(regularPoint)) self.assertAnglesAlmostEqual( expectedSep, regularPoint.separation(southPole1)) self.assertAnglesAlmostEqual( expectedSep, southPole2.separation(regularPoint)) self.assertAnglesAlmostEqual( expectedSep, regularPoint.separation(southPole2))
def testComputeAzAltFromPupilBase(self): """Test computeAzAltFromBasePupil with general values """ # transform the pupil vector back to the base vector # using the computed internal az/alt position for vectorPupil, vectorBase, pupilMagFactor, baseMagFactor in itertools.product( ((1, 0, 0), (2, 1, 0), (2, 0, 1), (2, 0.7, -0.8)), ((1, 0, 0), (0, 1, 0), (1, -0.7, 0.8)), (1, 1000), (1, 1000), ): with self.subTest(vectorPupil=vectorPupil, vectorBase=vectorBase, pupilMagFactor=pupilMagFactor, baseMagFactor=baseMagFactor): vectorPupilScaled = np.array(vectorPupil, dtype=float) * pupilMagFactor pupilMag = np.linalg.norm(vectorPupilScaled) vectorBaseScaled = np.array(vectorBase, dtype=float) * baseMagFactor pupilAzAlt = coordUtils.computeAzAltFromBasePupil(vectorPupil=vectorPupilScaled, vectorBase=vectorBaseScaled) # Check the round trip; note that the magnitude # of the returned vector will equal # the magnitude of the input vector. vectorBaseRoundTrip = coordUtils.convertVectorFromPupilToBase( vectorPupil=vectorPupilScaled, pupilAzAlt=pupilAzAlt) vectorBaseRoundTripMag = np.linalg.norm(vectorBaseRoundTrip) self.assertAlmostEqual(vectorBaseRoundTripMag, pupilMag, delta=1e-15*pupilMag) spBase = SpherePoint(Vector3d(*vectorBase)) spBaseRoundTrip = SpherePoint(Vector3d(*vectorBaseRoundTrip)) sep = spBase.separation(spBaseRoundTrip) self.assertLess(sep.asRadians(), 2e-15)
def testSeparationValueAbsolute(self): """Test if separation() returns specific values. """ # Test from "Meeus, p. 110" (test originally written for coord::Coord; # don't know exact reference) spica = SpherePoint(201.2983, -11.1614, degrees) arcturus = SpherePoint(213.9154, 19.1825, degrees) # Verify to precision of quoted distance and positions. self.assertAlmostEqual( 32.7930, spica.separation(arcturus).asDegrees(), 4) # Verify small angles: along a constant ra, add an arcsec to spica dec. epsilon = 1.0*geom.arcseconds spicaPlus = SpherePoint(spica.getLongitude(), spica.getLatitude() + epsilon) self.assertAnglesAlmostEqual(epsilon, spicaPlus.separation(spica))
def testBearingToValueOnEquator(self): """Test if bearingTo() returns the expected value from a point on the equator """ lon0 = 90.0 lat0 = 0.0 # These tests only work from the equator. arcLen = 10.0 trials = [ # Along celestial equator dict(lon=lon0, lat=lat0, bearing=0.0, lonEnd=lon0+arcLen, latEnd=lat0), # Along a meridian dict(lon=lon0, lat=lat0, bearing=90.0, lonEnd=lon0, latEnd=lat0+arcLen), # 180 degree arc (should go to antipodal point) dict(lon=lon0, lat=lat0, bearing=45.0, lonEnd=lon0+180.0, latEnd=-lat0), # dict(lon=lon0, lat=lat0, bearing=45.0, lonEnd=lon0+90.0, latEnd=lat0 + 45.0), dict(lon=lon0, lat=lat0, bearing=225.0, lonEnd=lon0-90.0, latEnd=lat0 - 45.0), dict(lon=lon0, lat=np.nextafter(-90.0, inf), bearing=90.0, lonEnd=lon0, latEnd=0.0), dict(lon=lon0, lat=np.nextafter(-90.0, inf), bearing=0.0, lonEnd=lon0 + 90.0, latEnd=0.0), # Argument at a pole should work dict(lon=lon0, lat=lat0, bearing=270.0, lonEnd=lon0, latEnd=-90.0), # Support for non-finite values dict(lon=lon0, lat=nan, bearing=nan, lonEnd=lon0, latEnd=45.0), dict(lon=lon0, lat=lat0, bearing=nan, lonEnd=nan, latEnd=90.0), dict(lon=inf, lat=lat0, bearing=nan, lonEnd=lon0, latEnd=42.0), dict(lon=lon0, lat=lat0, bearing=nan, lonEnd=-inf, latEnd=42.0), ] for trial in trials: origin = SpherePoint(trial['lon']*degrees, trial['lat']*degrees) end = SpherePoint(trial['lonEnd']*degrees, trial['latEnd']*degrees) bearing = origin.bearingTo(end) self.assertIsInstance(bearing, geom.Angle) if origin.isFinite() and end.isFinite(): self.assertGreaterEqual(bearing.asDegrees(), 0.0) self.assertLess(bearing.asDegrees(), 360.0) if origin.separation(end).asDegrees() != 180.0: if not math.isnan(trial['bearing']): self.assertAlmostEqual( trial['bearing'], bearing.asDegrees(), 12) else: self.assertTrue(math.isnan(bearing.asRadians()))
def testTicket1761(self): """Regression test for Ticket 1761. Checks for math errors caused by unnormalized vectors. """ refPoint = SpherePoint(lsst.sphgeom.Vector3d(0, 1, 0)) point1 = SpherePoint(lsst.sphgeom.Vector3d(0.1, 0.1, 0.1)) point2 = SpherePoint(lsst.sphgeom.Vector3d(0.6, 0.6, 0.6)) sep1 = refPoint.separation(point1) sep2 = refPoint.separation(point2) sepTrue = 54.735610317245339*degrees self.assertAnglesAlmostEqual(sepTrue, sep1) self.assertAnglesAlmostEqual(sepTrue, sep2)
def testComputeAzAltFromPupilBaseWithBaseEqualsPupil(self): """Test computeAzAltFromBasePupil with baseVector=pupilVector, so the telescope will to internal az, alt=0 """ zeroSp = SpherePoint(0, 0, radians) for vector, pupilMagFactor, baseMagFactor in itertools.product( ((1, 0, 0), (0.1, -1, 0), (0.1, -0.5, 0.5), (0.5, 0, 0.5), (1, 0.7, -0.8)), (1, 1000), (1, 1000), ): with self.subTest(vector=vector, pupilMagFactor=pupilMagFactor, baseMagFactor=baseMagFactor): vectorPupil = np.array(vector, dtype=float) * pupilMagFactor vectorBase = np.array(vector, dtype=float) * baseMagFactor obs = coordUtils.computeAzAltFromBasePupil(vectorPupil=vectorPupil, vectorBase=vectorBase) sep = zeroSp.separation(obs).asRadians() self.assertLess(sep, 1e-14)
def testOffsetValue(self): """Test if offset() returns the expected value. """ # This should cover arcs over the meridian, across the pole, etc. for lon1, lat1 in self._dataset: point1 = SpherePoint(lon1, lat1) for lon2, lat2 in self._dataset: if lon1 == lon2 and lat1 == lat2: continue point2 = SpherePoint(lon2, lat2) bearing = point1.bearingTo(point2) distance = point1.separation(point2) # offsetting point1 by bearing and distance should produce the same result as point2 newPoint = point1.offset(bearing, distance) self.assertIsInstance(newPoint, SpherePoint) self.assertSpherePointsAlmostEqual(point2, newPoint) if newPoint.atPole(): self.assertAnglesAlmostEqual(newPoint.getLongitude(), 0*degrees) # measuring the separation and bearing from point1 to the new point # should produce the requested separation and bearing measuredDistance = point1.separation(newPoint) self.assertAnglesAlmostEqual(measuredDistance, distance) if abs(measuredDistance.asDegrees() - 180) > 1e-5: # The two points are not opposite each other on the sphere, # so the bearing has a well defined value measuredBearing = point1.bearingTo(newPoint) self.assertAnglesAlmostEqual(measuredBearing, bearing) # offset by a negative amount in the opposite direction should produce the same result newPoint2 = point1.offset(bearing + 180 * degrees, -distance) self.assertIsInstance(newPoint2, SpherePoint) # check angular separation (longitude is checked below) self.assertSpherePointsAlmostEqual(newPoint, newPoint2) if point1.isFinite() and point2.isFinite(): if not point2.atPole(): self.assertAnglesAlmostEqual( point2.getLongitude(), newPoint.getLongitude()) self.assertAnglesAlmostEqual( point2.getLongitude(), newPoint2.getLongitude()) self.assertAnglesAlmostEqual( point2.getLatitude(), newPoint.getLatitude()) self.assertAnglesAlmostEqual( point2.getLatitude(), newPoint2.getLatitude()) else: self.assertTrue(math.isnan( newPoint.getLongitude().asRadians())) self.assertTrue(math.isnan( newPoint2.getLongitude().asRadians())) self.assertTrue(math.isnan( newPoint.getLatitude().asRadians())) self.assertTrue(math.isnan( newPoint2.getLatitude().asRadians())) # Test precision near the poles lon = 123.0*degrees almostPole = SpherePoint(lon, self.nextDown(90.0*degrees)) goSouth = almostPole.offset(-90.0*degrees, 90.0*degrees) self.assertAnglesAlmostEqual(lon, goSouth.getLongitude()) self.assertAnglesAlmostEqual(0.0*degrees, goSouth.getLatitude()) goEast = almostPole.offset(0.0*degrees, 90.0*degrees) self.assertAnglesAlmostEqual(lon + 90.0*degrees, goEast.getLongitude()) self.assertAnglesAlmostEqual(0.0*degrees, goEast.getLatitude())
def testRotatedValue(self): """Test if rotated() returns the expected value. """ # Try rotating about the equatorial pole (ie. along a parallel). longitude = 90.0 latitudes = [0.0, 30.0, 60.0] arcLen = 10.0 pole = SpherePoint(0.0*degrees, 90.0*degrees) for latitude in latitudes: point = SpherePoint(longitude*degrees, latitude*degrees) newPoint = point.rotated(pole, arcLen*degrees) self.assertIsInstance(newPoint, SpherePoint) self.assertAlmostEqual( longitude + arcLen, newPoint.getLongitude().asDegrees()) self.assertAlmostEqual( latitude, newPoint.getLatitude().asDegrees()) # Try with pole = vernal equinox and rotate up the 90 degree meridian. pole = SpherePoint(0.0*degrees, 0.0*degrees) for latitude in latitudes: point = SpherePoint(longitude*degrees, latitude*degrees) newPoint = point.rotated(pole, arcLen*degrees) self.assertAlmostEqual( longitude, newPoint.getLongitude().asDegrees()) self.assertAlmostEqual( latitude + arcLen, newPoint.getLatitude().asDegrees()) # Test accuracy close to coordinate pole point = SpherePoint(90.0*degrees, np.nextafter(90.0, -inf)*degrees) newPoint = point.rotated(pole, 90.0*degrees) self.assertAlmostEqual(270.0, newPoint.getLongitude().asDegrees()) self.assertAlmostEqual(90.0 - np.nextafter(90.0, -inf), newPoint.getLatitude().asDegrees()) # Generic pole; can't predict position, but test for rotation # invariant. pole = SpherePoint(283.5*degrees, -23.6*degrees) for lon, lat in self._dataset: point = SpherePoint(lon, lat) dist = point.separation(pole) newPoint = point.rotated(pole, -32.4*geom.radians) self.assertNotAlmostEqual(point.getLongitude().asDegrees(), newPoint.getLongitude().asDegrees()) self.assertNotAlmostEqual(point.getLatitude().asDegrees(), newPoint.getLatitude().asDegrees()) self.assertAnglesAlmostEqual(dist, newPoint.separation(pole)) # Non-finite values give undefined rotations for latitude in latitudes: point = SpherePoint(longitude*degrees, latitude*degrees) nanPoint = point.rotated(pole, nan*degrees) infPoint = point.rotated(pole, inf*degrees) self.assertTrue(math.isnan(nanPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(nanPoint.getLatitude().asRadians())) self.assertTrue(math.isnan(infPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(infPoint.getLatitude().asRadians())) # Non-finite points rotate into non-finite points for point in [ SpherePoint(-inf*degrees, 1.0*radians), SpherePoint(32.0*degrees, nan*radians), ]: newPoint = point.rotated(pole, arcLen*degrees) self.assertTrue(math.isnan(nanPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(nanPoint.getLatitude().asRadians())) self.assertTrue(math.isnan(infPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(infPoint.getLatitude().asRadians())) # Rotation around non-finite poles undefined for latitude in latitudes: point = SpherePoint(longitude*degrees, latitude*degrees) for pole in [ SpherePoint(-inf*degrees, 1.0*radians), SpherePoint(32.0*degrees, nan*radians), ]: newPoint = point.rotated(pole, arcLen*degrees) self.assertTrue(math.isnan( nanPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(nanPoint.getLatitude().asRadians())) self.assertTrue(math.isnan( infPoint.getLongitude().asRadians())) self.assertTrue(math.isnan(infPoint.getLatitude().asRadians()))