Mars Perseverance Rover is at Sol 186 on Mars. Since it's landing last February 18, 2021, it already roamed 1.97 km (1.22 mi) and spent 4611 hours on an alien planet. I'm going to describe the science and the technical aspect of the Rover. Instead, I will share to you on how to capture the raw images from Mars as captured by Mars Perseverance rover's many camera.
NASA Open API
The images are open for public to view. Stay tune on their Mars Perseverance Multimedia site when it's published. Or, you can always get ahead using NASA Open API. Of course, we'll make use of Google Apps Script to fetch from the API. Run the code on a daily basis. Then email the images properly into our Gmail inbox. Now, that's really cool way to get those images fresh.
Sample email output
Just a caution, the script only allows upto 100 images to be embedded in the Gmail body. It's the html table with rows capped at that number. Beyond that cap, will cause an error. As a remedy, I've added a line where the full html saved into a text file, saved into a my GDrive then attach it to the email notification.
The Script (have fun!):
/** | |
* GLOBAL VARIABLES | |
* INSERT NECESSARY API KEYS AND EMAIL ADDRESS | |
*/ | |
var api_key = 'YOUR NASA OPEN API KEY HERE'; //<--- INSERT YOUR API KEY HERE | |
var api_base_url = 'https://api.nasa.gov/mars-photos/api/v1'; | |
var email_address = 'EMAIL ADDRESS HERE'; //<---- INSERT THE EMAIL ADDRESS YOU NEED BE INCLUDED IN THE NOTIFICATION | |
var ss = SpreadsheetApp.openById('SHEET ID'); | |
var ws = ss.getSheetByName('Sol Log'); | |
/** | |
* THIS WILL EXTRACT THE NECESSARY MANIFEST FOR THE PERSEVERANCE ROVER | |
* WE NEED TO FETCH THIS FIRST FOR US TO GET THE LATEST SOL AVAILABLE IN | |
* API IMAGE RESOURCES | |
*/ | |
function fetch_manifest(api_base_url,rover_name,api_key){ | |
// https://api.nasa.gov/mars-photos/api/v1/manifests/perseverance?api_key=I7RZcAOdoem1ZyABObv93yazuSoDlBNz7cE9hf9H | |
var url = api_base_url.concat('/manifests/', rover_name,'?api_key=',api_key); | |
var response = UrlFetchApp.fetch(url); | |
var json = response.getContentText(); | |
var data = JSON.parse(json); | |
return data; | |
} | |
/** | |
* THIS WILL CAPTURE THE IMAGE RESOURCES | |
* PARAMETERS | |
* api_base_url | |
* rover_name = Perseverance | |
* api_key | |
* sol = the max sol capture in the fetch_manifest function | |
*/ | |
function get_sol_photos(api_base_url,rover_name,api_key,sol){ | |
var url = api_base_url.concat('/rovers/', rover_name,'/photos?sol=',sol,'&api_key=',api_key); | |
var response = UrlFetchApp.fetch(url); | |
var json = response.getContentText(); | |
var data = JSON.parse(json); | |
return data.photos; | |
} | |
/** | |
* THIS WILL EXTRACT CAPTURE THE CURRENT SOL IMAGE RESOURCES | |
*/ | |
function get_max_sol_photo(){ | |
var manifest = fetch_manifest(api_base_url,'perseverance',api_key); | |
var max_sol = manifest.photo_manifest.max_sol; | |
var photos = get_sol_photos(api_base_url,'perseverance',api_key,max_sol); | |
return {SOL:max_sol,PHOTOS:photos}; | |
} | |
/** | |
* THIS WILL CREATE THE HTML TABLE WITH THE IMAGE RESOURCE | |
* STRUCTURED ACCORDINGLY | |
*/ | |
function html_table_photos(photos){ | |
var html; | |
var html_full; | |
html = '<table style="font-family: arial, sans-serif;border-collapse: collapse;width: 70%;">'; | |
html += '<tr style="border: 1px solid #dddddd;text-align: left;padding: 8px;"><th style="border: 1px solid #dddddd;text-align: left;padding: 8px;">id</th style="border: 1px solid #dddddd;text-align: left;padding: 8px;"><th style="border: 1px solid #dddddd;text-align: left;padding: 8px;">camera</th><th style="border: 1px solid #dddddd;text-align: left;padding: 8px;">image</th></tr>'; | |
html_full = html; | |
photos.map(function(row,index){ | |
var image_row = ''.concat('<tr><td style="border: 1px solid #dddddd;text-align: left;padding: 8px;"><a href="',row.img_src,'">',row.id,'</a></td><td style="border: 1px solid #dddddd;text-align: left;padding: 8px;">',row.camera.name,'</td><td style="border: 1px solid #dddddd;text-align: left;padding: 8px;">','<a href="',row.img_src,'" target="_blank"><img src="',row.img_src,'" width="100"></a></td></tr>'); | |
//var image_row = ''.concat('<tr><td>',row.id,'</td><td>',row.camera.name,'</td><td>','<img src="cid:',row.img_src,' width="50"></td></tr>'); | |
if(index<=100){ | |
html += image_row; | |
} | |
html_full += image_row; | |
}) | |
html += '</table>'; | |
html_full += '</table>'; | |
//DriveApp.createFile('New HTML File', '<b>Hello, world!</b>', MimeType.HTML);' | |
return {EMAIL_TABLE:html,FULL_HTML:html_full}; | |
} | |
/** | |
* THIS WILL SEND EMAIL NOTIFICATION OF THE FETCH | |
* CURRENT SOL IMAGE RESOURCES. | |
*/ | |
function email_photos(){ | |
var photos = get_max_sol_photo(); | |
//Get the max sol recorded in db-sheet | |
//Get db | |
var get_logged_max_sol = ws.getDataRange().getValues(); | |
//Get last recorded | |
get_logged_max_sol = get_logged_max_sol[get_logged_max_sol.length-1][1];Logger.log(get_logged_max_sol); | |
//Check if max sol in api is same with what was last logged in db | |
if(get_logged_max_sol === photos.SOL){ | |
Logger.log("ALREADY CAPTURED"); | |
return null; | |
} | |
//Identify sols missed to send | |
for(var i=get_logged_max_sol+1; i<photos.SOL+1; i++){ | |
var html = html_table_photos(get_sol_photos(api_base_url,'perseverance',api_key,i)); | |
MailApp.sendEmail( | |
{ | |
to:email_address, | |
subject:'NASA Mars 2020 Perseverance Rover On Mars photos | Sol '.concat(i), | |
htmlBody:html.EMAIL_TABLE, | |
//Keep full html in a textfile since there's a cap for the lines of rows in the html table creation. | |
attachments:[DriveApp.getFolderById('DRIVE FOLDER ID HERE').createFile('NASA Mars 2020 Perseverance Rover On Mars photos | Sol '.concat(i),html.FULL_HTML,MimeType.PLAIN_TEXT)] | |
} | |
); | |
ws.appendRow([new Date(),i]); | |
} | |
} |