Extract GPS data from photo - ios

I have a hard time because I want to extract the GPS coordinates from a photo. I use the function imagePickerController:didFinishPickingMediaWithInfo to pick an image and the I am inserting that image in a collectionView using the new Photos framework.
I want to extract the GPS coordinates from the photo. I have done some research and I am aware of the fact that UIImage does not contain all the metadata, so I tried using the AssetsLibrary framework.
Inside didFinishPickingMediaWithInfo I am using the following code to extract the photo location:
var referenceURL : NSURL = info.objectForKey(UIImagePickerControllerReferenceURL) as NSURL
var library : ALAssetsLibrary = ALAssetsLibrary()
library.assetForURL(referenceURL, resultBlock: { (asset : ALAsset!) -> Void in
var rep : ALAssetRepresentation = asset.defaultRepresentation()
var metadata : NSDictionary = rep.metadata()
let location: AnyObject! = asset.valueForProperty(ALAssetPropertyLocation)
if location != nil {
println(location)
}
else
{
println("Location not found")
}
})
{
(error : NSError!) -> Void in
}
However, it doesn't find the location, even though I checked the image and it contains EXIF metadata (it contains also GPS locations, in which I am interested in). How can I retrieve the coordinates from photo?

ALAssetsLibrary is deprecated in iOS 10. Fortunately, with Photos framework, this is trivial to implement. When imagePickerController(_ picker:, didFinishPickingMediaWithInfo) is called, you can retrieve location information through a simple lookup. Take a look at the code below:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let URL = info[UIImagePickerControllerReferenceURL] as? URL {
let opts = PHFetchOptions()
opts.fetchLimit = 1
let assets = PHAsset.fetchAssets(withALAssetURLs: [URL], options: opts)
let asset = assets[0]
// The location is "asset.location", as a CLLocation
// ... Other stuff like dismiss omitted
}
Hope this helps. This is Swift 3, of course..

I found a solution using the following code:
if picker.sourceType == UIImagePickerControllerSourceType.PhotoLibrary
{
if let currentLat = pickedLat as CLLocationDegrees?
{
self.latitude = pickedLat!
self.longitude = pickedLong!
}
else
{
var library = ALAssetsLibrary()
library.enumerateGroupsWithTypes(ALAssetsGroupAll, usingBlock: { (group, stop) -> Void in
if (group != nil) {
println("Group is not nil")
println(group.valueForProperty(ALAssetsGroupPropertyName))
group.enumerateAssetsUsingBlock { (asset, index, stop) in
if asset != nil
{
if let location: CLLocation = asset.valueForProperty(ALAssetPropertyLocation) as CLLocation!
{ let lat = location.coordinate.latitude
let long = location.coordinate.longitude
self.latitude = lat
self.longitude = lat
println(lat)
println(long)
}
}
}
} else
{
println("The group is empty!")
}
})
{ (error) -> Void in
println("problem loading albums: \(error)")
}
}
}
What it does is that it reads the entire album and prints the location if the photo has that property, else it prints "location not found". It does so for every photo in the album.So I have another question... I want to display the location info just for the photo that I have selected, not for the entire album. Does anyone have a clue how this can be accomplished?

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
if picker.sourceType == UIImagePickerControllerSourceType.PhotoLibrary {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
pickedImage.image = image
let url = info[UIImagePickerControllerReferenceURL] as! NSURL
let library = ALAssetsLibrary()
library.assetForURL(url, resultBlock: { (asset) in
if let location = asset.valueForProperty(ALAssetPropertyLocation) as? CLLocation {
self.latitude = location.coordinate.latitude
self.longitude = location.coordinate.longitude
}
}, failureBlock: { (error: NSError!) in
print("Error!")
})
}
self.dismissViewControllerAnimated(true, completion: nil)
}
Finally managed to get this after trying a lot of different ways, it's remarkably poorly referenced in the apple documentation (or I just couldn't find it). Unfortunately any images that weren't taken through the stock "camera" app don't tend to have location metadata. But it works fine when they do.
Got the answer from https://stackoverflow.com/a/27556241/4337311

