29

I am writing GPS coordinates to my JPEG image, and the coordinates are correct (as demonstrated by my logcat output) but it appears that it's being corrupted somehow. Reading the exif data results in either null values or, in the case of my GPS: 512.976698 degrees, 512.976698 degrees. Can anyone shed some light on this problem?

writing it:

        try {
            ExifInterface exif = new ExifInterface(filename);
            exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
            exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
            exif.saveAttributes();
            Log.e("LATITUDE: ", latitude);
            Log.e("LONGITUDE: ", longitude);


        } catch (IOException e) {
            e.printStackTrace();
        }

and reading it:

        try {
            ExifInterface exif = new ExifInterface("/sdcard/globetrotter/mytags/"+ TAGS[position]);
            Log.e("LATITUDE EXTRACTED", exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
            Log.e("LONGITUDE EXTRACTED", exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE));
        } catch (IOException e) {
            e.printStackTrace();
        }

It goes in (for example) 37.715183, -117.260489 and comes out 33619970/65540, 14811136/3368550, 33619970/65540, 14811136/3368550. Am I doing it wrong?

EDIT:

So, the problem is I am not encoding it in the properly defined format, which is something like you see here:

enter image description here

Can anyone explain what this format is? Obviously the first number is 22/1 = 22 degrees, but I can't figure out how to compute the decimal there.

Brian D
  • 9,863
  • 18
  • 61
  • 96

6 Answers6

26

GPSLatitude

Indicates the latitude. The latitude is expressed as three RATIONAL values giving the degrees, minutes, and seconds, respectively. If latitude is expressed as degrees, minutes and seconds, a typical format would be dd/1,mm/1,ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format would be dd/1,mmmm/100,0/1.

https://docs.google.com/viewer?url=http%3A%2F%2Fwww.exif.org%2FExif2-2.PDF

The Android docs specify this without explanation: http://developer.android.com/reference/android/media/ExifInterface.html#TAG_GPS_LATITUDE

Exif data is standardized, and GPS data must be encoded using geographical coordinates (minutes, seconds, etc) described above instead of a fraction. Unless it's encoded in that format in the exif tag, it won't stick.

How to encode: http://en.wikipedia.org/wiki/Geographic_coordinate_conversion

How to decode: http://android-er.blogspot.com/2010/01/convert-exif-gps-info-to-degree-format.html

Brian D
  • 9,863
  • 18
  • 61
  • 96
  • 3
    It's been a long time since Android decide to support this lat/lon to degree transform. Now you can just call ExifInterface(file).setGpsInfo(location) and saveAttributes() to simply do the job. And be sure to create ExifInterface from File instead of Uri or you'll get IOException("ExifInterface does not support saving attributes for the current input."). https://developer.android.com/reference/android/support/media/ExifInterface.html#setGpsInfo(android.location.Location) – Robert Jul 22 '19 at 02:28
  • 1
    to update on the previous comment, that library is now deprecated, the replacement can be found here: https://developer.android.com/reference/androidx/exifinterface/media/ExifInterface#setGpsInfo(android.location.Location) – ProjectDelta Jun 20 '20 at 11:25
19

Here is some code I've done to geotag my pictures. It's not heavily tested yet, but it seems to be ok (JOSM editor and exiftool read location).

ExifInterface exif = new ExifInterface(filePath.getAbsolutePath());
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, GPS.convert(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, GPS.latitudeRef(latitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, GPS.convert(longitude));
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, GPS.longitudeRef(longitude));
exif.saveAttributes();

