.. raw:: html
:file: ../embed_widgets/sun_elevation.html
Sun elevation along a flight
============================
Flight AF291 from Kansai airport (RJBB) to Paris (LFPG) on January 3rd
2020 was flying quite higher than usual in latitude because of strong
headwinds on the usual route over Finland and the White Sea. See for
reference the following screenshot image from
`windy.com `__ taken on that day.
.. image:: images/windy.jpg
:scale: 40%
:alt: Windy map
:align: center
The usual route departs before noon from Japan and reaches Paris in
the middle of the afternoon, a twelve hour flight which feels like
noon-ish all along.
When we fly these latitudes (up to 77°, i.e. 11 degrees North of the Arctic
circle would) close the winter solstice, we experience at some point the
Arctic night.
.. code:: python
import altair as alt
(
f.assign(arctic_circle=66.5)
.chart("latitude", "arctic_circle")
.encode(
alt.Color("variable", legend=alt.Legend(title="angle")),
)
.properties(height=180, width=400)
)
.. raw:: html
The `astropy `__ library contains
all you need to compute the relative positions of astral bodies. A
common use case is to calculate the position of stars or satellites from
a given position on Earth. A fun thing to do with aircraft here would be
to compute the elevation (altitude, in angle) of the sun with respect to
the aircraft. The timestamp will help computing the position of the Sun,
before we calculate the altitude and azimuth angles from the
three-coordinate position (latitude, longitude, altitude) of the
aircraft.
.. code:: python
import pandas as pd
from astropy.coordinates import get_sun, AltAz, EarthLocation
from astropy.time import Time
# use astropy to compute the position of the Sun w.r.t the aircraft
time = Time(f.data.timestamp)
sun_position = get_sun(time)
aircraft_position = EarthLocation(
lat=f.data.latitude.values,
lon=f.data.longitude.values,
height=f.data.altitude.values * 0.3048, # convert to meter
)
aircraft_frame = AltAz(obstime=time, location=aircraft_position)
sun_altaz = sun_position.transform_to(aircraft_frame)
# include sun altitude and azimuth in the flight data
df = pd.DataFrame(
dict(
timestamp=f.data.timestamp,
sun_altitude=list(x.value for x in sun_altaz.alt),
sun_azimuth=list(x.value for x in sun_altaz.az),
)
)
g = f.merge(df, on="timestamp")
.. code:: ipython3
(
g.assign(horizon=0, twilight=-6)
.chart("sun_altitude", "horizon", "twilight")
.encode(
alt.Color(
"variable",
legend=alt.Legend(title="angle"),
scale=alt.Scale(
domain=["sun_altitude", "horizon", "twilight"],
range=["#eeba30", "#5a73ce", "#372e29"],
),
),
)
.properties(height=180, width=400)
)
.. raw:: html
We usually consider it twilight when the Sun is between 0° and -6° below
the horizon: the sky is still quite bright. In this particular
situation, information from outside is pretty confusing: we fly
westbound, never far from the
`terminator `__, local
time is always around noon, the Sun goes under the horizon for few hours
without really disappearing. It is dark but never really night.
The next step is to plot the trajectory with different colors:
.. raw:: html
- yellow (■ #eeba30) during day time (sun above the horizon);
- orange (■ #dc6900) when the sun is just below the horizon (from 0° to -6°);
- red (■ #ae0001) when the sun is between -6° and -10° below the horizon;
- black (■ #372e29) when the sun is the lowest.
.. code:: python
from ipyleaflet import Map, basemaps
from ipywidgets import Layout
map_ = Map(
center=(60, 80),
zoom=2,
layout=Layout(height="500px", max_width="500px"),
basemap=basemaps.CartoDB.Positron,
)
map_.add_layer(g, color="#eeba30") # yellow
map_.add_layer(g.query("sun_altitude < 0"), color="#dc6900") # orange
map_.add_layer(g.query("sun_altitude < -6"), color="#ae0001") # red
map_.add_layer(g.query("sun_altitude < -10"), color="#372e29") # black
map_
.. raw:: html
Now comes the fun when you have a window seat toward the sun: you take
pictures!
Using the timestamp in the EXIF information of each image, you may place
a marker on a map and get an idea of how far the Sun is below the
horizon, with a reference based on the color of the trajectory (yellow
for daylight, orange for twilight, etc.)
.. code:: python
import re
import cv2
import exifread
from ipywidgets import Image, Layout
from ipyleaflet import Popup
# for each JPG file in the folder (a pathlib.Path)
for file in sorted(list(picture_directory.glob("**/*.JPG"))):
with file.open("rb") as fh:
# parse EXIF information
exif_tags = exifread.process_file(fh)
s = exif_tags["EXIF DateTimeDigitized"].values
# Parse time and correct the timestamp of your device (+01:00 for mine)
ts = pd.Timestamp(re.sub(r"(\d+):(\d+):(\d+) ", r"\1/\2/\3 ", s) + "+01:00")
# safeguard, the picture must have been taken during the flight
assert f.start <= ts <= f.stop
# read the image
img = cv2.imread(file.as_posix())
# resize the image
scale = 0.1
width = int(img.shape[1] * scale)
height = int(img.shape[0] * scale)
dim = (width, height)
resized = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
_, arr = cv2.imencode(".jpg", resized)
# build an Image to place on the map
image = Image(value=arr.tobytes(), layout=Layout(min_width="200px"))
point = f.before(ts).at()
marker = point.leaflet(title=f"{ts}", draggable=False)
marker.popup = image
map_.add_layer(marker)