Repository: sunpy/sunpy

Issue:
Ensure Helioviewer client uses Helioviewer API version 2 exclusively
Version 2 of the Helioviewer API is the version that is being updated (version 1 is largely deprecated).  SunPy's helioviewer client - the actual code and the tests - should use version 2 of the API exclusively.


Discussion:
Need to put in 

https://api.helioviewer.org/v2/

somewhere.


Resolving patch:
diff --git a/sunpy/net/helioviewer.py b/sunpy/net/helioviewer.py
--- a/sunpy/net/helioviewer.py
+++ b/sunpy/net/helioviewer.py
@@ -18,46 +18,66 @@
 
 __all__ = ['HelioviewerClient']
 
-
 class HelioviewerClient(object):
     """Helioviewer.org Client"""
-    def __init__(self, url="https://legacy.helioviewer.org/api/"):
+    def __init__(self, url="https://api.helioviewer.org/"):
         """
-        url : location of the Helioviewer API.  The default location points to
-            version 1 of the API.  Version 1 of the Helioviewer API is
-            currently planned to be supported until the end of April 2017.
+        url : str
+            URL that points to the Helioviewer API.
         """
+        self.datasource_info = dict()
         self._api = url
-
+        self._init_src_dict()
+
+    def _init_src_dict(self):
+        datasources = self.get_data_sources()
+        for name, observ in datasources.items():
+            if name == "TRACE":  # TRACE only has measurement parameter
+                for instr, params in observ.items():
+                    self.datasource_info[(name, None, None, instr)] = params['sourceId']
+            else:
+                for inst, detect in observ.items():
+                    for wavelength, params in detect.items():
+                        if name in ["Hinode", "STEREO_A", "STEREO_B"] or wavelength in ["C2", "C3"]:  # These three observatories have instrument, detector and measurement as their parameters
+                            for wave, adict in params.items():
+                                self.datasource_info[(name, inst, wavelength, wave)] = adict['sourceId']
+                        else:
+                            self.datasource_info[(name, inst, None, wavelength)] = params['sourceId']
+        
     def get_data_sources(self, **kwargs):
         """
         Returns a structured list of datasources available at helioviewer.org.
+
+        Returns
+        -------
+        out : `dict`
+            A dictionary containing meta-information for each data source
+            that Helioviewer supports.
         """
         params = {"action": "getDataSources"}
         params.update(kwargs)
-
         return self._get_json(params)
 
-    def get_closest_image(self, date, **kwargs):
+    def get_closest_image(self, date, observatory=None, instrument=None, detector=None,
+                          measurement=None, **kwargs):
         """Finds the closest image available for the specified source and date.
-
         For more information on what types of requests are available and the
         expected usage for the response, consult the Helioviewer API
-        documentation: http://legacy.helioviewer.org/api/docs/v1/ .
+        documentation: https://api.helioviewer.org/docs/v2/ .
 
         Parameters
         ----------
         date : `astropy.time.Time`, str
             A `~sunpy.time.parse_time` parsable string or `~astropy.time.Time` object for the desired date of the image
         observatory : str
-            (Optional) Observatory name
+            Observatory name
         instrument : str
-            (Optional) instrument name
+            instrument name
         detector : str
-            (Optional) detector name
+            detector name
         measurement : str
-            (Optional) measurement name
-        sourceId : int
+            measurement name
+        sourceid : int
             (Optional) data source id
 
         Returns
@@ -69,13 +89,25 @@ def get_closest_image(self, date, **kwargs):
         --------
         >>> from sunpy.net import helioviewer
         >>> client = helioviewer.HelioviewerClient()  # doctest: +REMOTE_DATA
-        >>> metadata = client.get_closest_image('2012/01/01', sourceId=11)  # doctest: +REMOTE_DATA
+        >>> metadata = client.get_closest_image('2012/01/01', sourceid=11)  # doctest: +REMOTE_DATA
         >>> print(metadata['date'])  # doctest: +REMOTE_DATA
         2012-01-01T00:00:07.000
         """
+
+        if('sourceid' in kwargs):
+            source_id = kwargs['sourceid']
+        else:
+            key = (observatory, instrument, detector, measurement)
+            source_id = self.datasource_info[key]
+
         params = {
             "action": "getClosestImage",
-            "date": self._format_date(date)
+            "date": self._format_date(date),
+            "sourceId": source_id,
+            "observatory": observatory,
+            "instrument": instrument,
+            "detector": detector,
+            "measurement": measurement
         }
         params.update(kwargs)
 
@@ -86,12 +118,13 @@ def get_closest_image(self, date, **kwargs):
 
         return response
 