And class GPS is here. (method could be shorter, but it's readable at least)

/*
 * @author fabien
 */
public class GPS {
    private static StringBuilder sb = new StringBuilder(20);

    /**
     * returns ref for latitude which is S or N.
     * @param latitude
     * @return S or N
     */
    public static String latitudeRef(double latitude) {
        return latitude<0.0d?"S":"N";
    }

    /**
     * returns ref for latitude which is S or N.
     * @param latitude
     * @return S or N
     */
    public static String longitudeRef(double longitude) {
        return longitude<0.0d?"W":"E";
    }

    /**
     * convert latitude into DMS (degree minute second) format. For instance<br/>
     * -79.948862 becomes<br/>
     *  79/1,56/1,55903/1000<br/>
     * It works for latitude and longitude<br/>
     * @param latitude could be longitude.
     * @return
     */
    synchronized public static final String convert(double latitude) {
        latitude=Math.abs(latitude);
        int degree = (int) latitude;
        latitude *= 60;
        latitude -= (degree * 60.0d);
        int minute = (int) latitude;
        latitude *= 60;
        latitude -= (minute * 60.0d);
        int second = (int) (latitude*1000.0d);

        sb.setLength(0);
        sb.append(degree);
        sb.append("/1,");
        sb.append(minute);
        sb.append("/1,");
        sb.append(second);
        sb.append("/1000");
        return sb.toString();
    }
}
Filip Czaplicki
  • 189
  • 3
  • 12
Fabyen
  • 332
  • 3
  • 3
  • I'm using geo tagging in my application, how did you implement the above solution? I've added the Gps class and the pasted the first block of code where I'm saving the photo but,`latitude` and `longtitude` cannot be resolved to a variable, did you set these up as Geo coordinate variables somewhere in your main class? – Brian Var Nov 30 '14 at 23:37
  • @BrianJ Take a look at LocationManager, LocationListener to begin with. You can get the latitude and longitude via the LocationManager. – basickarl May 10 '15 at 18:16
5

Other answers delivered nice background info and even an example. This is not a direct answer to the question but I would like to add an even simpler example without the need to do any math. The Location class delivers a nice convert function:

public String getLonGeoCoordinates(Location location) {

    if (location == null) return "0/1,0/1,0/1000";
    // You can adapt this to latitude very easily by passing location.getLatitude()
    String[] degMinSec = Location.convert(location.getLongitude(), Location.FORMAT_SECONDS).split(":");
    return degMinSec[0] + "/1," + degMinSec[1] + "/1," + degMinSec[2] + "/1000";
}

I stored the return value in my image and the tag is parsed fine. You can check your image and the geocoordinates inside here: http://regex.info/exif.cgi

Edit

@ratanas comment translated to code:

public boolean storeGeoCoordsToImage(File imagePath, Location location) {

    // Avoid NullPointer
    if (imagePath == null || location == null) return false;

    // If we use Location.convert(), we do not have to worry about absolute values.

    try {
        // c&p and adapted from @Fabyen (sorry for being lazy)
        ExifInterface exif = new ExifInterface(imagePath.getAbsolutePath());
        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, getLatGeoCoordinates(location));
        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, location.getLatitude() < 0 ? "S" : "N");
        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, getLonGeoCoordinates(location));
        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, location.getLongitude() < 0 ? "W" : "E");
        exif.saveAttributes();
    } catch (IOException e) {
        // do something
        return false;
    }

    // Data was likely written. For sure no NullPointer. 
    return true;
}

Here are some nice LatLong converter: latlong.net

aProgger
  • 696
  • 1
  • 8
  • 24
  • 2
    This is indeed helpful, but it should also be noted that to use this for Exif, one must use the absolute value of latitude and longitude, and make sure to also provide the TAG_GPS_*_REF Exif attributes in the form of N,S,W,E according to the sign of the latitude and longitude. – ratana Jan 04 '16 at 01:39
2
ExifInterface exif = new ExifInterface(compressedImage.getPath());
        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE,gpsTracker.dec2DMS(gpsTracker.getLatitude()));
        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE,gpsTracker.dec2DMS(gpsTracker.getLongitude()));

Convertor double to String

 String dec2DMS(double coord) {
    coord = coord > 0 ? coord : -coord;  
    String sOut = Integer.toString((int)coord) + "/1,";   
    coord = (coord % 1) * 60;         
    sOut = sOut + Integer.toString((int)coord) + "/1,";   
    coord = (coord % 1) * 60000;             
    sOut = sOut + Integer.toString((int)coord) + "/1000";   
    return sOut;
}
Ibrahim Sušić
  • 438
  • 3
  • 20
1

The most modern and shortest solution (with AndroidX) is using ExifInterface.setGpsInfo(Location), for example:

ExifInterface exif = new ExifInterface(filename);
Location location = new Location(""); //may be empty
location.setLatitude(latitude); //double value
location.setLongitude(longitude); //double value
exif.setGpsInfo(location)
exif.saveAttributes();

Sources: one and two

antaki93
  • 704
  • 7
  • 10
-1

check android source code: https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r2/core/java/android/hardware/Camera.java

/** * Sets GPS longitude coordinate. This will be stored in JPEG EXIF * header. * * @param longitude GPS longitude coordinate. */ public void setGpsLongitude(double longitude) { set(KEY_GPS_LONGITUDE, Double.toString(longitude)); }

So it's a direct print, my log supports it as well: ExifInterface.TAG_GPS_LONGITUDE : -121.0553966

My conclusion is setting it as direct print is fine.

Helin Wang
  • 4,002
  • 1
  • 30
  • 34
  • If you examine the source code of [ExifInterface](https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r2/media/java/android/media/ExifInterface.java), you'll easily recognize that `getAttribute` and `getLatLong` functions both call `convertRationalLatLonToFloat` which does the conversion from degrees to a floating point number. If you bypass those functions, sure I guess a string is a string... you could store the GPS coordinates to be "my house" if you wanted to. We should really follow the [defined API](http://goo.gl/W76Cfx), though, and save this in degrees instead. – Brian D Apr 14 '14 at 07:37
  • Oh and by the way, if we dig deeper, we begin to understand that Camera.java is just an object that ferries data around for the Camera service and [a driver](http://osxr.org/android/source/device/samsung/crespo/libs3cjpeg/JpegEncoder.cpp#0504). The driver ultimately ultimately saves this value in rational format. ExifInterface magically converts it back to decimal for you when you fetch it, which is what you saw in your logs. – Brian D Apr 14 '14 at 08:07
  • One more thought. You aren't wrong. Setting through the `Camera` class with a direct floating point number will work. However, the original problem was really around setting data through `ExifInterface` and reading it back, only to discover it was all wrong. The answer I provided explains why. – Brian D Apr 14 '14 at 08:17