With iOS 11 everything becomes much easier to recover GPS coordinate from a picture in your photo roll.
First import the Photo SDK
import Photos
Then before pushing the UIImagePickerController, ask the authorisation to access the data :
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization { [weak self](_) in
// Present the UIImagePickerController
self?.present(picker, animated: true, completion: nil)
}
}
Then when you grab back the image from the UIImagePickerController, you only need to do the following to grab the coordinates :
let coordinate = (info[UIImagePickerControllerPHAsset] as? PHAsset)?.location?.coordinate

iOS 12 & 13
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// For Location
if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
print("============> \(asset.location?.coordinate.longitude ?? 0) \(asset.location?.coordinate.latitude ?? 0)")
}
}

Related

How to detect if an image (a GIF) is animated or still

I need to check if an image is animated or ordinary image. Is there any method in iOS to check this?
I think a file with gif extension can be still image or animated image.
How can I check if it's animated or still image?
For anyone that still comes here looking for an answer in Swift. Just to get if the image is an animated gif, this is the simplest I could come up to.
This is working fine on Swift 4. For Swift 4.2 a couple changes were made to the delegate methods, but nothing major.
THIS ONLY WORKS ON IOS 11+, if you need support for 9/10 see below.
func isAnimatedImage(_ imageUrl: URL) -> Bool {
if let data = try? Data(contentsOf: imageUrl),
let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
let count = CGImageSourceGetCount(source)
return count > 1
}
return false
}
and in my case I'm getting the image from a UIImagePickerController
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
if let imageURL = info[UIImagePickerControllerImageURL] as? URL {
let isImageAnimated = isAnimatedImage(imageURL)
print("isAnimated: \(isImageAnimated)")
}
picker.dismiss(animated: true, completion: nil)
}
IOS 9 and 10
For iOS 9 and 10 theres no way to get the UIImagePickerControllerImageURL since it's a new iOS 11 thing, so we need to use the Photo library to fetch the data from.
Import Photos Library at the top
import Photos
Add the correct code to fetch data
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
if let imageUrl = info[UIImagePickerControllerReferenceURL] as? URL {
let asset = PHAsset.fetchAssets(withALAssetURLs: [imageUrl], options: nil)
if let image = asset.firstObject {
PHImageManager.default().requestImageData(for: image, options: nil) { (imageData, _, _, _) in
let isImageAnimated = isAnimatedImage(imageData)
print("isAnimated: \(isImageAnimated)")
}
}
}
picker.dismiss(animated: true, completion: nil)
}
func isAnimatedImage(_ imageData: Data) -> Bool {
if let source = CGImageSourceCreateWithData(imageData as CFData, nil) {
let count = CGImageSourceGetCount(source)
return count > 1
}
return false
}
the following code snippets maybe help you.
Basically, it uses CGImageSourceGetCount to get the count of images for gif files. Then, it depends on the count to do a image action or the animation action.
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
....
}
}
Those codes from [轉]用UIImage承載GIF圖片
It is really possible but you may create your own method to do that by extending the NSData class.
here is the detail of getting an image extension: Detail

Get Image location from UIImagePickerController

