Leyendo los datos del GPS de la image devuelta por la camera en el iPhone iOS

Necesito get las coorderadas GPS de una image tomada con la camera del dispositivo iOS. No me importan las imágenes Camera Roll, solo la image tomada con UIImagePickerControllerSourceTypeCamera.

He leído muchas respuestas stackoverflow, como Get Exif data from UIImage – UIImagePickerController , que asume que estás utilizando el framework AssetsLibrary, que no parece funcionar en las imágenes de la camera, o que usa CoreLocaiton para get la latitud / longitud de la aplicación sí mismo, no de la image.

El uso de CoreLocation no es una opción . Eso no me dará las coorderadas cuando se presionó el button del obturador. (Con las soluciones basadas en CoreLocation, o bien necesita grabar las coorderadas antes de que aparezca la vista de la camera o después, y, por supuesto, si el dispositivo se está moviendo, las coorderadas serán incorrectas. Este método debería funcionar con un dispositivo estacionario).

Solo tengo iOS5, así que no necesito admitir dispositivos más antiguos. Esto también es para un producto comercial, así 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 image que devuelve la camera en iOS5? Todo lo que puedo pensar en este momento es save la image en Camera Roll y luego usar AssetsLibrary, pero eso parece hokey.

¡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 console 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]; elimina la geolocation. Para resolver este problema, debe usar la ruta de la foto original para get acceso a los metadatos de la image 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 }]; 

El resultado 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 camera y UIImagePickerController y, al less hasta iOS 5.1.1, no devuelve datos de location en los metadatos ni para fotos ni videos UIImagePickerController con UIImagePickerController .

No importa si los services de location están habilitados para la aplicación de camera o no; esto controla el uso de services de location de la aplicación Camera, no la function de camera dentro de UIImagePickerController .

Su aplicación necesitará usar la class CLLocation para get la location y luego agregarla a la image o al video que se devuelve desde la camera. Si su aplicación puede get la location dependerá de si el usuario autoriza el acceso a services de location para su aplicación. Y tenga en count que el usuario puede deshabilitar los services de location para su aplicación (o totalmente para el dispositivo) en cualquier momento a través de Settings > Location Servicios de Settings > Location .

Una posibilidad es dejar CoreLocation funcionando cuando la camera está visible. Registre cada CCLocation en una matriz junto con el time de la muestra. Cuando la foto regrese, encuentre su time, luego combine la location CC más cercana de la matriz.

Suena maldito pero funcionará.

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

También:

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

El autor establece claramente que las licencias comerciales están disponibles.

No puedo decir que haya necesitado hacer exactamente esto en mis cosas, pero de los documentos parece bastante claro que si usas UIImagePickerController puedes get la image que el usuario acaba de tomar del -imagePicker:didFinishPickingMediaWithInfo: delegate método. Utilice la key UIImagePickerControllerOriginalImage para get la image.

Una vez que haya obtenido la image, debería poder acceder a sus properties, incluidos los datos EXIF, tal como se describe en el documento QA1654 Acceso a las properties de image con ImageIO . Para crear CGImageSource, miro CGImageSourceCreateWithData() y uso los datos que obtienes del método CGImage de UIImage. Una vez que haya obtenido la fuente de la image, puede acceder a los diversos attributes a través de CGImageSourceCopyProperties() .

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

Aquí hay un ejemplo de código:

 - (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 testing en iOS 8 y funciona para videos, por lo que debería funcionar de manera similar para fotos con algunos retoques.

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

Respuesta rápida :

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