Lectura de los datos del GPS de la imagen devuelta por la cámara en el iPhone iOS

Necesito obtener las coordenadas GPS de una imagen tomada con la cámara del dispositivo iOS. No me importan las imágenes de Camera Roll, solo la imagen tomada con UIImagePickerControllerSourceTypeCamera.

He leído muchas respuestas de stackoverflow, como Obtener datos Exif de UIImage – UIImagePickerController , que supone que estás utilizando el marco AssetsLibrary, que no parece funcionar en las imágenes de la cámara, o utiliza CoreLocaiton para obtener la latitud / longitud de la aplicación sí mismo, no de la imagen.

Usar CoreLocation no es una opción . Eso no me dará las coordenadas cuando se presionó el botón del obturador. (Con las soluciones basadas en CoreLocation, o necesita registrar los coords antes de abrir la vista de la cámara o después, y por supuesto si el dispositivo se está moviendo las coordenadas serán incorrectas. Este método debería funcionar con un dispositivo estacionario).

Solo tengo iOS5, por lo que no necesito admitir dispositivos más antiguos. Esto también es para un producto comercial, por lo que no puedo usar http://code.google.com/p/iphone-exif/ .

Entonces, ¿cuáles son mis opciones para leer los datos del GPS de la imagen devuelta por la cámara en iOS5? Todo lo que puedo pensar en este momento es guardar la imagen en Camera Roll y luego usar AssetsLibrary, pero parece que es un truco.

¡Gracias!


Aquí está el código que escribí basado en la respuesta de Caleb.

UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; NSData *jpeg = UIImageJPEGRepresentation(image,1.0); CGImageSourceRef source ; source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL); NSDictionary *metadataNew = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL); NSLog(@"%@",metadataNew); 

y mi consola muestra:

  2012-04-26 14:15:37:137 ferret[2060:1799] { ColorModel = RGB; Depth = 8; Orientation = 6; PixelHeight = 1936; PixelWidth = 2592; "{Exif}" = { ColorSpace = 1; PixelXDimension = 2592; PixelYDimension = 1936; }; "{JFIF}" = { DensityUnit = 0; JFIFVersion = ( 1, 1 ); XDensity = 1; YDensity = 1; }; "{TIFF}" = { Orientation = 6; }; } 

Sin latitud / longitud

