API NASA #2: MARS Image of THAT Day

Cercando la parola chiave “API” su un motore di ricerca, oltre agli insetti che producono miele e coop che lo raccolgono, si trovano i metodi da utilizzare per recuperare i dati più disparati: dati meteo, foto e video, aforismi.

Ma la pagina che ha attirato subito la mia attenzione è stata quella della NASA.

In un precedente post ho già esplorato le funzioni di una parte delle API che mettono a disposizione, previa registrazione e ottenimento di una chiave unica per l’accesso a tutti i servizi. E’ possibile utilizzare una chiave DEMO_KEY, ma con alcune limitazioni.

Ho deciso di esplorare la sezione che riguarda Curiosity, il rover che la NASA ha fatto atterrare nell’Agosto del 2012 e che ci manda delle fantastiche immagini del suolo marziano.

Alcune di queste, riprese dalla MAST Camera, sono addirittura a colori.

Il rover in questione possiede 7 tra fotocamere e telecamere, alcune di queste puntate su strumenti scientifici, altre utilizzate come microscopi. Due di queste sono la Front  e la  Rear Hazard Avoidance Camera utilizzate per studiare il percorso del rover. I nomi che sono stati assegnati alle fotocamere sono:

  • Front Hazard Avoidance Camera (FHAZ);
  • Rear Hazard Avoidance Camera (RHAZ);
  • MAST Camera (MAST);
  • Chemistry and Camera Complex (CHEMCAM);
  • Mars Hand Lens Imager (MAHLI);
  • Mars Descend Imager (MARDI);
  • Navigation Camera (NAVCAM).

Nella mia applicazione ho usato un datepicker, un menù a tendina e il Carousel offerto da Bootstrap. Su richiesta (alla pressione di un pulsante), viene generata una tabella con i dati recuperati da JSON.

Per poter recuperare le informazioni tramite JSON dal sito della NASA, è necessario creare un link che contiene diversi parametri:

  • Data/Sol;
  • Nome Fotocamera;
  • Numero pagina;
  • API Key.

Un esempio di link da utilizzare è il seguente: https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=2015-6-3&camera=fhaz&api_key=DEMO_KEY

C’è la possibilità di scegliere tra l’inserimento di una specifica data, a partire dalla data di atterraggio (avvenuto esattamente il 06/08/2012), oppure l’inserimento di un numero specifico chiamato SOL (numero di giorni terrestri passati su Marte, a partire dall’atterraggio: ad oggi, il valore di SOL è 1384).

La risposta del server è in questo formato:

 {
        "id": 102685,
        "sol": 1004,
        "camera": {
            "id": 20,
            "name": "FHAZ",
            "rover_id": 5,
            "full_name": "Front Hazard Avoidance Camera"
        },
        "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01004/opgs/edr/fcam/FLB_486615455EDR_F0481570FHAZ00323M_.JPG",
        "earth_date": "2015-06-03",
        "rover": {
            "id": 5,
            "name": "Curiosity",
            "landing_date": "2012-08-06",
            "max_sol": 1384,
            "max_date": "2016-06-28",
            "total_photos": 263343,
            "cameras": [{
                "name": "FHAZ",
                "full_name": "Front Hazard Avoidance Camera"
            },
            {
                "name": "NAVCAM",
                "full_name": "Navigation Camera"
            },
            {
                "name": "MAST",
                "full_name": "Mast Camera"
            },
            {
                "name": "CHEMCAM",
                "full_name": "Chemistry and Camera Complex"
            },
            {
                "name": "MAHLI",
                "full_name": "Mars Hand Lens Imager"
            },
            {
                "name": "MARDI",
                "full_name": "Mars Descent Imager"
            },
            {
                "name": "RHAZ",
                "full_name": "Rear Hazard Avoidance Camera"
            }]
        }
    }

 

Ho deciso inoltre di implementare il menù a tendina e la tabella tramite HTML, mentre il datepicker e il Carousel in maniera programmatica, con Javascript/jQuery/jQuery UI.

 

HTML

<div class="container-fluid">
<div id="wrapper">
<div id="h-2">
<h1>Welcome to Mars!</h1>
</div>
<div id="sel-div">
<select id="myselect">
<option>Select a Camera!</option>
<option>Front Hazard Avoidance Camera</option>
<option>Rear Hazard Avoidance Camera</option>
<option>MAST Camera</option>
<option>Chemistry and Camera Complex</option>
<option>Mars Hand Lens Imager</option>
<option>Mars Descend Imager</option>
<option>Navigation Camera</option>
</select>
</div>
<!-- End of sel-div -->
<div id="btn-l">
<button type="button" class="btn btn-link">Reset</button>
</div>
<p id="p-date">Select a date: <input type="text" id="datepicker" /></p>

