NFurness-esristaff

Runtime Location. Part 3: A Custom Data Source

Blog Post created by NFurness-esristaff Employee on May 21, 2019

This is part 3 of a 4 part series on working with Location in your Runtime applications.

 

In parts 1 and 2 we introduced the AGSLocationDisplay and the AGSLocationDataSource and discussed how they work together to display location on your map view as well how to configure location appearance on the map view, and how the map view behaves as the location is updated.

 

We finished off with an understanding of how AGSLocationDataSources are created. In this post we'll create a new location data source that provides realtime location of the International Space Station, and show it in use in a simple application.

 

ISSLocationDataSource

There exists a very cool, simple, open source API that provides realtime locations of the International Space Station. You can find out about it here, but put simply you make an HTTP request to http://api.open-notify.org/iss-now.json and get JSON back with the current location.

 

Let's use that API to build a custom AGSLocationDataSource that provides the ISS's current location. We'll call it ISSLocationDataSource.

 

Building the data source

Starting with a simple project already linked to the ArcGIS Runtime, let's create a new Swift file named ISSLocationDataSource and define our subclass of AGSLocationDataSource:

 

class ISSLocationDataSource: AGSLocationDataSource {
...
}

 

Now let's implement doStart() and doStop():

 

class ISSLocationDataSource: AGSLocationDataSource {
    override func doStart() {
        startRequestingLocationUpdates()

        // Let Runtime know we're good to go
        didStartOrFailWithError(nil)
    }
   
    override func doStop() {
        stopRetrievingISSLocationsFromAPI()
       
        // Let Runtime know we're done shutting down
        didStop()
    }
}

 

Once we've started, we'll hit the API URL every 5 seconds using an NSTimer, and parse the response into an AGSLocation object:

 

...
private var pollingTimer: Timer?

func startRequestingLocationUpdates() {
    // Get ISS positions every 5 seconds (as recommended on the
    // API documentation pages):
    // http://open-notify.org/Open-Notify-API/ISS-Location-Now/
    pollingTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {
        [weak self] _ in
        // Request the next ISS location from the API and build an AGSLocation.
        self?.requestNextLocation { newISSLocation in
            // TO AGSLocationDisplay: new location available.
            self?.didUpdate(newISSLocation)
        }
    }
}

 

Reading the http://api.open-notify.org/iss-now.json URL and turning the JSON response into an AGSLocation happens in requestNextLocation() function which is called every 5 seconds using an NSTimer. Notice the call to didUpdate() on line 13. As discussed in part 2 of this series, that call will pass the new AGSLocation to the AGSLocationDisplay, which in turn will make sure location is updated on the AGSMapView as needed.

 

You can see a full implementation of the entire ISSLocationDataSource class here, including requestNextLocation() and the JSON Decoding logic.

 

Using our custom location data source

To use the new custom data source in a map view, we simply set the AGSMapView.locationDisplay.dataSource to an instance of our new class (line 8 below) and start the AGSLocationDisplay:

 

override func viewDidLoad() {
    super.viewDidLoad()

    // Set the Map.
    mapView.map = AGSMap(basemap: AGSBasemap.oceans())
   
    // Use our custom ISS Tracking Location Data Source.
    mapView.locationDisplay.dataSource = ISSLocationDataSource()
   
    // Start the AGSMapView's AGSLocationDisplay. This will start the
    // custom data source and begin receiving location updates from it.
    mapView.locationDisplay.start { (error) in
        guard error == nil else {
            print("Error starting up location tracking: \(error!.localizedDescription)")
            return
        }
    }
}

 

It's that easy! Now you have a map that shows the live current location of the ISS.

 

Of course, it's a little counter-intuitive to see the blue dot tracking the space station (we've been trained to associate it with our own location), so we use some of the AGSLocationDisplay configuration options to change the symbol to use an icon of the ISS. Find the entire Xcode project here.

 

 

Additional details about ISSLocationDataSource.requestNextLocation():

  • We use an AGSRequestOperation to get the JSON response from the API (source code).
  • We create a new AGSOperationQueue that processes 1 operation at a time. This way we don't have duplicate simultaneous calls to the API (source code).
  • For the very first location we obtain, since we don't yet have a heading or velocity, we create an AGSLocation with lastKnown = true (source code) which means it will be displayed differently in the map view (by default a grey dot rather than a blue dot, indicating that we're still acquiring a location).
  • We use AGSGeometryEngine to calculate the velocity of the ISS by comparing the new location with the previous location.

 

There's also a slightly more detailed version with an overview map, reference lat/lon grids, and a path-tracking geometry here.

 

In the last post in this series, we'll take a look at working with the AGSCLLocationDataSource class for some advanced strategies to inject custom behavior while leaning on the SDK's own data sources.

Outcomes