1 /**********************************************************
2 * Version $Id: georef.c 911 2011-02-14 16:38:15Z reklov_w $
3 *********************************************************/
4 /***************************************************************************/
5 /* RSC IDENTIFIER: GEOREF
6 *
7 * ABSTRACT
8 *
9 * This component provides conversions from Geodetic coordinates (latitude
10 * and longitude in radians) to a GEOREF coordinate string.
11 *
12 * ERROR HANDLING
13 *
14 * This component checks parameters for valid values. If an invalid value
15 * is found, the error code is combined with the current error code using
16 * the bitwise or. This combining allows multiple error codes to be
17 * returned. The possible error codes are:
18 *
19 * GEOREF_NO_ERROR : No errors occurred in function
20 * GEOREF_LAT_ERROR : Latitude outside of valid range
21 * (-90 to 90 degrees)
22 * GEOREF_LON_ERROR : Longitude outside of valid range
23 * (-180 to 360 degrees)
24 * GEOREF_STR_ERROR : A GEOREF string error: string too long,
25 * string too short, or string length
26 * not even.
27 * GEOREF_STR_LAT_ERROR : The latitude part of the GEOREF string
28 * (second or fourth character) is invalid.
29 * GEOREF_STR_LON_ERROR : The longitude part of the GEOREF string
30 * (first or third character) is invalid.
31 * GEOREF_STR_LAT_MIN_ERROR : The latitude minute part of the GEOREF
32 * string is greater than 60.
33 * GEOREF_STR_LON_MIN_ERROR : The longitude minute part of the GEOREF
34 * string is greater than 60.
35 * GEOREF_PRECISION_ERROR : The precision must be between 0 and 5
36 * inclusive.
37 *
38 *
39 * REUSE NOTES
40 *
41 * GEOREF is intended for reuse by any application that performs a
42 * conversion between Geodetic and GEOREF coordinates.
43 *
44 * REFERENCES
45 *
46 * Further information on GEOREF can be found in the Reuse Manual.
47 *
48 * GEOREF originated from : U.S. Army Topographic Engineering Center
49 * Geospatial Information Division
50 * 7701 Telegraph Road
51 * Alexandria, VA 22310-3864
52 *
53 * LICENSES
54 *
55 * None apply to this component.
56 *
57 * RESTRICTIONS
58 *
59 * GEOREF has no restrictions.
60 *
61 * ENVIRONMENT
62 *
63 * GEOREF was tested and certified in the following environments:
64 *
65 * 1. Solaris 2.5 with GCC version 2.8.1
66 * 2. Windows 95 with MS Visual C++ version 6
67 *
68 * MODIFICATIONS
69 *
70 * Date Description
71 * ---- -----------
72 * 20-02-97 Original Code
73 */
74
75
76 /***************************************************************************/
77 /*
78 * INCLUDES
79 */
80
81 #include <ctype.h>
82 #include <math.h>
83 #include <stdio.h>
84 #include <stdlib.h>
85 #include <string.h>
86 #include "georef.h"
87 /*
88 * ctype.h - Standard C character handling library
89 * math.h - Standard C math library
90 * stdio.h - Standard C input/output library
91 * stdlib.h - Standard C general utility library
92 * string.h - Standard C string handling library
93 * georef.h - for prototype error checking
94 */
95
96
97 /***************************************************************************/
98 /*
99 * DEFINES
100 */
101
102 #define TRUE 1
103 #define FALSE 0
104 #define LATITUDE_LOW -90 /* Minimum latitude */
105 #define LATITUDE_HIGH 90 /* Maximum latitude */
106 #define LONGITUDE_LOW -180 /* Minimum longitude */
107 #define LONGITUDE_HIGH 360 /* Maximum longitude */
108 #define MIN_PER_DEG 60 /* Number of minutes per degree */
109 #define GEOREF_MINIMUM 4 /* Minimum number of chars for GEOREF */
110 #define GEOREF_MAXIMUM 14 /* Maximum number of chars for GEOREF */
111 #define GEOREF_LETTERS 4 /* Number of letters in GEOREF string */
112 #define MAX_PRECISION 5 /* Maximum precision of minutes part */
113 #define LETTER_I 8 /* Index for letter I */
114 #define LETTER_M 12 /* Index for letter M */
115 #define LETTER_O 14 /* Index for letter O */
116 #define LETTER_Q 16 /* Index for letter Q */
117 #define LETTER_Z 25 /* Index for letter Z */
118 #define LETTER_A_OFFSET 65 /* Letter A offset in character set */
119 #define ZERO_OFFSET 48 /* Number zero offset in character set */
120 #define PI 3.14159265358979323e0 /* PI */
121 #define DEGREE_TO_RADIAN (PI / 180.0)
122 #define RADIAN_TO_DEGREE (180.0 / PI)
123 #define QUAD 15 /* Degrees per grid square */
124 #define ROUND_ERROR 0.0000005 /* Rounding factor */
125
126
127 /***************************************************************************/
128 /*
129 * FUNCTIONS
130 */
131
132
Extract_Degrees(char * georef,double * latitude,double * longitude)133 long Extract_Degrees (char *georef,
134 double *latitude,
135 double *longitude)
136 { /* BEGIN Extract_Degrees */
137 /*
138 * This function extracts the latitude and longitude degree parts of the
139 * GEOREF string. The latitude and longitude degree parts are the first four
140 * characters.
141 *
142 * georef : GEOREF string (input)
143 * latitude : Latitude in degrees (output)
144 * longitude : Longitude in degrees (output)
145 */
146 long i; /* counter in for loops */
147 long temp_char; /* temporary character */
148 long letter_number[GEOREF_LETTERS]; /* number corresponding to letter */
149 long error_code = GEOREF_NO_ERROR;
150
151 for (i=0;i<GEOREF_LETTERS;i++)
152 {
153 temp_char = toupper(georef[i]);
154 temp_char = temp_char - LETTER_A_OFFSET;
155 if ((!isalpha(georef[i]))
156 || (temp_char == LETTER_I)
157 || (temp_char == LETTER_O))
158 {
159 if ((i == 0) || (i == 2))
160 error_code |= GEOREF_STR_LON_ERROR;
161 else
162 error_code |= GEOREF_STR_LAT_ERROR;
163 }
164 letter_number[i] = temp_char;
165 }
166 for (i=0;i<4;i++)
167 {
168 if (letter_number[i] > LETTER_O)
169 letter_number[i] -= 2;
170 else if (letter_number[i] > LETTER_I)
171 letter_number[i] -= 1;
172 }
173 if ((letter_number[0] > 23) || (letter_number[2] > 14))
174 error_code |= GEOREF_STR_LON_ERROR;
175 if ((letter_number[1] > 11) || (letter_number[3] > 14))
176 error_code |= GEOREF_STR_LAT_ERROR;
177 *latitude = (double)(letter_number[1]) * QUAD + (double)(letter_number[3]);
178 *longitude = (double)(letter_number[0]) * QUAD + (double)(letter_number[2]);
179 return (error_code);
180 } /* END Extract_Degrees */
181
182
Extract_Minutes(char * georef,long start,long length,long ERROR_TYPE,double * minutes)183 long Extract_Minutes (char *georef,
184 long start,
185 long length,
186 long ERROR_TYPE,
187 double *minutes)
188 { /* BEGIN Extract_Minutes */
189 /*
190 * This function extracts the minutes from the GEOREF string. The minutes
191 * part begins at position start and has length length. The ERROR_TYPE is
192 * to allow this function to work with both latitude and longitude minutes.
193 *
194 * georef : GEOREF string (input)
195 * start : Start position in the GEOREF string (input)
196 * length : length of minutes in the GEOREF string (input)
197 * ERROR_TYPE : has a value of either GEOREF_STR_LAT_MIN_ERROR (input)
198 * or GEOREF_STR_LON_MIN_ERROR
199 * minutes: minute part (output)
200 */
201 long i; /* counter in for loop */
202 long error_code = GEOREF_NO_ERROR;
203 char temp_str[(GEOREF_MAXIMUM-GEOREF_LETTERS)/2 + 1];
204
205 for (i=0;i<length;i++)
206 {
207 if (isdigit(georef[start+i]))
208 temp_str[i] = georef[start+i];
209 else
210 error_code |= ERROR_TYPE;
211 }
212 temp_str[length] = 0;
213 *minutes = (double)atof(temp_str); /* need atof, atoi can't handle 59999 */
214 while (length > 2)
215 {
216 *minutes = *minutes / 10;
217 length = length - 1;
218 }
219 if (*minutes > (double)MIN_PER_DEG)
220 error_code |= ERROR_TYPE;
221 return (error_code);
222 } /* END OF Extract_Minutes */
223
224
Round_GEOREF(double value)225 long Round_GEOREF (double value)
226 /* Round value to nearest integer, using standard engineering rule */
227 { /* Round_GEOREF */
228 double ivalue;
229 long ival;
230 double fraction = modf (value, &ivalue);
231 ival = (long)(ivalue);
232 if ((fraction > 0.5) || ((fraction == 0.5) && (ival%2 == 1)))
233 ival++;
234 return (ival);
235 } /* Round_GEOREF */
236
237
Convert_Minutes_To_String(double minutes,long precision,char * str)238 void Convert_Minutes_To_String(double minutes,
239 long precision,
240 char *str)
241 { /* BEGIN Convert_Minutes_To_String */
242 /*
243 * This function converts minutes to a string of length precision.
244 *
245 * minutes : Minutes to be converted (input)
246 * precision : Length of resulting string (input)
247 * str : String to hold converted minutes (output)
248 */
249 double divisor;
250 long min;
251 divisor = pow (10.0, (5 - precision));
252 if (minutes == 60.0)
253 minutes = 59.999;
254 minutes = minutes * 1000;
255 min = Round_GEOREF (minutes/divisor);
256 sprintf (str, "%*.*ld", precision, precision, min);
257 if (precision == 1)
258 strcat (str, "0");
259 } /* END Convert_Minutes_To_String */
260
261
Convert_GEOREF_To_Geodetic(char * georef,double * latitude,double * longitude)262 long Convert_GEOREF_To_Geodetic (char *georef,
263 double *latitude,
264 double *longitude)
265 { /* BEGIN Convert_GEOREF_To_Geodetic */
266 /*
267 * This function converts a GEOREF coordinate string to Geodetic (latitude
268 * and longitude in radians) coordinates.
269 *
270 * georef : GEOREF coordinate string. (input)
271 * latitude : Latitude in radians. (output)
272 * longitude : Longitude in radians. (output)
273 *
274 * CALLS THE FOLLOWING FUNCTIONS:
275 *
276 * Extract_Degrees
277 * Extract_Minutes
278 */
279 long start; /* Position in the GEOREF string */
280 long minutes_length; /* length of minutes in the GEOREF string */
281 long georef_length; /* length of GEOREF string */
282 double origin_long; /* Origin longitude */
283 double origin_lat; /* Origin latitude */
284 double long_minutes; /* Longitude minute part of GEOREF */
285 double lat_minutes; /* Latitude minute part of GEOREF */
286 long error_code = GEOREF_NO_ERROR;
287
288 origin_long = (double)LONGITUDE_LOW;
289 origin_lat = (double)LATITUDE_LOW;
290 georef_length = strlen(georef);
291 if ((georef_length < GEOREF_MINIMUM) || (georef_length > GEOREF_MAXIMUM)
292 || ((georef_length % 2) != 0))
293 {
294 error_code |= GEOREF_STR_ERROR;
295 }
296 if (!error_code)
297 {
298 error_code |= Extract_Degrees(georef,latitude,longitude);
299 start = GEOREF_LETTERS;
300 minutes_length = (georef_length - start) / 2;
301 if (!error_code)
302 {
303 error_code |= Extract_Minutes(georef, start, minutes_length,
304 GEOREF_STR_LON_MIN_ERROR, &long_minutes);
305 if (!error_code)
306 {
307 error_code |= Extract_Minutes(georef, (start+minutes_length),
308 minutes_length, GEOREF_STR_LAT_MIN_ERROR, &lat_minutes);
309 *latitude = *latitude + origin_lat + lat_minutes / (double)MIN_PER_DEG;
310 *longitude = *longitude + origin_long + long_minutes / (double)MIN_PER_DEG;
311 *latitude = *latitude * DEGREE_TO_RADIAN;
312 *longitude = *longitude * DEGREE_TO_RADIAN;
313 }
314 }
315 }
316 return (error_code);
317 } /* END OF Convert_GEOREF_To_Geodetic */
318
319
Convert_Geodetic_To_GEOREF(double latitude,double longitude,long precision,char * georef)320 long Convert_Geodetic_To_GEOREF (double latitude,
321 double longitude,
322 long precision,
323 char *georef)
324 { /* BEGIN Convert_Geodetic_To_GEOREF */
325 /*
326 * This function converts Geodetic (latitude and longitude in radians)
327 * coordinates to a GEOREF coordinate string. Precision specifies the
328 * number of digits in the GEOREF string for latitude and longitude:
329 * 0 for nearest degree
330 * 1 for nearest ten minutes
331 * 2 for nearest minute
332 * 3 for nearest tenth of a minute
333 * 4 for nearest hundredth of a minute
334 * 5 for nearest thousandth of a minute
335 *
336 * latitude : Latitude in radians. (input)
337 * longitude : Longitude in radians. (input)
338 * precision : Precision specified by the user. (input)
339 * georef : GEOREF coordinate string. (output)
340 *
341 * CALLS THE FOLLOWING FUNCTIONS:
342 *
343 * Convert_Minutes_To_String
344 */
345
346 double long_min; /* GEOREF longitude minute part */
347 double lat_min; /* GEOREF latitude minute part */
348 double origin_long; /* Origin longitude (-180 degrees)*/
349 double origin_lat; /* Origin latitude (-90 degrees) */
350 long letter_number[GEOREF_LETTERS + 1]; /* GEOREF letters */
351 char long_min_str[MAX_PRECISION + 1]; /* Longitude minute string */
352 char lat_min_str[MAX_PRECISION + 1]; /* Latitude minute string */
353 long i; /* counter in for loop */
354 long error_code = GEOREF_NO_ERROR;
355
356 latitude = latitude * RADIAN_TO_DEGREE;
357 longitude = longitude * RADIAN_TO_DEGREE;
358 if ((latitude < (double)LATITUDE_LOW)
359 || (latitude > (double)LATITUDE_HIGH))
360 error_code |= GEOREF_LAT_ERROR;
361 if ((longitude < (double)LONGITUDE_LOW)
362 || (longitude > (double)LONGITUDE_HIGH))
363 error_code |= GEOREF_LON_ERROR;
364 if ((precision < 0) || (precision > MAX_PRECISION))
365 error_code |= GEOREF_PRECISION_ERROR;
366 if (!error_code)
367 {
368 if (longitude > 180)
369 longitude -= 360;
370 origin_long = (double)LONGITUDE_LOW;
371 origin_lat = (double)LATITUDE_LOW;
372 letter_number[0] = (long)((longitude-origin_long) / QUAD + ROUND_ERROR);
373 longitude = longitude - ((double)letter_number[0] * QUAD + origin_long);
374 letter_number[2] = (long)(longitude + ROUND_ERROR);
375 long_min = (longitude - (double)letter_number[2]) * (double)MIN_PER_DEG;
376 letter_number[1] = (long)((latitude - origin_lat) / QUAD + ROUND_ERROR);
377 latitude = latitude - ((double)letter_number[1] * QUAD + origin_lat);
378 letter_number[3] = (long)(latitude + ROUND_ERROR);
379 lat_min = (latitude - (double)letter_number[3]) * (double)MIN_PER_DEG;
380 for (i = 0;i < 4; i++)
381 {
382 if (letter_number[i] >= LETTER_I)
383 letter_number[i] += 1;
384 if (letter_number[i] >= LETTER_O)
385 letter_number[i] += 1;
386 }
387
388 if (letter_number[0] == 26)
389 { /* longitude of 180 degrees */
390 letter_number[0] = LETTER_Z;
391 letter_number[2] = LETTER_Q;
392 long_min = 59.999;
393 }
394 if (letter_number[1] == 13)
395 { /* latitude of 90 degrees */
396 letter_number[1] = LETTER_M;
397 letter_number[3] = LETTER_Q;
398 lat_min = 59.999;
399 }
400
401 for (i=0;i<4;i++)
402 georef[i] = (char)(letter_number[i] + LETTER_A_OFFSET);
403 georef[4] = 0;
404 Convert_Minutes_To_String(long_min,precision,long_min_str);
405 Convert_Minutes_To_String(lat_min,precision,lat_min_str);
406 strcat(georef,long_min_str);
407 strcat(georef,lat_min_str);
408 }
409 return (error_code);
410 } /* END OF Convert_Geodetic_To_GEOREF */
411