<div id="sel-info">
</div>
<!-- Bootstrap Carousel Reference http://www.w3schools.com/bootstrap/bootstrap_carousel.asp -->
<div id="marsCarousel" class="carousel">
<!-- Indicators -->
<ol class="carousel-indicators">
<!--
	<li data-target="#marsCarousel" data-slide-to="0" class="active"></li>
	<li data-target="#marsCarousel" data-slide-to="1"></li>
	<li data-target="#marsCarousel" data-slide-to="2"></li>
	<li data-target="#marsCarousel" data-slide-to="3"></li>
--></ol>
<!-- Wrapper for slides -->
<div class="carousel-inner" role="listbox">
<!--
<div class="item active">
<img src="img_1.jpg" alt="img_1">
<div class="carousel-caption">
</div>
</div>
<div class="item">
<img src="img_2.jpg" alt="img_2">
<div class="carousel-caption">
</div>
</div>
-->
</div>
<!-- Left and right controls -->
<a class="left carousel-control" href="#marsCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#marsCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div id="tablebtn">
<button type="button" class="btn btn-primary">More Info</button>
</div>
</div>
<!-- start data table div -->
<table class="table table-bordered table-hover table-condensed table-sm">
<tbody>
<tr>
<th scope="row">Rover Name</th>
<td id="rover-name"></td>
</tr>
<tr>
<th scope="row">Landing Date</th>
<td id="landing-date"></td>
</tr>
<tr>
<th scope="row">Image Date</th>
<td id="image-date"></td>
</tr>
<tr>
<th scope="row" alt="Number of days from landing date to selected date">Sol</th>
<td id="sol-date"></td>
</tr>
<tr>
<th scope="row">Max Sol</th>
<td id="max-sol"></td>
</tr>
<tr>
<th scope="row">Camera Name</th>
<td id="camera-name"></td>
</tr>
<tr>
<th scope="row">Total Photos</th>
<td id="total-photos"></td>
</tr>
<tr>
<th scope="row">Image Link</th>
<td id="image-link"></td>
</tr>
</tbody>
</table>
<!-- end data table div -->

</div>
<b>Return Object:</b>
<pre id="returnObject"></pre>

</div>

 

CSS3

Devo caricare nel CSS sia Bootstrap che un’interfaccia per il calendario:

https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css
//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/themes/smoothness/jquery-ui.css
@import url(https://fonts.googleapis.com/css?family=Satisfy);

#sel-div {
text-align: center;
margin-top: 20px;
}

#sel-info {
text-align: center;
}

#btn-l {
text-align: center;
}

#tablebtn {
text-align: center;
margin-top: 30px;
}

h1 {
margin-top: 20px;
font-family: 'Satisfy', cursive;
text-align: center;
font-size: 60px;
color: #e77d11;
}

#p-date {
text-align: center;
margin-top: 25px;
}

#marsCarousel {
margin-top: 20px;
margin-bottom: 5px;
display: none;
}

.carousel-inner &gt; .item &gt; img,
.carousel-inner &gt; .item &gt; a &gt; img {
width: 100%;
height: auto;
margin: auto;
}

@-moz-document url-prefix() {
fieldset {
display: table-cell;
}
}

.table {
padding: 10x;
margin-top: 10px;
margin-bottom: 10px;
layout: fixed;
display: none;
}

tbody > tr > th {
min-width: 150px;
margin-left: 20px;
}

.table > tbody > tr > td#image-link {
min-width: 200px;
margin-left: 10px;
overflow-x: scroll !important;
}

#returnObject {
margin: 20px;
max-height: 250px;
}

 

Javascript/jQuery e jQuery UI

Carico subito jQuery, jQuery UI e Bootstrap:

https://code.jquery.com/jquery-2.2.4.min.js
https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js

La prima cosa che si deve vedere sulla home, oltre al titolo, è il menù di selezione delle varie fotocamere, tutto il resto dovrà essere nascosto, per il momento:

$("#p-date").hide();
$("#tablebtn").hide();
$("#returnObject").hide();
$("#btn-l").hide();
$('b').hide();

