sunpy-1.0.3/0000755000175100001650000000000013531722607013167 5ustar vstsdocker00000000000000sunpy-1.0.3/CHANGELOG.rst0000644000175100001650000020027013531722525015210 0ustar vstsdocker00000000000000Sunpy v1.0.3 (2019-08-29) ========================= Features -------- - Add ability to disable progressbars when dowloading files using `sunpy.net.helioviewer.py` and edited docstrings to mention this feature. (`#3280 `__) Bug Fixes --------- - Fixed the handling of coordinates with velocity information when transforming between Astropy frames and SunPy frames. (`#3247 `__) - Fixed all coordinate transformations to properly handle a change in observation time. (`#3247 `__) - Fixed `~sunpy.physics.solar_rotation.calculate_solar_rotate_shift` so that it does not calculate a shift between the reference layer and itself, which would sometimes incorrectly result in a shift of a pixel due to numerical precision. (`#3255 `__) - Stop crash when ``LineAnimator`` ``axes_ranges`` entry given as ``1D`` array when data is ``>1D``, i.e. as an independent axis. (`#3283 `__) - Fixed a bug where the transformation from `~sunpy.coordinates.frames.Helioprojective` to `~sunpy.coordinates.frames.Heliocentric` used the Sun-observer distance from the wrong frame when shifting the origin, and thus might not give the correct answer if the observer was not the same for the two frames. (`#3291 `__) - Fixed a bug with the transformations between `~sunpy.coordinates.frames.Heliocentric` and `~sunpy.coordinates.frames.HeliographicStonyhurst` when the frame observation time was not the same as the observer observation time. The most common way to encounter this bug was when transforming from `~sunpy.coordinates.frames.Helioprojective` to any non-observer-based frame while also changing the observation time. (`#3291 `__) - Fixed a `sunpy.coordinates` bug where a frame using the default observer of Earth could have its observer overwritten during a transformation. (`#3291 `__) - VSO client `fetch` should not download when `wait` keyword argument is specified. (`#3298 `__) - Fixed a bug with `~sunpy.coordinates.wcs_utils.solar_frame_to_wcs_mapping` that assumed that the supplied frame was a SunPy frame. (`#3305 `__) - Fixed bugs with `~sunpy.coordinates.wcs_utils.solar_frame_to_wcs_mapping` if the input frame does not include an observation time or an observer. (`#3305 `__) Improved Documentation ---------------------- - Added more details to docstrings in `sunpy.coordinates.frames`. (`#3262 `__) - Added a link to package maintainer list in the API Stability page. (`#3281 `__) Trivial/Internal Changes ------------------------ - Allow running our sphinx-gallery examples as Jupyter notebooks via Binder (`#3256 `__) Sunpy v1.0.2 (2019-06-26) ========================= Bug Fixes --------- - `sunpy.map.sources.AIAMap` and `sunpy.map.sources.HMIMap` will no longer assume the existance of certain header keys. (`#3217 `__) - `sunpy.map.make_fitswcs_header` now supports specifying the map projection rather than defaulting to ``TAN``. (`#3218 `__) - Fix the behaviour of `sunpy.coordinates.frames.Helioprojective.calculate_distance` if the representation isn't Spherical. (`#3219 `__) - Fixed a bug where the longitude of a coordinate would not wrap at the expected angle following a frame transformation. (`#3223 `__) - Fixed a bug where passing a time or time interval to the differential rotation function threw an error because the new observer was not in HGS. (`#3225 `__) - Fixed bug where `~sunpy.coordinates.ephemeris.get_horizons_coord` was unable to accept `~astropy.time.Time` arrays as input. (`#3227 `__) - Fix the ticks on the default heliographic grid overlay so they are not white (and normally invisible) by default. (`#3235 `__) - Fixed a bug with `sunpy.net.hek.HEKClient` when the results returned were a mixed dataset. (`#3240 `__) - Fix `sunpy.physics.differential_rotation.differential_rotate` to rotate in the correct direction and to account for the rotation of the heliographic coordinate frame with time. (`#3245 `__) - Fixed a bug with the handling of changing observation times for transformations between `~astropy.coordinates.HCRS` and `~sunpy.coordinates.frames.HeliographicStonyhurst`, which also indirectly affected other transformations when changing observation times. (`#3246 `__) Improved Documentation ---------------------- - Cleaned and expanded upon the docstrings for each Fido Client. (`#3220 `__) - Added clarifying hyperlinks to the gallery example `getting_lasco_observer_location` to link to `astroquery` docs page. (`#3228 `__) Sunpy v1.0.1 (2019-06-07) ========================= Bug Fixes --------- - Fixed accuracy issues with the calculations of Carrington longitude (`~sunpy.coordinates.sun.L0`) and Carrington rotation number (`~sunpy.coordinates.sun.carrington_rotation_number`). (`#3178 `__) - Updated `sunpy.map.header_helper.make_fitswcs_header` to be more strict on the inputs it accepts. (`#3183 `__) - Fix the calculation of ``rsun_ref`` in `~sunpy.map.make_fitswcs_header` and and ensure that the default reference pixel is indexed from 1. (`#3184 `__) - Fixed the missing transformation between two `~sunpy.coordinates.HeliographicCarrington` frames with different observation times. (`#3186 `__) Improved Documentation ---------------------- - Clean up the docstring for `sunpy.physics.differential_rotation.solar_rotate_coordinate` to make the example clearer. (`#2708 `__) - Added new gallery examples and cleaned up various gallery examples. (`#3181 `__) Sunpy 1.0.0 (2019-06-01) ======================== Backwards Incompatible Changes ------------------------------ - Move the matplotlib animators from ``sunpy.visualisation.imageanimator`` and ``sunpy.visualization.mapcubeanimator`` to `sunpy.visualization.animator`. (`#2515 `__) - Make `sunpy.time.parse_time` return `astropy.time.Time` instead of `datetime.datetime`. (`#2611 `__) - The properties and methods of `sunpy.time.TimeRange` returns `astropy.time.Time` and `astropy.time.TimeDelta` instead of `datetime.datetime` and `datetime.timedelta` respectively. (`#2638 `__) - The `sunpy.instr.goes` module now accepts and returns `sunpy.timeseries.XRSTimeSeries` objects only. (`#2666 `__) - ``obstime`` keyword param of ``sunpy.instr.goes._goes_lx`` takes a non-scalar `astropy.time.Time` object instead of `numpy.ndarray`. The precision of times contained in `sunpy.timeseries` has been increased to 9 from 6. (`#2676 `__) - Removed ``sunpy.net.jsoc.attrs.Time`` because it served the same purpose as `sunpy.net.attrs.Time` after the switch to `astropy.time.Time`. (`#2694 `__) - Remove unused ``**kwargs`` within TimeSeries functions. (`#2717 `__) - Rotation matrices inside map objects were previously stored as numpy matrices, but are now stored as numpy arrays, as numpy will eventually remove their matrix datatype. See https://docs.scipy.org/doc/numpy/user/numpy-for-matlab-users.html for more information. (`#2719 `__) - The `sunpy.cm.show_colormaps` function now accepts the keyword 'search' instead of 'filter'. (`#2731 `__) - The keyword arguments to all client ``.fetch`` methods have been changed to support the new parfive downloader and to ensure consisteny across all Fido clients. (`#2797 `__) - The Helioviewer client has been switched to using the newer Helioviewer API. This has meant that we have changed some of the keywords that were passed into client's methods. We have enforced that several keywords (observatory,instrument,detector,measurement) need to be defined otherwise the functions cannot return any data. (`#2801 `__) - Maps no longer assume that the pixel units are arcseconds if the units aren't explicitly set. In addition to this if critical metadata is missing from when creating a map, the map will fail to initialize and will raise an error. (`#2847 `__) - axis_ranges kwarg of `sunpy.visualization.animator.base.ArrayAnimator`, `sunpy.visualization.animator.image.ImageAnimator` and `sunpy.visualization.animator.line.LineAnimator` now must be entered as None, [min, max] or pixel edges of each array element. Previously, pixel centers were expected. This change removes ambiguity in interpretation and ensures the extent of the plot can always be accurately derived. (`#2867 `__) - All keywords have been added (with defaults) to each `~sunpy.net.helioviewer.HelioviewerClient` function. This means that there will be some changes to the style of the PNG screenshot that is returned. Returns for the JPEG 2000 and the other functions should be the same but not guaranteed. (`#2883 `__) - Changed `sunpy.sun.models.interior` and `sunpy.sun.models.evolution` from `pandas.DataFrame` to `astropy.table.QTable` (`#2936 `__) - Minimum numpy version is now >=1.14.5 (`#2954 `__) - Removed ``sunpy.time.julian_day``, ``sunpy.time.julian_centuries``, ``sunpy.time.day_of_year``, ``sunpy.time.break_time``, ``sunpy.time.get_day``. (`#2999 `__) - Updated the solar values in `sunpy.sun.constants` to IAU 2015 values. (`#3001 `__) - Renamed `eccentricity_sunearth_orbit` to `eccentricity_sun_earth_orbit`. (`#3001 `__) - Renamed ``sunpy.image.rescale`` to `sunpy.image.resample`. (`#3044 `__) - Remove the ``basic_plot`` keyword argument from `~sunpy.map.Map.GenericMap.peek`. An example has been added to the gallery showing how to make a plot like this. (`#3109 `__) - `sunpy.map.GenericMap` will no longer use the key `solar_b0` as a value for heliographic latitude. (`#3115 `__) - `sunpy.map.GenericMap` now checks for a complete observer location rather than individually defaulting coordinates (lat, lon, distance) to Earth position. If any one of the three coordinates is missing from the header the observer will be defaulted to Earth and a warning raised. (`#3115 `__) - `sunpy.sun.sun` functions have been re-implemented using Astropy for significantly improved accuracy. Some functions have been removed. (`#3137 `__) - All of the functions in `sunpy.sun.sun` and all of the Sun-specific functions in `sunpy.coordinates.ephemeris` have been moved to the new module `sunpy.coordinates.sun`. (`#3163 `__) Deprecations and Removals ------------------------- - The deprecated ``sunpy.lightcurve``, ``sunpy.wcs`` and ``sunpy.spectra`` modules have now been removed. (`#2666 `__) - ``sunpy.instr.rhessi.get_obssumm_dbase_file`` ``sunpy.instr.rhessi.get_obssum_filename``, ``sunpy.instr.rhessi.get_obssumm_file`` have been removed. `Fido ` should be used to download these files. (`#2808 `__) - Removed ``heliographic_solar_center`` in favour of `~sunpy.coordinates.ephemeris.get_sun_L0` and `~sunpy.coordinates.ephemeris.get_sun_B0` (`#2830 `__) - Removed ``GenericClient.query`` in favour of `sunpy.net.dataretriever.GenericClient.search` (`#2830 `__) - Removed ``sunearth_distance`` in favour of ``get_sunearth_distance`` (`#2830 `__) - Removed ``remove_lytaf_events_from_lightcurve`` in favour of `sunpy.instr.lyra.remove_lytaf_events_from_timeseries` (`#2830 `__) - Removed ``sunpy.cm.get_cmap`` in favour of ``plt.get_cmap`` (`#2830 `__) - Removed ``database.query`` in favour of `sunpy.database.Database.search` (`#2830 `__) - Removed ``sunpy.net.vso.InteractiveVSOClient`` (`#2830 `__) - Removed ``MapCube`` in favour of `~sunpy.map.MapSequence` (`#2830 `__) - Removed ``solar_north`` in favour of ``get_sun_P`` (`#2830 `__) - Removed ``database.download`` in favour of `sunpy.database.Database.fetch` (`#2830 `__) - Removed ``sunpy.map.GenericMap.pixel_to_data`` in favour of `sunpy.map.GenericMap.pixel_to_world` (`#2830 `__) - Removed ``GenericClient.get`` in favour of `sunpy.net.dataretriever.GenericClient.fetch`. This changes applies to the other clients as well. (`#2830 `__) - Removed `Map.xrange` and `Map.yrange` (`#2830 `__) - Removed ``sunpy.net.attrs.Wave`` in favour of `a.Wavelength <~sunpy.net.vso.attrs.Wavelength>` (`#2830 `__) - Removed ``JSOCClient.check_request`` in favour of `drms.ExportRequest.status` (`#2830 `__) - `sunpy.net.vso.VSOClient.query_legacy` and `sunpy.net.vso.VSOClient.latest` have been deprecated as we strongly recommend people use `sunpy.net.Fido` for all queries. (`#2866 `__) - The deprecated ``sunpy.physics.transforms`` module has been removed, it is replaced by `sunpy.physics.solar_rotation` and `sunpy.physics.differential_rotation`. (`#2994 `__) - Removed `~sunpy.sun.sun.solar_cycle_number` because it was fundamentally flawed (`#3150 `__) Features -------- - Change arguments to `sunpy.test` from ``offline=`` and ``online=`` to ``online`` and ``online_only``. This matches the behavior of the figure keyword arguments and comes as a part of a move to using a modified version of the Astropy test runner. (`#1983 `__) - asdf schemas and tags were added for the SunPy coordinate frames and `~sunpy.map.GenericMap` allowing these objects to be saved to and restored from `asdf `__ files. (`#2366 `__) - The images from image tests are now saved in a local folder for easy access. (`#2507 `__) - ``sunpy.map.MapCube`` has been renamed to `sunpy.map.MapSequence` to better reflect its use as a collection of map objects. (`#2603 `__) - Net search attributes now support tab completion of values and display a table of possible values when printed, to allow easier discoverability of possible search values. (`#2663 `__) - Running the figure tests now creates a page showing the differences between the expected figures and the figures produced from running the tests. (`#2681 `__) - Add support for Dask arrays in `sunpy.map.Map`. The map factory now checks a whitelist of array types rather than strictly checking if the array is of type `numpy.ndarray`. (`#2689 `__) - Persist the name of a coordinate, i.e. "earth" even though a concrete coordinate object has been calculated and use this string representation to change the way the sunpy frames are printed. This is primarily to facilitate displaying the name of the body rather than the concrete coordinate when printing a `~astropy.coordinates.SkyCoord`. (`#2723 `__) - `~sunpy.net.hek.HEKClient.search` now returns an `astropy.table.Table` instead of list of a `dict`. (`#2759 `__) - Add a downscaled HMI image to the sample data. (`#2782 `__) - Now able to create a `sunpy.map.Map` using an array and a `astropy.wcs.WCS` object. (`#2793 `__) - The download manager for `Fido.fetch ` has been replaced with `parfive `__. This provides advanced progress bars, proper handling of overwriting and the ability to retry failed downloads. (`#2797 `__) - `sunpy.map.GenericMap` can now save out rice compressed FITS files. (`#2826 `__) - Now any SunPyDeprecationWarnings will cause an error when using pytest. (`#2830 `__) - Added full Tox support for SunPy tests, documentation build and figure tests. (`#2839 `__) - Transition the `sunpy.net.vso.VSOClient` from using suds to `zeep `__ as the SOAP library. This is a more actively maintained library, and should provide better support for the VSOs https endpoints. This change should have no effect on the public API of the `sunpy.net.vso.VSOClient`. (`#2866 `__) - Provided access to the Helioviewer header information using `~sunpy.net.helioviewer.HelioviewerClient.get_jp2_header` function. (`#2904 `__) - Add a new WSDL URL and port to support SunPy use of VSO instance at SDAC. (`#2912 `__) - Add support for COSMO K-Coronograph (KCOR) FITS data. (`#2916 `__) - Add logger messaging system based on `~astropy.logger.AstropyLogger`, cleaned up all warnings, removed all print statements. (`#2980 `__) - The function `sunpy.image.coalignment.get_correlation_shifts` now issues an error when the number of dimensions are not correct instead of a warning and returning None. (`#2980 `__) - The default location of the sunpy sample data has changed to be in the platform specific data directory as provided by `appdirs `__. (`#2993 `__) - Add timeseries support for EVE/ESP level 1 data in `sunpy.timeseries.sources.eve` (`#3032 `__) - The default style for Map plots have changed to reflect the changes in Astropy 3.2. (`#3054 `__) - `sunpy.coordinates.ephemeris.get_body_heliographic_stonyhurst` can now account for light travel time when computing the (apparent) body position, as long as the observer location is provided. (`#3055 `__) - Added a helper function (`sunpy.map.make_fitswcs_header`) that allows users to create a meta header for custom created `sunpy.map.GenericMap`. (`#3083 `__) - Map plotting now accepts the optional keyword `clip_interval` for specifying a percentile interval for clipping. For example, if the interval (5%, 99%) is specified, the bounds of the z axis are chosen such that the lowest 5% of pixels and the highest 1% of pixels are excluded. (`#3100 `__) - The new function `~sunpy.coordinates.get_horizons_coord` enables querying JPL HORIZONS for the locations of a wide range of solar-system bodies, including spacecraft. (`#3113 `__) Bug Fixes --------- - Fix the bug that prevented VSO queries for HMI data from downloading file without specifying ``a.Physobs``. (`#2621 `__) - Fix `sunpy.map.mapcube.MapCube.plot`. The code had not been updated to support the changes to the wcsaxes helper functions. (`#2627 `__) - Replace all use of the deprecated ``sunpy.cm.get_cmap`` with `matplotlib.pyplot.get_cmap` to prevent deprecation warnings being raised. (`#2635 `__) - Fix generation of the coordinate transformation graph with Astropy 3.1.dev (`#2636 `__) - Prevent helioviewer from erroring when downloading file to a directory that does not exist. It will now create the directory when required. (`#2642 `__) - Fix transformations into/out of Heliographic Stonyhurst frame when the coordinate representation is Cartesian. (`#2646 `__) - Running the figure tests with ``setup.py test`` now saves the figures and the hashes to the same directory as setup.py. (`#2658 `__) - `sunpy.instr.fermi.met_to_utc` now returns the correct utc time which takes into account the leap seconds that have passed. (`#2679 `__) - Support passing Python file objects to `sunpy.io.fits.write`. (`#2688 `__) - Added DRMS to setup.py so sunpy[all] installs it as a dependancy. (`#2693 `__) - Fix eve 0cs timeseries seperator regex to support Python 3.7 (`#2697 `__) - Fix the bug which crashes `~sunpy.map.sources.LASCOMap` for when 'date-obs' is reformatted agian from a self applied function. (`#2700 `__) - Change all instances of quantity_allclose to `astropy.units.allclose` this prevents pytest being needed to import `sunpy.coordinates` on Astropy 3 (`#2701 `__) - Fix RHESSI obssum file downloading to include the final day in the time range. (`#2714 `__) - Raise an error when transforming between HPC and HCC frames if the observer is not the same. (`#2725 `__) - Replaces the existing LASCO C2 and C3 color maps with new ones that perform better with JP2 and Level 0.5, 1 data. (`#2731 `__) - Do not attempt to save a FITS header comment for a keyword which is not in the header. This prevents an error on saving some maps after the metadata had been modified but not the comments. (`#2748 `__) - Add support for `~sunpy.map.sources.HMIMap` objects as input to `sunpy.instr.aia.aiaprep`. (`#2749 `__) - User can convert between HPC and HCC coordinates with different observers. This is implemented by automatically transforming the coordinate into HGS and then changing observer, and then transforming back to HCC. (`#2754 `__) - Changed default file type for Helioviewer to prevent decode errors. (`#2771 `__) - Increase figure size to avoid cutting off longer colormap names in `sunpy.cm.show_colormaps`. (`#2824 `__) - The sample data directory will no longer be created until files are downloaded to it. (`#2836 `__) - Timeseries and lightcurve will now respect updated config values for download directory. (`#2844 `__) - Always use _default_wrap_angle rather than hard coding a wrap angle in the init of a sunpy coordinate frame (`#2853 `__) - Ensure imageanimators only slice arrays with integers (`#2856 `__) - Fixed `sunpy.io.fits.write` to handle the keyword ``COMMENT`` correctly. (`#2880 `__) - If Carrington longitude ("crln_obs") is found in the FITS header, `~sunpy.map.Map` converts this to the correct Heliographic longitude. (`#2946 `__) - `sunpy.net.helio.hec.HECClient.time_query` now resolves the correct input time format. (`#2969 `__) - Fixes the calculation of the solar rotation of coordinates and the differential rotation of `sunpy.map.GenericMap`. (`#2972 `__) - Added back the FERMI GBM client to `sunpy.net.dataretriever.sources`. (`#2983 `__) - Fix bug in `sunpy.net.hek` which raised and error if a search returned zero results, now returns an empty `sunpy.net.hek.HEKTable`. (`#3046 `__) - `~sunpy.map.sources.AIAMap` now uses the provided HAE coordinates instead of the provided HGS coordinates to determine the observer location. (`#3056 `__) - Correctly zero pad milliseconds in the `sunpy.util.scraper.Scraper` formatting to prevent errors when the millisecond value was less than 100. (`#3063 `__) - Fix `sunpy.util.scraper.Scraper` failing if a directory is not found on a remote server. (`#3063 `__) - Correctly extract observer location from MDI and EIT data (`#3067 `__) - Fix HGS <> HCRS test due to Ecliptic frame changes in astropy 3.2 (`#3075 `__) - Fixes bug when creating a timeseries from a URL and bug when creating a TimeSeries from older GOES/XRS fits files. (`#3081 `__) - Added `~sunpy.map.EUVIMap.rsun_obs`. It returns a quantity in arcsec consistent with other `sunpy.map.GenericMap` and overwrites mapbase's assumption of a photospheric limb as seen from Earth. (`#3099 `__) - Fixed bugs related to using `~sunpy.map.GenericMap.plot` and `~sunpy.map.GenericMap.peek` with the ``inline`` Matplotlib backend in Jupyter notebook. (`#3103 `__) - Make a correction to `sunpy.coordinates.wcs_utils.solar_wcs_frame_mapping` so that `astropy.wcs.WCS` objects are correctly converted to `sunpy.coordinates.frames` objects irrespective of the ordering of the axes. (`#3116 `__) - The `solar_rotate_coordinate` function returns a coordinate that accounts for the location of the new observer. (`#3123 `__) - Add support for rotation parameters to `sunpy.map.make_fitswcs_header`. (`#3139 `__) - Improve the implementation of `~sunpy.physics.differential_rotation.differential_rotate` the image warping when transforming Maps for differential rotation and change in observer position. (`#3149 `__) - Fix a bug where new helioviewer sources potentially cause `~sunpy.net.helioviewer.HelioviewerClient.data_sources` to error. (`#3162 `__) Improved Documentation ---------------------- - Organise the gallery into sections based on example type and tidy up a little. (`#2624 `__) - Added gallery example showing the conversion of Helioprojective Coordinates to Altitude/Azimuth Coordinates to and back. (`#2656 `__) - Add contribution guidelines for the sunpy example gallery. (`#2682 `__) - Added a gallery example for "Downloading and plotting a HMI image" and "Creating a Composite map". (`#2746 `__) - Added an example for `~sunpy.visualization.animator.ImageAnimatorWCS`. (`#2752 `__) - Minor changes to the developer guide regarding sprint labels. (`#2765 `__) - Copyedited and corrected the solar cycles example. (`#2770 `__) - Changed "online" mark to "remote_data" and made formatting of marks consistent. (`#2799 `__) - Add a missing plot to the end of the units and coordinates guide. (`#2813 `__) - Added gallery example showing how to access the SunPy colormaps (`#2865 `__) - Added gallery example showing how to access the SunPy solar physics constants. (`#2882 `__) - Major clean up of the developer documentation. (`#2951 `__) - Overhaul of the install intructions for the guide section of our documentation. (`#3147 `__) Trivial/Internal Changes ------------------------ - `~sunpy.time.parse_time` now uses `singledispatch` underneath. (`#2408 `__) - Revert the handling of ``quantity_allclose`` now that `astropy/astropy#7252 `__ is merged. This also bumps the minimum astropy version to 3.0.2. (`#2598 `__) - Replace the subclasses of matplotlib Slider and Button in `sunpy.visualization` with partial functions. (`#2613 `__) - Sort the ana C source files before building to enable reproducible builds. (`#2637 `__) - We are now using `towncrier `__ to generate our changelogs. (`#2644 `__) - Moved figure tests to Python 3.6. (`#2655 `__) - Removed old metaclass used for Map and TimeSeries as we have now moved to Python 3.6. (`#2655 `__) - Updated astropy_helpers to v3.0.2. (`#2655 `__) - When running image tests, a comparison HTML page is now generated to show the generated images and expected images. (`#2660 `__) - Change to using pytest-cov for coverage report generation to enable support for parallel builds (`#2667 `__) - Use of `textwrap` to keep source code indented when multiline texts is used (`#2671 `__) - Fix mispelling of private attribute ``_default_heliographic_latitude`` in map. (`#2730 `__) - Miscellaneous fixes to developer docs about building sunpy's documentation. (`#2825 `__) - Changed `sunpy.instr.aia.aiaprep` to update BITPIX keyword to reflect the float64 dtype. (`#2831 `__) - Remove warning from ``GenericMap.submap`` when using pixel ``Quantities`` as input. (`#2833 `__) - Remove the usage of six and all ``__future__`` imports (`#2837 `__) - Fix SunPy Coordinate tests with Astropy 3.1 (`#2838 `__) - Stores entries from directories into database sorted by name. It adds mocks to the database user guide examples. (`#2873 `__) - Fix all DeprecationWarning: invalid escape sequence. (`#2885 `__) - Used `unittest.mock` for creating offline tests for simulating online tests for `test_noaa.py` (`#2900 `__) - Fix support for pip 19 and isolated builds (`#2915 `__) - Moved to using `AppDirs `__ as the place to host our configuration file. (`#2922 `__) - Users can now use fewer keywords in our `~sunpy.net.HelioviewerClient` to access the available sources. Either by `observatory` and `measurement` or `instrument` and `measurement` as this much information is enough to get the source ID for most of the cases. (`#2926 `__) - Remove the pytest dependancy on the ``GenericMap`` asdf tag. (`#2943 `__) - Fix initialization of `~sunpy.net.vso.VSOClient` when no WSDL link is found. (`#2981 `__) 0.9.0 ===== New Features ------------ - Added TimeUTime class to support utime. [#2409] - Example for fine-grained use of ticks and grids [#2435] - Maintiners Workflow Guide [#2411] - Decorator to append and/or prepend doc strings [#2386] - Adding `python setup.py test --figure-only` [#2557] - Fido.fetch now accepts pathlib.Path objects for path attribute.[#2559] - The `~sunpy.coordinates.HeliographicStonyhurst` coordinate system can now be specified using a cartesian system, which is sometimes known as the "Heliocentric Earth equatorial" (HEEQ) coordinate system. [#2437] API Changes ----------- - `sunpy.coordinates.representation` has been removed. Longitude wrapping is now done in the constructor of the frames. [#2431] - Propagation of ``obstime`` in the coordinate frame transformation has changed, this means in general when transforming directly between frames (not `~astropy.coordinates.SkyCoord`) you will have to specify ``obstime`` in more places. [#2461] - Transforming between Heliographic Stonyhurst and Carrington now requires that ``obstime`` be defined and the same on both the input and output frames. [#2461] - Removed the figure return from .peek() [#2487] Bug Fixes --------- - Improve TimeSeriesBase docstring [#2399] - Validate that pytest-doctestplus is installed [#2388] - Fix use of self.wcs in plot in mapbase [#2398] - Updated docstring with pointer to access EVE data for other levels [#2402] - Fix broken links and redirections in documentation [#2403] - Fixes Documentation changes due to NumPy 1.14 [#2404] - Added docstrings to functions in dowload.py [#2415] - Clean up database doc [#2414] - rhessi.py now uses sunpy.io instead of astropy.io [#2416] - Remove Gamma usage in Map [#2424] - Changed requirements to python-dateutil [#2426] - Clarify coordinate system definitions [#2429] - Improve Map Peek when using draw_grid [#2442] - Add HCC --> HGS test [#2443] - Testing the transformation linking SunPy and Astropy against published values [#2454] - Fixed title bug in sunpy.timeseries.rhessi [#2477] - Allow LineAnimator to accept a varying x-axis [#2491] - Indexing Bug Fix to LineAnimator [#2560] - Output sphinx warnings to stdout [#2553] - Docstring improvement for LineAnimator [#2514] - move the egg_info builds to circleci [#2512] - Added tests for TraceMap [#2504] - Fix HGS frame constructor and HPC ``calculate_distance`` with SkyCoord constructor. [#2463] - removed `wavelnth` keyword in meta desc of Maps to avoid using non standard FITS keyword like `nan` [#2456] - The documentation build now uses the Sphinx configuration from sphinx-astropy rather than from astropy-helpers.[#2494] - Migrate to hypothesis.strategies.datetimes [#2368] - Prevent a deprecation warning due to truth values of Quantity [#2358] - Print a warning when heliographic longitude is set to it's default value of 0 [#2480] - parse_time now parses numpy.datetime64 correctly. [#2572] 0.8.5 ===== Bug Fixes --------- - Removed AstropyDeprecationWarning from sunpy.coordinates.representation [#2476] - Fix for NorthOffsetFrame under Astropy 3.0 [#2486] - Fix lightcurve tests under numpy dev [#2505] - Updated depecration link of radiospectra [#2481] - Fixed Padding values in some of the documentation pages [#2497] - Move documentation build to circleci [#2509] - Fix Issue #2470 hgs_to_hcc(heliogcoord, heliocframe) [#2502] - Fixing CompositeMap object so that it respects masked maps [#2492] 0.8.4 ===== Bug Fixes --------- - Improve detection of ``SkyCoord`` frame instantiation when distance is `1*u.one`. This fixes a plotting bug with ``WCSAxes`` in Astropy 3.0 [#2465] - removed `wavelnth` keyword in meta desc of Maps to avoid using non standard FITS keyword like `nan` [#2427] - Change the default units for HPC distance from `u.km` to `None`. [#2465] 0.8.3 ===== Bug Fixes --------- - `~sunpy.net.dataretriever.clients.XRSClient` now reports time ranges of files correctly. [#2364] - Make parse_time work with datetime64s and pandas series [#2370] - CompositeMap axes scaling now uses map spatial units [#2310] - Moved license file to root of repository and updated README file [#2326] - Fix docstring formatting for net.vso.attrs [#2309]] - Fix coloring of ticks under matplotlib 2.0 default style [#2320] - Always index arrays with tuples in `ImageAnimator` [#2320] - Added links to possible attrs for FIDO in guide [#2317] [#2289] - Updated GitHub Readme [#2281] [#2283] - Fix matplotlib / pandas 0.21 bug in examples [#2336] - Fixes the off limb enhancement example [#2329] - Changes to masking hot pixels and picking bright pixels examples [#2325] [#2319] - Travis CI fix for numpy-dev build [#2340] - Updated masking brightest pixel example [#2338] - Changed TRAVIS cronjobs [#2338] - Support array values for `obstime` for coordinates and transformations [#2342] [#2346] - Updated Gallery off limb enhance example [#2337] - Documentation fixes for VSO [#2354] [#2353] - All tests within the documentation have been fixed [#2343] - Change to using pytest-remotedata for our online tests [#2345] - Fixed upstream astropy/numpy documentation issues [#2359] - Documentation for Map improved [#2361] - Fix the output units of pixel_to_world [#2362] - Documentation for Database improved [#2355] - Added test for mapsave [#2365] - Documentation for Sun improved [#2369] 0.8.2 ===== Bug Fixes --------- - Shows a warning if observation time is missing [#2293] - Updates MapCube to access the correct properties of the namedtuple SpatialPair [#2297] 0.8.1 ====== Bug fixes --------- - Fixed TimeSeries test failures due to missing test files [#2273] - Refactored a GOES test to avoid a Py3.6 issue [#2276] 0.8.0 ====== New Features ------------ - Solar differential rotation for maps and submaps included. - Solar rotation calculation and mapcube derotation now use sunpy coordinates. - Sample data now downloads automatically on import if not available and is now pluggable so can be used by affiliated packages. Shortcut names have been normalized and all LIGHTCURVE shortcuts have changed to TIMESERIES. - Calculation of points on an arc of a great circle connecting two points on the Sun. - Removed ``extract_time`` function from ``sunpy.time`` and also tests related to the function from ``sunpy.time.tests`` - User can now pass a custom time format as an argument inside ``sunpy.database.add_from_dir()`` in case the ``date-obs`` metadata cannot be read automatically from the files. - Add time format used by some SDO HMI FITS keywords - Now the ``sunpy.database.tables.display_entries()`` prints an astropy table. - Additional methods added inside the ``sunpy.database`` class to make it easier to display the database contents. - Remove unused ``sunpy.visualization.plotting`` module - Port the pyana wrapper to Python 3 - ``Map.peek(basic_plot-True)`` no longer issues warnings - Remove the ``sunpy.map.nddata_compat`` module, this makes ``Map.data`` and ``Map.meta`` read only. - Add a ``NorthOffsetFrame`` class for generating HGS-like coordinate systems with a shifted north pole. - Remove deprecated ``VSOClient.show`` method. - Deprecate ``sunpy.wcs``: ``sunpy.coordinates`` and ``sunpy.map`` now provide all that functionality in a more robust manner. - Added hdu index in ``sunpy.database.tables.DatabaseEntry`` as a column in the table. - Removed ``HelioviewerClient`` from the ``sunpy.net`` namespace. It should now be imported with ``from sunpy.net.helioviewer import HelioviewerClient``. - Removed compatibility with standalone ``wcsaxes`` and instead depend on the version in astropy 1.3. SunPy now therefore depends on astropy>-1.3. - Update to ``TimeRange.__repr__``; now includes the qualified name and ``id`` of the object. - A new ``sunpy.visualization.imageanimator.LineAnimator`` class has been added to animate 1D data. This has resulted in API change for the ``sunpy.visualization.imageanimator.ImageAnimator`` class. The updateimage method has been renamed to update\_plot. - Drop support for Python 3.4. - SunPy now requires WCSAxes and Map.draw\_grid only works with WCSAxes. - ``Helioprojective`` and ``HelioCentric`` frames now have an ``observer`` attribute which itself is a coordinate object (``SkyCoord``) instead of ``B0``, ``L0`` and ``D0`` to describe the position of the observer. - ``GenericMap.draw_grid`` now uses ``WCSAxes``, it will only work on a ``WCSAxes`` plot, this may be less performant than the previous implementation. - ``GenericMap.world_to_pixel`` and ``GenericMap.pixel_to_world`` now accept and return ``SkyCoord`` objects only. - ``GenericMap`` has a new property ``observer_coordinate`` which returns a ``SkyCoord`` describing the position of the observer. - ``GenericMap.submap`` now takes arguments of the form ``bottom_left`` and ``top_right`` rather than ``range_a`` and ``range_b``. This change enables submap to properly handle rotated maps and take input in the form of ``SkyCoord`` objects. - When referring to physical coordinates ``Pair.x`` has been replaced with ``SpatialPair.axis1``. This means values returned by ``GenericMap`` now differentiate between physical and pixel coordinates. - The physical radius of the Sun (length units) is now passed from Map into the coordinate frame so a consistent value is used when calculating distance to the solar surface in the ``HelioprojectiveFrame`` coordinate frame. - A new ``sunpy.visualization.imageanimator.ImageAnimatorWCS`` class has been added to animate N-Dimensional data with the associated WCS object. - Moved Docs to docs/ to follow the astropy style - Added SunPy specific warnings under util. - SunPy coordinate frames can now be transformed to and from Astropy coordinate frames - The time attribute for SunPy coordinate frames has been renamed from ``dateobs`` to ``obstime`` - Ephemeris calculations with higher accuracy are now available under ``sunpy.coordinates.ephemeris`` - Add support for SunPy coordinates to specify observer as a string of a major solar-system body, with the default being Earth. To make transformations using an observer specified as a string, ``obstime`` must be set. - Added VSO query result block level caching in the database module. This prevents re-downloading of files which have already been downloaded. Especially helpful in case of overlapping queries. - Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees. - All Clients that are able to search and download data now have a uniform API that is `search` and `fetch`. The older functions are still there but are deprecated for 0.8. Bug fixes --------- - Add tests for RHESSI instrument - Maps from Helioviewer JPEG2000 files now have correct image scaling. - Get and set methods for composite maps now use Map plot\_settings. - Simplified map names when plotting. - Fix bug in ``wcs.convert_data_to_pixel`` where crpix[1] was used for both axes. - Fix some leftover instances of ``GenericMap.units`` - Fixed bugs in ``sun`` equations - ``sunpy.io.fits.read`` will now return any parse-able HDUs even if some raise an error. - ``VSOClient`` no longer prints a lot of XML junk if the query fails. - Fix Map parsing of some header values to allow valid float strings like 'nan' and 'inf'. - Fix Map parsing of some header values to allow valid float strings like 'nan' and 'inf'. 0.7.8 ===== - The SunPy data directory "~/sunpy" is no longer created until it is used (issue #2018) - Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees. - Fix for surface gravity unit. - Support for Pandas 0.20.1 0.7.7 ===== - Fix errors with Numpy 1.12 0.7.6 ===== - Add Astropy 1.3 Support 0.7.5 ===== - Fix test faliure (mapbase) with 1.7.4 - Restrict supported Astropy version to 1.0-1.3. - Update to ``TimeRange.__repr__``; now includes the qualified name and ``id`` of the object. - Change the default representation for the Heliographic Carrington frame so Longitude follows the convention of going from 0-360 degrees. - Fix Map parsing of some header values to allow valid float strings like 'nan' and 'inf'. 0.7.0 ===== - Fixed test failures with numpy developer version.[#1808] - Added ``timeout`` parameter in ``sunpy.data.download_sample_data()`` - Fixed ``aiaprep`` to return properly sized map. - Deprecation warnings fixed when using image coalignment. - Sunpy is now Python 3.x compatible (3.4 and 3.5). - Added a unit check and warnings for map metadata. - Added IRIS SJI color maps. - Updated ``show_colormaps()`` with new string filter to show a subset of color maps. - Fixed MapCube animations by working around a bug in Astropy's ImageNormalize - Remove ``vso.QueryResponse.num_records()`` in favour of ``len(qr)`` - Add a ``draw_rectangle`` helper to ``GenericMap`` which can plot rectangles in the native coordinate system of the map. - Added the ability to shift maps to correct for incorrect map location, for example. - Bug fix for RHESSI summary light curve values. - Mapcube solar derotation and coalignment now pass keywords to the routine used to shift the images, scipy.ndimage.interpolation.shift. - Add automatic registration of ``GenericMap`` subclasses with the factory as long as they define an ``is_datasource_for`` method. - Added functions ``flareclass_to_flux`` and ``flux_to_flareclass`` which convert between GOES flux to GOES class numbers (e.g. X12, M3.4). - Removed old ``sunpy.util.goes_flare_class()`` - Bug fix for RHESSI summary light curve values. - The ``MapCube.as_array`` function now returns a masked numpy array if at least one of the input maps in the MapCube has a mask. - Map superpixel method now respects maps that have masks. - Map superpixel method now accepts numpy functions as an argument, or any user-defined function. - Map superpixel method no longer has the restriction that the number of original pixels in the x (or y) side of the superpixel exactly divides the number of original pixels in the x (or y) side of the original map data. - ``sunpy.physics.transforms`` has been deprecated and the code moved into ``sunpy.physics``. - Add the ``sunpy.coordinates`` module, this adds the core physical solar coordinates frame within the astropy coordinates framework. - Added ability of maps to draw contours on top of themselves (``draw_contours``) - Added concatenate functionality to lightcurve base class. - Fix Map to allow astropy.io.fits Header objects as valid input for meta arguments. - Added an examples gallery using ``sphinx-gallery``. - API clean up to constants. Removed constant() function which is now replaced by get(). - Prevent helioviewer tests from checking access to the API endpoint when running tests offline. - ``GenericMap.units`` is renamed to ``GenericMap.spatial_units`` to avoid confusion with ``NDData.unit``. - ``GenericMap`` now has a ``coordinate_frame`` property which returns an ``astropy.coordinates`` frame with all the meta data from the map populated. - ``GenericMap`` now has a ``_mpl_axes`` method which allows it to be specified as a projection to ``matplotlib`` methods and will return a ``WCSAxes`` object with ``WCS`` projection. 0.6.5 ===== - The draw\_grid keyword of the peek method of Map now accepts booleans or astropy quantities. - Fix bug in ``wcs.convert_data_to_pixel`` where crpix[1] was used for both axes. - Fixed bugs in ``sun`` equations 0.6.4 ===== - Bug fix for rhessi summary lightcurve values. - Fix docstring for ``pixel_to_data`` and ``data_to_pixel``. - Fix the URL for the Helioviewer API. (This fixes Helioviewer.) - Fix the way ``reshape_image_to_4d_superpixel`` checks the dimension of the new image. - Fix Map to allow astropy.io.fits Header objects as valid input for meta arguments. - Prevent helioviewer tests from checking access to API when running tests in offline mode. 0.6.3 ===== - Change setup.py extras to install suds-jurko not suds. 0.6.2 ===== - Changed start of GOES 2 operational time range back to 1980-01-04 so data from 1980 can be read into GOESLightCurve object - Fix bug with numpy 1.10 - update astropy\_helpers - Added new sample data 0.6.1 ===== - Fixed MapCube animations by working around a bug in Astropy's ImageNormalize - Small fix to RTD builds for Affiliated packages - SunPy can now be installed without having to install Astropy first. - MapCubes processed with ``coalignment.apply_shifts`` now have correct metadata. - Multiple fixes for WCS transformations, especially with solar-x, solar-y CTYPE headers. 0.6.0 ===== - Enforced the use of Astropy Quantities through out most of SunPy. - Dropped Support for Python 2.6. - Remove old style string formatting and other 2.6 compatibility lines. - Added vso like querying feature to JSOC Client. - Refactor the JSOC client so that it follows the .query() .get() interface of VSOClient and UnifedDownloader. - Provide ``__str__`` and ``__repr__`` methods on vso ``QueryResponse`` deprecate ``.show()``. - Downloaded files now keep file extensions rather than replacing all periods with underscores. - Update to TimeRange API, removed t1 and t0, start and end are now read-only attributes. - Added ability to download level3 data for lyra Light Curve along with corresponding tests. - Added support for gzipped FITS files. - Add STEREO HI Map subclass and color maps. - Map.rotate() no longer crops any image data. - For accuracy, default Map.rotate() transformation is set to bi-quartic. - ``sunpy.image.transform.affine_transform`` now casts integer data to float64 and sets NaN values to 0 for all transformations except scikit-image rotation with order <- 3. - CD matrix now updated, if present, when Map pixel size is changed. - Removed now-redundant method for rotating IRIS maps since the functionality exists in Map.rotate() - Provide ``__str__`` and ``__repr__`` methods on vso ``QueryResponse`` deprecate ``.show()`` - SunPy colormaps are now registered with matplotlib on import of ``sunpy.cm`` - ``sunpy.cm.get_cmap`` no longer defaults to 'sdoaia94' - Added database url config setting to be setup by default as a sqlite database in the sunpy working directory - Added a few tests for the sunpy.roi module - Added capability for figure-based tests - Removed now-redundant method for rotating IRIS maps since the functionality exists in Map.rotate(). - SunPy colormaps are now registered with matplotlib on import of ``sunpy.cm``. - ``sunpy.cm.get_cmap`` no longer defaults to 'sdoaia94'. - Added database url config setting to be setup by default as a sqlite database in the sunpy working directory. - Added a few tests for the sunpy.roi module. - Refactored mapcube co-alignment functionality. - Removed sample data from distribution and added ability to download sample files - Changed start of GOES 2 operational time range back to 1980-01-04 so data from 1980 can be read into GOESLightCurve object - Require JSOC request data calls have an email address attached. - Calculation of the solar rotation of a point on the Sun as seen from Earth, and its application to the de-rotation of mapcubes. - Downloaded files now keep file extensions rather than replacing all periods with underscores - Fixed the downloading of files with duplicate names in sunpy.database - Removed sample data from distribution and added ability to download sample files. - Added the calculation of the solar rotation of a point on the Sun as seen from Earth, and its application to the de-rotation of mapcubes. - Changed default for GOESLightCurve.create() so that it gets the data from the most recent existing GOES fits file. - Map plot functionality now uses the mask property if it is present, allowing the plotting of masked map data - Map Expects Quantities and returns quantities for most parameters. - Map now used Astropy.wcs for world <-> pixel conversions. - map.world\_to\_pixel now has a similar API to map.pixel\_to\_world. - map.shape has been replaced with map.dimensions, which is ordered x first. - map.rsun\_arcseconds is now map.rsun\_obs as it returns a quantity. - Map properties are now named tuples rather than dictionaries. - Improvement for Map plots, standardization and improved color tables, better access to plot variables through new plot\_settings variable. - Huge improvements in Instrument Map doc strings. Now contain instrument descriptions as well as reference links for more info. - net.jsoc can query data series with time sampling by a Sample attribute implemented in vso. - MapCube.plot and MapCube.peek now support a user defined plot\_function argument for customising the animation. - Added new sample data file, an AIA cutout file. - Moved documentation build directory to doc/build 0.5.5 ===== - Changed default for GOESLightCurve.create() so that it gets the data from the most recent existing GOES fits file. - Improvements to the Map documentation. - Typo fixes in sunpy.wcs documentation. 0.5.4 ===== - ``sunpy.image.transform.affine_transform`` now casts integer data to float64 and sets NaN values to 0 for all transformations except scikit-image rotation with order <- 3. - Updated SWPC/NOAA links due to their new website. - Exposed the raw AIA color tables in ``sunpy.cm.color_tables``. - Fixes ``map`` compatibility with Astropy 1.0.x. 0.5.3 ===== - Goes peek() plot now works with matplotlib 1.4.x - The ANA file reading C extensions will no longer compile under windows. Windows was not a supported platform for these C extensions previously. 0.5.2 ===== - If no CROTA keyword is specified in Map meta data, it will now default to 0 as specified by the FITS WCS standard. - Map now correctly parses and converts the CD matrix, as long as CDELT is specified as well. (Fixes SWAP files) - Fix of HELIO webservice URLs - MapCube.plot() is now fixed and returns a matplotlib.animation.FuncAnimation object. 0.5.1 ===== - MAJOR FIX: map.rotate() now works correctly for all submaps and off center rotations. - HELIO URL updated, querys should now work as expected. - All tabs removed from the code base. - All tests now use tempfile rather than creating files in the current directory. - Documentation builds under newer sphinx versions. - ANA and JP2 tests are skipped if dependancies are missing. - ANA tests are skipped on windows. 0.5.0 ===== - Added additional functionality to the GOES module i.e. the ability to calculate GOES temperature and emission measure from GOES fluxes. - changed \_maps attribute in MapCube to a non-hidden type - Added Nobeyama Radioheliograph data support to Lightcurve object. - Fixed some tests on map method to support Windows - Added a window/split method to time range - Updates to spectrogram documentation - Added method Database.add\_from\_hek\_query\_result to HEK database - Added method Database.download\_from\_vso\_query\_result - GOES Lightcurve now makes use of a new source of GOES data, provides metadata, and data back to 1981. - Removed sqlalchemy as a requirement for SunPy - Added support for NOAA solar cycle prediction in lightcurves - Some basic tests for GenericLightCurve on types of expected input. - Fix algorithm in sunpy.sun.equation\_of\_center - Added Docstrings to LightCurve methods. - Added tests for classes in sunpy.map.sources. Note that some classes (TRACE, RHESSI) were left out because SunPy is not able to read their FITS files. - Added functions that implement image coalignment with support for MapCubes. - Cleaned up the sunpy namespace, removed .units, /ssw and .sphinx. Also moved .coords .physics.transforms. - Added contains functionality to TimeRange module - Added t-'now' to parse\_time to privide utcnow datetime. - Fixed time dependant functions (.sun) to default to t-'now' - Fixed solar\_semidiameter\_angular\_size - Improved line quality and performances issues with map.draw\_grid() - Remove deprecated make\_map command. 0.4.2 ===== - Fixes to the operational range of GOES satellites - Fix the URL for HELIO queries. 0.4.1 ===== - Fix map.rotate() functionality - Change of source for GOES data. - Fix EIT test data and sunpy FITS saving - Some documentation fixes - fix file paths to use os.path.join for platform independance. 0.4.0 ===== - **Major** documentation refactor. A far reaching re-write and restructure. - Add a SunPy Database to store and search local data. - Add beta support for querying the HELIO HEC - Add beta HEK to VSO query translation. - Add the ability to download the GOES event list. - Add support for downloading and querying the LYTAF database. - Add support for ANA data. - Updated sun.constants to use astropy.constants objects which include units, source, and error instide. For more info check out https://docs.astropy.org/en/latest/constants/index.html - Add some beta support for IRIS data products - Add a new MapCubeAnimator class with interactive widgets which is returned by mapcube.peek(). - The Glymur library is now used to read JPEG2000 files. - GOESLightCurve now supports all satellites. - Add support for VSO queries through proxies. - Fix apparent Right Ascension calulations. - LightCurve meta data member now an OrderedDict Instance 0.3.2 ===== - Pass draw\_limb arguments to patches.Circle - Pass graw\_grid arguments to pyplot.plot() - Fix README code example - Fix Documentation links in potting guide - Update to new EVE data URL - Update LogicalLightcurve example in docs - Improved InteractiveVSOClient documentation - GOESLightCurve now fails politely if no data is avalible. Known Bugs: - sunpy.util.unit\_conversion.to\_angstrom does not work if 'nm' is passed in. 0.3.1 ===== - Bug Fix: Fix a regression in CompositeMap that made contor plots fail. - Bug Fix: Allow Map() to accept dict as metadata. - Bug Fix: Pass arguments from Map() to io.read\_file. 0.3.0 ===== - Removal of Optional PIL dependancy - Parse\_time now looks through nested lists/tuples - Draw\_limb and draw\_grid are now implemented on MapCube and CompositeMap - Caculations for differential roation added - mapcube.plot() now runs a mpl animation with optional controls - A basic Region of Interest framework now exists under sunpy.roi - STEREO COR colour maps have been ported from solarsoft. - sunpy.time.timerange has a split() method that divides up a time range into n equal parts. - Added download progress bar - pyfits is depricated in favor of Astropy spectra: - Plotting has been refactorted to use a consistent interface - spectra now no-longer inherits from numpy.ndarray instead has a .data attribute. Map: \* map now no-longer inherits from numpy.ndarray instead has a .data attribute. \* make\_map is deprecated in favor of Map which is a new factory class \* sunpy.map.Map is now sunpy.map.GenericMap \* mymap.header is now mymap.meta \* attributes of the map class are now read only, changes have to be made through map.meta \* new MapMeta class to replace MapHeader, MapMeta is not returned by sunpy.io \* The groundwork for GenericMap inherting from astropy.NDData has been done, there is now a NDDataStandin class to provide basic functionality. io: \* top level file\_tools improved to be more flexible and support multiple HDUs \* all functions in sunpy.io now assume mutliple HDUs, even JP2 ones. \* there is now a way to override the automatic filetype detection \* Automatic fits file detection improved \* extract\_waveunit added to io.fits for detection of common ways of storing wavelength unit in fits files. - A major re-work of all interal imports has resulted in a much cleaner namespace, i.e. sunpy.util.util is no longer used to import util. - Some SOHO and STEREO files were not reading properly due to a date\_obs parameter. - Sunpy will now read JP2 files without a comment parameter. - Memory leak in Crotate patched - Callisto: Max gap between files removed 0.2.0 ===== - Completely re-written plotting routines for most of the core datatypes. - JPEG 2000 support as an input file type. - Improved documentation for much of the code base, including re-written installation instructions. - New lightcurve object - LYRA support - GOES/XRS support - SDO/EVE support - New Spectrum and Spectrogram object (in development) - Spectrogram plotting routines - Callisto spectrum type and support - STEREO/SWAVES support - Map Object - Added support for LASCO, Yohkoh/XRT maps - A new CompositeMap object for overlaying maps - Resample method - Superpixel method - The addition of the rotate() method for 2D maps. sunpy-1.0.3/PKG-INFO0000644000175100001650000001521613531722607014271 0ustar vstsdocker00000000000000Metadata-Version: 2.1 Name: sunpy Version: 1.0.3 Summary: "SunPy: Python for Solar Physics" Home-page: https://sunpy.org Author: The SunPy Community Author-email: sunpy@googlegroups.com License: BSD 2-Clause Description: ******** `SunPy`_ ******** |Latest Version| |codecov| |matrix| |Research software impact| |DOI| |Powered by NumFOCUS| .. |Latest Version| image:: https://img.shields.io/pypi/v/sunpy.svg :target: https://pypi.python.org/pypi/sunpy/ .. |matrix| image:: https://img.shields.io/matrix/sunpy:openastronomy.org.svg?colorB=%23FE7900&label=Chat&logo=matrix&server_fqdn=matrix.openastronomy.org :target: https://chat.openastronomy.org/#/room/#sunpy:openastronomy.org .. |codecov| image:: https://codecov.io/gh/sunpy/sunpy/branch/master/graph/badge.svg :target: https://codecov.io/gh/sunpy/sunpy .. |Research software impact| image:: http://depsy.org/api/package/pypi/sunpy/badge.svg :target: http://depsy.org/package/python/sunpy .. |DOI| image:: https://zenodo.org/badge/2165383.svg :target: https://zenodo.org/badge/latestdoi/2165383 .. |Powered by NumFOCUS| image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A :target: https://numfocus.org .. |Binder| image:: https://mybinder.org/badge_logo.svg :target: https://mybinder.org/v2/gh/sunpy/sunpy/master?filepath=examples SunPy is an open-source Python library for Solar Physics data analysis and visualization. Our homepage `SunPy`_ has more information about the project. For some examples of using SunPy see our `gallery`_, to see the latest changes in SunPy see our `Changelog`_. .. _SunPy: https://sunpy.org .. _gallery: https://docs.sunpy.org/en/stable/generated/gallery/index.html .. _Changelog: https://docs.sunpy.org/en/latest/whatsnew/changelog.html Installation ============ The recommended way to install SunPy is with `conda`_. To install SunPy once conda is installed run the following two commands: .. code:: bash $ conda config --append channels conda-forge $ conda install sunpy For detailed installation instructions, see the `installation guide`_ in the SunPy docs. .. _conda: https://www.anaconda.com/distribution/ .. _installation guide: https://docs.sunpy.org/en/latest/guide/installation/index.html Developing ========== If you want to develop SunPy you will need to install from GitHub. The best way to do this is to create a new conda environment and install the git version of SunPy in it: .. warning:: Do not clone the sunpy repository into ``$HOME/sunpy``. Depending on the operating system this location is used to store downloaded data files. This will cause conflicts later on, so the last argument (``sunpy-git``) on the ``git clone`` line will become the local folder name of the cloned repository. .. code:: bash $ conda config --append channels conda-forge $ conda create -n sunpy-dev sunpy $ conda activate sunpy-dev $ conda remove sunpy $ git clone https://github.com/sunpy/sunpy.git sunpy-git $ cd sunpy-git $ pip install -e .[all,dev] For detailed installation instructions, see the `Newcomers' guide`_ in the SunPy docs. Usage ===== Here is a quick example of plotting an AIA image: .. code:: python >>> import sunpy.map >>> from sunpy.data.sample import AIA_171_IMAGE >>> aia = sunpy.map.Map(AIA_171_IMAGE) >>> aia.peek() Getting Help ============ For more information or to ask questions about SunPy, check out: - `SunPy Documentation`_ - `SunPy Matrix Channel`_ - `SunPy Mailing List`_ .. _SunPy Documentation: https://docs.sunpy.org/en/stable/ .. _SunPy Matrix Channel: https://chat.openastronomy.org/#/room/#sunpy:openastronomy.org .. _SunPy Mailing List: https://groups.google.com/forum/#!forum/sunpy Contributing ============ |Open Source Helpers| If you would like to get involved, start by joining the `SunPy mailing list`_ and check out the `Developers Guide`_ section of the SunPy docs. Stop by our chat room `#sunpy:openastronomy.org`_ if you have any questions. Help is always welcome so let us know what you like to work on, or check out the `issues page`_ for the list of known outstanding items. For more information on contributing to SunPy, please read our `Newcomers' guide`_. .. |Open Source Helpers| image:: https://www.codetriage.com/sunpy/sunpy/badges/users.svg :target: https://www.codetriage.com/sunpy/sunpy .. _SunPy mailing list: https://groups.google.com/forum/#!forum/sunpy .. _Developers Guide: https://docs.sunpy.org/en/latest/dev_guide/index.html .. _`#sunpy:openastronomy.org`: https://chat.openastronomy.org/#/room/#sunpy:openastronomy.org .. _issues page: https://github.com/sunpy/sunpy/issues .. _Newcomers' guide: https://docs.sunpy.org/en/latest/dev_guide/newcomers.html Code of Conduct =============== When you are interacting with the SunPy community you are asked to follow our `Code of Conduct`_. .. _Code of Conduct: https://docs.sunpy.org/en/latest/code_of_conduct.html Keywords: solar physics,solar,science,sun,wcs,coordinates Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Scientific/Engineering :: Physics Provides: sunpy Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: asdf Provides-Extra: jpeg2000 Provides-Extra: net Provides-Extra: database Provides-Extra: dev Provides-Extra: docs Provides-Extra: tests Provides-Extra: image Provides-Extra: all sunpy-1.0.3/pyproject.toml0000644000175100001650000000473513531722525016113 0ustar vstsdocker00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel", "numpy==1.14.5"] build-backend = 'setuptools.build_meta' [ tool.sunpy-bot ] # disable astropy checks changelog_check = false autoclose_stale_pull_request = false # SunPy Checks check_towncrier_changelog = true check_milestone = true post_pr_comment = true all_passed_message = """ Thanks for the pull request @{pr_handler.user}! Everything looks great! """ [ tool.sunpy-bot.towncrier_changelog ] verify_pr_number = true changelog_skip_label = "No Changelog Entry Needed" help_url = "https://github.com/Cadair/sunpy/blob/towncrier/changelog/README.rst" missing_file_message = """ * I didn't detect a changelog file in this pull request. Please add a changelog file to the `changelog/` directory following the instructions in the changelog [README](https://github.com/sunpy/sunpy/blob/master/changelog/README.rst). """ wrong_type_message = """ * The changelog file you added is not one of the allowed types. Please use one of the types described in the changelog [README](https://github.com/sunpy/sunpy/blob/master/changelog/README.rst) """ wrong_number_message = """ * The number in the changelog file you added does not match the number of this pull request. Please rename the file. """ [ tool.sunpy-bot.milestone_checker ] missing_message = """ * This pull request does not have a milestone assigned to it. Only maintainers can change this, so you don't need to worry about it. :smile: """ [tool.towncrier] package = "sunpy" filename = "CHANGELOG.rst" directory = "changelog/" issue_format = "`#{issue} `__" [[tool.towncrier.type]] directory = "breaking" name = "Backwards Incompatible Changes" showcontent = true [[tool.towncrier.type]] directory = "api" name = "API Changes" showcontent = true [[tool.towncrier.type]] directory = "removal" name = "Deprecations and Removals" showcontent = true [[tool.towncrier.type]] directory = "feature" name = "Features" showcontent = true [[tool.towncrier.type]] directory = "bugfix" name = "Bug Fixes" showcontent = true [[tool.towncrier.type]] directory = "doc" name = "Improved Documentation" showcontent = true [[tool.towncrier.type]] directory = "trivial" name = "Trivial/Internal Changes" showcontent = true sunpy-1.0.3/setup.cfg0000644000175100001650000000763113531722525015016 0ustar vstsdocker00000000000000[metadata] name = sunpy provides = sunpy description = "SunPy: Python for Solar Physics" long_description = file: README.rst long_description_content_type = text/x-rst author = The SunPy Community author_email = sunpy@googlegroups.com license = BSD 2-Clause license_file = LICENSE.rst url = https://sunpy.org edit_on_github = True github_project = sunpy/sunpy platform = any keywords = solar physics, solar, science, sun, wcs, coordinates classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Science/Research License :: OSI Approved :: BSD License Natural Language :: English Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Topic :: Scientific/Engineering :: Physics [options] python_requires = >=3.6 packages = find: include_package_data = True setup_requires = setuptools_scm install_requires = numpy>=1.14.5 scipy matplotlib>=1.3 pandas astropy>=3.1 parfive[ftp] [options.extras_require] database = sqlalchemy image = scikit-image jpeg2000 = glymur net = beautifulsoup4 drms python-dateutil zeep tqdm asdf = asdf tests = hypothesis pytest<5.1 pytest-astropy pytest-cov pytest-mock tox tox-conda docs = ruamel.yaml sphinx>=2 sphinx-astropy sphinx-gallery sunpy-sphinx-theme towncrier [options.package_data] sunpy.data = sunpyrc, test/*, test/*/* sunpy.database = tests/test_table.txt sunpy.io = special/asdf/schemas/sunpy.org/sunpy/*/*.yaml, special/asdf/schemas/sunpy.org/sunpy/*/*/*.yaml sunpy.tests = figure_hashes_py36.json [options.entry_points] asdf_extensions = sunpy = sunpy.io.special.asdf.extension:SunpyExtension #pytest11 = # asdf = asdf.tests.schema_tester [ah_bootstrap] auto_use = True [build_docs] source-dir = docs build-dir = docs/_build all_files = 1 [tool:pytest] minversion = 3.0 testpaths = "sunpy" "docs" # Skip sunpy/data to prevent importing the sample data (there are no tests in that dir anyway) norecursedirs = ".tox" "build" "docs[\/]_build" "docs[\/]generated" "*.egg-info" "astropy_helpers" "examples" "sunpy[\/]data" doctest_plus = enabled doctest_optionflags = NORMALIZE_WHITESPACE FLOAT_CMP ELLIPSIS addopts = -p no:warnings --doctest-rst -m "not figure" markers = online: marks this test function as needing online connectivity. figure: marks this test function as using hash-based Matplotlib figure verification. This mark is not meant to be directly applied, but is instead automatically applied when a test function uses the @sunpy.tests.helpers.figure_test decorator. flaky array_compare # Disable internet access for tests not marked remote_data remote_data_strict = True [pycodestyle] max_line_length = 100 [flake8] max-line-length = 100 [isort] line_length = 100 not_skip = __init__.py sections = FUTURE, STDLIB, THIRDPARTY, ASTROPY, FIRSTPARTY, LOCALFOLDER default_section = THIRDPARTY known_first_party = sunpy known_astropy = astropy, asdf multi_line_output = 0 balanced_wrapping = True include_trailing_comma = False length_sort = False length_sort_stdlib = True [coverage:run] omit = sunpy/conftest.py sunpy/cython_version* sunpy/*setup* sunpy/extern/* sunpy/*/tests/* sunpy/version* sunpy/__init__* sunpy/data/sample.py sunpy/data/_sample.py */sunpy/conftest.py */sunpy/cython_version* */sunpy/*setup* */sunpy/extern/* */sunpy/*/tests/* */sunpy/version* */sunpy/__init__* */sunpy/data/sample.py */sunpy/data/_sample.py [coverage:report] exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about packages we have installed except ImportError # Don't complain if tests don't hit assertions raise AssertionError raise NotImplementedError # Don't complain about script hooks def main\(.*\): # Ignore branches that don't pertain to this version of Python pragma: py{ignore_python_version} sunpy-1.0.3/setup.py0000755000175100001650000000312113531722525014700 0ustar vstsdocker00000000000000#!/usr/bin/env python import os import sys from itertools import chain from setuptools import setup from setuptools.config import read_configuration # Append cwd for pip 19 sys.path.append(os.path.abspath(".")) import ah_bootstrap # noqa from astropy_helpers.setup_helpers import register_commands, get_package_info # noqa ################################################################################ # Override the default Astropy Test Command ################################################################################ cmdclass = register_commands() try: from sunpy.tests.setup_command import SunPyTest # Overwrite the Astropy Testing framework cmdclass['test'] = type('SunPyTest', (SunPyTest,), {'package_name': 'sunpy'}) except Exception: # Catch everything, if it doesn't work, we still want SunPy to install. pass ################################################################################ # Programmatically generate some extras combos. ################################################################################ extras = read_configuration("setup.cfg")['options']['extras_require'] # Dev is everything extras['dev'] = list(chain(*extras.values())) # All is everything but tests and docs exclude_keys = ("tests", "docs", "dev") ex_extras = dict(filter(lambda i: i[0] not in exclude_keys, extras.items())) # Concatenate all the values together for 'all' extras['all'] = list(chain.from_iterable(ex_extras.values())) package_info = get_package_info() setup(extras_require=extras, use_scm_version=True, cmdclass=cmdclass, **package_info) sunpy-1.0.3/sunpy/0000755000175100001650000000000013531722607014345 5ustar vstsdocker00000000000000sunpy-1.0.3/sunpy/coordinates/0000755000175100001650000000000013531722607016657 5ustar vstsdocker00000000000000sunpy-1.0.3/sunpy/coordinates/frameattributes.py0000644000175100001650000001162213531722525022433 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import datetime import astropy.units as u from astropy.time import Time from astropy.coordinates import TimeAttribute, CoordinateAttribute from sunpy.time import parse_time __all__ = ['TimeFrameAttributeSunPy', 'ObserverCoordinateAttribute'] class TimeFrameAttributeSunPy(TimeAttribute): """ Frame attribute descriptor for quantities that are Time objects. See the `~astropy.coordinates.Attribute` API doc for further information. Parameters ---------- default : object Default value for the attribute if not provided secondary_attribute : str Name of a secondary instance attribute which supplies the value if ``default`` is ``None`` and no value was supplied during initialization. Returns ------- frame_attr : descriptor A new data descriptor to hold a frame attribute """ def convert_input(self, value): """ Convert input value to a Time object and validate by running through the Time constructor. Also check that the input was a scalar. Parameters ---------- value : object Input value to be converted. Returns ------- out, converted : correctly-typed object, boolean Tuple consisting of the correctly-typed object and a boolean which indicates if conversion was actually performed. Raises ------ ValueError If the input is not valid for this attribute. """ if value is None: return None, False elif value == 'now': return Time(datetime.datetime.now()), True elif isinstance(value, Time): out = value converted = False elif isinstance(value, str): try: out = Time(parse_time(value)) except Exception as err: raise ValueError(f'Invalid time input {self.name}={value!r}\n{err}') converted = True else: try: out = Time(value) except Exception as err: raise ValueError(f'Invalid time input {self.name}={value!r}\n{err}') converted = True return out, converted class ObserverCoordinateAttribute(CoordinateAttribute): """ An Attribute to describe the location of the observer in the solar system. The observer location can be given as a string of a known observer, which will be converted to a coordinate as long as the ``obstime`` attribute is valid on the instance of the frame. Alternatively a low-level frame class *or* a `~astropy.coordinates.SkyCoord` can be provided to specify the location of the observer. If a `~astropy.coordinates.SkyCoord` is passed it will always be converted to the low-level frame class when accessed. Parameters ---------- frame : a coordinate frame class The type of frame this attribute can be default : object Default value for the attribute if not provided secondary_attribute : str Name of a secondary instance attribute which supplies the value if ``default is None`` and no value was supplied during initialization. """ def convert_input(self, value): # Keep string here. if isinstance(value, str): return value, False else: return super().convert_input(value) def _convert_string_to_coord(self, out, obstime): """ Given a value and and frame instance calculate the position of the object given as a string. """ # Import here to prevent circular import from .frames import HeliographicStonyhurst from .ephemeris import get_body_heliographic_stonyhurst obscoord = get_body_heliographic_stonyhurst(out, obstime) if out == "earth": rep = obscoord.spherical rep.lon[()] = 0*u.deg obscoord = obscoord.realize_frame(rep) return obscoord def __get__(self, instance, frame_cls=None): # If instance is None then we can't get obstime so it doesn't matter. if instance is not None: # Get observer if the instance has one, or the default. observer = getattr(instance, '_' + self.name, self.default) # We have an instance of a frame, so get obstime obstime = getattr(instance, 'obstime', None) # If the observer is a string and we have obstime then calculate # the position of the observer. if isinstance(observer, str): if obstime is not None: new_observer = self._convert_string_to_coord(observer.lower(), obstime) new_observer.object_name = observer setattr(instance, '_' + self.name, new_observer) else: return observer return super().__get__(instance, frame_cls=frame_cls) sunpy-1.0.3/sunpy/coordinates/tests/0000755000175100001650000000000013531722607020021 5ustar vstsdocker00000000000000sunpy-1.0.3/sunpy/coordinates/tests/test_transformations.py0000644000175100001650000005652013531722525024672 0ustar vstsdocker00000000000000import numpy as np import pytest import astropy import astropy.units as u from astropy.tests.helper import quantity_allclose, assert_quantity_allclose from astropy.coordinates import (SkyCoord, get_body_barycentric, Angle, ConvertError, Longitude, CartesianRepresentation, get_body_barycentric_posvel, CartesianDifferential, SphericalDifferential) # Versions of Astropy that do not have HeliocentricMeanEcliptic have the same frame # with the misleading name HeliocentricTrueEcliptic try: from astropy.coordinates import HeliocentricMeanEcliptic except ImportError: from astropy.coordinates import HeliocentricTrueEcliptic as HeliocentricMeanEcliptic from astropy.time import Time from sunpy.coordinates import (Helioprojective, HeliographicStonyhurst, HeliographicCarrington, Heliocentric, get_earth) from sunpy.coordinates import sun from sunpy.time import parse_time def test_hcc_to_hgs(): ''' Check that a coordinate pointing to the observer in Heliocentric coordinates maps to the lattitude/longitude of the observer in HeliographicStonyhurst coordinates. ''' lat = 10 * u.deg lon = 20 * u.deg observer = HeliographicStonyhurst(lat=lat, lon=lon) hcc_in = Heliocentric(x=0*u.km, y=0*u.km, z=1*u.km, observer=observer) hgs_out = hcc_in.transform_to(HeliographicStonyhurst) assert_quantity_allclose(hgs_out.lat, lat) assert_quantity_allclose(hgs_out.lon, lon) def test_hpc_hpc(): # Use some unphysical values for solar parameters for testing, to make it # easier to calculate expected results. rsun = 1*u.m D0 = 1*u.km L0 = 1*u.deg observer_in = HeliographicStonyhurst(lat=0*u.deg, lon=0*u.deg, radius=D0) observer_out = HeliographicStonyhurst(lat=0*u.deg, lon=L0, radius=D0) hpc_in = Helioprojective(0*u.arcsec, 0*u.arcsec, rsun=rsun, observer=observer_in) hpc_out = Helioprojective(observer=observer_out, rsun=rsun) hpc_new = hpc_in.transform_to(hpc_out) assert hpc_new.observer == hpc_out.observer # Calculate the distance subtended by an angle of L0 from the centre of the # Sun. dd = -1 * rsun * np.tan(L0) # Calculate the angle corresponding to that distance as seen by the new # observer. theta = np.arctan2(dd, (D0 - rsun)) assert quantity_allclose(theta, hpc_new.Tx, rtol=1e-3) def test_hpc_hpc_sc(): # Use some unphysical values for solar parameters for testing, to make it # easier to calculate expected results. rsun = 1*u.m D0 = 1*u.km L0 = 1*u.deg observer_in = HeliographicStonyhurst(lat=0*u.deg, lon=0*u.deg, radius=D0) observer_out = HeliographicStonyhurst(lat=0*u.deg, lon=L0, radius=D0) sc_in = SkyCoord(0*u.arcsec, 0*u.arcsec, rsun=rsun, observer=observer_in, frame='helioprojective') hpc_out = Helioprojective(observer=observer_out, rsun=rsun) hpc_new = sc_in.transform_to(hpc_out) assert hpc_new.observer.lat == hpc_out.observer.lat assert hpc_new.observer.lon == hpc_out.observer.lon assert hpc_new.observer.radius == hpc_out.observer.radius def test_hpc_hpc_null(): hpc_in = Helioprojective(0*u.arcsec, 0*u.arcsec) hpc_out = Helioprojective() hpc_new = hpc_in.transform_to(hpc_out) assert hpc_new is not hpc_in assert quantity_allclose(hpc_new.Tx, hpc_in.Tx) assert quantity_allclose(hpc_new.Ty, hpc_in.Ty) assert hpc_out.observer == hpc_new.observer def test_hcrs_hgs(): # Get the current Earth location in HCRS adate = parse_time('2015/05/01 01:13:00') earth_hcrs = SkyCoord(get_body_barycentric('earth', adate), frame='icrs', obstime=adate).hcrs # Convert from HCRS to HGS earth_hgs = earth_hcrs.transform_to(HeliographicStonyhurst) # The HGS longitude of the Earth should be zero within numerical error # Due to an issue with wrapping at +-360, we shift it to pass the test. assert quantity_allclose((earth_hgs.lon+1*u.deg) % (360*u.deg), 1*u.deg, atol=1e-12*u.deg) # The HGS latitude and radius should be within valid ranges assert quantity_allclose(earth_hgs.lat, 0*u.deg, atol=7.3*u.deg) assert quantity_allclose(earth_hgs.radius, 1*u.AU, atol=0.017*u.AU) def test_hcrs_hgs_array_obstime(): # Get the Earth location in HCRS at two times times = Time(['2017-01-01', '2017-06-01']) earth_hcrs = SkyCoord(get_body_barycentric('earth', times), frame='icrs', obstime=times).hcrs # Transform each time in separate calls (uses scalar obstime) earth_hgs_0 = earth_hcrs[0].transform_to(HeliographicStonyhurst) earth_hgs_1 = earth_hcrs[1].transform_to(HeliographicStonyhurst) # Transform both times in one call (uses array obstime) earth_hgs = earth_hcrs.transform_to(HeliographicStonyhurst) # Confirm that the two approaches produce the same results assert quantity_allclose(earth_hgs_0.lon, earth_hgs[0].lon, atol=1e-12*u.deg) assert quantity_allclose(earth_hgs_0.lat, earth_hgs[0].lat, rtol=1e-10) assert quantity_allclose(earth_hgs_0.radius, earth_hgs[0].radius, rtol=1e-10) assert quantity_allclose(earth_hgs_1.lon, earth_hgs[1].lon, atol=1e-12*u.deg) assert quantity_allclose(earth_hgs_1.lat, earth_hgs[1].lat, rtol=1e-10) assert quantity_allclose(earth_hgs_1.radius, earth_hgs[1].radius, rtol=1e-10) def test_hgs_hcrs(): # This test checks the HGS->HCRS transformation by transforming from HGS to # HeliocentricMeanEcliptic (HME). It will fail if there are errors in Astropy's # HCRS->ICRS or ICRS->HME transformations. # Use published HGS coordinates in the Astronomical Almanac (2013), pages C6-C7 obstime = Time('2013-01-28') earth_hgs = SkyCoord(0*u.deg, -5.73*u.deg, 0.9848139*u.AU, frame=HeliographicStonyhurst, obstime=obstime) # Transform to HME at observation-time equinox earth_hme = earth_hgs.transform_to(HeliocentricMeanEcliptic(equinox=obstime)) # Validate against published values from the Astronomical Almanac (2013), page C6 per page E2 # The dominant source of inaccuracy is the limited precision of the published B0 used above assert quantity_allclose(earth_hme.lon, Angle('308d13m30.51s') - 180*u.deg, atol=5*u.arcsec) assert quantity_allclose(earth_hme.lat, -Angle('-0.27s'), atol=10*u.arcsec) assert quantity_allclose(earth_hme.distance, 0.9848139*u.AU, atol=5e-7*u.AU) def test_hgs_hgc_roundtrip(): obstime = "2011-01-01" hgsin = HeliographicStonyhurst(lat=10*u.deg, lon=20*u.deg, obstime=obstime) hgcout = hgsin.transform_to(HeliographicCarrington(obstime=obstime)) assert_quantity_allclose(hgsin.lat, hgcout.lat) assert_quantity_allclose(hgsin.lon + sun.L0(obstime), hgcout.lon) hgsout = hgcout.transform_to(HeliographicStonyhurst(obstime=obstime)) assert_quantity_allclose(hgsout.lat, hgsin.lat) assert_quantity_allclose(hgsout.lon, hgsin.lon) def test_hgs_cartesian_rep_to_hpc(): # This test checks transformation HGS->HPC when the coordinate is in a Cartesian # representation and that it is the same as a transformation from an HGS frame with a # spherical representation obstime = "2011-01-01" hgscoord_cart = SkyCoord(x=1*u.km, y=0.*u.km, z=0.*u.km, frame=HeliographicStonyhurst(obstime=obstime), representation_type='cartesian') hgscoord_sph = hgscoord_cart.copy() hgscoord_sph.representation_type = 'spherical' hpccoord_cart = hgscoord_cart.transform_to(Helioprojective(obstime=obstime)) hpccoord_sph = hgscoord_sph.transform_to(Helioprojective(obstime=obstime)) assert_quantity_allclose(hpccoord_cart.Tx, hpccoord_sph.Tx) assert_quantity_allclose(hpccoord_cart.Ty, hpccoord_sph.Ty) assert_quantity_allclose(hpccoord_cart.distance, hpccoord_sph.distance) def test_hgs_cartesian_rep_to_hcc(): # This test checks transformation HGS->HCC when the coordinate is in a Cartesian # representation and that it is the same as a transformation from an HGS frame with a # spherical representation obstime = "2011-01-01" hgscoord_cart = SkyCoord(x=1*u.km, y=0.*u.km, z=0.*u.km, frame=HeliographicStonyhurst(obstime=obstime), representation_type='cartesian') hgscoord_sph = hgscoord_cart.copy() hgscoord_sph.representation_type = 'spherical' hcccoord_cart = hgscoord_cart.transform_to(Heliocentric(obstime=obstime)) hcccoord_sph = hgscoord_sph.transform_to(Heliocentric(obstime=obstime)) assert_quantity_allclose(hcccoord_cart.x, hcccoord_sph.x) assert_quantity_allclose(hcccoord_cart.y, hcccoord_sph.y) assert_quantity_allclose(hcccoord_cart.z, hcccoord_sph.z) def test_hgs_cartesian_rep_to_hgc(): # This test checks transformation HGS->HCC when the coordinate is in a Cartesian # representation and that it is the same as a transformation from an HGS frame with a # spherical representation obstime = "2011-01-01" hgscoord_cart = SkyCoord(x=1*u.km, y=0.*u.km, z=0.*u.km, frame=HeliographicStonyhurst(obstime=obstime), representation_type='cartesian') hgscoord_sph = hgscoord_cart.copy() hgscoord_sph.representation_type = 'spherical' # HGC hgccoord_cart = hgscoord_cart.transform_to(HeliographicCarrington(obstime=obstime)) hgccoord_sph = hgscoord_sph.transform_to(HeliographicCarrington(obstime=obstime)) assert_quantity_allclose(hgccoord_cart.lat, hgccoord_sph.lat) assert_quantity_allclose(hgccoord_cart.lon, hgccoord_sph.lon) assert_quantity_allclose(hgccoord_cart.radius, hgccoord_sph.radius) def test_hcc_to_hpc_different_observer(): # This test checks transformation HCC->HPC in the case where the HCC and HPC frames are # defined by different observers. rsun = 1*u.m D0 = 1*u.km L0 = 1*u.deg observer_1 = HeliographicStonyhurst(lat=0*u.deg, lon=0*u.deg, radius=D0) observer_2 = HeliographicStonyhurst(lat=0*u.deg, lon=L0, radius=D0) hcc_frame = Heliocentric(observer=observer_1) hpc_frame = Helioprojective(observer=observer_2) hcccoord = SkyCoord(x=rsun, y=rsun, z=rsun, frame=hcc_frame) hpccoord_out = hcccoord.transform_to(hpc_frame) hpccoord_expected = hcccoord.transform_to(HeliographicStonyhurst).transform_to(hpc_frame) assert_quantity_allclose(hpccoord_out.Tx, hpccoord_expected.Tx) assert_quantity_allclose(hpccoord_out.Ty, hpccoord_expected.Ty) assert_quantity_allclose(hpccoord_out.distance, hpccoord_expected.distance) def test_hpc_to_hcc_different_observer(): # This test checks transformation HPC->HCC in the case where the HCC and HPC frames are # defined by different observers. rsun = 1*u.m D0 = 1*u.km L0 = 1*u.deg observer_1 = HeliographicStonyhurst(lat=0*u.deg, lon=0*u.deg, radius=D0) observer_2 = HeliographicStonyhurst(lat=0*u.deg, lon=L0, radius=D0) hcc_frame = Heliocentric(observer=observer_1) hpc_frame = Helioprojective(observer=observer_2, rsun=rsun) hpccoord = SkyCoord(Tx=0*u.arcsec, Ty=0*u.arcsec, frame=hpc_frame) hcccoord_out = hpccoord.transform_to(hcc_frame) hcccoord_expected = hpccoord.transform_to(HeliographicStonyhurst).transform_to(hcc_frame) assert_quantity_allclose(hcccoord_out.x, hcccoord_expected.x) assert_quantity_allclose(hcccoord_out.y, hcccoord_expected.y) assert_quantity_allclose(hcccoord_out.z, hcccoord_expected.z) def test_hcc_to_hpc_same_observer(): # This test checks transformation HCC->HPC in the case of same observer rsun = 1*u.m D0 = 1*u.km observer = HeliographicStonyhurst(lat=0*u.deg, lon=0*u.deg, radius=D0) hcc_frame = Heliocentric(observer=observer) hpc_frame = Helioprojective(observer=observer, rsun=rsun) hcccoord = SkyCoord(x=rsun, y=rsun, z=rsun, frame=hcc_frame) hpccoord_out = hcccoord.transform_to(hpc_frame) hpccoord_expected = hcccoord.transform_to(HeliographicStonyhurst).transform_to(hpc_frame) assert_quantity_allclose(hpccoord_out.Tx, hpccoord_expected.Tx) assert_quantity_allclose(hpccoord_out.Ty, hpccoord_expected.Ty) assert_quantity_allclose(hpccoord_out.distance, hpccoord_expected.distance) def test_hpc_to_hcc_same_observer(): # This test checks transformation HPC->HCC in the case of same observer rsun = 1*u.m D0 = 1 * u.km observer = HeliographicStonyhurst(lat=0 * u.deg, lon=0 * u.deg, radius=D0) hcc_frame = Heliocentric(observer=observer) hpc_frame = Helioprojective(observer=observer, rsun=rsun) hpccoord = SkyCoord(Tx=0 * u.arcsec, Ty=0 * u.arcsec, frame=hpc_frame) hcccoord_out = hpccoord.transform_to(hcc_frame) hcccoord_expected = hpccoord.transform_to(HeliographicStonyhurst).transform_to(hcc_frame) assert_quantity_allclose(hcccoord_out.x, hcccoord_expected.x) assert_quantity_allclose(hcccoord_out.y, hcccoord_expected.y) assert_quantity_allclose(hcccoord_out.z, hcccoord_expected.z) def test_hpc_hcc_different_observer_radius(): # Tests HPC->HCC with a change in observer at different distances from the Sun observer1 = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU) hpc = Helioprojective(0*u.arcsec, 0*u.arcsec, 0.5*u.AU, observer=observer1) observer2 = HeliographicStonyhurst(90*u.deg, 0*u.deg, 0.75*u.AU) hcc = hpc.transform_to(Heliocentric(observer=observer2)) assert_quantity_allclose(hcc.x, -0.5*u.AU) assert_quantity_allclose(hcc.y, 0*u.AU, atol=1e-10*u.AU) assert_quantity_allclose(hcc.z, 0*u.AU, atol=1e-10*u.AU) def test_hgs_hgs(): # Test HGS loopback transformation obstime = Time('2001-01-01') old = SkyCoord(90*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicStonyhurst(obstime=obstime)) new = old.transform_to(HeliographicStonyhurst(obstime=obstime + 1*u.day)) assert_quantity_allclose(new.lon, old.lon - 1*u.deg, atol=0.1*u.deg) # due to Earth motion assert_quantity_allclose(new.lat, old.lat, atol=1e-3*u.deg) assert_quantity_allclose(new.radius, old.radius, atol=1e-5*u.AU) def test_hgc_hgc(): # Test HGC loopback transformation obstime = Time('2001-01-01') old = SkyCoord(90*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicCarrington(obstime=obstime)) new = old.transform_to(HeliographicCarrington(obstime=obstime + 1*u.day)) assert_quantity_allclose(new.lon, old.lon - 14.1844*u.deg, atol=1e-4*u.deg) # solar rotation assert_quantity_allclose(new.lat, old.lat, atol=1e-4*u.deg) assert_quantity_allclose(new.radius, old.radius, atol=1e-5*u.AU) def test_hcc_hcc(): # Test same observer and changing obstime observer = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-02-01') from_hcc = Heliocentric(0.2*u.AU, 0.3*u.AU, 0.4*u.AU, observer=observer, obstime='2001-01-01') to_hcc = from_hcc.transform_to(Heliocentric(observer=observer, obstime='2001-03-31')) # Since the observer is the same, the coordinates should be nearly the same but not exactly # equal due to motion of the origin (the Sun) assert np.all(from_hcc.cartesian.xyz != to_hcc.cartesian.xyz) assert_quantity_allclose(from_hcc.cartesian.xyz, to_hcc.cartesian.xyz, rtol=2e-3) # Test changing observer and same obstime observer1 = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-01-01') observer2 = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-03-31') from_hcc = Heliocentric(0.2*u.AU, 0.3*u.AU, 0.4*u.AU, observer=observer1, obstime='2001-02-01') to_hcc = from_hcc.transform_to(Heliocentric(observer=observer2, obstime='2001-02-01')) # This change in observer is approximately a 90-degree rotation about the Y axis assert_quantity_allclose(to_hcc.x, -from_hcc.z, rtol=2e-3) assert_quantity_allclose(to_hcc.y, from_hcc.y, rtol=2e-3) assert_quantity_allclose(to_hcc.z, from_hcc.x, rtol=2e-3) def test_hcc_hgs_observer_mismatch(): # Test whether the transformation gives the same answer regardless of what obstime the observer # coordinate is represented in observer1 = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-01-01') observer2 = observer1.transform_to(HeliographicStonyhurst(obstime='2001-03-31')) hcc1 = Heliocentric(0.2*u.AU, 0.3*u.AU, 0.4*u.AU, observer=observer1, obstime=observer1.obstime) hgs1 = hcc1.transform_to(HeliographicStonyhurst(obstime=hcc1.obstime)) hcc2 = Heliocentric(0.2*u.AU, 0.3*u.AU, 0.4*u.AU, observer=observer2, obstime=observer1.obstime) hgs2 = hcc2.transform_to(HeliographicStonyhurst(obstime=hcc2.obstime)) assert_quantity_allclose(hgs1.lon, hgs2.lon) assert_quantity_allclose(hgs1.lat, hgs2.lat) assert_quantity_allclose(hgs1.radius, hgs2.radius) def test_hgs_hcc_observer_mismatch(): # Test whether the transformation gives the same answer regardless of what obstime the observer # coordinate is represented in observer1 = HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU, obstime='2001-01-01') observer2 = observer1.transform_to(HeliographicStonyhurst(obstime='2001-03-31')) hgs = HeliographicStonyhurst(20*u.deg, 40*u.deg, 0.5*u.AU, obstime=observer1.obstime) hcc1 = hgs.transform_to(Heliocentric(observer=observer1, obstime=hgs.obstime)) hcc2 = hgs.transform_to(Heliocentric(observer=observer2, obstime=hgs.obstime)) assert_quantity_allclose(hcc1.cartesian.xyz, hcc2.cartesian.xyz) def test_hgs_hcrs_sunspice(): # Compare our HGS->HCRS transformation against SunSPICE by transforming beyond it # "HEQ" is another name for HEEQ, which is equivalent to Heliographic Stonyhurst # "HAE" is equivalent to Astropy's Heliocentric Mean Ecliptic, and defaults to J2000.0 # # IDL> coord = [1.d, 0.d, 10.d] # IDL> convert_sunspice_lonlat, '2019-06-01', coord, 'HEQ', 'HAE', /au, /degrees # IDL> print, coord # 1.0000000 -108.65371 10.642778 old = SkyCoord(0*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicStonyhurst(obstime='2019-06-01')) new = old.transform_to(HeliocentricMeanEcliptic) assert_quantity_allclose(new.lon, Longitude(-108.65371*u.deg), atol=0.1*u.arcsec, rtol=0) assert_quantity_allclose(new.lat, 10.642778*u.deg, atol=0.1*u.arcsec, rtol=0) assert_quantity_allclose(new.distance, old.radius) # Transform to HAE precessed to the mean ecliptic of date instead of J2000.0 # IDL> coord = [1.d, 0.d, 10.d] # IDL> convert_sunspice_lonlat, '2019-06-01', coord, 'HEQ', 'HAE', /precess, /au, /degrees # IDL> print, coord # 1.0000000 -108.38240 10.640314 new = old.transform_to(HeliocentricMeanEcliptic(equinox='2019-06-01')) assert_quantity_allclose(new.lon, Longitude(-108.38240*u.deg), atol=0.1*u.arcsec, rtol=0) assert_quantity_allclose(new.lat, 10.640314*u.deg, atol=0.1*u.arcsec, rtol=0) assert_quantity_allclose(new.distance, old.radius) def test_hgs_hgc_sunspice(): # Compare our HGS->HGC transformation against SunSPICE # "HEQ" is another name for HEEQ, which is equivalent to Heliographic Stonyhurst # "Carrington" is offset by 0.076 degrees in longitude from our Heliographic Carrington (HGC) # because "Carrington" does not include light travel time to the observer, while our # HGC includes the light travel time to Earth (see Seidelmann et al. 2007). # # IDL> coord = [1.d, 0.d, 10.d] # IDL> convert_sunspice_lonlat, '2019-06-01', coord, 'HEQ', 'Carrington', /au, /degrees # IDL> print, coord # 1.0000000 16.688242 10.000000 old = SkyCoord(0*u.deg, 10*u.deg, 1*u.AU, frame=HeliographicStonyhurst(obstime='2019-06-01')) new = old.heliographic_carrington assert_quantity_allclose(new.lon, 16.688242*u.deg + 0.076*u.deg, atol=1e-2*u.arcsec, rtol=0) assert_quantity_allclose(new.lat, old.lat) assert_quantity_allclose(new.radius, old.radius) def test_hgs_hcc_sunspice(): # Compare our HGS->HCC transformation against SunSPICE # "HEQ" is another name for HEEQ, which is equivalent to Heliographic Stonyhurst # "HGRTN" is equivalent to our Heliocentric, but with the axes permuted # SunSPICE, like us, assumes an Earth observer if not explicitly specified # # IDL> coord = [7d5, 8d5, 9d5] # IDL> convert_sunspice_coord, '2019-06-01', coord, 'HEQ', 'HGRTN' # Assuming Earth observation # IDL> print, coord # 688539.32 800000.00 908797.89 old = SkyCoord(CartesianRepresentation([7e5, 8e5, 9e5]*u.km), frame=HeliographicStonyhurst(obstime='2019-06-01')) new = old.heliocentric assert_quantity_allclose(new.x, 800000.00*u.km, atol=1e-2*u.km) assert_quantity_allclose(new.y, 908797.89*u.km, atol=1e-2*u.km) assert_quantity_allclose(new.z, 688539.32*u.km, atol=1e-2*u.km) def test_hpc_hgs_implicit_hcc(): # An HPC->HGS transformation should give the same answer whether the transformation step # through HCC is implicit or explicit start = SkyCoord(0*u.arcsec, 0*u.arcsec, 0.5*u.AU, frame=Helioprojective(obstime='2019-06-01', observer='earth')) frame = HeliographicStonyhurst(obstime='2019-12-01') implicit = start.transform_to(frame) explicit1 = start.transform_to(Heliocentric(obstime=start.obstime, observer='earth')).\ transform_to(frame) explicit2 = start.transform_to(Heliocentric(obstime=frame.obstime, observer='earth')).\ transform_to(frame) assert_quantity_allclose(implicit.separation_3d(explicit1), 0*u.AU, atol=1e-10*u.AU) assert_quantity_allclose(implicit.separation_3d(explicit2), 0*u.AU, atol=1e-10*u.AU) @pytest.mark.skipif(astropy.__version__ < '3.2.0', reason="Not supported by Astropy <3.2") def test_velocity_hcrs_hgs(): # Obtain the position/velocity of Earth in ICRS obstime = Time(['2019-01-01', '2019-04-01', '2019-07-01', '2019-10-01']) pos, vel = get_body_barycentric_posvel('earth', obstime) loc = pos.with_differentials(vel.represent_as(CartesianDifferential)) earth = SkyCoord(loc, frame='icrs', obstime=obstime) # The velocity of Earth in HGS should be very close to zero. The velocity in the HGS Y # direction is slightly further away from zero because there is true latitudinal motion. new = earth.heliographic_stonyhurst assert_quantity_allclose(new.velocity.d_x, 0*u.km/u.s, atol=1e-15*u.km/u.s) assert_quantity_allclose(new.velocity.d_y, 0*u.km/u.s, atol=1e-14*u.km/u.s) assert_quantity_allclose(new.velocity.d_x, 0*u.km/u.s, atol=1e-15*u.km/u.s) # Test the loopback to ICRS newer = new.icrs assert_quantity_allclose(newer.velocity.d_x, vel.x) assert_quantity_allclose(newer.velocity.d_y, vel.y) assert_quantity_allclose(newer.velocity.d_z, vel.z) def test_velocity_hgs_hgc(): # Construct a simple HGS coordinate with zero velocity obstime = Time(['2019-01-01', '2019-04-01', '2019-07-01', '2019-10-01']) pos = CartesianRepresentation(1, 0, 0)*u.AU vel = CartesianDifferential(0, 0, 0)*u.km/u.s loc = (pos.with_differentials(vel))._apply('repeat', obstime.size) coord = SkyCoord(HeliographicStonyhurst(loc, obstime=obstime)) # The induced velocity in HGC should be entirely longitudinal, and approximately equal to one # full rotation every mean synodic period (27.2753 days) new = coord.heliographic_carrington new_vel = new.data.differentials['s'].represent_as(SphericalDifferential, new.data) assert_quantity_allclose(new_vel.d_lon, -360*u.deg / (27.27253*u.day), rtol=1e-2) assert_quantity_allclose(new_vel.d_lat, 0*u.deg/u.s) assert_quantity_allclose(new_vel.d_distance, 0*u.km/u.s, atol=1e-7*u.km/u.s) sunpy-1.0.3/sunpy/coordinates/tests/test_sun.py0000644000175100001650000002716413531722525022250 0ustar vstsdocker00000000000000import pytest from numpy.testing import assert_allclose from astropy.coordinates import Angle, EarthLocation, SkyCoord from astropy.time import Time import astropy.units as u from astropy.tests.helper import assert_quantity_allclose from astropy.utils.exceptions import ErfaWarning from sunpy.coordinates import sun @pytest.fixture def t1(): return Time('1992-10-13', scale='tdb') @pytest.fixture def t2(): return Time('2013-06-17', scale='tt') def test_true_longitude(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, C16) assert_quantity_allclose(sun.true_longitude(t1), Angle('199d54m26.17s'), atol=0.005*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, C12) assert_quantity_allclose(sun.true_longitude(t2), Angle('85d58m57.46s'), atol=0.005*u.arcsec) def test_apparent_longitude(t1, t2): aberration_1au = Angle('20.496s') # Validate against a published value from the Astronomical Almanac (1992, C2+C16+B30) assert_quantity_allclose(sun.apparent_longitude(t1), Angle('199d54m26.17s') + Angle('15.908s') - aberration_1au / 0.9976085, atol=0.005*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, C2+C12+B61) assert_quantity_allclose(sun.apparent_longitude(t2), Angle('85d58m57.46') + Angle('12.0489s') - aberration_1au / 1.0159149, atol=0.005*u.arcsec) def test_true_latitude(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, C16) # Disagreement with published precision may be due to changes in definitions after 1992 assert_quantity_allclose(sun.true_latitude(t1), Angle('0.72s'), atol=0.05*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, C12) assert_quantity_allclose(sun.true_latitude(t2), Angle('-0.42s'), atol=0.005*u.arcsec) def test_apparent_latitude(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, C2+C16) # Disagreement with published precision may be due to changes in definitions after 1992 assert_quantity_allclose(sun.apparent_latitude(t1), Angle('0.72s'), atol=0.05*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, C2+C12) assert_quantity_allclose(sun.apparent_latitude(t2), Angle('-0.42s'), atol=0.005*u.arcsec) def test_angular_radius(): # Regression-only test # The Astronomical Almanac publishes values, but I don't know what physical radius they use assert_quantity_allclose(sun.angular_radius("2012/11/11"), 968.871*u.arcsec, atol=1e-3*u.arcsec) with pytest.warns(ErfaWarning): assert_quantity_allclose(sun.angular_radius("2043/03/01"), 968.326*u.arcsec, atol=1e-3*u.arcsec) assert_quantity_allclose(sun.angular_radius("2001/07/21"), 944.039*u.arcsec, atol=1e-3*u.arcsec) def test_mean_obliquity_of_ecliptic(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, B30) # Note that the publication date pre-dates the IAU 2006 definition of obliquity assert_quantity_allclose(sun.mean_obliquity_of_ecliptic(t1), Angle('23d26m24.519s') + 0.308*u.arcsec, atol=0.05*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, B61) assert_quantity_allclose(sun.mean_obliquity_of_ecliptic(t2), Angle('23d26m08.0875s') + 7.0153*u.arcsec, atol=0.00005*u.arcsec) def test_true_rightascension(): # Regression-only test assert_quantity_allclose(sun.true_rightascension("2012/11/11"), 226.548*u.deg, atol=1e-3*u.deg) with pytest.warns(ErfaWarning): assert_quantity_allclose(sun.true_rightascension("2142/02/03"), 316.466*u.deg, atol=1e-3*u.deg) assert_quantity_allclose(sun.true_rightascension("2013/12/11"), 258.150*u.deg, atol=1e-3*u.deg) def test_true_rightascension_J2000(t1, t2): # Validate against JPL HORIZONS output # https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&TABLE_TYPE=OBSERVER&COMMAND=10&CENTER=500&QUANTITIES=1&ANG_FORMAT=HMS&EXTRA_PREC=YES&TIME_TYPE=TT&TLIST=2448908.5%0A2456460.5 assert_quantity_allclose(sun.true_rightascension(t1, equinox_of_date=False), Angle('13h13m53.65s'), atol=Angle('0h0m0.005s')) assert_quantity_allclose(sun.true_rightascension(t2, equinox_of_date=False), Angle('05h41m40.32s'), atol=Angle('0h0m0.005s')) def test_true_declination(): # Regression-only test assert_quantity_allclose(sun.true_declination("2012/11/11"), -17.470*u.deg, atol=1e-3*u.deg) with pytest.warns(ErfaWarning): assert_quantity_allclose(sun.true_declination("2245/12/01"), -21.717*u.deg, atol=1e-3*u.deg) assert_quantity_allclose(sun.true_declination("2014/05/27"), 21.245*u.deg, atol=1e-3*u.deg) def test_true_declination_J2000(t1, t2): # Validate against JPL HORIZONS output # https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&TABLE_TYPE=OBSERVER&COMMAND=10&CENTER=500&QUANTITIES=1&ANG_FORMAT=HMS&EXTRA_PREC=YES&TIME_TYPE=TT&TLIST=2448908.5%0A2456460.5 assert_quantity_allclose(sun.true_declination(t1, equinox_of_date=False), Angle('-7d49m20.84s'), atol=0.005*u.arcsec) assert_quantity_allclose(sun.true_declination(t2, equinox_of_date=False), Angle('23d22m13.97s'), atol=0.005*u.arcsec) def test_true_obliquity_of_ecliptic(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, B30) # Note that the publication date pre-dates the IAU 2006 definition of obliquity assert_quantity_allclose(sun.true_obliquity_of_ecliptic(t1), Angle('23d26m24.519s'), atol=0.05*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, B61) assert_quantity_allclose(sun.true_obliquity_of_ecliptic(t2), Angle('23d26m08.0875'), atol=0.00005*u.arcsec) def test_apparent_rightascension(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, C16) assert_quantity_allclose(sun.apparent_rightascension(t1), Angle('13h13m30.75s'), atol=Angle('0h0m0.005s')) # Validate against a published value from the Astronomical Almanac (2013, C12) assert_quantity_allclose(sun.apparent_rightascension(t2), Angle('5h42m28.88s'), atol=Angle('0h0m0.005s')) def test_apparent_rightascension_J2000(t1): # Regression-only test assert_quantity_allclose(sun.apparent_rightascension(t1, equinox_of_date=False), Angle('13h13m52.37s'), atol=Angle('0h0m0.005s')) def test_apparent_declination(t1, t2): # Validate against a published value from the Astronomical Almanac (1992, C16) assert_quantity_allclose(sun.apparent_declination(t1), Angle('-7d47m01.7s'), atol=0.05*u.arcsec) # Validate against a published value from the Astronomical Almanac (2013, C12) assert_quantity_allclose(sun.apparent_declination(t2), Angle('23d22m27.8s'), atol=0.05*u.arcsec) def test_apparent_declination_J2000(t1): # Regression-only test assert_quantity_allclose(sun.apparent_declination(t1, equinox_of_date=False), Angle('-7d49m13.1s'), atol=0.05*u.arcsec) def test_sky_position(t1, t2): pos1 = sun.sky_position(t1) ra1 = sun.apparent_rightascension(t1) dec1 = sun.apparent_declination(t1) assert_quantity_allclose(pos1, (ra1, dec1)) pos2 = sun.sky_position(t2, equinox_of_date=False) ra2 = sun.apparent_rightascension(t2, equinox_of_date=False) dec2 = sun.apparent_declination(t2, equinox_of_date=False) assert_quantity_allclose(pos2, (ra2, dec2)) def test_print_params(): # Test only for any issues with printing; accuracy is covered by other tests sun.print_params() def test_B0(): # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191) assert_quantity_allclose(sun.B0('1992-Oct-13'), 5.99*u.deg, atol=5e-3*u.deg) def test_B0_array_time(): # Validate against published values from the Astronomical Almanac (2013) sun_B0 = sun.B0(Time(['2013-04-01', '2013-12-01'], scale='tt')) assert_quantity_allclose(sun_B0[0], -6.54*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(sun_B0[1], 0.88*u.deg, atol=5e-3*u.deg) def test_L0(): # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191) assert_quantity_allclose(sun.L0('1992-Oct-13'), 238.63*u.deg, atol=2e-2*u.deg) def test_L0_array_time(): # Validate against published values from the Astronomical Almanac (2013) sun_L0 = sun.L0(Time(['2013-04-01', '2013-12-01'], scale='tt')) assert_quantity_allclose(sun_L0[0], 221.44*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(sun_L0[1], 237.83*u.deg, atol=5e-3*u.deg) def test_P(): # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191) assert_quantity_allclose(sun.P('1992-Oct-13'), 26.27*u.deg, atol=5e-3*u.deg) def test_P_array_time(): # Validate against published values from the Astronomical Almanac (2013) sun_P = sun.P(Time(['2013-04-01', '2013-12-01'], scale='tt')) assert_quantity_allclose(sun_P[0], -26.15*u.deg, atol=1e-2*u.deg) assert_quantity_allclose(sun_P[1], 16.05*u.deg, atol=1e-2*u.deg) def test_earth_distance(): # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191) assert_quantity_allclose(sun.earth_distance('1992-Oct-13'), 0.997608*u.AU, atol=5e-7*u.AU) def test_earth_distance_array_time(): # Validate against published values from the Astronomical Almanac (2013) sunearth_distance = sun.earth_distance(Time(['2013-04-01', '2013-12-01'], scale='tt')) assert_quantity_allclose(sunearth_distance[0], 0.9992311*u.AU, rtol=0, atol=5e-8*u.AU) assert_quantity_allclose(sunearth_distance[1], 0.9861362*u.AU, rtol=0, atol=5e-8*u.AU) def test_orientation(): # Not currently aware of a published value to check against, so just self-check for now # Check the Northern Hemisphere angle = sun.orientation(EarthLocation(lat=40*u.deg, lon=-75*u.deg), '2017-07-18 12:00') assert_quantity_allclose(angle, -59.4*u.deg, atol=0.1*u.deg) # Check the Southern Hemisphere angle = sun.orientation(EarthLocation(lat=-40*u.deg, lon=-75*u.deg), '2017-02-18 13:00') assert_quantity_allclose(angle, -110.8*u.deg, atol=0.1*u.deg) # Validate against published values from the Astronomical Almanac (2013, C4) @pytest.mark.parametrize("date, day_fraction, rotation_number", [('2012-12-29', 0.49, 2132), ('2013-01-25', 0.83, 2133), ('2013-02-22', 0.17, 2134), ('2013-03-21', 0.49, 2135), ('2013-04-17', 0.78, 2136), ('2013-05-15', 0.02, 2137), ('2013-06-11', 0.22, 2138), ('2013-07-08', 0.42, 2139), ('2013-08-04', 0.63, 2140), ('2013-08-31', 0.87, 2141), ('2013-09-28', 0.14, 2142), ('2013-10-25', 0.43, 2143), ('2013-11-21', 0.73, 2144), ('2013-12-19', 0.05, 2145), ('2014-01-15', 0.38, 2146), ('2014-02-11', 0.73, 2147)]) def test_carrington_rotation_number(date, day_fraction, rotation_number): assert_allclose(sun.carrington_rotation_number(Time(date, scale='tt') + day_fraction*u.day), rotation_number, rtol=0, atol=2e-4) sunpy-1.0.3/sunpy/coordinates/tests/test_frames.py0000644000175100001650000003624513531722525022720 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import numpy as np import pytest from sunpy.time import parse_time import astropy.units as u from astropy.tests.helper import assert_quantity_allclose from astropy.coordinates import (UnitSphericalRepresentation, SphericalRepresentation, CartesianRepresentation, SkyCoord) from ... import sun from ..frames import (Helioprojective, HeliographicStonyhurst, Heliocentric, HeliographicCarrington) RSUN_METERS = sun.constants.get('radius').si.to(u.m) DSUN_METERS = sun.constants.get('mean distance').si.to(u.m) def init_frame(frame, args, kwargs): if args and kwargs: return frame(*args, **kwargs) elif args: return frame(*args) elif kwargs: return frame(**kwargs) """ These are common 2D params, kwargs are frame specific """ two_D_parameters = [ ([0 * u.deg, 0 * u.arcsec], None), ([0 * u.deg, 0 * u.arcsec], {'obstime': '2011/01/01T00:00:00'}), ([0 * u.deg, 0 * u.arcsec], {'representation_type': 'unitspherical'}), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], None), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], None), ( [UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], {'obstime': '2011/01/01T00:00:00'}) ] """ These are common 3D params, kwargs are frame specific """ three_D_parameters = [ ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], None), ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], {'obstime': '2011/01/01T00:00:00'}), ([0 * u.deg, 0 * u.arcsec, 1 * u.Mm], {'representation_type': 'spherical'}), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], None), ( [SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1 * u.Mm)], {'obstime': '2011/01/01T00:00:00'}) ] # ============================================================================== # Helioprojective Tests # ============================================================================== @pytest.mark.parametrize('args, kwargs', two_D_parameters + [(None, {'Tx': 0 * u.deg, 'Ty': 0 * u.arcsec})]) def test_create_hpc_2d(args, kwargs): hpc1 = init_frame(Helioprojective, args, kwargs) # Check we have the right class! assert isinstance(hpc1, Helioprojective) rep_kwarg = kwargs.get('representation_type', None) if kwargs else None if rep_kwarg and rep_kwarg == 'unitspherical': # Check that we have a unitspherical representation assert isinstance(hpc1._data, UnitSphericalRepresentation) else: # Check that we have a 2D wrap180 representation assert isinstance(hpc1._data, UnitSphericalRepresentation) # Check the attrs are correct assert hpc1.Tx == 0 * u.arcsec assert hpc1.Ty == 0 * u.arcsec # Check the attrs are in the correct default units assert hpc1.Tx.unit is u.arcsec assert hpc1.Ty.unit is u.arcsec @pytest.mark.parametrize( 'args, kwargs', three_D_parameters + [(None, {'Tx': 0 * u.deg, 'Ty': 0 * u.arcsec, 'distance': 1 * u.Mm}), ([0 * u.deg, 0 * u.arcsec], {'distance': 1 * u.Mm})]) def test_create_3d(args, kwargs): hpc1 = init_frame(Helioprojective, args, kwargs) # Check we have the right class! assert isinstance(hpc1, Helioprojective) rep_kwarg = kwargs.get('representation_type', None) if kwargs else None if rep_kwarg and rep_kwarg == 'spherical': # Check that we have a unitspherical representation assert isinstance(hpc1._data, SphericalRepresentation) else: # Check that we have a 2D wrap180 representation assert isinstance(hpc1._data, SphericalRepresentation) # Check the attrs are correct assert hpc1.Tx == 0 * u.arcsec assert hpc1.Ty == 0 * u.arcsec assert hpc1.distance == 1 * u.Mm # Check the attrs are in the correct default units assert hpc1.Tx.unit is u.arcsec assert hpc1.Ty.unit is u.arcsec assert hpc1.distance.unit is u.Mm def test_cart_init(): hpc1 = Helioprojective(CartesianRepresentation(0 * u.km, 0 * u.km, 1 * u.Mm)) assert isinstance(hpc1, Helioprojective) assert isinstance(hpc1._data, CartesianRepresentation) # Test HPC Calculate Distance def test_hpc_distance(): hpc1 = Helioprojective(0 * u.deg, 0 * u.arcsec, observer=HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU)) assert isinstance(hpc1, Helioprojective) # Check that we have a 2D wrap180 representation assert isinstance(hpc1._data, UnitSphericalRepresentation) # Check the attrs are correct assert hpc1.Tx == 0 * u.arcsec assert hpc1.Ty == 0 * u.arcsec hpc2 = hpc1.calculate_distance() assert isinstance(hpc2._data, SphericalRepresentation) # Check the attrs are correct assert hpc2.Tx == 0 * u.arcsec assert hpc2.Ty == 0 * u.arcsec assert_quantity_allclose(hpc2.distance, DSUN_METERS - RSUN_METERS) def test_hpc_distance_cartesian(): # Test detection of distance in other representations hpc1 = Helioprojective(CartesianRepresentation(0 * u.km, 0 * u.km, 1 * u.Mm)) assert isinstance(hpc1, Helioprojective) assert isinstance(hpc1._data, CartesianRepresentation) assert hpc1.calculate_distance() is hpc1 def test_hpc_distance_off_limb(): hpc1 = Helioprojective(1500 * u.arcsec, 0 * u.arcsec, observer=HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU)) assert isinstance(hpc1, Helioprojective) # Check that we have a 2D wrap180 representation assert isinstance(hpc1._data, UnitSphericalRepresentation) # Check the attrs are correct assert hpc1.Tx == 1500 * u.arcsec assert hpc1.Ty == 0 * u.arcsec hpc2 = hpc1.calculate_distance() assert isinstance(hpc2._data, SphericalRepresentation) # Check the attrs are correct assert hpc2.Tx == 1500 * u.arcsec assert hpc2.Ty == 0 * u.arcsec assert_quantity_allclose(hpc2.distance, u.Quantity(np.nan, u.km)) def test_hpc_distance_3D(): hpc1 = Helioprojective(1500 * u.arcsec, 0 * u.arcsec, 100 * u.Mm) assert isinstance(hpc1, Helioprojective) # Check that we have a 2D wrap180 representation assert isinstance(hpc1._data, SphericalRepresentation) # Check the attrs are correct assert hpc1.Tx == 1500 * u.arcsec assert hpc1.Ty == 0 * u.arcsec hpc2 = hpc1.calculate_distance() assert hpc2 is hpc1 def test_wrapping_on(): hpc1 = Helioprojective(359.9*u.deg, 10*u.deg) assert_quantity_allclose(hpc1.Tx, -0.1*u.deg) assert_quantity_allclose(hpc1.Tx.wrap_angle, 180*u.deg) def test_wrapping_off(): hpc1 = Helioprojective(359.9*u.deg, 10*u.deg, wrap_longitude=False) assert_quantity_allclose(hpc1.Tx, 359.9*u.deg) assert_quantity_allclose(hpc1.Tx.wrap_angle, 360*u.deg) def test_hpc_default_observer(): # Observer is considered default if it hasn't been specified *and* if obstime isn't specified hpc = Helioprojective(0*u.arcsec, 0*u.arcsec) assert hpc.is_frame_attr_default('observer') hpc = Helioprojective(0*u.arcsec, 0*u.arcsec, obstime='2019-06-01') assert not hpc.is_frame_attr_default('observer') # ============================================================================== # ## Heliographic Tests # ============================================================================== def test_HEE_creation(): # Smoke test to make sure HEE constructors work fine _ = HeliographicStonyhurst(lon=0*u.deg, lat=90*u.deg, obstime=parse_time('2018-12-21')) _ = HeliographicStonyhurst(lon=0*u.deg, lat=90*u.deg, radius=1*u.km, obstime=parse_time('2018-12-21')) _ = HeliographicStonyhurst(x=1*u.km, y=1*u.km, z=1*u.km, obstime=parse_time('2018-12-21'), representation_type='cartesian') @pytest.mark.parametrize('frame', [HeliographicStonyhurst, HeliographicCarrington]) @pytest.mark.parametrize("args, kwargs", two_D_parameters[:2] + two_D_parameters[4:] + [(None, {'lat': 0*u.deg, 'lon': 0*u.arcsec})]) def test_create_hgs_2d(frame, args, kwargs): hgs1 = init_frame(frame, args, kwargs) # Check we have the right class! assert isinstance(hgs1, frame) # Check Carrington first because it's a subclass of Stonyhurst if isinstance(hgs1, HeliographicCarrington): # Check that we have a 2D wrap180 representation assert isinstance(hgs1._data, SphericalRepresentation) elif isinstance(hgs1, HeliographicStonyhurst): # Check that we have a 2D wrap180 representation assert isinstance(hgs1._data, SphericalRepresentation) # Check the attrs are correct assert hgs1.lon == 0 * u.deg assert hgs1.lat == 0 * u.deg # Check the attrs are in the correct default units assert hgs1.lon.unit is u.deg assert hgs1.lat.unit is u.deg assert hgs1.radius.unit is u.km @pytest.mark.parametrize('frame', [HeliographicStonyhurst, HeliographicCarrington]) @pytest.mark.parametrize("args, kwargs", two_D_parameters[2:4]) def test_create_hgs_force_2d(frame, args, kwargs): hgs1 = init_frame(frame, args, kwargs) # Check we have the right class! assert isinstance(hgs1, frame) rep_kwarg = kwargs.get('representation_type', None) if kwargs else None if rep_kwarg == 'unitspherical': assert isinstance(hgs1._data, UnitSphericalRepresentation) # Check the attrs are correct assert hgs1.lon == 0 * u.deg assert hgs1.lat == 0 * u.deg # Check the attrs are in the correct default units assert hgs1.lon.unit is u.deg assert hgs1.lat.unit is u.deg @pytest.mark.parametrize('frame', [HeliographicStonyhurst, HeliographicCarrington]) @pytest.mark.parametrize( "args, kwargs", three_D_parameters + [(None, {'lat': 0 * u.deg, 'lon': 0 * u.arcsec, 'radius': 1 * u.Mm}), ([0 * u.deg, 0 * u.arcsec], {'radius': 1 * u.Mm})]) def test_create_hgs_3d(frame, args, kwargs): hgs1 = init_frame(frame, args, kwargs) # Check we have the right class! assert isinstance(hgs1, frame) rep_kwarg = kwargs.get('representation_type', None) if kwargs else None if rep_kwarg == 'spherical': assert isinstance(hgs1._data, SphericalRepresentation) else: # Check Carrington first because it's a subclass of Stonyhurst if isinstance(hgs1, HeliographicCarrington): # Check that we have a 2D wrap180 representation assert isinstance(hgs1._data, SphericalRepresentation) elif isinstance(hgs1, HeliographicStonyhurst): # Check that we have a 2D wrap180 representation assert isinstance(hgs1._data, SphericalRepresentation) # Check the attrs are correct assert hgs1.lon == 0 * u.deg assert hgs1.lat == 0 * u.deg assert hgs1.radius == 1 * u.Mm # Check the attrs are in the correct default units assert hgs1.lon.unit is u.deg assert hgs1.lat.unit is u.deg assert hgs1.radius.unit is u.Mm def test_hgs_cart_init(): hpc1 = HeliographicStonyhurst(CartesianRepresentation(0 * u.km, 0 * u.km, 1 * u.Mm)) assert isinstance(hpc1, HeliographicStonyhurst) assert isinstance(hpc1._data, CartesianRepresentation) def test_hgs_wrapping_on(): hpc1 = HeliographicStonyhurst(350*u.deg, 10*u.deg) assert_quantity_allclose(hpc1.lon, -10*u.deg) assert_quantity_allclose(hpc1.lon.wrap_angle, 180*u.deg) def test_hgs_wrapping_off(): hpc1 = HeliographicStonyhurst(350*u.deg, 10*u.deg, wrap_longitude=False) assert_quantity_allclose(hpc1.lon, 350*u.deg) assert_quantity_allclose(hpc1.lon.wrap_angle, 360*u.deg) def test_hgc_wrapping_360(): hpc1 = HeliographicCarrington(350*u.deg, 10*u.deg) assert_quantity_allclose(hpc1.lon, 350*u.deg) assert_quantity_allclose(hpc1.lon.wrap_angle, 360*u.deg) # ============================================================================== # ## Heliocentric Tests # ============================================================================== @pytest.mark.parametrize( 'args, kwargs', [((10 * u.km, 10 * u.km, 10 * u.km), None), (None, {'x': 10 * u.km, 'y': 10 * u.km, 'z': 10 * u.km}), ([CartesianRepresentation(10 * u.km, 10 * u.km, 10 * u.km)], None), ([CartesianRepresentation(10 * u.km, 10 * u.km, 10 * u.km)], {'obstime': '2011/01/01T00:00:00'})]) def test_create_hcc_3d(args, kwargs): hcc = init_frame(Heliocentric, args, kwargs) assert isinstance(hcc, Heliocentric) assert isinstance(hcc._data, CartesianRepresentation) assert hcc.x == 10 * u.km assert hcc.y == 10 * u.km assert hcc.z == 10 * u.km # Check the attrs are in the correct default units assert hcc.x.unit is u.km assert hcc.y.unit is u.km assert hcc.z.unit is u.km def test_hcc_default_observer(): # Observer is considered default if it hasn't been specified *and* if obstime isn't specified hcc = Heliocentric(0*u.AU, 0*u.AU, 0*u.AU) assert hcc.is_frame_attr_default('observer') hcc = Heliocentric(0*u.AU, 0*u.AU, 0*u.AU, obstime='2019-06-01') assert not hcc.is_frame_attr_default('observer') # ============================================================================== # SkyCoord Tests # ============================================================================== two_D_parameters = [ ([0 * u.deg, 0 * u.arcsec], {}), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], {}), ([UnitSphericalRepresentation(0 * u.deg, 0 * u.arcsec)], {}), ([SphericalRepresentation(0 * u.deg, 0 * u.arcsec, 1*u.one)], {}), ] @pytest.mark.parametrize("args, kwargs", two_D_parameters + [([0 * u.deg, 0 * u.arcsec], {'representation_type': 'unitspherical'})]) def test_skycoord_hpc(args, kwargs): """ Test that when instantiating a HPC frame with SkyCoord calculate distance still works. """ sc = SkyCoord(*args, **kwargs, frame="helioprojective", obstime="2011-01-01T00:00:00") # Test the transform to HGS because it will force a `calculate_distance` call. hgs = sc.transform_to("heliographic_stonyhurst") assert isinstance(hgs.frame, HeliographicStonyhurst) @pytest.mark.parametrize("args, kwargs", two_D_parameters) def test_skycoord_hgs(args, kwargs): """ Test that when instantiating a HPC frame with SkyCoord correctly replaces distance. Note: We only need to test HGS here not HGC as they share the same constructor. """ RSUN_METERS = sun.constants.get('radius').si sc = SkyCoord(*args, **kwargs, frame="heliographic_stonyhurst", obstime="2011-01-01T00:00:00") assert_quantity_allclose(sc.radius, RSUN_METERS) sunpy-1.0.3/sunpy/coordinates/tests/__init__.py0000644000175100001650000000000013531722525022117 0ustar vstsdocker00000000000000sunpy-1.0.3/sunpy/coordinates/tests/test_wcs_utils.py0000644000175100001650000001342513531722525023452 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import numpy as np import sunpy.map from astropy.coordinates import BaseCoordinateFrame from astropy.wcs import WCS from sunpy.coordinates.frames import Helioprojective, Heliocentric, HeliographicStonyhurst, HeliographicCarrington from ..wcs_utils import solar_wcs_frame_mapping, solar_frame_to_wcs_mapping def test_hpc(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['HPLN', 'HPLT'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, Helioprojective) def test_hpc_flipped(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['HPLT', 'HPLN'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, Helioprojective) def test_hgs(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['HGLN', 'HGLT'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, HeliographicStonyhurst) def test_hgc(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['CRLN', 'CRLT'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, HeliographicCarrington) def test_hcc(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['SOLX', 'SOLY'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, Heliocentric) def test_none(): wcs = WCS(naxis=2) wcs.wcs.ctype = ['spam', 'eggs'] result = solar_wcs_frame_mapping(wcs) assert result is None def test_wcs_extras(): """ To enable proper creation of the coordinate systems, Map sticks three extra attributes on the WCS object: * heliographic_longitude * heliographic_latitude * dsun """ data = np.ones([6, 6], dtype=np.float64) header = {'CRVAL1': 0, 'CRVAL2': 0, 'CRPIX1': 5, 'CRPIX2': 5, 'CDELT1': 10, 'CDELT2': 10, 'CUNIT1': 'arcsec', 'CUNIT2': 'arcsec', 'PC1_1': 0, 'PC1_2': -1, 'PC2_1': 1, 'PC2_2': 0, 'NAXIS1': 6, 'NAXIS2': 6, 'CTYPE1': 'HPLN-TAN', 'CTYPE2': 'HPLT-TAN', 'date-obs': '1970/01/01T00:00:00', 'obsrvtry': 'Foo', 'detector': 'bar', 'wavelnth': 10, 'waveunit': 'm', 'hglt_obs': 0, 'hgln_obs': 0, 'dsun_obs': 10, 'rsun_ref': 690000000} generic_map = sunpy.map.Map((data, header)) wcs = generic_map.wcs assert wcs.heliographic_observer.lat.value == 0 assert wcs.heliographic_observer.lon.value == 0 assert wcs.heliographic_observer.radius.value == 10 assert wcs.rsun.value == header['rsun_ref'] result = solar_wcs_frame_mapping(wcs) assert isinstance(result, Helioprojective) assert result.observer.lat.value == 0 assert result.observer.lon.value == 0 assert result.observer.radius.value == 10 assert result.rsun.value == header['rsun_ref'] def test_hpc_frame_to_wcs(): frame = Helioprojective(obstime='2013-10-28') result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'HPLN-TAN' assert result_wcs.wcs.cunit[0] == 'arcsec' assert result_wcs.wcs.dateobs == '2013-10-28T00:00:00.000' assert isinstance(result_wcs.heliographic_observer, HeliographicStonyhurst) assert result_wcs.rsun == frame.rsun # Test a frame with no obstime and no observer frame = Helioprojective() result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'HPLN-TAN' assert result_wcs.wcs.cunit[0] == 'arcsec' assert result_wcs.wcs.dateobs == '' assert result_wcs.heliographic_observer is None assert result_wcs.rsun == frame.rsun def test_hgs_frame_to_wcs(): frame = HeliographicStonyhurst(obstime='2013-10-28') result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'HGLN-TAN' assert result_wcs.wcs.cunit[0] == 'deg' assert result_wcs.wcs.dateobs == '2013-10-28T00:00:00.000' # Test a frame with no obstime frame = HeliographicStonyhurst() result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'HGLN-TAN' assert result_wcs.wcs.cunit[0] == 'deg' assert result_wcs.wcs.dateobs == '' def test_hgc_frame_to_wcs(): frame = HeliographicCarrington(obstime='2013-10-28') result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'CRLN-TAN' assert result_wcs.wcs.cunit[0] == 'deg' assert result_wcs.wcs.dateobs == '2013-10-28T00:00:00.000' # Test a frame with no obstime frame = HeliographicCarrington() result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'CRLN-TAN' assert result_wcs.wcs.cunit[0] == 'deg' assert result_wcs.wcs.dateobs == '' def test_hcc_frame_to_wcs(): frame = Heliocentric(obstime='2013-10-28') result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'SOLX' assert result_wcs.wcs.dateobs == '2013-10-28T00:00:00.000' assert isinstance(result_wcs.heliographic_observer, HeliographicStonyhurst) # Test a frame with no obstime and no observer frame = Heliocentric() result_wcs = solar_frame_to_wcs_mapping(frame) assert isinstance(result_wcs, WCS) assert result_wcs.wcs.ctype[0] == 'SOLX' assert result_wcs.wcs.dateobs == '' assert result_wcs.heliographic_observer is None def test_non_sunpy_frame_to_wcs(): # For a non-SunPy frame, our mapping should return None frame = BaseCoordinateFrame() assert solar_frame_to_wcs_mapping(frame) is None sunpy-1.0.3/sunpy/coordinates/tests/test_frameattributes.py0000644000175100001650000001217313531722525024636 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import pytest import astropy.units as u from astropy.time import Time from astropy.tests.helper import assert_quantity_allclose from astropy.coordinates import ICRS, get_body_barycentric from sunpy.time import parse_time from ..frames import Helioprojective, HeliographicStonyhurst from ..frameattributes import TimeFrameAttributeSunPy, ObserverCoordinateAttribute from sunpy.coordinates import get_earth, frames @pytest.fixture def attr(): return TimeFrameAttributeSunPy() def test_now(attr): """ We can't actually test the value independantly """ result, converted = attr.convert_input('now') assert isinstance(result, Time) assert converted def test_none(attr): """ We can't actually test the value independantly """ result, converted = attr.convert_input(None) assert result is None assert not converted @pytest.mark.parametrize('input', [ Time('2012-01-01 00:00:00'), '2012/01/01T00:00:00', '20120101000000', '2012/01/01 00:00:00' ]) def test_convert(attr, input): result, converted = attr.convert_input(input) output = Time('2012-01-01 00:00:00') assert isinstance(result, Time) assert result == output @pytest.mark.parametrize('input', [ Time('2012-01-01 00:00:00'), '2012/01/01T00:00:00', '20120101000000', '2012/01/01 00:00:00' ]) def test_on_frame(input): hpc1 = Helioprojective(obstime=input) output = Time('2012-01-01 00:00:00') assert isinstance(hpc1.obstime, Time) assert hpc1.obstime == output def test_non_string(): output = parse_time('now') hpc1 = Helioprojective(obstime=output) assert isinstance(hpc1.obstime, Time) assert hpc1.obstime == output def test_on_frame_error(): with pytest.raises(ValueError): Helioprojective(obstime='ajshdasjdhk') def test_on_frame_error2(): with pytest.raises(ValueError): Helioprojective(obstime=17263871263) # ObserverCoordinateAttribute def test_string_coord(): oca = ObserverCoordinateAttribute(HeliographicStonyhurst) obstime = "2011-01-01" coord = oca._convert_string_to_coord("earth", obstime) assert isinstance(coord, HeliographicStonyhurst) assert coord.obstime == parse_time(obstime) def test_coord_get(): # Test default (instance=None) obs = Helioprojective.observer assert obs == "earth" # Test get obstime = "2013-04-01" obs = Helioprojective(observer="earth", obstime=obstime).observer earth = get_earth(obstime) assert isinstance(obs, HeliographicStonyhurst) assert_quantity_allclose(obs.lon, earth.lon) assert_quantity_allclose(obs.lat, earth.lat) assert_quantity_allclose(obs.radius, earth.radius) assert hasattr(obs, "object_name") assert obs.object_name == "earth" assert str(obs) == "" # Test get obstime = "2013-04-01" obs = Helioprojective(obstime=obstime).observer earth = get_earth(obstime) assert isinstance(obs, HeliographicStonyhurst) assert_quantity_allclose(obs.lon, earth.lon) assert_quantity_allclose(obs.lat, earth.lat) assert_quantity_allclose(obs.radius, earth.radius) # Test get mars obstime = Time(parse_time("2013-04-01")) obs = Helioprojective(observer="mars", obstime=obstime).observer out_icrs = ICRS(get_body_barycentric("mars", obstime)) mars = out_icrs.transform_to(HeliographicStonyhurst(obstime=obstime)) assert isinstance(obs, HeliographicStonyhurst) assert_quantity_allclose(obs.lon, mars.lon) assert_quantity_allclose(obs.lat, mars.lat) assert_quantity_allclose(obs.radius, mars.radius) assert hasattr(obs, "object_name") assert obs.object_name == "mars" assert str(obs) == "" def test_default_hcc_observer(): h = frames.Heliocentric() assert h.observer == "earth" h = frames.Heliocentric(observer="mars") assert h.observer == "mars" def test_obstime_hack(): """ Test that the obstime can be updated in place, this is used in the transform pipeline. """ h = frames.Heliocentric() assert h.observer == "earth" obstime = "2011-01-01" h._obstime = obstime assert isinstance(h.observer, frames.HeliographicStonyhurst) earth = get_earth(obstime) obs = h._observer assert isinstance(obs, HeliographicStonyhurst) assert_quantity_allclose(obs.lon, earth.lon) assert_quantity_allclose(obs.lat, earth.lat) assert_quantity_allclose(obs.radius, earth.radius) """ These two tests are to make sure that during the transformation stack the value of observer is correctly calculated. """ def test_default_observer_transform_hcc(): center = frames.HeliographicStonyhurst(0 * u.deg, 0 * u.deg, obstime="2017-07-11 15:00") hpc = center.transform_to(frames.Heliocentric(obstime="2017-07-11 15:00")) assert_quantity_allclose(hpc.y, -48484.509203 * u.km) def test_default_observer_transform_hpc(): center = frames.HeliographicStonyhurst(0 * u.deg, 0 * u.deg, obstime="2017-07-11 15:00") hpc = center.transform_to(frames.Helioprojective(obstime="2017-07-11 15:00")) assert_quantity_allclose(hpc.Ty, -66.062568 * u.arcsec) sunpy-1.0.3/sunpy/coordinates/tests/test_ephemeris.py0000644000175100001650000001116513531722525023416 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import pytest import astropy.units as u from astropy.config.paths import set_temp_cache from astropy.constants import c as speed_of_light from astropy.coordinates import SkyCoord from astropy.tests.helper import assert_quantity_allclose from astropy.time import Time from sunpy.coordinates.ephemeris import * from sunpy.time import parse_time def test_get_body_heliographic_stonyhurst(): # Validate against published values from the Astronomical Almanac (2013) e1 = get_body_heliographic_stonyhurst('earth', '2013-Jan-01') assert_quantity_allclose(e1.lon, 0*u.deg, atol=1e-12*u.deg) assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU) e2 = get_body_heliographic_stonyhurst('earth', '2013-Sep-01') assert_quantity_allclose(e2.lon, 0*u.deg, atol=1e-12*u.deg) assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU) def test_get_body_heliographic_stonyhurst_light_travel_time(): # Tests whether the apparent position of the Sun accoutns for light travel time t = Time('2012-06-05 22:34:48.350') # An arbitrary test time # Use the implemented correction for light travel time implementation = get_body_heliographic_stonyhurst('sun', t, observer=get_earth(t)) implementation_icrs = SkyCoord(implementation).icrs.cartesian # Use a manual correction for light travel time light_travel_time = get_earth(t).radius / speed_of_light manual = get_body_heliographic_stonyhurst('sun', t - light_travel_time) manual_icrs = SkyCoord(manual).icrs.cartesian difference = (implementation_icrs - manual_icrs).norm() assert_quantity_allclose(difference, 0*u.m, atol=1*u.m) def test_get_earth(): # Validate against published values from the Astronomical Almanac (2013) e1 = get_earth('2013-Jan-01') assert e1.lon == 0*u.deg assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU) e2 = get_earth('2013-Sep-01') assert e2.lon == 0*u.deg assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU) @pytest.mark.remote_data def test_get_horizons_coord(): # get_horizons_coord() depends on astroquery astroquery = pytest.importorskip("astroquery") # Validate against published values from the Astronomical Almanac (2013) e1 = get_horizons_coord('Geocenter', '2013-Jan-01') assert_quantity_allclose(e1.lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU) e2 = get_horizons_coord('Geocenter', '2013-Sep-01') assert_quantity_allclose(e1.lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU) @pytest.mark.remote_data def test_get_horizons_coord_array_time(): # get_horizons_coord() depends on astroquery astroquery = pytest.importorskip("astroquery") # Validate against published values from the Astronomical Almanac (2013, C8-C13) array_time = Time(['2013-05-01', '2013-06-01', '2013-04-01', '2013-03-01']) e = get_horizons_coord('Geocenter', array_time) assert_quantity_allclose(e[0].lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e[0].lat, -4.17*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e[0].radius, 1.0075271*u.AU, atol=5e-7*u.AU) assert_quantity_allclose(e[1].lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e[1].lat, -0.66*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e[1].radius, 1.0140013*u.AU, atol=5e-7*u.AU) assert_quantity_allclose(e[2].lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e[2].lat, -6.54*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e[2].radius, 0.9992311*u.AU, atol=5e-7*u.AU) assert_quantity_allclose(e[3].lon, 0*u.deg, atol=5e-6*u.deg) assert_quantity_allclose(e[3].lat, -7.22*u.deg, atol=5e-3*u.deg) assert_quantity_allclose(e[3].radius, 0.9908173*u.AU, atol=5e-7*u.AU) @pytest.mark.remote_data def test_consistency_with_horizons(): # get_horizons_coord() depends on astroquery astroquery = pytest.importorskip("astroquery") # Check whether the location of Earth is the same between Astropy and JPL HORIZONS now = parse_time('now') e1 = get_earth(now) e2 = get_horizons_coord('Geocenter', now) assert_quantity_allclose(e1.separation_3d(e2), 0*u.km, atol=35*u.km) sunpy-1.0.3/sunpy/coordinates/tests/test_offset_frame.py0000644000175100001650000000354513531722525024100 0ustar vstsdocker00000000000000import pytest import hypothesis.strategies as st from hypothesis import given, settings import astropy.units as u from astropy.tests.helper import assert_quantity_allclose from astropy.coordinates import SkyCoord, SkyOffsetFrame from sunpy.coordinates import NorthOffsetFrame @st.composite def latitude(draw, lat=st.floats(min_value=-90, max_value=90, allow_nan=False, allow_infinity=False)): return draw(lat) * u.deg @st.composite def lonitude(draw, lon=st.floats(min_value=-180, max_value=180, allow_nan=False, allow_infinity=False)): return draw(lon) * u.deg def test_null(): """ test init of a frame where the origins are the same. """ off = NorthOffsetFrame(north=SkyCoord(0*u.deg, 90*u.deg, frame='heliographic_stonyhurst')) assert isinstance(off, SkyOffsetFrame) assert off.origin.lat == 0*u.deg assert off.origin.lon == 0*u.deg @given(lon=lonitude(), lat=latitude()) @settings(deadline=5000) def test_transform(lon, lat): """ Test that the north pole in the new frame transforms back to the given north argument. """ north = SkyCoord(lon=lon, lat=lat, frame='heliographic_stonyhurst') off = NorthOffsetFrame(north=north) t_north = SkyCoord(lon=0*u.deg, lat=90*u.deg, frame=off) t_north = t_north.transform_to('heliographic_stonyhurst') assert_quantity_allclose(north.lon, t_north.lon, atol=1e6*u.deg) assert_quantity_allclose(north.lat, t_north.lat, atol=1e6*u.deg) def test_south_pole(): s = SkyCoord(-10*u.deg, 0*u.deg, frame='heliographic_stonyhurst') off = NorthOffsetFrame(north=s) assert_quantity_allclose(off.origin.lon, 170*u.deg) assert_quantity_allclose(off.origin.lat, -90*u.deg) def test_error(): with pytest.raises(TypeError): NorthOffsetFrame() sunpy-1.0.3/sunpy/coordinates/tests/test_utils.py0000644000175100001650000001753413531722525022603 0ustar vstsdocker00000000000000import pytest import numpy as np from astropy.coordinates import SkyCoord import astropy.units as u from sunpy.coordinates import sun import sunpy.map import sunpy.data.test from sunpy.coordinates import frames from sunpy.coordinates.utils import GreatArc # Test the great arc code against calculable quantities # The inner angle is the same between each pair of co-ordinates. You can # calculate these co-ordinates using the inner angle formulae as listed here: # https://en.wikipedia.org/wiki/Great-circle_distance # @pytest.mark.parametrize("start, end", [((0, 0), (0, 45)), ((0, 0), (45, 0)), ((0, 45), (0, 0)), ((45, 0), (0, 0)), ((12, 13), (12, 58)), ((-10, 6), (-10, 51)), ((-20, -50), (-20, -5)), ((10, -50), (87.53163324626676, -55))]) def test_great_arc_calculable(start, end): c = SkyCoord(start[0]*u.degree, start[1]*u.degree, frame=frames.HeliographicStonyhurst, observer=frames.HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU)) d = SkyCoord(end[0]*u.degree, end[1]*u.degree, frame=frames.HeliographicStonyhurst, observer=frames.HeliographicStonyhurst(0*u.deg, 0*u.deg, 1*u.AU)) gc = GreatArc(c, d) assert gc.start == c assert gc.end == d np.testing.assert_almost_equal(gc.inner_angle.to('deg').value, 45.0) np.testing.assert_almost_equal(gc.radius.to('km').value, sun.constants.radius.to('km').value) np.testing.assert_almost_equal(gc.distance.to('km').value, sun.constants.radius.to('km').value * 2 * np.pi/8, decimal=1) # Test the calculation of coordinates using varying numbers of points on # initialization of the GreatArc object. @pytest.mark.parametrize("points_requested, points_expected, first_point, last_point, last_inner_angle, last_distance", # Test default [(None, 100, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299), # Test int as an option (3, 3, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299), # Test equally spaced monotonically increasing numpy # array (np.linspace(0, 1, 43), 43, (600, -600), (-100, 800), 1.8683580432741789, 1300377.1981299), # Test unequally spaced monotonically increasing numpy # array (np.asarray([0.1, 0.2, 0.6, 0.67, 0.99]), 5, (604.68091703, -468.64217597), (-88.83212616, 792.76284375), 1.84967446, 1287373.4261486), # Test unequally spaced monotonically decreasing numpy # array (np.asarray([0.93, 0.78, 0.3, 0.001]), 4, (-21.28208654, 743.58866798), (600.1512768 , -598.78376614), 0.00186836, 1300.37719813), # Test numpy array that increases and decreases (np.asarray([0.94, 0.73, 0.8, 0.21]), 4, (-32.5852606 , 752.45507707), (585.45829119, -305.26965043), 0.39235519, 273079.2116073)]) def test_great_arc_coordinates(points_requested, points_expected, first_point, last_point, last_inner_angle, last_distance): m = sunpy.map.Map(sunpy.map.Map(sunpy.data.test.get_test_filepath('aia_171_level1.fits'))) coordinate_frame = m.coordinate_frame a = SkyCoord(600*u.arcsec, -600*u.arcsec, frame=coordinate_frame) b = SkyCoord(-100*u.arcsec, 800*u.arcsec, frame=coordinate_frame) gc = GreatArc(a, b, points=points_requested) coordinates = gc.coordinates() inner_angles = gc.inner_angles() distances = gc.distances() # Ensure a GreatArc object is returned assert isinstance(gc, GreatArc) # Test the properties of the GreatArc object assert gc.start == a assert gc.end == b assert gc.distance_unit == u.km assert gc.observer == a.observer assert gc.center.x == 0 * u.km assert gc.center.y == 0 * u.km assert gc.center.z == 0 * u.km np.testing.assert_almost_equal(gc.start_cartesian, np.asarray([428721.0913539, -428722.9051924, 341776.0910214])) np.testing.assert_almost_equal(gc.end_cartesian, np.asarray([-71429.5229381, 571439.071248, 390859.5797815])) np.testing.assert_almost_equal(gc.center_cartesian, np.asarray([0, 0, 0])) np.testing.assert_almost_equal(gc.v1, np.asarray([428721.0913539, -428722.9051924, 341776.0910214])) np.testing.assert_almost_equal(gc._r, 696000.000001501) np.testing.assert_almost_equal(gc.v2, np.asarray([-71429.5229381, 571439.071248, 390859.5797815])) np.testing.assert_almost_equal(gc.v3, np.asarray([56761.6265851, 466230.7005856, 513637.0815867])) # Inner angle assert gc.inner_angle.unit == u.rad np.testing.assert_almost_equal(gc.inner_angle.value, 1.8683580432741789) # Distance assert gc.distance.unit == u.km np.testing.assert_almost_equal(gc.distance.value, 1300377.1981298963) # Radius of the sphere assert gc.radius.unit == u.km np.testing.assert_almost_equal(gc.radius.value, 696000.000001501) # Test the calculation of the SkyCoords # Coordinates method # Number of points assert len(coordinates) == points_expected # Start and end coordinates np.testing.assert_almost_equal(coordinates[0].Tx.value, first_point[0]) np.testing.assert_almost_equal(coordinates[0].Ty.value, first_point[1]) np.testing.assert_almost_equal(coordinates[-1].Tx.value, last_point[0]) np.testing.assert_almost_equal(coordinates[-1].Ty.value, last_point[1]) # Inner angles method # Inner angles assert len(inner_angles) == points_expected np.testing.assert_almost_equal(inner_angles[-1].value, last_inner_angle) # Distances method assert len(distances) == points_expected np.testing.assert_almost_equal(distances[-1].value, last_distance) # Test that the great arc code rejects wrongly formatted points @pytest.mark.parametrize("points", [np.asarray([[0, 0.1], [0.2, 0.3]]), np.asarray([0.1, 0.2, -0.1, 0.4]), np.asarray([0.3, 1.1, 0.6, 0.7]), 'strings_not_permitted']) def test_great_arc_wrongly_formatted_points(points): m = sunpy.map.Map(sunpy.map.Map(sunpy.data.test.get_test_filepath('aia_171_level1.fits'))) coordinate_frame = m.coordinate_frame a = SkyCoord(600*u.arcsec, -600*u.arcsec, frame=coordinate_frame) b = SkyCoord(-100*u.arcsec, 800*u.arcsec, frame=coordinate_frame) with pytest.raises(ValueError): dummy = GreatArc(a, b, points=points) with pytest.raises(ValueError): dummy = GreatArc(a, b).coordinates(points=points) with pytest.raises(ValueError): dummy = GreatArc(a, b).inner_angles(points=points) with pytest.raises(ValueError): dummy = GreatArc(a, b).distances(points=points) with pytest.raises(ValueError): dummy = GreatArc(a, b).distances(points=points) # Test that the great arc code properly differentiates between the default # points and the requested points def test_great_arc_points_differentiates(): m = sunpy.map.Map(sunpy.map.Map(sunpy.data.test.get_test_filepath('aia_171_level1.fits'))) coordinate_frame = m.coordinate_frame a = SkyCoord(600*u.arcsec, -600*u.arcsec, frame=coordinate_frame) b = SkyCoord(-100*u.arcsec, 800*u.arcsec, frame=coordinate_frame) gc = GreatArc(a, b) coordinates = gc.coordinates(10) inner_angles = gc.inner_angles(11) distances = gc.distances(12) assert len(coordinates) == 10 and len(gc.coordinates()) == 100 assert len(inner_angles) == 11 and len(gc.inner_angles()) == 100 assert len(distances) == 12 and len(gc.distances()) == 100 sunpy-1.0.3/sunpy/coordinates/__init__.py0000644000175100001650000000172213531722525020771 0ustar vstsdocker00000000000000""" This subpackage contains: * A robust framework for working with coordinate systems * Functions to obtain the locations of solar-system bodies * Functions to calculate Sun-specific coordinate information (`sunpy.coordinates.sun`) The diagram below shows all of Sun-based and Earth-based coordinate systems available through `sunpy.coordinates`, as well as the transformations between them. Each frame is labeled with the name of its class and its alias (useful for converting other coordinates to them using attribute-style access). The frames colored in cyan are implemented in `astropy.coordinates`, and there are other astronomical frames that can be transformed to that are not shown below. See the documentation for `astropy.coordinates` for more information. """ from .frames import * from .offset_frame import * from . import transformations from .ephemeris import * from . import sun from .wcs_utils import * __doc__ += transformations._make_sunpy_graph() sunpy-1.0.3/sunpy/coordinates/utils.py0000644000175100001650000002460213531722525020374 0ustar vstsdocker00000000000000# # Calculates the co-ordinates along great arcs between two specified points # which are assumed to be on disk. # import numpy as np import astropy.units as u from astropy.coordinates import SkyCoord from sunpy.coordinates import frames __all__ = ['GreatArc'] class GreatArc: """ Calculate the properties of a great arc at user-specified points between a start and end point on a sphere. Parameters ---------- start : `~astropy.coordinates.SkyCoord` Start point. end : `~astropy.coordinates.SkyCoord` End point. center : `~astropy.coordinates.SkyCoord` Center of the sphere. points : `None`, `int`, `numpy.ndarray` Number of points along the great arc. If None, the arc is calculated at 100 equally spaced points from start to end. If int, the arc is calculated at "points" equally spaced points from start to end. If a numpy.ndarray is passed, it must be one dimensional and have values >=0 and <=1. The values in this array correspond to parameterized locations along the great arc from zero, denoting the start of the arc, to 1, denoting the end of the arc. Setting this keyword on initializing a GreatArc object sets the locations of the default points along the great arc. Methods ------- inner_angles : `~astropy.units.rad` Radian angles of the points along the great arc from the start to end co-ordinate. distances : `~astropy.units` Distances of the points along the great arc from the start to end co-ordinate. The units are defined as those returned after transforming the co-ordinate system of the start co-ordinate into its Cartesian equivalent. coordinates : `~astropy.coordinates.SkyCoord` Co-ordinates along the great arc in the co-ordinate frame of the start point. References ---------- [1] https://www.mathworks.com/matlabcentral/newsreader/view_thread/277881 [2] https://en.wikipedia.org/wiki/Great-circle_distance#Vector_version Example ------- >>> import matplotlib.pyplot as plt >>> from astropy.coordinates import SkyCoord >>> import astropy.units as u >>> from sunpy.coordinates.utils import GreatArc >>> import sunpy.map >>> from sunpy.data.sample import AIA_171_IMAGE # doctest: +REMOTE_DATA >>> m = sunpy.map.Map(AIA_171_IMAGE) # doctest: +REMOTE_DATA >>> a = SkyCoord(600*u.arcsec, -600*u.arcsec, frame=m.coordinate_frame) # doctest: +REMOTE_DATA >>> b = SkyCoord(-100*u.arcsec, 800*u.arcsec, frame=m.coordinate_frame) # doctest: +REMOTE_DATA >>> great_arc = GreatArc(a, b) # doctest: +REMOTE_DATA >>> ax = plt.subplot(projection=m) # doctest: +SKIP >>> m.plot(axes=ax) # doctest: +SKIP >>> ax.plot_coord(great_arc.coordinates(), color='c') # doctest: +SKIP >>> plt.show() # doctest: +SKIP """ def __init__(self, start, end, center=None, points=None): # Start point of the great arc self.start = start # End point of the great arc self.end = end # Parameterized location of points between the start and the end of the # great arc. # Default parameterized points location. self.default_points = np.linspace(0, 1, 100) # If the user requests a different set of default parameterized points # on initiation of the object, then these become the default. This # allows the user to use the methods without having to specify their # choice of points over and over again, while also allowing the # flexibility in the methods to calculate other values. self.default_points = self._points_handler(points) # Units of the start point self.distance_unit = u.km # Co-ordinate frame self.start_frame = self.start.frame # Observation time self.obstime = self.start.obstime # Observer self.observer = self.start.observer # Set the center of the sphere if center is None: self.center = SkyCoord(0 * self.distance_unit, 0 * self.distance_unit, 0 * self.distance_unit, frame=frames.Heliocentric) # Convert the start, end and center points to their Cartesian values self.start_cartesian = self.start.transform_to(frames.Heliocentric).cartesian.xyz.to(self.distance_unit).value self.end_cartesian = self.end.transform_to(frames.Heliocentric).cartesian.xyz.to(self.distance_unit).value self.center_cartesian = self.center.transform_to(frames.Heliocentric).cartesian.xyz.to(self.distance_unit).value # Great arc properties calculation # Vector from center to first point self.v1 = self.start_cartesian - self.center_cartesian # Distance of the first point from the center self._r = np.linalg.norm(self.v1) # Vector from center to second point self.v2 = self.end_cartesian - self.center_cartesian # The v3 vector lies in plane of v1 & v2 and is orthogonal to v1 self.v3 = np.cross(np.cross(self.v1, self.v2), self.v1) self.v3 = self._r * self.v3 / np.linalg.norm(self.v3) # Inner angle between v1 and v2 in radians self.inner_angle = np.arctan2(np.linalg.norm(np.cross(self.v1, self.v2)), np.dot(self.v1, self.v2)) * u.rad # Radius of the sphere self.radius = self._r * self.distance_unit # Distance on the sphere between the start point and the end point. self.distance = self.radius * self.inner_angle.value def _points_handler(self, points): """ Interprets the points keyword. """ if points is None: return self.default_points elif isinstance(points, int): return np.linspace(0, 1, points) elif isinstance(points, np.ndarray): if points.ndim > 1: raise ValueError('One dimensional numpy ndarrays only.') if np.any(points < 0) or np.any(points > 1): raise ValueError('All value in points array must be strictly >=0 and <=1.') return points else: raise ValueError('Incorrectly specified "points" keyword value.') def inner_angles(self, points=None): """ Calculates the inner angles for the parameterized points along the arc and returns the value in radians, from the start co-ordinate to the end. Parameters ---------- points : `None`, `int`, `numpy.ndarray` If None, use the default locations of parameterized points along the arc. If int, the arc is calculated at "points" equally spaced points from start to end. If a numpy.ndarray is passed, it must be one dimensional and have values >=0 and <=1. The values in this array correspond to parameterized locations along the great arc from zero, denoting the start of the arc, to 1, denoting the end of the arc. Returns ------- inner_angles : `~astropy.units.rad` Radian angles of the points along the great arc from the start to end co-ordinate. """ these_points = self._points_handler(points) return these_points.reshape(len(these_points), 1)*self.inner_angle def distances(self, points=None): """ Calculates the distance from the start co-ordinate to the end co-ordinate on the sphere for all the parameterized points. Parameters ---------- points : `None`, `int`, `numpy.ndarray` If None, use the default locations of parameterized points along the arc. If int, the arc is calculated at "points" equally spaced points from start to end. If a numpy.ndarray is passed, it must be one dimensional and have values >=0 and <=1. The values in this array correspond to parameterized locations along the great arc from zero, denoting the start of the arc, to 1, denoting the end of the arc. Returns ------- distances : `~astropy.units` Distances of the points along the great arc from the start to end co-ordinate. The units are defined as those returned after transforming the co-ordinate system of the start co-ordinate into its Cartesian equivalent. """ return self.radius * self.inner_angles(points=points).value def coordinates(self, points=None): """ Calculates the co-ordinates on the sphere from the start to the end co-ordinate for all the parameterized points. Co-ordinates are returned in the frame of the start coordinate. Parameters ---------- points : `None`, `int`, `numpy.ndarray` If None, use the default locations of parameterized points along the arc. If int, the arc is calculated at "points" equally spaced points from start to end. If a numpy.ndarray is passed, it must be one dimensional and have values >=0 and <=1. The values in this array correspond to parameterized locations along the great arc from zero, denoting the start of the arc, to 1, denoting the end of the arc. Returns ------- arc : `~astropy.coordinates.SkyCoord` Co-ordinates along the great arc in the co-ordinate frame of the start point. """ # Calculate the inner angles these_inner_angles = self.inner_angles(points=points) # Calculate the Cartesian locations from the first to second points great_arc_points_cartesian = (self.v1[np.newaxis, :] * np.cos(these_inner_angles) + self.v3[np.newaxis, :] * np.sin(these_inner_angles) + self.center_cartesian) * self.distance_unit # Return the coordinates of the great arc between the start and end # points return SkyCoord(great_arc_points_cartesian[:, 0], great_arc_points_cartesian[:, 1], great_arc_points_cartesian[:, 2], frame=frames.Heliocentric, obstime=self.obstime, observer=self.observer).transform_to(self.start_frame) sunpy-1.0.3/sunpy/coordinates/sun.py0000644000175100001650000003705013531722525020042 0ustar vstsdocker00000000000000""" Sun-specific coordinate calculations """ import warnings import numpy as np import astropy.units as u from astropy.time import Time from astropy.coordinates import Angle, Latitude, Longitude, SkyCoord # Versions of Astropy that do not have *MeanEcliptic frames have the same frames # with the misleading names *TrueEcliptic try: from astropy.coordinates import HeliocentricMeanEcliptic, GeocentricMeanEcliptic except ImportError: from astropy.coordinates import HeliocentricTrueEcliptic as HeliocentricMeanEcliptic from astropy.coordinates import GeocentricTrueEcliptic as GeocentricMeanEcliptic from astropy import _erfa as erfa from astropy.coordinates.builtin_frames.utils import get_jd12 from sunpy import log from sunpy.sun import constants from sunpy.time import parse_time from sunpy.time.time import _variables_for_parse_time_docstring from sunpy.util.decorators import add_common_docstring from .ephemeris import get_earth, _B0, _L0, _P, _earth_distance, _orientation __author__ = "Albert Y. Shih" __email__ = "ayshih@gmail.com" __all__ = [ "angular_radius", "sky_position", "carrington_rotation_number", "true_longitude", "apparent_longitude", "true_latitude", "apparent_latitude", "mean_obliquity_of_ecliptic", "true_rightascension", "true_declination", "true_obliquity_of_ecliptic", "apparent_rightascension", "apparent_declination", "print_params", "B0", "L0", "P", "earth_distance", "orientation" ] @add_common_docstring(**_variables_for_parse_time_docstring()) def angular_radius(t='now'): """ Return the angular radius of the Sun as viewed from Earth. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ solar_semidiameter_rad = constants.radius / earth_distance(t) return Angle(solar_semidiameter_rad.to(u.arcsec, equivalencies=u.dimensionless_angles())) @add_common_docstring(**_variables_for_parse_time_docstring()) def sky_position(t='now', equinox_of_date=True): """ Returns the apparent position of the Sun (right ascension and declination) on the celestial sphere using the equatorial coordinate system, referred to the true equinox of date (as default). Corrections for nutation and aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format equinox_of_date : `bool` If True, output is referred to the true equinox of date. Otherwise, output is referred to the J2000.0 epoch (ICRF orientation, not dynamical orientation). """ ra = apparent_rightascension(t, equinox_of_date=equinox_of_date) dec = apparent_declination(t, equinox_of_date=equinox_of_date) return ra, dec @add_common_docstring(**_variables_for_parse_time_docstring()) def carrington_rotation_number(t='now'): """ Return the Carrington rotation number. Each whole rotation number marks when the Sun's prime meridian coincides with the central meridian as seen from Earth, with the first rotation starting on 1853 November 9. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) # Estimate the Carrington rotation number by dividing the time that has elapsed since # JD 2398167.4 (late in the day on 1853 Nov 9), see Astronomical Algorithms (Meeus 1998, p.191), # by the mean synodic period (27.2753 days) estimate = (time.tt.jd - 2398167.4) / 27.2753 + 1 estimate_int, estimate_frac = divmod(estimate, 1) # The fractional rotation number from the above estimate is inaccurate, so calculate the actual # fractional rotation number from the longitude of the central meridian (L0) actual_frac = 1 - L0(time).to('deg').value / 360 # Calculate any adjustment to the integer rotation number due to wrapping wrap_adjustment = np.around(estimate_frac - actual_frac) actual = estimate_int + actual_frac + wrap_adjustment log.debug(f"Carrington rotation number: estimate is {estimate}, actual is {actual}") return actual @add_common_docstring(**_variables_for_parse_time_docstring()) def true_longitude(t='now'): """ Returns the Sun's true geometric longitude, referred to the mean equinox of date. No corrections for nutation or aberration are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) # Calculate Earth's true geometric longitude and add 180 degrees for the Sun's longitude. # This approach is used because Astropy's GeocentricMeanEcliptic includes aberration. earth = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='gcrs', obstime=time) coord = earth.transform_to(HeliocentricMeanEcliptic(equinox=time)) lon = coord.lon + 180*u.deg return Longitude(lon) @add_common_docstring(**_variables_for_parse_time_docstring()) def apparent_longitude(t='now'): """ Returns the Sun's apparent longitude, referred to the true equinox of date. Corrections for nutation and aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format Notes ----- The nutation model is IAU 2000A nutation with adjustments to match IAU 2006 precession. """ time = parse_time(t) sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=time) coord = sun.transform_to(GeocentricMeanEcliptic(equinox=time)) # Astropy's GeocentricMeanEcliptic already includes aberration, so only add nutation jd1, jd2 = get_jd12(time, 'tt') nut_lon, _ = erfa.nut06a(jd1, jd2)*u.radian lon = coord.lon + nut_lon return Longitude(lon) @add_common_docstring(**_variables_for_parse_time_docstring()) def true_latitude(t='now'): """ Returns the Sun's true geometric latitude, referred to the mean equinox of date. No corrections for nutation or aberration are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=time) coord = sun.transform_to(GeocentricMeanEcliptic(equinox=time)) # Astropy's GeocentricMeanEcliptic includes aberration from Earth motion, but the contribution # is negligible lat = coord.lat return Latitude(lat) @add_common_docstring(**_variables_for_parse_time_docstring()) def apparent_latitude(t='now'): """ Returns the Sun's apparent latitude, referred to the true equinox of date. Corrections for nutation and aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=time) coord = sun.transform_to(GeocentricMeanEcliptic(equinox=time)) # Astropy's GeocentricMeanEcliptic does not include nutation, but the contribution is negligible lat = coord.lat return Latitude(lat) @add_common_docstring(**_variables_for_parse_time_docstring()) def mean_obliquity_of_ecliptic(t='now'): """ Returns the mean obliquity of the ecliptic, using the IAU 2006 definition. No correction for nutation is included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2)*u.radian return Angle(obl, u.arcsec) @add_common_docstring(**_variables_for_parse_time_docstring()) def true_rightascension(t='now', equinox_of_date=True): """ Returns the Sun's true geometric right ascension relative to Earth, referred to the mean equinox of date (as default). No corrections for nutation or aberration are included. The correction due to light travel time would be negligible, so the output is also the astrometric right ascension. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format equinox_of_date : `bool` If True, output is referred to the mean equinox of date. Otherwise, output is referred to the J2000.0 epoch (ICRF orientation, not dynamical orientation). """ if equinox_of_date: # Mean equinox of date obl = mean_obliquity_of_ecliptic(t) # excludes nutation lon = true_longitude(t) lat = true_latitude(t) # See Astronomical Algorithms (Meeus 1998 p.93) y = np.sin(lon) * np.cos(obl) - np.tan(lat) * np.sin(obl) x = np.cos(lon) result = np.arctan2(y, x) else: # J2000.0 epoch # Calculate Earth's true geometric right ascension relative to the Sun and add 180 degrees. # This approach is used because Astropy's GCRS includes aberration. earth = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='gcrs', obstime=parse_time(t)) result = earth.hcrs.ra + 180*u.deg return Longitude(result, u.hourangle) @add_common_docstring(**_variables_for_parse_time_docstring()) def true_declination(t='now', equinox_of_date=True): """ Returns the Sun's true geometric declination relative to Earth, referred to the mean equinox of date (as default). No corrections for nutation or aberration are included. The correction due to light travel time would be negligible, so the output is also the astrometric declination. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format equinox_of_date : `bool` If True, output is referred to the mean equinox of date. Otherwise, output is referred to the J2000.0 epoch (ICRF orientation, not dynamical orientation). """ if equinox_of_date: # Mean equinox of date obl = mean_obliquity_of_ecliptic(t) # excludes nutation lon = true_longitude(t) lat = true_latitude(t) # See Astronomical Algorithms (Meeus 1998 p.93) result = np.arcsin(np.sin(lat) * np.cos(obl) + np.cos(lat) * np.sin(obl) * np.sin(lon)) else: # J2000.0 epoch # Calculate Earth's true geometric declination relative to the Sun and multipy by -1. # This approach is used because Astropy's GCRS includes aberration. earth = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='gcrs', obstime=parse_time(t)) result = -earth.hcrs.dec return Latitude(result, u.deg) @add_common_docstring(**_variables_for_parse_time_docstring()) def true_obliquity_of_ecliptic(t='now'): """ Returns the true obliquity of the ecliptic, using the IAU 2006 definition. Correction for nutation is included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format Notes ----- The nutation model is IAU 2000A nutation with adjustments to match IAU 2006 precession. """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2)*u.radian _, nut_obl = erfa.nut06a(jd1, jd2)*u.radian obl += nut_obl return Angle(obl, u.arcsec) @add_common_docstring(**_variables_for_parse_time_docstring()) def apparent_rightascension(t='now', equinox_of_date=True): """ Returns the Sun's apparent right ascension relative to Earth, referred to the true equinox of date (as default). Corrections for nutation or aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format equinox_of_date : `bool` If True, output is referred to the true equinox of date. Otherwise, output is referred to the J2000.0 epoch (ICRF orientation, not dynamical orientation). """ if equinox_of_date: # True equinox of date obl = true_obliquity_of_ecliptic(t) # includes nutation lon = apparent_longitude(t) lat = apparent_latitude(t) # See Astronomical Algorithms (Meeus 1998 p.93) y = np.sin(lon) * np.cos(obl) - np.tan(lat) * np.sin(obl) x = np.cos(lon) result = np.arctan2(y, x) else: # J2000.0 epoch sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=parse_time(t)) result = sun.gcrs.ra return Longitude(result, u.hourangle) @add_common_docstring(**_variables_for_parse_time_docstring()) def apparent_declination(t='now', equinox_of_date=True): """ Returns the Sun's apparent declination relative to Earth, referred to the true equinox of date (as default). Corrections for nutation or aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format equinox_of_date : `bool` If True, output is referred to the true equinox of date. Otherwise, output is referred to the J2000.0 epoch (ICRF orientation, not dynamical orientation). """ if equinox_of_date: # True equinox of date obl = true_obliquity_of_ecliptic(t) # includes nutation lon = apparent_longitude(t) lat = apparent_latitude(t) # See Astronomical Algorithms (Meeus 1998 p.93) result = np.arcsin(np.sin(lat) * np.cos(obl) + np.cos(lat) * np.sin(obl) * np.sin(lon)) else: # J2000.0 epoch sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=parse_time(t)) result = sun.gcrs.dec return Latitude(result, u.deg) @add_common_docstring(**_variables_for_parse_time_docstring()) def print_params(t='now'): """ Print out a summary of solar ephemeris. 'True' values are true geometric values referred to the mean equinox of date, with no corrections for nutation or aberration. 'Apparent' values are referred to the true equinox of date, with corrections for nutation and aberration (for Earth motion). Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ print('Solar Ephemeris for {} UTC\n'.format(parse_time(t).utc)) print('Distance = {}'.format(earth_distance(t))) print('Semidiameter = {}'.format(angular_radius(t))) print('True (long, lat) = ({}, {})'.format(true_longitude(t).to_string(), true_latitude(t).to_string())) print('Apparent (long, lat) = ({}, {})'.format(apparent_longitude(t).to_string(), apparent_latitude(t).to_string())) print('True (RA, Dec) = ({}, {})'.format(true_rightascension(t).to_string(), true_declination(t).to_string())) print('Apparent (RA, Dec) = ({}, {})'.format(apparent_rightascension(t).to_string(), apparent_declination(t).to_string())) print('Heliographic long. and lat of disk center = ({}, {})'.format(L0(t).to_string(), B0(t).to_string())) print('Position angle of north pole = {}'.format(P(t))) print('Carrington rotation number = {}'.format(carrington_rotation_number(t))) # The following functions belong to this module, but their code is still in the old location of # sunpy.coordinates.ephemeris. That code should be moved here after the deprecation period. # Create functions that call the appropriate private functions in sunpy.coordinates.ephemeris _functions = ['B0', 'L0', 'P', 'earth_distance', 'orientation'] for func in _functions: vars()[func] = vars()['_' + func] vars()[func].__module__ = __name__ # so that docs think that the function is local sunpy-1.0.3/sunpy/coordinates/offset_frame.py0000644000175100001650000001001113531722525021661 0ustar vstsdocker00000000000000import astropy.units as u from astropy.coordinates import SkyOffsetFrame, SphericalRepresentation, UnitSphericalRepresentation __all__ = ['NorthOffsetFrame'] class NorthOffsetFrame: """ A frame which is relative to some position and another frame. Based on `astropy.coordinates.SkyOffsetFrame` Coordinates in a NorthOffsetFrame are both centered on the position specified by the ``north`` keyword *and* they are oriented in the same manner as the ``north`` frame. Unlike `~astropy.coordinates.SkyOffsetFrame` a `NorthOffsetFrame` allows you to specify the position of the new north pole rather than the new origin to centre the new frame. Examples -------- A common use for this is to create a frame derived from Heliographic, which has the north pole at some point of interest. In this new frame, lines of longitude form great circles radially away from the point, and lines of latitude measure angular distance from the point. In this example the new frame is shifted so the new north pole is at (20, 20) in the Heliographic Stonyhurst frame. The new grid is overplotted in blue. .. plot:: :include-source: import matplotlib.pyplot as plt import astropy.units as u from astropy.coordinates import SkyCoord from sunpy.coordinates import NorthOffsetFrame import sunpy.map from sunpy.data.sample import AIA_171_IMAGE m = sunpy.map.Map(AIA_171_IMAGE) north = SkyCoord(20*u.deg, 20*u.deg, frame="heliographic_stonyhurst") new_frame = NorthOffsetFrame(north=north) ax = plt.subplot(projection=m) m.plot() overlay = ax.get_coords_overlay('heliographic_stonyhurst') overlay[0].set_ticks(spacing=30. * u.deg, color='white') overlay.grid(ls='-', color='white') overlay = ax.get_coords_overlay(new_frame) overlay[0].set_ticks(spacing=30. * u.deg) overlay.grid(ls='-', color='blue') Notes ----- ``NorthOffsetFrame`` is a wrapper around the `~astropy.coordinates.SkyOffsetFrame` factory class. This class will calculate the desired coordianates of the ``origin`` from the ``north`` keyword argument and then create a `~astropy.coordinates.SkyOffsetFrame`. Using this frame is equivalent to using `~astropy.coordinates.SkyOffsetFrame` with ``lat = lat - 90*u.deg`` for a position of the north pole in the original northern hemisphere. This class will only work for Heliographic-Stonyhurst and Heliographic Carrington frames, and not helioprojective. If you want to rotate a helioprojective frame, it would be more natural to use the `~astropy.coordinates.SkyOffsetFrame`. """ def __new__(cls, *args, **kwargs): origin_frame = kwargs.pop('north', None) if origin_frame is None: raise TypeError("Can't initialize an NorthOffsetFrame without origin= keyword.") if hasattr(origin_frame, 'frame'): origin_frame = origin_frame.frame if not isinstance(origin_frame.data, SphericalRepresentation): rep = origin_frame.represent_as(SphericalRepresentation) else: rep = origin_frame.data lon = rep.lon lat = rep.lat if lat > 0*u.deg: lat = lat - 90*u.deg rotation = None else: lon = lon - 180*u.deg lat = -90*u.deg - lat rotation = 180*u.deg if isinstance(origin_frame.data, UnitSphericalRepresentation): new_rep = origin_frame.representation_type(lon=lon, lat=lat) else: new_rep = origin_frame.representation_type(lon=lon, lat=lat, distance=rep.distance) new_origin = origin_frame.realize_frame(new_rep) kwargs['origin'] = new_origin kwargs['rotation'] = rotation return SkyOffsetFrame(*args, **kwargs) sunpy-1.0.3/sunpy/coordinates/wcs_utils.py0000644000175100001650000000606213531722525021250 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- import astropy.wcs.utils from astropy.wcs import WCSSUB_CELESTIAL from astropy.wcs import WCS from .frames import (BaseCoordinateFrame, SunPyBaseCoordinateFrame, Helioprojective, Heliocentric, HeliographicStonyhurst, HeliographicCarrington) __all__ = ['solar_wcs_frame_mapping', 'solar_frame_to_wcs_mapping'] def solar_wcs_frame_mapping(wcs): """ This function registers the coordinates frames to their FITS-WCS coordinate type values in the `astropy.wcs.utils.wcs_to_celestial_frame` registry. """ dateobs = wcs.wcs.dateobs or None # SunPy Map adds 'heliographic_observer' and 'rsun' attributes to the WCS # object. We check for them here, and default to None. if hasattr(wcs, 'heliographic_observer'): observer = wcs.heliographic_observer else: observer = None if hasattr(wcs, 'rsun'): rsun = wcs.rsun else: rsun = None # Truncate the ctype to the first four letters ctypes = {c[:4] for c in wcs.wcs.ctype} if {'HPLN', 'HPLT'} <= ctypes: return Helioprojective(obstime=dateobs, observer=observer, rsun=rsun) if {'HGLN', 'HGLT'} <= ctypes: return HeliographicStonyhurst(obstime=dateobs) if {'CRLN', 'CRLT'} <= ctypes: return HeliographicCarrington(obstime=dateobs) if {'SOLX', 'SOLY'} <= ctypes: return Heliocentric(obstime=dateobs, observer=observer) def solar_frame_to_wcs_mapping(frame, projection='TAN'): """ For a given frame, this function returns the corresponding WCS object. It registers the WCS coordinates types from their associated frame in the `astropy.wcs.utils.celestial_frame_to_wcs` registry. """ wcs = WCS(naxis=2) if hasattr(frame, 'rsun'): wcs.rsun = frame.rsun else: wcs.rsun = None if hasattr(frame, 'observer') and isinstance(frame.observer, BaseCoordinateFrame): wcs.heliographic_observer = frame.observer else: wcs.heliographic_observer = None if isinstance(frame, SunPyBaseCoordinateFrame): if frame.obstime: wcs.wcs.dateobs = frame.obstime.utc.isot if isinstance(frame, Helioprojective): xcoord = 'HPLN' + '-' + projection ycoord = 'HPLT' + '-' + projection wcs.wcs.cunit = ['arcsec', 'arcsec'] elif isinstance(frame, Heliocentric): xcoord = 'SOLX' ycoord = 'SOLY' wcs.wcs.cunit = ['deg', 'deg'] elif isinstance(frame, HeliographicCarrington): xcoord = 'CRLN' + '-' + projection ycoord = 'CRLT' + '-' + projection wcs.wcs.cunit = ['deg', 'deg'] elif isinstance(frame, HeliographicStonyhurst): xcoord = 'HGLN' + '-' + projection ycoord = 'HGLT' + '-' + projection wcs.wcs.cunit = ['deg', 'deg'] else: return None wcs.wcs.ctype = [xcoord, ycoord] return wcs astropy.wcs.utils.WCS_FRAME_MAPPINGS.append([solar_wcs_frame_mapping]) astropy.wcs.utils.FRAME_WCS_MAPPINGS.append([solar_frame_to_wcs_mapping]) sunpy-1.0.3/sunpy/coordinates/frames.py0000644000175100001650000005172513531722525020517 0ustar vstsdocker00000000000000""" Common solar physics coordinate systems. This submodule implements various solar physics coordinate frames for use with the `astropy.coordinates` module. """ import numpy as np import astropy.units as u from astropy.coordinates import Attribute, ConvertError from astropy.coordinates.baseframe import BaseCoordinateFrame, RepresentationMapping from astropy.coordinates.representation import (CartesianRepresentation, SphericalRepresentation, CylindricalRepresentation, UnitSphericalRepresentation) from sunpy.sun.constants import radius as _RSUN from .frameattributes import TimeFrameAttributeSunPy, ObserverCoordinateAttribute from sunpy.util.decorators import add_common_docstring from sunpy.time.time import _variables_for_parse_time_docstring __all__ = ['HeliographicStonyhurst', 'HeliographicCarrington', 'Heliocentric', 'Helioprojective'] def _frame_parameters(): """ Returns formatting dictionary to use with add_common_docstring to populate frame docstrings """ ret = {} # Each text block is missing the first indent because it already exists in the frame docstring ret['data'] = ("data : `~astropy.coordinates.BaseRepresentation` or ``None``\n" " A representation object or ``None`` to have no data\n" " (or use the coordinate component arguments, see below).") ret['common'] = (f"obstime : {_variables_for_parse_time_docstring()['parse_time_types']}\n" " The time of the observation. This is used to determine the\n" " position of solar-system bodies (e.g., the Sun and the Earth) as\n" " needed to define the origin and orientation of the frame.\n" " representation_type : `~astropy.coordinates.BaseRepresentation`, str, optional\n" " A representation class or string name of a representation class.\n" " This may change the valid coordinate component arguments from the\n" " defaults (see above). For example, passing\n" " ``representation_type='cartesian'`` will make the frame expect\n" " Cartesian coordinate component arguments (typically, ``x``, ``y``,\n" " and ``z``).\n" " copy : bool, optional\n" " If `True` (default), make copies of the input coordinate arrays.") ret['lonlat'] = ("lon : `~astropy.coordinates.Angle` or `~astropy.units.Quantity`, optional\n" " The longitude coordinate for this object (``lat`` must also be\n" " given and ``data`` must be ``None``).\n" " Not needed if ``data`` is given.\n" " lat : `~astropy.coordinates.Angle` or `~astropy.units.Quantity`, optional\n" " The latitude coordinate for this object (``lon`` must also be\n" " given and ``data`` must be ``None``).\n" " Not needed if ``data`` is given.") ret['radius'] = ("radius : `~astropy.units.Quantity`, optional\n" " The radial distance coordinate from Sun center for this object.\n" " Defaults to the radius of the Sun. Not needed if ``data`` is given.") ret['xyz'] = ("x : `~astropy.units.Quantity`, optional\n" " X-axis coordinate for this object. Not needed if ``data`` is given.\n" " y : `~astropy.units.Quantity`, optional\n" " Y-axis coordinate for this object. Not needed if ``data`` is given.\n" " z : `~astropy.units.Quantity`, optional\n" " Z-axis coordinate for this object. Not needed if ``data`` is given.") ret['observer'] = ("observer : `~sunpy.coordinates.frames.HeliographicStonyhurst`, str\n" " The location of the observer. If a string is provided,\n" " it must be a solar system body that can be parsed by\n" " `~sunpy.coordinates.ephemeris.get_body_heliographic_stonyhurst`\n" " at the time ``obstime``. Defaults to Earth center.") return ret class SunPyBaseCoordinateFrame(BaseCoordinateFrame): """ * Defines the frame attribute ``obstime`` for observation time. * Defines a default longitude wrap angle of 180 degrees, which can be overridden via the class variable ``_wrap_angle``. * Inject a nice way of representing the object which the coordinate represents. """ obstime = TimeFrameAttributeSunPy() _wrap_angle = 180*u.deg def __init__(self, *args, **kwargs): self.object_name = None # If wrap_longitude=False is passed in, do not impose a specific wrap angle for the frame if not kwargs.pop('wrap_longitude', True): self._wrap_angle = None super().__init__(*args, **kwargs) # If obstime is specified, treat the default observer (Earth) as explicitly set if self.obstime is not None and self.is_frame_attr_default('observer'): self._attr_names_with_defaults.remove('observer') return def represent_as(self, base, s='base', in_frame_units=False): """ If a frame wrap angle is set, use that wrap angle for any spherical representations. """ data = super().represent_as(base, s, in_frame_units=in_frame_units) if self._wrap_angle is not None and \ isinstance(data, (UnitSphericalRepresentation, SphericalRepresentation)): data.lon.wrap_angle = self._wrap_angle return data def __str__(self): """ We override this here so that when you print a SkyCoord it shows the observer as the string and not the whole massive coordinate. """ if getattr(self, "object_name", None): return f"<{self.__class__.__name__} Coordinate for '{self.object_name}'>" else: return super().__str__() @add_common_docstring(**_frame_parameters()) class HeliographicStonyhurst(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Stonyhurst Heliographic (HGS) system. - The origin is the center of the Sun. - The Z-axis (+90 degrees latitude) is aligned with the Sun's north pole. - The X-axis (0 degrees longitude and 0 degrees latitude) is aligned with the projection of the Sun-Earth line onto the Sun's equatorial plane. This system is also know as the Heliocentric Earth Equatorial (HEEQ) system when represented using Cartesian components. A new instance can be created using the following signatures (note that ``obstime`` and ``representation_type`` must be supplied as keywords):: HeliographicStonyhurst(lon, lat, obstime) HeliographicStonyhurst(lon, lat, radius, obstime) HeliographicStonyhurst(x, y, z, obstime, representation_type='cartesian') Parameters ---------- {data} {lonlat} {radius} {common} Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(1*u.deg, 1*u.deg, 2*u.km, ... frame="heliographic_stonyhurst", ... obstime="2010/01/01T00:00:45") >>> sc >>> sc.frame >>> sc = SkyCoord(HeliographicStonyhurst(-10*u.deg, 2*u.deg)) >>> sc >>> sc = SkyCoord(CartesianRepresentation(0*u.km, 45*u.km, 2*u.km), ... obstime="2011/01/05T00:00:50", ... frame="heliographic_stonyhurst") >>> sc Notes ----- This frame will always be converted a 3D frame where the radius defaults to ``rsun``. """ name = "heliographic_stonyhurst" default_representation = SphericalRepresentation frame_specific_representation_info = { SphericalRepresentation: [RepresentationMapping(reprname='lon', framename='lon', defaultunit=u.deg), RepresentationMapping(reprname='lat', framename='lat', defaultunit=u.deg), RepresentationMapping(reprname='distance', framename='radius', defaultunit=None)], CartesianRepresentation: [RepresentationMapping(reprname='x', framename='x'), RepresentationMapping(reprname='y', framename='y'), RepresentationMapping(reprname='z', framename='z')] } def __init__(self, *args, **kwargs): _rep_kwarg = kwargs.get('representation_type', None) super().__init__(*args, **kwargs) # Make 3D if specified as 2D # If representation was explicitly passed, do not change the rep. if not _rep_kwarg: if isinstance(self._data, UnitSphericalRepresentation): self._data = self.spherical def represent_as(self, base, s='base', in_frame_units=False): """ Unless the requested representation is UnitSphericalRepresentation, scale a coordinate with dimensionless length so that it has the length of the solar radius. """ data = super().represent_as(base, s, in_frame_units=in_frame_units) if not isinstance(data, UnitSphericalRepresentation) and \ data.norm().unit is u.one and u.allclose(data.norm(), 1*u.one): data *= _RSUN.to(u.km) return data @add_common_docstring(**_frame_parameters()) class HeliographicCarrington(HeliographicStonyhurst): """ A coordinate or frame in the Carrington Heliographic (HGC) system. - The origin is the center of the Sun. - The Z-axis (+90 degrees latitude) is aligned with the Sun's north pole. - The X-axis and Y-axis rotate with a period of 25.38 days. This system differs from Stonyhurst Heliographic (HGS) in its definition of longitude. A new instance can be created using the following signatures (note that ``obstime`` must be supplied as a keyword):: HeliographicCarrington(lon, lat, obstime) HeliographicCarrington(lon, lat, radius, obstime) Parameters ---------- {data} {lonlat} {radius} {common} Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(1*u.deg, 2*u.deg, 3*u.km, ... frame="heliographic_carrington", ... obstime="2010/01/01T00:00:30") >>> sc >>> sc = SkyCoord([1,2,3]*u.deg, [4,5,6]*u.deg, [5,6,7]*u.km, ... obstime="2010/01/01T00:00:45", frame="heliographic_carrington") >>> sc >>> sc = SkyCoord(CartesianRepresentation(0*u.km, 45*u.km, 2*u.km), ... obstime="2011/01/05T00:00:50", ... frame="heliographic_carrington") >>> sc """ name = "heliographic_carrington" _wrap_angle = 360*u.deg @add_common_docstring(**_frame_parameters()) class Heliocentric(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Heliocentric system, which is observer-based. - The origin is the center of the Sun. - The Z-axis is aligned with the Sun-observer line. - The Y-axis is aligned with the component of the vector to the Sun's north pole that is perpendicular to the Z-axis. This frame defaults to a Cartesian component representation, which is known as Heliocentric Cartesian (HCC). This frame can also be represented using cylindrical components, where where ``rho`` is the impact parameter and ``psi`` is the position angle. ``psi`` is measured relative to the west limb, rather than solar north, so is shifted by 90 degrees compared to the convention of the Heliocentric Radial (HCR) system. A new instance can be created using the following signatures (note that ``obstime`` and ``representation_type`` must be supplied as keywords):: Heliocentric(x, y, z, obstime) Heliocentric(rho, psi, z, obstime, representation_type='cylindrical') Parameters ---------- {data} {xyz} {observer} {common} Examples -------- >>> from astropy.coordinates import SkyCoord, CartesianRepresentation >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(CartesianRepresentation(10*u.km, 1*u.km, 2*u.km), ... obstime="2011/01/05T00:00:50", frame="heliocentric") >>> sc ): (x, y, z) in km (10., 1., 2.)> >>> sc = SkyCoord([1,2]*u.km, [3,4]*u.m, [5,6]*u.cm, frame="heliocentric", obstime="2011/01/01T00:00:54") >>> sc ): (x, y, z) in (km, m, cm) [(1., 3., 5.), (2., 4., 6.)]> >>> sc = SkyCoord(CylindricalRepresentation(10*u.km, 60*u.deg, 10*u.km), ... obstime="2011/01/05T00:00:50", ... frame="heliocentric") >>> sc ): (x, y, z) in km (5., 8.66025404, 10.)> """ default_representation = CartesianRepresentation frame_specific_representation_info = { CylindricalRepresentation: [RepresentationMapping('phi', 'psi', u.deg)] } observer = ObserverCoordinateAttribute(HeliographicStonyhurst, default="earth") @add_common_docstring(**_frame_parameters()) class Helioprojective(SunPyBaseCoordinateFrame): """ A coordinate or frame in the Helioprojective Cartesian (HPC) system, which is observer-based. - The origin is the location of the observer. - ``theta_x`` is the angle relative to the plane containing the Sun-observer line and the Sun's rotation axis, with positive values in the direction of the Sun's west limb. - ``theta_y`` is the angle relative to the Sun's equatorial plane, with positive values in the direction of the Sun's north pole. - ``distance`` is the Sun-observer distance. This system is frequently used in a projective form without ``distance`` specified. For observations looking very close to the center of the Sun, where the small-angle approximation is appropriate, ``theta_x`` and ``theta_y`` can be approximated as Cartesian components. A new instance can be created using the following signatures (note that ``obstime`` and ``observer`` must be supplied as keywords):: Helioprojective(theta_x, theta_y, obstime, observer) Helioprojective(theta_x, theta_y, distance, obstime, observer) Parameters ---------- {data} Tx : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_x coordinate for this object. Not needed if ``data`` is given. Ty : `~astropy.coordinates.Angle` or `~astropy.units.Quantity` The theta_y coordinate for this object. Not needed if ``data`` is given. distance : `~astropy.units.Quantity` The distance coordinate from the observer for this object. Not needed if ``data`` is given. {observer} rsun : `~astropy.units.Quantity` The physical (length) radius of the Sun. Used to calculate the position of the limb for calculating distance from the observer to the coordinate. Defaults to the solar radius. {common} Examples -------- >>> from astropy.coordinates import SkyCoord >>> import sunpy.coordinates >>> import astropy.units as u >>> sc = SkyCoord(0*u.deg, 0*u.deg, 5*u.km, obstime="2010/01/01T00:00:00", ... frame="helioprojective") >>> sc ): (Tx, Ty, distance) in (arcsec, arcsec, km) (0., 0., 5.)> >>> sc = SkyCoord(0*u.deg, 0*u.deg, obstime="2010/01/01T00:00:00", frame="helioprojective") >>> sc ): (Tx, Ty) in arcsec (0., 0.)> >>> sc = SkyCoord(CartesianRepresentation(1*u.AU, 1e5*u.km, -2e5*u.km), ... obstime="2011/01/05T00:00:50", ... frame="helioprojective") >>> sc ): (Tx, Ty, distance) in (arcsec, arcsec, AU) (137.87948623, -275.75878762, 1.00000112)> """ default_representation = SphericalRepresentation frame_specific_representation_info = { SphericalRepresentation: [RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec), RepresentationMapping(reprname='distance', framename='distance', defaultunit=None)], UnitSphericalRepresentation: [RepresentationMapping(reprname='lon', framename='Tx', defaultunit=u.arcsec), RepresentationMapping(reprname='lat', framename='Ty', defaultunit=u.arcsec)], } rsun = Attribute(default=_RSUN.to(u.km)) observer = ObserverCoordinateAttribute(HeliographicStonyhurst, default="earth") def calculate_distance(self): """ This method calculates the third coordinate of the Helioprojective frame. It assumes that the coordinate point is on the disk of the Sun at the rsun radius. If a point in the frame is off limb then NaN will be returned. Returns ------- new_frame : `~sunpy.coordinates.frames.HelioProjective` A new frame instance with all the attributes of the original but now with a third coordinate. """ # Skip if we already are 3D distance = self.spherical.distance if not (distance.unit is u.one and u.allclose(distance, 1*u.one)): return self if not isinstance(self.observer, BaseCoordinateFrame): raise ConvertError("Cannot calculate distance to the solar disk " "for observer '{}' " "without `obstime` being specified.".format(self.observer)) rep = self.represent_as(UnitSphericalRepresentation) lat, lon = rep.lat, rep.lon alpha = np.arccos(np.cos(lat) * np.cos(lon)).to(lat.unit) c = self.observer.radius**2 - self.rsun**2 b = -2 * self.observer.radius * np.cos(alpha) # Ingore sqrt of NaNs with np.errstate(invalid='ignore'): d = ((-1*b) - np.sqrt(b**2 - 4*c)) / 2 return self.realize_frame(SphericalRepresentation(lon=lon, lat=lat, distance=d)) sunpy-1.0.3/sunpy/coordinates/ephemeris.py0000644000175100001650000004041413531722525021214 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- """ Ephemeris calculations using SunPy coordinate frames """ import datetime import warnings import numpy as np import astropy.units as u from astropy.time import Time from astropy.coordinates import (SkyCoord, Angle, Longitude, ICRS, PrecessedGeocentric, AltAz, get_body_barycentric) from astropy.coordinates.representation import CartesianRepresentation, SphericalRepresentation from astropy._erfa.core import ErfaWarning from astropy.constants import c as speed_of_light # Versions of Astropy that do not have HeliocentricMeanEcliptic have the same frame # with the misleading name HeliocentricTrueEcliptic try: from astropy.coordinates import HeliocentricMeanEcliptic except ImportError: from astropy.coordinates import HeliocentricTrueEcliptic as HeliocentricMeanEcliptic from sunpy.time import parse_time from sunpy import log from sunpy.util.decorators import add_common_docstring, deprecated from sunpy.time.time import _variables_for_parse_time_docstring from .frames import HeliographicStonyhurst as HGS from .transformations import _SUN_DETILT_MATRIX, _SOLAR_NORTH_POLE_HCRS __author__ = "Albert Y. Shih" __email__ = "ayshih@gmail.com" __all__ = ['get_body_heliographic_stonyhurst', 'get_earth', 'get_sun_B0', 'get_sun_L0', 'get_sun_P', 'get_sunearth_distance', 'get_sun_orientation', 'get_horizons_coord'] @add_common_docstring(**_variables_for_parse_time_docstring()) def get_body_heliographic_stonyhurst(body, time='now', observer=None): """ Return a `~sunpy.coordinates.frames.HeliographicStonyhurst` frame for the location of a solar-system body at a specified time. The location can be corrected for light travel time to an observer. Parameters ---------- body : `str` The solar-system body for which to calculate positions time : {parse_time_types} Time to use in a parse_time-compatible format observer : `~astropy.coordinates.SkyCoord` If None, the returned coordinate is the instantaneous or "true" location. If not None, the returned coordinate is the astrometric location (i.e., accounts for light travel time to the specified observer) Returns ------- out : `~sunpy.coordinates.frames.HeliographicStonyhurst` Location of the solar-system body in the `~sunpy.coordinates.HeliographicStonyhurst` frame Notes ----- There is no correction for aberration due to observer motion. For a body close to the Sun in angular direction relative to the observer, the correction can be negligible because the apparent location of the body will shift in tandem with the Sun. """ obstime = parse_time(time) if observer is None: body_icrs = get_body_barycentric(body, obstime) else: observer_icrs = SkyCoord(observer).icrs.cartesian # This implementation is modeled after Astropy's `_get_apparent_body_position` light_travel_time = 0.*u.s emitted_time = obstime delta_light_travel_time = 1.*u.s # placeholder value while np.any(np.fabs(delta_light_travel_time) > 1.0e-8*u.s): body_icrs = get_body_barycentric(body, emitted_time) distance = (body_icrs - observer_icrs).norm() delta_light_travel_time = light_travel_time - distance / speed_of_light light_travel_time = distance / speed_of_light emitted_time = obstime - light_travel_time log.info(f"Apparent body location accounts for {light_travel_time.to('s').value:.2f}" " seconds of light travel time") body_hgs = ICRS(body_icrs).transform_to(HGS(obstime=obstime)) return body_hgs @add_common_docstring(**_variables_for_parse_time_docstring()) def get_earth(time='now'): """ Return a `~astropy.coordinates.SkyCoord` for the location of the Earth at a specified time in the `~sunpy.coordinates.frames.HeliographicStonyhurst` frame. The longitude will be 0 by definition. Parameters ---------- time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- out : `~astropy.coordinates.SkyCoord` Location of the Earth in the `~sunpy.coordinates.frames.HeliographicStonyhurst` frame """ earth = get_body_heliographic_stonyhurst('earth', time=time) # Explicitly set the longitude to 0 earth = SkyCoord(0*u.deg, earth.lat, earth.radius, frame=earth) return earth @add_common_docstring(**_variables_for_parse_time_docstring()) def get_horizons_coord(body, time='now', id_type='majorbody'): """ Queries JPL HORIZONS and returns a `~astropy.coordinates.SkyCoord` for the location of a solar-system body at a specified time. This location is the instantaneous or "true" location, and is not corrected for light travel time or observer motion. .. note:: This function requires the Astroquery package to be installed and requires an Internet connection. Parameters ---------- body : `str` The solar-system body for which to calculate positions. One can also use the search form linked below to find valid names or ID numbers. id_type : `str` If 'majorbody', search by name for planets, satellites, or other major bodies. If 'smallbody', search by name for asteroids or comets. If 'id', search by ID number. time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- `~astropy.coordinates.SkyCoord` Location of the solar-system body Notes ----- Be aware that there can be discrepancies between the coordinates returned by JPL HORIZONS, the coordinates reported in mission data files, and the coordinates returned by `~sunpy.coordinates.get_body_heliographic_stonyhurst`. References ---------- * `JPL HORIZONS `_ * `JPL HORIZONS form to search bodies `_ * `Astroquery `_ Examples -------- >>> from sunpy.coordinates import get_horizons_coord Query the location of Venus >>> get_horizons_coord('Venus barycenter', '2001-02-03 04:05:06') # doctest: +REMOTE_DATA INFO: Obtained JPL HORIZONS location for Venus Barycenter (2) [sunpy.coordinates.ephemeris] Query the location of the SDO spacecraft >>> get_horizons_coord('SDO', '2011-11-11 11:11:11') # doctest: +REMOTE_DATA INFO: Obtained JPL HORIZONS location for Solar Dynamics Observatory (spac [sunpy.coordinates.ephemeris] Query the location of the SOHO spacecraft via its ID number (-21) >>> get_horizons_coord(-21, '2004-05-06 11:22:33', 'id') # doctest: +REMOTE_DATA INFO: Obtained JPL HORIZONS location for SOHO (spacecraft) (-21) [sunpy.coordinates.ephemeris] """ obstime = parse_time(time) array_time = np.reshape(obstime, (-1,)) # Convert to an array, even if scalar # Import here so that astroquery is not a module-level dependency from astroquery.jplhorizons import Horizons query = Horizons(id=body, id_type=id_type, location='500@10', # Heliocentric (mean ecliptic) epochs=array_time.tdb.jd.tolist()) # Time must be provided in JD TDB try: result = query.vectors() except Exception: # Catch and re-raise all exceptions, and also provide query URL if generated if query.uri is not None: log.error(f"See the raw output from the JPL HORIZONS query at {query.uri}") raise log.info(f"Obtained JPL HORIZONS location for {result[0]['targetname']}") log.debug(f"See the raw output from the JPL HORIZONS query at {query.uri}") # JPL HORIZONS results are sorted by observation time, so this sorting needs to be undone. # Calling argsort() on an array returns the sequence of indices of the unsorted list to put the # list in order. Calling argsort() again on the output of argsort() reverses the mapping: # the output is the sequence of indices of the sorted list to put that list back in the # original unsorted order. unsorted_indices = obstime.argsort().argsort() result = result[unsorted_indices] vector = CartesianRepresentation(result['x'], result['y'], result['z']) coord = SkyCoord(vector, frame=HeliocentricMeanEcliptic, obstime=obstime) return coord.transform_to(HGS).reshape(obstime.shape) # The code beyond this point should be moved to sunpy.coordinates.sun after the deprecation period @add_common_docstring(**_variables_for_parse_time_docstring()) def _B0(time='now'): """ Return the B0 angle for the Sun at a specified time, which is the heliographic latitude of the Sun-disk center as seen from Earth. The range of B0 is +/-7.23 degrees. Parameters ---------- time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- out : `~astropy.coordinates.Angle` The position angle """ return Angle(get_earth(time).lat) # Function returns a SkyCoord's longitude in the de-tilted frame (HCRS rotated so that the Sun's # rotation axis is aligned with the Z axis) def _detilt_lon(coord): coord_detilt = coord.hcrs.cartesian.transform(_SUN_DETILT_MATRIX) return coord_detilt.represent_as(SphericalRepresentation).lon.to('deg') # J2000.0 epoch _J2000 = Time('J2000.0', scale='tt') # One of the two nodes of intersection between the ICRF equator and Sun's equator in HCRS _NODE = SkyCoord(_SOLAR_NORTH_POLE_HCRS.lon + 90*u.deg, 0*u.deg, frame='hcrs') # The longitude in the de-tilted frame of the Sun's prime meridian. # Siedelmann et al. (2007) and earlier define the apparent longitude of the meridian as seen from # Earth as 84.10 degrees eastward from the above-defined node of intersection. # Siedelmann et al. (2007) and later also define the true longitude of the meridian (i.e., without # light travel time to Earth) as 84.176 degrees eastward, but the apparent longitude is needed. _DLON_MERIDIAN = Longitude(_detilt_lon(_NODE) + 84.10*u.deg) @add_common_docstring(**_variables_for_parse_time_docstring()) def _L0(time='now'): """ Return the L0 angle for the Sun at a specified time, which is the Carrington longitude of the Sun-disk center as seen from Earth. Parameters ---------- time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- `~astropy.coordinates.Longitude` The Carrington longitude Notes ----- This longitude is calculated using the values from Siedelmann et al. (2007), with care taken to use the longitude as seen from Earth (see that paper's Appendix). References ---------- * Siedelmann et al. (2007), "Report of the IAU/IAG Working Group on cartographic coordinates and rotational elements: 2006" `(link) `_ """ obstime = parse_time(time) # Calculate the de-tilt longitude of the meridian due to the Sun's sidereal rotation dlon_meridian = Longitude(_DLON_MERIDIAN + (obstime - _J2000) * 14.1844*u.deg/u.day) # Calculate the de-tilt longitude of the Earth dlon_earth = _detilt_lon(get_earth(obstime)) return Longitude(dlon_earth - dlon_meridian) @add_common_docstring(**_variables_for_parse_time_docstring()) def _P(time='now'): """ Return the position (P) angle for the Sun at a specified time, which is the angle between geocentric north and solar north as seen from Earth, measured eastward from geocentric north. The range of P is +/-26.3 degrees. Parameters ---------- time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- out : `~astropy.coordinates.Angle` The position angle """ obstime = parse_time(time) # Define the frame where its Z axis is aligned with geocentric north geocentric = PrecessedGeocentric(equinox=obstime, obstime=obstime) return _sun_north_angle_to_z(geocentric) @add_common_docstring(**_variables_for_parse_time_docstring()) def _earth_distance(time='now'): """ Return the distance between the Sun and the Earth at a specified time. Parameters ---------- time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- out : `~astropy.coordinates.Distance` The Sun-Earth distance """ return get_earth(time).radius @add_common_docstring(**_variables_for_parse_time_docstring()) def _orientation(location, time='now'): """ Return the orientation angle for the Sun from a specified Earth location and time. The orientation angle is the angle between local zenith and solar north, measured eastward from local zenith. Parameters ---------- location : `~astropy.coordinates.EarthLocation` Observer location on Earth time : {parse_time_types} Time to use in a parse_time-compatible format Returns ------- out : `~astropy.coordinates.Angle` The orientation of the Sun """ obstime = parse_time(time) # Define the frame where its Z axis is aligned with local zenith local_frame = AltAz(obstime=obstime, location=location) return _sun_north_angle_to_z(local_frame) def _sun_north_angle_to_z(frame): """ Return the angle between solar north and the Z axis of the provided frame's coordinate system and observation time. """ # Find the Sun center in HGS at the frame's observation time(s) sun_center_repr = SphericalRepresentation(0*u.deg, 0*u.deg, 0*u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_center = SkyCoord(sun_center_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun north in HGS at the frame's observation time(s) # Only a rough value of the solar radius is needed here because, after the cross product, # only the direction from the Sun center to the Sun north pole matters sun_north_repr = SphericalRepresentation(0*u.deg, 90*u.deg, 690000*u.km) # The representation is repeated for as many times as are in obstime prior to transformation sun_north = SkyCoord(sun_north_repr._apply('repeat', frame.obstime.size), frame=HGS, obstime=frame.obstime) # Find the Sun center and Sun north in the frame's coordinate system sky_normal = sun_center.transform_to(frame).data.to_cartesian() sun_north = sun_north.transform_to(frame).data.to_cartesian() # Use cross products to obtain the sky projections of the two vectors (rotated by 90 deg) sun_north_in_sky = sun_north.cross(sky_normal) z_in_sky = CartesianRepresentation(0, 0, 1).cross(sky_normal) # Normalize directional vectors sky_normal /= sky_normal.norm() sun_north_in_sky /= sun_north_in_sky.norm() z_in_sky /= z_in_sky.norm() # Calculate the signed angle between the two projected vectors cos_theta = sun_north_in_sky.dot(z_in_sky) sin_theta = sun_north_in_sky.cross(z_in_sky).dot(sky_normal) angle = np.arctan2(sin_theta, cos_theta).to('deg') # If there is only one time, this function's output should be scalar rather than array if angle.size == 1: angle = angle[0] return Angle(angle) # The following functions are moved in the API to sunpy.coordinates.sun and renamed _old_names = ['get_sun_B0', 'get_sun_L0', 'get_sun_P', 'get_sunearth_distance', 'get_sun_orientation'] _new_module = 'sunpy.coordinates.sun.' _new_names = ['B0', 'L0', 'P', 'earth_distance', 'orientation'] # Create a deprecation hook for each of the functions # Note that the code for each of these functions is still in this module as a private function for old, new in zip(_old_names, _new_names): vars()[old] = deprecated('1.0', name=old, alternative=_new_module + new)(vars()['_' + new]) sunpy-1.0.3/sunpy/coordinates/transformations.py0000644000175100001650000005654213531722525022475 0ustar vstsdocker00000000000000# -*- coding: utf-8 -*- """ Coordinate Transformation Functions This module contains the functions for converting one `sunpy.coordinates.frames` object to another. .. warning:: The functions in this submodule should never be called directly, transforming between coordinate frames should be done using the ``.transform_to`` methods on `~astropy.coordinates.BaseCoordinateFrame` or `~astropy.coordinates.SkyCoord` instances. """ from copy import deepcopy import numpy as np import astropy.units as u from astropy.coordinates import (ICRS, HCRS, ConvertError, BaseCoordinateFrame, get_body_barycentric, get_body_barycentric_posvel) from astropy.coordinates.baseframe import frame_transform_graph from astropy.coordinates.representation import (CartesianRepresentation, SphericalRepresentation, UnitSphericalRepresentation, CartesianDifferential) from astropy.coordinates.transformations import (FunctionTransform, AffineTransform, FunctionTransformWithFiniteDifference) from astropy.coordinates.matrix_utilities import rotation_matrix, matrix_transpose from sunpy.sun import constants from .frames import Heliocentric, Helioprojective, HeliographicCarrington, HeliographicStonyhurst try: from astropy.coordinates.builtin_frames import _make_transform_graph_docs as make_transform_graph_docs except ImportError: from astropy.coordinates import make_transform_graph_docs as _make_transform_graph_docs make_transform_graph_docs = lambda: _make_transform_graph_docs(frame_transform_graph) RSUN_METERS = constants.get('radius').si.to(u.m) __all__ = ['hgs_to_hgc', 'hgc_to_hgs', 'hcc_to_hpc', 'hpc_to_hcc', 'hcc_to_hgs', 'hgs_to_hcc', 'hpc_to_hpc', 'hcrs_to_hgs', 'hgs_to_hcrs', 'hgs_to_hgs', 'hgc_to_hgc', 'hcc_to_hcc'] def _observers_are_equal(obs_1, obs_2, string_ok=False): if string_ok: if obs_1 == obs_2: return True if not (isinstance(obs_1, BaseCoordinateFrame) and isinstance(obs_2, BaseCoordinateFrame)): raise ValueError("To compare two observers, both must be instances of BaseCoordinateFrame. " "Cannot compare two observers {} and {}.".format(obs_1, obs_2)) return (u.allclose(obs_1.lat, obs_2.lat) and u.allclose(obs_1.lon, obs_2.lon) and u.allclose(obs_1.radius, obs_2.radius) and obs_1.obstime == obs_2.obstime) # ============================================================================= # ------------------------- Transformation Framework -------------------------- # ============================================================================= def _transform_obstime(frame, obstime): """ Transform a frame to a new obstime using the appropriate loopback transformation. If the new obstime is None, no transformation is performed. If the frame's obstime is None, the frame is copied with the new obstime. """ # If obstime is None or the obstime matches, nothing needs to be done if obstime is None or np.all(frame.obstime == obstime): return frame # Transform to the new obstime using the appropriate loopback transformation new_frame = frame.replicate(obstime=obstime) if frame.obstime is not None: return frame.transform_to(new_frame) else: return new_frame def _rotation_matrix_hgs_to_hgc(obstime): """ Return the rotation matrix from HGS to HGC at the same observation time """ if obstime is None: raise ValueError("To perform this transformation the coordinate" " Frame needs an obstime Attribute") # Import here to avoid a circular import from .sun import L0 # Rotation is only in longitude, so only around the Z axis return rotation_matrix(-L0(obstime), 'z') @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HeliographicStonyhurst, HeliographicCarrington) def hgs_to_hgc(hgscoord, hgcframe): """ Convert from Heliographic Stonyhurst to Heliographic Carrington. """ # First transform the HGS coord to the HGC obstime int_coord = _transform_obstime(hgscoord, hgcframe.obstime) # Rotate from HGS to HGC total_matrix = _rotation_matrix_hgs_to_hgc(int_coord.obstime) newrepr = int_coord.cartesian.transform(total_matrix) return hgcframe.realize_frame(newrepr) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HeliographicCarrington, HeliographicStonyhurst) def hgc_to_hgs(hgccoord, hgsframe): """ Convert from Heliographic Carrington to Heliographic Stonyhurst. """ # First transform the HGC coord to the HGS obstime int_coord = _transform_obstime(hgccoord, hgsframe.obstime) # Rotate from HGC to HGS total_matrix = matrix_transpose(_rotation_matrix_hgs_to_hgc(int_coord.obstime)) newrepr = int_coord.cartesian.transform(total_matrix) return hgsframe.realize_frame(newrepr) def _matrix_hcc_to_hpc(): # Returns the transformation matrix that permutes/swaps axes from HCC to HPC # HPC spherical coordinates are a left-handed frame with these equivalent Cartesian axes: # HPC_X = -HCC_Z # HPC_Y = HCC_X # HPC_Z = HCC_Y # (HPC_X and HPC_Y are not to be confused with HPC_Tx and HPC_Ty) return np.array([[0, 0, -1], [1, 0, 0], [0, 1, 0]]) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, Heliocentric, Helioprojective) def hcc_to_hpc(helioccoord, heliopframe): """ Convert from Heliocentric Cartesian to Helioprojective Cartesian. """ # Transform the HPC observer (in HGS) to the HPC obstime in case it's different observer = _transform_obstime(heliopframe.observer, heliopframe.obstime) # Loopback transform HCC coord to obstime and observer of HPC frame int_frame = Heliocentric(obstime=heliopframe.obstime, observer=observer) int_coord = helioccoord.transform_to(int_frame) # Shift the origin from the Sun to the observer distance = int_coord.observer.radius newrepr = int_coord.cartesian - CartesianRepresentation(0*u.m, 0*u.m, distance) # Permute/swap axes from HCC to HPC equivalent Cartesian newrepr = newrepr.transform(_matrix_hcc_to_hpc()) # Explicitly represent as spherical because external code (e.g., wcsaxes) expects it return heliopframe.realize_frame(newrepr.represent_as(SphericalRepresentation)) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, Helioprojective, Heliocentric) def hpc_to_hcc(heliopcoord, heliocframe): """ Convert from Helioprojective Cartesian to Heliocentric Cartesian. """ if not isinstance(heliopcoord.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform helioprojective coordinates to " "heliocentric coordinates for observer '{}' " "without `obstime` being specified.".format(heliopcoord.observer)) heliopcoord = heliopcoord.calculate_distance() # Permute/swap axes from HPC equivalent Cartesian to HCC newrepr = heliopcoord.cartesian.transform(matrix_transpose(_matrix_hcc_to_hpc())) # Transform the HPC observer (in HGS) to the HPC obstime in case it's different observer = _transform_obstime(heliopcoord.observer, heliopcoord.obstime) # Shift the origin from the observer to the Sun distance = observer.radius newrepr += CartesianRepresentation(0*u.m, 0*u.m, distance) # Complete the conversion of HPC to HCC at the obstime and observer of the HPC coord int_coord = Heliocentric(newrepr, obstime=heliopcoord.obstime, observer=observer) # Loopback transform HCC as needed return int_coord.transform_to(heliocframe) def _rotation_matrix_hcc_to_hgs(longitude, latitude): # Returns the rotation matrix from HCC to HGS based on the observer longitude and latitude # Permute the axes of HCC to match HGS Cartesian equivalent # HGS_X = HCC_Z # HGS_Y = HCC_X # HGS_Z = HCC_Y axes_matrix = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) # Rotate in latitude and longitude (sign difference because of direction difference) lat_matrix = rotation_matrix(latitude, 'y') lon_matrix = rotation_matrix(-longitude, 'z') return lon_matrix @ lat_matrix @ axes_matrix @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, Heliocentric, HeliographicStonyhurst) def hcc_to_hgs(helioccoord, heliogframe): """ Convert from Heliocentric Cartesian to Heliographic Stonyhurst. """ if not isinstance(helioccoord.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform heliocentric coordinates to " "heliographic coordinates for observer '{}' " "without `obstime` being specified.".format(helioccoord.observer)) # Transform the HCC observer (in HGS) to the HCC obstime in case it's different hcc_observer_at_hcc_obstime = _transform_obstime(helioccoord.observer, helioccoord.obstime) total_matrix = _rotation_matrix_hcc_to_hgs(hcc_observer_at_hcc_obstime.lon, hcc_observer_at_hcc_obstime.lat) # Transform from HCC to HGS at the HCC obstime newrepr = helioccoord.cartesian.transform(total_matrix) int_coord = HeliographicStonyhurst(newrepr, obstime=helioccoord.obstime) # Loopback transform HGS if there is a change in obstime return _transform_obstime(int_coord, heliogframe.obstime) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HeliographicStonyhurst, Heliocentric) def hgs_to_hcc(heliogcoord, heliocframe): """ Convert from Heliographic Stonyhurst to Heliocentric Cartesian. """ if not isinstance(heliocframe.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform heliographic coordinates to " "heliocentric coordinates for observer '{}' " "without `obstime` being specified.".format(heliocframe.observer)) # Loopback transform HGS if there is a change in obstime int_coord = _transform_obstime(heliogcoord, heliocframe.obstime) # Transform the HCC observer (in HGS) to the HCC obstime in case it's different hcc_observer_at_hcc_obstime = _transform_obstime(heliocframe.observer, heliocframe.obstime) total_matrix = matrix_transpose(_rotation_matrix_hcc_to_hgs(hcc_observer_at_hcc_obstime.lon, hcc_observer_at_hcc_obstime.lat)) # Transform from HGS to HCC at the same obstime newrepr = int_coord.cartesian.transform(total_matrix) return heliocframe.realize_frame(newrepr) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, Helioprojective, Helioprojective) def hpc_to_hpc(from_coo, to_frame): """ This converts from HPC to HPC, with different observer location parameters. It does this by transforming through HGS. """ if _observers_are_equal(from_coo.observer, to_frame.observer, string_ok=True) and \ np.all(from_coo.obstime == to_frame.obstime): return to_frame.realize_frame(from_coo.data) if not isinstance(to_frame.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform between helioprojective frames " "without `obstime` being specified for observer {}.".format(to_frame.observer)) if not isinstance(from_coo.observer, BaseCoordinateFrame): raise ConvertError("Cannot transform between helioprojective frames " "without `obstime` being specified for observer {}.".format(from_coo.observer)) hgs = from_coo.transform_to(HeliographicStonyhurst(obstime=to_frame.obstime)) hpc = hgs.transform_to(to_frame) return hpc def _make_rotation_matrix_from_reprs(start_representation, end_representation): """ Return the matrix for the direct rotation from one representation to a second representation. The representations need not be normalized first. """ A = start_representation.to_cartesian() B = end_representation.to_cartesian() rotation_axis = A.cross(B) rotation_angle = -np.arccos(A.dot(B) / (A.norm() * B.norm())) # negation is required # This line works around some input/output quirks of Astropy's rotation_matrix() matrix = np.array(rotation_matrix(rotation_angle, rotation_axis.xyz.value.tolist())) return matrix # The Sun's north pole is oriented RA=286.13 deg, dec=63.87 deg in ICRS, and thus HCRS as well # (See Archinal et al. 2011, # "Report of the IAU Working Group on Cartographic Coordinates and Rotational Elements: 2009") # The orientation of the north pole in ICRS/HCRS is assumed to be constant in time _SOLAR_NORTH_POLE_HCRS = UnitSphericalRepresentation(lon=286.13*u.deg, lat=63.87*u.deg) # Calculate the rotation matrix to de-tilt the Sun's rotation axis to be parallel to the Z axis _SUN_DETILT_MATRIX = _make_rotation_matrix_from_reprs(_SOLAR_NORTH_POLE_HCRS, CartesianRepresentation(0, 0, 1)) @frame_transform_graph.transform(AffineTransform, HCRS, HeliographicStonyhurst) def hcrs_to_hgs(hcrscoord, hgsframe): """ Convert from HCRS to Heliographic Stonyhurst (HGS). HGS shares the same origin (the Sun) as HCRS, but has its Z axis aligned with the Sun's rotation axis and its X axis aligned with the projection of the Sun-Earth vector onto the Sun's equatorial plane (i.e., the component of the Sun-Earth vector perpendicular to the Z axis). Thus, the transformation matrix is the product of the matrix to align the Z axis (by de-tilting the Sun's rotation axis) and the matrix to align the X axis. The first matrix is independent of time and is pre-computed, while the second matrix depends on the time-varying Sun-Earth vector. """ if hgsframe.obstime is None: raise ValueError("To perform this transformation the coordinate" " Frame needs an obstime Attribute") # Check whether differentials are involved on either end has_differentials = ((hcrscoord._data is not None and hcrscoord.data.differentials) or (hgsframe._data is not None and hgsframe.data.differentials)) # Determine the Sun-Earth vector in ICRS # Since HCRS is ICRS with an origin shift, this is also the Sun-Earth vector in HCRS # If differentials exist, also obtain Sun and Earth velocities if has_differentials: sun_pos_icrs, sun_vel = get_body_barycentric_posvel('sun', hgsframe.obstime) earth_pos_icrs, earth_vel = get_body_barycentric_posvel('earth', hgsframe.obstime) else: sun_pos_icrs = get_body_barycentric('sun', hgsframe.obstime) earth_pos_icrs = get_body_barycentric('earth', hgsframe.obstime) sun_earth = earth_pos_icrs - sun_pos_icrs # De-tilt the Sun-Earth vector to the frame with the Sun's rotation axis parallel to the Z axis sun_earth_detilt = sun_earth.transform(_SUN_DETILT_MATRIX) # Remove the component of the Sun-Earth vector that is parallel to the Sun's north pole # (The additional transpose operations are to handle both scalar and array obstime situations) hgs_x_axis_detilt = CartesianRepresentation((sun_earth_detilt.xyz.T * [1, 1, 0]).T) # The above vector, which is in the Sun's equatorial plane, is also the X axis of HGS x_axis = CartesianRepresentation(1, 0, 0) if hgsframe.obstime.isscalar: rot_matrix = _make_rotation_matrix_from_reprs(hgs_x_axis_detilt, x_axis) else: rot_matrix_list = [_make_rotation_matrix_from_reprs(vect, x_axis) for vect in hgs_x_axis_detilt] rot_matrix = np.stack(rot_matrix_list) total_matrix = rot_matrix @ _SUN_DETILT_MATRIX # All of the above is calculated for the HGS observation time # If the HCRS observation time is different, calculate the translation in origin if np.any(hcrscoord.obstime != hgsframe.obstime): sun_pos_old_icrs = get_body_barycentric('sun', hcrscoord.obstime) offset_icrf = sun_pos_icrs - sun_pos_old_icrs else: offset_icrf = sun_pos_icrs * 0 # preserves obstime shape # Add velocity if needed (at the HGS observation time) if has_differentials: vel_icrf = (sun_vel - earth_vel).represent_as(CartesianDifferential) offset_icrf = offset_icrf.with_differentials(vel_icrf) offset = offset_icrf.transform(total_matrix) return total_matrix, offset @frame_transform_graph.transform(AffineTransform, HeliographicStonyhurst, HCRS) def hgs_to_hcrs(hgscoord, hcrsframe): """ Convert from Heliographic Stonyhurst to HCRS. """ # Calculate the matrix and offset in the HCRS->HGS direction total_matrix, offset = hcrs_to_hgs(hcrsframe, hgscoord) # Invert the transformation to get the HGS->HCRS transformation reverse_matrix = matrix_transpose(total_matrix) # If differentials exist, properly negate the velocity if offset.differentials: pos = -offset.without_differentials() vel = -offset.differentials['s'] offset = pos.with_differentials(vel) else: offset = -offset reverse_offset = offset.transform(reverse_matrix) return reverse_matrix, reverse_offset @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HeliographicStonyhurst, HeliographicStonyhurst) def hgs_to_hgs(from_coo, to_frame): """ Convert between two Heliographic Stonyhurst frames. """ if np.all(from_coo.obstime == to_frame.obstime): return to_frame.realize_frame(from_coo.data) else: return from_coo.transform_to(HCRS(obstime=from_coo.obstime)).transform_to(to_frame) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, HeliographicCarrington, HeliographicCarrington) def hgc_to_hgc(from_coo, to_frame): """ Convert between two Heliographic Carrington frames. """ if np.all(from_coo.obstime == to_frame.obstime): return to_frame.realize_frame(from_coo.data) else: return from_coo.transform_to(HeliographicStonyhurst(obstime=from_coo.obstime)).\ transform_to(to_frame) @frame_transform_graph.transform(FunctionTransformWithFiniteDifference, Heliocentric, Heliocentric) def hcc_to_hcc(from_coo, to_frame): """ Convert between two Heliocentric frames. """ if _observers_are_equal(from_coo.observer, to_frame.observer, string_ok=True) and \ np.all(from_coo.obstime == to_frame.obstime): return to_frame.realize_frame(from_coo.data) # Convert through HGS hgscoord = from_coo.transform_to(HeliographicStonyhurst(obstime=to_frame.obstime)) return hgscoord.transform_to(to_frame) def _make_sunpy_graph(): """ Culls down the full transformation graph for SunPy purposes and returns the string version """ # Frames to keep in the transformation graph keep_list = ['icrs', 'hcrs', 'heliocentrictrueecliptic', 'heliocentricmeanecliptic', 'heliographic_stonyhurst', 'heliographic_carrington', 'heliocentric', 'helioprojective', 'gcrs', 'precessedgeocentric', 'geocentrictrueecliptic', 'geocentricmeanecliptic', 'cirs', 'altaz', 'itrs'] global frame_transform_graph backup_graph = deepcopy(frame_transform_graph) small_graph = deepcopy(frame_transform_graph) cull_list = [name for name in small_graph.get_names() if name not in keep_list] cull_frames = [small_graph.lookup_name(name) for name in cull_list] for frame in cull_frames: # Remove the part of the graph where the unwanted frame is the source frame if frame in small_graph._graph: del small_graph._graph[frame] # Remove all instances of the unwanted frame as the destination frame for entry in small_graph._graph: if frame in small_graph._graph[entry]: del (small_graph._graph[entry])[frame] # Clean up the node list for name in cull_list: small_graph._cached_names.pop(name) _add_astropy_node(small_graph) # Overwrite the main transform graph frame_transform_graph = small_graph docstr = make_transform_graph_docs() # Restore the main transform graph frame_transform_graph = backup_graph # Make adjustments to the graph docstr = _tweak_graph(docstr) return docstr def _add_astropy_node(graph): """ Add an 'Astropy' node that links to an ICRS node in the graph """ class Astropy(BaseCoordinateFrame): name = "REPLACE" @graph.transform(FunctionTransform, Astropy, ICRS) def fake_transform1(): pass @graph.transform(FunctionTransform, ICRS, Astropy) def fake_transform2(): pass def _tweak_graph(docstr): # Remove Astropy's diagram description output = docstr[docstr.find('.. Wrap the graph'):] # Change the Astropy node output = output.replace('Astropy [shape=oval label="Astropy\\n`REPLACE`"]', 'Astropy [shape=box3d style=filled fillcolor=lightcyan ' 'label="Other frames\\nin Astropy"]') # Change the Astropy<->ICRS links to black output = output.replace('ICRS -> Astropy[ color = "#783001" ]', 'ICRS -> Astropy[ color = "#000000" ]') output = output.replace('Astropy -> ICRS[ color = "#783001" ]', 'Astropy -> ICRS[ color = "#000000" ]') # Set the nodes to be filled and cyan by default output = output.replace('AstropyCoordinateTransformGraph {', 'AstropyCoordinateTransformGraph {\n' ' node [style=filled fillcolor=lightcyan]') # Set the nodes for SunPy frames to be white sunpy_frames = ['HeliographicStonyhurst', 'HeliographicCarrington', 'Heliocentric', 'Helioprojective'] for frame in sunpy_frames: output = output.replace(frame + ' [', frame + ' [fillcolor=white ') # Set the rank direction to be left->right (as opposed to top->bottom) # Force nodes for ICRS, HCRS, and "Other frames in Astropy" to be at the same rank output = output.replace(' overlap=false', ' overlap=false\n' ' rankdir=LR\n' ' {rank=same; ICRS; HCRS; Astropy}') output = output.replace('