I have tried multiple ways to get the location of an image which took through the UIImagePickerController Camera.
What I want to achieve is that, I want to select an image using UIImagePickerController Camera and I have to save it into Photo Library so that only I can take back the PHAsset from it and also the location associated with it.
//MARK: Saving an Image to PhotoLibrary and taking back the PHAsset
class func savingThis(image : UIImage, completion : (asset : PHAsset?) -> ())
{
var localIdentifier : String?
let imageManager = PHPhotoLibrary.sharedPhotoLibrary()
imageManager.performChanges({ () -> Void in
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
if let properAsset = request.placeholderForCreatedAsset {
localIdentifier = properAsset.localIdentifier
}
else {
completion(asset: nil)
}
}, completionHandler: { (success, error) -> Void in
if let properLocalIdentifier = localIdentifier {
let result = PHAsset.fetchAssetsWithLocalIdentifiers([properLocalIdentifier], options: nil)
if result.count > 0 {
completion(asset: result[0] as? PHAsset)
}
else {
completion(asset: nil)
}
}
else {
completion(asset: nil)
}
})
}
I have tried this code, to save and get back the PHAsset. But the problem is that this PHAsset does not have any location associated with it, wondering why? And what I missed?
I believe that I don't have to manually set GPS data into image's metadata right? I think that Photos Framework or Asset Library takes care of it. So as you know that Asset Library is deprecated our only option is to use Photos Framework. I read online that, Saving image to Photo Library takes care of it. Isn't it correct?
Is there any alternative? Should I use UIImageWriteToSavedPhotosAlbum method to save image to Camera Roll, And I can take back the very recent photo using Photos Framework. But I don't think that UIImageWriteToSavedPhotosAlbum will take care of the location thing.
Do you have any thoughts?
First of all thanking all who were all kind to take a look into the question.
I found my answer.
//MARK: Saving an Image to PhotoLibrary and taking back the PHAsset
class func savingThis(image : UIImage, completion : (asset : PHAsset?) -> ())
{
var localIdentifier : String?
let imageManager = PHPhotoLibrary.sharedPhotoLibrary()
imageManager.performChanges({ () -> Void in
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
request.location = // Assigned current location here :)
if let properAsset = request.placeholderForCreatedAsset {
localIdentifier = properAsset.localIdentifier
}
else {
completion(asset: nil)
}
}, completionHandler: { (success, error) -> Void in
if let properLocalIdentifier = localIdentifier {
let result = PHAsset.fetchAssetsWithLocalIdentifiers([properLocalIdentifier], options: nil)
if result.count > 0 {
completion(asset: let asset = result[0] as? PHAsset)
}
else {
completion(asset: nil)
}
}
else {
completion(asset: nil)
}
})
}
When you get a callback from UIImagePickerController:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo
info: [UIImagePickerController.InfoKey : Any]) {}
you can try to get the image location from the info dictionary.
For that, you need to request the related PHAsset object:
if let assetImage = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset {
print("Image location info = \( assetImage.location))")
}
Important - before using this approach you need to request the user permission:
PHPhotoLibrary.requestAuthorization()
If you do not do it the info dictionary for UIImagePickerController.InfoKey.phAsset will return nil.

How I could access to the Url of photo saved in the camera roll

I would like to save the URL of the photos have been taken from the camera or exinting in the camera roll and have been selected.
Your question is extremely vague with nothing for us to work with. But in any case, I just created an app requiring that logic so I would just share with you
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
// Clear data if picture is repicked
imageLocalURL = nil
imageData = nil
imageSelected = info[UIImagePickerControllerOriginalImage] as? UIImage
if info[UIImagePickerControllerReferenceURL] != nil {
// This would mean that image is chosen from photo library
referenceURL = info[UIImagePickerControllerReferenceURL] as? NSURL
let assets = PHAsset.fetchAssetsWithALAssetURLs([referenceURL! as NSURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInputWithOptions(nil, completionHandler: { (contentEditingInput, info) in
// This would be the URL of your image in photo library
self.imageLocalURL = contentEditingInput?.fullSizeImageURL
} else {
// This means that image is taken from camera
imageData = UIImageJPEGRepresentation(imageSelected!, 1.0)
}
dismissViewControllerAnimated(true, completion: nil)
}

How to get a location data from photos from iPhones?

I am having a trouble with extracting a location data from photos from iPhones by using PHPhotoLibrary. Can anyone teach me how?
Is it similar to how to do the same thing by using ALAssetsLibrary? Please.
You can use PHAsset location property to retrieve location data of image .
see PHAsset
Here is code:
let CameraImages = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: nil)
CameraImages.enumerateObjectsUsingBlock({
(obj, idx, bool) -> Void in
var asset = obj as! PHAsset
let locationData = asset.location
})
EDIT:
#Ryo comment: retrieve location data from the image that i choose from imagePickerController?
You can do as
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])
{
self.dismissViewControllerAnimated(false, completion: nil)
var arrayURL:[NSURL] = []
var url = info[UIImagePickerControllerReferenceURL] as! NSURL
arrayURL.append(url)
let ImageAsset = PHAsset.fetchAssetsWithALAssetURLs(url1, options: nil)
ImageAsset.enumerateObjectsUsingBlock({
(obj, idx, bool) -> Void in
var asset = obj as! PHAsset
let locationData = asset.location
print(locationData)
})
}
edit 2 :
# Ryo comment : how can i get a longitude and altitude from this location data?
just add this line
if locationData != nil {
let longi = locationData?.coordinate.longitude
let lati = locationData?.coordinate.latitude
}

