</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>
lwgunparse.o \
lwgparse.o \
lwsegmentize.o \
+ lwprint.o \
wktparse.tab.o \
lex.yy.o \
vsprintf.o \
OBJS= \
cu_algorithm.o \
+ cu_print.o \
cu_wkt.o \
cu_geodetic.o \
cu_measures.o \
--- /dev/null
+/**********************************************************************
+ * $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");
+}
--- /dev/null
+/**********************************************************************
+ * $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);
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())
{
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);
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);
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);
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);
--- /dev/null
+/**********************************************************************
+ * $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);
+}
#include "fmgr.h"
#include "utils/elog.h"
+#include "mb/pg_wchar.h"
# include "lib/stringinfo.h" /* for binary input */
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);
/*
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>'
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