Сохранение информации о геотеге с фотографией на iOS4.1

У меня серьезные проблемы с попыткой сохранить фотографию в фотопленке с информацией о геотеге на iOS4.1. Я использую следующий API ALAssetsLibrary:

- (void)writeImageDataToSavedPhotosAlbum:(NSData *)imageData 
                                metadata:(NSDictionary *)metadata 
                         completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock

У меня есть координаты GPS, которые я хочу сохранить с фотографией в качестве входных данных. К сожалению, нет документации или примера кода, описывающего, как формировать метаданные NSDictionary, инкапсулирующие GPS-координаты. Может ли кто-нибудь опубликовать пример кода, который, как известно, работает?

Я также пытался использовать библиотеку iPhone Exif для сохранения геоинформации в imageData вместо использования метаданных, но, к сожалению, библиотека iPhone Exif дает сбой. Любая помощь приветствуется.


person Deepak Sharma    schedule 07.10.2010    source источник


Ответы (6)


Вот код для копирования всей доступной информации из объекта CLLocation в правильный формат для словаря метаданных GPS:

- (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;
}

Назначьте словарь, возвращенный этим методом, в качестве значения ключа kCGImagePropertyGPSDictionary в словаре метаданных, который вы передаете writeImageDataToSavedPhotosAlbum:metadata:completionBlock: или CGImageDestinationAddImage().

person Anomie    schedule 15.03.2011
comment
Я думаю, что вы должны использовать Double вместо Float с широтой и долготой... иначе вы можете отклониться на несколько футов/метров. - person Marius; 09.06.2011
comment
@Anomie, говоря о kCGImagePropertyGPSDateStamp и kCGImagePropertyGPSTimeStamp, можно ли добавить нашу собственную дату/время вместо того, чтобы брать ее из location.timestamp? - person Shishir Shetty; 09.01.2012
comment
@ShishirShetty: Вы можете подделать любую часть данных геолокации, если хотите этого. - person Anomie; 10.01.2012
comment
@Anomie Да, но я пробовал это, например, в kCGImagePropertyGPSDateStamp и kCGImagePropertyGPSTimeStamp я добавил свою собственную дату (жестко запрограммированную), но все же, когда я проверил детали EXIF ​​​​изображения, он показал дату / время устройства, не то что я ввел. - person Shishir Shetty; 10.01.2012
comment
Здесь возникает глупый вопрос, но разве фреймворк не должен уже сохранять эту информацию, чтобы вы могли получить ее обратно через ALAssetPropertyLocation без самостоятельного добавления информации о местоположении? (Я спрашиваю, потому что кажется, что это не так, что требует точно такого кода, который вы описали, но все остальное, что я прочитал, предполагает, что информация уже должна быть на месте после того, как фотография сделана. Странно. ...) - person Joe D'Andrea; 17.06.2012
comment
@JoeD'Andrea: этот вопрос больше применим к сохранению произвольного UIImage; конечно, imagePickerController:didFinishPickingMediaWithInfo: должен включать UIImagePickerControllerMediaMetadata и образец буфера, полученный из captureStillImageAsynchronouslyFromConnection:completionHandler: должен содержать данные exif, но было ли это полным (включая GPS) в 4.1, я не помню. - person Anomie; 17.06.2012
comment
[gps setObject:@"0" forKey:(NSString *) kCGImagePropertyGPSAltitudeRef] не работает. это должно быть [gps setObject:[NSNumber numberWithInt:1] forKey:(NSString *)kCGImagePropertyGPSAltitudeRef]; - person neeraj; 16.01.2013
comment
@Anomie Для отметки времени / даты не работает ли какой-либо другой формат, кроме этого yyyy:MM:dd? Я пробовал в TIFF и GPS, MMM d, yyyy и многие другие, подобные этому, не работают. - person neeraj; 16.01.2013
comment
Потрясающий. Работает как шарм. Спасибо !! - person Nirav; 06.02.2013
comment
Еще раз спасибо за это. Я немного подчистил ваш код и разместил его как Gist: gist.github.com/rsattar/b06060df7ea293b398d1< /а> - person Rizwan Sattar; 25.07.2014
comment
Я использовал это для экспорта и повторного импорта фотографии из своего приложения, но не смог получить заголовок для правильного сохранения/восстановления с помощью клавиши kCGImagePropertyGPSTrack. В конце концов, kCGImagePropertyGPSDestBearing у меня сработало.. - person Mete; 21.10.2015

Я использовал этот код и создал NSMutableDictionary, чтобы помочь сохранить геотег и другие метаданные в изображение. Посмотрите мой пост в блоге здесь:

http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/

person Gustavo Ambrozio    schedule 14.05.2011

После долгих поисков я нашел и адаптировал это

Это превращает данные cclocation в подходящий 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];

}

Затем я добавляю его к существующим метаданным, которые вы получаете через камеру (которая по умолчанию не имеет данных GPS).

Я получаю исходные метаданные, подобные этому

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{  
    [imageMetaData setDictionary:[[info objectForKey:UIImagePickerControllerMediaMetadata] copy]];
}

затем я добавляю словарь GPS, созданный предыдущим методом.

[imageMetaData setObject:currentLocation forKey:(NSString*)kCGImagePropertyGPSDictionary];          

    [library writeImageToSavedPhotosAlbum:[viewImage CGImage] metadata:imageMetaData completionBlock:photoCompblock];   
person Toby Evetts    schedule 17.11.2010
comment
Хороший ответ, +1, но вы неправильно устанавливаете kCGImagePropertyGPSTimeStamp. Значение должно быть NSString, а не NSDate. - person Anomie; 15.03.2011
comment
Вам не нужно copy словарь, который является объектом для UIImagePickerControllerMediaMetadata; указание изменяемому словарю установить себя в соответствии с другим словарем изменит принимающий словарь, так что этот словарь станет копией. Копия, которую вы делаете с сообщением copy, потрачена впустую; хуже того, поскольку вы не выпускаете его, вы его сливаете. Вы можете решить эту проблему, выпустив или автоматически выпустив его, но лучше вообще не делать ненужную копию. - person Peter Hosey; 01.05.2011

Вот удобная категория CLLocation, которая сделает все это за вас:

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

person Philip    schedule 20.07.2013

Ответ Аноми в 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
}
person Srinija    schedule 09.10.2018
comment
Вы могли бы объяснить свой ответ немного больше - person user1506104; 09.10.2018

Класс для записи данных GPS в метаданные.

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)
      }
    }
  }
}
person Anirudha Mahale    schedule 28.05.2020