Saving Geotag info with photo on iOS4.1
Asked Answered
I

7

13

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 library is crashing. Any help is greatly appreciated.

Intertype answered 7/10, 2010 at 17:10 Comment(0)
L
24

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().

Linton answered 15/3, 2011 at 16:15 Comment(10)
I think you should use Double instead of Float with latitude and longitude... otherwise you could be out with a few feet/meters.Kellen
@Linton , talking of kCGImagePropertyGPSDateStamp and kCGImagePropertyGPSTimeStamp , is it possible to add our own date/time.,instead of taking it from "location.timestamp".?Alsace
@ShishirShetty: You could fake any bit of the geolocation data, if that's what you want to do.Linton
@Linton Yes, but I tried this , for eg, in kCGImagePropertyGPSDateStamp and kCGImagePropertyGPSTimeStamp I added my own date (a hard coded one), but still when I checked the EXIF details of the image, it showed the date/time of the device ,not that what I entered.Alsace
Silly question coming up here, but shouldn't the framework already be saving this info such that you can get it back via ALAssetPropertyLocation with no adding of location info on your own? (I ask because it appears as if it isn't, necessitating exactly the kind of code you outline, but everything else I read suggests that the info should already be in place after the photo is taken. Weird ...)Unstained
@JoeD'Andrea: This question has more applicability to saving an arbitrary UIImage; sure, imagePickerController:didFinishPickingMediaWithInfo: should include UIImagePickerControllerMediaMetadata and the sample buffer gotten from captureStillImageAsynchronouslyFromConnection:completionHandler: should contain exif data, but whether this was complete (including GPS) in 4.1 I don't recall.Linton
[gps setObject:@"0" forKey:(NSString *) kCGImagePropertyGPSAltitudeRef] doesn't work. it needs to be [gps setObject:[NSNumber numberWithInt:1] forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];Kerwinn
@Linton For time/date stamp, doesn't any other format work except this yyyy:MM:dd? I have tried in TIFF and GPS, MMM d, yyyy and many others like this don't work.Kerwinn
Thanks again for this. I cleaned up your code a bit and posted it as a Gist: gist.github.com/rsattar/b06060df7ea293b398d1Once
I used this to export and re-import a photo from my app - but couldn't get the heading to save/restore correctly using the kCGImagePropertyGPSTrack key. In the end, kCGImagePropertyGPSDestBearing worked for me..Baikal
P
11

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/

Pyre answered 14/5, 2011 at 21:31 Comment(0)
M
2

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];   
Mirza answered 17/11, 2010 at 16:19 Comment(2)
Good answer, +1, but you're setting kCGImagePropertyGPSTimeStamp wrong. The value must be an NSString rather than an NSDate.Linton
You don't need to copy the dictionary that is the object for UIImagePickerControllerMediaMetadata; telling a mutable dictionary to set itself to match another dictionary will modify the receiving dictionary, so that dictionary becomes the copy. The copy you make with the copy message is wasted; worse, since you don't release it, you leak it. You could solve that by releasing or autoreleasing it, but better to not make the unnecessary copy in the first place.Foulness
M
2

Here's a handy CLLocation category on gist to do all this for you:

https://gist.github.com/phildow/6043486

Motile answered 20/7, 2013 at 1:42 Comment(0)
B
0

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
}
Bernal answered 9/10, 2018 at 10:36 Comment(1)
you could explain your answer a little bit moreFebrifugal
V
0

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)
      }
    }
  }
}
Vassallo answered 28/5, 2020 at 5:36 Comment(0)
P
0
- (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);
}
Pothook answered 13/10, 2021 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.