📜 ⬆️ ⬇️

Get NASA photos from Mars using aiohttp

I'm a big fan of Andy Weier's The Martian. Reading it, I was wondering what Mark Watney felt while walking on the red planet. Recently I came across a post on Twillo , in which it was mentioned that NASA has a public API for accessing photos from rovers. So I decided to write my own application for viewing images directly in the browser.

Creating aiohttp application


Let's start with a simple one - install and run aiohttp. First, create a virtual environment. I suggest using Python 3.5, in which the new syntax async def and await appeared. If you want to develop this application in the future to better understand asynchronous programming, you can install Python 3.6 right away. Finally, install aiohttp:

pip install aiohttp 

Add a file to the project (let's call it nasa.py) with the code:

 from aiohttp import web async def get_mars_photo(request): return web.Response(text='A photo of Mars') app = web.Application() app.router.add_get('/', get_mars_photo, name='mars_photo') 

If you have not worked with aiohttp, then I will explain a few points:
')

Note: a request handler can also be a regular function, and not just a quorutine. But in order to understand the full power of asyncio, most functions will be defined as async def.

Application launch


To start the application, add a line to the end of the file:

 web.run_app(app, host='127.0.0.1', port=8080) 

And run it as usual python script:

 python nasa.py 

However, there is a better way. Among the many third-party libraries I found aiohttp-devtools . It provides a great runserver command that launches your application, and also supports live reloading.

 pip install aiohttp-devtools adev runserver -p 8080 nasa.py 

Now, at localhost: 8080, you should see the text “A photo of Mars”.

Using the NASA API


Let's go directly to getting photos. For this we will use the NASA API . Each rover has its own URL (for Curiosity, it's api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos [1]). We must also pass at least 2 parameters on each call:

  1. sol - the Martian day on which the photo was taken, starting with the landing of the rover (you can find the maximum value in the section rover / max_sol)
  2. API KEY - a key provided by NASA (for now you can use the test DEMO_KEY)

In response, we will receive a list of photos from the URL, information about the camera and the rover. A little tweak the nasa.py file:

 import random from aiohttp import web, ClientSession from aiohttp.web import HTTPFound NASA_API_KEY = 'DEMO_KEY' ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos' async def get_mars_image_url_from_nasa(): while True: sol = random.randint(0, 1722) params = {'sol': sol, 'api_key': NASA_API_KEY} async with ClientSession() as session: async with session.get(ROVER_URL, params=params) as resp: resp_dict = await resp.json() if 'photos' not in resp_dict: raise Exception photos = resp_dict['photos'] if not photos: continue return random.choice(photos)['img_src'] async def get_mars_photo(request): url = await get_mars_image_url_from_nasa() return HTTPFound(url) 

This is what happens here:


Getting a key for NASA API


The DEMO_KEY public key, proposed by NASA by default, works fine, but very soon you will be confronted with a call limit [2]. I recommend getting your own key here, which will be available after registration.

After launching the application, you can be redirected to the following image from Mars:

image

This is not what I expected ...

Image validation


The image above is not very inspiring. It turns out, rovers take a bunch of very boring photos. I want to see the same as Mark Watney in his incredible adventure. I'll try to fix it.

We need some sort of validation mechanism for the resulting images. Adapt the code:

 async def get_mars_photo_bytes(): while True: image_url = await get_mars_image_url_from_nasa() async with ClientSession() as session: async with session.get(image_url) as resp: image_bytes = await resp.read() if await validate_image(image_bytes): break return image_bytes async def get_mars_photo(request): image = await get_mars_photo_bytes() return web.Response(body=image, content_type='image/jpeg') 

Here is what has changed:


In addition, we removed the redirection (HTTPFound), so that we can get the following random image by simply reloading the page.

It remains to describe the validation of the photo. The simplest is to determine the minimum size.

Install Pillow:

 pip install pillow 

Our validation function will turn into:

 import io from PIL import Image async def validate_image(image_bytes): image = Image.open(io.BytesIO(image_bytes)) return image.width >= 1024 and image.height >= 1024 

image

Already something! Go ahead and drop all black and white images:

 async def validate_image(image_bytes): image = Image.open(io.BytesIO(image_bytes)) return image.width >= 1024 and image.height >= 1024 and image.mode != 'L' 

Now our program starts to produce more interesting photos:

image

And even sometimes selfies:

image

Conclusion


Source code of the program
 #!/usr/bin/env python3 import io import random from aiohttp import web, ClientSession from aiohttp.web import HTTPFound from PIL import Image NASA_API_KEY = 'DEMO_KEY' ROVER_URL = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos' async def validate_image(image_bytes): image = Image.open(io.BytesIO(image_bytes)) return image.width >= 1024 and image.height >= 1024 and image.mode != 'L' async def get_mars_image_url_from_nasa(): while True: sol = random.randint(0, 1722) params = {'sol': sol, 'api_key': NASA_API_KEY} async with ClientSession() as session: async with session.get(ROVER_URL, params=params) as resp: resp_dict = await resp.json() if 'photos' not in resp_dict: raise Exception photos = resp_dict['photos'] if not photos: continue return random.choice(photos)['img_src'] async def get_mars_photo_bytes(): while True: image_url = await get_mars_image_url_from_nasa() async with ClientSession() as session: async with session.get(image_url) as resp: image_bytes = await resp.read() if await validate_image(image_bytes): break return image_bytes async def get_mars_photo(request): image = await get_mars_photo_bytes() return web.Response(body=image, content_type='image/jpeg') app = web.Application() app.router.add_get('/', get_mars_photo, name='mars_photo') web.run_app(app, host='127.0.0.1', port=8080) 


There are many things to improve (for example, getting the max_sol value through the API, viewing images from several rovers, caching the URL), but while the program is doing its job: we can get a random photo from Mars and present ourselves at the site of future settlers.

Hope you enjoyed this short tutorial. If you notice a mistake or have any questions, let me know.

Translator's Notes:

[1] In fact, there are only 3 rovers, you can see a list of them with the request api.nasa.gov/mars-photos/api/v1/rovers/?API_KEY=DEMO_KEY .
[2] I would also add that the program works for a very long time due to the random image search.

Source: https://habr.com/ru/post/331834/


All Articles