1 /*
2  * Copyright (c) 2003-2005, Eric M. Johnston <emj@postal.net>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by Eric M. Johnston.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: exifgps.c,v 1.14 2007/12/15 20:57:10 ejohnst Exp $
33  */
34 
35 /*
36  * Exif GPS information tags.
37  *
38  * Note: things aren't quite complete.  Waiting on additional examples
39  * that include the tags marked unknown.
40  */
41 
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <math.h>
46 
47 #include "exif.h"
48 #include "exifint.h"
49 
50 #define DEGREE "�"
51 
52 
53 /* Speed. */
54 
55 static struct descrip gps_speed[] = {
56 	{ 'K',	"km/h" },
57 	{ 'M',	"mph" },
58 	{ 'N',	"knots" },
59 	{ -1,	"" },
60 };
61 
62 
63 /* Status. */
64 
65 static struct descrip gps_status[] = {
66 	{ 'A',	"Measurement In Progress" },
67 	{ 'V',	"Measurement Interoperability" },
68 	{ -1,	"Unknown" },
69 };
70 
71 
72 /* Distance. */
73 
74 static struct descrip gps_dist[] = {
75 	{ 'K',	"km" },
76 	{ 'M',	"mi" },
77 	{ 'N',	"knots" },
78 	{ -1,	"" },
79 };
80 
81 
82 /* Differential correction. */
83 
84 static struct descrip gps_diff[] = {
85 	{ 0,	"No Correction" },
86 	{ 1,	"Correction Applied" },
87 	{ -1,	"Unknown" },
88 };
89 
90 
91 /* Bearing reference. */
92 
93 static struct descrip gps_bear[] = {
94 	{ 'M',	"Magnetic North" },
95 	{ 'T',	"True North" },
96 	{ -1,	"Unknown" },
97 };
98 
99 
100 /* GPS info version 2.2.0.0 tags. */
101 
102 struct exiftag gpstags[] = {
103 	{ 0x0000, TIFF_BYTE,  4,  ED_VRB,
104 	    "GPSVersionID", "GPS Info Version", NULL },
105 	{ 0x0001, TIFF_ASCII, 2,  ED_VRB,
106 	    "GPSLatitudeRef", "Latitude Reference", NULL },
107 	{ 0x0002, TIFF_RTNL,  3,  ED_IMG,
108 	    "GPSLatitude", "Latitude", NULL },
109 	{ 0x0003, TIFF_ASCII, 2,  ED_VRB,
110 	    "GPSLongitudeRef", "Longitude Reference", NULL },
111 	{ 0x0004, TIFF_RTNL,  3,  ED_IMG,
112 	    "GPSLongitude", "Longitude", NULL },
113 	{ 0x0005, TIFF_BYTE,  1,  ED_VRB,
114 	    "GPSAltitudeRef", "Altitude Reference", NULL },
115 	{ 0x0006, TIFF_RTNL,  1,  ED_IMG,		/* meters */
116 	    "GPSAltitude", "Altitude", NULL },
117 	{ 0x0007, TIFF_RTNL,  3,  ED_IMG,
118 	    "GPSTimeStamp", "Time (UTC)", NULL },
119 	{ 0x0008, TIFF_ASCII, 0,  ED_IMG,
120 	    "GPSSatellites", "GPS Satellites", NULL },
121 	{ 0x0009, TIFF_ASCII, 2,  ED_IMG,
122 	    "GPSStatus", "GPS Status", gps_status },
123 	{ 0x000a, TIFF_ASCII, 2,  ED_IMG,
124 	    "GPSMeasureMode", "GPS Measurement Mode", NULL },
125 	{ 0x000b, TIFF_RTNL,  1,  ED_UNK,
126 	    "GPSDOP", "GPS Degree of Precision", NULL },
127 	{ 0x000c, TIFF_ASCII, 2,  ED_VRB,
128 	    "GPSSpeedRef", "GPS Speed Reference", gps_speed },
129 	{ 0x000d, TIFF_RTNL,  1,  ED_UNK,
130 	    "GPSSpeed", "Movement Speed", NULL },
131 	{ 0x000e, TIFF_ASCII, 2,  ED_VRB,
132 	    "GPSTrackRef", "GPS Direction Reference", gps_bear },
133 	{ 0x000f, TIFF_RTNL,  1,  ED_UNK,		/* degrees */
134 	    "GPSTrack", "Movement Direction", NULL },
135 	{ 0x0010, TIFF_ASCII, 2,  ED_VRB,
136 	    "GPSImgDirectionRef", "GPS Image Direction Ref", gps_bear },
137 	{ 0x0011, TIFF_RTNL,  1,  ED_UNK,		/* degrees */
138 	    "GPSImgDirection",  "Image Direction", NULL },
139 	{ 0x0012, TIFF_ASCII, 0,  ED_IMG,
140 	    "GPSMapDatum", "Geodetic Survey Data", NULL },
141 	{ 0x0013, TIFF_ASCII, 2,  ED_VRB,
142 	    "GPSDestLatitudeRef", "GPS Dest Latitude Ref", NULL },
143 	{ 0x0014, TIFF_RTNL,  3,  ED_IMG,
144 	    "GPSDestLatitude", "Destination Latitude", NULL },
145 	{ 0x0015, TIFF_ASCII, 2,  ED_VRB,
146 	    "GPSDestLongitudeRef", "GPS Dest Longitude Ref", NULL },
147 	{ 0x0016, TIFF_RTNL,  3,  ED_IMG,
148 	    "GPSDestLongitude", "Destination Longitude", NULL },
149 	{ 0x0017, TIFF_ASCII, 2,  ED_VRB,
150 	    "GPSDestBearingRef", "GPS Dest Bearing Ref", gps_bear },
151 	{ 0x0018, TIFF_RTNL,  1,  ED_UNK,		/* degrees */
152 	    "GPSDestBearing", "Destination Direction", NULL },
153 	{ 0x0019, TIFF_ASCII, 2,  ED_VRB,
154 	    "GPSDestDistanceRef", "GPS Dest Distance Ref", gps_dist },
155 	{ 0x001a, TIFF_RTNL,  1,  ED_UNK,
156 	    "GPSDestDistance", "Destination Distance", NULL },
157 	{ 0x001b, TIFF_UNDEF, 0,  ED_IMG,
158 	    "GPSProcessingMethod", "GPS Processing Method", NULL },
159 	{ 0x001c, TIFF_UNDEF, 0,  ED_IMG,
160 	    "GPSAreaInformation", "GPS Area", NULL },
161 	{ 0x001d, TIFF_ASCII, 11, ED_IMG,
162 	    "GPSDateStamp", "Date (UTC)", NULL },
163 	{ 0x001e, TIFF_SHORT, 1,  ED_IMG,
164 	    "GPSDifferental", "GPS Differential Correction", gps_diff },
165 	{ 0xffff, TIFF_UNKN,  0,  ED_UNK,
166 	    "Unknown", NULL, NULL },
167 };
168 
169 
170 /*
171  * Process GPS tags.
172  */
173 void
gpsprop(struct exifprop * prop,struct exiftags * t)174 gpsprop(struct exifprop *prop, struct exiftags *t)
175 {
176 	u_int32_t i, n, d;
177 	double deg, min, sec, alt;
178 	char fmt[32], buf[16];
179 	struct exifprop *tmpprop;
180 	enum byteorder o = t->md.order;
181 
182 	switch (prop->tag) {
183 
184 	/* Version. */
185 
186 	case 0x0000:
187 		exifstralloc(&prop->str, 8);
188 
189 		/* Convert the value back into a string. */
190 
191 		byte4exif(prop->value, (unsigned char *)buf, o);
192 
193 		for (i = 0; i < 4; i++) {
194 			prop->str[i * 2] = '0' + buf[i];
195 			prop->str[i * 2 + 1] = '.';
196 		}
197 		prop->str[7] = '\0';
198 		break;
199 
200 	/*
201 	 * Reference values.  The value is 2-count nul-terminated ASCII,
202 	 * not an offset to the ASCII string.
203 	 * XXX Shouldn't really be necessary now that short ASCII strings work.
204 	 */
205 
206 	case 0x0001:
207 	case 0x0003:
208 	case 0x0009:
209 	case 0x000a:
210 	case 0x000c:
211 	case 0x000e:
212 	case 0x0010:
213 	case 0x0013:
214 	case 0x0015:
215 	case 0x0017:
216 	case 0x0019:
217 		/* Clean-up from any earlier processing. */
218 
219 		free(prop->str);
220 		prop->str = NULL;
221 
222 		byte4exif(prop->value, (unsigned char *)buf, o);
223 
224 		for (i = 0; gpstags[i].tag < EXIF_T_UNKNOWN &&
225 		    gpstags[i].tag != prop->tag; i++);
226 		if (gpstags[i].table)
227 			prop->str = finddescr(gpstags[i].table,
228 			    (unsigned char)buf[0]);
229 		else {
230 			exifstralloc(&prop->str, 2);
231 			prop->str[0] = buf[0];
232 		}
233 		break;
234 
235 	/*
236 	 * Coordinate values.
237 	 *
238 	 * This is really kind of a mess.  The display behavior here is
239 	 * based on image samples from a Nikon D1X and a Fuji FinePix S1 Pro.
240 	 * The specification allows for fractional minutes (and no seconds).
241 	 * Not sure if there are any other combinations...
242 	 */
243 
244 	case 0x0002:
245 	case 0x0004:
246 	case 0x0014:
247 	case 0x0016:
248 	 	if (prop->count != 3) {
249 			exifwarn("unexpected GPS coordinate values");
250 			prop->lvl = ED_BAD;
251 			break;
252 		}
253 
254 		free(prop->str);
255 		prop->str = NULL;
256 		exifstralloc(&prop->str, 32);
257 
258 		/* Figure out the reference prefix. */
259 
260 		switch (prop->tag) {
261 		case 0x0002:
262 			tmpprop = findprop(t->props, gpstags, 0x0001);
263 			break;
264 		case 0x0004:
265 			tmpprop = findprop(t->props, gpstags, 0x0003);
266 			break;
267 		case 0x0014:
268 			tmpprop = findprop(t->props, gpstags, 0x0013);
269 			break;
270 		case 0x0016:
271 			tmpprop = findprop(t->props, gpstags, 0x0015);
272 			break;
273 		default:
274 			tmpprop = NULL;
275 		}
276 
277 		/* Degrees. */
278 
279 		i = 0;
280 		n = exif4byte(t->md.btiff + prop->value + i * 8, o);
281 		d = exif4byte(t->md.btiff + prop->value + 4 + i * 8, o);
282 
283 		strcpy(fmt, "%s %.f%s ");
284 		if (!n || !d)			/* Punt. */
285 			deg = 0.0;
286 		else {
287 			deg = (double)n / (double)d;
288 			if (d != 1)
289 				sprintf(fmt, "%%s %%.%df%%s ",
290 				    (int)log10((double)d));
291 		}
292 
293 		/* Minutes. */
294 
295 		i++;
296 		n = exif4byte(t->md.btiff + prop->value + i * 8, o);
297 		d = exif4byte(t->md.btiff + prop->value + 4 + i * 8, o);
298 
299 		if (!n || !d) {			/* Punt. */
300 			min = 0.0;
301 			strcat(fmt, "%.f'");
302 		} else {
303 			min = (double)n / (double)d;
304 			if (d != 1) {
305 				sprintf(buf, "%%.%df'", (int)log10((double)d));
306 				strcat(fmt, buf);
307 			} else
308 				strcat(fmt, "%.f'");
309 		}
310 
311 		/*
312 		 * Seconds.  We'll assume if minutes are fractional, we
313 		 * should just ignore seconds.
314 		 */
315 
316 		i++;
317 		n = exif4byte(t->md.btiff + prop->value + i * 8, o);
318 		d = exif4byte(t->md.btiff + prop->value + 4 + i * 8, o);
319 
320 		if (!n || !d) {			/* Assume no seconds. */
321 			snprintf(prop->str, 31, fmt, tmpprop && tmpprop->str ?
322 			    tmpprop->str : "", deg, DEGREE, min);
323 			break;
324 		} else {
325 			sec = (double)n / (double)d;
326 			if (d != 1) {
327 				sprintf(buf, " %%.%df", (int)log10((double)d));
328 				strcat(fmt, buf);
329 			} else
330 				strcat(fmt, " %.f");
331 		}
332 		snprintf(prop->str, 31, fmt, tmpprop && tmpprop->str ?
333 		    tmpprop->str : "", deg, DEGREE, min, sec);
334 		break;
335 
336 	/* Altitude. */
337 
338 	case 0x0006:
339 		n = exif4byte(t->md.btiff + prop->value, o);
340 		d = exif4byte(t->md.btiff + prop->value + 4, o);
341 
342 		/* Look up reference.  Non-zero means negative altitude. */
343 
344 		tmpprop = findprop(t->props, gpstags, 0x0005);
345 		if (tmpprop && tmpprop->value)
346 			n *= -1;
347 
348 		if (!n || !d)
349 			alt = 0.0;
350 		else
351 			alt = (double)n / (double)d;
352 
353 		/* Should already have a 32-byte buffer from parsetag(). */
354 
355 		snprintf(prop->str, 31, "%.2f m", alt);
356 		prop->str[31] = '\0';
357 		break;
358 
359 	/* Time. */
360 
361 	case 0x0007:
362 		/* Should already have a 32-byte buffer from parsetag(). */
363 
364 		prop->str[0] = '\0';
365 		for (i = 0; i < prop->count; i++) {
366 			n = exif4byte(t->md.btiff + prop->value + i * 8, o);
367 			d = exif4byte(t->md.btiff + prop->value + 4 + i * 8, o);
368 
369 			if (!d) break;
370 
371 			if (!i)
372 				sprintf(fmt, "%%02.%df", (int)log10((double)d));
373 			else
374 				sprintf(fmt, ":%%02.%df",
375 				    (int)log10((double)d));
376 
377 			snprintf(buf, 8, fmt, (double)n / (double)d);
378 			strcat(prop->str, buf);
379 		}
380 		break;
381 	}
382 }
383