Repository: sunpy/sunpy

Issue:
Rename Map unit parameter
Maps use NDData which carries a units parameter (with an s) and map currently holds the units of the axis in the unit parameter (no s). We should rename the unit parameter to reduce confusion.



Discussion:
The question is @sunpy/sunpy-developers what should we name it to? `world_units` `axes_units` `spatial_units`?

I like `spatial_units`.

Yes that's my preference as well.

`spatial_units` seems reasonable.

Is  world_units better, for consistency with WCS?

On Thu, May 12, 2016 at 7:37 AM, Drew Leonard notifications@github.com
wrote:

> spatial units seems reasonable.
> 
> —
> You are receiving this because you are on a team that was mentioned.
> Reply to this email directly or view it on GitHub
> https://github.com/sunpy/sunpy/issues/1751#issuecomment-218732260

Would it be better to drop this keyword completely and only have the WCS object hold this meta data?

@ehsteve no, that would break the model, these properties abstract the meta data (currently FITS-WCS metadata) from the `meta` the WCS object is then build from these properties.

@wafels `world_units` is maybe more technically correct, but seeing how `Map` is spatial only `spatial_units` makes the possible distinction from `spectral_units` in the future or similar. Of course that could work both ways.

I, however, forsee a future multi-dim object which has `spatial_units`, `spctral_units` and `temporal_units` all extracted from the metadata?

spatial_units +1

`spatial_units` :+1:

`spatial_units` 👍 