Impostiamo alcune variabili e scriviamo il metodo con cui la mostra applicazione si rende conto della selezione della fotocamera:

var selected_date,
    option = $("#myselect :selected"),
    opt_sel = option.text(),
    api_k = "&api_key=DEMO_KEY",
    camera = null;

$("#myselect").on('change', function() {
    // il nostro codice andrà qui
}

Rimuoviamo l’attributo “:selected” da qualsiasi selezione già effettuata in precedenza, recuperiamo la selezione effettuata, la trasformiamo in testo e nascondiamo il menù per far posto al calendario dal qual selezionare una data:

$('option:selected', this).attr('selected', true).siblings().removeAttr('selected');
option = $("#myselect :selected");
opt_sel = option.text(); // get selected camera name
// hide camera options
$("#myselect").hide();

Ogni fotocamera ha un nome particolare che dovrà essere usato nella creazione del link JSON; creo un ciclo che definisce, per ogni fotocamera, il proprio nome:

if (opt_sel === "Front Hazard Avoidance Camera") {
camera = "&camera=fhaz"
} else if (opt_sel === "Rear Hazard Avoidance Camera") {
camera = "&camera=rhaz"
} else if (opt_sel === "MAST Camera") {
camera = "&camera=mast"
} else if (opt_sel === "Chemistry and Camera Complex") {
camera = "&camera=chemcam"
} else if (opt_sel === "Mars Hand Lens Imager") {
camera = "&camera=mahli"
} else if (opt_sel === "Mars Descend Imager") {
camera = "&camera=mardi"
} else if (opt_sel === "Navigation Camera") {
camera = "&camera=navcam"
}

Faccio apparire il calendario e imponiamo subito le impostazioni di default:

$("#p-date").show();

$.datepicker.setDefaults({
// date format with ISO 8601 syntax (required by NASA APIs)
dateFormat: 'yy-mm-dd',
// minimum date: Curiosity's landing date on Mars
minDate: "2012-08-06",
// maximum date: yesterday!
maxDate: -1,
// change month and year directly
changeMonth: true,
changeYear: true,
      // once date is selected, start image retrieve with JSON query
      onClose: function() {
         // qui andrà il resto del nostro codice
      }

Il formato data richiesto è il formato ISO 8601, che prevede prima l’indicazione dell’anno, del mese e del giorno in questo esatto ordine, il tutto separato dal trattino. Imposto la data minima alla data di atterraggio del Rover e come data massima impongo la data di ieri; faccio inoltre in modo che sia possibile selezionare direttamente il mese e l’anno (compresi sempre tra data minima e massima già impostate) e imposto il callback del comportamento dell’applicazione.

$(".carousel-indicators").empty();
$(".carousel-inner").empty();
$("#returnObject").empty();

Svuoto tutti i contenitori che mi serviranno a breve per evitare sovrapposizioni e faccio partire la mia richiesta JSON:

$.getJSON("https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=" + selected_date + camera + api_k, function(photo_set) {
   // altro codice qui
}

Contiamo il numero di foto presenti nel file JSON con i metodi:

var string = JSON.stringify(json); // text transform
var photo_num = JSON.parse(string); // count # of objects
var photo_count = (photo_num.photos.length);

Utilizziamo il file JSON trasformato in testo da mostrare nella sua interezza nell’apposito div:

$("#returnObject").text(JSON.stringify(json, null, 4));

Arriva il momento in cui far apparire lo scheletro del Carosello:

if ($("#marsCarousel").is(":hidden")) {
$("#marsCarousel").toggle("display");
$("#tablebtn").toggle("display")
$("#returnObject").show();
};

Imposto ora il ciclo con cui creare le varie sezioni del Carosello

for (var i = 0; i < photo_count; i++) {
  // qui creo il carosello
}

Sono 2 le impostazioni che devo studiare: la prima si ha per la prima slide del Carosello, e a tutto quello che ad essa è legato, e la seconda impostazione vale per tutte le altre slide. Per la prima slide studio questo metodo:

if (i == 0) {
// first indicator in Carousel (active)
$(".carousel-indicators").append($('
	<li class="active" data-target="#marsCarousel" data-slide-to="0"></li>
'));

// first image in Carousel (active)
$(".carousel-inner").append($('
<div class="item active"><img src="' + photo_set["photos"][0]["img_src"] + '"></div>
'));

// some methods to retrieve table data:
// rover name
var r_name = photo_set["photos"][0]["rover"]["name"];
$("#rover-name").html(r_name);
//landing date
var l_date = photo_set["photos"][0]["rover"]["landing_date"];
$("#landing-date").html(l_date);
// date
var img_dt = photo_set["photos"][0]["earth_date"];
$("#image-date").html(img_dt);
// sol (number of days, from landing date to selected date)
var sol_dt = photo_set["photos"][0]["sol"];
$("#sol-date").html(sol_dt);
//max sol (number of days, from landing date to today)
var max_sl = photo_set["photos"][0]["rover"]["max_sol"];
$("#max-sol").html(max_sl);
// camera name
var cam_n = photo_set["photos"][0]["camera"]["full_name"];
$("#camera-name").html(cam_n);
// total photos
var photo_tot = photo_set["photos"][0]["rover"]["total_photos"];
$("#total-photos").html(photo_tot);

// FIRST image link
var img_lnk_0 = photo_set["photos"][0]["img_src"];
$("#image-link").html($('<a href="' + img_lnk_0 + '\" target="_blank">' + img_lnk_0 + '</a>'));

}

Questa distinzione è dovuta al fatto che alla prima immagine e al primo indicatore di posizione deve essere assegnato la classe “active”, che deve essere rimossa programmaticamente e assegnata alle successive immagini/indicatori di posizione.

Alla chiusura delle impostazioni di default del calendario scrivo i setter per calendario e carosello:

// setter for datepicker
$("#datepicker").datepicker();

// disable carousel autoslide
$(".carousel").carousel({
interval: false
});

Ora una sezione importante: ho trovato un modo per far apparire le note dell’immagine (Carousel Caption) all’esterno dell’immagine stessa:

       // thanks to http://www.bootply.com/wngSniePbu for ideas
$(".carousel").on('slide.bs.carousel', function(change_slide) {
var caption = $('div.item:nth-child(' + ($(change_slide.relatedTarget).index() + 1) + ') .carousel-caption');
$('#image-link').html(caption.html());
caption.css('display', 'none');
});

Ed ora il file completo:

console.clear();

$(document).ready(function() {

// Hide Datepicker for now
$("#p-date").hide();
$("#tablebtn").hide();
$("#returnObject").hide();
$("#btn-l").hide();
$('b').hide();

// some variable:
var selected_date,
option,
opt_sel;

// api key (get yours on NASA API page:
// https://api.nasa.gov/api.html#authentication)
var api_k = "&api_key=DEMO_KEY",
camera = null;

// behavior on camera selection
$("#myselect").on('change', function() {

$('option:selected', this).attr('selected', true).siblings().removeAttr('selected');
option = $("#myselect :selected");
opt_sel = option.text(); // get selected camera name
// hide camera options
$("#myselect").hide();

// empty div and show selected camera
$("#sel-info")
.empty()
.append('You have selected ' + opt_sel + '. Now select a date!

');
$("#btn-l").show();

// cycle to determine what camera is selected
if (opt_sel === "Front Hazard Avoidance Camera") {
camera = "&camera=fhaz"
} else if (opt_sel === "Rear Hazard Avoidance Camera") {
camera = "&camera=rhaz"
} else if (opt_sel === "MAST Camera") {
camera = "&camera=mast"
} else if (opt_sel === "Chemistry and Camera Complex") {
camera = "&camera=chemcam"
} else if (opt_sel === "Mars Hand Lens Imager") {
camera = "&camera=mahli"
} else if (opt_sel === "Mars Descend Imager") {
camera = "&camera=mardi"
} else if (opt_sel === "Navigation Camera") {
camera = "&camera=navcam"
}

// show datepicker
$("#p-date").show();

}); // End of myselect change function

// some global stuff for datepicker
$.datepicker.setDefaults({
// date format with ISO 8601 syntax (required by NASA APIs)
dateFormat: 'yy-mm-dd',
// minimum date: Curiosity's landing date on Mars
minDate: "2012-08-06",
// maximum date: yesterday!
maxDate: -1,
// change month and year directly
changeMonth: true,
changeYear: true,
// once date is selected, start image retrieve with JSON query
onClose: function() {

selected_date = $(this).val();
$("#p-date").hide();
var json = null;

$("#sel-info")
.empty()
.append('You have selected ' + opt_sel + ' and Date ' + selected_date + '.

');
$("#btn-l").show();

// empty divs to avoid multiple loads
$(".carousel-indicators").empty();
$(".carousel-inner").empty();
$("#returnObject").empty();

$.getJSON("https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=" + selected_date + camera + api_k, function(photo_set) {

json = photo_set;

var string = JSON.stringify(json); // text transform
var photo_num = JSON.parse(string); // count # of objects
var photo_count = (photo_num.photos.length); // Show

$("#returnObject").text(JSON.stringify(json, null, 4));
$('b').hide();
if ($("#marsCarousel").is(":hidden")) {
$("#marsCarousel").toggle("display");
$("#tablebtn").toggle("display")
$("#returnObject").show();
};

for (var i = 0; i < photo_count; i++) {
var img_lnk = photo_set["photos"][i]["img_src"];

if (i == 0) {
// first indicator in Carousel (active)
$(".carousel-indicators").append($('
	<li class="active" data-target="#marsCarousel" data-slide-to="0"></li>
'));

// first image in Carousel (active)
$(".carousel-inner").append($('
<div class="item active"><img src="' + photo_set["photos"][0]["img_src"] + '"></div>
'));

// some methods to retrieve table data:
// rover name
var r_name = photo_set["photos"][0]["rover"]["name"];
$("#rover-name").html(r_name);
//landing date
var l_date = photo_set["photos"][0]["rover"]["landing_date"];
$("#landing-date").html(l_date);
// date
var img_dt = photo_set["photos"][0]["earth_date"];
$("#image-date").html(img_dt);
// sol (number of days, from landing date to selected date)
var sol_dt = photo_set["photos"][0]["sol"];
$("#sol-date").html(sol_dt);
//max sol (number of days, from landing date to today)
var max_sl = photo_set["photos"][0]["rover"]["max_sol"];
$("#max-sol").html(max_sl);
// camera name
var cam_n = photo_set["photos"][0]["camera"]["full_name"];
$("#camera-name").html(cam_n);
// total photos
var photo_tot = photo_set["photos"][0]["rover"]["total_photos"];
$("#total-photos").html(photo_tot);

// FIRST image link
var img_lnk_0 = photo_set["photos"][0]["img_src"];
$("#image-link").html($('<a href="' + img_lnk_0 + '\" target="_blank">' + img_lnk_0 + '</a>'));

} else {
//indicators from 1 to n.
$(".carousel-indicators").append($('
	<li data-target="#marsCarousel" data-slide-to="' + [i] + '"></li>
'));
//images from 1 to n
$(".carousel-inner").append($('
<div class="item"><img src="' + photo_set["photos"][i]["img_src"] + '">
<div class="carousel-caption">

<a href="' + photo_set["photos"][i]["img_src"] + '" target="_blank">' + photo_set["photos"][i]["img_src"] + '</a>

</div>
</div>
'));

//some methods to retrieve table data
var r_name = photo_set["photos"][i]["rover"]["name"];
$("#rover-name").html(r_name);
var l_date = photo_set["photos"][i]["rover"]["landing_date"];
$("#landing-date").html(l_date);
var img_dt = photo_set["photos"][i]["earth_date"];
$("#image-date").html(img_dt);
var sol_dt = photo_set["photos"][i]["sol"];
$("#sol-date").html(sol_dt);
var max_sl = photo_set["photos"][i]["rover"]["max_sol"];
$("#max-sol").html(max_sl);
var cam_n = photo_set["photos"][i]["camera"]["full_name"];
$("#camera-name").html(cam_n);
var photo_tot = photo_set["photos"][i]["rover"]["total_photos"];
$("#total-photos").html(photo_tot);

} //end if cycle

} // end for cycle

}); // end getJSON

} // end onSelect function

}); // end datepicker defaults

// setter for datepicker
$("#datepicker").datepicker();

// disable carousel autoslide
$(".carousel").carousel({
interval: false
});

// thanks to http://www.bootply.com/wngSniePbu for ideas
$(".carousel").on('slide.bs.carousel', function(change_slide) {
var caption = $('div.item:nth-child(' + ($(change_slide.relatedTarget).index() + 1) + ') .carousel-caption');
$('#image-link').html(caption.html());
caption.css('display', 'none');
});

}) // end of (document).ready function

$(".btn-primary").on('click', function() {
$(".table").toggle("display");
});

$(".btn-link").on('click', function(e) {
//window.location.reload();
location.reload(true);
});

Di seguito la pagina su Codepen con l’applicazione completa:

 

Un’immagine tra le più suggestive che ho potuto finora vedere, è questa:

Immagine ripresa con la MAST Camera in data 27/06/2016 da Curiosity.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.