On MacOS I was able to get the place names from all my 90,000 of photos by querying the Photos.sqlite database.
I’m trying to do something similar in iOS now, but have only found coordinate.latitude
and coordinate.longitude
.
I see there’s a reverseGeocodeLocation
that takes a coordinate and returns a CLPlacemark, but geocoding requests are rate-limited for each app.
If I look in Photos app on the iphone I can see my pics have place name metadata already, is it exposed in an API somewhere?
private func fetchPhotoLocations() {
let fetchOptions = PHFetchOptions()
fetchOptions.includeHiddenAssets = false
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: fetchOptions)
assets.enumerateObjects { (asset, index, stop) in
if let location = asset.location {
let resources = PHAssetResource.assetResources(for: asset)
let filename = resources.first?.originalFilename ?? "Unknown"
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = false
if let resource = resources.first {
PHAssetResourceManager.default().requestData(for: resource, options: options) { (data) in
} completionHandler: { (error) in
if let error = error {
print("Error loading resource: (error)")
}
}
DispatchQueue.main.async {
self.photoLocations.append(PhotoLocation(
id: asset.localIdentifier,
coordinate: location.coordinate,
creationDate: asset.creationDate ?? Date(),
filename: filename
))
}
}
}
}
}
2
Answers
Instead of querying for every single photo, you can batch process locations and avoid duplicates by caching geocoding results.
Use
CLGeocoder
efficiently by avoiding excessive simultaneous requests.Unfortunately, the detailed place name metadata (like those displayed in the Photos app) is not publicly available via the Photos framework.
Here’s the corrected version of your code:
For large photo libraries, you can:
You can greatly optimize your reverse geocoding by taking advantage of some things:
Here’s what I would do: Sort your pictures to be reverse-geocoded by the date they were taken. (That should be in the EXIF metadata. You want to process them in the order they were taken because we tend to take a whole series of shots in one general location.) Take your first picture. Fetch the lat/long from the EXIF data. Calculate kilometers/degree of latitude and kilometers/degree of longitude. Save those values.
Create an empty table of city/state names and latitudes/logitudes.
Reverse geocode that first picture. Save its city/state and lat/long into your table, and remember the index in the table. (If you want to improve your data, fetch and save the lat/long of the centroid of the city/municipality, and its approximate diameter if available. Then when matching new locations, you can see if the new location is roughly inside that diameter.)
Load your next picture. Use your kilometers/degree values to calculate how far that picture’s location is from the current city/state you are working with. If it’s within some margin (say 1/2 kilometer) then just assume it’s in the same city/state, and assign that picture to the city/state without reverse-geocoding.
If a new picture is too far away from the previous picture to assume it’s in the same city/state, do math on all the other saved city/states in your table to find the distance between the new picture and each of those city/states. If you find a city/state close enough, assign that one to the picture. If not, reverse-geocode your new picture’s location and save IT to your city/state table.
Write code that runs the above process in the background, working through your photo library, tracking how many reverse-geocoding requests you make per minute, and simply pause when you hit the rate limit. (Note that on iOS, apps can’t really run in the background. You’d write your code to do its batch processing on a background thread so the UI stays responsive, and you’d need to keep the app frontmost and keep your phone awake in order for it to keep getting processor time. (If this is for personal use, you can cheat and do things like trigger a long-playing sound and set yourself up as a music playing app. Those are allowed to run in the background, and if you don’t submit it to the App Store you can get away with the cheat.)
If you shoot a large batch of pictures within a day’s drive, the above grouping approach should enable you to get city/state data for thousands of pictures with just a handful of reverse-geocoding requests.
*If you travel to wildly different latitudes, like Alaska and Key West, my suggestion of calculating a fixed number of kilometers per degree of latitude and longitude won’t work well because the distance between degree lines changes too much in different latitudes.
If you want to make your calculations more accurate you can look up the "Havershine formula", which lets you calculate "great circle" distances between any 2 points on the globe. It uses 3D trig to calculate distances between points on the surface of a sphere. It’s much more math-intensive than the approximations I suggested, and it’s more work to figure out how to use it, but it will work anywhere on the globe, from the poles to the equator. On a modern computer/phone it’s still quite fast.