【问题标题】:Saving Geotag info with photo on iOS4.1在 iOS4.1 上保存带有照片的地理标记信息
【发布时间】:2011-04-22 11:51:54
【问题描述】:

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

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

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

我也尝试使用 iPhone Exif 库将地理信息保存在 imageData 中,而不是使用元数据,但不幸的是 iPhone Exif 库正在崩溃。非常感谢任何帮助。

【问题讨论】:

    标签: iphone cocoa-touch ios4 assetslibrary


    【解决方案1】:

    以下代码可将 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;
    }
    

    将此方法返回的字典指定为您传递给writeImageDataToSavedPhotosAlbum:metadata:completionBlock:CGImageDestinationAddImage() 的元数据字典中kCGImagePropertyGPSDictionary 键的值。

    【讨论】:

    • 我认为你应该使用 Double 而不是 Float 的经纬度......否则你可能会离开几英尺/米。
    • @Anomie ,谈到 kCGImagePropertyGPSDateStamp 和 kCGImagePropertyGPSTimeStamp ,是否可以添加我们自己的日期/时间。而不是从“location.timestamp”中获取。?
    • @ShishirShetty:你可以伪造任何一点的地理位置数据,如果你想做的话。
    • [gps setObject:@"0" forKey:(NSString *) kCGImagePropertyGPSAltitudeRef] 不起作用。它必须是[gps setObject:[NSNumber numberWithInt:1] forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
    • 再次感谢您。我稍微清理了您的代码并将其发布为 Gist:gist.github.com/rsattar/b06060df7ea293b398d1
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      经过大量搜索,我找到并改编了这个

      这会将 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];   
      

      【讨论】:

      • 好答案,+1,但您设置 kCGImagePropertyGPSTimeStamp 错误。该值必须是 NSString 而不是 NSDate。
      • 你不需要copy作为UIImagePickerControllerMediaMetadata对象的字典;告诉可变字典将自己设置为匹配另一个字典将修改接收字典,以便字典成为副本。您使用copy 消息制作的副本被浪费了;更糟糕的是,既然你不释放它,你就会泄露它。您可以通过释放或自动释放它来解决这个问题,但最好不要一开始就制作不必要的副本。
      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        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
        }
        

        【讨论】:

        • 你可以多解释一下你的答案
        【解决方案6】:

        将 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)
              }
            }
          }
        }
        

        【讨论】:

          【解决方案7】:
          - (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);
          }
          

          【讨论】:

            猜你喜欢
            • 2011-09-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多