Fun with GPS metadata

Photoshop Script Snippets - Note: Full Scripts go in the Photoshop Scripts Forum

Moderators: Tom, Kukurykus

xbytor

Fun with GPS metadata

Post by xbytor »

I've written some routines for dealing with GPS metadata. Being the paranoid type, I wanted to handle just about anything that I'd find in EXIF or XMP metadata.

I adopted the exiftool mechanism for formatting GPS coordinates. Ultimately, the code relies on String.sprintf from stdlib.js to format the actual string. The headache comes from parsing the original GPS info.

Code: Select all/*
  From the exiftool documentation:
  Set the print format for GPS coordinates. FMT uses the same syntax as the
  printf format string. The specifiers correspond to degrees, minutes and
  seconds in that order, but minutes and seconds are optional. For example,
  the following table gives the output for the same coordinate using various
  formats:

                FMT                  Output
        -------------------    ------------------
        "%d deg %d' %.2f"\"    54 deg 59' 22.80"   (the default)
        "%d deg %.4f min"      54 deg 59.3800 min
        "%.6f degrees"         54.989667 degrees
*/
//
// These are my test case. I can just copy/paste these in to jsh for testing.
//
// Stdlib.strfGPSstr(undefined, "54.00 59.00' 22.80\"");
// Stdlib.strfGPSstr(undefined, "54,59,22");
// Stdlib.strfGPSstr(undefined, "54,59.22");
// Stdlib.strfGPSstr("%d deg %.4f min", "54,59.22");
// Stdlib.strfGPSstr(undefined, "54 59 22");
// Stdlib.strfGPSstr(undefined, "54.00 deg 59.00 min 22.23 secs");
//

Stdlib.DEFAULT_GPS_FORMAT = "%d deg %d' %.2f\"";;



The first function here takes a format spec and a GPS string such as you'd find in EXIF or XMP data. I should probably extract the parsing code so you can convert a GPS string into a single floating point number or a set of 3 numbers for degrees, minutes, and seconds.

