diff --git a/changelog/7220.feature.rst b/changelog/7220.feature.rst new file mode 100644 index 00000000000..f33f9ddd989 --- /dev/null +++ b/changelog/7220.feature.rst @@ -0,0 +1 @@ +Added a GONG synoptic map class which fixes non-compliant FITS metadata diff --git a/sunpy/data/test/gong_synoptic.header b/sunpy/data/test/gong_synoptic.header new file mode 100644 index 00000000000..f97c89fbe44 --- /dev/null +++ b/sunpy/data/test/gong_synoptic.header @@ -0,0 +1,78 @@ +SIMPLE = T / Written by IDL: Sat Sep 30 07:44:13 2023 +BITPIX = -32 / Bits per pixel +NAXIS = 2 / Number of axes +NAXIS1 = 360 / Axis length +NAXIS2 = 180 / Axis length +EXTEND = F / File may contain extensions +ORIGIN = 'National Solar Observatory -- GONG' / FITS file originator +OBS-SITE= 'NSO/GONG NETWORK' / Instrument site location +TELESCOP= 'NSO-GONG' / NSO/GONG Network +OBS-URL = 'http://gong.nso.edu' / The GONG website +DATE = '2023-09-30T06:44:00' +DATE-OBS= '2023-09-30' +TIME-OBS= '06:44 ' +DATATYPE= 'REAL*4 ' / Type of data +WAVELNTH= 676.8 / Wavelength of obs (nm) +CAM_TYPE= 'SMD ' / Camera manufacture type +SN_CAMS = '1014 1016 1027 1029 1017' / Camera serial numbers +CAR_ROT = 2276 / Carr-rot. number of position 0.0 +WCSNAME = 'Carrington Cylin-equal-area' +CTYPE1 = 'CRLN-CEA' / Carrington longitude +CTYPE2 = 'CRLT-CEA' / Carrington sine latitude +CRPIX1 = 180.500 / 180 degrees from initial longitude +CRPIX2 = 90.5000 / Equator of map +CRVAL1 = 130 / Carrington longitude of map cntr (deg) +CRVAL2 = 0.00000 / Sine-lat of Equator +PV2_1 = 1.00000 / Lambda for latitude coordinate +CDELT1 = 1.00000 / Carrington long. step (deg) +CDELT2 = 0.0111111 / Sine-lat step +WCSNAMEA= 'Carrington-rotation Cylin-equal-area' +CTYPE1A = 'CRN-CEA ' / Carrington rotation number +CTYPE2A = 'CRLT-CEA' / Carrington sine latitude +CRPIX1A = 180.500 / Half rotation from initial pixel +CRPIX2A = 90.5000 / Equator of map +CRVAL1A = 2275.638889 / Carr-rot of map cntr +CRVAL2A = 0.00000 / Sine-lat of Equator +PV2_1A = 1.00000 / Lambda for latitude coordinate +CDELT1A = -0.00277777777778 / Carrington step (rotation) +CDELT2A = 0.0111111 / Sine-lat step +IMTYPE = 'MAGNETIC' +NFILETOT= 4281 / Total number of files included +LONG0 = 310 / Init. Carr-long. of synoptic map +IMGMN01 = -0.082834 / Image mean +IMGRMS01= 23.409344 / Image RMS (Standard Deviation) +IMGSKW01= 4.185349 / Image skewness +IMGMIN01= -509.079071 / Image Min +IMGMAX01= 846.215271 / Image Max +IMGADV01= 7.367440 / Image Average Deviation +IMGVAR01= 547.997375 / Image Variance +IMGKUR01= 189.706238 / Image Kurtosis +MMREL = 'v2.3 ' +CARROT = 2276 +BUNIT = 'Gauss ' +LONGMC = 72.0 +LONGIMC = 72 +LONGADJ = -0.3273368 +MAPCOUNT= 4281 +MAPL0 = 10 +MAPEDGE = 310 +CREDGE = 2276.138889 / Carr-rot of map left edge +CRNOW = 2275.9731315 / Carr-rot of Now +CR60 = 2275.9722222 / Carr-rot of 60 degrees in +CRCENTER= 2275.6388889 / Carr-rot of map cntr +MAPNAME = '2276_310' +MAPDATE = '2023-09-30' / UT Date of this map (i.e. of last contributor) +MAPTIME = '06:44 ' / UT Time of this map (i.e. of last contributor) +FILELIST= 'mrbql230930t0644c2276_310' +COMMENT +COMMENT Please note: +COMMENT This is an hourly GONG product with UT date MAPDATE and UT time MAPTIME +COMMENT which is between 59.5 and 60.5 degrees from the left edge in the +COMMENT Carrington frame. +COMMENT +COMMENT This GONG product is produced from the QRII data stream. +COMMENT +COMMENT See http://gong.nso.edu for more information about GONG. +COMMENT +PLATFORM= 'Linux ncs-gong-proc1-lx redhat NOAO/IRAF V2.13-BETA' +FITSWASH= '$RCSfile: mag_hourly.kw,v $ $Revision: 2.4 $ $Date: 2021/11/05 23:35' diff --git a/sunpy/map/sources/__init__.py b/sunpy/map/sources/__init__.py index a2dbc319aa4..ced82a62f80 100644 --- a/sunpy/map/sources/__init__.py +++ b/sunpy/map/sources/__init__.py @@ -7,6 +7,7 @@ """ from sunpy.map.map_factory import Map +from .gong import * from .hinode import * from .iris import * from .mlso import * diff --git a/sunpy/map/sources/gong.py b/sunpy/map/sources/gong.py new file mode 100644 index 00000000000..60e669da87e --- /dev/null +++ b/sunpy/map/sources/gong.py @@ -0,0 +1,59 @@ +""" +GONG Map subclass definitions +""" +import numpy as np + +import astropy.units as u +from astropy.time import Time + +from sunpy.coordinates import get_earth +from sunpy.map import GenericMap + +__all__ = ['GONGSynopticMap'] + +from sunpy.map.mapbase import SpatialPair + + +class GONGSynopticMap(GenericMap): + """ + GONG Synoptic Map. + + The Global Oscillation Network Group (GONG) operates a six-station network of velocity + imagers located around the Earth that observe the Sun nearly continuously. GONG + produces hourly photospheric magnetograms using the Ni I 676.8 nm spectral line with an + array of 242×256 pixels covering the solar disk. These magnetograms are used to derive + synoptic maps which show a full-surface picture of the solar magnetic field. + + References + ---------- + * `GONG Page `_ + * `Magnetogram Synoptic Map Images Page `_ + * `FITS header keywords `_ + * `Instrument Paper (pp. 203–208) `_ + * `GONG+ Documentation `_ + """ + + @classmethod + def is_datasource_for(cls, data, header, **kwargs): + return (str(header.get('TELESCOP', '')).endswith('GONG') and + str(header.get('CTYPE1', '').startswith('CRLN'))) + + @property + def date(self): + return Time(f"{self.meta.get('date-obs')} {self.meta.get('time-obs')}") + + @property + def scale(self): + # Since, this map uses the cylindrical equal-area (CEA) projection, + # the spacing should be modified to 180/pi times the original value + # Reference: Section 5.5, Thompson 2006 + return SpatialPair(self.meta['cdelt1'] * self.spatial_units[0] / u.pixel, + self.meta['cdelt2'] * 180 / np.pi * self.spatial_units[0] / u.pixel) + + @property + def spatial_units(self): + return SpatialPair(u.deg, u.deg) + + @property + def observer_coordinate(self): + return get_earth(self.date) diff --git a/sunpy/map/sources/tests/test_gong_synoptic_source.py b/sunpy/map/sources/tests/test_gong_synoptic_source.py new file mode 100644 index 00000000000..e9a58b215d4 --- /dev/null +++ b/sunpy/map/sources/tests/test_gong_synoptic_source.py @@ -0,0 +1,52 @@ +import pytest + +import astropy.units as u + +from sunpy.data.test import get_dummy_map_from_header, get_test_filepath +from sunpy.map.sources.gong import GONGSynopticMap + + +@pytest.fixture +def gong_synoptic(): + return get_dummy_map_from_header(get_test_filepath('gong_synoptic.header')) + + +def test_fitstoGONGSynoptic(gong_synoptic): + """Tests the creation of GongSynopticMap using FITS.""" + assert isinstance(gong_synoptic, GONGSynopticMap) + + +def test_is_datasource_for(gong_synoptic): + """Test the is_datasource_for method of GongSynopticMap.""" + assert gong_synoptic.is_datasource_for(gong_synoptic.data, gong_synoptic.meta) + + +def test_observatory(gong_synoptic): + """Tests the observatory property of the GongSynopticMap object.""" + assert gong_synoptic.observatory == "NSO-GONG" + + +def test_measurement(gong_synoptic): + """Tests the measurement property of the GongSynopticMap object.""" + assert gong_synoptic.measurement == 676.8 + + +def test_date(gong_synoptic): + """Check that accessing the date doesn't raise a warning.""" + gong_synoptic.date + + +def test_unit(gong_synoptic): + assert gong_synoptic.unit == u.G + assert gong_synoptic.unit == u.Unit("Mx/cm^2") + assert gong_synoptic.unit.to_string() == 'G' + + +def test_spatial_units(gong_synoptic): + assert gong_synoptic.spatial_units[0] == u.deg + assert gong_synoptic.spatial_units[1] == u.deg + + +def test_wcs(gong_synoptic): + # Smoke test that WCS is valid and can transform from pixels to world coordinates + gong_synoptic.pixel_to_world(0*u.pix, 0*u.pix)