Extracting GPS coordinates from a photo and plotting it on a map


Today's Little Program extracts GPS coordinates from a photo and plots it on a map. Remember, Little Programs do little to no error checking, because that's how they roll.

#define STRICT
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <shlobj.h>
#include <shellapi.h>
#include <propidl.h>
#include <propkey.h>
#include <propvarutil.h>
#include <atlbase.h>
#include <atlalloc.h>
#include <strsafe.h>

void OpenMap(double dblLatitude, double dblLongitude)
{
 wchar_t szUrl[1024];
 StringCchPrintf(szUrl, ARRAYSIZE(szUrl),
  L"http://www.bing.com/maps/default.aspx?v=2&q=%f,%f",
  dblLatitude, dblLongitude);
 ShellExecute(nullptr, nullptr, szUrl, nullptr, nullptr, SW_NORMAL);
}

We start with a simple function that takes a latitude and longitude and opens a Web page that highlights that coordinate. In a real program, you probably would do something more interesting with the coordinates, but I'm opening a Web page just to do something.

class CPropVariant : public PROPVARIANT {
public:
 CPropVariant() { PropVariantInit(this); }
 ~CPropVariant() { PropVariantClear(this); }
};

The CProp­Variant class is an incredibly lame wrapper around PROP­VARIANT for RAII purposes.

HRESULT GetGPSCoordinateAsDecimal(
    IShellItem2 *psi2,
    REFPROPERTYKEY pkey,
    REFPROPERTYKEY pkeyRef,
    double *pdbl)
{
 CPropVariant spvar;
 HRESULT hr = psi2->GetProperty(pkey, &spvar);
 if (FAILED(hr)) return hr;

 double rgdbl[3];
 ULONG cElt;
 hr = PropVariantToDoubleVector(spvar, rgdbl, 3, &cElt);
 if (FAILED(hr)) return hr;
 if (cElt != 3) return E_INVALIDARG;

 double coord = rgdbl[0] + rgdbl[1] / 60.0 + rgdbl[2] / 60.0 / 60.0;

 CComHeapPtr<wchar_t> spszDir;
 hr = psi2->GetString(pkeyRef, &spszDir);
 if (FAILED(hr)) return hr;

 if (spszDir[0] == L'W' || spszDir[0] == L'S') coord = -coord;

 *pdbl = coord;
 return S_OK;
}

The Get­GPS­Coordinate­As­Decimal function is where the real work happens. GPS latitude and longitude are encoded in the shell property system as a bunch of related properties.

Property Type Meaning
System.GPS.DestLatitudeNumerator UINT[3] numerators for degrees, minutes, and seconds of latitude
System.GPS.DestLatitudeDenominator UINT[3] denominators for degrees, minutes, and seconds of latitude
System.GPS.DestLatitude double[3] degrees, minutes, and seconds of latitude (numerator ÷ denominator)
System.GPS.DestLatitudeRef string "N" or "S"
System.GPS.DestLongitudeNumerator UINT[3] numerators for degrees, minutes, and seconds of longitude
System.GPS.DestLongitudeDenominator UINT[3] denominators for degrees, minutes, and seconds of longitude
System.GPS.DestLongitude double[3] degrees, minutes, and seconds of Longitude (numerator ÷ denominator)
System.GPS.DestLongitudeRef string "E" or "W"

Each of the coordinates is recorded in DMS form as pairs of unsigned integers (numerator and denominator). The direction is recorded as a string as a separate property. Why this wacky format? Probably because that's the way EXIF records it.

For convenience, there is a combo property which does the division for you (but frustratingly, does not flip the sign for direction). And if you want the coordinates in decimal form, then you'll have to do the DMS-to-decimal conversion yourself.

We start by getting the DMS value as a PROP­VARIANT then converting it to an array of doubles. (There had better be three of them.) We then use the power of mathematics to convert from DMS to decimal degrees.

Finally, we flip the sign if the direction from center is West or South.

Now it's time to put these functions together.

int __cdecl wmain(int argc, wchar_t **argv)
{
 if (argc < 2) return 0;
 CCoInitialize init;

 CComPtr<IShellItem2> spsi2;
 if (FAILED(SHCreateItemFromParsingName(argv[1],
              nullptr, IID_PPV_ARGS(&spsi2)))) return 0;

 double dblLong, dblLat;
 if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Longitude,
                    PKEY_GPS_LongitudeRef, &dblLong))) return 0;
 if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Latitude,
                    PKEY_GPS_LatitudeRef, &dblLat))) return 0;

 OpenMap(dblLong, dblLat);

 return 0;
}

