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