Good start for the project
Tags: gsoc
Published: 2024-05-27 13:23:44

## First 2 weeks summarize:

This week I began working on refactoring `hek.py` functions.
I started by migrating the finished work in [GSoC2023](https://github.com/sunpy/sunpy/pull/7059) to a new [PR](https://github.com/sunpy/sunpy/pull/7619) to start working on it.
My first contribution was creating `util.py` file to include all utility functions needed for `hek.py`, a lot of functions that was added in HEKClient at first didn't make sense to remain there.

Now the new `util.py` file includes:
```python
def parse_times(table)
def parse_values_to_quantities(table)
def parse_columns_to_table(table, attributes, is_coord_prop = False)
def parse_unit(table, attribute, is_coord_prop = False)
def parse_chaincode(value, attribute, unit)
def get_unit(unit)

```

`get_unit` has been simplified in terms of implementation and interface, this was the first version:
```python
    def get_unit(attribute, str):
        if attribute["is_coord_prop"]:
            coord1_unit, coord2_unit, coord3_unit = None, None, None
            coord_units = re.split(r'[, ]', str)
            if len(coord_units) == 1: # deg
               coord1_unit = coord2_unit = u.Unit(coord_units[0])
            elif len(coord_units) == 2:
                coord1_unit = u.Unit(coord_units[0])
                coord2_unit = u.Unit(coord_units[1])
            else:
                coord1_unit = u.Unit(coord_units[0])
                coord2_unit = u.Unit(coord_units[1])
                coord3_unit = u.Unit(coord_units[2])
            return locals()[attribute["unit_prop"]]
        else:
            return u.Unit(str)
```
The first thing that has been done is to use unit aliases inside the function using context manager instead of putting the aliases globally.

The whole goal of this function is to parse a string into an astropy unit, but the big part of the function was splitting the string into more than one unit if the input was coordinate units, and then returning the unit assigned to `unit_prop`. I decided to just remove all of this and convert the unit into an array and return the first index, like this:
```python
   units = re.split(r'[, ]', unit)
   return u.Unit(units[0].lower())
```
And actually it works just fine with all HEK features and events, so I will keep it like this until some strange error appears.
And also the interface has been simplified to just take the string of the targeted unit.

This is the current version of `get_unit`:
```python
def get_unit(unit):
    """
    Converts string into astropy unit.

    Parameters
    ----------
    unit: str
        The targeted unit

    Returns
    -------
    unit
        Astropy unit object (e.g. <class 'astropy.units.core.Unit'> or <class 'astropy.units.core.CompositeUnit'>)

    Raises
    ------
    ValueError
        Because `unit` did not parse as unit.

    Notes
    ----
    For the complete list of HEK parameters: https://www.lmsal.com/hek/VOEvent_Spec.html

    """
    cm2 = u.def_unit("cm2", u.cm**3)
    m2 = u.def_unit("m2", u.m**2)
    m3 = u.def_unit("m3", u.m**3)

    aliases = {
        "steradian": u.sr,
        "arcseconds": u.arcsec,
        "degrees": u.deg,
        "sec": u.s,
        "emx": u.Mx,
        "amperes": u.A,
        "ergs": u.erg,
        "cubic centimeter": u.ml,
        "square centimeter": cm2,
        "cubic meter": m3,
        "square meter": m2,
    }

    with u.add_enabled_units([cm2, m2, m3]), u.set_enabled_aliases(aliases):
        # If they are units of coordinates, it will have more than one unit,
        # otherwise it will be just one unit.
        # NOTE: There is an assumption that coord1_unit, coord2_unit and coord3_unit will be the same.
        units = re.split(r'[, ]', unit)
        return u.Unit(units[0].lower())

```

Another thing that has been done was adding a documentation string for `parse_chaincode` function.
```python
def parse_chaincode(value, attribute, unit):
    """
    Parses a string representation of coordinates and convert them into a PolygonSkyRegion object
    using units based on the specified coordinate frame.

    Parameters
    ----------
    value: PolygonSkyRegion
        A polygon defined using vertices in sky coordinates.
    attribute: dict
        An object from coord_properties.json
    unit: str
        The unit of the coordinates

    Returns
    -------
    PolygonSkyRegion
        A polygon defined using vertices in sky coordinates.

    Raises
    ------
    IndexError
        Because `value` does not contain the expected '((' and '))' substrings.
    UnitConversionError
        Because the units set by `coord1_unit` or `coord2_unit` are incompatible with the values being assigned.

    """
    coord1_unit = u.deg
    coord2_unit = u.deg
    if attribute["frame"] == "helioprojective":
        coord1_unit = u.arcsec
        coord2_unit = u.arcsec
    elif attribute["frame"] == "heliocentric":
        coord1_unit = u.R_sun # Nominal solar radius
    elif attribute["frame"] == "icrs":
        coord1_unit = get_unit(unit)
        coord2_unit = get_unit(unit)

    coordinates_str = value.split('((')[1].split('))')[0]
    coord1_list = [float(coord.split()[0]) for coord in coordinates_str.split(',')] * coord1_unit
    coord2_list = [float(coord.split()[1]) for coord in coordinates_str.split(',')] * coord2_unit
    vertices = {}
    if attribute["frame"] == "heliocentric":
        vertices = SkyCoord(coord1_list, coord2_list, [1]* len(coord1_list) * u.AU, representation_type="cylindrical", frame="heliocentric")
    else:
        vertices = SkyCoord(coord1_list, coord2_list, frame=attribute["frame"])

    return PolygonSkyRegion(vertices = vertices)
```