-    def download_jp2(self, date, directory=None, overwrite=False, **kwargs):
+    def download_jp2(self, date, observatory=None, instrument=None, detector=None,
+                     measurement=None, directory=None, overwrite=False, **kwargs):
         """
         Downloads the JPEG 2000 that most closely matches the specified time and
         data source.
 
-        The data source may be specified either using it's sourceId from the
+        The data source may be specified either using it's sourceid from the
         get_data_sources query, or a combination of observatory, instrument,
         detector and measurement.
 
@@ -99,20 +132,19 @@ def download_jp2(self, date, directory=None, overwrite=False, **kwargs):
         ----------
         date : `astropy.time.Time`, str
             A string or `~astropy.time.Time` object for the desired date of the image
+
         directory : str
-            (Optional) Directory to download JPEG 2000 image to.
+            Directory to download JPEG 2000 image to.
         observatory : str
-            (Optional) Observatory name
+            Observatory name
         instrument : str
-            (Optional) instrument name
-        detector : str
-            (Optional) detector name
+            instrument name
         measurement : str
-            (Optional) measurement name
-        sourceId : int
+            measurement name
+        detector : str
+            detector name
+        sourceid : int
             (Optional) data source id
-        jpip : bool
-            (Optional) Returns a JPIP URI if set to True
 
         Returns
         -------
@@ -125,29 +157,39 @@ def download_jp2(self, date, directory=None, overwrite=False, **kwargs):
         >>> import sunpy.map
         >>> from sunpy.net import helioviewer
         >>> hv = helioviewer.HelioviewerClient()  # doctest: +REMOTE_DATA
-        >>> filepath = hv.download_jp2('2012/07/03 14:30:00', observatory='SDO', instrument='AIA', detector='AIA', measurement='171')   # doctest: +REMOTE_DATA
+        >>> filepath = hv.download_jp2('2012/07/03 14:30:00', observatory='SDO', instrument='HMI', detector=None, measurement='continuum')   # doctest: +REMOTE_DATA
         >>> aia = sunpy.map.Map(filepath)   # doctest: +REMOTE_DATA
         >>> aia.peek()   # doctest: +SKIP
-
-        >>> data_sources = hv.get_data_sources()  # doctest: +REMOTE_DATA
-        >>> file = hv.download_jp2('2012/07/03 14:30:00', sourceId=data_sources['SOHO']['LASCO']['C2']['white-light']['sourceId'])   # doctest: +REMOTE_DATA
         """
+
+        if('sourceid' in kwargs):
+            source_id = kwargs['sourceid']
+        else:
+            key = (observatory, instrument, detector, measurement)
+            source_id = self.datasource_info[key]
+
         params = {
             "action": "getJP2Image",
-            "date": self._format_date(date)
+            "date": self._format_date(date),
+            "sourceId" : source_id,
+            "directory": directory,
+            "observatory": observatory,
+            "instrument": instrument,
+            "detector": detector,
+            "measurement": measurement
         }
         params.update(kwargs)
-
         # JPIP URL response
         if 'jpip' in kwargs:
             return self._get_json(params)
 
         return self._get_file(params, directory, overwrite=overwrite)
 
-    def download_png(self, date, image_scale, layers, directory=None,
-                     overwrite=False, **kwargs):
-        """Downloads a PNG image using data from Helioviewer.org.
-
+    def download_png(self, date, image_scale, layers, eventLabels=False, events=None, 
+                     scale=False, watermark=False, directory=None, overwrite=False, **kwargs):
+        """
+        Downloads a PNG image using data from Helioviewer.org. It uses the
+        takeScreenshot function from the API to perform this task.
         Returns a single image containing all layers/image types requested.
         If an image is not available for the date requested the closest
         available image is returned. The region to be included in the