Find a photo with GPS information encoded inside it and pass it on the command line as a fully-qualified path. (Because I'm too lazy to call Get­Full­Path­Name.) The program should open a Web page that shows where the picture was taken.

Comments (13)
  1. Danny says:

    "Find a photo with GPS information encoded inside it…" – So next Little Program will get a photo as parameter and tell if the GPS information is encoded inside?

    [Read the code. It already does what you request. My point is that if you pass it a photo without GPS information, then the program isn't very interesting. -Raymond]
  2. Gabe says:

    Danny: Yes; it will replace the line "OpenMap(dblLong, dblLat);" with "return 1;".

  3. Motti says:

    Pet peeve: CPropVariant does not obay the Rule of Three.

    en.wikipedia.org/…/Rule_of_three_(C++_programming)

    [See: Little Program. "Hey, let me write a method that nobody calls." -Raymond]
  4. SimonRev says:

    In some ways it would be nice in C++ if the assignment operator and copy constructor were automatically =delete, with the ability to explicitly request them if you want them.

    For the kind of programming I do, 90% of the time I don't want them.  I just get in the habit of deriving from boost::noncopyable for most anything that I do.

  5. Simon Buchan says:

    @SimonRev: If you provide a move ctor/assignment operator they are, which you probably should be (nothrow if possible). Certainly a mistake for C++98 to autodefine them for classes with dtors, but =deleting them only on that now would break literally all the code :).

  6. voo says:

    @Simon Buchan: I don't know there are many classes I don't want to be movable either – there are many cases where moving/copying instances around just hides some bug or mistake.

    But yeah backcomp obviously – nothing we can do about it.

  7. Crescens2k says:

    @Motti Lanzkron:

    It may be a pet peeve, but remember, sample programs are there to show how something is done, not best practices.

    I'd imagine in real world code the additional constructors/assignment operators would be defined as a matter of course.

  8. Motti says:

    > [See: Little Program. "Hey, let me write a method that nobody calls." -Raymond]

    I get that, I would not have written these methods, only prevent them from existing.

       class CPropVariant : public PROPVARIANT, boost::noncopyable {

    ["Hey, let me create a dependency on an external library that doesn't come with the Platform SDK so it can implement a method that nobody calls. Also, so that people trying to port this to C# will have no clue what just happened." -Raymond]
  9. Joker_vD says:

    @voo: Excuse me, what's the purpose of an uncopyable AND unmovable thingie?

    @Motti: Gosh, just declare this CPropVariant inside GetGPSCoordinateAsDecimal, and voila, you can't give it away to anyone, especially if you change it to

     class CPropVariant {

     public:

       PROPVARIANT data;

       CPropVariant() { PropVariantInit(m); }

       ~CPropVariant() { PropVariantClear(m); }

     };

    to prevent the implicit cast to PROPVARIANT.

  10. Michael says:

    Since implicit declaration of copy ops is deprecated in C++11 if a destructor or one of the copy ops is defined, Raymond's clearly just programming for a 3011 world when the committee finally follows through with its deprecation.

    @Joker_vD: Any type that should only be used with "reference semantics" where you'd want to avoid implicit slicing. Also, any type whose object identity is critical to its correctness or the operations themselves wouldn't follow the contracts of those types. Such as std::atomic, std::mutex (and friends), SRWLOCK, CRITICAL_SECTION, and CONDITION_VARIABLE.

  11. Joker_vD says:

    "Any type that should only be used with "reference semantics" where you'd want to avoid implicit slicing."

    Pass around a shared_ptr to it? Because I've indeed seen uncopyable, unmovable singleton (via static field) thingies, and they were always passed around as &, and there were a couple of methods with surprise "delete this" inside, and… oh goodness. What a joy it was to debug.

  12. Rick C says:

    It's funny, because the linked Rule of Three article on Wikipedia, in it's very first sentence, links to the "rule of thumb" article, which, in *it's* very first sentence, says "A rule of thumb is a principle with broad application that is not intended to be strictly accurate or reliable for every situation."

    Like the pirate code, it's more of a guideline.

  13. Ben says:

    I love these little programs, and this is a particulary fun one. Thanks Raymond.

Comments are closed.

Skip to main content