Resolving patch:
diff --git a/sunpy/map/compositemap.py b/sunpy/map/compositemap.py
--- a/sunpy/map/compositemap.py
+++ b/sunpy/map/compositemap.py
@@ -433,15 +433,15 @@ def plot(self, axes=None, annotate=True, # pylint: disable=W0613
         if annotate:
             # x-axis label
             if self._maps[0].coordinate_system.x == 'HG':
-                xlabel = 'Longitude [{lon}]'.format(lon=self._maps[0].units.x)
+                xlabel = 'Longitude [{lon}]'.format(lon=self._maps[0].spatial_units.x)
             else:
-                xlabel = 'X-position [{solx}]'.format(solx=self._maps[0].units.x)
+                xlabel = 'X-position [{solx}]'.format(solx=self._maps[0].spatial_units.x)
 
             # y-axis label
             if self._maps[0].coordinate_system.y == 'HG':
-                ylabel = 'Latitude [{lat}]'.format(lat=self._maps[0].units.y)
+                ylabel = 'Latitude [{lat}]'.format(lat=self._maps[0].spatial_units.y)
             else:
-                ylabel = 'Y-position [{soly}]'.format(soly=self._maps[0].units.y)
+                ylabel = 'Y-position [{soly}]'.format(soly=self._maps[0].spatial_units.y)
 
             axes.set_xlabel(xlabel)
             axes.set_ylabel(ylabel)
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -238,7 +238,7 @@ def wcs(self):
         w2.wcs.crval = u.Quantity(self.reference_coordinate)
         w2.wcs.ctype = self.coordinate_system
         w2.wcs.pc = self.rotation_matrix
-        w2.wcs.cunit = self.units
+        w2.wcs.cunit = self.spatial_units
         w2.wcs.dateobs = self.date.isoformat()
         w2.heliographic_latitude = self.heliographic_latitude
         w2.heliographic_longitude = self.heliographic_longitude
@@ -446,8 +446,8 @@ def shift(self, x, y):
         new_meta = self.meta.copy()
 
         # Update crvals
-        new_meta['crval1'] = ((self.meta['crval1'] * self.units.x + x).to(self.units.x)).value
-        new_meta['crval2'] = ((self.meta['crval2'] * self.units.y + y).to(self.units.y)).value
+        new_meta['crval1'] = ((self.meta['crval1'] * self.spatial_units.x + x).to(self.spatial_units.x)).value
+        new_meta['crval2'] = ((self.meta['crval2'] * self.spatial_units.y + y).to(self.spatial_units.y)).value
 
         new_map.meta = new_meta
 
@@ -513,8 +513,8 @@ def heliographic_longitude(self):
     def reference_coordinate(self):
         """Reference point WCS axes in data units (i.e. crval1, crval2). This value
         includes a shift if one is set."""
-        return Pair(self.meta.get('crval1', 0.) * self.units.x,
-                    self.meta.get('crval2', 0.) * self.units.y)
+        return Pair(self.meta.get('crval1', 0.) * self.spatial_units.x,
+                    self.meta.get('crval2', 0.) * self.spatial_units.y)
 
     @property
     def reference_pixel(self):
@@ -526,11 +526,11 @@ def reference_pixel(self):
     def scale(self):
         """Image scale along the x and y axes in units/pixel (i.e. cdelt1, cdelt2)"""
         #TODO: Fix this if only CDi_j matrix is provided
-        return Pair(self.meta.get('cdelt1', 1.) * self.units.x / u.pixel,
-                    self.meta.get('cdelt2', 1.) * self.units.y / u.pixel)
+        return Pair(self.meta.get('cdelt1', 1.) * self.spatial_units.x / u.pixel,
+                    self.meta.get('cdelt2', 1.) * self.spatial_units.y / u.pixel)
 
     @property
-    def units(self):
+    def spatial_units(self):
         """Image coordinate units along the x and y axes (i.e. cunit1, cunit2)."""
         return Pair(u.Unit(self.meta.get('cunit1', 'arcsec')),
                     u.Unit(self.meta.get('cunit2', 'arcsec')))
@@ -699,13 +699,13 @@ def pixel_to_data(self, x, y, origin=0):
             x = u.Quantity(x, u.deg)
             y = u.Quantity(y, u.deg)
         else:
-            x = u.Quantity(x, self.units.x)
-            y = u.Quantity(y, self.units.y)
+            x = u.Quantity(x, self.spatial_units.x)
+            y = u.Quantity(y, self.spatial_units.y)
 
         x = Longitude(x, wrap_angle=180*u.deg)
         y = Latitude(y)
 
-        return x.to(self.units.x), y.to(self.units.y)
+        return x.to(self.spatial_units.x), y.to(self.spatial_units.y)
 
 
 # #### I/O routines #### #
@@ -1058,14 +1058,14 @@ def submap(self, range_a, range_b):
         if ((isinstance(range_a, u.Quantity) and isinstance(range_b, u.Quantity)) or
             (hasattr(range_a, 'unit') and hasattr(range_b, 'unit'))):
 
-            if (range_a.unit.is_equivalent(self.units.x) and
-                range_b.unit.is_equivalent(self.units.y)):
+            if (range_a.unit.is_equivalent(self.spatial_units.x) and
+                range_b.unit.is_equivalent(self.spatial_units.y)):
                 units = 'data'
             elif range_a.unit.is_equivalent(u.pixel) and range_b.unit.is_equivalent(u.pixel):
                 units = 'pixels'
             else:
                 raise u.UnitsError("range_a and range_b but be "
-                                   "in units convertable to {} or {}".format(self.units['x'],
+                                   "in units convertable to {} or {}".format(self.spatial_units['x'],
                                                                              u.pixel))
         else:
             raise TypeError("Arguments range_a and range_b to function submap "
@@ -1210,8 +1210,8 @@ def superpixel(self, dimensions, offset=(0, 0)*u.pixel, func=np.sum):
             new_meta['CD2_2'] *= dimensions[1].value
         new_meta['crpix1'] = (new_nx + 1) / 2.
         new_meta['crpix2'] = (new_ny + 1) / 2.
-        new_meta['crval1'] = self.center.x.to(self.units.x).value + 0.5*(offset[0]*self.scale.x).to(self.units.x).value
-        new_meta['crval2'] = self.center.y.to(self.units.y).value + 0.5*(offset[1]*self.scale.y).to(self.units.y).value
+        new_meta['crval1'] = self.center.x.to(self.spatial_units.x).value + 0.5*(offset[0]*self.scale.x).to(self.spatial_units.x).value
+        new_meta['crval2'] = self.center.y.to(self.spatial_units.y).value + 0.5*(offset[1]*self.scale.y).to(self.spatial_units.y).value
 
         # Create new map instance
         if self.mask is not None:
@@ -1263,7 +1263,7 @@ def draw_grid(self, axes=None, grid_spacing=15*u.deg, **kwargs):
 
         b0 = self.heliographic_latitude.to(u.deg).value
         l0 = self.heliographic_longitude.to(u.deg).value
-        units = self.units
+        units = self.spatial_units
 
         # Prep the plot kwargs
         plot_kw = {'color': 'white',
@@ -1390,7 +1390,7 @@ def draw_rectangle(self, bottom_left, width, height, axes=None, **kwargs):
         if wcsaxes_compat.is_wcsaxes(axes):
             axes_unit = u.deg
         else:
-            axes_unit = self.units[0]
+            axes_unit = self.spatial_units[0]
 
         bottom_left = bottom_left.to(axes_unit).value
         width = width.to(axes_unit).value
@@ -1557,15 +1557,15 @@ def plot(self, annotate=True, axes=None, title=True, **imshow_kwargs):
 
             # x-axis label
             if self.coordinate_system.x == 'HG':
-                xlabel = 'Longitude [{lon}]'.format(lon=self.units.x)
+                xlabel = 'Longitude [{lon}]'.format(lon=self.spatial_units.x)
             else:
-                xlabel = 'X-position [{xpos}]'.format(xpos=self.units.x)
+                xlabel = 'X-position [{xpos}]'.format(xpos=self.spatial_units.x)
 
             # y-axis label
             if self.coordinate_system.y == 'HG':
-                ylabel = 'Latitude [{lat}]'.format(lat=self.units.y)
+                ylabel = 'Latitude [{lat}]'.format(lat=self.spatial_units.y)
             else:
-                ylabel = 'Y-position [{ypos}]'.format(ypos=self.units.y)
+                ylabel = 'Y-position [{ypos}]'.format(ypos=self.spatial_units.y)
 
             axes.set_xlabel(xlabel)
             axes.set_ylabel(ylabel)


Test patch:
diff --git a/sunpy/map/tests/test_mapbase.py b/sunpy/map/tests/test_mapbase.py
--- a/sunpy/map/tests/test_mapbase.py
+++ b/sunpy/map/tests/test_mapbase.py
@@ -87,7 +87,7 @@ def test_wcs(aia171_test_map):
     assert set(wcs.wcs.ctype) == set([aia171_test_map.coordinate_system.x,
                              aia171_test_map.coordinate_system.y])
     np.testing.assert_allclose(wcs.wcs.pc, aia171_test_map.rotation_matrix)
-    assert set(wcs.wcs.cunit) == set([u.Unit(a) for a in aia171_test_map.units])
+    assert set(wcs.wcs.cunit) == set([u.Unit(a) for a in aia171_test_map.spatial_units])
 
 def test_dtype(generic_map):
     assert generic_map.dtype == np.float64
@@ -171,7 +171,7 @@ def test_heliographic_longitude(generic_map):
 
 
 def test_units(generic_map):
-    generic_map.units == ('arcsec', 'arcsec')
+    generic_map.spatial_units == ('arcsec', 'arcsec')
 
 
 #==============================================================================
@@ -269,11 +269,11 @@ def test_shift_applied(generic_map):
     shifted_map = generic_map.shift(x_shift, y_shift)
     assert shifted_map.reference_coordinate.x - x_shift == original_reference_coord[0]
     assert shifted_map.reference_coordinate.y - y_shift == original_reference_coord[1]
-    crval1 = ((generic_map.meta.get('crval1') * generic_map.units.x + \
-             shifted_map.shifted_value.x).to(shifted_map.units.x)).value
+    crval1 = ((generic_map.meta.get('crval1') * generic_map.spatial_units.x + \
+             shifted_map.shifted_value.x).to(shifted_map.spatial_units.x)).value
     assert shifted_map.meta.get('crval1') == crval1
-    crval2 = ((generic_map.meta.get('crval2') * generic_map.units.y + \
-             shifted_map.shifted_value.y).to(shifted_map.units.y)).value
+    crval2 = ((generic_map.meta.get('crval2') * generic_map.spatial_units.y + \
+             shifted_map.shifted_value.y).to(shifted_map.spatial_units.y)).value
     assert shifted_map.meta.get('crval2') == crval2
 
 def test_set_shift(generic_map):