Satellite cloud frames
The rules behind the satellite cloud overlay on the flight map.
They are platform-neutral; the reference implementation is
vulture-ios/Packages/Flights/Sources/Flights/Satellite/ (SatelliteTileManager+Tiles.swift,
+Availability.swift) and vulture-ios/Modules/App/Sources/Map/Flight/FlightMapViewModel.swift
(// MARK: - Satellite). The tile endpoint is in SkySight satellite tiles.
Constants
Section titled “Constants”| Name | Value | Meaning |
|---|---|---|
| Tile zoom | 6 | The only zoom that is downloaded. The map draws it up to zoom 7 (maximumZoomLevel), then overzooms. |
| Tile size | 512 px | The @2x tile the endpoint returns, declared as a 512 tile source. |
| Frame interval | 600 s | One frame every 10 minutes. |
| Bounds padding | 0.5° | Added on every side of the track’s bounding box. |
| Shown opacity | 0.5 | Fixed. The SkySight forecast opacity setting does not apply (“Satellite excluded”). |
A flight’s time range
Section titled “A flight’s time range”All times are UTC. A flight’s range is its first and last recorded fix:
start = flightDate(00:00 UTC) + startRecordingTime, seconds dropped (minute precision)end = flightDate(00:00 UTC) + endRecordingTime, seconds dropped
startRecordingTime and endRecordingTime are seconds since 00:00 UTC of the flight date and keep
counting past midnight (+86 400 per crossing), as Fix.rawTime does. Add them to the date as a
duration; do not set them as an hour of the day. See open point below.
Frames
Section titled “Frames”timestamps(start, end): start at floor(start / 600) * 600 (epoch seconds) and step by 600 s while
t <= end. Every frame in that list is downloaded. With several flights on the map, the frames of
each flight are computed separately and merged into one sorted set without duplicates, so two flights
on different days do not produce the frames for the days in between.
bounds(track): min/max latitude and longitude of every fix, each widened by 0.5°.
No track (no fixes) means no bounds and no overlay for that flight.
tileRange(bounds, zoom = 6), the standard slippy-map formula with n = 2^zoom:
minX = floor((minLon + 180) / 360 * n)maxX = floor((maxLon + 180) / 360 * n)minY = floor((1 - ln(tan(maxLat·π/180) + 1/cos(maxLat·π/180)) / π) / 2 * n)maxY = floor((1 - ln(tan(minLat·π/180) + 1/cos(minLat·π/180)) / π) / 2 * n)x ∈ max(0, minX) … min(n-1, maxX)y ∈ max(0, minY) … min(n-1, maxY)A flight’s download is every (x, y) in its range × every frame. Tiles are de-duplicated across the
flights on the map by z/x/y/epochSeconds.
Does the archive have imagery?
Section titled “Does the archive have imagery?”Measured against the service on 2026-10-02 (it answers a missing tile with 404 and says nothing else about its reach):
| Flight start (UTC) | Imagery |
|---|---|
before 2014-01-01 00:00 (1388534400) | none |
| 2014-01-01 to 2022-12-31 | only when the whole zoom-6 tile range of the padded bounds lies within x 28–43 and y 11–47 (22.5°W–67.5°E, about 75.5°N–66.5°S) |
from 2023-01-01 00:00 (1672531200) | worldwide |
A flight without imagery is left out of everything the overlay does: it is not downloaded, its frames are not added, and it does not count for “all tiles downloaded”. When no flight on the map has imagery, the cloud button is not offered. This matters because one failed tile fails the whole download.
Before 2025 the service has frames every 30 minutes, not every 10. A request between two frames answers with the earlier one, so the 10-minute list still works and the duplicates are harmless.
Which frame is shown
Section titled “Which frame is shown”- When the overlay is switched on: the selected flight’s midpoint,
(startRecordingTime + endRecordingTime) / 2(integer division) on the flight date, seconds dropped, floored to 600 s. - While the replay moves (scrub or playback): the selected flight’s current fix,
flightDate + fix.rawTime, floored to 600 s. - The frame actually shown is the downloaded frame nearest to that time (smallest absolute difference). With no selected flight, the overlay does not follow the replay.
- Frames swap as hard cuts, never a cross-fade, so two moments are never blended.
Known iOS quirks
Section titled “Known iOS quirks”- iOS builds the range and the replay time with
Calendar.date(bySettingHour:minute:second:of:). With a time past 24:00 UTC (a flight that crosses midnight UTC, common in Australia and New Zealand) that call does not give “the next day at …”. Android implements the duration addition above. To verify on iOS and, if confirmed, log in iOS changes.
