PostGIS  2.5.7dev-r@@SVN_REVISION@@

◆ lwdouble_to_dms()

static char* lwdouble_to_dms ( double  val,
const char *  pos_dir_symbol,
const char *  neg_dir_symbol,
const char *  format 
)
static

Definition at line 76 of file lwprint.c.

77 {
78  /* 3 numbers, 1 sign or compass dir, and 5 possible strings (degree signs, spaces, misc text, etc) between or around them.*/
79 # define NUM_PIECES 9
80 # define WORK_SIZE 1024
81  char pieces[NUM_PIECES][WORK_SIZE];
82  int current_piece = 0;
83  int is_negative = 0;
84 
85  double degrees = 0.0;
86  double minutes = 0.0;
87  double seconds = 0.0;
88 
89  int compass_dir_piece = -1;
90 
91  int reading_deg = 0;
92  int deg_digits = 0;
93  int deg_has_decpoint = 0;
94  int deg_dec_digits = 0;
95  int deg_piece = -1;
96 
97  int reading_min = 0;
98  int min_digits = 0;
99  int min_has_decpoint = 0;
100  int min_dec_digits = 0;
101  int min_piece = -1;
102 
103  int reading_sec = 0;
104  int sec_digits = 0;
105  int sec_has_decpoint = 0;
106  int sec_dec_digits = 0;
107  int sec_piece = -1;
108 
109  int round_pow = 0;
110 
111  int format_length = ((NULL == format) ? 0 : strlen(format));
112 
113  char * result;
114 
115  int index, following_byte_index;
116  int multibyte_char_width = 1;
117 
118  /* Initialize the working strs to blank. We may not populate all of them, and
119  * this allows us to concat them all at the end without worrying about how many
120  * we actually needed. */
121  for (index = 0; index < NUM_PIECES; index++)
122  {
123  pieces[index][0] = '\0';
124  }
125 
126  /* If no format is provided, use a default. */
127  if (0 == format_length)
128  {
129  /* C2B0 is UTF-8 for the degree symbol. */
130  format = "D\xC2\xB0""M'S.SSS\"C";
131  format_length = strlen(format);
132  }
133  else if (format_length > WORK_SIZE)
134  {
135  /* Sanity check, we don't want to overwrite an entire piece of work and no one should need a 1K-sized
136  * format string anyway. */
137  lwerror("Bad format, exceeds maximum length (%d).", WORK_SIZE);
138  }
139 
140  for (index = 0; index < format_length; index++)
141  {
142  char next_char = format[index];
143  switch (next_char)
144  {
145  case 'D':
146  if (reading_deg)
147  {
148  /* If we're reading degrees, add another digit. */
149  deg_has_decpoint ? deg_dec_digits++ : deg_digits++;
150  }
151  else
152  {
153  /* If we're not reading degrees, we are now. */
154  current_piece++;
155  deg_piece = current_piece;
156  if (deg_digits > 0)
157  {
158  lwerror("Bad format, cannot include degrees (DD.DDD) more than once.");
159  }
160  reading_deg = 1;
161  reading_min = 0;
162  reading_sec = 0;
163  deg_digits++;
164  }
165  break;
166  case 'M':
167  if (reading_min)
168  {
169  /* If we're reading minutes, add another digit. */
170  min_has_decpoint ? min_dec_digits++ : min_digits++;
171  }
172  else
173  {
174  /* If we're not reading minutes, we are now. */
175  current_piece++;
176  min_piece = current_piece;
177  if (min_digits > 0)
178  {
179  lwerror("Bad format, cannot include minutes (MM.MMM) more than once.");
180  }
181  reading_deg = 0;
182  reading_min = 1;
183  reading_sec = 0;
184  min_digits++;
185  }
186  break;
187  case 'S':
188  if (reading_sec)
189  {
190  /* If we're reading seconds, add another digit. */
191  sec_has_decpoint ? sec_dec_digits++ : sec_digits++;
192  }
193  else
194  {
195  /* If we're not reading seconds, we are now. */
196  current_piece++;
197  sec_piece = current_piece;
198  if (sec_digits > 0)
199  {
200  lwerror("Bad format, cannot include seconds (SS.SSS) more than once.");
201  }
202  reading_deg = 0;
203  reading_min = 0;
204  reading_sec = 1;
205  sec_digits++;
206  }
207  break;
208  case 'C':
209  /* We're done reading anything else we might have been reading. */
210  if (reading_deg || reading_min || reading_sec)
211  {
212  /* We were reading something, that means this is the next piece. */
213  reading_deg = 0;
214  reading_min = 0;
215  reading_sec = 0;
216  }
217  current_piece++;
218 
219  if (compass_dir_piece >= 0)
220  {
221  lwerror("Bad format, cannot include compass dir (C) more than once.");
222  }
223  /* The compass dir is a piece all by itself. */
224  compass_dir_piece = current_piece;
225  current_piece++;
226  break;
227  case '.':
228  /* If we're reading deg, min, or sec, we want a decimal point for it. */
229  if (reading_deg)
230  {
231  deg_has_decpoint = 1;
232  }
233  else if (reading_min)
234  {
235  min_has_decpoint = 1;
236  }
237  else if (reading_sec)
238  {
239  sec_has_decpoint = 1;
240  }
241  else
242  {
243  /* Not reading anything, just pass through the '.' */
244  strncat(pieces[current_piece], &next_char, 1);
245  }
246  break;
247  default:
248  /* Any other char is just passed through unchanged. But it does mean we are done reading D, M, or S.*/
249  if (reading_deg || reading_min || reading_sec)
250  {
251  /* We were reading something, that means this is the next piece. */
252  current_piece++;
253  reading_deg = 0;
254  reading_min = 0;
255  reading_sec = 0;
256  }
257 
258  /* Check if this is a multi-byte UTF-8 character. If so go ahead and read the rest of the bytes as well. */
259  multibyte_char_width = 1;
260  if (next_char & 0x80)
261  {
262  if ((next_char & 0xF8) == 0xF0)
263  {
264  multibyte_char_width += 3;
265  }
266  else if ((next_char & 0xF0) == 0xE0)
267  {
268  multibyte_char_width += 2;
269  }
270  else if ((next_char & 0xE0) == 0xC0)
271  {
272  multibyte_char_width += 1;
273  }
274  else
275  {
276  lwerror("Bad format, invalid high-order byte found first, format string may not be UTF-8.");
277  }
278  }
279  if (multibyte_char_width > 1)
280  {
281  if (index + multibyte_char_width >= format_length)
282  {
283  lwerror("Bad format, UTF-8 character first byte found with insufficient following bytes, format string may not be UTF-8.");
284  }
285  for (following_byte_index = (index + 1); following_byte_index < (index + multibyte_char_width); following_byte_index++)
286  {
287  if ((format[following_byte_index] & 0xC0) != 0x80)
288  {
289  lwerror("Bad format, invalid byte found following leading byte of multibyte character, format string may not be UTF-8.");
290  }
291  }
292  }
293  /* Copy all the character's bytes into the current piece. */
294  strncat(pieces[current_piece], &(format[index]), multibyte_char_width);
295  /* Now increment index past the rest of those bytes. */
296  index += multibyte_char_width - 1;
297  break;
298  }
299  if (current_piece >= NUM_PIECES)
300  {
301  lwerror("Internal error, somehow needed more pieces than it should.");
302  }
303  }
304  if (deg_piece < 0)
305  {
306  lwerror("Bad format, degrees (DD.DDD) must be included.");
307  }
308 
309  /* Divvy the number up into D, DM, or DMS */
310  if (val < 0)
311  {
312  val *= -1;
313  is_negative = 1;
314  }
315  degrees = val;
316  if (min_digits > 0)
317  {
318  /* Break degrees to integer and use fraction for minutes */
319  minutes = modf(val, &degrees) * 60;
320  }
321  if (sec_digits > 0)
322  {
323  if (0 == min_digits)
324  {
325  lwerror("Bad format, cannot include seconds (SS.SSS) without including minutes (MM.MMM).");
326  }
327  seconds = modf(minutes, &minutes) * 60;
328  if (sec_piece >= 0)
329  {
330  /* See if the formatted seconds round up to 60. If so, increment minutes and reset seconds. */
331  round_pow = pow(10, sec_dec_digits);
332  if (floorf(seconds * round_pow) / round_pow >= 60)
333  {
334  minutes += 1;
335  seconds = 0;
336  }
337  }
338  }
339 
340  /* Handle the compass direction. If not using compass dir, display degrees as a positive/negative number. */
341  if (compass_dir_piece >= 0)
342  {
343  strcpy(pieces[compass_dir_piece], is_negative ? neg_dir_symbol : pos_dir_symbol);
344  }
345  else if (is_negative)
346  {
347  degrees *= -1;
348  }
349 
350  /* Format the degrees into their string piece. */
351  if (deg_digits + deg_dec_digits + 2 > WORK_SIZE)
352  {
353  lwerror("Bad format, degrees (DD.DDD) number of digits was greater than our working limit.");
354  }
355  if(deg_piece >= 0)
356  {
357  sprintf(pieces[deg_piece], "%*.*f", deg_digits, deg_dec_digits, degrees);
358  }
359 
360  if (min_piece >= 0)
361  {
362  /* Format the minutes into their string piece. */
363  if (min_digits + min_dec_digits + 2 > WORK_SIZE)
364  {
365  lwerror("Bad format, minutes (MM.MMM) number of digits was greater than our working limit.");
366  }
367  sprintf(pieces[min_piece], "%*.*f", min_digits, min_dec_digits, minutes);
368  }
369  if (sec_piece >= 0)
370  {
371  /* Format the seconds into their string piece. */
372  if (sec_digits + sec_dec_digits + 2 > WORK_SIZE)
373  {
374  lwerror("Bad format, seconds (SS.SSS) number of digits was greater than our working limit.");
375  }
376  sprintf(pieces[sec_piece], "%*.*f", sec_digits, sec_dec_digits, seconds);
377  }
378 
379  /* Allocate space for the result. Leave plenty of room for excess digits, negative sign, etc.*/
380  result = (char*)lwalloc(format_length + WORK_SIZE);
381  memset(result, 0, format_length + WORK_SIZE);
382 
383  /* Append all the pieces together. There may be less than 9, but in that case the rest will be blank. */
384  strcpy(result, pieces[0]);
385  for (index = 1; index < NUM_PIECES; index++)
386  {
387  strcat(result, pieces[index]);
388  }
389 
390  return result;
391 }
void * lwalloc(size_t size)
Definition: lwutil.c:229
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
#define WORK_SIZE
#define NUM_PIECES

References lwalloc(), lwerror(), NUM_PIECES, and WORK_SIZE.

Referenced by lwdoubles_to_latlon().

Here is the call graph for this function:
Here is the caller graph for this function: