Сохранение информации Geotag с фотографией на iOS4.1
У меня возникли серьезные проблемы при попытке сохранить фотографию в фотопленку с информацией о геотегах на iOS4.1. Я использую следующий API ALAssetsLibrary:
- (void)writeImageDataToSavedPhotosAlbum:(NSData *)imageData
metadata:(NSDictionary *)metadata
completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock
У меня есть GPS-координаты, которые я хочу сохранить с фотографией в качестве входа. К сожалению, нет документации или примера кода, описывающего, как формировать метаданные NSDictionary, которые инкапсулируют координаты GPS. Может кто-нибудь опубликовать пример кода, который, как известно, работает?
Я также пытался использовать библиотеку iPhone Exif для сохранения географической информации в imageData вместо использования метаданных, но, к сожалению, библиотека iPhone Exif дает сбой. Любая помощь с благодарностью.
7 ответов
Вот код для копирования всей доступной информации из объекта 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()
,
Я использовал этот код и создал NSMutableDictionary, чтобы помочь сохранить геотег и другие метаданные в изображение. Проверьте мой блог здесь:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
Вот удобная категория CLLocation для GIST, чтобы сделать все это для вас:
После долгих поисков я нашел и адаптировал это
Это превращает данные 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];
Ответ Аноми в 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
}
Класс для записи данных 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)
}
}
}
}
- (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);
}