]> granicus.if.org Git - postgis/commitdiff
ST_AsLatLonText(geometry, format) from Jeff Adams
authorPaul Ramsey <pramsey@cleverelephant.ca>
Mon, 22 Feb 2010 20:42:05 +0000 (20:42 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Mon, 22 Feb 2010 20:42:05 +0000 (20:42 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@5305 b70326c6-7e19-0410-871a-916f4a2858ee

doc/reference_output.xml
liblwgeom/Makefile.in
liblwgeom/cunit/Makefile.in
liblwgeom/cunit/cu_print.c [new file with mode: 0644]
liblwgeom/cunit/cu_print.h [new file with mode: 0644]
liblwgeom/cunit/cu_tester.c
liblwgeom/cunit/cu_tester.h
liblwgeom/liblwgeom.h
liblwgeom/lwprint.c [new file with mode: 0644]
postgis/lwgeom_inout.c
postgis/postgis.sql.in.c

index e5d95410a5dc13c01a13e6b452e92e13b184c934..6069eefd8b902ef63226327c7bc8d4a27091b15d 100644 (file)
@@ -840,4 +840,103 @@ F000000000000000000000000000000000000000000000000');
                  </refsection>
        </refentry>
 
+
+       <refentry id="ST_AsLatLonText">
+                 <refnamediv>
+                       <refname>ST_AsLatLonText</refname>
+                       <refpurpose>Return the Degrees, Minutes, Seconds representation of the given point.</refpurpose>
+                 </refnamediv>
+
+                 <refsynopsisdiv>
+                       <funcsynopsis>
+                         <funcprototype>
+                               <funcdef>text <function>ST_AsLatLonText</function></funcdef>
+                               <paramdef><type>geometry </type> <parameter>pt</parameter></paramdef>
+                         </funcprototype>
+                         <funcprototype>
+                               <funcdef>text <function>ST_AsLatLonText</function></funcdef>
+                               <paramdef><type>geometry </type> <parameter>pt</parameter></paramdef>
+                               <paramdef><type>text </type> <parameter>format</parameter></paramdef>
+                         </funcprototype>
+                       </funcsynopsis>
+                 </refsynopsisdiv>
+
+                 <refsection>
+                       <title>Description</title>
+
+                       <para>Returns the Degrees, Minutes, Seconds representation of the point.</para>
+
+                       <note>
+                         <para>It is assumed the point is in a lat/lon projection.  The X (lon) and Y (lat) coordinates are normalized in the output
+                               to the "normal" range (-180 to +180 for lon, -90 to +90 for lat).</para>
+                       </note>
+                               <para>
+                                       The text parameter is a format string containing the format for the resulting text, similar to a date format string.  Valid tokens
+                                       are "D" for degrees, "M" for minutes, "S" for seconds, and "C" for cardinal direction (NSEW).  DMS tokens may be repeated to indicate
+                                       desired width and precision ("SSS.SSSS" means "  1.0023").
+                               </para>
+                               <para>
+                                       "M", "S", and "C" are optional.  If "C" is omitted, degrees are
+                                       shown with a "-" sign if south or west.  If "S" is omitted, minutes will be shown as decimal with as many digits of precision
+                                       as you specify.  If "M" is also omitted, degrees are shown as decimal with as many digits precision as you specify.
+                               </para>
+                               <para>
+                                       If the format string is omitted (or zero-length) a default format will be used.
+                               </para>
+                               <para>
+                       </para>
+
+                       <para>Availability: 2.0</para>
+                 </refsection>
+
+
+                 <refsection>
+                       <title>Examples</title>
+Default format.
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-3.2342342 -2.32498)'));
+      st_aslatlontext       
+----------------------------
+ 2°19'29.928"S 3°14'3.243"W
+</programlisting>
+Providing a format (same as the default).
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-3.2342342 -2.32498)', 'D°M''S.SSS"C'));
+      st_aslatlontext       
+----------------------------
+ 2°19'29.928"S 3°14'3.243"W
+</programlisting>
+Characters other than D, M, S, C and . are just passed through.
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-3.2342342 -2.32498)', 'D degrees, M minutes, S seconds to the C'));
+                                   st_aslatlontext                                    
+--------------------------------------------------------------------------------------
+ 2 degrees, 19 minutes, 30 seconds to the S 3 degrees, 14 minutes, 3 seconds to the W
+</programlisting>
+Signed degrees instead of cardinal directions.
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-3.2342342 -2.32498)', 'D°M''S.SSS"'));
+      st_aslatlontext       
+----------------------------
+ -2°19'29.928" -3°14'3.243"
+</programlisting>
+Decimal degrees.
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-3.2342342 -2.32498)', 'D.DDDD degrees C'));
+          st_aslatlontext          
+-----------------------------------
+ 2.3250 degrees S 3.2342 degrees W
+</programlisting>
+Excessively large values are normalized.
+<programlisting>
+SELECT (ST_AsLatLonText('POINT (-302.2342342 -792.32498)'));
+        st_aslatlontext        
+-------------------------------
+ 72°19'29.928"S 57°45'56.757"E
+</programlisting>
+                 </refsection>
+
+                 <!-- Optionally add a "See Also" section -->
+       </refentry>
+
   </sect1>
