PostGIS  2.5.0dev-r@@SVN_REVISION@@
lwprint.c
Go to the documentation of this file.
1 /**********************************************************************
2  *
3  * PostGIS - Spatial Types for PostgreSQL
4  * http://postgis.net
5  *
6  * PostGIS is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * PostGIS is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18  *
19  **********************************************************************
20  *
21  * Copyright (C) 2010-2015 Paul Ramsey <pramsey@cleverelephant.ca>
22  * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
23  *
24  **********************************************************************/
25
26
27 #include <stdio.h>
28 #include <string.h>
29 #include <math.h>
30 #include "liblwgeom_internal.h"
31
32 /* Ensures the given lat and lon are in the "normal" range:
33  * -90 to +90 for lat, -180 to +180 for lon. */
34 static void lwprint_normalize_latlon(double *lat, double *lon)
35 {
36  /* First remove all the truly excessive trips around the world via up or down. */
37  while (*lat > 270)
38  {
39  *lat -= 360;
40  }
41  while (*lat < -270)
42  {
43  *lat += 360;
44  }
45
46  /* Now see if latitude is past the top or bottom of the world.
47  * Past 90 or -90 puts us on the other side of the earth,
48  * so wrap latitude and add 180 to longitude to reflect that. */
49  if (*lat > 90)
50  {
51  *lat = 180 - *lat;
52  *lon += 180;
53  }
54  if (*lat < -90)
55  {
56  *lat = -180 - *lat;
57  *lon += 180;
58  }
59  /* Now make sure lon is in the normal range. Wrapping longitude
60  * has no effect on latitude. */
61  while (*lon > 180)
62  {
63  *lon -= 360;
64  }
65  while (*lon < -180)
66  {
67  *lon += 360;
68  }
69 }
70
71 /* Converts a single double to DMS given the specified DMS format string.
72  * Symbols are specified since N/S or E/W are the only differences when printing
73  * lat vs. lon. They are only used if the "C" (compass dir) token appears in the
74  * format string.
75  * NOTE: Format string and symbols are required to be in UTF-8. */
76 static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char * format)
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  /* Append all the pieces together. There may be less than 9, but in that case the rest will be blank. */
382  strcpy(result, pieces[0]);
383  for (index = 1; index < NUM_PIECES; index++)
384  {
385  strcat(result, pieces[index]);
386  }
387
388  return result;
389 }
390
391 /* Print two doubles (lat and lon) in DMS form using the specified format.
392  * First normalizes them so they will display as -90 to 90 and -180 to 180.
393  * Format string may be null or 0-length, in which case a default format will be used.
394  * NOTE: Format string is required to be in UTF-8.
395  * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up
396  */
397 static char * lwdoubles_to_latlon(double lat, double lon, const char * format)
398 {
399  char * lat_text;
400  char * lon_text;
401  char * result;
402
403  /* Normalize lat/lon to the normal (-90 to 90, -180 to 180) range. */
404  lwprint_normalize_latlon(&lat, &lon);
405  /* This is somewhat inefficient as the format is parsed twice. */
406  lat_text = lwdouble_to_dms(lat, "N", "S", format);
407  lon_text = lwdouble_to_dms(lon, "E", "W", format);
408
409  /* lat + lon + a space between + the null terminator. */
410  result = (char*)lwalloc(strlen(lat_text) + strlen(lon_text) + 2);
411  sprintf(result, "%s %s", lat_text, lon_text);
412  lwfree(lat_text);
413  lwfree(lon_text);
414  return result;
415 }
416
417 /* Print the X (lon) and Y (lat) of the given point in DMS form using
418  * the specified format.
419  * First normalizes the values so they will display as -90 to 90 and -180 to 180.
420  * Format string may be null or 0-length, in which case a default format will be used.
421  * NOTE: Format string is required to be in UTF-8.
422  * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up
423  */
424 char* lwpoint_to_latlon(const LWPOINT * pt, const char *format)
425 {
426  const POINT2D *p;
427  if (NULL == pt)
428  {
429  lwerror("Cannot convert a null point into formatted text.");
430  }
431  if (lwgeom_is_empty((LWGEOM *)pt))
432  {
433  lwerror("Cannot convert an empty point into formatted text.");
434  }
435  p = getPoint2d_cp(pt->point, 0);
436  return lwdoubles_to_latlon(p->y, p->x, format);
437 }
438
439 /*
440  * Removes trailing zeros and dot for a %f formatted number.
441  * Modifies input.
442  */
443 static void
445 {
446  char *ptr, *totrim = NULL;
447  int len;
448  int i;
449
450  LWDEBUGF(3, "input: %s", str);
451
452  ptr = strchr(str, '.');
453  if (!ptr) return; /* no dot, no decimal digits */
454
455  LWDEBUGF(3, "ptr: %s", ptr);
456
457  len = strlen(ptr);
458  for (i = len - 1; i; i--)
459  {
460  if (ptr[i] != '0') break;
461  totrim = &ptr[i];
462  }
463  if (totrim)
464  {
465  if (ptr == totrim - 1)
466  *ptr = '\0';
467  else
468  *totrim = '\0';
469  }
470
471  LWDEBUGF(3, "output: %s", str);
472 }
473
474 /*
475  * Print an ordinate value using at most the given number of decimal digits
476  *
477  * The actual number of printed decimal digits may be less than the
478  * requested ones if out of significant digits.
479  *
480  * The function will not write more than maxsize bytes, including the
481  * terminating NULL. Returns the number of bytes that would have been
482  * written if there was enough space (excluding terminating NULL).
483  * So a return of ``bufsize'' or more means that the string was
484  * truncated and misses a terminating NULL.
485  *
486  */
487 int
488 lwprint_double(double d, int maxdd, char* buf, size_t bufsize)
489 {
490  double ad = fabs(d);
491  int ndd = ad < 1 ? 0 : floor(log10(ad)) + 1; /* non-decimal digits */
492  int length = 0;
493  if (ad <= FP_TOLERANCE)
494  {
495  d = 0;
496  ad = 0;
497  }
498  if (ad < OUT_MAX_DOUBLE)
499  {
500  if (maxdd > (OUT_MAX_DOUBLE_PRECISION - ndd)) maxdd -= ndd;
501  length = snprintf(buf, bufsize, "%.*f", maxdd, d);
502  }
503  else
504  {
505  length = snprintf(buf, bufsize, "%g", d);
506  }
507  assert(length < bufsize);
508  trim_trailing_zeros(buf);
509  return length;
510 }
void lwfree(void *mem)
Definition: lwutil.c:244
#define OUT_MAX_DOUBLE_PRECISION
char * lwpoint_to_latlon(const LWPOINT *pt, const char *format)
Definition: lwprint.c:424
POINTARRAY * point
Definition: liblwgeom.h:410
static char * lwdoubles_to_latlon(double lat, double lon, const char *format)
Definition: lwprint.c:397
int lwprint_double(double d, int maxdd, char *buf, size_t bufsize)
Definition: lwprint.c:488
double x
Definition: liblwgeom.h:327
#define NUM_PIECES
#define WORK_SIZE
static void lwprint_normalize_latlon(double *lat, double *lon)
Definition: lwprint.c:34
#define FP_TOLERANCE
Floating point comparators.
double y
Definition: liblwgeom.h:327
#define OUT_MAX_DOUBLE
static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char *format)
Definition: lwprint.c:76
void * lwalloc(size_t size)
Definition: lwutil.c:229
int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members) ...
Definition: lwgeom.c:1386
#define LWDEBUGF(level, msg,...)
Definition: lwgeom_log.h:88
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
static void trim_trailing_zeros(char *str)
Definition: lwprint.c:444
const POINT2D * getPoint2d_cp(const POINTARRAY *pa, uint32_t n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from...
Definition: lwgeom_api.c:364