I am using AvFoundation to take still image and adding gps info to metadata and saving to a photo album using Asset library but gps info is not saving at all.
here
There are several answers above and elsewhere on SO that give most of what you want. Here is the working code I generated from all of these sources, and it seems to work just fine.
This is all done within the captureStillImageAsynchronouslyFromConnection block. My thanks to all the various contributors:
[stillCapture
captureStillImageAsynchronouslyFromConnection: stillConnection
completionHandler:
^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (error) {
NSLog(@"snap capture error %@", [error localizedDescription]);
return;
}
NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutableAttachments = CFDictionaryCreateMutableCopy(NULL, 0, attachments);
// Create GPS Dictionary
NSMutableDictionary *gps = [NSMutableDictionary dictionary];
CLLocation *location = <your location source here>;
// 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];
// Latitude
[gps setObject: (location.coordinate.latitude < 0) ? @"S" : @"N"
forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
[gps setObject:[NSNumber numberWithDouble:fabs(location.coordinate.latitude)]
forKey:(NSString *)kCGImagePropertyGPSLatitude];
// Longitude
[gps setObject: (location.coordinate.longitude < 0) ? @"W" : @"E"
forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
[gps setObject:[NSNumber numberWithDouble:fabs(location.coordinate.longitude)]
forKey:(NSString *)kCGImagePropertyGPSLongitude];
// Altitude
if (!isnan(location.altitude)){
// NB: many get this wrong, it is an int, not a string:
[gps setObject:[NSNumber numberWithInt: location.altitude >= 0 ? 0 : 1]
forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
[gps setObject:[NSNumber numberWithDouble:fabs(location.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 numberWithDouble:location.speed*3.6] forKey:(NSString *)kCGImagePropertyGPSSpeed];
}
// Heading
if (location.course >= 0){
[gps setObject:@"T" forKey:(NSString *)kCGImagePropertyGPSTrackRef];
[gps setObject:[NSNumber numberWithDouble:location.course] forKey:(NSString *)kCGImagePropertyGPSTrack];
}
CFDictionarySetValue(mutableAttachments, kCGImagePropertyGPSDictionary, CFBridgingRetain(gps));
// NSDictionary *ma = (__bridge NSDictionary *)(mutableAttachments);
// NSLog(@"photo attachments: %@", ma);
[ROOTVC.library
writeImageDataToSavedPhotosAlbum:jpegData
metadata:(__bridge id)mutableAttachments
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"XXX save to assets failed: %@", [error localizedDescription]);
} else {
[self processAsset:assetURL inGroup:ROOTVC.venue forMessage:savingMessage];
}
}];
if (mutableAttachments)
CFRelease(mutableAttachments);
if (attachments)
CFRelease(attachments);
}];
As always, I worry if I am getting the releases right. Here's a jhead(1) of the result:
File name : 20131001_082119/photo.jpeg
File size : 82876 bytes
File date : 2013:10:01 08:21:19
Resolution : 480 x 360
Orientation : rotate 90
Flash used : No
Focal length : 4.1mm (35mm equivalent: 30mm)
Exposure time: 0.0083 s (1/120)
Aperture : f/2.2
ISO equiv. : 6400
Whitebalance : Auto
Metering Mode: pattern
Exposure : program (auto)
GPS Latitude : N 40d 43m 22.32s
GPS Longitude: W 74d 34m 39.15s
GPS Altitude : 117.92m
I had exactly the same problem. I think the documentation on this topic isn't great, so I solved it in the end by looking at the metadata of a photo taken by the Camera app and trying to replicate it.
Here's a run down of the properties the Camera app saves:
If you stick to these you should be fine. Here's a sample:
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
NSDictionary *gpsDict = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:self.currentLocation.coordinate.latitude], kCGImagePropertyGPSLatitude,
@"N", kCGImagePropertyGPSLatitudeRef,
[NSNumber numberWithFloat:self.currentLocation.coordinate.longitude], kCGImagePropertyGPSLongitude,
@"E", kCGImagePropertyGPSLongitudeRef,
@"04:30:51.71", kCGImagePropertyGPSTimeStamp,
nil];
CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, gpsDict);
// Get the image
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
// Get the assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:[image CGImage] metadata:mutable completionBlock:captureComplete];
This is all in the completionHandler of the captureStillImageAsynchronouslyFromConnection method of your AVCaptureConnection object, and self.currentLocation is just a CLLocation. I hardcoded the timestamp and Lat/Lng Refs for the example to keep things simple.
Hope this helps!
Mason's answer really helped me. You'll need some modifications such as setting the absolute value of longitude & latitude. Here's a code snippet of using CoreLocation + Image I/O to write an UIImage to disk with GPS information:
- (BOOL)writeCGImage:(CGImageRef)theImage toURL:(NSURL*)url withType:(CFStringRef)imageType andOptions:(CFDictionaryRef)options {
CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef)url, imageType, 1, nil);
CGImageDestinationAddImage(myImageDest, theImage, options);
BOOL success = CGImageDestinationFinalize(myImageDest);
// Memory Mangement
CFRelease(myImageDest);
if (options)
CFRelease(options);
return success;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if (newLocation) {
[manager stopUpdatingLocation];
// Create formatted date
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:@"HH:mm:ss.SS"];
// Create GPS Dictionary
NSDictionary *gpsDict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:fabs(newLocation.coordinate.latitude)], kCGImagePropertyGPSLatitude
, ((newLocation.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef
, [NSNumber numberWithFloat:fabs(newLocation.coordinate.longitude)], kCGImagePropertyGPSLongitude
, ((newLocation.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef
, [formatter stringFromDate:[newLocation timestamp]], kCGImagePropertyGPSTimeStamp
, nil];
// Memory Management
[formatter release];
// Set GPS Dictionary to be part of media Metadata
// NOTE: mediaInfo in this sample is dictionary object returned in UIImagePickerController delegate:
// imagePickerController:didFinishPickingMediaWithInfo
if (mediaInfo && [mediaInfo objectForKey:UIImagePickerControllerMediaMetadata] && gpsDict) {
[[mediaInfo objectForKey:UIImagePickerControllerMediaMetadata] setValue:gpsDict forKey:@"{GPS}"];
}
// Save Image
if([self writeCGImage:[image CGImage] toURL:imageSaveURL withType:kUTTypeJPEG andOptions:(CFDictionaryRef)[mediaInfo objectForKey:UIImagePickerControllerMediaMetadata]]) {
// Image is written to device
}
}
}