Code: Select allStdlib.strfGPSstr = function(fmtStr, gpsStr) {
  // This is the most likely format
  var r = gpsStr.match(/(\d+\.\d+) (\d+\.\d+)\' (\d+\.\d+)\"/);
  if (r) {
    var d = Number(r[1]);
    var m = Number(r[2]);
    var s = Number(r[3]);
    return Stdlib.strfGPS(fmtStr, d, m, s);
  }

  // This is the format from the XMP Schema spec
  var r = gpsStr.match(/(\d+)\,(\d+)(\,|\.)(\d+)/);
  if (r) {
    var d = Number(r[1]);

    var sep = r[3];

    if (sep == '.') {
      var m = Number(r[2]);
      var s = Number("0." + r[4]) * 60;

    } else {
      var m = Number(r[2]);
      var s = Number(r[4]);
    }
    return Stdlib.strfGPS(fmtStr, d, m, s);
  }

  // This format should pick up just about anything else

  var rex = /(\d+(?:\.\d+)?)[^\d\.]+(\d+(?:\.\d+)?)[^\d\.]+(\d+(?:\.\d+)?)/;
  var r = gpsStr.match(rex);
  if (r) {
    var d = Number(r[1]);
    var m = Number(r[2]);
    var s = Number(r[3]);
    return Stdlib.strfGPS(fmtStr, d, m, s);
  }

  // if we can't figure out what's going on, just return the format spec
  return fmtStr;
};


Once we have the GPS info parsed up, we tweak the numbers a bit then call String.sprintf for formatting.

Code: Select allStdlib.strfGPS = function(fmtStr, deg, min, sec) {
  if (sec == undefined) {
    sec = 0;
  }
  if (min == undefined) {
    min = 0;
  }
  debugger;
  min += sec/60;
  deg += min/60;
  if (!fmtStr) {
    fmtStr = Stdlib.DEFAULT_GPS_FORMAT;
  }
  return String.sprintf(fmtStr, deg, min, sec);
};


This will be in the next sourceforge refresh.


-X
xbytor

Fun with GPS metadata

Post by xbytor »

After doing some more testing, I realized I wasn't properly dealing with fractional portions correctly. You can tell from the new test cases that this rev handles a wider variety of inputs.

I had to make two basic assumptions: First, if there is a fractional minute part, seconds will always be 0.00. Second, if there is a fractional degree part, minuets and seconds will always be 0.00.

With these two assumptions, I should probably be able to extend this to handle input strings that look like:
"123.343435 degrees"
"123 degrees 34.3435 mins"
"123,34.3435"

That, however, will have to wait.


Code: Select all//
// Test cases
//
// Stdlib.strfGPSstr(undefined, "54.00 59.00' 22.80\"");
// Stdlib.strfGPSstr(undefined, "28.00 9.97' 0.00\"");
// Stdlib.strfGPSstr("%d deg %.4f min", "28.00 9.97' 0.00\"");
// Stdlib.strfGPSstr("%d deg %.4f min", "28.00 9.50' 0.00\"");
// Stdlib.strfGPSstr(undefined, "28.00 9.50' 0.00\"");
// Stdlib.strfGPSstr("%f", "28.00 9.97' 0.00\"");
// Stdlib.strfGPSstr("%f", "28.50 0.00' 0.00\"");
// Stdlib.strfGPSstr(undefined, "28.50 0.00' 0.00\"");
// Stdlib.strfGPSstr(undefined, "54,59,22");
// Stdlib.strfGPSstr(undefined, "54,59.22");
// Stdlib.strfGPSstr("%d deg %.4f min", "54,59.22");
// Stdlib.strfGPSstr(undefined, "54 59 22");
// Stdlib.strfGPSstr(undefined, "54.00 deg 59.00 min 22.23 secs");
//

Stdlib.DEFAULT_GPS_FORMAT = "%d deg %d' %.2f\"";

Stdlib.strfGPSstr = function(fmtStr, gpsStr) {

  // This is the most likely format
  var r = gpsStr.match(/(\d+\.\d+) (\d+\.\d+)\' (\d+\.\d+)\"/);

  // This is the format from the XMP Schema spec
  if (!r) {
    var r2 = r = gpsStr.match(/(\d+)\,(\d+)(\,|\.)(\d+)/);
  }

  // This format should pick up just about anything else
  if (!r) {
    var rex = /(\d+(?:\.\d+)?)[^\d\.]+(\d+(?:\.\d+)?)[^\d\.]+(\d+(?:\.\d+)?)/;
    var r3 = r = gpsStr.match(rex);
  }

  if (!r) {
    return fmtStr;
  }

  // if we matched either the first or third patterns
  if (!r2) {
    var d = Number(r[1]);
    var m = Number(r[2]);
    var s = Number(r[3]);

    var xm = (d - Math.floor(d)) * 60;
    var xs = (m - Math.floor(m)) * 60;

    m += s/60;
    d += m/60;
    if (s == 0) {
      s = xs;
    }
    if (m == 0) {
      m = xm;
    }

    return Stdlib.strfGPS(fmtStr, d, m, s);
  }

  if (r2) {
    var d = Number(r[1]);

    var sep = r[3];

    if (sep == '.') {
      var m = Number(r[2]);
      var s = Number("0." + r[4]) * 60;

    } else {
      var m = Number(r[2]);
      var s = Number(r[4]);
    }
    return Stdlib.strfGPS(fmtStr, d, m, s);
  }

  // if we can't figure out what's going on, just return the format spec
  return fmtStr;
};

Stdlib.strfGPS = function(fmtStr, deg, min, sec) {
  if (sec == undefined) {
    sec = 0;
  }
  if (min == undefined) {
    min = 0;
  }
  if (min == Math.floor(min)) {
    min += sec/60;
  }
  if (deg == Math.floor(deg)) {
    deg += min/60;
  }
  if (fmtStr == undefined) {
    fmtStr = Stdlib.DEFAULT_GPS_FORMAT;
  }
  return String.sprintf(fmtStr, deg, min, sec);
};



-X