We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Shape of MapCoord for large non-spatial dimension
**Gammapy version 1.1**

**Bug description**
When creating a MapCoord with 2 coordinates and 3 energy bins calling the `shape` attribute results in a value error.  


**To Reproduce**
Following the example in `MapCoord.create` but using three energies instead of one.
```
from astropy.coordinates import SkyCoord
from gammapy.maps import MapCoord
lon, lat = [1, 2], [2, 3]
skycoord = SkyCoord(lon, lat, unit='deg')
energy = [1000, 3000, 5000]
c = MapCoord.create(dict(skycoord=skycoord,energy=energy))
c.shape
```
Resulting errormessage:
```
ValueError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between arg 0 with shape (2,) and arg 2 with shape (3,).
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gammapy/maps/coord.py`
Content:
```
1 # Licensed under a 3-clause BSD style license - see LICENSE.rst
2 import copy
3 import html
4 import numpy as np
5 from astropy import units as u
6 from astropy.coordinates import SkyCoord
7 
8 __all__ = ["MapCoord"]
9 
10 
11 def skycoord_to_lonlat(skycoord, frame=None):
12     """Convert SkyCoord to lon, lat, frame.
13 
14     Returns
15     -------
16     lon : `~numpy.ndarray`
17         Longitude in degrees.
18     lat : `~numpy.ndarray`
19         Latitude in degrees.
20     """
21     if frame:
22         skycoord = skycoord.transform_to(frame)
23 
24     return skycoord.data.lon.deg, skycoord.data.lat.deg, skycoord.frame.name
25 
26 
27 class MapCoord:
28     """Represents a sequence of n-dimensional map coordinates.
29 
30     Contains coordinates for 2 spatial dimensions and an arbitrary
31     number of additional non-spatial dimensions.
32 
33     For further information see :ref:`mapcoord`.
34 
35     Parameters
36     ----------
37     data : `dict` of `~numpy.ndarray`
38         Dictionary of coordinate arrays.
39     frame : {None, "icrs", "galactic"}
40         Spatial coordinate system. If None then the coordinate system
41         will be set to the native coordinate system of the geometry. Default is None.
42     match_by_name : bool, optional
43         Match coordinates to axes by name. If False, coordinates will be matched by index. Default is True.
44     """
45 
46     def __init__(self, data, frame=None, match_by_name=True):
47         if "lon" not in data or "lat" not in data:
48             raise ValueError("data dictionary must contain axes named 'lon' and 'lat'.")
49 
50         self._data = {k: np.atleast_1d(v) for k, v in data.items()}
51         self._frame = frame
52         self._match_by_name = match_by_name
53 
54     def __getitem__(self, key):
55         if isinstance(key, str):
56             return self._data[key]
57         else:
58             return list(self._data.values())[key]
59 
60     def __setitem__(self, key, value):
61         # TODO: check for broadcastability?
62         self._data[key] = value
63 
64     def __iter__(self):
65         return iter(self._data.values())
66 
67     def _repr_html_(self):
68         try:
69             return self.to_html()
70         except AttributeError:
71             return f"<pre>{html.escape(str(self))}</pre>"
72 
73     @property
74     def ndim(self):
75         """Number of dimensions."""
76         return len(self._data)
77 
78     @property
79     def axis_names(self):
80         """Names of axes."""
81         return list(self._data.keys())
82 
83     @property
84     def shape(self):
85         """Coordinate array shape."""
86         arrays = [_ for _ in self._data.values()]
87         return np.broadcast(*arrays).shape
88 
89     @property
90     def size(self):
91         """Product of all shape elements."""
92         return np.prod(self.shape)
93 
94     @property
95     def lon(self):
96         """Longitude coordinate in degrees."""
97         return self._data["lon"]
98 
99     @property
100     def lat(self):
101         """Latitude coordinate in degrees."""
102         return self._data["lat"]
103 
104     @property
105     def theta(self):
106         """Theta co-latitude angle in radians."""
107         theta = u.Quantity(self.lat, unit="deg", copy=False).to_value("rad")
108         return np.pi / 2.0 - theta
109 
110     @property
111     def phi(self):
112         """Phi longitude angle in radians."""
113         phi = u.Quantity(self.lon, unit="deg", copy=False).to_value("rad")
114         return phi
115 
116     @property
117     def frame(self):
118         """Coordinate system as a string."""
119         return self._frame
120 
121     @property
122     def match_by_name(self):
123         """Boolean flag: axis lookup by name return True or by index return False."""
124         return self._match_by_name
125 
126     @property
127     def skycoord(self):
128         """Coordinate object as a `~astropy.coordinates.SkyCoord`."""
129         return SkyCoord(self.lon, self.lat, unit="deg", frame=self.frame)
130 
131     @classmethod
132     def _from_lonlat(cls, coords, frame=None, axis_names=None):
133         """Create a `~MapCoord` from a tuple of coordinate vectors.
134 
135         The first two elements of the tuple should be longitude and latitude in degrees.
136 
137         Parameters
138         ----------
139         coords : tuple
140             Tuple of `~numpy.ndarray`.
141         frame : {"icrs", "galactic", None}
142             Set the coordinate system for longitude and latitude. If
143             None, longitude and latitude will be assumed to be in
144             the coordinate system native to a given map geometry. Default is None.
145         axis_names : list of str, optional
146             Axis names to use.
147 
148         Returns
149         -------
150         coord : `~MapCoord`
151             A coordinates object.
152         """
153         if axis_names is None:
154             axis_names = [f"axis{idx}" for idx in range(len(coords) - 2)]
155 
156         if isinstance(coords, (list, tuple)):
157             coords_dict = {"lon": coords[0], "lat": coords[1]}
158             for name, c in zip(axis_names, coords[2:]):
159                 coords_dict[name] = c
160         else:
161             raise ValueError("Unrecognized input type.")
162 
163         return cls(coords_dict, frame=frame, match_by_name=False)
164 
165     @classmethod
166     def _from_tuple(cls, coords, frame=None, axis_names=None):
167         """Create from a tuple of coordinate vectors."""
168         if isinstance(coords[0], (list, np.ndarray)) or np.isscalar(coords[0]):
169             return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)
170         elif isinstance(coords[0], SkyCoord):
171             lon, lat, frame = skycoord_to_lonlat(coords[0], frame=frame)
172             coords = (lon, lat) + coords[1:]
173             return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)
174         else:
175             raise TypeError(f"Type not supported: {type(coords)!r}")
176 
177     @classmethod
178     def _from_dict(cls, coords, frame=None):
179         """Create from a dictionary of coordinate vectors."""
180         if "lon" in coords and "lat" in coords:
181             return cls(coords, frame=frame)
182         elif "skycoord" in coords:
183             lon, lat, frame = skycoord_to_lonlat(coords["skycoord"], frame=frame)
184             coords_dict = {"lon": lon, "lat": lat}
185             for k, v in coords.items():
186                 if k == "skycoord":
187                     continue
188                 coords_dict[k] = v
189             return cls(coords_dict, frame=frame)
190         else:
191             raise ValueError("coords dict must contain 'lon'/'lat' or 'skycoord'.")
192 
193     @classmethod
194     def create(cls, data, frame=None, axis_names=None):
195         """Create a new `~MapCoord` object.
196 
197         This method can be used to create either unnamed (with tuple input)
198         or named (via dict input) axes.
199 
200         Parameters
201         ----------
202         data : tuple, dict, `~gammapy.maps.MapCoord` or `~astropy.coordinates.SkyCoord`
203             Object containing coordinate arrays.
204         frame : {"icrs", "galactic", None}
205             Set the coordinate system for longitude and latitude. If
206             None longitude and latitude will be assumed to be in
207             the coordinate system native to a given map geometry. Default is None.
208         axis_names : list of str, optional
209             Axis names uses if a tuple is provided. Default is None.
210 
211         Examples
212         --------
213         >>> from astropy.coordinates import SkyCoord
214         >>> from gammapy.maps import MapCoord
215 
216         >>> lon, lat = [1, 2], [2, 3]
217         >>> skycoord = SkyCoord(lon, lat, unit='deg')
218         >>> energy = [1000]
219         >>> c = MapCoord.create((lon,lat))
220         >>> c = MapCoord.create((skycoord,))
221         >>> c = MapCoord.create((lon,lat,energy))
222         >>> c = MapCoord.create(dict(lon=lon,lat=lat))
223         >>> c = MapCoord.create(dict(lon=lon,lat=lat,energy=energy))
224         >>> c = MapCoord.create(dict(skycoord=skycoord,energy=energy))
225         """
226         if isinstance(data, cls):
227             if data.frame is None or frame == data.frame:
228                 return data
229             else:
230                 return data.to_frame(frame)
231         elif isinstance(data, dict):
232             return cls._from_dict(data, frame=frame)
233         elif isinstance(data, (list, tuple)):
234             return cls._from_tuple(data, frame=frame, axis_names=axis_names)
235         elif isinstance(data, SkyCoord):
236             return cls._from_tuple((data,), frame=frame, axis_names=axis_names)
237         else:
238             raise TypeError(f"Unsupported input type: {type(data)!r}")
239 
240     def to_frame(self, frame):
241         """Convert to a different coordinate frame.
242 
243         Parameters
244         ----------
245         frame : {"icrs", "galactic"}
246             Coordinate system, either Galactic ("galactic") or Equatorial ("icrs").
247 
248         Returns
249         -------
250         coords : `~MapCoord`
251             A coordinates object.
252         """
253         if frame == self.frame:
254             return copy.deepcopy(self)
255         else:
256             lon, lat, frame = skycoord_to_lonlat(self.skycoord, frame=frame)
257             data = copy.deepcopy(self._data)
258             if isinstance(self.lon, u.Quantity):
259                 lon = u.Quantity(lon, unit="deg", copy=False)
260 
261             if isinstance(self.lon, u.Quantity):
262                 lat = u.Quantity(lat, unit="deg", copy=False)
263 
264             data["lon"] = lon
265             data["lat"] = lat
266             return self.__class__(data, frame, self._match_by_name)
267 
268     def apply_mask(self, mask):
269         """Return a masked copy of this coordinate object.
270 
271         Parameters
272         ----------
273         mask : `~numpy.ndarray`
274             Boolean mask.
275 
276         Returns
277         -------
278         coords : `~MapCoord`
279             A coordinates object.
280         """
281         try:
282             data = {k: v[mask] for k, v in self._data.items()}
283         except IndexError:
284             data = {}
285 
286             for name, coord in self._data.items():
287                 if name in ["lon", "lat"]:
288                     data[name] = np.squeeze(coord)[mask]
289                 else:
290                     data[name] = np.squeeze(coord, axis=-1)
291 
292         return self.__class__(data, self.frame, self._match_by_name)
293 
294     @property
295     def flat(self):
296         """Return flattened, valid coordinates."""
297         coords = self.broadcasted
298         is_finite = np.isfinite(coords[0])
299         return coords.apply_mask(is_finite)
300 
301     @property
302     def broadcasted(self):
303         """Return broadcasted coordinates."""
304         vals = np.broadcast_arrays(*self._data.values(), subok=True)
305         data = dict(zip(self._data.keys(), vals))
306         return self.__class__(
307             data=data, frame=self.frame, match_by_name=self._match_by_name
308         )
309 
310     def copy(self):
311         """Copy `MapCoord` object."""
312         return copy.deepcopy(self)
313 
314     def __str__(self):
315         return (
316             f"{self.__class__.__name__}\n\n"
317             f"\taxes     : {list(self._data.keys())}\n"
318             f"\tshape    : {self.shape[::-1]}\n"
319             f"\tndim     : {self.ndim}\n"
320             f"\tframe : {self.frame}\n"
321         )
322
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gammapy/maps/coord.py b/gammapy/maps/coord.py
--- a/gammapy/maps/coord.py
+++ b/gammapy/maps/coord.py
@@ -28,9 +28,10 @@
     """Represents a sequence of n-dimensional map coordinates.
 
     Contains coordinates for 2 spatial dimensions and an arbitrary
-    number of additional non-spatial dimensions.
+    number of additional non-spatial dimensions. Ensure correct broadcasting of the array
+    to allow for this.
 
-    For further information see :ref:`mapcoord`.
+    For further information and examples see :ref:`mapcoord`.
 
     Parameters
     ----------

{"golden_diff": "diff --git a/gammapy/maps/coord.py b/gammapy/maps/coord.py\n--- a/gammapy/maps/coord.py\n+++ b/gammapy/maps/coord.py\n@@ -28,9 +28,10 @@\n     \"\"\"Represents a sequence of n-dimensional map coordinates.\n \n     Contains coordinates for 2 spatial dimensions and an arbitrary\n-    number of additional non-spatial dimensions.\n+    number of additional non-spatial dimensions. Ensure correct broadcasting of the array\n+    to allow for this.\n \n-    For further information see :ref:`mapcoord`.\n+    For further information and examples see :ref:`mapcoord`.\n \n     Parameters\n     ----------\n", "issue": "Shape of MapCoord for large non-spatial dimension\n**Gammapy version 1.1**\r\n\r\n**Bug description**\r\nWhen creating a MapCoord with 2 coordinates and 3 energy bins calling the `shape` attribute results in a value error.  \r\n\r\n\r\n**To Reproduce**\r\nFollowing the example in `MapCoord.create` but using three energies instead of one.\r\n```\r\nfrom astropy.coordinates import SkyCoord\r\nfrom gammapy.maps import MapCoord\r\nlon, lat = [1, 2], [2, 3]\r\nskycoord = SkyCoord(lon, lat, unit='deg')\r\nenergy = [1000, 3000, 5000]\r\nc = MapCoord.create(dict(skycoord=skycoord,energy=energy))\r\nc.shape\r\n```\r\nResulting errormessage:\r\n```\r\nValueError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between arg 0 with shape (2,) and arg 2 with shape (3,).\r\n```\n", "before_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport copy\nimport html\nimport numpy as np\nfrom astropy import units as u\nfrom astropy.coordinates import SkyCoord\n\n__all__ = [\"MapCoord\"]\n\n\ndef skycoord_to_lonlat(skycoord, frame=None):\n    \"\"\"Convert SkyCoord to lon, lat, frame.\n\n    Returns\n    -------\n    lon : `~numpy.ndarray`\n        Longitude in degrees.\n    lat : `~numpy.ndarray`\n        Latitude in degrees.\n    \"\"\"\n    if frame:\n        skycoord = skycoord.transform_to(frame)\n\n    return skycoord.data.lon.deg, skycoord.data.lat.deg, skycoord.frame.name\n\n\nclass MapCoord:\n    \"\"\"Represents a sequence of n-dimensional map coordinates.\n\n    Contains coordinates for 2 spatial dimensions and an arbitrary\n    number of additional non-spatial dimensions.\n\n    For further information see :ref:`mapcoord`.\n\n    Parameters\n    ----------\n    data : `dict` of `~numpy.ndarray`\n        Dictionary of coordinate arrays.\n    frame : {None, \"icrs\", \"galactic\"}\n        Spatial coordinate system. If None then the coordinate system\n        will be set to the native coordinate system of the geometry. Default is None.\n    match_by_name : bool, optional\n        Match coordinates to axes by name. If False, coordinates will be matched by index. Default is True.\n    \"\"\"\n\n    def __init__(self, data, frame=None, match_by_name=True):\n        if \"lon\" not in data or \"lat\" not in data:\n            raise ValueError(\"data dictionary must contain axes named 'lon' and 'lat'.\")\n\n        self._data = {k: np.atleast_1d(v) for k, v in data.items()}\n        self._frame = frame\n        self._match_by_name = match_by_name\n\n    def __getitem__(self, key):\n        if isinstance(key, str):\n            return self._data[key]\n        else:\n            return list(self._data.values())[key]\n\n    def __setitem__(self, key, value):\n        # TODO: check for broadcastability?\n        self._data[key] = value\n\n    def __iter__(self):\n        return iter(self._data.values())\n\n    def _repr_html_(self):\n        try:\n            return self.to_html()\n        except AttributeError:\n            return f\"<pre>{html.escape(str(self))}</pre>\"\n\n    @property\n    def ndim(self):\n        \"\"\"Number of dimensions.\"\"\"\n        return len(self._data)\n\n    @property\n    def axis_names(self):\n        \"\"\"Names of axes.\"\"\"\n        return list(self._data.keys())\n\n    @property\n    def shape(self):\n        \"\"\"Coordinate array shape.\"\"\"\n        arrays = [_ for _ in self._data.values()]\n        return np.broadcast(*arrays).shape\n\n    @property\n    def size(self):\n        \"\"\"Product of all shape elements.\"\"\"\n        return np.prod(self.shape)\n\n    @property\n    def lon(self):\n        \"\"\"Longitude coordinate in degrees.\"\"\"\n        return self._data[\"lon\"]\n\n    @property\n    def lat(self):\n        \"\"\"Latitude coordinate in degrees.\"\"\"\n        return self._data[\"lat\"]\n\n    @property\n    def theta(self):\n        \"\"\"Theta co-latitude angle in radians.\"\"\"\n        theta = u.Quantity(self.lat, unit=\"deg\", copy=False).to_value(\"rad\")\n        return np.pi / 2.0 - theta\n\n    @property\n    def phi(self):\n        \"\"\"Phi longitude angle in radians.\"\"\"\n        phi = u.Quantity(self.lon, unit=\"deg\", copy=False).to_value(\"rad\")\n        return phi\n\n    @property\n    def frame(self):\n        \"\"\"Coordinate system as a string.\"\"\"\n        return self._frame\n\n    @property\n    def match_by_name(self):\n        \"\"\"Boolean flag: axis lookup by name return True or by index return False.\"\"\"\n        return self._match_by_name\n\n    @property\n    def skycoord(self):\n        \"\"\"Coordinate object as a `~astropy.coordinates.SkyCoord`.\"\"\"\n        return SkyCoord(self.lon, self.lat, unit=\"deg\", frame=self.frame)\n\n    @classmethod\n    def _from_lonlat(cls, coords, frame=None, axis_names=None):\n        \"\"\"Create a `~MapCoord` from a tuple of coordinate vectors.\n\n        The first two elements of the tuple should be longitude and latitude in degrees.\n\n        Parameters\n        ----------\n        coords : tuple\n            Tuple of `~numpy.ndarray`.\n        frame : {\"icrs\", \"galactic\", None}\n            Set the coordinate system for longitude and latitude. If\n            None, longitude and latitude will be assumed to be in\n            the coordinate system native to a given map geometry. Default is None.\n        axis_names : list of str, optional\n            Axis names to use.\n\n        Returns\n        -------\n        coord : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        if axis_names is None:\n            axis_names = [f\"axis{idx}\" for idx in range(len(coords) - 2)]\n\n        if isinstance(coords, (list, tuple)):\n            coords_dict = {\"lon\": coords[0], \"lat\": coords[1]}\n            for name, c in zip(axis_names, coords[2:]):\n                coords_dict[name] = c\n        else:\n            raise ValueError(\"Unrecognized input type.\")\n\n        return cls(coords_dict, frame=frame, match_by_name=False)\n\n    @classmethod\n    def _from_tuple(cls, coords, frame=None, axis_names=None):\n        \"\"\"Create from a tuple of coordinate vectors.\"\"\"\n        if isinstance(coords[0], (list, np.ndarray)) or np.isscalar(coords[0]):\n            return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)\n        elif isinstance(coords[0], SkyCoord):\n            lon, lat, frame = skycoord_to_lonlat(coords[0], frame=frame)\n            coords = (lon, lat) + coords[1:]\n            return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)\n        else:\n            raise TypeError(f\"Type not supported: {type(coords)!r}\")\n\n    @classmethod\n    def _from_dict(cls, coords, frame=None):\n        \"\"\"Create from a dictionary of coordinate vectors.\"\"\"\n        if \"lon\" in coords and \"lat\" in coords:\n            return cls(coords, frame=frame)\n        elif \"skycoord\" in coords:\n            lon, lat, frame = skycoord_to_lonlat(coords[\"skycoord\"], frame=frame)\n            coords_dict = {\"lon\": lon, \"lat\": lat}\n            for k, v in coords.items():\n                if k == \"skycoord\":\n                    continue\n                coords_dict[k] = v\n            return cls(coords_dict, frame=frame)\n        else:\n            raise ValueError(\"coords dict must contain 'lon'/'lat' or 'skycoord'.\")\n\n    @classmethod\n    def create(cls, data, frame=None, axis_names=None):\n        \"\"\"Create a new `~MapCoord` object.\n\n        This method can be used to create either unnamed (with tuple input)\n        or named (via dict input) axes.\n\n        Parameters\n        ----------\n        data : tuple, dict, `~gammapy.maps.MapCoord` or `~astropy.coordinates.SkyCoord`\n            Object containing coordinate arrays.\n        frame : {\"icrs\", \"galactic\", None}\n            Set the coordinate system for longitude and latitude. If\n            None longitude and latitude will be assumed to be in\n            the coordinate system native to a given map geometry. Default is None.\n        axis_names : list of str, optional\n            Axis names uses if a tuple is provided. Default is None.\n\n        Examples\n        --------\n        >>> from astropy.coordinates import SkyCoord\n        >>> from gammapy.maps import MapCoord\n\n        >>> lon, lat = [1, 2], [2, 3]\n        >>> skycoord = SkyCoord(lon, lat, unit='deg')\n        >>> energy = [1000]\n        >>> c = MapCoord.create((lon,lat))\n        >>> c = MapCoord.create((skycoord,))\n        >>> c = MapCoord.create((lon,lat,energy))\n        >>> c = MapCoord.create(dict(lon=lon,lat=lat))\n        >>> c = MapCoord.create(dict(lon=lon,lat=lat,energy=energy))\n        >>> c = MapCoord.create(dict(skycoord=skycoord,energy=energy))\n        \"\"\"\n        if isinstance(data, cls):\n            if data.frame is None or frame == data.frame:\n                return data\n            else:\n                return data.to_frame(frame)\n        elif isinstance(data, dict):\n            return cls._from_dict(data, frame=frame)\n        elif isinstance(data, (list, tuple)):\n            return cls._from_tuple(data, frame=frame, axis_names=axis_names)\n        elif isinstance(data, SkyCoord):\n            return cls._from_tuple((data,), frame=frame, axis_names=axis_names)\n        else:\n            raise TypeError(f\"Unsupported input type: {type(data)!r}\")\n\n    def to_frame(self, frame):\n        \"\"\"Convert to a different coordinate frame.\n\n        Parameters\n        ----------\n        frame : {\"icrs\", \"galactic\"}\n            Coordinate system, either Galactic (\"galactic\") or Equatorial (\"icrs\").\n\n        Returns\n        -------\n        coords : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        if frame == self.frame:\n            return copy.deepcopy(self)\n        else:\n            lon, lat, frame = skycoord_to_lonlat(self.skycoord, frame=frame)\n            data = copy.deepcopy(self._data)\n            if isinstance(self.lon, u.Quantity):\n                lon = u.Quantity(lon, unit=\"deg\", copy=False)\n\n            if isinstance(self.lon, u.Quantity):\n                lat = u.Quantity(lat, unit=\"deg\", copy=False)\n\n            data[\"lon\"] = lon\n            data[\"lat\"] = lat\n            return self.__class__(data, frame, self._match_by_name)\n\n    def apply_mask(self, mask):\n        \"\"\"Return a masked copy of this coordinate object.\n\n        Parameters\n        ----------\n        mask : `~numpy.ndarray`\n            Boolean mask.\n\n        Returns\n        -------\n        coords : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        try:\n            data = {k: v[mask] for k, v in self._data.items()}\n        except IndexError:\n            data = {}\n\n            for name, coord in self._data.items():\n                if name in [\"lon\", \"lat\"]:\n                    data[name] = np.squeeze(coord)[mask]\n                else:\n                    data[name] = np.squeeze(coord, axis=-1)\n\n        return self.__class__(data, self.frame, self._match_by_name)\n\n    @property\n    def flat(self):\n        \"\"\"Return flattened, valid coordinates.\"\"\"\n        coords = self.broadcasted\n        is_finite = np.isfinite(coords[0])\n        return coords.apply_mask(is_finite)\n\n    @property\n    def broadcasted(self):\n        \"\"\"Return broadcasted coordinates.\"\"\"\n        vals = np.broadcast_arrays(*self._data.values(), subok=True)\n        data = dict(zip(self._data.keys(), vals))\n        return self.__class__(\n            data=data, frame=self.frame, match_by_name=self._match_by_name\n        )\n\n    def copy(self):\n        \"\"\"Copy `MapCoord` object.\"\"\"\n        return copy.deepcopy(self)\n\n    def __str__(self):\n        return (\n            f\"{self.__class__.__name__}\\n\\n\"\n            f\"\\taxes     : {list(self._data.keys())}\\n\"\n            f\"\\tshape    : {self.shape[::-1]}\\n\"\n            f\"\\tndim     : {self.ndim}\\n\"\n            f\"\\tframe : {self.frame}\\n\"\n        )\n", "path": "gammapy/maps/coord.py"}], "after_files": [{"content": "# Licensed under a 3-clause BSD style license - see LICENSE.rst\nimport copy\nimport html\nimport numpy as np\nfrom astropy import units as u\nfrom astropy.coordinates import SkyCoord\n\n__all__ = [\"MapCoord\"]\n\n\ndef skycoord_to_lonlat(skycoord, frame=None):\n    \"\"\"Convert SkyCoord to lon, lat, frame.\n\n    Returns\n    -------\n    lon : `~numpy.ndarray`\n        Longitude in degrees.\n    lat : `~numpy.ndarray`\n        Latitude in degrees.\n    \"\"\"\n    if frame:\n        skycoord = skycoord.transform_to(frame)\n\n    return skycoord.data.lon.deg, skycoord.data.lat.deg, skycoord.frame.name\n\n\nclass MapCoord:\n    \"\"\"Represents a sequence of n-dimensional map coordinates.\n\n    Contains coordinates for 2 spatial dimensions and an arbitrary\n    number of additional non-spatial dimensions. Ensure correct broadcasting of the array\n    to allow for this.\n\n    For further information and examples see :ref:`mapcoord`.\n\n    Parameters\n    ----------\n    data : `dict` of `~numpy.ndarray`\n        Dictionary of coordinate arrays.\n    frame : {None, \"icrs\", \"galactic\"}\n        Spatial coordinate system. If None then the coordinate system\n        will be set to the native coordinate system of the geometry. Default is None.\n    match_by_name : bool, optional\n        Match coordinates to axes by name. If False, coordinates will be matched by index. Default is True.\n    \"\"\"\n\n    def __init__(self, data, frame=None, match_by_name=True):\n        if \"lon\" not in data or \"lat\" not in data:\n            raise ValueError(\"data dictionary must contain axes named 'lon' and 'lat'.\")\n\n        self._data = {k: np.atleast_1d(v) for k, v in data.items()}\n        self._frame = frame\n        self._match_by_name = match_by_name\n\n    def __getitem__(self, key):\n        if isinstance(key, str):\n            return self._data[key]\n        else:\n            return list(self._data.values())[key]\n\n    def __setitem__(self, key, value):\n        # TODO: check for broadcastability?\n        self._data[key] = value\n\n    def __iter__(self):\n        return iter(self._data.values())\n\n    def _repr_html_(self):\n        try:\n            return self.to_html()\n        except AttributeError:\n            return f\"<pre>{html.escape(str(self))}</pre>\"\n\n    @property\n    def ndim(self):\n        \"\"\"Number of dimensions.\"\"\"\n        return len(self._data)\n\n    @property\n    def axis_names(self):\n        \"\"\"Names of axes.\"\"\"\n        return list(self._data.keys())\n\n    @property\n    def shape(self):\n        \"\"\"Coordinate array shape.\"\"\"\n        arrays = [_ for _ in self._data.values()]\n        return np.broadcast(*arrays).shape\n\n    @property\n    def size(self):\n        \"\"\"Product of all shape elements.\"\"\"\n        return np.prod(self.shape)\n\n    @property\n    def lon(self):\n        \"\"\"Longitude coordinate in degrees.\"\"\"\n        return self._data[\"lon\"]\n\n    @property\n    def lat(self):\n        \"\"\"Latitude coordinate in degrees.\"\"\"\n        return self._data[\"lat\"]\n\n    @property\n    def theta(self):\n        \"\"\"Theta co-latitude angle in radians.\"\"\"\n        theta = u.Quantity(self.lat, unit=\"deg\", copy=False).to_value(\"rad\")\n        return np.pi / 2.0 - theta\n\n    @property\n    def phi(self):\n        \"\"\"Phi longitude angle in radians.\"\"\"\n        phi = u.Quantity(self.lon, unit=\"deg\", copy=False).to_value(\"rad\")\n        return phi\n\n    @property\n    def frame(self):\n        \"\"\"Coordinate system as a string.\"\"\"\n        return self._frame\n\n    @property\n    def match_by_name(self):\n        \"\"\"Boolean flag: axis lookup by name return True or by index return False.\"\"\"\n        return self._match_by_name\n\n    @property\n    def skycoord(self):\n        \"\"\"Coordinate object as a `~astropy.coordinates.SkyCoord`.\"\"\"\n        return SkyCoord(self.lon, self.lat, unit=\"deg\", frame=self.frame)\n\n    @classmethod\n    def _from_lonlat(cls, coords, frame=None, axis_names=None):\n        \"\"\"Create a `~MapCoord` from a tuple of coordinate vectors.\n\n        The first two elements of the tuple should be longitude and latitude in degrees.\n\n        Parameters\n        ----------\n        coords : tuple\n            Tuple of `~numpy.ndarray`.\n        frame : {\"icrs\", \"galactic\", None}\n            Set the coordinate system for longitude and latitude. If\n            None, longitude and latitude will be assumed to be in\n            the coordinate system native to a given map geometry. Default is None.\n        axis_names : list of str, optional\n            Axis names to use.\n\n        Returns\n        -------\n        coord : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        if axis_names is None:\n            axis_names = [f\"axis{idx}\" for idx in range(len(coords) - 2)]\n\n        if isinstance(coords, (list, tuple)):\n            coords_dict = {\"lon\": coords[0], \"lat\": coords[1]}\n            for name, c in zip(axis_names, coords[2:]):\n                coords_dict[name] = c\n        else:\n            raise ValueError(\"Unrecognized input type.\")\n\n        return cls(coords_dict, frame=frame, match_by_name=False)\n\n    @classmethod\n    def _from_tuple(cls, coords, frame=None, axis_names=None):\n        \"\"\"Create from a tuple of coordinate vectors.\"\"\"\n        if isinstance(coords[0], (list, np.ndarray)) or np.isscalar(coords[0]):\n            return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)\n        elif isinstance(coords[0], SkyCoord):\n            lon, lat, frame = skycoord_to_lonlat(coords[0], frame=frame)\n            coords = (lon, lat) + coords[1:]\n            return cls._from_lonlat(coords, frame=frame, axis_names=axis_names)\n        else:\n            raise TypeError(f\"Type not supported: {type(coords)!r}\")\n\n    @classmethod\n    def _from_dict(cls, coords, frame=None):\n        \"\"\"Create from a dictionary of coordinate vectors.\"\"\"\n        if \"lon\" in coords and \"lat\" in coords:\n            return cls(coords, frame=frame)\n        elif \"skycoord\" in coords:\n            lon, lat, frame = skycoord_to_lonlat(coords[\"skycoord\"], frame=frame)\n            coords_dict = {\"lon\": lon, \"lat\": lat}\n            for k, v in coords.items():\n                if k == \"skycoord\":\n                    continue\n                coords_dict[k] = v\n            return cls(coords_dict, frame=frame)\n        else:\n            raise ValueError(\"coords dict must contain 'lon'/'lat' or 'skycoord'.\")\n\n    @classmethod\n    def create(cls, data, frame=None, axis_names=None):\n        \"\"\"Create a new `~MapCoord` object.\n\n        This method can be used to create either unnamed (with tuple input)\n        or named (via dict input) axes.\n\n        Parameters\n        ----------\n        data : tuple, dict, `~gammapy.maps.MapCoord` or `~astropy.coordinates.SkyCoord`\n            Object containing coordinate arrays.\n        frame : {\"icrs\", \"galactic\", None}\n            Set the coordinate system for longitude and latitude. If\n            None longitude and latitude will be assumed to be in\n            the coordinate system native to a given map geometry. Default is None.\n        axis_names : list of str, optional\n            Axis names uses if a tuple is provided. Default is None.\n\n        Examples\n        --------\n        >>> from astropy.coordinates import SkyCoord\n        >>> from gammapy.maps import MapCoord\n\n        >>> lon, lat = [1, 2], [2, 3]\n        >>> skycoord = SkyCoord(lon, lat, unit='deg')\n        >>> energy = [1000]\n        >>> c = MapCoord.create((lon,lat))\n        >>> c = MapCoord.create((skycoord,))\n        >>> c = MapCoord.create((lon,lat,energy))\n        >>> c = MapCoord.create(dict(lon=lon,lat=lat))\n        >>> c = MapCoord.create(dict(lon=lon,lat=lat,energy=energy))\n        >>> c = MapCoord.create(dict(skycoord=skycoord,energy=energy))\n        \"\"\"\n        if isinstance(data, cls):\n            if data.frame is None or frame == data.frame:\n                return data\n            else:\n                return data.to_frame(frame)\n        elif isinstance(data, dict):\n            return cls._from_dict(data, frame=frame)\n        elif isinstance(data, (list, tuple)):\n            return cls._from_tuple(data, frame=frame, axis_names=axis_names)\n        elif isinstance(data, SkyCoord):\n            return cls._from_tuple((data,), frame=frame, axis_names=axis_names)\n        else:\n            raise TypeError(f\"Unsupported input type: {type(data)!r}\")\n\n    def to_frame(self, frame):\n        \"\"\"Convert to a different coordinate frame.\n\n        Parameters\n        ----------\n        frame : {\"icrs\", \"galactic\"}\n            Coordinate system, either Galactic (\"galactic\") or Equatorial (\"icrs\").\n\n        Returns\n        -------\n        coords : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        if frame == self.frame:\n            return copy.deepcopy(self)\n        else:\n            lon, lat, frame = skycoord_to_lonlat(self.skycoord, frame=frame)\n            data = copy.deepcopy(self._data)\n            if isinstance(self.lon, u.Quantity):\n                lon = u.Quantity(lon, unit=\"deg\", copy=False)\n\n            if isinstance(self.lon, u.Quantity):\n                lat = u.Quantity(lat, unit=\"deg\", copy=False)\n\n            data[\"lon\"] = lon\n            data[\"lat\"] = lat\n            return self.__class__(data, frame, self._match_by_name)\n\n    def apply_mask(self, mask):\n        \"\"\"Return a masked copy of this coordinate object.\n\n        Parameters\n        ----------\n        mask : `~numpy.ndarray`\n            Boolean mask.\n\n        Returns\n        -------\n        coords : `~MapCoord`\n            A coordinates object.\n        \"\"\"\n        try:\n            data = {k: v[mask] for k, v in self._data.items()}\n        except IndexError:\n            data = {}\n\n            for name, coord in self._data.items():\n                if name in [\"lon\", \"lat\"]:\n                    data[name] = np.squeeze(coord)[mask]\n                else:\n                    data[name] = np.squeeze(coord, axis=-1)\n\n        return self.__class__(data, self.frame, self._match_by_name)\n\n    @property\n    def flat(self):\n        \"\"\"Return flattened, valid coordinates.\"\"\"\n        coords = self.broadcasted\n        is_finite = np.isfinite(coords[0])\n        return coords.apply_mask(is_finite)\n\n    @property\n    def broadcasted(self):\n        \"\"\"Return broadcasted coordinates.\"\"\"\n        vals = np.broadcast_arrays(*self._data.values(), subok=True)\n        data = dict(zip(self._data.keys(), vals))\n        return self.__class__(\n            data=data, frame=self.frame, match_by_name=self._match_by_name\n        )\n\n    def copy(self):\n        \"\"\"Copy `MapCoord` object.\"\"\"\n        return copy.deepcopy(self)\n\n    def __str__(self):\n        return (\n            f\"{self.__class__.__name__}\\n\\n\"\n            f\"\\taxes     : {list(self._data.keys())}\\n\"\n            f\"\\tshape    : {self.shape[::-1]}\\n\"\n            f\"\\tndim     : {self.ndim}\\n\"\n            f\"\\tframe : {self.frame}\\n\"\n        )\n", "path": "gammapy/maps/coord.py"}]}