index 904891063206f5749bb7222b9b68fd230d5ab031..7973df8293fe6a1439c23b030abd54f043a8ee62 100644 (file)
@@ -44,6 +44,7 @@ SA_OBJS = \
        lwgunparse.o \
        lwgparse.o \
        lwsegmentize.o \
+       lwprint.o \
        wktparse.tab.o \
        lex.yy.o \
        vsprintf.o \
index 751e29ef4c2539653b772d99d922f209e2615788..919478f64d90bcdfb53d3a6d6b4cde35730092db 100644 (file)
@@ -18,6 +18,7 @@ CUNIT_CPPFLAGS=@CUNIT_CPPFLAGS@ -I..
 
 OBJS=  \
        cu_algorithm.o \
+       cu_print.o \
        cu_wkt.o \
        cu_geodetic.o \
        cu_measures.o \
diff --git a/liblwgeom/cunit/cu_print.c b/liblwgeom/cunit/cu_print.c
new file mode 100644 (file)
index 0000000..0b2f6b8
--- /dev/null
@@ -0,0 +1,197 @@
+/**********************************************************************
+ * $Id: cu_print.c 5181 2010-02-01 17:35:55Z pramsey $
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.refractions.net
+ * Copyright 2008 Paul Ramsey
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include "cu_print.h"
+
+/*
+** Called from test harness to register the tests in this file.
+*/
+CU_pSuite register_print_suite(void)
+{
+       CU_pSuite pSuite;
+       pSuite = CU_add_suite("PostGIS Print Suite", init_print_suite, clean_print_suite);
+       if (NULL == pSuite)
+       {
+               CU_cleanup_registry();
+               return NULL;
+       }
+
+       if (
+           (NULL == CU_add_test(pSuite, "test_lwprint_default_format()", test_lwprint_default_format)) ||
+           (NULL == CU_add_test(pSuite, "test_lwprint_format_order()", test_lwprint_format_order)) ||
+           (NULL == CU_add_test(pSuite, "test_lwprint_format_optional()", test_lwprint_format_optional)) ||
+           (NULL == CU_add_test(pSuite, "test_lwprint_oddball_format()", test_lwprint_oddball_format)) ||
+           (NULL == CU_add_test(pSuite, "test_lwprint_bad_formats()", test_lwprint_bad_formats))
+       )
+       {
+               CU_cleanup_registry();
+               return NULL;
+       }
+       return pSuite;
+}
+
+
+/*
+** The suite initialization function.
+** Create any re-used objects.
+*/
+int init_print_suite(void)
+{
+       return 0;
+}
+
+/*
+** The suite cleanup function.
+** Frees any global objects.
+*/
+int clean_print_suite(void)
+{
+       return 0;
+}
+
+static void test_lwprint_assert_format(char * point_wkt, const char * format, const char * expected)
+{
+       LWPOINT * test_point = (LWPOINT*)lwgeom_from_ewkt(point_wkt, PARSER_CHECK_NONE);
+       int num_old_failures, num_new_failures;
+       char * actual;
+       cu_error_msg_reset();
+       actual = lwpoint_to_latlon(test_point, format);
+       if (0 != strlen(cu_error_msg))
+       {
+               printf("\nAssert failed:\n\tFormat [%s] generated an error: %s\n", format, cu_error_msg);
+               CU_FAIL();
+       }
+       num_old_failures = CU_get_number_of_failures();
+       CU_ASSERT_STRING_EQUAL(actual, expected);
+       num_new_failures = CU_get_number_of_failures();
+       if (num_new_failures > num_old_failures)
+       {
+               printf("\nAssert failed:\n\t%s\t(actual)\n\t%s\t(expected)\n", actual, expected);
+       }
+       lwfree(test_point);
+}
+static void test_lwprint_assert_error(char * point_wkt, const char * format)
+{
+       LWPOINT * test_point = (LWPOINT*)lwgeom_from_ewkt(point_wkt, PARSER_CHECK_NONE);
+       cu_error_msg_reset();
+       lwpoint_to_latlon(test_point, format);
+       if (0 == strlen(cu_error_msg))
+       {
+               printf("\nAssert failed:\n\tFormat [%s] did not generate an error.\n", format);
+               CU_FAIL();
+       }
+       else
+       {
+               cu_error_msg_reset();
+       }
+       lwfree(test_point);
+}
+
+/*
+** Test points around the globe using the default format.  Null and empty string both mean use the default.
+*/
+void test_lwprint_default_format(void)
+{
+       test_lwprint_assert_format("POINT(0 0)",                NULL, "0\xC2\xB0""0'0.000\"N 0\xC2\xB0""0'0.000\"E");
+       test_lwprint_assert_format("POINT(45.4545 12.34567)",   ""  , "12\xC2\xB0""20'44.412\"N 45\xC2\xB0""27'16.200\"E");
+       test_lwprint_assert_format("POINT(180 90)",             NULL, "90\xC2\xB0""0'0.000\"N 180\xC2\xB0""0'0.000\"E");
+       test_lwprint_assert_format("POINT(181 91)",             ""  , "89\xC2\xB0""0'0.000\"N 1\xC2\xB0""0'0.000\"E");
+       test_lwprint_assert_format("POINT(180.0001 90.0001)",   NULL, "89\xC2\xB0""59'59.640\"N 0\xC2\xB0""0'0.360\"E");
+       test_lwprint_assert_format("POINT(45.4545 -12.34567)",  ""  , "12\xC2\xB0""20'44.412\"S 45\xC2\xB0""27'16.200\"E");
+       test_lwprint_assert_format("POINT(180 -90)",            NULL, "90\xC2\xB0""0'0.000\"S 180\xC2\xB0""0'0.000\"E");
+       test_lwprint_assert_format("POINT(181 -91)",            ""  , "89\xC2\xB0""0'0.000\"S 1\xC2\xB0""0'0.000\"E");
+       test_lwprint_assert_format("POINT(180.0001 -90.0001)",  NULL, "89\xC2\xB0""59'59.640\"S 0\xC2\xB0""0'0.360\"E");
+       test_lwprint_assert_format("POINT(-45.4545 12.34567)",  ""  , "12\xC2\xB0""20'44.412\"N 45\xC2\xB0""27'16.200\"W");
+       test_lwprint_assert_format("POINT(-180 90)",            NULL, "90\xC2\xB0""0'0.000\"N 180\xC2\xB0""0'0.000\"W");
+       test_lwprint_assert_format("POINT(-181 91)",            ""  , "89\xC2\xB0""0'0.000\"N 1\xC2\xB0""0'0.000\"W");
+       test_lwprint_assert_format("POINT(-180.0001 90.0001)",  NULL, "89\xC2\xB0""59'59.640\"N 0\xC2\xB0""0'0.360\"W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", ""  , "12\xC2\xB0""20'44.412\"S 45\xC2\xB0""27'16.200\"W");
+       test_lwprint_assert_format("POINT(-180 -90)",           NULL, "90\xC2\xB0""0'0.000\"S 180\xC2\xB0""0'0.000\"W");
+       test_lwprint_assert_format("POINT(-181 -91)",           ""  , "89\xC2\xB0""0'0.000\"S 1\xC2\xB0""0'0.000\"W");
+       test_lwprint_assert_format("POINT(-180.0001 -90.0001)", NULL, "89\xC2\xB0""59'59.640\"S 0\xC2\xB0""0'0.360\"W");
+       test_lwprint_assert_format("POINT(-2348982391.123456 -238749827.34879)", ""  , "12\xC2\xB0""39'4.356\"N 31\xC2\xB0""7'24.442\"W");
+}
+
+/*
+ * Test all possible combinations of the orders of the parameters.
+ */
+void test_lwprint_format_order(void)
+{
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C DD MM SS", "S 12 20 44 W 45 27 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C DD SS MM", "S 12 44 20 W 45 16 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C MM DD SS", "S 20 12 44 W 27 45 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C MM SS DD", "S 20 44 12 W 27 16 45");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C SS DD MM", "S 44 12 20 W 16 45 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "C SS MM DD", "S 44 20 12 W 16 27 45");
+
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD C MM SS", "12 S 20 44 45 W 27 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD C SS MM", "12 S 44 20 45 W 16 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM C DD SS", "20 S 12 44 27 W 45 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM C SS DD", "20 S 44 12 27 W 16 45");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS C DD MM", "44 S 12 20 16 W 45 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS C MM DD", "44 S 20 12 16 W 27 45");
+
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD MM C SS", "12 20 S 44 45 27 W 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD SS C MM", "12 44 S 20 45 16 W 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM DD C SS", "20 12 S 44 27 45 W 16");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM SS C DD", "20 44 S 12 27 16 W 45");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS DD C MM", "44 12 S 20 16 45 W 27");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS MM C DD", "44 20 S 12 16 27 W 45");
+
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD MM SS C", "12 20 44 S 45 27 16 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD SS MM C", "12 44 20 S 45 16 27 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM DD SS C", "20 12 44 S 27 45 16 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "MM SS DD C", "20 44 12 S 27 16 45 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS DD MM C", "44 12 20 S 16 45 27 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "SS MM DD C", "44 20 12 S 16 27 45 W");
+}
+
+/*
+ * Test with and without the optional parameters.
+ */
+void test_lwprint_format_optional(void)
+{
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD", "-12.346 -45.455");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD C", "12.346 S 45.455 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM", "-12.000 20.740 -45.000 27.270");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM C", "12.000 20.740 S 45.000 27.270 W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM SS.SSS", "-12.000 20.000 44.412 -45.000 27.000 16.200");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM SS.SSS C", "12.000 20.000 44.412 S 45.000 27.000 16.200 W");
+}
+
+void test_lwprint_oddball_format(void)
+{
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.DDDMM.MMMSS.SSSC", "12.00020.00044.412S 45.00027.00016.200W");
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DDMM.MMM", "-1220.740 -4527.270");
+       /* "##." will be printed as "##" */
+       test_lwprint_assert_format("POINT(-45.4545 -12.34567)", "DD.MM.MMM", "-1220.740 -4527.270");
+}
+
+/*
+ * Test using formats that should produce errors.
+ */
+void test_lwprint_bad_formats(void)
+{
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD.DDD SS.SSS");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "MM.MMM SS.SSS");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD.DDD SS.SSS DD");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD MM SS MM");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD MM SS SS");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C DD.DDD C");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C \xC2""DD.DDD");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C DD.DDD \xC2");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C DD\x80""MM ");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C DD \xFF""MM");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "C DD \xB0""MM");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD.DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");
+       test_lwprint_assert_error("POINT(1.23456 7.89012)", "DD.DDD jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj");
+}
diff --git a/liblwgeom/cunit/cu_print.h b/liblwgeom/cunit/cu_print.h
new file mode 100644 (file)
index 0000000..ed87cd5
--- /dev/null
@@ -0,0 +1,30 @@
+/**********************************************************************
+ * $Id: cu_print.h 4786 2009-11-11 19:02:19Z pramsey $
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.refractions.net
+ * Copyright 2008 Paul Ramsey
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "CUnit/Basic.h"
+
+#include "liblwgeom.h"
+#include "cu_tester.h"
+
+/***********************************************************************
+** for Print Suite
+*/
+
+/* Test functions */
+void test_lwprint_default_format(void);
+void test_lwprint_format_order(void);
+void test_lwprint_format_optional(void);
+void test_lwprint_bad_formats(void);
+void test_lwprint_oddball_format(void);
index f157278804685140b7d3ccc03cddb7e90d6ed313..4c65e22b8086115be5882e3cc8104353dcf1c93a 100644 (file)
@@ -106,6 +106,13 @@ int main()
                return CU_get_error();
        }
 
