1# -*- coding: utf-8 -*-
2# Licensed under a 3-clause BSD style license - see LICENSE.rst
3"""Test initialization and other aspects of Angle and subclasses"""
4
5import threading
6import warnings
7
8import numpy as np
9import pytest
10from numpy.testing import assert_allclose, assert_array_equal
11
12import astropy.units as u
13from astropy.coordinates.angles import Longitude, Latitude, Angle
14from astropy.coordinates.errors import (
15    IllegalSecondError, IllegalMinuteError, IllegalHourError,
16    IllegalSecondWarning, IllegalMinuteWarning)
17from astropy.utils.exceptions import AstropyDeprecationWarning
18
19
20def test_create_angles():
21    """
22    Tests creating and accessing Angle objects
23    """
24
25    ''' The "angle" is a fundamental object. The internal
26    representation is stored in radians, but this is transparent to the user.
27    Units *must* be specified rather than a default value be assumed. This is
28    as much for self-documenting code as anything else.
29
30    Angle objects simply represent a single angular coordinate. More specific
31    angular coordinates (e.g. Longitude, Latitude) are subclasses of Angle.'''
32
33    a1 = Angle(54.12412, unit=u.degree)
34    a2 = Angle("54.12412", unit=u.degree)
35    a3 = Angle("54:07:26.832", unit=u.degree)
36    a4 = Angle("54.12412 deg")
37    a5 = Angle("54.12412 degrees")
38    a6 = Angle("54.12412°")  # because we like Unicode
39    a7 = Angle((54, 7, 26.832), unit=u.degree)
40    a8 = Angle("54°07'26.832\"")
41    # (deg,min,sec) *tuples* are acceptable, but lists/arrays are *not*
42    # because of the need to eventually support arrays of coordinates
43    a9 = Angle([54, 7, 26.832], unit=u.degree)
44    assert_allclose(a9.value, [54, 7, 26.832])
45    assert a9.unit is u.degree
46
47    a10 = Angle(3.60827466667, unit=u.hour)
48    a11 = Angle("3:36:29.7888000120", unit=u.hour)
49    a12 = Angle((3, 36, 29.7888000120), unit=u.hour)  # *must* be a tuple
50    # Regression test for #5001
51    a13 = Angle((3, 36, 29.7888000120), unit='hour')
52
53    Angle(0.944644098745, unit=u.radian)
54
55    with pytest.raises(u.UnitsError):
56        Angle(54.12412)
57        # raises an exception because this is ambiguous
58
59    with pytest.raises(u.UnitsError):
60        Angle(54.12412, unit=u.m)
61
62    with pytest.raises(ValueError):
63        Angle(12.34, unit="not a unit")
64
65    a14 = Angle("03h36m29.7888000120")  # no trailing 's', but unambiguous
66
67    a15 = Angle("5h4m3s")  # single digits, no decimal
68    assert a15.unit == u.hourangle
69
70    a16 = Angle("1 d")
71    a17 = Angle("1 degree")
72
73    assert a16.degree == 1
74    assert a17.degree == 1
75
76    a18 = Angle("54 07.4472", unit=u.degree)
77    a19 = Angle("54:07.4472", unit=u.degree)
78    a20 = Angle("54d07.4472m", unit=u.degree)
79    a21 = Angle("3h36m", unit=u.hour)
80    a22 = Angle("3.6h", unit=u.hour)
81    a23 = Angle("- 3h", unit=u.hour)
82    a24 = Angle("+ 3h", unit=u.hour)
83
84    # ensure the above angles that should match do
85    assert a1 == a2 == a3 == a4 == a5 == a6 == a7 == a8 == a18 == a19 == a20
86    assert_allclose(a1.radian, a2.radian)
87    assert_allclose(a2.degree, a3.degree)
88    assert_allclose(a3.radian, a4.radian)
89    assert_allclose(a4.radian, a5.radian)
90    assert_allclose(a5.radian, a6.radian)
91    assert_allclose(a6.radian, a7.radian)
92
93    assert_allclose(a10.degree, a11.degree)
94    assert a11 == a12 == a13 == a14
95    assert a21 == a22
96    assert a23 == -a24
97
98    # check for illegal ranges / values
99    with pytest.raises(IllegalSecondError):
100        a = Angle("12 32 99", unit=u.degree)
101
102    with pytest.raises(IllegalMinuteError):
103        a = Angle("12 99 23", unit=u.degree)
104
105    with pytest.raises(IllegalSecondError):
106        a = Angle("12 32 99", unit=u.hour)
107
108    with pytest.raises(IllegalMinuteError):
109        a = Angle("12 99 23", unit=u.hour)
110
111    with pytest.raises(IllegalHourError):
112        a = Angle("99 25 51.0", unit=u.hour)
113
114    with pytest.raises(ValueError):
115        a = Angle("12 25 51.0xxx", unit=u.hour)
116
117    with pytest.raises(ValueError):
118        a = Angle("12h34321m32.2s")
119
120    assert a1 is not None
121
122
123def test_angle_from_view():
124    q = np.arange(3.) * u.deg
125    a = q.view(Angle)
126    assert type(a) is Angle
127    assert a.unit is q.unit
128    assert np.all(a == q)
129
130    q2 = np.arange(4) * u.m
131    with pytest.raises(u.UnitTypeError):
132        q2.view(Angle)
133
134
135def test_angle_ops():
136    """
137    Tests operations on Angle objects
138    """
139
140    # Angles can be added and subtracted. Multiplication and division by a
141    # scalar is also permitted. A negative operator is also valid.  All of
142    # these operate in a single dimension. Attempting to multiply or divide two
143    # Angle objects will return a quantity.  An exception will be raised if it
144    # is attempted to store output with a non-angular unit in an Angle [#2718].
145
146    a1 = Angle(3.60827466667, unit=u.hour)
147    a2 = Angle("54:07:26.832", unit=u.degree)
148    a1 + a2  # creates new Angle object
149    a1 - a2
150    -a1
151
152    assert_allclose((a1 * 2).hour, 2 * 3.6082746666700003)
153    assert abs((a1 / 3.123456).hour - 3.60827466667 / 3.123456) < 1e-10
154
155    # commutativity
156    assert (2 * a1).hour == (a1 * 2).hour
157
158    a3 = Angle(a1)  # makes a *copy* of the object, but identical content as a1
159    assert_allclose(a1.radian, a3.radian)
160    assert a1 is not a3
161
162    a4 = abs(-a1)
163    assert a4.radian == a1.radian
164
165    a5 = Angle(5.0, unit=u.hour)
166    assert a5 > a1
167    assert a5 >= a1
168    assert a1 < a5
169    assert a1 <= a5
170
171    # check operations with non-angular result give Quantity.
172    a6 = Angle(45., u.degree)
173    a7 = a6 * a5
174    assert type(a7) is u.Quantity
175
176    # but those with angular result yield Angle.
177    # (a9 is regression test for #5327)
178    a8 = a1 + 1.*u.deg
179    assert type(a8) is Angle
180    a9 = 1.*u.deg + a1
181    assert type(a9) is Angle
182
183    with pytest.raises(TypeError):
184        a6 *= a5
185
186    with pytest.raises(TypeError):
187        a6 *= u.m
188
189    with pytest.raises(TypeError):
190        np.sin(a6, out=a6)
191
192
193def test_angle_methods():
194    # Most methods tested as part of the Quantity tests.
195    # A few tests here which caused problems before: #8368
196    a = Angle([0., 2.], 'deg')
197    a_mean = a.mean()
198    assert type(a_mean) is Angle
199    assert a_mean == 1. * u.degree
200    a_std = a.std()
201    assert type(a_std) is Angle
202    assert a_std == 1. * u.degree
203    a_var = a.var()
204    assert type(a_var) is u.Quantity
205    assert a_var == 1. * u.degree ** 2
206    a_ptp = a.ptp()
207    assert type(a_ptp) is Angle
208    assert a_ptp == 2. * u.degree
209    a_max = a.max()
210    assert type(a_max) is Angle
211    assert a_max == 2. * u.degree
212    a_min = a.min()
213    assert type(a_min) is Angle
214    assert a_min == 0. * u.degree
215
216
217def test_angle_convert():
218    """
219    Test unit conversion of Angle objects
220    """
221    angle = Angle("54.12412", unit=u.degree)
222
223    assert_allclose(angle.hour, 3.60827466667)
224    assert_allclose(angle.radian, 0.944644098745)
225    assert_allclose(angle.degree, 54.12412)
226
227    assert len(angle.hms) == 3
228    assert isinstance(angle.hms, tuple)
229    assert angle.hms[0] == 3
230    assert angle.hms[1] == 36
231    assert_allclose(angle.hms[2], 29.78879999999947)
232    # also check that the namedtuple attribute-style access works:
233    assert angle.hms.h == 3
234    assert angle.hms.m == 36
235    assert_allclose(angle.hms.s, 29.78879999999947)
236
237    assert len(angle.dms) == 3
238    assert isinstance(angle.dms, tuple)
239    assert angle.dms[0] == 54
240    assert angle.dms[1] == 7
241    assert_allclose(angle.dms[2], 26.831999999992036)
242    # also check that the namedtuple attribute-style access works:
243    assert angle.dms.d == 54
244    assert angle.dms.m == 7
245    assert_allclose(angle.dms.s, 26.831999999992036)
246
247    assert isinstance(angle.dms[0], float)
248    assert isinstance(angle.hms[0], float)
249
250    # now make sure dms and signed_dms work right for negative angles
251    negangle = Angle("-54.12412", unit=u.degree)
252
253    assert negangle.dms.d == -54
254    assert negangle.dms.m == -7
255    assert_allclose(negangle.dms.s, -26.831999999992036)
256    assert negangle.signed_dms.sign == -1
257    assert negangle.signed_dms.d == 54
258    assert negangle.signed_dms.m == 7
259    assert_allclose(negangle.signed_dms.s, 26.831999999992036)
260
261
262def test_angle_formatting():
263    """
264    Tests string formatting for Angle objects
265    """
266
267    '''
268    The string method of Angle has this signature:
269    def string(self, unit=DEGREE, decimal=False, sep=" ", precision=5,
270               pad=False):
271
272    The "decimal" parameter defaults to False since if you need to print the
273    Angle as a decimal, there's no need to use the "format" method (see
274    above).
275    '''
276
277    angle = Angle("54.12412", unit=u.degree)
278
279    # __str__ is the default `format`
280    assert str(angle) == angle.to_string()
281
282    res = 'Angle as HMS: 3h36m29.7888s'
283    assert f"Angle as HMS: {angle.to_string(unit=u.hour)}" == res
284
285    res = 'Angle as HMS: 3:36:29.7888'
286    assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':')}" == res
287
288    res = 'Angle as HMS: 3:36:29.79'
289    assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep=':', precision=2)}" == res
290
291    # Note that you can provide one, two, or three separators passed as a
292    # tuple or list
293
294    res = 'Angle as HMS: 3h36m29.7888s'
295    assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour,
296                                                   sep=("h", "m", "s"),
297                                                   precision=4)) == res
298
299    res = 'Angle as HMS: 3-36|29.7888'
300    assert "Angle as HMS: {}".format(angle.to_string(unit=u.hour, sep=["-", "|"],
301                                                   precision=4)) == res
302
303    res = 'Angle as HMS: 3-36-29.7888'
304    assert f"Angle as HMS: {angle.to_string(unit=u.hour, sep='-', precision=4)}" == res
305
306    res = 'Angle as HMS: 03h36m29.7888s'
307    assert f"Angle as HMS: {angle.to_string(unit=u.hour, precision=4, pad=True)}" == res
308
309    # Same as above, in degrees
310
311    angle = Angle("3 36 29.78880", unit=u.degree)
312
313    res = 'Angle as DMS: 3d36m29.7888s'
314    assert f"Angle as DMS: {angle.to_string(unit=u.degree)}" == res
315
316    res = 'Angle as DMS: 3:36:29.7888'
317    assert f"Angle as DMS: {angle.to_string(unit=u.degree, sep=':')}" == res
318
319    res = 'Angle as DMS: 3:36:29.79'
320    assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=":",
321                                      precision=2)) == res
322
323    # Note that you can provide one, two, or three separators passed as a
324    # tuple or list
325
326    res = 'Angle as DMS: 3d36m29.7888s'
327    assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree,
328                                                   sep=("d", "m", "s"),
329                                                   precision=4)) == res
330
331    res = 'Angle as DMS: 3-36|29.7888'
332    assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep=["-", "|"],
333                                                   precision=4)) == res
334
335    res = 'Angle as DMS: 3-36-29.7888'
336    assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, sep="-",
337                                                    precision=4)) == res
338
339    res = 'Angle as DMS: 03d36m29.7888s'
340    assert "Angle as DMS: {}".format(angle.to_string(unit=u.degree, precision=4,
341                                                  pad=True)) == res
342
343    res = 'Angle as rad: 0.0629763rad'
344    assert f"Angle as rad: {angle.to_string(unit=u.radian)}" == res
345
346    res = 'Angle as rad decimal: 0.0629763'
347    assert f"Angle as rad decimal: {angle.to_string(unit=u.radian, decimal=True)}" == res
348
349    # check negative angles
350
351    angle = Angle(-1.23456789, unit=u.degree)
352    angle2 = Angle(-1.23456789, unit=u.hour)
353
354    assert angle.to_string() == '-1d14m04.444404s'
355    assert angle.to_string(pad=True) == '-01d14m04.444404s'
356    assert angle.to_string(unit=u.hour) == '-0h04m56.2962936s'
357    assert angle2.to_string(unit=u.hour, pad=True) == '-01h14m04.444404s'
358    assert angle.to_string(unit=u.radian, decimal=True) == '-0.0215473'
359
360
361def test_to_string_vector():
362    # Regression test for the fact that vectorize doesn't work with Numpy 1.6
363    assert Angle([1./7., 1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s"
364    assert Angle([1./7.], unit='deg').to_string()[0] == "0d08m34.28571429s"
365    assert Angle(1./7., unit='deg').to_string() == "0d08m34.28571429s"
366
367
368def test_angle_format_roundtripping():
369    """
370    Ensures that the string representation of an angle can be used to create a
371    new valid Angle.
372    """
373
374    a1 = Angle(0, unit=u.radian)
375    a2 = Angle(10, unit=u.degree)
376    a3 = Angle(0.543, unit=u.degree)
377    a4 = Angle('1d2m3.4s')
378
379    assert Angle(str(a1)).degree == a1.degree
380    assert Angle(str(a2)).degree == a2.degree
381    assert Angle(str(a3)).degree == a3.degree
382    assert Angle(str(a4)).degree == a4.degree
383
384    # also check Longitude/Latitude
385    ra = Longitude('1h2m3.4s')
386    dec = Latitude('1d2m3.4s')
387
388    assert_allclose(Angle(str(ra)).degree, ra.degree)
389    assert_allclose(Angle(str(dec)).degree, dec.degree)
390
391
392def test_radec():
393    """
394    Tests creation/operations of Longitude and Latitude objects
395    """
396
397    '''
398    Longitude and Latitude are objects that are subclassed from Angle. As with Angle, Longitude
399    and Latitude can parse any unambiguous format (tuples, formatted strings, etc.).
400
401    The intention is not to create an Angle subclass for every possible
402    coordinate object (e.g. galactic l, galactic b). However, equatorial Longitude/Latitude
403    are so prevalent in astronomy that it's worth creating ones for these
404    units. They will be noted as "special" in the docs and use of the just the
405    Angle class is to be used for other coordinate systems.
406    '''
407
408    with pytest.raises(u.UnitsError):
409        ra = Longitude("4:08:15.162342")  # error - hours or degrees?
410    with pytest.raises(u.UnitsError):
411        ra = Longitude("-4:08:15.162342")
412
413    # the "smart" initializer allows >24 to automatically do degrees, but the
414    # Angle-based one does not
415    # TODO: adjust in 0.3 for whatever behavior is decided on
416
417    # ra = Longitude("26:34:15.345634")  # unambiguous b/c hours don't go past 24
418    # assert_allclose(ra.degree, 26.570929342)
419    with pytest.raises(u.UnitsError):
420        ra = Longitude("26:34:15.345634")
421
422    # ra = Longitude(68)
423    with pytest.raises(u.UnitsError):
424        ra = Longitude(68)
425
426    with pytest.raises(u.UnitsError):
427        ra = Longitude(12)
428
429    with pytest.raises(ValueError):
430        ra = Longitude("garbage containing a d and no units")
431
432    ra = Longitude("12h43m23s")
433    assert_allclose(ra.hour, 12.7230555556)
434
435    ra = Longitude((56, 14, 52.52), unit=u.degree)      # can accept tuples
436    # TODO: again, fix based on >24 behavior
437    # ra = Longitude((56,14,52.52))
438    with pytest.raises(u.UnitsError):
439        ra = Longitude((56, 14, 52.52))
440    with pytest.raises(u.UnitsError):
441        ra = Longitude((12, 14, 52))  # ambiguous w/o units
442    ra = Longitude((12, 14, 52), unit=u.hour)
443
444    ra = Longitude([56, 64, 52.2], unit=u.degree)  # ...but not arrays (yet)
445
446    # Units can be specified
447    ra = Longitude("4:08:15.162342", unit=u.hour)
448
449    # TODO: this was the "smart" initializer behavior - adjust in 0.3 appropriately
450    # Where Longitude values are commonly found in hours or degrees, declination is
451    # nearly always specified in degrees, so this is the default.
452    # dec = Latitude("-41:08:15.162342")
453    with pytest.raises(u.UnitsError):
454        dec = Latitude("-41:08:15.162342")
455    dec = Latitude("-41:08:15.162342", unit=u.degree)  # same as above
456
457
458def test_negative_zero_dms():
459    # Test for DMS parser
460    a = Angle('-00:00:10', u.deg)
461    assert_allclose(a.degree, -10. / 3600.)
462
463    # Unicode minus
464    a = Angle('−00:00:10', u.deg)
465    assert_allclose(a.degree, -10. / 3600.)
466
467
468def test_negative_zero_dm():
469    # Test for DM parser
470    a = Angle('-00:10', u.deg)
471    assert_allclose(a.degree, -10. / 60.)
472
473
474def test_negative_zero_hms():
475    # Test for HMS parser
476    a = Angle('-00:00:10', u.hour)
477    assert_allclose(a.hour, -10. / 3600.)
478
479
480def test_negative_zero_hm():
481    # Test for HM parser
482    a = Angle('-00:10', u.hour)
483    assert_allclose(a.hour, -10. / 60.)
484
485
486def test_negative_sixty_hm():
487    # Test for HM parser
488    with pytest.warns(IllegalMinuteWarning):
489        a = Angle('-00:60', u.hour)
490    assert_allclose(a.hour, -1.)
491
492
493def test_plus_sixty_hm():
494    # Test for HM parser
495    with pytest.warns(IllegalMinuteWarning):
496        a = Angle('00:60', u.hour)
497    assert_allclose(a.hour, 1.)
498
499
500def test_negative_fifty_nine_sixty_dms():
501    # Test for DMS parser
502    with pytest.warns(IllegalSecondWarning):
503        a = Angle('-00:59:60', u.deg)
504    assert_allclose(a.degree, -1.)
505
506
507def test_plus_fifty_nine_sixty_dms():
508    # Test for DMS parser
509    with pytest.warns(IllegalSecondWarning):
510        a = Angle('+00:59:60', u.deg)
511    assert_allclose(a.degree, 1.)
512
513
514def test_negative_sixty_dms():
515    # Test for DMS parser
516    with pytest.warns(IllegalSecondWarning):
517        a = Angle('-00:00:60', u.deg)
518    assert_allclose(a.degree, -1. / 60.)
519
520
521def test_plus_sixty_dms():
522    # Test for DMS parser
523    with pytest.warns(IllegalSecondWarning):
524        a = Angle('+00:00:60', u.deg)
525    assert_allclose(a.degree, 1. / 60.)
526
527
528def test_angle_to_is_angle():
529    with pytest.warns(IllegalSecondWarning):
530        a = Angle('00:00:60', u.deg)
531    assert isinstance(a, Angle)
532    assert isinstance(a.to(u.rad), Angle)
533
534
535def test_angle_to_quantity():
536    with pytest.warns(IllegalSecondWarning):
537        a = Angle('00:00:60', u.deg)
538    q = u.Quantity(a)
539    assert isinstance(q, u.Quantity)
540    assert q.unit is u.deg
541
542
543def test_quantity_to_angle():
544    a = Angle(1.0*u.deg)
545    assert isinstance(a, Angle)
546    with pytest.raises(u.UnitsError):
547        Angle(1.0*u.meter)
548    a = Angle(1.0*u.hour)
549    assert isinstance(a, Angle)
550    assert a.unit is u.hourangle
551    with pytest.raises(u.UnitsError):
552        Angle(1.0*u.min)
553
554
555def test_angle_string():
556    with pytest.warns(IllegalSecondWarning):
557        a = Angle('00:00:60', u.deg)
558    assert str(a) == '0d01m00s'
559    a = Angle('00:00:59S', u.deg)
560    assert str(a) == '-0d00m59s'
561    a = Angle('00:00:59N', u.deg)
562    assert str(a) == '0d00m59s'
563    a = Angle('00:00:59E', u.deg)
564    assert str(a) == '0d00m59s'
565    a = Angle('00:00:59W', u.deg)
566    assert str(a) == '-0d00m59s'
567    a = Angle('-00:00:10', u.hour)
568    assert str(a) == '-0h00m10s'
569    a = Angle('00:00:59E', u.hour)
570    assert str(a) == '0h00m59s'
571    a = Angle('00:00:59W', u.hour)
572    assert str(a) == '-0h00m59s'
573    a = Angle(3.2, u.radian)
574    assert str(a) == '3.2rad'
575    a = Angle(4.2, u.microarcsecond)
576    assert str(a) == '4.2uarcsec'
577    a = Angle('1.0uarcsec')
578    assert a.value == 1.0
579    assert a.unit == u.microarcsecond
580    a = Angle('1.0uarcsecN')
581    assert a.value == 1.0
582    assert a.unit == u.microarcsecond
583    a = Angle('1.0uarcsecS')
584    assert a.value == -1.0
585    assert a.unit == u.microarcsecond
586    a = Angle('1.0uarcsecE')
587    assert a.value == 1.0
588    assert a.unit == u.microarcsecond
589    a = Angle('1.0uarcsecW')
590    assert a.value == -1.0
591    assert a.unit == u.microarcsecond
592    a = Angle("3d")
593    assert_allclose(a.value, 3.0)
594    assert a.unit == u.degree
595    a = Angle("3dN")
596    assert str(a) == "3d00m00s"
597    assert a.unit == u.degree
598    a = Angle("3dS")
599    assert str(a) == "-3d00m00s"
600    assert a.unit == u.degree
601    a = Angle("3dE")
602    assert str(a) == "3d00m00s"
603    assert a.unit == u.degree
604    a = Angle("3dW")
605    assert str(a) == "-3d00m00s"
606    assert a.unit == u.degree
607    a = Angle('10"')
608    assert_allclose(a.value, 10.0)
609    assert a.unit == u.arcsecond
610    a = Angle("10'N")
611    assert_allclose(a.value, 10.0)
612    assert a.unit == u.arcminute
613    a = Angle("10'S")
614    assert_allclose(a.value, -10.0)
615    assert a.unit == u.arcminute
616    a = Angle("10'E")
617    assert_allclose(a.value, 10.0)
618    assert a.unit == u.arcminute
619    a = Angle("10'W")
620    assert_allclose(a.value, -10.0)
621    assert a.unit == u.arcminute
622    a = Angle('45°55′12″N')
623    assert str(a) == '45d55m12s'
624    assert_allclose(a.value, 45.92)
625    assert a.unit == u.deg
626    a = Angle('45°55′12″S')
627    assert str(a) == '-45d55m12s'
628    assert_allclose(a.value, -45.92)
629    assert a.unit == u.deg
630    a = Angle('45°55′12″E')
631    assert str(a) == '45d55m12s'
632    assert_allclose(a.value, 45.92)
633    assert a.unit == u.deg
634    a = Angle('45°55′12″W')
635    assert str(a) == '-45d55m12s'
636    assert_allclose(a.value, -45.92)
637    assert a.unit == u.deg
638    with pytest.raises(ValueError):
639        Angle('00h00m10sN')
640    with pytest.raises(ValueError):
641        Angle('45°55′12″NS')
642
643
644def test_angle_repr():
645    assert 'Angle' in repr(Angle(0, u.deg))
646    assert 'Longitude' in repr(Longitude(0, u.deg))
647    assert 'Latitude' in repr(Latitude(0, u.deg))
648
649    a = Angle(0, u.deg)
650    repr(a)
651
652
653def test_large_angle_representation():
654    """Test that angles above 360 degrees can be output as strings,
655    in repr, str, and to_string.  (regression test for #1413)"""
656    a = Angle(350, u.deg) + Angle(350, u.deg)
657    a.to_string()
658    a.to_string(u.hourangle)
659    repr(a)
660    repr(a.to(u.hourangle))
661    str(a)
662    str(a.to(u.hourangle))
663
664
665def test_wrap_at_inplace():
666    a = Angle([-20, 150, 350, 360] * u.deg)
667    out = a.wrap_at('180d', inplace=True)
668    assert out is None
669    assert np.all(a.degree == np.array([-20., 150., -10., 0.]))
670
671
672def test_latitude():
673    with pytest.raises(ValueError):
674        lat = Latitude(['91d', '89d'])
675    with pytest.raises(ValueError):
676        lat = Latitude('-91d')
677
678    lat = Latitude(['90d', '89d'])
679    # check that one can get items
680    assert lat[0] == 90 * u.deg
681    assert lat[1] == 89 * u.deg
682    # and that comparison with angles works
683    assert np.all(lat == Angle(['90d', '89d']))
684    # check setitem works
685    lat[1] = 45. * u.deg
686    assert np.all(lat == Angle(['90d', '45d']))
687    # but not with values out of range
688    with pytest.raises(ValueError):
689        lat[0] = 90.001 * u.deg
690    with pytest.raises(ValueError):
691        lat[0] = -90.001 * u.deg
692    # these should also not destroy input (#1851)
693    assert np.all(lat == Angle(['90d', '45d']))
694
695    # conserve type on unit change (closes #1423)
696    angle = lat.to('radian')
697    assert type(angle) is Latitude
698    # but not on calculations
699    angle = lat - 190 * u.deg
700    assert type(angle) is Angle
701    assert angle[0] == -100 * u.deg
702
703    lat = Latitude('80d')
704    angle = lat / 2.
705    assert type(angle) is Angle
706    assert angle == 40 * u.deg
707
708    angle = lat * 2.
709    assert type(angle) is Angle
710    assert angle == 160 * u.deg
711
712    angle = -lat
713    assert type(angle) is Angle
714    assert angle == -80 * u.deg
715
716    # Test errors when trying to interoperate with longitudes.
717    with pytest.raises(TypeError) as excinfo:
718        lon = Longitude(10, 'deg')
719        lat = Latitude(lon)
720    assert "A Latitude angle cannot be created from a Longitude angle" in str(excinfo.value)
721
722    with pytest.raises(TypeError) as excinfo:
723        lon = Longitude(10, 'deg')
724        lat = Latitude([20], 'deg')
725        lat[0] = lon
726    assert "A Longitude angle cannot be assigned to a Latitude angle" in str(excinfo.value)
727
728    # Check we can work around the Lat vs Long checks by casting explicitly to Angle.
729    lon = Longitude(10, 'deg')
730    lat = Latitude(Angle(lon))
731    assert lat.value == 10.0
732    # Check setitem.
733    lon = Longitude(10, 'deg')
734    lat = Latitude([20], 'deg')
735    lat[0] = Angle(lon)
736    assert lat.value[0] == 10.0
737
738
739def test_longitude():
740    # Default wrapping at 360d with an array input
741    lon = Longitude(['370d', '88d'])
742    assert np.all(lon == Longitude(['10d', '88d']))
743    assert np.all(lon == Angle(['10d', '88d']))
744
745    # conserve type on unit change and keep wrap_angle (closes #1423)
746    angle = lon.to('hourangle')
747    assert type(angle) is Longitude
748    assert angle.wrap_angle == lon.wrap_angle
749    angle = lon[0]
750    assert type(angle) is Longitude
751    assert angle.wrap_angle == lon.wrap_angle
752    angle = lon[1:]
753    assert type(angle) is Longitude
754    assert angle.wrap_angle == lon.wrap_angle
755
756    # but not on calculations
757    angle = lon / 2.
758    assert np.all(angle == Angle(['5d', '44d']))
759    assert type(angle) is Angle
760    assert not hasattr(angle, 'wrap_angle')
761
762    angle = lon * 2. + 400 * u.deg
763    assert np.all(angle == Angle(['420d', '576d']))
764    assert type(angle) is Angle
765
766    # Test setting a mutable value and having it wrap
767    lon[1] = -10 * u.deg
768    assert np.all(lon == Angle(['10d', '350d']))
769
770    # Test wrapping and try hitting some edge cases
771    lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian)
772    assert np.all(lon.degree == np.array([0., 90, 180, 270, 0]))
773
774    lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian, wrap_angle='180d')
775    assert np.all(lon.degree == np.array([0., 90, -180, -90, 0]))
776
777    # Wrap on setting wrap_angle property (also test auto-conversion of wrap_angle to an Angle)
778    lon = Longitude(np.array([0, 0.5, 1.0, 1.5, 2.0]) * np.pi, unit=u.radian)
779    lon.wrap_angle = '180d'
780    assert np.all(lon.degree == np.array([0., 90, -180, -90, 0]))
781
782    lon = Longitude('460d')
783    assert lon == Angle('100d')
784    lon.wrap_angle = '90d'
785    assert lon == Angle('-260d')
786
787    # check that if we initialize a longitude with another longitude,
788    # wrap_angle is kept by default
789    lon2 = Longitude(lon)
790    assert lon2.wrap_angle == lon.wrap_angle
791    # but not if we explicitly set it
792    lon3 = Longitude(lon, wrap_angle='180d')
793    assert lon3.wrap_angle == 180 * u.deg
794
795    # check that wrap_angle is always an Angle
796    lon = Longitude(lon, wrap_angle=Longitude(180 * u.deg))
797    assert lon.wrap_angle == 180 * u.deg
798    assert lon.wrap_angle.__class__ is Angle
799
800    # check that wrap_angle is not copied
801    wrap_angle=180 * u.deg
802    lon = Longitude(lon, wrap_angle=wrap_angle)
803    assert lon.wrap_angle == 180 * u.deg
804    assert np.may_share_memory(lon.wrap_angle, wrap_angle)
805
806    # check for problem reported in #2037 about Longitude initializing to -0
807    lon = Longitude(0, u.deg)
808    lonstr = lon.to_string()
809    assert not lonstr.startswith('-')
810
811    # also make sure dtype is correctly conserved
812    assert Longitude(0, u.deg, dtype=float).dtype == np.dtype(float)
813    assert Longitude(0, u.deg, dtype=int).dtype == np.dtype(int)
814
815    # Test errors when trying to interoperate with latitudes.
816    with pytest.raises(TypeError) as excinfo:
817        lat = Latitude(10, 'deg')
818        lon = Longitude(lat)
819    assert "A Longitude angle cannot be created from a Latitude angle" in str(excinfo.value)
820
821    with pytest.raises(TypeError) as excinfo:
822        lat = Latitude(10, 'deg')
823        lon = Longitude([20], 'deg')
824        lon[0] = lat
825    assert "A Latitude angle cannot be assigned to a Longitude angle" in str(excinfo.value)
826
827    # Check we can work around the Lat vs Long checks by casting explicitly to Angle.
828    lat = Latitude(10, 'deg')
829    lon = Longitude(Angle(lat))
830    assert lon.value == 10.0
831    # Check setitem.
832    lat = Latitude(10, 'deg')
833    lon = Longitude([20], 'deg')
834    lon[0] = Angle(lat)
835    assert lon.value[0] == 10.0
836
837
838def test_wrap_at():
839    a = Angle([-20, 150, 350, 360] * u.deg)
840    assert np.all(a.wrap_at(360 * u.deg).degree == np.array([340., 150., 350., 0.]))
841    assert np.all(a.wrap_at(Angle(360, unit=u.deg)).degree == np.array([340., 150., 350., 0.]))
842    assert np.all(a.wrap_at('360d').degree == np.array([340., 150., 350., 0.]))
843    assert np.all(a.wrap_at('180d').degree == np.array([-20., 150., -10., 0.]))
844    assert np.all(a.wrap_at(np.pi * u.rad).degree == np.array([-20., 150., -10., 0.]))
845
846    # Test wrapping a scalar Angle
847    a = Angle('190d')
848    assert a.wrap_at('180d') == Angle('-170d')
849
850    a = Angle(np.arange(-1000.0, 1000.0, 0.125), unit=u.deg)
851    for wrap_angle in (270, 0.2, 0.0, 360.0, 500, -2000.125):
852        aw = a.wrap_at(wrap_angle * u.deg)
853        assert np.all(aw.degree >= wrap_angle - 360.0)
854        assert np.all(aw.degree < wrap_angle)
855
856        aw = a.to(u.rad).wrap_at(wrap_angle * u.deg)
857        assert np.all(aw.degree >= wrap_angle - 360.0)
858        assert np.all(aw.degree < wrap_angle)
859
860
861def test_is_within_bounds():
862    a = Angle([-20, 150, 350] * u.deg)
863    assert a.is_within_bounds('0d', '360d') is False
864    assert a.is_within_bounds(None, '360d') is True
865    assert a.is_within_bounds(-30 * u.deg, None) is True
866
867    a = Angle('-20d')
868    assert a.is_within_bounds('0d', '360d') is False
869    assert a.is_within_bounds(None, '360d') is True
870    assert a.is_within_bounds(-30 * u.deg, None) is True
871
872
873def test_angle_mismatched_unit():
874    a = Angle('+6h7m8s', unit=u.degree)
875    assert_allclose(a.value, 91.78333333333332)
876
877
878def test_regression_formatting_negative():
879    # Regression test for a bug that caused:
880    #
881    # >>> Angle(-1., unit='deg').to_string()
882    # '-1d00m-0s'
883    assert Angle(-0., unit='deg').to_string() == '-0d00m00s'
884    assert Angle(-1., unit='deg').to_string() == '-1d00m00s'
885    assert Angle(-0., unit='hour').to_string() == '-0h00m00s'
886    assert Angle(-1., unit='hour').to_string() == '-1h00m00s'
887
888
889def test_regression_formatting_default_precision():
890    # Regression test for issue #11140
891    assert Angle('10:20:30.12345678d').to_string() == '10d20m30.12345678s'
892    assert Angle('10d20m30.123456784564s').to_string() == '10d20m30.12345678s'
893    assert Angle('10d20m30.123s').to_string() == '10d20m30.123s'
894
895
896def test_empty_sep():
897    a = Angle('05h04m31.93830s')
898
899    assert a.to_string(sep='', precision=2, pad=True) == '050431.94'
900
901
902def test_create_tuple():
903    """
904    Tests creation of an angle with a (d,m,s) or (h,m,s) tuple
905    """
906    a1 = Angle((1, 30, 0), unit=u.degree)
907    assert a1.value == 1.5
908
909    a1 = Angle((1, 30, 0), unit=u.hourangle)
910    assert a1.value == 1.5
911
912
913def test_list_of_quantities():
914    a1 = Angle([1*u.deg, 1*u.hourangle])
915    assert a1.unit == u.deg
916    assert_allclose(a1.value, [1, 15])
917
918    a2 = Angle([1*u.hourangle, 1*u.deg], u.deg)
919    assert a2.unit == u.deg
920    assert_allclose(a2.value, [15, 1])
921
922
923def test_multiply_divide():
924    # Issue #2273
925    a1 = Angle([1, 2, 3], u.deg)
926    a2 = Angle([4, 5, 6], u.deg)
927    a3 = a1 * a2
928    assert_allclose(a3.value, [4, 10, 18])
929    assert a3.unit == (u.deg * u.deg)
930
931    a3 = a1 / a2
932    assert_allclose(a3.value, [.25, .4, .5])
933    assert a3.unit == u.dimensionless_unscaled
934
935
936def test_mixed_string_and_quantity():
937    a1 = Angle(['1d', 1. * u.deg])
938    assert_array_equal(a1.value, [1., 1.])
939    assert a1.unit == u.deg
940
941    a2 = Angle(['1d', 1 * u.rad * np.pi, '3d'])
942    assert_array_equal(a2.value, [1., 180., 3.])
943    assert a2.unit == u.deg
944
945
946def test_array_angle_tostring():
947    aobj = Angle([1, 2], u.deg)
948    assert aobj.to_string().dtype.kind == 'U'
949    assert np.all(aobj.to_string() == ['1d00m00s', '2d00m00s'])
950
951
952def test_wrap_at_without_new():
953    """
954    Regression test for subtle bugs from situations where an Angle is
955    created via numpy channels that don't do the standard __new__ but instead
956    depend on array_finalize to set state.  Longitude is used because the
957    bug was in its _wrap_angle not getting initialized correctly
958    """
959    l1 = Longitude([1]*u.deg)
960    l2 = Longitude([2]*u.deg)
961
962    l = np.concatenate([l1, l2])
963    assert l._wrap_angle is not None
964
965
966def test__str__():
967    """
968    Check the __str__ method used in printing the Angle
969    """
970
971    # scalar angle
972    scangle = Angle('10.2345d')
973    strscangle = scangle.__str__()
974    assert strscangle == '10d14m04.2s'
975
976    # non-scalar array angles
977    arrangle = Angle(['10.2345d', '-20d'])
978    strarrangle = arrangle.__str__()
979
980    assert strarrangle == '[10d14m04.2s -20d00m00s]'
981
982    # summarizing for large arrays, ... should appear
983    bigarrangle = Angle(np.ones(10000), u.deg)
984    assert '...' in bigarrangle.__str__()
985
986
987def test_repr_latex():
988    """
989    Check the _repr_latex_ method, used primarily by IPython notebooks
990    """
991
992    # try with both scalar
993    scangle = Angle(2.1, u.deg)
994    rlscangle = scangle._repr_latex_()
995
996    # and array angles
997    arrangle = Angle([1, 2.1], u.deg)
998    rlarrangle = arrangle._repr_latex_()
999
1000    assert rlscangle == r'$2^\circ06{}^\prime00{}^{\prime\prime}$'
1001    assert rlscangle.split('$')[1] in rlarrangle
1002
1003    # make sure the ... appears for large arrays
1004    bigarrangle = Angle(np.ones(50000)/50000., u.deg)
1005    assert '...' in bigarrangle._repr_latex_()
1006
1007
1008def test_angle_with_cds_units_enabled():
1009    """Regression test for #5350
1010
1011    Especially the example in
1012    https://github.com/astropy/astropy/issues/5350#issuecomment-248770151
1013    """
1014    from astropy.units import cds
1015    # the problem is with the parser, so remove it temporarily
1016    from astropy.coordinates.angle_formats import _AngleParser
1017    del _AngleParser._thread_local._parser
1018    with cds.enable():
1019        Angle('5d')
1020    del _AngleParser._thread_local._parser
1021    Angle('5d')
1022
1023
1024def test_longitude_nan():
1025    # Check that passing a NaN to Longitude doesn't raise a warning
1026    Longitude([0, np.nan, 1] * u.deg)
1027
1028
1029def test_latitude_nan():
1030    # Check that passing a NaN to Latitude doesn't raise a warning
1031    Latitude([0, np.nan, 1] * u.deg)
1032
1033
1034def test_angle_wrap_at_nan():
1035    # Check that no attempt is made to wrap a NaN angle
1036    angle = Angle([0, np.nan, 1] * u.deg)
1037    angle.flags.writeable = False  # to force an error if a write is attempted
1038    angle.wrap_at(180*u.deg, inplace=True)
1039
1040
1041def test_angle_multithreading():
1042    """
1043    Regression test for issue #7168
1044    """
1045    angles = ['00:00:00']*10000
1046
1047    def parse_test(i=0):
1048        Angle(angles, unit='hour')
1049    for i in range(10):
1050        threading.Thread(target=parse_test, args=(i,)).start()
1051
1052
1053@pytest.mark.parametrize("cls", [Angle, Longitude, Latitude])
1054@pytest.mark.parametrize("input, expstr, exprepr",
1055                         [(np.nan*u.deg,
1056                           "nan",
1057                           "nan deg"),
1058                          ([np.nan, 5, 0]*u.deg,
1059                           "[nan 5d00m00s 0d00m00s]",
1060                           "[nan, 5., 0.] deg"),
1061                          ([6, np.nan, 0]*u.deg,
1062                           "[6d00m00s nan 0d00m00s]",
1063                           "[6., nan, 0.] deg"),
1064                          ([np.nan, np.nan, np.nan]*u.deg,
1065                           "[nan nan nan]",
1066                           "[nan, nan, nan] deg"),
1067                          (np.nan*u.hour,
1068                           "nan",
1069                           "nan hourangle"),
1070                          ([np.nan, 5, 0]*u.hour,
1071                           "[nan 5h00m00s 0h00m00s]",
1072                           "[nan, 5., 0.] hourangle"),
1073                          ([6, np.nan, 0]*u.hour,
1074                           "[6h00m00s nan 0h00m00s]",
1075                           "[6., nan, 0.] hourangle"),
1076                          ([np.nan, np.nan, np.nan]*u.hour,
1077                           "[nan nan nan]",
1078                           "[nan, nan, nan] hourangle"),
1079                          (np.nan*u.rad,
1080                           "nan",
1081                           "nan rad"),
1082                          ([np.nan, 1, 0]*u.rad,
1083                           "[nan 1rad 0rad]",
1084                           "[nan, 1., 0.] rad"),
1085                          ([1.50, np.nan, 0]*u.rad,
1086                           "[1.5rad nan 0rad]",
1087                           "[1.5, nan, 0.] rad"),
1088                          ([np.nan, np.nan, np.nan]*u.rad,
1089                           "[nan nan nan]",
1090                           "[nan, nan, nan] rad")])
1091def test_str_repr_angles_nan(cls, input, expstr, exprepr):
1092    """
1093    Regression test for issue #11473
1094    """
1095    q = cls(input)
1096    assert str(q) == expstr
1097    # Deleting whitespaces since repr appears to be adding them for some values
1098    # making the test fail.
1099    assert repr(q).replace(" ", "") == f'<{cls.__name__}{exprepr}>'.replace(" ","")
1100