Save video on the gallery and store the path to the video

I have an app in which the user can record a video, this video is saved in the photo gallery, and I store the path to the video so that in the future the user could see again the video inside the app. The problem is that the method that I use I think it's giving me some kind of temporary path, and after some days, the video still in the gallery, but the path is not valid anymore and make the app crash. This is the code I'm using:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let mediaType: String = info["UIImagePickerControllerMediaType"] as! String
if mediaType == "public.movie" {
let tempImageURL = info[UIImagePickerControllerMediaURL] as! NSURL!
let pathString = tempImageURL.relativeString
self.dismissViewControllerAnimated(true, completion: {})
if picker.sourceType == UIImagePickerControllerSourceType.PhotoLibrary {
self.videoPath = pathString
// Save the path in the DB
} else {
VideoManager.saveVideo(tempImageURL, onComplete: { (path) -> Void in
self.videoPath = path
// Save the path in the DB
})
var fileManager: NSFileManager = NSFileManager()
fileManager.removeItemAtPath(pathString!, error: nil)
}
}
}
And the VideoManager.saveVideo method code is the following:
func saveVideo(videoURL: NSURL, onComplete:((path: String) -> Void)) {
var assetsLibrary: ALAssetsLibrary = ALAssetsLibrary()
assetsLibrary.writeVideoAtPathToSavedPhotosAlbum(videoURL, completionBlock: { (assetURL: NSURL!, error: NSError!) -> Void in
var path: String = error == nil ? "\(assetURL)" : kEmptyString
onComplete(path: path)
})
}
I don't know what I'm doing wrong, I've tried with the method UISaveVideoAtPathToSavedPhotosAlbum but without success.. Any ideas?
For giving a little more information, when the video is selected from the gallery, the url I get is like the following one:
file:///private/var/mobile/Containers/Data/Application/D2E8E31B-CEA0-43B0-8EF9-1820F6BDE4A9/tmp/trim.AD855155-AB78-4A16-9AA8-DF2B3F39824E.MOV
And when I record a new video using the camera, first I have this URL:
file:///private/var/mobile/Containers/Data/Application/D2E8E31B-CEA0-43B0-8EF9-1820F6BDE4A9/tmp/capture/capturedvideo.MOV
and when I do writeVideoAtPathToSavedPhotosAlbum it returns an URL like:
assets-library://asset/asset.MOV?id=958507B5-1353-4DDC-BC07-D9CBC6126657&ext=MOV
Both of them work, but some days later stop working.
OK finally I found the solution, the thing was that you can't access directly the photo gallery with the stored url, you got to use the assetLibrary.assetForURL method, which I was missing. In the end the imagepickercontroller delegate method is like this:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let mediaType: String = info["UIImagePickerControllerMediaType"] as! String
if mediaType == "public.movie" {
self.dismissViewControllerAnimated(true, completion: {})
if picker.sourceType == UIImagePickerControllerSourceType.PhotoLibrary {
let tempImageURL = info[UIImagePickerControllerReferenceURL] as! NSURL!
self.videoPath = tempImageURL.absoluteString
} else {
let tempImageURL = info[UIImagePickerControllerMediaURL] as! NSURL!
VideoManager.saveVideo(tempImageURL, onComplete: { (path) -> Void in
self.videoPath = path
})
}
}
}
I also was missing that when you record a video, you got to obtain the url using:
let tempImageURL = info[UIImagePickerControllerMediaURL] as! NSURL!
But when you get the video you have to do:
let tempImageURL = info[UIImagePickerControllerReferenceURL] as! NSURL!
Hope it helps!!

Resources