Add live data
This example demonstrates live data visualization on a map using the Mapbox Maps SDK for iOS.
It displays the current location of the International Space Station (ISS) by fetching its coordinates from a specified URL. The ISS is displayed by adding a SymbolLayer
and an iconImage
of a rocket. The addStyleLayer()
function includes a timer which sets up a DispatchQueue
to continuously fetch new coordinates from the url. Once the JSON response is successfully parsed, updateGeoJSONSource
is used to update the ISS location on the map, and the cameraOptions
object is updated to continuously track the space station movement.
This example code is part of the Maps SDK for iOS Examples App, a working iOS project available on Github. iOS developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.
See our Run the Maps SDK for iOS Examples App tutorial for step-by-step instructions.
import MapboxMaps
import UIKit
final class ViewController: UIViewController {
// Display the current location of the International Space Station (ISS)
private let url = URL(string: "https://api.wheretheiss.at/v1/satellites/25544")!
private var mapView: MapView!
private var issTimer: Timer?
private var cancelables = Set<AnyCancelable>()
struct Coordinates: Codable {
let longitude: Double
let latitude: Double
}
override func viewDidLoad() {
super.viewDidLoad()
// Set up map and camera
let centerCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
let camera = CameraOptions(center: centerCoordinate, zoom: 1)
let mapInitOptions = MapInitOptions(cameraOptions: camera, styleURI: .streets)
mapView = MapView(frame: view.bounds, mapInitOptions: mapInitOptions)
mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.addSubview(mapView)
// Add the live data layer once the map has finished loading.
mapView.mapboxMap.onMapLoaded.observeNext { _ in
self.addStyleLayer()
}.store(in: &cancelables)
}
func addStyleLayer() {
// Create an empty geoJSON source to hold location data once
// this information is received from the URL
let source = GeoJSONSource(id: "ISS-source")
var issLayer = SymbolLayer(id: "iss-layer", source: source.id)
// Mapbox Streets contains an image named `rocket`. Use that image
// to represent the location of the ISS.
issLayer.iconImage = .constant(.name("rocket"))
do {
try mapView.mapboxMap.addSource(source)
try mapView.mapboxMap.addLayer(issLayer)
// Create a `Timer` that updates the `GeoJSONSource`.
issTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.parseJSON { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let coordinates):
let locationCoordinates = LocationCoordinate2D(latitude: coordinates.latitude, longitude: coordinates.longitude)
// Update geoJSON source to display new location of ISS
let point = Point(locationCoordinates)
let pointFeature = Feature(geometry: point)
self.mapView.mapboxMap.updateGeoJSONSource(withId: source.id, geoJSON: .feature(pointFeature))
// Update camera to follow ISS
let issCamera = CameraOptions(center: locationCoordinates, zoom: 3)
self.mapView.camera.ease(to: issCamera, duration: 1)
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
}
} catch {
print("Failed to update the style layer. Error: \(error.localizedDescription)")
}
}
// Make a request to the ISS URL, decode the JSON, and return the new coordinates
func parseJSON(completion: @escaping (Result<Coordinates, Error>) -> Void) {
DispatchQueue.global().async { [url] in
let result: Result<Coordinates, Error>
do {
let data = try Data(contentsOf: url)
let coordinates = try JSONDecoder().decode(Coordinates.self, from: data)
result = .success(coordinates)
} catch {
result = .failure(error)
}
DispatchQueue.main.async {
completion(result)
}
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
issTimer?.invalidate()
issTimer = nil
}
}