I need to get the GPS coordinates of an image taken with the iOS device\'s camera. I do not care about the Camera Roll images, just the image taken with UIImagePickerControl
We have worked a lot with the camera and UIImagePickerController
and, at least up to and including iOS 5.1.1, it does not return location data in the metadata for either photos or videos shot with UIImagePickerController
.
It doesn't matter whether location services is enabled for the Camera app or not; this controls the Camera app's use of location services, not the camera function within UIImagePickerController
.
Your app will need to use the CLLocation
class to get the location and then add it to the image or video returned from the camera. Whether your app can get the location will depend on whether the user authorizes access to location services for your app. And note that the user can disable location services for you app (or entirely for the device) at any time via Settings > Location
Services.
Can't say I've needed to do exactly this in my own stuff, but from the docs it seems pretty clear that if you're using UIImagePickerController
you can get the image that the user just took from the -imagePicker:didFinishPickingMediaWithInfo:
delegate method. Use the key UIImagePickerControllerOriginalImage
to get the image.
Once you've got the image, you should be able to access its properties, including EXIF data, as described in QA1654 Accessing image properties with ImageIO. To create the CGImageSource, I'd look at CGImageSourceCreateWithData()
and use the data that you get from the UIImage's CGImage
method. Once you've got the image source, you can access the various attributes via CGImageSourceCopyProperties()
.
As point out by Chris Markle, Apple does strip out GPS data from EXIF. But you can open the RAW data of the image, and parse the data yourself or use a third party lib to do that, for example.
Here is a sample code:
- (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);
}
];
}
In your UIImagePickerController delegate, do the following:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSDictionary *metadata = [info valueForKey:UIImagePickerControllerMediaMetadata];
// metadata now contains all the image metadata. Extract GPS data from here.
}
Swift answer:
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)")
})
}
}
The problem is that since iOS 4 UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
strips the geolocation out. To solve this problem you have to use the original photo path to get access to the full image metadata. With something like this:
- (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
}];
The output should be something like:
{
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;
};
}