Preserving and Updating Image EXIF data in iOS

By StartxLabs
Date 06-01-18
Preserving and Updating Image EXIF data in iOS
" None"

EXIF metadata helps to gather information about an image like camera shutter speed, date and time when the image was taken, geolocation etc. which helps photographers to create the similar environment when an image was taken or it could help people to utilize that information as per there need.

But, it is usually seen that pictures that are saved in iOS get their EXIF data stripped off if it is not saved properly, so here is a go through on how to preserve EXIF data.

 

In this guide, we’ll do following tasks.

     1. Save Image with EXIF Data including latitude, longitude and altitude etc.

     2. Update Image EXIF Data

     3. Calculate average of latitude and longitude

     4. Google map marker draggable

 

To save camera image in photo library using PHPhotoLibrary and to save EXIF into image metadata get the image media metadata which is provided by the iOS you can view by using UIImagePickerControllerMediaMetadata from info which is provided by the imagepickerviewcontroller didFinishPickingMediaWithInfo and you can append the metadata by creating mutable data of EXIF.

let imageMetadata = info[UIImagePickerControllerMediaMetadata] as? [AnyHashable: Any]
And to save GPS data need to save into kCGImagePropertyGPSDictionary of mutableexifdata
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]
           if GPSDictionary == nil {

               GPSDictionary = [AnyHashable: Any]()

           }

 

Now let’s add GPS Value

The kCGImagePropertyGPSLatitude and kCGImagePropertyGPSLongitud does not store negative value so we have to convert the negative value to positive and fetch correct lat and long, we need to store the reference of latitude and longitude in  kCGImagePropertyGPSLatitudeRef and kCGImagePropertyGPSLongitudeRef and to get correct lat and long we will use the reference of lat and long which is saved above


 

GPSDictionary?[(kCGImagePropertyGPSLatitude as String)] = convertToPositive(latitude)

GPSDictionary?[(kCGImagePropertyGPSLatitudeRef as String)] = self.getLatitudeRefDirection(latitude)

GPSDictionary?[(kCGImagePropertyGPSLongitude as String)] = convertToPositive(longitude)

GPSDictionary?[(kCGImagePropertyGPSLongitudeRef as String)] = self.getLongtitudeRefDirection(longitude)

GPSDictionary?[(kCGImagePropertyGPSAltitude as String)] = altitude

GPSDictionary?[(kCGImagePropertyGPSTimeStamp as String)] = Date().isoTime()//"HH:mm:ss"

GPSDictionary?[(kCGImagePropertyGPSDateStamp as String)] = Date().isoDate()//yyyy:MM:dd

func convertToPositive(_ val : Double)-> Double

       if val > 0{

           return val

       }else{

           return (val * -1)

       }

   }

func getLatitudeRefDirection(_ lat : Double)-> String{

   if lat > 0{

           return "N"

       }else{

           return "S"

       }

   }

func getLongtitudeRefDirection(_ long: Double)-> String{

         if long > 0{

           return "E"

       }else{

           return "W"

       }   

   }

And to save GPS data

 

metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)] = GPSDictionary

It is for orientation in portrait mode

metadataAsMutable?[kCGImagePropertyTIFFOrientation as String] = "1"

And now we are going to write EXIF data into image

let source: CGImageSource = CGImageSourceCreateWithData(UIImageJPEGRepresentation(newImage, 1)! as NSData, nil)!

let UTI: CFString = CGImageSourceGetType(source)!       

           let newImageData = NSMutableData()

           let destination: CGImageDestination = CGImageDestinationCreateWithData((newImageData as CFMutableData), UTI, 1, nil)!

           CGImageDestinationAddImageFromSource(destination, source, 0, metadataAsMutable! as CFDictionary)
           CGImageDestinationFinalize(destination)

Now your EXIF data is written into the image and we have to save image into photo library


           

let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: newImageData as Data, options: nil)

 

And you can check whether metadata is stored in image or not by using this:-

if let imageSource = CGImageSourceCreateWithData(newImageData, nil) {
 let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
    print(imageProperties)
           }

 

And now you can use image data to upload it with EXIF data

 

And when you will pick image from gallery then it contains positive value of latitude and longitude so we need to set its correct value for calculating the average of latitude and longitude, you can achieve this by using this function:-


 

func getLatitudeValue(_ latitudeRef : String,_ latitude : Double)-> Double{
       if latitudeRef == "S"{

               return (latitude * -1)

       }else{

           return latitude
       }

     }

