Skip to content

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.

NameValueMeaning
Tile zoom6The only zoom that is downloaded. The map draws it up to zoom 7 (maximumZoomLevel), then overzooms.
Tile size512 pxThe @2x tile the endpoint returns, declared as a 512 tile source.
Frame interval600 sOne frame every 10 minutes.
Bounds padding0.5°Added on every side of the track’s bounding box.
Shown opacity0.5Fixed. The SkySight forecast opacity setting does not apply (“Satellite excluded”).

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.

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.

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-31only 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.

  • 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.
  • 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.