Repository: yt-project/yt

Issue:
BUG: convert_to_cartesian from _sanitize_center fails for Geographic geometry when bbox is subset of globe 
### Bug report

Geographic data that doesn't cover the full latitude/longitude range errors when plotting. 

**Code for reproduction**

```python
import yt 
import numpy as np
dshp = (16,16,16)
data = {'density': np.random.random(dshp)}

bbox = np.array([[0, 1000], 
                [-150., -100.],   # longitude range 
                [-90., 90.]])

ds = yt.load_uniform_grid(data, dshp, geometry='geographic', 
                          bbox = bbox,
                          axis_order=('altitude', 'longitude', 'latitude'))

yt.SlicePlot(ds, 'altitude', 'density')
```

**Actual outcome**

```
>>> yt.SlicePlot(ds, 'altitude', 'density')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/yt/visualization/plot_window.py", line 1821, in __init__
    (bounds, center, display_center) = get_window_parameters(
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/yt/visualization/plot_window.py", line 71, in get_window_parameters
    center, display_center = ds.coordinates.sanitize_center(center, axis)
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/yt/geometry/coordinates/geographic_coordinates.py", line 429, in sanitize_center
    center, display_center = super().sanitize_center(center, axis)
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/yt/geometry/coordinates/coordinate_handler.py", line 331, in sanitize_center
    display_center = self.convert_to_cartesian(center)
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/yt/geometry/coordinates/geographic_coordinates.py", line 335, in convert_to_cartesian
    theta = (90.0 - b) * np.pi / 180
  File "/Users/chavlin/.pyenv/versions/yt_NASA_SMD/lib/python3.10/site-packages/unyt/array.py", line 1931, in __array_ufunc__
    raise UnitOperationError(ufunc, u0, u1)
unyt.exceptions.UnitOperationError: The <ufunc 'subtract'> operator for unyt_arrays with units 'dimensionless' (dimensions '1') and 'code_length' (dimensions '(length)') is not well defined.
```

**Expected outcome**

plot!

**Version Information**
  * yt version: '4.4.dev0'
  
**Bug Origin**

#4839 fixed the equations in `convert_to_cartesian` for geographic data but didn't account for units (and introduced a subtraction where previously there was only a multiplication) and we must not have any plotting tests for geographic data with subsets of the globe... 



Discussion:

Resolving patch:
diff --git a/yt/geometry/coordinates/geographic_coordinates.py b/yt/geometry/coordinates/geographic_coordinates.py
--- a/yt/geometry/coordinates/geographic_coordinates.py
+++ b/yt/geometry/coordinates/geographic_coordinates.py
@@ -1,4 +1,5 @@
 import numpy as np
+import unyt
 
 from yt.utilities.lib.pixelization_routines import pixelize_cartesian, pixelize_cylinder
 
@@ -318,27 +319,28 @@ def convert_from_cartesian(self, coord):
 
     def convert_to_cartesian(self, coord):
         offset, factor = self._retrieve_radial_offset()
+
         if isinstance(coord, np.ndarray) and len(coord.shape) > 1:
             rad = self.axis_id[self.radial_axis]
             lon = self.axis_id["longitude"]
             lat = self.axis_id["latitude"]
             r = factor * coord[:, rad] + offset
-            theta = (90.0 - coord[:, lat]) * np.pi / 180
+            colatitude = _latitude_to_colatitude(coord[:, lat])
             phi = coord[:, lon] * np.pi / 180
             nc = np.zeros_like(coord)
             # r, theta, phi
-            nc[:, lat] = np.cos(phi) * np.sin(theta) * r
-            nc[:, lon] = np.sin(phi) * np.sin(theta) * r
-            nc[:, rad] = np.cos(theta) * r
+            nc[:, lat] = np.cos(phi) * np.sin(colatitude) * r
+            nc[:, lon] = np.sin(phi) * np.sin(colatitude) * r
+            nc[:, rad] = np.cos(colatitude) * r
         else:
             a, b, c = coord
-            theta = (90.0 - b) * np.pi / 180
+            colatitude = _latitude_to_colatitude(b)
             phi = a * np.pi / 180
             r = factor * c + offset
             nc = (
-                np.cos(phi) * np.sin(theta) * r,
-                np.sin(phi) * np.sin(theta) * r,
-                np.cos(theta) * r,
+                np.cos(phi) * np.sin(colatitude) * r,
+                np.sin(phi) * np.sin(colatitude) * r,
+                np.cos(colatitude) * r,
             )
         return nc
 
@@ -568,3 +570,16 @@ def sanitize_width(self, axis, width, depth):
             outermost = factor * self.ds.domain_left_edge[ri] + offset
             width = [outermost, 2.0 * outermost]
         return width
+
+
+def _latitude_to_colatitude(lat_vals):
+    # convert latitude to theta, accounting for units,
+    # including the case where the units are code_length
+    # due to how yt stores the domain_center units for
+    # geographic coordinates.
+    if isinstance(lat_vals, unyt.unyt_array):
+        if lat_vals.units.dimensions == unyt.dimensions.length:
+            return (90.0 - lat_vals.d) * np.pi / 180.0
+        ninety = unyt.unyt_quantity(90.0, "degree")
+        return (ninety - lat_vals).to("radian")
+    return (90 - lat_vals) * np.pi / 180.0


Test patch:
diff --git a/yt/geometry/coordinates/tests/test_geographic_coordinates.py b/yt/geometry/coordinates/tests/test_geographic_coordinates.py
--- a/yt/geometry/coordinates/tests/test_geographic_coordinates.py
+++ b/yt/geometry/coordinates/tests/test_geographic_coordinates.py
@@ -2,6 +2,7 @@
 
 import numpy as np
 import pytest
+import unyt
 from numpy.testing import assert_equal
 
 from yt.testing import assert_rel_equal, fake_amr_ds
@@ -118,3 +119,30 @@ def test_geographic_conversions(geometry):
     z = xyz[:, 2]
     assert z[0] == r_val
     assert z[1] == -r_val
+
+
+@pytest.mark.parametrize("geometry", ("geographic", "internal_geographic"))
+def test_geographic_conversions_with_units(geometry):
+    ds = fake_amr_ds(geometry=geometry)
+
+    # _sanitize_center will give all values in 'code_length'
+    coords = ds.arr(np.zeros((2, 3)), "code_length")
+    xyz_u = ds.coordinates.convert_to_cartesian(coords)
+    xyz = ds.coordinates.convert_to_cartesian(coords.d)
+    assert_equal(xyz, xyz_u)
+
+    coords = ds.arr(np.zeros((3,)), "code_length")
+    xyz_u = ds.coordinates.convert_to_cartesian(coords)
+    xyz = ds.coordinates.convert_to_cartesian(coords.d)
+    assert_equal(xyz, xyz_u)
+
+    # also check that if correct units are supplied, the
+    # result has dimensions of length.
+    coords = [
+        ds.arr(np.zeros((10,)), "degree"),
+        ds.arr(np.zeros((10,)), "degree"),
+        ds.arr(np.linspace(0, 100, 10), "code_length"),
+    ]
+    xyz = ds.coordinates.convert_to_cartesian(coords)
+    for dim in xyz:
+        assert dim.units.dimensions == unyt.dimensions.length