I am having major issues trying to save a photo to camera roll with geotag info on iOS4.1. I am using following ALAssetsLibrary API:
- (void)writeImageDataTo
Here is code to copy all available information from a CLLocation object into the proper format for a GPS metadata dictionary:
- (NSDictionary *)getGPSDictionaryForLocation:(CLLocation *)location {
NSMutableDictionary *gps = [NSMutableDictionary dictionary];
// GPS tag version
[gps setObject:@"2.2.0.0" forKey:(NSString *)kCGImagePropertyGPSVersion];
// Time and date must be provided as strings, not as an NSDate object
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[gps setObject:[formatter stringFromDate:location.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
[formatter setDateFormat:@"yyyy:MM:dd"];
[gps setObject:[formatter stringFromDate:location.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
[formatter release];
// Latitude
CGFloat latitude = location.coordinate.latitude;
if (latitude < 0) {
latitude = -latitude;
[gps setObject:@"S" forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
} else {
[gps setObject:@"N" forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString *)kCGImagePropertyGPSLatitude];
// Longitude
CGFloat longitude = location.coordinate.longitude;
if (longitude < 0) {
longitude = -longitude;
[gps setObject:@"W" forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
} else {
[gps setObject:@"E" forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString *)kCGImagePropertyGPSLongitude];
// Altitude
CGFloat altitude = location.altitude;
if (!isnan(altitude)){
if (altitude < 0) {
altitude = -altitude;
[gps setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
} else {
[gps setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
}
// Speed, must be converted from m/s to km/h
if (location.speed >= 0){
[gps setObject:@"K" forKey:(NSString *)kCGImagePropertyGPSSpeedRef];
[gps setObject:[NSNumber numberWithFloat:location.speed*3.6] forKey:(NSString *)kCGImagePropertyGPSSpeed];
}
// Heading
if (location.course >= 0){
[gps setObject:@"T" forKey:(NSString *)kCGImagePropertyGPSTrackRef];
[gps setObject:[NSNumber numberWithFloat:location.course] forKey:(NSString *)kCGImagePropertyGPSTrack];
}
return gps;
}
Assign the dictionary returned by this method as the value for the kCGImagePropertyGPSDictionary
key in the metadata dictionary you pass to writeImageDataToSavedPhotosAlbum:metadata:completionBlock:
or CGImageDestinationAddImage()
.
Anomie's response in Swift 4.0:
func getGPSDictionaryForLocation(location:CLLocation) -> [String:AnyObject] {
var gps = [String:AnyObject]()
var latitude = location.coordinate.latitude
if(latitude < 0){
latitude = -latitude
gps[kCGImagePropertyGPSLatitudeRef as String] = "S" as AnyObject
}else{
gps[kCGImagePropertyGPSLatitudeRef as String] = "N" as AnyObject
}
gps[kCGImagePropertyGPSLatitude as String] = latitude as AnyObject
var longitude = location.coordinate.longitude
if(longitude < 0){
longitude = -longitude
gps[kCGImagePropertyGPSLongitudeRef as String] = "W" as AnyObject
}else{
gps[kCGImagePropertyGPSLongitudeRef as String] = "E" as AnyObject
}
gps[kCGImagePropertyGPSLongitude as String] = longitude as AnyObject
gps[kCGImagePropertyGPSAltitude as String] = location.altitude as AnyObject
return gps
}
I used this code and created a NSMutableDictionary to help save geotag and other metadata to an image. Check out my blog post here:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
After much searching I found and adapted this
This turns cclocation data into a suitable NSDictionary
#import <ImageIO/ImageIO.h>
+(NSMutableDictionary *)updateExif:(CLLocation *)currentLocation{
NSMutableDictionary* locDict = [[NSMutableDictionary alloc] init];
CLLocationDegrees exifLatitude = currentLocation.coordinate.latitude;
CLLocationDegrees exifLongitude = currentLocation.coordinate.longitude;
[locDict setObject:currentLocation.timestamp forKey:(NSString*)kCGImagePropertyGPSTimeStamp];
if (exifLatitude <0.0){
exifLatitude = exifLatitude*(-1);
[locDict setObject:@"S" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
}else{
[locDict setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
}
[locDict setObject:[NSNumber numberWithFloat:exifLatitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
if (exifLongitude <0.0){
exifLongitude=exifLongitude*(-1);
[locDict setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}else{
[locDict setObject:@"E" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
}
[locDict setObject:[NSNumber numberWithFloat:exifLongitude] forKey:(NSString*) kCGImagePropertyGPSLongitude];
return [locDict autorelease];
}
Then I add it to the existing metadata that you get through the camera (which doesn't by default have the gps data)
I get the original metadata like this
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
[imageMetaData setDictionary:[[info objectForKey:UIImagePickerControllerMediaMetadata] copy]];
}
then I add the gps dictionary the previous method produces.
[imageMetaData setObject:currentLocation forKey:(NSString*)kCGImagePropertyGPSDictionary];
[library writeImageToSavedPhotosAlbum:[viewImage CGImage] metadata:imageMetaData completionBlock:photoCompblock];
Class to Write GPS Data to Meta-Data.
class GeoTagImage {
/// Writes GPS data into the meta data.
/// - Parameters:
/// - data: Coordinate meta data will be written to the copy of this data.
/// - coordinate: Cooordinates to write to meta data.
static func mark(_ data: Data, with coordinate: Coordinate) -> Data {
var source: CGImageSource? = nil
source = CGImageSourceCreateWithData((data as CFData?)!, nil)
// Get all the metadata in the image
let metadata = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil) as? [AnyHashable: Any]
// Make the metadata dictionary mutable so we can add properties to it
var metadataAsMutable = metadata
var EXIFDictionary = (metadataAsMutable?[(kCGImagePropertyExifDictionary as String)]) as? [AnyHashable: Any]
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]
if !(EXIFDictionary != nil) {
// If the image does not have an EXIF dictionary (not all images do), then create one.
EXIFDictionary = [:]
}
if !(GPSDictionary != nil) {
GPSDictionary = [:]
}
// add coordinates in the GPS Dictionary
GPSDictionary![(kCGImagePropertyGPSLatitude as String)] = coordinate.latitude
GPSDictionary![(kCGImagePropertyGPSLongitude as String)] = coordinate.longitude
EXIFDictionary![(kCGImagePropertyExifUserComment as String)] = "Raw Image"
// Add our modified EXIF data back into the image’s metadata
metadataAsMutable!.updateValue(GPSDictionary!, forKey: kCGImagePropertyGPSDictionary)
metadataAsMutable!.updateValue(EXIFDictionary!, forKey: kCGImagePropertyExifDictionary)
// This is the type of image (e.g., public.jpeg)
let UTI: CFString = CGImageSourceGetType(source!)!
// This will be the data CGImageDestinationRef will write into
let dest_data = NSMutableData()
let destination: CGImageDestination = CGImageDestinationCreateWithData(dest_data as CFMutableData, UTI, 1, nil)!
// Add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source!, 0, (metadataAsMutable as CFDictionary?))
// Tells the destination to write the image data and metadata into our data object.
// It will return false if something goes wrong
_ = CGImageDestinationFinalize(destination)
return (dest_data as Data)
}
/// Prints the Meta Data from the Data.
/// - Parameter data: Meta data will be printed of this object.
static func logMetaData(from data: Data) {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}
}
}
Here's a handy CLLocation category on gist to do all this for you:
https://gist.github.com/phildow/6043486