PostGIS  2.2.8dev-r@@SVN_REVISION@@
lwprint.c
Go to the documentation of this file.
1 /**********************************************************************
2  *
3  * This is free software; you can redistribute and/or modify it under
4  * the terms of the GNU General Public Licence. See the COPYING file.
5  *
6  **********************************************************************/
7 
8 #include <stdio.h>
9 #include <string.h>
10 #include "liblwgeom_internal.h"
11 
12 /* Ensures the given lat and lon are in the "normal" range:
13  * -90 to +90 for lat, -180 to +180 for lon. */
14 static void lwprint_normalize_latlon(double *lat, double *lon)
15 {
16  /* First remove all the truly excessive trips around the world via up or down. */
17  while (*lat > 270)
18  {
19  *lat -= 360;
20  }
21  while (*lat < -270)
22  {
23  *lat += 360;
24  }
25 
26  /* Now see if latitude is past the top or bottom of the world.
27  * Past 90 or -90 puts us on the other side of the earth,
28  * so wrap latitude and add 180 to longitude to reflect that. */
29  if (*lat > 90)
30  {
31  *lat = 180 - *lat;
32  *lon += 180;
33  }
34  if (*lat < -90)
35  {
36  *lat = -180 - *lat;
37  *lon += 180;
38  }
39  /* Now make sure lon is in the normal range. Wrapping longitude
40  * has no effect on latitude. */
41  while (*lon > 180)
42  {
43  *lon -= 360;
44  }
45  while (*lon < -180)
46  {
47  *lon += 360;
48  }
49 }
50 
51 /* Converts a single double to DMS given the specified DMS format string.
52  * Symbols are specified since N/S or E/W are the only differences when printing
53  * lat vs. lon. They are only used if the "C" (compass dir) token appears in the
54  * format string.
55  * NOTE: Format string and symbols are required to be in UTF-8. */
56 static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char * format)
57 {
58  /* 3 numbers, 1 sign or compass dir, and 5 possible strings (degree signs, spaces, misc text, etc) between or around them.*/
59  static int NUM_PIECES = 9;
60  static int WORK_SIZE = 1024;
61  char pieces[NUM_PIECES][WORK_SIZE];
62  int current_piece = 0;
63  int is_negative = 0;
64 
65  double degrees = 0.0;
66  double minutes = 0.0;
67  double seconds = 0.0;
68 
69  int compass_dir_piece = -1;
70 
71  int reading_deg = 0;
72  int deg_digits = 0;
73  int deg_has_decpoint = 0;
74  int deg_dec_digits = 0;
75  int deg_piece = -1;
76 
77  int reading_min = 0;
78  int min_digits = 0;
79  int min_has_decpoint = 0;
80  int min_dec_digits = 0;
81  int min_piece = -1;
82 
83  int reading_sec = 0;
84  int sec_digits = 0;
85  int sec_has_decpoint = 0;
86  int sec_dec_digits = 0;
87  int sec_piece = -1;
88 
89  int format_length = ((NULL == format) ? 0 : strlen(format));
90 
91  char * result;
92 
93  int index, following_byte_index;
94  int multibyte_char_width = 1;
95 
96  /* Initialize the working strs to blank. We may not populate all of them, and
97  * this allows us to concat them all at the end without worrying about how many
98  * we actually needed. */
99  for (index = 0; index < NUM_PIECES; index++)
100  {
101  pieces[index][0] = '\0';
102  }
103 
104  /* If no format is provided, use a default. */
105  if (0 == format_length)
106  {
107  /* C2B0 is UTF-8 for the degree symbol. */
108  format = "D\xC2\xB0""M'S.SSS\"C";
109  format_length = strlen(format);
110  }
111  else if (format_length > WORK_SIZE)
112  {
113  /* Sanity check, we don't want to overwrite an entire piece of work and no one should need a 1K-sized
114  * format string anyway. */
115  lwerror("Bad format, exceeds maximum length (%d).", WORK_SIZE);
116  }
117 
118  for (index = 0; index < format_length; index++)
119  {
120  char next_char = format[index];
121  switch (next_char)
122  {
123  case 'D':
124  if (reading_deg)
125  {
126  /* If we're reading degrees, add another digit. */
127  deg_has_decpoint ? deg_dec_digits++ : deg_digits++;
128  }
129  else
130  {
131  /* If we're not reading degrees, we are now. */
132  current_piece++;
133  deg_piece = current_piece;
134  if (deg_digits > 0)
135  {
136  lwerror("Bad format, cannot include degrees (DD.DDD) more than once.");
137  }
138  reading_deg = 1;
139  reading_min = 0;
140  reading_sec = 0;
141  deg_digits++;
142  }
143  break;
144  case 'M':
145  if (reading_min)
146  {
147  /* If we're reading minutes, add another digit. */
148  min_has_decpoint ? min_dec_digits++ : min_digits++;
149  }
150  else
151  {
152  /* If we're not reading minutes, we are now. */
153  current_piece++;
154  min_piece = current_piece;
155  if (min_digits > 0)
156  {
157  lwerror("Bad format, cannot include minutes (MM.MMM) more than once.");
158  }
159  reading_deg = 0;
160  reading_min = 1;
161  reading_sec = 0;
162  min_digits++;
163  }
164  break;
165  case 'S':
166  if (reading_sec)
167  {
168  /* If we're reading seconds, add another digit. */
169  sec_has_decpoint ? sec_dec_digits++ : sec_digits++;
170  }
171  else
172  {
173  /* If we're not reading seconds, we are now. */
174  current_piece++;
175  sec_piece = current_piece;
176  if (sec_digits > 0)
177  {
178  lwerror("Bad format, cannot include seconds (SS.SSS) more than once.");
179  }
180  reading_deg = 0;
181  reading_min = 0;
182  reading_sec = 1;
183  sec_digits++;
184  }
185  break;
186  case 'C':
187  /* We're done reading anything else we might have been reading. */
188  if (reading_deg || reading_min || reading_sec)
189  {
190  /* We were reading something, that means this is the next piece. */
191  reading_deg = 0;
192  reading_min = 0;
193  reading_sec = 0;
194  }
195  current_piece++;
196 
197  if (compass_dir_piece >= 0)
198  {
199  lwerror("Bad format, cannot include compass dir (C) more than once.");
200  }
201  /* The compass dir is a piece all by itself. */
202  compass_dir_piece = current_piece;
203  current_piece++;
204  break;
205  case '.':
206  /* If we're reading deg, min, or sec, we want a decimal point for it. */
207  if (reading_deg)
208  {
209  deg_has_decpoint = 1;
210  }
211  else if (reading_min)
212  {
213  min_has_decpoint = 1;
214  }
215  else if (reading_sec)
216  {
217  sec_has_decpoint = 1;
218  }
219  else
220  {
221  /* Not reading anything, just pass through the '.' */
222  strncat(pieces[current_piece], &next_char, 1);
223  }
224  break;
225  default:
226  /* Any other char is just passed through unchanged. But it does mean we are done reading D, M, or S.*/
227  if (reading_deg || reading_min || reading_sec)
228  {
229  /* We were reading something, that means this is the next piece. */
230  current_piece++;
231  reading_deg = 0;
232  reading_min = 0;
233  reading_sec = 0;
234  }
235 
236  /* Check if this is a multi-byte UTF-8 character. If so go ahead and read the rest of the bytes as well. */
237  multibyte_char_width = 1;
238  if (next_char & 0x80)
239  {
240  if ((next_char & 0xF8) == 0xF0)
241  {
242  multibyte_char_width += 3;
243  }
244  else if ((next_char & 0xF0) == 0xE0)
245  {
246  multibyte_char_width += 2;
247  }
248  else if ((next_char & 0xE0) == 0xC0)
249  {
250  multibyte_char_width += 1;
251  }
252  else
253  {
254  lwerror("Bad format, invalid high-order byte found first, format string may not be UTF-8.");
255  }
256  }
257  if (multibyte_char_width > 1)
258  {
259  if (index + multibyte_char_width >= format_length)
260  {
261  lwerror("Bad format, UTF-8 character first byte found with insufficient following bytes, format string may not be UTF-8.");
262  }
263  for (following_byte_index = (index + 1); following_byte_index < (index + multibyte_char_width); following_byte_index++)
264  {
265  if ((format[following_byte_index] & 0xC0) != 0x80)
266  {
267  lwerror("Bad format, invalid byte found following leading byte of multibyte character, format string may not be UTF-8.");
268  }
269  }
270  }
271  /* Copy all the character's bytes into the current piece. */
272  strncat(pieces[current_piece], &(format[index]), multibyte_char_width);
273  /* Now increment index past the rest of those bytes. */
274  index += multibyte_char_width - 1;
275  break;
276  }
277  if (current_piece >= NUM_PIECES)
278  {
279  lwerror("Internal error, somehow needed more pieces than it should.");
280  }
281  }
282  if (deg_piece < 0)
283  {
284  lwerror("Bad format, degrees (DD.DDD) must be included.");
285  }
286 
287  /* Divvy the number up into D, DM, or DMS */
288  if (val < 0)
289  {
290  val *= -1;
291  is_negative = 1;
292  }
293  degrees = val;
294  if (min_digits > 0)
295  {
296  degrees = (long)degrees;
297  minutes = (val - degrees) * 60;
298  }
299  if (sec_digits > 0)
300  {
301  if (0 == min_digits)
302  {
303  lwerror("Bad format, cannot include seconds (SS.SSS) without including minutes (MM.MMM).");
304  }
305  minutes = (long)minutes;
306  seconds = (val - (degrees + (minutes / 60))) * 3600;
307  }
308 
309  /* Handle the compass direction. If not using compass dir, display degrees as a positive/negative number. */
310  if (compass_dir_piece >= 0)
311  {
312  strcpy(pieces[compass_dir_piece], is_negative ? neg_dir_symbol : pos_dir_symbol);
313  }
314  else if (is_negative)
315  {
316  degrees *= -1;
317  }
318 
319  /* Format the degrees into their string piece. */
320  if (deg_digits + deg_dec_digits + 2 > WORK_SIZE)
321  {
322  lwerror("Bad format, degrees (DD.DDD) number of digits was greater than our working limit.");
323  }
324  if(deg_piece >= 0)
325  {
326  sprintf(pieces[deg_piece], "%*.*f", deg_digits, deg_dec_digits, degrees);
327  }
328 
329  if (min_piece >= 0)
330  {
331  /* Format the minutes into their string piece. */
332  if (min_digits + min_dec_digits + 2 > WORK_SIZE)
333  {
334  lwerror("Bad format, minutes (MM.MMM) number of digits was greater than our working limit.");
335  }
336  sprintf(pieces[min_piece], "%*.*f", min_digits, min_dec_digits, minutes);
337  }
338  if (sec_piece >= 0)
339  {
340  /* Format the seconds into their string piece. */
341  if (sec_digits + sec_dec_digits + 2 > WORK_SIZE)
342  {
343  lwerror("Bad format, seconds (SS.SSS) number of digits was greater than our working limit.");
344  }
345  sprintf(pieces[sec_piece], "%*.*f", sec_digits, sec_dec_digits, seconds);
346  }
347 
348  /* Allocate space for the result. Leave plenty of room for excess digits, negative sign, etc.*/
349  result = (char*)lwalloc(format_length + WORK_SIZE);
350  /* Append all the pieces together. There may be less than 9, but in that case the rest will be blank. */
351  strcpy(result, pieces[0]);
352  for (index = 1; index < NUM_PIECES; index++)
353  {
354  strcat(result, pieces[index]);
355  }
356 
357  return result;
358 }
359 
360 /* Print two doubles (lat and lon) in DMS form using the specified format.
361  * First normalizes them so they will display as -90 to 90 and -180 to 180.
362  * Format string may be null or 0-length, in which case a default format will be used.
363  * NOTE: Format string is required to be in UTF-8.
364  * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up
365  */
366 static char * lwdoubles_to_latlon(double lat, double lon, const char * format)
367 {
368  char * lat_text;
369  char * lon_text;
370  char * result;
371 
372  /* Normalize lat/lon to the normal (-90 to 90, -180 to 180) range. */
373  lwprint_normalize_latlon(&lat, &lon);
374  /* This is somewhat inefficient as the format is parsed twice. */
375  lat_text = lwdouble_to_dms(lat, "N", "S", format);
376  lon_text = lwdouble_to_dms(lon, "E", "W", format);
377 
378  /* lat + lon + a space between + the null terminator. */
379  result = (char*)lwalloc(strlen(lat_text) + strlen(lon_text) + 2);
380  sprintf(result, "%s %s", lat_text, lon_text);
381  lwfree(lat_text);
382  lwfree(lon_text);
383  return result;
384 }
385 
386 /* Print the X (lon) and Y (lat) of the given point in DMS form using
387  * the specified format.
388  * First normalizes the values so they will display as -90 to 90 and -180 to 180.
389  * Format string may be null or 0-length, in which case a default format will be used.
390  * NOTE: Format string is required to be in UTF-8.
391  * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up
392  */
393 char* lwpoint_to_latlon(const LWPOINT * pt, const char *format)
394 {
395  const POINT2D *p;
396  if (NULL == pt)
397  {
398  lwerror("Cannot convert a null point into formatted text.");
399  }
400  if (lwgeom_is_empty((LWGEOM *)pt))
401  {
402  lwerror("Cannot convert an empty point into formatted text.");
403  }
404  p = getPoint2d_cp(pt->point, 0);
405  return lwdoubles_to_latlon(p->y, p->x, format);
406 }
void lwfree(void *mem)
Definition: lwutil.c:214
char * lwpoint_to_latlon(const LWPOINT *pt, const char *format)
Definition: lwprint.c:393
static char * lwdoubles_to_latlon(double lat, double lon, const char *format)
Definition: lwprint.c:366
double x
Definition: liblwgeom.h:312
const POINT2D * getPoint2d_cp(const POINTARRAY *pa, int n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from...
Definition: lwgeom_api.c:472
static void lwprint_normalize_latlon(double *lat, double *lon)
Definition: lwprint.c:14
double y
Definition: liblwgeom.h:312
static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char *format)
Definition: lwprint.c:56
void * lwalloc(size_t size)
Definition: lwutil.c:199
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:1297
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:74