+       /* Add the print suite to the registry */
+       if (NULL == register_print_suite())
+       {
+               CU_cleanup_registry();
+               return CU_get_error();
+       }
+
        /* Add the homogenize suite to the registry */
        if (NULL == register_homogenize_suite())
        {
index 7b42a9520845a240bb875c8162a3fc46dd6f018a..04629068bb94a9c2b4fa3ec7d88d424cb9b4863e 100644 (file)
@@ -6,6 +6,7 @@ CU_pSuite register_measures_suite(void);
 CU_pSuite register_geodetic_suite(void);
 CU_pSuite register_libgeom_suite(void);
 CU_pSuite register_cg_suite(void);
+CU_pSuite register_print_suite(void);
 CU_pSuite register_wkt_suite(void);
 CU_pSuite register_homogenize_suite(void);
 CU_pSuite register_out_gml_suite(void);
@@ -17,6 +18,7 @@ int init_measures_suite(void);
 int init_geodetic_suite(void);
 int init_libgeom_suite(void);
 int init_cg_suite(void);
+int init_print_suite(void);
 int init_wkt_suite(void);
 int init_homogenize_suite(void);
 int init_out_gml_suite(void);
@@ -28,6 +30,7 @@ int clean_measures_suite(void);
 int clean_geodetic_suite(void);
 int clean_libgeom_suite(void);
 int clean_cg_suite(void);
+int clean_print_suite(void);
 int clean_wkt_suite(void);
 int clean_homogenize_suite(void);
 int clean_out_gml_suite(void);
index 4c457a2ef0eb457c20a4661d8bebb82bdd8a6606..dfbdb7e3cd5b627edad19bcefa92dced77482af1 100644 (file)
@@ -1233,6 +1233,7 @@ extern int lwpoly_compute_box2d_p(LWPOLY *poly, BOX2DFLOAT4 *box);
 extern int lwcollection_compute_box2d_p(LWCOLLECTION *col, BOX2DFLOAT4 *box);
 extern int lwcircstring_compute_box2d_p(LWCIRCSTRING *curve, BOX2DFLOAT4 *box);
 extern BOX2DFLOAT4 *lwgeom_compute_box2d(LWGEOM *lwgeom);
+extern char * lwpoint_to_latlon(LWPOINT * p, const char * format);
 
 extern void interpolate_point4d(POINT4D *A, POINT4D *B, POINT4D *I, double F);
 
diff --git a/liblwgeom/lwprint.c b/liblwgeom/lwprint.c
new file mode 100644 (file)
index 0000000..4d2abb1
--- /dev/null
@@ -0,0 +1,400 @@
+/**********************************************************************
+ * $Id: lwprint.c 5181 2010-02-01 17:35:55Z pramsey $
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include "liblwgeom.h"
+
+/* Ensures the given lat and lon are in the "normal" range:
+ * -90 to +90 for lat, -180 to +180 for lon. */
+static void lwprint_normalize_latlon(double *lat, double *lon)
+{
+       /* First remove all the truly excessive trips around the world via up or down. */
+       while (*lat > 270)
+       {
+               *lat -= 360;
+       }
+       while (*lat < -270)
+       {
+               *lat += 360;
+       }
+
+       /* Now see if latitude is past the top or bottom of the world.
+        * Past 90  or -90 puts us on the other side of the earth,
+            * so wrap latitude and add 180 to longitude to reflect that. */
+       if (*lat > 90)
+       {
+               *lat = 180 - *lat;
+               *lon += 180;
+       }
+       if (*lat < -90)
+       {
+               *lat = -180 - *lat;
+               *lon += 180;
+       }
+       /* Now make sure lon is in the normal range.  Wrapping longitude
+        * has no effect on latitude. */
+       while (*lon > 180)
+       {
+               *lon -= 360;
+       }
+       while (*lon < -180)
+       {
+               *lon += 360;
+       }
+}
+
+/* Converts a single double to DMS given the specified DMS format string.
+ * Symbols are specified since N/S or E/W are the only differences when printing
+ * lat vs. lon.  They are only used if the "C" (compass dir) token appears in the
+ * format string.
+ * NOTE: Format string and symbols are required to be in UTF-8. */
+static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char * format)
+{
+       /* 3 numbers, 1 sign or compass dir, and 5 possible strings (degree signs, spaces, misc text, etc) between or around them.*/
+       const int NUM_PIECES = 9;
+       const int WORK_SIZE = 1024;
+       char pieces[NUM_PIECES][WORK_SIZE];
+       int current_piece = 0;
+       int is_negative = 0;
+
+       double degrees = 0.0;
+       double minutes = 0.0;
+       double seconds = 0.0;
+
+       int compass_dir_piece = -1;
+
+       int reading_deg = 0;
+       int deg_digits = 0;
+       int deg_has_decpoint = 0;
+       int deg_dec_digits = 0;
+       int deg_piece = -1;
+
+       int reading_min = 0;
+       int min_digits = 0;
+       int min_has_decpoint = 0;
+       int min_dec_digits = 0;
+       int min_piece = -1;
+
+       int reading_sec = 0;
+       int sec_digits = 0;
+       int sec_has_decpoint = 0;
+       int sec_dec_digits = 0;
+       int sec_piece = -1;
+
+       int format_length = ((NULL == format) ? 0 : strlen(format));
+
+       char * result;
+
+       int index, following_byte_index;
+       int multibyte_char_width = 1;
+
+       /* Initialize the working strs to blank.  We may not populate all of them, and
+        * this allows us to concat them all at the end without worrying about how many
+        * we actually needed. */
+       for (index = 0; index < NUM_PIECES; index++)
+       {
+               pieces[index][0] = '\0';
+       }
+
+       /* If no format is provided, use a default. */
+       if (0 == format_length)
+       {
+               /* C2B0 is UTF-8 for the degree symbol. */
+               format = "D\xC2\xB0""M'S.SSS\"C";
+               format_length = strlen(format);
+       }
+       else if (format_length > WORK_SIZE)
+       {
+               /* Sanity check, we don't want to overwrite an entire piece of work and no one should need a 1K-sized
+               * format string anyway. */
+               lwerror("Bad format, exceeds maximum length (%d).", WORK_SIZE);
+       }
+
+       for (index = 0; index < format_length; index++)
+       {
+               char next_char = format[index];
+               switch (next_char)
+               {
+               case 'D':
+                       if (reading_deg)
+                       {
+                               /* If we're reading degrees, add another digit. */
+                               deg_has_decpoint ? deg_dec_digits++ : deg_digits++;
+                       }
+                       else
+                       {
+                               /* If we're not reading degrees, we are now. */
+                               current_piece++;
+                               deg_piece = current_piece;
+                               if (deg_digits > 0)
+                               {
+                                       lwerror("Bad format, cannot include degrees (DD.DDD) more than once.");
+                               }
+                               reading_deg = 1;
+                               reading_min = 0;
+                               reading_sec = 0;
+                               deg_digits++;
+                       }
+                       break;
+               case 'M':
+                       if (reading_min)
+                       {
+                               /* If we're reading minutes, add another digit. */
+                               min_has_decpoint ? min_dec_digits++ : min_digits++;
+                       }
+                       else
+                       {
+                               /* If we're not reading minutes, we are now. */
+                               current_piece++;
+                               min_piece = current_piece;
+                               if (min_digits > 0)
+                               {
+                                       lwerror("Bad format, cannot include minutes (MM.MMM) more than once.");
+                               }
+                               reading_deg = 0;
+                               reading_min = 1;
+                               reading_sec = 0;
+                               min_digits++;
+                       }
+                       break;
+               case 'S':
+                       if (reading_sec)
+                       {
+                               /* If we're reading seconds, add another digit. */
+                               sec_has_decpoint ? sec_dec_digits++ : sec_digits++;
+                       }
+                       else
+                       {
+                               /* If we're not reading seconds, we are now. */
+                               current_piece++;
+                               sec_piece = current_piece;
+                               if (sec_digits > 0)
+                               {
+                                       lwerror("Bad format, cannot include seconds (SS.SSS) more than once.");
+                               }
+                               reading_deg = 0;
+                               reading_min = 0;
+                               reading_sec = 1;
+                               sec_digits++;
+                       }
+                       break;
+               case 'C':
+                       /* We're done reading anything else we might have been reading. */
+                       if (reading_deg || reading_min || reading_sec)
+                       {
+                               /* We were reading something, that means this is the next piece. */
+                               reading_deg = 0;
+                               reading_min = 0;
+                               reading_sec = 0;
+                       }
+                       current_piece++;
+
+                       if (compass_dir_piece >= 0)
+                       {
+                               lwerror("Bad format, cannot include compass dir (C) more than once.");
+                       }
+                       /* The compass dir is a piece all by itself.  */
+                       compass_dir_piece = current_piece;
+                       current_piece++;
+                       break;
+               case '.':
+                       /* If we're reading deg, min, or sec, we want a decimal point for it. */
+                       if (reading_deg)
+                       {
+                               deg_has_decpoint = 1;
+                       }
+                       else if (reading_min)
+                       {
+                               min_has_decpoint = 1;
+                       }
+                       else if (reading_sec)
+                       {
+                               sec_has_decpoint = 1;
+                       }
+                       else
+                       {
+                               /* Not reading anything, just pass through the '.' */
+                               strncat(pieces[current_piece], &next_char, 1);
+                       }
+                       break;
+               default:
+                       /* Any other char is just passed through unchanged.  But it does mean we are done reading D, M, or S.*/
+                       if (reading_deg || reading_min || reading_sec)
+                       {
+                               /* We were reading something, that means this is the next piece. */
+                               current_piece++;
+                               reading_deg = 0;
+                               reading_min = 0;
+                               reading_sec = 0;
+                       }
+
+                       /* Check if this is a multi-byte UTF-8 character.  If so go ahead and read the rest of the bytes as well. */
+                       multibyte_char_width = 1;
+                       if (next_char & 0x80)
+                       {
+                               if ((next_char & 0xF8) == 0xF0)
+                               {
+                                       multibyte_char_width += 3;
+                               }
+                               else if ((next_char & 0xF0) == 0xE0)
+                               {
+                                       multibyte_char_width += 2;
+                               }
+                               else if ((next_char & 0xE0) == 0xC0)
+                               {
+                                       multibyte_char_width += 1;
+                               }
+                               else
+                               {
+                                       lwerror("Bad format, invalid high-order byte found first, format string may not be UTF-8.");
+                               }
+                       }
+                       if (multibyte_char_width > 1)
+                       {
+                               if (index + multibyte_char_width >= format_length)
+                               {
+                                       lwerror("Bad format, UTF-8 character first byte found with insufficient following bytes, format string may not be UTF-8.");
+                               }
+                               for (following_byte_index = (index + 1); following_byte_index < (index + multibyte_char_width); following_byte_index++)
+                               {
+                                       if ((format[following_byte_index] & 0xC0) != 0x80)
+                                       {
+                                               lwerror("Bad format, invalid byte found following leading byte of multibyte character, format string may not be UTF-8.");
+                                       }
+                               }
+                       }
+                       /* Copy all the character's bytes into the current piece. */
+                       strncat(pieces[current_piece], &(format[index]), multibyte_char_width);
+                       /* Now increment index past the rest of those bytes. */
+                       index += multibyte_char_width - 1;
+                       break;
+               }
+               if (current_piece >= NUM_PIECES)
+               {
+                       lwerror("Internal error, somehow needed more pieces than it should.");
+               }
+       }
+       if (deg_piece < 0)
+       {
+               lwerror("Bad format, degrees (DD.DDD) must be included.");
+       }
+
+       /* Divvy the number up into D, DM, or DMS */
+       if (val < 0)
+       {
+               val *= -1;
+               is_negative = 1;
+       }
+       degrees = val;
+       if (min_digits > 0)
+       {
+               degrees = (long)degrees;
+               minutes = (val - degrees) * 60;
+       }
+       if (sec_digits > 0)
+       {
+               if (0 == min_digits)
+               {
+                       lwerror("Bad format, cannot include seconds (SS.SSS) without including minutes (MM.MMM).");
+               }
+               minutes = (long)minutes;
+               seconds = (val - (degrees + (minutes / 60))) * 3600;
+       }
+
+       /* Handle the compass direction.  If not using compass dir, display degrees as a positive/negative number. */
+       if (compass_dir_piece >= 0)
+       {
+               strcpy(pieces[compass_dir_piece], is_negative ? neg_dir_symbol : pos_dir_symbol);
+       }
+       else if (is_negative)
+       {
+               degrees *= -1;
+       }
+
+       /* Format the degrees into their string piece. */
+       if (deg_digits + deg_dec_digits + 2 > WORK_SIZE)
+       {
+               lwerror("Bad format, degrees (DD.DDD) number of digits was greater than our working limit.");
+       }
+       sprintf(pieces[deg_piece], "%*.*f", deg_digits, deg_dec_digits, degrees);
+
+       if (min_piece >= 0)
+       {
+               /* Format the minutes into their string piece. */
+               if (min_digits + min_dec_digits + 2 > WORK_SIZE)
+               {
+                       lwerror("Bad format, minutes (MM.MMM) number of digits was greater than our working limit.");
+               }
+               sprintf(pieces[min_piece], "%*.*f", min_digits, min_dec_digits, minutes);
+       }
+       if (sec_piece >= 0)
+       {
+               /* Format the seconds into their string piece. */
+               if (sec_digits + sec_dec_digits + 2 > WORK_SIZE)
+               {
+                       lwerror("Bad format, seconds (SS.SSS) number of digits was greater than our working limit.");
+               }
+               sprintf(pieces[sec_piece], "%*.*f", sec_digits, sec_dec_digits, seconds);
+       }
+
+       /* Allocate space for the result.  Leave plenty of room for excess digits, negative sign, etc.*/
+       result = (char*)lwalloc(format_length + WORK_SIZE);
+       /* Append all the pieces together. There may be less than 9, but in that case the rest will be blank. */
+       strcpy(result, pieces[0]);
+       for (index = 1; index < NUM_PIECES; index++)
+       {
+               strcat(result, pieces[index]);
+       }
+
+       return result;
+}
+
+/* Print two doubles (lat and lon) in DMS form using the specified format.
+ * First normalizes them so they will display as -90 to 90 and -180 to 180.
+ * Format string may be null or 0-length, in which case a default format will be used.
+ * NOTE: Format string is required to be in UTF-8. */
+static char * lwdoubles_to_latlon(double lat, double lon, const char * format)
+{
+       char * lat_text;
+       char * lon_text;
+       char * result;
+
+       /* Normalize lat/lon to the normal (-90 to 90, -180 to 180) range. */
+       lwprint_normalize_latlon(&lat, &lon);
+       /* This is somewhat inefficient as the format is parsed twice. */
+       lat_text = lwdouble_to_dms(lat, "N", "S", format);
+       lon_text = lwdouble_to_dms(lon, "E", "W", format);
+
+       /* lat + lon + a space between + the null terminator. */
+       result = (char*)lwalloc(strlen(lat_text) + strlen(lon_text) + 2);
+       sprintf(result, "%s %s", lat_text, lon_text);
+       lwfree(lat_text);
+       lwfree(lon_text);
+       return result;
+}
+
+/* Print the X (lon) and Y (lat) of the given point in DMS form using
+ * the specified format.
+ * First normalizes the values so they will display as -90 to 90 and -180 to 180.
+ * Format string may be null or 0-length, in which case a default format will be used.
+ * NOTE: Format string is required to be in UTF-8. */
+char * lwpoint_to_latlon(LWPOINT * pt, const char * format)
+{
+       POINT2D p;
+       if (NULL == pt)
+       {
+               lwerror("Cannot convert a null point into formatted text.");
+       }
+       if (lwgeom_is_empty((LWGEOM *)pt))
+       {
+               lwerror("Cannot convert an empty point into formatted text.");
+       }
+       getPoint2d_p(pt->point, 0, &p);
+       return lwdoubles_to_latlon(p.y, p.x, format);
+}
index f0bbd2e01521bce31a8df0af7fc5712accee3707..bef05b0eb5f2a9bee82a4498ee297298a287947d 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "fmgr.h"
 #include "utils/elog.h"
