Observing Earth from space with Streamlit
Have you ever tried teaching teenagers Earth observation and environmental science in an interactive way? That was my goal at SatSchool, and that's why I made the SatSchool app!
In this post, we’ll talk about:
- Making a website with satellite data
- Making a quiz
- Why use Streamlit?
Can’t wait to dive in? Here is the SatSchool app and here is the repo code.
But before we get to the fun stuff…
What is the SatSchool app?
SatSchool is a school outreach program that introduces Earth observation concepts and career pathways to students (11-15 years old) in the UK. Students get their hands on satellite data to learn how satellites help us study the Earth from space.
As part of this program, I developed the SatSchool app— a “Hands on with Data” module—that introduces technical approaches to Earth observation challenges.
Making a website with satellite data
The SatSchool app features three themes: land, oceans, and ice (check out the source code here).
Land
This theme features an Amazon deforestation page.
Here I used:
- Support Vector Machine (SVM) classifier to make a deforestation map
- Input widgets for more forest/not-forest training points
st.session_state
to remember the training points (as students label more data based on what they see)
Students can view Landsat-8 satellite imagery centered over a region in Brazil and compare the deforested areas with protected areas. Many explore machine learning and experiment with classifiers for the first time!
They learn how their training data makes the classifier better or worse, how satellite imagery can solve problems, and how to dynamically measure deforestation over thousands of square kilometers without installation, powerful computers, or technical know-how!
Oceans
This theme explores the relationship between the sea surface temperature and chlorophyll.
Here I used:
- Altair charts to present simple graphs
st.number_input
andst.form
to make answering questions interactive (withst.balloons
for correct answers! 🎈).
By using geemap
with its split-panel map, students explore two global datasets and the connection between them.
Ice
This theme explores coding.
Here I used:
streamlit_ace
for the terminalst.session_state
for step-by-step instructions in anst.warning
box for aesthetic appeal
Students get introduced to radar satellite data and can bring up satellite images by using code before they realize they’re programming!
Making a quiz
I wanted to bring gamification into the learning process. Satellite imagery is cool, but how could I keep young people engaged so that they had fun learning?
Our quiz tests students on the SatSchool curriculum concepts. To get the best score, they have 30 seconds to answer as many questions as they can.
Here is how I did it in five steps:
Step 1
I did the following imports and variables:
import streamlit as st
import time
import random
import math
TIME_LIMIT = 30 #seconds
row = None
Step 2
I randomly generated math questions using the following code:
def gen_question():
#randomly choose two numbers in a range, alongside an operator
operators = ['+','-','//','*']
a,b = random.randint(1,10), random.randint(1,10)
op = random.choice(operators)
#construct the question text and evaluate the calculation
ques = f"What is {a} {op} {b}?"
ans = eval(f"{a}{op}{b}")
#we create some purposely incorrect answer options
option2 = eval(f"{b}{op}{a}")
option3 = eval(f"{b-2}{op}{a+5}")
option4 = eval(f"{b}{op}{a}-{a}")
#we want to avoid duplicate answer options, so use this inelegant solution
while option2 == ans:
option2 += 1
while option3 == ans or option3 == option2:
option3 += 1
while option4 == ans or option4 == option2 or option4 == option3:
option4 += 1
return {'question': ques,
'options': {
ans: True,
option2: False,
option3: False,
option4: False
}
}
Step 3
I initialised values in st.session_state
to keep track of timings, scores, etc.:
#initialise the session state if keys don't yet exist
if 'correct' not in st.session_state.keys():
st.session_state['correct'] = None
if "quiz_active" not in st.session_state.keys():
st.session_state["quiz_active"] = False
i,j,_ = st.columns([1,1,5])
if i.button("Start quiz", key='start_quiz', disabled=st.session_state['quiz_active']):
st.session_state['quiz_active'] = True
st.session_state['total_score'] = 0
st.session_state['count'] = 0
st.session_state['time_start'] = time.time()
st.session_state['time_now'] = time.time()
st.session_state['score'] = 0
st.session_state['correct'] = None
st.experimental_rerun()
if j.button("End quiz and reset", key='reset', disabled=not st.session_state['quiz_active']):
st.session_state['total_score'] = 0
st.session_state['count'] = 0
st.session_state['correct'] = None
st.session_state['quiz_active'] = False
st.session_state['time_start'] = None
st.experimental_rerun()
if not st.session_state['quiz_active']:
st.write(f'\\n Welcome to the quiz! You have {TIME_LIMIT} seconds to answer as many questions as you can.')
Step 4
I controlled the button layout by using a mix of st.columns()
, st.container()
, and st.empty()
:
question_empty = st.empty()
d,e,_ = st.columns([2,2,6])
with d:
total_score_empty = st.empty()
with e:
st.write('')
answer_empty = st.empty()
if st.session_state['quiz_active']:
#check for time up upon page update
if time.time() - st.session_state['time_start'] > TIME_LIMIT:
st.info(f"Time's up! You scored a total of **{st.session_state['total_score']:.2f}** \\
and answered **{st.session_state['count']}** questions.")
else:
with question_empty:
with st.container():
#get a newly generated question with answer options
row = gen_question()
st.markdown(f"Question {st.session_state['count']+1}: {row['question']}")
options = list(row['options'].keys())
random.shuffle(options)
a,b,_ = st.columns([2,2,6])
#construct the answer buttons, and pass in whether the answer is correct or not in the args
a.button(f"{options[0]}", on_click=answer, args=(str(row['options'][options[0]]),))
a.button(f"{options[1]}", on_click=answer, args=(str(row['options'][options[1]]),))
b.button(f"{options[2]}", on_click=answer, args=(str(row['options'][options[2]]),))
b.button(f"{options[3]}", on_click=answer, args=(str(row['options'][options[3]]),))
if st.session_state['correct'] == 'True' and st.session_state['count'] > 0:
answer_empty.success(f"Question {st.session_state['count']} correct!")
elif st.session_state['correct'] == 'False' and st.session_state['count'] > 0:
answer_empty.error(f"Question {st.session_state['count']} incorrect!")
total_score_empty.metric('Total score', value=f"{st.session_state['total_score']:.2f}", delta=f"{st.session_state['score']:.2f}")
Step 5
Finally, I defined a function that takes an answer and does something with the score:
def answer(ans):
st.session_state['correct'] = ans
if ans == 'True':
#motivate quickfire answers with an exponential decay
score = (1 * math.exp(-0.05*(time.time()-st.session_state['time_now'])))*10
st.session_state['score'] = max(1, score)
else:
#penalise wrong answers with a negative score
st.session_state['score'] = -10
#update the score but prevent it from becoming negative
st.session_state['total_score'] += st.session_state['score']
st.session_state['total_score'] = max(0, st.session_state['total_score'])
st.session_state['time_now'] = time.time()
st.session_state['count'] += 1
I saved my completed script and viewed my app with streamlit run
.
Also, if I wanted to, I could randomly choose from a selection of pre-written questions and implement a leaderboard functionality so that students could see their scores alongside others in the classroom.
Why use Streamlit?
Before I wrap this up…one more thing.
Why did I choose Streamlit? I’ve been using Streamlit for the past few years and knew it’d be perfect to make the SatSchool app because:
- I can quickly deploy my Python code without any front-end headaches.
- I can make a good-looking site and deploy my app to the Streamlit Community Cloud to share my work.
- I can easily extend and modify the site. When my SatSchool colleagues want to add new content or demos, I can quickly prototype it (especially with the help of Sebastian Flores Benner’s fantastic streamlit_book library).
- Streamlit’s amazing ecosystem of demos and third-party libraries make coding fast and fun. Third-party libraries are easy to integrate and offer lots of exciting functionality. Alongside the above-mentioned
streamlit_book
library, there is Qiusheng Wu’sgeemap
for the visualisation of satellite data,streamlit_timeline
,streamlit_ace
,streamlit-lottie
,streamlit_juxtapose
, and more.
Wrapping up
I had a lot of fun making the SatSchool app and I’m excited to hear what you think! There’s still a lot to do. Expect updated app demos as we take our program into schools and show people the power of satellite imagery.
I hope this post can show you how easy it is to make great apps with Streamlit. If you have any questions or want to share what you built, contact me on Twitter at @spiruel and check out SatSchool at @SatSchool_ and on our website.
Happy coding! 🛰️