Skip to content

Commit

Permalink
feat: planetary name resolvers (openstreetmap and gazetteer of planet…
Browse files Browse the repository at this point in the history
…ary features) (#103)

* feat: Add support for planetary body objects for target property

* 🐛 Add error when target object is unknown

* ✅ Add tests for planetary coordinates

* 📝 Update changelog

* 🥅 Improve error management with new '_ready' traitlets

* 🎨 Replace 200 constant by requests value

* 🎨 Make target property return BaseBodycentricRepresentation when survey body is a planetary body

* docs: Add warning when planetary body fetching is not returning the same object as expected

* 🐛 Try fixing planetary coordinate as target

* ✨ Allow target setting using a tuple of two floats

* 🚑 Fix missing line because of rebase

* ✅ Fix tests

* 🚑 Fix un-raised NameResolveError and improve comments

* 📝 Add new notebook 12 to show how to use ipyaladin with planetary body

* feat: add support for earth, return longitude and latitude objects instead of floats

* docs: document name resolvers

* fix: make tests pass

---------

Co-authored-by: MARCHAND MANON <[email protected]>
  • Loading branch information
Xen0Xys and ManonMarchand authored Sep 16, 2024
1 parent 244e702 commit 8123c49
Show file tree
Hide file tree
Showing 9 changed files with 490 additions and 55 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The method accepts `astropy.io.fits.HDUList`, `pathlib.Path`, or `string` representing paths (#86)
- New way to make a selection on the view with `selection` method (#100)
- Add selected sources export as `astropy.Table` list with property `selected_objects` (#100)
- Add function `get_view_as_fits` to export the view as a `astropy.io.fits.HDUList` (#86)
- Add function `get_view_as_fits` to export the view as a `astropy.io.fits.HDUList` (#98)
- Add function `save_view_as_image` to save the view as an image file (#108)
- Support for planetary objects for Aladin Lite target (#103)

### Deprecated

Expand Down
244 changes: 244 additions & 0 deletions examples/12_Planetary_surveys.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "8b1a9d152f4e1c7f",
"metadata": {},
"source": [
"# Planetary surveys\n",
"\n",
"With `ipyaladin`, you can also display planetary surfaces."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "initial_id",
"metadata": {},
"outputs": [],
"source": [
"from ipyaladin import Aladin\n",
"from astropy.table import Table"
]
},
{
"cell_type": "markdown",
"id": "60b133f13ce59125",
"metadata": {},
"source": [
"First, let's create an Aladin Lite widget with the Mars Viking MDIM21 survey."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "85d284fd3f5cc7bd",
"metadata": {},
"outputs": [],
"source": [
"mars = Aladin(\n",
" target=\"159.2135528 -58.6241989\",\n",
" survey=\"https://alasky.u-strasbg.fr/Planets/Mars_Viking_MDIM21\",\n",
" fov=10,\n",
")\n",
"mars"
]
},
{
"cell_type": "markdown",
"id": "35ea9256",
"metadata": {},
"source": [
"The target does not return a `SkyCoord` object anymore, since we don't represent the sky here. It is a couple of `Longitude` and `Latitude`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "43c34b52",
"metadata": {},
"outputs": [],
"source": [
"mars.target"
]
},
{
"cell_type": "markdown",
"id": "e6f17ff7",
"metadata": {},
"source": [
"The WCS are also defined on planetary surfaces (in [Marmo *et.al.* 2018](https://doi.org/10.1029/2018EA000388)). This is why you see a CTYPE starting with `MA` for Mars."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59bcf84c",
"metadata": {},
"outputs": [],
"source": [
"mars.wcs"
]
},
{
"cell_type": "markdown",
"id": "34747eb38e0db450",
"metadata": {},
"source": [
"Let's change the center of the view to Olympus Mons."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dbe012440a30b6fe",
"metadata": {},
"outputs": [],
"source": [
"mars.target = \"Olympus Mons\""
]
},
{
"cell_type": "markdown",
"id": "879f484a",
"metadata": {},
"source": [
"Any name recognized by the [Gazetter of Planetary Nomenclature](https://planetarynames.wr.usgs.gov/Nomenclature) will work. \n",
"\n",
"We can also add tables:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5461264f0485d880",
"metadata": {},
"outputs": [],
"source": [
"longitudes = [\n",
" 226.2,\n",
" 70.5,\n",
" 250.4,\n",
" -59.2,\n",
" 147.21,\n",
" 316.0,\n",
" 32.53,\n",
" -112.58,\n",
" 298.0,\n",
" 30.0,\n",
" 70.5,\n",
" 280.0,\n",
" 87.0,\n",
" 117.5,\n",
" 350.0,\n",
"]\n",
"latitudes = [\n",
" 18.65,\n",
" -42.4,\n",
" 40.5,\n",
" -13.9,\n",
" 25.02,\n",
" -49.7,\n",
" 70.0,\n",
" 1.57,\n",
" 25.0,\n",
" 19.79,\n",
" -42.4,\n",
" 45.0,\n",
" 12.9,\n",
" 46.7,\n",
" -45.0,\n",
"]\n",
"names = [\n",
" \"Olympus Mons\",\n",
" \"Hellas Planitia\",\n",
" \"Alba Mons\",\n",
" \"Valles Marineris\",\n",
" \"Elysium Mons\",\n",
" \"Argyre Basin\",\n",
" \"Vastitas Borealis\",\n",
" \"Tharsis Montes\",\n",
" \"Outflow channels\",\n",
" \"Arabia Terra\",\n",
" \"Hellas Basin\",\n",
" \"Tempe Terra\",\n",
" \"Isidis Basin\",\n",
" \"Utopia Basin\",\n",
" \"Noachis Terra\",\n",
"]\n",
"\n",
"table = Table([longitudes, latitudes, names], names=(\"Longitude\", \"Latitude\", \"Name\"))\n",
"mars.add_table(\n",
" table, color=\"#67d38d\", source_size=15, shape=\"cross\", name=\"Mars_features\"\n",
")"
]
},
{
"cell_type": "markdown",
"id": "4af1422b",
"metadata": {},
"source": [
"## Surveys of Earth"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "479ddc87",
"metadata": {},
"outputs": [],
"source": [
"earth = Aladin(\n",
" survey=\"CDS/P/Earth/DEM/elevation\",\n",
" fov=100,\n",
")\n",
"earth"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "983e6c40",
"metadata": {},
"outputs": [],
"source": [
"earth.target"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4609cf6b",
"metadata": {},
"outputs": [],
"source": [
"earth.wcs"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b887792",
"metadata": {},
"outputs": [],
"source": [
"earth.target = \"Strasbourg\" # ipyaladin's home"
]
},
{
"cell_type": "markdown",
"id": "d042bb57",
"metadata": {},
"source": [
"Any name recognized by OpenStreetMaps will work."
]
}
],
"metadata": {
"nbsphinx": {
"execute": "never"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
16 changes: 9 additions & 7 deletions js/models/event_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,6 @@ export default class EventHandler {
this.aladin.setFoV(fov);
});

this.aladin.on("layerChanged", (imageLayer, layerName, state) => {
if (layerName !== "base" || state !== "ADDED") return;
this.updateWCS();
this.model.set("_base_layer_last_view", imageLayer.id);
this.model.save_changes();
});

/* Div control */
this.model.on("change:_height", () => {
let height = this.model.get("_height");
Expand All @@ -158,6 +151,15 @@ export default class EventHandler {
this.model.save_changes();
});

this.aladin.on("layerChanged", (imageLayer, layerName, state) => {
if (layerName === "base")
this.model.set("_survey_body", imageLayer.hipsBody || "sky");
if (layerName !== "base" || state !== "ADDED") return;
this.updateWCS();
this.model.set("_base_layer_last_view", imageLayer.id);
this.model.save_changes();
});

this.aladin.on("resizeChanged", (width, height) => {
// Skip resize event when the div is hidden
if (width === 1 && height === 1) {
Expand Down
2 changes: 1 addition & 1 deletion src/ipyaladin/__about__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = "0.4.0"
__aladin_lite_version__ = "3.4.4-beta"
__aladin_lite_version__ = "3.5.1-alpha"
82 changes: 71 additions & 11 deletions src/ipyaladin/utils/_coordinate_parser.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import warnings
from typing import Tuple

from astropy.coordinates import SkyCoord, Angle
import requests
from astropy.coordinates import SkyCoord, Angle, Longitude, Latitude
import re

from ipyaladin.utils.exceptions import NameResolverWarning

def parse_coordinate_string(string: str) -> SkyCoord:

def _parse_coordinate_string(
string: str, body: str = "sky"
) -> Tuple[Longitude, Latitude]:
"""Parse a string containing coordinates.
Parameters
----------
string : str
The string containing the coordinates.
body : str
The planetary body to use for the coordinates. Default
is "sky" when there is no planetary body.
Returns
-------
Expand All @@ -19,20 +28,71 @@ def parse_coordinate_string(string: str) -> SkyCoord:
"""
if not _is_coordinate_string(string):
return SkyCoord.from_name(string)
coordinates: Tuple[str, str] = _split_coordinate_string(string)
if body == "sky" or body is None:
sesame = SkyCoord.from_name(string)
return sesame.icrs.ra.deg, sesame.icrs.dec.deg
return _from_name_on_planet(string, body)
# coordinates should be parsed from the string
coordinates = _split_coordinate_string(string)
# Parse ra and dec to astropy Angle objects
dec: Angle = Angle(coordinates[1], unit="deg")
lat: Angle = Angle(coordinates[1], unit="deg")
if _is_hour_angle_string(coordinates[0]):
ra = Angle(coordinates[0], unit="hour")
lon = Angle(coordinates[0], unit="hour")
else:
ra = Angle(coordinates[0], unit="deg")
lon = Angle(coordinates[0], unit="deg")
# Create SkyCoord object
if string[0] == "B":
return SkyCoord(ra=ra, dec=dec, equinox="B1950", frame="fk4")
if string[0] == "G":
return SkyCoord(l=ra, b=dec, frame="galactic")
return SkyCoord(ra=ra, dec=dec, frame="icrs")
coo = SkyCoord(ra=lon, dec=lat, equinox="B1950", frame="fk4")
elif string[0] == "G":
coo = SkyCoord(l=lon, b=lat, frame="galactic")
else:
coo = SkyCoord(ra=lon, dec=lat, frame="icrs")
return coo.icrs.ra.deg, coo.icrs.dec.deg


def _from_name_on_planet(string: str, body: str) -> SkyCoord:
"""Get coordinates from a name on a planetary body.
Parameters
----------
string : str
The name of the feature.
body : str
The planetary body to use for the coordinates.
Returns
-------
SkyCoord
An `astropy.coordinates.SkyCoord` object representing the coordinates.
"""
url = (
f"https://alasky.cds.unistra.fr/planetary-features/resolve"
f"?identifier={string.replace(' ', '+')}&body={body}&threshold=0.7&format=json"
)
request = requests.get(url)
if request.status_code != requests.codes.ok:
raise ValueError(
"No coordinates found for this requested planetary feature: " f"'{string}'"
)
data = request.json()["data"]
# response is different for earth
if body == "earth":
return data[0][0], data[0][1]
# case of every other planetary bodies
identifier = data[0][1]
lat = data[0][5] # inverted lon and lat in response
lon = data[0][6]
system = data[0][11]
if identifier != string:
warnings.warn(
f"Nothing found for '{string}' on {body}. However, a {identifier} exists. "
f"Moving to {identifier}.",
NameResolverWarning,
stacklevel=2,
)
if "+West" in system:
lon = 360 - lon
return lon, lat


def _is_coordinate_string(string: str) -> bool:
Expand Down
Loading

0 comments on commit 8123c49

Please sign in to comment.