+#include "mb/pg_wchar.h"
 # include "lib/stringinfo.h" /* for binary input */
 
 
@@ -36,6 +37,7 @@ Datum parse_WKT_lwgeom(PG_FUNCTION_ARGS);
 Datum LWGEOM_recv(PG_FUNCTION_ARGS);
 Datum LWGEOM_send(PG_FUNCTION_ARGS);
 Datum BOOL_to_text(PG_FUNCTION_ARGS);
+Datum LWGEOM_to_latlon(PG_FUNCTION_ARGS);
 
 
 /*
@@ -86,6 +88,89 @@ Datum LWGEOM_in(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(ret);
 }
 
+/*
+ * LWGEOM_to_latlon(GEOMETRY, text)
+ *  NOTE: Geometry must be a point.  It is assumed that the coordinates
+ *        of the point are in a lat/lon projection, and they will be
+ *        normalized in the output to -90-90 and -180-180.
+ *
+ *  The text parameter is a format string containing the format for the
+ *  resulting text, similar to a date format string.  Valid tokens
+ *  are "D" for degrees, "M" for minutes, "S" for seconds, and "C" for
+ *  cardinal direction (NSEW).  DMS tokens may be repeated to indicate
+ *  desired width and precision ("SSS.SSSS" means "  1.0023").
+ *  "M", "S", and "C" are optional.  If "C" is omitted, degrees are
+ *  shown with a "-" sign if south or west.  If "S" is omitted,
+ *  minutes will be shown as decimal with as many digits of precision
+ *  as you specify.  If "M" is omitted, degrees are shown as decimal
+ *  with as many digits precision as you specify.
+ *
+ *  If the format string is omitted (null or 0-length) a default
+ *  format will be used.
+ *
+ *  returns text
+ */
+PG_FUNCTION_INFO_V1(LWGEOM_to_latlon);
+Datum LWGEOM_to_latlon(PG_FUNCTION_ARGS)
+{
+       /* Get the parameters */
+       PG_LWGEOM *pg_lwgeom = (PG_LWGEOM *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+       text *format_text = PG_GETARG_TEXT_P(1);
+
+       LWGEOM *lwgeom;
+       char *format_str = NULL;
+       char *format_str_utf8 = NULL;
+
+       size_t str_size;
+
+       char * formatted_str_utf8;
+       char * formatted_str;
+       char * formatted_text;
+
+       /* Only supports points. */
+       uchar geom_type = TYPE_GETTYPE(pg_lwgeom->type);
+       if (POINTTYPE != geom_type)
+       {
+               lwerror("Only points are supported, you tried type %s.", lwgeom_typename(geom_type));
+       }
+       /* Convert to LWGEOM type */
+       lwgeom = lwgeom_deserialize(SERIALIZED_FORM(pg_lwgeom));
+
+       if (format_text != NULL)
+       {
+               str_size = VARSIZE(format_text)-VARHDRSZ; /* actual letters */
+               format_str = palloc( str_size+1); /* +1 for null term */
+               memcpy(format_str, VARDATA(format_text), str_size );
+               format_str[str_size] = 0; /* null term */
+
+               /* The input string supposedly will be in the database encoding, so convert to UTF-8. */
+               format_str_utf8 = (char *)pg_do_encoding_conversion((unsigned char *)format_str, str_size, GetDatabaseEncoding(), PG_UTF8);
+       }
+
+       /* Produce the formatted string. */
+       formatted_str_utf8 = lwpoint_to_latlon((LWPOINT *)lwgeom, format_str_utf8);
+
+       /* Convert the formatted string from UTF-8 back to database encoding. */
+       formatted_str = (char *)pg_do_encoding_conversion((unsigned char *)formatted_str_utf8, strlen(formatted_str_utf8), PG_UTF8, GetDatabaseEncoding());
+
+       /* Convert to the postgres output string type. */
+       str_size = strlen(formatted_str) + VARHDRSZ;
+       formatted_text = palloc(str_size);
+       memcpy(VARDATA(formatted_text), formatted_str, str_size - VARHDRSZ);
+       SET_VARSIZE(formatted_text, str_size);
+
+       /* clean up */
+       if (format_str != NULL) pfree(format_str);
+       /* If no encoding conversion happened, format_str_utf8 is just pointing at the same memory as format_str, so don't free it twice. */
+       if ((format_str_utf8 != NULL) && (format_str_utf8 != format_str)) pfree(format_str_utf8);
+
+       if (formatted_str != NULL) pfree(formatted_str);
+       /* Again, don't free memory twice. */
+       if ((formatted_str_utf8 != NULL) && (formatted_str_utf8 != formatted_str)) pfree(formatted_str_utf8);
+
+       PG_RETURN_POINTER(formatted_text);
+}
+
 /*
  * LWGEOM_out(lwgeom) --> cstring
  * output is 'SRID=#;<wkb in hex form>'
index bc2ee44f5a2dd0c2580e1f411ab448af7cdc7903..3400c60410f9272f6a55a3655bffac3169632f08 100644 (file)
@@ -1612,6 +1612,18 @@ CREATE OR REPLACE FUNCTION ST_AsEWKB(geometry,text)
        AS 'MODULE_PATHNAME','WKBFromLWGEOM'
        LANGUAGE 'C' IMMUTABLE STRICT;
 
+-- Availability: 2.0.0
+CREATE OR REPLACE FUNCTION ST_AsLatLonText(geometry, text)
+       RETURNS text
+       AS 'MODULE_PATHNAME','LWGEOM_to_latlon'
+       LANGUAGE 'C' IMMUTABLE STRICT;
+
+-- Availability: 2.0.0
+CREATE OR REPLACE FUNCTION ST_AsLatLonText(geometry)
+       RETURNS text
+       AS $$ SELECT ST_AsLatLonText($1, '') $$
+       LANGUAGE 'SQL' IMMUTABLE STRICT;
+
 -- Deprecation in 1.2.3
 CREATE OR REPLACE FUNCTION GeomFromEWKB(bytea)
        RETURNS geometry