13

我在尝试使用 iOS4.1 上的地理标签信息将照片保存到相机胶卷时遇到重大问题。我正在使用以下 ALAssetsLibrary API:

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

我有 GPS 坐标,我希望将照片保存为输入。不幸的是,没有文档或示例代码描述如何形成封装 GPS 坐标的元数据 NSDictionary。有人可以发布一个已知有效的示例代码吗?

我也尝试过使用 iPhone Exif 库将地理信息保存在 imageData 而不是使用元数据,但不幸的是 iPhone Exif 库正在崩溃。任何帮助是极大的赞赏。

4

7 回答 7

24

下面是将所有可用信息从 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()

于 2011-03-15T16:15:39.560 回答
11

我使用此代码并创建了一个 NSMutableDictionary 来帮助将地理标记和其他元数据保存到图像中。在这里查看我的博客文章:

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

于 2011-05-14T21:31:52.847 回答
2

这是一个方便的 CLLocation 类别,可以为您完成所有这些工作:

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

于 2013-07-20T01:42:27.107 回答
2

经过大量搜索,我发现并适应了这个

这会将 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];   
于 2010-11-17T16:19:25.357 回答
0

Anomie 在 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
}
于 2018-10-09T10:36:32.130 回答
0

将 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)
      }
    }
  }
}
于 2020-05-28T05:36:28.977 回答
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);
}
于 2021-10-13T12:01:19.600 回答