Forward Geocoding the Outdoors with Swift
In contrast to the geotagged, Yelp rated, and Google streetviewed locations that cover populated areas, points of interest in the wild are very sparse in terms of accessible API data. Searches for trailheads, rivers, mountain peaks, climbing routes, swim holes all have wildly different results between the big geo-based API providers.
I recently stayed at Fivemile Butte Fire Lookout Tower on Mt Hood, Oregon. The closest town is Dufur, OR some 20 miles away. There is no address, just a forest service access road. During the summer it’s possible to drive to up to the tower but in the winter it’s only accessible by snowshoe, ski or snowmobile. Will the big geo APIs be able to find this place?
Geocoding Overview
“Forward Geocoding” allows one to find the geographic coordinates from a human readable query string. “Reverse Geocoding” allows one to find the name and details of a point of interest using only the geographic coordinates. For this test I focused on the forward geocoding capabilities of Apple, Mapbox and Google to see how well they can find the Fivemile Butte Lookout.
Apple
Apple doesn’t have a great reputation in the geospatial space, so expectations are low. The big advantages Apple provides to developers are
- No external libraries/SDKs are necessary as everything is builtin to Mapkit, and
- There are no throttle limits or extra cost to utilize the capabilities.
There are two core MapKit APIs when searching for POIs: MKLocalSearchRequest
for complete string queries and MKLocalSearchCompleter
for partial string autocomplete functionality in search boxes. We’ll used the former for the test.
Mapkit MKLocalSearch API
The successful response of a MKLocalSearchRequest
execution will be a MKMapItem
object that includes coordinates and descriptions of the matching locality.
import MapKit
let searchReq = MKLocalSearchRequest()
searchReq.naturalLanguageQuery = "Fivemile Butte Lookout"
let search = MKLocalSearch(request: searchReq)
search.start(completionHandler: { (resp, err) in
guard let items = resp?.mapItems else {
print("no items found")
return
}
for item in items {
let p = item.placemark
print("\(item.name!), \(p.subAdministrativeArea!), \(p.administrativeArea!), \(p.country!)")
}
})
Result:
Butte Lookout Rd, Chelan, WA, United States
FAIL. Butte Lookout Rd is definitely not what we’re looking for.
Mapbox
Mapbox has beautiful maps for the outdoors that are fully customizable with topographic shading, contour lines, and named features on the map tiles themselves. Weather Hunt already uses these fantastic maps. Is their forward geocoding API for the outdoors also great? Let’s check it out.
Mapbox ForwardGeocoder API
Note, Mapbox limits their geocode requests to 50,000/mo on their free plan.
import MapboxGeocoder
let geocoder = Geocoder(accessToken: "my-secret-token")
let options = ForwardGeocodeOptions(query: "Fivemile Butte Lookout")
options.allowedISOCountryCodes = ["US"]
_ = geocoder.geocode(options) { (placemarks, attribution, error) in
guard let placemarks = placemarks else {
print("no items found")
return
}
for p in placemarks {
print("\(p.name), \(p.administrativeRegion!), \(p.country!)")
}
}
Results:
Fivemile, Texas, United States
Black Butte Lookout Drive, California, United States
Black Butte Lookout Road, California, United States
Black Butte Lookout Drive, California, United States
Horse Butte Lookout Road, Montana, United States
FAIL. Lots of “Buttes” but no lookout tower.
Google enables developers to utilize their Places APIs with all of their forward geocoding capabilities and place data without requiring use of their Maps. Neat. The forward geocoding capabilities are disguised behind the autocompleteQuery
method of the GMSPlacesClient
class. It’s free to use for the first 150,000 requests/day. Generous. Let’s see how it works.
Google GMSPlacesClient API
import GooglePlaces
GMSPlacesClient.provideAPIKey('my-api-key')
let placesClient = GMSPlacesClient.shared()
let filter = GMSAutocompleteFilter()
filter.type = .establishment
placesClient.autocompleteQuery("Fivemile Butte Lookout", bounds: nil, filter: filter, callback: {(results, error) -> Void in
if let error = error {
print("Autocomplete error \(error)")
return
}
if let results = results {
for result in results {
print("\(result.attributedFullText.string)")
}
}
})
Results:
Fivemile Butte Lookout Tower, Rail Hollow Road, Dufur, OR, USA
Fivemile Butte Lookout, Dufur Valley Rd, Dufur, OR, USA
SUCCESS. We have our match in Dufur, OR. Strange there are two matches for the same place, but that’s quite alright. If I want, I can grab the placeID
from the result and use it to get additional data such as coordinates, photos, and more.
This is just a first step of getting familiar with the geocoding capabilities of Apple, Mapbox, and Google. Perhaps a next step will be to run a more robust test that includes a bigger dataset of lookout towers or trailheads or waterfalls to see how many are recognizable by each service. Also, we just looked at forward geocoding. What about the reverse geocoding APIs from each?