Data
Overview
After extensive field trials, development & research, SkyPath decided to divide the world into 10x10 miles x 1000 feet high logical rectangular boxes, which were found to be the best balance; not to overload the pilots with information, but give enough granularity for the pilots to be able to take action and avoid CAT consequences.
SkyPath uses H3 hexagonal hierarchical geospatial indexing system with H3 resolution 5 to represent turbulence area by hexagons of different severity levels. SDK handles all work with H3 indexes out of the box so you don't need to do anything for it on your side.
SkyPath world map tiles logical view
Offline First
The SDK is offline first. All tracked turbulence data is stored offline until successfully sent to the server and the fetched data from the server is stored on the disk and accessible offline according to the description below. All configurations are stored on the disk and stay across app launches until explicit change.
Data Query
At first request, the whole data according to SkyPath.dataQuery
is fetched and then only the new data that appeared (delta) is fetched to save network traffic. When cached data expires or a delta can't be received, the whole fresh data will be received again.
There are thousands of turbulence reports around the globe. To reduce network traffic usage and keep only data that is currently needed the data fetch is separated into the different types controlled by the SkyPath.dataQuery
object that is set initially to default values and can be updated at any time. All of the below are optional to set but recommended due to your specific flow.
After updating SkyPath.dataQuery
in any way, the check, if need to fetch new data, will be made. And if the change requires a new server fetch, the server request will be made immediately. No need to call SkyPath.fetchData(refresh:)
.
You can update data query at any time and in both ways by setting a property only or a whole object.
SkyPath.shared.dataQuery.types = [.turbulence, .turbulencePolygons]
SkyPath.shared.dataQuery = DataQuery(types: [.turbulence, .turbulencePolygons])
Turbulence Polygons
A planet-wide aggregated turbulence area polygons as a GeoJSON string. Used to show turbulence areas worldwide without fetching too much data. DataQuery.types
should have .turbulencePolygons
to fetch it. Stored on disk, accessible offline.
Turbulence polygons are generated per time history separately and by default, only selected time history is fetched for polygons. To enable fetching polygons for all time histories up to selected set a corresponding flag. For example, if set to fetch 4h, then to have polygons available offline for 0.5h, 1h, 2h, and 4h you need to set this flag to true
.
SkyPath.shared.dataQuery.globalTurbulencePolygonsUpToEnabled = false
⚠ `globalTurbulencePolygonsUpToEnabled = true` consumes more network traffic
SDK periodically fetches global turbulence polygons and calls the delegate method when receiving new data.
// SkyPathDelegate
func didReceiveNewTurbulencePolygons() { }
Polygons data is generated per each history time separately, so if SkyPath.dataHistory
is set to .twoHours
for example, polygons for .fourHours
will not be available to query, because it will not be fetched from the server. It is possible also to fetch polygons for times up to selected by setting DataQuery. globalTurbulencePolygonsUpToEnabled = true
. It means that if set to .fourHours
polygons will be fetched also for .halfHour
, .hour
, .twoHours
, and .fourHours
. It will allow you to change time and see polygons data when offline.
If DataQuery.globalEnabled
== true
turbulence polygons will be fetched worldwide, otherwise, if polygon
is set - only inside the polygon. The default is true
.
It is recommended to keep turbulence polygons and low-level hexagons data on separate layers. Show and hide the corresponding layer when zooming in/out of the map. Here is an approximate zoom level when switching from hexagons to global polygons:
3320 meters/point (1660 meters/pixel) or 10892 feet/point (5446 feet/pixel)
Here is an example screenshot of the iPad Pro 9.7 inch screen with a visible area when to switch.
Route Corridor (Polygon)
Route corridor polygon is a geo-fence area to fetch data inside only. It should be a valid GeoJSON Polygon RFC 7946. Use your route line coordinates to create a polygon with some width distance (50-150 NM is good).
SDK will fetch all data in the corridor at first and then only the delta since the previous fetch in this corridor. If change the corridor to a different then all data will be fetched in the new corridor (not delta) and then will use delta again. So the route corridor should change only when the route line changes. Changing corridors frequently will result in big network traffic use. Also, choose the corridor width according to your needs because bigger width and bigger corridor will consume more network traffic.
⚠ Frequent changes and a bigger width of the corridor consumes more network traffic
It is fetched separately from other data types and as fast as possible, and also stored offline. Stored on disk, accessible offline.
let polygon: [CLLocationCoordinate2D] = []
SkyPath.shared.dataQuery.polygon = polygon
According to GeoJSON Polygon RFC 7946 it should be a closed ring with up to 250 coordinates, otherwise SkyPath.didFailToFetchNewData
will be called with an error. It is recommended to have fewer coordinates and simplify the polygon with enough tolerance.
To make a route corridor and simplification of the polygon you can use provided [CLLocationCoordinate2D].buffer(widthNM:)
and [CLLocationCoordinate2D].simplified(tolerance:)
methods from the SDK.
Usage example (a background thread is recommended to not block the main thread):
// where `polygon` is [CLLocationCoordinate2D]
dataQuery.polygon = polygon.buffer(widthNM: 100).simplified()
Also, a third-party library can be used to generate a polygon as well. GEOSwift is a good option. It is more efficient in terms of speed and time than provided buffer()
and simplified()
methods.
Viewport
A viewport is a polygon of a visible map area in the app to fetch the right data when it's needed. Please keep in mind, that the SDK will try to fetch the data for the viewport as soon as possible after updating SkyPath.shared.dataQuery.viewport
. So to save network traffic consider updating viewport
when it's needed.
All data in the viewport will be fetched at first and then a delta will be fetched only. But when changing a viewport all data will be fetched again.
⚠ Frequent changes and a bigger viewport consumes more network traffic
A good place could be when the pilot moved the map manually, released the finger and the map stopped moving after animation, or when the focused map area is moved by code far from the previously focused area. Stored in memory, accessible offline until app relaunch. The previous viewport data is replaced with new viewport data.
let polygon: [CLLocationCoordinate2D] = []
SkyPath.shared.dataQuery.viewport = polygon
The simplest viewport polygon would be a currently visible rectangle on the map like [NorthWest, NorthEast, SouthEast, SouthWest, NorthWest]
. For a more complex polygon, the following rules are applied.
Should be a closed ring and have max 250 coordinates, otherwise SkyPath.didFailToFetchNewData
will be called with an error. Simplify the polygon with enough tolerance.
To make a route corridor and simplification of the polygon you can use provided Array<CLLocationCoordinate2D>.buffer(widthNM:)
and Array<CLLocationCoordinate2D>.simplified(tolerance:)
methods from the SDK.
Usage example (a background thread is recommended to not block the main thread):
// where `polygon` is [CLLocationCoordinate2D]
dataQuery.viewport = polygon.simplified()
Also, a third-party library can be used to generate a polygon as well. GEOSwift is a good option. It is more efficient in terms of speed and time than provided buffer()
and simplified()
methods.
History Time
Set DataHistoryTime
to fetch data for. It's an enum with cases: halfHour
, hour
, twoHours
, fourHours
, and sixHours
. The default is twoHours
. The server does some data precalculations so only the specified time frames are supported.
SkyPath.shared.dataHistoryTime = .twoHours
When started, SDK fetches initial data from time back in time and then receives only updates since the last update.
It determines the data history to be fetched from the server. So if it is set to .twoHours
for example (the default one), there will be no data locally available for more than 2 hours ago. If need 4 hours of history, set SkyPath.dataHistoryTime
to .fourHours
.
Changing from lower time to higher will require an API request to fetch data, when changing from higher to lower time, data could be available immediately as it was already included in the higher history time fetch. Alternatively, time can be set to .sixTime
once to fetch 6 hours of history data always and then just query with a different time history to show on the map. Please note, that it will increase network traffic but will allow having more data available immediately and offline during changing time history.
Update
Set DataUpdateFrequency
, it's an enum with cases: none
, minimal
, medium
, fast
. The default is fast
(every 1 min). It controls time intervals to fetch data for global turbulence polygons, route polygons, viewport, and other data. When the data query route polygon or the viewport is changed, they are fetched as fast as possible not waiting for the next time interval.
SkyPath.shared.dataUpdateFrequency = .fast
To save network traffic, you can set it to none
to disable periodic new data fetch from the server at all or set it to minimal
to update with bigger time intervals.
Can be updated at any time in the project. Value is stored across app launches until changed.
The default is fast
.
When you change some query parameters SDK will start fetching fresh data immediately if needed. No need to force fetch manually.
At some point, if need to fetch fresh data immediately (not after a data query update) call the following:
SkyPath.shared.fetchData(refresh: true)
⚠ Frequent `fetchData(refresh: true)` consumes more network traffic
It could be helpful to know if SkyPath data was updated a long time ago (when offline for example), so check when the last time data was successfully received from the server.
SkyPath.shared.dataUpdatedAt
Data Types
SkyPath provides the following data types: turbulence
, traffic
, and pireps
. The default is turbulence
only. Set it if you need more than just turbulence data.
SkyPath.shared.dataQuery.types = [.turbulence, .pireps]
Turbulence Severity
Color Legend
Smooth #FFFFFF (alpha 0.6) Border #898989 (alpha 0.8) ⬡
Light #FFF04C (alpha 0.8) ⬣
Light-Moderate #FFBC09 (alpha 0.8) ⬣
Moderate #FF7100 (alpha 0.8) ⬣
Moderate-Occasionally Severe #FF0000 (alpha 0.8) ⬣
By default, all severities of turbulence will be fetched, but you can provide a list of severities to fetch.
SkyPath.shared.dataQuery.sevs = [.moderate, .moderateSevere]
.none
or smooth severity means that the pilot who crossed this hexagon didn't experience any turbulence, so his report is none. Each hexagon can have multiple reports left for it because multiple flights crossed this hexagon. By default, TurbulenceQuery
has aggregate = true
which means you'll get one TurbulenceItem
per hexagon (tile) with the most severe report for this hexagon.