Saving Geotag info with photo on iOS4.1
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)writeImageDataToSavedPhotosAlbum:(NSData *)imageData
metadata:(NSDictionary *)metadata
completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
I have the GPS coordinates that i wish to save with the photo as an input. Unfortunately, there is no documentation or sample code that describes how to form the metadata NSDictionary that encapsulates the GPS coordinates. Can somebody post a sample code that is known to work ?
I have also tried using iPhone Exif library to save geo info in imageData rather than using metadata, but unfortunately iPhone Exif l开发者_如何学编程ibrary is crashing. Any help is greatly appreciated.
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()
.
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];
Here's a handy CLLocation category on gist to do all this for you:
https://gist.github.com/phildow/6043486
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
}
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)
}
}
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *picture = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(picture);
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
CFStringRef UTI = CGImageSourceGetType(sourceRef);
NSMutableData *destinationData = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)destinationData, UTI, 1, NULL);
NSMutableDictionary *metadata = [[[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL)] autorelease];
CGFloat latitude = 54.7;
CGFloat longitude = 25.3;
CGFloat altitude = 100.5;
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject:@(latitude) forKey:(NSString *)kCGImagePropertyGPSLatitude];
[dictionary setObject:@(longitude) forKey:(NSString *)kCGImagePropertyGPSLongitude];
[dictionary setObject:@(altitude) forKey:(NSString *)kCGImagePropertyGPSAltitude];
[metadata setObject:dictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];
CGImageDestinationAddImageFromSource(destination, sourceRef, 0, (__bridge CFDictionaryRef)metadata);
BOOL success = CGImageDestinationFinalize(destination);
if (success)
{
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAsset];
[request addResourceWithType:PHAssetResourceTypePhoto data:imageData options:nil];
} completionHandler:^(BOOL success, NSError *error)
{
if (error)
{
NSLog(@"Error : %@",error);
}
}];
}
CFRelease(destination);
CFRelease(sourceRef);
}
精彩评论