This project uses an e-ink triple colour display on a Raspberry Pi Zero, to show the current location of the International Space Station (ISS), programmed in Python
The Raspberry Pi computers have been used for many great educational projects. One of which was in 2016 when two Raspberry Pi's were sent into space to run experiments on the International Space Station while the UK astronaut Tim Peake was on board . These two Pi's of the Astro Pi's project, run code and experiments that schools, children and teenagers had written for the project.
In 2019 the Raspberry Pi Foundation and ESA have teamed up again to get more code for a future mission on the ISS. This a great opportunity for children and teenagers to get directly involved in space, science and programming in one project.
I'm interested in astronomy and thought it would be a fun project to build a ISS real time tracker using a Raspberry Pi Zero W and a Triple e-ink display.
The project is not only to show where the space station is but also it's a good geography learning project for kids. As they check in from time to time to see where space station is they get to learn where the Cities and Countries of the world are.
The display I have used is the Seeed Studio 2.13 inch Triple colour e-ink display. My guide to the screen and how to program it is here.
Information about the ISS's current location is available via a api from NASA. So you can get the Longitude and Latitude right now. Then using a list of the Longitude and Latitude locations of 1000's of Towns and Cities around the world supplied in a text file. A python program can find out what is the closest location on earth and the display it on the e-ink display.
E-ink displays only use power to change the image unlike LCD displays that need power at all times. So the Rpi Zero w and the e-ink display are both low powered devices meaning they can happily run on a battery for days if required.
The ISS location is available in a python library called starfield available in pip or via a few api's online. I choose an api at http://open-notify.org/Open-Notify-API/ISS-Location-Now/ which creates a json script that can be read in python.
Using the longitude and latitude co-ordinates in a list of co-ordinates for locations around the world the python script determines the closest location.
This could be over 1000 km away when the ISS is over an ocean.
The location is then plotted on a map of the world so you can see where it is.
As the e-ink display is not designed to updated constantly it refreshes every 3 minutes. The ISS takes about 90 minutes to complete one orbit so in 3 minutes it travels about 1,386 km or 864 miles. A trail of dots show the path it has taken across the world.
Python Programming Guide to Tracking the ISS
To make a similar tracker the steps are:
enter all the libraries needed.
import json, urllib.request, time from haversine import haversine, Unit import csv from PIL import Image, ImageDraw,ImageFont import epd2in13b,traceback
- The first row is required to capture the ISS's location
- The second row is used to calculate this distance between two global locations
- The third line is used to import the world location list
- The forth row is a graphics library to create screen images
- The fith is the e-ink screen driver
This next bit of code is a function to connects to the website and requests the current location of the ISS and stores them in the lat and lon variables
def iss_long_lat(): """Get the ISS data from the tracking API""" url = "http://api.open-notify.org/iss-now.json" details = urllib.request.urlopen(url) result = json.loads(details.read()) loc =result["iss_position"] lat = loc["latitude"] lon = loc["longitude"] return lon,lat
The information returned looks like this
Now we can get the current ISS location, next we want to compare it to a list of longitude and latitude locations around the world held in a csv file and load it to an list. This uses two functions, one to load the csv file into a list and one to find the closest destination.
def getGeoData(): """Load city long lat list""" with open('./worldcities_lonlat.csv','r') as f: l = csv.reader(f) cities = list(l) return cities
The csv file is loaded and converted line by line into a list of four columns. Longitude, Latitude, Town/City, Country
11.0409 35.4839 Mahdia Tunisia
11.04 12.8705 Gashua Nigeria
11.03 50.9701 Erfurt Germany
11.027 -2.857 Tchibanga Gabon
11.0167 -1.8662 Mouila Gabon
11 49.47 Furth Germany
10.99 45.4404 Verona Italy
def dist(start,end): x = haversine(start,end) return x
This function uses the haversine library which is used to calculate the distance between two positions on the earth. This is required for the next function. You will need to install the haversine python3 library via pip
sudo pip3 install haversine
def CalcLoc(data,sz,iss): """Go through city list to find the closest destination to the ISS""" country = "" city = "" citydist = 0 mindist = 99999999 for i in range(sz): d = dist((float(data[i]),float(data[i])),(float(iss),float(iss))) if d < mindist: city = data[i] country = data[i] citydist = d mindist = d return (country,city,round(citydist,2))
This will work out what is the closest location to the ISS's position. It takes three inputs
- Data - the list of locations generated from the CSV file
- sz - a count of the total rows in the Data list
- iss - the current iss long lat locations
This function looks at each longitude and latitude of all the 12000+ locations in the list created from the csv file and compares that to the ISS's location using the 'dist' function.
if the returned distance is lower than the distance set in the variable 'mindist' then it records the 'country', 'city' and 'citydist' and the new 'mindist'
Once the whole list has been compared the final value for those varibles will be the closest destination to the ISS.
The line: d = dist((float(data[i]),float(data[i])),(float(iss),float(iss))) contains the Long and Lat from the csv file and the long Lat from the current ISS location.
float(data[i]) = data list at [current row][column 0]
As both sets of data come from a text file they are represented as a string, so the float command converts them to a floating point number for use in the distance calculation.
So now we know the ISS's location and the closest destination with the distance in Km so the next step is to display this information on the e-ink display.
Mini Map ISS location
The Mini map on the display has a red dot to show the ISS's current location. For this to work the map has to be a certain size.
The longitude and latitude lines split up the world in even sections. Longitude goes from the international date line in the Pacific Ocean near China at -180 to the UK at zero and back to the international date line at +180. That is a total of 360 degrees, not surprising being that the earth is round. The latitude lines go from the North pole to the South Pole from +90 to -90. A total of 180.
If you think of each pixel in the map of the earth being a longitude and latitude line then you want an image that is 360 pixels by 180 pixels or a mutiplication of that size.
What you want to do is, if you get a ISS location of 51 longitude and 0 latitude, which is in Southern England, you then plot the dot at 55 pixels from the left and 23 pixels from the top and your done. Unfortunately not. The problem is the negative numbers of the longitude and latitude will mean your point will be in the wrong location. The dot will appear in the arctic above Alaska.
So for your longitude position you need to +180 to the ISS longitude value. This means your value will always be between 0 and 360 and the dot will appear in the correct left to right pixel on your image.
For the Latitude this is a little more tricky. The same principle applies but as the range is -90 to +90 then you just add 90 to you latitude value.
But this will mean the south pole will have a value of zero and the North Pole a value of 180. The coordinates of the image are 0 at the top and 180 at the bottom. So your dot will appear on the wrong half of the world.
To flip it, just minus the value from 180. So with a latitude of -50, + 90 to make it +40. Then 180-40 to make it 140. Your dot will now be in the bottom half of the picture 140 pixels from the top.
Get the right world image
The image is also very important. If you don't use a good image of the earth your countries will be in the wrong places for the pixel positions. Though these two maps of the world look similar. The first one will show the iss position in the wrong place as the countries are straighter. The second one will give the correct position and looks more stretch from left to right. If you look at South America, it is more tilted than the first image.
The best place to get images is from Nasa. They are free and at the correct size to be scaled correctly. Such as this image https://visibleearth.nasa.gov/images/73963/bathymetry
For the e-ink display the map had to be scaled down to a 3rd of the size to 120 pixels by 60 pixels. So all longitude and latitude calculations need to be divided by 3 as well so the red dot can be located correctly.
So if you followed that, then the function to position the red dot will make sense. Simple as that!
self and self are the longitude and latitude of the iss. Float converts a string to a floating point number.
#position of red dot on mini map
#scale long lat coords to 120 x 60 image
x = round((float(self)+180)/3)
y = round((180-(float(self)+90))/3)
Updating the e-ink display
The next function is the biggest and uses the elments from the previous functions to build the image for the display. I will break this down into smaller section and explain what is happening.
"""update location text and map to the e-ink screen"""
issx = int(mapdot)
issy = int(mapdot)
epd = epd2in13b.EPD()
epd.init() #Initiate e-ink screen ready for image update
This function receives the iss's locations (issdetails), the position of the red dot (mapdot) and trail is a list of previous locations the iss has been at.
#Create an image to show the black sections of the display
HBlackimage = Image.new('1', (epd2in13b.EPD_HEIGHT, epd2in13b.EPD_WIDTH), 255) # 212x104
#Create an image to show the red sections of the display
HRedimage = Image.new('1', (epd2in13b.EPD_HEIGHT, epd2in13b.EPD_WIDTH), 255) #212x104
#assign the new black and red images to varibles
drawblack = ImageDraw.Draw(HBlackimage) drawred = ImageDraw.Draw(HRedimage)
#assign the fonts and sizes to variables
font14 = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 14)
font20 = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeSans.ttf', 20)
#show text at 130 pixels from the left of the screen and 0 & 18 pixels from the top
drawblack.text((130,0), 'The ISS', font = font20, fill=0)
drawblack.text((130,18), 'is near:', font = font20,fill=0)
#This line shows the Country the ISS is nearest to
drawred.text((5,52), issdetails, font = fontsize(issdetails), fill = 0)
#This line shows the closest City or Twon to the ISS
drawred.text((5,70), issdetails, font = fontsize(issdetails), fill = 0)
#This line shows the distance between the ISS and the City or Town as if it was on the ground.
drawblack.text((5,88), 'about ' + str(round(issdetails)) + ' Km away', font = font14, fill = 0)
#Load the image of the world. this is 120 pixels by 60 pixels
mapblk = Image.open('small-world-map.bmp')
#Position the map on the black image
#calculate the position for the red dot marking the ISS's location
isspos = (int(mapdot)-4,int(mapdot)-4,int(mapdot)+4,int(mapdot)+4)
#Draw the red dot on the Red image
drawred.ellipse(isspos,fill = 0)
#Draw the trail of the last 60 positions using the point command.
drawred.point(trail,fill = 0)
#Update Display with the black and red images
#send the display to sleep
epd.sleep() #end update
This section is the main program which calls the various functions to collect the required data and then finally sends them to the "updateink" function.
def main(): geodata = getGeoData() #Global city Longitude & Latidude list geosz = len(geodata) #get total rows in list trail =  #list to display trail while True: iss_lola = iss_long_lat() #Current Iss location loc = CalcLoc(geodata,geosz,iss_lola) #Closest City/Town mapxy = mapdot(iss_lola) #Red dot pixel position if len(trail) >= 60: #length of trail in points trail.pop(0) #if trail list 60 elements long then remove the first one to add a new one on the end #add new red dot coords to trail list. trail.append((int(mapxy),int(mapxy))) updateeink(loc,mapxy,trail) time.sleep(180) #wait 3 minutes
The full program is available for download here
This version has a few extras; the map switches between the left and right side and the text size changes depending on the amount of letters being displayed. iss-tracker-e-ink-display
Complete archive of Seeed Studio modified e-ink drivers, python scripts, map image and world location list iss-tracker-eink.zip
The e-ink display is available from www.seeedstudio.com
Waveshare e-ink infor and drivers (need pin assignments modifying to use with Seeed Studio e-ink): https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT_B
Live ISS tracking API: http://open-notify.org/Open-Notify-API/ISS-Location-Now/
Live Online ISS Tracking: http://www.isstracker.com/
Python Haversine library: https://pypi.org/project/haversine/
List of global cities and towns with coordinates https://simplemaps.com/data/world-cities