Compute Latency
This example demonstrates how to use direct function calls of the low-level TAT-C library to perform latency analysis.
Similar to the Collect Observations and Compute Coverage examples, the first steps are to define the satellites for the mission. This example again uses the NOAA-20 satellite with a two-line elements model from July 2022 and a VIIRS instrument with field of regard computed based on a 834km altitude and 3000km swath width. Points are distributed over the globe with a 5000 km characteristic distance and observations are collected over a 30 day period starting on July 14, 2022 at noon UTC.
[1]:
from tatc import utils
from tatc.schemas import Instrument, Satellite, TwoLineElements
viirs = Instrument(
name="VIIRS",
field_of_regard=utils.swath_width_to_field_of_regard(834000, 3000000),
req_target_sunlit=True,
)
noaa20 = Satellite(
name="NOAA 20",
orbit=TwoLineElements(
tle=[
"1 43013U 17073A 22195.78278435 .00000038 00000+0 38919-4 0 9996",
"2 43013 98.7169 133.9110 0001202 63.8768 296.2532 14.19561306241107",
]
),
instruments=[viirs],
)
from tatc.generation import generate_cubed_sphere_points
points_df = generate_cubed_sphere_points(5000e3)
from tatc.schemas import Point
points = points_df.apply(
lambda r: Point(id=r.point_id, latitude=r.geometry.y, longitude=r.geometry.x),
axis=1,
)
from datetime import datetime, timedelta, timezone
start = datetime(year=2022, month=7, day=14, hour=12, tzinfo=timezone.utc)
end = start + timedelta(days=30)
from tatc.analysis import collect_observations
from joblib import Parallel, delayed
import pandas as pd
observations_list = Parallel(n_jobs=-1)(
delayed(collect_observations)(point, noaa20, viirs, start, end) for point in points
)
observations = pd.concat(observations_list, ignore_index=True)
display(observations)
| point_id | geometry | satellite | instrument | start | end | epoch | sat_alt | sat_az | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 2022-07-26 23:05:45.077898+00:00 | 2022-07-26 23:08:44.767368+00:00 | 2022-07-26 23:07:14.922633+00:00 | 23.232253 | 96.383051 |
| 1 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 2022-07-29 00:07:14.187520+00:00 | 2022-07-29 00:14:00.195900+00:00 | 2022-07-29 00:10:37.191710+00:00 | 44.280829 | 81.971247 |
| 2 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 2022-07-29 23:48:34.454547+00:00 | 2022-07-29 23:54:41.391443+00:00 | 2022-07-29 23:51:37.922995+00:00 | 36.376684 | 86.224131 |
| 3 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 2022-07-30 23:30:01.123712+00:00 | 2022-07-30 23:35:11.392518+00:00 | 2022-07-30 23:32:36.258115+00:00 | 29.980316 | 90.546787 |
| 4 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 2022-07-31 23:11:40.871422+00:00 | 2022-07-31 23:15:23.426119+00:00 | 2022-07-31 23:13:32.148770500+00:00 | 24.749626 | 94.926625 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2327 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 2022-08-12 18:28:49.325939+00:00 | 2022-08-12 18:34:03.183661+00:00 | 2022-08-12 18:31:26.254800+00:00 | 31.260813 | 324.671157 |
| 2328 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 2022-08-12 21:48:42.817756+00:00 | 2022-08-12 21:49:48.545180+00:00 | 2022-08-12 21:49:15.681468+00:00 | 21.116289 | 13.096140 |
| 2329 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 2022-08-12 23:25:35.417650+00:00 | 2022-08-12 23:31:03.448822+00:00 | 2022-08-12 23:28:19.433236+00:00 | 32.782441 | 37.026059 |
| 2330 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 2022-08-13 01:04:19.959524+00:00 | 2022-08-13 01:11:46.499770+00:00 | 2022-08-13 01:08:03.229647+00:00 | 73.846580 | 59.292237 |
| 2331 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 2022-08-13 02:45:30.353434+00:00 | 2022-08-13 02:51:54.566357+00:00 | 2022-08-13 02:48:42.459895500+00:00 | 40.295326 | 263.804774 |
2332 rows × 9 columns
Latency analysis measures the interval between observations and downlink to the first ground station. The following specifies a ground station at Hoboken with a 10-degree minimum elevaiton angle for downlink.
[2]:
from tatc.schemas import GroundStation
hoboken = GroundStation(
name="Hoboken", latitude=40.74259, longitude=-74.02686, min_elevation_angle=10
)
from tatc.analysis import collect_downlinks
downlinks = collect_downlinks(hoboken, noaa20, start, end)
display(downlinks)
| station | geometry | satellite | start | end | epoch | |
|---|---|---|---|---|---|---|
| 0 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-07-14 17:12:43.783775+00:00 | 2022-07-14 17:22:57.443532+00:00 | 2022-07-14 17:17:50.613653500+00:00 |
| 1 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-07-14 18:54:04.803778+00:00 | 2022-07-14 19:02:24.959866+00:00 | 2022-07-14 18:58:14.881822+00:00 |
| 2 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-07-15 05:32:06.586963+00:00 | 2022-07-15 05:39:35.850388+00:00 | 2022-07-15 05:35:51.218675500+00:00 |
| 3 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-07-15 16:54:27.859081+00:00 | 2022-07-15 17:03:52.146143+00:00 | 2022-07-15 16:59:10.002612+00:00 |
| 4 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-07-15 18:34:28.301932+00:00 | 2022-07-15 18:44:07.965374+00:00 | 2022-07-15 18:39:18.133653+00:00 |
| ... | ... | ... | ... | ... | ... | ... |
| 87 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-08-11 18:27:37.077327+00:00 | 2022-08-11 18:37:35.966333+00:00 | 2022-08-11 18:32:36.521830+00:00 |
| 88 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-08-12 05:09:06.247799+00:00 | 2022-08-12 05:11:04.388216+00:00 | 2022-08-12 05:10:05.318007500+00:00 |
| 89 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-08-12 08:28:06.674082+00:00 | 2022-08-12 08:32:42.220364+00:00 | 2022-08-12 08:30:24.447223+00:00 |
| 90 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-08-12 16:30:10.560649+00:00 | 2022-08-12 16:37:44.339077+00:00 | 2022-08-12 16:33:57.449863+00:00 |
| 91 | Hoboken | POINT Z (-74.02686 40.74259 0.00000) | NOAA 20 | 2022-08-13 08:08:15.561170+00:00 | 2022-08-13 08:15:28.388595+00:00 | 2022-08-13 08:11:51.974882500+00:00 |
92 rows × 6 columns
Finally, latencies are computed by comparing the observations and downlink opportunities.
[3]:
from tatc.analysis import compute_latencies
latencies = compute_latencies(observations, downlinks)
display(latencies)
| point_id | geometry | satellite | instrument | sat_alt | sat_az | station | downlinked | latency | observed | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 23.232253 | 96.383051 | Hoboken | 2022-07-27 05:10:20.152447500+00:00 | 0 days 06:03:05.229814500 | 2022-07-26 23:07:14.922633+00:00 |
| 1 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 44.280829 | 81.971247 | Hoboken | 2022-07-29 06:13:31.927716500+00:00 | 0 days 06:02:54.736006500 | 2022-07-29 00:10:37.191710+00:00 |
| 2 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 36.376684 | 86.224131 | Hoboken | 2022-07-30 05:54:36.620577+00:00 | 0 days 06:02:58.697582 | 2022-07-29 23:51:37.922995+00:00 |
| 3 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 29.980316 | 90.546787 | Hoboken | 2022-07-31 05:35:38.300419+00:00 | 0 days 06:03:02.042304 | 2022-07-30 23:32:36.258115+00:00 |
| 4 | 0 | POINT Z (-157.51699 -67.51699 0.00000) | NOAA 20 | VIIRS | 24.749626 | 94.926625 | Hoboken | 2022-08-01 05:16:36.756741500+00:00 | 0 days 06:03:04.607971 | 2022-07-31 23:13:32.148770500+00:00 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2327 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 31.260813 | 324.671157 | Hoboken | 2022-08-13 08:11:51.974882500+00:00 | 0 days 13:40:25.720082500 | 2022-08-12 18:31:26.254800+00:00 |
| 2328 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 21.116289 | 13.096140 | Hoboken | 2022-08-13 08:11:51.974882500+00:00 | 0 days 10:22:36.293414500 | 2022-08-12 21:49:15.681468+00:00 |
| 2329 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 32.782441 | 37.026059 | Hoboken | 2022-08-13 08:11:51.974882500+00:00 | 0 days 08:43:32.541646500 | 2022-08-12 23:28:19.433236+00:00 |
| 2330 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 73.846580 | 59.292237 | Hoboken | 2022-08-13 08:11:51.974882500+00:00 | 0 days 07:03:48.745235500 | 2022-08-13 01:08:03.229647+00:00 |
| 2331 | 31 | POINT Z (157.24514 67.38106 0.00000) | NOAA 20 | VIIRS | 40.295326 | 263.804774 | Hoboken | 2022-08-13 08:11:51.974882500+00:00 | 0 days 05:23:09.514987 | 2022-08-13 02:48:42.459895500+00:00 |
2332 rows × 10 columns
Similar to Coverage Analysis, latency analysis can also be reduced to compute descriptive statistics for each observation point.
[4]:
from tatc.analysis import reduce_latencies
reduced_results = reduce_latencies(latencies)
display(reduced_results)
| geometry | latency | samples | |
|---|---|---|---|
| point_id | |||
| 0 | POINT Z (-157.51699 -67.51699 0.00000) | 0 days 05:58:48.795874 | 26 |
| 1 | POINT Z (-112.55097 -67.51699 0.00000) | 0 days 09:06:07.721909 | 26 |
| 2 | POINT Z (-67.58495 -67.51699 0.00000) | 0 days 06:02:48.350006 | 24 |
| 3 | POINT Z (-22.61894 -67.51699 0.00000) | 0 days 01:52:05.106818 | 24 |
| 4 | POINT Z (22.34708 -67.51699 0.00000) | 0 days 05:03:36.986779 | 25 |
| 5 | POINT Z (67.31310 -67.51699 0.00000) | 0 days 08:01:41.364035 | 25 |
| 6 | POINT Z (112.27912 -67.51699 0.00000) | 0 days 02:54:09.325805 | 26 |
| 7 | POINT Z (157.24514 -67.51699 0.00000) | 0 days 03:20:48.380798 | 27 |
| 8 | POINT Z (-157.51699 -22.55097 0.00000) | 0 days 06:12:31.869875 | 36 |
| 9 | POINT Z (-112.55097 -22.55097 0.00000) | 0 days 09:01:52.898139 | 36 |
| 10 | POINT Z (-67.58495 -22.55097 0.00000) | 0 days 04:04:53.354391 | 35 |
| 11 | POINT Z (-22.61894 -22.55097 0.00000) | 0 days 01:49:05.988634 | 35 |
| 12 | POINT Z (22.34708 -22.55097 0.00000) | 0 days 04:48:52.959409 | 35 |
| 13 | POINT Z (67.31310 -22.55097 0.00000) | 0 days 07:54:22.127545 | 36 |
| 14 | POINT Z (112.27912 -22.55097 0.00000) | 0 days 02:59:27.161054 | 35 |
| 15 | POINT Z (157.24514 -22.55097 0.00000) | 0 days 03:26:30.579920 | 34 |
| 16 | POINT Z (-157.51699 22.41505 0.00000) | 0 days 06:43:48.345735 | 35 |
| 17 | POINT Z (-112.55097 22.41505 0.00000) | 0 days 10:01:50.549304 | 36 |
| 18 | POINT Z (-67.58495 22.41505 0.00000) | 0 days 06:38:48.429340 | 34 |
| 19 | POINT Z (-22.61894 22.41505 0.00000) | 0 days 02:32:38.959483 | 36 |
| 20 | POINT Z (22.34708 22.41505 0.00000) | 0 days 05:19:27.337626 | 36 |
| 21 | POINT Z (67.31310 22.41505 0.00000) | 0 days 06:24:13.168950 | 34 |
| 22 | POINT Z (112.27912 22.41505 0.00000) | 0 days 02:10:53.365043 | 36 |
| 23 | POINT Z (157.24514 22.41505 0.00000) | 0 days 03:35:23.784569 | 35 |
| 24 | POINT Z (-157.51699 67.38106 0.00000) | 0 days 06:38:05.281820 | 196 |
| 25 | POINT Z (-112.55097 67.38106 0.00000) | 0 days 06:57:28.799021 | 195 |
| 26 | POINT Z (-67.58495 67.38106 0.00000) | 0 days 05:31:25.393057 | 193 |
| 27 | POINT Z (-22.61894 67.38106 0.00000) | 0 days 03:17:49.805215 | 195 |
| 28 | POINT Z (22.34708 67.38106 0.00000) | 0 days 04:37:56.758810 | 196 |
| 29 | POINT Z (67.31310 67.38106 0.00000) | 0 days 04:45:44.523214 | 195 |
| 30 | POINT Z (112.27912 67.38106 0.00000) | 0 days 05:34:45.688417 | 197 |
| 31 | POINT Z (157.24514 67.38106 0.00000) | 0 days 06:08:53.051926 | 198 |
Finally, GeoPlot can visualize the geospatial data.
[5]:
import geoplot as gplt
import contextily as ctx
reduced_results["latency_hr"] = reduced_results.latency / timedelta(hours=1)
ax = gplt.pointplot(
reduced_results,
hue="latency_hr",
legend=True,
legend_kwargs={"label": "Latency (hr)", "orientation": "horizontal"},
)
ctx.add_basemap(ax, crs=reduced_results.crs)
Similar to Coverage Analysis, cells can also be used to aggregate descriptive statistics. Note that, when working on the reduced results, the revisit aggregation function must perform a weighted average based on the number of samples for each point.
[6]:
from tatc.generation.cells import generate_cubed_sphere_cells
cells_df = generate_cubed_sphere_cells(5000e3)
import numpy as np
grid_results = (
cells_df.sjoin(reduced_results, how="inner", predicate="contains")
.dissolve(
by="cell_id",
aggfunc={
"samples": "sum",
"latency_hr": lambda r: np.average(
r, weights=reduced_results.loc[r.index, "samples"]
),
},
)
.reset_index()
)
display(grid_results)
| cell_id | geometry | samples | latency_hr | |
|---|---|---|---|---|
| 0 | 0 | POLYGON Z ((-135.03398 -90.00000 0.00000, -135... | 26 | 5.980221 |
| 1 | 1 | POLYGON Z ((-90.06796 -90.00000 0.00000, -90.0... | 26 | 9.102145 |
| 2 | 2 | POLYGON Z ((-45.10194 -90.00000 0.00000, -45.1... | 24 | 6.046764 |
| 3 | 3 | POLYGON Z ((-0.13593 -90.00000 0.00000, -0.135... | 24 | 1.868085 |
| 4 | 4 | POLYGON Z ((44.83009 -90.00000 0.00000, 44.830... | 25 | 5.060274 |
| 5 | 5 | POLYGON Z ((89.79611 -90.00000 0.00000, 89.796... | 25 | 8.028157 |
| 6 | 6 | POLYGON Z ((134.76213 -90.00000 0.00000, 134.7... | 26 | 2.902591 |
| 7 | 7 | POLYGON Z ((179.72815 -90.00000 0.00000, 179.7... | 27 | 3.346772 |
| 8 | 8 | POLYGON Z ((-135.03398 -45.03398 0.00000, -135... | 36 | 6.208853 |
| 9 | 9 | POLYGON Z ((-90.06796 -45.03398 0.00000, -90.0... | 36 | 9.031361 |
| 10 | 10 | POLYGON Z ((-45.10194 -45.03398 0.00000, -45.1... | 35 | 4.081487 |
| 11 | 11 | POLYGON Z ((-0.13593 -45.03398 0.00000, -0.135... | 35 | 1.818330 |
| 12 | 12 | POLYGON Z ((44.83009 -45.03398 0.00000, 44.830... | 35 | 4.814711 |
| 13 | 13 | POLYGON Z ((89.79611 -45.03398 0.00000, 89.796... | 36 | 7.906147 |
| 14 | 14 | POLYGON Z ((134.76213 -45.03398 0.00000, 134.7... | 35 | 2.990878 |
| 15 | 15 | POLYGON Z ((179.72815 -45.03398 0.00000, 179.7... | 34 | 3.441828 |
| 16 | 16 | POLYGON Z ((-135.03398 -0.06796 0.00000, -135.... | 35 | 6.730096 |
| 17 | 17 | POLYGON Z ((-90.06796 -0.06796 0.00000, -90.06... | 36 | 10.030708 |
| 18 | 18 | POLYGON Z ((-45.10194 -0.06796 0.00000, -45.10... | 34 | 6.646786 |
| 19 | 19 | POLYGON Z ((-0.13593 -0.06796 0.00000, -0.1359... | 36 | 2.544155 |
| 20 | 20 | POLYGON Z ((44.83009 -0.06796 0.00000, 44.8300... | 36 | 5.324260 |
| 21 | 21 | POLYGON Z ((89.79611 -0.06796 0.00000, 89.7961... | 34 | 6.403658 |
| 22 | 22 | POLYGON Z ((134.76213 -0.06796 0.00000, 134.76... | 36 | 2.181490 |
| 23 | 23 | POLYGON Z ((179.72815 -0.06796 0.00000, 179.72... | 35 | 3.589940 |
| 24 | 24 | POLYGON Z ((-135.03398 44.89806 0.00000, -135.... | 196 | 6.634801 |
| 25 | 25 | POLYGON Z ((-90.06796 44.89806 0.00000, -90.06... | 195 | 6.958000 |
| 26 | 26 | POLYGON Z ((-45.10194 44.89806 0.00000, -45.10... | 193 | 5.523720 |
| 27 | 27 | POLYGON Z ((-0.13593 44.89806 0.00000, -0.1359... | 195 | 3.297168 |
| 28 | 28 | POLYGON Z ((44.83009 44.89806 0.00000, 44.8300... | 196 | 4.632433 |
| 29 | 29 | POLYGON Z ((89.79611 44.89806 0.00000, 89.7961... | 195 | 4.762368 |
| 30 | 30 | POLYGON Z ((134.76213 44.89806 0.00000, 134.76... | 197 | 5.579358 |
| 31 | 31 | POLYGON Z ((179.72815 44.89806 0.00000, 179.72... | 198 | 6.148070 |
Finally, GeoPlot can illustrate the latency over global regions.
[7]:
import geoplot as gplt
import contextily as ctx
ax = gplt.choropleth(
grid_results,
hue="latency_hr",
alpha=0.5,
legend=True,
legend_kwargs={"label": "Latency (hr)", "orientation": "horizontal"},
)
ctx.add_basemap(
ax,
crs=reduced_results.crs,
)