@@ -166,12 +208,29 @@ def download_png(self, date, image_scale, layers, directory=None,
             The zoom scale of the image. Default scales that can be used are
             0.6, 1.2, 2.4, and so on, increasing or decreasing by a factor
             of 2. The full-res scale of an AIA image is 0.6.
+        directory : str
+            Directory to download PNG image to.
         layers : str
+            Image datasource layer/layers to include in the screeshot.
             Each layer string is comma-separated with these values, e.g.:
-            "[sourceId,visible,opacity]" or "[obs,inst,det,meas,visible,opacity]"
+            "[sourceid,visible,opacity]" or "[obs,inst,det,meas,visible,opacity]"
             Multiple layer string are by commas: "[layer1],[layer2],[layer3]"
-        directory : str
-            (Optional)  Directory to download JPEG 2000 image to.
+        eventLabels : bool
+            Optionally annotate each event marker with a text label.
+        events : str
+            (Optional)  List feature/event types and FRMs to use to annoate the
+            movie. Use the empty string to indicate that no feature/event annotations
+            should be shown e.g.: [AR,HMI_HARP;SPoCA,1],[CH,all,1]
+        scale : bool
+            (Optional) Optionally overlay an image scale indicator.
+        scaleType : str
+            (Optional) Image scale indicator.
+        scaleX : number
+            (Optional) Horizontal offset of the image scale indicator in arcseconds with respect
+            to the center of the Sun.
+        scaleY : number
+            (Optional) Vertical offset of the image scale indicator in arcseconds with respect
+            to the center of the Sun.
         x1 : float
             (Optional) The offset of the image's left boundary from the center
             of the sun, in arcseconds.
@@ -192,9 +251,13 @@ def download_png(self, date, image_scale, layers, directory=None,
             (Optional) Width of the image in pixels (Maximum: 1920).
         height : int
             (Optional) Height of the image in pixels (Maximum: 1200).
-        watermark
-            (Optional) Whether or not the include the timestamps and the
-            Helioviewer.org logo in the image (Default=True).
+        watermark : bool
+            (Optional) Optionally overlay a watermark consisting of a
+            Helioviewer logo and the datasource abbreviation(s) and
+            timestamp(s) displayed in the screenshot.
+        display : bool
+            (Optional) Set to `true` to directly output binary PNG image data.
+            Default is `false` (which outputs a JSON object).
 
         Returns
         -------
@@ -206,19 +269,24 @@ def download_png(self, date, image_scale, layers, directory=None,
         >>> from sunpy.net.helioviewer import HelioviewerClient
         >>> import datetime
         >>> hv = HelioviewerClient()  # doctest: +REMOTE_DATA
-        >>> file = hv.download_png('2012/07/16 10:08:00', 2.4, "[SDO,AIA,AIA,171,1,100]", x0=0, y0=0, width=1024, height=1024)   # doctest: +REMOTE_DATA
-        >>> file = hv.download_png('2012/07/16 10:08:00', 4.8, "[SDO,AIA,AIA,171,1,100],[SOHO,LASCO,C2,white-light,1,100]", x1=-2800, x2=2800, y1=-2800, y2=2800)   # doctest: +REMOTE_DATA
-        >>> file = hv.download_jp2(datetime.datetime.now(), observatory='SDO', instrument='HMI', detector='HMI', measurement='continuum')   # doctest: +REMOTE_DATA
+        >>> file = hv.download_png('2012/07/16 10:08:00', 2.4, "[SDO,AIA,AIA,171,1,100]", False, x0=0, y0=0, width=1024, height=1024)   # doctest: +REMOTE_DATA
+        >>> file = hv.download_png('2012/07/16 10:08:00', 4.8, "[SDO,AIA,AIA,171,1,100],[SOHO,LASCO,C2,white-light,1,100]", True, x1=-2800, x2=2800, y1=-2800, y2=2800)   # doctest: +REMOTE_DATA
         """
+        
         params = {
             "action": "takeScreenshot",
             "date": self._format_date(date),
             "imageScale": image_scale,
             "layers": layers,
-            "display": True
+            "eventLabels": eventLabels,
+            "display": True,
+            "events": events,
+            "scale": scale,
+            "watermark": watermark,
+            "directory": directory
         }
-        params.update(kwargs)
 
+        params.update(kwargs)
         return self._get_file(params, directory, overwrite=overwrite)
 
     def is_online(self):


Test patch:
diff --git a/sunpy/net/tests/test_helioviewer.py b/sunpy/net/tests/test_helioviewer.py
--- a/sunpy/net/tests/test_helioviewer.py
+++ b/sunpy/net/tests/test_helioviewer.py
@@ -33,7 +33,7 @@ class TestHelioviewerClient:
     def test_get_datasources(self, client):
         """Makes sure datasource query returns a valid result and source id
         is casted to an integer"""
-        assert type(client.sources['SDO']['AIA']['AIA']['171']['sourceId']) is int
+        assert type(client.sources['SDO']['AIA']['4500']['sourceId']) is int
 
     def test_get_closest_image(self, client):
         """Tests getClosestImage API method"""
@@ -41,14 +41,14 @@ def test_get_closest_image(self, client):
         im1 = client.get_closest_image('1994/01/01',
                                        observatory='SOHO',
                                        instrument='EIT',
-                                       detector='EIT',
-                                       measurement='195')
+                                       detector=None,
+                                       measurement='304')
         assert im1['width'] == im1['height'] == 1024
 
         # result should be same when using source id to query
-        source_id = client.sources['SOHO']['EIT']['EIT']['195']['sourceId']
+        source_id = client.sources['SOHO']['EIT']['304']['sourceId']
 
-        im2 = client.get_closest_image('1994/01/01', sourceId=source_id)
+        im2 = client.get_closest_image('1994/01/01', sourceid=source_id)
 
         assert im1 == im2
 
@@ -56,7 +56,7 @@ def test_get_closest_image(self, client):
     def test_download_jp2(self, client):
         """Tests getJP2Image API method"""
         filepath = client.download_jp2('2020/01/01', observatory='SOHO',
-                                       instrument='MDI', detector='MDI',
+                                       instrument='MDI', detector=None,
                                        measurement='continuum')
         map_ = sunpy.map.Map(filepath)
         assert isinstance(map_, sunpy.map.GenericMap)
@@ -69,8 +69,9 @@ def test_download_jp2_directory_not_exist(self, client, tmpdir):
             '2020/01/01',
             observatory='SOHO',
             instrument='MDI',
-            detector='MDI',
+            detector=None,
             measurement='continuum',
             directory=os.path.join(str(tmpdir), 'directorynotexist'))
 
         assert 'directorynotexist' in filepath
+  
\ No newline at end of file