El problema es que desde iOS 4 UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; quita la geolocalización. Para resolver este problema, debe usar la ruta de la foto original para tener acceso a los metadatos de la imagen completa. Con algo como esto:

 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *referenceURL = [info objectForKey:UIImagePickerControllerReferenceURL]; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library assetForURL:referenceURL resultBlock:^(ALAsset *asset) { ALAssetRepresentation *rep = [asset defaultRepresentation]; NSDictionary *metadata = rep.metadata; NSLog(@"%@", metadata); CGImageRef iref = [rep fullScreenImage] ; if (iref) { self.imageView.image = [UIImage imageWithCGImage:iref]; } } failureBlock:^(NSError *error) { // error handling }]; 

La salida debería ser algo así como:

 { ColorModel = RGB; DPIHeight = 72; DPIWidth = 72; Depth = 8; Orientation = 6; PixelHeight = 1936; PixelWidth = 2592; "{Exif}" = { ApertureValue = "2.970854"; BrightnessValue = "1.115874"; ColorSpace = 1; ComponentsConfiguration = ( 0, 0, 0, 1 ); DateTimeDigitized = "2012:07:14 21:55:05"; DateTimeOriginal = "2012:07:14 21:55:05"; ExifVersion = ( 2, 2, 1 ); ExposureMode = 0; ExposureProgram = 2; ExposureTime = "0.06666667"; FNumber = "2.8"; Flash = 24; FlashPixVersion = ( 1, 0 ); FocalLength = "3.85"; ISOSpeedRatings = ( 200 ); MeteringMode = 5; PixelXDimension = 2592; PixelYDimension = 1936; SceneCaptureType = 0; SensingMethod = 2; Sharpness = 2; ShutterSpeedValue = "3.9112"; SubjectArea = ( 1295, 967, 699, 696 ); WhiteBalance = 0; }; "{GPS}" = { Altitude = "1167.528"; AltitudeRef = 0; ImgDirection = "278.8303"; ImgDirectionRef = T; Latitude = "15.8235"; LatitudeRef = S; Longitude = "47.99416666666666"; LongitudeRef = W; TimeStamp = "00:55:04.59"; }; "{TIFF}" = { DateTime = "2012:07:14 21:55:05"; Make = Apple; Model = "iPhone 4"; Orientation = 6; ResolutionUnit = 2; Software = "5.1.1"; XResolution = 72; YResolution = 72; "_YCbCrPositioning" = 1; }; } 

Hemos trabajado mucho con la cámara y UIImagePickerController y, al menos hasta iOS 5.1.1, no devuelve datos de ubicación en los metadatos de las fotos o los videos UIImagePickerController con UIImagePickerController .

No importa si los servicios de ubicación están habilitados para la aplicación Cámara o no; esto controla el uso de servicios de ubicación de la aplicación de la cámara, no la función de la cámara dentro de UIImagePickerController .

Su aplicación necesitará usar la clase CLLocation para obtener la ubicación y luego agregarla a la imagen o video devuelto por la cámara. Si su aplicación puede obtener la ubicación dependerá de si el usuario autoriza el acceso a servicios de ubicación para su aplicación. Y tenga en cuenta que el usuario puede desactivar los servicios de ubicación para su aplicación (o completamente para el dispositivo) en cualquier momento a través de Settings > Location Servicios de Settings > Location .

No está utilizando los datos de imagen de la cámara en el código que ha publicado, ha generado una representación JPEG de la misma, lo que esencialmente descartaría todos los metadatos. Usa image.CGImage como sugirió Caleb.

También:

Esto también es para un producto comercial, por lo que no puedo usar http://code.google.com/p/iphone-exif/ .

El autor afirma claramente que hay licencias comerciales disponibles.

Una posibilidad es dejar ejecutándose CoreLocation cuando la cámara esté visible. Registre cada CCLocation en una matriz junto con el tiempo de la muestra. Cuando la foto regrese, encuentre su hora, luego haga coincidir la ubicación más cercana de la matriz.

Suena kludgy pero funcionará.

No puedo decir que haya necesitado hacer exactamente esto en mis propias cosas, pero de los documentos parece bastante claro que si está usando UIImagePickerController puede obtener la imagen que el usuario acaba de tomar de -imagePicker:didFinishPickingMediaWithInfo: delegar método. Use la clave UIImagePickerControllerOriginalImage para obtener la imagen.

Una vez que tenga la imagen, debería poder acceder a sus propiedades, incluidos los datos EXIF, como se describe en QA1654 Acceso a las propiedades de la imagen con ImageIO . Para crear CGImageSource, examinaría CGImageSourceCreateWithData() y usaría los datos que obtiene del método CGImage de UIImage. Una vez que tenga el origen de la imagen, puede acceder a los diversos atributos a través de CGImageSourceCopyProperties() .

Como señala Chris Markle , Apple elimina los datos GPS de EXIF. Pero puede abrir los datos RAW de la imagen y analizar los datos usted mismo o utilizar una lib de terceros para hacer eso, por ejemplo .

Aquí hay un código de muestra:

 - (void) imagePickerController: (UIImagePickerController *) picker didFinishPickingMediaWithInfo: (NSDictionary *) info { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library assetForURL:[info objectForKey:UIImagePickerControllerReferenceURL] resultBlock:^(ALAsset *asset) { ALAssetRepresentation *image_representation = [asset defaultRepresentation]; NSUInteger size = (NSUInteger)image_representation.size; // create a buffer to hold image data uint8_t *buffer = (Byte*)malloc(size); NSUInteger length = [image_representation getBytes:buffer fromOffset: 0.0 length:size error:nil]; if (length != 0) { // buffer -> NSData object; free buffer afterwards NSData *adata = [[NSData alloc] initWithBytesNoCopy:buffer length:size freeWhenDone:YES]; EXFJpeg* jpegScanner = [[EXFJpeg alloc] init]; [jpegScanner scanImageData: adata]; EXFMetaData* exifData = jpegScanner.exifMetaData; id latitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLatitude]]; id longitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLongitude]]; id datetime = [exifData tagValue:[NSNumber numberWithInt:EXIF_DateTime]]; id t = [exifData tagValue:[NSNumber numberWithInt:EXIF_Model]]; self.locationLabel.text = [NSString stringWithFormat:@"Local: %@ - %@",latitudeValue,longitudeValue]; self.dateLavel.text = [NSString stringWithFormat:@"Data: %@", datetime]; } else { NSLog(@"image_representation buffer length == 0"); } } failureBlock:^(NSError *error) { NSLog(@"couldn't get asset: %@", error); } ]; } 

En su delegado UIImagePickerController, haga lo siguiente:

 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSDictionary *metadata = [info valueForKey:UIImagePickerControllerMediaMetadata]; // metadata now contains all the image metadata. Extract GPS data from here. } 

Esto se prueba en iOS 8 y funciona para videos, por lo que debería funcionar de manera similar para las fotos con algunos ajustes.

 - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoUrl = (NSURL *)[info objectForKey:UIImagePickerControllerMediaURL]; NSString *moviePath = [videoUrl path]; if ( UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath) ) { ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; [assetLibrary assetForURL:[info objectForKey:UIImagePickerControllerReferenceURL] resultBlock:^(ALAsset *asset) { CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation]; NSLog(@"Location Meta: %@", location); } failureBlock:^(NSError *error) { NSLog(@"Video Date Error: %@", error); }]; } } 

Swift respuesta:

 import AssetsLibrary import CoreLocation // MARK: - UIImagePickerControllerDelegate extension ViewController: UIImagePickerControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { defer { dismiss(animated: true, completion: nil) } guard picker.sourceType == .photoLibrary else { return } guard let url = info[UIImagePickerControllerReferenceURL] as? URL else { return } let library = ALAssetsLibrary() library.asset(for: url, resultBlock: { (asset) in guard let coordinate = asset?.value(forProperty: ALAssetPropertyLocation) as? CLLocation else { return } print("\(coordinate)") // Getting human-readable address. let geocoder = CLGeocoder() geocoder.reverseGeocodeLocation(coordinate, completionHandler: { (placemarks, error) in guard let placemark = placemarks?.first else { return } print("\(placemark.addressDictionary)") }) }, failureBlock: { (error: Error?) in print("Unable to read metadata: \(error)") }) } }