{"id":2583,"date":"2013-11-25T07:00:00","date_gmt":"2013-11-25T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/11\/25\/extracting-gps-coordinates-from-a-photo-and-plotting-it-on-a-map\/"},"modified":"2013-11-25T07:00:00","modified_gmt":"2013-11-25T07:00:00","slug":"extracting-gps-coordinates-from-a-photo-and-plotting-it-on-a-map","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20131125-00\/?p=2583","title":{"rendered":"Extracting GPS coordinates from a photo and plotting it on a map"},"content":{"rendered":"<p>\nToday&#8217;s Little Program extracts GPS coordinates from a photo and plots\nit on a map.\nRemember, Little Programs do little to no error checking,\nbecause that&#8217;s how they roll.\n<\/p>\n<pre>\n#define STRICT\n#define UNICODE\n#define <a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">_UNICODE<\/a>\n#include &lt;windows.h&gt;\n#include &lt;shlobj.h&gt;\n#include &lt;shellapi.h&gt;\n#include &lt;propidl.h&gt;\n#include &lt;propkey.h&gt;\n#include &lt;propvarutil.h&gt;\n#include &lt;atlbase.h&gt;\n#include &lt;atlalloc.h&gt;\n#include &lt;strsafe.h&gt;\nvoid OpenMap(double dblLatitude, double dblLongitude)\n{\n wchar_t szUrl[1024];\n StringCchPrintf(szUrl, ARRAYSIZE(szUrl),\n  L\"http:\/\/www.bing.com\/maps\/default.aspx?v=2&amp;q=%f,%f\",\n  dblLatitude, dblLongitude);\n ShellExecute(nullptr, nullptr, szUrl, nullptr, nullptr, SW_NORMAL);\n}\n<\/pre>\n<p>\nWe start with a simple function that takes a latitude and longitude\nand opens a Web page that highlights that coordinate.\nIn a real program, you probably would do something more interesting\nwith the coordinates, but I&#8217;m opening a Web page just to do\n<i>something<\/i>.\n<\/p>\n<pre>\nclass CPropVariant : public PROPVARIANT {\npublic:\n CPropVariant() { PropVariantInit(this); }\n ~CPropVariant() { PropVariantClear(this); }\n};\n<\/pre>\n<p>\nThe <code>CProp&shy;Variant<\/code> class\nis an incredibly lame wrapper around\n<code>PROP&shy;VARIANT<\/code> for RAII purposes.\n<\/p>\n<pre>\nHRESULT GetGPSCoordinateAsDecimal(\n    IShellItem2 *psi2,\n    REFPROPERTYKEY pkey,\n    REFPROPERTYKEY pkeyRef,\n    double *pdbl)\n{\n CPropVariant spvar;\n HRESULT hr = psi2-&gt;GetProperty(pkey, &amp;spvar);\n if (FAILED(hr)) return hr;\n double rgdbl[3];\n ULONG cElt;\n hr = PropVariantToDoubleVector(spvar, rgdbl, 3, &amp;cElt);\n if (FAILED(hr)) return hr;\n if (cElt != 3) return E_INVALIDARG;\n double coord = rgdbl[0] + rgdbl[1] \/ 60.0 + rgdbl[2] \/ 60.0 \/ 60.0;\n CComHeapPtr&lt;wchar_t&gt; spszDir;\n hr = psi2-&gt;GetString(pkeyRef, &amp;spszDir);\n if (FAILED(hr)) return hr;\n if (spszDir[0] == L'W' || spszDir[0] == L'S') coord = -coord;\n *pdbl = coord;\n return S_OK;\n}\n<\/pre>\n<p>\nThe\n<code>Get&shy;GPS&shy;Coordinate&shy;As&shy;Decimal<\/code>\nfunction is where the real work happens.\nGPS latitude and longitude are\nencoded in the shell property system as\na bunch of related properties.\n<\/p>\n<table BORDER=\"1\" STYLE=\"border-collapse: collapse\" CELLPADDING=\"3\">\n<tr>\n<th>Property<\/th>\n<th>Type<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<tr>\n<td>System.GPS.DestLatitudeNumerator<\/td>\n<td>UINT[3]<\/td>\n<td>numerators for degrees, minutes, and seconds of latitude<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLatitudeDenominator<\/td>\n<td>UINT[3]<\/td>\n<td>denominators for degrees, minutes, and seconds of latitude<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLatitude<\/td>\n<td>double[3]<\/td>\n<td>degrees, minutes, and seconds of latitude (numerator &divide; denominator)<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLatitudeRef<\/td>\n<td>string<\/td>\n<td>&#8220;N&#8221; or &#8220;S&#8221;<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLongitudeNumerator<\/td>\n<td>UINT[3]<\/td>\n<td>numerators for degrees, minutes, and seconds of longitude<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLongitudeDenominator<\/td>\n<td>UINT[3]<\/td>\n<td>denominators for degrees, minutes, and seconds of longitude<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLongitude<\/td>\n<td>double[3]<\/td>\n<td>degrees, minutes, and seconds of Longitude (numerator &divide; denominator)<\/td>\n<\/tr>\n<tr>\n<td>System.GPS.DestLongitudeRef<\/td>\n<td>string<\/td>\n<td>&#8220;E&#8221; or &#8220;W&#8221;<\/td>\n<\/tr>\n<\/table>\n<p>\nEach of the coordinates is recorded in DMS form as pairs of unsigned\nintegers (numerator and denominator).\nThe direction is recorded as a string as a separate property.\nWhy this wacky format?\nProbably because that&#8217;s the way EXIF records it.\n<\/p>\n<p>\nFor convenience, there is a combo property which does the division for you\n(but frustratingly, does not flip the sign for direction).\nAnd if you want the coordinates in decimal form,\nthen you&#8217;ll have to do the DMS-to-decimal conversion yourself.\n<\/p>\n<p>\nWe start by getting the DMS value as a <code>PROP&shy;VARIANT<\/code>\nthen converting it to an array of <code>double<\/code>s.\n(There had better be three of them.)\nWe then use the power of mathematics to convert from DMS to decimal degrees.\n<\/p>\n<p>\nFinally, we flip the sign if the direction from center is\nWest or South.\n<\/p>\n<p>\nNow it&#8217;s time to put these functions together.\n<\/p>\n<pre>\nint __cdecl wmain(int argc, wchar_t **argv)\n{\n if (argc &lt; 2) return 0;\n CCoInitialize init;\n CComPtr&lt;IShellItem2&gt; spsi2;\n if (FAILED(SHCreateItemFromParsingName(argv[1],\n              nullptr, IID_PPV_ARGS(&amp;spsi2)))) return 0;\n double dblLong, dblLat;\n if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Longitude,\n                    PKEY_GPS_LongitudeRef, &amp;dblLong))) return 0;\n if (FAILED(GetGPSCoordinateAsDecimal(spsi2, PKEY_GPS_Latitude,\n                    PKEY_GPS_LatitudeRef, &amp;dblLat))) return 0;\n OpenMap(dblLong, dblLat);\n return 0;\n}\n<\/pre>\n<p>\nFind a photo with GPS information encoded inside it\nand pass it on the command line as a fully-qualified path.\n(Because I&#8217;m too lazy to call\n<code>Get&shy;Full&shy;Path&shy;Name<\/code>.)\nThe program should open a Web page that shows where the\npicture was taken.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today&#8217;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&#8217;s how they roll. #define STRICT #define UNICODE #define _UNICODE #include &lt;windows.h&gt; #include &lt;shlobj.h&gt; #include &lt;shellapi.h&gt; #include &lt;propidl.h&gt; #include &lt;propkey.h&gt; #include &lt;propvarutil.h&gt; #include &lt;atlbase.h&gt; #include &lt;atlalloc.h&gt; #include &lt;strsafe.h&gt; void OpenMap(double [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-2583","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Today&#8217;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&#8217;s how they roll. #define STRICT #define UNICODE #define _UNICODE #include &lt;windows.h&gt; #include &lt;shlobj.h&gt; #include &lt;shellapi.h&gt; #include &lt;propidl.h&gt; #include &lt;propkey.h&gt; #include &lt;propvarutil.h&gt; #include &lt;atlbase.h&gt; #include &lt;atlalloc.h&gt; #include &lt;strsafe.h&gt; void OpenMap(double [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2583","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=2583"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/2583\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=2583"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=2583"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=2583"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}