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