<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
Hi,<br>
<br>
Apologies in advance for a very long post.<br>
<br>
A few days ago I decided that I needed to be able to see the
location of my geotagged images from within gwenview. I've got
thousands of geotagged images and I randomly display them as my
wallpaper. Half the time I can't remember where the pictures were
taken so I was "Opening Wallpaper Image" (Kubuntu 18.04) with
gwenview, starting geotag, finding the file based on the truncated
name showing in the title bar of gwenview, then, from within geotag,
opening the browser with the location tagged. Well, that got real
tedious real fast so I decided to write my own little application
that I could start from gwenview and then maybe later, look into
incorporating it into gwenview. The application works great and
allows me to open Google Earth (not in the browser) with the image
location tagged with a "pushpin". Just for grins I also added a
fallback to tagging the location in Google maps in the browser if
you don't happen to have Google Earth installed.<br>
<br>
I looked into trying to incorporate the code into gwenview (it's
C++ and Qt5) but building gwenview was an absolute nightmare. Way
too many dependencies. At any rate, the code works as is and, if
you add it to your "K" menu (using kmenuedit), you can start it from
the File->Open With... in gwenview. So, I'm still using my
little command line app out of gwenview for the present (because it
works ;-) I threw the thing together in a couple of hours (I had
already done something similar in my LiDAR processing software so it
wasn't that difficult). It's not elegant but I thought you guys
might be interested. I don't think that attaching the code as a
file is the best idea (because some people don't like attachments)
so here it is in it's entirety (dependencies are Qt5 and
libexif-dev):<br>
<br>
<pre>#include <stdio.h></pre>
<pre>#include <stdlib.h></pre>
<pre>#include <errno.h></pre>
<pre>#include <math.h></pre>
<pre>#include <string.h></pre>
<pre>#include <getopt.h></pre>
<pre>#include <sys/types.h></pre>
<pre>#include <unistd.h></pre>
<pre>
#include <libexif/exif-data.h></pre>
<pre>
#include <QtCore></pre>
<pre>#include <QtWidgets></pre>
<pre>#include <QStandardPaths></pre>
<pre>
</pre>
<pre>void usage (char *string)</pre>
<pre>{</pre>
<pre> fprintf (stderr, "Program: %s\n", string);</pre>
<pre> fprintf (stderr, "Purpose: Reads EXIF data from image file and\n");</pre>
<pre> fprintf (stderr, "opens GoogleEarth (or browser in Google Maps)\n");</pre>
<pre> fprintf (stderr, "with pushpin at picture location.\n");</pre>
<pre> fprintf (stderr, "Usage: %s IMAGE_FILE_NAME\n\n", string);</pre>
<pre> fprintf (stderr, "Caveats:\n");</pre>
<pre> fprintf (stderr, "\tThe image file must contain GPS position data.\n\n");</pre>
<pre> exit (-1);</pre>
<pre>}</pre>
<pre>
</pre>
<pre>
int32_t main (int32_t argc, char **argv)</pre>
<pre>{</pre>
<pre> char img_name[1024], lat[64], latref[2], lon[64], lonref[2], c;</pre>
<pre> extern int optind;</pre>
<pre> QProcess *googleEarthProc = NULL;</pre>
<pre>
</pre>
<pre> QApplication a (argc, argv);</pre>
<pre>
</pre>
<pre> while ((c = getopt (argc, argv, "w")) != EOF)</pre>
<pre> {</pre>
<pre> switch (c)</pre>
<pre> {</pre>
<pre> case 'w':</pre>
<pre> // Placeholder in case you want to add a command line option</pre>
<pre> usage (argv[0]);</pre>
<pre> break;</pre>
<pre>
default:</pre>
<pre> usage (argv[0]);</pre>
<pre> break;</pre>
<pre> }</pre>
<pre> }</pre>
<pre>
</pre>
<pre> // Make sure we got the mandatory filename argument.</pre>
<pre>
if (optind >= argc) usage (argv[0]);</pre>
<pre>
</pre>
<pre> strcpy (img_name, argv[optind]);</pre>
<pre>
</pre>
<pre> ExifData *exif = exif_data_new_from_file (img_name);</pre>
<pre>
</pre>
<pre> ExifEntry *entry_lat = exif_data_get_entry (exif, (ExifTag) EXIF_TAG_GPS_LATITUDE);</pre>
<pre>
if (exif_entry_get_value (entry_lat, lat, sizeof (lat)))</pre>
<pre> {</pre>
<pre> if (QString (lat).isEmpty ()) return (-1);</pre>
<pre> }</pre>
<pre>
ExifEntry *entry_latref = exif_data_get_entry (exif, (ExifTag) EXIF_TAG_GPS_LATITUDE_REF);</pre>
<pre>
if (exif_entry_get_value (entry_latref, latref, sizeof (latref)))</pre>
<pre> {</pre>
<pre> if (QString (latref).isEmpty ()) return (-1);</pre>
<pre> }</pre>
<pre>
ExifEntry *entry_lon = exif_data_get_entry (exif, (ExifTag) EXIF_TAG_GPS_LONGITUDE);</pre>
<pre> </pre>
<pre> if (exif_entry_get_value (entry_lon, lon, sizeof (lon)))</pre>
<pre> {</pre>
<pre> if (QString (lon).isEmpty ()) return (-1);</pre>
<pre> }</pre>
<pre>
ExifEntry *entry_lonref = exif_data_get_entry (exif, (ExifTag) EXIF_TAG_GPS_LONGITUDE_REF);</pre>
<pre> </pre>
<pre> if (exif_entry_get_value (entry_lonref, lonref, sizeof (lonref)))</pre>
<pre> {</pre>
<pre> if (QString (lonref).isEmpty ()) return (-1);</pre>
<pre> }</pre>
<pre>
</pre>
<pre> QString arg, temp_folder, ge_tmp_name;</pre>
<pre> QStringList arguments;</pre>
<pre> int8_t process_id;</pre>
<pre> double pushpin_lat, pushpin_lon, pos[3];</pre>
<pre>
</pre>
<pre> QString ImageBaseName = QFileInfo (QString (img_name)).baseName ();</pre>
<pre> </pre>
<pre> </pre>
<pre> sscanf (lon, "%lf, %lf, %lf", &pos[0], &pos[1], &pos[2]);</pre>
<pre>
pos[2] /= 3600.0;</pre>
<pre> pos[1] /= 60.0;</pre>
<pre>
pushpin_lon = pos[0] + pos[1] + pos[2];</pre>
<pre> if (QString (lonref).contains ("W")) pushpin_lon = -pushpin_lon;</pre>
<pre>
</pre>
<pre> sscanf (lat, "%lf, %lf, %lf", &pos[0], &pos[1], &pos[2]);</pre>
<pre>
pos[2] /= 3600.0;</pre>
<pre> pos[1] /= 60.0;</pre>
<pre>
pushpin_lat = pos[0] + pos[1] + pos[2];</pre>
<pre> if (QString (latref).contains ("S")) pushpin_lat = -pushpin_lat;</pre>
<pre>
</pre>
<pre> exif_data_unref (exif);</pre>
<pre>
</pre>
<pre> // Check for google-earth or googleearth or google-earth-pro.</pre>
<pre>
QString command;</pre>
<pre> command = QStandardPaths::findExecutable ("google-earth-pro");</pre>
<pre> if (command.isEmpty ())</pre>
<pre> {</pre>
<pre> command = QStandardPaths::findExecutable ("google-earth");</pre>
<pre> if (command.isEmpty ())</pre>
<pre> {</pre>
<pre> command = QStandardPaths::findExecutable ("googleearth");</pre>
<pre> if (command.isEmpty ())</pre>
<pre> {</pre>
<pre> // If we couldn't find Google Earth open Google maps in browser with sat background and marker...</pre>
<pre> </pre>
<pre> QString browser_string = QString (<a class="moz-txt-link-rfc2396E" href="http://www.google.com/maps/place/%1,%2/@%1,%2,17z/data=!3m1!1e3">"http://www.google.com/maps/place/%1,%2/@%1,%2,17z/data=!3m1!1e3"</a>).arg (pushpin_lat, 0, 'f', 11).arg (pushpin_lon, 0, 'f', 11);</pre>
<pre> QDesktopServices::openUrl (QUrl (browser_string, QUrl::TolerantMode));</pre>
<pre>
return (0);</pre>
<pre> }</pre>
<pre> }</pre>
<pre> }</pre>
<pre> </pre>
<pre> </pre>
<pre> arguments.clear ();</pre>
<pre>
</pre>
<pre> temp_folder = QStandardPaths::writableLocation (QStandardPaths::TempLocation);</pre>
<pre> </pre>
<pre> </pre>
<pre> process_id = getpid ();</pre>
<pre>
ge_tmp_name = temp_folder + QString ("/gePic_GE_%1_tmp.kml").arg (process_id);</pre>
<pre>
QFile file (ge_tmp_name);</pre>
<pre> if (!file.open (QIODevice::WriteOnly | QIODevice::Text)) return (-1);</pre>
<pre> </pre>
<pre>
QTextStream ts (&file);</pre>
<pre>
</pre>
<pre> ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";</pre>
<pre> ts << "<kml xmlns=\<a class="moz-txt-link-rfc2396E" href="http://www.opengis.net/kml/2.2\">"http://www.opengis.net/kml/2.2\"</a>>\n";</pre>
<pre> ts << " <Document>\n";</pre>
<pre> ts << " <Placemark>\n";</pre>
<pre> ts << QString (" <name>%1</name>\n").arg (ImageBaseName);</pre>
<pre> ts << " <Point>\n";</pre>
<pre> ts << " <altitudeMode>relativeToGround</altitudeMode>\n";</pre>
<pre> ts << " <coordinates>\n";</pre>
<pre> ts << QString (" %1,%2\n").arg (pushpin_lon, 0, 'f', 11).arg (pushpin_lat, 0, 'f', 11);</pre>
<pre> ts << " </coordinates>\n";</pre>
<pre> ts << " </Point>\n";</pre>
<pre> ts << " </Placemark>\n";</pre>
<pre> ts << " </Document>\n";</pre>
<pre> ts << "</kml>\n";</pre>
<pre>
</pre>
<pre> file.close ();</pre>
<pre>
arguments << ge_tmp_name;</pre>
<pre>
googleEarthProc = new QProcess (0);</pre>
<pre>
googleEarthProc->start (command, arguments);</pre>
<pre>
googleEarthProc->waitForFinished ();</pre>
<pre>
file.remove ();</pre>
<pre> </pre>
<pre> return (0);</pre>
<pre>}</pre>
<br>
<br>
I wrote a silly little script to build a Makefile and make the app:<br>
<br>
<pre>#!/bin/bash</pre>
<pre>
</pre>
<pre># Building the .pro file using qmake</pre>
<pre>
rm -f xyPic.pro Makefile</pre>
<pre>
qmake -project -o xyPic.tmp</pre>
<pre>cat >xyPic.pro <<EOF</pre>
<pre>RC_FILE = xyPic.rc</pre>
<pre>LIBS += -lexif</pre>
<pre>QT += widgets</pre>
<pre>CONFIG += console</pre>
<pre>EOF</pre>
<pre>
</pre>
<pre>cat xyPic.tmp >>xyPic.pro</pre>
<pre>rm xyPic.tmp</pre>
<pre>
</pre>
<pre># Building the Makefile file using qmake</pre>
<pre>
qmake -o Makefile</pre>
<pre>
</pre>
<pre>make</pre>
<pre>
</pre>
<br>
All of the above is in the public domain (as is all of my
software). My LiDAR (and sonar) processing code does something a
bit more interesting. It builds a link file and makes Google Earth
monitor it for changes every "N" seconds to update the viewed area
and change the pushpin location but that would probably be overkill
for an image viewer. This program doesn't have any real error
handling, if you don't have GPS in the image it just doesn't come
up.<br>
<br>
Again, sorry for the long post. If there is a dev out there
that is interested in adding this to gwenview, go for it.<br>
<br>
V/r,<br>
Jan Depner<br>
<br>
<br>
<br>
</body>
</html>