func getLongitudeValue(_ longitudeRef : String,_ longitude : Double)-> Double{
       if longitudeRef == "W"{

           return (longitude * -1)

       }else{

           return longitude
       }
   }

When image is taken from outside it may not contain it’s orientation so we need to write once again to show image in correct orientation

let newImage = UIImage(data: data!)?.resizedImage(withMaxSize: 900)
                               let imageData: NSData = data! as NSData
                               if let imageSource = CGImageSourceCreateWithData(imageData, nil) {

 let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary

         print(imageProperties)

          var metadataAsMutable = imageProperties as? [AnyHashable: Any]

          metadataAsMutable![kCGImagePropertyTIFFOrientation as String] = "1"

                                   
let newImageData = self.data(from: newImage!, metadata: metadataAsMutable as! [AnyHashable : Any], mimetype: "image/jpeg")
}

 

To calculate average of latitude and longitude we will use this function:-


  

func getLatLngAverage()-> CLLocationCoordinate2D!{

       if geoCoordinates.count == 1 {

           return geoCoordinates.first
       }
       var x: Float64 = 0

       var y: Float64 = 0

       var z: Float64 = 0

       for geoCoordinate in geoCoordinates {

           let latitude = (geoCoordinate.latitude * Double.pi) / 180

           let longitude = (geoCoordinate.longitude * Double.pi) / 180

           x = x + cos(latitude) * cos(longitude)

           y = y + cos(latitude) * sin(longitude)

           z = z + sin(latitude) //Math.Sin(latitude)

       }

       let total = geoCoordinates.count

       x = x / Double(total)

       y = y / Double(total)

       z = z / Double(total)

       let centralLongitude = atan2(y, x)

       let centralSquareRoot = sqrt((x*x) + (y*y))

       let centralLatitude = atan2(z, centralSquareRoot)

       return CLLocationCoordinate2D(latitude: (centralLatitude * 180) / Double.pi, longitude:        (centralLongitude * 180) / Double.pi)
    

   }

 

 

Now, you can save the marker of map on average of latitude and longitude

 

Now draggable google map marker:-

 

Set marker.isdraggable = true

 

And use this function for draggable marker


  

 func mapView(_ mapView: GMSMapView, didEndDragging marker: GMSMarker) { 

       print(marker.position)

       self.mapView.clear()

       let camera = GMSCameraPosition.camera (withLatitude: marker.position.latitude,longitude: marker.position.longitude, zoom: 17.0)

       mapView.animate(to: camera)

       self.imageCoordinates = marker.position

       self.marker.position = CLLocationCoordinate2D(latitude: (marker.position.latitude), longitude: (marker.position.longitude))

       self.marker.infoWindowAnchor = CGPoint(x: 0.25, y: 0)

       self.marker.icon =  imageLiteral(resourceName: "marker")

       self.marker.map = self.mapView

       self.mapView.selectedMarker = self.marker

       geocoder.reverseGeocodeCoordinate(marker.position, completionHandler: {(reponse,error) in
         guard error == nil else {

               self.marker.title = "Incident Location"

               self.marker.snippet = "Lat:\(self.imageCoordinates.latitude)\nLong:\(self.imageCoordinates.longitude)"

               return

           }

           guard reponse?.firstResult() != nil else{
               self.marker.title = "Incident Location"

               self.marker.snippet = "Lat:\(self.imageCoordinates.latitude)\nLong:\(self.imageCoordinates.longitude)"
               return
           }
           if let result = reponse?.firstResult() {
               guard result.lines?[0] != nil else{
                   self.marker.title = "Incident Location"

                   self.marker.snippet = "Lat:\(self.imageCoordinates.latitude)\nLong:\(self.imageCoordinates.longitude)"

                   return
               }
               if ((result.lines?.indices.index(of: 1)) != nil){
                   guard result.lines?[1] != nil else{
                       self.marker.title = "Incident Location"

                       self.marker.snippet = "Lat:\(self.imageCoordinates.latitude)\nLong:\(self.imageCoordinates.longitude)"

                       return
                   }

                   self.marker.title = result.lines?[0]

                   self.marker.snippet = result.lines?[1]    

               }else{

                   self.marker.title = "Incident Location"

                   self.marker.snippet = "Lat:\(self.imageCoordinates.latitude)\nLong:\(self.imageCoordinates.longitude)"
                

               }

           }  

       })

   }

So it’s done image will have EXIF data and you can upload it and check the EXIF data and show the average of the latitude and longitude from the image GPS metadata


 

You can grab support source file on Github

Have an app idea? Get a free quote by contacting StartxLabs today!

subscribe to startxlabs

startxlabs