Blogs » Good ... Day?
The source code of the related program is available on GitHub.
I came across this reddit post a while ago: At what time are there the most people simultaneously awake in the world?
Well it is immediately obvious that we don't wake up at the same time (even if we're in the same time zone). However, it seems sensible to do an estimate the number of people under the Sun (so we can happily say good day to each other!)
I had a quick thought about how to do the estimate:
- it should be real time – or at least constantly refreshing;
- it should be somewhat precise;
- it should compute the solar terminator using astronomical algorithms instead of fetching from a database;
- ideally it should be an interactive program usable offline.
A side note about the thrid point. A database for the solar terminators is virtually impossible as it constantly changes every nanosecond, and differs even on the same date across different years. I wanted my program to be accessible offline, so I can't really fetch this data from observatories.
It's time for the implementation. To make it also accessible as a web app, Rust and egui are undoubtedly the choice.
What I needed to do is as follows:
- calculate the solar terminator using astronomical algorithms;
- calculate the number of people under the Sun;
- put them together in a web app.
Sounds simple enough?
The solar terminator
Before talking about the terminator, we need to know where the Sun is. There are various algorithms to find the position of the Sun in RA/Dec coordinate pair. A common one is to use the VSOP model. We then need to do a simple change to rotate it to use the J2000 epoch. Finally, all we need to do is to convert it to the spherical coordinate system:
where and . Trignometry suggests that and .
To visualise this, the position can be in fact represented by a point on the surface of the Earth, where the Sun is directly overhead (called the geographic position). This is done with
where GST is the Greenwich Sidereal Time.
Finding all these requires us to know the Julian date derived from UT1. However, the UTC is a fine approximation for the UT1 (plus it is easily accessible on our machines), so we can use the UTC time directly from the system clock.
We now have the position of the Sun. To save some calculation let's assume that the Sun is infinitely far away (it is already quite far!), and all we need to do is to draw a ring on the Earth's surface which is exactly 90 degrees away from the geographic position from the Sun. This is the solar terminator, which is, in fact, a great circle of the Earth.
Let the terminator be represented by the graph of , where is the latitude of the outline point and is the longitude, as shown in the diagram below:
To find establish a relationship between and , we could consider the great circle distance derived from the spherical law of cosines, between and an arbitary point with coordinates on the terminator:
From our construction we have and . Therefore, we can solve for :
This is the equation of the terminator. We can now plot it on the Earth's surface.
Population data
Luckily we have datasets like Gridded Population of the World, Version 4 (GPWv4), we can just iterate through the grid and count the number of people below/above the terminator. Since we wanted to calculate an estimate in real time, we can't really do this at the best precision (30 arc-seconds). I have chosen to use a 15 arc-minute grid, which is a good balance between precision and computation time.
I wrote the dataset into a binary file population_count.bin
so we can load this as a Vec
fairly quickly. For some reason the dataset includes fractional people (I guess this is due to authorities reporting precisions worse than 15 arc-minutes), so I just rounded them to the nearest integer. It turns out to be something like:
for (lon, terminator_lat) in self.terminator_outline.iter().enumerate() {
if self.sun_is_north {
for (i, &pop) in POPULATION_COUNT
.get()
.unwrap()
.iter()
.skip(lon)
.step_by(1440)
.enumerate()
{
if i as i64 <= (720.0 - (terminator_lat + 90.0) * 4.0) as i64 {
self.population_under_sun += pop;
} else {
break;
}
}
} else {
for (i, &pop) in POPULATION_COUNT
.get()
.unwrap()
.iter()
.skip(lon)
.step_by(1440)
.enumerate()
{
if i as i64 >= (720.0 - (terminator_lat + 90.0) * 4.0) as i64 {
self.population_under_sun += pop;
}
}
}
}
I also used the GPWv4 population density data to plot a world map for the web app.
Putting them together
This part is just messing with Rust and egui. I guess referring to the source code is pretty much self-explanatory. Here's the web app: (open in fullscreen)
PS: the tabs have docking support, and I contributed to it ;)
What's next...
Maybe I will do a visualization of the relative positions between the Sun and the Earth, since the graph area seems a bit empty. Anyways, that's something for another day.
I hope you like the app!