[librttopo] 01/19: Imported Upstream version 0.0~20151215-d1aaf2f

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat Apr 30 00:22:15 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository librttopo.

commit 6f279ec8118efc22ac974a5dfbd7af1a2c4f2263
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Feb 6 00:43:10 2016 +0100

    Imported Upstream version 0.0~20151215-d1aaf2f
---
 .gitignore                    |   20 +
 .gitlab-ci.yml                |   13 +
 COPYING                       |  339 ++
 Makefile                      |   23 +
 README.md                     |    7 +
 README.topo                   |   26 +
 TODO                          |    1 +
 Version.config                |   15 +
 aclocal.m4                    |   19 +
 autogen.sh                    |   91 +
 configure.ac                  |  429 +++
 g_serialized.txt              |  103 +
 macros/intltool.m4            |  216 ++
 macros/lib-ld.m4              |  110 +
 macros/lib-link.m4            |  709 ++++
 macros/lib-prefix.m4          |  185 +
 macros/libtool.m4             | 7997 +++++++++++++++++++++++++++++++++++++++++
 macros/ltoptions.m4           |  384 ++
 macros/ltsugar.m4             |  123 +
 macros/ltversion.m4           |   23 +
 macros/lt~obsolete.m4         |   98 +
 macros/po.m4                  |  449 +++
 macros/progtest.m4            |   92 +
 src/Makefile.in               |  148 +
 src/box2d.c                   |   47 +
 src/bytebuffer.c              |  358 ++
 src/bytebuffer.h              |   65 +
 src/g_box.c                   |  709 ++++
 src/g_serialized.c            | 1311 +++++++
 src/g_util.c                  |  232 ++
 src/librtgeom.h.in            | 2186 +++++++++++
 src/librtgeom_internal.h      |  500 +++
 src/librtgeom_topo.h          | 1352 +++++++
 src/librtgeom_topo_internal.h |   97 +
 src/measures.c                | 2322 ++++++++++++
 src/measures.h                |  129 +
 src/measures3d.c              | 1367 +++++++
 src/measures3d.h              |  105 +
 src/ptarray.c                 | 1865 ++++++++++
 src/rtalgorithm.c             |  903 +++++
 src/rtcircstring.c            |  334 ++
 src/rtcollection.c            |  598 +++
 src/rtcompound.c              |  277 ++
 src/rtcurvepoly.c             |  169 +
 src/rtgeodetic.c              | 3305 +++++++++++++++++
 src/rtgeodetic.h              |  163 +
 src/rtgeodetic_tree.c         |  951 +++++
 src/rtgeodetic_tree.h         |   60 +
 src/rtgeom.c                  | 2049 +++++++++++
 src/rtgeom_api.c              |  852 +++++
 src/rtgeom_debug.c            |  194 +
 src/rtgeom_geos.c             | 1739 +++++++++
 src/rtgeom_geos.h             |   45 +
 src/rtgeom_geos_clean.c       | 1059 ++++++
 src/rtgeom_geos_node.c        |  269 ++
 src/rtgeom_geos_split.c       |  545 +++
 src/rtgeom_log.h              |  102 +
 src/rtgeom_topo.c             | 5905 ++++++++++++++++++++++++++++++
 src/rthomogenize.c            |  273 ++
 src/rtin_geojson.c            |  599 +++
 src/rtin_twkb.c               |  683 ++++
 src/rtin_wkb.c                |  806 +++++
 src/rtiterator.c              |  283 ++
 src/rtline.c                  |  595 +++
 src/rtlinearreferencing.c     | 1401 ++++++++
 src/rtmcurve.c                |   34 +
 src/rtmline.c                 |  129 +
 src/rtmpoint.c                |  123 +
 src/rtmpoly.c                 |   70 +
 src/rtmsurface.c              |   34 +
 src/rtout_encoded_polyline.c  |  130 +
 src/rtout_geojson.c           |  788 ++++
 src/rtout_gml.c               | 1994 ++++++++++
 src/rtout_kml.c               |  198 +
 src/rtout_svg.c               |  670 ++++
 src/rtout_twkb.c              |  616 ++++
 src/rtout_twkb.h              |  107 +
 src/rtout_wkb.c               |  853 +++++
 src/rtout_wkt.c               |  694 ++++
 src/rtout_x3d.c               |  929 +++++
 src/rtpoint.c                 |  288 ++
 src/rtpoly.c                  |  596 +++
 src/rtprint.c                 |  425 +++
 src/rtpsurface.c              |  205 ++
 src/rtspheroid.c              |  701 ++++
 src/rtstroke.c                |  873 +++++
 src/rttin.c                   |  189 +
 src/rttopo_config.h.in        |   27 +
 src/rttree.c                  |  249 ++
 src/rttree.h                  |   43 +
 src/rttriangle.c              |  225 ++
 src/rtutil.c                  |  401 +++
 src/stringbuffer.c            |  343 ++
 src/stringbuffer.h            |   62 +
 src/varint.c                  |  208 ++
 src/varint.h                  |   56 +
 96 files changed, 60684 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e33c6a1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+*.o
+*.lo
+*.la
+.libs/
+src/Makefile
+autom4te.cache/
+config.guess
+config.log
+config.status
+config.sub
+configure
+cunit/Makefile
+install-sh
+librtgeom.h
+libtool
+ltmain.sh
+rtin_wkt_lex.c
+rtin_wkt_parse.c
+rtin_wkt_parse.h
+rttopo_config.h
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..d8e87d7
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,13 @@
+test:
+  script:
+    - apt-get update -qq
+    - apt-cache search libgeos
+    - apt-get install -y
+        build-essential autoconf libtool
+        libgeos-dev
+    - geos-config --version || true
+    - ./autogen.sh
+    - ./configure
+    - make
+    - make install
+    - make uninstall
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..74c6f4c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+all install uninstall clean: config.status
+	$(MAKE) -C src $@
+
+maintainer-clean-local:
+	rm -f configure
+	rm -f config.guess config.rpath config.sub
+	rm -f libtool install-sh ltmain.sh
+
+maintainer-clean: distclean
+	$(MAKE) maintainer-clean-local
+
+distclean-local:
+	rm -Rf autom4te.cache
+	rm -f config.log config.cache config.status
+
+distclean: clean
+	$(MAKE) distclean-local
+
+configure: configure.ac
+	./autogen.sh
+
+config.status: configure
+	./configure
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ef86f19
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+[![build status](https://gitlab.com/ci/projects/89102/status.png?ref=master)]
+(https://gitlab.com/ci/projects/89102?ref=master)
+
+This is a fork of PostGIS' liblwgeom, moved to its own repository to
+allow for faster release cycles independent from PostGIS itself.
+
+It is a work in progress, still not ready for use.
diff --git a/README.topo b/README.topo
new file mode 100644
index 0000000..22e19bf
--- /dev/null
+++ b/README.topo
@@ -0,0 +1,26 @@
+About topology support in librtgeom
+-----------------------------------
+ Author: Sandro Santilli <strk at keybit.net>
+ Last modified: Jun 13, 2015
+
+The topology support in librtgeom exposes an API to create and manage
+"standard" topologies that use provided callbacks to take care of actual
+data storage.
+
+The topology standard is based on what was provided by PostGIS at its
+version 2.0.0, which in turn is based on ISO SQL/MM (ISO 13249) with
+the addition of the "TopoGeometry" concept.
+
+The public header for topology support is `librtgeom_topo.h`.
+The caller has to setup a backend interface  (RTT_BE_IFACE) implementing
+all the required callbacks and will then be able to use the provided
+editing functions.
+
+The contract for each callback is fully specified in the header.
+The callbacks are as simple as possible while still allowing for
+backend-specific optimizations.
+
+The backend interface is an opaque object and callabcks are registered
+into it using free functions. This is to allow for modifying the required
+set of callbacks between versions of the library without breaking backward
+compatibility.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..cea612a
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+- Expose GEOSGeometry-based API
diff --git a/Version.config b/Version.config
new file mode 100644
index 0000000..93ff5de
--- /dev/null
+++ b/Version.config
@@ -0,0 +1,15 @@
+
+LIBRTGEOM_VERSION=0.0.0
+
+# Librtgeom interface versioning
+
+# Current interface, increments when adding an interface
+LIBRTGEOM_IFACE_CUR=0
+# Age of current interface, only changes when current interface
+# changes, either incrementing with it (compatible change) or
+# going back to zero (incompatible change).
+LIBRTGEOM_IFACE_AGE=0
+# Revision of current interface, set to 0 when adding an interface
+# or increment when just changing implementations.
+LIBRTGEOM_IFACE_REV=0
+
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 0000000..d88713e
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,19 @@
+# generated automatically by aclocal 1.14.1 -*- Autoconf -*-
+
+# Copyright (C) 1996-2013 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_include([macros/libtool.m4])
+m4_include([macros/ltoptions.m4])
+m4_include([macros/ltsugar.m4])
+m4_include([macros/ltversion.m4])
+m4_include([macros/lt~obsolete.m4])
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..6024906
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+#
+#
+# PostGIS Bootstrapping Script
+#
+giveup()
+{
+        echo
+        echo "  Something went wrong, giving up!"
+        echo
+        exit 1
+}
+
+OSTYPE=`uname -s`
+
+#
+# Solaris has GNU versions of utils with a g prefix
+#
+for grep in ggrep grep; do
+    GREP=`which $grep 2>/dev/null`
+    if test -x "${GREP}"; then
+        break;
+    fi
+done
+
+for sed in gsed sed; do
+    SED=`which $sed 2>/dev/null`
+    if test -x "${SED}"; then
+        break;
+    fi
+done
+
+#
+# Find and run Autoconf
+#
+AUTOCONF=`which autoconf 2>/dev/null`
+if [ ! ${AUTOCONF} ]; then
+    echo "Missing autoconf!"
+    exit
+fi
+AUTOCONF_VER=`${AUTOCONF} --version | ${GREP} -E "^.*[0-9]$" | ${SED} 's/^.* //'`
+
+for aclocal in aclocal aclocal-1.10 aclocal-1.9; do
+    ACLOCAL=`which $aclocal 2>/dev/null`
+    if test -x "${ACLOCAL}"; then
+        break;
+    fi
+done
+if [ ! ${ACLOCAL} ]; then
+    echo "Missing aclocal!"
+    exit
+fi
+ACLOCAL_VER=`${ACLOCAL} --version | ${GREP} -E "^.*[0-9]$" | ${SED} 's/^.* //'`
+
+for libtoolize in glibtoolize libtoolize; do
+    LIBTOOLIZE=`which $libtoolize 2>/dev/null`
+    if test -x "${LIBTOOLIZE}"; then
+        break;
+    fi
+done
+if [ ! ${LIBTOOLIZE} ]; then
+    echo "Missing libtoolize!"
+    exit
+fi
+LIBTOOLIZE_VER=`${LIBTOOLIZE} --version | ${GREP} -E "^.*[0-9]\.[0-9]" | ${SED} 's/^.* //'`
+LIBTOOLIZE_MAJOR_VER=`echo ${LIBTOOLIZE_VER} | cut -f1 -d'.'`
+
+# TODO: Check libtool version and add --install option only for 1.9b+
+LTOPTS="--force --copy"
+if test ${LIBTOOLIZE_MAJOR_VER} -ge 2; then
+    LTOPTS="${LTOPTS} --install"
+fi
+
+echo "* Running ${LIBTOOLIZE} (${LIBTOOLIZE_VER})"
+echo "   OPTIONS = ${LTOPTS}"
+${LIBTOOLIZE} ${LTOPTS} || giveup
+
+echo "* Running $ACLOCAL (${ACLOCAL_VER})"
+${ACLOCAL} -I macros || giveup
+
+echo "* Running ${AUTOCONF} (${AUTOCONF_VER})"
+${AUTOCONF} || giveup
+
+if test -f "${PWD}/configure"; then
+    echo "======================================"
+    echo "Now you are ready to run './configure'"
+    echo "======================================"
+else
+    echo "  Failed to generate ./configure script!"
+    giveup
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..86014e1
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,429 @@
+dnl **********************************************************************
+dnl *
+dnl * rttopo - topology library
+dnl * http://gitlab.com/rttopo/rttopo
+dnl * Copyright 2008 Mark Cave-Ayland
+dnl *
+dnl * This is free software; you can redistribute and/or modify it under
+dnl * the terms of the GNU General Public Licence. See the COPYING file.
+dnl *
+dnl **********************************************************************
+
+AC_INIT()
+AC_CONFIG_HEADERS([src/rttopo_config.h])
+AC_CONFIG_MACRO_DIR([macros])
+
+
+dnl Invoke libtool: we do this as it is the easiest way to find the PIC
+dnl flags required to build liblwgeom
+AC_PROG_LIBTOOL
+
+dnl
+dnl Compilers
+dnl
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_GREP
+
+dnl
+dnl Define PIC flags in PICFLAGS (note: this variable is set as part of libtool initialisation above)
+dnl
+PICFLAGS="$lt_prog_compiler_pic"
+AC_SUBST([PICFLAGS])
+
+dnl
+dnl For GCC enable additional warning flags -Wall and -Wmissing-prototypes (using macro included with libtool)
+dnl
+WARNFLAGS=""
+AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -Wall], [_cv_wall], [-Wall], [], [WARNFLAGS="$WARNFLAGS -Wall"], []) 
+AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -Wmissing-prototypes], [_cv_misprot], [-Wmissing-prototypes], [], [WARNFLAGS="$WARNFLAGS -Wmissing-prototypes"], []) 
+AC_SUBST([WARNFLAGS])
+
+dnl
+dnl For some GCC versions and platforms, floating point issues need to be
+dnl ironed out. 
+NUMERICFLAGS=""
+AC_LIBTOOL_COMPILER_OPTION([if $compiler supports -ffloat-store], [dummy_cv_ffloat_store], [-ffloat-store], [], [NUMERICFLAGS="$NUMERICFLAGS -ffloat-store"], [])
+AC_SUBST([NUMERICFLAGS])
+
+dnl
+dnl Exporting used library symbols in the module is a source of issues,
+dnl see https://trac.osgeo.org/postgis/ticket/3281
+dnl
+EXCLUDELIBS_LDFLAGS=""
+AC_LIBTOOL_COMPILER_OPTION([if $compiler supports --exclude-libs], [_cv_exclude_libs], [[-Wl,--exclude-libs,ALL]], [], [EXCLUDELIBS_LDFLAGS="-Wl,--exclude-libs,ALL"], [])
+AC_SUBST([EXCLUDELIBS_LDFLAGS])
+
+dnl
+dnl Define executable suffix for use with the loader Makefiles
+dnl
+EXESUFFIX="$ac_cv_exeext"
+AC_SUBST([EXESUFFIX])
+
+dnl
+dnl Liblwgeom version information imported from Version.config
+dnl
+
+LIBRTGEOM_CURRENT=`cat Version.config | grep ^LIBRTGEOM_IFACE_CUR | sed 's/[[^=]]*=\([[0-9]]\)/\1/g'`
+LIBRTGEOM_AGE=`cat Version.config | grep ^LIBRTGEOM_IFACE_AGE | sed 's/[[^=]]*=\([[0-9]]\)/\1/g'`
+LIBRTGEOM_REV=`cat Version.config | grep ^LIBRTGEOM_IFACE_REV | sed 's/[[^=]]*=\([[0-9]]\)/\1/g'`
+AC_SUBST([LIBRTGEOM_CURRENT])
+AC_SUBST([LIBRTGEOM_AGE])
+AC_SUBST([LIBRTGEOM_REV])
+
+LIBRTGEOM_VERSION=`grep ^LIBRTGEOM_VERSION Version.config | sed 's/.*=//'`
+AC_DEFINE_UNQUOTED([LIBRTGEOM_VERSION], ["$LIBRTGEOM_VERSION"], [rtgeom version])
+AC_SUBST([LIBRTGEOM_VERSION])
+
+
+dnl
+dnl Search for flex/bison to build the parser
+dnl
+
+AC_PROG_LEX
+AC_PROG_YACC
+AC_SUBST([LEX])
+AC_SUBST([YACC])
+
+dnl
+dnl Search for OS-specific headers
+dnl
+AC_CHECK_HEADER([ieeefp.h], [HAVE_IEEEFP_H=1], [HAVE_IEEEFP_H=0])
+AC_DEFINE_UNQUOTED([HAVE_IEEEFP_H], [$HAVE_IEEEFP_H], [ieeefp.h header])
+AC_CHECK_HEADER([termios.h], [HAVE_TERMIOS_H=1], [HAVE_TERMIOS_H=0])
+AC_DEFINE_UNQUOTED([HAVE_TERMIOS_H], [$HAVE_TERMIOS_H], [termios.h header])
+
+
+dnl
+dnl Check for platform-specific functions
+dnl
+AC_CHECK_FUNC(vasprintf, AC_DEFINE([HAVE_VASPRINTF]))
+AC_CHECK_FUNC(asprintf, AC_DEFINE([HAVE_ASPRINTF]))
+AC_FUNC_FSEEKO()
+
+dnl 
+dnl First see if we have isfinite in basic header
+dnl then check to see if it's a GNU extension
+dnl
+AC_CHECK_DECL(isfinite, 
+  [AC_DEFINE([HAVE_ISFINITE])], 
+  [],
+  [#include <math.h>])
+
+dnl Clear the cache
+unset ac_cv_have_decl_isfinite
+
+AC_CHECK_DECL(isfinite,
+  AC_DEFINE([HAVE_GNU_ISFINITE]),
+  [],
+  [[
+    #define _GNU_SOURCE 
+    #include <math.h>
+  ]])
+
+dnl 
+dnl MingW requires use of pwd -W to give proper Windows (not MingW) paths 
+dnl for in-place regression tests 
+dnl 
+case $host_os in
+         *mingw*)
+                 MINGWBUILD=1
+                 ;;
+         *)
+                 MINGWBUILD=0
+                 ;;
+esac
+AC_SUBST([MINGWBUILD])
+
+dnl
+dnl Allow the user to specify the location of the html/docbook.xsl stylesheet
+dnl
+
+dnl ===========================================================================
+dnl Detect libxml2 if it is installed
+dnl (needed to GeomFromGML and GeomFromKML functions)
+dnl ===========================================================================
+
+AC_ARG_WITH([xml2config], 
+	[AS_HELP_STRING([--with-xml2config=FILE], [specify an alternative xml2-config file])], 
+	[XML2CONFIG="$withval"], [XML2CONFIG=""])
+
+if test "x$XML2CONFIG" = "x"; then
+	dnl XML2CONFIG was not specified, so search within the current path
+	AC_PATH_PROG([XML2CONFIG], [xml2-config])
+
+	dnl If we couldn't find xml2-config, display a warning
+	if test "x$XML2CONFIG" = "x"; then
+		AC_MSG_ERROR([could not find xml2-config from libxml2 within the current path. You may need to try re-running configure with a --with-xml2config parameter.])
+	fi
+else
+	dnl XML2CONFIG was specified; display a message to the user
+	if test "x$XML2CONFIG" = "xyes"; then
+		AC_MSG_ERROR([you must specify a parameter to --with-xml2config, e.g. --with-xml2config=/path/to/xml2-config])
+	else
+		if test -f $XML2CONFIG; then
+			AC_MSG_RESULT([Using user-specified xml2-config file: $XML2CONFIG])
+		else
+			AC_MSG_ERROR([the user-specified xml2-config file $XML2CONFIG does not exist])
+		fi	
+	fi
+fi
+
+
+dnl Extract the linker and include flags 
+XML2_LDFLAGS=`$XML2CONFIG --libs`
+XML2_CPPFLAGS=`$XML2CONFIG --cflags`
+
+dnl Extract the version
+RTGEOM_LIBXML2_VERSION=`$XML2CONFIG --version`
+
+dnl Check headers file
+CPPFLAGS_SAVE="$CPPFLAGS"
+CPPFLAGS="$XML2_CPPFLAGS"
+AC_CHECK_HEADERS([libxml/tree.h libxml/parser.h libxml/xpath.h libxml/xpathInternals.h],
+		 [], [AC_MSG_ERROR([could not find headers include related to libxml2])])
+CPPFLAGS="$CPPFLAGS_SAVE"
+
+dnl Ensure we can link against libxml2
+LIBS_SAVE="$LIBS"
+LIBS="$XML2_LDFLAGS"
+AC_CHECK_LIB([xml2], [xmlInitParser], [], [AC_MSG_ERROR([could not find libxml2])], [])
+LIBS="$LIBS_SAVE"
+
+AC_DEFINE_UNQUOTED([RTGEOM_LIBXML2_VERSION], ["$RTGEOM_LIBXML2_VERSION"], [PostGIS libxml2 version])
+AC_SUBST([RTGEOM_LIBXML2_VERSION])
+
+
+
+dnl ===========================================================================
+dnl Detect the version of GEOS installed on the system
+dnl ===========================================================================
+
+AC_ARG_WITH([geosconfig], 
+	[AS_HELP_STRING([--with-geosconfig=FILE], [specify an alternative geos-config file])], 
+	[GEOSCONFIG="$withval"], [GEOSCONFIG=""])
+
+if test "x$GEOSCONFIG" = "x"; then
+	dnl GEOSCONFIG was not specified, so search within the current path
+	AC_PATH_PROG([GEOSCONFIG], [geos-config])
+
+	dnl If we couldn't find geos-config, display an error
+	if test "x$GEOSCONFIG" = "x"; then
+		AC_MSG_ERROR([could not find geos-config within the current path. You may need to try re-running configure with a --with-geosconfig parameter.])
+	fi
+else
+	dnl GEOSCONFIG was specified; display a message to the user
+	if test "x$GEOSCONFIG" = "xyes"; then
+		AC_MSG_ERROR([you must specify a parameter to --with-geosconfig, e.g. --with-geosconfig=/path/to/geos-config])
+	else
+		if test -f $GEOSCONFIG; then
+			AC_MSG_RESULT([Using user-specified geos-config file: $GEOSCONFIG])
+		else
+			AC_MSG_ERROR([the user-specified geos-config file $GEOSCONFIG does not exist])
+		fi	
+	fi
+fi
+
+dnl Extract the version information from geos_config
+dnl Note: we extract the major & minor separately, ensure they are numeric, 
+dnl and then combine to give the final version. 
+dnl This is to guard against user error... 
+GEOS_MAJOR_VERSION=`$GEOSCONFIG --version | cut -d. -f1 | sed 's/[[^0-9]]//g'`
+GEOS_MINOR_VERSION=`$GEOSCONFIG --version | cut -d. -f2 | sed 's/[[^0-9]]//g'`
+GEOS_PATCH_VERSION=`$GEOSCONFIG --version | cut -d. -f3 | sed 's/[[^0-9]]//g'`
+if test "x$GEOS_PATCH_VERSION" = "x"; then 
+	GEOS_PATCH_VERSION="0"; 
+fi
+GEOS_FULL_VERSION=`$GEOSCONFIG --version`
+RTGEOM_GEOS_VERSION="$GEOS_MAJOR_VERSION$GEOS_MINOR_VERSION"
+GEOS_NUMERIC_PATCH_VERSION=`printf "%02d" $GEOS_PATCH_VERSION`
+GEOS_NUMERIC_MINOR_VERSION=`printf "%02d" $GEOS_MINOR_VERSION`
+GEOS_NUMERIC_VERSION="$GEOS_MAJOR_VERSION$GEOS_NUMERIC_MINOR_VERSION$GEOS_NUMERIC_PATCH_VERSION"
+
+dnl Ensure that we are using GEOS >= 3.4.0
+AC_MSG_RESULT([checking GEOS version... $GEOS_FULL_VERSION])
+if test ! "$GEOS_NUMERIC_VERSION" -ge 30400; then
+	AC_MSG_ERROR([librttopo requires GEOS >= 3.4.0])
+fi
+
+dnl Extract the linker and include flags 
+GEOS_LDFLAGS=`$GEOSCONFIG --ldflags`
+GEOS_CPPFLAGS=-I`$GEOSCONFIG --includes`
+AC_SUBST([GEOS_LDFLAGS])
+AC_SUBST([GEOS_CPPFLAGS])
+
+dnl Ensure that we can parse geos_c.h
+CPPFLAGS_SAVE="$CPPFLAGS"
+CPPFLAGS="$GEOS_CPPFLAGS"
+AC_CHECK_HEADER([geos_c.h], [], [AC_MSG_ERROR([could not find geos_c.h - you may need to specify the directory of a geos-config file using --with-geosconfig])])
+CPPFLAGS="$CPPFLAGS_SAVE"
+
+dnl Ensure we can link against libgeos_c
+LIBS_SAVE="$LIBS"
+LIBS="$GEOS_LDFLAGS"
+AC_CHECK_LIB([geos_c], [initGEOS],
+	[],
+	[AC_MSG_ERROR([could not find libgeos_c - you may need to specify the directory of a geos-config file using --with-geosconfig])],
+	[])
+LIBS="$LIBS_SAVE"
+
+AC_DEFINE_UNQUOTED([RTGEOM_GEOS_VERSION], [$RTGEOM_GEOS_VERSION], [GEOS library version])	
+AC_SUBST([RTGEOM_GEOS_VERSION])
+AC_SUBST([GEOS_NUMERIC_VERSION])
+
+dnl ===========================================================================
+dnl Detect if json-c installed
+dnl ===========================================================================
+
+CHECK_JSON=yes
+HAVE_JSON=no
+HAVE_JSON_C=no
+
+AC_ARG_WITH([json],
+	[AS_HELP_STRING([--without-json], [build without json-c support])],
+	[CHECK_JSON="$withval"], [])
+
+if test "$CHECK_JSON" != "no"; then dnl {
+
+AC_ARG_WITH([jsondir],
+	[AS_HELP_STRING([--with-jsondir=PATH], [specify the json-c installation directory])],
+	[JSONDIR="$withval"], [JSONDIR=])
+
+if test ! "x$JSONDIR" = "x"; then
+	dnl Make sure that the directory exists
+	if test "x$JSONDIR" = "xyes"; then
+		AC_MSG_ERROR([you must specify a parameter to --with-jsondir, e.g. --with-jsondir=/path/to])
+	else
+		AC_MSG_RESULT([Using user-specified json-c directory: $JSONDIR])
+
+		dnl Add the include directory to JSON_CPPFLAGS
+		JSON_CPPFLAGS="-I$JSONDIR/include"
+		JSON_LDFLAGS="-L$JSONDIR/lib"
+	fi
+fi
+
+dnl Check that we can find the json/json.h header file
+CPPFLAGS_SAVE="$CPPFLAGS"
+CPPFLAGS="$JSON_CPPFLAGS"
+AC_CHECK_HEADER([json/json.h], [HAVE_JSON=yes], [
+  AC_CHECK_HEADER([json-c/json.h], [HAVE_JSON=yes; HAVE_JSON_C=yes], [])
+])	
+CPPFLAGS="$CPPFLAGS_SAVE"
+
+dnl Ensure we can link against libjson
+LIBS_SAVE="$LIBS"
+LIBS="$JSON_LDFLAGS"
+AC_CHECK_LIB([json-c], [json_object_get], [HAVE_JSON=yes; JSON_LDFLAGS="${JSON_LDFLAGS} -ljson-c"], [
+  AC_CHECK_LIB([json], [json_object_get], [HAVE_JSON=yes; JSON_LDFLAGS="${JSON_LDFLAGS} -ljson"], [], [])
+], [])
+LIBS="$LIBS_SAVE"
+
+if test "$HAVE_JSON" = "yes"; then
+	AC_DEFINE([HAVE_LIBJSON], 1, [Define to 1 if libjson is present])
+fi
+if test "$HAVE_JSON_C" = "yes"; then
+	AC_DEFINE([HAVE_LIBJSON_C], 1, [Define to 1 if libjson resides in a json-c subdir])
+fi
+
+AC_SUBST([JSON_CPPFLAGS])
+AC_SUBST([JSON_LDFLAGS])
+AC_SUBST([HAVE_JSON])
+
+fi dnl }
+
+
+
+
+dnl ===========================================================================
+dnl Allow the user to enable debugging with --enable-debug
+dnl
+dnl Currently we default to debug level 4. See DEBUG for more information. 
+dnl
+
+AC_ARG_ENABLE([debug], AC_HELP_STRING([--enable-debug], [Enable verbose debugging messages]), 
+	[RTGEOM_DEBUG_LEVEL=4], [RTGEOM_DEBUG_LEVEL=0]) 
+
+AC_DEFINE_UNQUOTED([RTGEOM_DEBUG_LEVEL], [$RTGEOM_DEBUG_LEVEL], [library debug level (0=disabled)])
+
+dnl ===========================================================================
+dnl Allow the user to enable GEOS profiling with --enable-profile
+dnl
+
+AC_ARG_ENABLE([profile], AC_HELP_STRING([--enable-profile], [Enable GEOS profiling messages]), 
+	[RTGEOM_PROFILE=1], [RTGEOM_PROFILE=0]) 
+
+AC_DEFINE_UNQUOTED([RTGEOM_PROFILE], [$RTGEOM_PROFILE], [Enable GEOS profiling (0=disabled)])
+
+dnl ===========================================================================
+dnl Define version macros
+dnl
+
+
+CPPFLAGS="$PGSQL_CPPFLAGS $GEOS_CPPFLAGS $XML2_CPPFLAGS $JSON_CPPFLAGS $PCRE_CPPFLAGS $CPPFLAGS"
+dnl AC_MSG_RESULT([CPPFLAGS: $CPPFLAGS])
+
+SHLIB_LINK="$PGSQL_LDFLAGS $GEOS_LDFLAGS -lgeos_c -lproj $JSON_LDFLAGS $XML2_LDFLAGS $PCRE_LDFLAGS $EXCLUDELIBS_LDFLAGS"
+AC_SUBST([SHLIB_LINK])
+dnl AC_MSG_RESULT([SHLIB_LINK: $SHLIB_LINK])
+
+dnl ====================================
+dnl interrupt tests
+dnl ====================================
+INTERRUPTTESTS="no"
+AC_ARG_WITH([interrupt-tests],
+    [AS_HELP_STRING([--without-interrupt-tests],
+                    [Disable the interrupt tests (for CI servers)])],
+    [], [])
+
+if test "x$with_interrupt_tests" != "xno"; then
+    INTERRUPTTESTS="yes"
+fi
+
+AC_SUBST([INTERRUPTTESTS])
+
+dnl ===========================================================================
+dnl SRID stuff
+dnl ===========================================================================
+
+SRID_MAX=999999
+SRID_USR_MAX=998999
+AC_SUBST([SRID_MAX])
+AC_SUBST([SRID_USR_MAX])
+
+AC_OUTPUT([
+   src/Makefile 
+   src/librtgeom.h
+   ])
+    
+dnl ===========================================================================
+dnl Display the configuration status information
+dnl ===========================================================================
+
+AC_MSG_RESULT()
+AC_MSG_RESULT([  rttopo is now configured for ${host}])
+AC_MSG_RESULT()
+AC_MSG_RESULT([ -------------- Compiler Info ------------- ])
+AC_MSG_RESULT([  C compiler:           ${CC} ${CFLAGS}])
+AC_MSG_RESULT()
+AC_MSG_RESULT([ -------------- Dependencies -------------- ])
+AC_MSG_RESULT([  GEOS config:          ${GEOSCONFIG}])
+AC_MSG_RESULT([  GEOS version:         ${GEOS_FULL_VERSION}])
+AC_MSG_RESULT([  Libxml2 config:       ${XML2CONFIG}])
+AC_MSG_RESULT([  Libxml2 version:      ${RTGEOM_LIBXML2_VERSION}])
+AC_MSG_RESULT([  JSON-C support:       ${HAVE_JSON}])
+AC_MSG_RESULT([  PCRE support:         ${HAVE_PCRE}])
+AC_MSG_RESULT([  Debug level:          ${RTGEOM_DEBUG_LEVEL}])
+AC_MSG_RESULT([  Perl:                 ${PERL}])
+AC_MSG_RESULT()
+
+if test "$GEOS_NUMERIC_VERSION" -lt 30500; then
+AC_MSG_WARN([ --------- GEOS VERSION WARNING ------------ ])
+AC_MSG_WARN([  You are building against GEOS ${GEOS_FULL_VERSION} ])
+AC_MSG_WARN([  To take advantage of all the features of ])
+AC_MSG_WARN([  PostGIS we recommend you build using ])
+AC_MSG_WARN([  GEOS 3.5.0 or higher. You can download ])
+AC_MSG_WARN([  the latest versions from ])
+AC_MSG_WARN([  http://trac.osgeo.org/geos ])
+AC_MSG_WARN()
+fi
+
diff --git a/g_serialized.txt b/g_serialized.txt
new file mode 100644
index 0000000..f516169
--- /dev/null
+++ b/g_serialized.txt
@@ -0,0 +1,103 @@
+
+GSERIALIZED FORM
+=================
+
+The new serialized form, used by GEOGRAPHY, attempts to learn from the
+lessons of the SERIALIZED_RTGEOM, making slightly different trade-offs
+between alignment and overall compactness.
+
+* It is understood that GSERIALIZED is be used primarily (only)
+  by PostGIS. Other users of the geometry library (for example, the
+  loaders and dumpers) will serialize to RTWKB or EWKB or various text
+  representations. Therefore, GSERIALIZED includes the uint32 "size"
+  field at the top used by PostgreSQL for varlena types.
+* Like SERIALIZED_RTGEOM, GSERIALIZED is built to be read and written
+  recursively, so that nested collections of collections (of ...) are
+  possible without restrictions on depth.
+* GSERIALIZED preserves double alignment of ordinate arrays. This will
+  allow coordinate access without memcpy.
+* GSERIALIZED includes a mandatory SRID, in recognition of the fact
+  that most production use of PostGIS does actually use an SRID. In
+  SERIALIZED_RTGEOM the SRID is optional.
+* GSERIALIZED places the dimensionality information, the SRID
+  information and the bounding boxes at the front of the structure,
+  and all sub-components inherit from those parent values.
+* GSERIALIZED retains the idea of optional bounding boxes, so that small
+  features do not carry the extra storage overhead of a largely redundant
+  bounding box.
+
+STRUCTURE
+---------
+
+Most of the internals of GSERIALIZED are anonymous. One thing that
+differs from SERIALIZED_RTGEOM is that the geometry type is no longer
+directly accessible from the structure (it was inside the one byte "type"
+at the top of the SERIALIZED_RTGEOM). To access the type in GSERIALIZED,
+you first have to figure out whether there is a an bounding box, how many
+dimensions that bounding box has, and move the data pointer appropriately
+before dereferencing. The gserialized_get_type(GSERIALIZED *s) function
+carries out this operation.
+
+typedef struct
+{
+	uint32 size; /* For PgSQL use, use VAR* macros to manipulate. */
+	uchar srid[3]; /* 21 bits of SRID (and 3 spare bits) */
+	uchar flags; /* HasZ, HasM, HasBBox, IsGeodetic */
+	uchar data[1]; /* See gserialized.txt */
+} GSERIALIZED;
+
+The standard header is as follows (using <> to denote 4-byte fields and
+[] to denote 8-byte fields):
+
+<size> size  /* Used by PgSQL */
+<srid        /* 3 bytes */
+ flags>      /* 1 byte */
+<bbox-xmin>  /* bounding box is optional */
+<bbox-xmax>  /* number of dimensions is variable and indicated in the flags */
+<bbox-ymin>
+<bbox-ymax>
+
+After the header comes the recursively searchable geometry
+representations. Each type is double aligned, so any combination is
+double aligned, and we start after a double aligned standard header,
+so we are golden:
+
+<pointype>
+<npoints>       /* 0 if empty, 1 otherwise */
+[double]
+[double]
+[double]
+
+<linestringtype>
+<npoints>      /* 0 if empty, N otherwise */
+[double]
+...
+[double]
+
+<polygontype>
+<nrings>        /* 0 if empty, N otherwise */
+<npointsring1>
+<npointsring2>
+<?pad?>         /* pad if nrings % 2 > 0 */
+[double]
+...
+[double]
+
+<collectiontype>
+<ngeoms>        /* 0 if empty, N otherwise */
+[geom]
+...
+[geom]
+
+<circularstringtype>
+<npoints>       /* 0 if empty, N otherwise */
+[double]
+...
+[double]
+
+<compoundstringtype>
+<ngeoms>        /* 0 if empty, N otherwise */
+[geom]
+...
+[geom]
+
diff --git a/macros/intltool.m4 b/macros/intltool.m4
new file mode 100644
index 0000000..122d773
--- /dev/null
+++ b/macros/intltool.m4
@@ -0,0 +1,216 @@
+## intltool.m4 - Configure intltool for the target system. -*-Shell-script-*-
+## Copyright (C) 2001 Eazel, Inc.
+## Author: Maciej Stachowiak <mjs at noisehavoc.org>
+##         Kenneth Christiansen <kenneth at gnu.org>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+##
+## As a special exception to the GNU General Public License, if you
+## distribute this file as part of a program that contains a
+## configuration script generated by Autoconf, you may include it under
+## the same distribution terms that you use for the rest of that program.
+
+dnl IT_PROG_INTLTOOL([MINIMUM-VERSION], [no-xml])
+# serial 40 IT_PROG_INTLTOOL
+AC_DEFUN([IT_PROG_INTLTOOL], [
+AC_PREREQ([2.50])dnl
+AC_REQUIRE([AM_NLS])dnl
+
+case "$am__api_version" in
+    1.[01234])
+	AC_MSG_ERROR([Automake 1.5 or newer is required to use intltool])
+    ;;
+    *)
+    ;;
+esac
+
+if test -n "$1"; then
+    AC_MSG_CHECKING([for intltool >= $1])
+
+    INTLTOOL_REQUIRED_VERSION_AS_INT=`echo $1 | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'`
+    INTLTOOL_APPLIED_VERSION=`intltool-update --version | head -1 | cut -d" " -f3`
+    [INTLTOOL_APPLIED_VERSION_AS_INT=`echo $INTLTOOL_APPLIED_VERSION | awk -F. '{ print $ 1 * 1000 + $ 2 * 100 + $ 3; }'`
+    ]
+    AC_MSG_RESULT([$INTLTOOL_APPLIED_VERSION found])
+    test "$INTLTOOL_APPLIED_VERSION_AS_INT" -ge "$INTLTOOL_REQUIRED_VERSION_AS_INT" ||
+	AC_MSG_ERROR([Your intltool is too old.  You need intltool $1 or later.])
+fi
+
+AC_PATH_PROG(INTLTOOL_UPDATE, [intltool-update])
+AC_PATH_PROG(INTLTOOL_MERGE, [intltool-merge])
+AC_PATH_PROG(INTLTOOL_EXTRACT, [intltool-extract])
+if test -z "$INTLTOOL_UPDATE" -o -z "$INTLTOOL_MERGE" -o -z "$INTLTOOL_EXTRACT"; then
+    AC_MSG_ERROR([The intltool scripts were not found. Please install intltool.])
+fi
+
+  INTLTOOL_DESKTOP_RULE='%.desktop:   %.desktop.in   $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+INTLTOOL_DIRECTORY_RULE='%.directory: %.directory.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+     INTLTOOL_KEYS_RULE='%.keys:      %.keys.in      $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -k -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+     INTLTOOL_PROP_RULE='%.prop:      %.prop.in      $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+      INTLTOOL_OAF_RULE='%.oaf:       %.oaf.in       $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -p $(top_srcdir)/po $< [$]@'
+     INTLTOOL_PONG_RULE='%.pong:      %.pong.in      $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+   INTLTOOL_SERVER_RULE='%.server:    %.server.in    $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -o -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+    INTLTOOL_SHEET_RULE='%.sheet:     %.sheet.in     $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+INTLTOOL_SOUNDLIST_RULE='%.soundlist: %.soundlist.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+       INTLTOOL_UI_RULE='%.ui:        %.ui.in        $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+      INTLTOOL_XML_RULE='%.xml:       %.xml.in       $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+      INTLTOOL_XML_NOMERGE_RULE='%.xml:       %.xml.in       $(INTLTOOL_MERGE) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u /tmp $< [$]@' 
+      INTLTOOL_XAM_RULE='%.xam:       %.xml.in       $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+      INTLTOOL_KBD_RULE='%.kbd:       %.kbd.in       $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -m -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+    INTLTOOL_CAVES_RULE='%.caves:     %.caves.in     $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+  INTLTOOL_SCHEMAS_RULE='%.schemas:   %.schemas.in   $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -s -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+    INTLTOOL_THEME_RULE='%.theme:     %.theme.in     $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' 
+    INTLTOOL_SERVICE_RULE='%.service: %.service.in   $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@'
+   INTLTOOL_POLICY_RULE='%.policy:    %.policy.in    $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -x -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@'
+
+_IT_SUBST(INTLTOOL_DESKTOP_RULE)
+_IT_SUBST(INTLTOOL_DIRECTORY_RULE)
+_IT_SUBST(INTLTOOL_KEYS_RULE)
+_IT_SUBST(INTLTOOL_PROP_RULE)
+_IT_SUBST(INTLTOOL_OAF_RULE)
+_IT_SUBST(INTLTOOL_PONG_RULE)
+_IT_SUBST(INTLTOOL_SERVER_RULE)
+_IT_SUBST(INTLTOOL_SHEET_RULE)
+_IT_SUBST(INTLTOOL_SOUNDLIST_RULE)
+_IT_SUBST(INTLTOOL_UI_RULE)
+_IT_SUBST(INTLTOOL_XAM_RULE)
+_IT_SUBST(INTLTOOL_KBD_RULE)
+_IT_SUBST(INTLTOOL_XML_RULE)
+_IT_SUBST(INTLTOOL_XML_NOMERGE_RULE)
+_IT_SUBST(INTLTOOL_CAVES_RULE)
+_IT_SUBST(INTLTOOL_SCHEMAS_RULE)
+_IT_SUBST(INTLTOOL_THEME_RULE)
+_IT_SUBST(INTLTOOL_SERVICE_RULE)
+_IT_SUBST(INTLTOOL_POLICY_RULE)
+
+# Check the gettext tools to make sure they are GNU
+AC_PATH_PROG(XGETTEXT, xgettext)
+AC_PATH_PROG(MSGMERGE, msgmerge)
+AC_PATH_PROG(MSGFMT, msgfmt)
+AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)
+if test -z "$XGETTEXT" -o -z "$MSGMERGE" -o -z "$MSGFMT"; then
+    AC_MSG_ERROR([GNU gettext tools not found; required for intltool])
+fi
+xgversion="`$XGETTEXT --version|grep '(GNU ' 2> /dev/null`"
+mmversion="`$MSGMERGE --version|grep '(GNU ' 2> /dev/null`"
+mfversion="`$MSGFMT --version|grep '(GNU ' 2> /dev/null`"
+if test -z "$xgversion" -o -z "$mmversion" -o -z "$mfversion"; then
+    AC_MSG_ERROR([GNU gettext tools not found; required for intltool])
+fi
+
+AC_PATH_PROG(INTLTOOL_PERL, perl)
+if test -z "$INTLTOOL_PERL"; then
+   AC_MSG_ERROR([perl not found])
+fi
+AC_MSG_CHECKING([for perl >= 5.8.1])
+$INTLTOOL_PERL -e "use 5.8.1;" > /dev/null 2>&1
+if test $? -ne 0; then
+   AC_MSG_ERROR([perl 5.8.1 is required for intltool])
+else
+   IT_PERL_VERSION="`$INTLTOOL_PERL -e \"printf '%vd', $^V\"`"
+   AC_MSG_RESULT([$IT_PERL_VERSION])
+fi
+if test "x$2" != "xno-xml"; then
+   AC_MSG_CHECKING([for XML::Parser])
+   if `$INTLTOOL_PERL -e "require XML::Parser" 2>/dev/null`; then
+       AC_MSG_RESULT([ok])
+   else
+       AC_MSG_ERROR([XML::Parser perl module is required for intltool])
+   fi
+fi
+
+# Substitute ALL_LINGUAS so we can use it in po/Makefile
+AC_SUBST(ALL_LINGUAS)
+
+# Set DATADIRNAME correctly if it is not set yet
+# (copied from glib-gettext.m4)
+if test -z "$DATADIRNAME"; then
+  AC_LINK_IFELSE(
+    [AC_LANG_PROGRAM([[]],
+                     [[extern int _nl_msg_cat_cntr;
+                       return _nl_msg_cat_cntr]])],
+    [DATADIRNAME=share],
+    [case $host in
+    *-*-solaris*)
+    dnl On Solaris, if bind_textdomain_codeset is in libc,
+    dnl GNU format message catalog is always supported,
+    dnl since both are added to the libc all together.
+    dnl Hence, we'd like to go with DATADIRNAME=share
+    dnl in this case.
+    AC_CHECK_FUNC(bind_textdomain_codeset,
+      [DATADIRNAME=share], [DATADIRNAME=lib])
+    ;;
+    *)
+    [DATADIRNAME=lib]
+    ;;
+    esac])
+fi
+AC_SUBST(DATADIRNAME)
+
+IT_PO_SUBDIR([po])
+
+])
+
+
+# IT_PO_SUBDIR(DIRNAME)
+# ---------------------
+# All po subdirs have to be declared with this macro; the subdir "po" is
+# declared by IT_PROG_INTLTOOL.
+#
+AC_DEFUN([IT_PO_SUBDIR],
+[AC_PREREQ([2.53])dnl We use ac_top_srcdir inside AC_CONFIG_COMMANDS.
+dnl
+dnl The following CONFIG_COMMANDS should be exetuted at the very end
+dnl of config.status.
+AC_CONFIG_COMMANDS_PRE([
+  AC_CONFIG_COMMANDS([$1/stamp-it], [
+    if [ ! grep "^# INTLTOOL_MAKEFILE$" "$1/Makefile.in" > /dev/null ]; then
+       AC_MSG_ERROR([$1/Makefile.in.in was not created by intltoolize.])
+    fi
+    rm -f "$1/stamp-it" "$1/stamp-it.tmp" "$1/POTFILES" "$1/Makefile.tmp"
+    >"$1/stamp-it.tmp"
+    [sed '/^#/d
+	 s/^[[].*] *//
+	 /^[ 	]*$/d
+	'"s|^|	$ac_top_srcdir/|" \
+      "$srcdir/$1/POTFILES.in" | sed '$!s/$/ \\/' >"$1/POTFILES"
+    ]
+    [sed '/^POTFILES =/,/[^\\]$/ {
+		/^POTFILES =/!d
+		r $1/POTFILES
+	  }
+	 ' "$1/Makefile.in" >"$1/Makefile"]
+    rm -f "$1/Makefile.tmp"
+    mv "$1/stamp-it.tmp" "$1/stamp-it"
+  ])
+])dnl
+])
+
+# _IT_SUBST(VARIABLE)
+# -------------------
+# Abstract macro to do either _AM_SUBST_NOTMAKE or AC_SUBST
+#
+AC_DEFUN([_IT_SUBST],
+[
+AC_SUBST([$1])
+m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([$1])])
+]
+)
+
+# deprecated macros
+AU_ALIAS([AC_PROG_INTLTOOL], [IT_PROG_INTLTOOL])
+# A hint is needed for aclocal from Automake <= 1.9.4:
+# AC_DEFUN([AC_PROG_INTLTOOL], ...)
+
diff --git a/macros/lib-ld.m4 b/macros/lib-ld.m4
new file mode 100644
index 0000000..96c4e2c
--- /dev/null
+++ b/macros/lib-ld.m4
@@ -0,0 +1,110 @@
+# lib-ld.m4 serial 3 (gettext-0.13)
+dnl Copyright (C) 1996-2003 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Subroutines of libtool.m4,
+dnl with replacements s/AC_/AC_LIB/ and s/lt_cv/acl_cv/ to avoid collision
+dnl with libtool.m4.
+
+dnl From libtool-1.4. Sets the variable with_gnu_ld to yes or no.
+AC_DEFUN([AC_LIB_PROG_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], acl_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU ld's only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  acl_cv_prog_gnu_ld=yes ;;
+*)
+  acl_cv_prog_gnu_ld=no ;;
+esac])
+with_gnu_ld=$acl_cv_prog_gnu_ld
+])
+
+dnl From libtool-1.4. Sets the variable LD.
+AC_DEFUN([AC_LIB_PROG_LD],
+[AC_ARG_WITH(gnu-ld,
+[  --with-gnu-ld           assume the C compiler uses GNU ld [default=no]],
+test "$withval" = no || with_gnu_ld=yes, with_gnu_ld=no)
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  AC_MSG_CHECKING([for ld used by GCC])
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [[\\/]* | [A-Za-z]:[\\/]*)]
+      [re_direlt='/[^/][^/]*/\.\./']
+      # Canonicalize the path of ld
+      ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'`
+      while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do
+	ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  AC_MSG_CHECKING([for GNU ld])
+else
+  AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(acl_cv_path_LD,
+[if test -z "$LD"; then
+  IFS="${IFS= 	}"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR-:}"
+  for ac_dir in $PATH; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      acl_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some GNU ld's only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$acl_cv_path_LD" -v 2>&1 < /dev/null` in
+      *GNU* | *'with BFD'*)
+	test "$with_gnu_ld" != no && break ;;
+      *)
+	test "$with_gnu_ld" != yes && break ;;
+      esac
+    fi
+  done
+  IFS="$ac_save_ifs"
+else
+  acl_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$acl_cv_path_LD"
+if test -n "$LD"; then
+  AC_MSG_RESULT($LD)
+else
+  AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+AC_LIB_PROG_LD_GNU
+])
diff --git a/macros/lib-link.m4 b/macros/lib-link.m4
new file mode 100644
index 0000000..e3d26fc
--- /dev/null
+++ b/macros/lib-link.m4
@@ -0,0 +1,709 @@
+# lib-link.m4 serial 13 (gettext-0.17)
+dnl Copyright (C) 2001-2007 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_PREREQ(2.54)
+
+dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and
+dnl augments the CPPFLAGS variable.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+  define([Name],[translit([$1],[./-], [___])])
+  define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-],
+                               [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])])
+  AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [
+    AC_LIB_LINKFLAGS_BODY([$1], [$2])
+    ac_cv_lib[]Name[]_libs="$LIB[]NAME"
+    ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME"
+    ac_cv_lib[]Name[]_cppflags="$INC[]NAME"
+    ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX"
+  ])
+  LIB[]NAME="$ac_cv_lib[]Name[]_libs"
+  LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs"
+  INC[]NAME="$ac_cv_lib[]Name[]_cppflags"
+  LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix"
+  AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+  AC_SUBST([LIB]NAME)
+  AC_SUBST([LTLIB]NAME)
+  AC_SUBST([LIB]NAME[_PREFIX])
+  dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the
+  dnl results of this search when this library appears as a dependency.
+  HAVE_LIB[]NAME=yes
+  undefine([Name])
+  undefine([NAME])
+])
+
+dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode)
+dnl searches for libname and the libraries corresponding to explicit and
+dnl implicit dependencies, together with the specified include files and
+dnl the ability to compile and link the specified testcode. If found, it
+dnl sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} and
+dnl LTLIB${NAME} variables and augments the CPPFLAGS variable, and
+dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs
+dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_HAVE_LINKFLAGS],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+  define([Name],[translit([$1],[./-], [___])])
+  define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-],
+                               [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])])
+
+  dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME
+  dnl accordingly.
+  AC_LIB_LINKFLAGS_BODY([$1], [$2])
+
+  dnl Add $INC[]NAME to CPPFLAGS before performing the following checks,
+  dnl because if the user has installed lib[]Name and not disabled its use
+  dnl via --without-lib[]Name-prefix, he wants to use it.
+  ac_save_CPPFLAGS="$CPPFLAGS"
+  AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+
+  AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [
+    ac_save_LIBS="$LIBS"
+    LIBS="$LIBS $LIB[]NAME"
+    AC_TRY_LINK([$3], [$4], [ac_cv_lib[]Name=yes], [ac_cv_lib[]Name=no])
+    LIBS="$ac_save_LIBS"
+  ])
+  if test "$ac_cv_lib[]Name" = yes; then
+    HAVE_LIB[]NAME=yes
+    AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the $1 library.])
+    AC_MSG_CHECKING([how to link with lib[]$1])
+    AC_MSG_RESULT([$LIB[]NAME])
+  else
+    HAVE_LIB[]NAME=no
+    dnl If $LIB[]NAME didn't lead to a usable library, we don't need
+    dnl $INC[]NAME either.
+    CPPFLAGS="$ac_save_CPPFLAGS"
+    LIB[]NAME=
+    LTLIB[]NAME=
+    LIB[]NAME[]_PREFIX=
+  fi
+  AC_SUBST([HAVE_LIB]NAME)
+  AC_SUBST([LIB]NAME)
+  AC_SUBST([LTLIB]NAME)
+  AC_SUBST([LIB]NAME[_PREFIX])
+  undefine([Name])
+  undefine([NAME])
+])
+
+dnl Determine the platform dependent parameters needed to use rpath:
+dnl   acl_libext,
+dnl   acl_shlibext,
+dnl   acl_hardcode_libdir_flag_spec,
+dnl   acl_hardcode_libdir_separator,
+dnl   acl_hardcode_direct,
+dnl   acl_hardcode_minus_L.
+AC_DEFUN([AC_LIB_RPATH],
+[
+  dnl Tell automake >= 1.10 to complain if config.rpath is missing.
+  m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])])
+  AC_REQUIRE([AC_PROG_CC])                dnl we use $CC, $GCC, $LDFLAGS
+  AC_REQUIRE([AC_LIB_PROG_LD])            dnl we use $LD, $with_gnu_ld
+  AC_REQUIRE([AC_CANONICAL_HOST])         dnl we use $host
+  AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir
+  AC_CACHE_CHECK([for shared library run path origin], acl_cv_rpath, [
+    CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \
+    ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh
+    . ./conftest.sh
+    rm -f ./conftest.sh
+    acl_cv_rpath=done
+  ])
+  wl="$acl_cv_wl"
+  acl_libext="$acl_cv_libext"
+  acl_shlibext="$acl_cv_shlibext"
+  acl_libname_spec="$acl_cv_libname_spec"
+  acl_library_names_spec="$acl_cv_library_names_spec"
+  acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec"
+  acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator"
+  acl_hardcode_direct="$acl_cv_hardcode_direct"
+  acl_hardcode_minus_L="$acl_cv_hardcode_minus_L"
+  dnl Determine whether the user wants rpath handling at all.
+  AC_ARG_ENABLE(rpath,
+    [  --disable-rpath         do not hardcode runtime library paths],
+    :, enable_rpath=yes)
+])
+
+dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables.
+dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found
+dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS_BODY],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-],
+                               [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])])
+  dnl Autoconf >= 2.61 supports dots in --with options.
+  define([N_A_M_E],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[translit([$1],[.],[_])],[$1])])
+  dnl By default, look in $includedir and $libdir.
+  use_additional=yes
+  AC_LIB_WITH_FINAL_PREFIX([
+    eval additional_includedir=\"$includedir\"
+    eval additional_libdir=\"$libdir\"
+  ])
+  AC_LIB_ARG_WITH([lib]N_A_M_E[-prefix],
+[  --with-lib]N_A_M_E[-prefix[=DIR]  search for lib$1 in DIR/include and DIR/lib
+  --without-lib]N_A_M_E[-prefix     don't search for lib$1 in includedir and libdir],
+[
+    if test "X$withval" = "Xno"; then
+      use_additional=no
+    else
+      if test "X$withval" = "X"; then
+        AC_LIB_WITH_FINAL_PREFIX([
+          eval additional_includedir=\"$includedir\"
+          eval additional_libdir=\"$libdir\"
+        ])
+      else
+        additional_includedir="$withval/include"
+        additional_libdir="$withval/$acl_libdirstem"
+      fi
+    fi
+])
+  dnl Search the library and its dependencies in $additional_libdir and
+  dnl $LDFLAGS. Using breadth-first-seach.
+  LIB[]NAME=
+  LTLIB[]NAME=
+  INC[]NAME=
+  LIB[]NAME[]_PREFIX=
+  rpathdirs=
+  ltrpathdirs=
+  names_already_handled=
+  names_next_round='$1 $2'
+  while test -n "$names_next_round"; do
+    names_this_round="$names_next_round"
+    names_next_round=
+    for name in $names_this_round; do
+      already_handled=
+      for n in $names_already_handled; do
+        if test "$n" = "$name"; then
+          already_handled=yes
+          break
+        fi
+      done
+      if test -z "$already_handled"; then
+        names_already_handled="$names_already_handled $name"
+        dnl See if it was already located by an earlier AC_LIB_LINKFLAGS
+        dnl or AC_LIB_HAVE_LINKFLAGS call.
+        uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'`
+        eval value=\"\$HAVE_LIB$uppername\"
+        if test -n "$value"; then
+          if test "$value" = yes; then
+            eval value=\"\$LIB$uppername\"
+            test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value"
+            eval value=\"\$LTLIB$uppername\"
+            test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value"
+          else
+            dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined
+            dnl that this library doesn't exist. So just drop it.
+            :
+          fi
+        else
+          dnl Search the library lib$name in $additional_libdir and $LDFLAGS
+          dnl and the already constructed $LIBNAME/$LTLIBNAME.
+          found_dir=
+          found_la=
+          found_so=
+          found_a=
+          eval libname=\"$acl_libname_spec\"    # typically: libname=lib$name
+          if test -n "$acl_shlibext"; then
+            shrext=".$acl_shlibext"             # typically: shrext=.so
+          else
+            shrext=
+          fi
+          if test $use_additional = yes; then
+            dir="$additional_libdir"
+            dnl The same code as in the loop below:
+            dnl First look for a shared library.
+            if test -n "$acl_shlibext"; then
+              if test -f "$dir/$libname$shrext"; then
+                found_dir="$dir"
+                found_so="$dir/$libname$shrext"
+              else
+                if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
+                  ver=`(cd "$dir" && \
+                        for f in "$libname$shrext".*; do echo "$f"; done \
+                        | sed -e "s,^$libname$shrext\\\\.,," \
+                        | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
+                        | sed 1q ) 2>/dev/null`
+                  if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
+                    found_dir="$dir"
+                    found_so="$dir/$libname$shrext.$ver"
+                  fi
+                else
+                  eval library_names=\"$acl_library_names_spec\"
+                  for f in $library_names; do
+                    if test -f "$dir/$f"; then
+                      found_dir="$dir"
+                      found_so="$dir/$f"
+                      break
+                    fi
+                  done
+                fi
+              fi
+            fi
+            dnl Then look for a static library.
+            if test "X$found_dir" = "X"; then
+              if test -f "$dir/$libname.$acl_libext"; then
+                found_dir="$dir"
+                found_a="$dir/$libname.$acl_libext"
+              fi
+            fi
+            if test "X$found_dir" != "X"; then
+              if test -f "$dir/$libname.la"; then
+                found_la="$dir/$libname.la"
+              fi
+            fi
+          fi
+          if test "X$found_dir" = "X"; then
+            for x in $LDFLAGS $LTLIB[]NAME; do
+              AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+              case "$x" in
+                -L*)
+                  dir=`echo "X$x" | sed -e 's/^X-L//'`
+                  dnl First look for a shared library.
+                  if test -n "$acl_shlibext"; then
+                    if test -f "$dir/$libname$shrext"; then
+                      found_dir="$dir"
+                      found_so="$dir/$libname$shrext"
+                    else
+                      if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then
+                        ver=`(cd "$dir" && \
+                              for f in "$libname$shrext".*; do echo "$f"; done \
+                              | sed -e "s,^$libname$shrext\\\\.,," \
+                              | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
+                              | sed 1q ) 2>/dev/null`
+                        if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then
+                          found_dir="$dir"
+                          found_so="$dir/$libname$shrext.$ver"
+                        fi
+                      else
+                        eval library_names=\"$acl_library_names_spec\"
+                        for f in $library_names; do
+                          if test -f "$dir/$f"; then
+                            found_dir="$dir"
+                            found_so="$dir/$f"
+                            break
+                          fi
+                        done
+                      fi
+                    fi
+                  fi
+                  dnl Then look for a static library.
+                  if test "X$found_dir" = "X"; then
+                    if test -f "$dir/$libname.$acl_libext"; then
+                      found_dir="$dir"
+                      found_a="$dir/$libname.$acl_libext"
+                    fi
+                  fi
+                  if test "X$found_dir" != "X"; then
+                    if test -f "$dir/$libname.la"; then
+                      found_la="$dir/$libname.la"
+                    fi
+                  fi
+                  ;;
+              esac
+              if test "X$found_dir" != "X"; then
+                break
+              fi
+            done
+          fi
+          if test "X$found_dir" != "X"; then
+            dnl Found the library.
+            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name"
+            if test "X$found_so" != "X"; then
+              dnl Linking with a shared library. We attempt to hardcode its
+              dnl directory into the executable's runpath, unless it's the
+              dnl standard /usr/lib.
+              if test "$enable_rpath" = no || test "X$found_dir" = "X/usr/$acl_libdirstem"; then
+                dnl No hardcoding is needed.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+              else
+                dnl Use an explicit option to hardcode DIR into the resulting
+                dnl binary.
+                dnl Potentially add DIR to ltrpathdirs.
+                dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
+                haveit=
+                for x in $ltrpathdirs; do
+                  if test "X$x" = "X$found_dir"; then
+                    haveit=yes
+                    break
+                  fi
+                done
+                if test -z "$haveit"; then
+                  ltrpathdirs="$ltrpathdirs $found_dir"
+                fi
+                dnl The hardcoding into $LIBNAME is system dependent.
+                if test "$acl_hardcode_direct" = yes; then
+                  dnl Using DIR/libNAME.so during linking hardcodes DIR into the
+                  dnl resulting binary.
+                  LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                else
+                  if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
+                    dnl Use an explicit option to hardcode DIR into the resulting
+                    dnl binary.
+                    LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                    dnl Potentially add DIR to rpathdirs.
+                    dnl The rpathdirs will be appended to $LIBNAME at the end.
+                    haveit=
+                    for x in $rpathdirs; do
+                      if test "X$x" = "X$found_dir"; then
+                        haveit=yes
+                        break
+                      fi
+                    done
+                    if test -z "$haveit"; then
+                      rpathdirs="$rpathdirs $found_dir"
+                    fi
+                  else
+                    dnl Rely on "-L$found_dir".
+                    dnl But don't add it if it's already contained in the LDFLAGS
+                    dnl or the already constructed $LIBNAME
+                    haveit=
+                    for x in $LDFLAGS $LIB[]NAME; do
+                      AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                      if test "X$x" = "X-L$found_dir"; then
+                        haveit=yes
+                        break
+                      fi
+                    done
+                    if test -z "$haveit"; then
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir"
+                    fi
+                    if test "$acl_hardcode_minus_L" != no; then
+                      dnl FIXME: Not sure whether we should use
+                      dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+                      dnl here.
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                    else
+                      dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH
+                      dnl here, because this doesn't fit in flags passed to the
+                      dnl compiler. So give up. No hardcoding. This affects only
+                      dnl very old systems.
+                      dnl FIXME: Not sure whether we should use
+                      dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+                      dnl here.
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+                    fi
+                  fi
+                fi
+              fi
+            else
+              if test "X$found_a" != "X"; then
+                dnl Linking with a static library.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a"
+              else
+                dnl We shouldn't come here, but anyway it's good to have a
+                dnl fallback.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name"
+              fi
+            fi
+            dnl Assume the include files are nearby.
+            additional_includedir=
+            case "$found_dir" in
+              */$acl_libdirstem | */$acl_libdirstem/)
+                basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'`
+                LIB[]NAME[]_PREFIX="$basedir"
+                additional_includedir="$basedir/include"
+                ;;
+            esac
+            if test "X$additional_includedir" != "X"; then
+              dnl Potentially add $additional_includedir to $INCNAME.
+              dnl But don't add it
+              dnl   1. if it's the standard /usr/include,
+              dnl   2. if it's /usr/local/include and we are using GCC on Linux,
+              dnl   3. if it's already present in $CPPFLAGS or the already
+              dnl      constructed $INCNAME,
+              dnl   4. if it doesn't exist as a directory.
+              if test "X$additional_includedir" != "X/usr/include"; then
+                haveit=
+                if test "X$additional_includedir" = "X/usr/local/include"; then
+                  if test -n "$GCC"; then
+                    case $host_os in
+                      linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+                    esac
+                  fi
+                fi
+                if test -z "$haveit"; then
+                  for x in $CPPFLAGS $INC[]NAME; do
+                    AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                    if test "X$x" = "X-I$additional_includedir"; then
+                      haveit=yes
+                      break
+                    fi
+                  done
+                  if test -z "$haveit"; then
+                    if test -d "$additional_includedir"; then
+                      dnl Really add $additional_includedir to $INCNAME.
+                      INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir"
+                    fi
+                  fi
+                fi
+              fi
+            fi
+            dnl Look for dependencies.
+            if test -n "$found_la"; then
+              dnl Read the .la file. It defines the variables
+              dnl dlname, library_names, old_library, dependency_libs, current,
+              dnl age, revision, installed, dlopen, dlpreopen, libdir.
+              save_libdir="$libdir"
+              case "$found_la" in
+                */* | *\\*) . "$found_la" ;;
+                *) . "./$found_la" ;;
+              esac
+              libdir="$save_libdir"
+              dnl We use only dependency_libs.
+              for dep in $dependency_libs; do
+                case "$dep" in
+                  -L*)
+                    additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'`
+                    dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME.
+                    dnl But don't add it
+                    dnl   1. if it's the standard /usr/lib,
+                    dnl   2. if it's /usr/local/lib and we are using GCC on Linux,
+                    dnl   3. if it's already present in $LDFLAGS or the already
+                    dnl      constructed $LIBNAME,
+                    dnl   4. if it doesn't exist as a directory.
+                    if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then
+                      haveit=
+                      if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then
+                        if test -n "$GCC"; then
+                          case $host_os in
+                            linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+                          esac
+                        fi
+                      fi
+                      if test -z "$haveit"; then
+                        haveit=
+                        for x in $LDFLAGS $LIB[]NAME; do
+                          AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                          if test "X$x" = "X-L$additional_libdir"; then
+                            haveit=yes
+                            break
+                          fi
+                        done
+                        if test -z "$haveit"; then
+                          if test -d "$additional_libdir"; then
+                            dnl Really add $additional_libdir to $LIBNAME.
+                            LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir"
+                          fi
+                        fi
+                        haveit=
+                        for x in $LDFLAGS $LTLIB[]NAME; do
+                          AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                          if test "X$x" = "X-L$additional_libdir"; then
+                            haveit=yes
+                            break
+                          fi
+                        done
+                        if test -z "$haveit"; then
+                          if test -d "$additional_libdir"; then
+                            dnl Really add $additional_libdir to $LTLIBNAME.
+                            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir"
+                          fi
+                        fi
+                      fi
+                    fi
+                    ;;
+                  -R*)
+                    dir=`echo "X$dep" | sed -e 's/^X-R//'`
+                    if test "$enable_rpath" != no; then
+                      dnl Potentially add DIR to rpathdirs.
+                      dnl The rpathdirs will be appended to $LIBNAME at the end.
+                      haveit=
+                      for x in $rpathdirs; do
+                        if test "X$x" = "X$dir"; then
+                          haveit=yes
+                          break
+                        fi
+                      done
+                      if test -z "$haveit"; then
+                        rpathdirs="$rpathdirs $dir"
+                      fi
+                      dnl Potentially add DIR to ltrpathdirs.
+                      dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
+                      haveit=
+                      for x in $ltrpathdirs; do
+                        if test "X$x" = "X$dir"; then
+                          haveit=yes
+                          break
+                        fi
+                      done
+                      if test -z "$haveit"; then
+                        ltrpathdirs="$ltrpathdirs $dir"
+                      fi
+                    fi
+                    ;;
+                  -l*)
+                    dnl Handle this in the next round.
+                    names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'`
+                    ;;
+                  *.la)
+                    dnl Handle this in the next round. Throw away the .la's
+                    dnl directory; it is already contained in a preceding -L
+                    dnl option.
+                    names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'`
+                    ;;
+                  *)
+                    dnl Most likely an immediate library name.
+                    LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep"
+                    LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep"
+                    ;;
+                esac
+              done
+            fi
+          else
+            dnl Didn't find the library; assume it is in the system directories
+            dnl known to the linker and runtime loader. (All the system
+            dnl directories known to the linker should also be known to the
+            dnl runtime loader, otherwise the system is severely misconfigured.)
+            LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name"
+          fi
+        fi
+      fi
+    done
+  done
+  if test "X$rpathdirs" != "X"; then
+    if test -n "$acl_hardcode_libdir_separator"; then
+      dnl Weird platform: only the last -rpath option counts, the user must
+      dnl pass all path elements in one option. We can arrange that for a
+      dnl single library, but not when more than one $LIBNAMEs are used.
+      alldirs=
+      for found_dir in $rpathdirs; do
+        alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir"
+      done
+      dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl.
+      acl_save_libdir="$libdir"
+      libdir="$alldirs"
+      eval flag=\"$acl_hardcode_libdir_flag_spec\"
+      libdir="$acl_save_libdir"
+      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+    else
+      dnl The -rpath options are cumulative.
+      for found_dir in $rpathdirs; do
+        acl_save_libdir="$libdir"
+        libdir="$found_dir"
+        eval flag=\"$acl_hardcode_libdir_flag_spec\"
+        libdir="$acl_save_libdir"
+        LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+      done
+    fi
+  fi
+  if test "X$ltrpathdirs" != "X"; then
+    dnl When using libtool, the option that works for both libraries and
+    dnl executables is -R. The -R options are cumulative.
+    for found_dir in $ltrpathdirs; do
+      LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir"
+    done
+  fi
+])
+
+dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR,
+dnl unless already present in VAR.
+dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes
+dnl contains two or three consecutive elements that belong together.
+AC_DEFUN([AC_LIB_APPENDTOVAR],
+[
+  for element in [$2]; do
+    haveit=
+    for x in $[$1]; do
+      AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+      if test "X$x" = "X$element"; then
+        haveit=yes
+        break
+      fi
+    done
+    if test -z "$haveit"; then
+      [$1]="${[$1]}${[$1]:+ }$element"
+    fi
+  done
+])
+
+dnl For those cases where a variable contains several -L and -l options
+dnl referring to unknown libraries and directories, this macro determines the
+dnl necessary additional linker options for the runtime path.
+dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL])
+dnl sets LDADDVAR to linker options needed together with LIBSVALUE.
+dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed,
+dnl otherwise linking without libtool is assumed.
+AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS],
+[
+  AC_REQUIRE([AC_LIB_RPATH])
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  $1=
+  if test "$enable_rpath" != no; then
+    if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then
+      dnl Use an explicit option to hardcode directories into the resulting
+      dnl binary.
+      rpathdirs=
+      next=
+      for opt in $2; do
+        if test -n "$next"; then
+          dir="$next"
+          dnl No need to hardcode the standard /usr/lib.
+          if test "X$dir" != "X/usr/$acl_libdirstem"; then
+            rpathdirs="$rpathdirs $dir"
+          fi
+          next=
+        else
+          case $opt in
+            -L) next=yes ;;
+            -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'`
+                 dnl No need to hardcode the standard /usr/lib.
+                 if test "X$dir" != "X/usr/$acl_libdirstem"; then
+                   rpathdirs="$rpathdirs $dir"
+                 fi
+                 next= ;;
+            *) next= ;;
+          esac
+        fi
+      done
+      if test "X$rpathdirs" != "X"; then
+        if test -n ""$3""; then
+          dnl libtool is used for linking. Use -R options.
+          for dir in $rpathdirs; do
+            $1="${$1}${$1:+ }-R$dir"
+          done
+        else
+          dnl The linker is used for linking directly.
+          if test -n "$acl_hardcode_libdir_separator"; then
+            dnl Weird platform: only the last -rpath option counts, the user
+            dnl must pass all path elements in one option.
+            alldirs=
+            for dir in $rpathdirs; do
+              alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir"
+            done
+            acl_save_libdir="$libdir"
+            libdir="$alldirs"
+            eval flag=\"$acl_hardcode_libdir_flag_spec\"
+            libdir="$acl_save_libdir"
+            $1="$flag"
+          else
+            dnl The -rpath options are cumulative.
+            for dir in $rpathdirs; do
+              acl_save_libdir="$libdir"
+              libdir="$dir"
+              eval flag=\"$acl_hardcode_libdir_flag_spec\"
+              libdir="$acl_save_libdir"
+              $1="${$1}${$1:+ }$flag"
+            done
+          fi
+        fi
+      fi
+    fi
+  fi
+  AC_SUBST([$1])
+])
diff --git a/macros/lib-prefix.m4 b/macros/lib-prefix.m4
new file mode 100644
index 0000000..a8684e1
--- /dev/null
+++ b/macros/lib-prefix.m4
@@ -0,0 +1,185 @@
+# lib-prefix.m4 serial 5 (gettext-0.15)
+dnl Copyright (C) 2001-2005 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and
+dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't
+dnl require excessive bracketing.
+ifdef([AC_HELP_STRING],
+[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])],
+[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])])
+
+dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed
+dnl to access previously installed libraries. The basic assumption is that
+dnl a user will want packages to use other packages he previously installed
+dnl with the same --prefix option.
+dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate
+dnl libraries, but is otherwise very convenient.
+AC_DEFUN([AC_LIB_PREFIX],
+[
+  AC_BEFORE([$0], [AC_LIB_LINKFLAGS])
+  AC_REQUIRE([AC_PROG_CC])
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  dnl By default, look in $includedir and $libdir.
+  use_additional=yes
+  AC_LIB_WITH_FINAL_PREFIX([
+    eval additional_includedir=\"$includedir\"
+    eval additional_libdir=\"$libdir\"
+  ])
+  AC_LIB_ARG_WITH([lib-prefix],
+[  --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib
+  --without-lib-prefix    don't search for libraries in includedir and libdir],
+[
+    if test "X$withval" = "Xno"; then
+      use_additional=no
+    else
+      if test "X$withval" = "X"; then
+        AC_LIB_WITH_FINAL_PREFIX([
+          eval additional_includedir=\"$includedir\"
+          eval additional_libdir=\"$libdir\"
+        ])
+      else
+        additional_includedir="$withval/include"
+        additional_libdir="$withval/$acl_libdirstem"
+      fi
+    fi
+])
+  if test $use_additional = yes; then
+    dnl Potentially add $additional_includedir to $CPPFLAGS.
+    dnl But don't add it
+    dnl   1. if it's the standard /usr/include,
+    dnl   2. if it's already present in $CPPFLAGS,
+    dnl   3. if it's /usr/local/include and we are using GCC on Linux,
+    dnl   4. if it doesn't exist as a directory.
+    if test "X$additional_includedir" != "X/usr/include"; then
+      haveit=
+      for x in $CPPFLAGS; do
+        AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+        if test "X$x" = "X-I$additional_includedir"; then
+          haveit=yes
+          break
+        fi
+      done
+      if test -z "$haveit"; then
+        if test "X$additional_includedir" = "X/usr/local/include"; then
+          if test -n "$GCC"; then
+            case $host_os in
+              linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+            esac
+          fi
+        fi
+        if test -z "$haveit"; then
+          if test -d "$additional_includedir"; then
+            dnl Really add $additional_includedir to $CPPFLAGS.
+            CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir"
+          fi
+        fi
+      fi
+    fi
+    dnl Potentially add $additional_libdir to $LDFLAGS.
+    dnl But don't add it
+    dnl   1. if it's the standard /usr/lib,
+    dnl   2. if it's already present in $LDFLAGS,
+    dnl   3. if it's /usr/local/lib and we are using GCC on Linux,
+    dnl   4. if it doesn't exist as a directory.
+    if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then
+      haveit=
+      for x in $LDFLAGS; do
+        AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+        if test "X$x" = "X-L$additional_libdir"; then
+          haveit=yes
+          break
+        fi
+      done
+      if test -z "$haveit"; then
+        if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then
+          if test -n "$GCC"; then
+            case $host_os in
+              linux*) haveit=yes;;
+            esac
+          fi
+        fi
+        if test -z "$haveit"; then
+          if test -d "$additional_libdir"; then
+            dnl Really add $additional_libdir to $LDFLAGS.
+            LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir"
+          fi
+        fi
+      fi
+    fi
+  fi
+])
+
+dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix,
+dnl acl_final_exec_prefix, containing the values to which $prefix and
+dnl $exec_prefix will expand at the end of the configure script.
+AC_DEFUN([AC_LIB_PREPARE_PREFIX],
+[
+  dnl Unfortunately, prefix and exec_prefix get only finally determined
+  dnl at the end of configure.
+  if test "X$prefix" = "XNONE"; then
+    acl_final_prefix="$ac_default_prefix"
+  else
+    acl_final_prefix="$prefix"
+  fi
+  if test "X$exec_prefix" = "XNONE"; then
+    acl_final_exec_prefix='${prefix}'
+  else
+    acl_final_exec_prefix="$exec_prefix"
+  fi
+  acl_save_prefix="$prefix"
+  prefix="$acl_final_prefix"
+  eval acl_final_exec_prefix=\"$acl_final_exec_prefix\"
+  prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the
+dnl variables prefix and exec_prefix bound to the values they will have
+dnl at the end of the configure script.
+AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX],
+[
+  acl_save_prefix="$prefix"
+  prefix="$acl_final_prefix"
+  acl_save_exec_prefix="$exec_prefix"
+  exec_prefix="$acl_final_exec_prefix"
+  $1
+  exec_prefix="$acl_save_exec_prefix"
+  prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_PREPARE_MULTILIB creates a variable acl_libdirstem, containing
+dnl the basename of the libdir, either "lib" or "lib64".
+AC_DEFUN([AC_LIB_PREPARE_MULTILIB],
+[
+  dnl There is no formal standard regarding lib and lib64. The current
+  dnl practice is that on a system supporting 32-bit and 64-bit instruction
+  dnl sets or ABIs, 64-bit libraries go under $prefix/lib64 and 32-bit
+  dnl libraries go under $prefix/lib. We determine the compiler's default
+  dnl mode by looking at the compiler's library search path. If at least
+  dnl of its elements ends in /lib64 or points to a directory whose absolute
+  dnl pathname ends in /lib64, we assume a 64-bit ABI. Otherwise we use the
+  dnl default, namely "lib".
+  acl_libdirstem=lib
+  searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'`
+  if test -n "$searchpath"; then
+    acl_save_IFS="${IFS= 	}"; IFS=":"
+    for searchdir in $searchpath; do
+      if test -d "$searchdir"; then
+        case "$searchdir" in
+          */lib64/ | */lib64 ) acl_libdirstem=lib64 ;;
+          *) searchdir=`cd "$searchdir" && pwd`
+             case "$searchdir" in
+               */lib64 ) acl_libdirstem=lib64 ;;
+             esac ;;
+        esac
+      fi
+    done
+    IFS="$acl_save_IFS"
+  fi
+])
diff --git a/macros/libtool.m4 b/macros/libtool.m4
new file mode 100644
index 0000000..d7c043f
--- /dev/null
+++ b/macros/libtool.m4
@@ -0,0 +1,7997 @@
+# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*-
+#
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+m4_define([_LT_COPYING], [dnl
+#   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
+#                 2006, 2007, 2008, 2009, 2010, 2011 Free Software
+#                 Foundation, Inc.
+#   Written by Gordon Matzigkeit, 1996
+#
+#   This file is part of GNU Libtool.
+#
+# GNU Libtool is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# As a special exception to the GNU General Public License,
+# if you distribute this file as part of a program or library that
+# is built using GNU Libtool, you may include this file under the
+# same distribution terms that you use for the rest of that program.
+#
+# GNU Libtool is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Libtool; see the file COPYING.  If not, a copy
+# can be downloaded from http://www.gnu.org/licenses/gpl.html, or
+# obtained by writing to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+])
+
+# serial 57 LT_INIT
+
+
+# LT_PREREQ(VERSION)
+# ------------------
+# Complain and exit if this libtool version is less that VERSION.
+m4_defun([LT_PREREQ],
+[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1,
+       [m4_default([$3],
+		   [m4_fatal([Libtool version $1 or higher is required],
+		             63)])],
+       [$2])])
+
+
+# _LT_CHECK_BUILDDIR
+# ------------------
+# Complain if the absolute build directory name contains unusual characters
+m4_defun([_LT_CHECK_BUILDDIR],
+[case `pwd` in
+  *\ * | *\	*)
+    AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;;
+esac
+])
+
+
+# LT_INIT([OPTIONS])
+# ------------------
+AC_DEFUN([LT_INIT],
+[AC_PREREQ([2.58])dnl We use AC_INCLUDES_DEFAULT
+AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+AC_BEFORE([$0], [LT_LANG])dnl
+AC_BEFORE([$0], [LT_OUTPUT])dnl
+AC_BEFORE([$0], [LTDL_INIT])dnl
+m4_require([_LT_CHECK_BUILDDIR])dnl
+
+dnl Autoconf doesn't catch unexpanded LT_ macros by default:
+m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl
+m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl
+dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4
+dnl unless we require an AC_DEFUNed macro:
+AC_REQUIRE([LTOPTIONS_VERSION])dnl
+AC_REQUIRE([LTSUGAR_VERSION])dnl
+AC_REQUIRE([LTVERSION_VERSION])dnl
+AC_REQUIRE([LTOBSOLETE_VERSION])dnl
+m4_require([_LT_PROG_LTMAIN])dnl
+
+_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}])
+
+dnl Parse OPTIONS
+_LT_SET_OPTIONS([$0], [$1])
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ltmain"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+AC_SUBST(LIBTOOL)dnl
+
+_LT_SETUP
+
+# Only expand once:
+m4_define([LT_INIT])
+])# LT_INIT
+
+# Old names:
+AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT])
+AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PROG_LIBTOOL], [])
+dnl AC_DEFUN([AM_PROG_LIBTOOL], [])
+
+
+# _LT_CC_BASENAME(CC)
+# -------------------
+# Calculate cc_basename.  Skip known compiler wrappers and cross-prefix.
+m4_defun([_LT_CC_BASENAME],
+[for cc_temp in $1""; do
+  case $cc_temp in
+    compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;;
+    distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;;
+    \-*) ;;
+    *) break;;
+  esac
+done
+cc_basename=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"`
+])
+
+
+# _LT_FILEUTILS_DEFAULTS
+# ----------------------
+# It is okay to use these file commands and assume they have been set
+# sensibly after `m4_require([_LT_FILEUTILS_DEFAULTS])'.
+m4_defun([_LT_FILEUTILS_DEFAULTS],
+[: ${CP="cp -f"}
+: ${MV="mv -f"}
+: ${RM="rm -f"}
+])# _LT_FILEUTILS_DEFAULTS
+
+
+# _LT_SETUP
+# ---------
+m4_defun([_LT_SETUP],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl
+
+_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl
+dnl
+_LT_DECL([], [host_alias], [0], [The host system])dnl
+_LT_DECL([], [host], [0])dnl
+_LT_DECL([], [host_os], [0])dnl
+dnl
+_LT_DECL([], [build_alias], [0], [The build system])dnl
+_LT_DECL([], [build], [0])dnl
+_LT_DECL([], [build_os], [0])dnl
+dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+dnl
+AC_REQUIRE([AC_PROG_LN_S])dnl
+test -z "$LN_S" && LN_S="ln -s"
+_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl
+dnl
+AC_REQUIRE([LT_CMD_MAX_LEN])dnl
+_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl
+_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl
+dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl
+m4_require([_LT_CMD_RELOAD])dnl
+m4_require([_LT_CHECK_MAGIC_METHOD])dnl
+m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl
+m4_require([_LT_CMD_OLD_ARCHIVE])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_WITH_SYSROOT])dnl
+
+_LT_CONFIG_LIBTOOL_INIT([
+# See if we are running on zsh, and set the options which allow our
+# commands through without removal of \ escapes INIT.
+if test -n "\${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+])
+if test -n "${ZSH_VERSION+set}" ; then
+   setopt NO_GLOB_SUBST
+fi
+
+_LT_CHECK_OBJDIR
+
+m4_require([_LT_TAG_COMPILER])dnl
+
+case $host_os in
+aix3*)
+  # AIX sometimes has problems with the GCC collect2 program.  For some
+  # reason, if we set the COLLECT_NAMES environment variable, the problems
+  # vanish in a puff of smoke.
+  if test "X${COLLECT_NAMES+set}" != Xset; then
+    COLLECT_NAMES=
+    export COLLECT_NAMES
+  fi
+  ;;
+esac
+
+# Global variables:
+ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except MSVC,
+# which needs '.lib').
+libext=a
+
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$CC" && CC=cc
+test -z "$LTCC" && LTCC=$CC
+test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS
+test -z "$LD" && LD=ld
+test -z "$ac_objext" && ac_objext=o
+
+_LT_CC_BASENAME([$compiler])
+
+# Only perform the check for file, if the check method requires it
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+case $deplibs_check_method in
+file_magic*)
+  if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+    _LT_PATH_MAGIC
+  fi
+  ;;
+esac
+
+# Use C for the default configuration in the libtool script
+LT_SUPPORTED_TAG([CC])
+_LT_LANG_C_CONFIG
+_LT_LANG_DEFAULT_CONFIG
+_LT_CONFIG_COMMANDS
+])# _LT_SETUP
+
+
+# _LT_PREPARE_SED_QUOTE_VARS
+# --------------------------
+# Define a few sed substitution that help us do robust quoting.
+m4_defun([_LT_PREPARE_SED_QUOTE_VARS],
+[# Backslashify metacharacters that are still active within
+# double-quoted strings.
+sed_quote_subst='s/\([["`$\\]]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([["`\\]]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Sed substitution to delay expansion of an escaped single quote.
+delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g'
+
+# Sed substitution to avoid accidental globbing in evaled expressions
+no_glob_subst='s/\*/\\\*/g'
+])
+
+# _LT_PROG_LTMAIN
+# ---------------
+# Note that this code is called both from `configure', and `config.status'
+# now that we use AC_CONFIG_COMMANDS to generate libtool.  Notably,
+# `config.status' has no value for ac_aux_dir unless we are using Automake,
+# so we pass a copy along to make sure it has a sensible value anyway.
+m4_defun([_LT_PROG_LTMAIN],
+[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl
+_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir'])
+ltmain="$ac_aux_dir/ltmain.sh"
+])# _LT_PROG_LTMAIN
+
+
+## ------------------------------------- ##
+## Accumulate code for creating libtool. ##
+## ------------------------------------- ##
+
+# So that we can recreate a full libtool script including additional
+# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS
+# in macros and then make a single call at the end using the `libtool'
+# label.
+
+
+# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS])
+# ----------------------------------------
+# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL_INIT],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_INIT],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_INIT])
+
+
+# _LT_CONFIG_LIBTOOL([COMMANDS])
+# ------------------------------
+# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later.
+m4_define([_LT_CONFIG_LIBTOOL],
+[m4_ifval([$1],
+          [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS],
+                     [$1
+])])])
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS])
+
+
+# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS])
+# -----------------------------------------------------
+m4_defun([_LT_CONFIG_SAVE_COMMANDS],
+[_LT_CONFIG_LIBTOOL([$1])
+_LT_CONFIG_LIBTOOL_INIT([$2])
+])
+
+
+# _LT_FORMAT_COMMENT([COMMENT])
+# -----------------------------
+# Add leading comment marks to the start of each line, and a trailing
+# full-stop to the whole comment if one is not present already.
+m4_define([_LT_FORMAT_COMMENT],
+[m4_ifval([$1], [
+m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])],
+              [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.])
+)])
+
+
+
+## ------------------------ ##
+## FIXME: Eliminate VARNAME ##
+## ------------------------ ##
+
+
+# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?])
+# -------------------------------------------------------------------
+# CONFIGNAME is the name given to the value in the libtool script.
+# VARNAME is the (base) name used in the configure script.
+# VALUE may be 0, 1 or 2 for a computed quote escaped value based on
+# VARNAME.  Any other value will be used directly.
+m4_define([_LT_DECL],
+[lt_if_append_uniq([lt_decl_varnames], [$2], [, ],
+    [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name],
+	[m4_ifval([$1], [$1], [$2])])
+    lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3])
+    m4_ifval([$4],
+	[lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])])
+    lt_dict_add_subkey([lt_decl_dict], [$2],
+	[tagged?], [m4_ifval([$5], [yes], [no])])])
+])
+
+
+# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION])
+# --------------------------------------------------------
+m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])])
+
+
+# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_tag_varnames],
+[_lt_decl_filter([tagged?], [yes], $@)])
+
+
+# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..])
+# ---------------------------------------------------------
+m4_define([_lt_decl_filter],
+[m4_case([$#],
+  [0], [m4_fatal([$0: too few arguments: $#])],
+  [1], [m4_fatal([$0: too few arguments: $#: $1])],
+  [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)],
+  [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)],
+  [lt_dict_filter([lt_decl_dict], $@)])[]dnl
+])
+
+
+# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...])
+# --------------------------------------------------
+m4_define([lt_decl_quote_varnames],
+[_lt_decl_filter([value], [1], $@)])
+
+
+# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_dquote_varnames],
+[_lt_decl_filter([value], [2], $@)])
+
+
+# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...])
+# ---------------------------------------------------
+m4_define([lt_decl_varnames_tagged],
+[m4_assert([$# <= 2])dnl
+_$0(m4_quote(m4_default([$1], [[, ]])),
+    m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]),
+    m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))])
+m4_define([_lt_decl_varnames_tagged],
+[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])])
+
+
+# lt_decl_all_varnames([SEPARATOR], [VARNAME1...])
+# ------------------------------------------------
+m4_define([lt_decl_all_varnames],
+[_$0(m4_quote(m4_default([$1], [[, ]])),
+     m4_if([$2], [],
+	   m4_quote(lt_decl_varnames),
+	m4_quote(m4_shift($@))))[]dnl
+])
+m4_define([_lt_decl_all_varnames],
+[lt_join($@, lt_decl_varnames_tagged([$1],
+			lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl
+])
+
+
+# _LT_CONFIG_STATUS_DECLARE([VARNAME])
+# ------------------------------------
+# Quote a variable value, and forward it to `config.status' so that its
+# declaration there will have the same value as in `configure'.  VARNAME
+# must have a single quote delimited value for this to work.
+m4_define([_LT_CONFIG_STATUS_DECLARE],
+[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`'])
+
+
+# _LT_CONFIG_STATUS_DECLARATIONS
+# ------------------------------
+# We delimit libtool config variables with single quotes, so when
+# we write them to config.status, we have to be sure to quote all
+# embedded single quotes properly.  In configure, this macro expands
+# each variable declared with _LT_DECL (and _LT_TAGDECL) into:
+#
+#    <var>='`$ECHO "$<var>" | $SED "$delay_single_quote_subst"`'
+m4_defun([_LT_CONFIG_STATUS_DECLARATIONS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames),
+    [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAGS
+# ----------------
+# Output comment and list of tags supported by the script
+m4_defun([_LT_LIBTOOL_TAGS],
+[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl
+available_tags="_LT_TAGS"dnl
+])
+
+
+# _LT_LIBTOOL_DECLARE(VARNAME, [TAG])
+# -----------------------------------
+# Extract the dictionary values for VARNAME (optionally with TAG) and
+# expand to a commented shell variable setting:
+#
+#    # Some comment about what VAR is for.
+#    visible_name=$lt_internal_name
+m4_define([_LT_LIBTOOL_DECLARE],
+[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1],
+					   [description])))[]dnl
+m4_pushdef([_libtool_name],
+    m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl
+m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])),
+    [0], [_libtool_name=[$]$1],
+    [1], [_libtool_name=$lt_[]$1],
+    [2], [_libtool_name=$lt_[]$1],
+    [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl
+m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl
+])
+
+
+# _LT_LIBTOOL_CONFIG_VARS
+# -----------------------
+# Produce commented declarations of non-tagged libtool config variables
+# suitable for insertion in the LIBTOOL CONFIG section of the `libtool'
+# script.  Tagged libtool config variables (even for the LIBTOOL CONFIG
+# section) are produced by _LT_LIBTOOL_TAG_VARS.
+m4_defun([_LT_LIBTOOL_CONFIG_VARS],
+[m4_foreach([_lt_var],
+    m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])])
+
+
+# _LT_LIBTOOL_TAG_VARS(TAG)
+# -------------------------
+m4_define([_LT_LIBTOOL_TAG_VARS],
+[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames),
+    [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])])
+
+
+# _LT_TAGVAR(VARNAME, [TAGNAME])
+# ------------------------------
+m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])])
+
+
+# _LT_CONFIG_COMMANDS
+# -------------------
+# Send accumulated output to $CONFIG_STATUS.  Thanks to the lists of
+# variables for single and double quote escaping we saved from calls
+# to _LT_DECL, we can put quote escaped variables declarations
+# into `config.status', and then the shell code to quote escape them in
+# for loops in `config.status'.  Finally, any additional code accumulated
+# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded.
+m4_defun([_LT_CONFIG_COMMANDS],
+[AC_PROVIDE_IFELSE([LT_OUTPUT],
+	dnl If the libtool generation code has been placed in $CONFIG_LT,
+	dnl instead of duplicating it all over again into config.status,
+	dnl then we will have config.status run $CONFIG_LT later, so it
+	dnl needs to know what name is stored there:
+        [AC_CONFIG_COMMANDS([libtool],
+            [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])],
+    dnl If the libtool generation code is destined for config.status,
+    dnl expand the accumulated commands and init code now:
+    [AC_CONFIG_COMMANDS([libtool],
+        [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])])
+])#_LT_CONFIG_COMMANDS
+
+
+# Initialize.
+m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT],
+[
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+sed_quote_subst='$sed_quote_subst'
+double_quote_subst='$double_quote_subst'
+delay_variable_subst='$delay_variable_subst'
+_LT_CONFIG_STATUS_DECLARATIONS
+LTCC='$LTCC'
+LTCFLAGS='$LTCFLAGS'
+compiler='$compiler_DEFAULT'
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+  eval 'cat <<_LTECHO_EOF
+\$[]1
+_LTECHO_EOF'
+}
+
+# Quote evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_quote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+# Double-quote double-evaled strings.
+for var in lt_decl_all_varnames([[ \
+]], lt_decl_dquote_varnames); do
+    case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in
+    *[[\\\\\\\`\\"\\\$]]*)
+      eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\""
+      ;;
+    *)
+      eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\""
+      ;;
+    esac
+done
+
+_LT_OUTPUT_LIBTOOL_INIT
+])
+
+# _LT_GENERATED_FILE_INIT(FILE, [COMMENT])
+# ------------------------------------
+# Generate a child script FILE with all initialization necessary to
+# reuse the environment learned by the parent script, and make the
+# file executable.  If COMMENT is supplied, it is inserted after the
+# `#!' sequence but before initialization text begins.  After this
+# macro, additional text can be appended to FILE to form the body of
+# the child script.  The macro ends with non-zero status if the
+# file could not be fully written (such as if the disk is full).
+m4_ifdef([AS_INIT_GENERATED],
+[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])],
+[m4_defun([_LT_GENERATED_FILE_INIT],
+[m4_require([AS_PREPARE])]dnl
+[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl
+[lt_write_fail=0
+cat >$1 <<_ASEOF || lt_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+$2
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$1 <<\_ASEOF || lt_write_fail=1
+AS_SHELL_SANITIZE
+_AS_PREPARE
+exec AS_MESSAGE_FD>&1
+_ASEOF
+test $lt_write_fail = 0 && chmod +x $1[]dnl
+m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT
+
+# LT_OUTPUT
+# ---------
+# This macro allows early generation of the libtool script (before
+# AC_OUTPUT is called), incase it is used in configure for compilation
+# tests.
+AC_DEFUN([LT_OUTPUT],
+[: ${CONFIG_LT=./config.lt}
+AC_MSG_NOTICE([creating $CONFIG_LT])
+_LT_GENERATED_FILE_INIT(["$CONFIG_LT"],
+[# Run this file to recreate a libtool stub with the current configuration.])
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+lt_cl_silent=false
+exec AS_MESSAGE_LOG_FD>>config.log
+{
+  echo
+  AS_BOX([Running $as_me.])
+} >&AS_MESSAGE_LOG_FD
+
+lt_cl_help="\
+\`$as_me' creates a local libtool stub from the current configuration,
+for use in further configure time tests before the real libtool is
+generated.
+
+Usage: $[0] [[OPTIONS]]
+
+  -h, --help      print this help, then exit
+  -V, --version   print version number, then exit
+  -q, --quiet     do not print progress messages
+  -d, --debug     don't remove temporary files
+
+Report bugs to <bug-libtool at gnu.org>."
+
+lt_cl_version="\
+m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl
+m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION])
+configured by $[0], generated by m4_PACKAGE_STRING.
+
+Copyright (C) 2011 Free Software Foundation, Inc.
+This config.lt script is free software; the Free Software Foundation
+gives unlimited permision to copy, distribute and modify it."
+
+while test $[#] != 0
+do
+  case $[1] in
+    --version | --v* | -V )
+      echo "$lt_cl_version"; exit 0 ;;
+    --help | --h* | -h )
+      echo "$lt_cl_help"; exit 0 ;;
+    --debug | --d* | -d )
+      debug=: ;;
+    --quiet | --q* | --silent | --s* | -q )
+      lt_cl_silent=: ;;
+
+    -*) AC_MSG_ERROR([unrecognized option: $[1]
+Try \`$[0] --help' for more information.]) ;;
+
+    *) AC_MSG_ERROR([unrecognized argument: $[1]
+Try \`$[0] --help' for more information.]) ;;
+  esac
+  shift
+done
+
+if $lt_cl_silent; then
+  exec AS_MESSAGE_FD>/dev/null
+fi
+_LTEOF
+
+cat >>"$CONFIG_LT" <<_LTEOF
+_LT_OUTPUT_LIBTOOL_COMMANDS_INIT
+_LTEOF
+
+cat >>"$CONFIG_LT" <<\_LTEOF
+AC_MSG_NOTICE([creating $ofile])
+_LT_OUTPUT_LIBTOOL_COMMANDS
+AS_EXIT(0)
+_LTEOF
+chmod +x "$CONFIG_LT"
+
+# configure is writing to config.log, but config.lt does its own redirection,
+# appending to config.log, which fails on DOS, as config.log is still kept
+# open by configure.  Here we exec the FD to /dev/null, effectively closing
+# config.log, so it can be properly (re)opened and appended to by config.lt.
+lt_cl_success=:
+test "$silent" = yes &&
+  lt_config_lt_args="$lt_config_lt_args --quiet"
+exec AS_MESSAGE_LOG_FD>/dev/null
+$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false
+exec AS_MESSAGE_LOG_FD>>config.log
+$lt_cl_success || AS_EXIT(1)
+])# LT_OUTPUT
+
+
+# _LT_CONFIG(TAG)
+# ---------------
+# If TAG is the built-in tag, create an initial libtool script with a
+# default configuration from the untagged config vars.  Otherwise add code
+# to config.status for appending the configuration named by TAG from the
+# matching tagged config vars.
+m4_defun([_LT_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_CONFIG_SAVE_COMMANDS([
+  m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl
+  m4_if(_LT_TAG, [C], [
+    # See if we are running on zsh, and set the options which allow our
+    # commands through without removal of \ escapes.
+    if test -n "${ZSH_VERSION+set}" ; then
+      setopt NO_GLOB_SUBST
+    fi
+
+    cfgfile="${ofile}T"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
+
+    cat <<_LT_EOF >> "$cfgfile"
+#! $SHELL
+
+# `$ECHO "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $as_me ($PACKAGE$TIMESTAMP) $VERSION
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+_LT_COPYING
+_LT_LIBTOOL_TAGS
+
+# ### BEGIN LIBTOOL CONFIG
+_LT_LIBTOOL_CONFIG_VARS
+_LT_LIBTOOL_TAG_VARS
+# ### END LIBTOOL CONFIG
+
+_LT_EOF
+
+  case $host_os in
+  aix3*)
+    cat <<\_LT_EOF >> "$cfgfile"
+# AIX sometimes has problems with the GCC collect2 program.  For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+  COLLECT_NAMES=
+  export COLLECT_NAMES
+fi
+_LT_EOF
+    ;;
+  esac
+
+  _LT_PROG_LTMAIN
+
+  # We use sed instead of cat because bash on DJGPP gets confused if
+  # if finds mixed CR/LF and LF-only lines.  Since sed operates in
+  # text mode, it properly converts lines to CR/LF.  This bash problem
+  # is reportedly fixed, but why not run on old versions too?
+  sed '$q' "$ltmain" >> "$cfgfile" \
+     || (rm -f "$cfgfile"; exit 1)
+
+  _LT_PROG_REPLACE_SHELLFNS
+
+   mv -f "$cfgfile" "$ofile" ||
+    (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile")
+  chmod +x "$ofile"
+],
+[cat <<_LT_EOF >> "$ofile"
+
+dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded
+dnl in a comment (ie after a #).
+# ### BEGIN LIBTOOL TAG CONFIG: $1
+_LT_LIBTOOL_TAG_VARS(_LT_TAG)
+# ### END LIBTOOL TAG CONFIG: $1
+_LT_EOF
+])dnl /m4_if
+],
+[m4_if([$1], [], [
+    PACKAGE='$PACKAGE'
+    VERSION='$VERSION'
+    TIMESTAMP='$TIMESTAMP'
+    RM='$RM'
+    ofile='$ofile'], [])
+])dnl /_LT_CONFIG_SAVE_COMMANDS
+])# _LT_CONFIG
+
+
+# LT_SUPPORTED_TAG(TAG)
+# ---------------------
+# Trace this macro to discover what tags are supported by the libtool
+# --tag option, using:
+#    autoconf --trace 'LT_SUPPORTED_TAG:$1'
+AC_DEFUN([LT_SUPPORTED_TAG], [])
+
+
+# C support is built-in for now
+m4_define([_LT_LANG_C_enabled], [])
+m4_define([_LT_TAGS], [])
+
+
+# LT_LANG(LANG)
+# -------------
+# Enable libtool support for the given language if not already enabled.
+AC_DEFUN([LT_LANG],
+[AC_BEFORE([$0], [LT_OUTPUT])dnl
+m4_case([$1],
+  [C],			[_LT_LANG(C)],
+  [C++],		[_LT_LANG(CXX)],
+  [Go],			[_LT_LANG(GO)],
+  [Java],		[_LT_LANG(GCJ)],
+  [Fortran 77],		[_LT_LANG(F77)],
+  [Fortran],		[_LT_LANG(FC)],
+  [Windows Resource],	[_LT_LANG(RC)],
+  [m4_ifdef([_LT_LANG_]$1[_CONFIG],
+    [_LT_LANG($1)],
+    [m4_fatal([$0: unsupported language: "$1"])])])dnl
+])# LT_LANG
+
+
+# _LT_LANG(LANGNAME)
+# ------------------
+m4_defun([_LT_LANG],
+[m4_ifdef([_LT_LANG_]$1[_enabled], [],
+  [LT_SUPPORTED_TAG([$1])dnl
+  m4_append([_LT_TAGS], [$1 ])dnl
+  m4_define([_LT_LANG_]$1[_enabled], [])dnl
+  _LT_LANG_$1_CONFIG($1)])dnl
+])# _LT_LANG
+
+
+m4_ifndef([AC_PROG_GO], [
+############################################################
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_GO.  When it is available in    #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+############################################################
+m4_defun([AC_PROG_GO],
+[AC_LANG_PUSH(Go)dnl
+AC_ARG_VAR([GOC],     [Go compiler command])dnl
+AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl
+_AC_ARG_VAR_LDFLAGS()dnl
+AC_CHECK_TOOL(GOC, gccgo)
+if test -z "$GOC"; then
+  if test -n "$ac_tool_prefix"; then
+    AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo])
+  fi
+fi
+if test -z "$GOC"; then
+  AC_CHECK_PROG(GOC, gccgo, gccgo, false)
+fi
+])#m4_defun
+])#m4_ifndef
+
+
+# _LT_LANG_DEFAULT_CONFIG
+# -----------------------
+m4_defun([_LT_LANG_DEFAULT_CONFIG],
+[AC_PROVIDE_IFELSE([AC_PROG_CXX],
+  [LT_LANG(CXX)],
+  [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_F77],
+  [LT_LANG(F77)],
+  [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])])
+
+AC_PROVIDE_IFELSE([AC_PROG_FC],
+  [LT_LANG(FC)],
+  [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])])
+
+dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal
+dnl pulling things in needlessly.
+AC_PROVIDE_IFELSE([AC_PROG_GCJ],
+  [LT_LANG(GCJ)],
+  [AC_PROVIDE_IFELSE([A][M_PROG_GCJ],
+    [LT_LANG(GCJ)],
+    [AC_PROVIDE_IFELSE([LT_PROG_GCJ],
+      [LT_LANG(GCJ)],
+      [m4_ifdef([AC_PROG_GCJ],
+	[m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([A][M_PROG_GCJ],
+	[m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])])
+       m4_ifdef([LT_PROG_GCJ],
+	[m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])])
+
+AC_PROVIDE_IFELSE([AC_PROG_GO],
+  [LT_LANG(GO)],
+  [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])])
+
+AC_PROVIDE_IFELSE([LT_PROG_RC],
+  [LT_LANG(RC)],
+  [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])])
+])# _LT_LANG_DEFAULT_CONFIG
+
+# Obsolete macros:
+AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)])
+AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)])
+AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)])
+AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)])
+AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_CXX], [])
+dnl AC_DEFUN([AC_LIBTOOL_F77], [])
+dnl AC_DEFUN([AC_LIBTOOL_FC], [])
+dnl AC_DEFUN([AC_LIBTOOL_GCJ], [])
+dnl AC_DEFUN([AC_LIBTOOL_RC], [])
+
+
+# _LT_TAG_COMPILER
+# ----------------
+m4_defun([_LT_TAG_COMPILER],
+[AC_REQUIRE([AC_PROG_CC])dnl
+
+_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl
+_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl
+_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl
+_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl
+
+# If no C compiler was specified, use CC.
+LTCC=${LTCC-"$CC"}
+
+# If no C compiler flags were specified, use CFLAGS.
+LTCFLAGS=${LTCFLAGS-"$CFLAGS"}
+
+# Allow CC to be a program name with arguments.
+compiler=$CC
+])# _LT_TAG_COMPILER
+
+
+# _LT_COMPILER_BOILERPLATE
+# ------------------------
+# Check for compiler boilerplate output or warnings with
+# the simple compiler test code.
+m4_defun([_LT_COMPILER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_compile_test_code" >conftest.$ac_ext
+eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_compiler_boilerplate=`cat conftest.err`
+$RM conftest*
+])# _LT_COMPILER_BOILERPLATE
+
+
+# _LT_LINKER_BOILERPLATE
+# ----------------------
+# Check for linker boilerplate output or warnings with
+# the simple link test code.
+m4_defun([_LT_LINKER_BOILERPLATE],
+[m4_require([_LT_DECL_SED])dnl
+ac_outfile=conftest.$ac_objext
+echo "$lt_simple_link_test_code" >conftest.$ac_ext
+eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err
+_lt_linker_boilerplate=`cat conftest.err`
+$RM -r conftest*
+])# _LT_LINKER_BOILERPLATE
+
+# _LT_REQUIRED_DARWIN_CHECKS
+# -------------------------
+m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[
+  case $host_os in
+    rhapsody* | darwin*)
+    AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:])
+    AC_CHECK_TOOL([NMEDIT], [nmedit], [:])
+    AC_CHECK_TOOL([LIPO], [lipo], [:])
+    AC_CHECK_TOOL([OTOOL], [otool], [:])
+    AC_CHECK_TOOL([OTOOL64], [otool64], [:])
+    _LT_DECL([], [DSYMUTIL], [1],
+      [Tool to manipulate archived DWARF debug symbol files on Mac OS X])
+    _LT_DECL([], [NMEDIT], [1],
+      [Tool to change global to local symbols on Mac OS X])
+    _LT_DECL([], [LIPO], [1],
+      [Tool to manipulate fat objects and archives on Mac OS X])
+    _LT_DECL([], [OTOOL], [1],
+      [ldd/readelf like tool for Mach-O binaries on Mac OS X])
+    _LT_DECL([], [OTOOL64], [1],
+      [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4])
+
+    AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod],
+      [lt_cv_apple_cc_single_mod=no
+      if test -z "${LT_MULTI_MODULE}"; then
+	# By default we will add the -single_module flag. You can override
+	# by either setting the environment variable LT_MULTI_MODULE
+	# non-empty at configure time, or by adding -multi_module to the
+	# link flags.
+	rm -rf libconftest.dylib*
+	echo "int foo(void){return 1;}" > conftest.c
+	echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD
+	$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \
+	  -dynamiclib -Wl,-single_module conftest.c 2>conftest.err
+        _lt_result=$?
+	# If there is a non-empty error log, and "single_module"
+	# appears in it, assume the flag caused a linker warning
+        if test -s conftest.err && $GREP single_module conftest.err; then
+	  cat conftest.err >&AS_MESSAGE_LOG_FD
+	# Otherwise, if the output was created with a 0 exit code from
+	# the compiler, it worked.
+	elif test -f libconftest.dylib && test $_lt_result -eq 0; then
+	  lt_cv_apple_cc_single_mod=yes
+	else
+	  cat conftest.err >&AS_MESSAGE_LOG_FD
+	fi
+	rm -rf libconftest.dylib*
+	rm -f conftest.*
+      fi])
+
+    AC_CACHE_CHECK([for -exported_symbols_list linker flag],
+      [lt_cv_ld_exported_symbols_list],
+      [lt_cv_ld_exported_symbols_list=no
+      save_LDFLAGS=$LDFLAGS
+      echo "_main" > conftest.sym
+      LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym"
+      AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+	[lt_cv_ld_exported_symbols_list=yes],
+	[lt_cv_ld_exported_symbols_list=no])
+	LDFLAGS="$save_LDFLAGS"
+    ])
+
+    AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load],
+      [lt_cv_ld_force_load=no
+      cat > conftest.c << _LT_EOF
+int forced_loaded() { return 2;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD
+      echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD
+      $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD
+      echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD
+      $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD
+      cat > conftest.c << _LT_EOF
+int main() { return 0;}
+_LT_EOF
+      echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD
+      $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err
+      _lt_result=$?
+      if test -s conftest.err && $GREP force_load conftest.err; then
+	cat conftest.err >&AS_MESSAGE_LOG_FD
+      elif test -f conftest && test $_lt_result -eq 0 && $GREP forced_load conftest >/dev/null 2>&1 ; then
+	lt_cv_ld_force_load=yes
+      else
+	cat conftest.err >&AS_MESSAGE_LOG_FD
+      fi
+        rm -f conftest.err libconftest.a conftest conftest.c
+        rm -rf conftest.dSYM
+    ])
+    case $host_os in
+    rhapsody* | darwin1.[[012]])
+      _lt_dar_allow_undefined='${wl}-undefined ${wl}suppress' ;;
+    darwin1.*)
+      _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+    darwin*) # darwin 5.x on
+      # if running on 10.5 or later, the deployment target defaults
+      # to the OS version, if on x86, and 10.4, the deployment
+      # target defaults to 10.4. Don't you love it?
+      case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in
+	10.0,*86*-darwin8*|10.0,*-darwin[[91]]*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+	10.[[012]]*)
+	  _lt_dar_allow_undefined='${wl}-flat_namespace ${wl}-undefined ${wl}suppress' ;;
+	10.*)
+	  _lt_dar_allow_undefined='${wl}-undefined ${wl}dynamic_lookup' ;;
+      esac
+    ;;
+  esac
+    if test "$lt_cv_apple_cc_single_mod" = "yes"; then
+      _lt_dar_single_mod='$single_module'
+    fi
+    if test "$lt_cv_ld_exported_symbols_list" = "yes"; then
+      _lt_dar_export_syms=' ${wl}-exported_symbols_list,$output_objdir/${libname}-symbols.expsym'
+    else
+      _lt_dar_export_syms='~$NMEDIT -s $output_objdir/${libname}-symbols.expsym ${lib}'
+    fi
+    if test "$DSYMUTIL" != ":" && test "$lt_cv_ld_force_load" = "no"; then
+      _lt_dsymutil='~$DSYMUTIL $lib || :'
+    else
+      _lt_dsymutil=
+    fi
+    ;;
+  esac
+])
+
+
+# _LT_DARWIN_LINKER_FEATURES([TAG])
+# ---------------------------------
+# Checks for linker and compiler features on darwin
+m4_defun([_LT_DARWIN_LINKER_FEATURES],
+[
+  m4_require([_LT_REQUIRED_DARWIN_CHECKS])
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_automatic, $1)=yes
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  if test "$lt_cv_ld_force_load" = "yes"; then
+    _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience ${wl}-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`'
+    m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes],
+                  [FC],  [_LT_TAGVAR(compiler_needs_object, $1)=yes])
+  else
+    _LT_TAGVAR(whole_archive_flag_spec, $1)=''
+  fi
+  _LT_TAGVAR(link_all_deplibs, $1)=yes
+  _LT_TAGVAR(allow_undefined_flag, $1)="$_lt_dar_allow_undefined"
+  case $cc_basename in
+     ifort*) _lt_dar_can_shared=yes ;;
+     *) _lt_dar_can_shared=$GCC ;;
+  esac
+  if test "$_lt_dar_can_shared" = "yes"; then
+    output_verbose_link_cmd=func_echo_all
+    _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod${_lt_dsymutil}"
+    _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dsymutil}"
+    _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring ${_lt_dar_single_mod}${_lt_dar_export_syms}${_lt_dsymutil}"
+    _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags${_lt_dar_export_syms}${_lt_dsymutil}"
+    m4_if([$1], [CXX],
+[   if test "$lt_cv_apple_cc_single_mod" != "yes"; then
+      _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dsymutil}"
+      _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's,^,_,' < \$export_symbols > \$output_objdir/\${libname}-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \${lib}-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \${lib}-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring${_lt_dar_export_syms}${_lt_dsymutil}"
+    fi
+],[])
+  else
+  _LT_TAGVAR(ld_shlibs, $1)=no
+  fi
+])
+
+# _LT_SYS_MODULE_PATH_AIX([TAGNAME])
+# ----------------------------------
+# Links a minimal program and checks the executable
+# for the system default hardcoded library path. In most cases,
+# this is /usr/lib:/lib, but when the MPI compilers are used
+# the location of the communication and MPI libs are included too.
+# If we don't find anything, use the default library path according
+# to the aix ld manual.
+# Store the results from the different compilers for each TAGNAME.
+# Allow to override them for all tags through lt_cv_aix_libpath.
+m4_defun([_LT_SYS_MODULE_PATH_AIX],
+[m4_require([_LT_DECL_SED])dnl
+if test "${lt_cv_aix_libpath+set}" = set; then
+  aix_libpath=$lt_cv_aix_libpath
+else
+  AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])],
+  [AC_LINK_IFELSE([AC_LANG_PROGRAM],[
+  lt_aix_libpath_sed='[
+      /Import File Strings/,/^$/ {
+	  /^0/ {
+	      s/^0  *\([^ ]*\) *$/\1/
+	      p
+	  }
+      }]'
+  _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  # Check for a 64-bit object if we didn't find anything.
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"`
+  fi],[])
+  if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then
+    _LT_TAGVAR([lt_cv_aix_libpath_], [$1])="/usr/lib:/lib"
+  fi
+  ])
+  aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])
+fi
+])# _LT_SYS_MODULE_PATH_AIX
+
+
+# _LT_SHELL_INIT(ARG)
+# -------------------
+m4_define([_LT_SHELL_INIT],
+[m4_divert_text([M4SH-INIT], [$1
+])])# _LT_SHELL_INIT
+
+
+
+# _LT_PROG_ECHO_BACKSLASH
+# -----------------------
+# Find how we can fake an echo command that does not interpret backslash.
+# In particular, with Autoconf 2.60 or later we add some code to the start
+# of the generated configure script which will find a shell with a builtin
+# printf (which we can use as an echo command).
+m4_defun([_LT_PROG_ECHO_BACKSLASH],
+[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+
+AC_MSG_CHECKING([how to print strings])
+# Test print first, because it will be a builtin if present.
+if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \
+   test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='print -r --'
+elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then
+  ECHO='printf %s\n'
+else
+  # Use this function as a fallback that always works.
+  func_fallback_echo ()
+  {
+    eval 'cat <<_LTECHO_EOF
+$[]1
+_LTECHO_EOF'
+  }
+  ECHO='func_fallback_echo'
+fi
+
+# func_echo_all arg...
+# Invoke $ECHO with all args, space-separated.
+func_echo_all ()
+{
+    $ECHO "$*" 
+}
+
+case "$ECHO" in
+  printf*) AC_MSG_RESULT([printf]) ;;
+  print*) AC_MSG_RESULT([print -r]) ;;
+  *) AC_MSG_RESULT([cat]) ;;
+esac
+
+m4_ifdef([_AS_DETECT_SUGGESTED],
+[_AS_DETECT_SUGGESTED([
+  test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || (
+    ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO
+    ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO
+    PATH=/empty FPATH=/empty; export PATH FPATH
+    test "X`printf %s $ECHO`" = "X$ECHO" \
+      || test "X`print -r -- $ECHO`" = "X$ECHO" )])])
+
+_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts])
+_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes])
+])# _LT_PROG_ECHO_BACKSLASH
+
+
+# _LT_WITH_SYSROOT
+# ----------------
+AC_DEFUN([_LT_WITH_SYSROOT],
+[AC_MSG_CHECKING([for sysroot])
+AC_ARG_WITH([sysroot],
+[  --with-sysroot[=DIR] Search for dependent libraries within DIR
+                        (or the compiler's sysroot if not specified).],
+[], [with_sysroot=no])
+
+dnl lt_sysroot will always be passed unquoted.  We quote it here
+dnl in case the user passed a directory name.
+lt_sysroot=
+case ${with_sysroot} in #(
+ yes)
+   if test "$GCC" = yes; then
+     lt_sysroot=`$CC --print-sysroot 2>/dev/null`
+   fi
+   ;; #(
+ /*)
+   lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"`
+   ;; #(
+ no|'')
+   ;; #(
+ *)
+   AC_MSG_RESULT([${with_sysroot}])
+   AC_MSG_ERROR([The sysroot must be an absolute path.])
+   ;;
+esac
+
+ AC_MSG_RESULT([${lt_sysroot:-no}])
+_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl
+[dependent libraries, and in which our libraries should be installed.])])
+
+# _LT_ENABLE_LOCK
+# ---------------
+m4_defun([_LT_ENABLE_LOCK],
+[AC_ARG_ENABLE([libtool-lock],
+  [AS_HELP_STRING([--disable-libtool-lock],
+    [avoid locking (might break parallel builds)])])
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+ia64-*-hpux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.$ac_objext` in
+      *ELF-32*)
+	HPUX_IA64_MODE="32"
+	;;
+      *ELF-64*)
+	HPUX_IA64_MODE="64"
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+*-*-irix6*)
+  # Find out which ABI we are using.
+  echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    if test "$lt_cv_prog_gnu_ld" = yes; then
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -melf32bsmip"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -melf32bmipn32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -melf64bmip"
+	;;
+      esac
+    else
+      case `/usr/bin/file conftest.$ac_objext` in
+	*32-bit*)
+	  LD="${LD-ld} -32"
+	  ;;
+	*N32*)
+	  LD="${LD-ld} -n32"
+	  ;;
+	*64-bit*)
+	  LD="${LD-ld} -64"
+	  ;;
+      esac
+    fi
+  fi
+  rm -rf conftest*
+  ;;
+
+x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \
+s390*-*linux*|s390*-*tpf*|sparc*-*linux*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+      *32-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_i386_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    case `/usr/bin/file conftest.o` in
+	      *x86-64*)
+		LD="${LD-ld} -m elf32_x86_64"
+		;;
+	      *)
+		LD="${LD-ld} -m elf_i386"
+		;;
+	    esac
+	    ;;
+	  powerpc64le-*)
+	    LD="${LD-ld} -m elf32lppclinux"
+	    ;;
+	  powerpc64-*)
+	    LD="${LD-ld} -m elf32ppclinux"
+	    ;;
+	  s390x-*linux*)
+	    LD="${LD-ld} -m elf_s390"
+	    ;;
+	  sparc64-*linux*)
+	    LD="${LD-ld} -m elf32_sparc"
+	    ;;
+	esac
+	;;
+      *64-bit*)
+	case $host in
+	  x86_64-*kfreebsd*-gnu)
+	    LD="${LD-ld} -m elf_x86_64_fbsd"
+	    ;;
+	  x86_64-*linux*)
+	    LD="${LD-ld} -m elf_x86_64"
+	    ;;
+	  powerpcle-*)
+	    LD="${LD-ld} -m elf64lppc"
+	    ;;
+	  powerpc-*)
+	    LD="${LD-ld} -m elf64ppc"
+	    ;;
+	  s390*-*linux*|s390*-*tpf*)
+	    LD="${LD-ld} -m elf64_s390"
+	    ;;
+	  sparc*-*linux*)
+	    LD="${LD-ld} -m elf64_sparc"
+	    ;;
+	esac
+	;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+
+*-*-sco3.2v5*)
+  # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+  SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS -belf"
+  AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf,
+    [AC_LANG_PUSH(C)
+     AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no])
+     AC_LANG_POP])
+  if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+    # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+    CFLAGS="$SAVE_CFLAGS"
+  fi
+  ;;
+*-*solaris*)
+  # Find out which ABI we are using.
+  echo 'int i;' > conftest.$ac_ext
+  if AC_TRY_EVAL(ac_compile); then
+    case `/usr/bin/file conftest.o` in
+    *64-bit*)
+      case $lt_cv_prog_gnu_ld in
+      yes*)
+        case $host in
+        i?86-*-solaris*)
+          LD="${LD-ld} -m elf_x86_64"
+          ;;
+        sparc*-*-solaris*)
+          LD="${LD-ld} -m elf64_sparc"
+          ;;
+        esac
+        # GNU ld 2.21 introduced _sol2 emulations.  Use them if available.
+        if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then
+          LD="${LD-ld}_sol2"
+        fi
+        ;;
+      *)
+	if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then
+	  LD="${LD-ld} -64"
+	fi
+	;;
+      esac
+      ;;
+    esac
+  fi
+  rm -rf conftest*
+  ;;
+esac
+
+need_locks="$enable_libtool_lock"
+])# _LT_ENABLE_LOCK
+
+
+# _LT_PROG_AR
+# -----------
+m4_defun([_LT_PROG_AR],
+[AC_CHECK_TOOLS(AR, [ar], false)
+: ${AR=ar}
+: ${AR_FLAGS=cru}
+_LT_DECL([], [AR], [1], [The archiver])
+_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive])
+
+AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file],
+  [lt_cv_ar_at_file=no
+   AC_COMPILE_IFELSE([AC_LANG_PROGRAM],
+     [echo conftest.$ac_objext > conftest.lst
+      lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD'
+      AC_TRY_EVAL([lt_ar_try])
+      if test "$ac_status" -eq 0; then
+	# Ensure the archiver fails upon bogus file names.
+	rm -f conftest.$ac_objext libconftest.a
+	AC_TRY_EVAL([lt_ar_try])
+	if test "$ac_status" -ne 0; then
+          lt_cv_ar_at_file=@
+        fi
+      fi
+      rm -f conftest.* libconftest.a
+     ])
+  ])
+
+if test "x$lt_cv_ar_at_file" = xno; then
+  archiver_list_spec=
+else
+  archiver_list_spec=$lt_cv_ar_at_file
+fi
+_LT_DECL([], [archiver_list_spec], [1],
+  [How to feed a file listing to the archiver])
+])# _LT_PROG_AR
+
+
+# _LT_CMD_OLD_ARCHIVE
+# -------------------
+m4_defun([_LT_CMD_OLD_ARCHIVE],
+[_LT_PROG_AR
+
+AC_CHECK_TOOL(STRIP, strip, :)
+test -z "$STRIP" && STRIP=:
+_LT_DECL([], [STRIP], [1], [A symbol stripping program])
+
+AC_CHECK_TOOL(RANLIB, ranlib, :)
+test -z "$RANLIB" && RANLIB=:
+_LT_DECL([], [RANLIB], [1],
+    [Commands used to install an old-style archive])
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+  case $host_os in
+  openbsd*)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib"
+    ;;
+  *)
+    old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib"
+    ;;
+  esac
+  old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib"
+fi
+
+case $host_os in
+  darwin*)
+    lock_old_archive_extraction=yes ;;
+  *)
+    lock_old_archive_extraction=no ;;
+esac
+_LT_DECL([], [old_postinstall_cmds], [2])
+_LT_DECL([], [old_postuninstall_cmds], [2])
+_LT_TAGDECL([], [old_archive_cmds], [2],
+    [Commands used to build an old-style archive])
+_LT_DECL([], [lock_old_archive_extraction], [0],
+    [Whether to use a lock for old archive extraction])
+])# _LT_CMD_OLD_ARCHIVE
+
+
+# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#		[OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------------------
+# Check whether the given compiler option works
+AC_DEFUN([_LT_COMPILER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4])
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+   lt_compiler_flag="$3"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   # The option is referenced via a variable to avoid confusing sed.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>conftest.err)
+   ac_status=$?
+   cat conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s "$ac_outfile"; then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings other than the usual output.
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp
+     $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+     if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then
+       $2=yes
+     fi
+   fi
+   $RM conftest*
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$5], , :, [$5])
+else
+    m4_if([$6], , :, [$6])
+fi
+])# _LT_COMPILER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], [])
+
+
+# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS,
+#                  [ACTION-SUCCESS], [ACTION-FAILURE])
+# ----------------------------------------------------
+# Check whether the given linker option works
+AC_DEFUN([_LT_LINKER_OPTION],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_SED])dnl
+AC_CACHE_CHECK([$1], [$2],
+  [$2=no
+   save_LDFLAGS="$LDFLAGS"
+   LDFLAGS="$LDFLAGS $3"
+   echo "$lt_simple_link_test_code" > conftest.$ac_ext
+   if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then
+     # The linker can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     if test -s conftest.err; then
+       # Append any errors to the config.log.
+       cat conftest.err 1>&AS_MESSAGE_LOG_FD
+       $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp
+       $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2
+       if diff conftest.exp conftest.er2 >/dev/null; then
+         $2=yes
+       fi
+     else
+       $2=yes
+     fi
+   fi
+   $RM -r conftest*
+   LDFLAGS="$save_LDFLAGS"
+])
+
+if test x"[$]$2" = xyes; then
+    m4_if([$4], , :, [$4])
+else
+    m4_if([$5], , :, [$5])
+fi
+])# _LT_LINKER_OPTION
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], [])
+
+
+# LT_CMD_MAX_LEN
+#---------------
+AC_DEFUN([LT_CMD_MAX_LEN],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+# find the maximum length of command line arguments
+AC_MSG_CHECKING([the maximum length of command line arguments])
+AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl
+  i=0
+  teststring="ABCD"
+
+  case $build_os in
+  msdosdjgpp*)
+    # On DJGPP, this test can blow up pretty badly due to problems in libc
+    # (any single argument exceeding 2000 bytes causes a buffer overrun
+    # during glob expansion).  Even if it were fixed, the result of this
+    # check would be larger than it should be.
+    lt_cv_sys_max_cmd_len=12288;    # 12K is about right
+    ;;
+
+  gnu*)
+    # Under GNU Hurd, this test is not required because there is
+    # no limit to the length of command line arguments.
+    # Libtool will interpret -1 as no limit whatsoever
+    lt_cv_sys_max_cmd_len=-1;
+    ;;
+
+  cygwin* | mingw* | cegcc*)
+    # On Win9x/ME, this test blows up -- it succeeds, but takes
+    # about 5 minutes as the teststring grows exponentially.
+    # Worse, since 9x/ME are not pre-emptively multitasking,
+    # you end up with a "frozen" computer, even though with patience
+    # the test eventually succeeds (with a max line length of 256k).
+    # Instead, let's just punt: use the minimum linelength reported by
+    # all of the supported platforms: 8192 (on NT/2K/XP).
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  mint*)
+    # On MiNT this can take a long time and run out of memory.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  amigaos*)
+    # On AmigaOS with pdksh, this test takes hours, literally.
+    # So we just punt and use a minimum line length of 8192.
+    lt_cv_sys_max_cmd_len=8192;
+    ;;
+
+  netbsd* | freebsd* | openbsd* | darwin* | dragonfly*)
+    # This has been around since 386BSD, at least.  Likely further.
+    if test -x /sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax`
+    elif test -x /usr/sbin/sysctl; then
+      lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax`
+    else
+      lt_cv_sys_max_cmd_len=65536	# usable default for all BSDs
+    fi
+    # And add a safety zone
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+    lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    ;;
+
+  interix*)
+    # We know the value 262144 and hardcode it with a safety zone (like BSD)
+    lt_cv_sys_max_cmd_len=196608
+    ;;
+
+  os2*)
+    # The test takes a long time on OS/2.
+    lt_cv_sys_max_cmd_len=8192
+    ;;
+
+  osf*)
+    # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure
+    # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not
+    # nice to cause kernel panics so lets avoid the loop below.
+    # First set a reasonable default.
+    lt_cv_sys_max_cmd_len=16384
+    #
+    if test -x /sbin/sysconfig; then
+      case `/sbin/sysconfig -q proc exec_disable_arg_limit` in
+        *1*) lt_cv_sys_max_cmd_len=-1 ;;
+      esac
+    fi
+    ;;
+  sco3.2v5*)
+    lt_cv_sys_max_cmd_len=102400
+    ;;
+  sysv5* | sco5v6* | sysv4.2uw2*)
+    kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null`
+    if test -n "$kargmax"; then
+      lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[	 ]]//'`
+    else
+      lt_cv_sys_max_cmd_len=32768
+    fi
+    ;;
+  *)
+    lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null`
+    if test -n "$lt_cv_sys_max_cmd_len" && \
+	test undefined != "$lt_cv_sys_max_cmd_len"; then
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4`
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3`
+    else
+      # Make teststring a little bigger before we do anything with it.
+      # a 1K string should be a reasonable start.
+      for i in 1 2 3 4 5 6 7 8 ; do
+        teststring=$teststring$teststring
+      done
+      SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}}
+      # If test is not a shell built-in, we'll probably end up computing a
+      # maximum length that is only half of the actual maximum length, but
+      # we can't tell.
+      while { test "X"`env echo "$teststring$teststring" 2>/dev/null` \
+	         = "X$teststring$teststring"; } >/dev/null 2>&1 &&
+	      test $i != 17 # 1/2 MB should be enough
+      do
+        i=`expr $i + 1`
+        teststring=$teststring$teststring
+      done
+      # Only check the string length outside the loop.
+      lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1`
+      teststring=
+      # Add a significant safety factor because C++ compilers can tack on
+      # massive amounts of additional arguments before passing them to the
+      # linker.  It appears as though 1/2 is a usable value.
+      lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2`
+    fi
+    ;;
+  esac
+])
+if test -n $lt_cv_sys_max_cmd_len ; then
+  AC_MSG_RESULT($lt_cv_sys_max_cmd_len)
+else
+  AC_MSG_RESULT(none)
+fi
+max_cmd_len=$lt_cv_sys_max_cmd_len
+_LT_DECL([], [max_cmd_len], [0],
+    [What is the maximum length of a command?])
+])# LT_CMD_MAX_LEN
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], [])
+
+
+# _LT_HEADER_DLFCN
+# ----------------
+m4_defun([_LT_HEADER_DLFCN],
+[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl
+])# _LT_HEADER_DLFCN
+
+
+# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+#                      ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ----------------------------------------------------------------
+m4_defun([_LT_TRY_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "$cross_compiling" = yes; then :
+  [$4]
+else
+  lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+  lt_status=$lt_dlunknown
+  cat > conftest.$ac_ext <<_LT_EOF
+[#line $LINENO "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+#  define LT_DLGLOBAL		RTLD_GLOBAL
+#else
+#  ifdef DL_GLOBAL
+#    define LT_DLGLOBAL		DL_GLOBAL
+#  else
+#    define LT_DLGLOBAL		0
+#  endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+   find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+#  ifdef RTLD_LAZY
+#    define LT_DLLAZY_OR_NOW		RTLD_LAZY
+#  else
+#    ifdef DL_LAZY
+#      define LT_DLLAZY_OR_NOW		DL_LAZY
+#    else
+#      ifdef RTLD_NOW
+#        define LT_DLLAZY_OR_NOW	RTLD_NOW
+#      else
+#        ifdef DL_NOW
+#          define LT_DLLAZY_OR_NOW	DL_NOW
+#        else
+#          define LT_DLLAZY_OR_NOW	0
+#        endif
+#      endif
+#    endif
+#  endif
+#endif
+
+/* When -fvisbility=hidden is used, assume the code has been annotated
+   correspondingly for the symbols needed.  */
+#if defined(__GNUC__) && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3))
+int fnord () __attribute__((visibility("default")));
+#endif
+
+int fnord () { return 42; }
+int main ()
+{
+  void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+  int status = $lt_dlunknown;
+
+  if (self)
+    {
+      if (dlsym (self,"fnord"))       status = $lt_dlno_uscore;
+      else
+        {
+	  if (dlsym( self,"_fnord"))  status = $lt_dlneed_uscore;
+          else puts (dlerror ());
+	}
+      /* dlclose (self); */
+    }
+  else
+    puts (dlerror ());
+
+  return status;
+}]
+_LT_EOF
+  if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then
+    (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null
+    lt_status=$?
+    case x$lt_status in
+      x$lt_dlno_uscore) $1 ;;
+      x$lt_dlneed_uscore) $2 ;;
+      x$lt_dlunknown|x*) $3 ;;
+    esac
+  else :
+    # compilation failed
+    $3
+  fi
+fi
+rm -fr conftest*
+])# _LT_TRY_DLOPEN_SELF
+
+
+# LT_SYS_DLOPEN_SELF
+# ------------------
+AC_DEFUN([LT_SYS_DLOPEN_SELF],
+[m4_require([_LT_HEADER_DLFCN])dnl
+if test "x$enable_dlopen" != xyes; then
+  enable_dlopen=unknown
+  enable_dlopen_self=unknown
+  enable_dlopen_self_static=unknown
+else
+  lt_cv_dlopen=no
+  lt_cv_dlopen_libs=
+
+  case $host_os in
+  beos*)
+    lt_cv_dlopen="load_add_on"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ;;
+
+  mingw* | pw32* | cegcc*)
+    lt_cv_dlopen="LoadLibrary"
+    lt_cv_dlopen_libs=
+    ;;
+
+  cygwin*)
+    lt_cv_dlopen="dlopen"
+    lt_cv_dlopen_libs=
+    ;;
+
+  darwin*)
+  # if libdl is installed we need to link against it
+    AC_CHECK_LIB([dl], [dlopen],
+		[lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],[
+    lt_cv_dlopen="dyld"
+    lt_cv_dlopen_libs=
+    lt_cv_dlopen_self=yes
+    ])
+    ;;
+
+  *)
+    AC_CHECK_FUNC([shl_load],
+	  [lt_cv_dlopen="shl_load"],
+      [AC_CHECK_LIB([dld], [shl_load],
+	    [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-ldld"],
+	[AC_CHECK_FUNC([dlopen],
+	      [lt_cv_dlopen="dlopen"],
+	  [AC_CHECK_LIB([dl], [dlopen],
+		[lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],
+	    [AC_CHECK_LIB([svld], [dlopen],
+		  [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"],
+	      [AC_CHECK_LIB([dld], [dld_link],
+		    [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-ldld"])
+	      ])
+	    ])
+	  ])
+	])
+      ])
+    ;;
+  esac
+
+  if test "x$lt_cv_dlopen" != xno; then
+    enable_dlopen=yes
+  else
+    enable_dlopen=no
+  fi
+
+  case $lt_cv_dlopen in
+  dlopen)
+    save_CPPFLAGS="$CPPFLAGS"
+    test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+    save_LDFLAGS="$LDFLAGS"
+    wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+    save_LIBS="$LIBS"
+    LIBS="$lt_cv_dlopen_libs $LIBS"
+
+    AC_CACHE_CHECK([whether a program can dlopen itself],
+	  lt_cv_dlopen_self, [dnl
+	  _LT_TRY_DLOPEN_SELF(
+	    lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes,
+	    lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross)
+    ])
+
+    if test "x$lt_cv_dlopen_self" = xyes; then
+      wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\"
+      AC_CACHE_CHECK([whether a statically linked program can dlopen itself],
+	  lt_cv_dlopen_self_static, [dnl
+	  _LT_TRY_DLOPEN_SELF(
+	    lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes,
+	    lt_cv_dlopen_self_static=no,  lt_cv_dlopen_self_static=cross)
+      ])
+    fi
+
+    CPPFLAGS="$save_CPPFLAGS"
+    LDFLAGS="$save_LDFLAGS"
+    LIBS="$save_LIBS"
+    ;;
+  esac
+
+  case $lt_cv_dlopen_self in
+  yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+  *) enable_dlopen_self=unknown ;;
+  esac
+
+  case $lt_cv_dlopen_self_static in
+  yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+  *) enable_dlopen_self_static=unknown ;;
+  esac
+fi
+_LT_DECL([dlopen_support], [enable_dlopen], [0],
+	 [Whether dlopen is supported])
+_LT_DECL([dlopen_self], [enable_dlopen_self], [0],
+	 [Whether dlopen of programs is supported])
+_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0],
+	 [Whether dlopen of statically linked programs is supported])
+])# LT_SYS_DLOPEN_SELF
+
+# Old name:
+AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], [])
+
+
+# _LT_COMPILER_C_O([TAGNAME])
+# ---------------------------
+# Check to see if options -c and -o are simultaneously supported by compiler.
+# This macro does not hard code the compiler like AC_PROG_CC_C_O.
+m4_defun([_LT_COMPILER_C_O],
+[m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no
+   $RM -r conftest 2>/dev/null
+   mkdir conftest
+   cd conftest
+   mkdir out
+   echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+   lt_compiler_flag="-o out/conftest2.$ac_objext"
+   # Insert the option either (1) after the last *FLAGS variable, or
+   # (2) before a word containing "conftest.", or (3) at the end.
+   # Note that $ac_compile itself does not contain backslashes and begins
+   # with a dollar sign (not a hyphen), so the echo should work correctly.
+   lt_compile=`echo "$ac_compile" | $SED \
+   -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
+   -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \
+   -e 's:$: $lt_compiler_flag:'`
+   (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD)
+   (eval "$lt_compile" 2>out/conftest.err)
+   ac_status=$?
+   cat out/conftest.err >&AS_MESSAGE_LOG_FD
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   if (exit $ac_status) && test -s out/conftest2.$ac_objext
+   then
+     # The compiler can only warn and ignore the option if not recognized
+     # So say no if there are warnings
+     $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp
+     $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2
+     if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then
+       _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+     fi
+   fi
+   chmod u+w . 2>&AS_MESSAGE_LOG_FD
+   $RM conftest*
+   # SGI C++ compiler will create directory out/ii_files/ for
+   # template instantiation
+   test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files
+   $RM out/* && rmdir out
+   cd ..
+   $RM -r conftest
+   $RM conftest*
+])
+_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1],
+	[Does compiler simultaneously support -c and -o options?])
+])# _LT_COMPILER_C_O
+
+
+# _LT_COMPILER_FILE_LOCKS([TAGNAME])
+# ----------------------------------
+# Check to see if we can do hard links to lock some files if needed
+m4_defun([_LT_COMPILER_FILE_LOCKS],
+[m4_require([_LT_ENABLE_LOCK])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+_LT_COMPILER_C_O([$1])
+
+hard_links="nottested"
+if test "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" = no && test "$need_locks" != no; then
+  # do not overwrite the value of need_locks provided by the user
+  AC_MSG_CHECKING([if we can lock with hard links])
+  hard_links=yes
+  $RM conftest*
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  touch conftest.a
+  ln conftest.a conftest.b 2>&5 || hard_links=no
+  ln conftest.a conftest.b 2>/dev/null && hard_links=no
+  AC_MSG_RESULT([$hard_links])
+  if test "$hard_links" = no; then
+    AC_MSG_WARN([`$CC' does not support `-c -o', so `make -j' may be unsafe])
+    need_locks=warn
+  fi
+else
+  need_locks=no
+fi
+_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?])
+])# _LT_COMPILER_FILE_LOCKS
+
+
+# _LT_CHECK_OBJDIR
+# ----------------
+m4_defun([_LT_CHECK_OBJDIR],
+[AC_CACHE_CHECK([for objdir], [lt_cv_objdir],
+[rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+  lt_cv_objdir=.libs
+else
+  # MS-DOS does not allow filenames that begin with a dot.
+  lt_cv_objdir=_libs
+fi
+rmdir .libs 2>/dev/null])
+objdir=$lt_cv_objdir
+_LT_DECL([], [objdir], [0],
+         [The name of the directory that contains temporary libtool files])dnl
+m4_pattern_allow([LT_OBJDIR])dnl
+AC_DEFINE_UNQUOTED(LT_OBJDIR, "$lt_cv_objdir/",
+  [Define to the sub-directory in which libtool stores uninstalled libraries.])
+])# _LT_CHECK_OBJDIR
+
+
+# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME])
+# --------------------------------------
+# Check hardcoding attributes.
+m4_defun([_LT_LINKER_HARDCODE_LIBPATH],
+[AC_MSG_CHECKING([how to hardcode library paths into programs])
+_LT_TAGVAR(hardcode_action, $1)=
+if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" ||
+   test -n "$_LT_TAGVAR(runpath_var, $1)" ||
+   test "X$_LT_TAGVAR(hardcode_automatic, $1)" = "Xyes" ; then
+
+  # We can hardcode non-existent directories.
+  if test "$_LT_TAGVAR(hardcode_direct, $1)" != no &&
+     # If the only mechanism to avoid hardcoding is shlibpath_var, we
+     # have to relink, otherwise we might link with an installed library
+     # when we should be linking with a yet-to-be-installed one
+     ## test "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" != no &&
+     test "$_LT_TAGVAR(hardcode_minus_L, $1)" != no; then
+    # Linking always hardcodes the temporary library directory.
+    _LT_TAGVAR(hardcode_action, $1)=relink
+  else
+    # We can link without hardcoding, and we can hardcode nonexisting dirs.
+    _LT_TAGVAR(hardcode_action, $1)=immediate
+  fi
+else
+  # We cannot hardcode anything, or else we can only hardcode existing
+  # directories.
+  _LT_TAGVAR(hardcode_action, $1)=unsupported
+fi
+AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)])
+
+if test "$_LT_TAGVAR(hardcode_action, $1)" = relink ||
+   test "$_LT_TAGVAR(inherit_rpath, $1)" = yes; then
+  # Fast installation is not supported
+  enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+     test "$enable_shared" = no; then
+  # Fast installation is not necessary
+  enable_fast_install=needless
+fi
+_LT_TAGDECL([], [hardcode_action], [0],
+    [How to hardcode a shared library path into an executable])
+])# _LT_LINKER_HARDCODE_LIBPATH
+
+
+# _LT_CMD_STRIPLIB
+# ----------------
+m4_defun([_LT_CMD_STRIPLIB],
+[m4_require([_LT_DECL_EGREP])
+striplib=
+old_striplib=
+AC_MSG_CHECKING([whether stripping libraries is possible])
+if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then
+  test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+  test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+  AC_MSG_RESULT([yes])
+else
+# FIXME - insert some real tests, host_os isn't really good enough
+  case $host_os in
+  darwin*)
+    if test -n "$STRIP" ; then
+      striplib="$STRIP -x"
+      old_striplib="$STRIP -S"
+      AC_MSG_RESULT([yes])
+    else
+      AC_MSG_RESULT([no])
+    fi
+    ;;
+  *)
+    AC_MSG_RESULT([no])
+    ;;
+  esac
+fi
+_LT_DECL([], [old_striplib], [1], [Commands to strip libraries])
+_LT_DECL([], [striplib], [1])
+])# _LT_CMD_STRIPLIB
+
+
+# _LT_SYS_DYNAMIC_LINKER([TAG])
+# -----------------------------
+# PORTME Fill in your ld.so characteristics
+m4_defun([_LT_SYS_DYNAMIC_LINKER],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_OBJDUMP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CHECK_SHELL_FEATURES])dnl
+AC_MSG_CHECKING([dynamic linker characteristics])
+m4_if([$1],
+	[], [
+if test "$GCC" = yes; then
+  case $host_os in
+    darwin*) lt_awk_arg="/^libraries:/,/LR/" ;;
+    *) lt_awk_arg="/^libraries:/" ;;
+  esac
+  case $host_os in
+    mingw* | cegcc*) lt_sed_strip_eq="s,=\([[A-Za-z]]:\),\1,g" ;;
+    *) lt_sed_strip_eq="s,=/,/,g" ;;
+  esac
+  lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq`
+  case $lt_search_path_spec in
+  *\;*)
+    # if the path contains ";" then we assume it to be the separator
+    # otherwise default to the standard path separator (i.e. ":") - it is
+    # assumed that no part of a normal pathname contains ";" but that should
+    # okay in the real world where ";" in dirpaths is itself problematic.
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'`
+    ;;
+  *)
+    lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"`
+    ;;
+  esac
+  # Ok, now we have the path, separated by spaces, we can step through it
+  # and add multilib dir if necessary.
+  lt_tmp_lt_search_path_spec=
+  lt_multi_os_dir=`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null`
+  for lt_sys_path in $lt_search_path_spec; do
+    if test -d "$lt_sys_path/$lt_multi_os_dir"; then
+      lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path/$lt_multi_os_dir"
+    else
+      test -d "$lt_sys_path" && \
+	lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path"
+    fi
+  done
+  lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk '
+BEGIN {RS=" "; FS="/|\n";} {
+  lt_foo="";
+  lt_count=0;
+  for (lt_i = NF; lt_i > 0; lt_i--) {
+    if ($lt_i != "" && $lt_i != ".") {
+      if ($lt_i == "..") {
+        lt_count++;
+      } else {
+        if (lt_count == 0) {
+          lt_foo="/" $lt_i lt_foo;
+        } else {
+          lt_count--;
+        }
+      }
+    }
+  }
+  if (lt_foo != "") { lt_freq[[lt_foo]]++; }
+  if (lt_freq[[lt_foo]] == 1) { print lt_foo; }
+}'`
+  # AWK program above erroneously prepends '/' to C:/dos/paths
+  # for these hosts.
+  case $host_os in
+    mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\
+      $SED 's,/\([[A-Za-z]]:\),\1,g'` ;;
+  esac
+  sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP`
+else
+  sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+fi])
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+shrext_cmds=".so"
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+need_lib_prefix=unknown
+hardcode_into_libs=no
+
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+need_version=unknown
+
+case $host_os in
+aix3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix $libname.a'
+  shlibpath_var=LIBPATH
+
+  # AIX 3 has no versioning support, so we append a major version to the name.
+  soname_spec='${libname}${release}${shared_ext}$major'
+  ;;
+
+aix[[4-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  hardcode_into_libs=yes
+  if test "$host_cpu" = ia64; then
+    # AIX 5 supports IA64
+    library_names_spec='${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext}$versuffix $libname${shared_ext}'
+    shlibpath_var=LD_LIBRARY_PATH
+  else
+    # With GCC up to 2.95.x, collect2 would create an import file
+    # for dependence libraries.  The import file would start with
+    # the line `#! .'.  This would cause the generated library to
+    # depend on `.', always an invalid library.  This was fixed in
+    # development snapshots of GCC prior to 3.0.
+    case $host_os in
+      aix4 | aix4.[[01]] | aix4.[[01]].*)
+      if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+	   echo ' yes '
+	   echo '#endif'; } | ${CC} -E - | $GREP yes > /dev/null; then
+	:
+      else
+	can_build_shared=no
+      fi
+      ;;
+    esac
+    # AIX (on Power*) has no versioning support, so currently we can not hardcode correct
+    # soname into executable. Probably we can add versioning support to
+    # collect2, so additional links can be useful in future.
+    if test "$aix_use_runtimelinking" = yes; then
+      # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+      # instead of lib<name>.a to let people know that these are not
+      # typical AIX shared libraries.
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    else
+      # We preserve .a as extension for shared libraries through AIX4.2
+      # and later when we are not doing run time linking.
+      library_names_spec='${libname}${release}.a $libname.a'
+      soname_spec='${libname}${release}${shared_ext}$major'
+    fi
+    shlibpath_var=LIBPATH
+  fi
+  ;;
+
+amigaos*)
+  case $host_cpu in
+  powerpc)
+    # Since July 2007 AmigaOS4 officially supports .so libraries.
+    # When compiling the executable, add -use-dynld -Lsobjs: to the compileline.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    ;;
+  m68k)
+    library_names_spec='$libname.ixlibrary $libname.a'
+    # Create ${libname}_ixlibrary.a entries in /sys/libs.
+    finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done'
+    ;;
+  esac
+  ;;
+
+beos*)
+  library_names_spec='${libname}${shared_ext}'
+  dynamic_linker="$host_os ld.so"
+  shlibpath_var=LIBRARY_PATH
+  ;;
+
+bsdi[[45]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+  sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+  # the default ld.so.conf also contains /usr/contrib/lib and
+  # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+  # libtool to hard-code these into programs
+  ;;
+
+cygwin* | mingw* | pw32* | cegcc*)
+  version_type=windows
+  shrext_cmds=".dll"
+  need_version=no
+  need_lib_prefix=no
+
+  case $GCC,$cc_basename in
+  yes,*)
+    # gcc
+    library_names_spec='$libname.dll.a'
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname~
+      chmod a+x \$dldir/$dlname~
+      if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then
+        eval '\''$striplib \$dldir/$dlname'\'' || exit \$?;
+      fi'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+
+    case $host_os in
+    cygwin*)
+      # Cygwin DLLs use 'cyg' prefix rather than 'lib'
+      soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+m4_if([$1], [],[
+      sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"])
+      ;;
+    mingw* | cegcc*)
+      # MinGW DLLs use traditional 'lib' prefix
+      soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    pw32*)
+      # pw32 DLLs use 'pw' prefix rather than 'lib'
+      library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+      ;;
+    esac
+    dynamic_linker='Win32 ld.exe'
+    ;;
+
+  *,cl*)
+    # Native MSVC
+    libname_spec='$name'
+    soname_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext}'
+    library_names_spec='${libname}.dll.lib'
+
+    case $build_os in
+    mingw*)
+      sys_lib_search_path_spec=
+      lt_save_ifs=$IFS
+      IFS=';'
+      for lt_path in $LIB
+      do
+        IFS=$lt_save_ifs
+        # Let DOS variable expansion print the short 8.3 style file name.
+        lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"`
+        sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path"
+      done
+      IFS=$lt_save_ifs
+      # Convert to MSYS style.
+      sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'`
+      ;;
+    cygwin*)
+      # Convert to unix form, then to dos form, then back to unix form
+      # but this time dos style (no spaces!) so that the unix form looks
+      # like /cygdrive/c/PROGRA~1:/cygdr...
+      sys_lib_search_path_spec=`cygpath --path --unix "$LIB"`
+      sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null`
+      sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      ;;
+    *)
+      sys_lib_search_path_spec="$LIB"
+      if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then
+        # It is most probably a Windows format PATH.
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'`
+      else
+        sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"`
+      fi
+      # FIXME: find the short name or the path components, as spaces are
+      # common. (e.g. "Program Files" -> "PROGRA~1")
+      ;;
+    esac
+
+    # DLL is installed to $(libdir)/../bin by postinstall_cmds
+    postinstall_cmds='base_file=`basename \${file}`~
+      dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\${base_file}'\''i; echo \$dlname'\''`~
+      dldir=$destdir/`dirname \$dlpath`~
+      test -d \$dldir || mkdir -p \$dldir~
+      $install_prog $dir/$dlname \$dldir/$dlname'
+    postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~
+      dlpath=$dir/\$dldll~
+       $RM \$dlpath'
+    shlibpath_overrides_runpath=yes
+    dynamic_linker='Win32 link.exe'
+    ;;
+
+  *)
+    # Assume MSVC wrapper
+    library_names_spec='${libname}`echo ${release} | $SED -e 's/[[.]]/-/g'`${versuffix}${shared_ext} $libname.lib'
+    dynamic_linker='Win32 ld.exe'
+    ;;
+  esac
+  # FIXME: first we should search . and the directory the executable is in
+  shlibpath_var=PATH
+  ;;
+
+darwin* | rhapsody*)
+  dynamic_linker="$host_os dyld"
+  version_type=darwin
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${major}$shared_ext ${libname}$shared_ext'
+  soname_spec='${libname}${release}${major}$shared_ext'
+  shlibpath_overrides_runpath=yes
+  shlibpath_var=DYLD_LIBRARY_PATH
+  shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`'
+m4_if([$1], [],[
+  sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"])
+  sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib'
+  ;;
+
+dgux*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname$shared_ext'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+freebsd* | dragonfly*)
+  # DragonFly does not have aout.  When/if they implement a new
+  # versioning mechanism, adjust this.
+  if test -x /usr/bin/objformat; then
+    objformat=`/usr/bin/objformat`
+  else
+    case $host_os in
+    freebsd[[23]].*) objformat=aout ;;
+    *) objformat=elf ;;
+    esac
+  fi
+  version_type=freebsd-$objformat
+  case $version_type in
+    freebsd-elf*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+      need_version=no
+      need_lib_prefix=no
+      ;;
+    freebsd-*)
+      library_names_spec='${libname}${release}${shared_ext}$versuffix $libname${shared_ext}$versuffix'
+      need_version=yes
+      ;;
+  esac
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_os in
+  freebsd2.*)
+    shlibpath_overrides_runpath=yes
+    ;;
+  freebsd3.[[01]]* | freebsdelf3.[[01]]*)
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \
+  freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1)
+    shlibpath_overrides_runpath=no
+    hardcode_into_libs=yes
+    ;;
+  *) # from 4.6 on, and DragonFly
+    shlibpath_overrides_runpath=yes
+    hardcode_into_libs=yes
+    ;;
+  esac
+  ;;
+
+haiku*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  dynamic_linker="$host_os runtime_loader"
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}${major} ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib'
+  hardcode_into_libs=yes
+  ;;
+
+hpux9* | hpux10* | hpux11*)
+  # Give a soname corresponding to the major version so that dld.sl refuses to
+  # link against other versions.
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  case $host_cpu in
+  ia64*)
+    shrext_cmds='.so'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.so"
+    shlibpath_var=LD_LIBRARY_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    if test "X$HPUX_IA64_MODE" = X32; then
+      sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib"
+    else
+      sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64"
+    fi
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  hppa*64*)
+    shrext_cmds='.sl'
+    hardcode_into_libs=yes
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH
+    shlibpath_overrides_runpath=yes # Unless +noenvvar is specified.
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64"
+    sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec
+    ;;
+  *)
+    shrext_cmds='.sl'
+    dynamic_linker="$host_os dld.sl"
+    shlibpath_var=SHLIB_PATH
+    shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    ;;
+  esac
+  # HP-UX runs *really* slowly unless shared libraries are mode 555, ...
+  postinstall_cmds='chmod 555 $lib'
+  # or fails outright, so override atomically:
+  install_override_mode=555
+  ;;
+
+interix[[3-9]]*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $host_os in
+    nonstopux*) version_type=nonstopux ;;
+    *)
+	if test "$lt_cv_prog_gnu_ld" = yes; then
+		version_type=linux # correct to gnu/linux during the next big refactor
+	else
+		version_type=irix
+	fi ;;
+  esac
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${release}${shared_ext} $libname${shared_ext}'
+  case $host_os in
+  irix5* | nonstopux*)
+    libsuff= shlibsuff=
+    ;;
+  *)
+    case $LD in # libtool.m4 will add one of these switches to LD
+    *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ")
+      libsuff= shlibsuff= libmagic=32-bit;;
+    *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ")
+      libsuff=32 shlibsuff=N32 libmagic=N32;;
+    *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ")
+      libsuff=64 shlibsuff=64 libmagic=64-bit;;
+    *) libsuff= shlibsuff= libmagic=never-match;;
+    esac
+    ;;
+  esac
+  shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+  shlibpath_overrides_runpath=no
+  sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+  sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+  hardcode_into_libs=yes
+  ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux*oldld* | linux*aout* | linux*coff*)
+  dynamic_linker=no
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+
+  # Some binutils ld are patched to set DT_RUNPATH
+  AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath],
+    [lt_cv_shlibpath_overrides_runpath=no
+    save_LDFLAGS=$LDFLAGS
+    save_libdir=$libdir
+    eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \
+	 LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\""
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])],
+      [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null],
+	 [lt_cv_shlibpath_overrides_runpath=yes])])
+    LDFLAGS=$save_LDFLAGS
+    libdir=$save_libdir
+    ])
+  shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath
+
+  # This implies no fast_install, which is unacceptable.
+  # Some rework will be needed to allow for fast_install
+  # before this can be enabled.
+  hardcode_into_libs=yes
+
+  # Append ld.so.conf contents to the search path
+  if test -f /etc/ld.so.conf; then
+    lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[	 ]*hwcap[	 ]/d;s/[:,	]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '`
+    sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra"
+  fi
+
+  # We used to test for /lib/ld.so.1 and disable shared libraries on
+  # powerpc, because MkLinux only supported shared libraries with the
+  # GNU dynamic linker.  Since this was broken with cross compilers,
+  # most powerpc-linux boxes support dynamic linking these days and
+  # people can always --disable-shared, the test was removed, and we
+  # assume the GNU/Linux dynamic linker is in use.
+  dynamic_linker='GNU/Linux ld.so'
+  ;;
+
+netbsdelf*-gnu)
+  version_type=linux
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='NetBSD ld.elf_so'
+  ;;
+
+netbsd*)
+  version_type=sunos
+  need_lib_prefix=no
+  need_version=no
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+    finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+    dynamic_linker='NetBSD (a.out) ld.so'
+  else
+    library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}'
+    soname_spec='${libname}${release}${shared_ext}$major'
+    dynamic_linker='NetBSD ld.elf_so'
+  fi
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  ;;
+
+newsos6)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  ;;
+
+*nto* | *qnx*)
+  version_type=qnx
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  dynamic_linker='ldqnx.so'
+  ;;
+
+openbsd*)
+  version_type=sunos
+  sys_lib_dlsearch_path_spec="/usr/lib"
+  need_lib_prefix=no
+  # Some older versions of OpenBSD (3.3 at least) *do* need versioned libs.
+  case $host_os in
+    openbsd3.3 | openbsd3.3.*)	need_version=yes ;;
+    *)				need_version=no  ;;
+  esac
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    case $host_os in
+      openbsd2.[[89]] | openbsd2.[[89]].*)
+	shlibpath_overrides_runpath=no
+	;;
+      *)
+	shlibpath_overrides_runpath=yes
+	;;
+      esac
+  else
+    shlibpath_overrides_runpath=yes
+  fi
+  ;;
+
+os2*)
+  libname_spec='$name'
+  shrext_cmds=".dll"
+  need_lib_prefix=no
+  library_names_spec='$libname${shared_ext} $libname.a'
+  dynamic_linker='OS/2 ld.exe'
+  shlibpath_var=LIBPATH
+  ;;
+
+osf3* | osf4* | osf5*)
+  version_type=osf
+  need_lib_prefix=no
+  need_version=no
+  soname_spec='${libname}${release}${shared_ext}$major'
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+  sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+  ;;
+
+rdos*)
+  dynamic_linker=no
+  ;;
+
+solaris*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  # ldd complains unless libraries are executable
+  postinstall_cmds='chmod +x $lib'
+  ;;
+
+sunos4*)
+  version_type=sunos
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${shared_ext}$versuffix'
+  finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  if test "$with_gnu_ld" = yes; then
+    need_lib_prefix=no
+  fi
+  need_version=yes
+  ;;
+
+sysv4 | sysv4.3*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  case $host_vendor in
+    sni)
+      shlibpath_overrides_runpath=no
+      need_lib_prefix=no
+      runpath_var=LD_RUN_PATH
+      ;;
+    siemens)
+      need_lib_prefix=no
+      ;;
+    motorola)
+      need_lib_prefix=no
+      need_version=no
+      shlibpath_overrides_runpath=no
+      sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+      ;;
+  esac
+  ;;
+
+sysv4*MP*)
+  if test -d /usr/nec ;then
+    version_type=linux # correct to gnu/linux during the next big refactor
+    library_names_spec='$libname${shared_ext}.$versuffix $libname${shared_ext}.$major $libname${shared_ext}'
+    soname_spec='$libname${shared_ext}.$major'
+    shlibpath_var=LD_LIBRARY_PATH
+  fi
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  version_type=freebsd-elf
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext} $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=yes
+  hardcode_into_libs=yes
+  if test "$with_gnu_ld" = yes; then
+    sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib'
+  else
+    sys_lib_search_path_spec='/usr/ccs/lib /usr/lib'
+    case $host_os in
+      sco3.2v5*)
+        sys_lib_search_path_spec="$sys_lib_search_path_spec /lib"
+	;;
+    esac
+  fi
+  sys_lib_dlsearch_path_spec='/usr/lib'
+  ;;
+
+tpf*)
+  # TPF is a cross-target only.  Preferred cross-host = GNU/Linux.
+  version_type=linux # correct to gnu/linux during the next big refactor
+  need_lib_prefix=no
+  need_version=no
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  shlibpath_var=LD_LIBRARY_PATH
+  shlibpath_overrides_runpath=no
+  hardcode_into_libs=yes
+  ;;
+
+uts4*)
+  version_type=linux # correct to gnu/linux during the next big refactor
+  library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major $libname${shared_ext}'
+  soname_spec='${libname}${release}${shared_ext}$major'
+  shlibpath_var=LD_LIBRARY_PATH
+  ;;
+
+*)
+  dynamic_linker=no
+  ;;
+esac
+AC_MSG_RESULT([$dynamic_linker])
+test "$dynamic_linker" = no && can_build_shared=no
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+  variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "${lt_cv_sys_lib_search_path_spec+set}" = set; then
+  sys_lib_search_path_spec="$lt_cv_sys_lib_search_path_spec"
+fi
+if test "${lt_cv_sys_lib_dlsearch_path_spec+set}" = set; then
+  sys_lib_dlsearch_path_spec="$lt_cv_sys_lib_dlsearch_path_spec"
+fi
+
+_LT_DECL([], [variables_saved_for_relink], [1],
+    [Variables whose values should be saved in libtool wrapper scripts and
+    restored at link time])
+_LT_DECL([], [need_lib_prefix], [0],
+    [Do we need the "lib" prefix for modules?])
+_LT_DECL([], [need_version], [0], [Do we need a version for libraries?])
+_LT_DECL([], [version_type], [0], [Library versioning type])
+_LT_DECL([], [runpath_var], [0],  [Shared library runtime path variable])
+_LT_DECL([], [shlibpath_var], [0],[Shared library path variable])
+_LT_DECL([], [shlibpath_overrides_runpath], [0],
+    [Is shlibpath searched before the hard-coded library search path?])
+_LT_DECL([], [libname_spec], [1], [Format of library name prefix])
+_LT_DECL([], [library_names_spec], [1],
+    [[List of archive names.  First name is the real one, the rest are links.
+    The last name is the one that the linker finds with -lNAME]])
+_LT_DECL([], [soname_spec], [1],
+    [[The coded name of the library, if different from the real name]])
+_LT_DECL([], [install_override_mode], [1],
+    [Permission mode override for installation of shared libraries])
+_LT_DECL([], [postinstall_cmds], [2],
+    [Command to use after installation of a shared archive])
+_LT_DECL([], [postuninstall_cmds], [2],
+    [Command to use after uninstallation of a shared archive])
+_LT_DECL([], [finish_cmds], [2],
+    [Commands used to finish a libtool library installation in a directory])
+_LT_DECL([], [finish_eval], [1],
+    [[As "finish_cmds", except a single script fragment to be evaled but
+    not shown]])
+_LT_DECL([], [hardcode_into_libs], [0],
+    [Whether we should hardcode library paths into libraries])
+_LT_DECL([], [sys_lib_search_path_spec], [2],
+    [Compile-time system search path for libraries])
+_LT_DECL([], [sys_lib_dlsearch_path_spec], [2],
+    [Run-time system search path for libraries])
+])# _LT_SYS_DYNAMIC_LINKER
+
+
+# _LT_PATH_TOOL_PREFIX(TOOL)
+# --------------------------
+# find a file program which can recognize shared library
+AC_DEFUN([_LT_PATH_TOOL_PREFIX],
+[m4_require([_LT_DECL_EGREP])dnl
+AC_MSG_CHECKING([for $1])
+AC_CACHE_VAL(lt_cv_path_MAGIC_CMD,
+[case $MAGIC_CMD in
+[[\\/*] |  ?:[\\/]*])
+  lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+  ;;
+*)
+  lt_save_MAGIC_CMD="$MAGIC_CMD"
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+dnl $ac_dummy forces splitting on constant user-supplied paths.
+dnl POSIX.2 word splitting is done only on the output of word expansions,
+dnl not every word.  This closes a longstanding sh security hole.
+  ac_dummy="m4_if([$2], , $PATH, [$2])"
+  for ac_dir in $ac_dummy; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$1; then
+      lt_cv_path_MAGIC_CMD="$ac_dir/$1"
+      if test -n "$file_magic_test_file"; then
+	case $deplibs_check_method in
+	"file_magic "*)
+	  file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"`
+	  MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+	  if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+	    $EGREP "$file_magic_regex" > /dev/null; then
+	    :
+	  else
+	    cat <<_LT_EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such.  This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem.  Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool at gnu.org
+
+_LT_EOF
+	  fi ;;
+	esac
+      fi
+      break
+    fi
+  done
+  IFS="$lt_save_ifs"
+  MAGIC_CMD="$lt_save_MAGIC_CMD"
+  ;;
+esac])
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+  AC_MSG_RESULT($MAGIC_CMD)
+else
+  AC_MSG_RESULT(no)
+fi
+_LT_DECL([], [MAGIC_CMD], [0],
+	 [Used to examine libraries when file_magic_cmd begins with "file"])dnl
+])# _LT_PATH_TOOL_PREFIX
+
+# Old name:
+AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], [])
+
+
+# _LT_PATH_MAGIC
+# --------------
+# find a file program which can recognize a shared library
+m4_defun([_LT_PATH_MAGIC],
+[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH)
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+  if test -n "$ac_tool_prefix"; then
+    _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH)
+  else
+    MAGIC_CMD=:
+  fi
+fi
+])# _LT_PATH_MAGIC
+
+
+# LT_PATH_LD
+# ----------
+# find the pathname to the GNU or non-GNU linker
+AC_DEFUN([LT_PATH_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PROG_ECHO_BACKSLASH])dnl
+
+AC_ARG_WITH([gnu-ld],
+    [AS_HELP_STRING([--with-gnu-ld],
+	[assume the C compiler uses GNU ld @<:@default=no@:>@])],
+    [test "$withval" = no || with_gnu_ld=yes],
+    [with_gnu_ld=no])dnl
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  AC_MSG_CHECKING([for ld used by $CC])
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [[\\/]]* | ?:[[\\/]]*)
+      re_direlt='/[[^/]][[^/]]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'`
+      while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do
+	ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  AC_MSG_CHECKING([for GNU ld])
+else
+  AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(lt_cv_path_LD,
+[if test -z "$LD"; then
+  lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$lt_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      lt_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$lt_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+	test "$with_gnu_ld" != no && break
+	;;
+      *)
+	test "$with_gnu_ld" != yes && break
+	;;
+      esac
+    fi
+  done
+  IFS="$lt_save_ifs"
+else
+  lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+  AC_MSG_RESULT($LD)
+else
+  AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+_LT_PATH_LD_GNU
+AC_SUBST([LD])
+
+_LT_TAGDECL([], [LD], [1], [The linker used to build libraries])
+])# LT_PATH_LD
+
+# Old names:
+AU_ALIAS([AM_PROG_LD], [LT_PATH_LD])
+AU_ALIAS([AC_PROG_LD], [LT_PATH_LD])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_LD], [])
+dnl AC_DEFUN([AC_PROG_LD], [])
+
+
+# _LT_PATH_LD_GNU
+#- --------------
+m4_defun([_LT_PATH_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  lt_cv_prog_gnu_ld=yes
+  ;;
+*)
+  lt_cv_prog_gnu_ld=no
+  ;;
+esac])
+with_gnu_ld=$lt_cv_prog_gnu_ld
+])# _LT_PATH_LD_GNU
+
+
+# _LT_CMD_RELOAD
+# --------------
+# find reload flag for linker
+#   -- PORTME Some linkers may need a different reload flag.
+m4_defun([_LT_CMD_RELOAD],
+[AC_CACHE_CHECK([for $LD option to reload object files],
+  lt_cv_ld_reload_flag,
+  [lt_cv_ld_reload_flag='-r'])
+reload_flag=$lt_cv_ld_reload_flag
+case $reload_flag in
+"" | " "*) ;;
+*) reload_flag=" $reload_flag" ;;
+esac
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    if test "$GCC" != yes; then
+      reload_cmds=false
+    fi
+    ;;
+  darwin*)
+    if test "$GCC" = yes; then
+      reload_cmds='$LTCC $LTCFLAGS -nostdlib ${wl}-r -o $output$reload_objs'
+    else
+      reload_cmds='$LD$reload_flag -o $output$reload_objs'
+    fi
+    ;;
+esac
+_LT_TAGDECL([], [reload_flag], [1], [How to create reloadable object files])dnl
+_LT_TAGDECL([], [reload_cmds], [2])dnl
+])# _LT_CMD_RELOAD
+
+
+# _LT_CHECK_MAGIC_METHOD
+# ----------------------
+# how to check for library dependencies
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_MAGIC_METHOD],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+AC_CACHE_CHECK([how to recognize dependent libraries],
+lt_cv_deplibs_check_method,
+[lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given extended regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix[[4-9]]*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+beos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+bsdi[[45]]*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)'
+  lt_cv_file_magic_cmd='/usr/bin/file -L'
+  lt_cv_file_magic_test_file=/shlib/libc.so
+  ;;
+
+cygwin*)
+  # func_win32_libid is a shell function defined in ltmain.sh
+  lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+  lt_cv_file_magic_cmd='func_win32_libid'
+  ;;
+
+mingw* | pw32*)
+  # Base MSYS/MinGW do not provide the 'file' command needed by
+  # func_win32_libid shell function, so use a weaker test based on 'objdump',
+  # unless we find 'file', for example because we are cross-compiling.
+  # func_win32_libid assumes BSD nm, so disallow it if using MS dumpbin.
+  if ( test "$lt_cv_nm_interface" = "BSD nm" && file / ) >/dev/null 2>&1; then
+    lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL'
+    lt_cv_file_magic_cmd='func_win32_libid'
+  else
+    # Keep this pattern in sync with the one in func_win32_libid.
+    lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)'
+    lt_cv_file_magic_cmd='$OBJDUMP -f'
+  fi
+  ;;
+
+cegcc*)
+  # use the weaker test based on 'objdump'. See mingw*.
+  lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?'
+  lt_cv_file_magic_cmd='$OBJDUMP -f'
+  ;;
+
+darwin* | rhapsody*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+freebsd* | dragonfly*)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    case $host_cpu in
+    i*86 )
+      # Not sure whether the presence of OpenBSD here was a mistake.
+      # Let's accept both of them until this is cleared up.
+      lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library'
+      lt_cv_file_magic_cmd=/usr/bin/file
+      lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+      ;;
+    esac
+  else
+    lt_cv_deplibs_check_method=pass_all
+  fi
+  ;;
+
+haiku*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+hpux10.20* | hpux11*)
+  lt_cv_file_magic_cmd=/usr/bin/file
+  case $host_cpu in
+  ia64*)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64'
+    lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so
+    ;;
+  hppa*64*)
+    [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]']
+    lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl
+    ;;
+  *)
+    lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library'
+    lt_cv_file_magic_test_file=/usr/lib/libc.sl
+    ;;
+  esac
+  ;;
+
+interix[[3-9]]*)
+  # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here
+  lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$'
+  ;;
+
+irix5* | irix6* | nonstopux*)
+  case $LD in
+  *-32|*"-32 ") libmagic=32-bit;;
+  *-n32|*"-n32 ") libmagic=N32;;
+  *-64|*"-64 ") libmagic=64-bit;;
+  *) libmagic=never-match;;
+  esac
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+# This must be glibc/ELF.
+linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+netbsd* | netbsdelf*-gnu)
+  if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$'
+  fi
+  ;;
+
+newos6*)
+  lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)'
+  lt_cv_file_magic_cmd=/usr/bin/file
+  lt_cv_file_magic_test_file=/usr/lib/libnls.so
+  ;;
+
+*nto* | *qnx*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+openbsd*)
+  if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$'
+  else
+    lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$'
+  fi
+  ;;
+
+osf3* | osf4* | osf5*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+rdos*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+solaris*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+
+sysv4 | sysv4.3*)
+  case $host_vendor in
+  motorola)
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]'
+    lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+    ;;
+  ncr)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  sequent)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )'
+    ;;
+  sni)
+    lt_cv_file_magic_cmd='/bin/file'
+    lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib"
+    lt_cv_file_magic_test_file=/lib/libc.so
+    ;;
+  siemens)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  pc)
+    lt_cv_deplibs_check_method=pass_all
+    ;;
+  esac
+  ;;
+
+tpf*)
+  lt_cv_deplibs_check_method=pass_all
+  ;;
+esac
+])
+
+file_magic_glob=
+want_nocaseglob=no
+if test "$build" = "$host"; then
+  case $host_os in
+  mingw* | pw32*)
+    if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then
+      want_nocaseglob=yes
+    else
+      file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"`
+    fi
+    ;;
+  esac
+fi
+
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+_LT_DECL([], [deplibs_check_method], [1],
+    [Method to check whether dependent libraries are shared objects])
+_LT_DECL([], [file_magic_cmd], [1],
+    [Command to use when deplibs_check_method = "file_magic"])
+_LT_DECL([], [file_magic_glob], [1],
+    [How to find potential files when deplibs_check_method = "file_magic"])
+_LT_DECL([], [want_nocaseglob], [1],
+    [Find potential files using nocaseglob when deplibs_check_method = "file_magic"])
+])# _LT_CHECK_MAGIC_METHOD
+
+
+# LT_PATH_NM
+# ----------
+# find the pathname to a BSD- or MS-compatible name lister
+AC_DEFUN([LT_PATH_NM],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM,
+[if test -n "$NM"; then
+  # Let the user override the test.
+  lt_cv_path_NM="$NM"
+else
+  lt_nm_to_check="${ac_tool_prefix}nm"
+  if test -n "$ac_tool_prefix" && test "$build" = "$host"; then
+    lt_nm_to_check="$lt_nm_to_check nm"
+  fi
+  for lt_tmp_nm in $lt_nm_to_check; do
+    lt_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do
+      IFS="$lt_save_ifs"
+      test -z "$ac_dir" && ac_dir=.
+      tmp_nm="$ac_dir/$lt_tmp_nm"
+      if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext" ; then
+	# Check to see if the nm accepts a BSD-compat flag.
+	# Adding the `sed 1q' prevents false positives on HP-UX, which says:
+	#   nm: unknown option "B" ignored
+	# Tru64's nm complains that /dev/null is an invalid object file
+	case `"$tmp_nm" -B /dev/null 2>&1 | sed '1q'` in
+	*/dev/null* | *'Invalid file or object type'*)
+	  lt_cv_path_NM="$tmp_nm -B"
+	  break
+	  ;;
+	*)
+	  case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in
+	  */dev/null*)
+	    lt_cv_path_NM="$tmp_nm -p"
+	    break
+	    ;;
+	  *)
+	    lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+	    continue # so that we can try to find one that supports BSD flags
+	    ;;
+	  esac
+	  ;;
+	esac
+      fi
+    done
+    IFS="$lt_save_ifs"
+  done
+  : ${lt_cv_path_NM=no}
+fi])
+if test "$lt_cv_path_NM" != "no"; then
+  NM="$lt_cv_path_NM"
+else
+  # Didn't find any BSD compatible name lister, look for dumpbin.
+  if test -n "$DUMPBIN"; then :
+    # Let the user override the test.
+  else
+    AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :)
+    case `$DUMPBIN -symbols /dev/null 2>&1 | sed '1q'` in
+    *COFF*)
+      DUMPBIN="$DUMPBIN -symbols"
+      ;;
+    *)
+      DUMPBIN=:
+      ;;
+    esac
+  fi
+  AC_SUBST([DUMPBIN])
+  if test "$DUMPBIN" != ":"; then
+    NM="$DUMPBIN"
+  fi
+fi
+test -z "$NM" && NM=nm
+AC_SUBST([NM])
+_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl
+
+AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface],
+  [lt_cv_nm_interface="BSD nm"
+  echo "int some_variable = 0;" > conftest.$ac_ext
+  (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$ac_compile" 2>conftest.err)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD)
+  (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out)
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD)
+  cat conftest.out >&AS_MESSAGE_LOG_FD
+  if $GREP 'External.*some_variable' conftest.out > /dev/null; then
+    lt_cv_nm_interface="MS dumpbin"
+  fi
+  rm -f conftest*])
+])# LT_PATH_NM
+
+# Old names:
+AU_ALIAS([AM_PROG_NM], [LT_PATH_NM])
+AU_ALIAS([AC_PROG_NM], [LT_PATH_NM])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_PROG_NM], [])
+dnl AC_DEFUN([AC_PROG_NM], [])
+
+# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+# --------------------------------
+# how to determine the name of the shared library
+# associated with a specific link library.
+#  -- PORTME fill in with the dynamic library characteristics
+m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB],
+[m4_require([_LT_DECL_EGREP])
+m4_require([_LT_DECL_OBJDUMP])
+m4_require([_LT_DECL_DLLTOOL])
+AC_CACHE_CHECK([how to associate runtime and link libraries],
+lt_cv_sharedlib_from_linklib_cmd,
+[lt_cv_sharedlib_from_linklib_cmd='unknown'
+
+case $host_os in
+cygwin* | mingw* | pw32* | cegcc*)
+  # two different shell functions defined in ltmain.sh
+  # decide which to use based on capabilities of $DLLTOOL
+  case `$DLLTOOL --help 2>&1` in
+  *--identify-strict*)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib
+    ;;
+  *)
+    lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback
+    ;;
+  esac
+  ;;
+*)
+  # fallback: assume linklib IS sharedlib
+  lt_cv_sharedlib_from_linklib_cmd="$ECHO"
+  ;;
+esac
+])
+sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd
+test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO
+
+_LT_DECL([], [sharedlib_from_linklib_cmd], [1],
+    [Command to associate shared and link libraries])
+])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB
+
+
+# _LT_PATH_MANIFEST_TOOL
+# ----------------------
+# locate the manifest tool
+m4_defun([_LT_PATH_MANIFEST_TOOL],
+[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :)
+test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt
+AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool],
+  [lt_cv_path_mainfest_tool=no
+  echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD
+  $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out
+  cat conftest.err >&AS_MESSAGE_LOG_FD
+  if $GREP 'Manifest Tool' conftest.out > /dev/null; then
+    lt_cv_path_mainfest_tool=yes
+  fi
+  rm -f conftest*])
+if test "x$lt_cv_path_mainfest_tool" != xyes; then
+  MANIFEST_TOOL=:
+fi
+_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl
+])# _LT_PATH_MANIFEST_TOOL
+
+
+# LT_LIB_M
+# --------
+# check for math library
+AC_DEFUN([LT_LIB_M],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+LIBM=
+case $host in
+*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*)
+  # These system don't have libm, or don't need it
+  ;;
+*-ncr-sysv4.3*)
+  AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw")
+  AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm")
+  ;;
+*)
+  AC_CHECK_LIB(m, cos, LIBM="-lm")
+  ;;
+esac
+AC_SUBST([LIBM])
+])# LT_LIB_M
+
+# Old name:
+AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_CHECK_LIBM], [])
+
+
+# _LT_COMPILER_NO_RTTI([TAGNAME])
+# -------------------------------
+m4_defun([_LT_COMPILER_NO_RTTI],
+[m4_require([_LT_TAG_COMPILER])dnl
+
+_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+
+if test "$GCC" = yes; then
+  case $cc_basename in
+  nvcc*)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;;
+  esac
+
+  _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions],
+    lt_cv_prog_compiler_rtti_exceptions,
+    [-fno-rtti -fno-exceptions], [],
+    [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"])
+fi
+_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1],
+	[Compiler flag to turn off builtin functions])
+])# _LT_COMPILER_NO_RTTI
+
+
+# _LT_CMD_GLOBAL_SYMBOLS
+# ----------------------
+m4_defun([_LT_CMD_GLOBAL_SYMBOLS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+AC_REQUIRE([LT_PATH_LD])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+AC_MSG_CHECKING([command to parse $NM output from $compiler object])
+AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe],
+[
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix.  What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[[BCDEGRST]]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)'
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+  symcode='[[BCDT]]'
+  ;;
+cygwin* | mingw* | pw32* | cegcc*)
+  symcode='[[ABCDGISTW]]'
+  ;;
+hpux*)
+  if test "$host_cpu" = ia64; then
+    symcode='[[ABCDEGRST]]'
+  fi
+  ;;
+irix* | nonstopux*)
+  symcode='[[BCDEGRST]]'
+  ;;
+osf*)
+  symcode='[[BCDEGQRST]]'
+  ;;
+solaris*)
+  symcode='[[BDRT]]'
+  ;;
+sco3.2v5*)
+  symcode='[[DT]]'
+  ;;
+sysv4.2uw2*)
+  symcode='[[DT]]'
+  ;;
+sysv5* | sco5v6* | unixware* | OpenUNIX*)
+  symcode='[[ABDT]]'
+  ;;
+sysv4)
+  symcode='[[DFNSTU]]'
+  ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+case `$NM -V 2>&1` in
+*GNU* | *'with BFD'*)
+  symcode='[[ABCDGIRSTW]]' ;;
+esac
+
+# Transform an extracted symbol line into a proper C declaration.
+# Some systems (esp. on ia64) link data and code symbols differently,
+# so use this general approach.
+lt_cv_sys_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern int \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_sys_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p'"
+lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n -e 's/^: \([[^ ]]*\)[[ ]]*$/  {\\\"\1\\\", (void *) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \(lib[[^ ]]*\)$/  {\"\2\", (void *) \&\2},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/  {\"lib\2\", (void *) \&\2},/p'"
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $build_os in
+mingw*)
+  opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+  ;;
+esac
+
+# Try without a prefix underscore, then with it.
+for ac_symprfx in "" "_"; do
+
+  # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol.
+  symxfrm="\\1 $ac_symprfx\\2 \\2"
+
+  # Write the raw and C identifiers.
+  if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+    # Fake it for dumpbin and say T for any non-static function
+    # and D for any global variable.
+    # Also find C++ and __fastcall symbols from MSVC++,
+    # which start with @ or ?.
+    lt_cv_sys_global_symbol_pipe="$AWK ['"\
+"     {last_section=section; section=\$ 3};"\
+"     /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\
+"     /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\
+"     \$ 0!~/External *\|/{next};"\
+"     / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\
+"     {if(hide[section]) next};"\
+"     {f=0}; \$ 0~/\(\).*\|/{f=1}; {printf f ? \"T \" : \"D \"};"\
+"     {split(\$ 0, a, /\||\r/); split(a[2], s)};"\
+"     s[1]~/^[@?]/{print s[1], s[1]; next};"\
+"     s[1]~prfx {split(s[1],t,\"@\"); print t[1], substr(t[1],length(prfx))}"\
+"     ' prfx=^$ac_symprfx]"
+  else
+    lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[	 ]]\($symcode$symcode*\)[[	 ]][[	 ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'"
+  fi
+  lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'"
+
+  # Check to see that the pipe works correctly.
+  pipe_works=no
+
+  rm -f conftest*
+  cat > conftest.$ac_ext <<_LT_EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(void);
+void nm_test_func(void){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+_LT_EOF
+
+  if AC_TRY_EVAL(ac_compile); then
+    # Now try to grab the symbols.
+    nlist=conftest.nm
+    if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then
+      # Try sorting and uniquifying the output.
+      if sort "$nlist" | uniq > "$nlist"T; then
+	mv -f "$nlist"T "$nlist"
+      else
+	rm -f "$nlist"T
+      fi
+
+      # Make sure that we snagged all the symbols we need.
+      if $GREP ' nm_test_var$' "$nlist" >/dev/null; then
+	if $GREP ' nm_test_func$' "$nlist" >/dev/null; then
+	  cat <<_LT_EOF > conftest.$ac_ext
+/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests.  */
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
+/* DATA imports from DLLs on WIN32 con't be const, because runtime
+   relocations are performed -- see ld's documentation on pseudo-relocs.  */
+# define LT@&t at _DLSYM_CONST
+#elif defined(__osf__)
+/* This system does not cope well with relocations in const data.  */
+# define LT@&t at _DLSYM_CONST
+#else
+# define LT@&t at _DLSYM_CONST const
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_LT_EOF
+	  # Now generate the symbol file.
+	  eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext'
+
+	  cat <<_LT_EOF >> conftest.$ac_ext
+
+/* The mapping between symbol names and symbols.  */
+LT@&t at _DLSYM_CONST struct {
+  const char *name;
+  void       *address;
+}
+lt__PROGRAM__LTX_preloaded_symbols[[]] =
+{
+  { "@PROGRAM@", (void *) 0 },
+_LT_EOF
+	  $SED "s/^$symcode$symcode* \(.*\) \(.*\)$/  {\"\2\", (void *) \&\2},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext
+	  cat <<\_LT_EOF >> conftest.$ac_ext
+  {0, (void *) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+  return lt__PROGRAM__LTX_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+_LT_EOF
+	  # Now try linking the two files.
+	  mv conftest.$ac_objext conftstm.$ac_objext
+	  lt_globsym_save_LIBS=$LIBS
+	  lt_globsym_save_CFLAGS=$CFLAGS
+	  LIBS="conftstm.$ac_objext"
+	  CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)"
+	  if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext}; then
+	    pipe_works=yes
+	  fi
+	  LIBS=$lt_globsym_save_LIBS
+	  CFLAGS=$lt_globsym_save_CFLAGS
+	else
+	  echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD
+	fi
+      else
+	echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD
+      fi
+    else
+      echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD
+    fi
+  else
+    echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD
+    cat conftest.$ac_ext >&5
+  fi
+  rm -rf conftest* conftst*
+
+  # Do not use the global_symbol_pipe unless it works.
+  if test "$pipe_works" = yes; then
+    break
+  else
+    lt_cv_sys_global_symbol_pipe=
+  fi
+done
+])
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+  lt_cv_sys_global_symbol_to_cdecl=
+fi
+if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then
+  AC_MSG_RESULT(failed)
+else
+  AC_MSG_RESULT(ok)
+fi
+
+# Response file support.
+if test "$lt_cv_nm_interface" = "MS dumpbin"; then
+  nm_file_list_spec='@'
+elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then
+  nm_file_list_spec='@'
+fi
+
+_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1],
+    [Take the output of nm and produce a listing of raw symbols and C names])
+_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1],
+    [Transform the output of nm in a proper C declaration])
+_LT_DECL([global_symbol_to_c_name_address],
+    [lt_cv_sys_global_symbol_to_c_name_address], [1],
+    [Transform the output of nm in a C name address pair])
+_LT_DECL([global_symbol_to_c_name_address_lib_prefix],
+    [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1],
+    [Transform the output of nm in a C name address pair when lib prefix is needed])
+_LT_DECL([], [nm_file_list_spec], [1],
+    [Specify filename containing input files for $NM])
+]) # _LT_CMD_GLOBAL_SYMBOLS
+
+
+# _LT_COMPILER_PIC([TAGNAME])
+# ---------------------------
+m4_defun([_LT_COMPILER_PIC],
+[m4_require([_LT_TAG_COMPILER])dnl
+_LT_TAGVAR(lt_prog_compiler_wl, $1)=
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+_LT_TAGVAR(lt_prog_compiler_static, $1)=
+
+m4_if([$1], [CXX], [
+  # C++ specific cases for pic, static, wl, etc.
+  if test "$GXX" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+    aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+    mingw* | cygwin* | os2* | pw32* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+    *djgpp*)
+      # DJGPP does not support shared libraries at all
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+      ;;
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	;;
+      esac
+      ;;
+    *qnx* | *nto*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+  else
+    case $host_os in
+      aix[[4-9]]*)
+	# All AIX code is PIC.
+	if test "$host_cpu" = ia64; then
+	  # AIX 5 now supports IA64 processor
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	else
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+	fi
+	;;
+      chorus*)
+	case $cc_basename in
+	cxch68*)
+	  # Green Hills C++ Compiler
+	  # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a"
+	  ;;
+	esac
+	;;
+      mingw* | cygwin* | os2* | pw32* | cegcc*)
+	# This hack is so that the source file can tell whether it is being
+	# built for inclusion in a dll (and should export symbols for example).
+	m4_if([$1], [GCJ], [],
+	  [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+	;;
+      dgux*)
+	case $cc_basename in
+	  ec++*)
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    ;;
+	  ghcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      freebsd* | dragonfly*)
+	# FreeBSD uses GNU C++
+	;;
+      hpux9* | hpux10* | hpux11*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+	    if test "$host_cpu" != ia64; then
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	    fi
+	    ;;
+	  aCC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+	    case $host_cpu in
+	    hppa*64*|ia64*)
+	      # +Z the default
+	      ;;
+	    *)
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	      ;;
+	    esac
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      interix*)
+	# This is c89, which is MS Visual C++ (no shared libs)
+	# Anyone wants to do a port?
+	;;
+      irix5* | irix6* | nonstopux*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    # CC pic flag -KPIC is the default.
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+	case $cc_basename in
+	  KCC*)
+	    # KAI C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	    ;;
+	  ecpc* )
+	    # old Intel C++ for x86_64 which still supported -KPIC.
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	    ;;
+	  icpc* )
+	    # Intel C++, used to be incompatible with GCC.
+	    # ICC 10 doesn't accept -KPIC any more.
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	    ;;
+	  pgCC* | pgcpp*)
+	    # Portland Group C++ compiler
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	  cxx*)
+	    # Compaq C++
+	    # Make sure the PIC flag is empty.  It appears that all Alpha
+	    # Linux and Compaq Tru64 Unix objects are PIC.
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    ;;
+	  xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*)
+	    # IBM XL 8.0, 9.0 on PPC and BlueGene
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+	    ;;
+	  *)
+	    case `$CC -V 2>&1 | sed 5q` in
+	    *Sun\ C*)
+	      # Sun C++ 5.9
+	      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	      ;;
+	    esac
+	    ;;
+	esac
+	;;
+      lynxos*)
+	;;
+      m88k*)
+	;;
+      mvs*)
+	case $cc_basename in
+	  cxx*)
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      netbsd* | netbsdelf*-gnu)
+	;;
+      *qnx* | *nto*)
+        # QNX uses GNU C++, but need to define -shared option too, otherwise
+        # it will coredump.
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+        ;;
+      osf3* | osf4* | osf5*)
+	case $cc_basename in
+	  KCC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,'
+	    ;;
+	  RCC*)
+	    # Rational C++ 2.4.1
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  cxx*)
+	    # Digital/Compaq C++
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    # Make sure the PIC flag is empty.  It appears that all Alpha
+	    # Linux and Compaq Tru64 Unix objects are PIC.
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      psos*)
+	;;
+      solaris*)
+	case $cc_basename in
+	  CC* | sunCC*)
+	    # Sun C++ 4.2, 5.x and Centerline C++
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	    ;;
+	  gcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      sunos4*)
+	case $cc_basename in
+	  CC*)
+	    # Sun C++ 4.x
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	  lcc*)
+	    # Lucid
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+	case $cc_basename in
+	  CC*)
+	    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	    ;;
+	esac
+	;;
+      tandem*)
+	case $cc_basename in
+	  NCC*)
+	    # NonStop-UX NCC 3.20
+	    _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	    ;;
+	  *)
+	    ;;
+	esac
+	;;
+      vxworks*)
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+	;;
+    esac
+  fi
+],
+[
+  if test "$GCC" = yes; then
+    _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+    _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+
+    case $host_os in
+      aix*)
+      # All AIX code is PIC.
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+        ;;
+      m68k)
+            # FIXME: we need at least 68020 code to build shared libraries, but
+            # adding the `-m68020' flag to GCC prevents building anything better,
+            # like `-m68040'.
+            _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4'
+        ;;
+      esac
+      ;;
+
+    beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*)
+      # PIC is the default for these OSes.
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      # Although the cygwin gcc ignores -fPIC, still need this for old-style
+      # (--disable-auto-import) libraries
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    darwin* | rhapsody*)
+      # PIC is the default on this platform
+      # Common symbols not allowed in MH_DYLIB files
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common'
+      ;;
+
+    haiku*)
+      # PIC is the default for Haiku.
+      # The "-static" flag exists, but is broken.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)=
+      ;;
+
+    hpux*)
+      # PIC is the default for 64-bit PA HP-UX, but not for 32-bit
+      # PA HP-UX.  On IA64 HP-UX, PIC is the default but the pic flag
+      # sets the default TLS model and affects inlining.
+      case $host_cpu in
+      hppa*64*)
+	# +Z the default
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	;;
+      esac
+      ;;
+
+    interix[[3-9]]*)
+      # Interix 3.x gcc -fpic/-fPIC options generate broken code.
+      # Instead, we relocate shared libraries at runtime.
+      ;;
+
+    msdosdjgpp*)
+      # Just because we use GCC doesn't mean we suddenly get shared libraries
+      # on systems that don't support them.
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      enable_shared=no
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic
+      fi
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+      ;;
+    esac
+
+    case $cc_basename in
+    nvcc*) # Cuda Compiler Driver 2.2
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker '
+      if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+        _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)"
+      fi
+      ;;
+    esac
+  else
+    # PORTME Check for flag to pass linker flags through the system compiler.
+    case $host_os in
+    aix*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      if test "$host_cpu" = ia64; then
+	# AIX 5 now supports IA64 processor
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      else
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp'
+      fi
+      ;;
+
+    mingw* | cygwin* | pw32* | os2* | cegcc*)
+      # This hack is so that the source file can tell whether it is being
+      # built for inclusion in a dll (and should export symbols for example).
+      m4_if([$1], [GCJ], [],
+	[_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT'])
+      ;;
+
+    hpux9* | hpux10* | hpux11*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but
+      # not for PA HP-UX.
+      case $host_cpu in
+      hppa*64*|ia64*)
+	# +Z the default
+	;;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z'
+	;;
+      esac
+      # Is there a better lt_prog_compiler_static that works with the bundled CC?
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='${wl}-a ${wl}archive'
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # PIC (with -KPIC) is the default.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+      case $cc_basename in
+      # old Intel for x86_64 which still supported -KPIC.
+      ecc*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # icc used to be incompatible with GCC.
+      # ICC 10 doesn't accept -KPIC any more.
+      icc* | ifort*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+        ;;
+      # Lahey Fortran 8.1.
+      lf95*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='--static'
+	;;
+      nagfor*)
+	# NAG Fortran compiler
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	;;
+      pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*)
+        # Portland Group compilers (*not* the Pentium gcc compiler,
+	# which looks to be a dead project)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+        ;;
+      ccc*)
+        _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+        # All Alpha code is PIC.
+        _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+        ;;
+      xl* | bgxl* | bgf* | mpixl*)
+	# IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink'
+	;;
+      *)
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*)
+	  # Sun Fortran 8.3 passes all unrecognized flags to the linker
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)=''
+	  ;;
+	*Sun\ F* | *Sun*Fortran*)
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+	  ;;
+	*Sun\ C*)
+	  # Sun C 5.9
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  ;;
+        *Intel*\ [[CF]]*Compiler*)
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-static'
+	  ;;
+	*Portland\ Group*)
+	  _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+	  _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic'
+	  _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+	  ;;
+	esac
+	;;
+      esac
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *nto* | *qnx*)
+      # QNX uses GNU C++, but need to define -shared option too, otherwise
+      # it will coredump.
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared'
+      ;;
+
+    osf3* | osf4* | osf5*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      # All OSF/1 code is PIC.
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    rdos*)
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared'
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      case $cc_basename in
+      f77* | f90* | f95* | sunf77* | sunf90* | sunf95*)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';;
+      *)
+	_LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld '
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4 | sysv4.2uw2* | sysv4.3*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec ;then
+	_LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic'
+	_LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      fi
+      ;;
+
+    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    unicos*)
+      _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,'
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic'
+      _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic'
+      ;;
+
+    *)
+      _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no
+      ;;
+    esac
+  fi
+])
+case $host_os in
+  # For platforms which do not support PIC, -DPIC is meaningless:
+  *djgpp*)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)=
+    ;;
+  *)
+    _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t at m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])"
+    ;;
+esac
+
+AC_CACHE_CHECK([for $compiler option to produce PIC],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)],
+  [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)])
+_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)
+
+#
+# Check to make sure the PIC flag actually works.
+#
+if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then
+  _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works],
+    [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)],
+    [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t at m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [],
+    [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in
+     "" | " "*) ;;
+     *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;;
+     esac],
+    [_LT_TAGVAR(lt_prog_compiler_pic, $1)=
+     _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no])
+fi
+_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1],
+	[Additional compiler flags for building library objects])
+
+_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1],
+	[How to pass a linker flag through the compiler])
+#
+# Check to make sure the static flag actually works.
+#
+wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\"
+_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works],
+  _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1),
+  $lt_tmp_static_flag,
+  [],
+  [_LT_TAGVAR(lt_prog_compiler_static, $1)=])
+_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1],
+	[Compiler flag to prevent dynamic linking])
+])# _LT_COMPILER_PIC
+
+
+# _LT_LINKER_SHLIBS([TAGNAME])
+# ----------------------------
+# See if the linker supports building shared libraries.
+m4_defun([_LT_LINKER_SHLIBS],
+[AC_REQUIRE([LT_PATH_LD])dnl
+AC_REQUIRE([LT_PATH_NM])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_DECL_SED])dnl
+m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl
+m4_require([_LT_TAG_COMPILER])dnl
+AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+m4_if([$1], [CXX], [
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  case $host_os in
+  aix[[4-9]]*)
+    # If we're using GNU nm, then we don't want the "-C" option.
+    # -C means demangle to AIX nm, but means don't demangle with GNU nm
+    # Also, AIX nm treats weak defined symbols like other global defined
+    # symbols, whereas GNU nm marks them as "W".
+    if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    else
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+    fi
+    ;;
+  pw32*)
+    _LT_TAGVAR(export_symbols_cmds, $1)="$ltdll_cmds"
+    ;;
+  cygwin* | mingw* | cegcc*)
+    case $cc_basename in
+    cl*)
+      _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+      ;;
+    *)
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+      ;;
+    esac
+    ;;
+  linux* | k*bsd*-gnu | gnu*)
+    _LT_TAGVAR(link_all_deplibs, $1)=no
+    ;;
+  *)
+    _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+    ;;
+  esac
+], [
+  runpath_var=
+  _LT_TAGVAR(allow_undefined_flag, $1)=
+  _LT_TAGVAR(always_export_symbols, $1)=no
+  _LT_TAGVAR(archive_cmds, $1)=
+  _LT_TAGVAR(archive_expsym_cmds, $1)=
+  _LT_TAGVAR(compiler_needs_object, $1)=no
+  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+  _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+  _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols'
+  _LT_TAGVAR(hardcode_automatic, $1)=no
+  _LT_TAGVAR(hardcode_direct, $1)=no
+  _LT_TAGVAR(hardcode_direct_absolute, $1)=no
+  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+  _LT_TAGVAR(hardcode_libdir_separator, $1)=
+  _LT_TAGVAR(hardcode_minus_L, $1)=no
+  _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+  _LT_TAGVAR(inherit_rpath, $1)=no
+  _LT_TAGVAR(link_all_deplibs, $1)=unknown
+  _LT_TAGVAR(module_cmds, $1)=
+  _LT_TAGVAR(module_expsym_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_new_cmds, $1)=
+  _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)=
+  _LT_TAGVAR(thread_safe_flag_spec, $1)=
+  _LT_TAGVAR(whole_archive_flag_spec, $1)=
+  # include_expsyms should be a list of space-separated symbols to be *always*
+  # included in the symbol list
+  _LT_TAGVAR(include_expsyms, $1)=
+  # exclude_expsyms can be an extended regexp of symbols to exclude
+  # it will be wrapped by ` (' and `)$', so one must not match beginning or
+  # end of line.  Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+  # as well as any symbol that contains `d'.
+  _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*']
+  # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+  # platforms (ab)use it in PIC code, but their linkers get confused if
+  # the symbol is explicitly referenced.  Since portable code cannot
+  # rely on this symbol name, it's probably fine to never include it in
+  # preloaded symbol tables.
+  # Exclude shared library initialization/finalization symbols.
+dnl Note also adjust exclude_expsyms for C++ above.
+  extract_expsyms_cmds=
+
+  case $host_os in
+  cygwin* | mingw* | pw32* | cegcc*)
+    # FIXME: the MSVC++ port hasn't been tested in a loooong time
+    # When not using gcc, we currently assume that we are using
+    # Microsoft Visual C++.
+    if test "$GCC" != yes; then
+      with_gnu_ld=no
+    fi
+    ;;
+  interix*)
+    # we just hope/assume this is gcc and not c89 (= MSVC++)
+    with_gnu_ld=yes
+    ;;
+  openbsd*)
+    with_gnu_ld=no
+    ;;
+  linux* | k*bsd*-gnu | gnu*)
+    _LT_TAGVAR(link_all_deplibs, $1)=no
+    ;;
+  esac
+
+  _LT_TAGVAR(ld_shlibs, $1)=yes
+
+  # On some targets, GNU ld is compatible enough with the native linker
+  # that we're better off using the native interface for both.
+  lt_use_gnu_ld_interface=no
+  if test "$with_gnu_ld" = yes; then
+    case $host_os in
+      aix*)
+	# The AIX port of GNU ld has always aspired to compatibility
+	# with the native linker.  However, as the warning in the GNU ld
+	# block says, versions before 2.19.5* couldn't really create working
+	# shared libraries, regardless of the interface used.
+	case `$LD -v 2>&1` in
+	  *\ \(GNU\ Binutils\)\ 2.19.5*) ;;
+	  *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;;
+	  *\ \(GNU\ Binutils\)\ [[3-9]]*) ;;
+	  *)
+	    lt_use_gnu_ld_interface=yes
+	    ;;
+	esac
+	;;
+      *)
+	lt_use_gnu_ld_interface=yes
+	;;
+    esac
+  fi
+
+  if test "$lt_use_gnu_ld_interface" = yes; then
+    # If archive_cmds runs LD, not CC, wlarc should be empty
+    wlarc='${wl}'
+
+    # Set some defaults for GNU ld with shared library support. These
+    # are reset later if shared libraries are not supported. Putting them
+    # here allows them to be overridden if necessary.
+    runpath_var=LD_RUN_PATH
+    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+    # ancient GNU ld didn't support --whole-archive et. al.
+    if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then
+      _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+    else
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+    supports_anon_versioning=no
+    case `$LD -v 2>&1` in
+      *GNU\ gold*) supports_anon_versioning=yes ;;
+      *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11
+      *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ...
+      *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ...
+      *\ 2.11.*) ;; # other 2.11 versions
+      *) supports_anon_versioning=yes ;;
+    esac
+
+    # See if GNU ld supports shared libraries.
+    case $host_os in
+    aix[[3-9]]*)
+      # On AIX/PPC, the GNU linker is very broken
+      if test "$host_cpu" != ia64; then
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.19, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support.  If you
+*** really care for shared libraries, you may want to install binutils
+*** 2.20 or above, or modify your PATH so that a non-GNU linker is found.
+*** You will then need to restart the configuration process.
+
+_LT_EOF
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    beos*)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	# Joseph Beckenbach <jrb3 at best.com> says some releases of gcc
+	# support --undefined.  This deserves some investigation.  FIXME
+	_LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+      # as there is no search path for DLLs.
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=no
+      _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+      _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols'
+      _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname']
+
+      if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	# If the export-symbols file already is a .def file (1st line
+	# is EXPORTS), use it as is; otherwise, prepend...
+	_LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	  cp $export_symbols $output_objdir/$soname.def;
+	else
+	  echo EXPORTS > $output_objdir/$soname.def;
+	  cat $export_symbols >> $output_objdir/$soname.def;
+	fi~
+	$CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    haiku*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    interix[[3-9]]*)
+      _LT_TAGVAR(hardcode_direct, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+      # Instead, shared libraries are loaded at an image base (0x10000000 by
+      # default) and relocated if they conflict, which is a slow very memory
+      # consuming and fragmenting process.  To avoid this, we pick a random,
+      # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+      # time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+      ;;
+
+    gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu)
+      tmp_diet=no
+      if test "$host_os" = linux-dietlibc; then
+	case $cc_basename in
+	  diet\ *) tmp_diet=yes;;	# linux-dietlibc with static linking (!diet-dyn)
+	esac
+      fi
+      if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \
+	 && test "$tmp_diet" = no
+      then
+	tmp_addflag=' $pic_flag'
+	tmp_sharedflag='-shared'
+	case $cc_basename,$host_cpu in
+        pgcc*)				# Portland Group C compiler
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag'
+	  ;;
+	pgf77* | pgf90* | pgf95* | pgfortran*)
+					# Portland Group f77 and f90 compilers
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  tmp_addflag=' $pic_flag -Mnomain' ;;
+	ecc*,ia64* | icc*,ia64*)	# Intel C compiler on ia64
+	  tmp_addflag=' -i_dynamic' ;;
+	efc*,ia64* | ifort*,ia64*)	# Intel Fortran compiler on ia64
+	  tmp_addflag=' -i_dynamic -nofor_main' ;;
+	ifc* | ifort*)			# Intel Fortran compiler
+	  tmp_addflag=' -nofor_main' ;;
+	lf95*)				# Lahey Fortran 8.1
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)=
+	  tmp_sharedflag='--shared' ;;
+	xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below)
+	  tmp_sharedflag='-qmkshrobj'
+	  tmp_addflag= ;;
+	nvcc*)	# Cuda Compiler Driver 2.2
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  _LT_TAGVAR(compiler_needs_object, $1)=yes
+	  ;;
+	esac
+	case `$CC -V 2>&1 | sed 5q` in
+	*Sun\ C*)			# Sun C 5.9
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	  _LT_TAGVAR(compiler_needs_object, $1)=yes
+	  tmp_sharedflag='-G' ;;
+	*Sun\ F*)			# Sun Fortran 8.3
+	  tmp_sharedflag='-G' ;;
+	esac
+	_LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+
+        if test "x$supports_anon_versioning" = xyes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+	    cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	    echo "local: *; };" >> $output_objdir/$libname.ver~
+	    $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+        fi
+
+	case $cc_basename in
+	xlf* | bgf* | bgxlf* | mpixlf*)
+	  # IBM XL Fortran 10.1 on PPC cannot create shared libs itself
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib'
+	  if test "x$supports_anon_versioning" = xyes; then
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+	      cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+	      echo "local: *; };" >> $output_objdir/$libname.ver~
+	      $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib'
+	  fi
+	  ;;
+	esac
+      else
+        _LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+	wlarc=
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      fi
+      ;;
+
+    solaris*)
+      if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+      elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)
+      case `$LD -v 2>&1` in
+        *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*)
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	cat <<_LT_EOF 1>&2
+
+*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 can not
+*** reliably create shared libraries on SCO systems.  Therefore, libtool
+*** is disabling shared libraries support.  We urge you to upgrade GNU
+*** binutils to release 2.16.91.0.3 or newer.  Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+_LT_EOF
+	;;
+	*)
+	  # For security reasons, it is highly recommended that you always
+	  # use absolute paths for naming shared libraries, and exclude the
+	  # DT_RUNPATH tag from executables and libraries.  But doing so
+	  # requires that you compile everything twice, which is a pain.
+	  if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+	  else
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	  fi
+	;;
+      esac
+      ;;
+
+    sunos4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      wlarc=
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+    esac
+
+    if test "$_LT_TAGVAR(ld_shlibs, $1)" = no; then
+      runpath_var=
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=
+      _LT_TAGVAR(whole_archive_flag_spec, $1)=
+    fi
+  else
+    # PORTME fill in a description of your system's linker (not GNU ld)
+    case $host_os in
+    aix3*)
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+      # Note: this linker hardcodes the directories in LIBPATH if there
+      # are no directories specified by -L.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      if test "$GCC" = yes && test -z "$lt_prog_compiler_static"; then
+	# Neither direct hardcoding nor static linking is supported with a
+	# broken collect2.
+	_LT_TAGVAR(hardcode_direct, $1)=unsupported
+      fi
+      ;;
+
+    aix[[4-9]]*)
+      if test "$host_cpu" = ia64; then
+	# On IA64, the linker does run time linking by default, so we don't
+	# have to do anything special.
+	aix_use_runtimelinking=no
+	exp_sym_flag='-Bexport'
+	no_entry_flag=""
+      else
+	# If we're using GNU nm, then we don't want the "-C" option.
+	# -C means demangle to AIX nm, but means don't demangle with GNU nm
+	# Also, AIX nm treats weak defined symbols like other global
+	# defined symbols, whereas GNU nm marks them as "W".
+	if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then
+	  _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	else
+	  _LT_TAGVAR(export_symbols_cmds, $1)='$NM -BCpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B")) && ([substr](\$ 3,1,1) != ".")) { print \$ 3 } }'\'' | sort -u > $export_symbols'
+	fi
+	aix_use_runtimelinking=no
+
+	# Test if we are trying to use run time linking or normal
+	# AIX style linking. If -brtl is somewhere in LDFLAGS, we
+	# need to do runtime linking.
+	case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+	  for ld_flag in $LDFLAGS; do
+	  if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+	    aix_use_runtimelinking=yes
+	    break
+	  fi
+	  done
+	  ;;
+	esac
+
+	exp_sym_flag='-bexport'
+	no_entry_flag='-bnoentry'
+      fi
+
+      # When large executables or shared objects are built, AIX ld can
+      # have problems creating the table of contents.  If linking a library
+      # or program results in "error TOC overflow" add -mminimal-toc to
+      # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+      # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+      _LT_TAGVAR(archive_cmds, $1)=''
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+      if test "$GCC" = yes; then
+	case $host_os in aix4.[[012]]|aix4.[[012]].*)
+	# We only want to do this on AIX 4.2 and lower, the check
+	# below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	   strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	  # We have reworked collect2
+	  :
+	  else
+	  # We have old collect2
+	  _LT_TAGVAR(hardcode_direct, $1)=unsupported
+	  # It fails to find uninstalled libraries when the uninstalled
+	  # path is not listed in the libpath.  Setting hardcode_minus_L
+	  # to unsupported forces relinking
+	  _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	  _LT_TAGVAR(hardcode_libdir_separator, $1)=
+	  fi
+	  ;;
+	esac
+	shared_flag='-shared'
+	if test "$aix_use_runtimelinking" = yes; then
+	  shared_flag="$shared_flag "'${wl}-G'
+	fi
+	_LT_TAGVAR(link_all_deplibs, $1)=no
+      else
+	# not using gcc
+	if test "$host_cpu" = ia64; then
+	# VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	# chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+	else
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag='${wl}-G'
+	  else
+	    shared_flag='${wl}-bM:SRE'
+	  fi
+	fi
+      fi
+
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+      # It seems that -bexpall does not export symbols beginning with
+      # underscore (_), so it is better to generate a list of symbols to export.
+      _LT_TAGVAR(always_export_symbols, $1)=yes
+      if test "$aix_use_runtimelinking" = yes; then
+	# Warning - without using the other runtime loading flags (-brtl),
+	# -berok will link without error, but may produce a broken library.
+	_LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+        # Determine the default libpath from the value encoded in an
+        # empty executable.
+        _LT_SYS_MODULE_PATH_AIX([$1])
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+      else
+	if test "$host_cpu" = ia64; then
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+	  _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+	  _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+	else
+	 # Determine the default libpath from the value encoded in an
+	 # empty executable.
+	 _LT_SYS_MODULE_PATH_AIX([$1])
+	 _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+	  # Warning - without using the other run time loading flags,
+	  # -berok will link without error, but may produce a broken library.
+	  _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+	  _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+	  if test "$with_gnu_ld" = yes; then
+	    # We only use this code for GNU lds that support --whole-archive.
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	  else
+	    # Exported symbols can be pulled into shared objects from archives
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+	  fi
+	  _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	  # This is similar to how AIX traditionally builds its shared libraries.
+	  _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+	fi
+      fi
+      ;;
+
+    amigaos*)
+      case $host_cpu in
+      powerpc)
+            # see comment about AmigaOS4 .so support
+            _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+            _LT_TAGVAR(archive_expsym_cmds, $1)=''
+        ;;
+      m68k)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+            _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes
+        ;;
+      esac
+      ;;
+
+    bsdi[[45]]*)
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic
+      ;;
+
+    cygwin* | mingw* | pw32* | cegcc*)
+      # When not using gcc, we currently assume that we are using
+      # Microsoft Visual C++.
+      # hardcode_libdir_flag_spec is actually meaningless, as there is
+      # no search path for DLLs.
+      case $cc_basename in
+      cl*)
+	# Native MSVC
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	_LT_TAGVAR(always_export_symbols, $1)=yes
+	_LT_TAGVAR(file_list_spec, $1)='@'
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	_LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	    sed -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	  else
+	    sed -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	  fi~
+	  $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	  linknames='
+	# The linker will not automatically build a static lib if we build a DLL.
+	# _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	_LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*'
+	_LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols'
+	# Don't use ranlib
+	_LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+	_LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+	  lt_tool_outputfile="@TOOL_OUTPUT@"~
+	  case $lt_outputfile in
+	    *.exe|*.EXE) ;;
+	    *)
+	      lt_outputfile="$lt_outputfile.exe"
+	      lt_tool_outputfile="$lt_tool_outputfile.exe"
+	      ;;
+	  esac~
+	  if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	    $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	    $RM "$lt_outputfile.manifest";
+	  fi'
+	;;
+      *)
+	# Assume MSVC wrapper
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	_LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	# Tell ltmain to make .lib files, not .a files.
+	libext=lib
+	# Tell ltmain to make .dll files, not .so files.
+	shrext_cmds=".dll"
+	# FIXME: Setting linknames here is a bad hack.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames='
+	# The linker will automatically build a .lib file if we build a DLL.
+	_LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	# FIXME: Should let the user specify the lib program.
+	_LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs'
+	_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	;;
+      esac
+      ;;
+
+    darwin* | rhapsody*)
+      _LT_DARWIN_LINKER_FEATURES($1)
+      ;;
+
+    dgux*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+    # support.  Future versions do this automatically, but an explicit c++rt0.o
+    # does not break anything, and helps significantly (at the cost of a little
+    # extra space).
+    freebsd2.2*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+    freebsd2.*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+    freebsd* | dragonfly*)
+      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    hpux9*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+
+      # hardcode_minus_L: Not really in the search PATH,
+      # but as the default location of the library.
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+      ;;
+
+    hpux10*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      if test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=:
+	_LT_TAGVAR(hardcode_direct, $1)=yes
+	_LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	# hardcode_minus_L: Not really in the search PATH,
+	# but as the default location of the library.
+	_LT_TAGVAR(hardcode_minus_L, $1)=yes
+      fi
+      ;;
+
+    hpux11*)
+      if test "$GCC" = yes && test "$with_gnu_ld" = no; then
+	case $host_cpu in
+	hppa*64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	esac
+      else
+	case $host_cpu in
+	hppa*64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	ia64*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	m4_if($1, [], [
+	  # Older versions of the 11.00 compiler do not understand -b yet
+	  # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does)
+	  _LT_LINKER_OPTION([if $CC understands -b],
+	    _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b],
+	    [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'],
+	    [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])],
+	  [_LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $libobjs $deplibs $compiler_flags'])
+	  ;;
+	esac
+      fi
+      if test "$with_gnu_ld" = no; then
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	case $host_cpu in
+	hppa*64*|ia64*)
+	  _LT_TAGVAR(hardcode_direct, $1)=no
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	  ;;
+	*)
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+
+	  # hardcode_minus_L: Not really in the search PATH,
+	  # but as the default location of the library.
+	  _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	  ;;
+	esac
+      fi
+      ;;
+
+    irix5* | irix6* | nonstopux*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	# Try to use the -exported_symbol ld option, if it does not
+	# work, assume that -exports_file does not work either and
+	# implicitly export all symbols.
+	# This should be the same for all languages, so no per-tag cache variable.
+	AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol],
+	  [lt_cv_irix_exported_symbol],
+	  [save_LDFLAGS="$LDFLAGS"
+	   LDFLAGS="$LDFLAGS -shared ${wl}-exported_symbol ${wl}foo ${wl}-update_registry ${wl}/dev/null"
+	   AC_LINK_IFELSE(
+	     [AC_LANG_SOURCE(
+	        [AC_LANG_CASE([C], [[int foo (void) { return 0; }]],
+			      [C++], [[int foo (void) { return 0; }]],
+			      [Fortran 77], [[
+      subroutine foo
+      end]],
+			      [Fortran], [[
+      subroutine foo
+      end]])])],
+	      [lt_cv_irix_exported_symbol=yes],
+	      [lt_cv_irix_exported_symbol=no])
+           LDFLAGS="$save_LDFLAGS"])
+	if test "$lt_cv_irix_exported_symbol" = yes; then
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations ${wl}-exports_file ${wl}$export_symbols -o $lib'
+	fi
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -exports_file $export_symbols -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(inherit_rpath, $1)=yes
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    netbsd* | netbsdelf*-gnu)
+      if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'  # a.out
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags'      # ELF
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    newsos6)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *nto* | *qnx*)
+      ;;
+
+    openbsd*)
+      if test -f /usr/libexec/ld.so; then
+	_LT_TAGVAR(hardcode_direct, $1)=yes
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags ${wl}-retain-symbols-file,$export_symbols'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	else
+	  case $host_os in
+	   openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*)
+	     _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+	     _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	     ;;
+	   *)
+	     _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags'
+	     _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	     ;;
+	  esac
+	fi
+      else
+	_LT_TAGVAR(ld_shlibs, $1)=no
+      fi
+      ;;
+
+    os2*)
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+      _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~echo DATA >> $output_objdir/$libname.def~echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+      _LT_TAGVAR(old_archive_from_new_cmds, $1)='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+      ;;
+
+    osf3*)
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+      else
+	_LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    osf4* | osf5*)	# as osf3* with the addition of -msym flag
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $pic_flag $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+      else
+	_LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~
+	$CC -shared${allow_undefined_flag} ${wl}-input ${wl}$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~$RM $lib.exp'
+
+	# Both c and cxx compiler support -rpath directly
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+      fi
+      _LT_TAGVAR(archive_cmds_need_lc, $1)='no'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+      ;;
+
+    solaris*)
+      _LT_TAGVAR(no_undefined_flag, $1)=' -z defs'
+      if test "$GCC" = yes; then
+	wlarc='${wl}'
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -shared $pic_flag ${wl}-z ${wl}text ${wl}-M ${wl}$lib.exp ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+      else
+	case `$CC -V 2>&1` in
+	*"Compilers 5.0"*)
+	  wlarc=''
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp'
+	  ;;
+	*)
+	  wlarc='${wl}'
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	  $CC -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp'
+	  ;;
+	esac
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      case $host_os in
+      solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+      *)
+	# The compiler driver will combine and reorder linker options,
+	# but understands `-z linker_flag'.  GCC discards it without `$wl',
+	# but is careful enough not to reorder.
+	# Supported since Solaris 2.6 (maybe 2.5.1?)
+	if test "$GCC" = yes; then
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+	else
+	  _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+	fi
+	;;
+      esac
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      ;;
+
+    sunos4*)
+      if test "x$host_vendor" = xsequent; then
+	# Use $CC to link under sequent, because it throws in some extra .o
+	# files that make .init and .fini sections work.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+      fi
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_direct, $1)=yes
+      _LT_TAGVAR(hardcode_minus_L, $1)=yes
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4)
+      case $host_vendor in
+	sni)
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true???
+	;;
+	siemens)
+	  ## LD is ld it makes a PLAMLIB
+	  ## CC just makes a GrossModule.
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs'
+	  _LT_TAGVAR(hardcode_direct, $1)=no
+        ;;
+	motorola)
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	  _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie
+	;;
+      esac
+      runpath_var='LD_RUN_PATH'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    sysv4.3*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport'
+      ;;
+
+    sysv4*MP*)
+      if test -d /usr/nec; then
+	_LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	runpath_var=LD_RUN_PATH
+	hardcode_runpath_var=yes
+	_LT_TAGVAR(ld_shlibs, $1)=yes
+      fi
+      ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    sysv5* | sco3.2v5* | sco5v6*)
+      # Note: We can NOT use -z defs as we might desire, because we do not
+      # link with -lc, and that would cause any symbols used from libc to
+      # always be unresolved, which means just about no library would
+      # ever link correctly.  If we're not using GNU ld we use -z text
+      # though, which does catch some bad symbols but isn't as heavy-handed
+      # as -z defs.
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+      _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+      _LT_TAGVAR(link_all_deplibs, $1)=yes
+      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+      runpath_var='LD_RUN_PATH'
+
+      if test "$GCC" = yes; then
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      else
+	_LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+      fi
+      ;;
+
+    uts4*)
+      _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      ;;
+
+    *)
+      _LT_TAGVAR(ld_shlibs, $1)=no
+      ;;
+    esac
+
+    if test x$host_vendor = xsni; then
+      case $host in
+      sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Blargedynsym'
+	;;
+      esac
+    fi
+  fi
+])
+AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld
+
+_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl
+_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl
+_LT_DECL([], [extract_expsyms_cmds], [2],
+    [The commands to extract the exported symbol list from a shared archive])
+
+#
+# Do we need to explicitly link libc?
+#
+case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in
+x|xyes)
+  # Assume -lc should be added
+  _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+
+  if test "$enable_shared" = yes && test "$GCC" = yes; then
+    case $_LT_TAGVAR(archive_cmds, $1) in
+    *'~'*)
+      # FIXME: we may have to deal with multi-command sequences.
+      ;;
+    '$CC '*)
+      # Test whether the compiler implicitly links with -lc since on some
+      # systems, -lgcc has to come before -lc. If gcc already passes -lc
+      # to ld, don't add -lc before -lgcc.
+      AC_CACHE_CHECK([whether -lc should be explicitly linked in],
+	[lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1),
+	[$RM conftest*
+	echo "$lt_simple_compile_test_code" > conftest.$ac_ext
+
+	if AC_TRY_EVAL(ac_compile) 2>conftest.err; then
+	  soname=conftest
+	  lib=conftest
+	  libobjs=conftest.$ac_objext
+	  deplibs=
+	  wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1)
+	  pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1)
+	  compiler_flags=-v
+	  linker_flags=-v
+	  verstring=
+	  output_objdir=.
+	  libname=conftest
+	  lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1)
+	  _LT_TAGVAR(allow_undefined_flag, $1)=
+	  if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1)
+	  then
+	    lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	  else
+	    lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	  fi
+	  _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag
+	else
+	  cat conftest.err 1>&5
+	fi
+	$RM conftest*
+	])
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)
+      ;;
+    esac
+  fi
+  ;;
+esac
+
+_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0],
+    [Whether or not to add -lc for building shared libraries])
+_LT_TAGDECL([allow_libtool_libs_with_static_runtimes],
+    [enable_shared_with_static_runtimes], [0],
+    [Whether or not to disallow shared libs when runtime libs are static])
+_LT_TAGDECL([], [export_dynamic_flag_spec], [1],
+    [Compiler flag to allow reflexive dlopens])
+_LT_TAGDECL([], [whole_archive_flag_spec], [1],
+    [Compiler flag to generate shared objects directly from archives])
+_LT_TAGDECL([], [compiler_needs_object], [1],
+    [Whether the compiler copes with passing no objects directly])
+_LT_TAGDECL([], [old_archive_from_new_cmds], [2],
+    [Create an old-style archive from a shared archive])
+_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2],
+    [Create a temporary old-style archive to link instead of a shared archive])
+_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive])
+_LT_TAGDECL([], [archive_expsym_cmds], [2])
+_LT_TAGDECL([], [module_cmds], [2],
+    [Commands used to build a loadable module if different from building
+    a shared archive.])
+_LT_TAGDECL([], [module_expsym_cmds], [2])
+_LT_TAGDECL([], [with_gnu_ld], [1],
+    [Whether we are building with GNU ld or not])
+_LT_TAGDECL([], [allow_undefined_flag], [1],
+    [Flag that allows shared libraries with undefined symbols to be built])
+_LT_TAGDECL([], [no_undefined_flag], [1],
+    [Flag that enforces no undefined symbols])
+_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1],
+    [Flag to hardcode $libdir into a binary during linking.
+    This must work even if $libdir does not exist])
+_LT_TAGDECL([], [hardcode_libdir_separator], [1],
+    [Whether we need a single "-rpath" flag with a separated argument])
+_LT_TAGDECL([], [hardcode_direct], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary])
+_LT_TAGDECL([], [hardcode_direct_absolute], [0],
+    [Set to "yes" if using DIR/libNAME${shared_ext} during linking hardcodes
+    DIR into the resulting binary and the resulting library dependency is
+    "absolute", i.e impossible to change by setting ${shlibpath_var} if the
+    library is relocated])
+_LT_TAGDECL([], [hardcode_minus_L], [0],
+    [Set to "yes" if using the -LDIR flag during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_shlibpath_var], [0],
+    [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR
+    into the resulting binary])
+_LT_TAGDECL([], [hardcode_automatic], [0],
+    [Set to "yes" if building a shared library automatically hardcodes DIR
+    into the library and all subsequent libraries and executables linked
+    against it])
+_LT_TAGDECL([], [inherit_rpath], [0],
+    [Set to yes if linker adds runtime paths of dependent libraries
+    to runtime path list])
+_LT_TAGDECL([], [link_all_deplibs], [0],
+    [Whether libtool must link a program against all its dependency libraries])
+_LT_TAGDECL([], [always_export_symbols], [0],
+    [Set to "yes" if exported symbols are required])
+_LT_TAGDECL([], [export_symbols_cmds], [2],
+    [The commands to list exported symbols])
+_LT_TAGDECL([], [exclude_expsyms], [1],
+    [Symbols that should not be listed in the preloaded symbols])
+_LT_TAGDECL([], [include_expsyms], [1],
+    [Symbols that must always be exported])
+_LT_TAGDECL([], [prelink_cmds], [2],
+    [Commands necessary for linking programs (against libraries) with templates])
+_LT_TAGDECL([], [postlink_cmds], [2],
+    [Commands necessary for finishing linking programs])
+_LT_TAGDECL([], [file_list_spec], [1],
+    [Specify filename containing input files])
+dnl FIXME: Not yet implemented
+dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1],
+dnl    [Compiler flag to generate thread safe objects])
+])# _LT_LINKER_SHLIBS
+
+
+# _LT_LANG_C_CONFIG([TAG])
+# ------------------------
+# Ensure that the configuration variables for a C compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_C_CONFIG],
+[m4_require([_LT_DECL_EGREP])dnl
+lt_save_CC="$CC"
+AC_LANG_PUSH(C)
+
+# Source file extension for C test sources.
+ac_ext=c
+
+# Object file extension for compiled C test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="int some_variable = 0;"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='int main(){return(0);}'
+
+_LT_TAG_COMPILER
+# Save the default compiler, since it gets overwritten when the other
+# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP.
+compiler_DEFAULT=$CC
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+## CAVEAT EMPTOR:
+## There is no encapsulation within the following macros, do not change
+## the running order or otherwise move them around unless you know exactly
+## what you are doing...
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_SYS_DYNAMIC_LINKER($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+  LT_SYS_DLOPEN_SELF
+  _LT_CMD_STRIPLIB
+
+  # Report which library types will actually be built
+  AC_MSG_CHECKING([if libtool supports shared libraries])
+  AC_MSG_RESULT([$can_build_shared])
+
+  AC_MSG_CHECKING([whether to build shared libraries])
+  test "$can_build_shared" = "no" && enable_shared=no
+
+  # On AIX, shared libraries and static libraries use the same namespace, and
+  # are all built from PIC.
+  case $host_os in
+  aix3*)
+    test "$enable_shared" = yes && enable_static=no
+    if test -n "$RANLIB"; then
+      archive_cmds="$archive_cmds~\$RANLIB \$lib"
+      postinstall_cmds='$RANLIB $lib'
+    fi
+    ;;
+
+  aix[[4-9]]*)
+    if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+      test "$enable_shared" = yes && enable_static=no
+    fi
+    ;;
+  esac
+  AC_MSG_RESULT([$enable_shared])
+
+  AC_MSG_CHECKING([whether to build static libraries])
+  # Make sure either enable_shared or enable_static is yes.
+  test "$enable_shared" = yes || enable_static=yes
+  AC_MSG_RESULT([$enable_static])
+
+  _LT_CONFIG($1)
+fi
+AC_LANG_POP
+CC="$lt_save_CC"
+])# _LT_LANG_C_CONFIG
+
+
+# _LT_LANG_CXX_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a C++ compiler are suitably
+# defined.  These variables are subsequently used by _LT_CONFIG to write
+# the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_CXX_CONFIG],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+m4_require([_LT_DECL_EGREP])dnl
+m4_require([_LT_PATH_MANIFEST_TOOL])dnl
+if test -n "$CXX" && ( test "X$CXX" != "Xno" &&
+    ( (test "X$CXX" = "Xg++" && `g++ -v >/dev/null 2>&1` ) ||
+    (test "X$CXX" != "Xg++"))) ; then
+  AC_PROG_CXXCPP
+else
+  _lt_caught_CXX_error=yes
+fi
+
+AC_LANG_PUSH(C++)
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(compiler_needs_object, $1)=no
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for C++ test sources.
+ac_ext=cpp
+
+# Object file extension for compiled C++ test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the CXX compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_caught_CXX_error" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="int some_variable = 0;"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }'
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC=$CC
+  lt_save_CFLAGS=$CFLAGS
+  lt_save_LD=$LD
+  lt_save_GCC=$GCC
+  GCC=$GXX
+  lt_save_with_gnu_ld=$with_gnu_ld
+  lt_save_path_LD=$lt_cv_path_LD
+  if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then
+    lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx
+  else
+    $as_unset lt_cv_prog_gnu_ld
+  fi
+  if test -n "${lt_cv_path_LDCXX+set}"; then
+    lt_cv_path_LD=$lt_cv_path_LDCXX
+  else
+    $as_unset lt_cv_path_LD
+  fi
+  test -z "${LDCXX+set}" || LD=$LDCXX
+  CC=${CXX-"c++"}
+  CFLAGS=$CXXFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    # We don't want -fno-exception when compiling C++ code, so set the
+    # no_builtin_flag separately
+    if test "$GXX" = yes; then
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin'
+    else
+      _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=
+    fi
+
+    if test "$GXX" = yes; then
+      # Set up default GNU C++ configuration
+
+      LT_PATH_LD
+
+      # Check if GNU C++ uses GNU ld as the underlying linker, since the
+      # archiving commands below assume that GNU ld is being used.
+      if test "$with_gnu_ld" = yes; then
+        _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+        # If archive_cmds runs LD, not CC, wlarc should be empty
+        # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to
+        #     investigate it a little bit more. (MM)
+        wlarc='${wl}'
+
+        # ancient GNU ld didn't support --whole-archive et. al.
+        if eval "`$CC -print-prog-name=ld` --help 2>&1" |
+	  $GREP 'no-whole-archive' > /dev/null; then
+          _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+        else
+          _LT_TAGVAR(whole_archive_flag_spec, $1)=
+        fi
+      else
+        with_gnu_ld=no
+        wlarc=
+
+        # A generic and very simple default shared library creation
+        # command for GNU C++ for the case where it uses the native
+        # linker, instead of GNU ld.  If possible, this setting should
+        # overridden to take advantage of the native linker features on
+        # the platform it is being used on.
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+      fi
+
+      # Commands to make compiler produce verbose output that lists
+      # what "hidden" libraries, object files and flags are used when
+      # linking a shared library.
+      output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+    else
+      GXX=no
+      with_gnu_ld=no
+      wlarc=
+    fi
+
+    # PORTME: fill in a description of your system's C++ link characteristics
+    AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries])
+    _LT_TAGVAR(ld_shlibs, $1)=yes
+    case $host_os in
+      aix3*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+      aix[[4-9]]*)
+        if test "$host_cpu" = ia64; then
+          # On IA64, the linker does run time linking by default, so we don't
+          # have to do anything special.
+          aix_use_runtimelinking=no
+          exp_sym_flag='-Bexport'
+          no_entry_flag=""
+        else
+          aix_use_runtimelinking=no
+
+          # Test if we are trying to use run time linking or normal
+          # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+          # need to do runtime linking.
+          case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*)
+	    for ld_flag in $LDFLAGS; do
+	      case $ld_flag in
+	      *-brtl*)
+	        aix_use_runtimelinking=yes
+	        break
+	        ;;
+	      esac
+	    done
+	    ;;
+          esac
+
+          exp_sym_flag='-bexport'
+          no_entry_flag='-bnoentry'
+        fi
+
+        # When large executables or shared objects are built, AIX ld can
+        # have problems creating the table of contents.  If linking a library
+        # or program results in "error TOC overflow" add -mminimal-toc to
+        # CXXFLAGS/CFLAGS for g++/gcc.  In the cases where that is not
+        # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+        _LT_TAGVAR(archive_cmds, $1)=''
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        _LT_TAGVAR(file_list_spec, $1)='${wl}-f,'
+
+        if test "$GXX" = yes; then
+          case $host_os in aix4.[[012]]|aix4.[[012]].*)
+          # We only want to do this on AIX 4.2 and lower, the check
+          # below for broken collect2 doesn't work under 4.3+
+	  collect2name=`${CC} -print-prog-name=collect2`
+	  if test -f "$collect2name" &&
+	     strings "$collect2name" | $GREP resolve_lib_name >/dev/null
+	  then
+	    # We have reworked collect2
+	    :
+	  else
+	    # We have old collect2
+	    _LT_TAGVAR(hardcode_direct, $1)=unsupported
+	    # It fails to find uninstalled libraries when the uninstalled
+	    # path is not listed in the libpath.  Setting hardcode_minus_L
+	    # to unsupported forces relinking
+	    _LT_TAGVAR(hardcode_minus_L, $1)=yes
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=
+	  fi
+          esac
+          shared_flag='-shared'
+	  if test "$aix_use_runtimelinking" = yes; then
+	    shared_flag="$shared_flag "'${wl}-G'
+	  fi
+        else
+          # not using gcc
+          if test "$host_cpu" = ia64; then
+	  # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release
+	  # chokes on -Wl,-G. The following line is correct:
+	  shared_flag='-G'
+          else
+	    if test "$aix_use_runtimelinking" = yes; then
+	      shared_flag='${wl}-G'
+	    else
+	      shared_flag='${wl}-bM:SRE'
+	    fi
+          fi
+        fi
+
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-bexpall'
+        # It seems that -bexpall does not export symbols beginning with
+        # underscore (_), so it is better to generate a list of symbols to
+	# export.
+        _LT_TAGVAR(always_export_symbols, $1)=yes
+        if test "$aix_use_runtimelinking" = yes; then
+          # Warning - without using the other runtime loading flags (-brtl),
+          # -berok will link without error, but may produce a broken library.
+          _LT_TAGVAR(allow_undefined_flag, $1)='-berok'
+          # Determine the default libpath from the value encoded in an empty
+          # executable.
+          _LT_SYS_MODULE_PATH_AIX([$1])
+          _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+
+          _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then func_echo_all "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+        else
+          if test "$host_cpu" = ia64; then
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $libdir:/usr/lib:/lib'
+	    _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs"
+	    _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\${wl}$no_entry_flag"' $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$exp_sym_flag:\$export_symbols"
+          else
+	    # Determine the default libpath from the value encoded in an
+	    # empty executable.
+	    _LT_SYS_MODULE_PATH_AIX([$1])
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-blibpath:$libdir:'"$aix_libpath"
+	    # Warning - without using the other run time loading flags,
+	    # -berok will link without error, but may produce a broken library.
+	    _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-bernotok'
+	    _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-berok'
+	    if test "$with_gnu_ld" = yes; then
+	      # We only use this code for GNU lds that support --whole-archive.
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	    else
+	      # Exported symbols can be pulled into shared objects from archives
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience'
+	    fi
+	    _LT_TAGVAR(archive_cmds_need_lc, $1)=yes
+	    # This is similar to how AIX traditionally builds its shared
+	    # libraries.
+	    _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs ${wl}-bnoentry $compiler_flags ${wl}-bE:$export_symbols${allow_undefined_flag}~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$soname'
+          fi
+        fi
+        ;;
+
+      beos*)
+	if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  # Joseph Beckenbach <jrb3 at best.com> says some releases of gcc
+	  # support --undefined.  This deserves some investigation.  FIXME
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	else
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	fi
+	;;
+
+      chorus*)
+        case $cc_basename in
+          *)
+	  # FIXME: insert proper C++ library support
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	  ;;
+        esac
+        ;;
+
+      cygwin* | mingw* | pw32* | cegcc*)
+	case $GXX,$cc_basename in
+	,cl* | no,cl*)
+	  # Native MSVC
+	  # hardcode_libdir_flag_spec is actually meaningless, as there is
+	  # no search path for DLLs.
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' '
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  _LT_TAGVAR(always_export_symbols, $1)=yes
+	  _LT_TAGVAR(file_list_spec, $1)='@'
+	  # Tell ltmain to make .lib files, not .a files.
+	  libext=lib
+	  # Tell ltmain to make .dll files, not .so files.
+	  shrext_cmds=".dll"
+	  # FIXME: Setting linknames here is a bad hack.
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-dll~linknames='
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	      $SED -n -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' -e '1\\\!p' < $export_symbols > $output_objdir/$soname.exp;
+	    else
+	      $SED -e 's/\\\\\\\(.*\\\\\\\)/-link\\\ -EXPORT:\\\\\\\1/' < $export_symbols > $output_objdir/$soname.exp;
+	    fi~
+	    $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~
+	    linknames='
+	  # The linker will not automatically build a static lib if we build a DLL.
+	  # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true'
+	  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+	  # Don't use ranlib
+	  _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib'
+	  _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~
+	    lt_tool_outputfile="@TOOL_OUTPUT@"~
+	    case $lt_outputfile in
+	      *.exe|*.EXE) ;;
+	      *)
+		lt_outputfile="$lt_outputfile.exe"
+		lt_tool_outputfile="$lt_tool_outputfile.exe"
+		;;
+	    esac~
+	    func_to_tool_file "$lt_outputfile"~
+	    if test "$MANIFEST_TOOL" != ":" && test -f "$lt_outputfile.manifest"; then
+	      $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1;
+	      $RM "$lt_outputfile.manifest";
+	    fi'
+	  ;;
+	*)
+	  # g++
+	  # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless,
+	  # as there is no search path for DLLs.
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir'
+	  _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-all-symbols'
+	  _LT_TAGVAR(allow_undefined_flag, $1)=unsupported
+	  _LT_TAGVAR(always_export_symbols, $1)=no
+	  _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes
+
+	  if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	    # If the export-symbols file already is a .def file (1st line
+	    # is EXPORTS), use it as is; otherwise, prepend...
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='if test "x`$SED 1q $export_symbols`" = xEXPORTS; then
+	      cp $export_symbols $output_objdir/$soname.def;
+	    else
+	      echo EXPORTS > $output_objdir/$soname.def;
+	      cat $export_symbols >> $output_objdir/$soname.def;
+	    fi~
+	    $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname ${wl}--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib'
+	  else
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	  fi
+	  ;;
+	esac
+	;;
+      darwin* | rhapsody*)
+        _LT_DARWIN_LINKER_FEATURES($1)
+	;;
+
+      dgux*)
+        case $cc_basename in
+          ec++*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          ghcx*)
+	    # Green Hills C++ Compiler
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      freebsd2.*)
+        # C++ shared libraries reported to be fairly broken before
+	# switch to ELF
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      freebsd-elf*)
+        _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+        ;;
+
+      freebsd* | dragonfly*)
+        # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF
+        # conventions
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+        ;;
+
+      haiku*)
+        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+        _LT_TAGVAR(link_all_deplibs, $1)=yes
+        ;;
+
+      hpux9*)
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+        _LT_TAGVAR(hardcode_direct, $1)=yes
+        _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+				             # but as the default
+				             # location of the library.
+
+        case $cc_basename in
+          CC*)
+            # FIXME: insert proper C++ library support
+            _LT_TAGVAR(ld_shlibs, $1)=no
+            ;;
+          aCC*)
+            _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            # Commands to make compiler produce verbose output that lists
+            # what "hidden" libraries, object files and flags are used when
+            # linking a shared library.
+            #
+            # There doesn't appear to be a way to prevent this compiler from
+            # explicitly linking system object files so we need to strip them
+            # from the output so that they don't get included in the library
+            # dependencies.
+            output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+            ;;
+          *)
+            if test "$GXX" = yes; then
+              _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag ${wl}+b ${wl}$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib'
+            else
+              # FIXME: insert proper C++ library support
+              _LT_TAGVAR(ld_shlibs, $1)=no
+            fi
+            ;;
+        esac
+        ;;
+
+      hpux10*|hpux11*)
+        if test $with_gnu_ld = no; then
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}+b ${wl}$libdir'
+	  _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+          case $host_cpu in
+            hppa*64*|ia64*)
+              ;;
+            *)
+	      _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+              ;;
+          esac
+        fi
+        case $host_cpu in
+          hppa*64*|ia64*)
+            _LT_TAGVAR(hardcode_direct, $1)=no
+            _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+            ;;
+          *)
+            _LT_TAGVAR(hardcode_direct, $1)=yes
+            _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+            _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH,
+					         # but as the default
+					         # location of the library.
+            ;;
+        esac
+
+        case $cc_basename in
+          CC*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          aCC*)
+	    case $host_cpu in
+	      hppa*64*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	      ia64*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	      *)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -b ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	        ;;
+	    esac
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+	    ;;
+          *)
+	    if test "$GXX" = yes; then
+	      if test $with_gnu_ld = no; then
+	        case $host_cpu in
+	          hppa*64*)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC ${wl}+h ${wl}$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	          ia64*)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	          *)
+	            _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag ${wl}+h ${wl}$soname ${wl}+b ${wl}$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	            ;;
+	        esac
+	      fi
+	    else
+	      # FIXME: insert proper C++ library support
+	      _LT_TAGVAR(ld_shlibs, $1)=no
+	    fi
+	    ;;
+        esac
+        ;;
+
+      interix[[3-9]]*)
+	_LT_TAGVAR(hardcode_direct, $1)=no
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	# Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc.
+	# Instead, shared libraries are loaded at an image base (0x10000000 by
+	# default) and relocated if they conflict, which is a slow very memory
+	# consuming and fragmenting process.  To avoid this, we pick a random,
+	# 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link
+	# time.  Moving up from 0x10000000 also allows more sbrk(2) space.
+	_LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+	_LT_TAGVAR(archive_expsym_cmds, $1)='sed "s,^,_," $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags ${wl}-h,$soname ${wl}--retain-symbols-file,$output_objdir/$soname.expsym ${wl}--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib'
+	;;
+      irix5* | irix6*)
+        case $cc_basename in
+          CC*)
+	    # SGI C++
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -ar", where "CC" is the IRIX C++ compiler.  This is
+	    # necessary to make sure instantiated templates are included
+	    # in the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs'
+	    ;;
+          *)
+	    if test "$GXX" = yes; then
+	      if test "$with_gnu_ld" = no; then
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+	      else
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` -o $lib'
+	      fi
+	    fi
+	    _LT_TAGVAR(link_all_deplibs, $1)=yes
+	    ;;
+        esac
+        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+        _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+        _LT_TAGVAR(inherit_rpath, $1)=yes
+        ;;
+
+      linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*)
+        case $cc_basename in
+          KCC*)
+	    # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+	    # KCC will only create a shared library if the output file
+	    # ends with ".so" (or ".sl" for HP-UX), so rename the library
+	    # to its proper name (with version) after linking.
+	    _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib ${wl}-retain-symbols-file,$export_symbols; mv \$templib $lib'
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -Bstatic", where "CC" is the KAI C++ compiler.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs'
+	    ;;
+	  icpc* | ecpc* )
+	    # Intel C++
+	    with_gnu_ld=yes
+	    # version 8.0 and above of icpc choke on multiply defined symbols
+	    # if we add $predep_objects and $postdep_objects, however 7.1 and
+	    # earlier do not add the objects themselves.
+	    case `$CC -V 2>&1` in
+	      *"Version 7."*)
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+		_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+		;;
+	      *)  # Version 8.0 or newer
+	        tmp_idyn=
+	        case $host_cpu in
+		  ia64*) tmp_idyn=' -i_dynamic';;
+		esac
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+		_LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+		;;
+	    esac
+	    _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive$convenience ${wl}--no-whole-archive'
+	    ;;
+          pgCC* | pgcpp*)
+            # Portland Group C++ compiler
+	    case `$CC -V` in
+	    *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*)
+	      _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~
+		compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"'
+	      _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~
+		$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~
+		$RANLIB $oldlib'
+	      _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+		$CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~
+		rm -rf $tpldir~
+		$CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~
+		$CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+	      ;;
+	    *) # Version 6 and above use weak symbols
+	      _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname -o $lib'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname ${wl}-retain-symbols-file ${wl}$export_symbols -o $lib'
+	      ;;
+	    esac
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}--rpath ${wl}$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`for conv in $convenience\"\"; do test  -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+            ;;
+	  cxx*)
+	    # Compaq C++
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $wl$soname  -o $lib ${wl}-retain-symbols-file $wl$export_symbols'
+
+	    runpath_var=LD_RUN_PATH
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed'
+	    ;;
+	  xl* | mpixl* | bgxl*)
+	    # IBM XL 8.0 on PPC, with GNU ld
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}--export-dynamic'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+	    if test "x$supports_anon_versioning" = xyes; then
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~
+		cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~
+		echo "local: *; };" >> $output_objdir/$libname.ver~
+		$CC -qmkshrobj $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-version-script ${wl}$output_objdir/$libname.ver -o $lib'
+	    fi
+	    ;;
+	  *)
+	    case `$CC -V 2>&1 | sed 5q` in
+	    *Sun\ C*)
+	      # Sun C++ 5.9
+	      _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+	      _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	      _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G${allow_undefined_flag} -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file ${wl}$export_symbols'
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	      _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` ${wl}--no-whole-archive'
+	      _LT_TAGVAR(compiler_needs_object, $1)=yes
+
+	      # Not sure whether something based on
+	      # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1
+	      # would be better.
+	      output_verbose_link_cmd='func_echo_all'
+
+	      # Archives containing C++ object files must be created using
+	      # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+	      # necessary to make sure instantiated templates are included
+	      # in the archive.
+	      _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+	      ;;
+	    esac
+	    ;;
+	esac
+	;;
+
+      lynxos*)
+        # FIXME: insert proper C++ library support
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      m88k*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      mvs*)
+        case $cc_basename in
+          cxx*)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+	  *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+	esac
+	;;
+
+      netbsd*)
+        if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then
+	  _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable  -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags'
+	  wlarc=
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	fi
+	# Workaround some broken pre-1.5 toolchains
+	output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"'
+	;;
+
+      *nto* | *qnx*)
+        _LT_TAGVAR(ld_shlibs, $1)=yes
+	;;
+
+      openbsd2*)
+        # C++ shared libraries are fairly broken
+	_LT_TAGVAR(ld_shlibs, $1)=no
+	;;
+
+      openbsd*)
+	if test -f /usr/libexec/ld.so; then
+	  _LT_TAGVAR(hardcode_direct, $1)=yes
+	  _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	  _LT_TAGVAR(hardcode_direct_absolute, $1)=yes
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib'
+	  _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	  if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-retain-symbols-file,$export_symbols -o $lib'
+	    _LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-E'
+	    _LT_TAGVAR(whole_archive_flag_spec, $1)="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+	  fi
+	  output_verbose_link_cmd=func_echo_all
+	else
+	  _LT_TAGVAR(ld_shlibs, $1)=no
+	fi
+	;;
+
+      osf3* | osf4* | osf5*)
+        case $cc_basename in
+          KCC*)
+	    # Kuck and Associates, Inc. (KAI) C++ Compiler
+
+	    # KCC will only create a shared library if the output file
+	    # ends with ".so" (or ".sl" for HP-UX), so rename the library
+	    # to its proper name (with version) after linking.
+	    _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\${tempext}\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath,$libdir'
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Archives containing C++ object files must be created using
+	    # the KAI C++ compiler.
+	    case $host in
+	      osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;;
+	      *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;;
+	    esac
+	    ;;
+          RCC*)
+	    # Rational C++ 2.4.1
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          cxx*)
+	    case $host in
+	      osf3*)
+	        _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname $soname `test -n "$verstring" && func_echo_all "${wl}-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+		;;
+	      *)
+	        _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*'
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~
+	          echo "-hidden">> $lib.exp~
+	          $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname ${wl}-input ${wl}$lib.exp  `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry ${output_objdir}/so_locations -o $lib~
+	          $RM $lib.exp'
+	        _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir'
+		;;
+	    esac
+
+	    _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	    # Commands to make compiler produce verbose output that lists
+	    # what "hidden" libraries, object files and flags are used when
+	    # linking a shared library.
+	    #
+	    # There doesn't appear to be a way to prevent this compiler from
+	    # explicitly linking system object files so we need to strip them
+	    # from the output so that they don't get included in the library
+	    # dependencies.
+	    output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list=""; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"'
+	    ;;
+	  *)
+	    if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+	      _LT_TAGVAR(allow_undefined_flag, $1)=' ${wl}-expect_unresolved ${wl}\*'
+	      case $host in
+	        osf3*)
+	          _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+		  ;;
+	        *)
+	          _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib ${allow_undefined_flag} $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && func_echo_all "${wl}-set_version ${wl}$verstring"` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+		  ;;
+	      esac
+
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-rpath ${wl}$libdir'
+	      _LT_TAGVAR(hardcode_libdir_separator, $1)=:
+
+	      # Commands to make compiler produce verbose output that lists
+	      # what "hidden" libraries, object files and flags are used when
+	      # linking a shared library.
+	      output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+
+	    else
+	      # FIXME: insert proper C++ library support
+	      _LT_TAGVAR(ld_shlibs, $1)=no
+	    fi
+	    ;;
+        esac
+        ;;
+
+      psos*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      sunos4*)
+        case $cc_basename in
+          CC*)
+	    # Sun C++ 4.x
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          lcc*)
+	    # Lucid
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      solaris*)
+        case $cc_basename in
+          CC* | sunCC*)
+	    # Sun C++ 4.2, 5.x and Centerline C++
+            _LT_TAGVAR(archive_cmds_need_lc,$1)=yes
+	    _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs'
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -G${allow_undefined_flag}  -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+	      $CC -G${allow_undefined_flag} ${wl}-M ${wl}$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	    _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir'
+	    _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	    case $host_os in
+	      solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+	      *)
+		# The compiler driver will combine and reorder linker options,
+		# but understands `-z linker_flag'.
+	        # Supported since Solaris 2.6 (maybe 2.5.1?)
+		_LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract'
+	        ;;
+	    esac
+	    _LT_TAGVAR(link_all_deplibs, $1)=yes
+
+	    output_verbose_link_cmd='func_echo_all'
+
+	    # Archives containing C++ object files must be created using
+	    # "CC -xar", where "CC" is the Sun C++ compiler.  This is
+	    # necessary to make sure instantiated templates are included
+	    # in the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs'
+	    ;;
+          gcx*)
+	    # Green Hills C++ Compiler
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+
+	    # The C++ compiler must be used to create the archive.
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs'
+	    ;;
+          *)
+	    # GNU C++ compiler with Solaris linker
+	    if test "$GXX" = yes && test "$with_gnu_ld" = no; then
+	      _LT_TAGVAR(no_undefined_flag, $1)=' ${wl}-z ${wl}defs'
+	      if $CC --version | $GREP -v '^2\.7' > /dev/null; then
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+		  $CC -shared $pic_flag -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	        # Commands to make compiler produce verbose output that lists
+	        # what "hidden" libraries, object files and flags are used when
+	        # linking a shared library.
+	        output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+	      else
+	        # g++ 2.7 appears to require `-G' NOT `-shared' on this
+	        # platform.
+	        _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $LDFLAGS $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags ${wl}-h $wl$soname -o $lib'
+	        _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~
+		  $CC -G -nostdlib ${wl}-M $wl$lib.exp -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp'
+
+	        # Commands to make compiler produce verbose output that lists
+	        # what "hidden" libraries, object files and flags are used when
+	        # linking a shared library.
+	        output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"'
+	      fi
+
+	      _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R $wl$libdir'
+	      case $host_os in
+		solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+		*)
+		  _LT_TAGVAR(whole_archive_flag_spec, $1)='${wl}-z ${wl}allextract$convenience ${wl}-z ${wl}defaultextract'
+		  ;;
+	      esac
+	    fi
+	    ;;
+        esac
+        ;;
+
+    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*)
+      _LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+      _LT_TAGVAR(archive_cmds_need_lc, $1)=no
+      _LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+      runpath_var='LD_RUN_PATH'
+
+      case $cc_basename in
+        CC*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+	*)
+	  _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	  ;;
+      esac
+      ;;
+
+      sysv5* | sco3.2v5* | sco5v6*)
+	# Note: We can NOT use -z defs as we might desire, because we do not
+	# link with -lc, and that would cause any symbols used from libc to
+	# always be unresolved, which means just about no library would
+	# ever link correctly.  If we're not using GNU ld we use -z text
+	# though, which does catch some bad symbols but isn't as heavy-handed
+	# as -z defs.
+	_LT_TAGVAR(no_undefined_flag, $1)='${wl}-z,text'
+	_LT_TAGVAR(allow_undefined_flag, $1)='${wl}-z,nodefs'
+	_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+	_LT_TAGVAR(hardcode_shlibpath_var, $1)=no
+	_LT_TAGVAR(hardcode_libdir_flag_spec, $1)='${wl}-R,$libdir'
+	_LT_TAGVAR(hardcode_libdir_separator, $1)=':'
+	_LT_TAGVAR(link_all_deplibs, $1)=yes
+	_LT_TAGVAR(export_dynamic_flag_spec, $1)='${wl}-Bexport'
+	runpath_var='LD_RUN_PATH'
+
+	case $cc_basename in
+          CC*)
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -G ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~
+	      '"$_LT_TAGVAR(old_archive_cmds, $1)"
+	    _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~
+	      '"$_LT_TAGVAR(reload_cmds, $1)"
+	    ;;
+	  *)
+	    _LT_TAGVAR(archive_cmds, $1)='$CC -shared ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared ${wl}-Bexport:$export_symbols ${wl}-h,$soname -o $lib $libobjs $deplibs $compiler_flags'
+	    ;;
+	esac
+      ;;
+
+      tandem*)
+        case $cc_basename in
+          NCC*)
+	    # NonStop-UX NCC 3.20
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+          *)
+	    # FIXME: insert proper C++ library support
+	    _LT_TAGVAR(ld_shlibs, $1)=no
+	    ;;
+        esac
+        ;;
+
+      vxworks*)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+
+      *)
+        # FIXME: insert proper C++ library support
+        _LT_TAGVAR(ld_shlibs, $1)=no
+        ;;
+    esac
+
+    AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)])
+    test "$_LT_TAGVAR(ld_shlibs, $1)" = no && can_build_shared=no
+
+    _LT_TAGVAR(GCC, $1)="$GXX"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+  LDCXX=$LD
+  LD=$lt_save_LD
+  GCC=$lt_save_GCC
+  with_gnu_ld=$lt_save_with_gnu_ld
+  lt_cv_path_LDCXX=$lt_cv_path_LD
+  lt_cv_path_LD=$lt_save_path_LD
+  lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld
+  lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld
+fi # test "$_lt_caught_CXX_error" != yes
+
+AC_LANG_POP
+])# _LT_LANG_CXX_CONFIG
+
+
+# _LT_FUNC_STRIPNAME_CNF
+# ----------------------
+# func_stripname_cnf prefix suffix name
+# strip PREFIX and SUFFIX off of NAME.
+# PREFIX and SUFFIX must not contain globbing or regex special
+# characters, hashes, percent signs, but SUFFIX may contain a leading
+# dot (in which case that matches only a dot).
+#
+# This function is identical to the (non-XSI) version of func_stripname,
+# except this one can be used by m4 code that may be executed by configure,
+# rather than the libtool script.
+m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl
+AC_REQUIRE([_LT_DECL_SED])
+AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])
+func_stripname_cnf ()
+{
+  case ${2} in
+  .*) func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%\\\\${2}\$%%"`;;
+  *)  func_stripname_result=`$ECHO "${3}" | $SED "s%^${1}%%; s%${2}\$%%"`;;
+  esac
+} # func_stripname_cnf
+])# _LT_FUNC_STRIPNAME_CNF
+
+# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME])
+# ---------------------------------
+# Figure out "hidden" library dependencies from verbose
+# compiler output when linking a shared library.
+# Parse the compiler output and extract the necessary
+# objects, libraries and library flags.
+m4_defun([_LT_SYS_HIDDEN_LIBDEPS],
+[m4_require([_LT_FILEUTILS_DEFAULTS])dnl
+AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl
+# Dependencies to place before and after the object being linked:
+_LT_TAGVAR(predep_objects, $1)=
+_LT_TAGVAR(postdep_objects, $1)=
+_LT_TAGVAR(predeps, $1)=
+_LT_TAGVAR(postdeps, $1)=
+_LT_TAGVAR(compiler_lib_search_path, $1)=
+
+dnl we can't use the lt_simple_compile_test_code here,
+dnl because it contains code intended for an executable,
+dnl not a library.  It's possible we should let each
+dnl tag define a new lt_????_link_test_code variable,
+dnl but it's only used here...
+m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF
+int a;
+void foo (void) { a = 0; }
+_LT_EOF
+], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF
+class Foo
+{
+public:
+  Foo (void) { a = 0; }
+private:
+  int a;
+};
+_LT_EOF
+], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer*4 a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF
+      subroutine foo
+      implicit none
+      integer a
+      a=0
+      return
+      end
+_LT_EOF
+], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF
+public class foo {
+  private int a;
+  public void bar (void) {
+    a = 0;
+  }
+};
+_LT_EOF
+], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF
+package foo
+func foo() {
+}
+_LT_EOF
+])
+
+_lt_libdeps_save_CFLAGS=$CFLAGS
+case "$CC $CFLAGS " in #(
+*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;;
+*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;;
+*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;;
+esac
+
+dnl Parse the compiler output and extract the necessary
+dnl objects, libraries and library flags.
+if AC_TRY_EVAL(ac_compile); then
+  # Parse the compiler output and extract the necessary
+  # objects, libraries and library flags.
+
+  # Sentinel used to keep track of whether or not we are before
+  # the conftest object file.
+  pre_test_object_deps_done=no
+
+  for p in `eval "$output_verbose_link_cmd"`; do
+    case ${prev}${p} in
+
+    -L* | -R* | -l*)
+       # Some compilers place space between "-{L,R}" and the path.
+       # Remove the space.
+       if test $p = "-L" ||
+          test $p = "-R"; then
+	 prev=$p
+	 continue
+       fi
+
+       # Expand the sysroot to ease extracting the directories later.
+       if test -z "$prev"; then
+         case $p in
+         -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;;
+         -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;;
+         -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;;
+         esac
+       fi
+       case $p in
+       =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;;
+       esac
+       if test "$pre_test_object_deps_done" = no; then
+	 case ${prev} in
+	 -L | -R)
+	   # Internal compiler library paths should come after those
+	   # provided the user.  The postdeps already come after the
+	   # user supplied libs so there is no need to process them.
+	   if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then
+	     _LT_TAGVAR(compiler_lib_search_path, $1)="${prev}${p}"
+	   else
+	     _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} ${prev}${p}"
+	   fi
+	   ;;
+	 # The "-l" case would never come before the object being
+	 # linked, so don't bother handling this case.
+	 esac
+       else
+	 if test -z "$_LT_TAGVAR(postdeps, $1)"; then
+	   _LT_TAGVAR(postdeps, $1)="${prev}${p}"
+	 else
+	   _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} ${prev}${p}"
+	 fi
+       fi
+       prev=
+       ;;
+
+    *.lto.$objext) ;; # Ignore GCC LTO objects
+    *.$objext)
+       # This assumes that the test object file only shows up
+       # once in the compiler output.
+       if test "$p" = "conftest.$objext"; then
+	 pre_test_object_deps_done=yes
+	 continue
+       fi
+
+       if test "$pre_test_object_deps_done" = no; then
+	 if test -z "$_LT_TAGVAR(predep_objects, $1)"; then
+	   _LT_TAGVAR(predep_objects, $1)="$p"
+	 else
+	   _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p"
+	 fi
+       else
+	 if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then
+	   _LT_TAGVAR(postdep_objects, $1)="$p"
+	 else
+	   _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p"
+	 fi
+       fi
+       ;;
+
+    *) ;; # Ignore the rest.
+
+    esac
+  done
+
+  # Clean up.
+  rm -f a.out a.exe
+else
+  echo "libtool.m4: error: problem compiling $1 test program"
+fi
+
+$RM -f confest.$objext
+CFLAGS=$_lt_libdeps_save_CFLAGS
+
+# PORTME: override above test on systems where it is broken
+m4_if([$1], [CXX],
+[case $host_os in
+interix[[3-9]]*)
+  # Interix 3.5 installs completely hosed .la files for C++, so rather than
+  # hack all around it, let's just trust "g++" to DTRT.
+  _LT_TAGVAR(predep_objects,$1)=
+  _LT_TAGVAR(postdep_objects,$1)=
+  _LT_TAGVAR(postdeps,$1)=
+  ;;
+
+linux*)
+  case `$CC -V 2>&1 | sed 5q` in
+  *Sun\ C*)
+    # Sun C++ 5.9
+
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+
+solaris*)
+  case $cc_basename in
+  CC* | sunCC*)
+    # The more standards-conforming stlport4 library is
+    # incompatible with the Cstd library. Avoid specifying
+    # it if it's in CXXFLAGS. Ignore libCrun as
+    # -library=stlport4 depends on it.
+    case " $CXX $CXXFLAGS " in
+    *" -library=stlport4 "*)
+      solaris_use_stlport4=yes
+      ;;
+    esac
+
+    # Adding this requires a known-good setup of shared libraries for
+    # Sun compiler versions before 5.6, else PIC objects from an old
+    # archive will be linked into the output, leading to subtle bugs.
+    if test "$solaris_use_stlport4" != yes; then
+      _LT_TAGVAR(postdeps,$1)='-library=Cstd -library=Crun'
+    fi
+    ;;
+  esac
+  ;;
+esac
+])
+
+case " $_LT_TAGVAR(postdeps, $1) " in
+*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;;
+esac
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=
+if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then
+ _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | ${SED} -e 's! -L! !g' -e 's!^ !!'`
+fi
+_LT_TAGDECL([], [compiler_lib_search_dirs], [1],
+    [The directories searched by this compiler when creating a shared library])
+_LT_TAGDECL([], [predep_objects], [1],
+    [Dependencies to place before and after the objects being linked to
+    create a shared library])
+_LT_TAGDECL([], [postdep_objects], [1])
+_LT_TAGDECL([], [predeps], [1])
+_LT_TAGDECL([], [postdeps], [1])
+_LT_TAGDECL([], [compiler_lib_search_path], [1],
+    [The library search path used internally by the compiler when linking
+    a shared library])
+])# _LT_SYS_HIDDEN_LIBDEPS
+
+
+# _LT_LANG_F77_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for a Fortran 77 compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_F77_CONFIG],
+[AC_LANG_PUSH(Fortran 77)
+if test -z "$F77" || test "X$F77" = "Xno"; then
+  _lt_disable_F77=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for f77 test sources.
+ac_ext=f
+
+# Object file extension for compiled f77 test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the F77 compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_F77" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${F77-"f77"}
+  CFLAGS=$FFLAGS
+  compiler=$CC
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+  GCC=$G77
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+	if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+	  test "$enable_shared" = yes && enable_static=no
+	fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$G77"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC="$lt_save_CC"
+  CFLAGS="$lt_save_CFLAGS"
+fi # test "$_lt_disable_F77" != yes
+
+AC_LANG_POP
+])# _LT_LANG_F77_CONFIG
+
+
+# _LT_LANG_FC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for a Fortran compiler are
+# suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_FC_CONFIG],
+[AC_LANG_PUSH(Fortran)
+
+if test -z "$FC" || test "X$FC" = "Xno"; then
+  _lt_disable_FC=yes
+fi
+
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+_LT_TAGVAR(allow_undefined_flag, $1)=
+_LT_TAGVAR(always_export_symbols, $1)=no
+_LT_TAGVAR(archive_expsym_cmds, $1)=
+_LT_TAGVAR(export_dynamic_flag_spec, $1)=
+_LT_TAGVAR(hardcode_direct, $1)=no
+_LT_TAGVAR(hardcode_direct_absolute, $1)=no
+_LT_TAGVAR(hardcode_libdir_flag_spec, $1)=
+_LT_TAGVAR(hardcode_libdir_separator, $1)=
+_LT_TAGVAR(hardcode_minus_L, $1)=no
+_LT_TAGVAR(hardcode_automatic, $1)=no
+_LT_TAGVAR(inherit_rpath, $1)=no
+_LT_TAGVAR(module_cmds, $1)=
+_LT_TAGVAR(module_expsym_cmds, $1)=
+_LT_TAGVAR(link_all_deplibs, $1)=unknown
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+_LT_TAGVAR(no_undefined_flag, $1)=
+_LT_TAGVAR(whole_archive_flag_spec, $1)=
+_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no
+
+# Source file extension for fc test sources.
+ac_ext=${ac_fc_srcext-f}
+
+# Object file extension for compiled fc test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# No sense in running all these tests if we already determined that
+# the FC compiler isn't working.  Some variables (like enable_shared)
+# are currently assumed to apply to all compilers on this platform,
+# and will be corrupted by setting them based on a non-working compiler.
+if test "$_lt_disable_FC" != yes; then
+  # Code to be used in simple compile tests
+  lt_simple_compile_test_code="\
+      subroutine t
+      return
+      end
+"
+
+  # Code to be used in simple link tests
+  lt_simple_link_test_code="\
+      program t
+      end
+"
+
+  # ltmain only uses $CC for tagged configurations so make sure $CC is set.
+  _LT_TAG_COMPILER
+
+  # save warnings/boilerplate of simple test code
+  _LT_COMPILER_BOILERPLATE
+  _LT_LINKER_BOILERPLATE
+
+  # Allow CC to be a program name with arguments.
+  lt_save_CC="$CC"
+  lt_save_GCC=$GCC
+  lt_save_CFLAGS=$CFLAGS
+  CC=${FC-"f95"}
+  CFLAGS=$FCFLAGS
+  compiler=$CC
+  GCC=$ac_cv_fc_compiler_gnu
+
+  _LT_TAGVAR(compiler, $1)=$CC
+  _LT_CC_BASENAME([$compiler])
+
+  if test -n "$compiler"; then
+    AC_MSG_CHECKING([if libtool supports shared libraries])
+    AC_MSG_RESULT([$can_build_shared])
+
+    AC_MSG_CHECKING([whether to build shared libraries])
+    test "$can_build_shared" = "no" && enable_shared=no
+
+    # On AIX, shared libraries and static libraries use the same namespace, and
+    # are all built from PIC.
+    case $host_os in
+      aix3*)
+        test "$enable_shared" = yes && enable_static=no
+        if test -n "$RANLIB"; then
+          archive_cmds="$archive_cmds~\$RANLIB \$lib"
+          postinstall_cmds='$RANLIB $lib'
+        fi
+        ;;
+      aix[[4-9]]*)
+	if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+	  test "$enable_shared" = yes && enable_static=no
+	fi
+        ;;
+    esac
+    AC_MSG_RESULT([$enable_shared])
+
+    AC_MSG_CHECKING([whether to build static libraries])
+    # Make sure either enable_shared or enable_static is yes.
+    test "$enable_shared" = yes || enable_static=yes
+    AC_MSG_RESULT([$enable_static])
+
+    _LT_TAGVAR(GCC, $1)="$ac_cv_fc_compiler_gnu"
+    _LT_TAGVAR(LD, $1)="$LD"
+
+    ## CAVEAT EMPTOR:
+    ## There is no encapsulation within the following macros, do not change
+    ## the running order or otherwise move them around unless you know exactly
+    ## what you are doing...
+    _LT_SYS_HIDDEN_LIBDEPS($1)
+    _LT_COMPILER_PIC($1)
+    _LT_COMPILER_C_O($1)
+    _LT_COMPILER_FILE_LOCKS($1)
+    _LT_LINKER_SHLIBS($1)
+    _LT_SYS_DYNAMIC_LINKER($1)
+    _LT_LINKER_HARDCODE_LIBPATH($1)
+
+    _LT_CONFIG($1)
+  fi # test -n "$compiler"
+
+  GCC=$lt_save_GCC
+  CC=$lt_save_CC
+  CFLAGS=$lt_save_CFLAGS
+fi # test "$_lt_disable_FC" != yes
+
+AC_LANG_POP
+])# _LT_LANG_FC_CONFIG
+
+
+# _LT_LANG_GCJ_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Java Compiler compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GCJ_CONFIG],
+[AC_REQUIRE([LT_PROG_GCJ])dnl
+AC_LANG_SAVE
+
+# Source file extension for Java test sources.
+ac_ext=java
+
+# Object file extension for compiled Java test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="class foo {}"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GCJ-"gcj"}
+CFLAGS=$GCJFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# GCJ did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+## CAVEAT EMPTOR:
+## There is no encapsulation within the following macros, do not change
+## the running order or otherwise move them around unless you know exactly
+## what you are doing...
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GCJ_CONFIG
+
+
+# _LT_LANG_GO_CONFIG([TAG])
+# --------------------------
+# Ensure that the configuration variables for the GNU Go compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_GO_CONFIG],
+[AC_REQUIRE([LT_PROG_GO])dnl
+AC_LANG_SAVE
+
+# Source file extension for Go test sources.
+ac_ext=go
+
+# Object file extension for compiled Go test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code="package main; func main() { }"
+
+# Code to be used in simple link tests
+lt_simple_link_test_code='package main; func main() { }'
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC=$CC
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=yes
+CC=${GOC-"gccgo"}
+CFLAGS=$GOFLAGS
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_TAGVAR(LD, $1)="$LD"
+_LT_CC_BASENAME([$compiler])
+
+# Go did not exist at the time GCC didn't implicitly link libc in.
+_LT_TAGVAR(archive_cmds_need_lc, $1)=no
+
+_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds
+_LT_TAGVAR(reload_flag, $1)=$reload_flag
+_LT_TAGVAR(reload_cmds, $1)=$reload_cmds
+
+## CAVEAT EMPTOR:
+## There is no encapsulation within the following macros, do not change
+## the running order or otherwise move them around unless you know exactly
+## what you are doing...
+if test -n "$compiler"; then
+  _LT_COMPILER_NO_RTTI($1)
+  _LT_COMPILER_PIC($1)
+  _LT_COMPILER_C_O($1)
+  _LT_COMPILER_FILE_LOCKS($1)
+  _LT_LINKER_SHLIBS($1)
+  _LT_LINKER_HARDCODE_LIBPATH($1)
+
+  _LT_CONFIG($1)
+fi
+
+AC_LANG_RESTORE
+
+GCC=$lt_save_GCC
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_GO_CONFIG
+
+
+# _LT_LANG_RC_CONFIG([TAG])
+# -------------------------
+# Ensure that the configuration variables for the Windows resource compiler
+# are suitably defined.  These variables are subsequently used by _LT_CONFIG
+# to write the compiler configuration to `libtool'.
+m4_defun([_LT_LANG_RC_CONFIG],
+[AC_REQUIRE([LT_PROG_RC])dnl
+AC_LANG_SAVE
+
+# Source file extension for RC test sources.
+ac_ext=rc
+
+# Object file extension for compiled RC test sources.
+objext=o
+_LT_TAGVAR(objext, $1)=$objext
+
+# Code to be used in simple compile tests
+lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }'
+
+# Code to be used in simple link tests
+lt_simple_link_test_code="$lt_simple_compile_test_code"
+
+# ltmain only uses $CC for tagged configurations so make sure $CC is set.
+_LT_TAG_COMPILER
+
+# save warnings/boilerplate of simple test code
+_LT_COMPILER_BOILERPLATE
+_LT_LINKER_BOILERPLATE
+
+# Allow CC to be a program name with arguments.
+lt_save_CC="$CC"
+lt_save_CFLAGS=$CFLAGS
+lt_save_GCC=$GCC
+GCC=
+CC=${RC-"windres"}
+CFLAGS=
+compiler=$CC
+_LT_TAGVAR(compiler, $1)=$CC
+_LT_CC_BASENAME([$compiler])
+_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes
+
+if test -n "$compiler"; then
+  :
+  _LT_CONFIG($1)
+fi
+
+GCC=$lt_save_GCC
+AC_LANG_RESTORE
+CC=$lt_save_CC
+CFLAGS=$lt_save_CFLAGS
+])# _LT_LANG_RC_CONFIG
+
+
+# LT_PROG_GCJ
+# -----------
+AC_DEFUN([LT_PROG_GCJ],
+[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ],
+  [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ],
+    [AC_CHECK_TOOL(GCJ, gcj,)
+      test "x${GCJFLAGS+set}" = xset || GCJFLAGS="-g -O2"
+      AC_SUBST(GCJFLAGS)])])[]dnl
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_GCJ], [])
+
+
+# LT_PROG_GO
+# ----------
+AC_DEFUN([LT_PROG_GO],
+[AC_CHECK_TOOL(GOC, gccgo,)
+])
+
+
+# LT_PROG_RC
+# ----------
+AC_DEFUN([LT_PROG_RC],
+[AC_CHECK_TOOL(RC, windres,)
+])
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_RC], [])
+
+
+# _LT_DECL_EGREP
+# --------------
+# If we don't have a new enough Autoconf to choose the best grep
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_EGREP],
+[AC_REQUIRE([AC_PROG_EGREP])dnl
+AC_REQUIRE([AC_PROG_FGREP])dnl
+test -z "$GREP" && GREP=grep
+_LT_DECL([], [GREP], [1], [A grep program that handles long lines])
+_LT_DECL([], [EGREP], [1], [An ERE matcher])
+_LT_DECL([], [FGREP], [1], [A literal string matcher])
+dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too
+AC_SUBST([GREP])
+])
+
+
+# _LT_DECL_OBJDUMP
+# --------------
+# If we don't have a new enough Autoconf to choose the best objdump
+# available, choose the one first in the user's PATH.
+m4_defun([_LT_DECL_OBJDUMP],
+[AC_CHECK_TOOL(OBJDUMP, objdump, false)
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper])
+AC_SUBST([OBJDUMP])
+])
+
+# _LT_DECL_DLLTOOL
+# ----------------
+# Ensure DLLTOOL variable is set.
+m4_defun([_LT_DECL_DLLTOOL],
+[AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])
+AC_SUBST([DLLTOOL])
+])
+
+# _LT_DECL_SED
+# ------------
+# Check for a fully-functional sed program, that truncates
+# as few characters as possible.  Prefer GNU sed if found.
+m4_defun([_LT_DECL_SED],
+[AC_PROG_SED
+test -z "$SED" && SED=sed
+Xsed="$SED -e 1s/^X//"
+_LT_DECL([], [SED], [1], [A sed program that does not truncate output])
+_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"],
+    [Sed that helps us avoid accidentally triggering echo(1) options like -n])
+])# _LT_DECL_SED
+
+m4_ifndef([AC_PROG_SED], [
+############################################################
+# NOTE: This macro has been submitted for inclusion into   #
+#  GNU Autoconf as AC_PROG_SED.  When it is available in   #
+#  a released version of Autoconf we should remove this    #
+#  macro and use it instead.                               #
+############################################################
+
+m4_defun([AC_PROG_SED],
+[AC_MSG_CHECKING([for a sed that does not truncate output])
+AC_CACHE_VAL(lt_cv_path_SED,
+[# Loop through the user's path and test for sed and gsed.
+# Then use that list of sed's as ones to test for truncation.
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  for lt_ac_prog in sed gsed; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then
+        lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext"
+      fi
+    done
+  done
+done
+IFS=$as_save_IFS
+lt_ac_max=0
+lt_ac_count=0
+# Add /usr/xpg4/bin/sed as it is typically found on Solaris
+# along with /bin/sed that truncates output.
+for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do
+  test ! -f $lt_ac_sed && continue
+  cat /dev/null > conftest.in
+  lt_ac_count=0
+  echo $ECHO_N "0123456789$ECHO_C" >conftest.in
+  # Check for GNU sed and select it if it is found.
+  if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then
+    lt_cv_path_SED=$lt_ac_sed
+    break
+  fi
+  while true; do
+    cat conftest.in conftest.in >conftest.tmp
+    mv conftest.tmp conftest.in
+    cp conftest.in conftest.nl
+    echo >>conftest.nl
+    $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break
+    cmp -s conftest.out conftest.nl || break
+    # 10000 chars as input seems more than enough
+    test $lt_ac_count -gt 10 && break
+    lt_ac_count=`expr $lt_ac_count + 1`
+    if test $lt_ac_count -gt $lt_ac_max; then
+      lt_ac_max=$lt_ac_count
+      lt_cv_path_SED=$lt_ac_sed
+    fi
+  done
+done
+])
+SED=$lt_cv_path_SED
+AC_SUBST([SED])
+AC_MSG_RESULT([$SED])
+])#AC_PROG_SED
+])#m4_ifndef
+
+# Old name:
+AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED])
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([LT_AC_PROG_SED], [])
+
+
+# _LT_CHECK_SHELL_FEATURES
+# ------------------------
+# Find out whether the shell is Bourne or XSI compatible,
+# or has some other useful features.
+m4_defun([_LT_CHECK_SHELL_FEATURES],
+[AC_MSG_CHECKING([whether the shell understands some XSI constructs])
+# Try some XSI features
+xsi_shell=no
+( _lt_dummy="a/b/c"
+  test "${_lt_dummy##*/},${_lt_dummy%/*},${_lt_dummy#??}"${_lt_dummy%"$_lt_dummy"}, \
+      = c,a/b,b/c, \
+    && eval 'test $(( 1 + 1 )) -eq 2 \
+    && test "${#_lt_dummy}" -eq 5' ) >/dev/null 2>&1 \
+  && xsi_shell=yes
+AC_MSG_RESULT([$xsi_shell])
+_LT_CONFIG_LIBTOOL_INIT([xsi_shell='$xsi_shell'])
+
+AC_MSG_CHECKING([whether the shell understands "+="])
+lt_shell_append=no
+( foo=bar; set foo baz; eval "$[1]+=\$[2]" && test "$foo" = barbaz ) \
+    >/dev/null 2>&1 \
+  && lt_shell_append=yes
+AC_MSG_RESULT([$lt_shell_append])
+_LT_CONFIG_LIBTOOL_INIT([lt_shell_append='$lt_shell_append'])
+
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+  lt_unset=unset
+else
+  lt_unset=false
+fi
+_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl
+
+# test EBCDIC or ASCII
+case `echo X|tr X '\101'` in
+ A) # ASCII based system
+    # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr
+  lt_SP2NL='tr \040 \012'
+  lt_NL2SP='tr \015\012 \040\040'
+  ;;
+ *) # EBCDIC based system
+  lt_SP2NL='tr \100 \n'
+  lt_NL2SP='tr \r\n \100\100'
+  ;;
+esac
+_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl
+_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl
+])# _LT_CHECK_SHELL_FEATURES
+
+
+# _LT_PROG_FUNCTION_REPLACE (FUNCNAME, REPLACEMENT-BODY)
+# ------------------------------------------------------
+# In `$cfgfile', look for function FUNCNAME delimited by `^FUNCNAME ()$' and
+# '^} FUNCNAME ', and replace its body with REPLACEMENT-BODY.
+m4_defun([_LT_PROG_FUNCTION_REPLACE],
+[dnl {
+sed -e '/^$1 ()$/,/^} # $1 /c\
+$1 ()\
+{\
+m4_bpatsubsts([$2], [$], [\\], [^\([	 ]\)], [\\\1])
+} # Extended-shell $1 implementation' "$cfgfile" > $cfgfile.tmp \
+  && mv -f "$cfgfile.tmp" "$cfgfile" \
+    || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+test 0 -eq $? || _lt_function_replace_fail=:
+])
+
+
+# _LT_PROG_REPLACE_SHELLFNS
+# -------------------------
+# Replace existing portable implementations of several shell functions with
+# equivalent extended shell implementations where those features are available..
+m4_defun([_LT_PROG_REPLACE_SHELLFNS],
+[if test x"$xsi_shell" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_dirname], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_basename], [dnl
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_dirname_and_basename], [dnl
+    case ${1} in
+      */*) func_dirname_result="${1%/*}${2}" ;;
+      *  ) func_dirname_result="${3}" ;;
+    esac
+    func_basename_result="${1##*/}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_stripname], [dnl
+    # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are
+    # positional parameters, so assign one to ordinary parameter first.
+    func_stripname_result=${3}
+    func_stripname_result=${func_stripname_result#"${1}"}
+    func_stripname_result=${func_stripname_result%"${2}"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_long_opt], [dnl
+    func_split_long_opt_name=${1%%=*}
+    func_split_long_opt_arg=${1#*=}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_split_short_opt], [dnl
+    func_split_short_opt_arg=${1#??}
+    func_split_short_opt_name=${1%"$func_split_short_opt_arg"}])
+
+  _LT_PROG_FUNCTION_REPLACE([func_lo2o], [dnl
+    case ${1} in
+      *.lo) func_lo2o_result=${1%.lo}.${objext} ;;
+      *)    func_lo2o_result=${1} ;;
+    esac])
+
+  _LT_PROG_FUNCTION_REPLACE([func_xform], [    func_xform_result=${1%.*}.lo])
+
+  _LT_PROG_FUNCTION_REPLACE([func_arith], [    func_arith_result=$(( $[*] ))])
+
+  _LT_PROG_FUNCTION_REPLACE([func_len], [    func_len_result=${#1}])
+fi
+
+if test x"$lt_shell_append" = xyes; then
+  _LT_PROG_FUNCTION_REPLACE([func_append], [    eval "${1}+=\\${2}"])
+
+  _LT_PROG_FUNCTION_REPLACE([func_append_quoted], [dnl
+    func_quote_for_eval "${2}"
+dnl m4 expansion turns \\\\ into \\, and then the shell eval turns that into \
+    eval "${1}+=\\\\ \\$func_quote_for_eval_result"])
+
+  # Save a `func_append' function call where possible by direct use of '+='
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1+="%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+else
+  # Save a `func_append' function call even when '+=' is not available
+  sed -e 's%func_append \([[a-zA-Z_]]\{1,\}\) "%\1="$\1%g' $cfgfile > $cfgfile.tmp \
+    && mv -f "$cfgfile.tmp" "$cfgfile" \
+      || (rm -f "$cfgfile" && cp "$cfgfile.tmp" "$cfgfile" && rm -f "$cfgfile.tmp")
+  test 0 -eq $? || _lt_function_replace_fail=:
+fi
+
+if test x"$_lt_function_replace_fail" = x":"; then
+  AC_MSG_WARN([Unable to substitute extended shell functions in $ofile])
+fi
+])
+
+# _LT_PATH_CONVERSION_FUNCTIONS
+# -----------------------------
+# Determine which file name conversion functions should be used by
+# func_to_host_file (and, implicitly, by func_to_host_path).  These are needed
+# for certain cross-compile configurations and native mingw.
+m4_defun([_LT_PATH_CONVERSION_FUNCTIONS],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_MSG_CHECKING([how to convert $build file names to $host format])
+AC_CACHE_VAL(lt_cv_to_host_file_cmd,
+[case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32
+        ;;
+    esac
+    ;;
+  *-*-cygwin* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin
+        ;;
+      *-*-cygwin* )
+        lt_cv_to_host_file_cmd=func_convert_file_noop
+        ;;
+      * ) # otherwise, assume *nix
+        lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin
+        ;;
+    esac
+    ;;
+  * ) # unhandled hosts (and "normal" native builds)
+    lt_cv_to_host_file_cmd=func_convert_file_noop
+    ;;
+esac
+])
+to_host_file_cmd=$lt_cv_to_host_file_cmd
+AC_MSG_RESULT([$lt_cv_to_host_file_cmd])
+_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd],
+         [0], [convert $build file names to $host format])dnl
+
+AC_MSG_CHECKING([how to convert $build file names to toolchain format])
+AC_CACHE_VAL(lt_cv_to_tool_file_cmd,
+[#assume ordinary cross tools, or native build.
+lt_cv_to_tool_file_cmd=func_convert_file_noop
+case $host in
+  *-*-mingw* )
+    case $build in
+      *-*-mingw* ) # actually msys
+        lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32
+        ;;
+    esac
+    ;;
+esac
+])
+to_tool_file_cmd=$lt_cv_to_tool_file_cmd
+AC_MSG_RESULT([$lt_cv_to_tool_file_cmd])
+_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd],
+         [0], [convert $build files to toolchain format])dnl
+])# _LT_PATH_CONVERSION_FUNCTIONS
diff --git a/macros/ltoptions.m4 b/macros/ltoptions.m4
new file mode 100644
index 0000000..5d9acd8
--- /dev/null
+++ b/macros/ltoptions.m4
@@ -0,0 +1,384 @@
+# Helper functions for option handling.                    -*- Autoconf -*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2008, 2009 Free Software Foundation,
+#   Inc.
+#   Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 7 ltoptions.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])])
+
+
+# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME)
+# ------------------------------------------
+m4_define([_LT_MANGLE_OPTION],
+[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])])
+
+
+# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME)
+# ---------------------------------------
+# Set option OPTION-NAME for macro MACRO-NAME, and if there is a
+# matching handler defined, dispatch to it.  Other OPTION-NAMEs are
+# saved as a flag.
+m4_define([_LT_SET_OPTION],
+[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl
+m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]),
+        _LT_MANGLE_DEFUN([$1], [$2]),
+    [m4_warning([Unknown $1 option `$2'])])[]dnl
+])
+
+
+# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET])
+# ------------------------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+m4_define([_LT_IF_OPTION],
+[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])])
+
+
+# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET)
+# -------------------------------------------------------
+# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME
+# are set.
+m4_define([_LT_UNLESS_OPTIONS],
+[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+	    [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option),
+		      [m4_define([$0_found])])])[]dnl
+m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3
+])[]dnl
+])
+
+
+# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST)
+# ----------------------------------------
+# OPTION-LIST is a space-separated list of Libtool options associated
+# with MACRO-NAME.  If any OPTION has a matching handler declared with
+# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about
+# the unknown option and exit.
+m4_defun([_LT_SET_OPTIONS],
+[# Set options
+m4_foreach([_LT_Option], m4_split(m4_normalize([$2])),
+    [_LT_SET_OPTION([$1], _LT_Option)])
+
+m4_if([$1],[LT_INIT],[
+  dnl
+  dnl Simply set some default values (i.e off) if boolean options were not
+  dnl specified:
+  _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no
+  ])
+  _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no
+  ])
+  dnl
+  dnl If no reference was made to various pairs of opposing options, then
+  dnl we run the default mode handler for the pair.  For example, if neither
+  dnl `shared' nor `disable-shared' was passed, we enable building of shared
+  dnl archives by default:
+  _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED])
+  _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC])
+  _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install],
+  		   [_LT_ENABLE_FAST_INSTALL])
+  ])
+])# _LT_SET_OPTIONS
+
+
+## --------------------------------- ##
+## Macros to handle LT_INIT options. ##
+## --------------------------------- ##
+
+# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME)
+# -----------------------------------------
+m4_define([_LT_MANGLE_DEFUN],
+[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])])
+
+
+# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE)
+# -----------------------------------------------
+m4_define([LT_OPTION_DEFINE],
+[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl
+])# LT_OPTION_DEFINE
+
+
+# dlopen
+# ------
+LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes
+])
+
+AU_DEFUN([AC_LIBTOOL_DLOPEN],
+[_LT_SET_OPTION([LT_INIT], [dlopen])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `dlopen' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], [])
+
+
+# win32-dll
+# ---------
+# Declare package support for building win32 dll's.
+LT_OPTION_DEFINE([LT_INIT], [win32-dll],
+[enable_win32_dll=yes
+
+case $host in
+*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*)
+  AC_CHECK_TOOL(AS, as, false)
+  AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+  AC_CHECK_TOOL(OBJDUMP, objdump, false)
+  ;;
+esac
+
+test -z "$AS" && AS=as
+_LT_DECL([], [AS],      [1], [Assembler program])dnl
+
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl
+
+test -z "$OBJDUMP" && OBJDUMP=objdump
+_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl
+])# win32-dll
+
+AU_DEFUN([AC_LIBTOOL_WIN32_DLL],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+_LT_SET_OPTION([LT_INIT], [win32-dll])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `win32-dll' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [])
+
+
+# _LT_ENABLE_SHARED([DEFAULT])
+# ----------------------------
+# implement the --enable-shared flag, and supports the `shared' and
+# `disable-shared' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_SHARED],
+[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([shared],
+    [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@],
+	[build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_shared=yes ;;
+    no) enable_shared=no ;;
+    *)
+      enable_shared=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_shared=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_shared=]_LT_ENABLE_SHARED_DEFAULT)
+
+    _LT_DECL([build_libtool_libs], [enable_shared], [0],
+	[Whether or not to build shared libraries])
+])# _LT_ENABLE_SHARED
+
+LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared])
+])
+
+AC_DEFUN([AC_DISABLE_SHARED],
+[_LT_SET_OPTION([LT_INIT], [disable-shared])
+])
+
+AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
+AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_SHARED], [])
+dnl AC_DEFUN([AM_DISABLE_SHARED], [])
+
+
+
+# _LT_ENABLE_STATIC([DEFAULT])
+# ----------------------------
+# implement the --enable-static flag, and support the `static' and
+# `disable-static' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_STATIC],
+[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([static],
+    [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@],
+	[build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_static=yes ;;
+    no) enable_static=no ;;
+    *)
+     enable_static=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_static=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_static=]_LT_ENABLE_STATIC_DEFAULT)
+
+    _LT_DECL([build_old_libs], [enable_static], [0],
+	[Whether or not to build static libraries])
+])# _LT_ENABLE_STATIC
+
+LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])])
+
+# Old names:
+AC_DEFUN([AC_ENABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static])
+])
+
+AC_DEFUN([AC_DISABLE_STATIC],
+[_LT_SET_OPTION([LT_INIT], [disable-static])
+])
+
+AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
+AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AM_ENABLE_STATIC], [])
+dnl AC_DEFUN([AM_DISABLE_STATIC], [])
+
+
+
+# _LT_ENABLE_FAST_INSTALL([DEFAULT])
+# ----------------------------------
+# implement the --enable-fast-install flag, and support the `fast-install'
+# and `disable-fast-install' LT_INIT options.
+# DEFAULT is either `yes' or `no'.  If omitted, it defaults to `yes'.
+m4_define([_LT_ENABLE_FAST_INSTALL],
+[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl
+AC_ARG_ENABLE([fast-install],
+    [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@],
+    [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])],
+    [p=${PACKAGE-default}
+    case $enableval in
+    yes) enable_fast_install=yes ;;
+    no) enable_fast_install=no ;;
+    *)
+      enable_fast_install=no
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for pkg in $enableval; do
+	IFS="$lt_save_ifs"
+	if test "X$pkg" = "X$p"; then
+	  enable_fast_install=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT)
+
+_LT_DECL([fast_install], [enable_fast_install], [0],
+	 [Whether or not to optimize for fast installation])dnl
+])# _LT_ENABLE_FAST_INSTALL
+
+LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])])
+LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])])
+
+# Old names:
+AU_DEFUN([AC_ENABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `fast-install' option into LT_INIT's first parameter.])
+])
+
+AU_DEFUN([AC_DISABLE_FAST_INSTALL],
+[_LT_SET_OPTION([LT_INIT], [disable-fast-install])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you put
+the `disable-fast-install' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], [])
+dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], [])
+
+
+# _LT_WITH_PIC([MODE])
+# --------------------
+# implement the --with-pic flag, and support the `pic-only' and `no-pic'
+# LT_INIT options.
+# MODE is either `yes' or `no'.  If omitted, it defaults to `both'.
+m4_define([_LT_WITH_PIC],
+[AC_ARG_WITH([pic],
+    [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@],
+	[try to use only PIC/non-PIC objects @<:@default=use both@:>@])],
+    [lt_p=${PACKAGE-default}
+    case $withval in
+    yes|no) pic_mode=$withval ;;
+    *)
+      pic_mode=default
+      # Look at the argument we got.  We use all the common list separators.
+      lt_save_ifs="$IFS"; IFS="${IFS}$PATH_SEPARATOR,"
+      for lt_pkg in $withval; do
+	IFS="$lt_save_ifs"
+	if test "X$lt_pkg" = "X$lt_p"; then
+	  pic_mode=yes
+	fi
+      done
+      IFS="$lt_save_ifs"
+      ;;
+    esac],
+    [pic_mode=default])
+
+test -z "$pic_mode" && pic_mode=m4_default([$1], [default])
+
+_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl
+])# _LT_WITH_PIC
+
+LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])])
+LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])])
+
+# Old name:
+AU_DEFUN([AC_LIBTOOL_PICMODE],
+[_LT_SET_OPTION([LT_INIT], [pic-only])
+AC_DIAGNOSE([obsolete],
+[$0: Remove this warning and the call to _LT_SET_OPTION when you
+put the `pic-only' option into LT_INIT's first parameter.])
+])
+
+dnl aclocal-1.4 backwards compatibility:
+dnl AC_DEFUN([AC_LIBTOOL_PICMODE], [])
+
+## ----------------- ##
+## LTDL_INIT Options ##
+## ----------------- ##
+
+m4_define([_LTDL_MODE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive],
+		 [m4_define([_LTDL_MODE], [nonrecursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [recursive],
+		 [m4_define([_LTDL_MODE], [recursive])])
+LT_OPTION_DEFINE([LTDL_INIT], [subproject],
+		 [m4_define([_LTDL_MODE], [subproject])])
+
+m4_define([_LTDL_TYPE], [])
+LT_OPTION_DEFINE([LTDL_INIT], [installable],
+		 [m4_define([_LTDL_TYPE], [installable])])
+LT_OPTION_DEFINE([LTDL_INIT], [convenience],
+		 [m4_define([_LTDL_TYPE], [convenience])])
diff --git a/macros/ltsugar.m4 b/macros/ltsugar.m4
new file mode 100644
index 0000000..9000a05
--- /dev/null
+++ b/macros/ltsugar.m4
@@ -0,0 +1,123 @@
+# ltsugar.m4 -- libtool m4 base layer.                         -*-Autoconf-*-
+#
+# Copyright (C) 2004, 2005, 2007, 2008 Free Software Foundation, Inc.
+# Written by Gary V. Vaughan, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 6 ltsugar.m4
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])])
+
+
+# lt_join(SEP, ARG1, [ARG2...])
+# -----------------------------
+# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their
+# associated separator.
+# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier
+# versions in m4sugar had bugs.
+m4_define([lt_join],
+[m4_if([$#], [1], [],
+       [$#], [2], [[$2]],
+       [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])])
+m4_define([_lt_join],
+[m4_if([$#$2], [2], [],
+       [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])])
+
+
+# lt_car(LIST)
+# lt_cdr(LIST)
+# ------------
+# Manipulate m4 lists.
+# These macros are necessary as long as will still need to support
+# Autoconf-2.59 which quotes differently.
+m4_define([lt_car], [[$1]])
+m4_define([lt_cdr],
+[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])],
+       [$#], 1, [],
+       [m4_dquote(m4_shift($@))])])
+m4_define([lt_unquote], $1)
+
+
+# lt_append(MACRO-NAME, STRING, [SEPARATOR])
+# ------------------------------------------
+# Redefine MACRO-NAME to hold its former content plus `SEPARATOR'`STRING'.
+# Note that neither SEPARATOR nor STRING are expanded; they are appended
+# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked).
+# No SEPARATOR is output if MACRO-NAME was previously undefined (different
+# than defined and empty).
+#
+# This macro is needed until we can rely on Autoconf 2.62, since earlier
+# versions of m4sugar mistakenly expanded SEPARATOR but not STRING.
+m4_define([lt_append],
+[m4_define([$1],
+	   m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])])
+
+
+
+# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...])
+# ----------------------------------------------------------
+# Produce a SEP delimited list of all paired combinations of elements of
+# PREFIX-LIST with SUFFIX1 through SUFFIXn.  Each element of the list
+# has the form PREFIXmINFIXSUFFIXn.
+# Needed until we can rely on m4_combine added in Autoconf 2.62.
+m4_define([lt_combine],
+[m4_if(m4_eval([$# > 3]), [1],
+       [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl
+[[m4_foreach([_Lt_prefix], [$2],
+	     [m4_foreach([_Lt_suffix],
+		]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[,
+	[_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])])
+
+
+# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ])
+# -----------------------------------------------------------------------
+# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited
+# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ.
+m4_define([lt_if_append_uniq],
+[m4_ifdef([$1],
+	  [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1],
+		 [lt_append([$1], [$2], [$3])$4],
+		 [$5])],
+	  [lt_append([$1], [$2], [$3])$4])])
+
+
+# lt_dict_add(DICT, KEY, VALUE)
+# -----------------------------
+m4_define([lt_dict_add],
+[m4_define([$1($2)], [$3])])
+
+
+# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE)
+# --------------------------------------------
+m4_define([lt_dict_add_subkey],
+[m4_define([$1($2:$3)], [$4])])
+
+
+# lt_dict_fetch(DICT, KEY, [SUBKEY])
+# ----------------------------------
+m4_define([lt_dict_fetch],
+[m4_ifval([$3],
+	m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]),
+    m4_ifdef([$1($2)], [m4_defn([$1($2)])]))])
+
+
+# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE])
+# -----------------------------------------------------------------
+m4_define([lt_if_dict_fetch],
+[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4],
+	[$5],
+    [$6])])
+
+
+# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...])
+# --------------------------------------------------------------
+m4_define([lt_dict_filter],
+[m4_if([$5], [], [],
+  [lt_join(m4_quote(m4_default([$4], [[, ]])),
+           lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]),
+		      [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl
+])
diff --git a/macros/ltversion.m4 b/macros/ltversion.m4
new file mode 100644
index 0000000..07a8602
--- /dev/null
+++ b/macros/ltversion.m4
@@ -0,0 +1,23 @@
+# ltversion.m4 -- version numbers			-*- Autoconf -*-
+#
+#   Copyright (C) 2004 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# @configure_input@
+
+# serial 3337 ltversion.m4
+# This file is part of GNU Libtool
+
+m4_define([LT_PACKAGE_VERSION], [2.4.2])
+m4_define([LT_PACKAGE_REVISION], [1.3337])
+
+AC_DEFUN([LTVERSION_VERSION],
+[macro_version='2.4.2'
+macro_revision='1.3337'
+_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
+_LT_DECL(, macro_revision, 0)
+])
diff --git a/macros/lt~obsolete.m4 b/macros/lt~obsolete.m4
new file mode 100644
index 0000000..c573da9
--- /dev/null
+++ b/macros/lt~obsolete.m4
@@ -0,0 +1,98 @@
+# lt~obsolete.m4 -- aclocal satisfying obsolete definitions.    -*-Autoconf-*-
+#
+#   Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
+#   Written by Scott James Remnant, 2004.
+#
+# This file is free software; the Free Software Foundation gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+
+# serial 5 lt~obsolete.m4
+
+# These exist entirely to fool aclocal when bootstrapping libtool.
+#
+# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN)
+# which have later been changed to m4_define as they aren't part of the
+# exported API, or moved to Autoconf or Automake where they belong.
+#
+# The trouble is, aclocal is a bit thick.  It'll see the old AC_DEFUN
+# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
+# using a macro with the same name in our local m4/libtool.m4 it'll
+# pull the old libtool.m4 in (it doesn't see our shiny new m4_define
+# and doesn't know about Autoconf macros at all.)
+#
+# So we provide this file, which has a silly filename so it's always
+# included after everything else.  This provides aclocal with the
+# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
+# because those macros already exist, or will be overwritten later.
+# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. 
+#
+# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
+# Yes, that means every name once taken will need to remain here until
+# we give up compatibility with versions before 1.7, at which point
+# we need to keep only those names which we still refer to.
+
+# This is to help aclocal find these macros, as it can't see m4_define.
+AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
+
+m4_ifndef([AC_LIBTOOL_LINKER_OPTION],	[AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
+m4_ifndef([AC_PROG_EGREP],		[AC_DEFUN([AC_PROG_EGREP])])
+m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH],	[AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_AC_SHELL_INIT],		[AC_DEFUN([_LT_AC_SHELL_INIT])])
+m4_ifndef([_LT_AC_SYS_LIBPATH_AIX],	[AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
+m4_ifndef([_LT_PROG_LTMAIN],		[AC_DEFUN([_LT_PROG_LTMAIN])])
+m4_ifndef([_LT_AC_TAGVAR],		[AC_DEFUN([_LT_AC_TAGVAR])])
+m4_ifndef([AC_LTDL_ENABLE_INSTALL],	[AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
+m4_ifndef([AC_LTDL_PREOPEN],		[AC_DEFUN([AC_LTDL_PREOPEN])])
+m4_ifndef([_LT_AC_SYS_COMPILER],	[AC_DEFUN([_LT_AC_SYS_COMPILER])])
+m4_ifndef([_LT_AC_LOCK],		[AC_DEFUN([_LT_AC_LOCK])])
+m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE],	[AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
+m4_ifndef([_LT_AC_TRY_DLOPEN_SELF],	[AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
+m4_ifndef([AC_LIBTOOL_PROG_CC_C_O],	[AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
+m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
+m4_ifndef([AC_LIBTOOL_OBJDIR],		[AC_DEFUN([AC_LIBTOOL_OBJDIR])])
+m4_ifndef([AC_LTDL_OBJDIR],		[AC_DEFUN([AC_LTDL_OBJDIR])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
+m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP],	[AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
+m4_ifndef([AC_PATH_MAGIC],		[AC_DEFUN([AC_PATH_MAGIC])])
+m4_ifndef([AC_PROG_LD_GNU],		[AC_DEFUN([AC_PROG_LD_GNU])])
+m4_ifndef([AC_PROG_LD_RELOAD_FLAG],	[AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
+m4_ifndef([AC_DEPLIBS_CHECK_METHOD],	[AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
+m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
+m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
+m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS],	[AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
+m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP],	[AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
+m4_ifndef([LT_AC_PROG_EGREP],		[AC_DEFUN([LT_AC_PROG_EGREP])])
+m4_ifndef([LT_AC_PROG_SED],		[AC_DEFUN([LT_AC_PROG_SED])])
+m4_ifndef([_LT_CC_BASENAME],		[AC_DEFUN([_LT_CC_BASENAME])])
+m4_ifndef([_LT_COMPILER_BOILERPLATE],	[AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
+m4_ifndef([_LT_LINKER_BOILERPLATE],	[AC_DEFUN([_LT_LINKER_BOILERPLATE])])
+m4_ifndef([_AC_PROG_LIBTOOL],		[AC_DEFUN([_AC_PROG_LIBTOOL])])
+m4_ifndef([AC_LIBTOOL_SETUP],		[AC_DEFUN([AC_LIBTOOL_SETUP])])
+m4_ifndef([_LT_AC_CHECK_DLFCN],		[AC_DEFUN([_LT_AC_CHECK_DLFCN])])
+m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER],	[AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
+m4_ifndef([_LT_AC_TAGCONFIG],		[AC_DEFUN([_LT_AC_TAGCONFIG])])
+m4_ifndef([AC_DISABLE_FAST_INSTALL],	[AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
+m4_ifndef([_LT_AC_LANG_CXX],		[AC_DEFUN([_LT_AC_LANG_CXX])])
+m4_ifndef([_LT_AC_LANG_F77],		[AC_DEFUN([_LT_AC_LANG_F77])])
+m4_ifndef([_LT_AC_LANG_GCJ],		[AC_DEFUN([_LT_AC_LANG_GCJ])])
+m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
+m4_ifndef([_LT_AC_LANG_C_CONFIG],	[AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
+m4_ifndef([_LT_AC_LANG_CXX_CONFIG],	[AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
+m4_ifndef([_LT_AC_LANG_F77_CONFIG],	[AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
+m4_ifndef([_LT_AC_LANG_GCJ_CONFIG],	[AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
+m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG],	[AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
+m4_ifndef([_LT_AC_LANG_RC_CONFIG],	[AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
+m4_ifndef([AC_LIBTOOL_CONFIG],		[AC_DEFUN([AC_LIBTOOL_CONFIG])])
+m4_ifndef([_LT_AC_FILE_LTDLL_C],	[AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
+m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS],	[AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
+m4_ifndef([_LT_AC_PROG_CXXCPP],		[AC_DEFUN([_LT_AC_PROG_CXXCPP])])
+m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS],	[AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
+m4_ifndef([_LT_PROG_ECHO_BACKSLASH],	[AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
+m4_ifndef([_LT_PROG_F77],		[AC_DEFUN([_LT_PROG_F77])])
+m4_ifndef([_LT_PROG_FC],		[AC_DEFUN([_LT_PROG_FC])])
+m4_ifndef([_LT_PROG_CXX],		[AC_DEFUN([_LT_PROG_CXX])])
diff --git a/macros/po.m4 b/macros/po.m4
new file mode 100644
index 0000000..0734762
--- /dev/null
+++ b/macros/po.m4
@@ -0,0 +1,449 @@
+# po.m4 serial 15 (gettext-0.17)
+dnl Copyright (C) 1995-2007 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper at cygnus.com>, 1995-2000.
+dnl   Bruno Haible <haible at clisp.cons.org>, 2000-2003.
+
+AC_PREREQ(2.50)
+
+dnl Checks for all prerequisites of the po subdirectory.
+AC_DEFUN([AM_PO_SUBDIRS],
+[
+  AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+  AC_REQUIRE([AC_PROG_INSTALL])dnl
+  AC_REQUIRE([AM_PROG_MKDIR_P])dnl defined by automake
+  AC_REQUIRE([AM_NLS])dnl
+
+  dnl Release version of the gettext macros. This is used to ensure that
+  dnl the gettext macros and po/Makefile.in.in are in sync.
+  AC_SUBST([GETTEXT_MACRO_VERSION], [0.17])
+
+  dnl Perform the following tests also if --disable-nls has been given,
+  dnl because they are needed for "make dist" to work.
+
+  dnl Search for GNU msgfmt in the PATH.
+  dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions.
+  dnl The second test excludes FreeBSD msgfmt.
+  AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
+    [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+     (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
+    :)
+  AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT)
+
+  dnl Test whether it is GNU msgfmt >= 0.15.
+changequote(,)dnl
+  case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;;
+    *) MSGFMT_015=$MSGFMT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([MSGFMT_015])
+changequote(,)dnl
+  case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;;
+    *) GMSGFMT_015=$GMSGFMT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([GMSGFMT_015])
+
+  dnl Search for GNU xgettext 0.12 or newer in the PATH.
+  dnl The first test excludes Solaris xgettext and early GNU xgettext versions.
+  dnl The second test excludes FreeBSD xgettext.
+  AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+    [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+     (if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)],
+    :)
+  dnl Remove leftover from FreeBSD xgettext call.
+  rm -f messages.po
+
+  dnl Test whether it is GNU xgettext >= 0.15.
+changequote(,)dnl
+  case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;;
+    *) XGETTEXT_015=$XGETTEXT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([XGETTEXT_015])
+
+  dnl Search for GNU msgmerge 0.11 or newer in the PATH.
+  AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge,
+    [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :)
+
+  dnl Installation directories.
+  dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we
+  dnl have to define it here, so that it can be used in po/Makefile.
+  test -n "$localedir" || localedir='${datadir}/locale'
+  AC_SUBST([localedir])
+
+  dnl Support for AM_XGETTEXT_OPTION.
+  test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS=
+  AC_SUBST([XGETTEXT_EXTRA_OPTIONS])
+
+  AC_CONFIG_COMMANDS([po-directories], [[
+    for ac_file in $CONFIG_FILES; do
+      # Support "outfile[:infile[:infile...]]"
+      case "$ac_file" in
+        *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+      esac
+      # PO directories have a Makefile.in generated from Makefile.in.in.
+      case "$ac_file" in */Makefile.in)
+        # Adjust a relative srcdir.
+        ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+        ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`"
+        ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+        # In autoconf-2.13 it is called $ac_given_srcdir.
+        # In autoconf-2.50 it is called $srcdir.
+        test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+        case "$ac_given_srcdir" in
+          .)  top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+          /*) top_srcdir="$ac_given_srcdir" ;;
+          *)  top_srcdir="$ac_dots$ac_given_srcdir" ;;
+        esac
+        # Treat a directory as a PO directory if and only if it has a
+        # POTFILES.in file. This allows packages to have multiple PO
+        # directories under different names or in different locations.
+        if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then
+          rm -f "$ac_dir/POTFILES"
+          test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES"
+          cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ 	]*\$/d" -e "s,.*,     $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES"
+          POMAKEFILEDEPS="POTFILES.in"
+          # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend
+          # on $ac_dir but don't depend on user-specified configuration
+          # parameters.
+          if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+            # The LINGUAS file contains the set of available languages.
+            if test -n "$OBSOLETE_ALL_LINGUAS"; then
+              test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
+            fi
+            ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
+            # Hide the ALL_LINGUAS assigment from automake < 1.5.
+            eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+            POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+          else
+            # The set of available languages was given in configure.in.
+            # Hide the ALL_LINGUAS assigment from automake < 1.5.
+            eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS'
+          fi
+          # Compute POFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+          # Compute UPDATEPOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+          # Compute DUMMYPOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+          # Compute GMOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+          case "$ac_given_srcdir" in
+            .) srcdirpre= ;;
+            *) srcdirpre='$(srcdir)/' ;;
+          esac
+          POFILES=
+          UPDATEPOFILES=
+          DUMMYPOFILES=
+          GMOFILES=
+          for lang in $ALL_LINGUAS; do
+            POFILES="$POFILES $srcdirpre$lang.po"
+            UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+            DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+            GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+          done
+          # CATALOGS depends on both $ac_dir and the user's LINGUAS
+          # environment variable.
+          INST_LINGUAS=
+          if test -n "$ALL_LINGUAS"; then
+            for presentlang in $ALL_LINGUAS; do
+              useit=no
+              if test "%UNSET%" != "$LINGUAS"; then
+                desiredlanguages="$LINGUAS"
+              else
+                desiredlanguages="$ALL_LINGUAS"
+              fi
+              for desiredlang in $desiredlanguages; do
+                # Use the presentlang catalog if desiredlang is
+                #   a. equal to presentlang, or
+                #   b. a variant of presentlang (because in this case,
+                #      presentlang can be used as a fallback for messages
+                #      which are not translated in the desiredlang catalog).
+                case "$desiredlang" in
+                  "$presentlang"*) useit=yes;;
+                esac
+              done
+              if test $useit = yes; then
+                INST_LINGUAS="$INST_LINGUAS $presentlang"
+              fi
+            done
+          fi
+          CATALOGS=
+          if test -n "$INST_LINGUAS"; then
+            for lang in $INST_LINGUAS; do
+              CATALOGS="$CATALOGS $lang.gmo"
+            done
+          fi
+          test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile"
+          sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile"
+          for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do
+            if test -f "$f"; then
+              case "$f" in
+                *.orig | *.bak | *~) ;;
+                *) cat "$f" >> "$ac_dir/Makefile" ;;
+              esac
+            fi
+          done
+        fi
+        ;;
+      esac
+    done]],
+   [# Capture the value of obsolete ALL_LINGUAS because we need it to compute
+    # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it
+    # from automake < 1.5.
+    eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"'
+    # Capture the value of LINGUAS because we need it to compute CATALOGS.
+    LINGUAS="${LINGUAS-%UNSET%}"
+   ])
+])
+
+dnl Postprocesses a Makefile in a directory containing PO files.
+AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE],
+[
+  # When this code is run, in config.status, two variables have already been
+  # set:
+  # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in,
+  # - LINGUAS is the value of the environment variable LINGUAS at configure
+  #   time.
+
+changequote(,)dnl
+  # Adjust a relative srcdir.
+  ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+  ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`"
+  ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+  # In autoconf-2.13 it is called $ac_given_srcdir.
+  # In autoconf-2.50 it is called $srcdir.
+  test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+  case "$ac_given_srcdir" in
+    .)  top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+    /*) top_srcdir="$ac_given_srcdir" ;;
+    *)  top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+  # Find a way to echo strings without interpreting backslash.
+  if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then
+    gt_echo='echo'
+  else
+    if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then
+      gt_echo='printf %s\n'
+    else
+      echo_func () {
+        cat <<EOT
+$*
+EOT
+      }
+      gt_echo='echo_func'
+    fi
+  fi
+
+  # A sed script that extracts the value of VARIABLE from a Makefile.
+  sed_x_variable='
+# Test if the hold space is empty.
+x
+s/P/P/
+x
+ta
+# Yes it was empty. Look if we have the expected variable definition.
+/^[	 ]*VARIABLE[	 ]*=/{
+  # Seen the first line of the variable definition.
+  s/^[	 ]*VARIABLE[	 ]*=//
+  ba
+}
+bd
+:a
+# Here we are processing a line from the variable definition.
+# Remove comment, more precisely replace it with a space.
+s/#.*$/ /
+# See if the line ends in a backslash.
+tb
+:b
+s/\\$//
+# Print the line, without the trailing backslash.
+p
+tc
+# There was no trailing backslash. The end of the variable definition is
+# reached. Clear the hold space.
+s/^.*$//
+x
+bd
+:c
+# A trailing backslash means that the variable definition continues in the
+# next line. Put a nonempty string into the hold space to indicate this.
+s/^.*$/P/
+x
+:d
+'
+changequote([,])dnl
+
+  # Set POTFILES to the value of the Makefile variable POTFILES.
+  sed_x_POTFILES=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/POTFILES/g'`
+  POTFILES=`sed -n -e "$sed_x_POTFILES" < "$ac_file"`
+  # Compute POTFILES_DEPS as
+  #   $(foreach file, $(POTFILES), $(top_srcdir)/$(file))
+  POTFILES_DEPS=
+  for file in $POTFILES; do
+    POTFILES_DEPS="$POTFILES_DEPS "'$(top_srcdir)/'"$file"
+  done
+  POMAKEFILEDEPS=""
+
+  if test -n "$OBSOLETE_ALL_LINGUAS"; then
+    test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
+  fi
+  if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+    # The LINGUAS file contains the set of available languages.
+    ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"`
+    POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+  else
+    # Set ALL_LINGUAS to the value of the Makefile variable LINGUAS.
+    sed_x_LINGUAS=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/LINGUAS/g'`
+    ALL_LINGUAS_=`sed -n -e "$sed_x_LINGUAS" < "$ac_file"`
+  fi
+  # Hide the ALL_LINGUAS assigment from automake < 1.5.
+  eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+  # Compute POFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+  # Compute UPDATEPOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+  # Compute DUMMYPOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+  # Compute GMOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+  # Compute PROPERTIESFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).properties)
+  # Compute CLASSFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).class)
+  # Compute QMFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).qm)
+  # Compute MSGFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang)).msg)
+  # Compute RESOURCESDLLFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang))/$(DOMAIN).resources.dll)
+  case "$ac_given_srcdir" in
+    .) srcdirpre= ;;
+    *) srcdirpre='$(srcdir)/' ;;
+  esac
+  POFILES=
+  UPDATEPOFILES=
+  DUMMYPOFILES=
+  GMOFILES=
+  PROPERTIESFILES=
+  CLASSFILES=
+  QMFILES=
+  MSGFILES=
+  RESOURCESDLLFILES=
+  for lang in $ALL_LINGUAS; do
+    POFILES="$POFILES $srcdirpre$lang.po"
+    UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+    DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+    GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+    PROPERTIESFILES="$PROPERTIESFILES \$(top_srcdir)/\$(DOMAIN)_$lang.properties"
+    CLASSFILES="$CLASSFILES \$(top_srcdir)/\$(DOMAIN)_$lang.class"
+    QMFILES="$QMFILES $srcdirpre$lang.qm"
+    frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+    MSGFILES="$MSGFILES $srcdirpre$frobbedlang.msg"
+    frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+    RESOURCESDLLFILES="$RESOURCESDLLFILES $srcdirpre$frobbedlang/\$(DOMAIN).resources.dll"
+  done
+  # CATALOGS depends on both $ac_dir and the user's LINGUAS
+  # environment variable.
+  INST_LINGUAS=
+  if test -n "$ALL_LINGUAS"; then
+    for presentlang in $ALL_LINGUAS; do
+      useit=no
+      if test "%UNSET%" != "$LINGUAS"; then
+        desiredlanguages="$LINGUAS"
+      else
+        desiredlanguages="$ALL_LINGUAS"
+      fi
+      for desiredlang in $desiredlanguages; do
+        # Use the presentlang catalog if desiredlang is
+        #   a. equal to presentlang, or
+        #   b. a variant of presentlang (because in this case,
+        #      presentlang can be used as a fallback for messages
+        #      which are not translated in the desiredlang catalog).
+        case "$desiredlang" in
+          "$presentlang"*) useit=yes;;
+        esac
+      done
+      if test $useit = yes; then
+        INST_LINGUAS="$INST_LINGUAS $presentlang"
+      fi
+    done
+  fi
+  CATALOGS=
+  JAVACATALOGS=
+  QTCATALOGS=
+  TCLCATALOGS=
+  CSHARPCATALOGS=
+  if test -n "$INST_LINGUAS"; then
+    for lang in $INST_LINGUAS; do
+      CATALOGS="$CATALOGS $lang.gmo"
+      JAVACATALOGS="$JAVACATALOGS \$(DOMAIN)_$lang.properties"
+      QTCATALOGS="$QTCATALOGS $lang.qm"
+      frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+      TCLCATALOGS="$TCLCATALOGS $frobbedlang.msg"
+      frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+      CSHARPCATALOGS="$CSHARPCATALOGS $frobbedlang/\$(DOMAIN).resources.dll"
+    done
+  fi
+
+  sed -e "s|@POTFILES_DEPS@|$POTFILES_DEPS|g" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@PROPERTIESFILES@|$PROPERTIESFILES|g" -e "s|@CLASSFILES@|$CLASSFILES|g" -e "s|@QMFILES@|$QMFILES|g" -e "s|@MSGFILES@|$MSGFILES|g" -e "s|@RESOURCESDLLFILES@|$RESOURCESDLLFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@JAVACATALOGS@|$JAVACATALOGS|g" -e "s|@QTCATALOGS@|$QTCATALOGS|g" -e "s|@TCLCATALOGS@|$TCL [...]
+  if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then
+    # Add dependencies that cannot be formulated as a simple suffix rule.
+    for lang in $ALL_LINGUAS; do
+      frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+      cat >> "$ac_file.tmp" <<EOF
+$frobbedlang.msg: $lang.po
+	@echo "\$(MSGFMT) -c --tcl -d \$(srcdir) -l $lang $srcdirpre$lang.po"; \
+	\$(MSGFMT) -c --tcl -d "\$(srcdir)" -l $lang $srcdirpre$lang.po || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+    done
+  fi
+  if grep -l '@CSHARPCATALOGS@' "$ac_file" > /dev/null; then
+    # Add dependencies that cannot be formulated as a simple suffix rule.
+    for lang in $ALL_LINGUAS; do
+      frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'`
+      cat >> "$ac_file.tmp" <<EOF
+$frobbedlang/\$(DOMAIN).resources.dll: $lang.po
+	@echo "\$(MSGFMT) -c --csharp -d \$(srcdir) -l $lang $srcdirpre$lang.po -r \$(DOMAIN)"; \
+	\$(MSGFMT) -c --csharp -d "\$(srcdir)" -l $lang $srcdirpre$lang.po -r "\$(DOMAIN)" || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+    done
+  fi
+  if test -n "$POMAKEFILEDEPS"; then
+    cat >> "$ac_file.tmp" <<EOF
+Makefile: $POMAKEFILEDEPS
+EOF
+  fi
+  mv "$ac_file.tmp" "$ac_file"
+])
+
+dnl Initializes the accumulator used by AM_XGETTEXT_OPTION.
+AC_DEFUN([AM_XGETTEXT_OPTION_INIT],
+[
+  XGETTEXT_EXTRA_OPTIONS=
+])
+
+dnl Registers an option to be passed to xgettext in the po subdirectory.
+AC_DEFUN([AM_XGETTEXT_OPTION],
+[
+  AC_REQUIRE([AM_XGETTEXT_OPTION_INIT])
+  XGETTEXT_EXTRA_OPTIONS="$XGETTEXT_EXTRA_OPTIONS $1"
+])
diff --git a/macros/progtest.m4 b/macros/progtest.m4
new file mode 100644
index 0000000..a56365c
--- /dev/null
+++ b/macros/progtest.m4
@@ -0,0 +1,92 @@
+# progtest.m4 serial 4 (gettext-0.14.2)
+dnl Copyright (C) 1996-2003, 2005 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper at cygnus.com>, 1996.
+
+AC_PREREQ(2.50)
+
+# Search path for a program which passes the given test.
+
+dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,
+dnl   TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])
+AC_DEFUN([AM_PATH_PROG_WITH_TEST],
+[
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+    PATH_SEPARATOR=';'
+  else
+    PATH_SEPARATOR=:
+  fi
+  rm -f conf$$.sh
+fi
+
+# Find out how to test for executable files. Don't use a zero-byte file,
+# as systems may use methods other than mode bits to determine executability.
+cat >conf$$.file <<_ASEOF
+#! /bin/sh
+exit 0
+_ASEOF
+chmod +x conf$$.file
+if test -x conf$$.file >/dev/null 2>&1; then
+  ac_executable_p="test -x"
+else
+  ac_executable_p="test -f"
+fi
+rm -f conf$$.file
+
+# Extract the first word of "$2", so it can be a program name with args.
+set dummy $2; ac_word=[$]2
+AC_MSG_CHECKING([for $ac_word])
+AC_CACHE_VAL(ac_cv_path_$1,
+[case "[$]$1" in
+  [[\\/]]* | ?:[[\\/]]*)
+    ac_cv_path_$1="[$]$1" # Let the user override the test with a path.
+    ;;
+  *)
+    ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in ifelse([$5], , $PATH, [$5]); do
+      IFS="$ac_save_IFS"
+      test -z "$ac_dir" && ac_dir=.
+      for ac_exec_ext in '' $ac_executable_extensions; do
+        if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then
+          echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD
+          if [$3]; then
+            ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext"
+            break 2
+          fi
+        fi
+      done
+    done
+    IFS="$ac_save_IFS"
+dnl If no 4th arg is given, leave the cache variable unset,
+dnl so AC_PATH_PROGS will keep looking.
+ifelse([$4], , , [  test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4"
+])dnl
+    ;;
+esac])dnl
+$1="$ac_cv_path_$1"
+if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then
+  AC_MSG_RESULT([$]$1)
+else
+  AC_MSG_RESULT(no)
+fi
+AC_SUBST($1)dnl
+])
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..de11c75
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,148 @@
+# **********************************************************************
+# *
+# * rttopo - topology library
+# * http://gitlab.com/rttopo/rttopo
+# * Copyright 2008 Mark Cave-Ayland
+# *
+# * This is free software; you can redistribute and/or modify it under
+# * the terms of the GNU General Public Licence. See the COPYING file.
+# *
+# **********************************************************************
+
+CC = @CC@
+CPPFLAGS = @CPPFLAGS@
+CFLAGS = @CFLAGS@ @PICFLAGS@ @WARNFLAGS@ @GEOS_CPPFLAGS@ @JSON_CPPFLAGS@
+LDFLAGS = @LDFLAGS@ @GEOS_LDFLAGS@ -lgeos_c @JSON_LDFLAGS@
+NUMERICFLAGS = @NUMERICFLAGS@
+top_builddir = @top_builddir@
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+libdir = @libdir@
+includedir = @includedir@
+SHELL = @SHELL@
+INSTALL = $(SHELL) ../install-sh
+LIBTOOL = @LIBTOOL@
+
+IFACE_CURRENT = @LIBRTGEOM_CURRENT@
+IFACE_AGE = @LIBRTGEOM_AGE@
+IFACE_REV = @LIBRTGEOM_REV@
+
+VERSION_INFO = $(IFACE_CURRENT):$(IFACE_REV):$(IFACE_AGE)
+
+
+YACC=@YACC@
+LEX=@LEX@
+
+# Standalone RTGEOM objects
+SA_OBJS = \
+	stringbuffer.o \
+	bytebuffer.o \
+	measures.o \
+	measures3d.o \
+	box2d.o \
+	ptarray.o \
+	rtgeom_api.o \
+	rtgeom.o \
+	rtpoint.o \
+	rtline.o \
+	rtpoly.o \
+	rttriangle.o \
+	rtmpoint.o \
+	rtmline.o \
+	rtmpoly.o \
+	rtcollection.o \
+	rtcircstring.o \
+	rtcompound.o \
+	rtcurvepoly.o \
+	rtmcurve.o \
+	rtmsurface.o \
+	rtpsurface.o \
+	rttin.o \
+	rtout_wkb.o \
+	rtin_geojson.o \
+	rtin_wkb.o \
+	rtin_twkb.o \
+	rtiterator.o \
+	rtout_wkt.o \
+	rtout_twkb.o \
+	rtutil.o \
+	rthomogenize.o \
+	rtalgorithm.o \
+	rtstroke.o \
+	rtlinearreferencing.o \
+	rtprint.o \
+	g_box.o \
+	g_serialized.o \
+	g_util.o \
+	rtgeodetic.o \
+	rtgeodetic_tree.o \
+	rttree.o \
+	rtout_gml.o \
+	rtout_kml.o \
+	rtout_geojson.o \
+	rtout_svg.o \
+	rtout_x3d.o \
+	rtgeom_debug.o \
+	rtgeom_geos.o \
+	rtgeom_geos_clean.o \
+	rtgeom_geos_node.o \
+	rtgeom_geos_split.o \
+	rtgeom_topo.o \
+	rtspheroid.o
+
+LDFLAGS += -no-undefined
+
+LT_SA_OBJS = $(SA_OBJS:.o=.lo)
+LT_OBJS = $(LT_SA_OBJS)
+
+SA_HEADERS = \
+	bytebuffer.h \
+	librtgeom.h \
+	librtgeom_internal.h \
+	rtgeodetic.h \
+	rtgeodetic_tree.h \
+	librtgeom_topo.h \
+	librtgeom_topo_internal.h \
+	rtgeom_log.h \
+	rtgeom_geos.h \
+	rtgeom_log.h \
+	rtout_twkb.h \
+	rttree.h \
+	measures3d.h \
+	measures.h \
+	stringbuffer.h \
+	varint.h
+
+all: librtgeom.la
+
+install: install-librtgeom
+
+uninstall: uninstall-librtgeom
+
+install-librtgeom: librtgeom.la
+	$(LIBTOOL) --mode=install $(INSTALL) librtgeom.la "$(DESTDIR)$(libdir)/librtgeom.la"
+	$(INSTALL) -m 0644 librtgeom.h "$(DESTDIR)$(includedir)/librtgeom.h"
+	$(INSTALL) -m 0644 librtgeom_topo.h "$(DESTDIR)$(includedir)/librtgeom_topo.h"
+
+uninstall-librtgeom:
+	$(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/librtgeom.la"
+	$(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/librtgeom.h"
+	$(LIBTOOL) --mode=uninstall rm -f "$(DESTDIR)$(includedir)/librtgeom_topo.h"
+
+$(LT_OBJS): rttopo_config.h $(SA_HEADERS)
+
+librtgeom.la: $(LT_OBJS)
+	$(LIBTOOL) --tag=CC --mode=link $(CC) -rpath $(libdir) $(LT_OBJS) \
+             -version-info $(VERSION_INFO) $(LDFLAGS) -o $@
+
+clean:
+	rm -f $(LT_OBJS) $(SA_OBJS)
+	rm -f librtgeom.la
+	rm -rf .libs
+
+distclean: clean
+	rm -f librtgeom.h Makefile
+
+# Command to build each of the .lo files
+$(LT_SA_OBJS): %.lo: %.c
+	$(LIBTOOL) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
diff --git a/src/box2d.c b/src/box2d.c
new file mode 100644
index 0000000..6a8085a
--- /dev/null
+++ b/src/box2d.c
@@ -0,0 +1,47 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "librtgeom_internal.h"
+
+#ifndef EPSILON
+#define EPSILON        1.0E-06
+#endif
+#ifndef FPeq
+#define FPeq(A,B)     (fabs((A) - (B)) <= EPSILON)
+#endif
+
+
+
+RTGBOX *
+box2d_clone(const RTCTX *ctx, const RTGBOX *in)
+{
+	RTGBOX *ret = rtalloc(ctx, sizeof(RTGBOX));
+	memcpy(ret, in, sizeof(RTGBOX));
+	return ret;
+}
diff --git a/src/bytebuffer.c b/src/bytebuffer.c
new file mode 100644
index 0000000..8ddc760
--- /dev/null
+++ b/src/bytebuffer.c
@@ -0,0 +1,358 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2015 Nicklas Av�n <nicklas.aven at jordogskog.no>
+ *
+ **********************************************************************/
+
+
+
+
+#include "librtgeom_internal.h"
+#include "bytebuffer.h"
+
+/**
+* Allocate a new bytebuffer_t. Use bytebuffer_destroy to free.
+*/
+bytebuffer_t* 
+bytebuffer_create(const RTCTX *ctx)
+{
+	RTDEBUG(2,"Entered bytebuffer_create");
+	return bytebuffer_create_with_size(ctx, BYTEBUFFER_STARTSIZE);
+}
+
+/**
+* Allocate a new bytebuffer_t. Use bytebuffer_destroy to free.
+*/
+bytebuffer_t* 
+bytebuffer_create_with_size(const RTCTX *ctx, size_t size)
+{
+	RTDEBUGF(2,"Entered bytebuffer_create_with_size %d", size);
+	bytebuffer_t *s;
+
+	s = rtalloc(ctx, sizeof(bytebuffer_t));
+	s->buf_start = rtalloc(ctx, size);
+	s->readcursor = s->writecursor = s->buf_start;
+	s->capacity = size;
+	memset(s->buf_start,0,size);
+	RTDEBUGF(4,"We create a buffer on %p of %d bytes", s->buf_start, size);
+	return s;
+}
+
+/**
+* Allocate just the internal buffer of an existing bytebuffer_t
+* struct. Useful for allocating short-lived bytebuffers off the stack.
+*/
+void 
+bytebuffer_init_with_size(const RTCTX *ctx, bytebuffer_t *b, size_t size)
+{
+	b->buf_start = rtalloc(ctx, size);
+	b->readcursor = b->writecursor = b->buf_start;
+	b->capacity = size;
+	memset(b->buf_start, 0, size);
+}
+
+/**
+* Free the bytebuffer_t and all memory managed within it.
+*/
+void 
+bytebuffer_destroy(const RTCTX *ctx, bytebuffer_t *s)
+{
+	RTDEBUG(2,"Entered bytebuffer_destroy");
+	RTDEBUGF(4,"The buffer has used %d bytes",bytebuffer_getlength(ctx, s));
+	
+	if ( s->buf_start ) 
+	{
+		RTDEBUGF(4,"let's free buf_start %p",s->buf_start);
+		rtfree(ctx, s->buf_start);
+		RTDEBUG(4,"buf_start is freed");
+	}
+	if ( s ) 
+	{
+		rtfree(ctx, s);		
+		RTDEBUG(4,"bytebuffer_t is freed");
+	}
+	return;
+}
+
+/**
+* Set the read cursor to the beginning
+*/
+void 
+bytebuffer_reset_reading(const RTCTX *ctx, bytebuffer_t *s)
+{
+	s->readcursor = s->buf_start;
+}
+
+/**
+* Reset the bytebuffer_t. Useful for starting a fresh string
+* without the expense of freeing and re-allocating a new
+* bytebuffer_t.
+*/
+void 
+bytebuffer_clear(const RTCTX *ctx, bytebuffer_t *s)
+{
+	s->readcursor = s->writecursor = s->buf_start;
+}
+
+/**
+* If necessary, expand the bytebuffer_t internal buffer to accomodate the
+* specified additional size.
+*/
+static inline void 
+bytebuffer_makeroom(const RTCTX *ctx, bytebuffer_t *s, size_t size_to_add)
+{
+	RTDEBUGF(2,"Entered bytebuffer_makeroom with space need of %d", size_to_add);
+	size_t current_write_size = (s->writecursor - s->buf_start);
+	size_t capacity = s->capacity;
+	size_t required_size = current_write_size + size_to_add;
+
+	RTDEBUGF(2,"capacity = %d and required size = %d",capacity ,required_size);
+	while (capacity < required_size)
+		capacity *= 2;
+
+	if ( capacity > s->capacity )
+	{
+		RTDEBUGF(4,"We need to realloc more memory. New capacity is %d", capacity);
+		s->buf_start = rtrealloc(ctx, s->buf_start, capacity);
+		s->capacity = capacity;
+		s->writecursor = s->buf_start + current_write_size;
+		s->readcursor = s->buf_start + (s->readcursor - s->buf_start);
+	}
+	return;
+}
+
+/**
+* Writes a uint8_t value to the buffer
+*/
+void 
+bytebuffer_append_byte(const RTCTX *ctx, bytebuffer_t *s, const uint8_t val)
+{	
+	RTDEBUGF(2,"Entered bytebuffer_append_byte with value %d", val);	
+	bytebuffer_makeroom(ctx, s, 1);
+	*(s->writecursor)=val;
+	s->writecursor += 1;
+	return;
+}
+
+
+/**
+* Writes a uint8_t value to the buffer
+*/
+void 
+bytebuffer_append_bulk(const RTCTX *ctx, bytebuffer_t *s, void * start, size_t size)
+{	
+	RTDEBUGF(2,"bytebuffer_append_bulk with size %d",size);	
+	bytebuffer_makeroom(ctx, s, size);
+	memcpy(s->writecursor, start, size);
+	s->writecursor += size;
+	return;
+}
+
+/**
+* Writes a uint8_t value to the buffer
+*/
+void 
+bytebuffer_append_bytebuffer(const RTCTX *ctx, bytebuffer_t *write_to,bytebuffer_t *write_from )
+{	
+	RTDEBUG(2,"bytebuffer_append_bytebuffer");	
+	size_t size = bytebuffer_getlength(ctx, write_from);
+	bytebuffer_makeroom(ctx, write_to, size);
+	memcpy(write_to->writecursor, write_from->buf_start, size);
+	write_to->writecursor += size;
+	return;
+}
+
+
+/**
+* Writes a signed varInt to the buffer
+*/
+void 
+bytebuffer_append_varint(const RTCTX *ctx, bytebuffer_t *b, const int64_t val)
+{	
+	size_t size;
+	bytebuffer_makeroom(ctx, b, 8);
+	size = varint_s64_encode_buf(ctx, val, b->writecursor);
+	b->writecursor += size;
+	return;
+}
+
+/**
+* Writes a unsigned varInt to the buffer
+*/
+void 
+bytebuffer_append_uvarint(const RTCTX *ctx, bytebuffer_t *b, const uint64_t val)
+{	
+	size_t size;
+	bytebuffer_makeroom(ctx, b, 8);
+	size = varint_u64_encode_buf(ctx, val, b->writecursor);
+	b->writecursor += size;
+	return;
+}
+
+
+/*
+* Writes Integer to the buffer
+*/
+void
+bytebuffer_append_int(const RTCTX *ctx, bytebuffer_t *buf, const int val, int swap)
+{
+	RTDEBUGF(2,"Entered bytebuffer_append_int with value %d, swap = %d", val, swap);	
+	
+	RTDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor);
+	char *iptr = (char*)(&val);
+	int i = 0;
+
+	if ( sizeof(int) != RTWKB_INT_SIZE )
+	{
+		rterror(ctx, "Machine int size is not %d bytes!", RTWKB_INT_SIZE);
+	}
+	
+	bytebuffer_makeroom(ctx, buf, RTWKB_INT_SIZE);
+	/* Machine/request arch mismatch, so flip byte order */
+	if ( swap)
+	{
+		RTDEBUG(4,"Ok, let's do the swaping thing");	
+		for ( i = 0; i < RTWKB_INT_SIZE; i++ )
+		{
+			*(buf->writecursor) = iptr[RTWKB_INT_SIZE - 1 - i];
+			buf->writecursor += 1;
+		}
+	}
+	/* If machine arch and requested arch match, don't flip byte order */
+	else
+	{
+		RTDEBUG(4,"Ok, let's do the memcopying thing");		
+		memcpy(buf->writecursor, iptr, RTWKB_INT_SIZE);
+		buf->writecursor += RTWKB_INT_SIZE;
+	}
+	
+	RTDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor);
+	return;
+
+}
+
+
+
+
+
+/**
+* Writes a float64 to the buffer
+*/
+void
+bytebuffer_append_double(const RTCTX *ctx, bytebuffer_t *buf, const double val, int swap)
+{
+	RTDEBUGF(2,"Entered bytebuffer_append_double with value %lf swap = %d", val, swap);	
+	
+	RTDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor);
+	char *dptr = (char*)(&val);
+	int i = 0;
+
+	if ( sizeof(double) != RTWKB_DOUBLE_SIZE )
+	{
+		rterror(ctx, "Machine double size is not %d bytes!", RTWKB_DOUBLE_SIZE);
+	}
+
+	bytebuffer_makeroom(ctx, buf, RTWKB_DOUBLE_SIZE);
+	
+	/* Machine/request arch mismatch, so flip byte order */
+	if ( swap )
+	{
+		RTDEBUG(4,"Ok, let's do the swapping thing");		
+		for ( i = 0; i < RTWKB_DOUBLE_SIZE; i++ )
+		{
+			*(buf->writecursor) = dptr[RTWKB_DOUBLE_SIZE - 1 - i];
+			buf->writecursor += 1;
+		}
+	}
+	/* If machine arch and requested arch match, don't flip byte order */
+	else
+	{
+		RTDEBUG(4,"Ok, let's do the memcopying thing");			
+		memcpy(buf->writecursor, dptr, RTWKB_DOUBLE_SIZE);
+		buf->writecursor += RTWKB_DOUBLE_SIZE;
+	}
+	
+	RTDEBUG(4,"Return from bytebuffer_append_double");		
+	return;
+
+}
+
+/**
+* Reads a signed varInt from the buffer
+*/
+int64_t 
+bytebuffer_read_varint(const RTCTX *ctx, bytebuffer_t *b)
+{
+	size_t size;
+	int64_t val = varint_s64_decode(ctx, b->readcursor, b->buf_start + b->capacity, &size);
+	b->readcursor += size;
+	return val;
+}
+
+/**
+* Reads a unsigned varInt from the buffer
+*/
+uint64_t 
+bytebuffer_read_uvarint(const RTCTX *ctx, bytebuffer_t *b)
+{	
+	size_t size;
+	uint64_t val = varint_u64_decode(ctx, b->readcursor, b->buf_start + b->capacity, &size);
+	b->readcursor += size;
+	return val;
+}
+
+/**
+* Returns the length of the current buffer
+*/
+size_t 
+bytebuffer_getlength(const RTCTX *ctx, bytebuffer_t *s)
+{
+	return (size_t) (s->writecursor - s->buf_start);
+}
+
+
+/**
+* Returns a new bytebuffer were both ingoing bytebuffers is merged.
+* Caller is responsible for freeing both incoming bytefyffers and resulting bytebuffer
+*/
+bytebuffer_t*
+bytebuffer_merge(const RTCTX *ctx, bytebuffer_t **buff_array, int nbuffers)
+{
+	size_t total_size = 0, current_size, acc_size = 0;
+	int i;
+	for ( i = 0; i < nbuffers; i++ )
+	{
+		total_size += bytebuffer_getlength(ctx, buff_array[i]);
+	}
+		
+	bytebuffer_t *res = bytebuffer_create_with_size(ctx, total_size);
+	for ( i = 0; i < nbuffers; i++)
+	{
+		current_size = bytebuffer_getlength(ctx, buff_array[i]);
+		memcpy(res->buf_start+acc_size, buff_array[i]->buf_start, current_size);
+		acc_size += current_size;
+	}
+	res->writecursor = res->buf_start + total_size;
+	res->readcursor = res->buf_start;
+	return res;
+}
+
+
diff --git a/src/bytebuffer.h b/src/bytebuffer.h
new file mode 100644
index 0000000..9597491
--- /dev/null
+++ b/src/bytebuffer.h
@@ -0,0 +1,65 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2015 Nicklas Avén <nicklas.aven at jordogskog.no>
+ *
+ **********************************************************************/
+
+
+
+#ifndef _BYTEBUFFER_H
+#define _BYTEBUFFER_H 1
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include "varint.h"
+
+#include "rtgeom_log.h"
+#define BYTEBUFFER_STARTSIZE 128
+
+typedef struct
+{
+	size_t capacity;
+	uint8_t *buf_start;
+	uint8_t *writecursor;	
+	uint8_t *readcursor;	
+}
+bytebuffer_t;
+
+void bytebuffer_init_with_size(const RTCTX *ctx, bytebuffer_t *b, size_t size);
+bytebuffer_t *bytebuffer_create_with_size(const RTCTX *ctx, size_t size);
+bytebuffer_t *bytebuffer_create(const RTCTX *ctx);
+void bytebuffer_destroy(const RTCTX *ctx, bytebuffer_t *s);
+void bytebuffer_clear(const RTCTX *ctx, bytebuffer_t *s);
+void bytebuffer_append_byte(const RTCTX *ctx, bytebuffer_t *s, const uint8_t val);
+void bytebuffer_append_varint(const RTCTX *ctx, bytebuffer_t *s, const int64_t val);
+void bytebuffer_append_uvarint(const RTCTX *ctx, bytebuffer_t *s, const uint64_t val);
+uint64_t bytebuffer_read_uvarint(const RTCTX *ctx, bytebuffer_t *s);
+int64_t bytebuffer_read_varint(const RTCTX *ctx, bytebuffer_t *s);
+size_t bytebuffer_getlength(const RTCTX *ctx, bytebuffer_t *s);
+bytebuffer_t* bytebuffer_merge(const RTCTX *ctx, bytebuffer_t **buff_array, int nbuffers);
+void bytebuffer_reset_reading(const RTCTX *ctx, bytebuffer_t *s);
+
+void bytebuffer_append_bytebuffer(const RTCTX *ctx, bytebuffer_t *write_to,bytebuffer_t *write_from);
+void bytebuffer_append_bulk(const RTCTX *ctx, bytebuffer_t *s, void * start, size_t size);
+void bytebuffer_append_int(const RTCTX *ctx, bytebuffer_t *buf, const int val, int swap);
+void bytebuffer_append_double(const RTCTX *ctx, bytebuffer_t *buf, const double val, int swap);
+#endif /* _BYTEBUFFER_H */
diff --git a/src/g_box.c b/src/g_box.c
new file mode 100644
index 0000000..cb209cf
--- /dev/null
+++ b/src/g_box.c
@@ -0,0 +1,709 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#if !HAVE_ISFINITE
+#endif
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include <stdlib.h>
+#include <math.h>
+
+RTGBOX* gbox_new(const RTCTX *ctx, uint8_t flags)
+{
+	RTGBOX *g = (RTGBOX*)rtalloc(ctx, sizeof(RTGBOX));
+	gbox_init(ctx, g);
+	g->flags = flags;
+	return g;
+}
+
+void gbox_init(const RTCTX *ctx, RTGBOX *gbox)
+{
+	memset(gbox, 0, sizeof(RTGBOX));
+}
+
+RTGBOX* gbox_clone(const RTCTX *ctx, const RTGBOX *gbox)
+{
+	RTGBOX *g = rtalloc(ctx, sizeof(RTGBOX));
+	memcpy(g, gbox, sizeof(RTGBOX));
+	return g;
+}
+
+/* TODO to be removed */
+BOX3D* box3d_from_gbox(const RTCTX *ctx, const RTGBOX *gbox)
+{
+	BOX3D *b;
+	assert(gbox);
+	
+	b = rtalloc(ctx, sizeof(BOX3D));
+
+	b->xmin = gbox->xmin;
+	b->xmax = gbox->xmax;
+	b->ymin = gbox->ymin;
+	b->ymax = gbox->ymax;
+
+	if ( RTFLAGS_GET_Z(gbox->flags) )
+	{
+		b->zmin = gbox->zmin;
+		b->zmax = gbox->zmax;
+	}
+	else
+	{
+		b->zmin = b->zmax = 0.0;
+	}
+
+	b->srid = SRID_UNKNOWN;
+ 	return b;	
+}
+
+/* TODO to be removed */
+RTGBOX* box3d_to_gbox(const RTCTX *ctx, const BOX3D *b3d)
+{
+	RTGBOX *b;
+	assert(b3d);
+	
+	b = rtalloc(ctx, sizeof(RTGBOX));
+
+	b->xmin = b3d->xmin;
+	b->xmax = b3d->xmax;
+	b->ymin = b3d->ymin;
+	b->ymax = b3d->ymax;
+	b->zmin = b3d->zmin;
+	b->zmax = b3d->zmax;
+
+ 	return b;
+}
+
+void gbox_expand(const RTCTX *ctx, RTGBOX *g, double d)
+{
+	g->xmin -= d;
+	g->xmax += d;
+	g->ymin -= d;
+	g->ymax += d;
+	if ( RTFLAGS_GET_Z(g->flags) )
+	{
+		g->zmin -= d;
+		g->zmax += d;
+	}
+	if ( RTFLAGS_GET_M(g->flags) )
+	{
+		g->mmin -= d;
+		g->mmax += d;
+	}
+}
+
+int gbox_union(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2, RTGBOX *gout)
+{
+	if ( ( ! g1 ) && ( ! g2 ) )
+		return RT_FALSE;
+
+	if  ( ! g1 )
+	{
+		memcpy(gout, g2, sizeof(RTGBOX));
+		return RT_TRUE;
+	}
+	if ( ! g2 )
+	{
+		memcpy(gout, g1, sizeof(RTGBOX));
+		return RT_TRUE;
+	}
+	
+	gout->flags = g1->flags;
+
+	gout->xmin = FP_MIN(g1->xmin, g2->xmin);
+	gout->xmax = FP_MAX(g1->xmax, g2->xmax);
+
+	gout->ymin = FP_MIN(g1->ymin, g2->ymin);
+	gout->ymax = FP_MAX(g1->ymax, g2->ymax);
+	
+	gout->zmin = FP_MIN(g1->zmin, g2->zmin);
+	gout->zmax = FP_MAX(g1->zmax, g2->zmax);
+
+	return RT_TRUE;
+}
+
+int gbox_same(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+	if (RTFLAGS_GET_ZM(g1->flags) != RTFLAGS_GET_ZM(g2->flags))
+		return RT_FALSE;
+
+	if (!gbox_same_2d(ctx, g1, g2)) return RT_FALSE;
+
+	if (RTFLAGS_GET_Z(g1->flags) && (g1->zmin != g2->zmin || g1->zmax != g2->zmax))
+		return RT_FALSE;
+	if (RTFLAGS_GET_M(g1->flags) && (g1->mmin != g2->mmin || g1->mmax != g2->mmax))
+		return RT_FALSE;
+
+	return RT_TRUE;
+}
+
+int gbox_same_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+    if (g1->xmin == g2->xmin && g1->ymin == g2->ymin &&
+        g1->xmax == g2->xmax && g1->ymax == g2->ymax)
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+int gbox_same_2d_float(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+  if  ((g1->xmax == g2->xmax || next_float_up(ctx, g1->xmax)   == next_float_up(ctx, g2->xmax))   &&
+       (g1->ymax == g2->ymax || next_float_up(ctx, g1->ymax)   == next_float_up(ctx, g2->ymax))   &&
+       (g1->xmin == g2->xmin || next_float_down(ctx, g1->xmin) == next_float_down(ctx, g1->xmin)) &&
+       (g1->ymin == g2->ymin || next_float_down(ctx, g2->ymin) == next_float_down(ctx, g2->ymin)))
+      return RT_TRUE;
+  return RT_FALSE;
+}
+
+int gbox_is_valid(const RTCTX *ctx, const RTGBOX *gbox)
+{
+	/* X */
+	if ( ! isfinite(gbox->xmin) || isnan(gbox->xmin) ||
+	     ! isfinite(gbox->xmax) || isnan(gbox->xmax) )
+		return RT_FALSE;
+		
+	/* Y */
+	if ( ! isfinite(gbox->ymin) || isnan(gbox->ymin) ||
+	     ! isfinite(gbox->ymax) || isnan(gbox->ymax) )
+		return RT_FALSE;
+		
+	/* Z */
+	if ( RTFLAGS_GET_GEODETIC(gbox->flags) || RTFLAGS_GET_Z(gbox->flags) )
+	{
+		if ( ! isfinite(gbox->zmin) || isnan(gbox->zmin) ||
+		     ! isfinite(gbox->zmax) || isnan(gbox->zmax) )
+			return RT_FALSE;
+	}
+
+	/* M */
+	if ( RTFLAGS_GET_M(gbox->flags) )
+	{
+		if ( ! isfinite(gbox->mmin) || isnan(gbox->mmin) ||
+		     ! isfinite(gbox->mmax) || isnan(gbox->mmax) )
+			return RT_FALSE;
+	}
+	
+	return RT_TRUE;		
+}
+
+int gbox_merge_point3d(const RTCTX *ctx, const POINT3D *p, RTGBOX *gbox)
+{
+	if ( gbox->xmin > p->x ) gbox->xmin = p->x;
+	if ( gbox->ymin > p->y ) gbox->ymin = p->y;
+	if ( gbox->zmin > p->z ) gbox->zmin = p->z;
+	if ( gbox->xmax < p->x ) gbox->xmax = p->x;
+	if ( gbox->ymax < p->y ) gbox->ymax = p->y;
+	if ( gbox->zmax < p->z ) gbox->zmax = p->z;
+	return RT_SUCCESS;
+}
+
+int gbox_init_point3d(const RTCTX *ctx, const POINT3D *p, RTGBOX *gbox)
+{
+	gbox->xmin = gbox->xmax = p->x;
+	gbox->ymin = gbox->ymax = p->y;
+	gbox->zmin = gbox->zmax = p->z;
+	return RT_SUCCESS;
+}
+
+int gbox_contains_point3d(const RTCTX *ctx, const RTGBOX *gbox, const POINT3D *pt)
+{
+	if ( gbox->xmin > pt->x || gbox->ymin > pt->y || gbox->zmin > pt->z ||
+	        gbox->xmax < pt->x || gbox->ymax < pt->y || gbox->zmax < pt->z )
+	{
+		return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+int gbox_merge(const RTCTX *ctx, const RTGBOX *new_box, RTGBOX *merge_box)
+{
+	assert(merge_box);
+
+	if ( RTFLAGS_GET_ZM(merge_box->flags) != RTFLAGS_GET_ZM(new_box->flags) )
+		return RT_FAILURE;
+
+	if ( new_box->xmin < merge_box->xmin) merge_box->xmin = new_box->xmin;
+	if ( new_box->ymin < merge_box->ymin) merge_box->ymin = new_box->ymin;
+	if ( new_box->xmax > merge_box->xmax) merge_box->xmax = new_box->xmax;
+	if ( new_box->ymax > merge_box->ymax) merge_box->ymax = new_box->ymax;
+
+	if ( RTFLAGS_GET_Z(merge_box->flags) || RTFLAGS_GET_GEODETIC(merge_box->flags) )
+	{
+		if ( new_box->zmin < merge_box->zmin) merge_box->zmin = new_box->zmin;
+		if ( new_box->zmax > merge_box->zmax) merge_box->zmax = new_box->zmax;
+	}
+	if ( RTFLAGS_GET_M(merge_box->flags) )
+	{
+		if ( new_box->mmin < merge_box->mmin) merge_box->mmin = new_box->mmin;
+		if ( new_box->mmax > merge_box->mmax) merge_box->mmax = new_box->mmax;
+	}
+
+	return RT_SUCCESS;
+}
+
+int gbox_overlaps(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+
+	/* Make sure our boxes are consistent */
+	if ( RTFLAGS_GET_GEODETIC(g1->flags) != RTFLAGS_GET_GEODETIC(g2->flags) )
+		rterror(ctx, "gbox_overlaps: cannot compare geodetic and non-geodetic boxes");
+
+	/* Check X/Y first */
+	if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin ||
+	     g1->xmin > g2->xmax || g1->ymin > g2->ymax )
+		return RT_FALSE;
+
+	/* Deal with the geodetic case special: we only compare the geodetic boxes (x/y/z) */
+	/* Never the M dimension */
+	if ( RTFLAGS_GET_GEODETIC(g1->flags) && RTFLAGS_GET_GEODETIC(g2->flags) )
+	{
+		if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax )
+			return RT_FALSE;
+		else
+			return RT_TRUE;		
+	}
+		
+	/* If both geodetic or both have Z, check Z */
+	if ( RTFLAGS_GET_Z(g1->flags) && RTFLAGS_GET_Z(g2->flags) )
+	{
+		if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax )
+			return RT_FALSE;
+	}
+	
+	/* If both have M, check M */
+	if ( RTFLAGS_GET_M(g1->flags) && RTFLAGS_GET_M(g2->flags) )
+	{
+		if ( g1->mmax < g2->mmin || g1->mmin > g2->mmax )
+			return RT_FALSE;
+	}
+	
+	return RT_TRUE;
+}
+
+int 
+gbox_overlaps_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+
+	/* Make sure our boxes are consistent */
+	if ( RTFLAGS_GET_GEODETIC(g1->flags) != RTFLAGS_GET_GEODETIC(g2->flags) )
+		rterror(ctx, "gbox_overlaps: cannot compare geodetic and non-geodetic boxes");
+
+	/* Check X/Y first */
+	if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin ||
+	     g1->xmin > g2->xmax || g1->ymin > g2->ymax )
+		return RT_FALSE;
+		
+	return RT_TRUE;
+}
+
+int 
+gbox_contains_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2)
+{
+	if ( ( g2->xmin < g1->xmin ) || ( g2->xmax > g1->xmax ) ||
+	     ( g2->ymin < g1->ymin ) || ( g2->ymax > g1->ymax ) )
+	{
+		return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+int 
+gbox_contains_point2d(const RTCTX *ctx, const RTGBOX *g, const RTPOINT2D *p)
+{
+	if ( ( g->xmin <= p->x ) && ( g->xmax >= p->x ) &&
+	     ( g->ymin <= p->y ) && ( g->ymax >= p->y ) )
+	{
+		return RT_TRUE;
+	}
+	return RT_FALSE;
+}
+
+/**
+* Warning, this function is only good for x/y/z boxes, used
+* in unit testing of geodetic box generation.
+*/
+RTGBOX* gbox_from_string(const RTCTX *ctx, const char *str)
+{
+	const char *ptr = str;
+	char *nextptr;
+	char *gbox_start = strstr(str, "RTGBOX((");
+	RTGBOX *gbox = gbox_new(ctx, gflags(ctx, 0,0,1));
+	if ( ! gbox_start ) return NULL; /* No header found */
+	ptr += 6;
+	gbox->xmin = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	ptr = nextptr + 1;
+	gbox->ymin = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	ptr = nextptr + 1;
+	gbox->zmin = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	ptr = nextptr + 3;
+	gbox->xmax = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	ptr = nextptr + 1;
+	gbox->ymax = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	ptr = nextptr + 1;
+	gbox->zmax = strtod(ptr, &nextptr);
+	if ( ptr == nextptr ) return NULL; /* No double found */
+	return gbox;
+}
+
+char* gbox_to_string(const RTCTX *ctx, const RTGBOX *gbox)
+{
+	static int sz = 128;
+	char *str = NULL;
+
+	if ( ! gbox )
+		return strdup("NULL POINTER");
+
+	str = (char*)rtalloc(ctx, sz);
+
+	if ( RTFLAGS_GET_GEODETIC(gbox->flags) )
+	{
+		snprintf(str, sz, "RTGBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax);
+		return str;
+	}
+	if ( RTFLAGS_GET_Z(gbox->flags) && RTFLAGS_GET_M(gbox->flags) )
+	{
+		snprintf(str, sz, "RTGBOX((%.8g,%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->zmax, gbox->mmax);
+		return str;
+	}
+	if ( RTFLAGS_GET_Z(gbox->flags) )
+	{
+		snprintf(str, sz, "RTGBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax);
+		return str;
+	}
+	if ( RTFLAGS_GET_M(gbox->flags) )
+	{
+		snprintf(str, sz, "RTGBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->mmax);
+		return str;
+	}
+	snprintf(str, sz, "RTGBOX((%.8g,%.8g),(%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->xmax, gbox->ymax);
+	return str;
+}
+
+RTGBOX* gbox_copy(const RTCTX *ctx, const RTGBOX *box)
+{
+	RTGBOX *copy = (RTGBOX*)rtalloc(ctx, sizeof(RTGBOX));
+	memcpy(copy, box, sizeof(RTGBOX));
+	return copy;
+}
+
+void gbox_duplicate(const RTCTX *ctx, const RTGBOX *original, RTGBOX *duplicate)
+{
+	assert(duplicate);
+	memcpy(duplicate, original, sizeof(RTGBOX));
+}
+
+size_t gbox_serialized_size(const RTCTX *ctx, uint8_t flags)
+{
+	if ( RTFLAGS_GET_GEODETIC(flags) )
+		return 6 * sizeof(float);
+	else
+		return 2 * RTFLAGS_NDIMS(flags) * sizeof(float);
+}
+
+
+/* ********************************************************************************
+** Compute cartesian bounding RTGBOX boxes from RTGEOM.
+*/
+
+int rt_arc_calculate_gbox_cartesian_2d(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, RTGBOX *gbox)
+{
+	RTPOINT2D xmin, ymin, xmax, ymax;
+	RTPOINT2D C;
+	int A2_side;
+	double radius_A;
+
+	RTDEBUG(2, "rt_arc_calculate_gbox_cartesian_2d called.");
+
+	radius_A = rt_arc_center(ctx, A1, A2, A3, &C);
+
+	/* Negative radius signals straight line, p1/p2/p3 are colinear */
+	if (radius_A < 0.0)
+	{
+        gbox->xmin = FP_MIN(A1->x, A3->x);
+        gbox->ymin = FP_MIN(A1->y, A3->y);
+        gbox->xmax = FP_MAX(A1->x, A3->x);
+        gbox->ymax = FP_MAX(A1->y, A3->y);
+	    return RT_SUCCESS;
+	}
+
+	/* Matched start/end points imply circle */
+	if ( A1->x == A3->x && A1->y == A3->y )
+	{
+		gbox->xmin = C.x - radius_A;
+		gbox->ymin = C.y - radius_A;
+		gbox->xmax = C.x + radius_A;
+		gbox->ymax = C.y + radius_A;
+		return RT_SUCCESS;
+	}
+
+	/* First approximation, bounds of start/end points */
+    gbox->xmin = FP_MIN(A1->x, A3->x);
+    gbox->ymin = FP_MIN(A1->y, A3->y);
+    gbox->xmax = FP_MAX(A1->x, A3->x);
+    gbox->ymax = FP_MAX(A1->y, A3->y);
+
+	/* Create points for the possible extrema */
+	xmin.x = C.x - radius_A;
+	xmin.y = C.y;
+	ymin.x = C.x;
+	ymin.y = C.y - radius_A;
+	xmax.x = C.x + radius_A;
+	xmax.y = C.y;
+	ymax.x = C.x;
+	ymax.y = C.y + radius_A;
+
+	/* Divide the circle into two parts, one on each side of a line
+	   joining p1 and p3. The circle extrema on the same side of that line
+	   as p2 is on, are also the extrema of the bbox. */
+
+	A2_side = rt_segment_side(ctx, A1, A3, A2);
+
+	if ( A2_side == rt_segment_side(ctx, A1, A3, &xmin) )
+		gbox->xmin = xmin.x;
+
+	if ( A2_side == rt_segment_side(ctx, A1, A3, &ymin) )
+		gbox->ymin = ymin.y;
+
+	if ( A2_side == rt_segment_side(ctx, A1, A3, &xmax) )
+		gbox->xmax = xmax.x;
+
+	if ( A2_side == rt_segment_side(ctx, A1, A3, &ymax) )
+		gbox->ymax = ymax.y;
+
+	return RT_SUCCESS;
+}
+
+
+static int rt_arc_calculate_gbox_cartesian(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2, const RTPOINT4D *p3, RTGBOX *gbox)
+{
+	int rv;
+
+	RTDEBUG(2, "rt_arc_calculate_gbox_cartesian called.");
+
+	rv = rt_arc_calculate_gbox_cartesian_2d(ctx, (RTPOINT2D*)p1, (RTPOINT2D*)p2, (RTPOINT2D*)p3, gbox);
+    gbox->zmin = FP_MIN(p1->z, p3->z);
+    gbox->mmin = FP_MIN(p1->m, p3->m);
+    gbox->zmax = FP_MAX(p1->z, p3->z);
+    gbox->mmax = FP_MAX(p1->m, p3->m);
+	return rv;
+}
+
+int ptarray_calculate_gbox_cartesian(const RTCTX *ctx, const RTPOINTARRAY *pa, RTGBOX *gbox )
+{
+	int i;
+	RTPOINT4D p;
+	int has_z, has_m;
+
+	if ( ! pa ) return RT_FAILURE;
+	if ( ! gbox ) return RT_FAILURE;
+	if ( pa->npoints < 1 ) return RT_FAILURE;
+
+	has_z = RTFLAGS_GET_Z(pa->flags);
+	has_m = RTFLAGS_GET_M(pa->flags);
+	gbox->flags = gflags(ctx, has_z, has_m, 0);
+	RTDEBUGF(4, "ptarray_calculate_gbox Z: %d M: %d", has_z, has_m);
+
+	rt_getPoint4d_p(ctx, pa, 0, &p);
+	gbox->xmin = gbox->xmax = p.x;
+	gbox->ymin = gbox->ymax = p.y;
+	if ( has_z )
+		gbox->zmin = gbox->zmax = p.z;
+	if ( has_m )
+		gbox->mmin = gbox->mmax = p.m;
+
+	for ( i = 1 ; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		gbox->xmin = FP_MIN(gbox->xmin, p.x);
+		gbox->xmax = FP_MAX(gbox->xmax, p.x);
+		gbox->ymin = FP_MIN(gbox->ymin, p.y);
+		gbox->ymax = FP_MAX(gbox->ymax, p.y);
+		if ( has_z )
+		{
+			gbox->zmin = FP_MIN(gbox->zmin, p.z);
+			gbox->zmax = FP_MAX(gbox->zmax, p.z);
+		}
+		if ( has_m )
+		{
+			gbox->mmin = FP_MIN(gbox->mmin, p.m);
+			gbox->mmax = FP_MAX(gbox->mmax, p.m);
+		}
+	}
+	return RT_SUCCESS;
+}
+
+static int rtcircstring_calculate_gbox_cartesian(const RTCTX *ctx, RTCIRCSTRING *curve, RTGBOX *gbox)
+{
+	uint8_t flags = gflags(ctx, RTFLAGS_GET_Z(curve->flags), RTFLAGS_GET_M(curve->flags), 0);
+	RTGBOX tmp;
+	RTPOINT4D p1, p2, p3;
+	int i;
+
+	if ( ! curve ) return RT_FAILURE;
+	if ( curve->points->npoints < 3 ) return RT_FAILURE;
+
+	tmp.flags = flags;
+
+	/* Initialize */
+	gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = FLT_MAX;
+	gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -1*FLT_MAX;
+
+	for ( i = 2; i < curve->points->npoints; i += 2 )
+	{
+		rt_getPoint4d_p(ctx, curve->points, i-2, &p1);
+		rt_getPoint4d_p(ctx, curve->points, i-1, &p2);
+		rt_getPoint4d_p(ctx, curve->points, i, &p3);
+
+		if (rt_arc_calculate_gbox_cartesian(ctx, &p1, &p2, &p3, &tmp) == RT_FAILURE)
+			continue;
+
+		gbox_merge(ctx, &tmp, gbox);
+	}
+
+	return RT_SUCCESS;
+}
+
+static int rtpoint_calculate_gbox_cartesian(const RTCTX *ctx, RTPOINT *point, RTGBOX *gbox)
+{
+	if ( ! point ) return RT_FAILURE;
+	return ptarray_calculate_gbox_cartesian(ctx,  point->point, gbox );
+}
+
+static int rtline_calculate_gbox_cartesian(const RTCTX *ctx, RTLINE *line, RTGBOX *gbox)
+{
+	if ( ! line ) return RT_FAILURE;
+	return ptarray_calculate_gbox_cartesian(ctx,  line->points, gbox );
+}
+
+static int rttriangle_calculate_gbox_cartesian(const RTCTX *ctx, RTTRIANGLE *triangle, RTGBOX *gbox)
+{
+	if ( ! triangle ) return RT_FAILURE;
+	return ptarray_calculate_gbox_cartesian(ctx,  triangle->points, gbox );
+}
+
+static int rtpoly_calculate_gbox_cartesian(const RTCTX *ctx, RTPOLY *poly, RTGBOX *gbox)
+{
+	if ( ! poly ) return RT_FAILURE;
+	if ( poly->nrings == 0 ) return RT_FAILURE;
+	/* Just need to check outer ring */
+	return ptarray_calculate_gbox_cartesian(ctx,  poly->rings[0], gbox );
+}
+
+static int rtcollection_calculate_gbox_cartesian(const RTCTX *ctx, RTCOLLECTION *coll, RTGBOX *gbox)
+{
+	RTGBOX subbox;
+	int i;
+	int result = RT_FAILURE;
+	int first = RT_TRUE;
+	assert(coll);
+	if ( (coll->ngeoms == 0) || !gbox)
+		return RT_FAILURE;
+
+	subbox.flags = coll->flags;
+
+	for ( i = 0; i < coll->ngeoms; i++ )
+	{
+		if ( rtgeom_calculate_gbox_cartesian(ctx, (RTGEOM*)(coll->geoms[i]), &subbox) == RT_SUCCESS )
+		{
+			/* Keep a copy of the sub-bounding box for later 
+			if ( coll->geoms[i]->bbox ) 
+				rtfree(ctx, coll->geoms[i]->bbox);
+			coll->geoms[i]->bbox = gbox_copy(ctx, &subbox); */
+			if ( first )
+			{
+				gbox_duplicate(ctx, &subbox, gbox);
+				first = RT_FALSE;
+			}
+			else
+			{
+				gbox_merge(ctx, &subbox, gbox);
+			}
+			result = RT_SUCCESS;
+		}
+	}
+	return result;
+}
+
+int rtgeom_calculate_gbox_cartesian(const RTCTX *ctx, const RTGEOM *rtgeom, RTGBOX *gbox)
+{
+	if ( ! rtgeom ) return RT_FAILURE;
+	RTDEBUGF(4, "rtgeom_calculate_gbox got type (%d) - %s", rtgeom->type, rttype_name(ctx, rtgeom->type));
+
+	switch (rtgeom->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_calculate_gbox_cartesian(ctx, (RTPOINT *)rtgeom, gbox);
+	case RTLINETYPE:
+		return rtline_calculate_gbox_cartesian(ctx, (RTLINE *)rtgeom, gbox);
+	case RTCIRCSTRINGTYPE:
+		return rtcircstring_calculate_gbox_cartesian(ctx, (RTCIRCSTRING *)rtgeom, gbox);
+	case RTPOLYGONTYPE:
+		return rtpoly_calculate_gbox_cartesian(ctx, (RTPOLY *)rtgeom, gbox);
+	case RTTRIANGLETYPE:
+		return rttriangle_calculate_gbox_cartesian(ctx, (RTTRIANGLE *)rtgeom, gbox);
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_calculate_gbox_cartesian(ctx, (RTCOLLECTION *)rtgeom, gbox);
+	}
+	/* Never get here, please. */
+	rterror(ctx, "unsupported type (%d) - %s", rtgeom->type, rttype_name(ctx, rtgeom->type));
+	return RT_FAILURE;
+}
+
+void gbox_float_round(const RTCTX *ctx, RTGBOX *gbox)
+{
+	gbox->xmin = next_float_down(ctx, gbox->xmin);
+	gbox->xmax = next_float_up(ctx, gbox->xmax);
+
+	gbox->ymin = next_float_down(ctx, gbox->ymin);
+	gbox->ymax = next_float_up(ctx, gbox->ymax);
+
+	if ( RTFLAGS_GET_M(gbox->flags) )
+	{
+		gbox->mmin = next_float_down(ctx, gbox->mmin);
+		gbox->mmax = next_float_up(ctx, gbox->mmax);
+	}
+
+	if ( RTFLAGS_GET_Z(gbox->flags) )
+	{
+		gbox->zmin = next_float_down(ctx, gbox->zmin);
+		gbox->zmax = next_float_up(ctx, gbox->zmax);
+	}
+}
+
diff --git a/src/g_serialized.c b/src/g_serialized.c
new file mode 100644
index 0000000..971334e
--- /dev/null
+++ b/src/g_serialized.c
@@ -0,0 +1,1311 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+/***********************************************************************
+* GSERIALIZED metadata utility functions.
+*/
+
+int gserialized_has_bbox(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	return RTFLAGS_GET_BBOX(gser->flags);
+}
+
+int gserialized_has_z(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	return RTFLAGS_GET_Z(gser->flags);
+}
+
+int gserialized_has_m(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	return RTFLAGS_GET_M(gser->flags);
+}
+
+int gserialized_get_zm(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	return 2 * RTFLAGS_GET_Z(gser->flags) + RTFLAGS_GET_M(gser->flags);
+}
+
+int gserialized_ndims(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	return RTFLAGS_NDIMS(gser->flags);
+}
+
+int gserialized_is_geodetic(const RTCTX *ctx, const GSERIALIZED *gser)
+{
+	  return RTFLAGS_GET_GEODETIC(gser->flags);
+}
+ 
+uint32_t gserialized_max_header_size(const RTCTX *ctx) 
+{
+	/* read GSERIALIZED size + max bbox according gbox_serialized_size(ctx, 2 + Z + M) + 1 int for type */
+	return sizeof(GSERIALIZED) + 8 * sizeof(float) + sizeof(int);
+}
+
+uint32_t gserialized_get_type(const RTCTX *ctx, const GSERIALIZED *s)
+{
+	uint32_t *ptr;
+	assert(s);
+	ptr = (uint32_t*)(s->data);
+	RTDEBUG(4,"entered");
+	if ( RTFLAGS_GET_BBOX(s->flags) )
+	{
+		RTDEBUGF(4,"skipping forward past bbox (%d bytes)",gbox_serialized_size(ctx, s->flags));
+		ptr += (gbox_serialized_size(ctx, s->flags) / sizeof(uint32_t));
+	}
+	return *ptr;
+}
+
+int32_t gserialized_get_srid(const RTCTX *ctx, const GSERIALIZED *s)
+{
+	int32_t srid = 0;
+	srid = srid | (s->srid[0] << 16);
+	srid = srid | (s->srid[1] << 8);
+	srid = srid | s->srid[2];
+	/* Only the first 21 bits are set. Slide up and back to pull
+	   the negative bits down, if we need them. */
+	srid = (srid<<11)>>11;
+	
+	/* 0 is our internal unknown value. We'll map back and forth here for now */
+	if ( srid == 0 ) 
+		return SRID_UNKNOWN;
+	else
+		return clamp_srid(ctx, srid);
+}
+
+void gserialized_set_srid(const RTCTX *ctx, GSERIALIZED *s, int32_t srid)
+{
+	RTDEBUGF(3, "Called with srid = %d", srid);
+
+	srid = clamp_srid(ctx, srid);
+
+	/* 0 is our internal unknown value.
+	 * We'll map back and forth here for now */
+	if ( srid == SRID_UNKNOWN )
+		srid = 0;
+		
+	s->srid[0] = (srid & 0x001F0000) >> 16;
+	s->srid[1] = (srid & 0x0000FF00) >> 8;
+	s->srid[2] = (srid & 0x000000FF);
+}
+
+GSERIALIZED* gserialized_copy(const RTCTX *ctx, const GSERIALIZED *g)
+{
+	GSERIALIZED *g_out = NULL;
+	assert(g);
+	g_out = (GSERIALIZED*)rtalloc(ctx, SIZE_GET(g->size));
+	memcpy((uint8_t*)g_out,(uint8_t*)g,SIZE_GET(g->size));
+	return g_out;
+}
+
+static size_t gserialized_is_empty_recurse(const RTCTX *ctx, const uint8_t *p, int *isempty);
+static size_t gserialized_is_empty_recurse(const RTCTX *ctx, const uint8_t *p, int *isempty)
+{
+	int i;
+	int32_t type, num;
+
+	memcpy(&type, p, 4);
+	memcpy(&num, p+4, 4);
+	
+	if ( rttype_is_collection(ctx, type) )
+	{
+		size_t lz = 8;
+		for ( i = 0; i < num; i++ )
+		{
+			lz += gserialized_is_empty_recurse(ctx, p+lz, isempty);
+			if ( ! *isempty )
+				return lz;
+		}
+		*isempty = RT_TRUE;
+		return lz;
+	}
+	else
+	{
+		*isempty = (num == 0 ? RT_TRUE : RT_FALSE);
+		return 8;
+	}
+}
+
+int gserialized_is_empty(const RTCTX *ctx, const GSERIALIZED *g)
+{
+	uint8_t *p = (uint8_t*)g;
+	int isempty = 0;
+	assert(g);
+
+	p += 8; /* Skip varhdr and srid/flags */
+	if( RTFLAGS_GET_BBOX(g->flags) )
+		p += gbox_serialized_size(ctx, g->flags); /* Skip the box */
+
+	gserialized_is_empty_recurse(ctx, p, &isempty);
+	return isempty;
+}
+
+char* gserialized_to_string(const RTCTX *ctx, const GSERIALIZED *g)
+{
+	return rtgeom_to_wkt(ctx, rtgeom_from_gserialized(ctx, g), RTWKT_ISO, 12, 0);
+}
+
+int gserialized_read_gbox_p(const RTCTX *ctx, const GSERIALIZED *g, RTGBOX *gbox)
+{
+
+	/* Null input! */
+	if ( ! ( g && gbox ) ) return RT_FAILURE;
+
+	/* Initialize the flags on the box */
+	gbox->flags = g->flags;
+
+	/* Has pre-calculated box */
+	if ( RTFLAGS_GET_BBOX(g->flags) )
+	{
+		int i = 0;
+		float *fbox = (float*)(g->data);
+		gbox->xmin = fbox[i++];
+		gbox->xmax = fbox[i++];
+		gbox->ymin = fbox[i++];
+		gbox->ymax = fbox[i++];
+
+		/* Geodetic? Read next dimension (geocentric Z) and return */
+		if ( RTFLAGS_GET_GEODETIC(g->flags) )
+		{
+			gbox->zmin = fbox[i++];
+			gbox->zmax = fbox[i++];
+			return RT_SUCCESS;
+		}
+		/* Cartesian? Read extra dimensions (if there) and return */
+		if ( RTFLAGS_GET_Z(g->flags) )
+		{
+			gbox->zmin = fbox[i++];
+			gbox->zmax = fbox[i++];
+		}
+		if ( RTFLAGS_GET_M(g->flags) )
+		{
+			gbox->mmin = fbox[i++];
+			gbox->mmax = fbox[i++];
+		}
+		return RT_SUCCESS;
+	}
+
+	return RT_FAILURE;
+}
+
+/*
+* Populate a bounding box *without* allocating an RTGEOM. Useful
+* for some performance purposes.
+*/
+static int gserialized_peek_gbox_p(const RTCTX *ctx, const GSERIALIZED *g, RTGBOX *gbox)
+{
+	uint32_t type = gserialized_get_type(ctx, g);
+
+	/* Peeking doesn't help if you already have a box or are geodetic */
+	if ( RTFLAGS_GET_GEODETIC(g->flags) || RTFLAGS_GET_BBOX(g->flags) )
+	{
+		return RT_FAILURE;
+	}
+	
+	/* Boxes of points are easy peasy */
+	if ( type == RTPOINTTYPE )
+	{
+		int i = 1; /* Start past <pointtype><padding> */
+		double *dptr = (double*)(g->data);
+
+		/* Read the empty flag */
+		int *iptr = (int*)(g->data);
+		int isempty = (iptr[1] == 0);
+
+		/* EMPTY point has no box */
+		if ( isempty ) return RT_FAILURE;
+
+		gbox->xmin = gbox->xmax = dptr[i++];
+		gbox->ymin = gbox->ymax = dptr[i++];
+		if ( RTFLAGS_GET_Z(g->flags) )
+		{
+			gbox->zmin = gbox->zmax = dptr[i++];
+		}
+		if ( RTFLAGS_GET_M(g->flags) )
+		{
+			gbox->mmin = gbox->mmax = dptr[i++];
+		}
+		gbox_float_round(ctx, gbox);
+		return RT_SUCCESS;
+	}
+	/* We can calculate the box of a two-point cartesian line trivially */
+	else if ( type == RTLINETYPE )
+	{
+		int ndims = RTFLAGS_NDIMS(g->flags);
+		int i = 0; /* Start at <linetype><npoints> */
+		double *dptr = (double*)(g->data);
+		int *iptr = (int*)(g->data);
+		int npoints = iptr[1]; /* Read the npoints */
+	
+		/* This only works with 2-point lines */
+		if ( npoints != 2 )
+			return RT_FAILURE;
+		
+		/* Advance to X */
+		/* Past <linetype><npoints> */
+		i++;
+		gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]);
+		gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]);
+	
+		/* Advance to Y */
+		i++;
+		gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]);
+		gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]);
+	
+		if ( RTFLAGS_GET_Z(g->flags) )
+		{
+			/* Advance to Z */
+			i++;
+			gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]);
+			gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]);
+		}
+		if ( RTFLAGS_GET_M(g->flags) )
+		{
+			/* Advance to M */
+			i++;
+			gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]);
+			gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]);
+		}
+		gbox_float_round(ctx, gbox);
+		return RT_SUCCESS;
+	}
+	/* We can also do single-entry multi-points */
+	else if ( type == RTMULTIPOINTTYPE )
+	{
+		int i = 0; /* Start at <multipointtype><ngeoms> */
+		double *dptr = (double*)(g->data);
+		int *iptr = (int*)(g->data);
+		int ngeoms = iptr[1]; /* Read the ngeoms */
+	
+		/* This only works with single-entry multipoints */
+		if ( ngeoms != 1 )
+			return RT_FAILURE;
+
+		/* Move forward two doubles (four ints) */
+		/* Past <multipointtype><ngeoms> */
+		/* Past <pointtype><emtpyflat> */
+		i += 2;
+
+		/* Read the doubles from the one point */
+		gbox->xmin = gbox->xmax = dptr[i++];
+		gbox->ymin = gbox->ymax = dptr[i++];
+		if ( RTFLAGS_GET_Z(g->flags) )
+		{
+			gbox->zmin = gbox->zmax = dptr[i++];
+		}
+		if ( RTFLAGS_GET_M(g->flags) )
+		{
+			gbox->mmin = gbox->mmax = dptr[i++];
+		}
+		gbox_float_round(ctx, gbox);
+		return RT_SUCCESS;
+	}
+	/* And we can do single-entry multi-lines with two vertices (!!!) */
+	else if ( type == RTMULTILINETYPE )
+	{
+		int ndims = RTFLAGS_NDIMS(g->flags);
+		int i = 0; /* Start at <multilinetype><ngeoms> */
+		double *dptr = (double*)(g->data);
+		int *iptr = (int*)(g->data);
+		int ngeoms = iptr[1]; /* Read the ngeoms */
+		int npoints;
+	
+		/* This only works with 1-line multilines */
+		if ( ngeoms != 1 )
+			return RT_FAILURE;
+
+		/* Npoints is at <multilinetype><ngeoms><linetype><npoints> */
+		npoints = iptr[3];
+
+		if ( npoints != 2 )
+			return RT_FAILURE;
+
+		/* Advance to X */
+		/* Move forward two doubles (four ints) */
+		/* Past <multilinetype><ngeoms> */
+		/* Past <linetype><npoints> */
+		i += 2;
+		gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]);
+		gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]);
+	
+		/* Advance to Y */
+		i++;
+		gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]);
+		gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]);
+	
+		if ( RTFLAGS_GET_Z(g->flags) )
+		{
+			/* Advance to Z */
+			i++;
+			gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]);
+			gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]);
+		}
+		if ( RTFLAGS_GET_M(g->flags) )
+		{
+			/* Advance to M */
+			i++;
+			gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]);
+			gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]);
+		}
+		gbox_float_round(ctx, gbox);
+		return RT_SUCCESS;
+	}
+	
+	return RT_FAILURE;
+}
+
+/**
+* Read the bounding box off a serialization and calculate one if
+* it is not already there.
+*/
+int gserialized_get_gbox_p(const RTCTX *ctx, const GSERIALIZED *g, RTGBOX *box)
+{
+	/* Try to just read the serialized box. */
+	if ( gserialized_read_gbox_p(ctx, g, box) == RT_SUCCESS )
+	{
+		return RT_SUCCESS;
+	}
+	/* No box? Try to peek into simpler geometries and */
+	/* derive a box without creating an rtgeom */
+	else if ( gserialized_peek_gbox_p(ctx, g, box) == RT_SUCCESS )
+	{
+		return RT_SUCCESS;
+	}
+	/* Damn! Nothing for it but to create an rtgeom... */
+	/* See http://trac.osgeo.org/postgis/ticket/1023 */
+	else
+	{
+		RTGEOM *rtgeom = rtgeom_from_gserialized(ctx, g);
+		int ret = rtgeom_calculate_gbox(ctx, rtgeom, box);
+		gbox_float_round(ctx, box);
+		rtgeom_free(ctx, rtgeom);
+		return ret;
+	}
+}
+
+
+/***********************************************************************
+* Calculate the GSERIALIZED size for an RTGEOM.
+*/
+
+/* Private functions */
+
+static size_t gserialized_from_any_size(const RTCTX *ctx, const RTGEOM *geom); /* Local prototype */
+
+static size_t gserialized_from_rtpoint_size(const RTCTX *ctx, const RTPOINT *point)
+{
+	size_t size = 4; /* Type number. */
+
+	assert(point);
+
+	size += 4; /* Number of points (one or zero (empty)). */
+	size += point->point->npoints * RTFLAGS_NDIMS(point->flags) * sizeof(double);
+
+	RTDEBUGF(3, "point size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_rtline_size(const RTCTX *ctx, const RTLINE *line)
+{
+	size_t size = 4; /* Type number. */
+
+	assert(line);
+
+	size += 4; /* Number of points (zero => empty). */
+	size += line->points->npoints * RTFLAGS_NDIMS(line->flags) * sizeof(double);
+
+	RTDEBUGF(3, "linestring size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_rttriangle_size(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	size_t size = 4; /* Type number. */
+
+	assert(triangle);
+
+	size += 4; /* Number of points (zero => empty). */
+	size += triangle->points->npoints * RTFLAGS_NDIMS(triangle->flags) * sizeof(double);
+
+	RTDEBUGF(3, "triangle size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_rtpoly_size(const RTCTX *ctx, const RTPOLY *poly)
+{
+	size_t size = 4; /* Type number. */
+	int i = 0;
+
+	assert(poly);
+
+	size += 4; /* Number of rings (zero => empty). */
+	if ( poly->nrings % 2 )
+		size += 4; /* Padding to double alignment. */
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		size += 4; /* Number of points in ring. */
+		size += poly->rings[i]->npoints * RTFLAGS_NDIMS(poly->flags) * sizeof(double);
+	}
+
+	RTDEBUGF(3, "polygon size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_rtcircstring_size(const RTCTX *ctx, const RTCIRCSTRING *curve)
+{
+	size_t size = 4; /* Type number. */
+
+	assert(curve);
+
+	size += 4; /* Number of points (zero => empty). */
+	size += curve->points->npoints * RTFLAGS_NDIMS(curve->flags) * sizeof(double);
+
+	RTDEBUGF(3, "circstring size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_rtcollection_size(const RTCTX *ctx, const RTCOLLECTION *col)
+{
+	size_t size = 4; /* Type number. */
+	int i = 0;
+
+	assert(col);
+
+	size += 4; /* Number of sub-geometries (zero => empty). */
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		size_t subsize = gserialized_from_any_size(ctx, col->geoms[i]);
+		size += subsize;
+		RTDEBUGF(3, "rtcollection subgeom(%d) size = %d", i, subsize);
+	}
+
+	RTDEBUGF(3, "rtcollection size = %d", size);
+
+	return size;
+}
+
+static size_t gserialized_from_any_size(const RTCTX *ctx, const RTGEOM *geom)
+{
+	RTDEBUGF(2, "Input type: %s", rttype_name(ctx, geom->type));
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		return gserialized_from_rtpoint_size(ctx, (RTPOINT *)geom);
+	case RTLINETYPE:
+		return gserialized_from_rtline_size(ctx, (RTLINE *)geom);
+	case RTPOLYGONTYPE:
+		return gserialized_from_rtpoly_size(ctx, (RTPOLY *)geom);
+	case RTTRIANGLETYPE:
+		return gserialized_from_rttriangle_size(ctx, (RTTRIANGLE *)geom);
+	case RTCIRCSTRINGTYPE:
+		return gserialized_from_rtcircstring_size(ctx, (RTCIRCSTRING *)geom);
+	case RTCURVEPOLYTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return gserialized_from_rtcollection_size(ctx, (RTCOLLECTION *)geom);
+	default:
+		rterror(ctx, "Unknown geometry type: %d - %s", geom->type, rttype_name(ctx, geom->type));
+		return 0;
+	}
+}
+
+/* Public function */
+
+size_t gserialized_from_rtgeom_size(const RTCTX *ctx, const RTGEOM *geom)
+{
+	size_t size = 8; /* Header overhead. */
+	assert(geom);
+	
+	if ( geom->bbox )
+		size += gbox_serialized_size(ctx, geom->flags);	
+		
+	size += gserialized_from_any_size(ctx, geom);
+	RTDEBUGF(3, "g_serialize size = %d", size);
+	
+	return size;
+}
+
+/***********************************************************************
+* Serialize an RTGEOM into GSERIALIZED.
+*/
+
+/* Private functions */
+
+static size_t gserialized_from_rtgeom_any(const RTCTX *ctx, const RTGEOM *geom, uint8_t *buf);
+
+static size_t gserialized_from_rtpoint(const RTCTX *ctx, const RTPOINT *point, uint8_t *buf)
+{
+	uint8_t *loc;
+	int ptsize = ptarray_point_size(ctx, point->point);
+	int type = RTPOINTTYPE;
+
+	assert(point);
+	assert(buf);
+
+	if ( RTFLAGS_GET_ZM(point->flags) != RTFLAGS_GET_ZM(point->point->flags) )
+		rterror(ctx, "Dimensions mismatch in rtpoint");
+
+	RTDEBUGF(2, "rtpoint_to_gserialized(%p, %p) called", point, buf);
+
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+	/* Write in the number of points (0 => empty). */
+	memcpy(loc, &(point->point->npoints), sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Copy in the ordinates. */
+	if ( point->point->npoints > 0 )
+	{
+		memcpy(loc, rt_getPoint_internal(ctx, point->point, 0), ptsize);
+		loc += ptsize;
+	}
+
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rtline(const RTCTX *ctx, const RTLINE *line, uint8_t *buf)
+{
+	uint8_t *loc;
+	int ptsize;
+	size_t size;
+	int type = RTLINETYPE;
+
+	assert(line);
+	assert(buf);
+
+	RTDEBUGF(2, "rtline_to_gserialized(%p, %p) called", line, buf);
+
+	if ( RTFLAGS_GET_Z(line->flags) != RTFLAGS_GET_Z(line->points->flags) )
+		rterror(ctx, "Dimensions mismatch in rtline");
+
+	ptsize = ptarray_point_size(ctx, line->points);
+
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the npoints. */
+	memcpy(loc, &(line->points->npoints), sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	RTDEBUGF(3, "rtline_to_gserialized added npoints (%d)", line->points->npoints);
+
+	/* Copy in the ordinates. */
+	if ( line->points->npoints > 0 )
+	{
+		size = line->points->npoints * ptsize;
+		memcpy(loc, rt_getPoint_internal(ctx, line->points, 0), size);
+		loc += size;
+	}
+	RTDEBUGF(3, "rtline_to_gserialized copied serialized_pointlist (%d bytes)", ptsize * line->points->npoints);
+
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rtpoly(const RTCTX *ctx, const RTPOLY *poly, uint8_t *buf)
+{
+	int i;
+	uint8_t *loc;
+	int ptsize;
+	int type = RTPOLYGONTYPE;
+
+	assert(poly);
+	assert(buf);
+
+	RTDEBUG(2, "rtpoly_to_gserialized called");
+
+	ptsize = sizeof(double) * RTFLAGS_NDIMS(poly->flags);
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the nrings. */
+	memcpy(loc, &(poly->nrings), sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the npoints per ring. */
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		memcpy(loc, &(poly->rings[i]->npoints), sizeof(uint32_t));
+		loc += sizeof(uint32_t);
+	}
+
+	/* Add in padding if necessary to remain double aligned. */
+	if ( poly->nrings % 2 )
+	{
+		memset(loc, 0, sizeof(uint32_t));
+		loc += sizeof(uint32_t);
+	}
+
+	/* Copy in the ordinates. */
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		RTPOINTARRAY *pa = poly->rings[i];
+		size_t pasize;
+
+		if ( RTFLAGS_GET_ZM(poly->flags) != RTFLAGS_GET_ZM(pa->flags) )
+			rterror(ctx, "Dimensions mismatch in rtpoly");
+
+		pasize = pa->npoints * ptsize;
+		memcpy(loc, rt_getPoint_internal(ctx, pa, 0), pasize);
+		loc += pasize;
+	}
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rttriangle(const RTCTX *ctx, const RTTRIANGLE *triangle, uint8_t *buf)
+{
+	uint8_t *loc;
+	int ptsize;
+	size_t size;
+	int type = RTTRIANGLETYPE;
+
+	assert(triangle);
+	assert(buf);
+
+	RTDEBUGF(2, "rttriangle_to_gserialized(%p, %p) called", triangle, buf);
+
+	if ( RTFLAGS_GET_ZM(triangle->flags) != RTFLAGS_GET_ZM(triangle->points->flags) )
+		rterror(ctx, "Dimensions mismatch in rttriangle");
+
+	ptsize = ptarray_point_size(ctx, triangle->points);
+
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the npoints. */
+	memcpy(loc, &(triangle->points->npoints), sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	RTDEBUGF(3, "rttriangle_to_gserialized added npoints (%d)", triangle->points->npoints);
+
+	/* Copy in the ordinates. */
+	if ( triangle->points->npoints > 0 )
+	{
+		size = triangle->points->npoints * ptsize;
+		memcpy(loc, rt_getPoint_internal(ctx, triangle->points, 0), size);
+		loc += size;
+	}
+	RTDEBUGF(3, "rttriangle_to_gserialized copied serialized_pointlist (%d bytes)", ptsize * triangle->points->npoints);
+
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rtcircstring(const RTCTX *ctx, const RTCIRCSTRING *curve, uint8_t *buf)
+{
+	uint8_t *loc;
+	int ptsize;
+	size_t size;
+	int type = RTCIRCSTRINGTYPE;
+
+	assert(curve);
+	assert(buf);
+
+	if (RTFLAGS_GET_ZM(curve->flags) != RTFLAGS_GET_ZM(curve->points->flags))
+		rterror(ctx, "Dimensions mismatch in rtcircstring");
+
+
+	ptsize = ptarray_point_size(ctx, curve->points);
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the npoints. */
+	memcpy(loc, &curve->points->npoints, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Copy in the ordinates. */
+	if ( curve->points->npoints > 0 )
+	{
+		size = curve->points->npoints * ptsize;
+		memcpy(loc, rt_getPoint_internal(ctx, curve->points, 0), size);
+		loc += size;
+	}
+
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rtcollection(const RTCTX *ctx, const RTCOLLECTION *coll, uint8_t *buf)
+{
+	size_t subsize = 0;
+	uint8_t *loc;
+	int i;
+	int type;
+
+	assert(coll);
+	assert(buf);
+
+	type = coll->type;
+	loc = buf;
+
+	/* Write in the type. */
+	memcpy(loc, &type, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Write in the number of subgeoms. */
+	memcpy(loc, &coll->ngeoms, sizeof(uint32_t));
+	loc += sizeof(uint32_t);
+
+	/* Serialize subgeoms. */
+	for ( i=0; i<coll->ngeoms; i++ )
+	{
+		if (RTFLAGS_GET_ZM(coll->flags) != RTFLAGS_GET_ZM(coll->geoms[i]->flags))
+			rterror(ctx, "Dimensions mismatch in rtcollection");
+		subsize = gserialized_from_rtgeom_any(ctx, coll->geoms[i], loc);
+		loc += subsize;
+	}
+
+	return (size_t)(loc - buf);
+}
+
+static size_t gserialized_from_rtgeom_any(const RTCTX *ctx, const RTGEOM *geom, uint8_t *buf)
+{
+	assert(geom);
+	assert(buf);
+
+	RTDEBUGF(2, "Input type (%d) %s, hasz: %d hasm: %d",
+		geom->type, rttype_name(ctx, geom->type),
+		RTFLAGS_GET_Z(geom->flags), RTFLAGS_GET_M(geom->flags));
+	RTDEBUGF(2, "RTGEOM(%p) uint8_t(%p)", geom, buf);
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		return gserialized_from_rtpoint(ctx, (RTPOINT *)geom, buf);
+	case RTLINETYPE:
+		return gserialized_from_rtline(ctx, (RTLINE *)geom, buf);
+	case RTPOLYGONTYPE:
+		return gserialized_from_rtpoly(ctx, (RTPOLY *)geom, buf);
+	case RTTRIANGLETYPE:
+		return gserialized_from_rttriangle(ctx, (RTTRIANGLE *)geom, buf);
+	case RTCIRCSTRINGTYPE:
+		return gserialized_from_rtcircstring(ctx, (RTCIRCSTRING *)geom, buf);
+	case RTCURVEPOLYTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return gserialized_from_rtcollection(ctx, (RTCOLLECTION *)geom, buf);
+	default:
+		rterror(ctx, "Unknown geometry type: %d - %s", geom->type, rttype_name(ctx, geom->type));
+		return 0;
+	}
+	return 0;
+}
+
+static size_t gserialized_from_gbox(const RTCTX *ctx, const RTGBOX *gbox, uint8_t *buf)
+{
+	uint8_t *loc = buf;
+	float f;
+	size_t return_size;
+
+	assert(buf);
+
+	f = next_float_down(ctx, gbox->xmin);
+	memcpy(loc, &f, sizeof(float));
+	loc += sizeof(float);
+
+	f = next_float_up(ctx, gbox->xmax);
+	memcpy(loc, &f, sizeof(float));
+	loc += sizeof(float);
+
+	f = next_float_down(ctx, gbox->ymin);
+	memcpy(loc, &f, sizeof(float));
+	loc += sizeof(float);
+
+	f = next_float_up(ctx, gbox->ymax);
+	memcpy(loc, &f, sizeof(float));
+	loc += sizeof(float);
+
+	if ( RTFLAGS_GET_GEODETIC(gbox->flags) )
+	{
+		f = next_float_down(ctx, gbox->zmin);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+
+		f = next_float_up(ctx, gbox->zmax);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+
+		return_size = (size_t)(loc - buf);
+		RTDEBUGF(4, "returning size %d", return_size);
+		return return_size;
+	}
+
+	if ( RTFLAGS_GET_Z(gbox->flags) )
+	{
+		f = next_float_down(ctx, gbox->zmin);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+
+		f = next_float_up(ctx, gbox->zmax);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+
+	}
+
+	if ( RTFLAGS_GET_M(gbox->flags) )
+	{
+		f = next_float_down(ctx, gbox->mmin);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+
+		f = next_float_up(ctx, gbox->mmax);
+		memcpy(loc, &f, sizeof(float));
+		loc += sizeof(float);
+	}
+	return_size = (size_t)(loc - buf);
+	RTDEBUGF(4, "returning size %d", return_size);
+	return return_size;
+}
+
+/* Public function */
+
+GSERIALIZED* gserialized_from_rtgeom(const RTCTX *ctx, RTGEOM *geom, int is_geodetic, size_t *size)
+{
+	size_t expected_size = 0;
+	size_t return_size = 0;
+	uint8_t *serialized = NULL;
+	uint8_t *ptr = NULL;
+	GSERIALIZED *g = NULL;
+	assert(geom);
+
+	/*
+	** See if we need a bounding box, add one if we don't have one.
+	*/
+	if ( (! geom->bbox) && rtgeom_needs_bbox(ctx, geom) && (!rtgeom_is_empty(ctx, geom)) )
+	{
+		rtgeom_add_bbox(ctx, geom);
+	}
+	
+	/*
+	** Harmonize the flags to the state of the rtgeom 
+	*/
+	if ( geom->bbox )
+		RTFLAGS_SET_BBOX(geom->flags, 1);
+
+	/* Set up the uint8_t buffer into which we are going to write the serialized geometry. */
+	expected_size = gserialized_from_rtgeom_size(ctx, geom);
+	serialized = rtalloc(ctx, expected_size);
+	ptr = serialized;
+
+	/* Move past size, srid and flags. */
+	ptr += 8;
+
+	/* Write in the serialized form of the gbox, if necessary. */
+	if ( geom->bbox )
+		ptr += gserialized_from_gbox(ctx, geom->bbox, ptr);
+
+	/* Write in the serialized form of the geometry. */
+	ptr += gserialized_from_rtgeom_any(ctx, geom, ptr);
+
+	/* Calculate size as returned by data processing functions. */
+	return_size = ptr - serialized;
+
+	if ( expected_size != return_size ) /* Uh oh! */
+	{
+		rterror(ctx, "Return size (%d) not equal to expected size (%d)!", return_size, expected_size);
+		return NULL;
+	}
+
+	if ( size ) /* Return the output size to the caller if necessary. */
+		*size = return_size;
+
+	g = (GSERIALIZED*)serialized;
+
+	/*
+	** We are aping PgSQL code here, PostGIS code should use
+	** VARSIZE to set this for real.
+	*/
+	g->size = return_size << 2;
+
+	/* Set the SRID! */
+	gserialized_set_srid(ctx, g, geom->srid);
+
+	g->flags = geom->flags;
+
+	return g;
+}
+
+/***********************************************************************
+* De-serialize GSERIALIZED into an RTGEOM.
+*/
+
+static RTGEOM* rtgeom_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size);
+
+static RTPOINT* rtpoint_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint8_t *start_ptr = data_ptr;
+	RTPOINT *point;
+	uint32_t npoints = 0;
+
+	assert(data_ptr);
+
+	point = (RTPOINT*)rtalloc(ctx, sizeof(RTPOINT));
+	point->srid = SRID_UNKNOWN; /* Default */
+	point->bbox = NULL;
+	point->type = RTPOINTTYPE;
+	point->flags = g_flags;
+
+	data_ptr += 4; /* Skip past the type. */
+	npoints = rt_get_uint32_t(ctx, data_ptr); /* Zero => empty geometry */
+	data_ptr += 4; /* Skip past the npoints. */
+
+	if ( npoints > 0 )
+		point->point = ptarray_construct_reference_data(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), 1, data_ptr);
+	else
+		point->point = ptarray_construct(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), 0); /* Empty point */
+
+	data_ptr += npoints * RTFLAGS_NDIMS(g_flags) * sizeof(double);
+
+	if ( g_size )
+		*g_size = data_ptr - start_ptr;
+
+	return point;
+}
+
+static RTLINE* rtline_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint8_t *start_ptr = data_ptr;
+	RTLINE *line;
+	uint32_t npoints = 0;
+
+	assert(data_ptr);
+
+	line = (RTLINE*)rtalloc(ctx, sizeof(RTLINE));
+	line->srid = SRID_UNKNOWN; /* Default */
+	line->bbox = NULL;
+	line->type = RTLINETYPE;
+	line->flags = g_flags;
+
+	data_ptr += 4; /* Skip past the type. */
+	npoints = rt_get_uint32_t(ctx, data_ptr); /* Zero => empty geometry */
+	data_ptr += 4; /* Skip past the npoints. */
+
+	if ( npoints > 0 )
+		line->points = ptarray_construct_reference_data(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), npoints, data_ptr);
+		
+	else
+		line->points = ptarray_construct(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), 0); /* Empty linestring */
+
+	data_ptr += RTFLAGS_NDIMS(g_flags) * npoints * sizeof(double);
+
+	if ( g_size )
+		*g_size = data_ptr - start_ptr;
+
+	return line;
+}
+
+static RTPOLY* rtpoly_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint8_t *start_ptr = data_ptr;
+	RTPOLY *poly;
+	uint8_t *ordinate_ptr;
+	uint32_t nrings = 0;
+	int i = 0;
+
+	assert(data_ptr);
+
+	poly = (RTPOLY*)rtalloc(ctx, sizeof(RTPOLY));
+	poly->srid = SRID_UNKNOWN; /* Default */
+	poly->bbox = NULL;
+	poly->type = RTPOLYGONTYPE;
+	poly->flags = g_flags;
+
+	data_ptr += 4; /* Skip past the polygontype. */
+	nrings = rt_get_uint32_t(ctx, data_ptr); /* Zero => empty geometry */
+	poly->nrings = nrings;
+	RTDEBUGF(4, "nrings = %d", nrings);
+	data_ptr += 4; /* Skip past the nrings. */
+
+	ordinate_ptr = data_ptr; /* Start the ordinate pointer. */
+	if ( nrings > 0)
+	{
+		poly->rings = (RTPOINTARRAY**)rtalloc(ctx,  sizeof(RTPOINTARRAY*) * nrings );
+		ordinate_ptr += nrings * 4; /* Move past all the npoints values. */
+		if ( nrings % 2 ) /* If there is padding, move past that too. */
+			ordinate_ptr += 4;
+	}
+	else /* Empty polygon */
+	{
+		poly->rings = NULL;
+	}
+
+	for ( i = 0; i < nrings; i++ )
+	{
+		uint32_t npoints = 0;
+
+		/* Read in the number of points. */
+		npoints = rt_get_uint32_t(ctx, data_ptr);
+		data_ptr += 4;
+
+		/* Make a point array for the ring, and move the ordinate pointer past the ring ordinates. */
+		poly->rings[i] = ptarray_construct_reference_data(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), npoints, ordinate_ptr);
+		
+		ordinate_ptr += sizeof(double) * RTFLAGS_NDIMS(g_flags) * npoints;
+	}
+
+	if ( g_size )
+		*g_size = ordinate_ptr - start_ptr;
+
+	return poly;
+}
+
+static RTTRIANGLE* rttriangle_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint8_t *start_ptr = data_ptr;
+	RTTRIANGLE *triangle;
+	uint32_t npoints = 0;
+
+	assert(data_ptr);
+
+	triangle = (RTTRIANGLE*)rtalloc(ctx, sizeof(RTTRIANGLE));
+	triangle->srid = SRID_UNKNOWN; /* Default */
+	triangle->bbox = NULL;
+	triangle->type = RTTRIANGLETYPE;
+	triangle->flags = g_flags;
+
+	data_ptr += 4; /* Skip past the type. */
+	npoints = rt_get_uint32_t(ctx, data_ptr); /* Zero => empty geometry */
+	data_ptr += 4; /* Skip past the npoints. */
+
+	if ( npoints > 0 )
+		triangle->points = ptarray_construct_reference_data(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), npoints, data_ptr);		
+	else
+		triangle->points = ptarray_construct(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), 0); /* Empty triangle */
+
+	data_ptr += RTFLAGS_NDIMS(g_flags) * npoints * sizeof(double);
+
+	if ( g_size )
+		*g_size = data_ptr - start_ptr;
+
+	return triangle;
+}
+
+static RTCIRCSTRING* rtcircstring_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint8_t *start_ptr = data_ptr;
+	RTCIRCSTRING *circstring;
+	uint32_t npoints = 0;
+
+	assert(data_ptr);
+
+	circstring = (RTCIRCSTRING*)rtalloc(ctx, sizeof(RTCIRCSTRING));
+	circstring->srid = SRID_UNKNOWN; /* Default */
+	circstring->bbox = NULL;
+	circstring->type = RTCIRCSTRINGTYPE;
+	circstring->flags = g_flags;
+
+	data_ptr += 4; /* Skip past the circstringtype. */
+	npoints = rt_get_uint32_t(ctx, data_ptr); /* Zero => empty geometry */
+	data_ptr += 4; /* Skip past the npoints. */
+
+	if ( npoints > 0 )
+		circstring->points = ptarray_construct_reference_data(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), npoints, data_ptr);		
+	else
+		circstring->points = ptarray_construct(ctx, RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), 0); /* Empty circularstring */
+
+	data_ptr += RTFLAGS_NDIMS(g_flags) * npoints * sizeof(double);
+
+	if ( g_size )
+		*g_size = data_ptr - start_ptr;
+
+	return circstring;
+}
+
+static RTCOLLECTION* rtcollection_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint32_t type;
+	uint8_t *start_ptr = data_ptr;
+	RTCOLLECTION *collection;
+	uint32_t ngeoms = 0;
+	int i = 0;
+
+	assert(data_ptr);
+
+	type = rt_get_uint32_t(ctx, data_ptr);
+	data_ptr += 4; /* Skip past the type. */
+
+	collection = (RTCOLLECTION*)rtalloc(ctx, sizeof(RTCOLLECTION));
+	collection->srid = SRID_UNKNOWN; /* Default */
+	collection->bbox = NULL;
+	collection->type = type;
+	collection->flags = g_flags;
+
+	ngeoms = rt_get_uint32_t(ctx, data_ptr);
+	collection->ngeoms = ngeoms; /* Zero => empty geometry */
+	data_ptr += 4; /* Skip past the ngeoms. */
+
+	if ( ngeoms > 0 )
+		collection->geoms = rtalloc(ctx, sizeof(RTGEOM*) * ngeoms);
+	else
+		collection->geoms = NULL;
+
+	/* Sub-geometries are never de-serialized with boxes (#1254) */
+	RTFLAGS_SET_BBOX(g_flags, 0);
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		uint32_t subtype = rt_get_uint32_t(ctx, data_ptr);
+		size_t subsize = 0;
+
+		if ( ! rtcollection_allows_subtype(ctx, type, subtype) )
+		{
+			rterror(ctx, "Invalid subtype (%s) for collection type (%s)", rttype_name(ctx, subtype), rttype_name(ctx, type));
+			rtfree(ctx, collection);
+			return NULL;
+		}
+		collection->geoms[i] = rtgeom_from_gserialized_buffer(ctx, data_ptr, g_flags, &subsize);
+		data_ptr += subsize;
+	}
+
+	if ( g_size )
+		*g_size = data_ptr - start_ptr;
+
+	return collection;
+}
+
+RTGEOM* rtgeom_from_gserialized_buffer(const RTCTX *ctx, uint8_t *data_ptr, uint8_t g_flags, size_t *g_size)
+{
+	uint32_t type;
+
+	assert(data_ptr);
+
+	type = rt_get_uint32_t(ctx, data_ptr);
+
+	RTDEBUGF(2, "Got type %d (%s), hasz=%d hasm=%d geodetic=%d hasbox=%d", type, rttype_name(ctx, type),
+		RTFLAGS_GET_Z(g_flags), RTFLAGS_GET_M(g_flags), RTFLAGS_GET_GEODETIC(g_flags), RTFLAGS_GET_BBOX(g_flags));
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		return (RTGEOM *)rtpoint_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	case RTLINETYPE:
+		return (RTGEOM *)rtline_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	case RTCIRCSTRINGTYPE:
+		return (RTGEOM *)rtcircstring_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	case RTPOLYGONTYPE:
+		return (RTGEOM *)rtpoly_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	case RTTRIANGLETYPE:
+		return (RTGEOM *)rttriangle_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return (RTGEOM *)rtcollection_from_gserialized_buffer(ctx, data_ptr, g_flags, g_size);
+	default:
+		rterror(ctx, "Unknown geometry type: %d - %s", type, rttype_name(ctx, type));
+		return NULL;
+	}
+}
+
+RTGEOM* rtgeom_from_gserialized(const RTCTX *ctx, const GSERIALIZED *g)
+{
+	uint8_t g_flags = 0;
+	int32_t g_srid = 0;
+	uint32_t g_type = 0;
+	uint8_t *data_ptr = NULL;
+	RTGEOM *rtgeom = NULL;
+	RTGBOX bbox;
+	size_t g_size = 0;
+
+	assert(g);
+
+	g_srid = gserialized_get_srid(ctx, g);
+	g_flags = g->flags;
+	g_type = gserialized_get_type(ctx, g);
+	RTDEBUGF(4, "Got type %d (%s), srid=%d", g_type, rttype_name(ctx, g_type), g_srid);
+
+	data_ptr = (uint8_t*)g->data;
+	if ( RTFLAGS_GET_BBOX(g_flags) )
+		data_ptr += gbox_serialized_size(ctx, g_flags);
+
+	rtgeom = rtgeom_from_gserialized_buffer(ctx, data_ptr, g_flags, &g_size);
+
+	if ( ! rtgeom ) 
+		rterror(ctx, "rtgeom_from_gserialized: unable create geometry"); /* Ooops! */
+
+	rtgeom->type = g_type;
+	rtgeom->flags = g_flags;
+
+	if ( gserialized_read_gbox_p(ctx, g, &bbox) == RT_SUCCESS )
+	{
+		rtgeom->bbox = gbox_copy(ctx, &bbox);
+	}
+	else if ( rtgeom_needs_bbox(ctx, rtgeom) && (rtgeom_calculate_gbox(ctx, rtgeom, &bbox) == RT_SUCCESS) )
+	{
+		rtgeom->bbox = gbox_copy(ctx, &bbox);
+	}
+	else
+	{
+		rtgeom->bbox = NULL;
+	}
+
+	rtgeom_set_srid(ctx, rtgeom, g_srid);
+
+	return rtgeom;
+}
+
diff --git a/src/g_util.c b/src/g_util.c
new file mode 100644
index 0000000..c26d47f
--- /dev/null
+++ b/src/g_util.c
@@ -0,0 +1,232 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include <ctype.h>
+
+#include "librtgeom_internal.h"
+
+/* Structure for the type array */
+struct geomtype_struct
+{
+	char *typename;
+	int type;
+	int z;
+	int m;
+};
+
+/* Type array. Note that the order of this array is important in
+   that any typename in the list must *NOT* occur within an entry
+   before it. Otherwise if we search for "POINT" at the top of the
+   list we would also match MULTIPOINT, for example. */
+
+struct geomtype_struct geomtype_struct_array[] =
+{
+	{ "GEOMETRYCOLLECTIONZM", RTCOLLECTIONTYPE, 1, 1 },
+	{ "GEOMETRYCOLLECTIONZ", RTCOLLECTIONTYPE, 1, 0 },
+	{ "GEOMETRYCOLLECTIONM", RTCOLLECTIONTYPE, 0, 1 },
+	{ "GEOMETRYCOLLECTION", RTCOLLECTIONTYPE, 0, 0 },
+
+	{ "GEOMETRYZM", 0, 1, 1 },
+	{ "GEOMETRYZ", 0, 1, 0 },
+	{ "GEOMETRYM", 0, 0, 1 },
+	{ "GEOMETRY", 0, 0, 0 },
+
+	{ "POLYHEDRALSURFACEZM", RTPOLYHEDRALSURFACETYPE, 1, 1 },
+	{ "POLYHEDRALSURFACEZ", RTPOLYHEDRALSURFACETYPE, 1, 0 },
+	{ "POLYHEDRALSURFACEM", RTPOLYHEDRALSURFACETYPE, 0, 1 },
+	{ "POLYHEDRALSURFACE", RTPOLYHEDRALSURFACETYPE, 0, 0 },
+
+	{ "TINZM", RTTINTYPE, 1, 1 },
+	{ "TINZ", RTTINTYPE, 1, 0 },
+	{ "TINM", RTTINTYPE, 0, 1 },
+	{ "TIN", RTTINTYPE, 0, 0 },
+
+	{ "CIRCULARSTRINGZM", RTCIRCSTRINGTYPE, 1, 1 },
+	{ "CIRCULARSTRINGZ", RTCIRCSTRINGTYPE, 1, 0 },
+	{ "CIRCULARSTRINGM", RTCIRCSTRINGTYPE, 0, 1 },
+	{ "CIRCULARSTRING", RTCIRCSTRINGTYPE, 0, 0 },
+
+	{ "COMPOUNDCURVEZM", RTCOMPOUNDTYPE, 1, 1 },
+	{ "COMPOUNDCURVEZ", RTCOMPOUNDTYPE, 1, 0 },
+	{ "COMPOUNDCURVEM", RTCOMPOUNDTYPE, 0, 1 },
+	{ "COMPOUNDCURVE", RTCOMPOUNDTYPE, 0, 0 },
+
+	{ "CURVEPOLYGONZM", RTCURVEPOLYTYPE, 1, 1 },
+	{ "CURVEPOLYGONZ", RTCURVEPOLYTYPE, 1, 0 },
+	{ "CURVEPOLYGONM", RTCURVEPOLYTYPE, 0, 1 },
+	{ "CURVEPOLYGON", RTCURVEPOLYTYPE, 0, 0 },
+
+	{ "MULTICURVEZM", RTMULTICURVETYPE, 1, 1 },
+	{ "MULTICURVEZ", RTMULTICURVETYPE, 1, 0 },
+	{ "MULTICURVEM", RTMULTICURVETYPE, 0, 1 },
+	{ "MULTICURVE", RTMULTICURVETYPE, 0, 0 },
+
+	{ "MULTISURFACEZM", RTMULTISURFACETYPE, 1, 1 },
+	{ "MULTISURFACEZ", RTMULTISURFACETYPE, 1, 0 },
+	{ "MULTISURFACEM", RTMULTISURFACETYPE, 0, 1 },
+	{ "MULTISURFACE", RTMULTISURFACETYPE, 0, 0 },
+
+	{ "MULTILINESTRINGZM", RTMULTILINETYPE, 1, 1 },
+	{ "MULTILINESTRINGZ", RTMULTILINETYPE, 1, 0 },
+	{ "MULTILINESTRINGM", RTMULTILINETYPE, 0, 1 },
+	{ "MULTILINESTRING", RTMULTILINETYPE, 0, 0 },
+
+	{ "MULTIPOLYGONZM", RTMULTIPOLYGONTYPE, 1, 1 },
+	{ "MULTIPOLYGONZ", RTMULTIPOLYGONTYPE, 1, 0 },
+	{ "MULTIPOLYGONM", RTMULTIPOLYGONTYPE, 0, 1 },
+	{ "MULTIPOLYGON", RTMULTIPOLYGONTYPE, 0, 0 },
+
+	{ "MULTIPOINTZM", RTMULTIPOINTTYPE, 1, 1 },
+	{ "MULTIPOINTZ", RTMULTIPOINTTYPE, 1, 0 },
+	{ "MULTIPOINTM", RTMULTIPOINTTYPE, 0, 1 },
+	{ "MULTIPOINT", RTMULTIPOINTTYPE, 0, 0 },
+
+	{ "LINESTRINGZM", RTLINETYPE, 1, 1 },
+	{ "LINESTRINGZ", RTLINETYPE, 1, 0 },
+	{ "LINESTRINGM", RTLINETYPE, 0, 1 },
+	{ "LINESTRING", RTLINETYPE, 0, 0 },
+
+	{ "TRIANGLEZM", RTTRIANGLETYPE, 1, 1 },
+	{ "TRIANGLEZ", RTTRIANGLETYPE, 1, 0 },
+	{ "TRIANGLEM", RTTRIANGLETYPE, 0, 1 },
+	{ "TRIANGLE", RTTRIANGLETYPE, 0, 0 },
+
+	{ "POLYGONZM", RTPOLYGONTYPE, 1, 1 },
+	{ "POLYGONZ", RTPOLYGONTYPE, 1, 0 },
+	{ "POLYGONM", RTPOLYGONTYPE, 0, 1 },
+	{ "POLYGON", RTPOLYGONTYPE, 0, 0 },
+
+	{ "POINTZM", RTPOINTTYPE, 1, 1 },
+	{ "POINTZ", RTPOINTTYPE, 1, 0 },
+	{ "POINTM", RTPOINTTYPE, 0, 1 },
+	{ "POINT", RTPOINTTYPE, 0, 0 }
+
+};
+#define GEOMTYPE_STRUCT_ARRAY_LEN (sizeof geomtype_struct_array/sizeof(struct geomtype_struct))
+
+/*
+* We use a very simple upper case mapper here, because the system toupper() function
+* is locale dependent and may have trouble mapping lower case strings to the upper
+* case ones we expect (see, the "Turkisk I", http://www.i18nguy.com/unicode/turkish-i18n.html)
+* We could also count on PgSQL sending us *lower* case inputs, as it seems to do that
+* regardless of the case the user provides for the type arguments.
+*/
+const char dumb_upper_map[128] = "................................................0123456789.......ABCDEFGHIJKLMNOPQRSTUVWXYZ......ABCDEFGHIJKLMNOPQRSTUVWXYZ.....";
+
+static char dump_toupper(const RTCTX *ctx, int in)
+{
+	if ( in < 0 || in > 127 ) 
+		return '.';
+	return dumb_upper_map[in];
+}
+
+uint8_t gflags(const RTCTX *ctx, int hasz, int hasm, int geodetic)
+{
+	uint8_t flags = 0;
+	if ( hasz )
+		RTFLAGS_SET_Z(flags, 1);
+	if ( hasm )
+		RTFLAGS_SET_M(flags, 1);
+	if ( geodetic )
+		RTFLAGS_SET_GEODETIC(flags, 1);
+	return flags;
+}
+
+/**
+* Calculate type integer and dimensional flags from string input.
+* Case insensitive, and insensitive to spaces at front and back.
+* Type == 0 in the case of the string "GEOMETRY" or "GEOGRAPHY".
+* Return RT_SUCCESS for success.
+*/
+int geometry_type_from_string(const RTCTX *ctx, const char *str, uint8_t *type, int *z, int *m)
+{
+	char *tmpstr;
+	int tmpstartpos, tmpendpos;
+	int i;
+
+	assert(str);
+	assert(type);
+	assert(z);
+	assert(m);
+
+	/* Initialize. */
+	*type = 0;
+	*z = 0;
+	*m = 0;
+
+	/* Locate any leading/trailing spaces */
+	tmpstartpos = 0;
+	for (i = 0; i < strlen(str); i++)
+	{
+		if (str[i] != ' ')
+		{
+			tmpstartpos = i;
+			break;
+		}
+	}
+
+	tmpendpos = strlen(str) - 1;
+	for (i = strlen(str) - 1; i >= 0; i--)
+	{
+		if (str[i] != ' ')
+		{
+			tmpendpos = i;
+			break;
+		}
+	}
+
+	/* Copy and convert to upper case for comparison */
+	tmpstr = rtalloc(ctx, tmpendpos - tmpstartpos + 2);
+	for (i = tmpstartpos; i <= tmpendpos; i++)
+		tmpstr[i - tmpstartpos] = dump_toupper(ctx, str[i]);
+
+	/* Add NULL to terminate */
+	tmpstr[i - tmpstartpos] = '\0';
+
+	/* Now check for the type */
+	for (i = 0; i < GEOMTYPE_STRUCT_ARRAY_LEN; i++)
+	{
+		if (!strcmp(tmpstr, geomtype_struct_array[i].typename))
+		{
+			*type = geomtype_struct_array[i].type;
+			*z = geomtype_struct_array[i].z;
+			*m = geomtype_struct_array[i].m;
+
+			rtfree(ctx, tmpstr);
+
+			return RT_SUCCESS;
+		}
+
+	}
+
+	rtfree(ctx, tmpstr);
+
+	return RT_FAILURE;
+}
+
+
+
+
diff --git a/src/librtgeom.h.in b/src/librtgeom.h.in
new file mode 100644
index 0000000..e9e9378
--- /dev/null
+++ b/src/librtgeom.h.in
@@ -0,0 +1,2186 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Sandro Santilli <strk at keybit.net>
+ * Copyright 2011 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright 2007-2008 Mark Cave-Ayland
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#ifndef _LIBRTGEOM_H
+#define _LIBRTGEOM_H 1
+
+#define GEOS_USE_ONLY_R_API 1
+#include <geos_c.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdint.h>
+
+/**
+* @file librtgeom.h
+*
+* This library is the generic geometry handling section of PostGIS. The geometry
+* objects, constructors, destructors, and a set of spatial processing functions,
+* are implemented here.
+*
+* The library is designed for use in non-PostGIS applications if necessary. The
+* units tests at cunit/cu_tester.c and the loader/dumper programs at
+* ../loader/shp2pgsql.c are examples of non-PostGIS applications using librtgeom.
+*
+* Programs using this library can install their custom memory managers and error
+* handlers by calling the rtgeom_set_handlers(const RTCTX *ctx) function, otherwise the default 
+* ones will be used.
+*/
+
+/**
+* Return types for functions with status returns.
+*/
+#define RT_TRUE 1
+#define RT_FALSE 0
+#define RT_UNKNOWN 2
+#define RT_FAILURE 0
+#define RT_SUCCESS 1
+
+/**
+* RTRTTYPE numbers
+*/
+#define	RTPOINTTYPE                1
+#define	RTLINETYPE                 2
+#define	RTPOLYGONTYPE              3
+#define	RTMULTIPOINTTYPE           4
+#define	RTMULTILINETYPE            5
+#define	RTMULTIPOLYGONTYPE         6
+#define	RTCOLLECTIONTYPE           7
+#define RTCIRCSTRINGTYPE           8
+#define RTCOMPOUNDTYPE             9
+#define RTCURVEPOLYTYPE           10
+#define RTMULTICURVETYPE          11
+#define RTMULTISURFACETYPE        12
+#define RTPOLYHEDRALSURFACETYPE   13
+#define RTTRIANGLETYPE            14
+#define RTTINTYPE                 15
+
+#define RTNUMTYPES                16
+
+/**
+* Flags applied in EWKB to indicate Z/M dimensions and
+* presence/absence of SRID and bounding boxes
+*/
+#define RTWKBZOFFSET  0x80000000
+#define RTWKBMOFFSET  0x40000000
+#define RTWKBSRIDFLAG 0x20000000
+#define RTWKBBBOXFLAG 0x10000000
+
+/** Ordinate names */
+typedef enum RTORD_T {
+  RTORD_X = 0,
+  RTORD_Y = 1,
+  RTORD_Z = 2,
+  RTORD_M = 3
+} RTORD;
+
+/**
+* Macros for manipulating the 'flags' byte. A uint8_t used as follows: 
+* ---RGBMZ
+* Three unused bits, followed by ReadOnly, Geodetic, HasBBox, HasM and HasZ flags.
+*/
+#define RTFLAGS_GET_Z(flags) ((flags) & 0x01)
+#define RTFLAGS_GET_M(flags) (((flags) & 0x02)>>1)
+#define RTFLAGS_GET_BBOX(flags) (((flags) & 0x04)>>2)
+#define RTFLAGS_GET_GEODETIC(flags) (((flags) & 0x08)>>3)
+#define RTFLAGS_GET_READONLY(flags) (((flags) & 0x10)>>4)
+#define RTFLAGS_GET_SOLID(flags) (((flags) & 0x20)>>5)
+#define RTFLAGS_SET_Z(flags, value) ((flags) = (value) ? ((flags) | 0x01) : ((flags) & 0xFE))
+#define RTFLAGS_SET_M(flags, value) ((flags) = (value) ? ((flags) | 0x02) : ((flags) & 0xFD))
+#define RTFLAGS_SET_BBOX(flags, value) ((flags) = (value) ? ((flags) | 0x04) : ((flags) & 0xFB))
+#define RTFLAGS_SET_GEODETIC(flags, value) ((flags) = (value) ? ((flags) | 0x08) : ((flags) & 0xF7))
+#define RTFLAGS_SET_READONLY(flags, value) ((flags) = (value) ? ((flags) | 0x10) : ((flags) & 0xEF))
+#define RTFLAGS_SET_SOLID(flags, value) ((flags) = (value) ? ((flags) | 0x20) : ((flags) & 0xDF))
+#define RTFLAGS_NDIMS(flags) (2 + RTFLAGS_GET_Z(flags) + RTFLAGS_GET_M(flags))
+#define RTFLAGS_GET_ZM(flags) (RTFLAGS_GET_M(flags) + RTFLAGS_GET_Z(flags) * 2)
+#define RTFLAGS_NDIMS_BOX(flags) (RTFLAGS_GET_GEODETIC(flags) ? 3 : RTFLAGS_NDIMS(flags))
+
+/**
+* Macros for manipulating the 'typemod' int. An int32_t used as follows:
+* Plus/minus = Top bit.
+* Spare bits = Next 2 bits.
+* SRID = Next 21 bits.
+* RTTYPE = Next 6 bits.
+* ZM Flags = Bottom 2 bits.
+*/
+
+#define RTTYPMOD_GET_SRID(typmod) ((((typmod) & 0x1FFFFF00)<<3)>>11)
+#define RTTYPMOD_SET_SRID(typmod, srid) ((typmod) = (((typmod) & 0xE00000FF) | ((srid & 0x001FFFFF)<<8)))
+#define RTTYPMOD_GET_TYPE(typmod) ((typmod & 0x000000FC)>>2)
+#define RTTYPMOD_SET_TYPE(typmod, type) ((typmod) = (typmod & 0xFFFFFF03) | ((type & 0x0000003F)<<2))
+#define RTTYPMOD_GET_Z(typmod) ((typmod & 0x00000002)>>1)
+#define RTTYPMOD_SET_Z(typmod) ((typmod) = typmod | 0x00000002)
+#define RTTYPMOD_GET_M(typmod) (typmod & 0x00000001)
+#define RTTYPMOD_SET_M(typmod) ((typmod) = typmod | 0x00000001)
+#define RTTYPMOD_GET_NDIMS(typmod) (2+RTTYPMOD_GET_Z(typmod)+RTTYPMOD_GET_M(typmod))
+
+/**
+* Maximum allowed SRID value in serialized geometry.
+* Currently we are using 21 bits (2097152) of storage for SRID.
+*/
+#define SRID_MAXIMUM @SRID_MAX@
+
+/**
+ * Maximum valid SRID value for the user
+ * We reserve 1000 values for internal use
+ */
+#define SRID_USER_MAXIMUM @SRID_USR_MAX@
+
+/** Unknown SRID value */
+#define SRID_UNKNOWN 0
+#define SRID_IS_UNKNOWN(x) ((int)x<=0)
+
+/* 
+** EPSG WGS84 geographics, OGC standard default SRS, better be in 
+** the SPATIAL_REF_SYS table!
+*/
+#define SRID_DEFAULT 4326
+
+#ifndef __GNUC__
+# define __attribute__(x)
+#endif
+
+/**
+ * RT library context
+ */
+typedef struct RTCTX_T RTCTX;
+
+/**
+ * Global functions for memory/logging handlers.
+ */
+typedef void* (*rtallocator)(size_t size);
+typedef void* (*rtreallocator)(void *mem, size_t size);
+typedef void (*rtfreeor)(void* mem);
+typedef void (*rtreporter)(const char* fmt, va_list ap, void *arg)
+  __attribute__ (( format(printf, 1, 0) ));
+typedef void (*rtdebuglogger)(int level, const char* fmt, va_list ap, void *arg)
+  __attribute__ (( format(printf, 2,0) ));
+
+
+/**
+ * Initialize the library with custom memory management functions
+ * you want your
+ * application to use.
+ * @ingroup system
+ */
+RTCTX *rtgeom_init(rtallocator allocator,
+                   rtreallocator reallocator,
+                   rtfreeor freeor);
+
+/** Return rtgeom version string (not to be freed) */
+const char* rtgeom_version(void);
+
+
+/**
+* Return a valid SRID from an arbitrary integer
+* Raises a notice if what comes out is different from
+* what went in.
+* Raises an error if SRID value is out of bounds.
+*/
+extern int clamp_srid(const RTCTX *ctx, int srid);
+
+/* Raise an rterror if srids do not match */
+void error_if_srid_mismatch(const RTCTX *ctx, int srid1, int srid2);
+
+/**
+ * This functions are called by programs which want to set up
+ * custom handling for error reporting
+ */
+extern void rtgeom_set_error_logger(RTCTX *ctx,
+          rtreporter logger, void *arg);
+extern void rtgeom_set_notice_logger(RTCTX *ctx,
+          rtreporter logger, void *arg);
+extern void rtgeom_set_debug_logger(RTCTX *ctx,
+          rtdebuglogger logger, void *arg);
+
+/**
+ * Request interruption of any running code
+ *
+ * Safe for use from signal handlers
+ *
+ * Interrupted code will (as soon as it finds out
+ * to be interrupted) cleanup and return as soon as possible.
+ *
+ * The return value from interrupted code is undefined,
+ * it is the caller responsibility to not take it in consideration.
+ * 
+ */
+extern void rtgeom_request_interrupt(const RTCTX *ctx);
+
+/**
+ * Cancel any interruption request
+ */
+extern void rtgeom_cancel_interrupt(const RTCTX *ctx);
+
+/**
+ * Install a callback to be called periodically during
+ * algorithm execution. Mostly only needed on WIN32 to
+ * dispatch queued signals.
+ *
+ * The callback is invoked before checking for interrupt
+ * being requested, so you can request interruption from
+ * the callback, if you want (see rtgeom_request_interrupt).
+ *
+ */
+typedef void (rtinterrupt_callback)();
+extern rtinterrupt_callback *rtgeom_register_interrupt_callback(const RTCTX *ctx, rtinterrupt_callback *);
+
+/******************************************************************/
+
+typedef struct {
+	double afac, bfac, cfac, dfac, efac, ffac, gfac, hfac, ifac, xoff, yoff, zoff;
+} RTAFFINE;
+
+/******************************************************************/
+
+typedef struct
+{
+	double xmin, ymin, zmin;
+	double xmax, ymax, zmax;
+	int32_t srid;
+}
+BOX3D;
+
+/******************************************************************
+* RTGBOX structure. 
+* We include the flags (information about dimensinality), 
+* so we don't have to constantly pass them
+* into functions that use the RTGBOX.
+*/
+typedef struct
+{
+	uint8_t flags;
+	double xmin;
+	double xmax;
+	double ymin;
+	double ymax;
+	double zmin;
+	double zmax;
+	double mmin;
+	double mmax;
+} RTGBOX;
+
+
+/******************************************************************
+* SPHEROID
+*
+*  Standard definition of an ellipsoid (what wkt calls a spheroid)
+*    f = (a-b)/a
+*    e_sq = (a*a - b*b)/(a*a)
+*    b = a - fa
+*/
+typedef struct
+{
+	double	a;	/* semimajor axis */
+	double	b; 	/* semiminor axis b = (a - fa) */
+	double	f;	/* flattening f = (a-b)/a */
+	double	e;	/* eccentricity (first) */
+	double	e_sq;	/* eccentricity squared (first) e_sq = (a*a-b*b)/(a*a) */
+	double  radius;  /* spherical average radius = (2*a+b)/3 */
+	char	name[20];  /* name of ellipse */
+}
+SPHEROID;
+
+/******************************************************************
+* RTPOINT2D, POINT3D, RTPOINT3DM, RTPOINT4D
+*/
+typedef struct
+{
+	double x, y;
+}
+RTPOINT2D;
+
+typedef struct
+{
+	double x, y, z;
+}
+RTPOINT3DZ;
+
+typedef struct
+{
+	double x, y, z;
+}
+POINT3D;
+
+typedef struct
+{
+	double x, y, m;
+}
+RTPOINT3DM;
+
+typedef struct
+{
+	double x, y, z, m;
+}
+RTPOINT4D;
+
+/******************************************************************
+*  RTPOINTARRAY
+*  Point array abstracts a lot of the complexity of points and point lists.
+*  It handles 2d/3d translation
+*    (2d points converted to 3d will have z=0 or NaN)
+*  DO NOT MIX 2D and 3D POINTS! EVERYTHING* is either one or the other
+*/
+typedef struct
+{
+	/* Array of POINT 2D, 3D or 4D, possibly missaligned. */
+	uint8_t *serialized_pointlist;
+
+	/* Use RTFLAGS_* macros to handle */
+	uint8_t  flags;
+
+	int npoints;   /* how many points we are currently storing */
+	int maxpoints; /* how many points we have space for in serialized_pointlist */
+}
+RTPOINTARRAY;
+
+/******************************************************************
+* GSERIALIZED
+*/
+typedef struct
+{
+	uint32_t size; /* For PgSQL use only, use VAR* macros to manipulate. */
+	uint8_t srid[3]; /* 24 bits of SRID */
+	uint8_t flags; /* HasZ, HasM, HasBBox, IsGeodetic, IsReadOnly */
+	uint8_t data[1]; /* See gserialized.txt */
+} GSERIALIZED;
+
+
+/******************************************************************
+* RTGEOM (any geometry type)
+*
+* Abstract type, note that 'type', 'bbox' and 'srid' are available in 
+* all geometry variants.
+*/
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	void *data;
+}
+RTGEOM;
+
+/* RTPOINTYPE */
+typedef struct
+{
+	uint8_t type; /* RTPOINTTYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	RTPOINTARRAY *point;  /* hide 2d/3d (this will be an array of 1 point) */
+}
+RTPOINT; /* "light-weight point" */
+
+/* RTLINETYPE */
+typedef struct
+{
+	uint8_t type; /* RTLINETYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	RTPOINTARRAY *points; /* array of POINT3D */
+}
+RTLINE; /* "light-weight line" */
+
+/* TRIANGLE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	RTPOINTARRAY *points;
+}
+RTTRIANGLE;
+
+/* RTCIRCSTRINGTYPE */
+typedef struct
+{
+	uint8_t type; /* RTCIRCSTRINGTYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	RTPOINTARRAY *points; /* array of POINT(3D/3DM) */
+}
+RTCIRCSTRING; /* "light-weight circularstring" */
+
+/* RTPOLYGONTYPE */
+typedef struct
+{
+	uint8_t type; /* RTPOLYGONTYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int nrings;   /* how many rings we are currently storing */
+	int maxrings; /* how many rings we have space for in **rings */
+	RTPOINTARRAY **rings; /* list of rings (list of points) */
+}
+RTPOLY; /* "light-weight polygon" */
+
+/* RTMULTIPOINTTYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTPOINT **geoms;
+}
+RTMPOINT;
+
+/* RTMULTILINETYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTLINE **geoms;
+}
+RTMLINE;
+
+/* RTMULTIPOLYGONTYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTPOLY **geoms;
+}
+RTMPOLY;
+
+/* RTCOLLECTIONTYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTGEOM **geoms;
+}
+RTCOLLECTION;
+
+/* RTCOMPOUNDTYPE */
+typedef struct
+{
+	uint8_t type; /* RTCOMPOUNDTYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTGEOM **geoms;
+}
+RTCOMPOUND; /* "light-weight compound line" */
+
+/* RTCURVEPOLYTYPE */
+typedef struct
+{
+	uint8_t type; /* RTCURVEPOLYTYPE */
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int nrings;    /* how many rings we are currently storing */
+	int maxrings;  /* how many rings we have space for in **rings */
+	RTGEOM **rings; /* list of rings (list of points) */
+}
+RTCURVEPOLY; /* "light-weight polygon" */
+
+/* MULTICURVE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTGEOM **geoms;
+}
+RTMCURVE;
+
+/* RTMULTISURFACETYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTGEOM **geoms;
+}
+RTMSURFACE;
+
+/* RTPOLYHEDRALSURFACETYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTPOLY **geoms;
+}
+RTPSURFACE;
+
+/* RTTINTYPE */
+typedef struct
+{
+	uint8_t type;
+	uint8_t flags;
+	RTGBOX *bbox;
+	int32_t srid;
+	int ngeoms;   /* how many geometries we are currently storing */
+	int maxgeoms; /* how many geometries we have space for in **geoms */
+	RTTRIANGLE **geoms;
+}
+RTTIN;
+
+/* Casts RTGEOM->RT* (return NULL if cast is illegal) */
+extern RTMPOLY *rtgeom_as_rtmpoly(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTMLINE *rtgeom_as_rtmline(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTMPOINT *rtgeom_as_rtmpoint(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTCOLLECTION *rtgeom_as_rtcollection(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTPOLY *rtgeom_as_rtpoly(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTLINE *rtgeom_as_rtline(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTPOINT *rtgeom_as_rtpoint(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTCIRCSTRING *rtgeom_as_rtcircstring(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTCURVEPOLY *rtgeom_as_rtcurvepoly(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTCOMPOUND *rtgeom_as_rtcompound(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTPSURFACE *rtgeom_as_rtpsurface(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTTRIANGLE *rtgeom_as_rttriangle(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTTIN *rtgeom_as_rttin(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTGEOM *rtgeom_as_multi(const RTCTX *ctx, const RTGEOM *rtgeom);
+extern RTGEOM *rtgeom_as_curve(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/* Casts RT*->RTGEOM (artays cast) */
+extern RTGEOM *rttin_as_rtgeom(const RTCTX *ctx, const RTTIN *obj);
+extern RTGEOM *rttriangle_as_rtgeom(const RTCTX *ctx, const RTTRIANGLE *obj);
+extern RTGEOM *rtpsurface_as_rtgeom(const RTCTX *ctx, const RTPSURFACE *obj);
+extern RTGEOM *rtmpoly_as_rtgeom(const RTCTX *ctx, const RTMPOLY *obj);
+extern RTGEOM *rtmline_as_rtgeom(const RTCTX *ctx, const RTMLINE *obj);
+extern RTGEOM *rtmpoint_as_rtgeom(const RTCTX *ctx, const RTMPOINT *obj);
+extern RTGEOM *rtcollection_as_rtgeom(const RTCTX *ctx, const RTCOLLECTION *obj);
+extern RTGEOM *rtcircstring_as_rtgeom(const RTCTX *ctx, const RTCIRCSTRING *obj);
+extern RTGEOM *rtcompound_as_rtgeom(const RTCTX *ctx, const RTCOMPOUND *obj);
+extern RTGEOM *rtcurvepoly_as_rtgeom(const RTCTX *ctx, const RTCURVEPOLY *obj);
+extern RTGEOM *rtpoly_as_rtgeom(const RTCTX *ctx, const RTPOLY *obj);
+extern RTGEOM *rtline_as_rtgeom(const RTCTX *ctx, const RTLINE *obj);
+extern RTGEOM *rtpoint_as_rtgeom(const RTCTX *ctx, const RTPOINT *obj);
+
+
+extern RTCOLLECTION* rtcollection_add_rtgeom(const RTCTX *ctx, RTCOLLECTION *col, const RTGEOM *geom);
+extern RTMPOINT* rtmpoint_add_rtpoint(const RTCTX *ctx, RTMPOINT *mobj, const RTPOINT *obj);
+extern RTMLINE* rtmline_add_rtline(const RTCTX *ctx, RTMLINE *mobj, const RTLINE *obj);
+extern RTMPOLY* rtmpoly_add_rtpoly(const RTCTX *ctx, RTMPOLY *mobj, const RTPOLY *obj);
+extern RTPSURFACE* rtpsurface_add_rtpoly(const RTCTX *ctx, RTPSURFACE *mobj, const RTPOLY *obj);
+extern RTTIN* rttin_add_rttriangle(const RTCTX *ctx, RTTIN *mobj, const RTTRIANGLE *obj);
+
+
+
+/***********************************************************************
+** Utility functions for flag byte and srid_flag integer.
+*/
+
+/**
+* Construct a new flags char.
+*/
+extern uint8_t gflags(const RTCTX *ctx, int hasz, int hasm, int geodetic);
+
+/**
+* Extract the geometry type from the serialized form (it hides in 
+* the anonymous data area, so this is a handy function).
+*/
+extern uint32_t gserialized_get_type(const RTCTX *ctx, const GSERIALIZED *g);
+
+/** 
+* Returns the size in bytes to read from toast to get the basic 
+* information from a geometry: GSERIALIZED struct, bbox and type 
+*/
+extern uint32_t gserialized_max_header_size(const RTCTX *ctx);
+
+/**
+* Extract the SRID from the serialized form (it is packed into
+* three bytes so this is a handy function).
+*/
+extern int32_t gserialized_get_srid(const RTCTX *ctx, const GSERIALIZED *g);
+
+/**
+* Write the SRID into the serialized form (it is packed into
+* three bytes so this is a handy function).
+*/
+extern void gserialized_set_srid(const RTCTX *ctx, GSERIALIZED *g, int32_t srid);
+
+/**
+* Check if a #GSERIALIZED is empty without deserializing first.
+* Only checks if the number of elements of the parent geometry
+* is zero, will not catch collections of empty, eg: 
+* GEOMETRYCOLLECTION(POINT EMPTY) 
+*/
+extern int gserialized_is_empty(const RTCTX *ctx, const GSERIALIZED *g);
+
+/**
+* Check if a #GSERIALIZED has a bounding box without deserializing first.
+*/
+extern int gserialized_has_bbox(const RTCTX *ctx, const GSERIALIZED *gser);
+
+/**
+* Check if a #GSERIALIZED has a Z ordinate.
+*/
+extern int gserialized_has_z(const RTCTX *ctx, const GSERIALIZED *gser);
+
+/**
+* Check if a #GSERIALIZED has an M ordinate.
+*/
+extern int gserialized_has_m(const RTCTX *ctx, const GSERIALIZED *gser);
+
+/**
+* Check if a #GSERIALIZED is a geography.
+*/
+extern int gserialized_is_geodetic(const RTCTX *ctx, const GSERIALIZED *gser);
+
+/**
+* Return a number indicating presence of Z and M coordinates.
+* 0 = None, 1 = M, 2 = Z, 3 = ZM
+*/
+extern int gserialized_get_zm(const RTCTX *ctx, const GSERIALIZED *gser);
+
+/**
+* Return the number of dimensions (2, 3, 4) in a geometry
+*/
+extern int gserialized_ndims(const RTCTX *ctx, const GSERIALIZED *gser);
+
+
+/**
+* Call this function to drop BBOX and SRID
+* from RTGEOM. If RTGEOM type is *not* flagged
+* with the HASBBOX flag and has a bbox, it
+* will be released.
+*/
+extern void rtgeom_drop_bbox(const RTCTX *ctx, RTGEOM *rtgeom);
+extern void rtgeom_drop_srid(const RTCTX *ctx, RTGEOM *rtgeom);
+
+/** 
+ * Compute a bbox if not already computed 
+ *
+ * After calling this function rtgeom->bbox is only
+ * NULL if the geometry is empty.
+ */
+extern void rtgeom_add_bbox(const RTCTX *ctx, RTGEOM *rtgeom);
+/**
+* Compute a box for geom and all sub-geometries, if not already computed
+*/
+extern void rtgeom_add_bbox_deep(const RTCTX *ctx, RTGEOM *rtgeom, RTGBOX *gbox);
+
+/** 
+ * Get a non-empty geometry bounding box, computing and
+ * caching it if not already there
+ *
+ * NOTE: empty geometries don't have a bounding box so
+ *       you'd still get a NULL for them.
+ */
+extern const RTGBOX *rtgeom_get_bbox(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/** 
+* Determine whether a RTGEOM can contain sub-geometries or not 
+*/
+extern int rtgeom_is_collection(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/******************************************************************/
+/* Functions that work on type numbers */
+
+/** 
+* Determine whether a type number is a collection or not
+*/
+extern int rttype_is_collection(const RTCTX *ctx, uint8_t type);
+
+/**
+* Given an rttype number, what homogeneous collection can hold it?
+*/
+extern int rttype_get_collectiontype(const RTCTX *ctx, uint8_t type);
+
+/**
+* Return the type name string associated with a type number 
+* (e.g. Point, LineString, Polygon)
+*/
+extern const char *rttype_name(const RTCTX *ctx, uint8_t type);
+
+
+/******************************************************************/
+
+/*
+ * copies a point from the point array into the parameter point
+ * will set point's z=0 (or NaN) if pa is 2d
+ * will set point's m=0 (or NaN) if pa is 3d or 2d
+ * NOTE: point is a real POINT3D *not* a pointer
+ */
+extern RTPOINT4D rt_getPoint4d(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/*
+ * copies a point from the point array into the parameter point
+ * will set point's z=0 (or NaN) if pa is 2d
+ * will set point's m=0 (or NaN) if pa is 3d or 2d
+ * NOTE: this will modify the point4d pointed to by 'point'.
+ */
+extern int rt_getPoint4d_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT4D *point);
+
+/*
+ * copies a point from the point array into the parameter point
+ * will set point's z=0 (or NaN) if pa is 2d
+ * NOTE: point is a real POINT3D *not* a pointer
+ */
+extern RTPOINT3DZ rt_getPoint3dz(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+extern RTPOINT3DM rt_getPoint3dm(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/*
+ * copies a point from the point array into the parameter point
+ * will set point's z=0 (or NaN) if pa is 2d
+ * NOTE: this will modify the point3d pointed to by 'point'.
+ */
+extern int rt_getPoint3dz_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT3DZ *point);
+extern int rt_getPoint3dm_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT3DM *point);
+
+
+/*
+ * copies a point from the point array into the parameter point
+ * z value (if present is not returned)
+ * NOTE: point is a real POINT3D *not* a pointer
+ */
+extern RTPOINT2D rt_getPoint2d(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/*
+ * copies a point from the point array into the parameter point
+ * z value (if present is not returned)
+ * NOTE: this will modify the point2d pointed to by 'point'.
+ */
+extern int rt_getPoint2d_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT2D *point);
+
+/**
+* Returns a RTPOINT2D pointer into the RTPOINTARRAY serialized_ptlist, 
+* suitable for reading from. This is very high performance
+* and declared const because you aren't allowed to muck with the 
+* values, only read them.
+*/
+extern const RTPOINT2D* rt_getPoint2d_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/**
+* Returns a RTPOINT3DZ pointer into the RTPOINTARRAY serialized_ptlist, 
+* suitable for reading from. This is very high performance
+* and declared const because you aren't allowed to muck with the 
+* values, only read them.
+*/
+extern const RTPOINT3DZ* rt_getPoint3dz_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/**
+* Returns a RTPOINT4D pointer into the RTPOINTARRAY serialized_ptlist, 
+* suitable for reading from. This is very high performance
+* and declared const because you aren't allowed to muck with the 
+* values, only read them.
+*/
+extern const RTPOINT4D* rt_getPoint4d_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/*
+ * set point N to the given value
+ * NOTE that the pointarray can be of any
+ * dimension, the appropriate ordinate values
+ * will be extracted from it
+ *
+ * N must be a valid point index 
+ */
+extern void ptarray_set_point4d(const RTCTX *ctx, RTPOINTARRAY *pa, int n, const RTPOINT4D *p4d);
+
+/*
+ * get a pointer to nth point of a RTPOINTARRAY
+ * You'll need to cast it to appropriate dimensioned point.
+ * Note that if you cast to a higher dimensional point you'll
+ * possibly corrupt the RTPOINTARRAY.
+ *
+ * WARNING: Don't cast this to a POINT !
+ * it would not be reliable due to memory alignment constraints
+ */
+extern uint8_t *rt_getPoint_internal(const RTCTX *ctx, const RTPOINTARRAY *pa, int n);
+
+/*
+ * size of point represeneted in the RTPOINTARRAY
+ * 16 for 2d, 24 for 3d, 32 for 4d
+ */
+extern int ptarray_point_size(const RTCTX *ctx, const RTPOINTARRAY *pa);
+
+
+/** 
+* Construct an empty pointarray, allocating storage and setting
+* the npoints, but not filling in any information. Should be used in conjunction
+* with ptarray_set_point4d to fill in the information in the array.
+*/
+extern RTPOINTARRAY* ptarray_construct(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints);
+
+/**
+* Construct a new #RTPOINTARRAY, <em>copying</em> in the data from ptlist 
+*/
+extern RTPOINTARRAY* ptarray_construct_copy_data(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints, const uint8_t *ptlist);
+
+/**
+* Construct a new #RTPOINTARRAY, <em>referencing</em> to the data from ptlist 
+*/
+extern RTPOINTARRAY* ptarray_construct_reference_data(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints, uint8_t *ptlist);
+
+/**
+* Create a new #RTPOINTARRAY with no points. Allocate enough storage
+* to hold maxpoints vertices before having to reallocate the storage
+* area.
+*/
+extern RTPOINTARRAY* ptarray_construct_empty(const RTCTX *ctx, char hasz, char hasm, uint32_t maxpoints);
+
+/**
+* Append a point to the end of an existing #RTPOINTARRAY 
+* If allow_duplicate is RT_TRUE, then a duplicate point will
+* not be added.
+*/
+extern int ptarray_append_point(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *pt, int allow_duplicates);
+
+/**
+ * Append a #RTPOINTARRAY, pa2 to the end of an existing #RTPOINTARRAY, pa1.
+ *
+ * If gap_tolerance is >= 0 then the end point of pa1 will be checked for
+ * being within gap_tolerance 2d distance from start point of pa2 or an
+ * error will be raised and RT_FAILURE returned.
+ * A gap_tolerance < 0 disables the check.
+ *
+ * If end point of pa1 and start point of pa2 are 2d-equal, then pa2 first
+ * point will not be appended.
+ */
+extern int ptarray_append_ptarray(const RTCTX *ctx, RTPOINTARRAY *pa1, RTPOINTARRAY *pa2, double gap_tolerance);
+
+/**
+* Insert a point into an existing #RTPOINTARRAY. Zero
+* is the index of the start of the array.
+*/
+extern int ptarray_insert_point(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *p, int where);
+
+/**
+* Remove a point from an existing #RTPOINTARRAY. Zero
+* is the index of the start of the array.
+*/
+extern int ptarray_remove_point(const RTCTX *ctx, RTPOINTARRAY *pa, int where);
+
+/**
+ * @brief Add a point in a pointarray.
+ *
+ * @param pa the source RTPOINTARRAY
+ * @param p the point to add
+ * @param pdims number of ordinates in p (2..4)
+ * @param where to insert the point. 0 prepends, pa->npoints appends
+ *
+ * @returns a newly constructed RTPOINTARRAY using a newly allocated buffer
+ *          for the actual points, or NULL on error.
+ */
+extern RTPOINTARRAY *ptarray_addPoint(const RTCTX *ctx, const RTPOINTARRAY *pa, uint8_t *p, size_t pdims, uint32_t where);
+
+/**
+ * @brief Remove a point from a pointarray.
+ * 	@param which -  is the offset (starting at 0)
+ * @return #RTPOINTARRAY is newly allocated
+ */
+extern RTPOINTARRAY *ptarray_removePoint(const RTCTX *ctx, RTPOINTARRAY *pa, uint32_t where);
+
+/**
+ * @brief Merge two given RTPOINTARRAY and returns a pointer
+ * on the new aggregate one.
+ * Warning: this function free the two inputs RTPOINTARRAY
+ * @return #RTPOINTARRAY is newly allocated
+ */
+extern RTPOINTARRAY *ptarray_merge(const RTCTX *ctx, RTPOINTARRAY *pa1, RTPOINTARRAY *pa2);
+
+extern int ptarray_is_closed(const RTCTX *ctx, const RTPOINTARRAY *pa);
+extern int ptarray_is_closed_2d(const RTCTX *ctx, const RTPOINTARRAY *pa);
+extern int ptarray_is_closed_3d(const RTCTX *ctx, const RTPOINTARRAY *pa);
+extern int ptarray_is_closed_z(const RTCTX *ctx, const RTPOINTARRAY *pa);
+extern void ptarray_longitude_shift(const RTCTX *ctx, RTPOINTARRAY *pa);
+extern int ptarray_isccw(const RTCTX *ctx, const RTPOINTARRAY *pa);
+extern void ptarray_reverse(const RTCTX *ctx, RTPOINTARRAY *pa);
+extern RTPOINTARRAY* ptarray_flip_coordinates(const RTCTX *ctx, RTPOINTARRAY *pa);
+
+/**
+ * @d1 start location (distance from start / total distance)
+ * @d2   end location (distance from start / total distance)
+ * @param tolerance snap to vertices at locations < tolerance away from given ones
+ */
+extern RTPOINTARRAY *ptarray_substring(const RTCTX *ctx, RTPOINTARRAY *pa, double d1, double d2,
+                                                          double tolerance);
+
+
+/**
+* Strip out the Z/M components of an #RTGEOM
+*/
+extern RTGEOM* rtgeom_force_2d(const RTCTX *ctx, const RTGEOM *geom);
+extern RTGEOM* rtgeom_force_3dz(const RTCTX *ctx, const RTGEOM *geom);
+extern RTGEOM* rtgeom_force_3dm(const RTCTX *ctx, const RTGEOM *geom);
+extern RTGEOM* rtgeom_force_4d(const RTCTX *ctx, const RTGEOM *geom);
+
+extern RTGEOM* rtgeom_simplify(const RTCTX *ctx, const RTGEOM *igeom, double dist, int preserve_collapsed);
+extern RTGEOM* rtgeom_set_effective_area(const RTCTX *ctx, const RTGEOM *igeom, int set_area, double area);
+
+/* 
+ * Force to use SFS 1.1 geometry type
+ * (rather than SFS 1.2 and/or SQL/MM)
+ */
+extern RTGEOM* rtgeom_force_sfs(const RTCTX *ctx, RTGEOM *geom, int version);
+
+
+/*--------------------------------------------------------
+ * all the base types (point/line/polygon) will have a
+ * basic constructor, basic de-serializer, basic serializer,
+ * bounding box finder and (TODO) serialized form size finder.
+ *--------------------------------------------------------*/
+
+/*
+ * convenience functions to hide the RTPOINTARRAY
+ */
+extern int rtpoint_getPoint2d_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT2D *out);
+extern int rtpoint_getPoint3dz_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT3DZ *out);
+extern int rtpoint_getPoint3dm_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT3DM *out);
+extern int rtpoint_getPoint4d_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT4D *out);
+
+/******************************************************************
+ * RTLINE functions
+ ******************************************************************/
+
+/**
+ * Add a RTPOINT to an RTLINE
+ */
+extern int rtline_add_rtpoint(const RTCTX *ctx, RTLINE *line, RTPOINT *point, int where);
+
+/******************************************************************
+ * RTPOLY functions
+ ******************************************************************/
+
+/**
+* Add a ring, allocating extra space if necessary. The polygon takes
+* ownership of the passed point array.
+*/
+extern int rtpoly_add_ring(const RTCTX *ctx, RTPOLY *poly, RTPOINTARRAY *pa);
+
+/**
+* Add a ring, allocating extra space if necessary. The curvepolygon takes
+* ownership of the passed point array.
+*/
+extern int rtcurvepoly_add_ring(const RTCTX *ctx, RTCURVEPOLY *poly, RTGEOM *ring);
+
+/**
+* Add a component, allocating extra space if necessary. The compoundcurve
+* takes owership of the passed geometry.
+*/
+extern int rtcompound_add_rtgeom(const RTCTX *ctx, RTCOMPOUND *comp, RTGEOM *geom);
+
+/**
+* Construct an equivalent compound curve from a linestring. 
+* Compound curves can have linear components, so this works fine 
+*/
+extern RTCOMPOUND* rtcompound_construct_from_rtline(const RTCTX *ctx, const RTLINE *rtpoly);
+
+/**
+* Construct an equivalent curve polygon from a polygon. Curve polygons
+* can have linear rings as their rings, so this works fine (in theory?)
+*/
+extern RTCURVEPOLY* rtcurvepoly_construct_from_rtpoly(const RTCTX *ctx, RTPOLY *rtpoly);
+
+
+/******************************************************************
+ * RTGEOM functions
+ ******************************************************************/
+
+extern int rtcollection_ngeoms(const RTCTX *ctx, const RTCOLLECTION *col);
+
+/* Given a generic geometry/collection, return the "simplest" form. */
+extern RTGEOM *rtgeom_homogenize(const RTCTX *ctx, const RTGEOM *geom);
+
+
+/******************************************************************
+ * RTMULTIx and RTCOLLECTION functions
+ ******************************************************************/
+
+RTGEOM *rtcollection_getsubgeom(const RTCTX *ctx, RTCOLLECTION *col, int gnum);
+RTCOLLECTION* rtcollection_extract(const RTCTX *ctx, RTCOLLECTION *col, int type);
+
+
+/******************************************************************
+ * SERIALIZED FORM functions
+ ******************************************************************/
+
+/**
+* Set the SRID on an RTGEOM
+* For collections, only the parent gets an SRID, all
+* the children get SRID_UNKNOWN.
+*/
+extern void rtgeom_set_srid(const RTCTX *ctx, RTGEOM *geom, int srid);
+
+/*------------------------------------------------------
+ * other stuff
+ *
+ * handle the double-to-float conversion.  The results of this
+ * will usually be a slightly bigger box because of the difference
+ * between float8 and float4 representations.
+ */
+
+extern BOX3D* box3d_from_gbox(const RTCTX *ctx, const RTGBOX *gbox);
+extern RTGBOX* box3d_to_gbox(const RTCTX *ctx, const BOX3D *b3d);
+
+void expand_box3d(BOX3D *box, double d);
+
+
+/****************************************************************
+ * MEMORY MANAGEMENT
+ ****************************************************************/
+
+/*
+* The *_free family of functions frees *all* memory associated
+* with the pointer. When the recursion gets to the level of the 
+* RTPOINTARRAY, the RTPOINTARRAY is only freed if it is not flagged
+* as "read only". RTGEOMs constructed on top of GSERIALIZED
+* from PgSQL use read only point arrays.
+*/
+
+extern void ptarray_free(const RTCTX *ctx, RTPOINTARRAY *pa);
+extern void rtpoint_free(const RTCTX *ctx, RTPOINT *pt);
+extern void rtline_free(const RTCTX *ctx, RTLINE *line);
+extern void rtpoly_free(const RTCTX *ctx, RTPOLY *poly);
+extern void rttriangle_free(const RTCTX *ctx, RTTRIANGLE *triangle);
+extern void rtmpoint_free(const RTCTX *ctx, RTMPOINT *mpt);
+extern void rtmline_free(const RTCTX *ctx, RTMLINE *mline);
+extern void rtmpoly_free(const RTCTX *ctx, RTMPOLY *mpoly);
+extern void rtpsurface_free(const RTCTX *ctx, RTPSURFACE *psurf);
+extern void rttin_free(const RTCTX *ctx, RTTIN *tin);
+extern void rtcollection_free(const RTCTX *ctx, RTCOLLECTION *col);
+extern void rtcircstring_free(const RTCTX *ctx, RTCIRCSTRING *curve);
+extern void rtgeom_free(const RTCTX *ctx, RTGEOM *geom);
+
+/*
+* The *_release family of functions frees the RTGEOM structures
+* surrounding the RTPOINTARRAYs but leaves the RTPOINTARRAYs
+* intact. Useful when re-shaping geometries between types,
+* or splicing geometries together.
+*/
+
+extern void rtpoint_release(const RTCTX *ctx, RTPOINT *rtpoint);
+extern void rtline_release(const RTCTX *ctx, RTLINE *rtline);
+extern void rtpoly_release(const RTCTX *ctx, RTPOLY *rtpoly);
+extern void rttriangle_release(const RTCTX *ctx, RTTRIANGLE *rttriangle);
+extern void rtcircstring_release(const RTCTX *ctx, RTCIRCSTRING *rtcirc);
+extern void rtmpoint_release(const RTCTX *ctx, RTMPOINT *rtpoint);
+extern void rtmline_release(const RTCTX *ctx, RTMLINE *rtline);
+extern void rtmpoly_release(const RTCTX *ctx, RTMPOLY *rtpoly);
+extern void rtpsurface_release(RTPSURFACE *rtpsurface);
+extern void rttin_release(RTTIN *rttin);
+extern void rtcollection_release(const RTCTX *ctx, RTCOLLECTION *rtcollection);
+extern void rtgeom_release(const RTCTX *ctx, RTGEOM *rtgeom);
+
+
+/****************************************************************
+* Utility
+****************************************************************/
+
+extern void printBOX3D(const RTCTX *ctx, BOX3D *b);
+extern void printPA(const RTCTX *ctx, RTPOINTARRAY *pa);
+extern void printRTPOINT(const RTCTX *ctx, RTPOINT *point);
+extern void printRTLINE(const RTCTX *ctx, RTLINE *line);
+extern void printRTPOLY(const RTCTX *ctx, RTPOLY *poly);
+extern void printRTTRIANGLE(const RTCTX *ctx, RTTRIANGLE *triangle);
+extern void printRTPSURFACE(const RTCTX *ctx, RTPSURFACE *psurf);
+extern void printRTTIN(const RTCTX *ctx, RTTIN *tin);
+
+extern float  next_float_down(const RTCTX *ctx, double d);
+extern float  next_float_up(const RTCTX *ctx, double d);
+extern double next_double_down(const RTCTX *ctx, float d);
+extern double next_double_up(const RTCTX *ctx, float d);
+
+/* general utilities 2D */
+extern double  distance2d_pt_pt(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2);
+extern double  distance2d_sqr_pt_pt(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2);
+extern double  distance2d_pt_seg(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINT2D *A, const RTPOINT2D *B);
+extern double  distance2d_sqr_pt_seg(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINT2D *A, const RTPOINT2D *B);
+extern RTGEOM* rtgeom_closest_line(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern RTGEOM* rtgeom_furthest_line(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern RTGEOM* rtgeom_closest_point(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern RTGEOM* rtgeom_furthest_point(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern double  rtgeom_mindistance2d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern double  rtgeom_mindistance2d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance);
+extern double  rtgeom_maxdistance2d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern double  rtgeom_maxdistance2d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance);
+
+/* 3D */
+extern double distance3d_pt_pt(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2);
+extern double distance3d_pt_seg(const POINT3D *p, const POINT3D *A, const POINT3D *B);
+
+extern RTGEOM* rtgeom_furthest_line_3d(const RTCTX *ctx, RTGEOM *rt1, RTGEOM *rt2);
+extern RTGEOM* rtgeom_closest_line_3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern RTGEOM* rtgeom_closest_point_3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+
+
+extern double rtgeom_mindistance3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern double rtgeom_mindistance3d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance);
+extern double rtgeom_maxdistance3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2);
+extern double rtgeom_maxdistance3d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance);
+
+extern double rtgeom_area(const RTCTX *ctx, const RTGEOM *geom);
+extern double rtgeom_length(const RTCTX *ctx, const RTGEOM *geom);
+extern double rtgeom_length_2d(const RTCTX *ctx, const RTGEOM *geom);
+extern double rtgeom_perimeter(const RTCTX *ctx, const RTGEOM *geom);
+extern double rtgeom_perimeter_2d(const RTCTX *ctx, const RTGEOM *geom);
+extern void rtgeom_affine(const RTCTX *ctx, RTGEOM *geom, const RTAFFINE *affine);
+extern void rtgeom_scale(const RTCTX *ctx, RTGEOM *geom, const RTPOINT4D *factors);
+extern int rtgeom_dimension(const RTCTX *ctx, const RTGEOM *geom);
+
+extern RTPOINT* rtline_get_rtpoint(const RTCTX *ctx, const RTLINE *line, int where);
+extern RTPOINT* rtcircstring_get_rtpoint(const RTCTX *ctx, const RTCIRCSTRING *circ, int where);
+
+extern RTPOINT* rtcompound_get_startpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp);
+extern RTPOINT* rtcompound_get_endpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp);
+extern RTPOINT* rtcompound_get_rtpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp, int where);
+
+
+extern double ptarray_length_2d(const RTCTX *ctx, const RTPOINTARRAY *pts);
+extern double ptarray_length(const RTCTX *ctx, const RTPOINTARRAY *pts);
+extern double ptarray_arc_length_2d(const RTCTX *ctx, const RTPOINTARRAY *pts);
+
+
+extern int pt_in_ring_2d(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINTARRAY *ring);
+extern int azimuth_pt_pt(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, double *ret);
+extern int rtpoint_inside_circle(const RTCTX *ctx, const RTPOINT *p, double cx, double cy, double rad);
+extern void rtgeom_reverse(const RTCTX *ctx, RTGEOM *rtgeom);
+extern void rtline_reverse(const RTCTX *ctx, RTLINE *line);
+extern void rtpoly_reverse(const RTCTX *ctx, RTPOLY *poly);
+extern void rttriangle_reverse(const RTCTX *ctx, RTTRIANGLE *triangle);
+extern char* rtgeom_summary(const RTCTX *ctx, const RTGEOM *rtgeom, int offset);
+extern char* rtpoint_to_latlon(const RTCTX *ctx, const RTPOINT *p, const char *format);
+extern int rtgeom_startpoint(const RTCTX *ctx, const RTGEOM* rtgeom, RTPOINT4D* pt);
+
+/**
+* Ensure the outer ring is clockwise oriented and all inner rings 
+* are counter-clockwise.
+*/
+extern void rtgeom_force_clockwise(const RTCTX *ctx, RTGEOM *rtgeom);
+extern void rtpoly_force_clockwise(const RTCTX *ctx, RTPOLY *poly);
+extern void rttriangle_force_clockwise(const RTCTX *ctx, RTTRIANGLE *triangle);
+
+
+extern void interpolate_point4d(const RTCTX *ctx, RTPOINT4D *A, RTPOINT4D *B, RTPOINT4D *I, double F);
+
+void rtgeom_longitude_shift(const RTCTX *ctx, RTGEOM *rtgeom);
+
+
+/**
+* @brief Check whether or not a rtgeom is big enough to warrant a bounding box.
+*
+* Check whether or not a rtgeom is big enough to warrant a bounding box
+* when stored in the serialized form on disk. Currently only points are
+* considered small enough to not require a bounding box, because the
+* index operations can generate a large number of box-retrieval operations
+* when scanning keys.
+*/
+extern int rtgeom_needs_bbox(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Count the total number of vertices in any #RTGEOM.
+*/
+extern int rtgeom_count_vertices(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Count the total number of rings in any #RTGEOM. Multipolygons
+* and other collections get counted, not the same as OGC st_numrings.
+*/
+extern int rtgeom_count_rings(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return true or false depending on whether a geometry has
+* a valid SRID set.
+*/
+extern int rtgeom_has_srid(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return true or false depending on whether a geometry is an "empty"
+* geometry (no vertices members)
+*/
+extern int rtgeom_is_empty(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return true or false depending on whether a geometry is a linear
+* feature that closes on itself.
+*/
+extern int rtgeom_is_closed(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return the dimensionality (relating to point/line/poly) of an rtgeom
+*/
+extern int rtgeom_dimensionality(const RTCTX *ctx, RTGEOM *geom);
+
+/* Is rtgeom1 geometrically equal to rtgeom2 ? */
+char rtgeom_same(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2);
+char ptarray_same(const RTCTX *ctx, const RTPOINTARRAY *pa1, const RTPOINTARRAY *pa2);
+char rtpoint_same(const RTCTX *ctx, const RTPOINT *p1, const RTPOINT *p2);
+char rtline_same(const RTCTX *ctx, const RTLINE *p1, const RTLINE *p2);
+char rtpoly_same(const RTCTX *ctx, const RTPOLY *p1, const RTPOLY *p2);
+char rttriangle_same(const RTCTX *ctx, const RTTRIANGLE *p1, const RTTRIANGLE *p2);
+char rtcollection_same(const RTCTX *ctx, const RTCOLLECTION *p1, const RTCOLLECTION *p2);
+char rtcircstring_same(const RTCTX *ctx, const RTCIRCSTRING *p1, const RTCIRCSTRING *p2);
+
+
+/**
+ * @brief Clone RTGEOM object. Serialized point lists are not copied.
+ *
+ * #RTGBOX are copied
+ *
+ * @see ptarray_clone 
+ */
+extern RTGEOM *rtgeom_clone(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/**
+* Deep clone an RTGEOM, everything is copied
+*/
+extern RTGEOM *rtgeom_clone_deep(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/* TODO Move to Internal */
+RTPOINT *rtpoint_clone(const RTCTX *ctx, const RTPOINT *rtgeom);
+RTPOINTARRAY *ptarray_clone_deep(const RTCTX *ctx, const RTPOINTARRAY *ptarray);
+
+
+/*
+* Geometry constructors. These constructors to not copy the point arrays
+* passed to them, they just take references, so do not free them out
+* from underneath the geometries.
+*/
+extern RTPOINT* rtpoint_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *point);
+extern RTMPOINT *rtmpoint_construct(const RTCTX *ctx, int srid, const RTPOINTARRAY *pa);
+extern RTLINE* rtline_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points);
+extern RTCIRCSTRING* rtcircstring_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points);
+extern RTPOLY* rtpoly_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, uint32_t nrings, RTPOINTARRAY **points);
+extern RTCURVEPOLY* rtcurvepoly_construct(int srid, RTGBOX *bbox, uint32_t nrings, RTGEOM **geoms);
+extern RTTRIANGLE* rttriangle_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points);
+extern RTCOLLECTION* rtcollection_construct(const RTCTX *ctx, uint8_t type, int srid, RTGBOX *bbox, uint32_t ngeoms, RTGEOM **geoms); 
+/*
+* Empty geometry constructors.
+*/
+extern RTGEOM* rtgeom_construct_empty(const RTCTX *ctx, uint8_t type, int srid, char hasz, char hasm);
+extern RTPOINT* rtpoint_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTLINE* rtline_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTPOLY* rtpoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTCURVEPOLY* rtcurvepoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTCIRCSTRING* rtcircstring_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTCOMPOUND* rtcompound_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTTRIANGLE* rttriangle_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTMPOINT* rtmpoint_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTMLINE* rtmline_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTMPOLY* rtmpoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm);
+extern RTCOLLECTION* rtcollection_construct_empty(const RTCTX *ctx, uint8_t type, int srid, char hasz, char hasm);
+
+
+/* Other constructors */
+extern RTPOINT *rtpoint_make2d(const RTCTX *ctx, int srid, double x, double y);
+extern RTPOINT *rtpoint_make3dz(const RTCTX *ctx, int srid, double x, double y, double z);
+extern RTPOINT *rtpoint_make3dm(const RTCTX *ctx, int srid, double x, double y, double m);
+extern RTPOINT *rtpoint_make4d(const RTCTX *ctx, int srid, double x, double y, double z, double m);
+extern RTPOINT *rtpoint_make(const RTCTX *ctx, int srid, int hasz, int hasm, const RTPOINT4D *p);
+extern RTLINE *rtline_from_rtgeom_array(const RTCTX *ctx, int srid, uint32_t ngeoms, RTGEOM **geoms);
+extern RTLINE *rtline_from_ptarray(const RTCTX *ctx, int srid, uint32_t npoints, RTPOINT **points); /* TODO: deprecate */
+extern RTLINE *rtline_from_rtmpoint(const RTCTX *ctx, int srid, const RTMPOINT *mpoint);
+extern RTLINE *rtline_addpoint(RTLINE *line, RTPOINT *point, uint32_t where);
+extern RTLINE *rtline_removepoint(const RTCTX *ctx, RTLINE *line, uint32_t which);
+extern void rtline_setPoint4d(const RTCTX *ctx, RTLINE *line, uint32_t which, RTPOINT4D *newpoint);
+extern RTPOLY *rtpoly_from_rtlines(const RTCTX *ctx, const RTLINE *shell, uint32_t nholes, const RTLINE **holes);
+extern RTTRIANGLE *rttriangle_from_rtline(const RTCTX *ctx, const RTLINE *shell);
+
+
+/* Some point accessors */
+extern double rtpoint_get_x(const RTCTX *ctx, const RTPOINT *point);
+extern double rtpoint_get_y(const RTCTX *ctx, const RTPOINT *point);
+extern double rtpoint_get_z(const RTCTX *ctx, const RTPOINT *point);
+extern double rtpoint_get_m(const RTCTX *ctx, const RTPOINT *point);
+
+/**
+* Return SRID number
+*/
+extern int32_t rtgeom_get_srid(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return RTRTTYPE number
+*/
+extern uint32_t rtgeom_get_type(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return #RT_TRUE if geometry has Z ordinates
+*/
+extern int rtgeom_has_z(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return #RT_TRUE if geometry has M ordinates.
+*/
+extern int rtgeom_has_m(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Return the number of dimensions (2, 3, 4) in a geometry
+*/
+extern int rtgeom_ndims(const RTCTX *ctx, const RTGEOM *geom);
+
+/*
+ * Given a point, returns the location of closest point on pointarray
+ * as a fraction of total length (0: first point -- 1: last point).
+ *
+ * If not-null, the third argument will be set to the actual distance
+ * of the point from the pointarray.
+ */
+extern double ptarray_locate_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT4D *pt, double *dist, RTPOINT4D *p_located);
+
+/**
+* Add a measure dimension to a line, interpolating linearly from the start 
+* to the end value.
+*/
+extern RTLINE *rtline_measured_from_rtline(const RTCTX *ctx, const RTLINE *rtline, double m_start, double m_end);
+extern RTMLINE* rtmline_measured_from_rtmline(const RTCTX *ctx, const RTMLINE *rtmline, double m_start, double m_end);
+
+/**
+* Determine the location(s) along a measured line where m occurs and 
+* return as a multipoint. Offset to left (positive) or right (negative).
+*/
+extern RTGEOM* rtgeom_locate_along(const RTCTX *ctx, const RTGEOM *rtin, double m, double offset);
+
+/**
+* Determine the segments along a measured line that fall within the m-range
+* given. Return as a multiline or geometrycollection.
+* Offset to left (positive) or right (negative).
+*/
+extern RTCOLLECTION* rtgeom_locate_between(const RTCTX *ctx, const RTGEOM *rtin, double from, double to, double offset);
+
+/**
+* Find the measure value at the location on the line closest to the point.
+*/
+extern double rtgeom_interpolate_point(const RTCTX *ctx, const RTGEOM *rtin, const RTPOINT *rtpt);
+
+/**
+* Find the time of closest point of approach
+*
+* @param mindist if not null will be set to the minimum distance between
+*                the trajectories at the closest point of approach.
+*
+* @return the time value in which the minimum distance was reached, -1
+*         if inputs are invalid (rterror is called in that case),
+*         -2 if the trajectories do not share any point in time.
+*/
+extern double rtgeom_tcpa(const RTCTX *ctx, const RTGEOM *g1, const RTGEOM *g2, double *mindist);
+
+/**
+* Is the closest point of approach within a distance ?
+*
+* @return RT_TRUE or RT_FALSE
+*/
+extern int rtgeom_cpa_within(const RTCTX *ctx, const RTGEOM *g1, const RTGEOM *g2, double maxdist);
+
+/**
+* Return RT_TRUE or RT_FALSE depending on whether or not a geometry is
+* a linestring with measure value growing from start to end vertex
+*/
+extern int rtgeom_is_trajectory(const RTCTX *ctx, const RTGEOM *geom);
+extern int rtline_is_trajectory(const RTCTX *ctx, const RTLINE *geom);
+
+/*
+ * Ensure every segment is at most 'dist' long.
+ * Returned RTGEOM might is unchanged if a POINT.
+ */
+extern RTGEOM *rtgeom_segmentize2d(const RTCTX *ctx, RTGEOM *line, double dist);
+extern RTPOINTARRAY *ptarray_segmentize2d(const RTCTX *ctx, const RTPOINTARRAY *ipa, double dist);
+extern RTLINE *rtline_segmentize2d(const RTCTX *ctx, RTLINE *line, double dist);
+extern RTPOLY *rtpoly_segmentize2d(const RTCTX *ctx, RTPOLY *line, double dist);
+extern RTCOLLECTION *rtcollection_segmentize2d(const RTCTX *ctx, RTCOLLECTION *coll, double dist);
+
+/**
+* Calculate the GeoHash (http://geohash.org) string for a geometry. Caller must free.
+*/
+char *rtgeom_geohash(const RTCTX *ctx, const RTGEOM *rtgeom, int precision);
+unsigned int geohash_point_as_int(const RTCTX *ctx, RTPOINT2D *pt);
+
+
+/**
+* The return values of rtline_crossing_direction(const RTCTX *ctx)
+*/
+enum RTCG_LINE_CROSS_TYPE {
+    LINE_NO_CROSS = 0,
+    LINE_CROSS_LEFT = -1,
+    LINE_CROSS_RIGHT = 1,
+    LINE_MULTICROSS_END_LEFT = -2,
+    LINE_MULTICROSS_END_RIGHT = 2,
+    LINE_MULTICROSS_END_SAME_FIRST_LEFT = -3,
+    LINE_MULTICROSS_END_SAME_FIRST_RIGHT = 3
+};
+
+/**
+* Given two lines, characterize how (and if) they cross each other
+*/
+int rtline_crossing_direction(const RTCTX *ctx, const RTLINE *l1, const RTLINE *l2);
+
+/**
+* Given a geometry clip  based on the from/to range of one of its ordinates (x, y, z, m). Use for m- and z- clipping.
+*/
+RTCOLLECTION* rtgeom_clip_to_ordinate_range(const RTCTX *ctx, const RTGEOM *rtin, char ordinate, double from, double to, double offset);
+
+/**
+ * Macros for specifying GML options. 
+ * @{
+ */
+/** For GML3 only, include srsDimension attribute in output */
+#define RT_GML_IS_DIMS     (1<<0)
+/** For GML3 only, declare that datas are lat/lon. Swaps axis order */
+#define RT_GML_IS_DEGREE   (1<<1)
+/** For GML3, use <LineString> rather than <Curve> for lines */
+#define RT_GML_SHORTLINE   (1<<2)
+/** For GML2 and GML3, output only extent of geometry */
+#define RT_GML_EXTENT      (1<<4)
+
+
+#define IS_DIMS(x) ((x) & RT_GML_IS_DIMS)
+#define IS_DEGREE(x) ((x) & RT_GML_IS_DEGREE)
+/** @} */
+
+/**
+ * Macros for specifying X3D options. 
+ * @{
+ */
+/** For flip X/Y coordinates to Y/X */
+#define RT_X3D_FLIP_XY     (1<<0)
+#define RT_X3D_USE_GEOCOORDS     (1<<1)
+#define X3D_USE_GEOCOORDS(x) ((x) & RT_X3D_USE_GEOCOORDS)
+
+
+
+extern char* rtgeom_to_gml2(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, const char *prefix);
+extern char* rtgeom_extent_to_gml2(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, const char *prefix);
+/**
+ * @param opts output options bitfield, see RT_GML macros for meaning
+ */
+extern char* rtgeom_extent_to_gml3(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, int opts, const char *prefix);
+extern char* rtgeom_to_gml3(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, int opts, const char *prefix, const char *id);
+extern char* rtgeom_to_kml2(const RTCTX *ctx, const RTGEOM *geom, int precision, const char *prefix);
+extern char* rtgeom_to_geojson(const RTCTX *ctx, const RTGEOM *geo, char *srs, int precision, int has_bbox);
+extern char* rtgeom_to_svg(const RTCTX *ctx, const RTGEOM *geom, int precision, int relative);
+extern char* rtgeom_to_x3d3(const RTCTX *ctx, const RTGEOM *geom, char *srs, int precision, int opts, const char *defid);
+
+/**
+ * Create an RTGEOM object from a GeoJSON representation
+ *
+ * @param geojson the GeoJSON input
+ * @param srs output parameter. Will be set to a newly allocated
+ *            string holding the spatial reference string, or NULL
+ *            if no such parameter is found in input.
+ *            If not null, the pointer must be freed with rtfree.
+ */
+extern RTGEOM* rtgeom_from_geojson(const RTCTX *ctx, const char *geojson, char **srs);
+
+/**
+* Initialize a spheroid object for use in geodetic functions.
+*/
+extern void spheroid_init(const RTCTX *ctx, SPHEROID *s, double a, double b);
+
+/**
+* Calculate the geodetic distance from rtgeom1 to rtgeom2 on the spheroid. 
+* A spheroid with major axis == minor axis will be treated as a sphere.
+* Pass in a tolerance in spheroid units.
+*/
+extern double rtgeom_distance_spheroid(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2, const SPHEROID *spheroid, double tolerance);
+
+/**
+* Calculate the location of a point on a spheroid, give a start point, bearing and distance.
+*/
+extern RTPOINT* rtgeom_project_spheroid(const RTCTX *ctx, const RTPOINT *r, const SPHEROID *spheroid, double distance, double azimuth);
+
+/**
+* Derive a new geometry with vertices added to ensure no vertex is more 
+* than max_seg_length (in radians) from any other vertex.
+*/
+extern RTGEOM* rtgeom_segmentize_sphere(const RTCTX *ctx, const RTGEOM *rtg_in, double max_seg_length);
+
+/**
+* Calculate the bearing between two points on a spheroid.
+*/
+extern double rtgeom_azumith_spheroid(const RTCTX *ctx, const RTPOINT *r, const RTPOINT *s, const SPHEROID *spheroid);
+
+/**
+* Calculate the geodetic area of a rtgeom on the sphere. The result
+* will be multiplied by the average radius of the supplied spheroid.
+*/
+extern double rtgeom_area_sphere(const RTCTX *ctx, const RTGEOM *rtgeom, const SPHEROID *spheroid);
+
+/**
+* Calculate the geodetic area of a rtgeom on the spheroid. The result
+* will have the squared units of the spheroid axes.
+*/
+extern double rtgeom_area_spheroid(const RTCTX *ctx, const RTGEOM *rtgeom, const SPHEROID *spheroid);
+
+/**
+* Calculate the geodetic length of a rtgeom on the unit sphere. The result
+* will have to by multiplied by the real radius to get the real length.
+*/
+extern double rtgeom_length_spheroid(const RTCTX *ctx, const RTGEOM *geom, const SPHEROID *s);
+
+/**
+* Calculate covers predicate for two rtgeoms on the sphere. Currently
+* only handles point-in-polygon.
+*/
+extern int rtgeom_covers_rtgeom_sphere(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2);
+
+/**
+* Remove repeated points!
+*/
+extern RTGEOM* rtgeom_remove_repeated_points(const RTCTX *ctx, const RTGEOM *in, double tolerance);
+
+extern char rttriangle_is_repeated_points(const RTCTX *ctx, RTTRIANGLE *triangle);
+
+/**
+ * Swap ordinate values in every vertex of the geometry.
+ *
+ * Ordinates to swap are specified using an index with meaning:
+ * 0=x, 1=y, 2=z, 3=m
+ *
+ * Swapping an existing ordinate with an unexisting one results
+ * in undefined value being written in the existing ordinate.
+ * Caller should verify and prevent such calls.
+ *
+ * Availability: 2.2.0
+ */
+extern void rtgeom_swap_ordinates(const RTCTX *ctx, RTGEOM *in, RTORD o1, RTORD o2);
+
+/**
+* Reverse the X and Y coordinate order. Useful for geometries in lat/lon order
+* than need to be converted to lon/lat order.
+*
+* NOTE: uses rtgeom_swap_ordinates internally,
+*       kept for backward compatibility
+*/
+extern RTGEOM* rtgeom_flip_coordinates(const RTCTX *ctx, RTGEOM *in);
+
+struct RTPOINTITERATOR;
+typedef struct RTPOINTITERATOR RTPOINTITERATOR;
+
+/**
+ * Create a new RTPOINTITERATOR over supplied RTGEOM*
+ */
+extern RTPOINTITERATOR* rtpointiterator_create(const RTCTX *ctx, const RTGEOM* g);
+
+/**
+ * Create a new RTPOINTITERATOR over supplied RTGEOM*
+ * Supports modification of coordinates during iteration.
+ */
+extern RTPOINTITERATOR* rtpointiterator_create_rw(const RTCTX *ctx, RTGEOM* g);
+
+/** 
+ * Free all memory associated with the iterator
+ */
+extern void rtpointiterator_destroy(const RTCTX *ctx, RTPOINTITERATOR* s);
+
+/**
+ * Returns RT_TRUE if there is another point available in the iterator.
+ */
+extern int rtpointiterator_has_next(const RTCTX *ctx, RTPOINTITERATOR* s);
+
+/**
+ * Attempts to replace the next point int the iterator with p, and advances
+ * the iterator to the next point.
+ * Returns RT_SUCCESS if the assignment was successful, RT_FAILURE otherwise.
+ * */
+extern int rtpointiterator_modify_next(const RTCTX *ctx, RTPOINTITERATOR* s, const RTPOINT4D* p);
+
+/**
+ * Attempts to assign the next point in the iterator to p, and advances
+ * the iterator to the next point.  If p is NULL, the iterator will be
+ * advanced without reading a point.
+ * Returns RT_SUCCESS if the assignment was successful, RT_FAILURE otherwise.
+ * */
+extern int rtpointiterator_next(const RTCTX *ctx, RTPOINTITERATOR* s, RTPOINT4D* p);
+
+/**
+ * Attempts to assigns the next point in the iterator to p.  Does not advance.
+ * Returns RT_SUCCESS if the assignment was successful, RT_FAILURE otherwise.
+ */
+extern int rtpointiterator_peek(const RTCTX *ctx, RTPOINTITERATOR* s, RTPOINT4D* p);
+
+
+/**
+* Convert a single hex digit into the corresponding char
+*/
+extern uint8_t parse_hex(const RTCTX *ctx, char *str);
+
+/**
+* Convert a char into a human readable hex digit 
+*/
+extern void deparse_hex(const RTCTX *ctx, uint8_t str, char *result);
+
+
+
+/***********************************************************************
+** Functions for managing serialized forms and bounding boxes.
+*/
+
+/**
+* Calculate the geocentric bounding box directly from the serialized
+* form of the geodetic coordinates. Only accepts serialized geographies
+* flagged as geodetic. Caller is responsible for disposing of the RTGBOX.
+*/
+extern RTGBOX* gserialized_calculate_gbox_geocentric(const GSERIALIZED *g);
+
+/**
+* Calculate the geocentric bounding box directly from the serialized
+* form of the geodetic coordinates. Only accepts serialized geographies
+* flagged as geodetic.
+*/
+int gserialized_calculate_gbox_geocentric_p(const GSERIALIZED *g, RTGBOX *g_box);
+
+/**
+* Return a RTWKT representation of the gserialized geometry. 
+* Caller is responsible for disposing of the char*.
+*/
+extern char* gserialized_to_string(const RTCTX *ctx, const GSERIALIZED *g);
+
+/**
+* Return a copy of the input serialized geometry. 
+*/ 
+extern GSERIALIZED* gserialized_copy(const RTCTX *ctx, const GSERIALIZED *g);
+
+/**
+* Check that coordinates of RTGEOM are all within the geodetic range (-180, -90, 180, 90)
+*/
+extern int rtgeom_check_geodetic(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Gently move coordinates of RTGEOM if they are close enough into geodetic range.
+*/
+extern int rtgeom_nudge_geodetic(const RTCTX *ctx, RTGEOM *geom);
+
+/**
+* Force coordinates of RTGEOM into geodetic range (-180, -90, 180, 90)
+*/
+extern int rtgeom_force_geodetic(const RTCTX *ctx, RTGEOM *geom);
+
+/**
+* Set the RTFLAGS geodetic bit on geometry an all sub-geometries and pointlists
+*/
+extern void rtgeom_set_geodetic(const RTCTX *ctx, RTGEOM *geom, int value);
+
+/**
+* Calculate the geodetic bounding box for an RTGEOM. Z/M coordinates are 
+* ignored for this calculation. Pass in non-null, geodetic bounding box for function
+* to fill out. RTGEOM must have been built from a GSERIALIZED to provide
+* double aligned point arrays.
+*/
+extern int rtgeom_calculate_gbox_geodetic(const RTCTX *ctx, const RTGEOM *geom, RTGBOX *gbox);
+
+/**
+* Calculate the 2-4D bounding box of a geometry. Z/M coordinates are honored 
+* for this calculation, though for curves they are not included in calculations
+* of curvature.
+*/
+extern int rtgeom_calculate_gbox_cartesian(const RTCTX *ctx, const RTGEOM *rtgeom, RTGBOX *gbox);
+
+/**
+* Calculate bounding box of a geometry, automatically taking into account
+* whether it is cartesian or geodetic.
+*/
+extern int rtgeom_calculate_gbox(const RTCTX *ctx, const RTGEOM *rtgeom, RTGBOX *gbox);
+
+/**
+* New function to read doubles directly from the double* coordinate array
+* of an aligned rtgeom #RTPOINTARRAY (built by de-serializing a #GSERIALIZED).
+*/
+extern int rt_getPoint2d_p_ro(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT2D **point);
+
+/**
+* Calculate geodetic (x/y/z) box and add values to gbox. Return #RT_SUCCESS on success.
+*/
+extern int ptarray_calculate_gbox_geodetic(const RTCTX *ctx, const RTPOINTARRAY *pa, RTGBOX *gbox);
+
+/**
+* Calculate box (x/y) and add values to gbox. Return #RT_SUCCESS on success.
+*/
+extern int ptarray_calculate_gbox_cartesian(const RTCTX *ctx, const RTPOINTARRAY *pa, RTGBOX *gbox );
+
+/**
+* Calculate a spherical point that falls outside the geocentric gbox
+*/
+void gbox_pt_outside(const RTCTX *ctx, const RTGBOX *gbox, RTPOINT2D *pt_outside);
+
+/**
+* Create a new gbox with the dimensionality indicated by the flags. Caller
+* is responsible for freeing.
+*/
+extern RTGBOX* gbox_new(const RTCTX *ctx, uint8_t flags);
+
+/**
+* Zero out all the entries in the #RTGBOX. Useful for cleaning
+* statically allocated gboxes.
+*/
+extern void gbox_init(const RTCTX *ctx, RTGBOX *gbox);
+
+/**
+* Update the merged #RTGBOX to be large enough to include itself and the new box.
+*/
+extern int gbox_merge(const RTCTX *ctx, const RTGBOX *new_box, RTGBOX *merged_box);
+
+/**
+* Update the output #RTGBOX to be large enough to include both inputs.
+*/
+extern int gbox_union(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2, RTGBOX *gout);
+
+/**
+* Move the box minimums down and the maximums up by the distance provided.
+*/
+extern void gbox_expand(const RTCTX *ctx, RTGBOX *g, double d);
+
+/**
+* Initialize a #RTGBOX using the values of the point.
+*/
+extern int gbox_init_point3d(const RTCTX *ctx, const POINT3D *p, RTGBOX *gbox);
+
+/**
+* Update the #RTGBOX to be large enough to include itself and the new point.
+*/
+extern int gbox_merge_point3d(const RTCTX *ctx, const POINT3D *p, RTGBOX *gbox);
+
+/**
+* Return true if the point is inside the gbox
+*/
+extern int gbox_contains_point3d(const RTCTX *ctx, const RTGBOX *gbox, const POINT3D *pt);
+
+/**
+* Allocate a string representation of the #RTGBOX, based on dimensionality of flags.
+*/
+extern char* gbox_to_string(const RTCTX *ctx, const RTGBOX *gbox);
+
+/**
+* Return a copy of the #RTGBOX, based on dimensionality of flags.
+*/
+extern RTGBOX* gbox_copy(const RTCTX *ctx, const RTGBOX *gbox);
+
+/**
+* Warning, do not use this function, it is very particular about inputs.
+*/
+extern RTGBOX* gbox_from_string(const RTCTX *ctx, const char *str);
+
+/**
+* Return #RT_TRUE if the #RTGBOX overlaps, #RT_FALSE otherwise. 
+*/
+extern int gbox_overlaps(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+* Return #RT_TRUE if the #RTGBOX overlaps on the 2d plane, #RT_FALSE otherwise. 
+*/
+extern int gbox_overlaps_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+* Return #RT_TRUE if the first #RTGBOX contains the second on the 2d plane, #RT_FALSE otherwise. 
+*/
+extern int  gbox_contains_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+* Copy the values of original #RTGBOX into duplicate.
+*/
+extern void gbox_duplicate(const RTCTX *ctx, const RTGBOX *original, RTGBOX *duplicate);
+
+/**
+* Return the number of bytes necessary to hold a #RTGBOX of this dimension in 
+* serialized form.
+*/
+extern size_t gbox_serialized_size(const RTCTX *ctx, uint8_t flags);
+
+/**
+* Check if 2 given Gbox are the same
+*/
+extern int gbox_same(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+* Check if 2 given RTGBOX are the same in x and y
+*/
+extern int gbox_same_2d(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+* Check if two given RTGBOX are the same in x and y, or would round to the same
+* RTGBOX in x and if serialized in GSERIALIZED
+*/
+extern int gbox_same_2d_float(const RTCTX *ctx, const RTGBOX *g1, const RTGBOX *g2);
+
+/**
+ * Round given RTGBOX to float boundaries
+ *
+ * This turns a RTGBOX into the version it would become
+ * after a serialize/deserialize round trip.
+ */
+extern void gbox_float_round(const RTCTX *ctx, RTGBOX *gbox);
+
+/**
+* Return false if any of the dimensions is NaN or infinite
+*/
+extern int gbox_is_valid(const RTCTX *ctx, const RTGBOX *gbox);
+
+/**
+* Utility function to get type number from string. For example, a string 'POINTZ' 
+* would return type of 1 and z of 1 and m of 0. Valid 
+*/
+extern int geometry_type_from_string(const RTCTX *ctx, const char *str, uint8_t *type, int *z, int *m);
+
+/**
+* Calculate required memory segment to contain a serialized form of the RTGEOM.
+* Primarily used internally by the serialization code. Exposed to allow the cunit
+* tests to exercise it.
+*/
+extern size_t gserialized_from_rtgeom_size(const RTCTX *ctx, const RTGEOM *geom);
+
+/**
+* Allocate a new #GSERIALIZED from an #RTGEOM. For all non-point types, a bounding
+* box will be calculated and embedded in the serialization. The geodetic flag is used
+* to control the box calculation (cartesian or geocentric). If set, the size pointer
+* will contain the size of the final output, which is useful for setting the PgSQL 
+* VARSIZE information.
+*/
+extern GSERIALIZED* gserialized_from_rtgeom(const RTCTX *ctx, RTGEOM *geom, int is_geodetic, size_t *size);
+
+/**
+* Allocate a new #RTGEOM from a #GSERIALIZED. The resulting #RTGEOM will have coordinates
+* that are double aligned and suitable for direct reading using rt_getPoint2d_p_ro
+*/
+extern RTGEOM* rtgeom_from_gserialized(const RTCTX *ctx, const GSERIALIZED *g);
+
+/**
+* Pull a #RTGBOX from the header of a #GSERIALIZED, if one is available. If
+* it is not, calculate it from the geometry. If that doesn't work (null
+* or empty) return RT_FAILURE.
+*/
+extern int gserialized_get_gbox_p(const RTCTX *ctx, const GSERIALIZED *g, RTGBOX *gbox);
+
+
+/**
+ * Parser check flags
+ * 
+ *  @see rtgeom_from_wkb
+ *  @see rtgeom_from_hexwkb
+ */
+#define RT_PARSER_CHECK_MINPOINTS  1
+#define RT_PARSER_CHECK_ODD        2
+#define RT_PARSER_CHECK_CLOSURE    4
+#define RT_PARSER_CHECK_ZCLOSURE   8
+
+#define RT_PARSER_CHECK_NONE   0
+#define RT_PARSER_CHECK_ALL	(RT_PARSER_CHECK_MINPOINTS | RT_PARSER_CHECK_ODD | RT_PARSER_CHECK_CLOSURE)
+
+/**
+ * Parser result structure: returns the result of attempting to convert
+ * (E)RTWKT/(E)RTWKB to RTGEOM
+ */
+typedef struct struct_rtgeom_parser_result
+{
+	const char *wkinput;        /* Copy of pointer to input RTWKT/RTWKB */
+	uint8_t *serialized_rtgeom;   /* Pointer to serialized RTGEOM */
+	int size;                   /* Size of serialized RTGEOM in bytes */
+	RTGEOM *geom;               /* Pointer to RTGEOM struct */
+	const char *message;        /* Error/warning message */
+	int errcode;                /* Error/warning number */
+	int errlocation;            /* Location of error */
+	int parser_check_flags;     /* Bitmask of validity checks run during this parse */
+}
+RTGEOM_PARSER_RESULT;
+
+/*
+ * Parser error messages (these must match the message array in rtgparse.c)
+ */
+#define PARSER_ERROR_MOREPOINTS     1
+#define PARSER_ERROR_ODDPOINTS      2
+#define PARSER_ERROR_UNCLOSED       3
+#define PARSER_ERROR_MIXDIMS        4
+#define PARSER_ERROR_INVALIDGEOM    5
+#define RTPARSER_ERROR_INVALIDWKBTYPE 6
+#define PARSER_ERROR_INCONTINUOUS   7
+#define PARSER_ERROR_TRIANGLEPOINTS 8
+#define PARSER_ERROR_LESSPOINTS     9
+#define PARSER_ERROR_OTHER          10
+
+
+
+/*
+ * Unparser result structure: returns the result of attempting to convert RTGEOM to (E)RTWKT/(E)RTWKB
+ */
+typedef struct struct_rtgeom_unparser_result
+{
+	uint8_t *serialized_rtgeom;	/* Copy of pointer to input serialized RTGEOM */
+	char *wkoutput;			/* Pointer to RTWKT or RTWKB output */
+	int size;			/* Size of serialized RTGEOM in bytes */
+	const char *message;		/* Error/warning message */
+	int errlocation;		/* Location of error */
+}
+RTGEOM_UNPARSER_RESULT;
+
+/*
+ * Unparser error messages (these must match the message array in rtgunparse.c)
+ */
+#define UNPARSER_ERROR_MOREPOINTS 	1
+#define UNPARSER_ERROR_ODDPOINTS	2
+#define UNPARSER_ERROR_UNCLOSED		3
+
+
+/*
+** Variants available for RTWKB and RTWKT output types
+*/
+
+#define RTWKB_ISO 0x01
+#define RTWKB_SFSQL 0x02
+#define RTWKB_EXTENDED 0x04
+#define RTWKB_NDR 0x08
+#define RTWKB_XDR 0x10
+#define RTWKB_HEX 0x20
+#define RTWKB_NO_NPOINTS 0x40 /* Internal use only */
+#define RTWKB_NO_SRID 0x80 /* Internal use only */
+
+#define RTWKT_ISO 0x01
+#define RTWKT_SFSQL 0x02
+#define RTWKT_EXTENDED 0x04
+
+
+/*
+** Variants available for TWKB
+*/
+#define TWKB_BBOX 0x01 /* User wants bboxes */
+#define TWKB_SIZE 0x02 /* User wants sizes */
+#define TWKB_ID 0x04 /* User wants id */
+#define RTTWKB_NO_TYPE 0x10 /* No type because it is a sub geoemtry */
+#define TWKB_NO_ID 0x20 /* No ID because it is a subgeoemtry */
+#define TWKB_DEFAULT_PRECISION 0 /* Aim for 1m (or ft) rounding by default */
+
+/*
+** New parsing and unparsing functions.
+*/
+
+/**
+* @param rtgeom geometry to convert to RTWKT
+* @param variant output format to use (RTWKT_ISO, RTWKT_SFSQL, RTWKT_EXTENDED)
+*/
+extern char*   rtgeom_to_wkt(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, int precision, size_t *size_out);
+
+/**
+* @param rtgeom geometry to convert to RTWKT
+* @param variant output format to use
+*                (RTWKB_ISO, RTWKB_SFSQL, RTWKB_EXTENDED, RTWKB_NDR, RTWKB_XDR)
+*/
+extern uint8_t*  rtgeom_to_wkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, size_t *size_out);
+
+/**
+* @param rtgeom geometry to convert to HEXWKB
+* @param variant output format to use
+*                (RTWKB_ISO, RTWKB_SFSQL, RTWKB_EXTENDED, RTWKB_NDR, RTWKB_XDR)
+*/
+extern char*   rtgeom_to_hexwkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, size_t *size_out);
+
+/**
+* @param rtgeom geometry to convert to EWKT
+*/
+extern char *rtgeom_to_ewkt(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+/**
+ * @param check parser check flags, see RT_PARSER_CHECK_* macros
+ * @param size length of RTWKB byte buffer
+ * @param wkb RTWKB byte buffer
+ */
+extern RTGEOM* rtgeom_from_wkb(const RTCTX *ctx, const uint8_t *wkb, const size_t wkb_size, const char check);
+
+/**
+ * @param check parser check flags, see RT_PARSER_CHECK_* macros
+ */
+extern RTGEOM* rtgeom_from_hexwkb(const RTCTX *ctx, const char *hexwkb, const char check);
+
+extern uint8_t*  bytes_from_hexbytes(const RTCTX *ctx, const char *hexbuf, size_t hexsize);
+
+extern char*   hexbytes_from_bytes(const RTCTX *ctx, uint8_t *bytes, size_t size);
+
+
+/* Memory management */
+extern void *rtalloc(const RTCTX *ctx, size_t size);
+extern void *rtrealloc(const RTCTX *ctx, void *mem, size_t size);
+extern void rtfree(const RTCTX *ctx, void *mem);
+
+/* Utilities */
+extern char *rtmessage_truncate(const RTCTX *ctx, char *str, int startpos, int endpos, int maxlength, int truncdirection);
+
+/*
+* TWKB functions
+*/
+
+/**
+ * @param check parser check flags, see RT_PARSER_CHECK_* macros
+ * @param size parser check flags, see RT_PARSER_CHECK_* macros
+ */
+extern RTGEOM* rtgeom_from_twkb(const RTCTX *ctx, uint8_t *twkb, size_t twkb_size, char check);
+
+/**
+ * @param geom input geometry
+ * @param variant what variations on TWKB are requested?
+ * @param twkb_size returns the length of the output TWKB in bytes if set
+ */
+extern uint8_t* rtgeom_to_twkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m, size_t *twkb_size);
+
+extern uint8_t* rtgeom_to_twkb_with_idlist(const RTCTX *ctx, const RTGEOM *geom, int64_t *idlist, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m, size_t *twkb_size);
+
+/*******************************************************************************
+ * SQLMM internal functions - TODO: Move into separate header files
+ ******************************************************************************/
+
+int rtgeom_has_arc(const RTCTX *ctx, const RTGEOM *geom);
+RTGEOM *rtgeom_stroke(const RTCTX *ctx, const RTGEOM *geom, uint32_t perQuad);
+RTGEOM *rtgeom_unstroke(const RTCTX *ctx, const RTGEOM *geom);
+
+/*******************************************************************************
+ * GEOS proxy functions on RTGEOM
+ ******************************************************************************/
+
+/** Return GEOS version string (not to be freed) */
+const char* rtgeom_geos_version(void);
+
+/** Convert an RTGEOM to a GEOS Geometry and convert back -- for debug only */
+RTGEOM* rtgeom_geos_noop(const RTCTX *ctx, const RTGEOM *geom) ;
+
+RTGEOM *rtgeom_normalize(const RTCTX *ctx, const RTGEOM *geom);
+RTGEOM *rtgeom_intersection(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2);
+RTGEOM *rtgeom_difference(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2);
+RTGEOM *rtgeom_symdifference(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2);
+RTGEOM *rtgeom_union(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2);
+RTGEOM *rtgeom_linemerge(const RTCTX *ctx, const RTGEOM *geom1);
+RTGEOM *rtgeom_unaryunion(const RTCTX *ctx, const RTGEOM *geom1);
+RTGEOM *rtgeom_clip_by_rect(const RTCTX *ctx, const RTGEOM *geom1, double x0, double y0, double x1, double y1);
+RTCOLLECTION *rtgeom_subdivide(const RTCTX *ctx, const RTGEOM *geom, int maxvertices);
+
+/**
+ * Snap vertices and segments of a geometry to another using a given tolerance.
+ *
+ * @param geom1 the geometry to snap
+ * @param geom2 the geometry to snap to
+ * @param tolerance the distance under which vertices and segments are snapped
+ * 
+ * Requires GEOS-3.3.0+
+ */
+RTGEOM* rtgeom_snap(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2, double tolerance);
+
+/*
+ * Return the set of paths shared between two linear geometries,
+ * and their direction (same or opposite).
+ *
+ * @param geom1 a lineal geometry
+ * @param geom2 another lineal geometry
+ *
+ * Requires GEOS-3.3.0+
+ */
+RTGEOM* rtgeom_sharedpaths(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2);
+
+/*
+ * An offset curve against the input line.
+ *
+ * @param rtline a lineal geometry
+ * @param size offset distance. Offset left if negative and right if positive
+ * @param quadsegs number of quadrature segments in curves (try 8)
+ * @param joinStyle (1 = round, 2 = mitre, 3 = bevel)
+ * @param mitreLimit (try 5.0)
+ * @return derived geometry (linestring or multilinestring)
+ *
+ * Requires GEOS-3.2.0+
+ */
+RTGEOM* rtgeom_offsetcurve(const RTCTX *ctx, const RTLINE *rtline, double size, int quadsegs, int joinStyle, double mitreLimit);
+
+/*
+ * Return true if the input geometry is "simple" as per OGC defn.
+ *
+ * @return 1 if simple, 0 if non-simple, -1 on exception (rterror is called
+ *         in that case)
+ */
+int rtgeom_is_simple(const RTCTX *ctx, const RTGEOM *rtgeom);
+
+
+/*******************************************************************************
+ * GEOS-dependent extra functions on RTGEOM
+ ******************************************************************************/
+
+/**
+ * Take a geometry and return an areal geometry
+ * (Polygon or MultiPolygon).
+ * Actually a wrapper around GEOSpolygonize,
+ * transforming the resulting collection into
+ * a valid polygon Geometry.
+ */
+RTGEOM* rtgeom_buildarea(const RTCTX *ctx, const RTGEOM *geom) ;
+
+
+/**
+ * Attempts to make an invalid geometries valid w/out losing points.
+ *
+ * NOTE: this is only available when librtgeom is built against
+ *       GEOS 3.3.0 or higher
+ */
+RTGEOM* rtgeom_make_valid(const RTCTX *ctx, RTGEOM* geom);
+
+/*
+ * Split (multi)polygon by line; (multi)line by (multi)line,
+ * (multi)point or (multi)polygon boundary.
+ *
+ * Collections are accepted as first argument.
+ * Returns all obtained pieces as a collection.
+ */
+RTGEOM* rtgeom_split(const RTCTX *ctx, const RTGEOM* rtgeom_in, const RTGEOM* blade_in);
+
+/*
+ * Fully node a set of linestrings, using the least nodes preserving
+ * all the input ones.
+ *
+ * Requires GEOS-3.3.0 or higher
+ */
+RTGEOM* rtgeom_node(const RTCTX *ctx, const RTGEOM* rtgeom_in);
+
+/**
+ * Take vertices of a geometry and build a delaunay
+ * triangulation on them.
+ *
+ * @param geom the input geometry
+ * @param tolerance an optional snapping tolerance for improved tolerance
+ * @param edgeOnly if non-zero the result will be a MULTILINESTRING,
+ *                 otherwise it'll be a COLLECTION of polygons.
+ */
+RTGEOM* rtgeom_delaunay_triangulation(const RTCTX *ctx, const RTGEOM *geom, double tolerance, int edgeOnly);
+
+#endif /* !defined _LIBRTGEOM_H  */
+
diff --git a/src/librtgeom_internal.h b/src/librtgeom_internal.h
new file mode 100644
index 0000000..deea156
--- /dev/null
+++ b/src/librtgeom_internal.h
@@ -0,0 +1,500 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2011-2012 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2011 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (C) 2007-2008 Mark Cave-Ayland
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#ifndef _LIBRTGEOM_INTERNAL_H
+#define _LIBRTGEOM_INTERNAL_H 1
+
+#include "rttopo_config.h"
+#include "librtgeom.h"
+
+#include "rtgeom_log.h"
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#if HAVE_IEEEFP_H
+#include <ieeefp.h>
+#endif
+
+#if defined(PJ_VERSION) && PJ_VERSION >= 490
+/* Enable new geodesic functions */
+#define PROJ_GEODESIC 1
+#else
+/* Use the old (pre-2.2) geodesic functions */
+#define PROJ_GEODESIC 0
+#endif
+
+
+#include <float.h>
+
+#include "librtgeom.h"
+
+/**
+* Floating point comparators.
+*/
+#define FP_TOLERANCE 1e-12
+#define FP_IS_ZERO(A) (fabs(A) <= FP_TOLERANCE)
+#define FP_MAX(A, B) (((A) > (B)) ? (A) : (B))
+#define FP_MIN(A, B) (((A) < (B)) ? (A) : (B))
+#define FP_ABS(a)   ((a) <	(0) ? -(a) : (a))
+#define FP_EQUALS(A, B) (fabs((A)-(B)) <= FP_TOLERANCE)
+#define FP_NEQUALS(A, B) (fabs((A)-(B)) > FP_TOLERANCE)
+#define FP_LT(A, B) (((A) + FP_TOLERANCE) < (B))
+#define FP_LTEQ(A, B) (((A) - FP_TOLERANCE) <= (B))
+#define FP_GT(A, B) (((A) - FP_TOLERANCE) > (B))
+#define FP_GTEQ(A, B) (((A) + FP_TOLERANCE) >= (B))
+#define FP_CONTAINS_TOP(A, X, B) (FP_LT(A, X) && FP_LTEQ(X, B))
+#define FP_CONTAINS_BOTTOM(A, X, B) (FP_LTEQ(A, X) && FP_LT(X, B))
+#define FP_CONTAINS_INCL(A, X, B) (FP_LTEQ(A, X) && FP_LTEQ(X, B))
+#define FP_CONTAINS_EXCL(A, X, B) (FP_LT(A, X) && FP_LT(X, B))
+#define FP_CONTAINS(A, X, B) FP_CONTAINS_EXCL(A, X, B)
+
+
+/*
+* this will change to NaN when I figure out how to
+* get NaN in a platform-independent way
+*/
+#define NO_VALUE 0.0
+#define NO_Z_VALUE NO_VALUE
+#define NO_M_VALUE NO_VALUE
+
+
+/**
+* Well-Known Text (RTWKT) Output Variant Types
+*/
+#define RTWKT_NO_TYPE 0x08 /* Internal use only */
+#define RTWKT_NO_PARENS 0x10 /* Internal use only */
+#define RTWKT_IS_CHILD 0x20 /* Internal use only */
+
+/**
+* Well-Known Binary (RTWKB) Output Variant Types
+*/
+
+#define RTWKB_DOUBLE_SIZE 8 /* Internal use only */
+#define RTWKB_INT_SIZE 4 /* Internal use only */
+#define RTWKB_BYTE_SIZE 1 /* Internal use only */
+
+/**
+* Well-Known Binary (RTWKB) Geometry Types 
+*/
+#define RTWKB_POINT_TYPE 1
+#define RTWKB_LINESTRING_TYPE 2
+#define RTWKB_POLYGON_TYPE 3
+#define RTWKB_MULTIPOINT_TYPE 4
+#define RTWKB_MULTILINESTRING_TYPE 5
+#define RTWKB_MULTIPOLYGON_TYPE 6
+#define RTWKB_GEOMETRYCOLLECTION_TYPE 7
+#define RTWKB_CIRCULARSTRING_TYPE 8
+#define RTWKB_COMPOUNDCURVE_TYPE 9
+#define RTWKB_CURVEPOLYGON_TYPE 10
+#define RTWKB_MULTICURVE_TYPE 11
+#define RTWKB_MULTISURFACE_TYPE 12
+#define RTWKB_CURVE_TYPE 13 /* from ISO draft, not sure is real */
+#define RTWKB_SURFACE_TYPE 14 /* from ISO draft, not sure is real */
+#define RTWKB_POLYHEDRALSURFACE_TYPE 15
+#define RTWKB_TIN_TYPE 16
+#define RTWKB_TRIANGLE_TYPE 17
+
+/**
+* Macro for reading the size from the GSERIALIZED size attribute.
+* Cribbed from PgSQL, top 30 bits are size. Use VARSIZE() when working
+* internally with PgSQL.
+*/
+#define SIZE_GET(varsize) (((varsize) >> 2) & 0x3FFFFFFF)
+#define SIZE_SET(varsize, size) (((varsize) & 0x00000003)|(((size) & 0x3FFFFFFF) << 2 ))
+
+/**
+* Tolerance used to determine equality.
+*/
+#define EPSILON_SQLMM 1e-8
+
+/*
+ * Export functions
+ */
+#define OUT_MAX_DOUBLE 1E15
+#define OUT_SHOW_DIGS_DOUBLE 20
+#define OUT_MAX_DOUBLE_PRECISION 15
+#define OUT_MAX_DIGS_DOUBLE (OUT_SHOW_DIGS_DOUBLE + 2) /* +2 mean add dot and sign */
+
+
+/**
+* Constants for point-in-polygon return values
+*/
+#define RT_INSIDE 1
+#define RT_BOUNDARY 0
+#define RT_OUTSIDE -1
+
+#define RTGEOM_GEOS_ERRMSG_MAXSIZE 256
+
+struct RTCTX_T {
+  GEOSContextHandle_t gctx;
+  char rtgeom_geos_errmsg[RTGEOM_GEOS_ERRMSG_MAXSIZE];
+  rtallocator rtalloc_var;
+  rtreallocator rtrealloc_var;
+  rtfreeor rtfree_var;
+  rtreporter error_logger;
+  void * error_logger_arg;
+  rtreporter notice_logger;
+  void * notice_logger_arg;
+  rtdebuglogger debug_logger;
+  void * debug_logger_arg;
+};
+
+/*
+* Internal prototypes
+*/
+
+/* Machine endianness */
+#define XDR 0 /* big endian */
+#define NDR 1 /* little endian */
+extern char getMachineEndian(const RTCTX *ctx);
+
+
+/*
+* Force dims
+*/
+RTGEOM* rtgeom_force_dims(const RTCTX *ctx, const RTGEOM *rtgeom, int hasz, int hasm);
+RTPOINT* rtpoint_force_dims(const RTCTX *ctx, const RTPOINT *rtpoint, int hasz, int hasm);
+RTLINE* rtline_force_dims(const RTCTX *ctx, const RTLINE *rtline, int hasz, int hasm);
+RTPOLY* rtpoly_force_dims(const RTCTX *ctx, const RTPOLY *rtpoly, int hasz, int hasm);
+RTCOLLECTION* rtcollection_force_dims(const RTCTX *ctx, const RTCOLLECTION *rtcol, int hasz, int hasm);
+RTPOINTARRAY* ptarray_force_dims(const RTCTX *ctx, const RTPOINTARRAY *pa, int hasz, int hasm);
+
+/**
+ * Swap ordinate values o1 and o2 on a given RTPOINTARRAY
+ *
+ * Ordinates semantic is: 0=x 1=y 2=z 3=m
+ */
+void ptarray_swap_ordinates(const RTCTX *ctx, RTPOINTARRAY *pa, RTORD o1, RTORD o2);
+
+/*
+* Is Empty?
+*/
+int rtpoly_is_empty(const RTCTX *ctx, const RTPOLY *poly);
+int rtcollection_is_empty(const RTCTX *ctx, const RTCOLLECTION *col);
+int rtcircstring_is_empty(const RTCTX *ctx, const RTCIRCSTRING *circ);
+int rttriangle_is_empty(const RTCTX *ctx, const RTTRIANGLE *triangle);
+int rtline_is_empty(const RTCTX *ctx, const RTLINE *line);
+int rtpoint_is_empty(const RTCTX *ctx, const RTPOINT *point);
+
+/*
+* Number of vertices?
+*/
+int rtline_count_vertices(const RTCTX *ctx, RTLINE *line);
+int rtpoly_count_vertices(const RTCTX *ctx, RTPOLY *poly);
+int rtcollection_count_vertices(const RTCTX *ctx, RTCOLLECTION *col);
+
+/*
+* Read from byte buffer
+*/
+extern uint32_t rt_get_uint32_t(const RTCTX *ctx, const uint8_t *loc);
+extern int32_t rt_get_int32_t(const RTCTX *ctx, const uint8_t *loc);
+
+/*
+* DP simplification
+*/
+
+/**
+ * @param minpts minimun number of points to retain, if possible.
+ */
+RTPOINTARRAY* ptarray_simplify(const RTCTX *ctx, RTPOINTARRAY *inpts, double epsilon, unsigned int minpts);
+RTLINE* rtline_simplify(const RTCTX *ctx, const RTLINE *iline, double dist, int preserve_collapsed);
+RTPOLY* rtpoly_simplify(const RTCTX *ctx, const RTPOLY *ipoly, double dist, int preserve_collapsed);
+RTCOLLECTION* rtcollection_simplify(const RTCTX *ctx, const RTCOLLECTION *igeom, double dist, int preserve_collapsed);
+
+/*
+* Computational geometry
+*/
+int signum(const RTCTX *ctx, double n);
+
+/*
+* The possible ways a pair of segments can interact. Returned by rt_segment_intersects 
+*/
+enum RTCG_SEGMENT_INTERSECTION_TYPE {
+    SEG_ERROR = -1,
+    SEG_NO_INTERSECTION = 0,
+    SEG_COLINEAR = 1,
+    SEG_CROSS_LEFT = 2,
+    SEG_CROSS_RIGHT = 3,
+    SEG_TOUCH_LEFT = 4,
+    SEG_TOUCH_RIGHT = 5
+};
+
+/*
+* Do the segments intersect? How?
+*/
+int rt_segment_intersects(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *q1, const RTPOINT2D *q2);
+
+/*
+* Get/Set an enumeratoed ordinate. (x,y,z,m)
+*/
+double rtpoint_get_ordinate(const RTCTX *ctx, const RTPOINT4D *p, char ordinate);
+void rtpoint_set_ordinate(const RTCTX *ctx, RTPOINT4D *p, char ordinate, double value);
+
+/* 
+* Generate an interpolated coordinate p given an interpolation value and ordinate to apply it to
+*/
+int point_interpolate(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2, RTPOINT4D *p, int hasz, int hasm, char ordinate, double interpolation_value);
+
+
+/**
+* Clip a line based on the from/to range of one of its ordinates. Use for m- and z- clipping 
+*/
+RTCOLLECTION *rtline_clip_to_ordinate_range(const RTCTX *ctx, const RTLINE *line, char ordinate, double from, double to);
+
+/**
+* Clip a multi-line based on the from/to range of one of its ordinates. Use for m- and z- clipping 
+*/
+RTCOLLECTION *rtmline_clip_to_ordinate_range(const RTCTX *ctx, const RTMLINE *mline, char ordinate, double from, double to);
+
+/**
+* Clip a multi-point based on the from/to range of one of its ordinates. Use for m- and z- clipping 
+*/
+RTCOLLECTION *rtmpoint_clip_to_ordinate_range(const RTCTX *ctx, const RTMPOINT *mpoint, char ordinate, double from, double to);
+
+/**
+* Clip a point based on the from/to range of one of its ordinates. Use for m- and z- clipping 
+*/
+RTCOLLECTION *rtpoint_clip_to_ordinate_range(const RTCTX *ctx, const RTPOINT *mpoint, char ordinate, double from, double to);
+
+/*
+* Geohash
+*/
+int rtgeom_geohash_precision(const RTCTX *ctx, RTGBOX bbox, RTGBOX *bounds);
+char *geohash_point(const RTCTX *ctx, double longitude, double latitude, int precision);
+void decode_geohash_bbox(const RTCTX *ctx, char *geohash, double *lat, double *lon, int precision);
+
+/*
+* Point comparisons
+*/
+int p4d_same(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2);
+int p3d_same(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2);
+int p2d_same(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2);
+
+/*
+* Area calculations
+*/
+double rtpoly_area(const RTCTX *ctx, const RTPOLY *poly);
+double rtcurvepoly_area(const RTCTX *ctx, const RTCURVEPOLY *curvepoly);
+double rttriangle_area(const RTCTX *ctx, const RTTRIANGLE *triangle);
+
+/**
+* Pull a #RTGBOX from the header of a #GSERIALIZED, if one is available. If
+* it is not, return RT_FAILURE.
+*/
+extern int gserialized_read_gbox_p(const RTCTX *ctx, const GSERIALIZED *g, RTGBOX *gbox);
+
+/*
+* Length calculations
+*/
+double rtcompound_length(const RTCTX *ctx, const RTCOMPOUND *comp);
+double rtcompound_length_2d(const RTCTX *ctx, const RTCOMPOUND *comp);
+double rtline_length(const RTCTX *ctx, const RTLINE *line);
+double rtline_length_2d(const RTCTX *ctx, const RTLINE *line);
+double rtcircstring_length(const RTCTX *ctx, const RTCIRCSTRING *circ);
+double rtcircstring_length_2d(const RTCTX *ctx, const RTCIRCSTRING *circ);
+double rtpoly_perimeter(const RTCTX *ctx, const RTPOLY *poly);
+double rtpoly_perimeter_2d(const RTCTX *ctx, const RTPOLY *poly);
+double rtcurvepoly_perimeter(const RTCTX *ctx, const RTCURVEPOLY *poly);
+double rtcurvepoly_perimeter_2d(const RTCTX *ctx, const RTCURVEPOLY *poly);
+double rttriangle_perimeter(const RTCTX *ctx, const RTTRIANGLE *triangle);
+double rttriangle_perimeter_2d(const RTCTX *ctx, const RTTRIANGLE *triangle);
+
+/*
+* Segmentization
+*/
+RTLINE *rtcircstring_stroke(const RTCTX *ctx, const RTCIRCSTRING *icurve, uint32_t perQuad);
+RTLINE *rtcompound_stroke(const RTCTX *ctx, const RTCOMPOUND *icompound, uint32_t perQuad);
+RTPOLY *rtcurvepoly_stroke(const RTCTX *ctx, const RTCURVEPOLY *curvepoly, uint32_t perQuad);
+
+/*
+* Affine
+*/
+void ptarray_affine(const RTCTX *ctx, RTPOINTARRAY *pa, const RTAFFINE *affine);
+
+/*
+* Scale
+*/
+void ptarray_scale(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *factor);
+
+/*
+* PointArray
+*/
+int ptarray_has_z(const RTCTX *ctx, const RTPOINTARRAY *pa);
+int ptarray_has_m(const RTCTX *ctx, const RTPOINTARRAY *pa);
+double ptarray_signed_area(const RTCTX *ctx, const RTPOINTARRAY *pa);
+
+/*
+* Clone support
+*/
+RTLINE *rtline_clone(const RTCTX *ctx, const RTLINE *rtgeom);
+RTPOLY *rtpoly_clone(const RTCTX *ctx, const RTPOLY *rtgeom);
+RTTRIANGLE *rttriangle_clone(const RTCTX *ctx, const RTTRIANGLE *rtgeom);
+RTCOLLECTION *rtcollection_clone(const RTCTX *ctx, const RTCOLLECTION *rtgeom);
+RTCIRCSTRING *rtcircstring_clone(const RTCTX *ctx, const RTCIRCSTRING *curve);
+RTPOINTARRAY *ptarray_clone(const RTCTX *ctx, const RTPOINTARRAY *ptarray);
+RTGBOX *box2d_clone(const RTCTX *ctx, const RTGBOX *rtgeom);
+RTLINE *rtline_clone_deep(const RTCTX *ctx, const RTLINE *rtgeom);
+RTPOLY *rtpoly_clone_deep(const RTCTX *ctx, const RTPOLY *rtgeom);
+RTCOLLECTION *rtcollection_clone_deep(const RTCTX *ctx, const RTCOLLECTION *rtgeom);
+RTGBOX *gbox_clone(const RTCTX *ctx, const RTGBOX *gbox);
+
+/*
+* Startpoint
+*/
+int rtpoly_startpoint(const RTCTX *ctx, const RTPOLY* rtpoly, RTPOINT4D* pt);
+int ptarray_startpoint(const RTCTX *ctx, const RTPOINTARRAY* pa, RTPOINT4D* pt);
+int rtcollection_startpoint(const RTCTX *ctx, const RTCOLLECTION* col, RTPOINT4D* pt);
+
+/*
+ * Write into *ret the coordinates of the closest point on
+ * segment A-B to the reference input point R
+ */
+void closest_point_on_segment(const RTCTX *ctx, const RTPOINT4D *R, const RTPOINT4D *A, const RTPOINT4D *B, RTPOINT4D *ret);
+
+/* 
+* Repeated points
+*/
+RTPOINTARRAY *ptarray_remove_repeated_points_minpoints(const RTCTX *ctx, const RTPOINTARRAY *in, double tolerance, int minpoints);
+RTPOINTARRAY *ptarray_remove_repeated_points(const RTCTX *ctx, const RTPOINTARRAY *in, double tolerance);
+RTGEOM* rtmpoint_remove_repeated_points(const RTCTX *ctx, const RTMPOINT *in, double tolerance);
+RTGEOM* rtline_remove_repeated_points(const RTCTX *ctx, const RTLINE *in, double tolerance);
+RTGEOM* rtcollection_remove_repeated_points(const RTCTX *ctx, const RTCOLLECTION *in, double tolerance);
+RTGEOM* rtpoly_remove_repeated_points(const RTCTX *ctx, const RTPOLY *in, double tolerance);
+
+/*
+* Closure test
+*/
+int rtline_is_closed(const RTCTX *ctx, const RTLINE *line);
+int rtpoly_is_closed(const RTCTX *ctx, const RTPOLY *poly);
+int rtcircstring_is_closed(const RTCTX *ctx, const RTCIRCSTRING *curve);
+int rtcompound_is_closed(const RTCTX *ctx, const RTCOMPOUND *curve);
+int rtpsurface_is_closed(const RTCTX *ctx, const RTPSURFACE *psurface);
+int rttin_is_closed(const RTCTX *ctx, const RTTIN *tin);
+
+/**
+* Snap to grid
+*/
+
+/**
+* Snap-to-grid Support
+*/
+typedef struct gridspec_t
+{
+	double ipx;
+	double ipy;
+	double ipz;
+	double ipm;
+	double xsize;
+	double ysize;
+	double zsize;
+	double msize;
+}
+gridspec;
+
+RTGEOM* rtgeom_grid(const RTCTX *ctx, const RTGEOM *rtgeom, const gridspec *grid);
+RTCOLLECTION* rtcollection_grid(const RTCTX *ctx, const RTCOLLECTION *coll, const gridspec *grid);
+RTPOINT* rtpoint_grid(const RTCTX *ctx, const RTPOINT *point, const gridspec *grid);
+RTPOLY* rtpoly_grid(const RTCTX *ctx, const RTPOLY *poly, const gridspec *grid);
+RTLINE* rtline_grid(const RTCTX *ctx, const RTLINE *line, const gridspec *grid);
+RTCIRCSTRING* rtcircstring_grid(const RTCTX *ctx, const RTCIRCSTRING *line, const gridspec *grid);
+RTPOINTARRAY* ptarray_grid(const RTCTX *ctx, const RTPOINTARRAY *pa, const gridspec *grid);
+
+/*
+* What side of the line formed by p1 and p2 does q fall? 
+* Returns -1 for left and 1 for right and 0 for co-linearity
+*/
+int rt_segment_side(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *q);
+int rt_arc_side(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, const RTPOINT2D *Q);
+int rt_arc_calculate_gbox_cartesian_2d(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, RTGBOX *gbox);
+double rt_arc_center(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *p3, RTPOINT2D *result);
+int rt_pt_in_seg(const RTCTX *ctx, const RTPOINT2D *P, const RTPOINT2D *A1, const RTPOINT2D *A2);
+int rt_pt_in_arc(const RTCTX *ctx, const RTPOINT2D *P, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3);
+int rt_arc_is_pt(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3);
+double rt_seg_length(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2);
+double rt_arc_length(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3);
+int pt_in_ring_2d(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINTARRAY *ring);
+int ptarray_contains_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt);
+int ptarrayarc_contains_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt);
+int ptarray_contains_point_partial(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt, int check_closed, int *winding_number);
+int ptarrayarc_contains_point_partial(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt, int check_closed, int *winding_number);
+int rtcompound_contains_point(const RTCTX *ctx, const RTCOMPOUND *comp, const RTPOINT2D *pt);
+int rtgeom_contains_point(const RTCTX *ctx, const RTGEOM *geom, const RTPOINT2D *pt);
+
+/**
+* Split a line by a point and push components to the provided multiline.
+* If the point doesn't split the line, push nothing to the container.
+* Returns 0 if the point is off the line.
+* Returns 1 if the point is on the line boundary (endpoints).
+* Return 2 if the point is on the interior of the line (only case in which
+* a split happens).
+*
+* NOTE: the components pushed to the output vector have their SRID stripped 
+*/
+int rtline_split_by_point_to(const RTCTX *ctx, const RTLINE* ln, const RTPOINT* pt, RTMLINE* to);
+
+/** Ensure the collection can hold at least up to ngeoms geometries */
+void rtcollection_reserve(const RTCTX *ctx, RTCOLLECTION *col, int ngeoms);
+
+/** Check if subtype is allowed in collectiontype */
+extern int rtcollection_allows_subtype(const RTCTX *ctx, int collectiontype, int subtype);
+
+/** RTGBOX utility functions to figure out coverage/location on the globe */
+double gbox_angular_height(const RTCTX *ctx, const RTGBOX* gbox);
+double gbox_angular_width(const RTCTX *ctx, const RTGBOX* gbox);
+int gbox_centroid(const RTCTX *ctx, const RTGBOX* gbox, RTPOINT2D* out);
+
+/* Utilities */
+extern void trim_trailing_zeros(const RTCTX *ctx, char *num);
+
+extern uint8_t RTMULTITYPE[RTNUMTYPES];
+
+extern rtinterrupt_callback *_rtgeom_interrupt_callback;
+extern int _rtgeom_interrupt_requested;
+#define RT_ON_INTERRUPT(x) { \
+  if ( _rtgeom_interrupt_callback ) { \
+    (*_rtgeom_interrupt_callback)(); \
+  } \
+  if ( _rtgeom_interrupt_requested ) { \
+    _rtgeom_interrupt_requested = 0; \
+    rtnotice(ctx, "librtgeom code interrupted"); \
+    x; \
+  } \
+}
+
+int ptarray_npoints_in_rect(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTGBOX *gbox);
+int gbox_contains_point2d(const RTCTX *ctx, const RTGBOX *g, const RTPOINT2D *p);
+int rtpoly_contains_point(const RTCTX *ctx, const RTPOLY *poly, const RTPOINT2D *pt);
+
+#endif /* _LIBRTGEOM_INTERNAL_H */
diff --git a/src/librtgeom_topo.h b/src/librtgeom_topo.h
new file mode 100644
index 0000000..14294e4
--- /dev/null
+++ b/src/librtgeom_topo.h
@@ -0,0 +1,1352 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#ifndef LIBRTGEOM_TOPO_H
+#define LIBRTGEOM_TOPO_H 1
+
+#include "librtgeom.h"
+
+/* INT64 */
+typedef int64_t RTT_INT64;
+
+/** Identifier of topology element */
+typedef RTT_INT64 RTT_ELEMID;
+
+/*
+ * ISO primitive elements
+ */
+
+/** NODE */
+typedef struct
+{
+  RTT_ELEMID node_id;
+  RTT_ELEMID containing_face; /* -1 if not isolated */
+  RTPOINT *geom;
+}
+RTT_ISO_NODE;
+
+void rtt_iso_node_release(RTT_ISO_NODE* node);
+
+/** Node fields */
+#define RTT_COL_NODE_NODE_ID         1<<0
+#define RTT_COL_NODE_CONTAINING_FACE 1<<1
+#define RTT_COL_NODE_GEOM            1<<2
+#define RTT_COL_NODE_ALL            (1<<3)-1
+
+/** EDGE */
+typedef struct
+{
+  RTT_ELEMID edge_id;
+  RTT_ELEMID start_node;
+  RTT_ELEMID end_node;
+  RTT_ELEMID face_left;
+  RTT_ELEMID face_right;
+  RTT_ELEMID next_left;
+  RTT_ELEMID next_right;
+  RTLINE *geom;
+}
+RTT_ISO_EDGE;
+
+/** Edge fields */
+#define RTT_COL_EDGE_EDGE_ID         1<<0
+#define RTT_COL_EDGE_START_NODE      1<<1
+#define RTT_COL_EDGE_END_NODE        1<<2
+#define RTT_COL_EDGE_FACE_LEFT       1<<3
+#define RTT_COL_EDGE_FACE_RIGHT      1<<4
+#define RTT_COL_EDGE_NEXT_LEFT       1<<5
+#define RTT_COL_EDGE_NEXT_RIGHT      1<<6
+#define RTT_COL_EDGE_GEOM            1<<7
+#define RTT_COL_EDGE_ALL            (1<<8)-1
+
+/** FACE */
+typedef struct
+{
+  RTT_ELEMID face_id;
+  RTGBOX *mbr;
+}
+RTT_ISO_FACE;
+
+/** Face fields */
+#define RTT_COL_FACE_FACE_ID         1<<0
+#define RTT_COL_FACE_MBR             1<<1
+#define RTT_COL_FACE_ALL            (1<<2)-1
+
+typedef enum RTT_SPATIALTYPE_T {
+  RTT_PUNTAL = 0,
+  RTT_LINEAL = 1,
+  RTT_AREAL = 2,
+  RTT_COLLECTION = 3
+} RTT_SPATIALTYPE;
+
+/*
+ * Backend handling functions
+ */
+
+/* opaque pointers referencing native backend objects */
+
+/**
+ * Backend private data pointer
+ *
+ * Only the backend handler needs to know what it really is.
+ * It will be passed to all registered callback functions.
+ */
+typedef struct RTT_BE_DATA_T RTT_BE_DATA;
+
+/**
+ * Backend interface handler
+ *
+ * Embeds all registered backend callbacks and private data pointer.
+ * Will need to be passed (directly or indirectly) to al public facing
+ * APIs of this library.
+ */
+typedef struct RTT_BE_IFACE_T RTT_BE_IFACE;
+
+/**
+ * Topology handler.
+ *
+ * Embeds backend interface handler.
+ * Will need to be passed to all topology manipulation APIs
+ * of this library.
+ */
+typedef struct RTT_BE_TOPOLOGY_T RTT_BE_TOPOLOGY;
+
+/**
+ * Structure containing base backend callbacks
+ *
+ * Used for registering into the backend iface
+ */
+typedef struct RTT_BE_CALLBACKS_T {
+
+  /**
+   * Read last error message from backend
+   *
+   * @return NULL-terminated error string
+   */
+  const char* (*lastErrorMessage) (const RTT_BE_DATA* be);
+
+  /**
+   * Create a new topology in the backend
+   *
+   * @param name the topology name
+   * @param srid the topology SRID
+   * @param precision the topology precision/tolerance
+   * @param hasZ non-zero if topology primitives should have a Z ordinate
+   * @return a topology handler, which embeds the backend data/params
+   *         or NULL on error (@see lastErrorMessage)
+   */
+  RTT_BE_TOPOLOGY* (*createTopology) (
+    const RTT_BE_DATA* be,
+    const char* name, int srid, double precision, int hasZ
+  );
+
+  /**
+   * Load a topology from the backend
+   *
+   * @param name the topology name
+   * @return a topology handler, which embeds the backend data/params
+   *         or NULL on error (@see lastErrorMessage)
+   */
+  RTT_BE_TOPOLOGY* (*loadTopologyByName) (
+    const RTT_BE_DATA* be,
+    const char* name
+  );
+
+  /**
+   * Release memory associated to a backend topology
+   *
+   * @param topo the backend topology handler
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   */
+  int (*freeTopology) (RTT_BE_TOPOLOGY* topo);
+
+  /**
+   * Get nodes by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of element identifiers
+   * @param numelems input/output parameter, pass number of node identifiers
+   *                 in the input array, gets number of node in output array.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_NODE_* macros
+   *
+   * @return an array of nodes
+   *         or NULL in the following cases:
+   *         - no edge found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *           (@see lastErrorMessage)
+   *
+   */
+  RTT_ISO_NODE* (*getNodeById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get nodes within distance by point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   * @param dist the distance
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_NODE_* macros
+   * @param limit max number of nodes to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of nodes or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  RTT_ISO_NODE* (*getNodeWithinDistance2D) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTPOINT* pt, double dist, int* numelems,
+      int fields, int limit
+  );
+
+  /**
+   * Insert nodes
+   *
+   * Insert node primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param nodes the nodes to insert. Those with a node_id set to -1
+   *              it will be replaced to an automatically assigned identifier
+   * @param nelems number of elements in the nodes array
+   *
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   */
+  int (*insertNodes) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ISO_NODE* nodes,
+      int numelems
+  );
+
+  /**
+   * Get edge by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of element identifiers
+   * @param numelems input/output parameter, pass number of edge identifiers
+   *                 in the input array, gets number of edges in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_EDGE_* macros
+   *
+   * @return an array of edges or NULL in the following cases:
+   *         - none found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  RTT_ISO_EDGE* (*getEdgeById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get edges within distance by point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   * @param dist the distance
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_EDGE_* macros
+   * @param limit max number of edges to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of edges or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  RTT_ISO_EDGE* (*getEdgeWithinDistance2D) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTPOINT* pt, double dist, int* numelems,
+      int fields, int limit
+  );
+
+  /**
+   * Get next available edge identifier
+   *
+   * Identifiers returned by this function should not be considered
+   * available anymore.
+   *
+   * @param topo the topology to act upon
+   *
+   * @return next available edge identifier or -1 on error
+   */
+  RTT_ELEMID (*getNextEdgeId) (
+      const RTT_BE_TOPOLOGY* topo
+  );
+
+  /**
+   * Insert edges
+   *
+   * Insert edge primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param edges the edges to insert. Those with a edge_id set to -1
+   *              it will be replaced to an automatically assigned identifier
+   * @param nelems number of elements in the edges array
+   *
+   * @return number of inserted edges, or -1 (@see lastErrorMessage)
+   */
+  int (*insertEdges) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ISO_EDGE* edges,
+      int numelems
+  );
+
+  /**
+   * Update edges selected by fields match/mismatch
+   *
+   * @param topo the topology to act upon
+   * @param sel_edge an RTT_ISO_EDGE object with selecting fields set.
+   * @param sel_fields fields used to select edges to be updated,
+   *                   see RTT_COL_EDGE_* macros
+   * @param upd_edge an RTT_ISO_EDGE object with updated fields set.
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see RTT_COL_EDGE_* macros
+   * @param exc_edge an RTT_ISO_EDGE object with exclusion fields set,
+   *                 can be NULL if no exlusion condition exists.
+   * @param exc_fields fields used for excluding edges from the update,
+   *                   see RTT_COL_EDGE_* macros
+   *
+   * @return number of edges being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateEdges) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_EDGE* sel_edge, int sel_fields,
+      const RTT_ISO_EDGE* upd_edge, int upd_fields,
+      const RTT_ISO_EDGE* exc_edge, int exc_fields
+  );
+
+  /**
+   * Get faces by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of element identifiers
+   * @param numelems input/output parameter, pass number of edge identifiers
+   *                 in the input array, gets number of node in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_FACE_* macros
+   *
+   * @return an array of faces or NULL in the following cases:
+   *         - none found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  RTT_ISO_FACE* (*getFaceById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Get face containing point
+   *
+   * @param topo the topology to act upon
+   * @param pt the query point
+   *
+   * @return a face identifier, -1 if no face contains the point
+   *         (could be in universe face or on an edge)
+   *         or -2 on error (@see lastErrorMessage)
+   */
+  RTT_ELEMID (*getFaceContainingPoint) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTPOINT* pt
+  );
+
+  /**
+   * Update TopoGeometry objects after an edge split event
+   *
+   * @param topo the topology to act upon
+   * @param split_edge identifier of the edge that was split.
+   * @param new_edge1 identifier of the first new edge that was created
+   *        as a result of edge splitting.
+   * @param new_edge2 identifier of the second new edge that was created
+   *        as a result of edge splitting, or -1 if the old edge was
+   *        modified rather than replaced.
+   *
+	 * @return 1 on success, 0 on error
+   *
+   * @note on splitting an edge, the new edges both have the
+   *       same direction as the original one. If a second new edge was
+   *       created, its start node will be equal to the first new edge
+   *       end node.
+   */
+  int (*updateTopoGeomEdgeSplit) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID split_edge, RTT_ELEMID new_edge1, RTT_ELEMID new_edge2
+  );
+
+  /**
+   * Delete edges
+   *
+   * @param topo the topology to act upon
+   * @param sel_edge an RTT_ISO_EDGE object with selecting fields set.
+   * @param sel_fields fields used to select edges to be deleted,
+   *                   see RTT_COL_EDGE_* macros
+   *
+   * @return number of edges being deleted or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*deleteEdges) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_EDGE* sel_edge, int sel_fields
+  );
+
+  /**
+   * Get edges whose bounding box overlaps a given 2D bounding box
+   *
+   * @param topo the topology to act upon
+   * @param box the query box
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_NODE_* macros
+   * @param limit max number of nodes to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of nodes or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  RTT_ISO_NODE* (*getNodeWithinBox2D) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTGBOX* box,
+      int* numelems, int fields, int limit
+  );
+
+  /**
+   * Get edges whose bounding box overlaps a given 2D bounding box
+   *
+   * @param topo the topology to act upon
+   * @param box the query box
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_EDGE_* macros
+   * @param limit max number of edges to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of edges or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  RTT_ISO_EDGE* (*getEdgeWithinBox2D) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTGBOX* box,
+      int* numelems, int fields, int limit
+  );
+
+  /**
+   * Get edges that start or end on any of the given node identifiers
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of node identifiers
+   * @param numelems input/output parameter, pass number of node identifiers
+   *                 in the input array, gets number of edges in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_EDGE_* macros
+   *
+   * @return an array of edges that are incident to a node
+   *         or NULL in the following cases:
+   *         - no edge found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *           (@see lastErrorMessage)
+   */
+  RTT_ISO_EDGE* (*getEdgeByNode) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids, int* numelems, int fields
+  );
+
+  /**
+   * Update nodes selected by fields match/mismatch
+   *
+   * @param topo the topology to act upon
+   * @param sel_node an RTT_ISO_NODE object with selecting fields set.
+   * @param sel_fields fields used to select nodes to be updated,
+   *                   see RTT_COL_NODE_* macros
+   * @param upd_node an RTT_ISO_NODE object with updated fields set.
+   * @param upd_fields fields to be updated for the selected nodes,
+   *                   see RTT_COL_NODE_* macros
+   * @param exc_node an RTT_ISO_NODE object with exclusion fields set,
+   *                 can be NULL if no exlusion condition exists.
+   * @param exc_fields fields used for excluding nodes from the update,
+   *                   see RTT_COL_NODE_* macros
+   *
+   * @return number of nodes being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateNodes) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_NODE* sel_node, int sel_fields,
+      const RTT_ISO_NODE* upd_node, int upd_fields,
+      const RTT_ISO_NODE* exc_node, int exc_fields
+  );
+
+  /**
+   * Update TopoGeometry objects after a face split event
+   *
+   * @param topo the topology to act upon
+   * @param split_face identifier of the face that was split.
+   * @param new_face1 identifier of the first new face that was created
+   *        as a result of face splitting.
+   * @param new_face2 identifier of the second new face that was created
+   *        as a result of face splitting, or -1 if the old face was
+   *        modified rather than replaced.
+   *
+	 * @return 1 on success, 0 on error (@see lastErroMessage)
+   *
+   */
+  int (*updateTopoGeomFaceSplit) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID split_face, RTT_ELEMID new_face1, RTT_ELEMID new_face2
+  );
+
+  /**
+   * Insert faces
+   *
+   * Insert face primitives in the topology, performing no
+   * consistency checks.
+   *
+   * @param topo the topology to act upon
+   * @param faces the faces to insert. Those with a node_id set to -1
+   *              it will be replaced to an automatically assigned identifier
+   * @param nelems number of elements in the faces array
+   *
+   * @return number of inserted faces, or -1 (@see lastErrorMessage)
+   */
+  int (*insertFaces) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ISO_FACE* faces,
+      int numelems
+  );
+
+  /**
+   * Update faces by id
+   *
+   * @param topo the topology to act upon
+   * @param faces an array of RTT_ISO_FACE object with selecting id
+   *              and setting mbr.
+   * @param numfaces number of faces in the "faces" array
+   *
+   * @return number of faces being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateFacesById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_FACE* faces, int numfaces
+  );
+
+  /*
+   * Get the ordered list edge visited by a side walk
+   *
+   * The walk starts from the side of an edge and stops when
+   * either the max number of visited edges OR the starting
+   * position is reached. The output list never includes a
+   * duplicated signed edge identifier.
+   *
+   * It is expected that the walk uses the "next_left" and "next_right"
+   * attributes of ISO edges to perform the walk (rather than recomputing
+   * the turns at each node).
+   *
+   * @param topo the topology to operate on
+   * @param edge walk start position and direction:
+   *             abs value identifies the edge, sign expresses
+   *             side (left if positive, right if negative)
+   *             and direction (forward if positive, backward if negative).
+   * @param numedges output parameter, gets the number of edges visited
+   * @param limit max edges to return (to avoid an infinite loop in case
+   *              of a corrupted topology). 0 is for unlimited.
+   *              The function is expected to error out if the limit is hit.
+   *
+   * @return an array of signed edge identifiers (positive edges being
+   *         walked in their direction, negative ones in opposite) or
+   *         NULL on error (@see lastErroMessage)
+   */
+  RTT_ELEMID* (*getRingEdges) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID edge, int *numedges, int limit
+  );
+
+  /**
+   * Update edges by id
+   *
+   * @param topo the topology to act upon
+   * @param edges an array of RTT_ISO_EDGE object with selecting id
+   *              and updating fields.
+   * @param numedges number of edges in the "edges" array
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see RTT_COL_EDGE_* macros
+   *
+   * @return number of edges being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateEdgesById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_EDGE* edges, int numedges,
+      int upd_fields
+  );
+
+  /**
+   * \brief
+   * Get edges that have any of the given faces on the left or right side
+   * and optionally whose bounding box overlaps the given one.
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of face identifiers
+   * @param numelems input/output parameter, pass number of face identifiers
+   *                 in the input array, gets number of edges in output array
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_EDGE_* macros
+   * @param box optional bounding box to further restrict matches, use
+   *            NULL for no further restriction.
+   *
+   * @return an array of edges identifiers or NULL in the following cases:
+   *         - no edge found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   */
+  RTT_ISO_EDGE* (*getEdgeByFace) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids, int* numelems, int fields,
+      const RTGBOX *box
+  );
+
+  /**
+   * Get isolated nodes contained in any of the given faces
+   *
+   * @param topo the topology to act upon
+   * @param faces an array of face identifiers
+   * @param numelems input/output parameter, pass number of face
+   *                 identifiers in the input array, gets number of
+   *                 nodes in output array if the return is not null,
+   *                 otherwise see @return section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_NODE_* macros
+   * @param box optional bounding box to further restrict matches, use
+   *            NULL for no further restriction.
+   *
+   * @return an array of nodes or NULL in the following cases:
+   *         - no nod found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1, @see lastErrorMessage)
+   */
+  RTT_ISO_NODE* (*getNodeByFace) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* faces, int* numelems, int fields,
+      const RTGBOX *box
+  );
+
+  /**
+   * Update nodes by id
+   *
+   * @param topo the topology to act upon
+   * @param nodes an array of RTT_ISO_EDGE objects with selecting id
+   *              and updating fields.
+   * @param numnodes number of nodes in the "nodes" array
+   * @param upd_fields fields to be updated for the selected edges,
+   *                   see RTT_COL_NODE_* macros
+   *
+   * @return number of nodes being updated or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*updateNodesById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ISO_NODE* nodes, int numnodes,
+      int upd_fields
+  );
+
+  /**
+   * Delete faces by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of face identifiers
+   * @param numelems number of face identifiers in the ids array
+   *
+   * @return number of faces being deleted or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*deleteFacesById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids,
+      int numelems
+  );
+
+  /**
+   * Get topology SRID
+   * @return 0 for unknown
+   */
+  int (*topoGetSRID) (
+      const RTT_BE_TOPOLOGY* topo
+  );
+
+  /**
+   * Get topology precision
+   */
+  double (*topoGetPrecision) (
+      const RTT_BE_TOPOLOGY* topo
+  );
+
+  /**
+   * Get topology Z flag
+   * @return 1 if topology elements do have Z value, 0 otherwise
+   */
+  int (*topoHasZ) (
+      const RTT_BE_TOPOLOGY* topo
+  );
+
+  /**
+   * Delete nodes by id
+   *
+   * @param topo the topology to act upon
+   * @param ids an array of node identifiers
+   * @param numelems number of node identifiers in the ids array
+   *
+   * @return number of nodes being deleted or -1 on error
+   *         (@see lastErroMessage)
+   */
+  int (*deleteNodesById) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTT_ELEMID* ids,
+      int numelems
+  );
+
+  /**
+   * Check TopoGeometry objects before an edge removal event
+   *
+   * @param topo the topology to act upon
+   * @param rem_edge identifier of the edge that's been removed
+   * @param face_left identifier of the face on the edge's left side
+   * @param face_right identifier of the face on the edge's right side
+   *
+   * @return 1 to allow, 0 to forbid the operation
+   *         (reporting reason via lastErrorMessage)
+   *
+   */
+  int (*checkTopoGeomRemEdge) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID rem_edge,
+      RTT_ELEMID face_left,
+      RTT_ELEMID face_right
+  );
+
+  /**
+   * Update TopoGeometry objects after healing two faces
+   *
+   * @param topo the topology to act upon
+   * @param face1 identifier of the first face
+   * @param face2 identifier of the second face
+   * @param newface identifier of the new face
+   *
+   * @note that newface may or may not be equal to face1 or face2,
+   *       while face1 should never be the same as face2.
+   *
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   *
+   */
+  int (*updateTopoGeomFaceHeal) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID face1, RTT_ELEMID face2, RTT_ELEMID newface
+  );
+
+  /**
+   * Check TopoGeometry objects before a node removal event
+   *
+   * @param topo the topology to act upon
+   * @param rem_node identifier of the node that's been removed
+   * @param e1 identifier of the first connected edge
+   * @param e2 identifier of the second connected edge
+   *
+   * The operation should be forbidden if any TopoGeometry object
+   * exists which contains only one of the two healed edges.
+   *
+   * The operation should also be forbidden if the removed node
+   * takes part in the definition of a TopoGeometry, although
+   * this wasn't the case yet as of PostGIS version 2.1.8:
+   * https://trac.osgeo.org/postgis/ticket/3239
+   *
+   * @return 1 to allow, 0 to forbid the operation
+   *         (reporting reason via lastErrorMessage)
+   *
+   */
+  int (*checkTopoGeomRemNode) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID rem_node,
+      RTT_ELEMID e1,
+      RTT_ELEMID e2
+  );
+
+  /**
+   * Update TopoGeometry objects after healing two edges
+   *
+   * @param topo the topology to act upon
+   * @param edge1 identifier of the first edge
+   * @param edge2 identifier of the second edge
+   * @param newedge identifier of the new edge, taking the space
+   *                previously occupied by both original edges
+   *
+   * @note that newedge may or may not be equal to edge1 or edge2,
+   *       while edge1 should never be the same as edge2.
+   *
+   * @return 1 on success, 0 on error (@see lastErrorMessage)
+   *
+   */
+  int (*updateTopoGeomEdgeHeal) (
+      const RTT_BE_TOPOLOGY* topo,
+      RTT_ELEMID edge1, RTT_ELEMID edge2, RTT_ELEMID newedge
+  );
+
+  /**
+   * Get faces whose bounding box overlaps a given 2D bounding box
+   *
+   * @param topo the topology to act upon
+   * @param box the query box
+   * @param numelems output parameter, gets number of elements found
+   *                 if the return is not null, otherwise see @return
+   *                 section for semantic.
+   * @param fields fields to be filled in the returned structure, see
+   *               RTT_COL_FACE_* macros
+   * @param limit max number of faces to return, 0 for no limit, -1
+   *              to only check for existance if a matching row.
+   *
+   * @return an array of faces or null in the following cases:
+   *         - limit=-1 ("numelems" is set to 1 if found, 0 otherwise)
+   *         - limit>0 and no records found ("numelems" is set to 0)
+   *         - error ("numelems" is set to -1)
+   *
+   */
+  RTT_ISO_FACE* (*getFaceWithinBox2D) (
+      const RTT_BE_TOPOLOGY* topo,
+      const RTGBOX* box,
+      int* numelems, int fields, int limit
+  );
+
+} RTT_BE_CALLBACKS;
+
+
+/**
+ * Create a new backend interface
+ *
+ * Ownership to caller delete with rtt_FreeBackendIface
+ *
+ * @param ctx librtgeom context, create with rtgeom_init
+ * @param data Backend data, passed as first parameter to all callback functions
+ */
+RTT_BE_IFACE* rtt_CreateBackendIface(const RTCTX* ctx, const RTT_BE_DATA* data);
+
+/**
+ * Register backend callbacks into the opaque iface handler
+ *
+ * @param iface the backend interface handler (see rtt_CreateBackendIface)
+ * @param cb a pointer to the callbacks structure; ownership left to caller.
+ */
+void rtt_BackendIfaceRegisterCallbacks(RTT_BE_IFACE* iface, const RTT_BE_CALLBACKS* cb);
+
+/** Release memory associated with an RTT_BE_IFACE */
+void rtt_FreeBackendIface(RTT_BE_IFACE* iface);
+
+/********************************************************************
+ *
+ * End of BE interface
+ *
+ *******************************************************************/
+
+/**
+ * Topology errors type
+ */
+typedef enum RTT_TOPOERR_TYPE_T {
+  RTT_TOPOERR_EDGE_CROSSES_NODE,
+  RTT_TOPOERR_EDGE_INVALID,
+  RTT_TOPOERR_EDGE_NOT_SIMPLE,
+  RTT_TOPOERR_EDGE_CROSSES_EDGE,
+  RTT_TOPOERR_EDGE_STARTNODE_MISMATCH,
+  RTT_TOPOERR_EDGE_ENDNODE_MISMATCH,
+  RTT_TOPOERR_FACE_WITHOUT_EDGES,
+  RTT_TOPOERR_FACE_HAS_NO_RINGS,
+  RTT_TOPOERR_FACE_OVERLAPS_FACE,
+  RTT_TOPOERR_FACE_WITHIN_FACE
+} RTT_TOPOERR_TYPE;
+
+/** Topology error */
+typedef struct RTT_TOPOERR_T {
+  /** Type of error */
+  RTT_TOPOERR_TYPE err;
+  /** Identifier of first affected element */
+  RTT_ELEMID elem1;
+  /** Identifier of second affected element (0 if inapplicable) */
+  RTT_ELEMID elem2;
+} RTT_TOPOERR;
+
+/*
+ * Topology functions
+ */
+
+/** Opaque topology structure
+ *
+ * Embeds backend interface and topology
+ */
+typedef struct RTT_TOPOLOGY_T RTT_TOPOLOGY;
+
+
+/*******************************************************************
+ *
+ * Non-ISO signatures here
+ *
+ *******************************************************************/
+
+/**
+ * Initializes a new topology
+ *
+ * @param iface the backend interface handler (see rtt_CreateBackendIface)
+ * @param name name of the new topology
+ * @param srid the topology SRID
+ * @param prec the topology precision/tolerance
+ * @param hasz non-zero if topology primitives should have a Z ordinate
+ *
+ * @return the handler of the topology, or NULL on error
+ *         (librtgeom error handler will be invoked with error message)
+ */
+RTT_TOPOLOGY *rtt_CreateTopology(RTT_BE_IFACE *iface, const char *name,
+                        int srid, double prec, int hasz);
+
+/**
+ * Loads an existing topology by name from the database
+ *
+ * @param iface the backend interface handler (see rtt_CreateBackendIface)
+ * @param name name of the topology to load
+ *
+ * @return the handler of the topology, or NULL on error
+ *         (librtgeom error handler will be invoked with error message)
+ */
+RTT_TOPOLOGY *rtt_LoadTopology(RTT_BE_IFACE *iface, const char *name);
+
+/**
+ * Drop a topology and all its associated objects from the database
+ *
+ * @param topo the topology to drop
+ */
+void rtt_DropTopology(RTT_TOPOLOGY* topo);
+
+/** Release memory associated with an RTT_TOPOLOGY
+ *
+ * @param topo the topology to release (it's not removed from db)
+ */
+void rtt_FreeTopology(RTT_TOPOLOGY* topo);
+
+/**
+ * Retrieve the id of a node at a point location
+ *
+ * @param topo the topology to operate on
+ * @param point the point to use for query
+ * @param tol max distance around the given point to look for a node
+ * @return a node identifier if one is found, 0 if none is found, -1
+ *         on error (multiple nodes within distance).
+ *         The librtgeom error handler will be invoked in case of error.
+ */
+RTT_ELEMID rtt_GetNodeByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol);
+
+/**
+ * Find the edge-id of an edge that intersects a given point
+ *
+ * @param topo the topology to operate on
+ * @param point the point to use for query
+ * @param tol max distance around the given point to look for an
+ *            intersecting edge
+ * @return an edge identifier if one is found, 0 if none is found, -1
+ *         on error (multiple edges within distance).
+ *         The librtgeom error handler will be invoked in case of error.
+ */
+RTT_ELEMID rtt_GetEdgeByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol);
+
+/**
+ * Find the face-id of a face containing a given point
+ *
+ * @param topo the topology to operate on
+ * @param point the point to use for query
+ * @param tol max distance around the given point to look for a
+ *            containing face
+ * @return a face identifier if one is found (0 if universe), -1
+ *         on error (multiple faces within distance or point on node
+ *         or edge).
+ *         The librtgeom error handler will be invoked in case of error.
+ */
+RTT_ELEMID rtt_GetFaceByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol);
+
+
+/*******************************************************************
+ *
+ * Topology population (non-ISO)
+ *
+ *******************************************************************/
+
+/**
+ * Adds a point to the topology
+ *
+ * The given point will snap to existing nodes or edges within given
+ * tolerance. An existing edge may be split by the point.
+ *
+ * @param topo the topology to operate on
+ * @param point the point to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ *
+ * @return identifier of added (or pre-existing) node or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ */
+RTT_ELEMID rtt_AddPoint(RTT_TOPOLOGY* topo, RTPOINT* point, double tol);
+
+/**
+ * Adds a linestring to the topology
+ *
+ * The given line will snap to existing nodes or edges within given
+ * tolerance. Existing edges or faces may be split by the line.
+ *
+ * @param topo the topology to operate on
+ * @param line the line to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ * @param nedges output parameter, will be set to number of edges the
+ *               line was split into, or -1 on error
+ *               (librtgeom error handler will be invoked with error message)
+ *
+ * @return an array of <nedges> edge identifiers that sewed togheter
+ *         will build up the input linestring (after snapping). Caller
+ *         will need to free the array using rtfree(const RTCTX *ctx), if not null.
+ */
+RTT_ELEMID* rtt_AddLine(RTT_TOPOLOGY* topo, RTLINE* line, double tol,
+                        int* nedges);
+
+/**
+ * Adds a polygon to the topology
+ *
+ * The boundary of the given polygon will snap to existing nodes or
+ * edges within given tolerance.
+ * Existing edges or faces may be split by the boundary of the polygon.
+ *
+ * @param topo the topology to operate on
+ * @param poly the polygon to add
+ * @param tol snap tolerance, the topology tolerance will be used if 0
+ * @param nfaces output parameter, will be set to number of faces the
+ *               polygon was split into, or -1 on error
+ *               (librtgeom error handler will be invoked with error message)
+ *
+ * @return an array of <nfaces> face identifiers that sewed togheter
+ *         will build up the input polygon (after snapping). Caller
+ *         will need to free the array using rtfree(const RTCTX *ctx), if not null.
+ */
+RTT_ELEMID* rtt_AddPolygon(RTT_TOPOLOGY* topo, RTPOLY* poly, double tol,
+                        int* nfaces);
+
+/*******************************************************************
+ *
+ * ISO signatures here
+ *
+ *******************************************************************/
+
+/**
+ * Populate an empty topology with data from a simple geometry
+ *
+ * For ST_CreateTopoGeo
+ *
+ * @param topo the topology to operate on
+ * @param geom the geometry to import
+ *
+ */
+void rtt_CreateTopoGeo(RTT_TOPOLOGY* topo, RTGEOM *geom);
+
+/**
+ * Add an isolated node
+ *
+ * For ST_AddIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param face the identifier of containing face or -1 for "unknown"
+ * @param pt the node position
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (coincident nodes, crossing edges,
+ *                    actual face containement)
+ *
+ * @return ID of the newly added node, or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_AddIsoNode(RTT_TOPOLOGY* topo, RTT_ELEMID face,
+                          RTPOINT* pt, int skipChecks);
+
+/**
+ * Move an isolated node
+ *
+ * For ST_MoveIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param node the identifier of the nod to be moved
+ * @param pt the new node position
+ * @return 0 on success, -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+int rtt_MoveIsoNode(RTT_TOPOLOGY* topo,
+                    RTT_ELEMID node, RTPOINT* pt);
+
+/**
+ * Remove an isolated node
+ *
+ * For ST_RemoveIsoNode
+ *
+ * @param topo the topology to operate on
+ * @param node the identifier of the node to be moved
+ * @return 0 on success, -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+int rtt_RemoveIsoNode(RTT_TOPOLOGY* topo, RTT_ELEMID node);
+
+/**
+ * Remove an isolated edge
+ *
+ * For ST_RemIsoEdge
+ *
+ * @param topo the topology to operate on
+ * @param edge the identifier of the edge to be moved
+ * @return 0 on success, -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+int rtt_RemIsoEdge(RTT_TOPOLOGY* topo, RTT_ELEMID edge);
+
+/**
+ * Add an isolated edge connecting two existing isolated nodes
+ *
+ * For ST_AddIsoEdge
+ *
+ * @param topo the topology to operate on
+ * @param start_node identifier of the starting node
+ * @param end_node identifier of the ending node
+ * @param geom the edge geometry
+ * @return ID of the newly added edge, or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_AddIsoEdge(RTT_TOPOLOGY* topo,
+                          RTT_ELEMID startNode, RTT_ELEMID endNode,
+                          const RTLINE *geom);
+
+/**
+ * Add a new edge possibly splitting a face (modifying it)
+ *
+ * For ST_AddEdgeModFace
+ *
+ * If the new edge splits a face, the face is shrinked and a new one
+ * is created. Unless the face being split is the Universal Face, the
+ * new face will be on the right side of the newly added edge.
+ *
+ * @param topo the topology to operate on
+ * @param start_node identifier of the starting node
+ * @param end_node identifier of the ending node
+ * @param geom the edge geometry
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (curve being simple and valid, start/end nodes
+ *                    consistency actual face containement)
+ *
+ * @return ID of the newly added edge or null on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_AddEdgeModFace(RTT_TOPOLOGY* topo,
+                              RTT_ELEMID start_node, RTT_ELEMID end_node,
+                              RTLINE *geom, int skipChecks);
+
+/**
+ * Add a new edge possibly splitting a face (replacing with two new faces)
+ *
+ * For ST_AddEdgeNewFaces
+ *
+ * If the new edge splits a face, the face is replaced by two new faces.
+ *
+ * @param topo the topology to operate on
+ * @param start_node identifier of the starting node
+ * @param end_node identifier of the ending node
+ * @param geom the edge geometry
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (curve being simple and valid, start/end nodes
+ *                    consistency actual face containement)
+ * @return ID of the newly added edge
+ *
+ */
+RTT_ELEMID rtt_AddEdgeNewFaces(RTT_TOPOLOGY* topo,
+                              RTT_ELEMID start_node, RTT_ELEMID end_node,
+                              RTLINE *geom, int skipChecks);
+
+/**
+ * Remove an edge, possibly merging two faces (replacing both with a new one)
+ *
+ * For ST_RemEdgeNewFace
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be removed
+ * @return the id of newly created face, 0 if no new face was created
+ *         or -1 on error
+ *
+ */
+RTT_ELEMID rtt_RemEdgeNewFace(RTT_TOPOLOGY* topo, RTT_ELEMID edge);
+
+/**
+ * Remove an edge, possibly merging two faces (replacing one with the other)
+ *
+ * For ST_RemEdgeModFace
+ *
+ * Preferentially keep the face on the right, to be symmetric with
+ * rtt_AddEdgeModFace.
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be removed
+ * @return the id of the face that takes the space previously occupied
+ *         by the removed edge, or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_RemEdgeModFace(RTT_TOPOLOGY* topo, RTT_ELEMID edge);
+
+/**
+ * Changes the shape of an edge without affecting the topology structure.
+ *
+ * For ST_ChangeEdgeGeom
+ *
+ * @param topo the topology to operate on
+ * @param curve the edge geometry
+ * @return 0 on success, -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+int rtt_ChangeEdgeGeom(RTT_TOPOLOGY* topo, RTT_ELEMID edge, RTLINE* curve);
+
+/**
+ * Split an edge by a node, modifying the original edge and adding a new one.
+ *
+ * For ST_ModEdgeSplit
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be split
+ * @param pt geometry of the new node
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (coincident node, point not on edge...)
+ * @return the id of newly created node, or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_ModEdgeSplit(RTT_TOPOLOGY* topo, RTT_ELEMID edge, RTPOINT* pt, int skipChecks);
+
+/**
+ * Split an edge by a node, replacing it with two new edges
+ *
+ * For ST_NewEdgesSplit
+ *
+ * @param topo the topology to operate on
+ * @param edge identifier of the edge to be split
+ * @param pt geometry of the new node
+ * @param skipChecks if non-zero skips consistency checks
+ *                   (coincident node, point not on edge...)
+ * @return the id of newly created node
+ *
+ */
+RTT_ELEMID rtt_NewEdgesSplit(RTT_TOPOLOGY* topo, RTT_ELEMID edge, RTPOINT* pt, int skipChecks);
+
+/**
+ * Merge two edges, modifying the first and deleting the second
+ *
+ * For ST_ModEdgeHeal
+ *
+ * @param topo the topology to operate on
+ * @param e1 identifier of first edge
+ * @param e2 identifier of second edge
+ * @return the id of the removed node or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_ModEdgeHeal(RTT_TOPOLOGY* topo, RTT_ELEMID e1, RTT_ELEMID e2);
+
+/**
+ * Merge two edges, replacing both with a new one
+ *
+ * For ST_NewEdgeHeal
+ *
+ * @param topo the topology to operate on
+ * @param e1 identifier of first edge
+ * @param e2 identifier of second edge
+ * @return the id of the new edge or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+RTT_ELEMID rtt_NewEdgeHeal(RTT_TOPOLOGY* topo, RTT_ELEMID e1, RTT_ELEMID e2);
+
+/**
+ * Return the list of directed edges bounding a face
+ *
+ * For ST_GetFaceEdges
+ *
+ * @param topo the topology to operate on
+ * @param face identifier of the face
+ * @param edges will be set to an array of signed edge identifiers, will
+ *              need to be released with rtfree
+ * @return the number of edges in the edges array, or -1 on error
+ *         (librtgeom error handler will be invoked with error message)
+ *
+ */
+int rtt_GetFaceEdges(RTT_TOPOLOGY* topo, RTT_ELEMID face, RTT_ELEMID **edges);
+
+/**
+ * Return the geometry of a face
+ *
+ * For ST_GetFaceGeometry
+ *
+ * @param topo the topology to operate on
+ * @param face identifier of the face
+ * @return a polygon geometry representing the face, ownership to caller,
+ *         to be released with rtgeom_release, or NULL on error
+ *         (librtgeom error handler will be invoked with error message)
+ */
+RTGEOM* rtt_GetFaceGeometry(RTT_TOPOLOGY* topo, RTT_ELEMID face);
+
+#endif /* LIBRTGEOM_TOPO_H */
diff --git a/src/librtgeom_topo_internal.h b/src/librtgeom_topo_internal.h
new file mode 100644
index 0000000..1b87c59
--- /dev/null
+++ b/src/librtgeom_topo_internal.h
@@ -0,0 +1,97 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#ifndef LIBRTGEOM_TOPO_INTERNAL_H
+#define LIBRTGEOM_TOPO_INTERNAL_H 1
+
+#include "rttopo_config.h"
+#include "geos_c.h"
+
+#include "librtgeom.h"
+#include "librtgeom_topo.h"
+
+/************************************************************************
+ *
+ * Generic SQL handler
+ *
+ ************************************************************************/
+
+struct RTT_BE_IFACE_T
+{
+  const RTT_BE_DATA *data;
+  const RTT_BE_CALLBACKS *cb;
+  const RTCTX *ctx;
+};
+
+const char* rtt_be_lastErrorMessage(const RTT_BE_IFACE* be);
+
+RTT_BE_TOPOLOGY * rtt_be_loadTopologyByName(RTT_BE_IFACE *be, const char *name);
+
+int rtt_be_freeTopology(RTT_TOPOLOGY *topo);
+
+RTT_ISO_NODE* rtt_be_getNodeWithinDistance2D(RTT_TOPOLOGY* topo, RTPOINT* pt, double dist, int* numelems, int fields, int limit);
+
+RTT_ISO_NODE* rtt_be_getNodeById(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids, int* numelems, int fields);
+
+int rtt_be_ExistsCoincidentNode(RTT_TOPOLOGY* topo, RTPOINT* pt);
+int rtt_be_insertNodes(RTT_TOPOLOGY* topo, RTT_ISO_NODE* node, int numelems);
+
+int rtt_be_ExistsEdgeIntersectingPoint(RTT_TOPOLOGY* topo, RTPOINT* pt);
+
+RTT_ELEMID rtt_be_getNextEdgeId(RTT_TOPOLOGY* topo);
+RTT_ISO_EDGE* rtt_be_getEdgeById(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                               int* numelems, int fields);
+RTT_ISO_EDGE* rtt_be_getEdgeWithinDistance2D(RTT_TOPOLOGY* topo, RTPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit);
+int
+rtt_be_insertEdges(RTT_TOPOLOGY* topo, RTT_ISO_EDGE* edge, int numelems);
+int
+rtt_be_updateEdges(RTT_TOPOLOGY* topo, const RTT_ISO_EDGE* sel_edge, int sel_fields, const RTT_ISO_EDGE* upd_edge, int upd_fields, const RTT_ISO_EDGE* exc_edge, int exc_fields);
+int
+rtt_be_deleteEdges(RTT_TOPOLOGY* topo, const RTT_ISO_EDGE* sel_edge, int sel_fields);
+
+RTT_ELEMID rtt_be_getFaceContainingPoint(RTT_TOPOLOGY* topo, RTPOINT* pt);
+
+int rtt_be_updateTopoGeomEdgeSplit(RTT_TOPOLOGY* topo, RTT_ELEMID split_edge, RTT_ELEMID new_edge1, RTT_ELEMID new_edge2);
+
+
+/************************************************************************
+ *
+ * Internal objects
+ *
+ ************************************************************************/
+
+struct RTT_TOPOLOGY_T
+{
+  const RTT_BE_IFACE *be_iface;
+  RTT_BE_TOPOLOGY *be_topo;
+  int srid;
+  double precision;
+  int hasZ;
+};
+
+#endif /* LIBRTGEOM_TOPO_INTERNAL_H */
diff --git a/src/measures.c b/src/measures.c
new file mode 100644
index 0000000..af0655d
--- /dev/null
+++ b/src/measures.c
@@ -0,0 +1,2322 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2006 Refractions Research Inc.
+ * Copyright 2010 Nicklas Avén
+ * Copyright 2012 Paul Ramsey
+ *
+ **********************************************************************/
+
+
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "measures.h"
+#include "rtgeom_log.h"
+
+
+
+/*------------------------------------------------------------------------------------------------------------
+Initializing functions
+The functions starting the distance-calculation processses
+--------------------------------------------------------------------------------------------------------------*/
+
+RTGEOM *
+rtgeom_closest_line(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+  return rt_dist2d_distanceline(ctx, rt1, rt2, rt1->srid, DIST_MIN);
+}
+
+RTGEOM *
+rtgeom_furthest_line(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+  return rt_dist2d_distanceline(ctx, rt1, rt2, rt1->srid, DIST_MAX);
+}
+
+RTGEOM *
+rtgeom_closest_point(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+  return rt_dist2d_distancepoint(ctx, rt1, rt2, rt1->srid, DIST_MIN);  
+}
+
+RTGEOM *
+rtgeom_furthest_point(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+  return rt_dist2d_distancepoint(ctx, rt1, rt2, rt1->srid, DIST_MAX);  
+}
+
+
+void
+rt_dist2d_distpts_init(const RTCTX *ctx, DISTPTS *dl, int mode)
+{
+	dl->twisted = -1;
+	dl->p1.x = dl->p1.y = 0.0;
+	dl->p2.x = dl->p2.y = 0.0;
+	dl->mode = mode;
+	dl->tolerance = 0.0;
+	if ( mode == DIST_MIN )
+		dl->distance = FLT_MAX;
+	else
+		dl->distance = -1 * FLT_MAX;
+}
+
+/**
+Function initializing shortestline and longestline calculations.
+*/
+RTGEOM *
+rt_dist2d_distanceline(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, int srid, int mode)
+{
+	double x1,x2,y1,y2;
+
+	double initdistance = ( mode == DIST_MIN ? FLT_MAX : -1.0);
+	DISTPTS thedl;
+	RTPOINT *rtpoints[2];
+	RTGEOM *result;
+
+	thedl.mode = mode;
+	thedl.distance = initdistance;
+	thedl.tolerance = 0.0;
+
+	RTDEBUG(2, "rt_dist2d_distanceline is called");
+
+	if (!rt_dist2d_comp(ctx,  rt1,rt2,&thedl))
+	{
+		/*should never get here. all cases ought to be error handled earlier*/
+		rterror(ctx, "Some unspecified error.");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+
+	/*if thedl.distance is unchanged there where only empty geometries input*/
+	if (thedl.distance == initdistance)
+	{
+		RTDEBUG(3, "didn't find geometries to measure between, returning null");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+	else
+	{
+		x1=thedl.p1.x;
+		y1=thedl.p1.y;
+		x2=thedl.p2.x;
+		y2=thedl.p2.y;
+
+		rtpoints[0] = rtpoint_make2d(ctx, srid, x1, y1);
+		rtpoints[1] = rtpoint_make2d(ctx, srid, x2, y2);
+
+		result = (RTGEOM *)rtline_from_ptarray(ctx, srid, 2, rtpoints);
+	}
+	return result;
+}
+
+/**
+Function initializing closestpoint calculations.
+*/
+RTGEOM *
+rt_dist2d_distancepoint(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2,int srid,int mode)
+{
+	double x,y;
+	DISTPTS thedl;
+	double initdistance = FLT_MAX;
+	RTGEOM *result;
+
+	thedl.mode = mode;
+	thedl.distance= initdistance;
+	thedl.tolerance = 0;
+
+	RTDEBUG(2, "rt_dist2d_distancepoint is called");
+
+	if (!rt_dist2d_comp(ctx,  rt1,rt2,&thedl))
+	{
+		/*should never get here. all cases ought to be error handled earlier*/
+		rterror(ctx, "Some unspecified error.");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+	if (thedl.distance == initdistance)
+	{
+		RTDEBUG(3, "didn't find geometries to measure between, returning null");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+	else
+	{
+		x=thedl.p1.x;
+		y=thedl.p1.y;
+		result = (RTGEOM *)rtpoint_make2d(ctx, srid, x, y);
+	}
+	return result;
+}
+
+
+/**
+Function initialazing max distance calculation
+*/
+double
+rtgeom_maxdistance2d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	RTDEBUG(2, "rtgeom_maxdistance2d is called");
+
+	return rtgeom_maxdistance2d_tolerance(ctx,  rt1, rt2, 0.0 );
+}
+
+/**
+Function handling max distance calculations and dfyllywithin calculations.
+The difference is just the tolerance.
+*/
+double
+rtgeom_maxdistance2d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance)
+{
+	/*double thedist;*/
+	DISTPTS thedl;
+	RTDEBUG(2, "rtgeom_maxdistance2d_tolerance is called");
+	thedl.mode = DIST_MAX;
+	thedl.distance= -1;
+	thedl.tolerance = tolerance;
+	if (rt_dist2d_comp(ctx,  rt1,rt2,&thedl))
+	{
+		return thedl.distance;
+	}
+	/*should never get here. all cases ought to be error handled earlier*/
+	rterror(ctx, "Some unspecified error.");
+	return -1;
+}
+
+/**
+	Function initialazing min distance calculation
+*/
+double
+rtgeom_mindistance2d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	RTDEBUG(2, "rtgeom_mindistance2d is called");
+	return rtgeom_mindistance2d_tolerance(ctx,  rt1, rt2, 0.0 );
+}
+
+/**
+	Function handling min distance calculations and dwithin calculations.
+	The difference is just the tolerance.
+*/
+double
+rtgeom_mindistance2d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance)
+{
+	DISTPTS thedl;
+	RTDEBUG(2, "rtgeom_mindistance2d_tolerance is called");
+	thedl.mode = DIST_MIN;
+	thedl.distance= FLT_MAX;
+	thedl.tolerance = tolerance;
+	if (rt_dist2d_comp(ctx,  rt1,rt2,&thedl))
+	{
+		return thedl.distance;
+	}
+	/*should never get here. all cases ought to be error handled earlier*/
+	rterror(ctx, "Some unspecified error.");
+	return FLT_MAX;
+}
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Initializing functions
+--------------------------------------------------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------------------------------------------
+Preprocessing functions
+Functions preparing geometries for distance-calculations
+--------------------------------------------------------------------------------------------------------------*/
+
+/**
+	This function just deserializes geometries
+	Bboxes is not checked here since it is the subgeometries
+	bboxes we will use anyway.
+*/
+int
+rt_dist2d_comp(const RTCTX *ctx, const RTGEOM *rt1,const RTGEOM *rt2, DISTPTS *dl)
+{
+	RTDEBUG(2, "rt_dist2d_comp is called");
+
+	return rt_dist2d_recursive(ctx, rt1, rt2, dl);
+}
+
+static int
+rt_dist2d_is_collection(const RTCTX *ctx, const RTGEOM *g)
+{
+
+	switch (g->type)
+	{
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTCOMPOUNDTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+		return RT_TRUE;
+		break;
+
+	default:
+		return RT_FALSE;
+	}
+}
+
+/**
+This is a recursive function delivering every possible combinatin of subgeometries
+*/
+int rt_dist2d_recursive(const RTCTX *ctx, const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS *dl)
+{
+	int i, j;
+	int n1=1;
+	int n2=1;
+	RTGEOM *g1 = NULL;
+	RTGEOM *g2 = NULL;
+	RTCOLLECTION *c1 = NULL;
+	RTCOLLECTION *c2 = NULL;
+
+	RTDEBUGF(2, "rt_dist2d_comp is called with type1=%d, type2=%d", rtg1->type, rtg2->type);
+
+	if (rt_dist2d_is_collection(ctx, rtg1))
+	{
+		RTDEBUG(3, "First geometry is collection");
+		c1 = rtgeom_as_rtcollection(ctx, rtg1);
+		n1 = c1->ngeoms;
+	}
+	if (rt_dist2d_is_collection(ctx, rtg2))
+	{
+		RTDEBUG(3, "Second geometry is collection");
+		c2 = rtgeom_as_rtcollection(ctx, rtg2);
+		n2 = c2->ngeoms;
+	}
+
+	for ( i = 0; i < n1; i++ )
+	{
+
+		if (rt_dist2d_is_collection(ctx, rtg1))
+		{
+			g1 = c1->geoms[i];
+		}
+		else
+		{
+			g1 = (RTGEOM*)rtg1;
+		}
+
+		if (rtgeom_is_empty(ctx, g1)) return RT_TRUE;
+
+		if (rt_dist2d_is_collection(ctx, g1))
+		{
+			RTDEBUG(3, "Found collection inside first geometry collection, recursing");
+			if (!rt_dist2d_recursive(ctx, g1, rtg2, dl)) return RT_FALSE;
+			continue;
+		}
+		for ( j = 0; j < n2; j++ )
+		{
+			if (rt_dist2d_is_collection(ctx, rtg2))
+			{
+				g2 = c2->geoms[j];
+			}
+			else
+			{
+				g2 = (RTGEOM*)rtg2;
+			}
+			if (rt_dist2d_is_collection(ctx, g2))
+			{
+				RTDEBUG(3, "Found collection inside second geometry collection, recursing");
+				if (!rt_dist2d_recursive(ctx, g1, g2, dl)) return RT_FALSE;
+				continue;
+			}
+
+			if ( ! g1->bbox )
+			{
+				rtgeom_add_bbox(ctx, g1);
+			}
+			if ( ! g2->bbox )
+			{
+				rtgeom_add_bbox(ctx, g2);
+			}
+
+			/*If one of geometries is empty, return. True here only means continue searching. False would have stoped the process*/
+			if (rtgeom_is_empty(ctx, g1)||rtgeom_is_empty(ctx, g2)) return RT_TRUE;
+
+			if ( (dl->mode != DIST_MAX) && 
+				 (! rt_dist2d_check_overlap(ctx, g1, g2)) && 
+			     (g1->type == RTLINETYPE || g1->type == RTPOLYGONTYPE) && 
+			     (g2->type == RTLINETYPE || g2->type == RTPOLYGONTYPE) )	
+			{
+				if (!rt_dist2d_distribute_fast(ctx, g1, g2, dl)) return RT_FALSE;
+			}
+			else
+			{
+				if (!rt_dist2d_distribute_bruteforce(ctx, g1, g2, dl)) return RT_FALSE;
+				if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/				
+			}
+		}
+	}
+	return RT_TRUE;
+}
+
+
+int
+rt_dist2d_distribute_bruteforce(const RTCTX *ctx, const RTGEOM *rtg1,const RTGEOM *rtg2, DISTPTS *dl)
+{
+
+	int	t1 = rtg1->type;
+	int	t2 = rtg2->type;
+
+	switch ( t1 )
+	{
+		case RTPOINTTYPE:
+		{
+			dl->twisted = 1;
+			switch ( t2 )
+			{
+				case RTPOINTTYPE:
+					return rt_dist2d_point_point(ctx, (RTPOINT *)rtg1, (RTPOINT *)rtg2, dl);
+				case RTLINETYPE:
+					return rt_dist2d_point_line(ctx, (RTPOINT *)rtg1, (RTLINE *)rtg2, dl);
+				case RTPOLYGONTYPE:
+					return rt_dist2d_point_poly(ctx, (RTPOINT *)rtg1, (RTPOLY *)rtg2, dl);
+				case RTCIRCSTRINGTYPE:
+					return rt_dist2d_point_circstring(ctx, (RTPOINT *)rtg1, (RTCIRCSTRING *)rtg2, dl);
+				case RTCURVEPOLYTYPE:
+					return rt_dist2d_point_curvepoly(ctx, (RTPOINT *)rtg1, (RTCURVEPOLY *)rtg2, dl);
+				default:
+					rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			}
+		}
+		case RTLINETYPE:
+		{
+			dl->twisted = 1;
+			switch ( t2 )
+			{
+				case RTPOINTTYPE:
+					dl->twisted=(-1);
+					return rt_dist2d_point_line(ctx, (RTPOINT *)rtg2, (RTLINE *)rtg1, dl);
+				case RTLINETYPE:
+					return rt_dist2d_line_line(ctx, (RTLINE *)rtg1, (RTLINE *)rtg2, dl);
+				case RTPOLYGONTYPE:
+					return rt_dist2d_line_poly(ctx, (RTLINE *)rtg1, (RTPOLY *)rtg2, dl);
+				case RTCIRCSTRINGTYPE:
+					return rt_dist2d_line_circstring(ctx, (RTLINE *)rtg1, (RTCIRCSTRING *)rtg2, dl);
+				case RTCURVEPOLYTYPE:
+					return rt_dist2d_line_curvepoly(ctx, (RTLINE *)rtg1, (RTCURVEPOLY *)rtg2, dl);
+				default:
+					rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			}
+		}
+		case RTCIRCSTRINGTYPE:
+		{
+			dl->twisted = 1;
+			switch ( t2 )
+			{
+				case RTPOINTTYPE:
+					dl->twisted = -1;
+					return rt_dist2d_point_circstring(ctx, (RTPOINT *)rtg2, (RTCIRCSTRING *)rtg1, dl);
+				case RTLINETYPE:
+					dl->twisted = -1;
+					return rt_dist2d_line_circstring(ctx, (RTLINE *)rtg2, (RTCIRCSTRING *)rtg1, dl);
+				case RTPOLYGONTYPE:
+					return rt_dist2d_circstring_poly(ctx, (RTCIRCSTRING *)rtg1, (RTPOLY *)rtg2, dl);
+				case RTCIRCSTRINGTYPE:
+					return rt_dist2d_circstring_circstring(ctx, (RTCIRCSTRING *)rtg1, (RTCIRCSTRING *)rtg2, dl);
+				case RTCURVEPOLYTYPE:
+					return rt_dist2d_circstring_curvepoly(ctx, (RTCIRCSTRING *)rtg1, (RTCURVEPOLY *)rtg2, dl);
+				default:
+					rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			}			
+		}
+		case RTPOLYGONTYPE:
+		{
+			dl->twisted = -1;
+			switch ( t2 )
+			{
+				case RTPOINTTYPE:
+					return rt_dist2d_point_poly(ctx, (RTPOINT *)rtg2, (RTPOLY *)rtg1, dl);
+				case RTLINETYPE:
+					return rt_dist2d_line_poly(ctx, (RTLINE *)rtg2, (RTPOLY *)rtg1, dl);
+				case RTCIRCSTRINGTYPE:
+					return rt_dist2d_circstring_poly(ctx, (RTCIRCSTRING *)rtg2, (RTPOLY *)rtg1, dl);
+				case RTPOLYGONTYPE:
+					dl->twisted = 1;
+					return rt_dist2d_poly_poly(ctx, (RTPOLY *)rtg1, (RTPOLY *)rtg2, dl);
+				case RTCURVEPOLYTYPE:
+					dl->twisted = 1;
+					return rt_dist2d_poly_curvepoly(ctx, (RTPOLY *)rtg1, (RTCURVEPOLY *)rtg2, dl);
+				default:
+					rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			}
+		}
+		case RTCURVEPOLYTYPE:
+		{
+			dl->twisted = (-1);
+			switch ( t2 )
+			{
+				case RTPOINTTYPE:
+					return rt_dist2d_point_curvepoly(ctx, (RTPOINT *)rtg2, (RTCURVEPOLY *)rtg1, dl);
+				case RTLINETYPE:
+					return rt_dist2d_line_curvepoly(ctx, (RTLINE *)rtg2, (RTCURVEPOLY *)rtg1, dl);
+				case RTPOLYGONTYPE:
+					return rt_dist2d_poly_curvepoly(ctx, (RTPOLY *)rtg2, (RTCURVEPOLY *)rtg1, dl);
+				case RTCIRCSTRINGTYPE:
+					return rt_dist2d_circstring_curvepoly(ctx, (RTCIRCSTRING *)rtg2, (RTCURVEPOLY *)rtg1, dl);
+				case RTCURVEPOLYTYPE:
+					dl->twisted = 1;
+					return rt_dist2d_curvepoly_curvepoly(ctx, (RTCURVEPOLY *)rtg1, (RTCURVEPOLY *)rtg2, dl);
+				default:
+					rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			}			
+		}
+		default:
+		{
+			rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t1));
+		}
+	}
+
+	/*You shouldn't being able to get here*/
+	rterror(ctx, "unspecified error in function rt_dist2d_distribute_bruteforce");
+	return RT_FALSE;
+}
+
+
+
+
+/**
+
+We have to check for overlapping bboxes
+*/
+int
+rt_dist2d_check_overlap(const RTCTX *ctx, RTGEOM *rtg1,RTGEOM *rtg2)
+{
+	RTDEBUG(2, "rt_dist2d_check_overlap is called");
+	if ( ! rtg1->bbox )
+		rtgeom_calculate_gbox(ctx, rtg1, rtg1->bbox);
+	if ( ! rtg2->bbox )
+		rtgeom_calculate_gbox(ctx, rtg2, rtg2->bbox);
+
+	/*Check if the geometries intersect.
+	*/
+	if ((rtg1->bbox->xmax<rtg2->bbox->xmin||rtg1->bbox->xmin>rtg2->bbox->xmax||rtg1->bbox->ymax<rtg2->bbox->ymin||rtg1->bbox->ymin>rtg2->bbox->ymax))
+	{
+		RTDEBUG(3, "geometries bboxes did not overlap");
+		return RT_FALSE;
+	}
+	RTDEBUG(3, "geometries bboxes overlap");
+	return RT_TRUE;
+}
+
+/**
+
+Here the geometries are distributed for the new faster distance-calculations
+*/
+int
+rt_dist2d_distribute_fast(const RTCTX *ctx, RTGEOM *rtg1, RTGEOM *rtg2, DISTPTS *dl)
+{
+	RTPOINTARRAY *pa1, *pa2;
+	int	type1 = rtg1->type;
+	int	type2 = rtg2->type;
+
+	RTDEBUGF(2, "rt_dist2d_distribute_fast is called with typ1=%d, type2=%d", rtg1->type, rtg2->type);
+
+	switch (type1)
+	{
+	case RTLINETYPE:
+		pa1 = ((RTLINE *)rtg1)->points;
+		break;
+	case RTPOLYGONTYPE:
+		pa1 = ((RTPOLY *)rtg1)->rings[0];
+		break;
+	default:
+		rterror(ctx, "Unsupported geometry1 type: %s", rttype_name(ctx, type1));
+		return RT_FALSE;
+	}
+	switch (type2)
+	{
+	case RTLINETYPE:
+		pa2 = ((RTLINE *)rtg2)->points;
+		break;
+	case RTPOLYGONTYPE:
+		pa2 = ((RTPOLY *)rtg2)->rings[0];
+		break;
+	default:
+		rterror(ctx, "Unsupported geometry2 type: %s", rttype_name(ctx, type1));
+		return RT_FALSE;
+	}
+	dl->twisted=1;
+	return rt_dist2d_fast_ptarray_ptarray(ctx, pa1, pa2, dl, rtg1->bbox, rtg2->bbox);
+}
+
+/*------------------------------------------------------------------------------------------------------------
+End of Preprocessing functions
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/*------------------------------------------------------------------------------------------------------------
+Brute force functions
+The old way of calculating distances, now used for:
+1)	distances to points (because there shouldn't be anything to gain by the new way of doing it)
+2)	distances when subgeometries geometries bboxes overlaps
+--------------------------------------------------------------------------------------------------------------*/
+
+/**
+
+point to point calculation
+*/
+int
+rt_dist2d_point_point(const RTCTX *ctx, RTPOINT *point1, RTPOINT *point2, DISTPTS *dl)
+{
+	const RTPOINT2D *p1, *p2;
+
+	p1 = rt_getPoint2d_cp(ctx, point1->point, 0);
+	p2 = rt_getPoint2d_cp(ctx, point2->point, 0);
+
+	return rt_dist2d_pt_pt(ctx, p1, p2, dl);
+}
+/**
+
+point to line calculation
+*/
+int
+rt_dist2d_point_line(const RTCTX *ctx, RTPOINT *point, RTLINE *line, DISTPTS *dl)
+{
+	const RTPOINT2D *p;
+	RTDEBUG(2, "rt_dist2d_point_line is called");
+	p = rt_getPoint2d_cp(ctx, point->point, 0);
+	return rt_dist2d_pt_ptarray(ctx, p, line->points, dl);
+}
+
+int
+rt_dist2d_point_circstring(const RTCTX *ctx, RTPOINT *point, RTCIRCSTRING *circ, DISTPTS *dl)
+{
+	const RTPOINT2D *p;
+	p = rt_getPoint2d_cp(ctx, point->point, 0);
+	return rt_dist2d_pt_ptarrayarc(ctx, p, circ->points, dl);
+}
+
+/**
+ * 1. see if pt in outer boundary. if no, then treat the outer ring like a line
+ * 2. if in the boundary, test to see if its in a hole.
+ *    if so, then return dist to hole, else return 0 (point in polygon)
+ */
+int
+rt_dist2d_point_poly(const RTCTX *ctx, RTPOINT *point, RTPOLY *poly, DISTPTS *dl)
+{
+	const RTPOINT2D *p;
+	int i;
+
+	RTDEBUG(2, "rt_dist2d_point_poly called");
+
+	p = rt_getPoint2d_cp(ctx, point->point, 0);
+
+	if (dl->mode == DIST_MAX)
+	{
+		RTDEBUG(3, "looking for maxdistance");
+		return rt_dist2d_pt_ptarray(ctx, p, poly->rings[0], dl);
+	}
+	/* Return distance to outer ring if not inside it */
+	if ( ptarray_contains_point(ctx, poly->rings[0], p) == RT_OUTSIDE )	
+	{
+		RTDEBUG(3, "first point not inside outer-ring");
+		return rt_dist2d_pt_ptarray(ctx, p, poly->rings[0], dl);
+	}
+
+	/*
+	 * Inside the outer ring.
+	 * Scan though each of the inner rings looking to
+	 * see if its inside.  If not, distance==0.
+	 * Otherwise, distance = pt to ring distance
+	 */
+	for ( i = 1;  i < poly->nrings; i++)
+	{
+		/* Inside a hole. Distance = pt -> ring */
+		if ( ptarray_contains_point(ctx, poly->rings[i], p) != RT_OUTSIDE )
+		{
+			RTDEBUG(3, " inside an hole");
+			return rt_dist2d_pt_ptarray(ctx, p, poly->rings[i], dl);
+		}
+	}
+
+	RTDEBUG(3, " inside the polygon");
+	if (dl->mode == DIST_MIN)
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = p->x;
+		dl->p1.y = dl->p2.y = p->y;
+	}
+	return RT_TRUE; /* Is inside the polygon */
+}
+
+int
+rt_dist2d_point_curvepoly(const RTCTX *ctx, RTPOINT *point, RTCURVEPOLY *poly, DISTPTS *dl)
+{
+	const RTPOINT2D *p;
+	int i;
+
+	p = rt_getPoint2d_cp(ctx, point->point, 0);
+
+	if (dl->mode == DIST_MAX)
+		rterror(ctx, "rt_dist2d_point_curvepoly cannot calculate max distance");
+
+	/* Return distance to outer ring if not inside it */
+	if ( rtgeom_contains_point(ctx, poly->rings[0], p) == RT_OUTSIDE )	
+	{
+		return rt_dist2d_recursive(ctx, (RTGEOM*)point, poly->rings[0], dl);
+	}
+
+	/*
+	 * Inside the outer ring.
+	 * Scan though each of the inner rings looking to
+	 * see if its inside.  If not, distance==0.
+	 * Otherwise, distance = pt to ring distance
+	 */
+	for ( i = 1;  i < poly->nrings; i++)
+	{
+		/* Inside a hole. Distance = pt -> ring */
+		if ( rtgeom_contains_point(ctx, poly->rings[i], p) != RT_OUTSIDE )
+		{
+			RTDEBUG(3, " inside a hole");
+			return rt_dist2d_recursive(ctx, (RTGEOM*)point, poly->rings[i], dl);
+		}
+	}
+
+	RTDEBUG(3, " inside the polygon");
+	if (dl->mode == DIST_MIN)
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = p->x;
+		dl->p1.y = dl->p2.y = p->y;
+	}
+
+	return RT_TRUE; /* Is inside the polygon */
+}
+
+/**
+
+line to line calculation
+*/
+int
+rt_dist2d_line_line(const RTCTX *ctx, RTLINE *line1, RTLINE *line2, DISTPTS *dl)
+{
+	RTPOINTARRAY *pa1 = line1->points;
+	RTPOINTARRAY *pa2 = line2->points;
+	RTDEBUG(2, "rt_dist2d_line_line is called");
+	return rt_dist2d_ptarray_ptarray(ctx, pa1, pa2, dl);
+}
+
+int
+rt_dist2d_line_circstring(const RTCTX *ctx, RTLINE *line1, RTCIRCSTRING *line2, DISTPTS *dl)
+{
+	return rt_dist2d_ptarray_ptarrayarc(ctx, line1->points, line2->points, dl);
+}
+
+/**
+ * line to polygon calculation
+ * Brute force.
+ * Test line-ring distance against each ring.
+ * If there's an intersection (distance==0) then return 0 (crosses boundary).
+ * Otherwise, test to see if any point is inside outer rings of polygon,
+ * but not in inner rings.
+ * If so, return 0  (line inside polygon),
+ * otherwise return min distance to a ring (could be outside
+ * polygon or inside a hole)
+ */
+int
+rt_dist2d_line_poly(const RTCTX *ctx, RTLINE *line, RTPOLY *poly, DISTPTS *dl)
+{
+	const RTPOINT2D *pt;
+	int i;
+
+	RTDEBUGF(2, "rt_dist2d_line_poly called (%d rings)", poly->nrings);
+
+	pt = rt_getPoint2d_cp(ctx, line->points, 0);
+	if ( ptarray_contains_point(ctx, poly->rings[0], pt) == RT_OUTSIDE )
+	{
+		return rt_dist2d_ptarray_ptarray(ctx, line->points, poly->rings[0], dl);
+	}
+
+	for (i=1; i<poly->nrings; i++)
+	{
+		if (!rt_dist2d_ptarray_ptarray(ctx, line->points, poly->rings[i], dl)) return RT_FALSE;
+
+		RTDEBUGF(3, " distance from ring %d: %f, mindist: %f",
+		         i, dl->distance, dl->tolerance);
+		/* just a check if  the answer is already given */
+		if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; 
+	}
+
+	/*
+	 * No intersection, have to check if a point is
+	 * inside polygon
+	 */
+	pt = rt_getPoint2d_cp(ctx, line->points, 0);
+
+	/*
+	 * Outside outer ring, so min distance to a ring
+	 * is the actual min distance
+
+	if ( ! pt_in_ring_2d(ctx, &pt, poly->rings[0]) )
+	{
+		return ;
+	} */
+
+	/*
+	 * Its in the outer ring.
+	 * Have to check if its inside a hole
+	 */
+	for (i=1; i<poly->nrings; i++)
+	{
+		if ( ptarray_contains_point(ctx, poly->rings[i], pt) != RT_OUTSIDE )
+		{
+			/*
+			 * Its inside a hole, then the actual
+			 * distance is the min ring distance
+			 */
+			return RT_TRUE;
+		}
+	}
+	if (dl->mode == DIST_MIN)
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+	}
+	return RT_TRUE; /* Not in hole, so inside polygon */
+}
+
+int
+rt_dist2d_line_curvepoly(const RTCTX *ctx, RTLINE *line, RTCURVEPOLY *poly, DISTPTS *dl)
+{
+	const RTPOINT2D *pt = rt_getPoint2d_cp(ctx, line->points, 0);
+	int i;
+
+	if ( rtgeom_contains_point(ctx, poly->rings[0], pt) == RT_OUTSIDE )
+	{
+		return rt_dist2d_recursive(ctx, (RTGEOM*)line, poly->rings[0], dl);
+	}
+
+	for ( i = 1; i < poly->nrings; i++ )
+	{
+		if ( ! rt_dist2d_recursive(ctx, (RTGEOM*)line, poly->rings[i], dl) )
+			return RT_FALSE;
+
+		if ( dl->distance<=dl->tolerance && dl->mode == DIST_MIN ) 
+			return RT_TRUE; 
+	}
+
+	for ( i=1; i < poly->nrings; i++ )
+	{
+		if ( rtgeom_contains_point(ctx, poly->rings[i],pt) != RT_OUTSIDE )
+		{
+			/* Its inside a hole, then the actual */
+			return RT_TRUE;
+		}	
+	}
+
+	if (dl->mode == DIST_MIN)
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+	}
+
+	return RT_TRUE; /* Not in hole, so inside polygon */
+}
+
+/**
+Function handling polygon to polygon calculation
+1	if we are looking for maxdistance, just check the outer rings.
+2	check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings
+3	check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole of poly1
+4	check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole of poly2
+5	If we have come all the way here we know that the first point of one of them is inside the other ones outer ring and not in holes so we check wich one is inside.
+ */
+int
+rt_dist2d_poly_poly(const RTCTX *ctx, RTPOLY *poly1, RTPOLY *poly2, DISTPTS *dl)
+{
+
+	const RTPOINT2D *pt;
+	int i;
+
+	RTDEBUG(2, "rt_dist2d_poly_poly called");
+
+	/*1	if we are looking for maxdistance, just check the outer rings.*/
+	if (dl->mode == DIST_MAX)
+	{
+		return rt_dist2d_ptarray_ptarray(ctx, poly1->rings[0], poly2->rings[0], dl);
+	}
+
+
+	/* 2	check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings
+	here it would be possible to handle the information about wich one is inside wich one and only search for the smaller ones in the bigger ones holes.*/
+	pt = rt_getPoint2d_cp(ctx, poly1->rings[0], 0);
+	if ( ptarray_contains_point(ctx, poly2->rings[0], pt) == RT_OUTSIDE )
+	{
+		pt = rt_getPoint2d_cp(ctx, poly2->rings[0], 0);
+		if ( ptarray_contains_point(ctx, poly1->rings[0], pt) == RT_OUTSIDE )
+		{
+			return rt_dist2d_ptarray_ptarray(ctx, poly1->rings[0], poly2->rings[0], dl);
+		}
+	}
+
+	/*3	check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole of poly1*/
+	pt = rt_getPoint2d_cp(ctx, poly2->rings[0], 0);
+	for (i=1; i<poly1->nrings; i++)
+	{
+		/* Inside a hole */
+		if ( ptarray_contains_point(ctx, poly1->rings[i], pt) != RT_OUTSIDE )
+		{
+			return rt_dist2d_ptarray_ptarray(ctx, poly1->rings[i], poly2->rings[0], dl);
+		}
+	}
+
+	/*4	check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole of poly2*/
+	pt = rt_getPoint2d_cp(ctx, poly1->rings[0], 0);
+	for (i=1; i<poly2->nrings; i++)
+	{
+		/* Inside a hole */
+		if ( ptarray_contains_point(ctx, poly2->rings[i], pt) != RT_OUTSIDE )
+		{
+			return rt_dist2d_ptarray_ptarray(ctx, poly1->rings[0], poly2->rings[i], dl);
+		}
+	}
+
+
+	/*5	If we have come all the way here we know that the first point of one of them is inside the other ones outer ring and not in holes so we check wich one is inside.*/
+	pt = rt_getPoint2d_cp(ctx, poly1->rings[0], 0);
+	if ( ptarray_contains_point(ctx, poly2->rings[0], pt) != RT_OUTSIDE )
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+		return RT_TRUE;
+	}
+
+	pt = rt_getPoint2d_cp(ctx, poly2->rings[0], 0);
+	if ( ptarray_contains_point(ctx, poly1->rings[0], pt) != RT_OUTSIDE )
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+		return RT_TRUE;
+	}
+
+
+	rterror(ctx, "Unspecified error in function rt_dist2d_poly_poly");
+	return RT_FALSE;
+}
+
+int
+rt_dist2d_poly_curvepoly(const RTCTX *ctx, RTPOLY *poly1, RTCURVEPOLY *curvepoly2, DISTPTS *dl)
+{
+	RTCURVEPOLY *curvepoly1 = rtcurvepoly_construct_from_rtpoly(ctx, poly1);
+	int rv = rt_dist2d_curvepoly_curvepoly(ctx, curvepoly1, curvepoly2, dl);
+	rtgeom_free(ctx, (RTGEOM*)curvepoly1);
+	return rv;
+}
+
+int
+rt_dist2d_circstring_poly(const RTCTX *ctx, RTCIRCSTRING *circ, RTPOLY *poly, DISTPTS *dl)
+{
+	RTCURVEPOLY *curvepoly = rtcurvepoly_construct_from_rtpoly(ctx, poly);
+	int rv = rt_dist2d_line_curvepoly(ctx, (RTLINE*)circ, curvepoly, dl);
+	rtgeom_free(ctx, (RTGEOM*)curvepoly);
+	return rv;
+}
+
+
+int
+rt_dist2d_circstring_curvepoly(const RTCTX *ctx, RTCIRCSTRING *circ, RTCURVEPOLY *poly, DISTPTS *dl)
+{
+	return rt_dist2d_line_curvepoly(ctx, (RTLINE*)circ, poly, dl);
+}
+
+int
+rt_dist2d_circstring_circstring(const RTCTX *ctx, RTCIRCSTRING *line1, RTCIRCSTRING *line2, DISTPTS *dl)
+{
+	return rt_dist2d_ptarrayarc_ptarrayarc(ctx, line1->points, line2->points, dl);
+}
+
+static const RTPOINT2D *
+rt_curvering_getfirstpoint2d_cp(const RTCTX *ctx, RTGEOM *geom)
+{
+	switch( geom->type )
+	{
+		case RTLINETYPE:
+			return rt_getPoint2d_cp(ctx, ((RTLINE*)geom)->points, 0);
+		case RTCIRCSTRINGTYPE:
+			return rt_getPoint2d_cp(ctx, ((RTCIRCSTRING*)geom)->points, 0);
+		case RTCOMPOUNDTYPE:
+		{
+			RTCOMPOUND *comp = (RTCOMPOUND*)geom;
+			RTLINE *line = (RTLINE*)(comp->geoms[0]);
+			return rt_getPoint2d_cp(ctx, line->points, 0);			
+		}
+		default:
+			rterror(ctx, "rt_curvering_getfirstpoint2d_cp: unknown type");
+	}
+	return NULL;
+}
+
+int
+rt_dist2d_curvepoly_curvepoly(const RTCTX *ctx, RTCURVEPOLY *poly1, RTCURVEPOLY *poly2, DISTPTS *dl)
+{
+	const RTPOINT2D *pt;
+	int i;
+
+	RTDEBUG(2, "rt_dist2d_curvepoly_curvepoly called");
+
+	/*1	if we are looking for maxdistance, just check the outer rings.*/
+	if (dl->mode == DIST_MAX)
+	{
+		return rt_dist2d_recursive(ctx, poly1->rings[0],	poly2->rings[0], dl);
+	}
+
+
+	/* 2	check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings
+	here it would be possible to handle the information about wich one is inside wich one and only search for the smaller ones in the bigger ones holes.*/
+	pt = rt_curvering_getfirstpoint2d_cp(ctx, poly1->rings[0]);
+	if ( rtgeom_contains_point(ctx, poly2->rings[0], pt) == RT_OUTSIDE )
+	{
+		pt = rt_curvering_getfirstpoint2d_cp(ctx, poly2->rings[0]);
+		if ( rtgeom_contains_point(ctx, poly1->rings[0], pt) == RT_OUTSIDE )
+		{
+			return rt_dist2d_recursive(ctx, poly1->rings[0], poly2->rings[0], dl);
+		}
+	}
+
+	/*3	check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole of poly1*/
+	pt = rt_curvering_getfirstpoint2d_cp(ctx, poly2->rings[0]);
+	for (i = 1; i < poly1->nrings; i++)
+	{
+		/* Inside a hole */
+		if ( rtgeom_contains_point(ctx, poly1->rings[i], pt) != RT_OUTSIDE )
+		{
+			return rt_dist2d_recursive(ctx, poly1->rings[i], poly2->rings[0], dl);
+		}
+	}
+
+	/*4	check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole of poly2*/
+	pt = rt_curvering_getfirstpoint2d_cp(ctx, poly1->rings[0]);
+	for (i = 1; i < poly2->nrings; i++)
+	{
+		/* Inside a hole */
+		if ( rtgeom_contains_point(ctx, poly2->rings[i], pt) != RT_OUTSIDE )
+		{
+			return rt_dist2d_recursive(ctx, poly1->rings[0],	poly2->rings[i], dl);
+		}
+	}
+
+
+	/*5	If we have come all the way here we know that the first point of one of them is inside the other ones outer ring and not in holes so we check wich one is inside.*/
+	pt = rt_curvering_getfirstpoint2d_cp(ctx, poly1->rings[0]);
+	if ( rtgeom_contains_point(ctx, poly2->rings[0], pt) != RT_OUTSIDE )
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+		return RT_TRUE;
+	}
+
+	pt = rt_curvering_getfirstpoint2d_cp(ctx, poly2->rings[0]);
+	if ( rtgeom_contains_point(ctx, poly1->rings[0], pt) != RT_OUTSIDE )
+	{
+		dl->distance = 0.0;
+		dl->p1.x = dl->p2.x = pt->x;
+		dl->p1.y = dl->p2.y = pt->y;
+		return RT_TRUE;
+	}
+
+	rterror(ctx, "Unspecified error in function rt_dist2d_curvepoly_curvepoly");
+	return RT_FALSE;
+}
+
+
+
+/**
+ * search all the segments of pointarray to see which one is closest to p1
+ * Returns minimum distance between point and pointarray
+ */
+int
+rt_dist2d_pt_ptarray(const RTCTX *ctx, const RTPOINT2D *p, RTPOINTARRAY *pa,DISTPTS *dl)
+{
+	int t;
+	const RTPOINT2D *start, *end;
+	int twist = dl->twisted;
+
+	RTDEBUG(2, "rt_dist2d_pt_ptarray is called");
+
+	start = rt_getPoint2d_cp(ctx, pa, 0);
+
+	if ( !rt_dist2d_pt_pt(ctx, p, start, dl) ) return RT_FALSE;
+
+	for (t=1; t<pa->npoints; t++)
+	{
+		dl->twisted=twist;
+		end = rt_getPoint2d_cp(ctx, pa, t);
+		if (!rt_dist2d_pt_seg(ctx, p, start, end, dl)) return RT_FALSE;
+
+		if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/
+		start = end;
+	}
+
+	return RT_TRUE;
+}
+
+/**
+* Search all the arcs of pointarray to see which one is closest to p1
+* Returns minimum distance between point and arc pointarray.
+*/
+int
+rt_dist2d_pt_ptarrayarc(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINTARRAY *pa, DISTPTS *dl)
+{
+	int t;
+	const RTPOINT2D *A1;
+	const RTPOINT2D *A2;
+	const RTPOINT2D *A3;
+	int twist = dl->twisted;
+
+	RTDEBUG(2, "rt_dist2d_pt_ptarrayarc is called");
+
+	if ( pa->npoints % 2 == 0 || pa->npoints < 3 )
+	{
+		rterror(ctx, "rt_dist2d_pt_ptarrayarc called with non-arc input");
+		return RT_FALSE;
+	}
+
+	if (dl->mode == DIST_MAX)
+	{
+		rterror(ctx, "rt_dist2d_pt_ptarrayarc does not currently support DIST_MAX mode");
+		return RT_FALSE;
+	}
+
+	A1 = rt_getPoint2d_cp(ctx, pa, 0);
+
+	if ( ! rt_dist2d_pt_pt(ctx, p, A1, dl) ) 
+		return RT_FALSE;
+
+	for ( t=1; t<pa->npoints; t += 2 )
+	{
+		dl->twisted = twist;
+		A2 = rt_getPoint2d_cp(ctx, pa, t);
+		A3 = rt_getPoint2d_cp(ctx, pa, t+1);
+		
+		if ( rt_dist2d_pt_arc(ctx, p, A1, A2, A3, dl) == RT_FALSE ) 
+			return RT_FALSE;
+
+		if ( dl->distance <= dl->tolerance && dl->mode == DIST_MIN ) 
+			return RT_TRUE; /*just a check if  the answer is already given*/
+			
+		A1 = A3;
+	}
+
+	return RT_TRUE;
+}
+
+
+
+
+/**
+* test each segment of l1 against each segment of l2.
+*/
+int
+rt_dist2d_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,DISTPTS *dl)
+{
+	int t,u;
+	const RTPOINT2D	*start, *end;
+	const RTPOINT2D	*start2, *end2;
+	int twist = dl->twisted;
+
+	RTDEBUGF(2, "rt_dist2d_ptarray_ptarray called (points: %d-%d)",l1->npoints, l2->npoints);
+
+	if (dl->mode == DIST_MAX)/*If we are searching for maxdistance we go straight to point-point calculation since the maxdistance have to be between two vertexes*/
+	{
+		for (t=0; t<l1->npoints; t++) /*for each segment in L1 */
+		{
+			start = rt_getPoint2d_cp(ctx, l1, t);
+			for (u=0; u<l2->npoints; u++) /*for each segment in L2 */
+			{
+				start2 = rt_getPoint2d_cp(ctx, l2, u);
+				rt_dist2d_pt_pt(ctx, start, start2, dl);
+				RTDEBUGF(4, "maxdist_ptarray_ptarray; seg %i * seg %i, dist = %g\n",t,u,dl->distance);
+				RTDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f",
+				         t, u, dl->distance, dl->tolerance);
+			}
+		}
+	}
+	else
+	{
+		start = rt_getPoint2d_cp(ctx, l1, 0);
+		for (t=1; t<l1->npoints; t++) /*for each segment in L1 */
+		{
+			end = rt_getPoint2d_cp(ctx, l1, t);
+			start2 = rt_getPoint2d_cp(ctx, l2, 0);
+			for (u=1; u<l2->npoints; u++) /*for each segment in L2 */
+			{
+				end2 = rt_getPoint2d_cp(ctx, l2, u);
+				dl->twisted=twist;
+				rt_dist2d_seg_seg(ctx, start, end, start2, end2, dl);
+				RTDEBUGF(4, "mindist_ptarray_ptarray; seg %i * seg %i, dist = %g\n",t,u,dl->distance);
+				RTDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f",
+				         t, u, dl->distance, dl->tolerance);
+				if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/
+				start2 = end2;
+			}
+			start = end;
+		}
+	}
+	return RT_TRUE;
+}
+
+/**
+* Test each segment of pa against each arc of pb for distance.
+*/
+int
+rt_dist2d_ptarray_ptarrayarc(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINTARRAY *pb, DISTPTS *dl)
+{
+	int t, u;
+	const RTPOINT2D *A1;
+	const RTPOINT2D *A2;
+	const RTPOINT2D *B1;
+	const RTPOINT2D *B2;
+	const RTPOINT2D *B3;
+	int twist = dl->twisted;
+
+	RTDEBUGF(2, "rt_dist2d_ptarray_ptarrayarc called (points: %d-%d)",pa->npoints, pb->npoints);
+
+	if ( pb->npoints % 2 == 0 || pb->npoints < 3 )
+	{
+		rterror(ctx, "rt_dist2d_ptarray_ptarrayarc called with non-arc input");
+		return RT_FALSE;
+	}
+
+	if ( dl->mode == DIST_MAX )
+	{
+		rterror(ctx, "rt_dist2d_ptarray_ptarrayarc does not currently support DIST_MAX mode");
+		return RT_FALSE;
+	}
+	else
+	{
+		A1 = rt_getPoint2d_cp(ctx, pa, 0);
+		for ( t=1; t < pa->npoints; t++ ) /* For each segment in pa */
+		{
+			A2 = rt_getPoint2d_cp(ctx, pa, t);
+			B1 = rt_getPoint2d_cp(ctx, pb, 0);
+			for ( u=1; u < pb->npoints; u += 2 ) /* For each arc in pb */
+			{
+				B2 = rt_getPoint2d_cp(ctx, pb, u);
+				B3 = rt_getPoint2d_cp(ctx, pb, u+1);
+				dl->twisted = twist;
+
+				rt_dist2d_seg_arc(ctx, A1, A2, B1, B2, B3, dl);
+
+				/* If we've found a distance within tolerance, we're done */
+				if ( dl->distance <= dl->tolerance && dl->mode == DIST_MIN ) 
+					return RT_TRUE; 
+
+				B1 = B3;
+			}
+			A1 = A2;
+		}
+	}
+	return RT_TRUE;
+}
+
+/**
+* Test each arc of pa against each arc of pb for distance.
+*/
+int
+rt_dist2d_ptarrayarc_ptarrayarc(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINTARRAY *pb, DISTPTS *dl)
+{
+	int t, u;
+	const RTPOINT2D *A1;
+	const RTPOINT2D *A2;
+	const RTPOINT2D *A3;
+	const RTPOINT2D *B1;
+	const RTPOINT2D *B2;
+	const RTPOINT2D *B3;
+	int twist = dl->twisted;
+
+	RTDEBUGF(2, "rt_dist2d_ptarrayarc_ptarrayarc called (points: %d-%d)",pa->npoints, pb->npoints);
+
+	if (dl->mode == DIST_MAX)
+	{
+		rterror(ctx, "rt_dist2d_ptarrayarc_ptarrayarc does not currently support DIST_MAX mode");
+		return RT_FALSE;
+	}
+	else
+	{
+		A1 = rt_getPoint2d_cp(ctx, pa, 0);
+		for ( t=1; t < pa->npoints; t += 2 ) /* For each segment in pa */
+		{
+			A2 = rt_getPoint2d_cp(ctx, pa, t);
+			A3 = rt_getPoint2d_cp(ctx, pa, t+1);
+			B1 = rt_getPoint2d_cp(ctx, pb, 0);
+			for ( u=1; u < pb->npoints; u += 2 ) /* For each arc in pb */
+			{
+				B2 = rt_getPoint2d_cp(ctx, pb, u);
+				B3 = rt_getPoint2d_cp(ctx, pb, u+1);
+				dl->twisted = twist;
+
+				rt_dist2d_arc_arc(ctx, A1, A2, A3, B1, B2, B3, dl);
+
+				/* If we've found a distance within tolerance, we're done */
+				if ( dl->distance <= dl->tolerance && dl->mode == DIST_MIN ) 
+					return RT_TRUE; 
+
+				B1 = B3;
+			}
+			A1 = A3;
+		}
+	}
+	return RT_TRUE;
+}
+
+/**
+* Calculate the shortest distance between an arc and an edge.
+* Line/circle approach from http://stackoverflow.com/questions/1073336/circle-line-collision-detection 
+*/
+int 
+rt_dist2d_seg_arc(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *B1, const RTPOINT2D *B2, const RTPOINT2D *B3, DISTPTS *dl)
+{
+	RTPOINT2D C; /* center of arc circle */
+	double radius_C; /* radius of arc circle */
+	RTPOINT2D D; /* point on A closest to C */
+	double dist_C_D; /* distance from C to D */
+	int pt_in_arc, pt_in_seg;
+	DISTPTS dltmp;
+	
+	/* Bail out on crazy modes */
+	if ( dl->mode < 0 )
+		rterror(ctx, "rt_dist2d_seg_arc does not support maxdistance mode");
+
+	/* What if the "arc" is a point? */
+	if ( rt_arc_is_pt(ctx, B1, B2, B3) )
+		return rt_dist2d_pt_seg(ctx, B1, A1, A2, dl);
+
+	/* Calculate center and radius of the circle. */
+	radius_C = rt_arc_center(ctx, B1, B2, B3, &C);
+
+	/* This "arc" is actually a line (B2 is colinear with B1,B3) */
+	if ( radius_C < 0.0 )
+		return rt_dist2d_seg_seg(ctx, A1, A2, B1, B3, dl);
+
+	/* Calculate distance between the line and circle center */
+	rt_dist2d_distpts_init(ctx, &dltmp, DIST_MIN);
+	if ( rt_dist2d_pt_seg(ctx, &C, A1, A2, &dltmp) == RT_FALSE )
+		rterror(ctx, "rt_dist2d_pt_seg failed in rt_dist2d_seg_arc");
+
+	D = dltmp.p1;
+	dist_C_D = dltmp.distance;
+	
+	/* Line intersects circle, maybe arc intersects edge? */
+	/* If so, that's the closest point. */
+	/* If not, the closest point is one of the end points of A */
+	if ( dist_C_D < radius_C )
+	{
+		double length_A; /* length of the segment A */
+		RTPOINT2D E, F; /* points of interection of edge A and circle(B) */
+		double dist_D_EF; /* distance from D to E or F (same distance both ways) */
+
+		dist_D_EF = sqrt(radius_C*radius_C - dist_C_D*dist_C_D);
+		length_A = sqrt((A2->x-A1->x)*(A2->x-A1->x)+(A2->y-A1->y)*(A2->y-A1->y));
+
+		/* Point of intersection E */
+		E.x = D.x - (A2->x-A1->x) * dist_D_EF / length_A;
+		E.y = D.y - (A2->y-A1->y) * dist_D_EF / length_A;
+		/* Point of intersection F */
+		F.x = D.x + (A2->x-A1->x) * dist_D_EF / length_A;
+		F.y = D.y + (A2->y-A1->y) * dist_D_EF / length_A;
+
+
+		/* If E is within A and within B then it's an interesction point */
+		pt_in_arc = rt_pt_in_arc(ctx, &E, B1, B2, B3);
+		pt_in_seg = rt_pt_in_seg(ctx, &E, A1, A2);
+		
+		if ( pt_in_arc && pt_in_seg )
+		{
+			dl->distance = 0.0;
+			dl->p1 = E;
+			dl->p2 = E;
+			return RT_TRUE;
+		}
+		
+		/* If F is within A and within B then it's an interesction point */
+		pt_in_arc = rt_pt_in_arc(ctx, &F, B1, B2, B3);
+		pt_in_seg = rt_pt_in_seg(ctx, &F, A1, A2);
+		
+		if ( pt_in_arc && pt_in_seg )
+		{
+			dl->distance = 0.0;
+			dl->p1 = F;
+			dl->p2 = F;
+			return RT_TRUE;
+		}
+	}
+	
+	/* Line grazes circle, maybe arc intersects edge? */
+	/* If so, grazing point is the closest point. */
+	/* If not, the closest point is one of the end points of A */
+	else if ( dist_C_D == radius_C )
+	{		
+		/* Closest point D is also the point of grazing */
+		pt_in_arc = rt_pt_in_arc(ctx, &D, B1, B2, B3);
+		pt_in_seg = rt_pt_in_seg(ctx, &D, A1, A2);
+
+		/* Is D contained in both A and B? */
+		if ( pt_in_arc && pt_in_seg )
+		{
+			dl->distance = 0.0;
+			dl->p1 = D;
+			dl->p2 = D;
+			return RT_TRUE;
+		}
+	}
+	/* Line misses circle. */
+	/* If closest point to A on circle is within B, then that's the closest */
+	/* Otherwise, the closest point will be an end point of A */
+	else
+	{
+		RTPOINT2D G; /* Point on circle closest to A */
+		G.x = C.x + (D.x-C.x) * radius_C / dist_C_D;
+		G.y = C.y + (D.y-C.y) * radius_C / dist_C_D;
+		
+		pt_in_arc = rt_pt_in_arc(ctx, &G, B1, B2, B3);
+		pt_in_seg = rt_pt_in_seg(ctx, &D, A1, A2);
+		
+		/* Closest point is on the interior of A and B */
+		if ( pt_in_arc && pt_in_seg )
+			return rt_dist2d_pt_pt(ctx, &D, &G, dl);
+
+	}
+	
+	/* Now we test the many combinations of end points with either */
+	/* arcs or edges. Each previous check determined if the closest */
+	/* potential point was within the arc/segment inscribed on the */
+	/* line/circle holding the arc/segment. */
+
+	/* Closest point is in the arc, but not in the segment, so */
+	/* one of the segment end points must be the closest. */
+	if ( pt_in_arc & ! pt_in_seg )
+	{
+		rt_dist2d_pt_arc(ctx, A1, B1, B2, B3, dl);
+		rt_dist2d_pt_arc(ctx, A2, B1, B2, B3, dl);		
+		return RT_TRUE;
+	}
+	/* or, one of the arc end points is the closest */
+	else if  ( pt_in_seg && ! pt_in_arc )
+	{
+		rt_dist2d_pt_seg(ctx, B1, A1, A2, dl);
+		rt_dist2d_pt_seg(ctx, B3, A1, A2, dl);
+		return RT_TRUE;			
+	}
+	/* Finally, one of the end-point to end-point combos is the closest. */
+	else
+	{
+		rt_dist2d_pt_pt(ctx, A1, B1, dl);
+		rt_dist2d_pt_pt(ctx, A1, B3, dl);
+		rt_dist2d_pt_pt(ctx, A2, B1, dl);
+		rt_dist2d_pt_pt(ctx, A2, B3, dl);
+		return RT_TRUE;
+	}
+	
+	return RT_FALSE;
+}
+
+int
+rt_dist2d_pt_arc(const RTCTX *ctx, const RTPOINT2D* P, const RTPOINT2D* A1, const RTPOINT2D* A2, const RTPOINT2D* A3, DISTPTS* dl)
+{
+	double radius_A, d;
+	RTPOINT2D C; /* center of circle defined by arc A */
+	RTPOINT2D X; /* point circle(A) where line from C to P crosses */
+	
+	if ( dl->mode < 0 )
+		rterror(ctx, "rt_dist2d_pt_arc does not support maxdistance mode");
+
+	/* What if the arc is a point? */
+	if ( rt_arc_is_pt(ctx, A1, A2, A3) )
+		return rt_dist2d_pt_pt(ctx, P, A1, dl);
+
+	/* Calculate centers and radii of circles. */
+	radius_A = rt_arc_center(ctx, A1, A2, A3, &C);
+	
+	/* This "arc" is actually a line (A2 is colinear with A1,A3) */
+	if ( radius_A < 0.0 )
+		return rt_dist2d_pt_seg(ctx, P, A1, A3, dl);
+	
+	/* Distance from point to center */	
+	d = distance2d_pt_pt(ctx, &C, P);
+	
+	/* X is the point on the circle where the line from P to C crosses */
+	X.x = C.x + (P->x - C.x) * radius_A / d;
+	X.y = C.y + (P->y - C.y) * radius_A / d;
+
+	/* Is crossing point inside the arc? Or arc is actually circle? */
+	if ( p2d_same(ctx, A1, A3) || rt_pt_in_arc(ctx, &X, A1, A2, A3) )
+	{
+		rt_dist2d_pt_pt(ctx, P, &X, dl);
+	}
+	else 
+	{
+		/* Distance is the minimum of the distances to the arc end points */
+		rt_dist2d_pt_pt(ctx, A1, P, dl);
+		rt_dist2d_pt_pt(ctx, A3, P, dl);
+	}
+	return RT_TRUE;
+}
+
+
+int
+rt_dist2d_arc_arc(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, 
+                  const RTPOINT2D *B1, const RTPOINT2D *B2, const RTPOINT2D *B3,
+                  DISTPTS *dl)
+{
+	RTPOINT2D CA, CB; /* Center points of arcs A and B */
+	double radius_A, radius_B, d; /* Radii of arcs A and B */
+	RTPOINT2D P; /* Temporary point P */
+	RTPOINT2D D; /* Mid-point between the centers CA and CB */
+	int pt_in_arc_A, pt_in_arc_B; /* Test whether potential intersection point is within the arc */
+	
+	if ( dl->mode != DIST_MIN )
+		rterror(ctx, "rt_dist2d_arc_arc only supports mindistance");
+	
+	/* TODO: Handle case where arc is closed circle (A1 = A3) */
+	
+	/* What if one or both of our "arcs" is actually a point? */
+	if ( rt_arc_is_pt(ctx, B1, B2, B3) && rt_arc_is_pt(ctx, A1, A2, A3) )
+		return rt_dist2d_pt_pt(ctx, B1, A1, dl);
+	else if ( rt_arc_is_pt(ctx, B1, B2, B3) )
+		return rt_dist2d_pt_arc(ctx, B1, A1, A2, A3, dl);
+	else if ( rt_arc_is_pt(ctx, A1, A2, A3) )
+		return rt_dist2d_pt_arc(ctx, A1, B1, B2, B3, dl);
+	
+	/* Calculate centers and radii of circles. */
+	radius_A = rt_arc_center(ctx, A1, A2, A3, &CA);
+	radius_B = rt_arc_center(ctx, B1, B2, B3, &CB);
+
+	/* Two co-linear arcs?!? That's two segments. */
+	if ( radius_A < 0 && radius_B < 0 )
+		return rt_dist2d_seg_seg(ctx, A1, A3, B1, B3, dl);
+
+	/* A is co-linear, delegate to rt_dist_seg_arc here. */
+	if ( radius_A < 0 )
+		return rt_dist2d_seg_arc(ctx, A1, A3, B1, B2, B3, dl);
+
+	/* B is co-linear, delegate to rt_dist_seg_arc here. */
+	if ( radius_B < 0 )
+		return rt_dist2d_seg_arc(ctx, B1, B3, A1, A2, A3, dl);
+
+	/* Make sure that arc "A" has the bigger radius */
+	if ( radius_B > radius_A )
+	{
+		const RTPOINT2D *tmp;
+		tmp = B1; B1 = A1; A1 = tmp;
+		tmp = B2; B2 = A2; A2 = tmp;
+		tmp = B3; B3 = A3; A3 = tmp;
+		P = CB; CB = CA; CA = P;
+		d = radius_B; radius_B = radius_A; radius_A = d;
+	}
+	
+	/* Center-center distance */
+	d = distance2d_pt_pt(ctx, &CA, &CB);
+
+	/* Equal circles. Arcs may intersect at multiple points, or at none! */
+	if ( FP_EQUALS(d, 0.0) && FP_EQUALS(radius_A, radius_B) )
+	{
+		rterror(ctx, "rt_dist2d_arc_arc can't handle cojoint circles, uh oh");
+	}
+	
+	/* Circles touch at a point. Is that point within the arcs? */
+	if ( d == (radius_A + radius_B) )
+	{
+		D.x = CA.x + (CB.x - CA.x) * radius_A / d;
+		D.y = CA.y + (CB.y - CA.y) * radius_A / d;
+		
+		pt_in_arc_A = rt_pt_in_arc(ctx, &D, A1, A2, A3);
+		pt_in_arc_B = rt_pt_in_arc(ctx, &D, B1, B2, B3);
+		
+		/* Arcs do touch at D, return it */
+		if ( pt_in_arc_A && pt_in_arc_B )
+		{
+			dl->distance = 0.0;
+			dl->p1 = D;
+			dl->p2 = D;
+			return RT_TRUE;
+		}
+	}
+	/* Disjoint or contained circles don't intersect. Closest point may be on */
+	/* the line joining CA to CB. */
+	else if ( d > (radius_A + radius_B) /* Disjoint */ || d < (radius_A - radius_B) /* Contained */ )
+	{
+		RTPOINT2D XA, XB; /* Points where the line from CA to CB cross their circle bounds */
+		
+		/* Calculate hypothetical nearest points, the places on the */
+		/* two circles where the center-center line crosses. If both */
+		/* arcs contain their hypothetical points, that's the crossing distance */
+		XA.x = CA.x + (CB.x - CA.x) * radius_A / d;
+		XA.y = CA.y + (CB.y - CA.y) * radius_A / d;
+		XB.x = CB.x + (CA.x - CB.x) * radius_B / d;
+		XB.y = CB.y + (CA.y - CB.y) * radius_B / d;
+		
+		pt_in_arc_A = rt_pt_in_arc(ctx, &XA, A1, A2, A3);
+		pt_in_arc_B = rt_pt_in_arc(ctx, &XB, B1, B2, B3);
+		
+		/* If the nearest points are both within the arcs, that's our answer */
+		/* the shortest distance is at the nearest points */
+		if ( pt_in_arc_A && pt_in_arc_B )
+		{
+			return rt_dist2d_pt_pt(ctx, &XA, &XB, dl);
+		}
+	}
+	/* Circles cross at two points, are either of those points in both arcs? */
+	/* http://paulbourke.net/geometry/2circle/ */
+	else if ( d < (radius_A + radius_B) )
+	{
+		RTPOINT2D E, F; /* Points where circle(A) and circle(B) cross */
+		/* Distance from CA to D */
+		double a = (radius_A*radius_A - radius_B*radius_B + d*d) / (2*d);
+		/* Distance from D to E or F */
+		double h = sqrt(radius_A*radius_A - a*a);
+		
+		/* Location of D */
+		D.x = CA.x + (CB.x - CA.x) * a / d;
+		D.y = CA.y + (CB.y - CA.y) * a / d;
+		
+		/* Start from D and project h units perpendicular to CA-D to get E */
+		E.x = D.x + (D.y - CA.y) * h / a;
+		E.y = D.y + (D.x - CA.x) * h / a;
+
+		/* Crossing point E contained in arcs? */
+		pt_in_arc_A = rt_pt_in_arc(ctx, &E, A1, A2, A3);
+		pt_in_arc_B = rt_pt_in_arc(ctx, &E, B1, B2, B3);
+
+		if ( pt_in_arc_A && pt_in_arc_B ) 
+		{
+			dl->p1 = dl->p2 = E;
+			dl->distance = 0.0;
+			return RT_TRUE;
+		}
+
+		/* Start from D and project h units perpendicular to CA-D to get F */
+		F.x = D.x - (D.y - CA.y) * h / a;
+		F.y = D.y - (D.x - CA.x) * h / a;
+		
+		/* Crossing point F contained in arcs? */
+		pt_in_arc_A = rt_pt_in_arc(ctx, &F, A1, A2, A3);
+		pt_in_arc_B = rt_pt_in_arc(ctx, &F, B1, B2, B3);
+
+		if ( pt_in_arc_A && pt_in_arc_B ) 
+		{
+			dl->p1 = dl->p2 = F;
+			dl->distance = 0.0;
+			return RT_TRUE;
+		}
+	} 
+	else
+	{
+		rterror(ctx, "rt_dist2d_arc_arc: arcs neither touch, intersect nor are disjoint! INCONCEIVABLE!");
+		return RT_FALSE;
+	}
+
+	/* Closest point is in the arc A, but not in the arc B, so */
+	/* one of the B end points must be the closest. */
+	if ( pt_in_arc_A & ! pt_in_arc_B )
+	{
+		rt_dist2d_pt_arc(ctx, B1, A1, A2, A3, dl);
+		rt_dist2d_pt_arc(ctx, B3, A1, A2, A3, dl);
+		return RT_TRUE;
+	}
+	/* Closest point is in the arc B, but not in the arc A, so */
+	/* one of the A end points must be the closest. */
+	else if  ( pt_in_arc_B && ! pt_in_arc_A )
+	{
+		rt_dist2d_pt_arc(ctx, A1, B1, B2, B3, dl);
+		rt_dist2d_pt_arc(ctx, A3, B1, B2, B3, dl);		
+		return RT_TRUE;			
+	}
+	/* Finally, one of the end-point to end-point combos is the closest. */
+	else
+	{
+		rt_dist2d_pt_pt(ctx, A1, B1, dl);
+		rt_dist2d_pt_pt(ctx, A1, B3, dl);
+		rt_dist2d_pt_pt(ctx, A2, B1, dl);
+		rt_dist2d_pt_pt(ctx, A2, B3, dl);
+		return RT_TRUE;
+	}	
+
+	return RT_TRUE;
+}
+
+/**
+Finds the shortest distance between two segments.
+This function is changed so it is not doing any comparasion of distance
+but just sending every possible combination further to rt_dist2d_pt_seg
+*/
+int
+rt_dist2d_seg_seg(const RTCTX *ctx, const RTPOINT2D *A, const RTPOINT2D *B, const RTPOINT2D *C, const RTPOINT2D *D, DISTPTS *dl)
+{
+	double	s_top, s_bot,s;
+	double	r_top, r_bot,r;
+
+	RTDEBUGF(2, "rt_dist2d_seg_seg [%g,%g]->[%g,%g] by [%g,%g]->[%g,%g]",
+	         A->x,A->y,B->x,B->y, C->x,C->y, D->x, D->y);
+
+	/*A and B are the same point */
+	if (  ( A->x == B->x) && (A->y == B->y) )
+	{
+		return rt_dist2d_pt_seg(ctx, A,C,D,dl);
+	}
+	/*U and V are the same point */
+
+	if (  ( C->x == D->x) && (C->y == D->y) )
+	{
+		dl->twisted= ((dl->twisted) * (-1));
+		return rt_dist2d_pt_seg(ctx, D,A,B,dl);
+	}
+	/* AB and CD are line segments */
+	/* from comp.graphics.algo
+
+	Solving the above for r and s yields
+				(Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy)
+	           r = ----------------------------- (eqn 1)
+				(Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
+
+		 	(Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
+		s = ----------------------------- (eqn 2)
+			(Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
+	Let P be the position vector of the intersection point, then
+		P=A+r(B-A) or
+		Px=Ax+r(Bx-Ax)
+		Py=Ay+r(By-Ay)
+	By examining the values of r & s, you can also determine some other limiting conditions:
+		If 0<=r<=1 & 0<=s<=1, intersection exists
+		r<0 or r>1 or s<0 or s>1 line segments do not intersect
+		If the denominator in eqn 1 is zero, AB & CD are parallel
+		If the numerator in eqn 1 is also zero, AB & CD are collinear.
+
+	*/
+	r_top = (A->y-C->y)*(D->x-C->x) - (A->x-C->x)*(D->y-C->y);
+	r_bot = (B->x-A->x)*(D->y-C->y) - (B->y-A->y)*(D->x-C->x);
+
+	s_top = (A->y-C->y)*(B->x-A->x) - (A->x-C->x)*(B->y-A->y);
+	s_bot = (B->x-A->x)*(D->y-C->y) - (B->y-A->y)*(D->x-C->x);
+
+	if  ( (r_bot==0) || (s_bot == 0) )
+	{
+		if ((rt_dist2d_pt_seg(ctx, A,C,D,dl)) && (rt_dist2d_pt_seg(ctx, B,C,D,dl)))
+		{
+			dl->twisted= ((dl->twisted) * (-1));  /*here we change the order of inputted geometrys and that we  notice by changing sign on dl->twisted*/
+			return ((rt_dist2d_pt_seg(ctx, C,A,B,dl)) && (rt_dist2d_pt_seg(ctx, D,A,B,dl))); /*if all is successful we return true*/
+		}
+		else
+		{
+			return RT_FALSE; /* if any of the calls to rt_dist2d_pt_seg goes wrong we return false*/
+		}
+	}
+
+	s = s_top/s_bot;
+	r=  r_top/r_bot;
+
+	if (((r<0) || (r>1) || (s<0) || (s>1)) || (dl->mode == DIST_MAX))
+	{
+		if ((rt_dist2d_pt_seg(ctx, A,C,D,dl)) && (rt_dist2d_pt_seg(ctx, B,C,D,dl)))
+		{
+			dl->twisted= ((dl->twisted) * (-1));  /*here we change the order of inputted geometrys and that we  notice by changing sign on dl->twisted*/
+			return ((rt_dist2d_pt_seg(ctx, C,A,B,dl)) && (rt_dist2d_pt_seg(ctx, D,A,B,dl))); /*if all is successful we return true*/
+		}
+		else
+		{
+			return RT_FALSE; /* if any of the calls to rt_dist2d_pt_seg goes wrong we return false*/
+		}
+	}
+	else
+	{
+		if (dl->mode == DIST_MIN)	/*If there is intersection we identify the intersection point and return it but only if we are looking for mindistance*/
+		{
+			RTPOINT2D theP;
+
+			if (((A->x==C->x)&&(A->y==C->y))||((A->x==D->x)&&(A->y==D->y)))
+			{
+				theP.x = A->x;
+				theP.y = A->y;
+			}
+			else if (((B->x==C->x)&&(B->y==C->y))||((B->x==D->x)&&(B->y==D->y)))
+			{
+				theP.x = B->x;
+				theP.y = B->y;
+			}
+			else
+			{
+				theP.x = A->x+r*(B->x-A->x);
+				theP.y = A->y+r*(B->y-A->y);
+			}
+			dl->distance=0.0;
+			dl->p1=theP;
+			dl->p2=theP;
+		}
+		return RT_TRUE;
+
+	}
+	rterror(ctx, "unspecified error in function rt_dist2d_seg_seg");
+	return RT_FALSE; /*If we have come here something is wrong*/
+}
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Brute force functions
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/*------------------------------------------------------------------------------------------------------------
+New faster distance calculations
+--------------------------------------------------------------------------------------------------------------*/
+
+/**
+
+The new faster calculation comparing pointarray to another pointarray
+the arrays can come from both polygons and linestrings.
+The naming is not good but comes from that it compares a
+chosen selection of the points not all of them
+*/
+int
+rt_dist2d_fast_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,DISTPTS *dl, RTGBOX *box1, RTGBOX *box2)
+{
+	/*here we define two lists to hold our calculated "z"-values and the order number in the geometry*/
+
+	double k, thevalue;
+	float	deltaX, deltaY, c1m, c2m;
+	RTPOINT2D	c1, c2;
+	const RTPOINT2D *theP;
+	float min1X, max1X, max1Y, min1Y,min2X, max2X, max2Y, min2Y;
+	int t;
+	int n1 = l1->npoints;
+	int n2 = l2->npoints;
+	
+	LISTSTRUCT *list1, *list2;
+	list1 = (LISTSTRUCT*)rtalloc(ctx, sizeof(LISTSTRUCT)*n1); 
+	list2 = (LISTSTRUCT*)rtalloc(ctx, sizeof(LISTSTRUCT)*n2);
+	
+	RTDEBUG(2, "rt_dist2d_fast_ptarray_ptarray is called");
+
+	max1X = box1->xmax;
+	min1X = box1->xmin;
+	max1Y = box1->ymax;
+	min1Y = box1->ymin;
+	max2X = box2->xmax;
+	min2X = box2->xmin;
+	max2Y = box2->ymax;
+	min2Y = box2->ymin;
+	/*we want the center of the bboxes, and calculate the slope between the centerpoints*/
+	c1.x = min1X + (max1X-min1X)/2;
+	c1.y = min1Y + (max1Y-min1Y)/2;
+	c2.x = min2X + (max2X-min2X)/2;
+	c2.y = min2Y + (max2Y-min2Y)/2;
+
+	deltaX=(c2.x-c1.x);
+	deltaY=(c2.y-c1.y);
+
+
+	/*Here we calculate where the line perpendicular to the center-center line crosses the axes for each vertex
+	if the center-center line is vertical the perpendicular line will be horizontal and we find it's crossing the Y-axes with z = y-kx */
+	if ((deltaX*deltaX)<(deltaY*deltaY))        /*North or South*/
+	{
+		k = -deltaX/deltaY;
+		for (t=0; t<n1; t++) /*for each segment in L1 */
+		{
+			theP = rt_getPoint2d_cp(ctx, l1, t);
+			thevalue = theP->y - (k * theP->x);
+			list1[t].themeasure=thevalue;
+			list1[t].pnr=t;
+
+		}
+		for (t=0; t<n2; t++) /*for each segment in L2*/
+		{
+			theP = rt_getPoint2d_cp(ctx, l2, t);
+			thevalue = theP->y - (k * theP->x);
+			list2[t].themeasure=thevalue;
+			list2[t].pnr=t;
+
+		}
+		c1m = c1.y-(k*c1.x);
+		c2m = c2.y-(k*c2.x);
+	}
+
+
+	/*if the center-center line is horizontal the perpendicular line will be vertical. To eliminate problems with deviding by zero we are here mirroring the coordinate-system
+	 and we find it's crossing the X-axes with z = x-(1/k)y */
+	else        /*West or East*/
+	{
+		k = -deltaY/deltaX;
+		for (t=0; t<n1; t++) /*for each segment in L1 */
+		{
+			theP = rt_getPoint2d_cp(ctx, l1, t);
+			thevalue = theP->x - (k * theP->y);
+			list1[t].themeasure=thevalue;
+			list1[t].pnr=t;
+			/* rtnotice(ctx, "l1 %d, measure=%f",t,thevalue ); */
+		}
+		for (t=0; t<n2; t++) /*for each segment in L2*/
+		{
+			theP = rt_getPoint2d_cp(ctx, l2, t);
+			thevalue = theP->x - (k * theP->y);
+			list2[t].themeasure=thevalue;
+			list2[t].pnr=t;
+			/* rtnotice(ctx, "l2 %d, measure=%f",t,thevalue ); */
+		}
+		c1m = c1.x-(k*c1.y);
+		c2m = c2.x-(k*c2.y);
+	}
+
+	/*we sort our lists by the calculated values*/
+	qsort(list1, n1, sizeof(LISTSTRUCT), struct_cmp_by_measure);
+	qsort(list2, n2, sizeof(LISTSTRUCT), struct_cmp_by_measure);
+
+	if (c1m < c2m)
+	{
+		if (!rt_dist2d_pre_seg_seg(ctx, l1,l2,list1,list2,k,dl)) 
+		{
+			rtfree(ctx, list1);
+			rtfree(ctx, list2);
+			return RT_FALSE;
+		}
+	}
+	else
+	{
+		dl->twisted= ((dl->twisted) * (-1));
+		if (!rt_dist2d_pre_seg_seg(ctx, l2,l1,list2,list1,k,dl)) 
+		{
+			rtfree(ctx, list1);
+			rtfree(ctx, list2);
+			return RT_FALSE;
+		}
+	}
+	rtfree(ctx, list1);
+	rtfree(ctx, list2);	
+	return RT_TRUE;
+}
+
+int
+struct_cmp_by_measure(const void *a, const void *b)
+{
+	LISTSTRUCT *ia = (LISTSTRUCT*)a;
+	LISTSTRUCT *ib = (LISTSTRUCT*)b;
+	return ( ia->themeasure>ib->themeasure ) ? 1 : -1;
+}
+
+/**
+	preparation before rt_dist2d_seg_seg.
+*/
+int
+rt_dist2d_pre_seg_seg(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,LISTSTRUCT *list1, LISTSTRUCT *list2,double k, DISTPTS *dl)
+{
+	const RTPOINT2D *p1, *p2, *p3, *p4, *p01, *p02;
+	int pnr1,pnr2,pnr3,pnr4, n1, n2, i, u, r, twist;
+	double maxmeasure;
+	n1=	l1->npoints;
+	n2 = l2->npoints;
+
+	RTDEBUG(2, "rt_dist2d_pre_seg_seg is called");
+
+	p1 = rt_getPoint2d_cp(ctx, l1, list1[0].pnr);
+	p3 = rt_getPoint2d_cp(ctx, l2, list2[0].pnr);
+	rt_dist2d_pt_pt(ctx, p1, p3, dl);
+	maxmeasure = sqrt(dl->distance*dl->distance + (dl->distance*dl->distance*k*k));
+	twist = dl->twisted; /*to keep the incomming order between iterations*/
+	for (i =(n1-1); i>=0; --i)
+	{
+		/*we break this iteration when we have checked every
+		point closer to our perpendicular "checkline" than
+		our shortest found distance*/
+		if (((list2[0].themeasure-list1[i].themeasure)) > maxmeasure) break;
+		for (r=-1; r<=1; r +=2) /*because we are not iterating in the original pointorder we have to check the segment before and after every point*/
+		{
+			pnr1 = list1[i].pnr;
+			p1 = rt_getPoint2d_cp(ctx, l1, pnr1);
+			if (pnr1+r<0)
+			{
+				p01 = rt_getPoint2d_cp(ctx, l1, (n1-1));
+				if (( p1->x == p01->x) && (p1->y == p01->y)) pnr2 = (n1-1);
+				else pnr2 = pnr1; /* if it is a line and the last and first point is not the same we avoid the edge between start and end this way*/
+			}
+
+			else if (pnr1+r>(n1-1))
+			{
+				p01 = rt_getPoint2d_cp(ctx, l1, 0);
+				if (( p1->x == p01->x) && (p1->y == p01->y)) pnr2 = 0;
+				else pnr2 = pnr1; /* if it is a line and the last and first point is not the same we avoid the edge between start and end this way*/
+			}
+			else pnr2 = pnr1+r;
+
+
+			p2 = rt_getPoint2d_cp(ctx, l1, pnr2);
+			for (u=0; u<n2; ++u)
+			{
+				if (((list2[u].themeasure-list1[i].themeasure)) >= maxmeasure) break;
+				pnr3 = list2[u].pnr;
+				p3 = rt_getPoint2d_cp(ctx, l2, pnr3);
+				if (pnr3==0)
+				{
+					p02 = rt_getPoint2d_cp(ctx, l2, (n2-1));
+					if (( p3->x == p02->x) && (p3->y == p02->y)) pnr4 = (n2-1);
+					else pnr4 = pnr3; /* if it is a line and the last and first point is not the same we avoid the edge between start and end this way*/
+				}
+				else pnr4 = pnr3-1;
+
+				p4 = rt_getPoint2d_cp(ctx, l2, pnr4);
+				dl->twisted=twist;
+				if (!rt_dist2d_selected_seg_seg(ctx, p1, p2, p3, p4, dl)) return RT_FALSE;
+
+				if (pnr3>=(n2-1))
+				{
+					p02 = rt_getPoint2d_cp(ctx, l2, 0);
+					if (( p3->x == p02->x) && (p3->y == p02->y)) pnr4 = 0;
+					else pnr4 = pnr3; /* if it is a line and the last and first point is not the same we avoid the edge between start and end this way*/
+				}
+
+				else pnr4 = pnr3+1;
+
+				p4 = rt_getPoint2d_cp(ctx, l2, pnr4);
+				dl->twisted=twist; /*we reset the "twist" for each iteration*/
+				if (!rt_dist2d_selected_seg_seg(ctx, p1, p2, p3, p4, dl)) return RT_FALSE;
+
+				maxmeasure = sqrt(dl->distance*dl->distance + (dl->distance*dl->distance*k*k));/*here we "translate" the found mindistance so it can be compared to our "z"-values*/
+			}
+		}
+	}
+
+	return RT_TRUE;
+}
+
+
+/**
+	This is the same function as rt_dist2d_seg_seg but
+	without any calculations to determine intersection since we
+	already know they do not intersect
+*/
+int
+rt_dist2d_selected_seg_seg(const RTCTX *ctx, const RTPOINT2D *A, const RTPOINT2D *B, const RTPOINT2D *C, const RTPOINT2D *D, DISTPTS *dl)
+{
+	RTDEBUGF(2, "rt_dist2d_selected_seg_seg [%g,%g]->[%g,%g] by [%g,%g]->[%g,%g]",
+	         A->x,A->y,B->x,B->y, C->x,C->y, D->x, D->y);
+
+	/*A and B are the same point */
+	if (  ( A->x == B->x) && (A->y == B->y) )
+	{
+		return rt_dist2d_pt_seg(ctx, A,C,D,dl);
+	}
+	/*U and V are the same point */
+
+	if (  ( C->x == D->x) && (C->y == D->y) )
+	{
+		dl->twisted= ((dl->twisted) * (-1));
+		return rt_dist2d_pt_seg(ctx, D,A,B,dl);
+	}
+
+	if ((rt_dist2d_pt_seg(ctx, A,C,D,dl)) && (rt_dist2d_pt_seg(ctx, B,C,D,dl)))
+	{
+		dl->twisted= ((dl->twisted) * (-1));  /*here we change the order of inputted geometrys and that we  notice by changing sign on dl->twisted*/
+		return ((rt_dist2d_pt_seg(ctx, C,A,B,dl)) && (rt_dist2d_pt_seg(ctx, D,A,B,dl))); /*if all is successful we return true*/
+	}
+	else
+	{
+		return RT_FALSE; /* if any of the calls to rt_dist2d_pt_seg goes wrong we return false*/
+	}
+}
+
+/*------------------------------------------------------------------------------------------------------------
+End of New faster distance calculations
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/*------------------------------------------------------------------------------------------------------------
+Functions in common for Brute force and new calculation
+--------------------------------------------------------------------------------------------------------------*/
+
+/**
+rt_dist2d_comp from p to line A->B
+This one is now sending every occation to rt_dist2d_pt_pt
+Before it was handling occations where r was between 0 and 1 internally
+and just returning the distance without identifying the points.
+To get this points it was nessecary to change and it also showed to be about 10%faster.
+*/
+int
+rt_dist2d_pt_seg(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINT2D *A, const RTPOINT2D *B, DISTPTS *dl)
+{
+	RTPOINT2D c;
+	double	r;
+	/*if start==end, then use pt distance */
+	if (  ( A->x == B->x) && (A->y == B->y) )
+	{
+		return rt_dist2d_pt_pt(ctx, p,A,dl);
+	}
+	/*
+	 * otherwise, we use comp.graphics.algorithms
+	 * Frequently Asked Questions method
+	 *
+	 *  (1)        AC dot AB
+	 *         r = ---------
+	 *              ||AB||^2
+	 *	r has the following meaning:
+	 *	r=0 P = A
+	 *	r=1 P = B
+	 *	r<0 P is on the backward extension of AB
+	 *	r>1 P is on the forward extension of AB
+	 *	0<r<1 P is interior to AB
+	 */
+
+	r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	/*This is for finding the maxdistance.
+	the maxdistance have to be between two vertexes,
+	compared to mindistance which can be between
+	tvo vertexes vertex.*/
+	if (dl->mode == DIST_MAX)
+	{
+		if (r>=0.5)
+		{
+			return rt_dist2d_pt_pt(ctx, p,A,dl);
+		}
+		if (r<0.5)
+		{
+			return rt_dist2d_pt_pt(ctx, p,B,dl);
+		}
+	}
+
+	if (r<0)	/*If p projected on the line is outside point A*/
+	{
+		return rt_dist2d_pt_pt(ctx, p,A,dl);
+	}
+	if (r>=1)	/*If p projected on the line is outside point B or on point B*/
+	{
+		return rt_dist2d_pt_pt(ctx, p,B,dl);
+	}
+	
+	/*If the point p is on the segment this is a more robust way to find out that*/
+	if (( ((A->y-p->y)*(B->x-A->x)==(A->x-p->x)*(B->y-A->y) ) ) && (dl->mode ==  DIST_MIN))
+	{
+		dl->distance = 0.0;
+		dl->p1 = *p;
+		dl->p2 = *p;
+	}
+	
+	/*If the projection of point p on the segment is between A and B
+	then we find that "point on segment" and send it to rt_dist2d_pt_pt*/
+	c.x=A->x + r * (B->x-A->x);
+	c.y=A->y + r * (B->y-A->y);
+
+	return rt_dist2d_pt_pt(ctx, p,&c,dl);
+}
+
+
+/**
+
+Compares incomming points and
+stores the points closest to each other
+or most far away from each other
+depending on dl->mode (max or min)
+*/
+int
+rt_dist2d_pt_pt(const RTCTX *ctx, const RTPOINT2D *thep1, const RTPOINT2D *thep2, DISTPTS *dl)
+{
+	double hside = thep2->x - thep1->x;
+	double vside = thep2->y - thep1->y;
+	double dist = sqrt ( hside*hside + vside*vside );
+
+	if (((dl->distance - dist)*(dl->mode))>0) /*multiplication with mode to handle mindistance (mode=1)  and maxdistance (mode = (-1)*/
+	{
+		dl->distance = dist;
+
+		if (dl->twisted>0)	/*To get the points in right order. twisted is updated between 1 and (-1) every time the order is changed earlier in the chain*/
+		{
+			dl->p1 = *thep1;
+			dl->p2 = *thep2;
+		}
+		else
+		{
+			dl->p1 = *thep2;
+			dl->p2 = *thep1;
+		}
+	}
+	return RT_TRUE;
+}
+
+
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Functions in common for Brute force and new calculation
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/**
+The old function nessecary for ptarray_segmentize2d in ptarray.c
+*/
+double
+distance2d_pt_pt(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2)
+{
+	double hside = p2->x - p1->x;
+	double vside = p2->y - p1->y;
+
+	return sqrt ( hside*hside + vside*vside );
+
+}
+
+double
+distance2d_sqr_pt_pt(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2)
+{
+	double hside = p2->x - p1->x;
+	double vside = p2->y - p1->y;
+
+	return  hside*hside + vside*vside;
+
+}
+
+
+/**
+
+The old function nessecary for ptarray_segmentize2d in ptarray.c
+*/
+double
+distance2d_pt_seg(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINT2D *A, const RTPOINT2D *B)
+{
+	double	r,s;
+
+	/*if start==end, then use pt distance */
+	if (  ( A->x == B->x) && (A->y == B->y) )
+		return distance2d_pt_pt(ctx, p,A);
+
+	/*
+	 * otherwise, we use comp.graphics.algorithms
+	 * Frequently Asked Questions method
+	 *
+	 *  (1)     	      AC dot AB
+	        *         r = ---------
+	        *               ||AB||^2
+	 *	r has the following meaning:
+	 *	r=0 P = A
+	 *	r=1 P = B
+	 *	r<0 P is on the backward extension of AB
+	 *	r>1 P is on the forward extension of AB
+	 *	0<r<1 P is interior to AB
+	 */
+
+	r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	if (r<0) return distance2d_pt_pt(ctx, p,A);
+	if (r>1) return distance2d_pt_pt(ctx, p,B);
+
+
+	/*
+	 * (2)
+	 *	     (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
+	 *	s = -----------------------------
+	 *	             	L^2
+	 *
+	 *	Then the distance from C to P = |s|*L.
+	 *
+	 */
+
+	s = ( (A->y-p->y)*(B->x-A->x)- (A->x-p->x)*(B->y-A->y) ) /
+	    ( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	return FP_ABS(s) * sqrt(
+	           (B->x-A->x)*(B->x-A->x) + (B->y-A->y)*(B->y-A->y)
+	       );
+}
+
+/* return distance squared, useful to avoid sqrt calculations */
+double
+distance2d_sqr_pt_seg(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINT2D *A, const RTPOINT2D *B)
+{
+	double	r,s;
+
+	if (  ( A->x == B->x) && (A->y == B->y) )
+		return distance2d_sqr_pt_pt(ctx, p,A);
+
+	r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	if (r<0) return distance2d_sqr_pt_pt(ctx, p,A);
+	if (r>1) return distance2d_sqr_pt_pt(ctx, p,B);
+
+
+	/*
+	 * (2)
+	 *	     (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
+	 *	s = -----------------------------
+	 *	             	L^2
+	 *
+	 *	Then the distance from C to P = |s|*L.
+	 *
+	 */
+
+	s = ( (A->y-p->y)*(B->x-A->x)- (A->x-p->x)*(B->y-A->y) ) /
+	    ( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	return s * s * ( (B->x-A->x)*(B->x-A->x) + (B->y-A->y)*(B->y-A->y) );
+}
+
+
+
+/**
+ * Compute the azimuth of segment AB in radians.
+ * Return 0 on exception (same point), 1 otherwise.
+ */
+int
+azimuth_pt_pt(const RTCTX *ctx, const RTPOINT2D *A, const RTPOINT2D *B, double *d)
+{
+	if ( A->x == B->x )
+	{
+		if ( A->y < B->y ) *d=0.0;
+		else if ( A->y > B->y ) *d=M_PI;
+		else return 0;
+		return 1;
+	}
+
+	if ( A->y == B->y )
+	{
+		if ( A->x < B->x ) *d=M_PI/2;
+		else if ( A->x > B->x ) *d=M_PI+(M_PI/2);
+		else return 0;
+		return 1;
+	}
+
+	if ( A->x < B->x )
+	{
+		if ( A->y < B->y )
+		{
+			*d=atan(fabs(A->x - B->x) / fabs(A->y - B->y) );
+		}
+		else /* ( A->y > B->y )  - equality case handled above */
+		{
+			*d=atan(fabs(A->y - B->y) / fabs(A->x - B->x) )
+			   + (M_PI/2);
+		}
+	}
+
+	else /* ( A->x > B->x ) - equality case handled above */
+	{
+		if ( A->y > B->y )
+		{
+			*d=atan(fabs(A->x - B->x) / fabs(A->y - B->y) )
+			   + M_PI;
+		}
+		else /* ( A->y < B->y )  - equality case handled above */
+		{
+			*d=atan(fabs(A->y - B->y) / fabs(A->x - B->x) )
+			   + (M_PI+(M_PI/2));
+		}
+	}
+
+	return 1;
+}
+
+
diff --git a/src/measures.h b/src/measures.h
new file mode 100644
index 0000000..17cdab3
--- /dev/null
+++ b/src/measures.h
@@ -0,0 +1,129 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2010 Nicklas Avén
+ * Copyright 2010 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ * Copyright 2010 Nicklas Avén
+ *
+ * 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 "librtgeom_internal.h"
+
+/* for the measure functions*/
+#define DIST_MAX		-1
+#define DIST_MIN		1
+
+/** 
+* Structure used in distance-calculations
+*/
+typedef struct
+{
+	double distance;	/*the distance between p1 and p2*/
+	RTPOINT2D p1;
+	RTPOINT2D p2;
+	int mode;	/*the direction of looking, if thedir = -1 then we look for maxdistance and if it is 1 then we look for mindistance*/
+	int twisted; /*To preserve the order of incoming points to match the first and secon point in shortest and longest line*/
+	double tolerance; /*the tolerance for dwithin and dfullywithin*/
+} DISTPTS;
+
+typedef struct
+{
+	double themeasure;	/*a value calculated to compare distances*/
+	int pnr;	/*pointnumber. the ordernumber of the point*/
+} LISTSTRUCT;
+
+
+/*
+* Preprocessing functions
+*/
+int rt_dist2d_comp(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, DISTPTS *dl);
+int rt_dist2d_distribute_bruteforce(const RTCTX *ctx, const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS *dl);
+int rt_dist2d_recursive(const RTCTX *ctx, const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS *dl);
+int rt_dist2d_check_overlap(const RTCTX *ctx, RTGEOM *rtg1, RTGEOM *rtg2);
+int rt_dist2d_distribute_fast(const RTCTX *ctx, RTGEOM *rtg1, RTGEOM *rtg2, DISTPTS *dl);
+
+/*
+* Brute force functions
+*/
+int rt_dist2d_pt_ptarray(const RTCTX *ctx, const RTPOINT2D *p, RTPOINTARRAY *pa, DISTPTS *dl);
+int rt_dist2d_pt_ptarrayarc(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINTARRAY *pa, DISTPTS *dl);
+int rt_dist2d_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2, DISTPTS *dl);
+int rt_dist2d_ptarray_ptarrayarc(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINTARRAY *pb, DISTPTS *dl);
+int rt_dist2d_ptarrayarc_ptarrayarc(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINTARRAY *pb, DISTPTS *dl);
+int rt_dist2d_ptarray_poly(RTPOINTARRAY *pa, RTPOLY *poly, DISTPTS *dl);
+int rt_dist2d_point_point(const RTCTX *ctx, RTPOINT *point1, RTPOINT *point2, DISTPTS *dl);
+int rt_dist2d_point_line(const RTCTX *ctx, RTPOINT *point, RTLINE *line, DISTPTS *dl);
+int rt_dist2d_point_circstring(const RTCTX *ctx, RTPOINT *point, RTCIRCSTRING *circ, DISTPTS *dl);
+int rt_dist2d_point_poly(const RTCTX *ctx, RTPOINT *point, RTPOLY *poly, DISTPTS *dl);
+int rt_dist2d_point_curvepoly(const RTCTX *ctx, RTPOINT *point, RTCURVEPOLY *poly, DISTPTS *dl);
+int rt_dist2d_line_line(const RTCTX *ctx, RTLINE *line1, RTLINE *line2, DISTPTS *dl);
+int rt_dist2d_line_circstring(const RTCTX *ctx, RTLINE *line1, RTCIRCSTRING *line2, DISTPTS *dl);
+int rt_dist2d_line_poly(const RTCTX *ctx, RTLINE *line, RTPOLY *poly, DISTPTS *dl);
+int rt_dist2d_line_curvepoly(const RTCTX *ctx, RTLINE *line, RTCURVEPOLY *poly, DISTPTS *dl);
+int rt_dist2d_circstring_circstring(const RTCTX *ctx, RTCIRCSTRING *line1, RTCIRCSTRING *line2, DISTPTS *dl);
+int rt_dist2d_circstring_poly(const RTCTX *ctx, RTCIRCSTRING *circ, RTPOLY *poly, DISTPTS *dl);
+int rt_dist2d_circstring_curvepoly(const RTCTX *ctx, RTCIRCSTRING *circ, RTCURVEPOLY *poly, DISTPTS *dl);
+int rt_dist2d_poly_poly(const RTCTX *ctx, RTPOLY *poly1, RTPOLY *poly2, DISTPTS *dl);
+int rt_dist2d_poly_curvepoly(const RTCTX *ctx, RTPOLY *poly1, RTCURVEPOLY *curvepoly2, DISTPTS *dl);
+int rt_dist2d_curvepoly_curvepoly(const RTCTX *ctx, RTCURVEPOLY *poly1, RTCURVEPOLY *poly2, DISTPTS *dl);
+
+/*
+* New faster distance calculations
+*/
+int rt_dist2d_pre_seg_seg(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,LISTSTRUCT *list1, LISTSTRUCT *list2,double k, DISTPTS *dl);
+int rt_dist2d_selected_seg_seg(const RTCTX *ctx, const RTPOINT2D *A, const RTPOINT2D *B, const RTPOINT2D *C, const RTPOINT2D *D, DISTPTS *dl);
+int struct_cmp_by_measure(const void *a, const void *b);
+int rt_dist2d_fast_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1,RTPOINTARRAY *l2, DISTPTS *dl,  RTGBOX *box1, RTGBOX *box2);
+
+/*
+* Distance calculation primitives. 
+*/
+int rt_dist2d_pt_pt(const RTCTX *ctx, const RTPOINT2D *P,  const RTPOINT2D *Q,  DISTPTS *dl);
+int rt_dist2d_pt_seg(const RTCTX *ctx, const RTPOINT2D *P,  const RTPOINT2D *A1, const RTPOINT2D *A2, DISTPTS *dl);
+int rt_dist2d_pt_arc(const RTCTX *ctx, const RTPOINT2D *P,  const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, DISTPTS *dl);
+int rt_dist2d_seg_seg(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *B1, const RTPOINT2D *B2, DISTPTS *dl);
+int rt_dist2d_seg_arc(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *B1, const RTPOINT2D *B2, const RTPOINT2D *B3, DISTPTS *dl);
+int rt_dist2d_arc_arc(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, const RTPOINT2D *B1, const RTPOINT2D *B2, const RTPOINT2D* B3, DISTPTS *dl);
+void rt_dist2d_distpts_init(const RTCTX *ctx, DISTPTS *dl, int mode);
+
+/*
+* Length primitives
+*/
+double rt_arc_length(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3);
+
+/*
+* Geometry returning functions
+*/
+RTGEOM* rt_dist2d_distancepoint(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, int srid, int mode);
+RTGEOM* rt_dist2d_distanceline(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, int srid, int mode);
+
+
diff --git a/src/measures3d.c b/src/measures3d.c
new file mode 100644
index 0000000..cabdf39
--- /dev/null
+++ b/src/measures3d.c
@@ -0,0 +1,1367 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Nicklas Avén
+ * Copyright 2011 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ * Copyright 2011 Nicklas Avén
+ *
+ * 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 <string.h>
+#include <stdlib.h>
+
+#include "measures3d.h"
+#include "rtgeom_log.h"
+
+
+static inline int
+get_3dvector_from_points(const RTCTX *ctx, RTPOINT3DZ *p1,RTPOINT3DZ *p2, VECTOR3D *v)
+{
+	v->x=p2->x-p1->x;
+	v->y=p2->y-p1->y;
+	v->z=p2->z-p1->z;
+	
+	return RT_TRUE;
+}
+
+static inline int
+get_3dcross_product(const RTCTX *ctx, VECTOR3D *v1,VECTOR3D *v2, VECTOR3D *v)
+{
+	v->x=(v1->y*v2->z)-(v1->z*v2->y);
+	v->y=(v1->z*v2->x)-(v1->x*v2->z);
+	v->z=(v1->x*v2->y)-(v1->y*v2->x);
+
+	return RT_TRUE;
+}
+
+
+/**
+This function is used to create a vertical line used for cases where one if the 
+geometries lacks z-values. The vertical line crosses the 2d point that is closest 
+and the z-range is from maxz to minz in the geoemtrie that has z values.
+*/
+static 
+RTGEOM* create_v_line(const RTCTX *ctx, const RTGEOM *rtgeom,double x, double y, int srid)
+{
+	
+	RTPOINT *rtpoints[2];
+	RTGBOX gbox;
+	int rv = rtgeom_calculate_gbox(ctx, rtgeom, &gbox);
+	
+	if ( rv == RT_FAILURE )
+		return NULL;
+	
+	rtpoints[0] = rtpoint_make3dz(ctx, srid, x, y, gbox.zmin);
+	rtpoints[1] = rtpoint_make3dz(ctx, srid, x, y, gbox.zmax);
+	
+	 return (RTGEOM *)rtline_from_ptarray(ctx, srid, 2, rtpoints);		
+}
+
+RTGEOM * 
+rtgeom_closest_line_3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	return rt_dist3d_distanceline(ctx, rt1, rt2, rt1->srid, DIST_MIN);
+}
+
+RTGEOM * 
+rtgeom_furthest_line_3d(const RTCTX *ctx, RTGEOM *rt1, RTGEOM *rt2)
+{
+	return rt_dist3d_distanceline(ctx, rt1, rt2, rt1->srid, DIST_MAX);
+}
+
+RTGEOM * 
+rtgeom_closest_point_3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	return rt_dist3d_distancepoint(ctx, rt1, rt2, rt1->srid, DIST_MIN);
+}
+
+
+/**
+Function initializing 3dshortestline and 3dlongestline calculations.
+*/
+RTGEOM *
+rt_dist3d_distanceline(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, int srid, int mode)
+{
+	RTDEBUG(2, "rt_dist3d_distanceline is called");
+	double x1,x2,y1,y2, z1, z2, x, y;
+	double initdistance = ( mode == DIST_MIN ? FLT_MAX : -1.0);
+	DISTPTS3D thedl;
+	RTPOINT *rtpoints[2];
+	RTGEOM *result;
+
+	thedl.mode = mode;
+	thedl.distance = initdistance;
+	thedl.tolerance = 0.0;
+
+	/*Check if we really have 3D geoemtries*/
+	/*If not, send it to 2D-calculations which will give the same result*/
+	/*as an infinite z-value at one or two of the geometries*/
+	if(!rtgeom_has_z(ctx, rt1) || !rtgeom_has_z(ctx, rt2))
+	{
+		
+		rtnotice(ctx, "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\"");
+		
+		if(!rtgeom_has_z(ctx, rt1) && !rtgeom_has_z(ctx, rt2))
+			return rt_dist2d_distanceline(ctx, rt1, rt2, srid, mode);	
+		
+		DISTPTS thedl2d;
+		thedl2d.mode = mode;
+		thedl2d.distance = initdistance;
+		thedl2d.tolerance = 0.0;
+		if (!rt_dist2d_comp(ctx,  rt1,rt2,&thedl2d))
+		{
+			/*should never get here. all cases ought to be error handled earlier*/
+			rterror(ctx, "Some unspecified error.");
+			result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+		}
+		RTGEOM *vertical_line;
+		if(!rtgeom_has_z(ctx, rt1))
+		{
+			x=thedl2d.p1.x;
+			y=thedl2d.p1.y;
+
+			vertical_line = create_v_line(ctx, rt2,x,y,srid);
+			if (!rt_dist3d_recursive(ctx, vertical_line, rt2, &thedl))
+			{
+				/*should never get here. all cases ought to be error handled earlier*/
+				rtfree(ctx, vertical_line);
+				rterror(ctx, "Some unspecified error.");
+				result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+			}			
+			rtfree(ctx, vertical_line);	
+		}	
+		if(!rtgeom_has_z(ctx, rt2))
+		{
+			x=thedl2d.p2.x;
+			y=thedl2d.p2.y;			
+			
+			vertical_line = create_v_line(ctx, rt1,x,y,srid);
+			if (!rt_dist3d_recursive(ctx, rt1, vertical_line, &thedl))
+			{
+				/*should never get here. all cases ought to be error handled earlier*/
+				rtfree(ctx, vertical_line);
+				rterror(ctx, "Some unspecified error.");
+				return (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+			}	
+			rtfree(ctx, vertical_line);		
+		}			
+				
+	}
+	else
+	{		
+		if (!rt_dist3d_recursive(ctx, rt1, rt2, &thedl))
+		{
+			/*should never get here. all cases ought to be error handled earlier*/
+			rterror(ctx, "Some unspecified error.");
+			result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+		}
+	}
+	/*if thedl.distance is unchanged there where only empty geometries input*/
+	if (thedl.distance == initdistance)
+	{
+		RTDEBUG(3, "didn't find geometries to measure between, returning null");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+	else
+	{
+		x1=thedl.p1.x;
+		y1=thedl.p1.y;
+		z1=thedl.p1.z;
+		x2=thedl.p2.x;
+		y2=thedl.p2.y;
+		z2=thedl.p2.z;
+
+		rtpoints[0] = rtpoint_make3dz(ctx, srid, x1, y1, z1);
+		rtpoints[1] = rtpoint_make3dz(ctx, srid, x2, y2, z2);
+
+		result = (RTGEOM *)rtline_from_ptarray(ctx, srid, 2, rtpoints);
+	}
+
+	return result;
+}
+
+/**
+Function initializing 3dclosestpoint calculations.
+*/
+RTGEOM *
+rt_dist3d_distancepoint(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, int srid, int mode)
+{
+	
+	double x,y,z;
+	DISTPTS3D thedl;
+	double initdistance = FLT_MAX;
+	RTGEOM *result;
+
+	thedl.mode = mode;
+	thedl.distance= initdistance;
+	thedl.tolerance = 0;
+
+	RTDEBUG(2, "rt_dist3d_distancepoint is called");
+	
+	/*Check if we really have 3D geoemtries*/
+	/*If not, send it to 2D-calculations which will give the same result*/
+	/*as an infinite z-value at one or two of the geometries*/
+	if(!rtgeom_has_z(ctx, rt1) || !rtgeom_has_z(ctx, rt2))
+	{		
+		rtnotice(ctx, "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\"");
+				
+		if(!rtgeom_has_z(ctx, rt1) && !rtgeom_has_z(ctx, rt2))
+			return rt_dist2d_distancepoint(ctx, rt1, rt2, srid, mode);
+			
+		
+		DISTPTS thedl2d;
+		thedl2d.mode = mode;
+		thedl2d.distance = initdistance;
+		thedl2d.tolerance = 0.0;
+		if (!rt_dist2d_comp(ctx,  rt1,rt2,&thedl2d))
+		{
+			/*should never get here. all cases ought to be error handled earlier*/
+			rterror(ctx, "Some unspecified error.");
+			return (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+		}
+		
+		RTGEOM *vertical_line;
+		if(!rtgeom_has_z(ctx, rt1))
+		{
+			x=thedl2d.p1.x;
+			y=thedl2d.p1.y;
+			
+			vertical_line = create_v_line(ctx, rt2,x,y,srid);	
+			if (!rt_dist3d_recursive(ctx, vertical_line, rt2, &thedl))
+			{
+				/*should never get here. all cases ought to be error handled earlier*/
+				rtfree(ctx, vertical_line);	
+				rterror(ctx, "Some unspecified error.");
+				return (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+			}		
+			rtfree(ctx, vertical_line);		
+		}	
+				
+		if(!rtgeom_has_z(ctx, rt2))
+		{
+			x=thedl2d.p2.x;
+			y=thedl2d.p2.y;
+
+			vertical_line = create_v_line(ctx, rt1,x,y,srid);
+			if (!rt_dist3d_recursive(ctx, rt1, vertical_line, &thedl))
+			{
+				/*should never get here. all cases ought to be error handled earlier*/
+				rtfree(ctx, vertical_line);	
+				rterror(ctx, "Some unspecified error.");
+				result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+			}	
+			rtfree(ctx, vertical_line);			
+		}	
+		
+	}
+	else
+	{
+		if (!rt_dist3d_recursive(ctx, rt1, rt2, &thedl))
+		{
+			/*should never get here. all cases ought to be error handled earlier*/
+			rterror(ctx, "Some unspecified error.");
+			result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+		}
+	}
+	if (thedl.distance == initdistance)
+	{
+		RTDEBUG(3, "didn't find geometries to measure between, returning null");
+		result = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, srid, 0, 0);
+	}
+	else
+	{
+		x=thedl.p1.x;
+		y=thedl.p1.y;
+		z=thedl.p1.z;
+		result = (RTGEOM *)rtpoint_make3dz(ctx, srid, x, y, z);
+	}
+
+	return result;
+}
+
+
+/**
+Function initializing 3d max distance calculation
+*/
+double
+rtgeom_maxdistance3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	RTDEBUG(2, "rtgeom_maxdistance3d is called");
+
+	return rtgeom_maxdistance3d_tolerance(ctx,  rt1, rt2, 0.0 );
+}
+
+/**
+Function handling 3d max distance calculations and dfullywithin calculations.
+The difference is just the tolerance.
+*/
+double
+rtgeom_maxdistance3d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance)
+{
+	if(!rtgeom_has_z(ctx, rt1) || !rtgeom_has_z(ctx, rt2))
+	{
+		rtnotice(ctx, "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\"");
+		return rtgeom_maxdistance2d_tolerance(ctx, rt1, rt2, tolerance);	
+	}
+	/*double thedist;*/
+	DISTPTS3D thedl;
+	RTDEBUG(2, "rtgeom_maxdistance3d_tolerance is called");
+	thedl.mode = DIST_MAX;
+	thedl.distance= -1;
+	thedl.tolerance = tolerance;
+	if (rt_dist3d_recursive(ctx, rt1, rt2, &thedl))
+	{
+		return thedl.distance;
+	}
+	/*should never get here. all cases ought to be error handled earlier*/
+	rterror(ctx, "Some unspecified error.");
+	return -1;
+}
+
+/**
+	Function initializing 3d min distance calculation
+*/
+double
+rtgeom_mindistance3d(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2)
+{
+	RTDEBUG(2, "rtgeom_mindistance3d is called");
+	return rtgeom_mindistance3d_tolerance(ctx,  rt1, rt2, 0.0 );
+}
+
+/**
+	Function handling 3d min distance calculations and dwithin calculations.
+	The difference is just the tolerance.
+*/
+double
+rtgeom_mindistance3d_tolerance(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2, double tolerance)
+{
+	if(!rtgeom_has_z(ctx, rt1) || !rtgeom_has_z(ctx, rt2))
+	{
+		rtnotice(ctx, "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\"");
+		
+		return rtgeom_mindistance2d_tolerance(ctx, rt1, rt2, tolerance);	
+	}
+	DISTPTS3D thedl;
+	RTDEBUG(2, "rtgeom_mindistance3d_tolerance is called");
+	thedl.mode = DIST_MIN;
+	thedl.distance= FLT_MAX;
+	thedl.tolerance = tolerance;
+	if (rt_dist3d_recursive(ctx, rt1, rt2, &thedl))
+	{
+		return thedl.distance;
+	}
+	/*should never get here. all cases ought to be error handled earlier*/
+	rterror(ctx, "Some unspecified error.");
+	return FLT_MAX;
+}
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Initializing functions
+--------------------------------------------------------------------------------------------------------------*/
+
+/*------------------------------------------------------------------------------------------------------------
+Preprocessing functions
+Functions preparing geometries for distance-calculations
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/**
+This is a recursive function delivering every possible combination of subgeometries
+*/
+int rt_dist3d_recursive(const RTCTX *ctx, const RTGEOM *rtg1,const RTGEOM *rtg2, DISTPTS3D *dl)
+{
+	int i, j;
+	int n1=1;
+	int n2=1;
+	RTGEOM *g1 = NULL;
+	RTGEOM *g2 = NULL;
+	RTCOLLECTION *c1 = NULL;
+	RTCOLLECTION *c2 = NULL;
+
+	RTDEBUGF(2, "rt_dist3d_recursive is called with type1=%d, type2=%d", rtg1->type, rtg2->type);
+
+	if (rtgeom_is_collection(ctx, rtg1))
+	{
+		RTDEBUG(3, "First geometry is collection");
+		c1 = rtgeom_as_rtcollection(ctx, rtg1);
+		n1 = c1->ngeoms;
+	}
+	if (rtgeom_is_collection(ctx, rtg2))
+	{
+		RTDEBUG(3, "Second geometry is collection");
+		c2 = rtgeom_as_rtcollection(ctx, rtg2);
+		n2 = c2->ngeoms;
+	}
+
+	for ( i = 0; i < n1; i++ )
+	{
+
+		if (rtgeom_is_collection(ctx, rtg1))
+		{
+			g1 = c1->geoms[i];
+		}
+		else
+		{
+			g1 = (RTGEOM*)rtg1;
+		}
+
+		if (rtgeom_is_empty(ctx, g1)) return RT_TRUE;
+
+		if (rtgeom_is_collection(ctx, g1))
+		{
+			RTDEBUG(3, "Found collection inside first geometry collection, recursing");
+			if (!rt_dist3d_recursive(ctx, g1, rtg2, dl)) return RT_FALSE;
+			continue;
+		}
+		for ( j = 0; j < n2; j++ )
+		{
+			if (rtgeom_is_collection(ctx, rtg2))
+			{
+				g2 = c2->geoms[j];
+			}
+			else
+			{
+				g2 = (RTGEOM*)rtg2;
+			}
+			if (rtgeom_is_collection(ctx, g2))
+			{
+				RTDEBUG(3, "Found collection inside second geometry collection, recursing");
+				if (!rt_dist3d_recursive(ctx, g1, g2, dl)) return RT_FALSE;
+				continue;
+			}
+
+
+			/*If one of geometries is empty, return. True here only means continue searching. False would have stoped the process*/
+			if (rtgeom_is_empty(ctx, g1)||rtgeom_is_empty(ctx, g2)) return RT_TRUE;
+
+
+			if (!rt_dist3d_distribute_bruteforce(ctx, g1, g2, dl)) return RT_FALSE;
+			if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/
+		}
+	}
+	return RT_TRUE;
+}
+
+
+
+/**
+
+This function distributes the brute-force for 3D so far the only type, tasks depending on type
+*/
+int
+rt_dist3d_distribute_bruteforce(const RTCTX *ctx, const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS3D *dl)
+{
+
+	int	t1 = rtg1->type;
+	int	t2 = rtg2->type;
+
+	RTDEBUGF(2, "rt_dist3d_distribute_bruteforce is called with typ1=%d, type2=%d", rtg1->type, rtg2->type);
+
+	if  ( t1 == RTPOINTTYPE )
+	{
+		if  ( t2 == RTPOINTTYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_point_point(ctx, (RTPOINT *)rtg1, (RTPOINT *)rtg2, dl);
+		}
+		else if  ( t2 == RTLINETYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_point_line(ctx, (RTPOINT *)rtg1, (RTLINE *)rtg2, dl);
+		}
+		else if  ( t2 == RTPOLYGONTYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_point_poly(ctx, (RTPOINT *)rtg1, (RTPOLY *)rtg2,dl);
+		}
+		else
+		{
+			rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			return RT_FALSE;
+		}
+	}
+	else if ( t1 == RTLINETYPE )
+	{
+		if ( t2 == RTPOINTTYPE )
+		{
+			dl->twisted=(-1);
+			return rt_dist3d_point_line(ctx, (RTPOINT *)rtg2,(RTLINE *)rtg1,dl);
+		}
+		else if ( t2 == RTLINETYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_line_line(ctx, (RTLINE *)rtg1,(RTLINE *)rtg2,dl);
+		}
+		else if ( t2 == RTPOLYGONTYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_line_poly(ctx, (RTLINE *)rtg1,(RTPOLY *)rtg2,dl);
+		}
+		else
+		{
+			rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			return RT_FALSE;
+		}
+	}
+	else if ( t1 == RTPOLYGONTYPE )
+	{
+		if ( t2 == RTPOLYGONTYPE )
+		{
+			dl->twisted=1;
+			return rt_dist3d_poly_poly(ctx, (RTPOLY *)rtg1, (RTPOLY *)rtg2,dl);
+		}
+		else if ( t2 == RTPOINTTYPE )
+		{
+			dl->twisted=-1;
+			return rt_dist3d_point_poly(ctx, (RTPOINT *)rtg2, (RTPOLY *)rtg1,dl);
+		}
+		else if ( t2 == RTLINETYPE )
+		{
+			dl->twisted=-1;
+			return rt_dist3d_line_poly(ctx, (RTLINE *)rtg2,(RTPOLY *)rtg1,dl);
+		}
+		else
+		{
+			rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t2));
+			return RT_FALSE;
+		}
+	}
+	else
+	{
+		rterror(ctx, "Unsupported geometry type: %s", rttype_name(ctx, t1));
+		return RT_FALSE;
+	}
+	/*You shouldn't being able to get here*/
+	rterror(ctx, "unspecified error in function rt_dist3d_distribute_bruteforce");
+	return RT_FALSE;
+}
+
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Preprocessing functions
+--------------------------------------------------------------------------------------------------------------*/
+
+
+/*------------------------------------------------------------------------------------------------------------
+Brute force functions
+So far the only way to do 3D-calculations
+--------------------------------------------------------------------------------------------------------------*/
+
+/**
+
+point to point calculation
+*/
+int
+rt_dist3d_point_point(const RTCTX *ctx, RTPOINT *point1, RTPOINT *point2, DISTPTS3D *dl)
+{
+	RTPOINT3DZ p1;
+	RTPOINT3DZ p2;
+	RTDEBUG(2, "rt_dist3d_point_point is called");
+
+	rt_getPoint3dz_p(ctx, point1->point, 0, &p1);
+	rt_getPoint3dz_p(ctx, point2->point, 0, &p2);
+
+	return rt_dist3d_pt_pt(ctx, &p1, &p2,dl);
+}
+/**
+
+point to line calculation
+*/
+int
+rt_dist3d_point_line(const RTCTX *ctx, RTPOINT *point, RTLINE *line, DISTPTS3D *dl)
+{
+	RTPOINT3DZ p;
+	RTPOINTARRAY *pa = line->points;
+	RTDEBUG(2, "rt_dist3d_point_line is called");
+
+	rt_getPoint3dz_p(ctx, point->point, 0, &p);
+	return rt_dist3d_pt_ptarray(ctx, &p, pa, dl);
+}
+
+/**
+
+Computes point to polygon distance
+For mindistance that means:
+1)find the plane of the polygon 
+2)projecting the point to the plane of the polygon 
+3)finding if that projected point is inside the polygon, if so the distance is measured to that projected point
+4) if not in polygon above, check the distance against the boundary of the polygon
+for max distance it is artays point against boundary
+
+*/
+int
+rt_dist3d_point_poly(const RTCTX *ctx, RTPOINT *point, RTPOLY *poly, DISTPTS3D *dl)
+{
+	RTPOINT3DZ p, projp;/*projp is "point projected on plane"*/
+	PLANE3D plane;
+	RTDEBUG(2, "rt_dist3d_point_poly is called");
+	rt_getPoint3dz_p(ctx, point->point, 0, &p);
+	
+	/*If we are lookig for max distance, longestline or dfullywithin*/
+	if (dl->mode == DIST_MAX)
+	{
+		RTDEBUG(3, "looking for maxdistance");
+		return rt_dist3d_pt_ptarray(ctx, &p, poly->rings[0], dl);
+	}
+	
+	/*Find the plane of the polygon, the "holes" have to be on the same plane. so we only care about the boudary*/
+	if(!define_plane(ctx, poly->rings[0], &plane))
+		return RT_FALSE;
+	
+	/*get our point projected on the plane of the polygon*/
+	project_point_on_plane(ctx, &p, &plane, &projp);
+	
+	return rt_dist3d_pt_poly(ctx, &p, poly,&plane, &projp, dl);
+}
+
+
+/**
+
+line to line calculation
+*/
+int
+rt_dist3d_line_line(const RTCTX *ctx, RTLINE *line1, RTLINE *line2, DISTPTS3D *dl)
+{
+	RTPOINTARRAY *pa1 = line1->points;
+	RTPOINTARRAY *pa2 = line2->points;
+	RTDEBUG(2, "rt_dist3d_line_line is called");
+
+	return rt_dist3d_ptarray_ptarray(ctx, pa1, pa2, dl);
+}
+
+/**
+
+line to polygon calculation
+*/
+int rt_dist3d_line_poly(const RTCTX *ctx, RTLINE *line, RTPOLY *poly, DISTPTS3D *dl)
+{
+	PLANE3D plane;	
+	RTDEBUG(2, "rt_dist3d_line_poly is called");	
+		
+	if (dl->mode == DIST_MAX)
+	{
+		return rt_dist3d_ptarray_ptarray(ctx, line->points, poly->rings[0], dl);
+	}
+	
+	if(!define_plane(ctx, poly->rings[0], &plane))
+		return RT_FALSE;
+	
+	return rt_dist3d_ptarray_poly(ctx, line->points, poly,&plane, dl);
+}
+
+/**
+
+polygon to polygon calculation
+*/
+int rt_dist3d_poly_poly(const RTCTX *ctx, RTPOLY *poly1, RTPOLY *poly2, DISTPTS3D *dl)
+{		
+	PLANE3D plane;		
+	RTDEBUG(2, "rt_dist3d_poly_poly is called");
+	if (dl->mode == DIST_MAX)
+	{
+		return rt_dist3d_ptarray_ptarray(ctx, poly1->rings[0], poly2->rings[0], dl);
+	}
+	
+	if(!define_plane(ctx, poly2->rings[0], &plane))
+		return RT_FALSE;
+	
+	/*What we do here is to compare the bondary of one polygon with the other polygon 
+	and then take the second boudary comparing with the first polygon*/
+	dl->twisted=1;
+	if(!rt_dist3d_ptarray_poly(ctx, poly1->rings[0], poly2,&plane, dl))
+		return RT_FALSE;
+	if(dl->distance==0.0) /*Just check if the answer already is given*/
+		return RT_TRUE;
+	
+	if(!define_plane(ctx, poly1->rings[0], &plane))
+		return RT_FALSE;
+	dl->twisted=-1; /*because we swithc the order of geometries we swithch "twisted" to -1 which will give the right order of points in shortest line.*/
+	return rt_dist3d_ptarray_poly(ctx, poly2->rings[0], poly1,&plane, dl);
+}
+
+/**
+
+ * search all the segments of pointarray to see which one is closest to p
+ * Returns distance between point and pointarray
+ */
+int
+rt_dist3d_pt_ptarray(const RTCTX *ctx, RTPOINT3DZ *p, RTPOINTARRAY *pa,DISTPTS3D *dl)
+{
+	int t;
+	RTPOINT3DZ	start, end;
+	int twist = dl->twisted;
+
+	RTDEBUG(2, "rt_dist3d_pt_ptarray is called");
+
+	rt_getPoint3dz_p(ctx, pa, 0, &start);
+
+	for (t=1; t<pa->npoints; t++)
+	{
+		dl->twisted=twist;
+		rt_getPoint3dz_p(ctx, pa, t, &end);
+		if (!rt_dist3d_pt_seg(ctx, p, &start, &end,dl)) return RT_FALSE;
+
+		if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/
+		start = end;
+	}
+
+	return RT_TRUE;
+}
+
+
+/**
+
+If searching for min distance, this one finds the closest point on segment A-B from p.
+if searching for max distance it just sends p-A and p-B to pt-pt calculation
+*/
+int
+rt_dist3d_pt_seg(const RTCTX *ctx, RTPOINT3DZ *p, RTPOINT3DZ *A, RTPOINT3DZ *B, DISTPTS3D *dl)
+{
+	RTPOINT3DZ c;
+	double	r;
+	/*if start==end, then use pt distance */
+	if (  ( A->x == B->x) && (A->y == B->y) && (A->z == B->z)  )
+	{
+		return rt_dist3d_pt_pt(ctx, p,A,dl);
+	}
+
+
+	r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y)  + ( p->z-A->z) * (B->z-A->z) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y)+(B->z-A->z)*(B->z-A->z) );
+
+	/*This is for finding the 3Dmaxdistance.
+	the maxdistance have to be between two vertexes,
+	compared to mindistance which can be between
+	tvo vertexes vertex.*/
+	if (dl->mode == DIST_MAX)
+	{
+		if (r>=0.5)
+		{
+			return rt_dist3d_pt_pt(ctx, p,A,dl);
+		}
+		if (r<0.5)
+		{
+			return rt_dist3d_pt_pt(ctx, p,B,dl);
+		}
+	}
+
+	if (r<0)	/*If the first vertex A is closest to the point p*/
+	{
+		return rt_dist3d_pt_pt(ctx, p,A,dl);
+	}
+	if (r>1)	/*If the second vertex B is closest to the point p*/
+	{
+		return rt_dist3d_pt_pt(ctx, p,B,dl);
+	}
+
+	/*else if the point p is closer to some point between a and b
+	then we find that point and send it to rt_dist3d_pt_pt*/
+	c.x=A->x + r * (B->x-A->x);
+	c.y=A->y + r * (B->y-A->y);
+	c.z=A->z + r * (B->z-A->z);
+
+	return rt_dist3d_pt_pt(ctx, p,&c,dl);
+}
+
+double
+distance3d_pt_pt(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2)
+{
+  double dx = p2->x - p1->x;
+  double dy = p2->y - p1->y;
+  double dz = p2->z - p1->z;
+  return sqrt ( dx*dx + dy*dy + dz*dz);
+}
+
+
+/**
+
+Compares incomming points and
+stores the points closest to each other
+or most far away from each other
+depending on dl->mode (max or min)
+*/
+int
+rt_dist3d_pt_pt(const RTCTX *ctx, RTPOINT3DZ *thep1, RTPOINT3DZ *thep2,DISTPTS3D *dl)
+{
+	double dx = thep2->x - thep1->x;
+	double dy = thep2->y - thep1->y;
+	double dz = thep2->z - thep1->z;
+	double dist = sqrt ( dx*dx + dy*dy + dz*dz);
+	RTDEBUGF(2, "rt_dist3d_pt_pt called (with points: p1.x=%f, p1.y=%f,p1.z=%f,p2.x=%f, p2.y=%f,p2.z=%f)",thep1->x,thep1->y,thep1->z,thep2->x,thep2->y,thep2->z );
+
+	if (((dl->distance - dist)*(dl->mode))>0) /*multiplication with mode to handle mindistance (mode=1)  and maxdistance (mode = (-1)*/
+	{
+		dl->distance = dist;
+
+		if (dl->twisted>0)	/*To get the points in right order. twisted is updated between 1 and (-1) every time the order is changed earlier in the chain*/
+		{
+			dl->p1 = *thep1;
+			dl->p2 = *thep2;
+		}
+		else
+		{
+			dl->p1 = *thep2;
+			dl->p2 = *thep1;
+		}
+	}
+	return RT_TRUE;
+}
+
+
+/**
+
+Finds all combinationes of segments between two pointarrays
+*/
+int
+rt_dist3d_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,DISTPTS3D *dl)
+{
+	int t,u;
+	RTPOINT3DZ	start, end;
+	RTPOINT3DZ	start2, end2;
+	int twist = dl->twisted;
+	RTDEBUGF(2, "rt_dist3d_ptarray_ptarray called (points: %d-%d)",l1->npoints, l2->npoints);
+
+
+
+	if (dl->mode == DIST_MAX)/*If we are searching for maxdistance we go straight to point-point calculation since the maxdistance have to be between two vertexes*/
+	{
+		for (t=0; t<l1->npoints; t++) /*for each segment in L1 */
+		{
+			rt_getPoint3dz_p(ctx, l1, t, &start);
+			for (u=0; u<l2->npoints; u++) /*for each segment in L2 */
+			{
+				rt_getPoint3dz_p(ctx, l2, u, &start2);
+				rt_dist3d_pt_pt(ctx, &start,&start2,dl);
+				RTDEBUGF(4, "maxdist_ptarray_ptarray; seg %i * seg %i, dist = %g\n",t,u,dl->distance);
+				RTDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f",
+				         t, u, dl->distance, dl->tolerance);
+			}
+		}
+	}
+	else
+	{
+		rt_getPoint3dz_p(ctx, l1, 0, &start);
+		for (t=1; t<l1->npoints; t++) /*for each segment in L1 */
+		{
+			rt_getPoint3dz_p(ctx, l1, t, &end);
+			rt_getPoint3dz_p(ctx, l2, 0, &start2);
+			for (u=1; u<l2->npoints; u++) /*for each segment in L2 */
+			{
+				rt_getPoint3dz_p(ctx, l2, u, &end2);
+				dl->twisted=twist;
+				rt_dist3d_seg_seg(ctx, &start, &end, &start2, &end2,dl);
+				RTDEBUGF(4, "mindist_ptarray_ptarray; seg %i * seg %i, dist = %g\n",t,u,dl->distance);
+				RTDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f",
+				         t, u, dl->distance, dl->tolerance);
+				if (dl->distance<=dl->tolerance && dl->mode == DIST_MIN) return RT_TRUE; /*just a check if  the answer is already given*/
+				start2 = end2;
+			}
+			start = end;
+		}
+	}
+	return RT_TRUE;
+}
+
+/**
+
+Finds the two closest points on two linesegments
+*/
+int 
+rt_dist3d_seg_seg(const RTCTX *ctx, RTPOINT3DZ *s1p1, RTPOINT3DZ *s1p2, RTPOINT3DZ *s2p1, RTPOINT3DZ *s2p2, DISTPTS3D *dl)
+{
+	VECTOR3D v1, v2, vl;
+	double s1k, s2k; /*two variables representing where on Line 1 (s1k) and where on Line 2 (s2k) a connecting line between the two lines is perpendicular to both lines*/
+	RTPOINT3DZ p1, p2;
+	double a, b, c, d, e, D;
+			
+	/*s1p1 and s1p2 are the same point */
+	if (  ( s1p1->x == s1p2->x) && (s1p1->y == s1p2->y) && (s1p1->z == s1p2->z) )
+	{
+		return rt_dist3d_pt_seg(ctx, s1p1,s2p1,s2p2,dl);
+	}
+	/*s2p1 and s2p2 are the same point */
+	if (  ( s2p1->x == s2p2->x) && (s2p1->y == s2p2->y) && (s2p1->z == s2p2->z) )
+	{
+		dl->twisted= ((dl->twisted) * (-1));
+		return rt_dist3d_pt_seg(ctx, s2p1,s1p1,s1p2,dl);
+	}
+		
+/*
+	Here we use algorithm from softsurfer.com
+	that can be found here
+	http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
+*/
+	
+	if (!get_3dvector_from_points(ctx, s1p1, s1p2, &v1))
+		return RT_FALSE;	
+
+	if (!get_3dvector_from_points(ctx, s2p1, s2p2, &v2))
+		return RT_FALSE;	
+
+	if (!get_3dvector_from_points(ctx, s2p1, s1p1, &vl))
+		return RT_FALSE;	
+
+	a = DOT(v1,v1);
+	b = DOT(v1,v2);
+	c = DOT(v2,v2);
+	d = DOT(v1,vl);
+	e = DOT(v2,vl);
+	D = a*c - b*b; 
+
+
+	if (D <0.000000001) 
+	{        /* the lines are almost parallel*/
+		s1k = 0.0; /*If the lines are paralell we try by using the startpoint of first segment. If that gives a projected point on the second line outside segment 2 it wil be found that s2k is >1 or <0.*/
+		if(b>c)   /* use the largest denominator*/
+		{
+			s2k=d/b;
+		}
+		else
+		{
+			s2k =e/c;
+		}
+	}
+	else 
+	{
+		s1k = (b*e - c*d) / D;
+		s2k = (a*e - b*d) / D;
+	}
+
+	/* Now we check if the projected closest point on the infinite lines is outside our segments. If so the combinations with start and end points will be tested*/
+	if(s1k<0.0||s1k>1.0||s2k<0.0||s2k>1.0)
+	{
+		if(s1k<0.0) 
+		{
+
+			if (!rt_dist3d_pt_seg(ctx, s1p1, s2p1, s2p2, dl))
+			{
+				return RT_FALSE;
+			}
+		}
+		if(s1k>1.0)
+		{
+
+			if (!rt_dist3d_pt_seg(ctx, s1p2, s2p1, s2p2, dl))
+			{
+				return RT_FALSE;
+			}
+		}
+		if(s2k<0.0)
+		{
+			dl->twisted= ((dl->twisted) * (-1));
+			if (!rt_dist3d_pt_seg(ctx, s2p1, s1p1, s1p2, dl))
+			{
+				return RT_FALSE;
+			}
+		}
+		if(s2k>1.0)
+		{
+			dl->twisted= ((dl->twisted) * (-1));
+			if (!rt_dist3d_pt_seg(ctx, s2p2, s1p1, s1p2, dl))
+			{
+				return RT_FALSE;
+			}
+		}
+	}
+	else
+	{/*Find the closest point on the edges of both segments*/
+		p1.x=s1p1->x+s1k*(s1p2->x-s1p1->x);
+		p1.y=s1p1->y+s1k*(s1p2->y-s1p1->y);
+		p1.z=s1p1->z+s1k*(s1p2->z-s1p1->z);
+
+		p2.x=s2p1->x+s2k*(s2p2->x-s2p1->x);
+		p2.y=s2p1->y+s2k*(s2p2->y-s2p1->y);
+		p2.z=s2p1->z+s2k*(s2p2->z-s2p1->z);
+
+		if (!rt_dist3d_pt_pt(ctx, &p1,&p2,dl))/* Send the closest points to point-point calculation*/
+		{
+			return RT_FALSE;
+		}
+	}
+	return RT_TRUE;
+}
+
+/**
+
+Checking if the point projected on the plane of the polygon actually is inside that polygon. 
+If so the mindistance is between that projected point and our original point.
+If not we check from original point to the bounadary.
+If the projected point is inside a hole of the polygon we check the distance to the boudary of that hole.
+*/
+int
+rt_dist3d_pt_poly(const RTCTX *ctx, RTPOINT3DZ *p, RTPOLY *poly, PLANE3D *plane,RTPOINT3DZ *projp, DISTPTS3D *dl)
+{	
+	int i;
+	
+	RTDEBUG(2, "rt_dist3d_point_poly called");
+
+	
+	if(pt_in_ring_3d(ctx, projp, poly->rings[0], plane))
+	{
+		for (i=1; i<poly->nrings; i++)
+		{
+			/* Inside a hole. Distance = pt -> ring */
+			if ( pt_in_ring_3d(ctx, projp, poly->rings[i], plane ))
+			{
+				RTDEBUG(3, " inside an hole");
+				return rt_dist3d_pt_ptarray(ctx, p, poly->rings[i], dl);
+			}
+		}		
+		
+		return rt_dist3d_pt_pt(ctx, p,projp,dl);/* If the projected point is inside the polygon the shortest distance is between that point and the inputed point*/
+	}
+	else
+	{
+		return rt_dist3d_pt_ptarray(ctx, p, poly->rings[0], dl); /*If the projected point is outside the polygon we search for the closest distance against the boundarry instead*/
+	}	
+	
+	return RT_TRUE;
+	
+}
+
+/**
+
+Computes pointarray to polygon distance
+*/
+int rt_dist3d_ptarray_poly(const RTCTX *ctx, RTPOINTARRAY *pa, RTPOLY *poly,PLANE3D *plane, DISTPTS3D *dl)
+{
+	
+
+	int i,j,k;
+	double f, s1, s2;
+	VECTOR3D projp1_projp2;
+	RTPOINT3DZ p1, p2,projp1, projp2, intersectionp;
+	
+	rt_getPoint3dz_p(ctx, pa, 0, &p1);
+	
+	s1=project_point_on_plane(ctx, &p1, plane, &projp1); /*the sign of s1 tells us on which side of the plane the point is. */
+	rt_dist3d_pt_poly(ctx, &p1, poly, plane,&projp1, dl);	
+	
+	for (i=1;i<pa->npoints;i++)
+	{		
+		int intersects;
+		rt_getPoint3dz_p(ctx, pa, i, &p2);
+		s2=project_point_on_plane(ctx, &p2, plane, &projp2);	
+		rt_dist3d_pt_poly(ctx, &p2, poly, plane,&projp2, dl);
+		
+		/*If s1and s2 has different signs that means they are on different sides of the plane of the polygon.
+		That means that the edge between the points crosses the plane and might intersect with the polygon*/
+		if((s1*s2)<=0) 
+		{
+			f=fabs(s1)/(fabs(s1)+fabs(s2)); /*The size of s1 and s2 is the distance from the point to the plane.*/
+			get_3dvector_from_points(ctx, &projp1, &projp2,&projp1_projp2);
+			
+			/*get the point where the line segment crosses the plane*/
+			intersectionp.x=projp1.x+f*projp1_projp2.x;
+			intersectionp.y=projp1.y+f*projp1_projp2.y;
+			intersectionp.z=projp1.z+f*projp1_projp2.z;
+			
+			intersects = RT_TRUE; /*We set intersects to true until the opposite is proved*/
+			
+			if(pt_in_ring_3d(ctx, &intersectionp, poly->rings[0], plane)) /*Inside outer ring*/
+			{
+				for (k=1;k<poly->nrings; k++)
+				{
+					/* Inside a hole, so no intersection with the polygon*/
+					if ( pt_in_ring_3d(ctx, &intersectionp, poly->rings[k], plane ))
+					{
+						intersects=RT_FALSE;
+						break;
+					}
+				}		
+				if(intersects) 
+				{
+					dl->distance=0.0;
+					dl->p1.x=intersectionp.x;
+					dl->p1.y=intersectionp.y;
+					dl->p1.z=intersectionp.z;
+					
+					dl->p2.x=intersectionp.x;
+					dl->p2.y=intersectionp.y;
+					dl->p2.z=intersectionp.z;
+					return RT_TRUE;
+					
+				}					
+			}			
+		}
+		
+		projp1=projp2;
+		s1=s2;
+		p1=p2;
+	}	
+	
+	/*check or pointarray against boundary and inner boundaries of the polygon*/
+	for (j=0;j<poly->nrings;j++)
+	{
+		rt_dist3d_ptarray_ptarray(ctx, pa, poly->rings[j], dl);
+	}
+	
+return RT_TRUE;
+}	
+
+
+/**
+
+Here we define the plane of a polygon (boundary pointarray of a polygon)
+the plane is stored as a pont in plane (plane.pop) and a normal vector (plane.pv)
+*/
+int
+define_plane(const RTCTX *ctx, RTPOINTARRAY *pa, PLANE3D *pl)
+{
+	int i,j, numberofvectors, pointsinslice;
+	RTPOINT3DZ p, p1, p2;
+
+	double sumx=0;
+	double sumy=0;
+	double sumz=0;
+	double vl; /*vector length*/
+
+	VECTOR3D v1, v2, v;
+	
+	if((pa->npoints-1)==3) /*Triangle is special case*/
+	{
+		pointsinslice=1;		
+	}
+	else
+	{
+		pointsinslice=(int) floor((pa->npoints-1)/4); /*divide the pointarray into 4 slices*/
+	}
+	
+	/*find the avg point*/
+	for (i=0;i<(pa->npoints-1);i++)
+	{
+		rt_getPoint3dz_p(ctx, pa, i, &p);
+		sumx+=p.x;
+		sumy+=p.y;
+		sumz+=p.z;		
+	}	
+	pl->pop.x=(sumx/(pa->npoints-1));
+	pl->pop.y=(sumy/(pa->npoints-1));
+	pl->pop.z=(sumz/(pa->npoints-1));
+	
+	sumx=0;
+	sumy=0;
+	sumz=0;
+	numberofvectors= floor((pa->npoints-1)/pointsinslice); /*the number of vectors we try can be 3, 4 or 5*/
+	
+	rt_getPoint3dz_p(ctx, pa, 0, &p1);
+	for (j=pointsinslice;j<pa->npoints;j+=pointsinslice)
+	{
+		rt_getPoint3dz_p(ctx, pa, j, &p2);	
+		
+		if (!get_3dvector_from_points(ctx, &(pl->pop), &p1, &v1) || !get_3dvector_from_points(ctx, &(pl->pop), &p2, &v2))
+			return RT_FALSE;	
+		/*perpendicular vector is cross product of v1 and v2*/
+		if (!get_3dcross_product(ctx, &v1,&v2, &v))
+			return RT_FALSE;		
+		vl=VECTORLENGTH(v);
+		sumx+=(v.x/vl);
+		sumy+=(v.y/vl);
+		sumz+=(v.z/vl);	
+		p1=p2;
+	}
+	pl->pv.x=(sumx/numberofvectors);
+	pl->pv.y=(sumy/numberofvectors);
+	pl->pv.z=(sumz/numberofvectors);
+	
+	return 1;
+}
+
+/**
+
+Finds a point on a plane from where the original point is perpendicular to the plane
+*/
+double 
+project_point_on_plane(const RTCTX *ctx, RTPOINT3DZ *p,  PLANE3D *pl, RTPOINT3DZ *p0)
+{
+/*In our plane definition we have a point on the plane and a normal vektor (pl.pv), perpendicular to the plane
+this vector will be paralell to the line between our inputted point above the plane and the point we are searching for on the plane.
+So, we already have a direction from p to find p0, but we don't know the distance.
+*/
+
+	VECTOR3D v1;
+	double f;
+	
+	if (!get_3dvector_from_points(ctx, &(pl->pop), p, &v1))
+	return RT_FALSE;	
+	
+	f=-(DOT(pl->pv,v1)/DOT(pl->pv,pl->pv));
+	
+	p0->x=p->x+pl->pv.x*f;
+	p0->y=p->y+pl->pv.y*f;
+	p0->z=p->z+pl->pv.z*f;      
+	
+	return f;		
+}
+
+
+
+
+/**
+ * pt_in_ring_3d(ctx): crossing number test for a point in a polygon
+ *      input:   p = a point,
+ *               pa = vertex points of a ring V[n+1] with V[n]=V[0]
+*		plane=the plane that the vertex points are lying on
+ *      returns: 0 = outside, 1 = inside
+ *
+ *	Our polygons have first and last point the same,
+ *
+*	The difference in 3D variant is that we exclude the dimension that faces the plane least.
+*	That is the dimension with the highest number in pv
+ */
+int
+pt_in_ring_3d(const RTCTX *ctx, const RTPOINT3DZ *p, const RTPOINTARRAY *ring,PLANE3D *plane)
+{
+	
+	int cn = 0;    /* the crossing number counter */
+	int i;
+	RTPOINT3DZ v1, v2;
+
+	RTPOINT3DZ	first, last;
+
+	rt_getPoint3dz_p(ctx, ring, 0, &first);
+	rt_getPoint3dz_p(ctx, ring, ring->npoints-1, &last);
+	if ( memcmp(&first, &last, sizeof(RTPOINT3DZ)) )
+	{
+		rterror(ctx, "pt_in_ring_3d: V[n] != V[0] (%g %g %g!= %g %g %g)",
+		        first.x, first.y, first.z, last.x, last.y, last.z);
+		return RT_FALSE;
+	}
+
+	RTDEBUGF(2, "pt_in_ring_3d called with point: %g %g %g", p->x, p->y, p->z);
+	/* printPA(ctx, ring); */
+
+	/* loop through all edges of the polygon */
+	rt_getPoint3dz_p(ctx, ring, 0, &v1);
+	
+	
+	if(fabs(plane->pv.z)>=fabs(plane->pv.x)&&fabs(plane->pv.z)>=fabs(plane->pv.y))	/*If the z vector of the normal vector to the plane is larger than x and y vector we project the ring to the xy-plane*/
+	{
+		for (i=0; i<ring->npoints-1; i++)
+		{
+			double vt;
+			rt_getPoint3dz_p(ctx, ring, i+1, &v2);
+
+			/* edge from vertex i to vertex i+1 */
+			if
+			(
+			    /* an upward crossing */
+			    ((v1.y <= p->y) && (v2.y > p->y))
+			    /* a downward crossing */
+			    || ((v1.y > p->y) && (v2.y <= p->y))
+			)
+			{
+
+				vt = (double)(p->y - v1.y) / (v2.y - v1.y);
+
+				/* P.x <intersect */
+				if (p->x < v1.x + vt * (v2.x - v1.x))
+				{
+					/* a valid crossing of y=p.y right of p.x */
+					++cn;
+				}
+			}
+			v1 = v2;
+		}
+	}
+	else if(fabs(plane->pv.y)>=fabs(plane->pv.x)&&fabs(plane->pv.y)>=fabs(plane->pv.z))	/*If the y vector of the normal vector to the plane is larger than x and z vector we project the ring to the xz-plane*/
+	{
+		for (i=0; i<ring->npoints-1; i++)
+			{
+				double vt;
+				rt_getPoint3dz_p(ctx, ring, i+1, &v2);
+
+				/* edge from vertex i to vertex i+1 */
+				if
+				(
+				    /* an upward crossing */
+				    ((v1.z <= p->z) && (v2.z > p->z))
+				    /* a downward crossing */
+				    || ((v1.z > p->z) && (v2.z <= p->z))
+				)
+				{
+
+					vt = (double)(p->z - v1.z) / (v2.z - v1.z);
+
+					/* P.x <intersect */
+					if (p->x < v1.x + vt * (v2.x - v1.x))
+					{
+						/* a valid crossing of y=p.y right of p.x */
+						++cn;
+					}
+				}
+				v1 = v2;
+			}
+	}
+	else	/*Hopefully we only have the cases where x part of the normal vector is largest left*/
+	{
+		for (i=0; i<ring->npoints-1; i++)
+			{
+				double vt;
+				rt_getPoint3dz_p(ctx, ring, i+1, &v2);
+
+				/* edge from vertex i to vertex i+1 */
+				if
+				(
+				    /* an upward crossing */
+				    ((v1.z <= p->z) && (v2.z > p->z))
+				    /* a downward crossing */
+				    || ((v1.z > p->z) && (v2.z <= p->z))
+				)
+				{
+
+					vt = (double)(p->z - v1.z) / (v2.z - v1.z);
+
+					/* P.x <intersect */
+					if (p->y < v1.y + vt * (v2.y - v1.y))
+					{
+						/* a valid crossing of y=p.y right of p.x */
+						++cn;
+					}
+				}
+				v1 = v2;
+			}
+	}
+	RTDEBUGF(3, "pt_in_ring_3d returning %d", cn&1);
+
+	return (cn&1);    /* 0 if even (out), and 1 if odd (in) */
+}
+
+
+
+/*------------------------------------------------------------------------------------------------------------
+End of Brute force functions
+--------------------------------------------------------------------------------------------------------------*/
+
+
+
diff --git a/src/measures3d.h b/src/measures3d.h
new file mode 100644
index 0000000..62781b7
--- /dev/null
+++ b/src/measures3d.h
@@ -0,0 +1,105 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+#ifndef _MEASURES3D_H
+#define _MEASURES3D_H 1
+#include <float.h>
+#include "measures.h"
+
+#define DOT(u,v)   ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
+#define VECTORLENGTH(v)   sqrt(((v).x * (v).x) + ((v).y * (v).y) + ((v).z * (v).z))
+
+
+/**
+
+Structure used in distance-calculations
+*/
+typedef struct
+{
+	double distance;	/*the distance between p1 and p2*/
+	RTPOINT3DZ p1;
+	RTPOINT3DZ p2;
+	int mode;	/*the direction of looking, if thedir = -1 then we look for 3dmaxdistance and if it is 1 then we look for 3dmindistance*/
+	int twisted; /*To preserve the order of incoming points to match the first and second point in 3dshortest and 3dlongest line*/
+	double tolerance; /*the tolerance for 3ddwithin and 3ddfullywithin*/
+} DISTPTS3D;
+
+typedef struct
+{
+	double	x,y,z;  
+}
+VECTOR3D; 
+
+typedef struct
+{
+	RTPOINT3DZ		pop;  /*Point On Plane*/
+	VECTOR3D	pv;  /*Perpendicular normal vector*/
+}
+PLANE3D; 
+
+
+/*
+Geometry returning functions
+*/
+RTGEOM * rt_dist3d_distancepoint(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2,int srid,int mode);
+RTGEOM * rt_dist3d_distanceline(const RTCTX *ctx, const RTGEOM *rt1, const RTGEOM *rt2,int srid,int mode);
+
+/*
+Preprocessing functions
+*/
+int rt_dist3d_distribute_bruteforce(const RTCTX *ctx, const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS3D *dl);
+int rt_dist3d_recursive(const RTCTX *ctx, const RTGEOM *rtg1,const RTGEOM *rtg2, DISTPTS3D *dl);
+int rt_dist3d_distribute_fast(const RTGEOM *rtg1, const RTGEOM *rtg2, DISTPTS3D *dl);
+
+/*
+Brute force functions
+*/
+int rt_dist3d_pt_ptarray(const RTCTX *ctx, RTPOINT3DZ *p, RTPOINTARRAY *pa, DISTPTS3D *dl);
+int rt_dist3d_point_point(const RTCTX *ctx, RTPOINT *point1, RTPOINT *point2, DISTPTS3D *dl);
+int rt_dist3d_point_line(const RTCTX *ctx, RTPOINT *point, RTLINE *line, DISTPTS3D *dl);
+int rt_dist3d_line_line(const RTCTX *ctx, RTLINE *line1,RTLINE *line2 , DISTPTS3D *dl);
+int rt_dist3d_point_poly(const RTCTX *ctx, RTPOINT *point, RTPOLY *poly, DISTPTS3D *dl);
+int rt_dist3d_line_poly(const RTCTX *ctx, RTLINE *line, RTPOLY *poly, DISTPTS3D *dl);
+int rt_dist3d_poly_poly(const RTCTX *ctx, RTPOLY *poly1, RTPOLY *poly2, DISTPTS3D *dl);
+int rt_dist3d_ptarray_ptarray(const RTCTX *ctx, RTPOINTARRAY *l1, RTPOINTARRAY *l2,DISTPTS3D *dl);
+int rt_dist3d_seg_seg(const RTCTX *ctx, RTPOINT3DZ *A, RTPOINT3DZ *B, RTPOINT3DZ *C, RTPOINT3DZ *D, DISTPTS3D *dl);
+int rt_dist3d_pt_pt(const RTCTX *ctx, RTPOINT3DZ *p1, RTPOINT3DZ *p2, DISTPTS3D *dl);
+int rt_dist3d_pt_seg(const RTCTX *ctx, RTPOINT3DZ *p, RTPOINT3DZ *A, RTPOINT3DZ *B, DISTPTS3D *dl);
+int rt_dist3d_pt_poly(const RTCTX *ctx, RTPOINT3DZ *p, RTPOLY *poly, PLANE3D *plane,RTPOINT3DZ *projp,  DISTPTS3D *dl);
+int rt_dist3d_ptarray_poly(const RTCTX *ctx, RTPOINTARRAY *pa, RTPOLY *poly, PLANE3D *plane, DISTPTS3D *dl);
+
+
+
+double project_point_on_plane(const RTCTX *ctx, RTPOINT3DZ *p,  PLANE3D *pl, RTPOINT3DZ *p0);
+int define_plane(const RTCTX *ctx, RTPOINTARRAY *pa, PLANE3D *pl);
+int pt_in_ring_3d(const RTCTX *ctx, const RTPOINT3DZ *p, const RTPOINTARRAY *ring,PLANE3D *plane);
+
+/*
+Helper functions
+*/
+
+
+#endif /* !defined _MEASURES3D_H  */
diff --git a/src/ptarray.c b/src/ptarray.c
new file mode 100644
index 0000000..1f39c9b
--- /dev/null
+++ b/src/ptarray.c
@@ -0,0 +1,1865 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2012 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <string.h>
+
+#include "rttopo_config.h"
+/*#define RTGEOM_DEBUG_LEVEL 4*/
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+int
+ptarray_has_z(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	if ( ! pa ) return RT_FALSE;
+	return RTFLAGS_GET_Z(pa->flags);
+}
+
+int
+ptarray_has_m(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	if ( ! pa ) return RT_FALSE;
+	return RTFLAGS_GET_M(pa->flags);
+}
+
+/*
+ * Size of point represeneted in the RTPOINTARRAY
+ * 16 for 2d, 24 for 3d, 32 for 4d
+ */
+int inline
+ptarray_point_size(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	RTDEBUGF(5, "ptarray_point_size: RTFLAGS_NDIMS(pa->flags)=%x",RTFLAGS_NDIMS(pa->flags));
+
+	return sizeof(double)*RTFLAGS_NDIMS(pa->flags);
+}
+
+RTPOINTARRAY*
+ptarray_construct(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints)
+{
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, hasz, hasm, npoints);
+	pa->npoints = npoints;
+	return pa;
+}
+
+RTPOINTARRAY*
+ptarray_construct_empty(const RTCTX *ctx, char hasz, char hasm, uint32_t maxpoints)
+{
+	RTPOINTARRAY *pa = rtalloc(ctx, sizeof(RTPOINTARRAY));
+	pa->serialized_pointlist = NULL;
+	
+	/* Set our dimsionality info on the bitmap */
+	pa->flags = gflags(ctx, hasz, hasm, 0);
+	
+	/* We will be allocating a bit of room */
+	pa->npoints = 0;
+	pa->maxpoints = maxpoints;
+	
+	/* Allocate the coordinate array */
+	if ( maxpoints > 0 )
+		pa->serialized_pointlist = rtalloc(ctx, maxpoints * ptarray_point_size(ctx, pa));
+	else 
+		pa->serialized_pointlist = NULL;
+
+	return pa;
+}
+
+/*
+* Add a point into a pointarray. Only adds as many dimensions as the 
+* pointarray supports.
+*/
+int
+ptarray_insert_point(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *p, int where)
+{
+	size_t point_size = ptarray_point_size(ctx, pa);
+	RTDEBUGF(5,"pa = %p; p = %p; where = %d", pa, p, where);
+	RTDEBUGF(5,"pa->npoints = %d; pa->maxpoints = %d", pa->npoints, pa->maxpoints);
+	
+	if ( RTFLAGS_GET_READONLY(pa->flags) ) 
+	{
+		rterror(ctx, "ptarray_insert_point: called on read-only point array");
+		return RT_FAILURE;
+	}
+	
+	/* Error on invalid offset value */
+	if ( where > pa->npoints || where < 0)
+	{
+		rterror(ctx, "ptarray_insert_point: offset out of range (%d)", where);
+		return RT_FAILURE;
+	}
+	
+	/* If we have no storage, let's allocate some */
+	if( pa->maxpoints == 0 || ! pa->serialized_pointlist ) 
+	{
+		pa->maxpoints = 32;
+		pa->npoints = 0;
+		pa->serialized_pointlist = rtalloc(ctx, ptarray_point_size(ctx, pa) * pa->maxpoints);
+	}
+
+	/* Error out if we have a bad situation */
+	if ( pa->npoints > pa->maxpoints )
+	{
+		rterror(ctx, "npoints (%d) is greated than maxpoints (%d)", pa->npoints, pa->maxpoints);
+		return RT_FAILURE;
+	}
+	
+	/* Check if we have enough storage, add more if necessary */
+	if( pa->npoints == pa->maxpoints )
+	{
+		pa->maxpoints *= 2;
+		pa->serialized_pointlist = rtrealloc(ctx, pa->serialized_pointlist, ptarray_point_size(ctx, pa) * pa->maxpoints);
+	}
+	
+	/* Make space to insert the new point */
+	if( where < pa->npoints )
+	{
+		size_t copy_size = point_size * (pa->npoints - where);
+		memmove(rt_getPoint_internal(ctx, pa, where+1), rt_getPoint_internal(ctx, pa, where), copy_size);
+		RTDEBUGF(5,"copying %d bytes to start vertex %d from start vertex %d", copy_size, where+1, where);
+	}
+	
+	/* We have one more point */
+	++pa->npoints;
+	
+	/* Copy the new point into the gap */
+	ptarray_set_point4d(ctx, pa, where, p);
+	RTDEBUGF(5,"copying new point to start vertex %d", point_size, where);
+	
+	return RT_SUCCESS;
+}
+
+int
+ptarray_append_point(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *pt, int repeated_points)
+{
+
+	/* Check for pathology */
+	if( ! pa || ! pt ) 
+	{
+		rterror(ctx, "ptarray_append_point: null input");
+		return RT_FAILURE;
+	}
+
+	/* Check for duplicate end point */
+	if ( repeated_points == RT_FALSE && pa->npoints > 0 )
+	{
+		RTPOINT4D tmp;
+		rt_getPoint4d_p(ctx, pa, pa->npoints-1, &tmp);
+		RTDEBUGF(4,"checking for duplicate end point (pt = POINT(%g %g) pa->npoints-q = POINT(%g %g))",pt->x,pt->y,tmp.x,tmp.y);
+
+		/* Return RT_SUCCESS and do nothing else if previous point in list is equal to this one */
+		if ( (pt->x == tmp.x) && (pt->y == tmp.y) &&
+		     (RTFLAGS_GET_Z(pa->flags) ? pt->z == tmp.z : 1) &&
+		     (RTFLAGS_GET_M(pa->flags) ? pt->m == tmp.m : 1) )
+		{
+			return RT_SUCCESS;
+		}
+	}
+
+	/* Append is just a special case of insert */
+	return ptarray_insert_point(ctx, pa, pt, pa->npoints);
+}
+
+int
+ptarray_append_ptarray(const RTCTX *ctx, RTPOINTARRAY *pa1, RTPOINTARRAY *pa2, double gap_tolerance)
+{
+	unsigned int poff = 0;
+	unsigned int npoints;
+	unsigned int ncap;
+	unsigned int ptsize;
+
+	/* Check for pathology */
+	if( ! pa1 || ! pa2 ) 
+	{
+		rterror(ctx, "ptarray_append_ptarray: null input");
+		return RT_FAILURE;
+	}
+
+	npoints = pa2->npoints;
+	
+	if ( ! npoints ) return RT_SUCCESS; /* nothing more to do */
+
+	if( RTFLAGS_GET_READONLY(pa1->flags) )
+	{
+		rterror(ctx, "ptarray_append_ptarray: target pointarray is read-only");
+		return RT_FAILURE;
+	}
+
+	if( RTFLAGS_GET_ZM(pa1->flags) != RTFLAGS_GET_ZM(pa2->flags) )
+	{
+		rterror(ctx, "ptarray_append_ptarray: appending mixed dimensionality is not allowed");
+		return RT_FAILURE;
+	}
+
+	ptsize = ptarray_point_size(ctx, pa1);
+
+	/* Check for duplicate end point */
+	if ( pa1->npoints )
+	{
+		RTPOINT2D tmp1, tmp2;
+		rt_getPoint2d_p(ctx, pa1, pa1->npoints-1, &tmp1);
+		rt_getPoint2d_p(ctx, pa2, 0, &tmp2);
+
+		/* If the end point and start point are the same, then don't copy start point */
+		if (p2d_same(ctx, &tmp1, &tmp2)) {
+			poff = 1;
+			--npoints;
+		}
+		else if ( gap_tolerance == 0 || ( gap_tolerance > 0 &&
+		           distance2d_pt_pt(ctx, &tmp1, &tmp2) > gap_tolerance ) ) 
+		{
+			rterror(ctx, "Second line start point too far from first line end point");
+			return RT_FAILURE;
+		} 
+	}
+
+	/* Check if we need extra space */
+	ncap = pa1->npoints + npoints;
+	if ( pa1->maxpoints < ncap )
+	{
+		pa1->maxpoints = ncap > pa1->maxpoints*2 ?
+		                 ncap : pa1->maxpoints*2;
+		pa1->serialized_pointlist = rtrealloc(ctx, pa1->serialized_pointlist, ptsize * pa1->maxpoints);
+	}
+
+	memcpy(rt_getPoint_internal(ctx, pa1, pa1->npoints),
+	       rt_getPoint_internal(ctx, pa2, poff), ptsize * npoints);
+
+	pa1->npoints = ncap;
+
+	return RT_SUCCESS;
+}
+
+/*
+* Add a point into a pointarray. Only adds as many dimensions as the 
+* pointarray supports.
+*/
+int
+ptarray_remove_point(const RTCTX *ctx, RTPOINTARRAY *pa, int where)
+{
+	size_t ptsize = ptarray_point_size(ctx, pa);
+
+	/* Check for pathology */
+	if( ! pa ) 
+	{
+		rterror(ctx, "ptarray_remove_point: null input");
+		return RT_FAILURE;
+	}
+	
+	/* Error on invalid offset value */
+	if ( where >= pa->npoints || where < 0)
+	{
+		rterror(ctx, "ptarray_remove_point: offset out of range (%d)", where);
+		return RT_FAILURE;
+	}
+	
+	/* If the point is any but the last, we need to copy the data back one point */
+	if( where < pa->npoints - 1 )
+	{
+		memmove(rt_getPoint_internal(ctx, pa, where), rt_getPoint_internal(ctx, pa, where+1), ptsize * (pa->npoints - where - 1));
+	}
+	
+	/* We have one less point */
+	pa->npoints--;
+	
+	return RT_SUCCESS;
+}
+
+/**
+* Build a new #RTPOINTARRAY, but on top of someone else's ordinate array. 
+* Flag as read-only, so that ptarray_free(ctx) does not free the serialized_ptlist
+*/
+RTPOINTARRAY* ptarray_construct_reference_data(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints, uint8_t *ptlist)
+{
+	RTPOINTARRAY *pa = rtalloc(ctx, sizeof(RTPOINTARRAY));
+	RTDEBUGF(5, "hasz = %d, hasm = %d, npoints = %d, ptlist = %p", hasz, hasm, npoints, ptlist);
+	pa->flags = gflags(ctx, hasz, hasm, 0);
+	RTFLAGS_SET_READONLY(pa->flags, 1); /* We don't own this memory, so we can't alter or free it. */
+	pa->npoints = npoints;
+	pa->maxpoints = npoints;
+	pa->serialized_pointlist = ptlist;
+	return pa;
+}
+
+
+RTPOINTARRAY*
+ptarray_construct_copy_data(const RTCTX *ctx, char hasz, char hasm, uint32_t npoints, const uint8_t *ptlist)
+{
+	RTPOINTARRAY *pa = rtalloc(ctx, sizeof(RTPOINTARRAY));
+
+	pa->flags = gflags(ctx, hasz, hasm, 0);
+	pa->npoints = npoints;
+	pa->maxpoints = npoints;
+
+	if ( npoints > 0 )
+	{
+		pa->serialized_pointlist = rtalloc(ctx, ptarray_point_size(ctx, pa) * npoints);
+		memcpy(pa->serialized_pointlist, ptlist, ptarray_point_size(ctx, pa) * npoints);
+	}
+	else
+	{
+		pa->serialized_pointlist = NULL;
+	}
+
+	return pa;
+}
+
+void ptarray_free(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	if(pa)
+	{
+		if(pa->serialized_pointlist && ( ! RTFLAGS_GET_READONLY(pa->flags) ) )
+			rtfree(ctx, pa->serialized_pointlist);	
+		rtfree(ctx, pa);
+		RTDEBUG(5,"Freeing a PointArray");
+	}
+}
+
+
+void
+ptarray_reverse(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	/* TODO change this to double array operations once point array is double aligned */
+	RTPOINT4D pbuf;
+	uint32_t i;
+	int ptsize = ptarray_point_size(ctx, pa);
+	int last = pa->npoints-1;
+	int mid = pa->npoints/2;
+
+	for (i=0; i<mid; i++)
+	{
+		uint8_t *from, *to;
+		from = rt_getPoint_internal(ctx, pa, i);
+		to = rt_getPoint_internal(ctx, pa, (last-i));
+		memcpy((uint8_t *)&pbuf, to, ptsize);
+		memcpy(to, from, ptsize);
+		memcpy(from, (uint8_t *)&pbuf, ptsize);
+	}
+
+}
+
+
+/**
+ * Reverse X and Y axis on a given RTPOINTARRAY
+ */
+RTPOINTARRAY*
+ptarray_flip_coordinates(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	int i;
+	double d;
+	RTPOINT4D p;
+
+	for (i=0 ; i < pa->npoints ; i++)
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		d = p.y;
+		p.y = p.x;
+		p.x = d;
+		ptarray_set_point4d(ctx, pa, i, &p);
+	}
+
+	return pa;
+}
+
+void
+ptarray_swap_ordinates(const RTCTX *ctx, RTPOINTARRAY *pa, RTORD o1, RTORD o2)
+{
+	int i;
+	double d, *dp1, *dp2;
+	RTPOINT4D p;
+
+#if PARANOIA_LEVEL > 0
+  assert(o1 < 4);
+  assert(o2 < 4);
+#endif
+
+  dp1 = ((double*)&p)+(unsigned)o1;
+  dp2 = ((double*)&p)+(unsigned)o2;
+	for (i=0 ; i < pa->npoints ; i++)
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		d = *dp2;
+		*dp2 = *dp1;
+		*dp1 = d;
+		ptarray_set_point4d(ctx, pa, i, &p);
+	}
+}
+
+
+/**
+ * @brief Returns a modified #RTPOINTARRAY so that no segment is
+ * 		longer than the given distance (computed using 2d).
+ *
+ * Every input point is kept.
+ * Z and M values for added points (if needed) are set to 0.
+ */
+RTPOINTARRAY *
+ptarray_segmentize2d(const RTCTX *ctx, const RTPOINTARRAY *ipa, double dist)
+{
+	double	segdist;
+	RTPOINT4D	p1, p2;
+	RTPOINT4D pbuf;
+	RTPOINTARRAY *opa;
+	int ipoff=0; /* input point offset */
+	int hasz = RTFLAGS_GET_Z(ipa->flags);
+	int hasm = RTFLAGS_GET_M(ipa->flags);
+
+	pbuf.x = pbuf.y = pbuf.z = pbuf.m = 0;
+
+	/* Initial storage */
+	opa = ptarray_construct_empty(ctx, hasz, hasm, ipa->npoints);
+	
+	/* Add first point */
+	rt_getPoint4d_p(ctx, ipa, ipoff, &p1);
+	ptarray_append_point(ctx, opa, &p1, RT_FALSE);
+
+	ipoff++;
+
+	while (ipoff<ipa->npoints)
+	{
+		/*
+		 * We use these pointers to avoid
+		 * "strict-aliasing rules break" warning raised
+		 * by gcc (3.3 and up).
+		 *
+		 * It looks that casting a variable address (also
+		 * referred to as "type-punned pointer")
+		 * breaks those "strict" rules.
+		 *
+		 */
+		RTPOINT4D *p1ptr=&p1, *p2ptr=&p2;
+
+		rt_getPoint4d_p(ctx, ipa, ipoff, &p2);
+
+		segdist = distance2d_pt_pt(ctx, (RTPOINT2D *)p1ptr, (RTPOINT2D *)p2ptr);
+
+		if (segdist > dist) /* add an intermediate point */
+		{
+			pbuf.x = p1.x + (p2.x-p1.x)/segdist * dist;
+			pbuf.y = p1.y + (p2.y-p1.y)/segdist * dist;
+			if( hasz ) 
+				pbuf.z = p1.z + (p2.z-p1.z)/segdist * dist;
+			if( hasm )
+				pbuf.m = p1.m + (p2.m-p1.m)/segdist * dist;
+			ptarray_append_point(ctx, opa, &pbuf, RT_FALSE);
+			p1 = pbuf;
+		}
+		else /* copy second point */
+		{
+			ptarray_append_point(ctx, opa, &p2, (ipa->npoints==2)?RT_TRUE:RT_FALSE);
+			p1 = p2;
+			ipoff++;
+		}
+
+		RT_ON_INTERRUPT(ptarray_free(ctx, opa); return NULL);
+	}
+
+	return opa;
+}
+
+char
+ptarray_same(const RTCTX *ctx, const RTPOINTARRAY *pa1, const RTPOINTARRAY *pa2)
+{
+	uint32_t i;
+	size_t ptsize;
+
+	if ( RTFLAGS_GET_ZM(pa1->flags) != RTFLAGS_GET_ZM(pa2->flags) ) return RT_FALSE;
+	RTDEBUG(5,"dimensions are the same");
+	
+	if ( pa1->npoints != pa2->npoints ) return RT_FALSE;
+	RTDEBUG(5,"npoints are the same");
+
+	ptsize = ptarray_point_size(ctx, pa1);
+	RTDEBUGF(5, "ptsize = %d", ptsize);
+
+	for (i=0; i<pa1->npoints; i++)
+	{
+		if ( memcmp(rt_getPoint_internal(ctx, pa1, i), rt_getPoint_internal(ctx, pa2, i), ptsize) )
+			return RT_FALSE;
+		RTDEBUGF(5,"point #%d is the same",i);
+	}
+
+	return RT_TRUE;
+}
+
+RTPOINTARRAY *
+ptarray_addPoint(const RTCTX *ctx, const RTPOINTARRAY *pa, uint8_t *p, size_t pdims, uint32_t where)
+{
+	RTPOINTARRAY *ret;
+	RTPOINT4D pbuf;
+	size_t ptsize = ptarray_point_size(ctx, pa);
+
+	RTDEBUGF(3, "pa %x p %x size %d where %d",
+	         pa, p, pdims, where);
+
+	if ( pdims < 2 || pdims > 4 )
+	{
+		rterror(ctx, "ptarray_addPoint: point dimension out of range (%d)",
+		        pdims);
+		return NULL;
+	}
+
+	if ( where > pa->npoints )
+	{
+		rterror(ctx, "ptarray_addPoint: offset out of range (%d)",
+		        where);
+		return NULL;
+	}
+
+	RTDEBUG(3, "called with a %dD point");
+
+	pbuf.x = pbuf.y = pbuf.z = pbuf.m = 0.0;
+	memcpy((uint8_t *)&pbuf, p, pdims*sizeof(double));
+
+	RTDEBUG(3, "initialized point buffer");
+
+	ret = ptarray_construct(ctx, RTFLAGS_GET_Z(pa->flags),
+	                        RTFLAGS_GET_M(pa->flags), pa->npoints+1);
+
+	if ( where == -1 ) where = pa->npoints;
+
+	if ( where )
+	{
+		memcpy(rt_getPoint_internal(ctx, ret, 0), rt_getPoint_internal(ctx, pa, 0), ptsize*where);
+	}
+
+	memcpy(rt_getPoint_internal(ctx, ret, where), (uint8_t *)&pbuf, ptsize);
+
+	if ( where+1 != ret->npoints )
+	{
+		memcpy(rt_getPoint_internal(ctx, ret, where+1),
+		       rt_getPoint_internal(ctx, pa, where),
+		       ptsize*(pa->npoints-where));
+	}
+
+	return ret;
+}
+
+RTPOINTARRAY *
+ptarray_removePoint(const RTCTX *ctx, RTPOINTARRAY *pa, uint32_t which)
+{
+	RTPOINTARRAY *ret;
+	size_t ptsize = ptarray_point_size(ctx, pa);
+
+	RTDEBUGF(3, "pa %x which %d", pa, which);
+
+#if PARANOIA_LEVEL > 0
+	if ( which > pa->npoints-1 )
+	{
+		rterror(ctx, "ptarray_removePoint: offset (%d) out of range (%d..%d)",
+		        which, 0, pa->npoints-1);
+		return NULL;
+	}
+
+	if ( pa->npoints < 3 )
+	{
+		rterror(ctx, "ptarray_removePointe: can't remove a point from a 2-vertex RTPOINTARRAY");
+	}
+#endif
+
+	ret = ptarray_construct(ctx, RTFLAGS_GET_Z(pa->flags),
+	                        RTFLAGS_GET_M(pa->flags), pa->npoints-1);
+
+	/* copy initial part */
+	if ( which )
+	{
+		memcpy(rt_getPoint_internal(ctx, ret, 0), rt_getPoint_internal(ctx, pa, 0), ptsize*which);
+	}
+
+	/* copy final part */
+	if ( which < pa->npoints-1 )
+	{
+		memcpy(rt_getPoint_internal(ctx, ret, which), rt_getPoint_internal(ctx, pa, which+1),
+		       ptsize*(pa->npoints-which-1));
+	}
+
+	return ret;
+}
+
+RTPOINTARRAY *
+ptarray_merge(const RTCTX *ctx, RTPOINTARRAY *pa1, RTPOINTARRAY *pa2)
+{
+	RTPOINTARRAY *pa;
+	size_t ptsize = ptarray_point_size(ctx, pa1);
+
+	if (RTFLAGS_GET_ZM(pa1->flags) != RTFLAGS_GET_ZM(pa2->flags))
+		rterror(ctx, "ptarray_cat: Mixed dimension");
+
+	pa = ptarray_construct(ctx,  RTFLAGS_GET_Z(pa1->flags),
+	                        RTFLAGS_GET_M(pa1->flags),
+	                        pa1->npoints + pa2->npoints);
+
+	memcpy(         rt_getPoint_internal(ctx, pa, 0),
+	                rt_getPoint_internal(ctx, pa1, 0),
+	                ptsize*(pa1->npoints));
+
+	memcpy(         rt_getPoint_internal(ctx, pa, pa1->npoints),
+	                rt_getPoint_internal(ctx, pa2, 0),
+	                ptsize*(pa2->npoints));
+
+	ptarray_free(ctx, pa1);
+	ptarray_free(ctx, pa2);
+
+	return pa;
+}
+
+
+/**
+ * @brief Deep clone a pointarray (also clones serialized pointlist)
+ */
+RTPOINTARRAY *
+ptarray_clone_deep(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	RTPOINTARRAY *out = rtalloc(ctx, sizeof(RTPOINTARRAY));
+	size_t size;
+
+	RTDEBUG(3, "ptarray_clone_deep called.");
+
+	out->flags = in->flags;
+	out->npoints = in->npoints;
+	out->maxpoints = in->maxpoints;
+
+	RTFLAGS_SET_READONLY(out->flags, 0);
+
+	size = in->npoints * ptarray_point_size(ctx, in);
+	out->serialized_pointlist = rtalloc(ctx, size);
+	memcpy(out->serialized_pointlist, in->serialized_pointlist, size);
+
+	return out;
+}
+
+/**
+ * @brief Clone a RTPOINTARRAY object. Serialized pointlist is not copied.
+ */
+RTPOINTARRAY *
+ptarray_clone(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	RTPOINTARRAY *out = rtalloc(ctx, sizeof(RTPOINTARRAY));
+
+	RTDEBUG(3, "ptarray_clone_deep called.");
+
+	out->flags = in->flags;
+	out->npoints = in->npoints;
+	out->maxpoints = in->maxpoints;
+
+	RTFLAGS_SET_READONLY(out->flags, 1);
+
+	out->serialized_pointlist = in->serialized_pointlist;
+
+	return out;
+}
+
+/**
+* Check for ring closure using whatever dimensionality is declared on the 
+* pointarray.
+*/
+int
+ptarray_is_closed(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	return 0 == memcmp(rt_getPoint_internal(ctx, in, 0), rt_getPoint_internal(ctx, in, in->npoints-1), ptarray_point_size(ctx, in));
+}
+
+
+int
+ptarray_is_closed_2d(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	return 0 == memcmp(rt_getPoint_internal(ctx, in, 0), rt_getPoint_internal(ctx, in, in->npoints-1), sizeof(RTPOINT2D));
+}
+
+int
+ptarray_is_closed_3d(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	return 0 == memcmp(rt_getPoint_internal(ctx, in, 0), rt_getPoint_internal(ctx, in, in->npoints-1), sizeof(POINT3D));
+}
+
+int
+ptarray_is_closed_z(const RTCTX *ctx, const RTPOINTARRAY *in)
+{
+	if ( RTFLAGS_GET_Z(in->flags) )
+		return ptarray_is_closed_3d(ctx, in);
+	else
+		return ptarray_is_closed_2d(ctx, in);
+}
+
+/**
+* Return 1 if the point is inside the RTPOINTARRAY, -1 if it is outside,
+* and 0 if it is on the boundary.
+*/
+int 
+ptarray_contains_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt)
+{
+	return ptarray_contains_point_partial(ctx, pa, pt, RT_TRUE, NULL);
+}
+
+int 
+ptarray_contains_point_partial(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt, int check_closed, int *winding_number)
+{
+	int wn = 0;
+	int i;
+	double side;
+	const RTPOINT2D *seg1;
+	const RTPOINT2D *seg2;
+	double ymin, ymax;
+
+	seg1 = rt_getPoint2d_cp(ctx, pa, 0);
+	seg2 = rt_getPoint2d_cp(ctx, pa, pa->npoints-1);
+	if ( check_closed && ! p2d_same(ctx, seg1, seg2) )
+		rterror(ctx, "ptarray_contains_point called on unclosed ring");
+	
+	for ( i=1; i < pa->npoints; i++ )
+	{
+		seg2 = rt_getPoint2d_cp(ctx, pa, i);
+		
+		/* Zero length segments are ignored. */
+		if ( seg1->x == seg2->x && seg1->y == seg2->y )
+		{
+			seg1 = seg2;
+			continue;
+		}
+			
+		ymin = FP_MIN(seg1->y, seg2->y);
+		ymax = FP_MAX(seg1->y, seg2->y);
+		
+		/* Only test segments in our vertical range */
+		if ( pt->y > ymax || pt->y < ymin ) 
+		{
+			seg1 = seg2;
+			continue;
+		}
+
+		side = rt_segment_side(ctx, seg1, seg2, pt);
+
+		/* 
+		* A point on the boundary of a ring is not contained. 
+		* WAS: if (fabs(side) < 1e-12), see #852 
+		*/
+		if ( (side == 0) && rt_pt_in_seg(ctx, pt, seg1, seg2) )
+		{
+			return RT_BOUNDARY;
+		}
+
+		/*
+		* If the point is to the left of the line, and it's rising,
+		* then the line is to the right of the point and
+		* circling counter-clockwise, so incremement.
+		*/
+		if ( (side < 0) && (seg1->y <= pt->y) && (pt->y < seg2->y) )
+		{
+			wn++;
+		}
+		
+		/*
+		* If the point is to the right of the line, and it's falling,
+		* then the line is to the right of the point and circling
+		* clockwise, so decrement.
+		*/
+		else if ( (side > 0) && (seg2->y <= pt->y) && (pt->y < seg1->y) )
+		{
+			wn--;
+		}
+		
+		seg1 = seg2;
+	}
+
+	/* Sent out the winding number for calls that are building on this as a primitive */
+	if ( winding_number )
+		*winding_number = wn;
+
+	/* Outside */
+	if (wn == 0)
+	{
+		return RT_OUTSIDE;
+	}
+	
+	/* Inside */
+	return RT_INSIDE;
+}
+
+/**
+* For RTPOINTARRAYs representing CIRCULARSTRINGS. That is, linked triples
+* with each triple being control points of a circular arc. Such
+* RTPOINTARRAYs have an odd number of vertices.
+*
+* Return 1 if the point is inside the RTPOINTARRAY, -1 if it is outside,
+* and 0 if it is on the boundary.
+*/
+
+int 
+ptarrayarc_contains_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt)
+{
+	return ptarrayarc_contains_point_partial(ctx, pa, pt, RT_TRUE /* Check closed*/, NULL);
+}
+
+int 
+ptarrayarc_contains_point_partial(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt, int check_closed, int *winding_number)
+{
+	int wn = 0;
+	int i, side;
+	const RTPOINT2D *seg1;
+	const RTPOINT2D *seg2;
+	const RTPOINT2D *seg3;
+	RTGBOX gbox;
+
+	/* Check for not an arc ring (artays have odd # of points) */
+	if ( (pa->npoints % 2) == 0 )
+	{
+		rterror(ctx, "ptarrayarc_contains_point called with even number of points");
+		return RT_OUTSIDE;
+	}
+
+	/* Check for not an arc ring (artays have >= 3 points) */
+	if ( pa->npoints < 3 )
+	{
+		rterror(ctx, "ptarrayarc_contains_point called too-short pointarray");
+		return RT_OUTSIDE;
+	}
+
+	/* Check for unclosed case */
+	seg1 = rt_getPoint2d_cp(ctx, pa, 0);
+	seg3 = rt_getPoint2d_cp(ctx, pa, pa->npoints-1);
+	if ( check_closed && ! p2d_same(ctx, seg1, seg3) )
+	{
+		rterror(ctx, "ptarrayarc_contains_point called on unclosed ring");
+		return RT_OUTSIDE;
+	} 
+	/* OK, it's closed. Is it just one circle? */
+	else if ( p2d_same(ctx, seg1, seg3) && pa->npoints == 3 )
+	{
+		double radius, d;
+		RTPOINT2D c;
+		seg2 = rt_getPoint2d_cp(ctx, pa, 1);
+		
+		/* Wait, it's just a point, so it can't contain anything */
+		if ( rt_arc_is_pt(ctx, seg1, seg2, seg3) )
+			return RT_OUTSIDE;
+			
+		/* See if the point is within the circle radius */
+		radius = rt_arc_center(ctx, seg1, seg2, seg3, &c);
+		d = distance2d_pt_pt(ctx, pt, &c);
+		if ( FP_EQUALS(d, radius) )
+			return RT_BOUNDARY; /* Boundary of circle */
+		else if ( d < radius ) 
+			return RT_INSIDE; /* Inside circle */
+		else 
+			return RT_OUTSIDE; /* Outside circle */
+	} 
+	else if ( p2d_same(ctx, seg1, pt) || p2d_same(ctx, seg3, pt) )
+	{
+		return RT_BOUNDARY; /* Boundary case */
+	}
+
+	/* Start on the ring */
+	seg1 = rt_getPoint2d_cp(ctx, pa, 0);
+	for ( i=1; i < pa->npoints; i += 2 )
+	{
+		seg2 = rt_getPoint2d_cp(ctx, pa, i);
+		seg3 = rt_getPoint2d_cp(ctx, pa, i+1);
+		
+		/* Catch an easy boundary case */
+		if( p2d_same(ctx, seg3, pt) )
+			return RT_BOUNDARY;
+		
+		/* Skip arcs that have no size */
+		if ( rt_arc_is_pt(ctx, seg1, seg2, seg3) )
+		{
+			seg1 = seg3;
+			continue;
+		}
+		
+		/* Only test segments in our vertical range */
+		rt_arc_calculate_gbox_cartesian_2d(ctx, seg1, seg2, seg3, &gbox);
+		if ( pt->y > gbox.ymax || pt->y < gbox.ymin ) 
+		{
+			seg1 = seg3;
+			continue;
+		}
+
+		/* Outside of horizontal range, and not between end points we also skip */
+		if ( (pt->x > gbox.xmax || pt->x < gbox.xmin) && 
+			 (pt->y > FP_MAX(seg1->y, seg3->y) || pt->y < FP_MIN(seg1->y, seg3->y)) ) 
+		{
+			seg1 = seg3;
+			continue;
+		}		
+		
+		side = rt_arc_side(ctx, seg1, seg2, seg3, pt);
+		
+		/* On the boundary */
+		if ( (side == 0) && rt_pt_in_arc(ctx, pt, seg1, seg2, seg3) )
+		{
+			return RT_BOUNDARY;
+		}
+		
+		/* Going "up"! Point to left of arc. */
+		if ( side < 0 && (seg1->y <= pt->y) && (pt->y < seg3->y) )
+		{
+			wn++;
+		}
+
+		/* Going "down"! */
+		if ( side > 0 && (seg2->y <= pt->y) && (pt->y < seg1->y) )
+		{
+			wn--;
+		}
+		
+		/* Inside the arc! */
+		if ( pt->x <= gbox.xmax && pt->x >= gbox.xmin ) 
+		{
+			RTPOINT2D C;
+			double radius = rt_arc_center(ctx, seg1, seg2, seg3, &C);
+			double d = distance2d_pt_pt(ctx, pt, &C);
+
+			/* On the boundary! */
+			if ( d == radius )
+				return RT_BOUNDARY;
+			
+			/* Within the arc! */
+			if ( d  < radius )
+			{
+				/* Left side, increment winding number */
+				if ( side < 0 )
+					wn++;
+				/* Right side, decrement winding number */
+				if ( side > 0 ) 
+					wn--;
+			}
+		}
+
+		seg1 = seg3;
+	}
+
+	/* Sent out the winding number for calls that are building on this as a primitive */
+	if ( winding_number )
+		*winding_number = wn;
+
+	/* Outside */
+	if (wn == 0)
+	{
+		return RT_OUTSIDE;
+	}
+	
+	/* Inside */
+	return RT_INSIDE;
+}
+
+/**
+* Returns the area in cartesian units. Area is negative if ring is oriented CCW, 
+* positive if it is oriented CW and zero if the ring is degenerate or flat.
+* http://en.wikipedia.org/wiki/Shoelace_formula
+*/
+double
+ptarray_signed_area(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	const RTPOINT2D *P1;
+	const RTPOINT2D *P2;
+	const RTPOINT2D *P3;
+	double sum = 0.0;
+	double x0, x, y1, y2;
+	int i;
+	
+	if (! pa || pa->npoints < 3 )
+		return 0.0;
+		
+	P1 = rt_getPoint2d_cp(ctx, pa, 0);
+	P2 = rt_getPoint2d_cp(ctx, pa, 1);
+	x0 = P1->x;
+	for ( i = 1; i < pa->npoints - 1; i++ )
+	{
+		P3 = rt_getPoint2d_cp(ctx, pa, i+1);
+		x = P2->x - x0;
+		y1 = P3->y;
+		y2 = P1->y;
+		sum += x * (y2-y1);
+		
+		/* Move forwards! */
+		P1 = P2;
+		P2 = P3;
+	}
+	return sum / 2.0;	
+}
+
+int
+ptarray_isccw(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	double area = 0;
+	area = ptarray_signed_area(ctx, pa);
+	if ( area > 0 ) return RT_FALSE;
+	else return RT_TRUE;
+}
+
+RTPOINTARRAY*
+ptarray_force_dims(const RTCTX *ctx, const RTPOINTARRAY *pa, int hasz, int hasm)
+{
+	/* TODO handle zero-length point arrays */
+	int i;
+	int in_hasz = RTFLAGS_GET_Z(pa->flags);
+	int in_hasm = RTFLAGS_GET_M(pa->flags);
+	RTPOINT4D pt;
+	RTPOINTARRAY *pa_out = ptarray_construct_empty(ctx, hasz, hasm, pa->npoints);
+	
+	for( i = 0; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &pt);
+		if( hasz && ! in_hasz )
+			pt.z = 0.0;
+		if( hasm && ! in_hasm )
+			pt.m = 0.0;
+		ptarray_append_point(ctx, pa_out, &pt, RT_TRUE);
+	} 
+
+	return pa_out;
+}
+
+RTPOINTARRAY *
+ptarray_substring(const RTCTX *ctx, RTPOINTARRAY *ipa, double from, double to, double tolerance)
+{
+	RTPOINTARRAY *dpa;
+	RTPOINT4D pt;
+	RTPOINT4D p1, p2;
+	RTPOINT4D *p1ptr=&p1; /* don't break strict-aliasing rule */
+	RTPOINT4D *p2ptr=&p2;
+	int nsegs, i;
+	double length, slength, tlength;
+	int state = 0; /* 0=before, 1=inside */
+
+	/*
+	 * Create a dynamic pointarray with an initial capacity
+	 * equal to full copy of input points
+	 */
+	dpa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(ipa->flags), RTFLAGS_GET_M(ipa->flags), ipa->npoints);
+
+	/* Compute total line length */
+	length = ptarray_length_2d(ctx, ipa);
+
+
+	RTDEBUGF(3, "Total length: %g", length);
+
+
+	/* Get 'from' and 'to' lengths */
+	from = length*from;
+	to = length*to;
+
+
+	RTDEBUGF(3, "From/To: %g/%g", from, to);
+
+
+	tlength = 0;
+	rt_getPoint4d_p(ctx, ipa, 0, &p1);
+	nsegs = ipa->npoints - 1;
+	for ( i = 0; i < nsegs; i++ )
+	{
+		double dseg;
+
+		rt_getPoint4d_p(ctx, ipa, i+1, &p2);
+
+
+		RTDEBUGF(3 ,"Segment %d: (%g,%g,%g,%g)-(%g,%g,%g,%g)",
+		         i, p1.x, p1.y, p1.z, p1.m, p2.x, p2.y, p2.z, p2.m);
+
+
+		/* Find the length of this segment */
+		slength = distance2d_pt_pt(ctx, (RTPOINT2D *)p1ptr, (RTPOINT2D *)p2ptr);
+
+		/*
+		 * We are before requested start.
+		 */
+		if ( state == 0 ) /* before */
+		{
+
+			RTDEBUG(3, " Before start");
+
+			if ( fabs ( from - ( tlength + slength ) ) <= tolerance )
+			{
+
+				RTDEBUG(3, "  Second point is our start");
+
+				/*
+				 * Second point is our start
+				 */
+				ptarray_append_point(ctx, dpa, &p2, RT_FALSE);
+				state=1; /* we're inside now */
+				goto END;
+			}
+
+			else if ( fabs(from - tlength) <= tolerance )
+			{
+
+				RTDEBUG(3, "  First point is our start");
+
+				/*
+				 * First point is our start
+				 */
+				ptarray_append_point(ctx, dpa, &p1, RT_FALSE);
+
+				/*
+				 * We're inside now, but will check
+				 * 'to' point as well
+				 */
+				state=1;
+			}
+
+			/*
+			 * Didn't reach the 'from' point,
+			 * nothing to do
+			 */
+			else if ( from > tlength + slength ) goto END;
+
+			else  /* tlength < from < tlength+slength */
+			{
+
+				RTDEBUG(3, "  Seg contains first point");
+
+				/*
+				 * Our start is between first and
+				 * second point
+				 */
+				dseg = (from - tlength) / slength;
+
+				interpolate_point4d(ctx, &p1, &p2, &pt, dseg);
+
+				ptarray_append_point(ctx, dpa, &pt, RT_FALSE);
+
+				/*
+				 * We're inside now, but will check
+				 * 'to' point as well
+				 */
+				state=1;
+			}
+		}
+
+		if ( state == 1 ) /* inside */
+		{
+
+			RTDEBUG(3, " Inside");
+
+			/*
+			 * 'to' point is our second point.
+			 */
+			if ( fabs(to - ( tlength + slength ) ) <= tolerance )
+			{
+
+				RTDEBUG(3, " Second point is our end");
+
+				ptarray_append_point(ctx, dpa, &p2, RT_FALSE);
+				break; /* substring complete */
+			}
+
+			/*
+			 * 'to' point is our first point.
+			 * (should only happen if 'to' is 0)
+			 */
+			else if ( fabs(to - tlength) <= tolerance )
+			{
+
+				RTDEBUG(3, " First point is our end");
+
+				ptarray_append_point(ctx, dpa, &p1, RT_FALSE);
+
+				break; /* substring complete */
+			}
+
+			/*
+			 * Didn't reach the 'end' point,
+			 * just copy second point
+			 */
+			else if ( to > tlength + slength )
+			{
+				ptarray_append_point(ctx, dpa, &p2, RT_FALSE);
+				goto END;
+			}
+
+			/*
+			 * 'to' point falls on this segment
+			 * Interpolate and break.
+			 */
+			else if ( to < tlength + slength )
+			{
+
+				RTDEBUG(3, " Seg contains our end");
+
+				dseg = (to - tlength) / slength;
+				interpolate_point4d(ctx, &p1, &p2, &pt, dseg);
+
+				ptarray_append_point(ctx, dpa, &pt, RT_FALSE);
+
+				break;
+			}
+
+			else
+			{
+				RTDEBUG(3, "Unhandled case");
+			}
+		}
+
+
+END:
+
+		tlength += slength;
+		memcpy(&p1, &p2, sizeof(RTPOINT4D));
+	}
+
+	RTDEBUGF(3, "Out of loop, ptarray has %d points", dpa->npoints);
+
+	return dpa;
+}
+
+/*
+ * Write into the *ret argument coordinates of the closes point on
+ * the given segment to the reference input point.
+ */
+void
+closest_point_on_segment(const RTCTX *ctx, const RTPOINT4D *p, const RTPOINT4D *A, const RTPOINT4D *B, RTPOINT4D *ret)
+{
+	double r;
+
+	if (  FP_EQUALS(A->x, B->x) && FP_EQUALS(A->y, B->y) )
+	{
+		*ret = *A;
+		return;
+	}
+
+	/*
+	 * We use comp.graphics.algorithms Frequently Asked Questions method
+	 *
+	 * (1)           AC dot AB
+	 *           r = ----------
+	 *                ||AB||^2
+	 *	r has the following meaning:
+	 *	r=0 P = A
+	 *	r=1 P = B
+	 *	r<0 P is on the backward extension of AB
+	 *	r>1 P is on the forward extension of AB
+	 *	0<r<1 P is interior to AB
+	 *
+	 */
+	r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
+
+	if (r<0)
+	{
+		*ret = *A;
+		return;
+	}
+	if (r>1)
+	{
+		*ret = *B;
+		return;
+	}
+
+	ret->x = A->x + ( (B->x - A->x) * r );
+	ret->y = A->y + ( (B->y - A->y) * r );
+	ret->z = A->z + ( (B->z - A->z) * r );
+	ret->m = A->m + ( (B->m - A->m) * r );
+}
+
+/*
+ * Given a point, returns the location of closest point on pointarray
+ * and, optionally, it's actual distance from the point array.
+ */
+double
+ptarray_locate_point(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT4D *p4d, double *mindistout, RTPOINT4D *proj4d)
+{
+	double mindist=-1;
+	double tlen, plen;
+	int t, seg=-1;
+	RTPOINT4D	start4d, end4d, projtmp;
+	RTPOINT2D proj, p;
+	const RTPOINT2D *start = NULL, *end = NULL;
+
+	/* Initialize our 2D copy of the input parameter */
+	p.x = p4d->x;
+	p.y = p4d->y;
+	
+	if ( ! proj4d ) proj4d = &projtmp;
+	
+	start = rt_getPoint2d_cp(ctx, pa, 0);
+	
+	/* If the pointarray has only one point, the nearest point is */
+	/* just that point */
+	if ( pa->npoints == 1 )
+	{
+		rt_getPoint4d_p(ctx, pa, 0, proj4d);
+		if ( mindistout )
+			*mindistout = distance2d_pt_pt(ctx, &p, start);
+		return 0.0;
+	}
+	
+	/* Loop through pointarray looking for nearest segment */
+	for (t=1; t<pa->npoints; t++)
+	{
+		double dist;
+		end = rt_getPoint2d_cp(ctx, pa, t);
+		dist = distance2d_pt_seg(ctx, &p, start, end);
+
+		if (t==1 || dist < mindist )
+		{
+			mindist = dist;
+			seg=t-1;
+		}
+
+		if ( mindist == 0 )
+		{
+			RTDEBUG(3, "Breaking on mindist=0");
+			break;
+		}
+
+		start = end;
+	}
+
+	if ( mindistout ) *mindistout = mindist;
+
+	RTDEBUGF(3, "Closest segment: %d", seg);
+	RTDEBUGF(3, "mindist: %g", mindist);
+
+	/*
+	 * We need to project the
+	 * point on the closest segment.
+	 */
+	rt_getPoint4d_p(ctx, pa, seg, &start4d);
+	rt_getPoint4d_p(ctx, pa, seg+1, &end4d);
+	closest_point_on_segment(ctx, p4d, &start4d, &end4d, proj4d);
+	
+	/* Copy 4D values into 2D holder */
+	proj.x = proj4d->x;
+	proj.y = proj4d->y;
+
+	RTDEBUGF(3, "Closest segment:%d, npoints:%d", seg, pa->npoints);
+
+	/* For robustness, force 1 when closest point == endpoint */
+	if ( (seg >= (pa->npoints-2)) && p2d_same(ctx, &proj, end) )
+	{
+		return 1.0;
+	}
+
+	RTDEBUGF(3, "Closest point on segment: %g,%g", proj.x, proj.y);
+
+	tlen = ptarray_length_2d(ctx, pa);
+
+	RTDEBUGF(3, "tlen %g", tlen);
+
+	/* Location of any point on a zero-length line is 0 */
+	/* See http://trac.osgeo.org/postgis/ticket/1772#comment:2 */
+	if ( tlen == 0 ) return 0;
+
+	plen=0;
+	start = rt_getPoint2d_cp(ctx, pa, 0);
+	for (t=0; t<seg; t++, start=end)
+	{
+		end = rt_getPoint2d_cp(ctx, pa, t+1);
+		plen += distance2d_pt_pt(ctx, start, end);
+
+		RTDEBUGF(4, "Segment %d made plen %g", t, plen);
+	}
+
+	plen+=distance2d_pt_pt(ctx, &proj, start);
+
+	RTDEBUGF(3, "plen %g, tlen %g", plen, tlen);
+
+	return plen/tlen;
+}
+
+/**
+ * @brief Longitude shift for a pointarray.
+ *  	Y remains the same
+ *  	X is converted:
+ *	 		from -180..180 to 0..360
+ *	 		from 0..360 to -180..180
+ *  	X < 0 becomes X + 360
+ *  	X > 180 becomes X - 360
+ */
+void
+ptarray_longitude_shift(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	int i;
+	double x;
+
+	for (i=0; i<pa->npoints; i++)
+	{
+		memcpy(&x, rt_getPoint_internal(ctx, pa, i), sizeof(double));
+		if ( x < 0 ) x+= 360;
+		else if ( x > 180 ) x -= 360;
+		memcpy(rt_getPoint_internal(ctx, pa, i), &x, sizeof(double));
+	}
+}
+
+
+/*
+ * Returns a RTPOINTARRAY with consecutive equal points
+ * removed. Equality test on all dimensions of input.
+ *
+ * Artays returns a newly allocated object.
+ *
+ */
+RTPOINTARRAY *
+ptarray_remove_repeated_points_minpoints(const RTCTX *ctx, const RTPOINTARRAY *in, double tolerance, int minpoints)
+{
+	RTPOINTARRAY* out;
+	size_t ptsize;
+	size_t ipn, opn;
+	const RTPOINT2D *last_point, *this_point;
+	double tolsq = tolerance * tolerance;
+
+	if ( minpoints < 1 ) minpoints = 1;
+
+	RTDEBUGF(3, "%s called", __func__);
+
+	/* Single or zero point arrays can't have duplicates */
+	if ( in->npoints < 3 ) return ptarray_clone_deep(ctx, in);
+
+	ptsize = ptarray_point_size(ctx, in);
+
+	RTDEBUGF(3, " ptsize: %d", ptsize);
+
+	/* Allocate enough space for all points */
+	out = ptarray_construct(ctx, RTFLAGS_GET_Z(in->flags),
+	                        RTFLAGS_GET_M(in->flags), in->npoints);
+
+	/* Now fill up the actual points (NOTE: could be optimized) */
+
+	opn=1;
+	memcpy(rt_getPoint_internal(ctx, out, 0), rt_getPoint_internal(ctx, in, 0), ptsize);
+	last_point = rt_getPoint2d_cp(ctx, in, 0);
+	RTDEBUGF(3, " first point copied, out points: %d", opn);
+	for ( ipn = 1; ipn < in->npoints; ++ipn)
+	{
+		this_point = rt_getPoint2d_cp(ctx, in, ipn);
+		if ( (ipn >= in->npoints-minpoints+1 && opn < minpoints) || 
+		     (tolerance == 0 && memcmp(rt_getPoint_internal(ctx, in, ipn-1), rt_getPoint_internal(ctx, in, ipn), ptsize) != 0) ||
+		     (tolerance > 0.0 && distance2d_sqr_pt_pt(ctx, last_point, this_point) > tolsq) )
+		{
+			/* The point is different from the previous,
+			 * we add it to output */
+			memcpy(rt_getPoint_internal(ctx, out, opn++), rt_getPoint_internal(ctx, in, ipn), ptsize);
+			last_point = this_point;
+			RTDEBUGF(3, " Point %d differs from point %d. Out points: %d", ipn, ipn-1, opn);
+		}
+	}
+
+	RTDEBUGF(3, " in:%d out:%d", out->npoints, opn);
+	out->npoints = opn;
+
+	return out;
+}
+
+RTPOINTARRAY *
+ptarray_remove_repeated_points(const RTCTX *ctx, const RTPOINTARRAY *in, double tolerance)
+{
+	return ptarray_remove_repeated_points_minpoints(ctx, in, tolerance, 2);
+}
+
+static void
+ptarray_dp_findsplit(const RTCTX *ctx, RTPOINTARRAY *pts, int p1, int p2, int *split, double *dist)
+{
+	int k;
+	const RTPOINT2D *pk, *pa, *pb;
+	double tmp, d;
+
+	RTDEBUG(4, "function called");
+
+	*split = p1;
+	d = -1;
+
+	if (p1 + 1 < p2)
+	{
+
+		pa = rt_getPoint2d_cp(ctx, pts, p1);
+		pb = rt_getPoint2d_cp(ctx, pts, p2);
+
+		RTDEBUGF(4, "P%d(%f,%f) to P%d(%f,%f)",
+		         p1, pa->x, pa->y, p2, pb->x, pb->y);
+
+		for (k=p1+1; k<p2; k++)
+		{
+			pk = rt_getPoint2d_cp(ctx, pts, k);
+
+			RTDEBUGF(4, "P%d(%f,%f)", k, pk->x, pk->y);
+
+			/* distance computation */
+			tmp = distance2d_sqr_pt_seg(ctx, pk, pa, pb);
+
+			if (tmp > d)
+			{
+				d = tmp;	/* record the maximum */
+				*split = k;
+
+				RTDEBUGF(4, "P%d is farthest (%g)", k, d);
+			}
+		}
+		*dist = d;
+
+	} /* length---should be redone if can == 0 */
+	else
+	{
+		RTDEBUG(3, "segment too short, no split/no dist");
+		*dist = -1;
+	}
+
+}
+
+RTPOINTARRAY *
+ptarray_simplify(const RTCTX *ctx, RTPOINTARRAY *inpts, double epsilon, unsigned int minpts)
+{
+	int *stack;			/* recursion stack */
+	int sp=-1;			/* recursion stack pointer */
+	int p1, split;
+	double dist;
+	RTPOINTARRAY *outpts;
+	RTPOINT4D pt;
+
+	double eps_sqr = epsilon * epsilon;
+
+	/* Allocate recursion stack */
+	stack = rtalloc(ctx, sizeof(int)*inpts->npoints);
+
+	p1 = 0;
+	stack[++sp] = inpts->npoints-1;
+
+	RTDEBUGF(2, "Input has %d pts and %d dims", inpts->npoints,
+	                                            RTFLAGS_NDIMS(inpts->flags));
+
+	/* Allocate output RTPOINTARRAY, and add first point. */
+	outpts = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(inpts->flags), RTFLAGS_GET_M(inpts->flags), inpts->npoints);
+	rt_getPoint4d_p(ctx, inpts, 0, &pt);
+	ptarray_append_point(ctx, outpts, &pt, RT_FALSE);
+
+	RTDEBUG(3, "Added P0 to simplified point array (size 1)");
+
+	do
+	{
+
+		ptarray_dp_findsplit(ctx, inpts, p1, stack[sp], &split, &dist);
+
+		RTDEBUGF(3, "Farthest point from P%d-P%d is P%d (dist. %g)", p1, stack[sp], split, dist);
+
+		if (dist > eps_sqr || ( outpts->npoints+sp+1 < minpts && dist >= 0 ) )
+		{
+			RTDEBUGF(4, "Added P%d to stack (outpts:%d)", split, sp);
+			stack[++sp] = split;
+		}
+		else
+		{
+			rt_getPoint4d_p(ctx, inpts, stack[sp], &pt);
+			RTDEBUGF(4, "npoints , minpoints %d %d", outpts->npoints, minpts);
+			ptarray_append_point(ctx, outpts, &pt, RT_FALSE);
+			
+			RTDEBUGF(4, "Added P%d to simplified point array (size: %d)", stack[sp], outpts->npoints);
+
+			p1 = stack[sp--];
+		}
+
+		RTDEBUGF(4, "stack pointer = %d", sp);
+	}
+	while (! (sp<0) );
+
+	rtfree(ctx, stack);
+	return outpts;
+}
+
+/**
+* Find the 2d length of the given #RTPOINTARRAY, using circular
+* arc interpolation between each coordinate triple.
+* Length(A1, A2, A3, A4, A5) = Length(A1, A2, A3)+Length(A3, A4, A5)
+*/
+double
+ptarray_arc_length_2d(const RTCTX *ctx, const RTPOINTARRAY *pts)
+{
+	double dist = 0.0;
+	int i;
+	const RTPOINT2D *a1;
+	const RTPOINT2D *a2;
+	const RTPOINT2D *a3;
+
+	if ( pts->npoints % 2 != 1 )
+        rterror(ctx, "arc point array with even number of points");
+        
+	a1 = rt_getPoint2d_cp(ctx, pts, 0);
+	
+	for ( i=2; i < pts->npoints; i += 2 )
+	{
+    	a2 = rt_getPoint2d_cp(ctx, pts, i-1);
+		a3 = rt_getPoint2d_cp(ctx, pts, i);
+		dist += rt_arc_length(ctx, a1, a2, a3);
+		a1 = a3;
+	}
+	return dist;
+}
+
+/**
+* Find the 2d length of the given #RTPOINTARRAY (even if it's 3d)
+*/
+double
+ptarray_length_2d(const RTCTX *ctx, const RTPOINTARRAY *pts)
+{
+	double dist = 0.0;
+	int i;
+	const RTPOINT2D *frm;
+	const RTPOINT2D *to;
+
+	if ( pts->npoints < 2 ) return 0.0;
+
+	frm = rt_getPoint2d_cp(ctx, pts, 0);
+	
+	for ( i=1; i < pts->npoints; i++ )
+	{
+		to = rt_getPoint2d_cp(ctx, pts, i);
+
+		dist += sqrt( ((frm->x - to->x)*(frm->x - to->x))  +
+		              ((frm->y - to->y)*(frm->y - to->y)) );
+		
+		frm = to;
+	}
+	return dist;
+}
+
+/**
+* Find the 3d/2d length of the given #RTPOINTARRAY
+* (depending on its dimensionality)
+*/
+double
+ptarray_length(const RTCTX *ctx, const RTPOINTARRAY *pts)
+{
+	double dist = 0.0;
+	int i;
+	RTPOINT3DZ frm;
+	RTPOINT3DZ to;
+
+	if ( pts->npoints < 2 ) return 0.0;
+
+	/* compute 2d length if 3d is not available */
+	if ( ! RTFLAGS_GET_Z(pts->flags) ) return ptarray_length_2d(ctx, pts);
+
+	rt_getPoint3dz_p(ctx, pts, 0, &frm);
+	for ( i=1; i < pts->npoints; i++ )
+	{
+		rt_getPoint3dz_p(ctx, pts, i, &to);
+		dist += sqrt( ((frm.x - to.x)*(frm.x - to.x)) +
+		              ((frm.y - to.y)*(frm.y - to.y)) +
+		              ((frm.z - to.z)*(frm.z - to.z)) );
+		frm = to;
+	}
+	return dist;
+}
+
+
+/*
+ * Get a pointer to nth point of a RTPOINTARRAY.
+ *
+ * Casting to returned pointer to RTPOINT2D* should be safe,
+ * as gserialized format artays keeps the RTPOINTARRAY pointer
+ * aligned to double boundary.
+ */
+uint8_t *
+rt_getPoint_internal(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	size_t size;
+	uint8_t *ptr;
+
+#if PARANOIA_LEVEL > 0
+	if ( pa == NULL )
+	{
+		rterror(ctx, "rt_getPoint got NULL pointarray");
+		return NULL;
+	}
+	
+	RTDEBUGF(5, "(n=%d, pa.npoints=%d, pa.maxpoints=%d)",n,pa->npoints,pa->maxpoints);
+
+	if ( ( n < 0 ) || 
+	     ( n > pa->npoints ) ||
+	     ( n >= pa->maxpoints ) )
+	{
+		rterror(ctx, "rt_getPoint_internal called outside of ptarray range (n=%d, pa.npoints=%d, pa.maxpoints=%d)",n,pa->npoints,pa->maxpoints);
+		return NULL; /*error */
+	}
+#endif
+
+	size = ptarray_point_size(ctx, pa);
+	
+	ptr = pa->serialized_pointlist + size * n;
+	if ( RTFLAGS_NDIMS(pa->flags) == 2)
+	{
+		RTDEBUGF(5, "point = %g %g", *((double*)(ptr)), *((double*)(ptr+8)));
+	}
+	else if ( RTFLAGS_NDIMS(pa->flags) == 3)
+	{
+		RTDEBUGF(5, "point = %g %g %g", *((double*)(ptr)), *((double*)(ptr+8)), *((double*)(ptr+16)));
+	}
+	else if ( RTFLAGS_NDIMS(pa->flags) == 4)
+	{
+		RTDEBUGF(5, "point = %g %g %g %g", *((double*)(ptr)), *((double*)(ptr+8)), *((double*)(ptr+16)), *((double*)(ptr+24)));
+	}
+
+	return ptr;
+}
+
+
+/**
+ * Affine transform a pointarray.
+ */
+void
+ptarray_affine(const RTCTX *ctx, RTPOINTARRAY *pa, const RTAFFINE *a)
+{
+	int i;
+	double x,y,z;
+	RTPOINT4D p4d;
+
+	RTDEBUG(2, "rtgeom_affine_ptarray start");
+
+	if ( RTFLAGS_GET_Z(pa->flags) )
+	{
+		RTDEBUG(3, " has z");
+
+		for (i=0; i<pa->npoints; i++)
+		{
+			rt_getPoint4d_p(ctx, pa, i, &p4d);
+			x = p4d.x;
+			y = p4d.y;
+			z = p4d.z;
+			p4d.x = a->afac * x + a->bfac * y + a->cfac * z + a->xoff;
+			p4d.y = a->dfac * x + a->efac * y + a->ffac * z + a->yoff;
+			p4d.z = a->gfac * x + a->hfac * y + a->ifac * z + a->zoff;
+			ptarray_set_point4d(ctx, pa, i, &p4d);
+
+			RTDEBUGF(3, " POINT %g %g %g => %g %g %g", x, y, x, p4d.x, p4d.y, p4d.z);
+		}
+	}
+	else
+	{
+		RTDEBUG(3, " doesn't have z");
+
+		for (i=0; i<pa->npoints; i++)
+		{
+			rt_getPoint4d_p(ctx, pa, i, &p4d);
+			x = p4d.x;
+			y = p4d.y;
+			p4d.x = a->afac * x + a->bfac * y + a->xoff;
+			p4d.y = a->dfac * x + a->efac * y + a->yoff;
+			ptarray_set_point4d(ctx, pa, i, &p4d);
+
+			RTDEBUGF(3, " POINT %g %g %g => %g %g %g", x, y, x, p4d.x, p4d.y, p4d.z);
+		}
+	}
+
+	RTDEBUG(3, "rtgeom_affine_ptarray end");
+
+}
+
+/**
+ * Scale a pointarray.
+ */
+void
+ptarray_scale(const RTCTX *ctx, RTPOINTARRAY *pa, const RTPOINT4D *fact)
+{
+  int i;
+  RTPOINT4D p4d;
+
+  RTDEBUG(3, "ptarray_scale start");
+
+  for (i=0; i<pa->npoints; ++i)
+  {
+    rt_getPoint4d_p(ctx, pa, i, &p4d);
+    p4d.x *= fact->x;
+    p4d.y *= fact->y;
+    p4d.z *= fact->z;
+    p4d.m *= fact->m;
+    ptarray_set_point4d(ctx, pa, i, &p4d);
+  }
+
+  RTDEBUG(3, "ptarray_scale end");
+
+}
+
+int
+ptarray_startpoint(const RTCTX *ctx, const RTPOINTARRAY* pa, RTPOINT4D* pt)
+{
+	return rt_getPoint4d_p(ctx, pa, 0, pt);
+}
+
+
+
+
+/*
+ * Stick an array of points to the given gridspec.
+ * Return "gridded" points in *outpts and their number in *outptsn.
+ *
+ * Two consecutive points falling on the same grid cell are collapsed
+ * into one single point.
+ *
+ */
+RTPOINTARRAY *
+ptarray_grid(const RTCTX *ctx, const RTPOINTARRAY *pa, const gridspec *grid)
+{
+	RTPOINT4D pt;
+	int ipn; /* input point numbers */
+	RTPOINTARRAY *dpa;
+
+	RTDEBUGF(2, "ptarray_grid called on %p", pa);
+
+	dpa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(pa->flags),RTFLAGS_GET_M(pa->flags), pa->npoints);
+
+	for (ipn=0; ipn<pa->npoints; ++ipn)
+	{
+
+		rt_getPoint4d_p(ctx, pa, ipn, &pt);
+
+		if ( grid->xsize )
+			pt.x = rint((pt.x - grid->ipx)/grid->xsize) *
+			         grid->xsize + grid->ipx;
+
+		if ( grid->ysize )
+			pt.y = rint((pt.y - grid->ipy)/grid->ysize) *
+			         grid->ysize + grid->ipy;
+
+		if ( RTFLAGS_GET_Z(pa->flags) && grid->zsize )
+			pt.z = rint((pt.z - grid->ipz)/grid->zsize) *
+			         grid->zsize + grid->ipz;
+
+		if ( RTFLAGS_GET_M(pa->flags) && grid->msize )
+			pt.m = rint((pt.m - grid->ipm)/grid->msize) *
+			         grid->msize + grid->ipm;
+
+		ptarray_append_point(ctx, dpa, &pt, RT_FALSE);
+
+	}
+
+	return dpa;
+}
+
+int 
+ptarray_npoints_in_rect(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTGBOX *gbox)
+{
+	const RTPOINT2D *pt;
+	int n = 0;
+	int i;
+	for ( i = 0; i < pa->npoints; i++ )
+	{
+		pt = rt_getPoint2d_cp(ctx, pa, i);
+		if ( gbox_contains_point2d(ctx, gbox, pt) )
+			n++;
+	}
+	return n;
+}
+
+
diff --git a/src/rtalgorithm.c b/src/rtalgorithm.c
new file mode 100644
index 0000000..6395f0b
--- /dev/null
+++ b/src/rtalgorithm.c
@@ -0,0 +1,903 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2008 Paul Ramsey
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include <ctype.h> /* for tolower */
+
+
+/**
+* Returns -1 if n < 0.0 and 1 if n > 0.0
+*/
+int signum(const RTCTX *ctx, double n)
+{
+	if( n < 0 ) return -1;
+	if( n > 0 ) return 1;
+	return 0;
+}
+
+int
+p4d_same(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2)
+{
+	if( FP_EQUALS(p1->x,p2->x) && FP_EQUALS(p1->y,p2->y) && FP_EQUALS(p1->z,p2->z) && FP_EQUALS(p1->m,p2->m) )
+		return RT_TRUE;
+	else
+		return RT_FALSE;
+}
+
+int
+p3d_same(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2)
+{
+	if( FP_EQUALS(p1->x,p2->x) && FP_EQUALS(p1->y,p2->y) && FP_EQUALS(p1->z,p2->z) )
+		return RT_TRUE;
+	else
+		return RT_FALSE;
+}
+
+int
+p2d_same(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2)
+{
+	if( FP_EQUALS(p1->x,p2->x) && FP_EQUALS(p1->y,p2->y) )
+		return RT_TRUE;
+	else
+		return RT_FALSE;
+}
+
+/**
+* rt_segment_side(ctx)
+*
+* Return -1  if point Q is left of segment P
+* Return  1  if point Q is right of segment P
+* Return  0  if point Q in on segment P
+*/
+int rt_segment_side(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *q)
+{
+	double side = ( (q->x - p1->x) * (p2->y - p1->y) - (p2->x - p1->x) * (q->y - p1->y) );
+	if ( side == 0.0 )
+		return 0;
+	else
+		return signum(ctx, side);
+}
+
+/**
+* Returns the length of a linear segment
+*/
+double
+rt_seg_length(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2)
+{
+	return sqrt((A1->x-A2->x)*(A1->x-A2->x)+(A1->y-A2->y)*(A1->y-A2->y));
+}
+
+/**
+* Returns true if P is on the same side of the plane partition 
+* defined by A1/A3 as A2 is. Only makes sense if P has already been
+* determined to be on the circle defined by A1/A2/A3.
+*/
+int
+rt_pt_in_arc(const RTCTX *ctx, const RTPOINT2D *P, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3)
+{
+	return rt_segment_side(ctx, A1, A3, A2) == rt_segment_side(ctx, A1, A3, P);
+}
+
+/**
+* Returns true if P is between A1/A2. Only makes sense if P has already been
+* deterined to be on the line defined by A1/A2.
+*/
+int
+rt_pt_in_seg(const RTCTX *ctx, const RTPOINT2D *P, const RTPOINT2D *A1, const RTPOINT2D *A2)
+{
+	return ((A1->x <= P->x && P->x < A2->x) || (A1->x >= P->x && P->x > A2->x)) ||
+	       ((A1->y <= P->y && P->y < A2->y) || (A1->y >= P->y && P->y > A2->y));
+}
+
+/**
+* Returns true if arc A is actually a point (all vertices are the same) .
+*/
+int
+rt_arc_is_pt(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3)
+{
+	if ( A1->x == A2->x && A2->x == A3->x && 
+	     A1->y == A2->y && A2->y == A3->y )
+		return RT_TRUE;
+	else
+		return RT_FALSE;
+}
+
+/**
+* Returns the length of a circular arc segment
+*/
+double
+rt_arc_length(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3)
+{
+	RTPOINT2D C;
+	double radius_A, circumference_A;
+	int a2_side, clockwise;
+	double a1, a3;
+	double angle;
+	
+	if ( rt_arc_is_pt(ctx, A1, A2, A3) )
+		return 0.0;
+	
+	radius_A = rt_arc_center(ctx, A1, A2, A3, &C);
+
+	/* Co-linear! Return linear distance! */
+	if ( radius_A < 0 ) 
+	{
+        double dx = A1->x - A3->x;
+        double dy = A1->y - A3->y;
+		return sqrt(dx*dx + dy*dy);
+	}
+	
+	/* Closed circle! Return the circumference! */
+	circumference_A = M_PI * 2 * radius_A;
+	if ( p2d_same(ctx, A1, A3) )
+		return circumference_A;
+	
+	/* Determine the orientation of the arc */
+	a2_side = rt_segment_side(ctx, A1, A3, A2);
+
+	/* The side of the A1/A3 line that A2 falls on dictates the sweep  
+	   direction from A1 to A3. */
+	if ( a2_side == -1 ) 
+		clockwise = RT_TRUE;
+	else 
+		clockwise = RT_FALSE;
+		
+	/* Angles of each point that defines the arc section */
+	a1 = atan2(A1->y - C.y, A1->x - C.x);
+	a3 = atan2(A3->y - C.y, A3->x - C.x);
+
+	/* What's the sweep from A1 to A3? */
+	if ( clockwise )
+	{
+		if ( a1 > a3 )
+			angle = a1 - a3;
+		else
+			angle = 2*M_PI + a1 - a3; 
+	}
+	else
+	{
+		if ( a3 > a1 )
+			angle = a3 - a1;
+		else
+			angle = 2*M_PI + a3 - a1; 			
+	}
+
+	/* Length as proportion of circumference */
+	return circumference_A * (angle / (2*M_PI));
+}
+
+int rt_arc_side(const RTCTX *ctx, const RTPOINT2D *A1, const RTPOINT2D *A2, const RTPOINT2D *A3, const RTPOINT2D *Q)
+{
+	RTPOINT2D C;
+	double radius_A;
+	double side_Q, side_A2;
+	double d;
+	
+	side_Q = rt_segment_side(ctx, A1, A3, Q);
+	radius_A = rt_arc_center(ctx, A1, A2, A3, &C);
+	side_A2 = rt_segment_side(ctx, A1, A3, A2);
+	
+	/* Linear case */
+	if ( radius_A < 0 )
+		return side_Q;
+		
+	d = distance2d_pt_pt(ctx, Q, &C);
+	
+	/* Q is on the arc boundary */
+	if ( d == radius_A && side_Q == side_A2 )
+	{	
+		return 0;
+	}
+	
+	/* Q on A1-A3 line, so its on opposite side to A2 */
+	if ( side_Q == 0 )
+	{
+		return -1 * side_A2;
+	}
+	
+	/* 
+	* Q is inside the arc boundary, so it's not on the side we 
+	* might think from examining only the end points
+	*/
+	if ( d < radius_A && side_Q == side_A2 )
+	{
+		side_Q *= -1;
+	}
+	
+	return side_Q;
+}
+
+/**
+* Determines the center of the circle defined by the three given points.
+* In the event the circle is complete, the midpoint of the segment defined
+* by the first and second points is returned.  If the points are colinear,
+* as determined by equal slopes, then NULL is returned.  If the interior
+* point is coincident with either end point, they are taken as colinear.
+*/
+double
+rt_arc_center(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *p3, RTPOINT2D *result)	
+{
+	RTPOINT2D c;
+	double cx, cy, cr;
+	double dx21, dy21, dx31, dy31, h21, h31, d;
+
+	c.x = c.y = 0.0;
+
+	RTDEBUGF(2, "rt_arc_center called (%.16f,%.16f), (%.16f,%.16f), (%.16f,%.16f).", p1->x, p1->y, p2->x, p2->y, p3->x, p3->y);
+
+	/* Closed circle */
+	if (fabs(p1->x - p3->x) < EPSILON_SQLMM &&
+	    fabs(p1->y - p3->y) < EPSILON_SQLMM)
+	{
+		cx = p1->x + (p2->x - p1->x) / 2.0;
+		cy = p1->y + (p2->y - p1->y) / 2.0;
+		c.x = cx;
+		c.y = cy;
+		*result = c;
+		cr = sqrt(pow(cx - p1->x, 2.0) + pow(cy - p1->y, 2.0));
+		return cr;
+	}
+
+	/* Using cartesian eguations from page https://en.wikipedia.org/wiki/Circumscribed_circle */
+	dx21 = p2->x - p1->x;
+	dy21 = p2->y - p1->y;
+	dx31 = p3->x - p1->x;
+	dy31 = p3->y - p1->y;
+
+	h21 = pow(dx21, 2.0) + pow(dy21, 2.0);
+	h31 = pow(dx31, 2.0) + pow(dy31, 2.0);
+
+	/* 2 * |Cross product|, d<0 means clockwise and d>0 counterclockwise sweeping angle */
+	d = 2 * (dx21 * dy31 - dx31 * dy21);
+
+	/* Check colinearity, |Cross product| = 0 */
+	if (fabs(d) < EPSILON_SQLMM)
+		return -1.0;
+
+	/* Calculate centroid coordinates and radius */
+	cx = p1->x + (h21 * dy31 - h31 * dy21) / d;
+	cy = p1->y - (h21 * dx31 - h31 * dx21) / d;
+	c.x = cx;
+	c.y = cy;
+	*result = c;
+	cr = sqrt(pow(cx - p1->x, 2) + pow(cy - p1->y, 2));
+
+	RTDEBUGF(2, "rt_arc_center center is (%.16f,%.16f)", result->x, result->y);
+
+	return cr;
+}
+
+int
+pt_in_ring_2d(const RTCTX *ctx, const RTPOINT2D *p, const RTPOINTARRAY *ring)
+{
+	int cn = 0;    /* the crossing number counter */
+	int i;
+	const RTPOINT2D *v1, *v2;
+	const RTPOINT2D *first, *last;
+
+	first = rt_getPoint2d_cp(ctx, ring, 0);
+	last = rt_getPoint2d_cp(ctx, ring, ring->npoints-1);
+	if ( memcmp(first, last, sizeof(RTPOINT2D)) )
+	{
+		rterror(ctx, "pt_in_ring_2d: V[n] != V[0] (%g %g != %g %g)",
+		        first->x, first->y, last->x, last->y);
+		return RT_FALSE;
+
+	}
+
+	RTDEBUGF(2, "pt_in_ring_2d called with point: %g %g", p->x, p->y);
+	/* printPA(ctx, ring); */
+
+	/* loop through all edges of the polygon */
+	v1 = rt_getPoint2d_cp(ctx, ring, 0);
+	for (i=0; i<ring->npoints-1; i++)
+	{
+		double vt;
+		v2 = rt_getPoint2d_cp(ctx, ring, i+1);
+
+		/* edge from vertex i to vertex i+1 */
+		if
+		(
+		    /* an upward crossing */
+		    ((v1->y <= p->y) && (v2->y > p->y))
+		    /* a downward crossing */
+		    || ((v1->y > p->y) && (v2->y <= p->y))
+		)
+		{
+
+			vt = (double)(p->y - v1->y) / (v2->y - v1->y);
+
+			/* P->x <intersect */
+			if (p->x < v1->x + vt * (v2->x - v1->x))
+			{
+				/* a valid crossing of y=p->y right of p->x */
+				++cn;
+			}
+		}
+		v1 = v2;
+	}
+
+	RTDEBUGF(3, "pt_in_ring_2d returning %d", cn&1);
+
+	return (cn&1);    /* 0 if even (out), and 1 if odd (in) */
+}
+
+
+static int 
+rt_seg_interact(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *q1, const RTPOINT2D *q2)
+{
+	double minq=FP_MIN(q1->x,q2->x);
+	double maxq=FP_MAX(q1->x,q2->x);
+	double minp=FP_MIN(p1->x,p2->x);
+	double maxp=FP_MAX(p1->x,p2->x);
+
+	if (FP_GT(minp,maxq) || FP_LT(maxp,minq))
+		return RT_FALSE;
+
+	minq=FP_MIN(q1->y,q2->y);
+	maxq=FP_MAX(q1->y,q2->y);
+	minp=FP_MIN(p1->y,p2->y);
+	maxp=FP_MAX(p1->y,p2->y);
+
+	if (FP_GT(minp,maxq) || FP_LT(maxp,minq))
+		return RT_FALSE;
+
+	return RT_TRUE;
+}
+
+/**
+** @brief returns the kind of #RTCG_SEGMENT_INTERSECTION_TYPE  behavior of lineseg 1 (constructed from p1 and p2) and lineseg 2 (constructed from q1 and q2)
+**	@param p1 start point of first straight linesegment
+**	@param p2 end point of first straight linesegment
+**	@param q1 start point of second line segment
+**	@param q2 end point of second line segment
+**	@return a #RTCG_SEGMENT_INTERSECTION_TYPE
+** 	Returns one of
+**		SEG_ERROR = -1,
+**		SEG_NO_INTERSECTION = 0,
+**		SEG_COLINEAR = 1,
+**		SEG_CROSS_LEFT = 2,
+**		SEG_CROSS_RIGHT = 3,
+*/
+int rt_segment_intersects(const RTCTX *ctx, const RTPOINT2D *p1, const RTPOINT2D *p2, const RTPOINT2D *q1, const RTPOINT2D *q2)
+{
+
+	int pq1, pq2, qp1, qp2;
+
+	/* No envelope interaction => we are done. */
+	if (!rt_seg_interact(ctx, p1, p2, q1, p2))
+	{
+		return SEG_NO_INTERSECTION;
+	}
+
+	/* Are the start and end points of q on the same side of p? */
+	pq1=rt_segment_side(ctx, p1,p2,q1);
+	pq2=rt_segment_side(ctx, p1,p2,q2);
+	if ((pq1>0 && pq2>0) || (pq1<0 && pq2<0))
+	{
+		return SEG_NO_INTERSECTION;
+	}
+
+	/* Are the start and end points of p on the same side of q? */
+	qp1=rt_segment_side(ctx, q1,q2,p1);
+	qp2=rt_segment_side(ctx, q1,q2,p2);
+	if ( (qp1 > 0.0 && qp2 > 0.0) || (qp1 < 0.0 && qp2 < 0.0) )
+	{
+		return SEG_NO_INTERSECTION;
+	}
+
+	/* Nobody is on one side or another? Must be colinear. */
+	if ( pq1 == 0.0 && pq2 == 0.0 && qp1 == 0.0 && qp2 == 0.0 )
+	{
+		return SEG_COLINEAR;
+	}
+
+	/*
+	** When one end-point touches, the sidedness is determined by the
+	** location of the other end-point. Only touches by the first point
+	** will be considered "real" to avoid double counting.
+	*/
+	RTDEBUGF(4, "pq1=%.15g pq2=%.15g", pq1, pq2);
+	RTDEBUGF(4, "qp1=%.15g qp2=%.15g", qp1, qp2);
+
+	/* Second point of p or q touches, it's not a crossing. */
+	if ( pq2 == 0 || qp2 == 0 )
+	{
+		return SEG_NO_INTERSECTION;
+	}
+
+	/* First point of p touches, it's a "crossing". */
+	if ( pq1 == 0 )
+	{
+		if ( pq2 > 0 )
+			return SEG_CROSS_RIGHT;
+		else
+			return SEG_CROSS_LEFT;
+	}
+
+	/* First point of q touches, it's a crossing. */
+	if ( qp1 == 0 )
+	{
+		if ( pq1 < pq2 )
+			return SEG_CROSS_RIGHT;
+		else
+			return SEG_CROSS_LEFT;
+	}
+
+	/* The segments cross, what direction is the crossing? */
+	if ( pq1 < pq2 )
+		return SEG_CROSS_RIGHT;
+	else
+		return SEG_CROSS_LEFT;
+
+	/* This should never happen! */
+	return SEG_ERROR;
+}
+
+/**
+** @brief rtline_crossing_direction: returns the kind of #RTCG_LINE_CROSS_TYPE behavior  of 2 linestrings
+** @param l1 first line string
+** @param l2 second line string
+** @return a #RTCG_LINE_CROSS_TYPE
+**   LINE_NO_CROSS = 0
+**   LINE_CROSS_LEFT = -1
+**   LINE_CROSS_RIGHT = 1
+**   LINE_MULTICROSS_END_LEFT = -2
+**   LINE_MULTICROSS_END_RIGHT = 2
+**   LINE_MULTICROSS_END_SAME_FIRST_LEFT = -3
+**   LINE_MULTICROSS_END_SAME_FIRST_RIGHT = 3
+**
+*/
+int rtline_crossing_direction(const RTCTX *ctx, const RTLINE *l1, const RTLINE *l2)
+{
+	int i = 0, j = 0;
+	const RTPOINT2D *p1, *p2, *q1, *q2;
+	RTPOINTARRAY *pa1 = NULL, *pa2 = NULL;
+	int cross_left = 0;
+	int cross_right = 0;
+	int first_cross = 0;
+	int this_cross = 0;
+
+	pa1 = (RTPOINTARRAY*)l1->points;
+	pa2 = (RTPOINTARRAY*)l2->points;
+
+	/* One-point lines can't intersect (and shouldn't exist). */
+	if ( pa1->npoints < 2 || pa2->npoints < 2 )
+		return LINE_NO_CROSS;
+
+	RTDEBUGF(4, "l1 = %s", rtgeom_to_ewkt(ctx, (RTGEOM*)l1));
+	RTDEBUGF(4, "l2 = %s", rtgeom_to_ewkt(ctx, (RTGEOM*)l2));
+
+	/* Initialize first point of q */
+	q1 = rt_getPoint2d_cp(ctx, pa2, 0);
+
+	for ( i = 1; i < pa2->npoints; i++ )
+	{
+
+		/* Update second point of q to next value */
+		q2 = rt_getPoint2d_cp(ctx, pa2, i);
+
+		/* Initialize first point of p */
+		p1 = rt_getPoint2d_cp(ctx, pa1, 0);
+
+		for ( j = 1; j < pa1->npoints; j++ )
+		{
+
+			/* Update second point of p to next value */
+			p2 = rt_getPoint2d_cp(ctx, pa1, j);
+
+			this_cross = rt_segment_intersects(ctx, p1, p2, q1, q2);
+
+			RTDEBUGF(4, "i=%d, j=%d (%.8g %.8g, %.8g %.8g)", this_cross, i, j, p1->x, p1->y, p2->x, p2->y);
+
+			if ( this_cross == SEG_CROSS_LEFT )
+			{
+				RTDEBUG(4,"this_cross == SEG_CROSS_LEFT");
+				cross_left++;
+				if ( ! first_cross )
+					first_cross = SEG_CROSS_LEFT;
+			}
+
+			if ( this_cross == SEG_CROSS_RIGHT )
+			{
+				RTDEBUG(4,"this_cross == SEG_CROSS_RIGHT");
+				cross_right++;
+				if ( ! first_cross )
+					first_cross = SEG_CROSS_LEFT;
+			}
+
+			/*
+			** Crossing at a co-linearity can be turned handled by extending
+			** segment to next vertext and seeing if the end points straddle
+			** the co-linear segment.
+			*/
+			if ( this_cross == SEG_COLINEAR )
+			{
+				RTDEBUG(4,"this_cross == SEG_COLINEAR");
+				/* TODO: Add logic here and in segment_intersects()
+				continue;
+				*/
+			}
+
+			RTDEBUG(4,"this_cross == SEG_NO_INTERSECTION");
+
+			/* Turn second point of p into first point */
+			p1 = p2;
+
+		}
+
+		/* Turn second point of q into first point */
+		q1 = q2;
+
+	}
+
+	RTDEBUGF(4, "first_cross=%d, cross_left=%d, cross_right=%d", first_cross, cross_left, cross_right);
+
+	if ( !cross_left && !cross_right )
+		return LINE_NO_CROSS;
+
+	if ( !cross_left && cross_right == 1 )
+		return LINE_CROSS_RIGHT;
+
+	if ( !cross_right && cross_left == 1 )
+		return LINE_CROSS_LEFT;
+
+	if ( cross_left - cross_right == 1 )
+		return LINE_MULTICROSS_END_LEFT;
+
+	if ( cross_left - cross_right == -1 )
+		return LINE_MULTICROSS_END_RIGHT;
+
+	if ( cross_left - cross_right == 0 && first_cross == SEG_CROSS_LEFT )
+		return LINE_MULTICROSS_END_SAME_FIRST_LEFT;
+
+	if ( cross_left - cross_right == 0 && first_cross == SEG_CROSS_RIGHT )
+		return LINE_MULTICROSS_END_SAME_FIRST_RIGHT;
+
+	return LINE_NO_CROSS;
+
+}
+
+
+
+
+
+static char *base32 = "0123456789bcdefghjkmnpqrstuvwxyz";
+
+/*
+** Calculate the geohash, iterating downwards and gaining precision.
+** From geohash-native.c, (c) 2008 David Troy <dave at roundhousetech.com>
+** Released under the MIT License.
+*/
+char * geohash_point(const RTCTX *ctx, double longitude, double latitude, int precision)
+{
+	int is_even=1, i=0;
+	double lat[2], lon[2], mid;
+	char bits[] = {16,8,4,2,1};
+	int bit=0, ch=0;
+	char *geohash = NULL;
+
+	geohash = rtalloc(ctx, precision + 1);
+
+	lat[0] = -90.0;
+	lat[1] = 90.0;
+	lon[0] = -180.0;
+	lon[1] = 180.0;
+
+	while (i < precision)
+	{
+		if (is_even)
+		{
+			mid = (lon[0] + lon[1]) / 2;
+			if (longitude >= mid)
+			{
+				ch |= bits[bit];
+				lon[0] = mid;
+			}
+			else
+			{
+				lon[1] = mid;
+			}
+		}
+		else
+		{
+			mid = (lat[0] + lat[1]) / 2;
+			if (latitude >= mid)
+			{
+				ch |= bits[bit];
+				lat[0] = mid;
+			}
+			else
+			{
+				lat[1] = mid;
+			}
+		}
+
+		is_even = !is_even;
+		if (bit < 4)
+		{
+			bit++;
+		}
+		else
+		{
+			geohash[i++] = base32[ch];
+			bit = 0;
+			ch = 0;
+		}
+	}
+	geohash[i] = 0;
+	return geohash;
+}
+
+
+/*
+** Calculate the geohash, iterating downwards and gaining precision.
+** From geohash-native.c, (c) 2008 David Troy <dave at roundhousetech.com>
+** Released under the MIT License.
+*/
+unsigned int geohash_point_as_int(const RTCTX *ctx, RTPOINT2D *pt)
+{
+	int is_even=1;
+	double lat[2], lon[2], mid;
+	int bit=32;
+	unsigned int ch = 0;
+
+	double longitude = pt->x;
+	double latitude = pt->y;
+
+	lat[0] = -90.0;
+	lat[1] = 90.0;
+	lon[0] = -180.0;
+	lon[1] = 180.0;
+
+	while (--bit >= 0)
+	{
+		if (is_even)
+		{
+			mid = (lon[0] + lon[1]) / 2;
+			if (longitude > mid)
+			{
+				ch |= 0x0001 << bit;
+				lon[0] = mid;
+			}
+			else
+			{
+				lon[1] = mid;
+			}
+		}
+		else
+		{
+			mid = (lat[0] + lat[1]) / 2;
+			if (latitude > mid)
+			{
+				ch |= 0x0001 << bit;
+				lat[0] = mid;
+			}
+			else
+			{
+				lat[1] = mid;
+			}
+		}
+
+		is_even = !is_even;
+	}
+	return ch;
+}
+
+/*
+** Decode a GeoHash into a bounding box. The lat and lon arguments should
+** both be passed as double arrays of length 2 at a minimum where the values
+** set in them will be the southwest and northeast coordinates of the bounding
+** box accordingly. A precision less than 0 indicates that the entire length
+** of the GeoHash should be used.
+*/
+void decode_geohash_bbox(const RTCTX *ctx, char *geohash, double *lat, double *lon, int precision)
+{
+	int i, j, hashlen;
+	char c, cd, mask, is_even = 1;
+	static char bits[] = {16, 8, 4, 2, 1};
+
+	lat[0] = -90.0;
+	lat[1] = 90.0;
+	lon[0] = -180.0;
+	lon[1] = 180.0;
+
+	hashlen = strlen(geohash);
+
+	if (precision < 0 || precision > hashlen)
+	{
+		precision = hashlen;
+	}
+
+	for (i = 0; i < precision; i++)
+	{
+		c = tolower(geohash[i]);
+		cd = strchr(base32, c) - base32;
+
+		for (j = 0; j < 5; j++)
+		{
+			mask = bits[j];
+			if (is_even)
+			{
+				lon[!(cd & mask)] = (lon[0] + lon[1]) / 2;
+			}
+			else
+			{
+				lat[!(cd & mask)] = (lat[0] + lat[1]) / 2;
+			}
+			is_even = !is_even;
+		}
+	}
+}
+
+int rtgeom_geohash_precision(const RTCTX *ctx, RTGBOX bbox, RTGBOX *bounds)
+{
+	double minx, miny, maxx, maxy;
+	double latmax, latmin, lonmax, lonmin;
+	double lonwidth, latwidth;
+	double latmaxadjust, lonmaxadjust, latminadjust, lonminadjust;
+	int precision = 0;
+
+	/* Get the bounding box, return error if things don't work out. */
+	minx = bbox.xmin;
+	miny = bbox.ymin;
+	maxx = bbox.xmax;
+	maxy = bbox.ymax;
+
+	if ( minx == maxx && miny == maxy )
+	{
+		/* It's a point. Doubles have 51 bits of precision.
+		** 2 * 51 / 5 == 20 */
+		return 20;
+	}
+
+	lonmin = -180.0;
+	latmin = -90.0;
+	lonmax = 180.0;
+	latmax = 90.0;
+
+	/* Shrink a world bounding box until one of the edges interferes with the
+	** bounds of our rectangle. */
+	while ( 1 )
+	{
+		lonwidth = lonmax - lonmin;
+		latwidth = latmax - latmin;
+		latmaxadjust = lonmaxadjust = latminadjust = lonminadjust = 0.0;
+
+		if ( minx > lonmin + lonwidth / 2.0 )
+		{
+			lonminadjust = lonwidth / 2.0;
+		}
+		else if ( maxx < lonmax - lonwidth / 2.0 )
+		{
+			lonmaxadjust = -1 * lonwidth / 2.0;
+		}
+		if ( miny > latmin + latwidth / 2.0 )
+		{
+			latminadjust = latwidth / 2.0;
+		}
+		else if (maxy < latmax - latwidth / 2.0 )
+		{
+			latmaxadjust = -1 * latwidth / 2.0;
+		}
+		/* Only adjust if adjustments are legal (we haven't crossed any edges). */
+		if ( (lonminadjust || lonmaxadjust) && (latminadjust || latmaxadjust ) )
+		{
+			latmin += latminadjust;
+			lonmin += lonminadjust;
+			latmax += latmaxadjust;
+			lonmax += lonmaxadjust;
+			/* Each adjustment cycle corresponds to 2 bits of storage in the
+			** geohash.	*/
+			precision += 2;
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	/* Save the edges of our bounds, in case someone cares later. */
+	bounds->xmin = lonmin;
+	bounds->xmax = lonmax;
+	bounds->ymin = latmin;
+	bounds->ymax = latmax;
+
+	/* Each geohash character (base32) can contain 5 bits of information.
+	** We are returning the precision in characters, so here we divide. */
+	return precision / 5;
+}
+
+
+/*
+** Return a geohash string for the geometry. <http://geohash.org>
+** Where the precision is non-positive, calculate a precision based on the
+** bounds of the feature. Big features have loose precision.
+** Small features have tight precision.
+*/
+char * rtgeom_geohash(const RTCTX *ctx, const RTGEOM *rtgeom, int precision)
+{
+	RTGBOX gbox;
+	RTGBOX gbox_bounds;
+	double lat, lon;
+	int result;
+
+	gbox_init(ctx, &gbox);
+	gbox_init(ctx, &gbox_bounds);
+
+	result = rtgeom_calculate_gbox_cartesian(ctx, rtgeom, &gbox);	
+	if ( result == RT_FAILURE ) return NULL;
+
+	/* Return error if we are being fed something outside our working bounds */
+	if ( gbox.xmin < -180 || gbox.ymin < -90 || gbox.xmax > 180 || gbox.ymax > 90 )
+	{
+		rterror(ctx, "Geohash requires inputs in decimal degrees, got (%g %g, %g %g).",
+			 gbox.xmin, gbox.ymin,
+			 gbox.xmax, gbox.ymax);
+		return NULL;
+	}
+
+	/* What is the center of our geometry bounds? We'll use that to
+	** approximate location. */
+	lon = gbox.xmin + (gbox.xmax - gbox.xmin) / 2;
+	lat = gbox.ymin + (gbox.ymax - gbox.ymin) / 2;
+
+	if ( precision <= 0 )
+	{
+		precision = rtgeom_geohash_precision(ctx, gbox, &gbox_bounds);
+	}
+
+	/*
+	** Return the geohash of the center, with a precision determined by the
+	** extent of the bounds.
+	** Possible change: return the point at the center of the precision bounds?
+	*/
+	return geohash_point(ctx, lon, lat, precision);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/rtcircstring.c b/src/rtcircstring.c
new file mode 100644
index 0000000..26326e9
--- /dev/null
+++ b/src/rtcircstring.c
@@ -0,0 +1,334 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/* basic RTCIRCSTRING functions */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+void printRTCIRCSTRING(const RTCTX *ctx, RTCIRCSTRING *curve);
+void rtcircstring_reverse(const RTCTX *ctx, RTCIRCSTRING *curve);
+void rtcircstring_release(const RTCTX *ctx, RTCIRCSTRING *rtcirc);
+char rtcircstring_same(const RTCTX *ctx, const RTCIRCSTRING *me, const RTCIRCSTRING *you);
+RTCIRCSTRING * rtcircstring_from_rtpointarray(const RTCTX *ctx, int srid, uint32_t npoints, RTPOINT **points);
+RTCIRCSTRING * rtcircstring_from_rtmpoint(const RTCTX *ctx, int srid, RTMPOINT *mpoint);
+RTCIRCSTRING * rtcircstring_addpoint(const RTCTX *ctx, RTCIRCSTRING *curve, RTPOINT *point, uint32_t where);
+RTCIRCSTRING * rtcircstring_removepoint(const RTCTX *ctx, RTCIRCSTRING *curve, uint32_t index);
+void rtcircstring_setPoint4d(const RTCTX *ctx, RTCIRCSTRING *curve, uint32_t index, RTPOINT4D *newpoint);
+
+
+
+/*
+ * Construct a new RTCIRCSTRING.  points will *NOT* be copied
+ * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
+ */
+RTCIRCSTRING *
+rtcircstring_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points)
+{
+	RTCIRCSTRING *result;
+
+	/*
+	* The first arc requires three points.  Each additional
+	* arc requires two more points.  Thus the minimum point count
+	* is three, and the count must be odd.
+	*/
+	if (points->npoints % 2 != 1 || points->npoints < 3)
+	{
+		rtnotice(ctx, "rtcircstring_construct: invalid point count %d", points->npoints);
+	}
+
+	result = (RTCIRCSTRING*) rtalloc(ctx, sizeof(RTCIRCSTRING));
+
+	result->type = RTCIRCSTRINGTYPE;
+	
+	result->flags = points->flags;
+	RTFLAGS_SET_BBOX(result->flags, bbox?1:0);
+
+	result->srid = srid;
+	result->points = points;
+	result->bbox = bbox;
+
+	return result;
+}
+
+RTCIRCSTRING *
+rtcircstring_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTCIRCSTRING *result = rtalloc(ctx, sizeof(RTCIRCSTRING));
+	result->type = RTCIRCSTRINGTYPE;
+	result->flags = gflags(ctx, hasz,hasm,0);
+	result->srid = srid;
+	result->points = ptarray_construct_empty(ctx, hasz, hasm, 1);
+	result->bbox = NULL;
+	return result;
+}
+
+void
+rtcircstring_release(const RTCTX *ctx, RTCIRCSTRING *rtcirc)
+{
+	rtgeom_release(ctx, rtcircstring_as_rtgeom(ctx, rtcirc));
+}
+
+
+void rtcircstring_free(const RTCTX *ctx, RTCIRCSTRING *curve)
+{
+	if ( ! curve ) return;
+	
+	if ( curve->bbox )
+		rtfree(ctx, curve->bbox);
+	if ( curve->points )
+		ptarray_free(ctx, curve->points);
+	rtfree(ctx, curve);
+}
+
+
+
+void printRTCIRCSTRING(const RTCTX *ctx, RTCIRCSTRING *curve)
+{
+	rtnotice(ctx, "RTCIRCSTRING {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(curve->flags));
+	rtnotice(ctx, "    srid = %i", (int)curve->srid);
+	printPA(ctx, curve->points);
+	rtnotice(ctx, "}");
+}
+
+/* @brief Clone RTCIRCSTRING object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTCIRCSTRING *
+rtcircstring_clone(const RTCTX *ctx, const RTCIRCSTRING *g)
+{
+	return (RTCIRCSTRING *)rtline_clone(ctx, (RTLINE *)g);
+}
+
+
+void rtcircstring_reverse(const RTCTX *ctx, RTCIRCSTRING *curve)
+{
+	ptarray_reverse(ctx, curve->points);
+}
+
+/* check coordinate equality */
+char
+rtcircstring_same(const RTCTX *ctx, const RTCIRCSTRING *me, const RTCIRCSTRING *you)
+{
+	return ptarray_same(ctx, me->points, you->points);
+}
+
+/*
+ * Construct a RTCIRCSTRING from an array of RTPOINTs
+ * RTCIRCSTRING dimensions are large enough to host all input dimensions.
+ */
+RTCIRCSTRING *
+rtcircstring_from_rtpointarray(const RTCTX *ctx, int srid, uint32_t npoints, RTPOINT **points)
+{
+	int zmflag=0;
+	uint32_t i;
+	RTPOINTARRAY *pa;
+	uint8_t *newpoints, *ptr;
+	size_t ptsize, size;
+
+	/*
+	 * Find output dimensions, check integrity
+	 */
+	for (i = 0; i < npoints; i++)
+	{
+		if (points[i]->type != RTPOINTTYPE)
+		{
+			rterror(ctx, "rtcurve_from_rtpointarray: invalid input type: %s",
+			        rttype_name(ctx, points[i]->type));
+			return NULL;
+		}
+		if (RTFLAGS_GET_Z(points[i]->flags)) zmflag |= 2;
+		if (RTFLAGS_GET_M(points[i]->flags)) zmflag |= 1;
+		if (zmflag == 3) break;
+	}
+
+	if (zmflag == 0) ptsize = 2 * sizeof(double);
+	else if (zmflag == 3) ptsize = 4 * sizeof(double);
+	else ptsize = 3 * sizeof(double);
+
+	/*
+	 * Allocate output points array
+	 */
+	size = ptsize * npoints;
+	newpoints = rtalloc(ctx, size);
+	memset(newpoints, 0, size);
+
+	ptr = newpoints;
+	for (i = 0; i < npoints; i++)
+	{
+		size = ptarray_point_size(ctx, points[i]->point);
+		memcpy(ptr, rt_getPoint_internal(ctx, points[i]->point, 0), size);
+		ptr += ptsize;
+	}
+	pa = ptarray_construct_reference_data(ctx, zmflag&2, zmflag&1, npoints, newpoints);
+	
+	return rtcircstring_construct(ctx, srid, NULL, pa);
+}
+
+/*
+ * Construct a RTCIRCSTRING from a RTMPOINT
+ */
+RTCIRCSTRING *
+rtcircstring_from_rtmpoint(const RTCTX *ctx, int srid, RTMPOINT *mpoint)
+{
+	uint32_t i;
+	RTPOINTARRAY *pa;
+	char zmflag = RTFLAGS_GET_ZM(mpoint->flags);
+	size_t ptsize, size;
+	uint8_t *newpoints, *ptr;
+
+	if (zmflag == 0) ptsize = 2 * sizeof(double);
+	else if (zmflag == 3) ptsize = 4 * sizeof(double);
+	else ptsize = 3 * sizeof(double);
+
+	/* Allocate space for output points */
+	size = ptsize * mpoint->ngeoms;
+	newpoints = rtalloc(ctx, size);
+	memset(newpoints, 0, size);
+
+	ptr = newpoints;
+	for (i = 0; i < mpoint->ngeoms; i++)
+	{
+		memcpy(ptr,
+		       rt_getPoint_internal(ctx, mpoint->geoms[i]->point, 0),
+		       ptsize);
+		ptr += ptsize;
+	}
+
+	pa = ptarray_construct_reference_data(ctx, zmflag&2, zmflag&1, mpoint->ngeoms, newpoints);
+	
+	RTDEBUGF(3, "rtcurve_from_rtmpoint: constructed pointarray for %d points, %d zmflag", mpoint->ngeoms, zmflag);
+
+	return rtcircstring_construct(ctx, srid, NULL, pa);
+}
+
+RTCIRCSTRING *
+rtcircstring_addpoint(const RTCTX *ctx, RTCIRCSTRING *curve, RTPOINT *point, uint32_t where)
+{
+	RTPOINTARRAY *newpa;
+	RTCIRCSTRING *ret;
+
+	newpa = ptarray_addPoint(ctx, curve->points,
+	                         rt_getPoint_internal(ctx, point->point, 0),
+	                         RTFLAGS_NDIMS(point->flags), where);
+	ret = rtcircstring_construct(ctx, curve->srid, NULL, newpa);
+
+	return ret;
+}
+
+RTCIRCSTRING *
+rtcircstring_removepoint(const RTCTX *ctx, RTCIRCSTRING *curve, uint32_t index)
+{
+	RTPOINTARRAY *newpa;
+	RTCIRCSTRING *ret;
+
+	newpa = ptarray_removePoint(ctx, curve->points, index);
+	ret = rtcircstring_construct(ctx, curve->srid, NULL, newpa);
+
+	return ret;
+}
+
+/*
+ * Note: input will be changed, make sure you have permissions for this.
+ * */
+void
+rtcircstring_setPoint4d(const RTCTX *ctx, RTCIRCSTRING *curve, uint32_t index, RTPOINT4D *newpoint)
+{
+	ptarray_set_point4d(ctx, curve->points, index, newpoint);
+}
+
+int
+rtcircstring_is_closed(const RTCTX *ctx, const RTCIRCSTRING *curve)
+{
+	if (RTFLAGS_GET_Z(curve->flags))
+		return ptarray_is_closed_3d(ctx, curve->points);
+
+	return ptarray_is_closed_2d(ctx, curve->points);
+}
+
+int rtcircstring_is_empty(const RTCTX *ctx, const RTCIRCSTRING *circ)
+{
+	if ( !circ->points || circ->points->npoints < 1 )
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+double rtcircstring_length(const RTCTX *ctx, const RTCIRCSTRING *circ)
+{
+	return rtcircstring_length_2d(ctx, circ);
+}
+
+double rtcircstring_length_2d(const RTCTX *ctx, const RTCIRCSTRING *circ)
+{
+	if ( rtcircstring_is_empty(ctx, circ) )
+		return 0.0;
+	
+	return ptarray_arc_length_2d(ctx, circ->points);
+}
+
+/*
+ * Returns freshly allocated #RTPOINT that corresponds to the index where.
+ * Returns NULL if the geometry is empty or the index invalid.
+ */
+RTPOINT* rtcircstring_get_rtpoint(const RTCTX *ctx, const RTCIRCSTRING *circ, int where) {
+	RTPOINT4D pt;
+	RTPOINT *rtpoint;
+	RTPOINTARRAY *pa;
+
+	if ( rtcircstring_is_empty(ctx, circ) || where < 0 || where >= circ->points->npoints )
+		return NULL;
+
+	pa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(circ->flags), RTFLAGS_GET_M(circ->flags), 1);
+	pt = rt_getPoint4d(ctx, circ->points, where);
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+	rtpoint = rtpoint_construct(ctx, circ->srid, NULL, pa);
+	return rtpoint;
+}
+
+/*
+* Snap to grid 
+*/
+RTCIRCSTRING* rtcircstring_grid(const RTCTX *ctx, const RTCIRCSTRING *line, const gridspec *grid)
+{
+	RTCIRCSTRING *oline;
+	RTPOINTARRAY *opa;
+
+	opa = ptarray_grid(ctx, line->points, grid);
+
+	/* Skip line3d with less then 2 points */
+	if ( opa->npoints < 2 ) return NULL;
+
+	/* TODO: grid bounding box... */
+	oline = rtcircstring_construct(ctx, line->srid, NULL, opa);
+
+	return oline;
+}
+
diff --git a/src/rtcollection.c b/src/rtcollection.c
new file mode 100644
index 0000000..62c1fca
--- /dev/null
+++ b/src/rtcollection.c
@@ -0,0 +1,598 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+#define CHECK_RTGEOM_ZM 1
+
+void
+rtcollection_release(const RTCTX *ctx, RTCOLLECTION *rtcollection)
+{
+	rtgeom_release(ctx, rtcollection_as_rtgeom(ctx, rtcollection));
+}
+
+
+RTCOLLECTION *
+rtcollection_construct(const RTCTX *ctx, uint8_t type, int srid, RTGBOX *bbox,
+                       uint32_t ngeoms, RTGEOM **geoms)
+{
+	RTCOLLECTION *ret;
+	int hasz, hasm;
+#ifdef CHECK_RTGEOM_ZM
+	char zm;
+	uint32_t i;
+#endif
+
+	RTDEBUGF(2, "rtcollection_construct called with %d, %d, %p, %d, %p.", type, srid, bbox, ngeoms, geoms);
+
+	if( ! rttype_is_collection(ctx, type) )
+		rterror(ctx, "Non-collection type specified in collection constructor!");
+
+	hasz = 0;
+	hasm = 0;
+	if ( ngeoms > 0 )
+	{
+		hasz = RTFLAGS_GET_Z(geoms[0]->flags);
+		hasm = RTFLAGS_GET_M(geoms[0]->flags);
+#ifdef CHECK_RTGEOM_ZM
+		zm = RTFLAGS_GET_ZM(geoms[0]->flags);
+
+		RTDEBUGF(3, "rtcollection_construct type[0]=%d", geoms[0]->type);
+
+		for (i=1; i<ngeoms; i++)
+		{
+			RTDEBUGF(3, "rtcollection_construct type=[%d]=%d", i, geoms[i]->type);
+
+			if ( zm != RTFLAGS_GET_ZM(geoms[i]->flags) )
+				rterror(ctx, "rtcollection_construct: mixed dimension geometries: %d/%d", zm, RTFLAGS_GET_ZM(geoms[i]->flags));
+		}
+#endif
+	}
+
+
+	ret = rtalloc(ctx, sizeof(RTCOLLECTION));
+	ret->type = type;
+	ret->flags = gflags(ctx, hasz,hasm,0);
+	RTFLAGS_SET_BBOX(ret->flags, bbox?1:0);
+	ret->srid = srid;
+	ret->ngeoms = ngeoms;
+	ret->maxgeoms = ngeoms;
+	ret->geoms = geoms;
+	ret->bbox = bbox;
+
+	return ret;
+}
+
+RTCOLLECTION *
+rtcollection_construct_empty(const RTCTX *ctx, uint8_t type, int srid, char hasz, char hasm)
+{
+	RTCOLLECTION *ret;
+	if( ! rttype_is_collection(ctx, type) )
+		rterror(ctx, "Non-collection type specified in collection constructor!");
+
+	ret = rtalloc(ctx, sizeof(RTCOLLECTION));
+	ret->type = type;
+	ret->flags = gflags(ctx, hasz,hasm,0);
+	ret->srid = srid;
+	ret->ngeoms = 0;
+	ret->maxgeoms = 1; /* Allocate room for sub-members, just in case. */
+	ret->geoms = rtalloc(ctx, ret->maxgeoms * sizeof(RTGEOM*));
+	ret->bbox = NULL;
+
+	return ret;
+}
+
+RTGEOM *
+rtcollection_getsubgeom(const RTCTX *ctx, RTCOLLECTION *col, int gnum)
+{
+	return (RTGEOM *)col->geoms[gnum];
+}
+
+/**
+ * @brief Clone #RTCOLLECTION object. #RTPOINTARRAY are not copied.
+ * 			Bbox is cloned if present in input.
+ */
+RTCOLLECTION *
+rtcollection_clone(const RTCTX *ctx, const RTCOLLECTION *g)
+{
+	uint32_t i;
+	RTCOLLECTION *ret = rtalloc(ctx, sizeof(RTCOLLECTION));
+	memcpy(ret, g, sizeof(RTCOLLECTION));
+	if ( g->ngeoms > 0 )
+	{
+		ret->geoms = rtalloc(ctx, sizeof(RTGEOM *)*g->ngeoms);
+		for (i=0; i<g->ngeoms; i++)
+		{
+			ret->geoms[i] = rtgeom_clone(ctx, g->geoms[i]);
+		}
+		if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	}
+	else
+	{
+		ret->bbox = NULL; /* empty collection */
+		ret->geoms = NULL;
+	}
+	return ret;
+}
+
+/**
+* @brief Deep clone #RTCOLLECTION object. #RTPOINTARRAY are copied.
+*/
+RTCOLLECTION *
+rtcollection_clone_deep(const RTCTX *ctx, const RTCOLLECTION *g)
+{
+	uint32_t i;
+	RTCOLLECTION *ret = rtalloc(ctx, sizeof(RTCOLLECTION));
+	memcpy(ret, g, sizeof(RTCOLLECTION));
+	if ( g->ngeoms > 0 )
+	{
+		ret->geoms = rtalloc(ctx, sizeof(RTGEOM *)*g->ngeoms);
+		for (i=0; i<g->ngeoms; i++)
+		{
+			ret->geoms[i] = rtgeom_clone_deep(ctx, g->geoms[i]);
+		}
+		if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	}
+	else
+	{
+		ret->bbox = NULL; /* empty collection */
+		ret->geoms = NULL;
+	}
+	return ret;
+}
+
+/**
+ * Ensure the collection can hold up at least ngeoms
+ */
+void rtcollection_reserve(const RTCTX *ctx, RTCOLLECTION *col, int ngeoms)
+{
+	if ( ngeoms <= col->maxgeoms ) return;
+
+	/* Allocate more space if we need it */
+	do { col->maxgeoms *= 2; } while ( col->maxgeoms < ngeoms );
+	col->geoms = rtrealloc(ctx, col->geoms, sizeof(RTGEOM*) * col->maxgeoms);
+}
+
+/**
+* Appends geom to the collection managed by col. Does not copy or
+* clone, simply takes a reference on the passed geom.
+*/
+RTCOLLECTION* rtcollection_add_rtgeom(const RTCTX *ctx, RTCOLLECTION *col, const RTGEOM *geom)
+{
+	if ( col == NULL || geom == NULL ) return NULL;
+
+	if ( col->geoms == NULL && (col->ngeoms || col->maxgeoms) ) {
+		rterror(ctx, "Collection is in inconsistent state. Null memory but non-zero collection counts.");
+		return NULL;
+	}
+
+	/* Check type compatibility */
+	if ( ! rtcollection_allows_subtype(ctx, col->type, geom->type) ) {
+		rterror(ctx, "%s cannot contain %s element", rttype_name(ctx, col->type), rttype_name(ctx, geom->type));
+		return NULL;
+	}
+
+	/* In case this is a truly empty, make some initial space  */
+	if ( col->geoms == NULL )
+	{
+		col->maxgeoms = 2;
+		col->ngeoms = 0;
+		col->geoms = rtalloc(ctx, col->maxgeoms * sizeof(RTGEOM*));
+	}
+
+	/* Allocate more space if we need it */
+	rtcollection_reserve(ctx, col, col->ngeoms + 1);
+
+#if PARANOIA_LEVEL > 1
+	/* See http://trac.osgeo.org/postgis/ticket/2933 */
+	/* Make sure we don't already have a reference to this geom */
+	{
+	int i = 0;
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		if ( col->geoms[i] == geom )
+		{
+			RTDEBUGF(4, "Found duplicate geometry in collection %p == %p", col->geoms[i], geom);
+			return col;
+		}
+	}
+	}
+#endif
+
+	col->geoms[col->ngeoms] = (RTGEOM*)geom;
+	col->ngeoms++;
+	return col;
+}
+
+
+RTCOLLECTION *
+rtcollection_segmentize2d(const RTCTX *ctx, RTCOLLECTION *col, double dist)
+{
+	uint32_t i;
+	RTGEOM **newgeoms;
+
+	if ( ! col->ngeoms ) return rtcollection_clone(ctx, col);
+
+	newgeoms = rtalloc(ctx, sizeof(RTGEOM *)*col->ngeoms);
+	for (i=0; i<col->ngeoms; i++)
+	{
+		newgeoms[i] = rtgeom_segmentize2d(ctx, col->geoms[i], dist);
+		if ( ! newgeoms[i] ) {
+			while (i--) rtgeom_free(ctx, newgeoms[i]);
+			rtfree(ctx, newgeoms);
+			return NULL;
+		}
+	}
+
+	return rtcollection_construct(ctx, col->type, col->srid, NULL, col->ngeoms, newgeoms);
+}
+
+/** @brief check for same geometry composition
+ *
+ */
+char
+rtcollection_same(const RTCTX *ctx, const RTCOLLECTION *c1, const RTCOLLECTION *c2)
+{
+	uint32_t i;
+
+	RTDEBUG(2, "rtcollection_same called");
+
+	if ( c1->type != c2->type ) return RT_FALSE;
+	if ( c1->ngeoms != c2->ngeoms ) return RT_FALSE;
+
+	for ( i = 0; i < c1->ngeoms; i++ )
+	{
+		if ( ! rtgeom_same(ctx, c1->geoms[i], c2->geoms[i]) )
+			return RT_FALSE;
+	}
+
+	/* Former method allowed out-of-order equality between collections
+
+		hit = rtalloc(ctx, sizeof(uint32_t)*c1->ngeoms);
+		memset(hit, 0, sizeof(uint32_t)*c1->ngeoms);
+
+		for (i=0; i<c1->ngeoms; i++)
+		{
+			char found=0;
+			for (j=0; j<c2->ngeoms; j++)
+			{
+				if ( hit[j] ) continue;
+				if ( rtgeom_same(ctx, c1->geoms[i], c2->geoms[j]) )
+				{
+					hit[j] = 1;
+					found=1;
+					break;
+				}
+			}
+			if ( ! found ) return RT_FALSE;
+		}
+	*/
+
+	return RT_TRUE;
+}
+
+int rtcollection_ngeoms(const RTCTX *ctx, const RTCOLLECTION *col)
+{
+	int i;
+	int ngeoms = 0;
+
+	if ( ! col )
+	{
+		rterror(ctx, "Null input geometry.");
+		return 0;
+	}
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		if ( col->geoms[i])
+		{
+			switch (col->geoms[i]->type)
+			{
+			case RTPOINTTYPE:
+			case RTLINETYPE:
+			case RTCIRCSTRINGTYPE:
+			case RTPOLYGONTYPE:
+				ngeoms += 1;
+				break;
+			case RTMULTIPOINTTYPE:
+			case RTMULTILINETYPE:
+			case RTMULTICURVETYPE:
+			case RTMULTIPOLYGONTYPE:
+				ngeoms += col->ngeoms;
+				break;
+			case RTCOLLECTIONTYPE:
+				ngeoms += rtcollection_ngeoms(ctx, (RTCOLLECTION*)col->geoms[i]);
+				break;
+			}
+		}
+	}
+	return ngeoms;
+}
+
+void rtcollection_free(const RTCTX *ctx, RTCOLLECTION *col)
+{
+	int i;
+	if ( ! col ) return;
+	
+	if ( col->bbox )
+	{
+		rtfree(ctx, col->bbox);
+	}
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		RTDEBUGF(4,"freeing geom[%d]", i);
+		if ( col->geoms && col->geoms[i] )
+			rtgeom_free(ctx, col->geoms[i]);
+	}
+	if ( col->geoms )
+	{
+		rtfree(ctx, col->geoms);
+	}
+	rtfree(ctx, col);
+}
+
+
+/**
+* Takes a potentially heterogeneous collection and returns a homogeneous
+* collection consisting only of the specified type.
+*/
+RTCOLLECTION* rtcollection_extract(const RTCTX *ctx, RTCOLLECTION *col, int type)
+{
+	int i = 0;
+	RTGEOM **geomlist;
+	RTCOLLECTION *outcol;
+	int geomlistsize = 16;
+	int geomlistlen = 0;
+	uint8_t outtype;
+
+	if ( ! col ) return NULL;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		outtype = RTMULTIPOINTTYPE;
+		break;
+	case RTLINETYPE:
+		outtype = RTMULTILINETYPE;
+		break;
+	case RTPOLYGONTYPE:
+		outtype = RTMULTIPOLYGONTYPE;
+		break;
+	default:
+		rterror(ctx, "Only POLYGON, LINESTRING and POINT are supported by rtcollection_extract. %s requested.", rttype_name(ctx, type));
+		return NULL;
+	}
+
+	geomlist = rtalloc(ctx, sizeof(RTGEOM*) * geomlistsize);
+
+	/* Process each sub-geometry */
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		int subtype = col->geoms[i]->type;
+		/* Don't bother adding empty sub-geometries */
+		if ( rtgeom_is_empty(ctx, col->geoms[i]) )
+		{
+			continue;
+		}
+		/* Copy our sub-types into the output list */
+		if ( subtype == type )
+		{
+			/* We've over-run our buffer, double the memory segment */
+			if ( geomlistlen == geomlistsize )
+			{
+				geomlistsize *= 2;
+				geomlist = rtrealloc(ctx, geomlist, sizeof(RTGEOM*) * geomlistsize);
+			}
+			geomlist[geomlistlen] = rtgeom_clone(ctx, col->geoms[i]);
+			geomlistlen++;
+		}
+		/* Recurse into sub-collections */
+		if ( rttype_is_collection(ctx,  subtype ) )
+		{
+			int j = 0;
+			RTCOLLECTION *tmpcol = rtcollection_extract(ctx, (RTCOLLECTION*)col->geoms[i], type);
+			for ( j = 0; j < tmpcol->ngeoms; j++ )
+			{
+				/* We've over-run our buffer, double the memory segment */
+				if ( geomlistlen == geomlistsize )
+				{
+					geomlistsize *= 2;
+					geomlist = rtrealloc(ctx, geomlist, sizeof(RTGEOM*) * geomlistsize);
+				}
+				geomlist[geomlistlen] = tmpcol->geoms[j];
+				geomlistlen++;
+			}
+			rtfree(ctx, tmpcol);
+		}
+	}
+
+	if ( geomlistlen > 0 )
+	{
+		RTGBOX gbox;
+		outcol = rtcollection_construct(ctx, outtype, col->srid, NULL, geomlistlen, geomlist);
+		rtgeom_calculate_gbox(ctx, (RTGEOM *) outcol, &gbox);
+		outcol->bbox = gbox_copy(ctx, &gbox);
+	}
+	else
+	{
+		rtfree(ctx, geomlist);
+		outcol = rtcollection_construct_empty(ctx, outtype, col->srid, RTFLAGS_GET_Z(col->flags), RTFLAGS_GET_M(col->flags));
+	}
+
+	return outcol;
+}
+
+RTGEOM*
+rtcollection_remove_repeated_points(const RTCTX *ctx, const RTCOLLECTION *coll, double tolerance)
+{
+	uint32_t i;
+	RTGEOM **newgeoms;
+
+	newgeoms = rtalloc(ctx, sizeof(RTGEOM *)*coll->ngeoms);
+	for (i=0; i<coll->ngeoms; i++)
+	{
+		newgeoms[i] = rtgeom_remove_repeated_points(ctx, coll->geoms[i], tolerance);
+	}
+
+	return (RTGEOM*)rtcollection_construct(ctx, coll->type,
+	                                       coll->srid, coll->bbox ? gbox_copy(ctx, coll->bbox) : NULL,
+	                                       coll->ngeoms, newgeoms);
+}
+
+
+RTCOLLECTION*
+rtcollection_force_dims(const RTCTX *ctx, const RTCOLLECTION *col, int hasz, int hasm)
+{
+	RTCOLLECTION *colout;
+	
+	/* Return 2D empty */
+	if( rtcollection_is_empty(ctx, col) )
+	{
+		colout = rtcollection_construct_empty(ctx, col->type, col->srid, hasz, hasm);
+	}
+	else
+	{
+		int i;
+		RTGEOM **geoms = NULL;
+		geoms = rtalloc(ctx, sizeof(RTGEOM*) * col->ngeoms);
+		for( i = 0; i < col->ngeoms; i++ )
+		{
+			geoms[i] = rtgeom_force_dims(ctx, col->geoms[i], hasz, hasm);
+		}
+		colout = rtcollection_construct(ctx, col->type, col->srid, NULL, col->ngeoms, geoms);
+	}
+	return colout;
+}
+
+int rtcollection_is_empty(const RTCTX *ctx, const RTCOLLECTION *col)
+{
+	int i;
+	if ( (col->ngeoms == 0) || (!col->geoms) )
+		return RT_TRUE;
+	for( i = 0; i < col->ngeoms; i++ )
+	{
+		if ( ! rtgeom_is_empty(ctx, col->geoms[i]) ) return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+
+int rtcollection_count_vertices(const RTCTX *ctx, RTCOLLECTION *col)
+{
+	int i = 0;
+	int v = 0; /* vertices */
+	assert(col);
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		v += rtgeom_count_vertices(ctx, col->geoms[i]);
+	}
+	return v;
+}
+
+RTCOLLECTION* rtcollection_simplify(const RTCTX *ctx, const RTCOLLECTION *igeom, double dist, int preserve_collapsed)
+{
+ 	int i;
+	RTCOLLECTION *out = rtcollection_construct_empty(ctx, igeom->type, igeom->srid, RTFLAGS_GET_Z(igeom->flags), RTFLAGS_GET_M(igeom->flags));
+
+	if( rtcollection_is_empty(ctx, igeom) )
+		return out; /* should we return NULL instead ? */
+
+	for( i = 0; i < igeom->ngeoms; i++ )
+	{
+		RTGEOM *ngeom = rtgeom_simplify(ctx, igeom->geoms[i], dist, preserve_collapsed);
+		if ( ngeom ) out = rtcollection_add_rtgeom(ctx, out, ngeom);
+	}
+
+	return out;
+}
+
+int rtcollection_allows_subtype(const RTCTX *ctx, int collectiontype, int subtype)
+{
+	if ( collectiontype == RTCOLLECTIONTYPE )
+		return RT_TRUE;
+	if ( collectiontype == RTMULTIPOINTTYPE &&
+	        subtype == RTPOINTTYPE )
+		return RT_TRUE;
+	if ( collectiontype == RTMULTILINETYPE &&
+	        subtype == RTLINETYPE )
+		return RT_TRUE;
+	if ( collectiontype == RTMULTIPOLYGONTYPE &&
+	        subtype == RTPOLYGONTYPE )
+		return RT_TRUE;
+	if ( collectiontype == RTCOMPOUNDTYPE &&
+	        (subtype == RTLINETYPE || subtype == RTCIRCSTRINGTYPE) )
+		return RT_TRUE;
+	if ( collectiontype == RTCURVEPOLYTYPE &&
+	        (subtype == RTCIRCSTRINGTYPE || subtype == RTLINETYPE || subtype == RTCOMPOUNDTYPE) )
+		return RT_TRUE;
+	if ( collectiontype == RTMULTICURVETYPE &&
+	        (subtype == RTCIRCSTRINGTYPE || subtype == RTLINETYPE || subtype == RTCOMPOUNDTYPE) )
+		return RT_TRUE;
+	if ( collectiontype == RTMULTISURFACETYPE &&
+	        (subtype == RTPOLYGONTYPE || subtype == RTCURVEPOLYTYPE) )
+		return RT_TRUE;
+	if ( collectiontype == RTPOLYHEDRALSURFACETYPE &&
+	        subtype == RTPOLYGONTYPE )
+		return RT_TRUE;
+	if ( collectiontype == RTTINTYPE &&
+	        subtype == RTTRIANGLETYPE )
+		return RT_TRUE;
+
+	/* Must be a bad combination! */
+	return RT_FALSE;
+}
+
+int
+rtcollection_startpoint(const RTCTX *ctx, const RTCOLLECTION* col, RTPOINT4D* pt)
+{
+	if ( col->ngeoms < 1 )
+		return RT_FAILURE;
+		
+	return rtgeom_startpoint(ctx, col->geoms[0], pt);
+}
+
+
+RTCOLLECTION* rtcollection_grid(const RTCTX *ctx, const RTCOLLECTION *coll, const gridspec *grid)
+{
+	uint32_t i;
+	RTCOLLECTION *newcoll;
+	
+	newcoll = rtcollection_construct_empty(ctx, coll->type, coll->srid, rtgeom_has_z(ctx, (RTGEOM*)coll), rtgeom_has_m(ctx, (RTGEOM*)coll));
+
+	for (i=0; i<coll->ngeoms; i++)
+	{
+		RTGEOM *g = rtgeom_grid(ctx, coll->geoms[i], grid);
+		if ( g ) 
+			rtcollection_add_rtgeom(ctx, newcoll, g);
+	}
+
+	return newcoll;
+}
diff --git a/src/rtcompound.c b/src/rtcompound.c
new file mode 100644
index 0000000..843bc3b
--- /dev/null
+++ b/src/rtcompound.c
@@ -0,0 +1,277 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+
+int
+rtcompound_is_closed(const RTCTX *ctx, const RTCOMPOUND *compound)
+{
+	size_t size;
+	int npoints=0;
+
+	if ( rtgeom_has_z(ctx, (RTGEOM*)compound) )
+	{
+		size = sizeof(POINT3D);
+	}
+	else
+	{
+		size = sizeof(RTPOINT2D);
+	}
+
+	if ( compound->geoms[compound->ngeoms - 1]->type == RTCIRCSTRINGTYPE )
+	{
+		npoints = ((RTCIRCSTRING *)compound->geoms[compound->ngeoms - 1])->points->npoints;
+	}
+	else if (compound->geoms[compound->ngeoms - 1]->type == RTLINETYPE)
+	{
+		npoints = ((RTLINE *)compound->geoms[compound->ngeoms - 1])->points->npoints;
+	}
+
+	if ( memcmp(rt_getPoint_internal(ctx,  (RTPOINTARRAY *)compound->geoms[0]->data, 0),
+	            rt_getPoint_internal(ctx,  (RTPOINTARRAY *)compound->geoms[compound->ngeoms - 1]->data,
+	                               npoints - 1),
+	            size) ) 
+	{
+		return RT_FALSE;
+	}
+
+	return RT_TRUE;
+}
+
+double rtcompound_length(const RTCTX *ctx, const RTCOMPOUND *comp)
+{
+	double length = 0.0;
+	RTLINE *line;
+	if ( rtgeom_is_empty(ctx, (RTGEOM*)comp) )
+		return 0.0;
+	line = rtcompound_stroke(ctx, comp, 32);
+	length = rtline_length(ctx, line);
+	rtline_free(ctx, line);
+	return length;
+}
+
+double rtcompound_length_2d(const RTCTX *ctx, const RTCOMPOUND *comp)
+{
+	double length = 0.0;
+	RTLINE *line;
+	if ( rtgeom_is_empty(ctx, (RTGEOM*)comp) )
+		return 0.0;
+	line = rtcompound_stroke(ctx, comp, 32);
+	length = rtline_length_2d(ctx, line);
+	rtline_free(ctx, line);
+	return length;
+}
+
+int rtcompound_add_rtgeom(const RTCTX *ctx, RTCOMPOUND *comp, RTGEOM *geom)
+{
+	RTCOLLECTION *col = (RTCOLLECTION*)comp;
+	
+	/* Empty things can't continuously join up with other things */
+	if ( rtgeom_is_empty(ctx, geom) )
+	{
+		RTDEBUG(4, "Got an empty component for a compound curve!");
+		return RT_FAILURE;
+	}
+	
+	if( col->ngeoms > 0 )
+	{
+		RTPOINT4D last, first;
+		/* First point of the component we are adding */
+		RTLINE *newline = (RTLINE*)geom;
+		/* Last point of the previous component */
+		RTLINE *prevline = (RTLINE*)(col->geoms[col->ngeoms-1]);
+
+		rt_getPoint4d_p(ctx, newline->points, 0, &first);
+		rt_getPoint4d_p(ctx, prevline->points, prevline->points->npoints-1, &last);
+		
+		if ( !(FP_EQUALS(first.x,last.x) && FP_EQUALS(first.y,last.y)) )
+		{
+			RTDEBUG(4, "Components don't join up end-to-end!");
+			RTDEBUGF(4, "first pt (%g %g %g %g) last pt (%g %g %g %g)", first.x, first.y, first.z, first.m, last.x, last.y, last.z, last.m);			
+			return RT_FAILURE;
+		}
+	}
+	
+	col = rtcollection_add_rtgeom(ctx, col, geom);
+	return RT_SUCCESS;
+}
+
+RTCOMPOUND *
+rtcompound_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTCOMPOUND *ret = (RTCOMPOUND*)rtcollection_construct_empty(ctx, RTCOMPOUNDTYPE, srid, hasz, hasm);
+	return ret;
+}
+
+int rtgeom_contains_point(const RTCTX *ctx, const RTGEOM *geom, const RTPOINT2D *pt)
+{
+	switch( geom->type )
+	{
+		case RTLINETYPE:
+			return ptarray_contains_point(ctx, ((RTLINE*)geom)->points, pt);
+		case RTCIRCSTRINGTYPE:
+			return ptarrayarc_contains_point(ctx, ((RTCIRCSTRING*)geom)->points, pt);
+		case RTCOMPOUNDTYPE:
+			return rtcompound_contains_point(ctx, (RTCOMPOUND*)geom, pt);
+	}
+	rterror(ctx, "rtgeom_contains_point failed");
+	return RT_FAILURE;
+}
+
+int 
+rtcompound_contains_point(const RTCTX *ctx, const RTCOMPOUND *comp, const RTPOINT2D *pt)
+{
+	int i;
+	RTLINE *rtline;
+	RTCIRCSTRING *rtcirc;
+	int wn = 0;
+	int winding_number = 0;
+	int result;
+
+	for ( i = 0; i < comp->ngeoms; i++ )
+	{
+		RTGEOM *rtgeom = comp->geoms[i];
+		if ( rtgeom->type == RTLINETYPE )
+		{
+			rtline = rtgeom_as_rtline(ctx, rtgeom);
+			if ( comp->ngeoms == 1 )
+			{
+				return ptarray_contains_point(ctx, rtline->points, pt); 
+			}
+			else
+			{
+				/* Don't check closure while doing p-i-p test */
+				result = ptarray_contains_point_partial(ctx, rtline->points, pt, RT_FALSE, &winding_number);
+			}
+		}
+		else
+		{
+			rtcirc = rtgeom_as_rtcircstring(ctx, rtgeom);
+			if ( ! rtcirc ) {
+				rterror(ctx, "Unexpected component of type %s in compound curve", rttype_name(ctx, rtgeom->type));
+				return 0;
+			}
+			if ( comp->ngeoms == 1 )
+			{
+				return ptarrayarc_contains_point(ctx, rtcirc->points, pt); 				
+			}
+			else
+			{
+				/* Don't check closure while doing p-i-p test */
+				result = ptarrayarc_contains_point_partial(ctx, rtcirc->points, pt, RT_FALSE, &winding_number);
+			}
+		}
+
+		/* Propogate boundary condition */
+		if ( result == RT_BOUNDARY ) 
+			return RT_BOUNDARY;
+
+		wn += winding_number;
+	}
+
+	/* Outside */
+	if (wn == 0)
+		return RT_OUTSIDE;
+	
+	/* Inside */
+	return RT_INSIDE;
+}	
+
+RTCOMPOUND *
+rtcompound_construct_from_rtline(const RTCTX *ctx, const RTLINE *rtline)
+{
+  RTCOMPOUND* ogeom = rtcompound_construct_empty(ctx, rtline->srid, RTFLAGS_GET_Z(rtline->flags), RTFLAGS_GET_M(rtline->flags));
+  rtcompound_add_rtgeom(ctx, ogeom, rtgeom_clone(ctx, (RTGEOM*)rtline));
+	/* ogeom->bbox = rtline->bbox; */
+  return ogeom;
+}
+
+RTPOINT* 
+rtcompound_get_rtpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp, int where)
+{
+	int i;
+	int count = 0;
+	int npoints = 0;
+	if ( rtgeom_is_empty(ctx, (RTGEOM*)rtcmp) )
+		return NULL;
+	
+	npoints = rtgeom_count_vertices(ctx, (RTGEOM*)rtcmp);
+	if ( where < 0 || where >= npoints )
+	{
+		rterror(ctx, "%s: index %d is not in range of number of vertices (%d) in input", __func__, where, npoints);
+		return NULL;
+	}
+	
+	for ( i = 0; i < rtcmp->ngeoms; i++ )
+	{
+		RTGEOM* part = rtcmp->geoms[i];
+		int npoints_part = rtgeom_count_vertices(ctx, part);
+		if ( where >= count && where < count + npoints_part )
+		{
+			return rtline_get_rtpoint(ctx, (RTLINE*)part, where - count);
+		}
+		else
+		{
+			count += npoints_part;
+		}
+	}
+
+	return NULL;	
+}
+
+
+
+RTPOINT *
+rtcompound_get_startpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp)
+{
+	return rtcompound_get_rtpoint(ctx, rtcmp, 0);
+}
+
+RTPOINT *
+rtcompound_get_endpoint(const RTCTX *ctx, const RTCOMPOUND *rtcmp)
+{
+	RTLINE *rtline;
+	if ( rtcmp->ngeoms < 1 )
+	{
+		return NULL;
+	}
+	
+	rtline = (RTLINE*)(rtcmp->geoms[rtcmp->ngeoms-1]);
+
+	if ( (!rtline) || (!rtline->points) || (rtline->points->npoints < 1) )
+	{
+		return NULL;
+	}
+	
+	return rtline_get_rtpoint(ctx, rtline, rtline->points->npoints-1);
+}
+
diff --git a/src/rtcurvepoly.c b/src/rtcurvepoly.c
new file mode 100644
index 0000000..4ef4fd9
--- /dev/null
+++ b/src/rtcurvepoly.c
@@ -0,0 +1,169 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/* basic RTCURVEPOLY manipulation */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+RTCURVEPOLY *
+rtcurvepoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTCURVEPOLY *ret;
+
+	ret = rtalloc(ctx, sizeof(RTCURVEPOLY));
+	ret->type = RTCURVEPOLYTYPE;
+	ret->flags = gflags(ctx, hasz, hasm, 0);
+	ret->srid = srid;
+	ret->nrings = 0;
+	ret->maxrings = 1; /* Allocate room for sub-members, just in case. */
+	ret->rings = rtalloc(ctx, ret->maxrings * sizeof(RTGEOM*));
+	ret->bbox = NULL;
+
+	return ret;
+}
+
+RTCURVEPOLY *
+rtcurvepoly_construct_from_rtpoly(const RTCTX *ctx, RTPOLY *rtpoly)
+{
+	RTCURVEPOLY *ret;
+	int i;
+	ret = rtalloc(ctx, sizeof(RTCURVEPOLY));
+	ret->type = RTCURVEPOLYTYPE;
+	ret->flags = rtpoly->flags;
+	ret->srid = rtpoly->srid;
+	ret->nrings = rtpoly->nrings;
+	ret->maxrings = rtpoly->nrings; /* Allocate room for sub-members, just in case. */
+	ret->rings = rtalloc(ctx, ret->maxrings * sizeof(RTGEOM*));
+	ret->bbox = rtpoly->bbox ? gbox_clone(ctx, rtpoly->bbox) : NULL;
+	for ( i = 0; i < ret->nrings; i++ )
+	{
+		ret->rings[i] = rtline_as_rtgeom(ctx, rtline_construct(ctx, ret->srid, NULL, ptarray_clone_deep(ctx, rtpoly->rings[i])));
+	}
+	return ret;
+}
+
+int rtcurvepoly_add_ring(const RTCTX *ctx, RTCURVEPOLY *poly, RTGEOM *ring)
+{
+	int i;
+	
+	/* Can't do anything with NULLs */
+	if( ! poly || ! ring ) 
+	{
+		RTDEBUG(4,"NULL inputs!!! quitting");
+		return RT_FAILURE;
+	}
+
+	/* Check that we're not working with garbage */
+	if ( poly->rings == NULL && (poly->nrings || poly->maxrings) )
+	{
+		RTDEBUG(4,"mismatched nrings/maxrings");
+		rterror(ctx, "Curvepolygon is in inconsistent state. Null memory but non-zero collection counts.");
+	}
+
+	/* Check that we're adding an allowed ring type */
+	if ( ! ( ring->type == RTLINETYPE || ring->type == RTCIRCSTRINGTYPE || ring->type == RTCOMPOUNDTYPE ) )
+	{
+		RTDEBUGF(4,"got incorrect ring type: %s",rttype_name(ctx, ring->type));
+		return RT_FAILURE;
+	}
+
+		
+	/* In case this is a truly empty, make some initial space  */
+	if ( poly->rings == NULL )
+	{
+		poly->maxrings = 2;
+		poly->nrings = 0;
+		poly->rings = rtalloc(ctx, poly->maxrings * sizeof(RTGEOM*));
+	}
+
+	/* Allocate more space if we need it */
+	if ( poly->nrings == poly->maxrings )
+	{
+		poly->maxrings *= 2;
+		poly->rings = rtrealloc(ctx, poly->rings, sizeof(RTGEOM*) * poly->maxrings);
+	}
+
+	/* Make sure we don't already have a reference to this geom */
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if ( poly->rings[i] == ring )
+		{
+			RTDEBUGF(4, "Found duplicate geometry in collection %p == %p", poly->rings[i], ring);
+			return RT_SUCCESS;
+		}
+	}
+
+	/* Add the ring and increment the ring count */
+	poly->rings[poly->nrings] = (RTGEOM*)ring;
+	poly->nrings++;
+	return RT_SUCCESS;	
+}
+
+/**
+ * This should be rewritten to make use of the curve itself.
+ */
+double
+rtcurvepoly_area(const RTCTX *ctx, const RTCURVEPOLY *curvepoly)
+{
+	double area = 0.0;
+	RTPOLY *poly;
+	if( rtgeom_is_empty(ctx, (RTGEOM*)curvepoly) )
+		return 0.0;
+	poly = rtcurvepoly_stroke(ctx, curvepoly, 32);
+	area = rtpoly_area(ctx, poly);
+	rtpoly_free(ctx, poly);
+	return area;
+}
+
+
+double
+rtcurvepoly_perimeter(const RTCTX *ctx, const RTCURVEPOLY *poly)
+{
+	double result=0.0;
+	int i;
+
+	for (i=0; i<poly->nrings; i++)
+		result += rtgeom_length(ctx, poly->rings[i]);
+
+	return result;
+}
+
+double
+rtcurvepoly_perimeter_2d(const RTCTX *ctx, const RTCURVEPOLY *poly)
+{
+	double result=0.0;
+	int i;
+
+	for (i=0; i<poly->nrings; i++)
+		result += rtgeom_length_2d(ctx, poly->rings[i]);
+
+	return result;
+}
diff --git a/src/rtgeodetic.c b/src/rtgeodetic.c
new file mode 100644
index 0000000..e29d216
--- /dev/null
+++ b/src/rtgeodetic.c
@@ -0,0 +1,3305 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright 2009 David Skea <David.Skea at gov.bc.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeodetic.h"
+#include "rtgeom_log.h"
+
+/**
+* For testing geodetic bounding box, we have a magic global variable.
+* When this is true (when the cunit tests set it), use the slow, but
+* guaranteed correct, algorithm. Otherwise use the regular one.
+*/
+int gbox_geocentric_slow = RT_FALSE;
+
+/**
+* Convert a longitude to the range of -PI,PI
+*/
+double longitude_radians_normalize(const RTCTX *ctx, double lon)
+{
+	if ( lon == -1.0 * M_PI )
+		return M_PI;
+	if ( lon == -2.0 * M_PI )
+		return 0.0;
+
+	if ( lon > 2.0 * M_PI )
+		lon = remainder(lon, 2.0 * M_PI);
+
+	if ( lon < -2.0 * M_PI )
+		lon = remainder(lon, -2.0 * M_PI);
+
+	if ( lon > M_PI )
+		lon = -2.0 * M_PI + lon;
+
+	if ( lon < -1.0 * M_PI )
+		lon = 2.0 * M_PI + lon;
+		
+	if ( lon == -2.0 * M_PI )
+		lon *= -1.0;
+
+	return lon;
+}
+
+/**
+* Convert a latitude to the range of -PI/2,PI/2
+*/
+double latitude_radians_normalize(const RTCTX *ctx, double lat)
+{
+
+	if ( lat > 2.0 * M_PI )
+		lat = remainder(lat, 2.0 * M_PI);
+
+	if ( lat < -2.0 * M_PI )
+		lat = remainder(lat, -2.0 * M_PI);
+
+	if ( lat > M_PI )
+		lat = M_PI - lat;
+
+	if ( lat < -1.0 * M_PI )
+		lat = -1.0 * M_PI - lat;
+
+	if ( lat > M_PI_2 )
+		lat = M_PI - lat;
+
+	if ( lat < -1.0 * M_PI_2 )
+		lat = -1.0 * M_PI - lat;
+
+	return lat;
+}
+
+/**
+* Convert a longitude to the range of -180,180
+* @param lon longitude in degrees
+*/
+double longitude_degrees_normalize(const RTCTX *ctx, double lon)
+{
+	if ( lon > 360.0 )
+		lon = remainder(lon, 360.0);
+
+	if ( lon < -360.0 )
+		lon = remainder(lon, -360.0);
+
+	if ( lon > 180.0 )
+		lon = -360.0 + lon;
+
+	if ( lon < -180.0 )
+		lon = 360 + lon;
+
+	if ( lon == -180.0 )
+		return 180.0;
+
+	if ( lon == -360.0 )
+		return 0.0;
+
+	return lon;
+}
+
+/**
+* Convert a latitude to the range of -90,90
+* @param lat latitude in degrees
+*/
+double latitude_degrees_normalize(const RTCTX *ctx, double lat)
+{
+
+	if ( lat > 360.0 )
+		lat = remainder(lat, 360.0);
+
+	if ( lat < -360.0 )
+		lat = remainder(lat, -360.0);
+
+	if ( lat > 180.0 )
+		lat = 180.0 - lat;
+
+	if ( lat < -180.0 )
+		lat = -180.0 - lat;
+
+	if ( lat > 90.0 )
+		lat = 180.0 - lat;
+
+	if ( lat < -90.0 )
+		lat = -180.0 - lat;
+
+	return lat;
+}
+
+/**
+* Shift a point around by a number of radians
+*/
+void point_shift(const RTCTX *ctx, GEOGRAPHIC_POINT *p, double shift)
+{
+	double lon = p->lon + shift;
+	if ( lon > M_PI )
+		p->lon = -1.0 * M_PI + (lon - M_PI);
+	else
+		p->lon = lon;
+	return;
+}
+
+int geographic_point_equals(const RTCTX *ctx, const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2)
+{
+	return FP_EQUALS(g1->lat, g2->lat) && FP_EQUALS(g1->lon, g2->lon);
+}
+
+/**
+* Initialize a geographic point
+* @param lon longitude in degrees
+* @param lat latitude in degrees
+*/
+void geographic_point_init(const RTCTX *ctx, double lon, double lat, GEOGRAPHIC_POINT *g)
+{
+	g->lat = latitude_radians_normalize(ctx, deg2rad(lat));
+	g->lon = longitude_radians_normalize(ctx, deg2rad(lon));
+}
+
+/** Returns the angular height (latitudinal span) of the box in radians */
+double 
+gbox_angular_height(const RTCTX *ctx, const RTGBOX* gbox)
+{
+	double d[6];
+	int i;
+	double zmin = FLT_MAX;
+	double zmax = -1 * FLT_MAX;
+	POINT3D pt;
+	
+	/* Take a copy of the box corners so we can treat them as a list */
+	/* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
+	memcpy(d, &(gbox->xmin), 6*sizeof(double));
+	
+	/* Generate all 8 corner vectors of the box */
+	for ( i = 0; i < 8; i++ )
+	{
+		pt.x = d[i / 4];
+		pt.y = d[2 + (i % 4) / 2];
+		pt.z = d[4 + (i % 2)];
+		normalize(ctx, &pt);
+		if ( pt.z < zmin ) zmin = pt.z;
+		if ( pt.z > zmax ) zmax = pt.z;
+	}
+	return asin(zmax) - asin(zmin);
+}
+
+/** Returns the angular width (longitudinal span) of the box in radians */
+double 
+gbox_angular_width(const RTCTX *ctx, const RTGBOX* gbox)
+{
+	double d[6];
+	int i, j;
+	POINT3D pt[3];
+	double maxangle;
+	double magnitude;
+
+	/* Take a copy of the box corners so we can treat them as a list */
+	/* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
+	memcpy(d, &(gbox->xmin), 6*sizeof(double));
+
+	/* Start with the bottom corner */
+	pt[0].x = gbox->xmin;
+	pt[0].y = gbox->ymin;
+	magnitude = sqrt(pt[0].x*pt[0].x + pt[0].y*pt[0].y);
+	pt[0].x /= magnitude;
+	pt[0].y /= magnitude;
+
+	/* Generate all 8 corner vectors of the box */
+	/* Find the vector furthest from our seed vector */
+	for ( j = 0; j < 2; j++ )
+	{
+		maxangle = -1 * FLT_MAX;
+		for ( i = 0; i < 4; i++ )
+		{
+			double angle, dotprod;
+			POINT3D pt_n;
+		
+			pt_n.x = d[i / 2];
+			pt_n.y = d[2 + (i % 2)];
+			magnitude = sqrt(pt_n.x*pt_n.x + pt_n.y*pt_n.y);
+			pt_n.x /= magnitude;
+			pt_n.y /= magnitude;
+			pt_n.z = 0.0;
+
+			dotprod = pt_n.x*pt[j].x + pt_n.y*pt[j].y;
+			angle = acos(dotprod > 1.0 ? 1.0 : dotprod);
+			if ( angle > maxangle )
+			{
+				pt[j+1] = pt_n;
+				maxangle = angle;
+			}
+		}
+	}
+	
+	/* Return the distance between the two furthest vectors */
+	return maxangle;
+}
+
+/** Computes the average(ish) center of the box and returns success. */
+int
+gbox_centroid(const RTCTX *ctx, const RTGBOX* gbox, RTPOINT2D* out)
+{
+	double d[6];
+	GEOGRAPHIC_POINT g;
+	POINT3D pt;
+	int i;
+
+	/* Take a copy of the box corners so we can treat them as a list */
+	/* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
+	memcpy(d, &(gbox->xmin), 6*sizeof(double));
+	
+	/* Zero out our return vector */
+	pt.x = pt.y = pt.z = 0.0;
+
+	for ( i = 0; i < 8; i++ )
+	{
+		POINT3D pt_n;
+	
+		pt_n.x = d[i / 4];
+		pt_n.y = d[2 + ((i % 4) / 2)];
+		pt_n.z = d[4 + (i % 2)];
+		normalize(ctx, &pt_n);
+	
+		pt.x += pt_n.x;
+		pt.y += pt_n.y;
+		pt.z += pt_n.z;		
+	}
+	
+	pt.x /= 8.0;
+	pt.y /= 8.0;
+	pt.z /= 8.0;
+	normalize(ctx, &pt);
+
+	cart2geog(ctx, &pt, &g);
+	out->x = longitude_degrees_normalize(ctx, rad2deg(g.lon));
+	out->y = latitude_degrees_normalize(ctx, rad2deg(g.lat));
+	
+	return RT_SUCCESS;
+}
+
+/**
+* Check to see if this geocentric gbox is wrapped around a pole.
+* Only makes sense if this gbox originated from a polygon, as it's assuming
+* the box is generated from external edges and there's an "interior" which
+* contains the pole.
+*
+* This function is overdetermined, for very large polygons it might add an
+* unwarranted pole. STILL NEEDS WORK!
+*/
+static int gbox_check_poles(const RTCTX *ctx, RTGBOX *gbox)
+{
+	int rv = RT_FALSE;
+	RTDEBUG(4, "checking poles");
+	RTDEBUGF(4, "gbox %s", gbox_to_string(ctx, gbox));
+	/* Z axis */
+	if ( gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
+	     gbox->ymin < 0.0 && gbox->ymax > 0.0 )
+	{
+		if ( (gbox->zmin + gbox->zmax) > 0.0 )
+		{
+			RTDEBUG(4, "enclosed positive z axis");
+			gbox->zmax = 1.0;
+		}
+		else
+		{
+			RTDEBUG(4, "enclosed negative z axis");
+			gbox->zmin = -1.0;
+		}
+		rv = RT_TRUE;
+	}
+
+	/* Y axis */
+	if ( gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
+	     gbox->zmin < 0.0 && gbox->zmax > 0.0 )
+	{
+		if ( gbox->ymin + gbox->ymax > 0.0 )
+		{
+			RTDEBUG(4, "enclosed positive y axis");
+			gbox->ymax = 1.0;
+		}
+		else
+		{
+			RTDEBUG(4, "enclosed negative y axis");
+			gbox->ymin = -1.0;
+		}
+		rv = RT_TRUE;
+	}
+
+	/* X axis */
+	if ( gbox->ymin < 0.0 && gbox->ymax > 0.0 &&
+	     gbox->zmin < 0.0 && gbox->zmax > 0.0 )
+	{
+		if ( gbox->xmin + gbox->xmax > 0.0 )
+		{
+			RTDEBUG(4, "enclosed positive x axis");
+			gbox->xmax = 1.0;
+		}
+		else
+		{
+			RTDEBUG(4, "enclosed negative x axis");
+			gbox->xmin = -1.0;
+		}
+		rv = RT_TRUE;
+	}
+
+	return rv;
+}
+
+/**
+* Convert spherical coordinates to cartesion coordinates on unit sphere
+*/
+void geog2cart(const RTCTX *ctx, const GEOGRAPHIC_POINT *g, POINT3D *p)
+{
+	p->x = cos(g->lat) * cos(g->lon);
+	p->y = cos(g->lat) * sin(g->lon);
+	p->z = sin(g->lat);
+}
+
+/**
+* Convert cartesion coordinates on unit sphere to spherical coordinates 
+*/
+void cart2geog(const RTCTX *ctx, const POINT3D *p, GEOGRAPHIC_POINT *g)
+{
+	g->lon = atan2(p->y, p->x);
+	g->lat = asin(p->z);
+}
+
+/**
+* Convert lon/lat coordinates to cartesion coordinates on unit sphere
+*/
+void ll2cart(const RTCTX *ctx, const RTPOINT2D *g, POINT3D *p)
+{
+	double x_rad = M_PI * g->x / 180.0;
+	double y_rad = M_PI * g->y / 180.0;
+	double cos_y_rad = cos(y_rad);
+	p->x = cos_y_rad * cos(x_rad);
+	p->y = cos_y_rad * sin(x_rad);
+	p->z = sin(y_rad);
+}
+
+/**
+* Convert cartesion coordinates on unit sphere to lon/lat coordinates 
+static void cart2ll(const POINT3D *p, RTPOINT2D *g)
+{
+	g->x = longitude_degrees_normalize(ctx, 180.0 * atan2(p->y, p->x) / M_PI);
+	g->y = latitude_degrees_normalize(ctx, 180.0 * asin(p->z) / M_PI);
+}
+*/
+
+/**
+* Calculate the dot product of two unit vectors
+* (-1 == opposite, 0 == orthogonal, 1 == identical)
+*/
+static double dot_product(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2)
+{
+	return (p1->x*p2->x) + (p1->y*p2->y) + (p1->z*p2->z);
+}
+
+/**
+* Calculate the cross product of two vectors
+*/
+static void cross_product(const RTCTX *ctx, const POINT3D *a, const POINT3D *b, POINT3D *n)
+{
+	n->x = a->y * b->z - a->z * b->y;
+	n->y = a->z * b->x - a->x * b->z;
+	n->z = a->x * b->y - a->y * b->x;
+	return;
+}
+
+/**
+* Calculate the sum of two vectors
+*/
+void vector_sum(const RTCTX *ctx, const POINT3D *a, const POINT3D *b, POINT3D *n)
+{
+	n->x = a->x + b->x;
+	n->y = a->y + b->y;
+	n->z = a->z + b->z;
+	return;
+}
+
+/**
+* Calculate the difference of two vectors
+*/
+static void vector_difference(const RTCTX *ctx, const POINT3D *a, const POINT3D *b, POINT3D *n)
+{
+	n->x = a->x - b->x;
+	n->y = a->y - b->y;
+	n->z = a->z - b->z;
+	return;
+}
+
+/**
+* Scale a vector out by a factor
+*/
+static void vector_scale(const RTCTX *ctx, POINT3D *n, double scale)
+{
+	n->x *= scale;
+	n->y *= scale;
+	n->z *= scale;
+	return;
+}
+
+/*
+* static inline double vector_magnitude(const POINT3D* v)
+* {
+*	return sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
+* }
+*/
+
+/**
+* Angle between two unit vectors
+*/
+double vector_angle(const RTCTX *ctx, const POINT3D* v1, const POINT3D* v2)
+{
+	POINT3D v3, normal;
+	double angle, x, y;
+
+	cross_product(ctx, v1, v2, &normal);
+	normalize(ctx, &normal);
+	cross_product(ctx, &normal, v1, &v3);
+	
+	x = dot_product(ctx, v1, v2);
+	y = dot_product(ctx, v2, &v3);
+	
+	angle = atan2(y, x);
+	return angle;
+}
+
+/**
+* Normalize to a unit vector.
+*/
+static void normalize2d(const RTCTX *ctx, RTPOINT2D *p)
+{
+	double d = sqrt(p->x*p->x + p->y*p->y);
+	if (FP_IS_ZERO(d))
+	{
+		p->x = p->y = 0.0;
+		return;
+	}
+	p->x = p->x / d;
+	p->y = p->y / d;
+	return;
+}
+
+/**
+* Calculates the unit normal to two vectors, trying to avoid
+* problems with over-narrow or over-wide cases.
+*/
+void unit_normal(const RTCTX *ctx, const POINT3D *P1, const POINT3D *P2, POINT3D *normal)
+{
+	double p_dot = dot_product(ctx, P1, P2);
+	POINT3D P3;
+	
+	/* If edge is really large, calculate a narrower equivalent angle A1/A3. */
+	if ( p_dot < 0 )
+	{
+		vector_sum(ctx, P1, P2, &P3);
+		normalize(ctx, &P3);
+	}
+	/* If edge is narrow, calculate a wider equivalent angle A1/A3. */
+	else if ( p_dot > 0.95 )
+	{
+		vector_difference(ctx, P2, P1, &P3);
+		normalize(ctx, &P3);
+	}
+	/* Just keep the current angle in A1/A3. */
+	else
+	{
+		P3 = *P2;
+	}
+	
+	/* Normals to the A-plane and B-plane */
+	cross_product(ctx, P1, &P3, normal);
+	normalize(ctx, normal);
+}
+
+/** 
+* Rotates v1 through an angle (in radians) within the plane defined by v1/v2, returns
+* the rotated vector in n.
+*/
+void vector_rotate(const RTCTX *ctx, const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n)
+{
+	POINT3D u;
+	double cos_a = cos(angle);
+	double sin_a = sin(angle);
+	double uxuy, uyuz, uxuz;
+	double ux2, uy2, uz2;
+	double rxx, rxy, rxz, ryx, ryy, ryz, rzx, rzy, rzz;
+	
+	/* Need a unit vector normal to rotate around */
+	unit_normal(ctx, v1, v2, &u);
+	
+	uxuy = u.x * u.y;
+	uxuz = u.x * u.z;
+	uyuz = u.y * u.z;
+	
+	ux2 = u.x * u.x;
+	uy2 = u.y * u.y;
+	uz2 = u.z * u.z;
+	
+	rxx = cos_a + ux2 * (1 - cos_a);
+	rxy = uxuy * (1 - cos_a) - u.z * sin_a;
+	rxz = uxuz * (1 - cos_a) + u.y * sin_a;
+	
+	ryx = uxuy * (1 - cos_a) + u.z * sin_a;
+	ryy = cos_a + uy2 * (1 - cos_a);
+	ryz = uyuz * (1 - cos_a) - u.x * sin_a;
+	
+	rzx = uxuz * (1 - cos_a) - u.y * sin_a;
+	rzy = uyuz * (1 - cos_a) + u.x * sin_a;
+	rzz = cos_a + uz2 * (1 - cos_a);
+
+	n->x = rxx * v1->x + rxy * v1->y + rxz * v1->z;
+	n->y = ryx * v1->x + ryy * v1->y + ryz * v1->z;
+	n->z = rzx * v1->x + rzy * v1->y + rzz * v1->z;
+
+	normalize(ctx, n);
+}
+
+/**
+* Normalize to a unit vector.
+*/
+void normalize(const RTCTX *ctx, POINT3D *p)
+{
+	double d = sqrt(p->x*p->x + p->y*p->y + p->z*p->z);
+	if (FP_IS_ZERO(d))
+	{
+		p->x = p->y = p->z = 0.0;
+		return;
+	}
+	p->x = p->x / d;
+	p->y = p->y / d;
+	p->z = p->z / d;
+	return;
+}
+
+
+/**
+* Computes the cross product of two vectors using their lat, lng representations.
+* Good even for small distances between p and q.
+*/
+void robust_cross_product(const RTCTX *ctx, const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a)
+{
+	double lon_qpp = (q->lon + p->lon) / -2.0;
+	double lon_qmp = (q->lon - p->lon) / 2.0;
+	double sin_p_lat_minus_q_lat = sin(p->lat-q->lat);
+	double sin_p_lat_plus_q_lat = sin(p->lat+q->lat);
+	double sin_lon_qpp = sin(lon_qpp);
+	double sin_lon_qmp = sin(lon_qmp);
+	double cos_lon_qpp = cos(lon_qpp);
+	double cos_lon_qmp = cos(lon_qmp);
+	a->x = sin_p_lat_minus_q_lat * sin_lon_qpp * cos_lon_qmp -
+	       sin_p_lat_plus_q_lat * cos_lon_qpp * sin_lon_qmp;
+	a->y = sin_p_lat_minus_q_lat * cos_lon_qpp * cos_lon_qmp +
+	       sin_p_lat_plus_q_lat * sin_lon_qpp * sin_lon_qmp;
+	a->z = cos(p->lat) * cos(q->lat) * sin(q->lon-p->lon);
+}
+
+void x_to_z(const RTCTX *ctx, POINT3D *p)
+{
+	double tmp = p->z;
+	p->z = p->x;
+	p->x = tmp;
+}
+
+void y_to_z(const RTCTX *ctx, POINT3D *p)
+{
+	double tmp = p->z;
+	p->z = p->y;
+	p->y = tmp;
+}
+
+
+int crosses_dateline(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
+{
+	double sign_s = signum(s->lon);
+	double sign_e = signum(e->lon);
+	double ss = fabs(s->lon);
+	double ee = fabs(e->lon);
+	if ( sign_s == sign_e )
+	{
+		return RT_FALSE;
+	}
+	else
+	{
+		double dl = ss + ee;
+		if ( dl < M_PI )
+			return RT_FALSE;
+		else if ( FP_EQUALS(dl, M_PI) )
+			return RT_FALSE;
+		else
+			return RT_TRUE;
+	}
+}
+
+/**
+* Returns -1 if the point is to the left of the plane formed
+* by the edge, 1 if the point is to the right, and 0 if the 
+* point is on the plane.
+*/
+static int 
+edge_point_side(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
+{
+	POINT3D normal, pt;
+	double w;
+	/* Normal to the plane defined by e */
+	robust_cross_product(ctx, &(e->start), &(e->end), &normal);
+	normalize(ctx, &normal);
+	geog2cart(ctx, p, &pt);
+	/* We expect the dot product of with normal with any vector in the plane to be zero */
+	w = dot_product(ctx, &normal, &pt);
+	RTDEBUGF(4,"dot product %.9g",w);
+	if ( FP_IS_ZERO(w) )
+	{
+		RTDEBUG(4, "point is on plane (dot product is zero)");
+		return 0;
+	}
+	
+	if ( w < 0 )
+		return -1;
+	else 
+		return 1;
+}
+
+/**
+* Returns the angle in radians at point B of the triangle formed by A-B-C
+*/
+static double
+sphere_angle(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b,  const GEOGRAPHIC_POINT *c)
+{
+	POINT3D normal1, normal2;
+	robust_cross_product(ctx, b, a, &normal1);
+	robust_cross_product(ctx, b, c, &normal2);
+	normalize(ctx, &normal1);
+	normalize(ctx, &normal2);
+	return sphere_distance_cartesian(ctx, &normal1, &normal2);	
+}
+
+/**
+* Computes the spherical area of a triangle. If C is to the left of A/B, 
+* the area is negative. If C is to the right of A/B, the area is positive.
+*
+* @param a The first triangle vertex.
+* @param b The second triangle vertex.
+* @param c The last triangle vertex.
+* @return the signed area in radians.
+*/
+static double 
+sphere_signed_area(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
+{
+	double angle_a, angle_b, angle_c;
+	double area_radians = 0.0;
+	int side;
+	GEOGRAPHIC_EDGE e;
+	
+	angle_a = sphere_angle(ctx, b,a,c);
+	angle_b = sphere_angle(ctx, a,b,c);
+	angle_c = sphere_angle(ctx, b,c,a);
+	
+	area_radians = angle_a + angle_b + angle_c - M_PI;
+
+	/* What's the direction of the B/C edge? */
+	e.start = *a;
+	e.end = *b;
+	side = edge_point_side(ctx, &e, c);
+	
+	/* Co-linear points implies no area */
+	if ( side == 0 ) 
+		return 0.0;
+
+	/* Add the sign to the area */
+	return side * area_radians;
+}
+
+
+
+/**
+* Returns true if the point p is on the great circle plane.
+* Forms the scalar triple product of A,B,p and if the volume of the
+* resulting parallelepiped is near zero the point p is on the
+* great circle plane.
+*/
+int edge_point_on_plane(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
+{
+	int side = edge_point_side(ctx, e, p);
+	if ( side == 0 )
+		return RT_TRUE;
+		
+	return RT_FALSE;
+}
+
+/**
+* Returns true if the point p is inside the cone defined by the
+* two ends of the edge e.
+*/
+int edge_point_in_cone(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
+{
+	POINT3D vcp, vs, ve, vp;
+	double vs_dot_vcp, vp_dot_vcp;
+	geog2cart(ctx, &(e->start), &vs);
+	geog2cart(ctx, &(e->end), &ve);
+	/* Antipodal case, everything is inside. */
+	if ( vs.x == -1.0 * ve.x && vs.y == -1.0 * ve.y && vs.z == -1.0 * ve.z )
+		return RT_TRUE;
+	geog2cart(ctx, p, &vp);
+	/* The normalized sum bisects the angle between start and end. */
+	vector_sum(ctx, &vs, &ve, &vcp);
+	normalize(ctx, &vcp);
+	/* The projection of start onto the center defines the minimum similarity */
+	vs_dot_vcp = dot_product(ctx, &vs, &vcp);
+	RTDEBUGF(4,"vs_dot_vcp %.19g",vs_dot_vcp);
+	/* The projection of candidate p onto the center */
+	vp_dot_vcp = dot_product(ctx, &vp, &vcp);
+	RTDEBUGF(4,"vp_dot_vcp %.19g",vp_dot_vcp);
+	/* If p is more similar than start then p is inside the cone */
+	RTDEBUGF(4,"fabs(vp_dot_vcp - vs_dot_vcp) %.39g",fabs(vp_dot_vcp - vs_dot_vcp));
+
+	/*
+	** We want to test that vp_dot_vcp is >= vs_dot_vcp but there are
+	** numerical stability issues for values that are very very nearly
+	** equal. Unfortunately there are also values of vp_dot_vcp that are legitimately
+	** very close to but still less than vs_dot_vcp which we also need to catch.
+	** The tolerance of 10-17 seems to do the trick on 32-bit and 64-bit architectures,
+	** for the test cases here.
+	** However, tuning the tolerance value feels like a dangerous hack.
+	** Fundamentally, the problem is that this test is so sensitive.
+	*/
+
+	/* 1.1102230246251565404236316680908203125e-16 */
+
+	if ( vp_dot_vcp > vs_dot_vcp || fabs(vp_dot_vcp - vs_dot_vcp) < 2e-16 )
+	{
+		RTDEBUG(4, "point is in cone");
+		return RT_TRUE;
+	}
+	RTDEBUG(4, "point is not in cone");
+	return RT_FALSE;
+}
+
+/**
+* True if the longitude of p is within the range of the longitude of the ends of e
+*/
+int edge_contains_coplanar_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
+{
+	GEOGRAPHIC_EDGE g;
+	GEOGRAPHIC_POINT q;
+	double slon = fabs((e->start).lon) + fabs((e->end).lon);
+	double dlon = fabs(fabs((e->start).lon) - fabs((e->end).lon));
+	double slat = (e->start).lat + (e->end).lat;
+
+	RTDEBUGF(4, "e.start == GPOINT(%.6g %.6g) ", (e->start).lat, (e->start).lon);
+	RTDEBUGF(4, "e.end == GPOINT(%.6g %.6g) ", (e->end).lat, (e->end).lon);
+	RTDEBUGF(4, "p == GPOINT(%.6g %.6g) ", p->lat, p->lon);
+
+	/* Copy values into working registers */
+	g = *e;
+	q = *p;
+
+	/* Vertical plane, we need to do this calculation in latitude */
+	if ( FP_EQUALS( g.start.lon, g.end.lon ) )
+	{
+		RTDEBUG(4, "vertical plane, we need to do this calculation in latitude");
+		/* Supposed to be co-planar... */
+		if ( ! FP_EQUALS( q.lon, g.start.lon ) )
+			return RT_FALSE;
+
+		if ( ( g.start.lat <= q.lat && q.lat <= g.end.lat ) ||
+		     ( g.end.lat <= q.lat && q.lat <= g.start.lat ) )
+		{
+			return RT_TRUE;
+		}
+		else
+		{
+			return RT_FALSE;
+		}
+	}
+
+	/* Over the pole, we need normalize latitude and do this calculation in latitude */
+	if ( FP_EQUALS( slon, M_PI ) && ( signum(g.start.lon) != signum(g.end.lon) || FP_EQUALS(dlon, M_PI) ) )
+	{
+		RTDEBUG(4, "over the pole...");
+		/* Antipodal, everything (or nothing?) is inside */
+		if ( FP_EQUALS( slat, 0.0 ) )
+			return RT_TRUE;
+
+		/* Point *is* the north pole */
+		if ( slat > 0.0 && FP_EQUALS(q.lat, M_PI_2 ) )
+			return RT_TRUE;
+
+		/* Point *is* the south pole */
+		if ( slat < 0.0 && FP_EQUALS(q.lat, -1.0 * M_PI_2) )
+			return RT_TRUE;
+
+		RTDEBUG(4, "coplanar?...");
+
+		/* Supposed to be co-planar... */
+		if ( ! FP_EQUALS( q.lon, g.start.lon ) )
+			return RT_FALSE;
+
+		RTDEBUG(4, "north or south?...");
+
+		/* Over north pole, test based on south pole */
+		if ( slat > 0.0 )
+		{
+			RTDEBUG(4, "over the north pole...");
+			if ( q.lat > FP_MIN(g.start.lat, g.end.lat) )
+				return RT_TRUE;
+			else
+				return RT_FALSE;
+		}
+		else
+			/* Over south pole, test based on north pole */
+		{
+			RTDEBUG(4, "over the south pole...");
+			if ( q.lat < FP_MAX(g.start.lat, g.end.lat) )
+				return RT_TRUE;
+			else
+				return RT_FALSE;
+		}
+	}
+
+	/* Dateline crossing, flip everything to the opposite hemisphere */
+	else if ( slon > M_PI && ( signum(g.start.lon) != signum(g.end.lon) ) )
+	{
+		RTDEBUG(4, "crosses dateline, flip longitudes...");
+		if ( g.start.lon > 0.0 )
+			g.start.lon -= M_PI;
+		else
+			g.start.lon += M_PI;
+		if ( g.end.lon > 0.0 )
+			g.end.lon -= M_PI;
+		else
+			g.end.lon += M_PI;
+
+		if ( q.lon > 0.0 )
+			q.lon -= M_PI;
+		else
+			q.lon += M_PI;
+	}
+
+	if ( ( g.start.lon <= q.lon && q.lon <= g.end.lon ) ||
+	     ( g.end.lon <= q.lon && q.lon <= g.start.lon ) )
+	{
+		RTDEBUG(4, "true, this edge contains point");
+		return RT_TRUE;
+	}
+
+	RTDEBUG(4, "false, this edge does not contain point");
+	return RT_FALSE;
+}
+
+
+/**
+* Given two points on a unit sphere, calculate their distance apart in radians.
+*/
+double sphere_distance(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
+{
+	double d_lon = e->lon - s->lon;
+	double cos_d_lon = cos(d_lon);
+	double cos_lat_e = cos(e->lat);
+	double sin_lat_e = sin(e->lat);
+	double cos_lat_s = cos(s->lat);
+	double sin_lat_s = sin(s->lat);
+
+	double a1 = POW2(cos_lat_e * sin(d_lon));
+	double a2 = POW2(cos_lat_s * sin_lat_e - sin_lat_s * cos_lat_e * cos_d_lon);
+	double a = sqrt(a1 + a2);
+	double b = sin_lat_s * sin_lat_e + cos_lat_s * cos_lat_e * cos_d_lon;
+	return atan2(a, b);
+}
+
+/**
+* Given two unit vectors, calculate their distance apart in radians.
+*/
+double sphere_distance_cartesian(const RTCTX *ctx, const POINT3D *s, const POINT3D *e)
+{
+	return acos(dot_product(ctx, s, e));
+}
+
+/**
+* Given two points on a unit sphere, calculate the direction from s to e.
+*/
+double sphere_direction(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d)
+{
+	double heading = 0.0;
+	double f;
+	
+	/* Starting from the poles? Special case. */
+	if ( FP_IS_ZERO(cos(s->lat)) )
+		return (s->lat > 0.0) ? M_PI : 0.0;
+
+	f = (sin(e->lat) - sin(s->lat) * cos(d)) / (sin(d) * cos(s->lat));
+	if ( FP_EQUALS(f, 1.0) )
+		heading = 0.0;
+	else if ( fabs(f) > 1.0 )
+	{
+		RTDEBUGF(4, "f = %g", f);
+		heading = acos(f);
+	}
+	else
+		heading = acos(f);
+
+	if ( sin(e->lon - s->lon) < 0.0 )
+		heading = -1 * heading;
+
+	return heading;
+}
+
+#if 0 /* unused */
+/**
+* Computes the spherical excess of a spherical triangle defined by
+* the three vectices A, B, C. Computes on the unit sphere (i.e., divides
+* edge lengths by the radius, even if the radius is 1.0). The excess is
+* signed based on the sign of the delta longitude of A and B.
+*
+* @param a The first triangle vertex.
+* @param b The second triangle vertex.
+* @param c The last triangle vertex.
+* @return the signed spherical excess.
+*/
+static double sphere_excess(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
+{
+	double a_dist = sphere_distance(ctx, b, c);
+	double b_dist = sphere_distance(ctx, c, a);
+	double c_dist = sphere_distance(ctx, a, b);
+	double hca = sphere_direction(ctx, c, a, b_dist);
+	double hcb = sphere_direction(ctx, c, b, a_dist);
+	double sign = signum(hcb-hca);
+	double ss = (a_dist + b_dist + c_dist) / 2.0;
+	double E = tan(ss/2.0)*tan((ss-a_dist)/2.0)*tan((ss-b_dist)/2.0)*tan((ss-c_dist)/2.0);
+	return 4.0 * atan(sqrt(fabs(E))) * sign;
+}
+#endif
+
+
+/**
+* Returns true if the point p is on the minor edge defined by the
+* end points of e.
+*/
+int edge_contains_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
+{
+	if ( edge_point_in_cone(ctx, e, p) && edge_point_on_plane(ctx, e, p) )
+		/*	if ( edge_contains_coplanar_point(ctx, e, p) && edge_point_on_plane(ctx, e, p) ) */
+	{
+		RTDEBUG(4, "point is on edge");
+		return RT_TRUE;
+	}
+	RTDEBUG(4, "point is not on edge");
+	return RT_FALSE;
+}
+
+/**
+* Used in great circle to compute the pole of the great circle.
+*/
+double z_to_latitude(const RTCTX *ctx, double z, int top)
+{
+	double sign = signum(z);
+	double tlat = acos(z);
+	RTDEBUGF(4, "inputs: z(%.8g) sign(%.8g) tlat(%.8g)", z, sign, tlat);
+	if (FP_IS_ZERO(z))
+	{
+		if (top) return M_PI_2;
+		else return -1.0 * M_PI_2;
+	}
+	if (fabs(tlat) > M_PI_2 )
+	{
+		tlat = sign * (M_PI - fabs(tlat));
+	}
+	else
+	{
+		tlat = sign * tlat;
+	}
+	RTDEBUGF(4, "output: tlat(%.8g)", tlat);
+	return tlat;
+}
+
+/**
+* Computes the pole of the great circle disk which is the intersection of
+* the great circle with the line of maximum/minimum gradiant that lies on
+* the great circle plane.
+*/
+int clairaut_cartesian(const RTCTX *ctx, const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
+{
+	POINT3D t1, t2;
+	GEOGRAPHIC_POINT vN1, vN2;
+	RTDEBUG(4,"entering function");
+	unit_normal(ctx, start, end, &t1);
+	unit_normal(ctx, end, start, &t2);
+	RTDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
+	RTDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
+	cart2geog(ctx, &t1, &vN1);
+	cart2geog(ctx, &t2, &vN2);
+	g_top->lat = z_to_latitude(ctx, t1.z,RT_TRUE);
+	g_top->lon = vN2.lon;
+	g_bottom->lat = z_to_latitude(ctx, t2.z,RT_FALSE);
+	g_bottom->lon = vN1.lon;
+	RTDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
+	RTDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
+	return RT_SUCCESS;
+}
+
+/**
+* Computes the pole of the great circle disk which is the intersection of
+* the great circle with the line of maximum/minimum gradiant that lies on
+* the great circle plane.
+*/
+int clairaut_geographic(const RTCTX *ctx, const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
+{
+	POINT3D t1, t2;
+	GEOGRAPHIC_POINT vN1, vN2;
+	RTDEBUG(4,"entering function");
+	robust_cross_product(ctx, start, end, &t1);
+	normalize(ctx, &t1);
+	robust_cross_product(ctx, end, start, &t2);
+	normalize(ctx, &t2);
+	RTDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
+	RTDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
+	cart2geog(ctx, &t1, &vN1);
+	cart2geog(ctx, &t2, &vN2);
+	g_top->lat = z_to_latitude(ctx, t1.z,RT_TRUE);
+	g_top->lon = vN2.lon;
+	g_bottom->lat = z_to_latitude(ctx, t2.z,RT_FALSE);
+	g_bottom->lon = vN1.lon;
+	RTDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
+	RTDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
+	return RT_SUCCESS;
+}
+
+/**
+* Returns true if an intersection can be calculated, and places it in *g.
+* Returns false otherwise.
+*/
+int edge_intersection(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g)
+{
+	POINT3D ea, eb, v;
+	RTDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", e1->start.lat, e1->start.lon, e1->end.lat, e1->end.lon);
+	RTDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", e2->start.lat, e2->start.lon, e2->end.lat, e2->end.lon);
+
+	RTDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e1->start.lon), rad2deg(e1->start.lat), rad2deg(e1->end.lon), rad2deg(e1->end.lat));
+	RTDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e2->start.lon), rad2deg(e2->start.lat), rad2deg(e2->end.lon), rad2deg(e2->end.lat));
+
+	if ( geographic_point_equals(ctx, &(e1->start), &(e2->start)) )
+	{
+		*g = e1->start;
+		return RT_TRUE;
+	}
+	if ( geographic_point_equals(ctx, &(e1->end), &(e2->end)) )
+	{
+		*g = e1->end;
+		return RT_TRUE;
+	}
+	if ( geographic_point_equals(ctx, &(e1->end), &(e2->start)) )
+	{
+		*g = e1->end;
+		return RT_TRUE;
+	}
+	if ( geographic_point_equals(ctx, &(e1->start), &(e2->end)) )
+	{
+		*g = e1->start;
+		return RT_TRUE;
+	}
+
+	robust_cross_product(ctx, &(e1->start), &(e1->end), &ea);
+	normalize(ctx, &ea);
+	robust_cross_product(ctx, &(e2->start), &(e2->end), &eb);
+	normalize(ctx, &eb);
+	RTDEBUGF(4, "e1 cross product == POINT(%.12g %.12g %.12g)", ea.x, ea.y, ea.z);
+	RTDEBUGF(4, "e2 cross product == POINT(%.12g %.12g %.12g)", eb.x, eb.y, eb.z);
+	RTDEBUGF(4, "fabs(dot_product(ctx, ea, eb)) == %.14g", fabs(dot_product(ctx, &ea, &eb)));
+	if ( FP_EQUALS(fabs(dot_product(ctx, &ea, &eb)), 1.0) )
+	{
+		RTDEBUGF(4, "parallel edges found! dot_product = %.12g", dot_product(ctx, &ea, &eb));
+		/* Parallel (maybe equal) edges! */
+		/* Hack alert, only returning ONE end of the edge right now, most do better later. */
+		/* Hack alart #2, returning a value of 2 to indicate a co-linear crossing event. */
+		if ( edge_contains_point(ctx, e1, &(e2->start)) )
+		{
+			*g = e2->start;
+			return 2;
+		}
+		if ( edge_contains_point(ctx, e1, &(e2->end)) )
+		{
+			*g = e2->end;
+			return 2;
+		}
+		if ( edge_contains_point(ctx, e2, &(e1->start)) )
+		{
+			*g = e1->start;
+			return 2;
+		}
+		if ( edge_contains_point(ctx, e2, &(e1->end)) )
+		{
+			*g = e1->end;
+			return 2;
+		}
+	}
+	unit_normal(ctx, &ea, &eb, &v);
+	RTDEBUGF(4, "v == POINT(%.12g %.12g %.12g)", v.x, v.y, v.z);
+	g->lat = atan2(v.z, sqrt(v.x * v.x + v.y * v.y));
+	g->lon = atan2(v.y, v.x);
+	RTDEBUGF(4, "g == GPOINT(%.12g %.12g)", g->lat, g->lon);
+	RTDEBUGF(4, "g == POINT(%.12g %.12g)", rad2deg(g->lon), rad2deg(g->lat));
+	if ( edge_contains_point(ctx, e1, g) && edge_contains_point(ctx, e2, g) )
+	{
+		return RT_TRUE;
+	}
+	else
+	{
+		RTDEBUG(4, "flipping point to other side of sphere");
+		g->lat = -1.0 * g->lat;
+		g->lon = g->lon + M_PI;
+		if ( g->lon > M_PI )
+		{
+			g->lon = -1.0 * (2.0 * M_PI - g->lon);
+		}
+		if ( edge_contains_point(ctx, e1, g) && edge_contains_point(ctx, e2, g) )
+		{
+			return RT_TRUE;
+		}
+	}
+	return RT_FALSE;
+}
+
+double edge_distance_to_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest)
+{
+	double d1 = 1000000000.0, d2, d3, d_nearest;
+	POINT3D n, p, k;
+	GEOGRAPHIC_POINT gk, g_nearest;
+
+	/* Zero length edge, */
+	if ( geographic_point_equals(ctx, &(e->start), &(e->end)) )
+	{
+		*closest = e->start;
+		return sphere_distance(ctx, &(e->start), gp);
+	}
+
+	robust_cross_product(ctx, &(e->start), &(e->end), &n);
+	normalize(ctx, &n);
+	geog2cart(ctx, gp, &p);
+	vector_scale(ctx, &n, dot_product(ctx, &p, &n));
+	vector_difference(ctx, &p, &n, &k);
+	normalize(ctx, &k);
+	cart2geog(ctx, &k, &gk);
+	if ( edge_contains_point(ctx, e, &gk) )
+	{
+		d1 = sphere_distance(ctx, gp, &gk);
+	}
+	d2 = sphere_distance(ctx, gp, &(e->start));
+	d3 = sphere_distance(ctx, gp, &(e->end));
+
+	d_nearest = d1;
+	g_nearest = gk;
+
+	if ( d2 < d_nearest )
+	{
+		d_nearest = d2;
+		g_nearest = e->start;
+	}
+	if ( d3 < d_nearest )
+	{
+		d_nearest = d3;
+		g_nearest = e->end;
+	}
+	if (closest)
+		*closest = g_nearest;
+
+	return d_nearest;
+}
+
+/**
+* Calculate the distance between two edges.
+* IMPORTANT: this test does not check for edge intersection!!! (distance == 0) 
+* You have to check for intersection before calling this function.
+*/
+double edge_distance_to_edge(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2)
+{
+	double d;
+	GEOGRAPHIC_POINT gcp1s, gcp1e, gcp2s, gcp2e, c1, c2;
+	double d1s = edge_distance_to_point(ctx, e1, &(e2->start), &gcp1s);
+	double d1e = edge_distance_to_point(ctx, e1, &(e2->end), &gcp1e);
+	double d2s = edge_distance_to_point(ctx, e2, &(e1->start), &gcp2s);
+	double d2e = edge_distance_to_point(ctx, e2, &(e1->end), &gcp2e);
+
+	d = d1s;
+	c1 = gcp1s;
+	c2 = e2->start;
+
+	if ( d1e < d )
+	{
+		d = d1e;
+		c1 = gcp1e;
+		c2 = e2->end;
+	}
+
+	if ( d2s < d )
+	{
+		d = d2s;
+		c1 = e1->start;
+		c2 = gcp2s;
+	}
+
+	if ( d2e < d )
+	{
+		d = d2e;
+		c1 = e1->end;
+		c2 = gcp2e;
+	}
+
+	if ( closest1 ) *closest1 = c1;
+	if ( closest2 ) *closest2 = c2;
+
+	return d;
+}
+
+
+/**
+* Given a starting location r, a distance and an azimuth
+* to the new point, compute the location of the projected point on the unit sphere.
+*/
+int sphere_project(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n)
+{
+	double d = distance;
+	double lat1 = r->lat;
+	double lon1 = r->lon;
+	double lat2, lon2;
+
+	lat2 = asin(sin(lat1)*cos(d) + cos(lat1)*sin(d)*cos(azimuth));
+
+	/* If we're going straight up or straight down, we don't need to calculate the longitude */
+	/* TODO: this isn't quite true, what if we're going over the pole? */
+	if ( FP_EQUALS(azimuth, M_PI) || FP_EQUALS(azimuth, 0.0) )
+	{
+		lon2 = r->lon;
+	}
+	else
+	{
+		lon2 = lon1 + atan2(sin(azimuth)*sin(d)*cos(lat1), cos(d)-sin(lat1)*sin(lat2));
+	}
+	
+	if ( isnan(lat2) || isnan(lon2) )
+		return RT_FAILURE;
+
+	n->lat = lat2;
+	n->lon = lon2;
+
+	return RT_SUCCESS;
+}
+
+
+int edge_calculate_gbox_slow(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, RTGBOX *gbox)
+{
+	int steps = 1000000;
+	int i;
+	double dx, dy, dz;
+	double distance = sphere_distance(ctx, &(e->start), &(e->end));
+	POINT3D pn, p, start, end;
+
+	/* Edge is zero length, just return the naive box */
+	if ( FP_IS_ZERO(distance) )
+	{
+		RTDEBUG(4, "edge is zero length. returning");
+		geog2cart(ctx, &(e->start), &start);
+		geog2cart(ctx, &(e->end), &end);
+		gbox_init_point3d(ctx, &start, gbox);
+		gbox_merge_point3d(ctx, &end, gbox);
+		return RT_SUCCESS;
+	}
+
+	/* Edge is antipodal (one point on each side of the globe),
+	   set the box to contain the whole world and return */
+	if ( FP_EQUALS(distance, M_PI) )
+	{
+		RTDEBUG(4, "edge is antipodal. setting to maximum size box, and returning");
+		gbox->xmin = gbox->ymin = gbox->zmin = -1.0;
+		gbox->xmax = gbox->ymax = gbox->zmax = 1.0;
+		return RT_SUCCESS; 
+	}
+
+	/* Walk along the chord between start and end incrementally,
+	   normalizing at each step. */
+	geog2cart(ctx, &(e->start), &start);
+	geog2cart(ctx, &(e->end), &end);
+	dx = (end.x - start.x)/steps;
+	dy = (end.y - start.y)/steps;
+	dz = (end.z - start.z)/steps;
+	p = start;
+	gbox->xmin = gbox->xmax = p.x;
+	gbox->ymin = gbox->ymax = p.y;
+	gbox->zmin = gbox->zmax = p.z;
+	for ( i = 0; i < steps; i++ )
+	{
+		p.x += dx;
+		p.y += dy;
+		p.z += dz;
+		pn = p;
+		normalize(ctx, &pn);
+		gbox_merge_point3d(ctx, &pn, gbox);
+	}
+	return RT_SUCCESS;
+}
+
+/**
+* The magic function, given an edge in spherical coordinates, calculate a
+* 3D bounding box that fully contains it, taking into account the curvature
+* of the sphere on which it is inscribed. 
+*
+* Any arc on the sphere defines a plane that bisects the sphere. In this plane,
+* the arc is a portion of a unit circle.
+* Projecting the end points of the axes (1,0,0), (-1,0,0) etc, into the plane
+* and normalizing yields potential extrema points. Those points on the 
+* side of the plane-dividing line formed by the end points that is opposite
+* the origin of the plane are extrema and should be added to the bounding box.
+*/
+int edge_calculate_gbox(const RTCTX *ctx, const POINT3D *A1, const POINT3D *A2, RTGBOX *gbox)
+{
+	RTPOINT2D R1, R2, RX, O;
+	POINT3D AN, A3;
+	POINT3D X[6];
+	int i, o_side;
+
+	/* Initialize the box with the edge end points */
+	gbox_init_point3d(ctx, A1, gbox);
+	gbox_merge_point3d(ctx, A2, gbox);
+	
+	/* Zero length edge, just return! */
+	if ( p3d_same(ctx, A1, A2) )
+		return RT_SUCCESS;
+	
+	/* Error out on antipodal edge */
+	if ( FP_EQUALS(A1->x, -1*A2->x) && FP_EQUALS(A1->y, -1*A2->y) && FP_EQUALS(A1->z, -1*A2->z) )
+	{
+		rterror(ctx, "Antipodal (180 degrees long) edge detected!");
+		return RT_FAILURE;
+	}
+	
+	/* Create A3, a vector in the plane of A1/A2, orthogonal to A1  */
+	unit_normal(ctx, A1, A2, &AN);
+	unit_normal(ctx, &AN, A1, &A3);
+
+	/* Project A1 and A2 into the 2-space formed by the plane A1/A3 */
+	R1.x = 1.0;
+	R1.y = 0.0;
+	R2.x = dot_product(ctx, A2, A1);
+	R2.y = dot_product(ctx, A2, &A3);
+
+	/* Initialize our 3-space axis points (x+, x-, y+, y-, z+, z-) */
+	memset(X, 0, sizeof(POINT3D) * 6);
+	X[0].x = X[2].y = X[4].z =  1.0;
+	X[1].x = X[3].y = X[5].z = -1.0;
+	
+	/* Initialize a 2-space origin point. */
+	O.x = O.y = 0.0;
+	/* What side of the line joining R1/R2 is O? */
+	o_side = rt_segment_side(ctx, &R1, &R2, &O);
+	
+	/* Add any extrema! */
+	for ( i = 0; i < 6; i++ )
+	{
+		/* Convert 3-space axis points to 2-space unit vectors */
+		RX.x = dot_product(ctx, &(X[i]), A1);
+		RX.y = dot_product(ctx, &(X[i]), &A3);
+		normalize2d(ctx, &RX);
+		
+		/* Any axis end on the side of R1/R2 opposite the origin */
+		/* is an extreme point in the arc, so we add the 3-space */
+		/* version of the point on R1/R2 to the gbox */
+		if ( rt_segment_side(ctx, &R1, &R2, &RX) != o_side )
+		{
+			POINT3D Xn;
+			Xn.x = RX.x * A1->x + RX.y * A3.x;
+			Xn.y = RX.x * A1->y + RX.y * A3.y;
+			Xn.z = RX.x * A1->z + RX.y * A3.z;
+			
+			gbox_merge_point3d(ctx, &Xn, gbox);
+		}
+	}
+
+	return RT_SUCCESS;
+}
+
+void rtpoly_pt_outside(const RTCTX *ctx, const RTPOLY *poly, RTPOINT2D *pt_outside)
+{	
+	/* Make sure we have boxes */
+	if ( poly->bbox )
+	{
+		gbox_pt_outside(ctx, poly->bbox, pt_outside);
+		return;
+	}
+	else
+	{
+		RTGBOX gbox;
+		rtgeom_calculate_gbox_geodetic(ctx, (RTGEOM*)poly, &gbox);
+		gbox_pt_outside(ctx, &gbox, pt_outside);
+		return;
+	}
+}
+
+/**
+* Given a unit geocentric gbox, return a lon/lat (degrees) coordinate point point that is
+* guaranteed to be outside the box (and therefore anything it contains).
+*/
+void gbox_pt_outside(const RTCTX *ctx, const RTGBOX *gbox, RTPOINT2D *pt_outside)
+{
+	double grow = M_PI / 180.0 / 60.0; /* one arc-minute */
+	int i;
+	RTGBOX ge;
+	POINT3D corners[8];
+	POINT3D pt;
+	GEOGRAPHIC_POINT g;
+
+	while ( grow < M_PI )
+	{
+		/* Assign our box and expand it slightly. */
+		ge = *gbox;
+		if ( ge.xmin > -1 ) ge.xmin -= grow;
+		if ( ge.ymin > -1 ) ge.ymin -= grow;
+		if ( ge.zmin > -1 ) ge.zmin -= grow;
+		if ( ge.xmax < 1 )  ge.xmax += grow;
+		if ( ge.ymax < 1 )  ge.ymax += grow;
+		if ( ge.zmax < 1 )  ge.zmax += grow;
+
+		/* Build our eight corner points */
+		corners[0].x = ge.xmin;
+		corners[0].y = ge.ymin;
+		corners[0].z = ge.zmin;
+
+		corners[1].x = ge.xmin;
+		corners[1].y = ge.ymax;
+		corners[1].z = ge.zmin;
+
+		corners[2].x = ge.xmin;
+		corners[2].y = ge.ymin;
+		corners[2].z = ge.zmax;
+
+		corners[3].x = ge.xmax;
+		corners[3].y = ge.ymin;
+		corners[3].z = ge.zmin;
+
+		corners[4].x = ge.xmax;
+		corners[4].y = ge.ymax;
+		corners[4].z = ge.zmin;
+
+		corners[5].x = ge.xmax;
+		corners[5].y = ge.ymin;
+		corners[5].z = ge.zmax;
+
+		corners[6].x = ge.xmin;
+		corners[6].y = ge.ymax;
+		corners[6].z = ge.zmax;
+
+		corners[7].x = ge.xmax;
+		corners[7].y = ge.ymax;
+		corners[7].z = ge.zmax;
+
+		RTDEBUG(4, "trying to use a box corner point...");
+		for ( i = 0; i < 8; i++ )
+		{
+			normalize(ctx, &(corners[i]));
+			RTDEBUGF(4, "testing corner %d: POINT(%.8g %.8g %.8g)", i, corners[i].x, corners[i].y, corners[i].z);
+			if ( ! gbox_contains_point3d(ctx, gbox, &(corners[i])) )
+			{
+				RTDEBUGF(4, "corner %d is outside our gbox", i);
+				pt = corners[i];
+				normalize(ctx, &pt);
+				cart2geog(ctx, &pt, &g);
+				pt_outside->x = rad2deg(g.lon);
+				pt_outside->y = rad2deg(g.lat);
+				RTDEBUGF(4, "returning POINT(%.8g %.8g) as outside point", pt_outside->x, pt_outside->y);
+				return;
+			}
+		}
+
+		/* Try a wider growth to push the corners outside the original box. */
+		grow *= 2.0;
+	}
+
+	/* This should never happen! */
+	rterror(ctx, "BOOM! Could not generate outside point!");
+	return;
+}
+
+
+/**
+* Create a new point array with no segment longer than the input segment length (expressed in radians!)
+* @param pa_in - input point array pointer
+* @param max_seg_length - maximum output segment length in radians
+*/
+static RTPOINTARRAY* 
+ptarray_segmentize_sphere(const RTCTX *ctx, const RTPOINTARRAY *pa_in, double max_seg_length)
+{
+	RTPOINTARRAY *pa_out;
+	int hasz = ptarray_has_z(ctx, pa_in);
+	int hasm = ptarray_has_m(ctx, pa_in);
+	int pa_in_offset = 0; /* input point offset */
+	RTPOINT4D p1, p2, p;
+	POINT3D q1, q2, q, qn;
+	GEOGRAPHIC_POINT g1, g2, g;
+	double d;
+	
+	/* Just crap out on crazy input */
+	if ( ! pa_in )
+		rterror(ctx, "ptarray_segmentize_sphere: null input pointarray");
+	if ( max_seg_length <= 0.0 )	
+		rterror(ctx, "ptarray_segmentize_sphere: maximum segment length must be positive");
+
+	/* Empty starting array */
+	pa_out = ptarray_construct_empty(ctx, hasz, hasm, pa_in->npoints);
+
+	/* Add first point */
+	rt_getPoint4d_p(ctx, pa_in, pa_in_offset, &p1);
+	ptarray_append_point(ctx, pa_out, &p1, RT_FALSE);
+	geographic_point_init(ctx, p1.x, p1.y, &g1);
+	pa_in_offset++;
+	
+	while ( pa_in_offset < pa_in->npoints )
+	{
+		rt_getPoint4d_p(ctx, pa_in, pa_in_offset, &p2);
+		geographic_point_init(ctx, p2.x, p2.y, &g2);
+		
+		/* Skip duplicate points (except in case of 2-point lines!) */
+		if ( (pa_in->npoints > 2) && p4d_same(ctx, &p1, &p2) )
+		{
+			/* Move one offset forward */
+			p1 = p2;
+			g1 = g2;
+			pa_in_offset++;
+			continue;
+		}
+
+		/* How long is this edge? */
+		d = sphere_distance(ctx, &g1, &g2);
+		
+		/* We need to segmentize this edge */
+		if ( d > max_seg_length )
+		{
+			int nsegs = 1 + d / max_seg_length;
+			int i;
+			double dx, dy, dz, dzz = 0, dmm = 0;
+			
+			geog2cart(ctx, &g1, &q1);
+			geog2cart(ctx, &g2, &q2);
+			
+			dx = (q2.x - q1.x) / nsegs;
+			dy = (q2.y - q1.y) / nsegs;
+			dz = (q2.z - q1.z) / nsegs;
+			
+			/* The independent Z/M values on the ptarray */
+			if ( hasz ) dzz = (p2.z - p1.z) / nsegs;
+			if ( hasm ) dmm = (p2.m - p1.m) / nsegs;
+			
+			q = q1;
+			p = p1;
+			
+			for ( i = 0; i < nsegs - 1; i++ )
+			{
+				/* Move one increment forwards */
+				q.x += dx; q.y += dy; q.z += dz;
+				qn = q;
+				normalize(ctx, &qn);
+				
+				/* Back to spherical coordinates */
+				cart2geog(ctx, &qn, &g);
+				/* Back to lon/lat */
+				p.x = rad2deg(g.lon);
+				p.y = rad2deg(g.lat);
+				if ( hasz )
+					p.z += dzz;
+				if ( hasm )
+					p.m += dmm;
+				ptarray_append_point(ctx, pa_out, &p, RT_FALSE);
+			}
+			
+			ptarray_append_point(ctx, pa_out, &p2, RT_FALSE);
+		}
+		/* This edge is already short enough */
+		else
+		{
+			ptarray_append_point(ctx, pa_out, &p2, (pa_in->npoints==2)?RT_TRUE:RT_FALSE);
+		}
+
+		/* Move one offset forward */
+		p1 = p2;
+		g1 = g2;
+		pa_in_offset++;
+	}
+	
+	return pa_out;	
+}
+
+/**
+* Create a new, densified geometry where no segment is longer than max_seg_length.
+* Input geometry is not altered, output geometry must be freed by caller.
+* @param rtg_in = input geometry
+* @param max_seg_length = maximum segment length in radians
+*/
+RTGEOM* 
+rtgeom_segmentize_sphere(const RTCTX *ctx, const RTGEOM *rtg_in, double max_seg_length)
+{
+	RTPOINTARRAY *pa_out;
+	RTLINE *rtline;
+	RTPOLY *rtpoly_in, *rtpoly_out;
+	RTCOLLECTION *rtcol_in, *rtcol_out;
+	int i;
+	
+	/* Reflect NULL */
+	if ( ! rtg_in )
+		return NULL;
+		
+	/* Clone empty */
+	if ( rtgeom_is_empty(ctx, rtg_in) )
+		return rtgeom_clone(ctx, rtg_in);
+	
+	switch (rtg_in->type)
+	{
+	case RTMULTIPOINTTYPE:
+	case RTPOINTTYPE:
+		return rtgeom_clone_deep(ctx, rtg_in);
+		break;
+	case RTLINETYPE:
+		rtline = rtgeom_as_rtline(ctx, rtg_in);
+		pa_out = ptarray_segmentize_sphere(ctx, rtline->points, max_seg_length);
+		return rtline_as_rtgeom(ctx, rtline_construct(ctx, rtg_in->srid, NULL, pa_out));
+		break;
+	case RTPOLYGONTYPE:
+		rtpoly_in = rtgeom_as_rtpoly(ctx, rtg_in);
+		rtpoly_out = rtpoly_construct_empty(ctx, rtg_in->srid, rtgeom_has_z(ctx, rtg_in), rtgeom_has_m(ctx, rtg_in));
+		for ( i = 0; i < rtpoly_in->nrings; i++ )
+		{
+			pa_out = ptarray_segmentize_sphere(ctx, rtpoly_in->rings[i], max_seg_length);
+			rtpoly_add_ring(ctx, rtpoly_out, pa_out);
+		}
+		return rtpoly_as_rtgeom(ctx, rtpoly_out);
+		break;
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		rtcol_in = rtgeom_as_rtcollection(ctx, rtg_in);
+		rtcol_out = rtcollection_construct_empty(ctx, rtg_in->type, rtg_in->srid, rtgeom_has_z(ctx, rtg_in), rtgeom_has_m(ctx, rtg_in));
+		for ( i = 0; i < rtcol_in->ngeoms; i++ )
+		{
+			rtcollection_add_rtgeom(ctx, rtcol_out, rtgeom_segmentize_sphere(ctx, rtcol_in->geoms[i], max_seg_length));
+		}
+		return rtcollection_as_rtgeom(ctx, rtcol_out);
+		break;
+	default:
+		rterror(ctx, "rtgeom_segmentize_sphere: unsupported input geometry type: %d - %s",
+		        rtg_in->type, rttype_name(ctx, rtg_in->type));
+		break;
+	}
+	
+	rterror(ctx, "rtgeom_segmentize_sphere got to the end of the function, should not happen");
+	return NULL;
+}
+
+
+/**
+* Returns the area of the ring (ring must be closed) in square radians (surface of
+* the sphere is 4*PI).
+*/
+double 
+ptarray_area_sphere(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	int i;
+	const RTPOINT2D *p;
+	GEOGRAPHIC_POINT a, b, c;
+	double area = 0.0;
+	
+	/* Return zero on nonsensical inputs */
+	if ( ! pa || pa->npoints < 4 )
+		return 0.0;
+	
+	p = rt_getPoint2d_cp(ctx, pa, 0);
+	geographic_point_init(ctx, p->x, p->y, &a);
+	p = rt_getPoint2d_cp(ctx, pa, 1);
+	geographic_point_init(ctx, p->x, p->y, &b);
+	
+	for ( i = 2; i < pa->npoints-1; i++ )
+	{
+		p = rt_getPoint2d_cp(ctx, pa, i);
+		geographic_point_init(ctx, p->x, p->y, &c);
+		area += sphere_signed_area(ctx, &a, &b, &c);
+		b = c;
+	}
+	
+	return fabs(area);
+}
+
+
+static double ptarray_distance_spheroid(const RTCTX *ctx, const RTPOINTARRAY *pa1, const RTPOINTARRAY *pa2, const SPHEROID *s, double tolerance, int check_intersection)
+{
+	GEOGRAPHIC_EDGE e1, e2;
+	GEOGRAPHIC_POINT g1, g2;
+	GEOGRAPHIC_POINT nearest1, nearest2;
+	POINT3D A1, A2, B1, B2;
+	const RTPOINT2D *p;
+	double distance;
+	int i, j;
+	int use_sphere = (s->a == s->b ? 1 : 0);
+
+	/* Make result really big, so that everything will be smaller than it */
+	distance = FLT_MAX;
+
+	/* Empty point arrays? Return negative */
+	if ( pa1->npoints == 0 || pa2->npoints == 0 )
+		return -1.0;
+
+	/* Handle point/point case here */
+	if ( pa1->npoints == 1 && pa2->npoints == 1 )
+	{
+		p = rt_getPoint2d_cp(ctx, pa1, 0);
+		geographic_point_init(ctx, p->x, p->y, &g1);
+		p = rt_getPoint2d_cp(ctx, pa2, 0);
+		geographic_point_init(ctx, p->x, p->y, &g2);
+		/* Sphere special case, axes equal */
+		distance = s->radius * sphere_distance(ctx, &g1, &g2);
+		if ( use_sphere )
+			return distance;
+		/* Below tolerance, actual distance isn't of interest */
+		else if ( distance < 0.95 * tolerance )
+			return distance;
+		/* Close or greater than tolerance, get the real answer to be sure */
+		else
+			return spheroid_distance(ctx, &g1, &g2, s);
+	}
+
+	/* Handle point/line case here */
+	if ( pa1->npoints == 1 || pa2->npoints == 1 )
+	{
+		/* Handle one/many case here */
+		int i;
+		const RTPOINTARRAY *pa_one;
+		const RTPOINTARRAY *pa_many;
+
+		if ( pa1->npoints == 1 )
+		{
+			pa_one = pa1;
+			pa_many = pa2;
+		}
+		else
+		{
+			pa_one = pa2;
+			pa_many = pa1;
+		}
+
+		/* Initialize our point */
+		p = rt_getPoint2d_cp(ctx, pa_one, 0);
+		geographic_point_init(ctx, p->x, p->y, &g1);
+
+		/* Initialize start of line */
+		p = rt_getPoint2d_cp(ctx, pa_many, 0);
+		geographic_point_init(ctx, p->x, p->y, &(e1.start));
+
+		/* Iterate through the edges in our line */
+		for ( i = 1; i < pa_many->npoints; i++ )
+		{
+			double d;
+			p = rt_getPoint2d_cp(ctx, pa_many, i);
+			geographic_point_init(ctx, p->x, p->y, &(e1.end));
+			/* Get the spherical distance between point and edge */
+			d = s->radius * edge_distance_to_point(ctx, &e1, &g1, &g2);
+			/* New shortest distance! Record this distance / location */
+			if ( d < distance )
+			{
+				distance = d;
+				nearest2 = g2;
+			}
+			/* We've gotten closer than the tolerance... */
+			if ( d < tolerance )
+			{
+				/* Working on a sphere? The answer is correct, return */
+				if ( use_sphere )
+				{
+					return d;
+				}
+				/* Far enough past the tolerance that the spheroid calculation won't change things */
+				else if ( d < tolerance * 0.95 )
+				{
+					return d;
+				}
+				/* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */
+				else
+				{
+					d = spheroid_distance(ctx, &g1, &nearest2, s);
+					/* Yes, closer than tolerance, return! */
+					if ( d < tolerance )
+						return d;
+				}
+			}
+			e1.start = e1.end;
+		}
+
+		/* On sphere, return answer */
+		if ( use_sphere )
+			return distance;
+		/* On spheroid, calculate final answer based on closest approach */
+		else
+			return spheroid_distance(ctx, &g1, &nearest2, s);
+
+	}
+
+	/* Initialize start of line 1 */
+	p = rt_getPoint2d_cp(ctx, pa1, 0);
+	geographic_point_init(ctx, p->x, p->y, &(e1.start));
+	geog2cart(ctx, &(e1.start), &A1);
+
+
+	/* Handle line/line case */
+	for ( i = 1; i < pa1->npoints; i++ )
+	{
+		p = rt_getPoint2d_cp(ctx, pa1, i);
+		geographic_point_init(ctx, p->x, p->y, &(e1.end));
+		geog2cart(ctx, &(e1.end), &A2);
+
+		/* Initialize start of line 2 */
+		p = rt_getPoint2d_cp(ctx, pa2, 0);
+		geographic_point_init(ctx, p->x, p->y, &(e2.start));
+		geog2cart(ctx, &(e2.start), &B1);
+
+		for ( j = 1; j < pa2->npoints; j++ )
+		{
+			double d;
+
+			p = rt_getPoint2d_cp(ctx, pa2, j);
+			geographic_point_init(ctx, p->x, p->y, &(e2.end));
+			geog2cart(ctx, &(e2.end), &B2);
+
+			RTDEBUGF(4, "e1.start == GPOINT(%.6g %.6g) ", e1.start.lat, e1.start.lon);
+			RTDEBUGF(4, "e1.end == GPOINT(%.6g %.6g) ", e1.end.lat, e1.end.lon);
+			RTDEBUGF(4, "e2.start == GPOINT(%.6g %.6g) ", e2.start.lat, e2.start.lon);
+			RTDEBUGF(4, "e2.end == GPOINT(%.6g %.6g) ", e2.end.lat, e2.end.lon);
+
+			if ( check_intersection && edge_intersects(ctx, &A1, &A2, &B1, &B2) )
+			{
+				RTDEBUG(4,"edge intersection! returning 0.0");
+				return 0.0;
+			}
+			d = s->radius * edge_distance_to_edge(ctx, &e1, &e2, &g1, &g2);
+			RTDEBUGF(4,"got edge_distance_to_edge %.8g", d);
+
+			if ( d < distance )
+			{
+				distance = d;
+				nearest1 = g1;
+				nearest2 = g2;
+			}
+			if ( d < tolerance )
+			{
+				if ( use_sphere )
+				{
+					return d;
+				}
+				else
+				{
+					d = spheroid_distance(ctx, &nearest1, &nearest2, s);
+					if ( d < tolerance )
+						return d;
+				}
+			}
+
+			/* Copy end to start to allow a new end value in next iteration */
+			e2.start = e2.end;
+			B1 = B2;
+		}
+
+		/* Copy end to start to allow a new end value in next iteration */
+		e1.start = e1.end;
+		A1 = A2;
+	}
+	RTDEBUGF(4,"finished all loops, returning %.8g", distance);
+
+	if ( use_sphere )
+		return distance;
+	else
+		return spheroid_distance(ctx, &nearest1, &nearest2, s);
+}
+
+
+/**
+* Calculate the area of an RTGEOM. Anything except POLYGON, MULTIPOLYGON
+* and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons
+* calculate external ring area and subtract internal ring area. A RTGBOX is
+* required to calculate an outside point.
+*/
+double rtgeom_area_sphere(const RTCTX *ctx, const RTGEOM *rtgeom, const SPHEROID *spheroid)
+{
+	int type;
+	double radius2 = spheroid->radius * spheroid->radius;
+
+	assert(rtgeom);
+
+	/* No area in nothing */
+	if ( rtgeom_is_empty(ctx, rtgeom) )
+		return 0.0;
+
+	/* Read the geometry type number */
+	type = rtgeom->type;
+
+	/* Anything but polygons and collections returns zero */
+	if ( ! ( type == RTPOLYGONTYPE || type == RTMULTIPOLYGONTYPE || type == RTCOLLECTIONTYPE ) )
+		return 0.0;
+
+	/* Actually calculate area */
+	if ( type == RTPOLYGONTYPE )
+	{
+		RTPOLY *poly = (RTPOLY*)rtgeom;
+		int i;
+		double area = 0.0;
+
+		/* Just in case there's no rings */
+		if ( poly->nrings < 1 )
+			return 0.0;
+
+		/* First, the area of the outer ring */
+		area += radius2 * ptarray_area_sphere(ctx, poly->rings[0]);
+
+		/* Subtract areas of inner rings */
+		for ( i = 1; i < poly->nrings; i++ )
+		{
+			area -= radius2 * ptarray_area_sphere(ctx, poly->rings[i]);
+		}
+		return area;
+	}
+
+	/* Recurse into sub-geometries to get area */
+	if ( type == RTMULTIPOLYGONTYPE || type == RTCOLLECTIONTYPE )
+	{
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom;
+		int i;
+		double area = 0.0;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			area += rtgeom_area_sphere(ctx, col->geoms[i], spheroid);
+		}
+		return area;
+	}
+
+	/* Shouldn't get here. */
+	return 0.0;
+}
+
+
+/**
+* Calculate a projected point given a source point, a distance and a bearing.
+* @param r - location of first point.
+* @param spheroid - spheroid definition.
+* @param distance - distance, in units of the spheroid def'n.
+* @param azimuth - azimuth in radians.
+* @return s - location of projected point.
+* 
+*/
+RTPOINT* rtgeom_project_spheroid(const RTCTX *ctx, const RTPOINT *r, const SPHEROID *spheroid, double distance, double azimuth)
+{
+	GEOGRAPHIC_POINT geo_source, geo_dest;
+	RTPOINT4D pt_dest;
+	double x, y;
+	RTPOINTARRAY *pa;
+	RTPOINT *rtp;
+
+	/* Check the azimuth validity, convert to radians */
+	if ( azimuth < -2.0 * M_PI || azimuth > 2.0 * M_PI ) 
+	{
+		rterror(ctx, "Azimuth must be between -2PI and 2PI");
+		return NULL;
+	}
+
+	/* Check the distance validity */
+	if ( distance < 0.0 || distance > (M_PI * spheroid->radius) )
+	{
+		rterror(ctx, "Distance must be between 0 and %g", M_PI * spheroid->radius);
+		return NULL;
+	}
+		
+	/* Convert to ta geodetic point */
+	x = rtpoint_get_x(ctx, r);
+	y = rtpoint_get_y(ctx, r);
+	geographic_point_init(ctx, x, y, &geo_source);
+	
+	/* Try the projection */
+	if( spheroid_project(ctx, &geo_source, spheroid, distance, azimuth, &geo_dest) == RT_FAILURE ) 
+	{
+		RTDEBUGF(3, "Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
+		rterror(ctx, "Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
+		return NULL;
+	}
+	
+	/* Build the output RTPOINT */
+	pa = ptarray_construct(ctx, 0, 0, 1);
+	pt_dest.x = rad2deg(longitude_radians_normalize(ctx, geo_dest.lon));
+	pt_dest.y = rad2deg(latitude_radians_normalize(ctx, geo_dest.lat));
+	pt_dest.z = pt_dest.m = 0.0;
+	ptarray_set_point4d(ctx, pa, 0, &pt_dest);
+	rtp = rtpoint_construct(ctx, r->srid, NULL, pa);
+	rtgeom_set_geodetic(ctx, rtpoint_as_rtgeom(ctx, rtp), RT_TRUE);
+	return rtp;
+}
+
+
+/**
+* Calculate a bearing (azimuth) given a source and destination point.
+* @param r - location of first point.
+* @param s - location of second point.
+* @param spheroid - spheroid definition.
+* @return azimuth - azimuth in radians. 
+* 
+*/
+double rtgeom_azumith_spheroid(const RTCTX *ctx, const RTPOINT *r, const RTPOINT *s, const SPHEROID *spheroid)
+{
+	GEOGRAPHIC_POINT g1, g2;
+	double x1, y1, x2, y2;
+
+	/* Convert r to a geodetic point */
+	x1 = rtpoint_get_x(ctx, r);
+	y1 = rtpoint_get_y(ctx, r);
+	geographic_point_init(ctx, x1, y1, &g1);
+
+	/* Convert s to a geodetic point */
+	x2 = rtpoint_get_x(ctx, s);
+	y2 = rtpoint_get_y(ctx, s);
+	geographic_point_init(ctx, x2, y2, &g2);
+	
+	/* Same point, return NaN */
+	if ( FP_EQUALS(x1, x2) && FP_EQUALS(y1, y2) )
+	{
+		return NAN;
+	}
+	
+	/* Do the direction calculation */
+	return spheroid_direction(ctx, &g1, &g2, spheroid);
+}
+
+/**
+* Calculate the distance between two RTGEOMs, using the coordinates are
+* longitude and latitude. Return immediately when the calulated distance drops
+* below the tolerance (useful for dwithin calculations).
+* Return a negative distance for incalculable cases.
+*/
+double rtgeom_distance_spheroid(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2, const SPHEROID *spheroid, double tolerance)
+{
+	uint8_t type1, type2;
+	int check_intersection = RT_FALSE;
+	RTGBOX gbox1, gbox2;
+
+	gbox_init(ctx, &gbox1);
+	gbox_init(ctx, &gbox2);
+	
+	assert(rtgeom1);
+	assert(rtgeom2);
+	
+	RTDEBUGF(4, "entered function, tolerance %.8g", tolerance);
+
+	/* What's the distance to an empty geometry? We don't know.
+	   Return a negative number so the caller can catch this case. */
+	if ( rtgeom_is_empty(ctx, rtgeom1) || rtgeom_is_empty(ctx, rtgeom2) )
+	{
+		return -1.0;
+	}
+
+	type1 = rtgeom1->type;
+	type2 = rtgeom2->type;
+
+	/* Make sure we have boxes */
+	if ( rtgeom1->bbox )
+		gbox1 = *(rtgeom1->bbox);
+	else
+		rtgeom_calculate_gbox_geodetic(ctx, rtgeom1, &gbox1);
+
+	/* Make sure we have boxes */
+	if ( rtgeom2->bbox )
+		gbox2 = *(rtgeom2->bbox);
+	else
+		rtgeom_calculate_gbox_geodetic(ctx, rtgeom2, &gbox2);
+
+	/* If the boxes aren't disjoint, we have to check for edge intersections */
+	if ( gbox_overlaps(ctx, &gbox1, &gbox2) )
+		check_intersection = RT_TRUE;
+
+	/* Point/line combinations can all be handled with simple point array iterations */
+	if ( ( type1 == RTPOINTTYPE || type1 == RTLINETYPE ) &&
+	     ( type2 == RTPOINTTYPE || type2 == RTLINETYPE ) )
+	{
+		RTPOINTARRAY *pa1, *pa2;
+
+		if ( type1 == RTPOINTTYPE )
+			pa1 = ((RTPOINT*)rtgeom1)->point;
+		else
+			pa1 = ((RTLINE*)rtgeom1)->points;
+
+		if ( type2 == RTPOINTTYPE )
+			pa2 = ((RTPOINT*)rtgeom2)->point;
+		else
+			pa2 = ((RTLINE*)rtgeom2)->points;
+
+		return ptarray_distance_spheroid(ctx, pa1, pa2, spheroid, tolerance, check_intersection);
+	}
+
+	/* Point/Polygon cases, if point-in-poly, return zero, else return distance. */
+	if ( ( type1 == RTPOLYGONTYPE && type2 == RTPOINTTYPE ) ||
+	     ( type2 == RTPOLYGONTYPE && type1 == RTPOINTTYPE ) )
+	{
+		const RTPOINT2D *p;
+		RTPOLY *rtpoly;
+		RTPOINT *rtpt;
+		double distance = FLT_MAX;
+		int i;
+
+		if ( type1 == RTPOINTTYPE )
+		{
+			rtpt = (RTPOINT*)rtgeom1;
+			rtpoly = (RTPOLY*)rtgeom2;
+		}
+		else
+		{
+			rtpt = (RTPOINT*)rtgeom2;
+			rtpoly = (RTPOLY*)rtgeom1;
+		}
+		p = rt_getPoint2d_cp(ctx, rtpt->point, 0);
+
+		/* Point in polygon implies zero distance */
+		if ( rtpoly_covers_point2d(ctx, rtpoly, p) )
+		{
+			return 0.0;
+		}
+		
+		/* Not inside, so what's the actual distance? */
+		for ( i = 0; i < rtpoly->nrings; i++ )
+		{
+			double ring_distance = ptarray_distance_spheroid(ctx, rtpoly->rings[i], rtpt->point, spheroid, tolerance, check_intersection);
+			if ( ring_distance < distance )
+				distance = ring_distance;
+			if ( distance < tolerance )
+				return distance;
+		}
+		return distance;
+	}
+
+	/* Line/polygon case, if start point-in-poly, return zero, else return distance. */
+	if ( ( type1 == RTPOLYGONTYPE && type2 == RTLINETYPE ) ||
+	     ( type2 == RTPOLYGONTYPE && type1 == RTLINETYPE ) )
+	{
+		const RTPOINT2D *p;
+		RTPOLY *rtpoly;
+		RTLINE *rtline;
+		double distance = FLT_MAX;
+		int i;
+
+		if ( type1 == RTLINETYPE )
+		{
+			rtline = (RTLINE*)rtgeom1;
+			rtpoly = (RTPOLY*)rtgeom2;
+		}
+		else
+		{
+			rtline = (RTLINE*)rtgeom2;
+			rtpoly = (RTPOLY*)rtgeom1;
+		}
+		p = rt_getPoint2d_cp(ctx, rtline->points, 0);
+
+		RTDEBUG(4, "checking if a point of line is in polygon");
+
+		/* Point in polygon implies zero distance */
+		if ( rtpoly_covers_point2d(ctx, rtpoly, p) ) 
+			return 0.0;
+
+		RTDEBUG(4, "checking ring distances");
+
+		/* Not contained, so what's the actual distance? */
+		for ( i = 0; i < rtpoly->nrings; i++ )
+		{
+			double ring_distance = ptarray_distance_spheroid(ctx, rtpoly->rings[i], rtline->points, spheroid, tolerance, check_intersection);
+			RTDEBUGF(4, "ring[%d] ring_distance = %.8g", i, ring_distance);
+			if ( ring_distance < distance )
+				distance = ring_distance;
+			if ( distance < tolerance )
+				return distance;
+		}
+		RTDEBUGF(4, "all rings checked, returning distance = %.8g", distance);
+		return distance;
+
+	}
+
+	/* Polygon/polygon case, if start point-in-poly, return zero, else return distance. */
+	if ( ( type1 == RTPOLYGONTYPE && type2 == RTPOLYGONTYPE ) ||
+	     ( type2 == RTPOLYGONTYPE && type1 == RTPOLYGONTYPE ) )
+	{
+		const RTPOINT2D *p;
+		RTPOLY *rtpoly1 = (RTPOLY*)rtgeom1;
+		RTPOLY *rtpoly2 = (RTPOLY*)rtgeom2;
+		double distance = FLT_MAX;
+		int i, j;
+
+		/* Point of 2 in polygon 1 implies zero distance */
+		p = rt_getPoint2d_cp(ctx, rtpoly1->rings[0], 0);
+		if ( rtpoly_covers_point2d(ctx, rtpoly2, p) )
+			return 0.0;
+
+		/* Point of 1 in polygon 2 implies zero distance */
+		p = rt_getPoint2d_cp(ctx, rtpoly2->rings[0], 0);
+		if ( rtpoly_covers_point2d(ctx, rtpoly1, p) )
+			return 0.0;
+
+		/* Not contained, so what's the actual distance? */
+		for ( i = 0; i < rtpoly1->nrings; i++ )
+		{
+			for ( j = 0; j < rtpoly2->nrings; j++ )
+			{
+				double ring_distance = ptarray_distance_spheroid(ctx, rtpoly1->rings[i], rtpoly2->rings[j], spheroid, tolerance, check_intersection);
+				if ( ring_distance < distance )
+					distance = ring_distance;
+				if ( distance < tolerance )
+					return distance;
+			}
+		}
+		return distance;
+	}
+
+	/* Recurse into collections */
+	if ( rttype_is_collection(ctx, type1) )
+	{
+		int i;
+		double distance = FLT_MAX;
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom1;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			double geom_distance = rtgeom_distance_spheroid(ctx, col->geoms[i], rtgeom2, spheroid, tolerance);
+			if ( geom_distance < distance )
+				distance = geom_distance;
+			if ( distance < tolerance )
+				return distance;
+		}
+		return distance;
+	}
+
+	/* Recurse into collections */
+	if ( rttype_is_collection(ctx, type2) )
+	{
+		int i;
+		double distance = FLT_MAX;
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom2;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			double geom_distance = rtgeom_distance_spheroid(ctx, rtgeom1, col->geoms[i], spheroid, tolerance);
+			if ( geom_distance < distance )
+				distance = geom_distance;
+			if ( distance < tolerance )
+				return distance;
+		}
+		return distance;
+	}
+
+
+	rterror(ctx, "arguments include unsupported geometry type (%s, %s)", rttype_name(ctx, type1), rttype_name(ctx, type1));
+	return -1.0;
+
+}
+
+
+int rtgeom_covers_rtgeom_sphere(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2)
+{
+	int type1, type2;
+	RTGBOX gbox1, gbox2;
+	gbox1.flags = gbox2.flags = 0;
+		
+	assert(rtgeom1);
+	assert(rtgeom2);
+
+	type1 = rtgeom1->type;
+	type2 = rtgeom2->type;
+
+	/* Currently a restricted implementation */
+	if ( ! ( (type1 == RTPOLYGONTYPE || type1 == RTMULTIPOLYGONTYPE || type1 == RTCOLLECTIONTYPE) &&
+	         (type2 == RTPOINTTYPE || type2 == RTMULTIPOINTTYPE || type2 == RTCOLLECTIONTYPE) ) )
+	{
+		rterror(ctx, "rtgeom_covers_rtgeom_sphere: only POLYGON covers POINT tests are currently supported");
+		return RT_FALSE;
+	}
+
+	/* Make sure we have boxes */
+	if ( rtgeom1->bbox )
+		gbox1 = *(rtgeom1->bbox);
+	else
+		rtgeom_calculate_gbox_geodetic(ctx, rtgeom1, &gbox1);
+
+	/* Make sure we have boxes */
+	if ( rtgeom2->bbox )
+		gbox2 = *(rtgeom2->bbox);
+	else
+		rtgeom_calculate_gbox_geodetic(ctx, rtgeom2, &gbox2);
+
+
+	/* Handle the polygon/point case */
+	if ( type1 == RTPOLYGONTYPE && type2 == RTPOINTTYPE )
+	{
+		RTPOINT2D pt_to_test;
+		rt_getPoint2d_p(ctx, ((RTPOINT*)rtgeom2)->point, 0, &pt_to_test);
+		return rtpoly_covers_point2d(ctx, (RTPOLY*)rtgeom1, &pt_to_test);
+	}
+
+	/* If any of the first argument parts covers the second argument, it's true */
+	if ( rttype_is_collection(ctx,  type1 ) )
+	{
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom1;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			if ( rtgeom_covers_rtgeom_sphere(ctx, col->geoms[i], rtgeom2) )
+			{
+				return RT_TRUE;
+			}
+		}
+		return RT_FALSE;
+	}
+
+	/* Only if all of the second arguments are covered by the first argument is the condition true */
+	if ( rttype_is_collection(ctx,  type2 ) )
+	{
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom2;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			if ( ! rtgeom_covers_rtgeom_sphere(ctx, rtgeom1, col->geoms[i]) )
+			{
+				return RT_FALSE;
+			}
+		}
+		return RT_TRUE;
+	}
+
+	/* Don't get here */
+	rterror(ctx, "rtgeom_covers_rtgeom_sphere: reached end of function without resolution");
+	return RT_FALSE;
+
+}
+
+/**
+* Given a polygon (lon/lat decimal degrees) and point (lon/lat decimal degrees) and
+* a guaranteed outside point (lon/lat decimal degrees) (calculate with gbox_pt_outside(ctx))
+* return RT_TRUE if point is inside or on edge of polygon.
+*/
+int rtpoly_covers_point2d(const RTCTX *ctx, const RTPOLY *poly, const RTPOINT2D *pt_to_test)
+{
+	int i;
+	int in_hole_count = 0;
+	POINT3D p;
+	GEOGRAPHIC_POINT gpt_to_test;
+	RTPOINT2D pt_outside;
+	RTGBOX gbox;
+	gbox.flags = 0;
+
+	/* Nulls and empties don't contain anything! */
+	if ( ! poly || rtgeom_is_empty(ctx, (RTGEOM*)poly) )
+	{
+		RTDEBUG(4,"returning false, geometry is empty or null");
+		return RT_FALSE;
+	}
+
+	/* Make sure we have boxes */
+	if ( poly->bbox )
+		gbox = *(poly->bbox);
+	else
+		rtgeom_calculate_gbox_geodetic(ctx, (RTGEOM*)poly, &gbox);
+
+	/* Point not in box? Done! */
+	geographic_point_init(ctx, pt_to_test->x, pt_to_test->y, &gpt_to_test);
+	geog2cart(ctx, &gpt_to_test, &p);
+	if ( ! gbox_contains_point3d(ctx, &gbox, &p) )
+	{
+		RTDEBUG(4, "the point is not in the box!");
+		return RT_FALSE;
+	}
+
+	/* Calculate our outside point from the gbox */
+	gbox_pt_outside(ctx, &gbox, &pt_outside);
+
+	RTDEBUGF(4, "pt_outside POINT(%.18g %.18g)", pt_outside.x, pt_outside.y);
+	RTDEBUGF(4, "pt_to_test POINT(%.18g %.18g)", pt_to_test->x, pt_to_test->y);
+	RTDEBUGF(4, "polygon %s", rtgeom_to_ewkt(ctx, (RTGEOM*)poly));
+	RTDEBUGF(4, "gbox %s", gbox_to_string(ctx, &gbox));
+
+	/* Not in outer ring? We're done! */
+	if ( ! ptarray_contains_point_sphere(ctx, poly->rings[0], &pt_outside, pt_to_test) )
+	{
+		RTDEBUG(4,"returning false, point is outside ring");
+		return RT_FALSE;
+	}
+
+	RTDEBUGF(4, "testing %d rings", poly->nrings);
+
+	/* But maybe point is in a hole... */
+	for ( i = 1; i < poly->nrings; i++ )
+	{
+		RTDEBUGF(4, "ring test loop %d", i);
+		/* Count up hole containment. Odd => outside boundary. */
+		if ( ptarray_contains_point_sphere(ctx, poly->rings[i], &pt_outside, pt_to_test) )
+			in_hole_count++;
+	}
+
+	RTDEBUGF(4, "in_hole_count == %d", in_hole_count);
+
+	if ( in_hole_count % 2 )
+	{
+		RTDEBUG(4,"returning false, inner ring containment count is odd");
+		return RT_FALSE;
+	}
+
+	RTDEBUG(4,"returning true, inner ring containment count is even");
+	return RT_TRUE;
+}
+
+
+/**
+* This function can only be used on RTGEOM that is built on top of
+* GSERIALIZED, otherwise alignment errors will ensue.
+*/
+int rt_getPoint2d_p_ro(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT2D **point)
+{
+	uint8_t *pa_ptr = NULL;
+	assert(pa);
+	assert(n >= 0);
+	assert(n < pa->npoints);
+
+	pa_ptr = rt_getPoint_internal(ctx, pa, n);
+	/* printf( "pa_ptr[0]: %g\n", *((double*)pa_ptr)); */
+	*point = (RTPOINT2D*)pa_ptr;
+
+	return RT_SUCCESS;
+}
+
+int ptarray_calculate_gbox_geodetic(const RTCTX *ctx, const RTPOINTARRAY *pa, RTGBOX *gbox)
+{
+	int i;
+	int first = RT_TRUE;
+	const RTPOINT2D *p;
+	POINT3D A1, A2;
+	RTGBOX edge_gbox;
+
+	assert(gbox);
+	assert(pa);
+
+	gbox_init(ctx, &edge_gbox);
+	edge_gbox.flags = gbox->flags;
+
+	if ( pa->npoints == 0 ) return RT_FAILURE;
+
+	if ( pa->npoints == 1 )
+	{
+		p = rt_getPoint2d_cp(ctx, pa, 0);
+		ll2cart(ctx, p, &A1);
+		gbox->xmin = gbox->xmax = A1.x;
+		gbox->ymin = gbox->ymax = A1.y;
+		gbox->zmin = gbox->zmax = A1.z;
+		return RT_SUCCESS;
+	}
+
+	p = rt_getPoint2d_cp(ctx, pa, 0);
+	ll2cart(ctx, p, &A1);
+	
+	for ( i = 1; i < pa->npoints; i++ )
+	{
+		
+		p = rt_getPoint2d_cp(ctx, pa, i);
+		ll2cart(ctx, p, &A2);
+		
+		edge_calculate_gbox(ctx, &A1, &A2, &edge_gbox);
+
+		/* Initialize the box */
+		if ( first )
+		{
+			gbox_duplicate(ctx, &edge_gbox, gbox);
+			first = RT_FALSE;
+		}
+		/* Expand the box where necessary */
+		else
+		{
+			gbox_merge(ctx, &edge_gbox, gbox);
+		}
+		
+		A1 = A2;
+	}
+
+	return RT_SUCCESS;
+}
+
+static int rtpoint_calculate_gbox_geodetic(const RTCTX *ctx, const RTPOINT *point, RTGBOX *gbox)
+{
+	assert(point);
+	return ptarray_calculate_gbox_geodetic(ctx, point->point, gbox);
+}
+
+static int rtline_calculate_gbox_geodetic(const RTCTX *ctx, const RTLINE *line, RTGBOX *gbox)
+{
+	assert(line);
+	return ptarray_calculate_gbox_geodetic(ctx, line->points, gbox);
+}
+
+static int rtpolygon_calculate_gbox_geodetic(const RTCTX *ctx, const RTPOLY *poly, RTGBOX *gbox)
+{
+	RTGBOX ringbox;
+	int i;
+	int first = RT_TRUE;
+	assert(poly);
+	if ( poly->nrings == 0 )
+		return RT_FAILURE;
+	ringbox.flags = gbox->flags;
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if ( ptarray_calculate_gbox_geodetic(ctx, poly->rings[i], &ringbox) == RT_FAILURE )
+			return RT_FAILURE;
+		if ( first )
+		{
+			gbox_duplicate(ctx, &ringbox, gbox);
+			first = RT_FALSE;
+		}
+		else
+		{
+			gbox_merge(ctx, &ringbox, gbox);
+		}
+	}
+
+	/* If the box wraps a poly, push that axis to the absolute min/max as appropriate */
+	gbox_check_poles(ctx, gbox);
+
+	return RT_SUCCESS;
+}
+
+static int rttriangle_calculate_gbox_geodetic(const RTCTX *ctx, const RTTRIANGLE *triangle, RTGBOX *gbox)
+{
+	assert(triangle);
+	return ptarray_calculate_gbox_geodetic(ctx, triangle->points, gbox);
+}
+
+
+static int rtcollection_calculate_gbox_geodetic(const RTCTX *ctx, const RTCOLLECTION *coll, RTGBOX *gbox)
+{
+	RTGBOX subbox;
+	int i;
+	int result = RT_FAILURE;
+	int first = RT_TRUE;
+	assert(coll);
+	if ( coll->ngeoms == 0 )
+		return RT_FAILURE;
+
+	subbox.flags = gbox->flags;
+
+	for ( i = 0; i < coll->ngeoms; i++ )
+	{
+		if ( rtgeom_calculate_gbox_geodetic(ctx, (RTGEOM*)(coll->geoms[i]), &subbox) == RT_SUCCESS )
+		{
+			/* Keep a copy of the sub-bounding box for later */
+			if ( coll->geoms[i]->bbox ) 
+				rtfree(ctx, coll->geoms[i]->bbox);
+			coll->geoms[i]->bbox = gbox_copy(ctx, &subbox);
+			if ( first )
+			{
+				gbox_duplicate(ctx, &subbox, gbox);
+				first = RT_FALSE;
+			}
+			else
+			{
+				gbox_merge(ctx, &subbox, gbox);
+			}
+			result = RT_SUCCESS;
+		}
+	}
+	return result;
+}
+
+int rtgeom_calculate_gbox_geodetic(const RTCTX *ctx, const RTGEOM *geom, RTGBOX *gbox)
+{
+	int result = RT_FAILURE;
+	RTDEBUGF(4, "got type %d", geom->type);
+
+	/* Add a geodetic flag to the incoming gbox */
+	gbox->flags = gflags(ctx, RTFLAGS_GET_Z(geom->flags),RTFLAGS_GET_M(geom->flags),1);
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		result = rtpoint_calculate_gbox_geodetic(ctx, (RTPOINT*)geom, gbox);
+		break;
+	case RTLINETYPE:
+		result = rtline_calculate_gbox_geodetic(ctx, (RTLINE *)geom, gbox);
+		break;
+	case RTPOLYGONTYPE:
+		result = rtpolygon_calculate_gbox_geodetic(ctx, (RTPOLY *)geom, gbox);
+		break;
+	case RTTRIANGLETYPE:
+		result = rttriangle_calculate_gbox_geodetic(ctx, (RTTRIANGLE *)geom, gbox);
+		break;
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		result = rtcollection_calculate_gbox_geodetic(ctx, (RTCOLLECTION *)geom, gbox);
+		break;
+	default:
+		rterror(ctx, "rtgeom_calculate_gbox_geodetic: unsupported input geometry type: %d - %s",
+		        geom->type, rttype_name(ctx, geom->type));
+		break;
+	}
+	return result;
+}
+
+
+
+static int ptarray_check_geodetic(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	int t;
+	RTPOINT2D pt;
+
+	assert(pa);
+
+	for (t=0; t<pa->npoints; t++)
+	{
+		rt_getPoint2d_p(ctx, pa, t, &pt);
+		/* printf( "%d (%g, %g)\n", t, pt.x, pt.y); */
+		if ( pt.x < -180.0 || pt.y < -90.0 || pt.x > 180.0 || pt.y > 90.0 )
+			return RT_FALSE;
+	}
+
+	return RT_TRUE;
+}
+
+static int rtpoint_check_geodetic(const RTCTX *ctx, const RTPOINT *point)
+{
+	assert(point);
+	return ptarray_check_geodetic(ctx, point->point);
+}
+
+static int rtline_check_geodetic(const RTCTX *ctx, const RTLINE *line)
+{
+	assert(line);
+	return ptarray_check_geodetic(ctx, line->points);
+}
+
+static int rtpoly_check_geodetic(const RTCTX *ctx, const RTPOLY *poly)
+{
+	int i = 0;
+	assert(poly);
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if ( ptarray_check_geodetic(ctx, poly->rings[i]) == RT_FALSE )
+			return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+static int rttriangle_check_geodetic(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	assert(triangle);
+	return ptarray_check_geodetic(ctx, triangle->points);
+}
+
+
+static int rtcollection_check_geodetic(const RTCTX *ctx, const RTCOLLECTION *col)
+{
+	int i = 0;
+	assert(col);
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		if ( rtgeom_check_geodetic(ctx, col->geoms[i]) == RT_FALSE )
+			return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+int rtgeom_check_geodetic(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( rtgeom_is_empty(ctx, geom) ) 
+		return RT_TRUE;
+		
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_check_geodetic(ctx, (RTPOINT *)geom);
+	case RTLINETYPE:
+		return rtline_check_geodetic(ctx, (RTLINE *)geom);
+	case RTPOLYGONTYPE:
+		return rtpoly_check_geodetic(ctx, (RTPOLY *)geom);
+	case RTTRIANGLETYPE:
+		return rttriangle_check_geodetic(ctx, (RTTRIANGLE *)geom);
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_check_geodetic(ctx, (RTCOLLECTION *)geom);
+	default:
+		rterror(ctx, "rtgeom_check_geodetic: unsupported input geometry type: %d - %s",
+		        geom->type, rttype_name(ctx, geom->type));
+	}
+	return RT_FALSE;
+}
+
+static int ptarray_force_geodetic(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	int t;
+	int changed = RT_FALSE;
+	RTPOINT4D pt;
+
+	assert(pa);
+
+	for ( t=0; t < pa->npoints; t++ )
+	{
+		rt_getPoint4d_p(ctx, pa, t, &pt);
+		if ( pt.x < -180.0 || pt.x > 180.0 || pt.y < -90.0 || pt.y > 90.0 )
+		{
+			pt.x = longitude_degrees_normalize(ctx, pt.x); 
+			pt.y = latitude_degrees_normalize(ctx, pt.y); 
+			ptarray_set_point4d(ctx, pa, t, &pt);
+			changed = RT_TRUE;
+		}
+	}
+	return changed;  
+}
+
+static int rtpoint_force_geodetic(const RTCTX *ctx, RTPOINT *point)
+{
+	assert(point);
+	return ptarray_force_geodetic(ctx, point->point);
+}
+
+static int rtline_force_geodetic(const RTCTX *ctx, RTLINE *line)
+{
+	assert(line);
+	return ptarray_force_geodetic(ctx, line->points);
+}
+
+static int rtpoly_force_geodetic(const RTCTX *ctx, RTPOLY *poly)
+{
+	int i = 0;
+	int changed = RT_FALSE;
+	assert(poly);
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if ( ptarray_force_geodetic(ctx, poly->rings[i]) == RT_TRUE )
+			changed = RT_TRUE;
+	}
+	return changed;
+}
+
+static int rtcollection_force_geodetic(const RTCTX *ctx, RTCOLLECTION *col)
+{
+	int i = 0;
+	int changed = RT_FALSE;
+	assert(col);
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		if ( rtgeom_force_geodetic(ctx, col->geoms[i]) == RT_TRUE )
+			changed = RT_TRUE;
+	}
+	return changed;
+}
+
+int rtgeom_force_geodetic(const RTCTX *ctx, RTGEOM *geom)
+{
+	switch ( rtgeom_get_type(ctx, geom) )
+	{
+		case RTPOINTTYPE:
+			return rtpoint_force_geodetic(ctx, (RTPOINT *)geom);
+		case RTLINETYPE:
+			return rtline_force_geodetic(ctx, (RTLINE *)geom);
+		case RTPOLYGONTYPE:
+			return rtpoly_force_geodetic(ctx, (RTPOLY *)geom);
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+			return rtcollection_force_geodetic(ctx, (RTCOLLECTION *)geom);
+		default:
+			rterror(ctx, "unsupported input geometry type: %d", rtgeom_get_type(ctx, geom));
+	}
+	return RT_FALSE;
+}
+
+
+double ptarray_length_spheroid(const RTCTX *ctx, const RTPOINTARRAY *pa, const SPHEROID *s)
+{
+	GEOGRAPHIC_POINT a, b;
+	double za = 0.0, zb = 0.0;
+	RTPOINT4D p;
+	int i;
+	int hasz = RT_FALSE;
+	double length = 0.0;
+	double seglength = 0.0;
+
+	/* Return zero on non-sensical inputs */
+	if ( ! pa || pa->npoints < 2 )
+		return 0.0;
+
+	/* See if we have a third dimension */
+	hasz = RTFLAGS_GET_Z(pa->flags);
+
+	/* Initialize first point */
+	rt_getPoint4d_p(ctx, pa, 0, &p);
+	geographic_point_init(ctx, p.x, p.y, &a);
+	if ( hasz ) 
+		za = p.z;
+
+	/* Loop and sum the length for each segment */
+	for ( i = 1; i < pa->npoints; i++ )
+	{
+		seglength = 0.0;
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		geographic_point_init(ctx, p.x, p.y, &b);
+		if ( hasz ) 
+			zb = p.z;
+
+		/* Special sphere case */
+		if ( s->a == s->b )
+			seglength = s->radius * sphere_distance(ctx, &a, &b);
+		/* Spheroid case */
+		else
+			seglength = spheroid_distance(ctx, &a, &b, s);
+
+		/* Add in the vertical displacement if we're in 3D */
+		if ( hasz ) 
+			seglength = sqrt( (zb-za)*(zb-za) + seglength*seglength );
+			
+		/* Add this segment length to the total */
+		length += seglength;
+
+		/* B gets incremented in the next loop, so we save the value here */
+		a = b;
+		za = zb;
+	}
+	return length;
+}
+
+double rtgeom_length_spheroid(const RTCTX *ctx, const RTGEOM *geom, const SPHEROID *s)
+{
+	int type;
+	int i = 0;
+	double length = 0.0;
+
+	assert(geom);
+
+	/* No area in nothing */
+	if ( rtgeom_is_empty(ctx, geom) )
+		return 0.0;
+
+	type = geom->type;
+
+	if ( type == RTPOINTTYPE || type == RTMULTIPOINTTYPE )
+		return 0.0;
+
+	if ( type == RTLINETYPE )
+		return ptarray_length_spheroid(ctx, ((RTLINE*)geom)->points, s);
+
+	if ( type == RTPOLYGONTYPE )
+	{
+		RTPOLY *poly = (RTPOLY*)geom;
+		for ( i = 0; i < poly->nrings; i++ )
+		{
+			length += ptarray_length_spheroid(ctx, poly->rings[i], s);
+		}
+		return length;
+	}
+
+	if ( type == RTTRIANGLETYPE )
+		return ptarray_length_spheroid(ctx, ((RTTRIANGLE*)geom)->points, s);
+
+	if ( rttype_is_collection(ctx,  type ) )
+	{
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			length += rtgeom_length_spheroid(ctx, col->geoms[i], s);
+		}
+		return length;
+	}
+
+	rterror(ctx, "unsupported type passed to rtgeom_length_sphere");
+	return 0.0;
+}
+
+/**
+* When features are snapped or sometimes they are just this way, they are very close to 
+* the geodetic bounds but slightly over. This routine nudges those points, and only
+* those points, back over to the bounds.
+* http://trac.osgeo.org/postgis/ticket/1292
+*/
+static int 
+ptarray_nudge_geodetic(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+
+	int i;
+	RTPOINT4D p;
+	int altered = RT_FALSE;
+	int rv = RT_FALSE;
+	static double tolerance = 1e-10;
+
+	if ( ! pa )
+		rterror(ctx, "ptarray_nudge_geodetic called with null input");
+
+	for(i = 0; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		if ( p.x < -180.0 && (-180.0 - p.x < tolerance) )
+		{
+			p.x = -180.0;
+			altered = RT_TRUE;
+		}
+		if ( p.x > 180.0 && (p.x - 180.0 < tolerance) )
+		{
+			p.x = 180.0;
+			altered = RT_TRUE;
+		}
+		if ( p.y < -90.0 && (-90.0 - p.y < tolerance) )
+		{
+			p.y = -90.0;
+			altered = RT_TRUE;
+		}
+		if ( p.y > 90.0 && (p.y - 90.0 < tolerance) )
+		{
+			p.y = 90.0;
+			altered = RT_TRUE;
+		}
+		if ( altered == RT_TRUE )
+		{
+			ptarray_set_point4d(ctx, pa, i, &p);
+			altered = RT_FALSE;
+			rv = RT_TRUE;
+		}
+	}
+	return rv;
+}
+
+/**
+* When features are snapped or sometimes they are just this way, they are very close to 
+* the geodetic bounds but slightly over. This routine nudges those points, and only
+* those points, back over to the bounds.
+* http://trac.osgeo.org/postgis/ticket/1292
+*/
+int 
+rtgeom_nudge_geodetic(const RTCTX *ctx, RTGEOM *geom)
+{
+	int type;
+	int i = 0;
+	int rv = RT_FALSE;
+
+	assert(geom);
+
+	/* No points in nothing */
+	if ( rtgeom_is_empty(ctx, geom) )
+		return RT_FALSE;
+
+	type = geom->type;
+
+	if ( type == RTPOINTTYPE )
+		return ptarray_nudge_geodetic(ctx, ((RTPOINT*)geom)->point);
+
+	if ( type == RTLINETYPE )
+		return ptarray_nudge_geodetic(ctx, ((RTLINE*)geom)->points);
+
+	if ( type == RTPOLYGONTYPE )
+	{
+		RTPOLY *poly = (RTPOLY*)geom;
+		for ( i = 0; i < poly->nrings; i++ )
+		{
+			int n = ptarray_nudge_geodetic(ctx, poly->rings[i]);
+			rv = (rv == RT_TRUE ? rv : n);
+		}
+		return rv;
+	}
+
+	if ( type == RTTRIANGLETYPE )
+		return ptarray_nudge_geodetic(ctx, ((RTTRIANGLE*)geom)->points);
+
+	if ( rttype_is_collection(ctx,  type ) )
+	{
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			int n = rtgeom_nudge_geodetic(ctx, col->geoms[i]);
+			rv = (rv == RT_TRUE ? rv : n);
+		}
+		return rv;
+	}
+
+	rterror(ctx, "unsupported type (%s) passed to rtgeom_nudge_geodetic", rttype_name(ctx, type));
+	return rv;
+}
+
+
+/**
+* Utility function for checking if P is within the cone defined by A1/A2.
+*/
+static int
+point_in_cone(const RTCTX *ctx, const POINT3D *A1, const POINT3D *A2, const POINT3D *P)
+{
+	POINT3D AC; /* Center point of A1/A2 */
+	double min_similarity, similarity;
+	
+	/* The normalized sum bisects the angle between start and end. */
+	vector_sum(ctx, A1, A2, &AC);
+	normalize(ctx, &AC);
+	
+	/* The projection of start onto the center defines the minimum similarity */
+	min_similarity = dot_product(ctx, A1, &AC);
+
+	/* The projection of candidate p onto the center */
+	similarity = dot_product(ctx, P, &AC);
+
+	/* If the point is more similar than the end, the point is in the cone */
+	if ( similarity > min_similarity || fabs(similarity - min_similarity) < 2e-16 )
+	{
+		return RT_TRUE;
+	}
+	return RT_FALSE;
+}
+
+
+/**
+* Utility function for ptarray_contains_point_sphere(ctx)
+*/
+static int 
+point3d_equals(const RTCTX *ctx, const POINT3D *p1, const POINT3D *p2)
+{
+	return FP_EQUALS(p1->x, p2->x) && FP_EQUALS(p1->y, p2->y) && FP_EQUALS(p1->z, p2->z);
+}
+
+/**
+* Utility function for edge_intersects(ctx), signum with a tolerance
+* in determining if the value is zero.
+*/
+static int
+dot_product_side(const RTCTX *ctx, const POINT3D *p, const POINT3D *q)
+{
+	double dp = dot_product(ctx, p, q);
+
+	if ( FP_IS_ZERO(dp) )
+		return 0;
+		
+	return dp < 0.0 ? -1 : 1;
+}
+
+/**
+* Returns non-zero if edges A and B interact. The type of interaction is given in the 
+* return value with the bitmask elements defined above.
+*/
+int 
+edge_intersects(const RTCTX *ctx, const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2)
+{
+	POINT3D AN, BN, VN;  /* Normals to plane A and plane B */
+	double ab_dot;
+	int a1_side, a2_side, b1_side, b2_side;
+	int rv = PIR_NO_INTERACT;
+	
+	/* Normals to the A-plane and B-plane */
+	unit_normal(ctx, A1, A2, &AN);
+	unit_normal(ctx, B1, B2, &BN);
+	
+	/* Are A-plane and B-plane basically the same? */
+	ab_dot = dot_product(ctx, &AN, &BN);
+	if ( FP_EQUALS(fabs(ab_dot), 1.0) )
+	{
+		/* Co-linear case */
+		if ( point_in_cone(ctx, A1, A2, B1) || point_in_cone(ctx, A1, A2, B2) || 
+		     point_in_cone(ctx, B1, B2, A1) || point_in_cone(ctx, B1, B2, A2) )
+		{
+			rv |= PIR_INTERSECTS;
+			rv |= PIR_COLINEAR;
+		}
+		return rv;
+	}
+	
+	/* What side of plane-A and plane-B do the end points */
+	/* of A and B fall? */
+	a1_side = dot_product_side(ctx, &BN, A1);
+	a2_side = dot_product_side(ctx, &BN, A2);
+	b1_side = dot_product_side(ctx, &AN, B1);
+	b2_side = dot_product_side(ctx, &AN, B2);
+
+	/* Both ends of A on the same side of plane B. */
+	if ( a1_side == a2_side && a1_side != 0 )
+	{
+		/* No intersection. */
+		return PIR_NO_INTERACT;
+	}
+
+	/* Both ends of B on the same side of plane A. */
+	if ( b1_side == b2_side && b1_side != 0 )
+	{
+		/* No intersection. */
+		return PIR_NO_INTERACT;
+	}
+
+	/* A straddles B and B straddles A, so... */
+	if ( a1_side != a2_side && (a1_side + a2_side) == 0 &&
+	     b1_side != b2_side && (b1_side + b2_side) == 0 )
+	{
+		/* Have to check if intersection point is inside both arcs */
+		unit_normal(ctx, &AN, &BN, &VN);
+		if ( point_in_cone(ctx, A1, A2, &VN) && point_in_cone(ctx, B1, B2, &VN) )
+		{
+			return PIR_INTERSECTS;
+		}
+
+		/* Have to check if intersection point is inside both arcs */
+		vector_scale(ctx, &VN, -1);
+		if ( point_in_cone(ctx, A1, A2, &VN) && point_in_cone(ctx, B1, B2, &VN) )
+		{
+			return PIR_INTERSECTS;
+		}
+		
+		return PIR_NO_INTERACT;
+	}
+
+	/* The rest are all intersects variants... */
+	rv |= PIR_INTERSECTS;
+
+	/* A touches B */
+	if ( a1_side == 0 )
+	{
+		/* Touches at A1, A2 is on what side? */
+		rv |= (a2_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
+	}
+	else if ( a2_side == 0 )
+	{
+		/* Touches at A2, A1 is on what side? */
+		rv |= (a1_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
+	}
+
+	/* B touches A */
+	if ( b1_side == 0 )
+	{
+		/* Touches at B1, B2 is on what side? */
+		rv |= (b2_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
+	}
+	else if ( b2_side == 0 )
+	{
+		/* Touches at B2, B1 is on what side? */
+		rv |= (b1_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
+	}
+	
+	return rv;
+}
+
+/**
+* This routine returns RT_TRUE if the stabline joining the pt_outside and pt_to_test
+* crosses the ring an odd number of times, or if the pt_to_test is on the ring boundary itself,
+* returning RT_FALSE otherwise.
+* The pt_outside *must* be guaranteed to be outside the ring (use the geography_pt_outside() function
+* to derive one in postgis, or the gbox_pt_outside(ctx) function if you don't mind burning CPU cycles
+* building a gbox first).
+*/
+int ptarray_contains_point_sphere(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt_outside, const RTPOINT2D *pt_to_test)
+{
+	POINT3D S1, S2; /* Stab line end points */
+	POINT3D E1, E2; /* Edge end points (3-space) */
+	RTPOINT2D p; /* Edge end points (lon/lat) */
+	int count = 0, i, inter;
+
+	/* Null input, not enough points for a ring? You ain't closed! */
+	if ( ! pa || pa->npoints < 4 )
+		return RT_FALSE;
+
+	/* Set up our stab line */
+	ll2cart(ctx, pt_to_test, &S1);
+	ll2cart(ctx, pt_outside, &S2);
+
+	/* Initialize first point */
+	rt_getPoint2d_p(ctx, pa, 0, &p);
+	ll2cart(ctx, &p, &E1);
+
+	/* Walk every edge and see if the stab line hits it */
+	for ( i = 1; i < pa->npoints; i++ )
+	{
+		RTDEBUGF(4, "testing edge (%d)", i);
+		RTDEBUGF(4, "  start point == POINT(%.12g %.12g)", p.x, p.y);
+
+		/* Read next point. */
+		rt_getPoint2d_p(ctx, pa, i, &p);
+		ll2cart(ctx, &p, &E2);
+
+		/* Skip over too-short edges. */
+		if ( point3d_equals(ctx, &E1, &E2) )
+		{
+			continue;
+		}
+		
+		/* Our test point is on an edge end! Point is "in ring" by our definition */
+		if ( point3d_equals(ctx, &S1, &E1) )
+		{
+			return RT_TRUE;
+		}
+		
+		/* Calculate relationship between stab line and edge */
+		inter = edge_intersects(ctx, &S1, &S2, &E1, &E2);
+		
+		/* We have some kind of interaction... */
+		if ( inter & PIR_INTERSECTS )
+		{
+			/* If the stabline is touching the edge, that implies the test point */
+			/* is on the edge, so we're done, the point is in (on) the ring. */
+			if ( (inter & PIR_A_TOUCH_RIGHT) || (inter & PIR_A_TOUCH_LEFT) )
+			{
+				return RT_TRUE;
+			}
+			
+			/* It's a touching interaction, disregard all the left-side ones. */
+			/* It's a co-linear intersection, ignore those. */
+			if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR )
+			{
+				/* Do nothing, to avoid double counts. */
+				RTDEBUGF(4,"    edge (%d) crossed, disregarding to avoid double count", i, count);
+			}
+			else
+			{
+				/* Increment crossingn count. */
+				count++;
+				RTDEBUGF(4,"    edge (%d) crossed, count == %d", i, count);
+			}
+		}
+		else
+		{
+			RTDEBUGF(4,"    edge (%d) did not cross", i);
+		}
+		
+		/* Increment to next edge */
+		E1 = E2;
+	}
+
+	RTDEBUGF(4,"final count == %d", count);
+
+	/* An odd number of crossings implies containment! */
+	if ( count % 2 )
+	{
+		return RT_TRUE;
+	}
+
+	return RT_FALSE;
+}
diff --git a/src/rtgeodetic.h b/src/rtgeodetic.h
new file mode 100644
index 0000000..34b0f5c
--- /dev/null
+++ b/src/rtgeodetic.h
@@ -0,0 +1,163 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#ifndef _RTGEODETIC_H
+#define _RTGEODETIC_H 1
+
+/* For NAN */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <math.h>
+
+#ifndef NAN
+#define NAN 0.0/0.0
+#endif
+
+extern int gbox_geocentric_slow;
+
+#define POW2(x) ((x)*(x))
+
+/**
+* Point in spherical coordinates on the world. Units of radians.
+*/
+typedef struct
+{
+	double lon;
+	double lat;
+} GEOGRAPHIC_POINT;
+
+/**
+* Two-point great circle segment from a to b.
+*/
+typedef struct
+{
+	GEOGRAPHIC_POINT start;
+	GEOGRAPHIC_POINT end;
+} GEOGRAPHIC_EDGE;
+
+/**
+* Holder for sorting points in distance algorithm
+*/
+typedef struct
+{
+	double measure;
+	uint32_t index;
+} DISTANCE_ORDER;
+
+/**
+* Conversion functions
+*/
+#define deg2rad(d) (M_PI * (d) / 180.0)
+#define rad2deg(r) (180.0 * (r) / M_PI)
+
+/**
+* Ape a java function
+*/
+#define signum(a) ((a) < 0 ? -1 : ((a) > 0 ? 1 : (a)))
+
+
+/**
+* Bitmask elements for edge_intersects(const RTCTX *ctx) return value.
+*/
+#define PIR_NO_INTERACT    0x00
+#define PIR_INTERSECTS     0x01
+#define PIR_COLINEAR       0x02
+#define PIR_A_TOUCH_RIGHT   0x04
+#define PIR_A_TOUCH_LEFT  0x08
+#define PIR_B_TOUCH_RIGHT   0x10
+#define PIR_B_TOUCH_LEFT  0x20
+
+
+/*
+* Geodetic calculations
+*/
+void geog2cart(const RTCTX *ctx, const GEOGRAPHIC_POINT *g, POINT3D *p);
+void cart2geog(const RTCTX *ctx, const POINT3D *p, GEOGRAPHIC_POINT *g);
+void robust_cross_product(const RTCTX *ctx, const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a);
+void x_to_z(const RTCTX *ctx, POINT3D *p);
+void y_to_z(const RTCTX *ctx, POINT3D *p);
+int edge_point_on_plane(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p);
+int edge_point_in_cone(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p);
+int edge_contains_coplanar_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p);
+int edge_contains_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p);
+double z_to_latitude(const RTCTX *ctx, double z, int top);
+int clairaut_cartesian(const RTCTX *ctx, const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom);
+int clairaut_geographic(const RTCTX *ctx, const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom);
+double sphere_distance(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e);
+double sphere_distance_cartesian(const RTCTX *ctx, const POINT3D *s, const POINT3D *e);
+int sphere_project(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n);
+int edge_calculate_gbox_slow(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, RTGBOX *gbox);
+int edge_calculate_gbox(const RTCTX *ctx, const POINT3D *A1, const POINT3D *A2, RTGBOX *gbox);
+int edge_intersection(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g);
+int edge_intersects(const RTCTX *ctx, const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2);
+double edge_distance_to_point(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest);
+double edge_distance_to_edge(const RTCTX *ctx, const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2);
+void geographic_point_init(const RTCTX *ctx, double lon, double lat, GEOGRAPHIC_POINT *g);
+int ptarray_contains_point_sphere(const RTCTX *ctx, const RTPOINTARRAY *pa, const RTPOINT2D *pt_outside, const RTPOINT2D *pt_to_test);
+int rtpoly_covers_point2d(const RTCTX *ctx, const RTPOLY *poly, const RTPOINT2D *pt_to_test);
+void rtpoly_pt_outside(const RTCTX *ctx, const RTPOLY *poly, RTPOINT2D *pt_outside);
+int ptarray_point_in_ring(const RTPOINTARRAY *pa, const RTPOINT2D *pt_outside, const RTPOINT2D *pt_to_test);
+double ptarray_area_sphere(const RTCTX *ctx, const RTPOINTARRAY *pa);
+double latitude_degrees_normalize(const RTCTX *ctx, double lat);
+double longitude_degrees_normalize(const RTCTX *ctx, double lon);
+double ptarray_length_spheroid(const RTCTX *ctx, const RTPOINTARRAY *pa, const SPHEROID *s);
+int geographic_point_equals(const RTCTX *ctx, const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2);
+int crosses_dateline(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e);
+void point_shift(const RTCTX *ctx, GEOGRAPHIC_POINT *p, double shift);
+double longitude_radians_normalize(const RTCTX *ctx, double lon);
+double latitude_radians_normalize(const RTCTX *ctx, double lat);
+void vector_sum(const RTCTX *ctx, const POINT3D *a, const POINT3D *b, POINT3D *n);
+double vector_angle(const RTCTX *ctx, const POINT3D* v1, const POINT3D* v2);
+void vector_rotate(const RTCTX *ctx, const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n);
+void normalize(const RTCTX *ctx, POINT3D *p);
+void unit_normal(const RTCTX *ctx, const POINT3D *P1, const POINT3D *P2, POINT3D *normal);
+double sphere_direction(const RTCTX *ctx, const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d);
+void ll2cart(const RTCTX *ctx, const RTPOINT2D *g, POINT3D *p);
+
+/*
+** Prototypes for spheroid functions.
+*/
+double spheroid_distance(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid);
+double spheroid_direction(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid);
+int spheroid_project(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g);
+
+
+#endif /* _RTGEODETIC_H */
+
+
+
+/**
+* Notes for rewrite
+* 
+* Define separate POINT types for 2-d-points-in-radiands and 3-d-points-in-geocentric
+* Maintain consistent units (radians?) throughout all calculations
+* Put an index pointer onto RTGEOM itself, and cache the indexed RTGEOM instead of a bare tree
+* only primitive objects should get a tree
+*/
+
+
diff --git a/src/rtgeodetic_tree.c b/src/rtgeodetic_tree.c
new file mode 100644
index 0000000..304592f
--- /dev/null
+++ b/src/rtgeodetic_tree.c
@@ -0,0 +1,951 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+#define _GNU_SOURCE
+
+#include "librtgeom_internal.h"
+#include "rtgeodetic_tree.h"
+#include "rtgeom_log.h"
+
+
+/* Internal prototype */
+static CIRC_NODE* circ_nodes_merge(const RTCTX *ctx, CIRC_NODE** nodes, int num_nodes);
+static double circ_tree_distance_tree_internal(const RTCTX *ctx, const CIRC_NODE* n1, const CIRC_NODE* n2, double threshold, double* min_dist, double* max_dist, GEOGRAPHIC_POINT* closest1, GEOGRAPHIC_POINT* closest2);
+
+
+/**
+* Internal nodes have their point references set to NULL.
+*/
+static inline int 
+circ_node_is_leaf(const RTCTX *ctx, const CIRC_NODE* node)
+{
+	return (node->num_nodes == 0);
+}
+
+/**
+* Recurse from top of node tree and free all children.
+* does not free underlying point array.
+*/
+void 
+circ_tree_free(const RTCTX *ctx, CIRC_NODE* node)
+{
+	int i;
+	if ( ! node ) return;
+	
+	for ( i = 0; i < node->num_nodes; i++ )
+		circ_tree_free(ctx, node->nodes[i]);
+
+	if ( node->nodes ) rtfree(ctx, node->nodes);
+	rtfree(ctx, node);
+}
+
+
+/**
+* Create a new leaf node, storing pointers back to the end points for later.
+*/
+static CIRC_NODE* 
+circ_node_leaf_new(const RTCTX *ctx, const RTPOINTARRAY* pa, int i)
+{
+	RTPOINT2D *p1, *p2;
+	POINT3D q1, q2, c;
+	GEOGRAPHIC_POINT g1, g2, gc;
+	CIRC_NODE *node;
+	double diameter;
+
+	p1 = (RTPOINT2D*)rt_getPoint_internal(ctx, pa, i);
+	p2 = (RTPOINT2D*)rt_getPoint_internal(ctx, pa, i+1);
+	geographic_point_init(ctx, p1->x, p1->y, &g1);
+	geographic_point_init(ctx, p2->x, p2->y, &g2);
+
+	RTDEBUGF(3,"edge #%d (%g %g, %g %g)", i, p1->x, p1->y, p2->x, p2->y);
+	
+	diameter = sphere_distance(ctx, &g1, &g2);
+
+	/* Zero length edge, doesn't get a node */
+	if ( FP_EQUALS(diameter, 0.0) )
+		return NULL;
+
+	/* Allocate */
+	node = rtalloc(ctx, sizeof(CIRC_NODE));
+	node->p1 = p1;
+	node->p2 = p2;
+	
+	/* Convert ends to X/Y/Z, sum, and normalize to get mid-point */
+	geog2cart(ctx, &g1, &q1);
+	geog2cart(ctx, &g2, &q2);
+	vector_sum(ctx, &q1, &q2, &c);
+	normalize(ctx, &c);
+	cart2geog(ctx, &c, &gc);
+	node->center = gc;
+	node->radius = diameter / 2.0;
+
+	RTDEBUGF(3,"edge #%d CENTER(%g %g) RADIUS=%g", i, gc.lon, gc.lat, node->radius);
+
+	/* Leaf has no children */
+	node->num_nodes = 0;
+	node->nodes = NULL;
+	node->edge_num = i;
+    
+	/* Zero out metadata */
+	node->pt_outside.x = 0.0;
+	node->pt_outside.y = 0.0;
+	node->geom_type = 0;
+	
+	return node;
+}
+
+/**
+* Return a point node (zero radius, referencing one point)
+*/
+static CIRC_NODE* 
+circ_node_leaf_point_new(const RTCTX *ctx, const RTPOINTARRAY* pa)
+{
+	CIRC_NODE* tree = rtalloc(ctx, sizeof(CIRC_NODE));
+	tree->p1 = tree->p2 = (RTPOINT2D*)rt_getPoint_internal(ctx, pa, 0);
+	geographic_point_init(ctx, tree->p1->x, tree->p1->y, &(tree->center));
+	tree->radius = 0.0;
+	tree->nodes = NULL;
+	tree->num_nodes = 0;
+	tree->edge_num = 0;
+	tree->geom_type = RTPOINTTYPE;
+	tree->pt_outside.x = 0.0;
+	tree->pt_outside.y = 0.0;
+	return tree;
+}
+
+/**
+* Comparing on geohash ensures that nearby nodes will be close 
+* to each other in the list.
+*/  
+static int
+circ_node_compare(const void* v1, const void* v2, void *c)
+{
+	RTPOINT2D p1, p2;
+	unsigned int u1, u2;
+  const RTCTX *ctx = (RTCTX *)c;
+	CIRC_NODE *c1 = *((CIRC_NODE**)v1);
+	CIRC_NODE *c2 = *((CIRC_NODE**)v2);
+	p1.x = rad2deg((c1->center).lon);
+	p1.y = rad2deg((c1->center).lat);
+	p2.x = rad2deg((c2->center).lon);
+	p2.y = rad2deg((c2->center).lat);
+	u1 = geohash_point_as_int(ctx, &p1);
+	u2 = geohash_point_as_int(ctx, &p2);
+	if ( u1 < u2 ) return -1;
+	if ( u1 > u2 ) return 1;
+	return 0;
+}
+
+/**
+* Given the centers of two circles, and the offset distance we want to put the new center between them
+* (calculated as the distance implied by the radii of the inputs and the distance between the centers)
+* figure out where the new center point is, by getting the direction from c1 to c2 and projecting
+* from c1 in that direction by the offset distance.
+*/
+static int
+circ_center_spherical(const RTCTX *ctx, const GEOGRAPHIC_POINT* c1, const GEOGRAPHIC_POINT* c2, double distance, double offset, GEOGRAPHIC_POINT* center)
+{
+	/* Direction from c1 to c2 */
+	double dir = sphere_direction(ctx, c1, c2, distance);
+
+	RTDEBUGF(4,"calculating spherical center", dir);
+
+	RTDEBUGF(4,"dir is %g", dir);
+
+	/* Catch sphere_direction when it barfs */
+	if ( isnan(dir) )
+		return RT_FAILURE;
+	
+	/* Center of new circle is projection from start point, using offset distance*/
+	return sphere_project(ctx, c1, offset, dir, center);
+}
+
+/**
+* Where the circ_center_spherical(ctx) function fails, we need a fall-back. The failures 
+* happen in short arcs, where the spherical distance between two points is practically
+* the same as the straight-line distance, so our fallback will be to use the straight-line
+* between the two to calculate the new projected center. For proportions far from 0.5
+* this will be increasingly more incorrect.
+*/
+static int
+circ_center_cartesian(const RTCTX *ctx, const GEOGRAPHIC_POINT* c1, const GEOGRAPHIC_POINT* c2, double distance, double offset, GEOGRAPHIC_POINT* center)
+{
+	POINT3D p1, p2;
+	POINT3D p1p2, pc;
+	double proportion = offset/distance;
+	
+	RTDEBUG(4,"calculating cartesian center");
+	
+	geog2cart(ctx, c1, &p1);
+	geog2cart(ctx, c2, &p2);
+	
+	/* Difference between p2 and p1 */
+	p1p2.x = p2.x - p1.x;
+	p1p2.y = p2.y - p1.y;
+	p1p2.z = p2.z - p1.z;
+
+	/* Scale difference to proportion */
+	p1p2.x *= proportion;
+	p1p2.y *= proportion;
+	p1p2.z *= proportion;
+	
+	/* Add difference to p1 to get approximate center point */
+	pc.x = p1.x + p1p2.x;
+	pc.y = p1.y + p1p2.y;
+	pc.z = p1.z + p1p2.z;
+	normalize(ctx, &pc);
+	
+	/* Convert center point to geographics */
+	cart2geog(ctx, &pc, center);
+	
+	return RT_SUCCESS;
+}
+
+
+/**
+* Create a new internal node, calculating the new measure range for the node,
+* and storing pointers to the child nodes.
+*/
+static CIRC_NODE* 
+circ_node_internal_new(const RTCTX *ctx, CIRC_NODE** c, int num_nodes)
+{
+	CIRC_NODE *node = NULL;
+	GEOGRAPHIC_POINT new_center, c1;
+	double new_radius;
+	double offset1, dist, D, r1, ri;
+	int i, new_geom_type;
+
+	RTDEBUGF(3, "called with %d nodes --", num_nodes);
+
+	/* Can't do anything w/ empty input */
+	if ( num_nodes < 1 )
+		return node;
+	
+	/* Initialize calculation with values of the first circle */
+	new_center = c[0]->center;
+	new_radius = c[0]->radius;
+	new_geom_type = c[0]->geom_type;
+	
+	/* Merge each remaining circle into the new circle */
+	for ( i = 1; i < num_nodes; i++ )
+	{
+		c1 = new_center; 
+		r1 = new_radius;
+		
+		dist = sphere_distance(ctx, &c1, &(c[i]->center));
+		ri = c[i]->radius;
+
+		/* Promote geometry types up the tree, getting more and more collected */
+		/* Go until we find a value */
+		if ( ! new_geom_type )
+		{
+			new_geom_type = c[i]->geom_type;
+		}
+		/* Promote singleton to a multi-type */
+		else if ( ! rttype_is_collection(ctx, new_geom_type) )
+		{
+			/* Anonymous collection if types differ */
+			if ( new_geom_type != c[i]->geom_type )
+			{
+				new_geom_type = RTCOLLECTIONTYPE;
+			}
+			else
+			{
+				new_geom_type = rttype_get_collectiontype(ctx, new_geom_type);				
+			}
+		}
+		/* If we can't add next feature to this collection cleanly, promote again to anonymous collection */
+		else if ( new_geom_type != rttype_get_collectiontype(ctx, c[i]->geom_type) )
+		{
+			new_geom_type = RTCOLLECTIONTYPE;
+		}
+
+
+		RTDEBUGF(3, "distance between new (%g %g) and %i (%g %g) is %g", c1.lon, c1.lat, i, c[i]->center.lon, c[i]->center.lat, dist);
+		
+		if ( FP_EQUALS(dist, 0) )
+		{
+			RTDEBUG(3, "  distance between centers is zero");
+			new_radius = r1 + 2*dist;
+			new_center = c1;
+		}
+		else if ( dist < fabs(r1 - ri) )
+		{
+			/* new contains next */
+			if ( r1 > ri )
+			{
+				RTDEBUG(3, "  c1 contains ci");
+				new_center = c1;
+				new_radius = r1;
+			}
+			/* next contains new */
+			else
+			{
+				RTDEBUG(3, "  ci contains c1");
+				new_center = c[i]->center;
+				new_radius = ri;
+			}
+		}
+		else
+		{	
+			RTDEBUG(3, "  calculating new center");
+			/* New circle diameter */
+			D = dist + r1 + ri;
+			RTDEBUGF(3,"    D is %g", D);
+			
+			/* New radius */
+			new_radius = D / 2.0;
+			
+			/* Distance from cn1 center to the new center */
+			offset1 = ri + (D - (2.0*r1 + 2.0*ri)) / 2.0;
+			RTDEBUGF(3,"    offset1 is %g", offset1);
+			
+			/* Sometimes the sphere_direction function fails... this causes the center calculation */
+			/* to fail too. In that case, we're going to fall back ot a cartesian calculation, which */
+			/* is less exact, so we also have to pad the radius by (hack alert) an arbitrary amount */
+			/* which is hopefully artays big enough to contain the input edges */
+			if ( circ_center_spherical(ctx, &c1, &(c[i]->center), dist, offset1, &new_center) == RT_FAILURE )
+			{
+				circ_center_cartesian(ctx, &c1, &(c[i]->center), dist, offset1, &new_center);
+				new_radius *= 1.1;
+			}
+		}
+		RTDEBUGF(3, " new center is (%g %g) new radius is %g", new_center.lon, new_center.lat, new_radius);	
+	}
+	
+	node = rtalloc(ctx, sizeof(CIRC_NODE));
+	node->p1 = NULL;
+	node->p2 = NULL;
+	node->center = new_center;
+	node->radius = new_radius;
+	node->num_nodes = num_nodes;
+	node->nodes = c;
+	node->edge_num = -1;
+	node->geom_type = new_geom_type;
+	node->pt_outside.x = 0.0;
+	node->pt_outside.y = 0.0;
+	return node;
+}
+
+/**
+* Build a tree of nodes from a point array, one node per edge.
+*/
+CIRC_NODE* 
+circ_tree_new(const RTCTX *ctx, const RTPOINTARRAY* pa)
+{
+	int num_edges;
+	int i, j;
+	CIRC_NODE **nodes;
+	CIRC_NODE *node;
+	CIRC_NODE *tree;
+
+	/* Can't do anything with no points */
+	if ( pa->npoints < 1 )
+		return NULL;
+		
+	/* Special handling for a single point */
+	if ( pa->npoints == 1 )
+		return circ_node_leaf_point_new(ctx, pa);
+		
+	/* First create a flat list of nodes, one per edge. */
+	num_edges = pa->npoints - 1;
+	nodes = rtalloc(ctx, sizeof(CIRC_NODE*) * pa->npoints);
+	j = 0;
+	for ( i = 0; i < num_edges; i++ )
+	{
+		node = circ_node_leaf_new(ctx, pa, i);
+		if ( node ) /* Not zero length? */
+			nodes[j++] = node;
+	}
+	
+	/* Special case: only zero-length edges. Make a point node. */
+	if ( j == 0 ) {
+		rtfree(ctx, nodes);
+		return circ_node_leaf_point_new(ctx, pa);
+	}
+
+	/* Merge the node list pairwise up into a tree */
+	tree = circ_nodes_merge(ctx, nodes, j);
+
+	/* Free the old list structure, leaving the tree in place */
+	rtfree(ctx, nodes);
+
+	return tree;
+}
+
+/**
+* Given a list of nodes, sort them into a spatially consistent
+* order, then pairwise merge them up into a tree. Should make
+* handling multipoints and other collections more efficient
+*/
+static void
+circ_nodes_sort(const RTCTX *ctx, CIRC_NODE** nodes, int num_nodes)
+{
+	qsort_r(nodes, num_nodes, sizeof(CIRC_NODE*), circ_node_compare, (void *)ctx);
+}
+
+
+static CIRC_NODE*
+circ_nodes_merge(const RTCTX *ctx, CIRC_NODE** nodes, int num_nodes)
+{
+	CIRC_NODE **inodes = NULL;
+	int num_children = num_nodes;
+	int inode_num = 0;
+	int num_parents = 0;
+	int j;
+
+	/* TODO, roll geom_type *up* as tree is built, changing to collection types as simple types are merged 
+	 * TODO, change the distance algorithm to drive down to simple types first, test pip on poly/other cases, then test edges
+	 */
+
+	while( num_children > 1 )
+	{
+		for ( j = 0; j < num_children; j++ )
+		{
+			inode_num = (j % CIRC_NODE_SIZE);
+			if ( inode_num == 0 )
+				inodes = rtalloc(ctx, sizeof(CIRC_NODE*)*CIRC_NODE_SIZE);
+
+			inodes[inode_num] = nodes[j];
+			
+			if ( inode_num == CIRC_NODE_SIZE-1 )
+				nodes[num_parents++] = circ_node_internal_new(ctx, inodes, CIRC_NODE_SIZE);
+		}
+		
+		/* Clean up any remaining nodes... */
+		if ( inode_num == 0 )
+		{
+			/* Promote solo nodes without merging */
+			nodes[num_parents++] = inodes[0];
+			rtfree(ctx, inodes);
+		}
+		else if ( inode_num < CIRC_NODE_SIZE-1 )
+		{
+			/* Merge spare nodes */
+			nodes[num_parents++] = circ_node_internal_new(ctx, inodes, inode_num+1);
+		}
+		
+		num_children = num_parents;	
+		num_parents = 0;
+	}
+	
+	/* Return a reference to the head of the tree */
+	return nodes[0];
+}
+
+
+/**
+* Returns a #RTPOINT2D that is a vertex of the input shape
+*/
+int circ_tree_get_point(const RTCTX *ctx, const CIRC_NODE* node, RTPOINT2D* pt)
+{
+	if ( circ_node_is_leaf(ctx, node) )
+	{
+		pt->x = node->p1->x;
+		pt->y = node->p1->y;
+		return RT_SUCCESS;
+	}
+	else
+	{
+		return circ_tree_get_point(ctx, node->nodes[0], pt);
+	}
+}
+
+
+/**
+* Walk the tree and count intersections between the stab line and the edges.
+* odd => containment, even => no containment.
+* KNOWN PROBLEM: Grazings (think of a sharp point, just touching the
+*   stabline) will be counted for one, which will throw off the count.
+*/
+int circ_tree_contains_point(const RTCTX *ctx, const CIRC_NODE* node, const RTPOINT2D* pt, const RTPOINT2D* pt_outside, int* on_boundary)
+{
+	GEOGRAPHIC_POINT closest;
+	GEOGRAPHIC_EDGE stab_edge, edge;
+	POINT3D S1, S2, E1, E2;
+	double d;
+	int i, c;
+	
+	/* Construct a stabline edge from our "inside" to our known outside point */
+	geographic_point_init(ctx, pt->x, pt->y, &(stab_edge.start));
+	geographic_point_init(ctx, pt_outside->x, pt_outside->y, &(stab_edge.end));
+	geog2cart(ctx, &(stab_edge.start), &S1);
+	geog2cart(ctx, &(stab_edge.end), &S2);
+	
+	RTDEBUG(3, "entered");
+	
+	/* 
+	* If the stabline doesn't cross within the radius of a node, there's no 
+	* way it can cross.
+	*/
+		
+	RTDEBUGF(3, "working on node %p, edge_num %d, radius %g, center POINT(%g %g)", node, node->edge_num, node->radius, rad2deg(node->center.lon), rad2deg(node->center.lat));
+	d = edge_distance_to_point(ctx, &stab_edge, &(node->center), &closest);
+	RTDEBUGF(3, "edge_distance_to_point=%g, node_radius=%g", d, node->radius);
+	if ( FP_LTEQ(d, node->radius) )
+	{
+		RTDEBUGF(3,"entering this branch (%p)", node);
+		
+		/* Return the crossing number of this leaf */
+		if ( circ_node_is_leaf(ctx, node) )
+		{
+			int inter;
+			RTDEBUGF(3, "leaf node calculation (edge %d)", node->edge_num);
+			geographic_point_init(ctx, node->p1->x, node->p1->y, &(edge.start));
+			geographic_point_init(ctx, node->p2->x, node->p2->y, &(edge.end));
+			geog2cart(ctx, &(edge.start), &E1);
+			geog2cart(ctx, &(edge.end), &E2);
+			
+			inter = edge_intersects(ctx, &S1, &S2, &E1, &E2);
+			
+			if ( inter & PIR_INTERSECTS )
+			{
+				RTDEBUG(3," got stab line edge_intersection with this edge!");
+				/* To avoid double counting crossings-at-a-vertex, */
+				/* artays ignore crossings at "lower" ends of edges*/
+
+				if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR )
+				{
+					RTDEBUG(3,"  rejecting stab line grazing by left-side edge");
+					return 0;
+				}
+				else
+				{
+					RTDEBUG(3,"  accepting stab line intersection");
+					return 1;
+				}
+			}
+		}
+		/* Or, add up the crossing numbers of all children of this node. */
+		else
+		{
+			c = 0;
+			for ( i = 0; i < node->num_nodes; i++ )
+			{
+				RTDEBUG(3,"internal node calculation");
+				RTDEBUGF(3," calling circ_tree_contains_point on child %d!", i);
+				c += circ_tree_contains_point(ctx, node->nodes[i], pt, pt_outside, on_boundary);
+			}
+			return c % 2;
+		}
+	}
+	else
+	{
+		RTDEBUGF(3,"skipping this branch (%p)", node);
+	}
+	
+	return 0;
+}
+
+static double 
+circ_node_min_distance(const RTCTX *ctx, const CIRC_NODE* n1, const CIRC_NODE* n2)
+{
+	double d = sphere_distance(ctx, &(n1->center), &(n2->center));
+	double r1 = n1->radius;
+	double r2 = n2->radius;
+	
+	if ( d < r1 + r2 )
+		return 0.0;
+		
+	return d - r1 - r2;
+}
+
+static double 
+circ_node_max_distance(const RTCTX *ctx, const CIRC_NODE *n1, const CIRC_NODE *n2)
+{
+	return sphere_distance(ctx, &(n1->center), &(n2->center)) + n1->radius + n2->radius;
+}
+
+double
+circ_tree_distance_tree(const RTCTX *ctx, const CIRC_NODE* n1, const CIRC_NODE* n2, const SPHEROID* spheroid, double threshold)
+{
+	double min_dist = FLT_MAX;
+	double max_dist = FLT_MAX;
+	GEOGRAPHIC_POINT closest1, closest2;
+	/* Quietly decrease the threshold just a little to avoid cases where */
+	/* the actual spheroid distance is larger than the sphere distance */
+	/* causing the return value to be larger than the threshold value */
+	double threshold_radians = 0.95 * threshold / spheroid->radius;
+	
+	circ_tree_distance_tree_internal(ctx, n1, n2, threshold_radians, &min_dist, &max_dist, &closest1, &closest2);
+
+	/* Spherical case */
+	if ( spheroid->a == spheroid->b )
+	{
+		return spheroid->radius * sphere_distance(ctx, &closest1, &closest2);
+	}
+	else
+	{
+		return spheroid_distance(ctx, &closest1, &closest2, spheroid);		
+	}
+}
+
+static double 
+circ_tree_distance_tree_internal(const RTCTX *ctx, const CIRC_NODE* n1, const CIRC_NODE* n2, double threshold, double* min_dist, double* max_dist, GEOGRAPHIC_POINT* closest1, GEOGRAPHIC_POINT* closest2)
+{	
+	double max;
+	double d, d_min;
+	int i;
+	
+	RTDEBUGF(4, "entered, min_dist=%.8g max_dist=%.8g, type1=%d, type2=%d", *min_dist, *max_dist, n1->geom_type, n2->geom_type);
+/*
+	circ_tree_print(ctx, n1, 0);
+	circ_tree_print(ctx, n2, 0);
+*/
+	
+	/* Short circuit if we've already hit the minimum */
+	if( *min_dist < threshold || *min_dist == 0.0 )
+		return *min_dist;
+	
+	/* If your minimum is greater than anyone's maximum, you can't hold the winner */
+	if( circ_node_min_distance(ctx, n1, n2) > *max_dist )
+	{
+		RTDEBUGF(4, "pruning pair %p, %p", n1, n2);		
+		return FLT_MAX;
+	}
+	
+	/* If your maximum is a new low, we'll use that as our new global tolerance */
+	max = circ_node_max_distance(ctx, n1, n2);
+	RTDEBUGF(5, "max %.8g", max);
+	if( max < *max_dist )
+		*max_dist = max;
+
+	/* Polygon on one side, primitive type on the other. Check for point-in-polygon */
+	/* short circuit. */
+	if ( n1->geom_type == RTPOLYGONTYPE && n2->geom_type && ! rttype_is_collection(ctx, n2->geom_type) )
+	{
+		RTPOINT2D pt;
+		circ_tree_get_point(ctx, n2, &pt);
+		RTDEBUGF(4, "n1 is polygon, testing if contains (%.5g,%.5g)", pt.x, pt.y);
+		if ( circ_tree_contains_point(ctx, n1, &pt, &(n1->pt_outside), NULL) )
+		{
+			RTDEBUG(4, "it does");
+			*min_dist = 0.0;
+			geographic_point_init(ctx, pt.x, pt.y, closest1);
+			geographic_point_init(ctx, pt.x, pt.y, closest2);
+			return *min_dist;
+		}			
+	}
+	/* Polygon on one side, primitive type on the other. Check for point-in-polygon */
+	/* short circuit. */
+	if ( n2->geom_type == RTPOLYGONTYPE && n1->geom_type && ! rttype_is_collection(ctx, n1->geom_type) )
+	{
+		RTPOINT2D pt;
+		circ_tree_get_point(ctx, n1, &pt);
+		RTDEBUGF(4, "n2 is polygon, testing if contains (%.5g,%.5g)", pt.x, pt.y);
+		if ( circ_tree_contains_point(ctx, n2, &pt, &(n2->pt_outside), NULL) )
+		{
+			RTDEBUG(4, "it does");
+			geographic_point_init(ctx, pt.x, pt.y, closest1);
+			geographic_point_init(ctx, pt.x, pt.y, closest2);
+			*min_dist = 0.0;
+			return *min_dist;
+		}		
+	}
+	
+	/* Both leaf nodes, do a real distance calculation */
+	if( circ_node_is_leaf(ctx, n1) && circ_node_is_leaf(ctx, n2) )
+	{
+		double d;
+		GEOGRAPHIC_POINT close1, close2;
+		RTDEBUGF(4, "testing leaf pair [%d], [%d]", n1->edge_num, n2->edge_num);		
+		/* One of the nodes is a point */
+		if ( n1->p1 == n1->p2 || n2->p1 == n2->p2 )
+		{
+			GEOGRAPHIC_EDGE e;
+			GEOGRAPHIC_POINT gp1, gp2;
+
+			/* Both nodes are points! */
+			if ( n1->p1 == n1->p2 && n2->p1 == n2->p2 )
+			{
+				geographic_point_init(ctx, n1->p1->x, n1->p1->y, &gp1);
+				geographic_point_init(ctx, n2->p1->x, n2->p1->y, &gp2);
+				close1 = gp1; close2 = gp2;
+				d = sphere_distance(ctx, &gp1, &gp2);
+			}				
+			/* Node 1 is a point */
+			else if ( n1->p1 == n1->p2 )
+			{
+				geographic_point_init(ctx, n1->p1->x, n1->p1->y, &gp1);
+				geographic_point_init(ctx, n2->p1->x, n2->p1->y, &(e.start));
+				geographic_point_init(ctx, n2->p2->x, n2->p2->y, &(e.end));
+				close1 = gp1;
+				d = edge_distance_to_point(ctx, &e, &gp1, &close2);
+			}
+			/* Node 2 is a point */
+			else
+			{
+				geographic_point_init(ctx, n2->p1->x, n2->p1->y, &gp1);
+				geographic_point_init(ctx, n1->p1->x, n1->p1->y, &(e.start));
+				geographic_point_init(ctx, n1->p2->x, n1->p2->y, &(e.end));
+				close1 = gp1;
+				d = edge_distance_to_point(ctx, &e, &gp1, &close2);
+			}
+			RTDEBUGF(4, "  got distance %g", d);		
+		}
+		/* Both nodes are edges */
+		else
+		{
+			GEOGRAPHIC_EDGE e1, e2;
+			GEOGRAPHIC_POINT g;
+			POINT3D A1, A2, B1, B2;
+			geographic_point_init(ctx, n1->p1->x, n1->p1->y, &(e1.start));
+			geographic_point_init(ctx, n1->p2->x, n1->p2->y, &(e1.end));
+			geographic_point_init(ctx, n2->p1->x, n2->p1->y, &(e2.start));
+			geographic_point_init(ctx, n2->p2->x, n2->p2->y, &(e2.end));
+			geog2cart(ctx, &(e1.start), &A1);
+			geog2cart(ctx, &(e1.end), &A2);
+			geog2cart(ctx, &(e2.start), &B1);
+			geog2cart(ctx, &(e2.end), &B2);
+			if ( edge_intersects(ctx, &A1, &A2, &B1, &B2) )
+			{
+				d = 0.0;
+				edge_intersection(ctx, &e1, &e2, &g);
+				close1 = close2 = g;
+			}
+			else
+			{
+				d = edge_distance_to_edge(ctx, &e1, &e2, &close1, &close2);
+			}
+			RTDEBUGF(4, "edge_distance_to_edge returned %g", d);		
+		}
+		if ( d < *min_dist )
+		{
+			*min_dist = d;
+			*closest1 = close1;
+			*closest2 = close2;
+		}
+		return d;
+	}
+	else
+	{	
+		d_min = FLT_MAX;
+		/* Drive the recursion into the COLLECTION types first so we end up with */
+		/* pairings of primitive geometries that can be forced into the point-in-polygon */
+		/* tests above. */
+		if ( n1->geom_type && rttype_is_collection(ctx, n1->geom_type) )
+		{
+			for ( i = 0; i < n1->num_nodes; i++ )
+			{
+				d = circ_tree_distance_tree_internal(ctx, n1->nodes[i], n2, threshold, min_dist, max_dist, closest1, closest2);
+				d_min = FP_MIN(d_min, d);
+			}
+		}
+		else if ( n2->geom_type && rttype_is_collection(ctx, n2->geom_type) )
+		{
+			for ( i = 0; i < n2->num_nodes; i++ )
+			{
+				d = circ_tree_distance_tree_internal(ctx, n1, n2->nodes[i], threshold, min_dist, max_dist, closest1, closest2);
+				d_min = FP_MIN(d_min, d);
+			}
+		}
+		else if ( ! circ_node_is_leaf(ctx, n1) )
+		{
+			for ( i = 0; i < n1->num_nodes; i++ )
+			{
+				d = circ_tree_distance_tree_internal(ctx, n1->nodes[i], n2, threshold, min_dist, max_dist, closest1, closest2);
+				d_min = FP_MIN(d_min, d);
+			}
+		}
+		else if ( ! circ_node_is_leaf(ctx, n2) )
+		{
+			for ( i = 0; i < n2->num_nodes; i++ )
+			{
+				d = circ_tree_distance_tree_internal(ctx, n1, n2->nodes[i], threshold, min_dist, max_dist, closest1, closest2);
+				d_min = FP_MIN(d_min, d);
+			}
+		}
+		else
+		{
+			/* Never get here */
+		}
+		
+		return d_min;
+	}
+}
+
+
+
+
+
+void circ_tree_print(const RTCTX *ctx, const CIRC_NODE* node, int depth)
+{
+	int i;
+
+	if (circ_node_is_leaf(ctx, node))
+	{
+		printf("%*s[%d] C(%.5g %.5g) R(%.5g) ((%.5g %.5g),(%.5g,%.5g))", 
+		  3*depth + 6, "NODE", node->edge_num,
+		  node->center.lon, node->center.lat,
+		  node->radius,
+		  node->p1->x, node->p1->y,
+		  node->p2->x, node->p2->y
+		);
+  		if ( node->geom_type )
+  		{
+  			printf(" %s", rttype_name(ctx, node->geom_type));
+  		}		
+  		if ( node->geom_type == RTPOLYGONTYPE )
+  		{
+  			printf(" O(%.5g %.5g)", node->pt_outside.x, node->pt_outside.y);
+  		}				
+  		printf("\n");
+		  
+	}	
+	else
+	{
+		printf("%*s C(%.5g %.5g) R(%.5g)", 
+		  3*depth + 6, "NODE", 
+		  node->center.lon, node->center.lat,
+		  node->radius
+		);
+		if ( node->geom_type )
+		{
+			printf(" %s", rttype_name(ctx, node->geom_type));
+		}
+  		if ( node->geom_type == RTPOLYGONTYPE )
+  		{
+  			printf(" O(%.5g %.5g)", node->pt_outside.x, node->pt_outside.y);
+  		}		
+		printf("\n");
+	}
+	for ( i = 0; i < node->num_nodes; i++ )
+	{
+		circ_tree_print(ctx, node->nodes[i], depth + 1);
+	}
+	return;
+}
+
+
+static CIRC_NODE*
+rtpoint_calculate_circ_tree(const RTCTX *ctx, const RTPOINT* rtpoint)
+{
+	CIRC_NODE* node;
+	node = circ_tree_new(ctx, rtpoint->point);
+	node->geom_type = rtgeom_get_type(ctx, (RTGEOM*)rtpoint);;
+	return node;
+}
+
+static CIRC_NODE*
+rtline_calculate_circ_tree(const RTCTX *ctx, const RTLINE* rtline)
+{
+	CIRC_NODE* node;
+	node = circ_tree_new(ctx, rtline->points);
+	node->geom_type = rtgeom_get_type(ctx, (RTGEOM*)rtline);
+	return node;
+}
+
+static CIRC_NODE*
+rtpoly_calculate_circ_tree(const RTCTX *ctx, const RTPOLY* rtpoly)
+{
+	int i = 0, j = 0;
+	CIRC_NODE** nodes;
+	CIRC_NODE* node;
+
+	/* One ring? Handle it like a line. */
+	if ( rtpoly->nrings == 1 )
+	{
+		node = circ_tree_new(ctx, rtpoly->rings[0]);			
+	}
+	else
+	{
+		/* Calculate a tree for each non-trivial ring of the polygon */
+		nodes = rtalloc(ctx, rtpoly->nrings * sizeof(CIRC_NODE*));
+		for ( i = 0; i < rtpoly->nrings; i++ )
+		{
+			node = circ_tree_new(ctx, rtpoly->rings[i]);
+			if ( node )
+				nodes[j++] = node;
+		}
+		/* Put the trees into a spatially correlated order */
+		circ_nodes_sort(ctx, nodes, j);
+		/* Merge the trees pairwise up to a parent node and return */
+		node = circ_nodes_merge(ctx, nodes, j);
+		/* Don't need the working list any more */
+		rtfree(ctx, nodes);
+	}
+
+	/* Metatdata about polygons, we need this to apply P-i-P tests */
+	/* selectively when doing distance calculations */
+	node->geom_type = rtgeom_get_type(ctx, (RTGEOM*)rtpoly);
+	rtpoly_pt_outside(ctx, rtpoly, &(node->pt_outside));
+	
+	return node;
+}
+
+static CIRC_NODE*
+rtcollection_calculate_circ_tree(const RTCTX *ctx, const RTCOLLECTION* rtcol)
+{
+	int i = 0, j = 0;
+	CIRC_NODE** nodes;
+	CIRC_NODE* node;
+
+	/* One geometry? Done! */
+	if ( rtcol->ngeoms == 1 )
+		return rtgeom_calculate_circ_tree(ctx, rtcol->geoms[0]);	
+	
+	/* Calculate a tree for each sub-geometry*/
+	nodes = rtalloc(ctx, rtcol->ngeoms * sizeof(CIRC_NODE*));
+	for ( i = 0; i < rtcol->ngeoms; i++ )
+	{
+		node = rtgeom_calculate_circ_tree(ctx, rtcol->geoms[i]);
+		if ( node )
+			nodes[j++] = node;
+	}
+	/* Put the trees into a spatially correlated order */
+	circ_nodes_sort(ctx, nodes, j);
+	/* Merge the trees pairwise up to a parent node and return */
+	node = circ_nodes_merge(ctx, nodes, j);
+	/* Don't need the working list any more */
+	rtfree(ctx, nodes);
+	node->geom_type = rtgeom_get_type(ctx, (RTGEOM*)rtcol);
+	return node;
+}
+
+CIRC_NODE*
+rtgeom_calculate_circ_tree(const RTCTX *ctx, const RTGEOM* rtgeom)
+{
+	if ( rtgeom_is_empty(ctx, rtgeom) )
+		return NULL;
+		
+	switch ( rtgeom->type )
+	{
+		case RTPOINTTYPE:
+			return rtpoint_calculate_circ_tree(ctx, (RTPOINT*)rtgeom);
+		case RTLINETYPE:
+			return rtline_calculate_circ_tree(ctx, (RTLINE*)rtgeom);
+		case RTPOLYGONTYPE:
+			return rtpoly_calculate_circ_tree(ctx, (RTPOLY*)rtgeom);
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+			return rtcollection_calculate_circ_tree(ctx, (RTCOLLECTION*)rtgeom);
+		default:
+			rterror(ctx, "Unable to calculate spherical index tree for type %s", rttype_name(ctx, rtgeom->type));
+			return NULL;
+	}
+	
+}
diff --git a/src/rtgeodetic_tree.h b/src/rtgeodetic_tree.h
new file mode 100644
index 0000000..25097da
--- /dev/null
+++ b/src/rtgeodetic_tree.h
@@ -0,0 +1,60 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+
+#ifndef _RTGEODETIC_TREE_H
+#define _RTGEODETIC_TREE_H 1
+
+#include "rtgeodetic.h"
+
+#define CIRC_NODE_SIZE 8
+
+/**
+* Note that p1 and p2 are pointers into an independent RTPOINTARRAY, do not free them.
+*/
+typedef struct circ_node
+{
+	GEOGRAPHIC_POINT center;
+	double radius;
+	int num_nodes;
+	struct circ_node** nodes;
+	int edge_num;
+    int geom_type;
+    RTPOINT2D pt_outside;
+	RTPOINT2D* p1;
+	RTPOINT2D* p2;
+} CIRC_NODE;
+
+void circ_tree_print(const RTCTX *ctx, const CIRC_NODE* node, int depth);
+CIRC_NODE* circ_tree_new(const RTCTX *ctx, const RTPOINTARRAY* pa);
+void circ_tree_free(const RTCTX *ctx, CIRC_NODE* node);
+int circ_tree_contains_point(const RTCTX *ctx, const CIRC_NODE* node, const RTPOINT2D* pt, const RTPOINT2D* pt_outside, int* on_boundary);
+double circ_tree_distance_tree(const RTCTX *ctx, const CIRC_NODE* n1, const CIRC_NODE* n2, const SPHEROID *spheroid, double threshold);
+CIRC_NODE* rtgeom_calculate_circ_tree(const RTCTX *ctx, const RTGEOM* rtgeom);
+int circ_tree_get_point(const RTCTX *ctx, const CIRC_NODE* node, RTPOINT2D* pt);
+
+#endif /* _RTGEODETIC_TREE_H */
+
+
diff --git a/src/rtgeom.c b/src/rtgeom.c
new file mode 100644
index 0000000..1308319
--- /dev/null
+++ b/src/rtgeom.c
@@ -0,0 +1,2049 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+/** Force Right-hand-rule on RTGEOM polygons **/
+void
+rtgeom_force_clockwise(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	RTCOLLECTION *coll;
+	int i;
+
+	switch (rtgeom->type)
+	{
+	case RTPOLYGONTYPE:
+		rtpoly_force_clockwise(ctx, (RTPOLY *)rtgeom);
+		return;
+
+	case RTTRIANGLETYPE:
+		rttriangle_force_clockwise(ctx, (RTTRIANGLE *)rtgeom);
+		return;
+
+		/* Not handle POLYHEDRALSURFACE and TIN
+		   as they are supposed to be well oriented */
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		coll = (RTCOLLECTION *)rtgeom;
+		for (i=0; i<coll->ngeoms; i++)
+			rtgeom_force_clockwise(ctx, coll->geoms[i]);
+		return;
+	}
+}
+
+/** Reverse vertex order of RTGEOM **/
+void
+rtgeom_reverse(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	int i;
+	RTCOLLECTION *col;
+
+	switch (rtgeom->type)
+	{
+	case RTLINETYPE:
+		rtline_reverse(ctx, (RTLINE *)rtgeom);
+		return;
+	case RTPOLYGONTYPE:
+		rtpoly_reverse(ctx, (RTPOLY *)rtgeom);
+		return;
+	case RTTRIANGLETYPE:
+		rttriangle_reverse(ctx, (RTTRIANGLE *)rtgeom);
+		return;
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		col = (RTCOLLECTION *)rtgeom;
+		for (i=0; i<col->ngeoms; i++)
+			rtgeom_reverse(ctx, col->geoms[i]);
+		return;
+	}
+}
+
+RTPOINT *
+rtgeom_as_rtpoint(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTPOINTTYPE )
+		return (RTPOINT *)rtgeom;
+	else return NULL;
+}
+
+RTLINE *
+rtgeom_as_rtline(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTLINETYPE )
+		return (RTLINE *)rtgeom;
+	else return NULL;
+}
+
+RTCIRCSTRING *
+rtgeom_as_rtcircstring(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTCIRCSTRINGTYPE )
+		return (RTCIRCSTRING *)rtgeom;
+	else return NULL;
+}
+
+RTCOMPOUND *
+rtgeom_as_rtcompound(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTCOMPOUNDTYPE )
+		return (RTCOMPOUND *)rtgeom;
+	else return NULL;
+}
+
+RTCURVEPOLY *
+rtgeom_as_rtcurvepoly(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTCURVEPOLYTYPE )
+		return (RTCURVEPOLY *)rtgeom;
+	else return NULL;
+}
+
+RTPOLY *
+rtgeom_as_rtpoly(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTPOLYGONTYPE )
+		return (RTPOLY *)rtgeom;
+	else return NULL;
+}
+
+RTTRIANGLE *
+rtgeom_as_rttriangle(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTTRIANGLETYPE )
+		return (RTTRIANGLE *)rtgeom;
+	else return NULL;
+}
+
+RTCOLLECTION *
+rtgeom_as_rtcollection(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom_is_collection(ctx, rtgeom) )
+		return (RTCOLLECTION*)rtgeom;
+	else return NULL;
+}
+
+RTMPOINT *
+rtgeom_as_rtmpoint(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTMULTIPOINTTYPE )
+		return (RTMPOINT *)rtgeom;
+	else return NULL;
+}
+
+RTMLINE *
+rtgeom_as_rtmline(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTMULTILINETYPE )
+		return (RTMLINE *)rtgeom;
+	else return NULL;
+}
+
+RTMPOLY *
+rtgeom_as_rtmpoly(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom == NULL ) return NULL;
+	if ( rtgeom->type == RTMULTIPOLYGONTYPE )
+		return (RTMPOLY *)rtgeom;
+	else return NULL;
+}
+
+RTPSURFACE *
+rtgeom_as_rtpsurface(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom->type == RTPOLYHEDRALSURFACETYPE )
+		return (RTPSURFACE *)rtgeom;
+	else return NULL;
+}
+
+RTTIN *
+rtgeom_as_rttin(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	if ( rtgeom->type == RTTINTYPE )
+		return (RTTIN *)rtgeom;
+	else return NULL;
+}
+
+RTGEOM * rttin_as_rtgeom(const RTCTX *ctx, const RTTIN *obj)
+{
+	return (RTGEOM *)obj;
+}
+
+RTGEOM * rtpsurface_as_rtgeom(const RTCTX *ctx, const RTPSURFACE *obj)
+{
+	return (RTGEOM *)obj;
+}
+
+RTGEOM * rtmpoly_as_rtgeom(const RTCTX *ctx, const RTMPOLY *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtmline_as_rtgeom(const RTCTX *ctx, const RTMLINE *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtmpoint_as_rtgeom(const RTCTX *ctx, const RTMPOINT *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtcollection_as_rtgeom(const RTCTX *ctx, const RTCOLLECTION *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtcircstring_as_rtgeom(const RTCTX *ctx, const RTCIRCSTRING *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtcurvepoly_as_rtgeom(const RTCTX *ctx, const RTCURVEPOLY *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtcompound_as_rtgeom(const RTCTX *ctx, const RTCOMPOUND *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtpoly_as_rtgeom(const RTCTX *ctx, const RTPOLY *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rttriangle_as_rtgeom(const RTCTX *ctx, const RTTRIANGLE *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtline_as_rtgeom(const RTCTX *ctx, const RTLINE *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+RTGEOM * rtpoint_as_rtgeom(const RTCTX *ctx, const RTPOINT *obj)
+{
+	if ( obj == NULL ) return NULL;
+	return (RTGEOM *)obj;
+}
+
+
+/**
+** Look-up for the correct MULTI* type promotion for singleton types.
+*/
+uint8_t RTMULTITYPE[RTNUMTYPES] =
+{
+	0,
+	RTMULTIPOINTTYPE,        /*  1 */
+	RTMULTILINETYPE,         /*  2 */
+	RTMULTIPOLYGONTYPE,      /*  3 */
+	0,0,0,0,
+	RTMULTICURVETYPE,        /*  8 */
+	RTMULTICURVETYPE,        /*  9 */
+	RTMULTISURFACETYPE,      /* 10 */
+	RTPOLYHEDRALSURFACETYPE, /* 11 */
+	0, 0,
+	RTTINTYPE,               /* 14 */
+	0
+};
+
+/**
+* Create a new RTGEOM of the appropriate MULTI* type.
+*/
+RTGEOM *
+rtgeom_as_multi(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	RTGEOM **ogeoms;
+	RTGEOM *ogeom = NULL;
+	RTGBOX *box = NULL;
+	int type;
+
+	type = rtgeom->type;
+
+	if ( ! RTMULTITYPE[type] ) return rtgeom_clone(ctx, rtgeom);
+
+	if( rtgeom_is_empty(ctx, rtgeom) )
+	{
+		ogeom = (RTGEOM *)rtcollection_construct_empty(ctx, 
+			RTMULTITYPE[type],
+			rtgeom->srid,
+			RTFLAGS_GET_Z(rtgeom->flags),
+			RTFLAGS_GET_M(rtgeom->flags)
+		);
+	}
+	else
+	{
+		ogeoms = rtalloc(ctx, sizeof(RTGEOM*));
+		ogeoms[0] = rtgeom_clone(ctx, rtgeom);
+
+		/* Sub-geometries are not allowed to have bboxes or SRIDs, move the bbox to the collection */
+		box = ogeoms[0]->bbox;
+		ogeoms[0]->bbox = NULL;
+		ogeoms[0]->srid = SRID_UNKNOWN;
+
+		ogeom = (RTGEOM *)rtcollection_construct(ctx, RTMULTITYPE[type], rtgeom->srid, box, 1, ogeoms);
+	}
+
+	return ogeom;
+}
+
+/**
+* Create a new RTGEOM of the appropriate CURVE* type.
+*/
+RTGEOM *
+rtgeom_as_curve(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	RTGEOM *ogeom;
+	int type = rtgeom->type;
+	/*
+	int hasz = RTFLAGS_GET_Z(rtgeom->flags);
+	int hasm = RTFLAGS_GET_M(rtgeom->flags);
+	int srid = rtgeom->srid;
+	*/
+
+	switch(type)
+	{
+		case RTLINETYPE:
+			/* turn to COMPOUNDCURVE */
+			ogeom = (RTGEOM*)rtcompound_construct_from_rtline(ctx, (RTLINE*)rtgeom);
+			break;
+		case RTPOLYGONTYPE:
+			ogeom = (RTGEOM*)rtcurvepoly_construct_from_rtpoly(ctx, rtgeom_as_rtpoly(ctx, rtgeom));
+			break;
+		case RTMULTILINETYPE:
+			/* turn to MULTICURVE */
+			ogeom = rtgeom_clone(ctx, rtgeom);
+			ogeom->type = RTMULTICURVETYPE;
+			break;
+		case RTMULTIPOLYGONTYPE:
+			/* turn to MULTISURFACE */
+			ogeom = rtgeom_clone(ctx, rtgeom);
+			ogeom->type = RTMULTISURFACETYPE;
+			break;
+		case RTCOLLECTIONTYPE:
+		default:
+			ogeom = rtgeom_clone(ctx, rtgeom);
+			break;
+	}
+
+	/* TODO: copy bbox from input geom ? */
+
+	return ogeom;
+}
+
+
+/**
+* Free the containing RTGEOM and the associated BOX. Leave the underlying 
+* geoms/points/point objects intact. Useful for functions that are stripping
+* out subcomponents of complex objects, or building up new temporary objects
+* on top of subcomponents.
+*/
+void
+rtgeom_release(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	if ( ! rtgeom )
+		rterror(ctx, "rtgeom_release: someone called on 0x0");
+
+	RTDEBUGF(3, "releasing type %s", rttype_name(ctx, rtgeom->type));
+
+	/* Drop bounding box (artays a copy) */
+	if ( rtgeom->bbox )
+	{
+		RTDEBUGF(3, "rtgeom_release: releasing bbox. %p", rtgeom->bbox);
+		rtfree(ctx, rtgeom->bbox);
+	}
+	rtfree(ctx, rtgeom);
+
+}
+
+
+/* @brief Clone RTGEOM object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTGEOM *
+rtgeom_clone(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	RTDEBUGF(2, "rtgeom_clone called with %p, %s",
+	         rtgeom, rttype_name(ctx, rtgeom->type));
+
+	switch (rtgeom->type)
+	{
+	case RTPOINTTYPE:
+		return (RTGEOM *)rtpoint_clone(ctx, (RTPOINT *)rtgeom);
+	case RTLINETYPE:
+		return (RTGEOM *)rtline_clone(ctx, (RTLINE *)rtgeom);
+	case RTCIRCSTRINGTYPE:
+		return (RTGEOM *)rtcircstring_clone(ctx, (RTCIRCSTRING *)rtgeom);
+	case RTPOLYGONTYPE:
+		return (RTGEOM *)rtpoly_clone(ctx, (RTPOLY *)rtgeom);
+	case RTTRIANGLETYPE:
+		return (RTGEOM *)rttriangle_clone(ctx, (RTTRIANGLE *)rtgeom);
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return (RTGEOM *)rtcollection_clone(ctx, (RTCOLLECTION *)rtgeom);
+	default:
+		rterror(ctx, "rtgeom_clone: Unknown geometry type: %s", rttype_name(ctx, rtgeom->type));
+		return NULL;
+	}
+}
+
+/** 
+* Deep-clone an #RTGEOM object. #RTPOINTARRAY <em>are</em> copied. 
+*/
+RTGEOM *
+rtgeom_clone_deep(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	RTDEBUGF(2, "rtgeom_clone called with %p, %s",
+	         rtgeom, rttype_name(ctx, rtgeom->type));
+
+	switch (rtgeom->type)
+	{
+	case RTPOINTTYPE:
+	case RTLINETYPE:
+	case RTCIRCSTRINGTYPE:
+	case RTTRIANGLETYPE:
+		return (RTGEOM *)rtline_clone_deep(ctx, (RTLINE *)rtgeom);
+	case RTPOLYGONTYPE:
+		return (RTGEOM *)rtpoly_clone_deep(ctx, (RTPOLY *)rtgeom);
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return (RTGEOM *)rtcollection_clone_deep(ctx, (RTCOLLECTION *)rtgeom);
+	default:
+		rterror(ctx, "rtgeom_clone_deep: Unknown geometry type: %s", rttype_name(ctx, rtgeom->type));
+		return NULL;
+	}
+}
+
+
+/**
+ * Return an alloced string
+ */
+char*
+rtgeom_to_ewkt(const RTCTX *ctx, const RTGEOM *rtgeom)
+{
+	char* wkt = NULL;
+	size_t wkt_size = 0;
+	
+	wkt = rtgeom_to_wkt(ctx, rtgeom, RTWKT_EXTENDED, 12, &wkt_size);
+
+	if ( ! wkt )
+	{
+		rterror(ctx, "Error writing geom %p to RTWKT", rtgeom);
+	}
+
+	return wkt;
+}
+
+/**
+ * @brief geom1 same as geom2
+ *  	iff
+ *      	+ have same type
+ *			+ have same # objects
+ *      	+ have same bvol
+ *      	+ each object in geom1 has a corresponding object in geom2 (see above)
+ *	@param rtgeom1
+ *	@param rtgeom2
+ */
+char
+rtgeom_same(const RTCTX *ctx, const RTGEOM *rtgeom1, const RTGEOM *rtgeom2)
+{
+	RTDEBUGF(2, "rtgeom_same(ctx, %s, %s) called",
+	         rttype_name(ctx, rtgeom1->type),
+	         rttype_name(ctx, rtgeom2->type));
+
+	if ( rtgeom1->type != rtgeom2->type )
+	{
+		RTDEBUG(3, " type differ");
+
+		return RT_FALSE;
+	}
+
+	if ( RTFLAGS_GET_ZM(rtgeom1->flags) != RTFLAGS_GET_ZM(rtgeom2->flags) )
+	{
+		RTDEBUG(3, " ZM flags differ");
+
+		return RT_FALSE;
+	}
+
+	/* Check boxes if both already computed  */
+	if ( rtgeom1->bbox && rtgeom2->bbox )
+	{
+		/*rtnotice(ctx, "bbox1:%p, bbox2:%p", rtgeom1->bbox, rtgeom2->bbox);*/
+		if ( ! gbox_same(ctx, rtgeom1->bbox, rtgeom2->bbox) )
+		{
+			RTDEBUG(3, " bounding boxes differ");
+
+			return RT_FALSE;
+		}
+	}
+
+	/* geoms have same type, invoke type-specific function */
+	switch (rtgeom1->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_same(ctx, (RTPOINT *)rtgeom1,
+		                    (RTPOINT *)rtgeom2);
+	case RTLINETYPE:
+		return rtline_same(ctx, (RTLINE *)rtgeom1,
+		                   (RTLINE *)rtgeom2);
+	case RTPOLYGONTYPE:
+		return rtpoly_same(ctx, (RTPOLY *)rtgeom1,
+		                   (RTPOLY *)rtgeom2);
+	case RTTRIANGLETYPE:
+		return rttriangle_same(ctx, (RTTRIANGLE *)rtgeom1,
+		                       (RTTRIANGLE *)rtgeom2);
+	case RTCIRCSTRINGTYPE:
+		return rtcircstring_same(ctx, (RTCIRCSTRING *)rtgeom1,
+					 (RTCIRCSTRING *)rtgeom2);
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_same(ctx, (RTCOLLECTION *)rtgeom1,
+		                         (RTCOLLECTION *)rtgeom2);
+	default:
+		rterror(ctx, "rtgeom_same: unsupported geometry type: %s",
+		        rttype_name(ctx, rtgeom1->type));
+		return RT_FALSE;
+	}
+
+}
+
+int
+rtpoint_inside_circle(const RTCTX *ctx, const RTPOINT *p, double cx, double cy, double rad)
+{
+	const RTPOINT2D *pt;
+	RTPOINT2D center;
+
+	if ( ! p || ! p->point )
+		return RT_FALSE;
+		
+	pt = rt_getPoint2d_cp(ctx, p->point, 0);
+
+	center.x = cx;
+	center.y = cy;
+
+	if ( distance2d_pt_pt(ctx, pt, &center) < rad ) 
+		return RT_TRUE;
+
+	return RT_FALSE;
+}
+
+void
+rtgeom_drop_bbox(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	if ( rtgeom->bbox ) rtfree(ctx, rtgeom->bbox);
+	rtgeom->bbox = NULL;
+	RTFLAGS_SET_BBOX(rtgeom->flags, 0);
+}
+
+/**
+ * Ensure there's a box in the RTGEOM.
+ * If the box is already there just return,
+ * else compute it.
+ */
+void
+rtgeom_add_bbox(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	/* an empty RTGEOM has no bbox */
+	if ( rtgeom_is_empty(ctx, rtgeom) ) return;
+
+	if ( rtgeom->bbox ) return;
+	RTFLAGS_SET_BBOX(rtgeom->flags, 1);
+	rtgeom->bbox = gbox_new(ctx, rtgeom->flags);
+	rtgeom_calculate_gbox(ctx, rtgeom, rtgeom->bbox);
+}
+
+void 
+rtgeom_add_bbox_deep(const RTCTX *ctx, RTGEOM *rtgeom, RTGBOX *gbox)
+{
+	if ( rtgeom_is_empty(ctx, rtgeom) ) return;
+
+	RTFLAGS_SET_BBOX(rtgeom->flags, 1);
+	
+	if ( ! ( gbox || rtgeom->bbox ) )
+	{
+		rtgeom->bbox = gbox_new(ctx, rtgeom->flags);
+		rtgeom_calculate_gbox(ctx, rtgeom, rtgeom->bbox);		
+	}
+	else if ( gbox && ! rtgeom->bbox )
+	{
+		rtgeom->bbox = gbox_clone(ctx, gbox);
+	}
+	
+	if ( rtgeom_is_collection(ctx, rtgeom) )
+	{
+		int i;
+		RTCOLLECTION *rtcol = (RTCOLLECTION*)rtgeom;
+
+		for ( i = 0; i < rtcol->ngeoms; i++ )
+		{
+			rtgeom_add_bbox_deep(ctx, rtcol->geoms[i], rtgeom->bbox);
+		}
+	}
+}
+
+const RTGBOX *
+rtgeom_get_bbox(const RTCTX *ctx, const RTGEOM *rtg)
+{
+	/* add it if not already there */
+	rtgeom_add_bbox(ctx, (RTGEOM *)rtg);
+	return rtg->bbox;
+}
+
+
+/**
+* Calculate the gbox for this goemetry, a cartesian box or
+* geodetic box, depending on how it is flagged.
+*/
+int rtgeom_calculate_gbox(const RTCTX *ctx, const RTGEOM *rtgeom, RTGBOX *gbox)
+{
+	gbox->flags = rtgeom->flags;
+	if( RTFLAGS_GET_GEODETIC(rtgeom->flags) )
+		return rtgeom_calculate_gbox_geodetic(ctx, rtgeom, gbox);
+	else
+		return rtgeom_calculate_gbox_cartesian(ctx, rtgeom, gbox);	
+}
+
+void
+rtgeom_drop_srid(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	rtgeom->srid = SRID_UNKNOWN;	/* TODO: To be changed to SRID_UNKNOWN */
+}
+
+RTGEOM *
+rtgeom_segmentize2d(const RTCTX *ctx, RTGEOM *rtgeom, double dist)
+{
+	switch (rtgeom->type)
+	{
+	case RTLINETYPE:
+		return (RTGEOM *)rtline_segmentize2d(ctx, (RTLINE *)rtgeom,
+		                                     dist);
+	case RTPOLYGONTYPE:
+		return (RTGEOM *)rtpoly_segmentize2d(ctx, (RTPOLY *)rtgeom,
+		                                     dist);
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		return (RTGEOM *)rtcollection_segmentize2d(ctx, 
+		           (RTCOLLECTION *)rtgeom, dist);
+
+	default:
+		return rtgeom_clone(ctx, rtgeom);
+	}
+}
+
+RTGEOM*
+rtgeom_force_2d(const RTCTX *ctx, const RTGEOM *geom)
+{	
+	return rtgeom_force_dims(ctx, geom, 0, 0);
+}
+
+RTGEOM*
+rtgeom_force_3dz(const RTCTX *ctx, const RTGEOM *geom)
+{	
+	return rtgeom_force_dims(ctx, geom, 1, 0);
+}
+
+RTGEOM*
+rtgeom_force_3dm(const RTCTX *ctx, const RTGEOM *geom)
+{	
+	return rtgeom_force_dims(ctx, geom, 0, 1);
+}
+
+RTGEOM*
+rtgeom_force_4d(const RTCTX *ctx, const RTGEOM *geom)
+{	
+	return rtgeom_force_dims(ctx, geom, 1, 1);
+}
+
+RTGEOM*
+rtgeom_force_dims(const RTCTX *ctx, const RTGEOM *geom, int hasz, int hasm)
+{	
+	switch(geom->type)
+	{
+		case RTPOINTTYPE:
+			return rtpoint_as_rtgeom(ctx, rtpoint_force_dims(ctx, (RTPOINT*)geom, hasz, hasm));
+		case RTCIRCSTRINGTYPE:
+		case RTLINETYPE:
+		case RTTRIANGLETYPE:
+			return rtline_as_rtgeom(ctx, rtline_force_dims(ctx, (RTLINE*)geom, hasz, hasm));
+		case RTPOLYGONTYPE:
+			return rtpoly_as_rtgeom(ctx, rtpoly_force_dims(ctx, (RTPOLY*)geom, hasz, hasm));
+		case RTCOMPOUNDTYPE:
+		case RTCURVEPOLYTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTPOLYHEDRALSURFACETYPE:
+		case RTTINTYPE:
+		case RTCOLLECTIONTYPE:
+			return rtcollection_as_rtgeom(ctx, rtcollection_force_dims(ctx, (RTCOLLECTION*)geom, hasz, hasm));
+		default:
+			rterror(ctx, "rtgeom_force_2d: unsupported geom type: %s", rttype_name(ctx, geom->type));
+			return NULL;
+	}
+}
+
+RTGEOM*
+rtgeom_force_sfs(const RTCTX *ctx, RTGEOM *geom, int version)
+{	
+	RTCOLLECTION *col;
+	int i;
+	RTGEOM *g;
+
+	/* SFS 1.2 version */
+	if (version == 120)
+	{
+		switch(geom->type)
+		{
+			/* SQL/MM types */
+			case RTCIRCSTRINGTYPE:
+			case RTCOMPOUNDTYPE:
+			case RTCURVEPOLYTYPE:
+			case RTMULTICURVETYPE:
+			case RTMULTISURFACETYPE:
+				return rtgeom_stroke(ctx, geom, 32);
+
+			case RTCOLLECTIONTYPE:
+				col = (RTCOLLECTION*)geom;
+				for ( i = 0; i < col->ngeoms; i++ ) 
+					col->geoms[i] = rtgeom_force_sfs(ctx, (RTGEOM*)col->geoms[i], version);
+
+				return rtcollection_as_rtgeom(ctx, (RTCOLLECTION*)geom);
+
+			default:
+				return (RTGEOM *)geom;
+		}
+	}
+	
+
+	/* SFS 1.1 version */
+	switch(geom->type)
+	{
+		/* SQL/MM types */
+		case RTCIRCSTRINGTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTCURVEPOLYTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+			return rtgeom_stroke(ctx, geom, 32);
+
+		/* SFS 1.2 types */
+		case RTTRIANGLETYPE:
+			g = rtpoly_as_rtgeom(ctx, rtpoly_from_rtlines(ctx, (RTLINE*)geom, 0, NULL));
+			rtgeom_free(ctx, geom);
+			return g;
+
+		case RTTINTYPE:
+			col = (RTCOLLECTION*) geom;
+			for ( i = 0; i < col->ngeoms; i++ )
+			{
+				g = rtpoly_as_rtgeom(ctx, rtpoly_from_rtlines(ctx, (RTLINE*)col->geoms[i], 0, NULL));
+				rtgeom_free(ctx, col->geoms[i]);
+				col->geoms[i] = g;
+			}
+			col->type = RTCOLLECTIONTYPE;
+			return rtmpoly_as_rtgeom(ctx, (RTMPOLY*)geom);
+		
+		case RTPOLYHEDRALSURFACETYPE:
+			geom->type = RTCOLLECTIONTYPE;
+			return (RTGEOM *)geom;
+
+		/* Collection */
+		case RTCOLLECTIONTYPE:
+			col = (RTCOLLECTION*)geom;
+			for ( i = 0; i < col->ngeoms; i++ ) 
+				col->geoms[i] = rtgeom_force_sfs(ctx, (RTGEOM*)col->geoms[i], version);
+
+			return rtcollection_as_rtgeom(ctx, (RTCOLLECTION*)geom);
+		
+		default:
+			return (RTGEOM *)geom;
+	}
+}
+
+int32_t 
+rtgeom_get_srid(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( ! geom ) return SRID_UNKNOWN;
+	return geom->srid;
+}
+
+uint32_t 
+rtgeom_get_type(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( ! geom ) return 0;
+	return geom->type;
+}
+
+int 
+rtgeom_has_z(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( ! geom ) return RT_FALSE;
+	return RTFLAGS_GET_Z(geom->flags);
+}
+
+int 
+rtgeom_has_m(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( ! geom ) return RT_FALSE;
+	return RTFLAGS_GET_M(geom->flags);
+}
+
+int 
+rtgeom_ndims(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( ! geom ) return 0;
+	return RTFLAGS_NDIMS(geom->flags);
+}
+
+
+void
+rtgeom_set_geodetic(const RTCTX *ctx, RTGEOM *geom, int value)
+{
+	RTPOINT *pt;
+	RTLINE *ln;
+	RTPOLY *ply;
+	RTCOLLECTION *col;
+	int i;
+	
+	RTFLAGS_SET_GEODETIC(geom->flags, value);
+	if ( geom->bbox )
+		RTFLAGS_SET_GEODETIC(geom->bbox->flags, value);
+	
+	switch(geom->type)
+	{
+		case RTPOINTTYPE:
+			pt = (RTPOINT*)geom;
+			if ( pt->point )
+				RTFLAGS_SET_GEODETIC(pt->point->flags, value);
+			break;
+		case RTLINETYPE:
+			ln = (RTLINE*)geom;
+			if ( ln->points )
+				RTFLAGS_SET_GEODETIC(ln->points->flags, value);
+			break;
+		case RTPOLYGONTYPE:
+			ply = (RTPOLY*)geom;
+			for ( i = 0; i < ply->nrings; i++ )
+				RTFLAGS_SET_GEODETIC(ply->rings[i]->flags, value);
+			break;
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+			col = (RTCOLLECTION*)geom;
+			for ( i = 0; i < col->ngeoms; i++ )
+				rtgeom_set_geodetic(ctx, col->geoms[i], value);
+			break;
+		default:
+			rterror(ctx, "rtgeom_set_geodetic: unsupported geom type: %s", rttype_name(ctx, geom->type));
+			return;
+	}
+}
+
+void
+rtgeom_longitude_shift(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+	int i;
+	switch (rtgeom->type)
+	{
+		RTPOINT *point;
+		RTLINE *line;
+		RTPOLY *poly;
+		RTTRIANGLE *triangle;
+		RTCOLLECTION *coll;
+
+	case RTPOINTTYPE:
+		point = (RTPOINT *)rtgeom;
+		ptarray_longitude_shift(ctx, point->point);
+		return;
+	case RTLINETYPE:
+		line = (RTLINE *)rtgeom;
+		ptarray_longitude_shift(ctx, line->points);
+		return;
+	case RTPOLYGONTYPE:
+		poly = (RTPOLY *)rtgeom;
+		for (i=0; i<poly->nrings; i++)
+			ptarray_longitude_shift(ctx, poly->rings[i]);
+		return;
+	case RTTRIANGLETYPE:
+		triangle = (RTTRIANGLE *)rtgeom;
+		ptarray_longitude_shift(ctx, triangle->points);
+		return;
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		coll = (RTCOLLECTION *)rtgeom;
+		for (i=0; i<coll->ngeoms; i++)
+			rtgeom_longitude_shift(ctx, coll->geoms[i]);
+		return;
+	default:
+		rterror(ctx, "rtgeom_longitude_shift: unsupported geom type: %s",
+		        rttype_name(ctx, rtgeom->type));
+	}
+}
+
+int 
+rtgeom_is_closed(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	
+	if( rtgeom_is_empty(ctx, geom) )
+		return RT_FALSE;
+	
+	/* Test linear types for closure */
+	switch (type)
+	{
+	case RTLINETYPE:
+		return rtline_is_closed(ctx, (RTLINE*)geom);
+	case RTPOLYGONTYPE:
+		return rtpoly_is_closed(ctx, (RTPOLY*)geom);
+	case RTCIRCSTRINGTYPE:
+		return rtcircstring_is_closed(ctx, (RTCIRCSTRING*)geom);
+	case RTCOMPOUNDTYPE:
+		return rtcompound_is_closed(ctx, (RTCOMPOUND*)geom);
+	case RTTINTYPE:
+		return rttin_is_closed(ctx, (RTTIN*)geom);
+	case RTPOLYHEDRALSURFACETYPE:
+		return rtpsurface_is_closed(ctx, (RTPSURFACE*)geom);
+	}
+	
+	/* Recurse into collections and see if anything is not closed */
+	if ( rtgeom_is_collection(ctx, geom) )
+	{
+		RTCOLLECTION *col = rtgeom_as_rtcollection(ctx, geom);
+		int i;
+		int closed;
+		for ( i = 0; i < col->ngeoms; i++ ) 
+		{
+			closed = rtgeom_is_closed(ctx, col->geoms[i]);
+			if ( ! closed ) 
+				return RT_FALSE;
+		}
+		return RT_TRUE;
+	}
+	
+	/* All non-linear non-collection types we will call closed */
+	return RT_TRUE;
+}
+
+int 
+rtgeom_is_collection(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if( ! geom ) return RT_FALSE;
+	return rttype_is_collection(ctx, geom->type);
+}
+
+/** Return TRUE if the geometry may contain sub-geometries, i.e. it is a MULTI* or COMPOUNDCURVE */
+int
+rttype_is_collection(const RTCTX *ctx, uint8_t type)
+{
+
+	switch (type)
+	{
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+		return RT_TRUE;
+		break;
+
+	default:
+		return RT_FALSE;
+	}
+}
+
+/**
+* Given an rttype number, what homogeneous collection can hold it?
+*/
+int 
+rttype_get_collectiontype(const RTCTX *ctx, uint8_t type)
+{
+	switch (type)
+	{
+		case RTPOINTTYPE:
+			return RTMULTIPOINTTYPE;
+		case RTLINETYPE:
+			return RTMULTILINETYPE;
+		case RTPOLYGONTYPE:
+			return RTMULTIPOLYGONTYPE;
+		case RTCIRCSTRINGTYPE:
+			return RTMULTICURVETYPE;
+		case RTCOMPOUNDTYPE:
+			return RTMULTICURVETYPE;
+		case RTCURVEPOLYTYPE:
+			return RTMULTISURFACETYPE;
+		case RTTRIANGLETYPE:
+			return RTTINTYPE;
+		default:
+			return RTCOLLECTIONTYPE;
+	}
+}
+
+
+void rtgeom_free(const RTCTX *ctx, RTGEOM *rtgeom)
+{
+
+	/* There's nothing here to free... */
+	if( ! rtgeom ) return;
+
+	RTDEBUGF(5,"freeing a %s",rttype_name(ctx, rtgeom->type));
+	
+	switch (rtgeom->type)
+	{
+	case RTPOINTTYPE:
+		rtpoint_free(ctx, (RTPOINT *)rtgeom);
+		break;
+	case RTLINETYPE:
+		rtline_free(ctx, (RTLINE *)rtgeom);
+		break;
+	case RTPOLYGONTYPE:
+		rtpoly_free(ctx, (RTPOLY *)rtgeom);
+		break;
+	case RTCIRCSTRINGTYPE:
+		rtcircstring_free(ctx, (RTCIRCSTRING *)rtgeom);
+		break;
+	case RTTRIANGLETYPE:
+		rttriangle_free(ctx, (RTTRIANGLE *)rtgeom);
+		break;
+	case RTMULTIPOINTTYPE:
+		rtmpoint_free(ctx, (RTMPOINT *)rtgeom);
+		break;
+	case RTMULTILINETYPE:
+		rtmline_free(ctx, (RTMLINE *)rtgeom);
+		break;
+	case RTMULTIPOLYGONTYPE:
+		rtmpoly_free(ctx, (RTMPOLY *)rtgeom);
+		break;
+	case RTPOLYHEDRALSURFACETYPE:
+		rtpsurface_free(ctx, (RTPSURFACE *)rtgeom);
+		break;
+	case RTTINTYPE:
+		rttin_free(ctx, (RTTIN *)rtgeom);
+		break;
+	case RTCURVEPOLYTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTCOLLECTIONTYPE:
+		rtcollection_free(ctx, (RTCOLLECTION *)rtgeom);
+		break;
+	default:
+		rterror(ctx, "rtgeom_free called with unknown type (%d) %s", rtgeom->type, rttype_name(ctx, rtgeom->type));
+	}
+	return;
+}
+
+int rtgeom_needs_bbox(const RTCTX *ctx, const RTGEOM *geom)
+{
+	assert(geom);
+	if ( geom->type == RTPOINTTYPE )
+	{
+		return RT_FALSE;
+	}
+	else if ( geom->type == RTLINETYPE )
+	{
+		if ( rtgeom_count_vertices(ctx, geom) <= 2 )
+			return RT_FALSE;
+		else
+			return RT_TRUE;
+	}
+	else if ( geom->type == RTMULTIPOINTTYPE )
+	{
+		if ( ((RTCOLLECTION*)geom)->ngeoms == 1 )
+			return RT_FALSE;
+		else
+			return RT_TRUE;
+	}
+	else if ( geom->type == RTMULTILINETYPE )
+	{
+		if ( ((RTCOLLECTION*)geom)->ngeoms == 1 && rtgeom_count_vertices(ctx, geom) <= 2 )
+			return RT_FALSE;
+		else
+			return RT_TRUE;
+	}
+	else
+	{
+		return RT_TRUE;
+	}
+}
+
+/**
+* Count points in an #RTGEOM.
+*/
+int rtgeom_count_vertices(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int result = 0;
+	
+	/* Null? Zero. */
+	if( ! geom ) return 0;
+	
+	RTDEBUGF(4, "rtgeom_count_vertices got type %s",
+	         rttype_name(ctx, geom->type));
+
+	/* Empty? Zero. */
+	if( rtgeom_is_empty(ctx, geom) ) return 0;
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		result = 1;
+		break;
+	case RTTRIANGLETYPE:
+	case RTCIRCSTRINGTYPE: 
+	case RTLINETYPE:
+		result = rtline_count_vertices(ctx, (RTLINE *)geom);
+		break;
+	case RTPOLYGONTYPE:
+		result = rtpoly_count_vertices(ctx, (RTPOLY *)geom);
+		break;
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		result = rtcollection_count_vertices(ctx, (RTCOLLECTION *)geom);
+		break;
+	default:
+		rterror(ctx, "%s: unsupported input geometry type: %s",
+		        __func__, rttype_name(ctx, geom->type));
+		break;
+	}
+	RTDEBUGF(3, "counted %d vertices", result);
+	return result;
+}
+
+/**
+* For an #RTGEOM, returns 0 for points, 1 for lines, 
+* 2 for polygons, 3 for volume, and the max dimension 
+* of a collection.
+*/
+int rtgeom_dimension(const RTCTX *ctx, const RTGEOM *geom)
+{
+
+	/* Null? Zero. */
+	if( ! geom ) return -1;
+	
+	RTDEBUGF(4, "rtgeom_dimension got type %s",
+	         rttype_name(ctx, geom->type));
+
+	/* Empty? Zero. */
+	/* if( rtgeom_is_empty(ctx, geom) ) return 0; */
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+	case RTMULTIPOINTTYPE:
+		return 0;
+	case RTCIRCSTRINGTYPE: 
+	case RTLINETYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTILINETYPE:
+		return 1;
+	case RTTRIANGLETYPE:
+	case RTPOLYGONTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTTINTYPE:
+		return 2;
+	case RTPOLYHEDRALSURFACETYPE:
+	{
+		/* A closed polyhedral surface contains a volume. */
+		int closed = rtpsurface_is_closed(ctx, (RTPSURFACE*)geom);
+		return ( closed ? 3 : 2 );
+	}
+	case RTCOLLECTIONTYPE:
+	{
+		int maxdim = 0, i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for( i = 0; i < col->ngeoms; i++ )
+		{
+			int dim = rtgeom_dimension(ctx, col->geoms[i]);
+			maxdim = ( dim > maxdim ? dim : maxdim );
+		}
+		return maxdim;
+	}
+	default:
+		rterror(ctx, "%s: unsupported input geometry type: %s",
+		        __func__, rttype_name(ctx, geom->type));
+	}
+	return -1;
+}
+
+/**
+* Count rings in an #RTGEOM.
+*/
+int rtgeom_count_rings(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int result = 0;
+	
+	/* Null? Empty? Zero. */
+	if( ! geom || rtgeom_is_empty(ctx, geom) ) 
+		return 0;
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+	case RTCIRCSTRINGTYPE: 
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTLINETYPE:
+		result = 0;
+		break;
+	case RTTRIANGLETYPE:
+		result = 1;
+		break;
+	case RTPOLYGONTYPE:
+		result = ((RTPOLY *)geom)->nrings;
+		break;
+	case RTCURVEPOLYTYPE:
+		result = ((RTCURVEPOLY *)geom)->nrings;
+		break;
+	case RTMULTISURFACETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+	{
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		int i = 0;
+		for( i = 0; i < col->ngeoms; i++ )
+			result += rtgeom_count_rings(ctx, col->geoms[i]);
+		break;
+	}
+	default:
+		rterror(ctx, "rtgeom_count_rings: unsupported input geometry type: %s", rttype_name(ctx, geom->type));
+		break;
+	}
+	RTDEBUGF(3, "counted %d rings", result);
+	return result;
+}
+
+int rtgeom_is_empty(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int result = RT_FALSE;
+	RTDEBUGF(4, "rtgeom_is_empty: got type %s",
+	         rttype_name(ctx, geom->type));
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_is_empty(ctx, (RTPOINT*)geom);
+		break;
+	case RTLINETYPE:
+		return rtline_is_empty(ctx, (RTLINE*)geom);
+		break;
+	case RTCIRCSTRINGTYPE:
+		return rtcircstring_is_empty(ctx, (RTCIRCSTRING*)geom);
+		break;
+	case RTPOLYGONTYPE:
+		return rtpoly_is_empty(ctx, (RTPOLY*)geom);
+		break;
+	case RTTRIANGLETYPE:
+		return rttriangle_is_empty(ctx, (RTTRIANGLE*)geom);
+		break;
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTICURVETYPE:
+	case RTMULTISURFACETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_is_empty(ctx, (RTCOLLECTION *)geom);
+		break;
+	default:
+		rterror(ctx, "rtgeom_is_empty: unsupported input geometry type: %s",
+		        rttype_name(ctx, geom->type));
+		break;
+	}
+	return result;
+}
+
+int rtgeom_has_srid(const RTCTX *ctx, const RTGEOM *geom)
+{
+	if ( geom->srid != SRID_UNKNOWN )
+		return RT_TRUE;
+
+	return RT_FALSE;
+}
+
+
+static int rtcollection_dimensionality(const RTCTX *ctx, RTCOLLECTION *col)
+{
+	int i;
+	int dimensionality = 0;
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		int d = rtgeom_dimensionality(ctx, col->geoms[i]);
+		if ( d > dimensionality )
+			dimensionality = d;
+	}
+	return dimensionality;
+}
+
+extern int rtgeom_dimensionality(const RTCTX *ctx, RTGEOM *geom)
+{
+	int dim;
+
+	RTDEBUGF(3, "rtgeom_dimensionality got type %s",
+	         rttype_name(ctx, geom->type));
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+	case RTMULTIPOINTTYPE:
+		return 0;
+		break;
+	case RTLINETYPE:
+	case RTCIRCSTRINGTYPE:
+	case RTMULTILINETYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+		return 1;
+		break;
+	case RTPOLYGONTYPE:
+	case RTTRIANGLETYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTISURFACETYPE:
+		return 2;
+		break;
+
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+		dim = rtgeom_is_closed(ctx, geom)?3:2;
+		return dim;
+		break;
+
+	case RTCOLLECTIONTYPE:
+		return rtcollection_dimensionality(ctx, (RTCOLLECTION *)geom);
+		break;
+	default:
+		rterror(ctx, "rtgeom_dimensionality: unsupported input geometry type: %s",
+		        rttype_name(ctx, geom->type));
+		break;
+	}
+	return 0;
+}
+
+extern RTGEOM* rtgeom_remove_repeated_points(const RTCTX *ctx, const RTGEOM *in, double tolerance)
+{
+	RTDEBUGF(4, "rtgeom_remove_repeated_points got type %s",
+	         rttype_name(ctx, in->type));
+
+	if(rtgeom_is_empty(ctx, in)) 
+	{
+		return rtgeom_clone_deep(ctx, in);
+	}
+
+	switch (in->type)
+	{
+	case RTMULTIPOINTTYPE:
+		return rtmpoint_remove_repeated_points(ctx, (RTMPOINT*)in, tolerance);
+		break;
+	case RTLINETYPE:
+		return rtline_remove_repeated_points(ctx, (RTLINE*)in, tolerance);
+
+	case RTMULTILINETYPE:
+	case RTCOLLECTIONTYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+		return rtcollection_remove_repeated_points(ctx, (RTCOLLECTION *)in, tolerance);
+
+	case RTPOLYGONTYPE:
+		return rtpoly_remove_repeated_points(ctx, (RTPOLY *)in, tolerance);
+		break;
+
+	case RTPOINTTYPE:
+	case RTTRIANGLETYPE:
+	case RTTINTYPE:
+		/* No point is repeated for a single point, or for Triangle or TIN */
+		return rtgeom_clone_deep(ctx, in);
+
+	case RTCIRCSTRINGTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTICURVETYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTISURFACETYPE:
+		/* Dunno how to handle these, will return untouched */
+		return rtgeom_clone_deep(ctx, in);
+
+	default:
+		rtnotice(ctx, "%s: unsupported geometry type: %s",
+		         __func__, rttype_name(ctx, in->type));
+		return rtgeom_clone_deep(ctx, in);
+		break;
+	}
+	return 0;
+}
+
+RTGEOM* rtgeom_flip_coordinates(const RTCTX *ctx, RTGEOM *in)
+{
+  rtgeom_swap_ordinates(ctx, in,RTORD_X,RTORD_Y);
+  return in;
+}
+
+void rtgeom_swap_ordinates(const RTCTX *ctx, RTGEOM *in, RTORD o1, RTORD o2)
+{
+	RTCOLLECTION *col;
+	RTPOLY *poly;
+	int i;
+
+#if PARANOIA_LEVEL > 0
+  assert(o1 < 4);
+  assert(o2 < 4);
+#endif
+
+	if ( (!in) || rtgeom_is_empty(ctx, in) ) return;
+
+  /* TODO: check for rtgeom NOT having the specified dimension ? */
+
+	RTDEBUGF(4, "rtgeom_flip_coordinates, got type: %s",
+	         rttype_name(ctx, in->type));
+
+	switch (in->type)
+	{
+	case RTPOINTTYPE:
+		ptarray_swap_ordinates(ctx, rtgeom_as_rtpoint(ctx, in)->point, o1, o2);
+		break;
+
+	case RTLINETYPE:
+		ptarray_swap_ordinates(ctx, rtgeom_as_rtline(ctx, in)->points, o1, o2);
+		break;
+
+	case RTCIRCSTRINGTYPE:
+		ptarray_swap_ordinates(ctx, rtgeom_as_rtcircstring(ctx, in)->points, o1, o2);
+		break;
+
+	case RTPOLYGONTYPE:
+		poly = (RTPOLY *) in;
+		for (i=0; i<poly->nrings; i++)
+		{
+			ptarray_swap_ordinates(ctx, poly->rings[i], o1, o2);
+		}
+		break;
+
+	case RTTRIANGLETYPE:
+		ptarray_swap_ordinates(ctx, rtgeom_as_rttriangle(ctx, in)->points, o1, o2);
+		break;
+
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTICURVETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+		col = (RTCOLLECTION *) in;
+		for (i=0; i<col->ngeoms; i++)
+		{
+			rtgeom_swap_ordinates(ctx, col->geoms[i], o1, o2);
+		}
+		break;
+
+	default:
+		rterror(ctx, "rtgeom_swap_ordinates: unsupported geometry type: %s",
+		        rttype_name(ctx, in->type));
+		return;
+	}
+
+	/* only refresh bbox if X or Y changed */
+	if ( in->bbox && (o1 < 2 || o2 < 2) ) 
+	{
+		rtgeom_drop_bbox(ctx, in);
+		rtgeom_add_bbox(ctx, in);
+	}
+}
+
+void rtgeom_set_srid(const RTCTX *ctx, RTGEOM *geom, int32_t srid)
+{
+	int i;
+
+	RTDEBUGF(4,"entered with srid=%d",srid);
+
+	geom->srid = srid;
+
+	if ( rtgeom_is_collection(ctx, geom) )
+	{
+		/* All the children are set to the same SRID value */
+		RTCOLLECTION *col = rtgeom_as_rtcollection(ctx, geom);
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			rtgeom_set_srid(ctx, col->geoms[i], srid);
+		}
+	}
+}
+
+RTGEOM* rtgeom_simplify(const RTCTX *ctx, const RTGEOM *igeom, double dist, int preserve_collapsed)
+{
+	switch (igeom->type)
+	{
+	case RTPOINTTYPE:
+	case RTMULTIPOINTTYPE:
+		return rtgeom_clone(ctx, igeom);
+	case RTLINETYPE:
+		return (RTGEOM*)rtline_simplify(ctx, (RTLINE*)igeom, dist, preserve_collapsed);
+	case RTPOLYGONTYPE:
+		return (RTGEOM*)rtpoly_simplify(ctx, (RTPOLY*)igeom, dist, preserve_collapsed);
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		return (RTGEOM*)rtcollection_simplify(ctx, (RTCOLLECTION *)igeom, dist, preserve_collapsed);
+	default:
+		rterror(ctx, "%s: unsupported geometry type: %s", __func__, rttype_name(ctx, igeom->type));
+	}
+	return NULL;
+}
+
+double rtgeom_area(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	
+	if ( type == RTPOLYGONTYPE )
+		return rtpoly_area(ctx, (RTPOLY*)geom);
+	else if ( type == RTCURVEPOLYTYPE )
+		return rtcurvepoly_area(ctx, (RTCURVEPOLY*)geom);
+	else if (type ==  RTTRIANGLETYPE )
+		return rttriangle_area(ctx, (RTTRIANGLE*)geom);
+	else if ( rtgeom_is_collection(ctx, geom) )
+	{
+		double area = 0.0;
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for ( i = 0; i < col->ngeoms; i++ )
+			area += rtgeom_area(ctx, col->geoms[i]);
+		return area;
+	}
+	else
+		return 0.0;
+}
+
+double rtgeom_perimeter(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	if ( type == RTPOLYGONTYPE )
+		return rtpoly_perimeter(ctx, (RTPOLY*)geom);
+	else if ( type == RTCURVEPOLYTYPE )
+		return rtcurvepoly_perimeter(ctx, (RTCURVEPOLY*)geom);
+	else if ( type == RTTRIANGLETYPE )
+		return rttriangle_perimeter(ctx, (RTTRIANGLE*)geom);
+	else if ( rtgeom_is_collection(ctx, geom) )
+	{
+		double perimeter = 0.0;
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for ( i = 0; i < col->ngeoms; i++ )
+			perimeter += rtgeom_perimeter(ctx, col->geoms[i]);
+		return perimeter;
+	}
+	else
+		return 0.0;
+}
+
+double rtgeom_perimeter_2d(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	if ( type == RTPOLYGONTYPE )
+		return rtpoly_perimeter_2d(ctx, (RTPOLY*)geom);
+	else if ( type == RTCURVEPOLYTYPE )
+		return rtcurvepoly_perimeter_2d(ctx, (RTCURVEPOLY*)geom);
+	else if ( type == RTTRIANGLETYPE )
+		return rttriangle_perimeter_2d(ctx, (RTTRIANGLE*)geom);
+	else if ( rtgeom_is_collection(ctx, geom) )
+	{
+		double perimeter = 0.0;
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for ( i = 0; i < col->ngeoms; i++ )
+			perimeter += rtgeom_perimeter_2d(ctx, col->geoms[i]);
+		return perimeter;
+	}
+	else
+		return 0.0;
+}
+
+double rtgeom_length(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	if ( type == RTLINETYPE )
+		return rtline_length(ctx, (RTLINE*)geom);
+	else if ( type == RTCIRCSTRINGTYPE )
+		return rtcircstring_length(ctx, (RTCIRCSTRING*)geom);
+	else if ( type == RTCOMPOUNDTYPE )
+		return rtcompound_length(ctx, (RTCOMPOUND*)geom);
+	else if ( rtgeom_is_collection(ctx, geom) )
+	{
+		double length = 0.0;
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for ( i = 0; i < col->ngeoms; i++ )
+			length += rtgeom_length(ctx, col->geoms[i]);
+		return length;
+	}
+	else
+		return 0.0;
+}
+
+double rtgeom_length_2d(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+	if ( type == RTLINETYPE )
+		return rtline_length_2d(ctx, (RTLINE*)geom);
+	else if ( type == RTCIRCSTRINGTYPE )
+		return rtcircstring_length_2d(ctx, (RTCIRCSTRING*)geom);
+	else if ( type == RTCOMPOUNDTYPE )
+		return rtcompound_length_2d(ctx, (RTCOMPOUND*)geom);
+	else if ( rtgeom_is_collection(ctx, geom) )
+	{
+		double length = 0.0;
+		int i;
+		RTCOLLECTION *col = (RTCOLLECTION*)geom;
+		for ( i = 0; i < col->ngeoms; i++ )
+			length += rtgeom_length_2d(ctx, col->geoms[i]);
+		return length;
+	}
+	else
+		return 0.0;
+}
+
+void
+rtgeom_affine(const RTCTX *ctx, RTGEOM *geom, const RTAFFINE *affine)
+{
+	int type = geom->type;
+	int i;
+
+	switch(type) 
+	{
+		/* Take advantage of fact tht pt/ln/circ/tri have same memory structure */
+		case RTPOINTTYPE:
+		case RTLINETYPE:
+		case RTCIRCSTRINGTYPE:
+		case RTTRIANGLETYPE:
+		{
+			RTLINE *l = (RTLINE*)geom;
+			ptarray_affine(ctx, l->points, affine);
+			break;
+		}
+		case RTPOLYGONTYPE:
+		{
+			RTPOLY *p = (RTPOLY*)geom;
+			for( i = 0; i < p->nrings; i++ )
+				ptarray_affine(ctx, p->rings[i], affine);
+			break;
+		}
+		case RTCURVEPOLYTYPE:
+		{
+			RTCURVEPOLY *c = (RTCURVEPOLY*)geom;
+			for( i = 0; i < c->nrings; i++ )
+				rtgeom_affine(ctx, c->rings[i], affine);
+			break;
+		}
+		default:
+		{
+			if( rtgeom_is_collection(ctx, geom) )
+			{
+				RTCOLLECTION *c = (RTCOLLECTION*)geom;
+				for( i = 0; i < c->ngeoms; i++ )
+				{
+					rtgeom_affine(ctx, c->geoms[i], affine);
+				}
+			}
+			else 
+			{
+				rterror(ctx, "rtgeom_affine: unable to handle type '%s'", rttype_name(ctx, type));
+			}
+		}
+	}
+
+}
+
+void
+rtgeom_scale(const RTCTX *ctx, RTGEOM *geom, const RTPOINT4D *factor)
+{
+	int type = geom->type;
+	int i;
+
+	switch(type) 
+	{
+		/* Take advantage of fact tht pt/ln/circ/tri have same memory structure */
+		case RTPOINTTYPE:
+		case RTLINETYPE:
+		case RTCIRCSTRINGTYPE:
+		case RTTRIANGLETYPE:
+		{
+			RTLINE *l = (RTLINE*)geom;
+			ptarray_scale(ctx, l->points, factor);
+			break;
+		}
+		case RTPOLYGONTYPE:
+		{
+			RTPOLY *p = (RTPOLY*)geom;
+			for( i = 0; i < p->nrings; i++ )
+				ptarray_scale(ctx, p->rings[i], factor);
+			break;
+		}
+		case RTCURVEPOLYTYPE:
+		{
+			RTCURVEPOLY *c = (RTCURVEPOLY*)geom;
+			for( i = 0; i < c->nrings; i++ )
+				rtgeom_scale(ctx, c->rings[i], factor);
+			break;
+		}
+		default:
+		{
+			if( rtgeom_is_collection(ctx, geom) )
+			{
+				RTCOLLECTION *c = (RTCOLLECTION*)geom;
+				for( i = 0; i < c->ngeoms; i++ )
+				{
+					rtgeom_scale(ctx, c->geoms[i], factor);
+				}
+			}
+			else 
+			{
+				rterror(ctx, "rtgeom_scale: unable to handle type '%s'", rttype_name(ctx, type));
+			}
+		}
+	}
+
+	/* Recompute bbox if needed */
+
+	if ( geom->bbox ) 
+	{
+		/* TODO: expose a gbox_scale function */
+		geom->bbox->xmin *= factor->x;
+		geom->bbox->xmax *= factor->x;
+		geom->bbox->ymin *= factor->y;
+		geom->bbox->ymax *= factor->y;
+		geom->bbox->zmin *= factor->z;
+		geom->bbox->zmax *= factor->z;
+		geom->bbox->mmin *= factor->m;
+		geom->bbox->mmax *= factor->m;
+	}
+}
+
+RTGEOM*
+rtgeom_construct_empty(const RTCTX *ctx, uint8_t type, int srid, char hasz, char hasm)
+{
+	switch(type) 
+	{
+		case RTPOINTTYPE:
+			return rtpoint_as_rtgeom(ctx, rtpoint_construct_empty(ctx, srid, hasz, hasm));
+		case RTLINETYPE:
+			return rtline_as_rtgeom(ctx, rtline_construct_empty(ctx, srid, hasz, hasm));
+		case RTPOLYGONTYPE:
+			return rtpoly_as_rtgeom(ctx, rtpoly_construct_empty(ctx, srid, hasz, hasm));
+		case RTCURVEPOLYTYPE:
+			return rtcurvepoly_as_rtgeom(ctx, rtcurvepoly_construct_empty(ctx, srid, hasz, hasm));
+		case RTCIRCSTRINGTYPE:
+			return rtcircstring_as_rtgeom(ctx, rtcircstring_construct_empty(ctx, srid, hasz, hasm));
+		case RTTRIANGLETYPE:
+			return rttriangle_as_rtgeom(ctx, rttriangle_construct_empty(ctx, srid, hasz, hasm));
+		case RTCOMPOUNDTYPE:
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+			return rtcollection_as_rtgeom(ctx, rtcollection_construct_empty(ctx, type, srid, hasz, hasm));
+		default:
+			rterror(ctx, "rtgeom_construct_empty: unsupported geometry type: %s",
+		        	rttype_name(ctx, type));
+			return NULL;
+	}
+}
+
+int
+rtgeom_startpoint(const RTCTX *ctx, const RTGEOM* rtgeom, RTPOINT4D* pt)
+{
+	if ( ! rtgeom )
+		return RT_FAILURE;
+		
+	switch( rtgeom->type ) 
+	{
+		case RTPOINTTYPE:
+			return ptarray_startpoint(ctx, ((RTPOINT*)rtgeom)->point, pt);
+		case RTTRIANGLETYPE:
+		case RTCIRCSTRINGTYPE:
+		case RTLINETYPE:
+			return ptarray_startpoint(ctx, ((RTLINE*)rtgeom)->points, pt);
+		case RTPOLYGONTYPE:
+			return rtpoly_startpoint(ctx, (RTPOLY*)rtgeom, pt);
+		case RTCURVEPOLYTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+			return rtcollection_startpoint(ctx, (RTCOLLECTION*)rtgeom, pt);
+		default:
+			rterror(ctx, "int: unsupported geometry type: %s",
+		        	rttype_name(ctx, rtgeom->type));
+			return RT_FAILURE;
+	}
+}
+
+
+RTGEOM *
+rtgeom_grid(const RTCTX *ctx, const RTGEOM *rtgeom, const gridspec *grid)
+{
+	switch ( rtgeom->type )
+	{
+		case RTPOINTTYPE:
+			return (RTGEOM *)rtpoint_grid(ctx, (RTPOINT *)rtgeom, grid);
+		case RTLINETYPE:
+			return (RTGEOM *)rtline_grid(ctx, (RTLINE *)rtgeom, grid);
+		case RTPOLYGONTYPE:
+			return (RTGEOM *)rtpoly_grid(ctx, (RTPOLY *)rtgeom, grid);
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOLLECTIONTYPE:
+		case RTCOMPOUNDTYPE:
+			return (RTGEOM *)rtcollection_grid(ctx, (RTCOLLECTION *)rtgeom, grid);
+		case RTCIRCSTRINGTYPE:
+			return (RTGEOM *)rtcircstring_grid(ctx, (RTCIRCSTRING *)rtgeom, grid);
+		default:
+			rterror(ctx, "rtgeom_grid: Unsupported geometry type: %s",
+			        rttype_name(ctx, rtgeom->type));
+			return NULL;
+	}
+}
+
+
+/* Prototype for recursion */
+static int 
+rtgeom_subdivide_recursive(const RTCTX *ctx, const RTGEOM *geom, int maxvertices, int depth, RTCOLLECTION *col, const RTGBOX *clip);
+
+static int
+rtgeom_subdivide_recursive(const RTCTX *ctx, const RTGEOM *geom, int maxvertices, int depth, RTCOLLECTION *col, const RTGBOX *clip)
+{
+	const int maxdepth = 50;
+	int nvertices = 0;
+	int i, n = 0;
+	double width = clip->xmax - clip->xmin;
+	double height = clip->ymax - clip->ymin;
+	RTGBOX subbox1, subbox2;
+	RTGEOM *clipped1, *clipped2;
+	
+	if ( geom->type == RTPOLYHEDRALSURFACETYPE || geom->type == RTTINTYPE )
+	{
+		rterror(ctx, "%s: unsupported geometry type '%s'", __func__, rttype_name(ctx, geom->type));
+	}
+	
+	if ( width == 0.0 && height == 0.0 )
+		return 0;
+	
+	/* Artays just recurse into collections */
+	if ( rtgeom_is_collection(ctx, geom) )
+	{
+		RTCOLLECTION *incol = (RTCOLLECTION*)geom;
+		int n = 0;
+		for ( i = 0; i < incol->ngeoms; i++ )
+		{
+			/* Don't increment depth yet, since we aren't actually subdividing geomtries yet */
+			n += rtgeom_subdivide_recursive(ctx, incol->geoms[i], maxvertices, depth, col, clip);
+		}
+		return n;
+	}
+	
+	/* But don't go too far. 2^25 = 33M, that's enough subdivision */
+	/* Signal callers above that we depth'ed out with a negative */
+	/* return value */
+	if ( depth > maxdepth )
+	{
+		return 0;
+	}
+	
+	nvertices = rtgeom_count_vertices(ctx, geom);
+	/* Skip empties entirely */
+	if ( nvertices == 0 )
+	{
+		return 0;
+	}
+	
+	/* If it is under the vertex tolerance, just add it, we're done */
+	if ( nvertices < maxvertices )
+	{
+		rtcollection_add_rtgeom(ctx, col, rtgeom_clone_deep(ctx, geom));
+		return 1;
+	}
+	
+	subbox1 = subbox2 = *clip;
+	if ( width > height )
+	{
+		subbox1.xmax = subbox2.xmin = (clip->xmin + clip->xmax)/2;
+	}
+	else
+	{
+		subbox1.ymax = subbox2.ymin = (clip->ymin + clip->ymax)/2;
+	}
+	
+	if ( height == 0 )
+	{
+		subbox1.ymax += FP_TOLERANCE;
+		subbox2.ymax += FP_TOLERANCE;
+		subbox1.ymin -= FP_TOLERANCE;
+		subbox2.ymin -= FP_TOLERANCE;
+	}
+
+	if ( width == 0 )
+	{
+		subbox1.xmax += FP_TOLERANCE;
+		subbox2.xmax += FP_TOLERANCE;
+		subbox1.xmin -= FP_TOLERANCE;
+		subbox2.xmin -= FP_TOLERANCE;
+	}
+		
+	clipped1 = rtgeom_clip_by_rect(ctx, geom, subbox1.xmin, subbox1.ymin, subbox1.xmax, subbox1.ymax);
+	clipped2 = rtgeom_clip_by_rect(ctx, geom, subbox2.xmin, subbox2.ymin, subbox2.xmax, subbox2.ymax);
+	
+	if ( clipped1 )
+	{
+		n += rtgeom_subdivide_recursive(ctx, clipped1, maxvertices, ++depth, col, &subbox1);
+		rtgeom_free(ctx, clipped1);
+	}
+
+	if ( clipped2 )
+	{
+		n += rtgeom_subdivide_recursive(ctx, clipped2, maxvertices, ++depth, col, &subbox2);
+		rtgeom_free(ctx, clipped2);
+	}
+	
+	return n;
+	
+}
+
+RTCOLLECTION *
+rtgeom_subdivide(const RTCTX *ctx, const RTGEOM *geom, int maxvertices)
+{
+	static int startdepth = 0;
+	static int minmaxvertices = 8;
+	RTCOLLECTION *col;
+	RTGBOX clip;
+	
+	col = rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, geom->srid, rtgeom_has_z(ctx, geom), rtgeom_has_m(ctx, geom));
+
+	if ( rtgeom_is_empty(ctx, geom) )
+		return col;
+
+	if ( maxvertices < minmaxvertices )
+	{
+		rtcollection_free(ctx, col);
+		rterror(ctx, "%s: cannot subdivide to fewer than %d vertices per output", __func__, minmaxvertices);
+	}
+	
+	clip = *(rtgeom_get_bbox(ctx, geom));
+	rtgeom_subdivide_recursive(ctx, geom, maxvertices, startdepth, col, &clip);
+	rtgeom_set_srid(ctx, (RTGEOM*)col, geom->srid);
+	return col;
+}
+
+
+int
+rtgeom_is_trajectory(const RTCTX *ctx, const RTGEOM *geom)
+{
+	int type = geom->type;
+
+	if( type != RTLINETYPE ) 
+	{
+		rtnotice(ctx, "Geometry is not a LINESTRING");
+		return RT_FALSE;
+	}
+	return rtline_is_trajectory(ctx, (RTLINE*)geom);
+}
+
diff --git a/src/rtgeom_api.c b/src/rtgeom_api.c
new file mode 100644
index 0000000..2a3f30f
--- /dev/null
+++ b/src/rtgeom_api.c
@@ -0,0 +1,852 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+/*
+ * Lower this to reduce integrity checks
+ */
+#define PARANOIA_LEVEL 1
+
+
+const char *
+rtgeom_version()
+{
+  static char *ptr = NULL;
+  static char buf[256];
+  if ( ! ptr )
+  {
+    ptr = buf;
+    snprintf(ptr, 256, LIBRTGEOM_VERSION);
+  }
+
+  return ptr;
+}
+
+
+/**********************************************************************
+ * BOX routines
+ *
+ * returns the float thats very close to the input, but <=
+ *  handles the funny differences in float4 and float8 reps.
+ **********************************************************************/
+
+typedef union
+{
+	float value;
+	uint32_t word;
+} ieee_float_shape_type;
+
+#define GET_FLOAT_WORD(i,d)			\
+	do {					\
+		ieee_float_shape_type gf_u;	\
+		gf_u.value = (d);		\
+		(i) = gf_u.word;		\
+	} while (0)
+
+
+#define SET_FLOAT_WORD(d,i)			\
+	do {					\
+		ieee_float_shape_type sf_u;	\
+		sf_u.word = (i);		\
+		(d) = sf_u.value;		\
+	} while (0)
+
+
+/*
+ * Returns the next smaller or next larger float
+ * from x (in direction of y).
+ */
+static float
+nextafterf_custom(const RTCTX *ctx, float x, float y)
+{
+	int hx,hy,ix,iy;
+
+	GET_FLOAT_WORD(hx,x);
+	GET_FLOAT_WORD(hy,y);
+	ix = hx&0x7fffffff;             /* |x| */
+	iy = hy&0x7fffffff;             /* |y| */
+
+	if ((ix>0x7f800000) ||   /* x is nan */
+	        (iy>0x7f800000))     /* y is nan */
+		return x+y;
+	if (x==y) return y;              /* x=y, return y */
+	if (ix==0)
+	{
+		/* x == 0 */
+		SET_FLOAT_WORD(x,(hy&0x80000000)|1);/* return +-minsubnormal */
+		y = x*x;
+		if (y==x) return y;
+		else return x;   /* raise underflow flag */
+	}
+	if (hx>=0)
+	{
+		/* x > 0 */
+		if (hx>hy)
+		{
+			/* x > y, x -= ulp */
+			hx -= 1;
+		}
+		else
+		{
+			/* x < y, x += ulp */
+			hx += 1;
+		}
+	}
+	else
+	{
+		/* x < 0 */
+		if (hy>=0||hx>hy)
+		{
+			/* x < y, x -= ulp */
+			hx -= 1;
+		}
+		else
+		{
+			/* x > y, x += ulp */
+			hx += 1;
+		}
+	}
+	hy = hx&0x7f800000;
+	if (hy>=0x7f800000) return x+x;  /* overflow  */
+	if (hy<0x00800000)
+	{
+		/* underflow */
+		y = x*x;
+		if (y!=x)
+		{
+			/* raise underflow flag */
+			SET_FLOAT_WORD(y,hx);
+			return y;
+		}
+	}
+	SET_FLOAT_WORD(x,hx);
+	return x;
+}
+
+
+float next_float_down(const RTCTX *ctx, double d)
+{
+	float result  = d;
+
+	if ( ((double) result) <=d)
+		return result;
+
+	return nextafterf_custom(ctx, result, result - 1000000);
+
+}
+
+/*
+ * Returns the float thats very close to the input, but >=.
+ * handles the funny differences in float4 and float8 reps.
+ */
+float
+next_float_up(const RTCTX *ctx, double d)
+{
+	float result  = d;
+
+	if ( ((double) result) >=d)
+		return result;
+
+	return nextafterf_custom(ctx, result, result + 1000000);
+}
+
+
+/*
+ * Returns the double thats very close to the input, but <.
+ * handles the funny differences in float4 and float8 reps.
+ */
+double
+next_double_down(const RTCTX *ctx, float d)
+{
+	double result  = d;
+
+	if ( result < d)
+		return result;
+
+	return nextafterf_custom(ctx, result, result - 1000000);
+}
+
+/*
+ * Returns the double thats very close to the input, but >
+ * handles the funny differences in float4 and float8 reps.
+ */
+double
+next_double_up(const RTCTX *ctx, float d)
+{
+	double result  = d;
+
+	if ( result > d)
+		return result;
+
+	return nextafterf_custom(ctx, result, result + 1000000);
+}
+
+
+/************************************************************************
+ * RTPOINTARRAY support functions
+ *
+ * TODO: should be moved to ptarray.c probably
+ *
+ ************************************************************************/
+
+/*
+ * Copies a point from the point array into the parameter point
+ * will set point's z=NO_Z_VALUE if pa is 2d
+ * will set point's m=NO_M_VALUE if pa is 3d or 2d
+ *
+ * NOTE: point is a real POINT3D *not* a pointer
+ */
+RTPOINT4D
+rt_getPoint4d(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	RTPOINT4D result;
+	rt_getPoint4d_p(ctx, pa, n, &result);
+	return result;
+}
+
+/*
+ * Copies a point from the point array into the parameter point
+ * will set point's z=NO_Z_VALUE  if pa is 2d
+ * will set point's m=NO_M_VALUE  if pa is 3d or 2d
+ *
+ * NOTE: this will modify the point4d pointed to by 'point'.
+ */
+int
+rt_getPoint4d_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT4D *op)
+{
+	uint8_t *ptr;
+	int zmflag;
+
+#if PARANOIA_LEVEL > 0
+	if ( ! pa ) rterror(ctx, "rt_getPoint4d_p: NULL pointarray");
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "rt_getPoint4d_p: point offset out of range");
+	}
+#endif
+
+	RTDEBUG(4, "rt_getPoint4d_p called.");
+
+	/* Get a pointer to nth point offset and zmflag */
+	ptr=rt_getPoint_internal(ctx, pa, n);
+	zmflag=RTFLAGS_GET_ZM(pa->flags);
+
+	RTDEBUGF(4, "ptr %p, zmflag %d", ptr, zmflag);
+
+	switch (zmflag)
+	{
+	case 0: /* 2d  */
+		memcpy(op, ptr, sizeof(RTPOINT2D));
+		op->m=NO_M_VALUE;
+		op->z=NO_Z_VALUE;
+		break;
+
+	case 3: /* ZM */
+		memcpy(op, ptr, sizeof(RTPOINT4D));
+		break;
+
+	case 2: /* Z */
+		memcpy(op, ptr, sizeof(RTPOINT3DZ));
+		op->m=NO_M_VALUE;
+		break;
+
+	case 1: /* M */
+		memcpy(op, ptr, sizeof(RTPOINT3DM));
+		op->m=op->z; /* we use Z as temporary storage */
+		op->z=NO_Z_VALUE;
+		break;
+
+	default:
+		rterror(ctx, "Unknown ZM flag ??");
+	}
+	return 1;
+
+}
+
+
+
+/*
+ * Copy a point from the point array into the parameter point
+ * will set point's z=NO_Z_VALUE if pa is 2d
+ * NOTE: point is a real RTPOINT3DZ *not* a pointer
+ */
+RTPOINT3DZ
+rt_getPoint3dz(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	RTPOINT3DZ result;
+	rt_getPoint3dz_p(ctx, pa, n, &result);
+	return result;
+}
+
+/*
+ * Copy a point from the point array into the parameter point
+ * will set point's z=NO_Z_VALUE if pa is 2d
+ *
+ * NOTE: point is a real RTPOINT3DZ *not* a pointer
+ */
+RTPOINT3DM
+rt_getPoint3dm(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	RTPOINT3DM result;
+	rt_getPoint3dm_p(ctx, pa, n, &result);
+	return result;
+}
+
+/*
+ * Copy a point from the point array into the parameter point
+ * will set point's z=NO_Z_VALUE if pa is 2d
+ *
+ * NOTE: this will modify the point3dz pointed to by 'point'.
+ */
+int
+rt_getPoint3dz_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT3DZ *op)
+{
+	uint8_t *ptr;
+
+#if PARANOIA_LEVEL > 0
+	if ( ! pa ) return 0;
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		RTDEBUGF(4, "%d out of numpoint range (%d)", n, pa->npoints);
+		return 0; /*error */
+	}
+#endif
+
+	RTDEBUGF(2, "rt_getPoint3dz_p called on array of %d-dimensions / %u pts",
+	         RTFLAGS_NDIMS(pa->flags), pa->npoints);
+
+	/* Get a pointer to nth point offset */
+	ptr=rt_getPoint_internal(ctx, pa, n);
+
+	/*
+	 * if input RTPOINTARRAY has the Z, it is artays
+	 * at third position so make a single copy
+	 */
+	if ( RTFLAGS_GET_Z(pa->flags) )
+	{
+		memcpy(op, ptr, sizeof(RTPOINT3DZ));
+	}
+
+	/*
+	 * Otherwise copy the 2d part and initialize
+	 * Z to NO_Z_VALUE
+	 */
+	else
+	{
+		memcpy(op, ptr, sizeof(RTPOINT2D));
+		op->z=NO_Z_VALUE;
+	}
+
+	return 1;
+
+}
+
+/*
+ * Copy a point from the point array into the parameter point
+ * will set point's m=NO_Z_VALUE if pa has no M
+ *
+ * NOTE: this will modify the point3dm pointed to by 'point'.
+ */
+int
+rt_getPoint3dm_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT3DM *op)
+{
+	uint8_t *ptr;
+	int zmflag;
+
+#if PARANOIA_LEVEL > 0
+	if ( ! pa ) return 0;
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "%d out of numpoint range (%d)", n, pa->npoints);
+		return 0; /*error */
+	}
+#endif
+
+	RTDEBUGF(2, "rt_getPoint3dm_p(ctx, %d) called on array of %d-dimensions / %u pts",
+	         n, RTFLAGS_NDIMS(pa->flags), pa->npoints);
+
+
+	/* Get a pointer to nth point offset and zmflag */
+	ptr=rt_getPoint_internal(ctx, pa, n);
+	zmflag=RTFLAGS_GET_ZM(pa->flags);
+
+	/*
+	 * if input RTPOINTARRAY has the M and NO Z,
+	 * we can issue a single memcpy
+	 */
+	if ( zmflag == 1 )
+	{
+		memcpy(op, ptr, sizeof(RTPOINT3DM));
+		return 1;
+	}
+
+	/*
+	 * Otherwise copy the 2d part and
+	 * initialize M to NO_M_VALUE
+	 */
+	memcpy(op, ptr, sizeof(RTPOINT2D));
+
+	/*
+	 * Then, if input has Z skip it and
+	 * copy next double, otherwise initialize
+	 * M to NO_M_VALUE
+	 */
+	if ( zmflag == 3 )
+	{
+		ptr+=sizeof(RTPOINT3DZ);
+		memcpy(&(op->m), ptr, sizeof(double));
+	}
+	else
+	{
+		op->m=NO_M_VALUE;
+	}
+
+	return 1;
+}
+
+
+/*
+ * Copy a point from the point array into the parameter point
+ * z value (if present) is not returned.
+ *
+ * NOTE: point is a real RTPOINT2D *not* a pointer
+ */
+RTPOINT2D
+rt_getPoint2d(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	const RTPOINT2D *result;
+	result = rt_getPoint2d_cp(ctx, pa, n);
+	return *result;
+}
+
+/*
+ * Copy a point from the point array into the parameter point
+ * z value (if present) is not returned.
+ *
+ * NOTE: this will modify the point2d pointed to by 'point'.
+ */
+int
+rt_getPoint2d_p(const RTCTX *ctx, const RTPOINTARRAY *pa, int n, RTPOINT2D *point)
+{
+#if PARANOIA_LEVEL > 0
+	if ( ! pa ) return 0;
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "rt_getPoint2d_p: point offset out of range");
+		return 0; /*error */
+	}
+#endif
+
+	/* this does x,y */
+	memcpy(point, rt_getPoint_internal(ctx, pa, n), sizeof(RTPOINT2D));
+	return 1;
+}
+
+/**
+* Returns a pointer into the RTPOINTARRAY serialized_ptlist, 
+* suitable for reading from. This is very high performance
+* and declared const because you aren't allowed to muck with the 
+* values, only read them.
+*/
+const RTPOINT2D*
+rt_getPoint2d_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	if ( ! pa ) return 0;
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "rt_getPoint2D_const_p: point offset out of range");
+		return 0; /*error */
+	}
+
+	return (const RTPOINT2D*)rt_getPoint_internal(ctx, pa, n);
+}
+
+const RTPOINT3DZ*
+rt_getPoint3dz_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	if ( ! pa ) return 0;
+	
+	if ( ! RTFLAGS_GET_Z(pa->flags) )
+	{
+		rterror(ctx, "rt_getPoint3dz_cp: no Z coordinates in point array");
+		return 0; /*error */
+	}
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "rt_getPoint3dz_cp: point offset out of range");
+		return 0; /*error */
+	}
+
+	return (const RTPOINT3DZ*)rt_getPoint_internal(ctx, pa, n);
+}
+
+
+const RTPOINT4D*
+rt_getPoint4d_cp(const RTCTX *ctx, const RTPOINTARRAY *pa, int n)
+{
+	if ( ! pa ) return 0;
+	
+	if ( ! (RTFLAGS_GET_Z(pa->flags) && RTFLAGS_GET_Z(pa->flags)) )
+	{
+		rterror(ctx, "rt_getPoint3dz_cp: no Z and M coordinates in point array");
+		return 0; /*error */
+	}
+
+	if ( (n<0) || (n>=pa->npoints))
+	{
+		rterror(ctx, "rt_getPoint3dz_cp: point offset out of range");
+		return 0; /*error */
+	}
+
+	return (const RTPOINT4D*)rt_getPoint_internal(ctx, pa, n);
+}
+
+
+
+/*
+ * set point N to the given value
+ * NOTE that the pointarray can be of any
+ * dimension, the appropriate ordinate values
+ * will be extracted from it
+ *
+ */
+void
+ptarray_set_point4d(const RTCTX *ctx, RTPOINTARRAY *pa, int n, const RTPOINT4D *p4d)
+{
+	uint8_t *ptr;
+	assert(n >= 0 && n < pa->npoints);
+	ptr=rt_getPoint_internal(ctx, pa, n);
+	switch ( RTFLAGS_GET_ZM(pa->flags) )
+	{
+	case 3:
+		memcpy(ptr, p4d, sizeof(RTPOINT4D));
+		break;
+	case 2:
+		memcpy(ptr, p4d, sizeof(RTPOINT3DZ));
+		break;
+	case 1:
+		memcpy(ptr, p4d, sizeof(RTPOINT2D));
+		ptr+=sizeof(RTPOINT2D);
+		memcpy(ptr, &(p4d->m), sizeof(double));
+		break;
+	case 0:
+		memcpy(ptr, p4d, sizeof(RTPOINT2D));
+		break;
+	}
+}
+
+
+
+
+/*****************************************************************************
+ * Basic sub-geometry types
+ *****************************************************************************/
+
+/* handle missaligned uint32_t32 data */
+uint32_t
+rt_get_uint32_t(const RTCTX *ctx, const uint8_t *loc)
+{
+	uint32_t result;
+
+	memcpy(&result, loc, sizeof(uint32_t));
+	return result;
+}
+
+/* handle missaligned signed int32_t data */
+int32_t
+rt_get_int32_t(const RTCTX *ctx, const uint8_t *loc)
+{
+	int32_t result;
+
+	memcpy(&result,loc, sizeof(int32_t));
+	return result;
+}
+
+
+/************************************************
+ * debugging routines
+ ************************************************/
+
+void printBOX3D(const RTCTX *ctx, BOX3D *box)
+{
+	rtnotice(ctx, "BOX3D: %g %g, %g %g", box->xmin, box->ymin,
+	         box->xmax, box->ymax);
+}
+
+void printPA(const RTCTX *ctx, RTPOINTARRAY *pa)
+{
+	int t;
+	RTPOINT4D pt;
+	char *mflag;
+
+
+	if ( RTFLAGS_GET_M(pa->flags) ) mflag = "M";
+	else mflag = "";
+
+	rtnotice(ctx, "      RTPOINTARRAY%s{", mflag);
+	rtnotice(ctx, "                 ndims=%i,   ptsize=%i",
+	         RTFLAGS_NDIMS(pa->flags), ptarray_point_size(ctx, pa));
+	rtnotice(ctx, "                 npoints = %i", pa->npoints);
+
+	for (t =0; t<pa->npoints; t++)
+	{
+		rt_getPoint4d_p(ctx, pa, t, &pt);
+		if (RTFLAGS_NDIMS(pa->flags) == 2)
+		{
+			rtnotice(ctx, "                    %i : %lf,%lf",t,pt.x,pt.y);
+		}
+		if (RTFLAGS_NDIMS(pa->flags) == 3)
+		{
+			rtnotice(ctx, "                    %i : %lf,%lf,%lf",t,pt.x,pt.y,pt.z);
+		}
+		if (RTFLAGS_NDIMS(pa->flags) == 4)
+		{
+			rtnotice(ctx, "                    %i : %lf,%lf,%lf,%lf",t,pt.x,pt.y,pt.z,pt.m);
+		}
+	}
+
+	rtnotice(ctx, "      }");
+}
+
+
+/**
+ * Given a string with at least 2 chars in it, convert them to
+ * a byte value.  No error checking done!
+ */
+uint8_t
+parse_hex(const RTCTX *ctx, char *str)
+{
+	/* do this a little brute force to make it faster */
+
+	uint8_t		result_high = 0;
+	uint8_t		result_low = 0;
+
+	switch (str[0])
+	{
+	case '0' :
+		result_high = 0;
+		break;
+	case '1' :
+		result_high = 1;
+		break;
+	case '2' :
+		result_high = 2;
+		break;
+	case '3' :
+		result_high = 3;
+		break;
+	case '4' :
+		result_high = 4;
+		break;
+	case '5' :
+		result_high = 5;
+		break;
+	case '6' :
+		result_high = 6;
+		break;
+	case '7' :
+		result_high = 7;
+		break;
+	case '8' :
+		result_high = 8;
+		break;
+	case '9' :
+		result_high = 9;
+		break;
+	case 'A' :
+	case 'a' :
+		result_high = 10;
+		break;
+	case 'B' :
+	case 'b' :
+		result_high = 11;
+		break;
+	case 'C' :
+	case 'c' :
+		result_high = 12;
+		break;
+	case 'D' :
+	case 'd' :
+		result_high = 13;
+		break;
+	case 'E' :
+	case 'e' :
+		result_high = 14;
+		break;
+	case 'F' :
+	case 'f' :
+		result_high = 15;
+		break;
+	}
+	switch (str[1])
+	{
+	case '0' :
+		result_low = 0;
+		break;
+	case '1' :
+		result_low = 1;
+		break;
+	case '2' :
+		result_low = 2;
+		break;
+	case '3' :
+		result_low = 3;
+		break;
+	case '4' :
+		result_low = 4;
+		break;
+	case '5' :
+		result_low = 5;
+		break;
+	case '6' :
+		result_low = 6;
+		break;
+	case '7' :
+		result_low = 7;
+		break;
+	case '8' :
+		result_low = 8;
+		break;
+	case '9' :
+		result_low = 9;
+		break;
+	case 'A' :
+	case 'a' :
+		result_low = 10;
+		break;
+	case 'B' :
+	case 'b' :
+		result_low = 11;
+		break;
+	case 'C' :
+	case 'c' :
+		result_low = 12;
+		break;
+	case 'D' :
+	case 'd' :
+		result_low = 13;
+		break;
+	case 'E' :
+	case 'e' :
+		result_low = 14;
+		break;
+	case 'F' :
+	case 'f' :
+		result_low = 15;
+		break;
+	}
+	return (uint8_t) ((result_high<<4) + result_low);
+}
+
+
+/**
+ * Given one byte, populate result with two byte representing
+ * the hex number.
+ *
+ * Ie. deparse_hex(ctx,  255, mystr)
+ *		-> mystr[0] = 'F' and mystr[1] = 'F'
+ *
+ * No error checking done
+ */
+void
+deparse_hex(const RTCTX *ctx, uint8_t str, char *result)
+{
+	int	input_high;
+	int  input_low;
+	static char outchr[]=
+	{
+		"0123456789ABCDEF"
+	};
+
+	input_high = (str>>4);
+	input_low = (str & 0x0F);
+
+	result[0] = outchr[input_high];
+	result[1] = outchr[input_low];
+
+}
+
+
+/**
+ * Find interpolation point I
+ * between point A and point B
+ * so that the len(AI) == len(AB)*F
+ * and I falls on AB segment.
+ *
+ * Example:
+ *
+ *   F=0.5  :    A----I----B
+ *   F=1    :    A---------B==I
+ *   F=0    : A==I---------B
+ *   F=.2   :    A-I-------B
+ */
+void
+interpolate_point4d(const RTCTX *ctx, RTPOINT4D *A, RTPOINT4D *B, RTPOINT4D *I, double F)
+{
+#if PARANOIA_LEVEL > 0
+	double absF=fabs(F);
+	if ( absF < 0 || absF > 1 )
+	{
+		rterror(ctx, "interpolate_point4d: invalid F (%g)", F);
+	}
+#endif
+	I->x=A->x+((B->x-A->x)*F);
+	I->y=A->y+((B->y-A->y)*F);
+	I->z=A->z+((B->z-A->z)*F);
+	I->m=A->m+((B->m-A->m)*F);
+}
+
+
+int _rtgeom_interrupt_requested = 0;
+void
+rtgeom_request_interrupt(const RTCTX *ctx) {
+  _rtgeom_interrupt_requested = 1;
+}
+void
+rtgeom_cancel_interrupt(const RTCTX *ctx) {
+  _rtgeom_interrupt_requested = 0;
+}
+
+rtinterrupt_callback *_rtgeom_interrupt_callback = 0;
+rtinterrupt_callback *
+rtgeom_register_interrupt_callback(const RTCTX *ctx, rtinterrupt_callback *cb) {
+  rtinterrupt_callback *old = _rtgeom_interrupt_callback;
+  _rtgeom_interrupt_callback = cb;
+  return old;
+}
diff --git a/src/rtgeom_debug.c b/src/rtgeom_debug.c
new file mode 100644
index 0000000..1031e27
--- /dev/null
+++ b/src/rtgeom_debug.c
@@ -0,0 +1,194 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2004 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include "rtgeom_log.h"
+#include "librtgeom.h"
+
+#include <stdio.h>
+#include <string.h>
+
+/* Place to hold the ZM string used in other summaries */
+static char tflags[6];
+
+static char *
+rtgeom_flagchars(const RTCTX *ctx, RTGEOM *rtg)
+{
+	int flagno = 0;
+	if ( RTFLAGS_GET_Z(rtg->flags) ) tflags[flagno++] = 'Z';
+	if ( RTFLAGS_GET_M(rtg->flags) ) tflags[flagno++] = 'M';
+	if ( RTFLAGS_GET_BBOX(rtg->flags) ) tflags[flagno++] = 'B';
+	if ( RTFLAGS_GET_GEODETIC(rtg->flags) ) tflags[flagno++] = 'G';
+	if ( rtg->srid != SRID_UNKNOWN ) tflags[flagno++] = 'S';
+	tflags[flagno] = '\0';
+
+	RTDEBUGF(4, "Flags: %s - returning %p", rtg->flags, tflags);
+
+	return tflags;
+}
+
+/*
+ * Returns an alloced string containing summary for the RTGEOM object
+ */
+static char *
+rtpoint_summary(const RTCTX *ctx, RTPOINT *point, int offset)
+{
+	char *result;
+	char *pad="";
+	char *zmflags = rtgeom_flagchars(ctx, (RTGEOM*)point);
+
+	result = (char *)rtalloc(ctx, 128+offset);
+
+	sprintf(result, "%*.s%s[%s]",
+	        offset, pad, rttype_name(ctx, point->type),
+	        zmflags);
+	return result;
+}
+
+static char *
+rtline_summary(const RTCTX *ctx, RTLINE *line, int offset)
+{
+	char *result;
+	char *pad="";
+	char *zmflags = rtgeom_flagchars(ctx, (RTGEOM*)line);
+
+	result = (char *)rtalloc(ctx, 128+offset);
+
+	sprintf(result, "%*.s%s[%s] with %d points",
+	        offset, pad, rttype_name(ctx, line->type),
+	        zmflags,
+	        line->points->npoints);
+	return result;
+}
+
+
+static char *
+rtcollection_summary(const RTCTX *ctx, RTCOLLECTION *col, int offset)
+{
+	size_t size = 128;
+	char *result;
+	char *tmp;
+	int i;
+	static char *nl = "\n";
+	char *pad="";
+	char *zmflags = rtgeom_flagchars(ctx, (RTGEOM*)col);
+
+	RTDEBUG(2, "rtcollection_summary called");
+
+	result = (char *)rtalloc(ctx, size);
+
+	sprintf(result, "%*.s%s[%s] with %d elements\n",
+	        offset, pad, rttype_name(ctx, col->type),
+	        zmflags,
+	        col->ngeoms);
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		tmp = rtgeom_summary(ctx, col->geoms[i], offset+2);
+		size += strlen(tmp)+1;
+		result = rtrealloc(ctx, result, size);
+
+		RTDEBUGF(4, "Reallocated %d bytes for result", size);
+		if ( i > 0 ) strcat(result,nl);
+
+		strcat(result, tmp);
+		rtfree(ctx, tmp);
+	}
+
+	RTDEBUG(3, "rtcollection_summary returning");
+
+	return result;
+}
+
+static char *
+rtpoly_summary(const RTCTX *ctx, RTPOLY *poly, int offset)
+{
+	char tmp[256];
+	size_t size = 64*(poly->nrings+1)+128;
+	char *result;
+	int i;
+	char *pad="";
+	static char *nl = "\n";
+	char *zmflags = rtgeom_flagchars(ctx, (RTGEOM*)poly);
+
+	RTDEBUG(2, "rtpoly_summary called");
+
+	result = (char *)rtalloc(ctx, size);
+
+	sprintf(result, "%*.s%s[%s] with %i rings\n",
+	        offset, pad, rttype_name(ctx, poly->type),
+	        zmflags,
+	        poly->nrings);
+
+	for (i=0; i<poly->nrings; i++)
+	{
+		sprintf(tmp,"%s   ring %i has %i points",
+		        pad, i, poly->rings[i]->npoints);
+		if ( i > 0 ) strcat(result,nl);
+		strcat(result,tmp);
+	}
+
+	RTDEBUG(3, "rtpoly_summary returning");
+
+	return result;
+}
+
+char *
+rtgeom_summary(const RTCTX *ctx, const RTGEOM *rtgeom, int offset)
+{
+	char *result;
+
+	switch (rtgeom->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_summary(ctx, (RTPOINT *)rtgeom, offset);
+
+	case RTCIRCSTRINGTYPE:
+	case RTTRIANGLETYPE:
+	case RTLINETYPE:
+		return rtline_summary(ctx, (RTLINE *)rtgeom, offset);
+
+	case RTPOLYGONTYPE:
+		return rtpoly_summary(ctx, (RTPOLY *)rtgeom, offset);
+
+	case RTTINTYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTICURVETYPE:
+	case RTCURVEPOLYTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_summary(ctx, (RTCOLLECTION *)rtgeom, offset);
+	default:
+		result = (char *)rtalloc(ctx, 256);
+		sprintf(result, "Object is of unknown type: %d",
+		        rtgeom->type);
+		return result;
+	}
+
+	return NULL;
+}
diff --git a/src/rtgeom_geos.c b/src/rtgeom_geos.c
new file mode 100644
index 0000000..6d0b127
--- /dev/null
+++ b/src/rtgeom_geos.c
@@ -0,0 +1,1739 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011-2014 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#include "rtgeom_geos.h"
+#include "librtgeom.h"
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+#include <stdlib.h>
+
+RTTIN * rttin_from_geos(const RTCTX *ctx, const GEOSGeometry *geom, int want3d);
+
+#undef RTGEOM_PROFILE_BUILDAREA
+
+const char *
+rtgeom_get_last_geos_error(const RTCTX *ctx)
+{
+  return ctx->rtgeom_geos_errmsg;
+}
+
+static void
+rtgeom_geos_notice(const char *msg, void *ctx)
+{
+	rtnotice(ctx, "%s\n", msg);
+}
+
+extern void
+rtgeom_geos_error(const char *msg, void *ptr)
+{
+  RTCTX *ctx = (RTCTX *)ptr;
+
+  /* TODO:  write in the context, not in the global ! */
+
+	/* Call the supplied function */
+	if ( RTGEOM_GEOS_ERRMSG_MAXSIZE-1 < snprintf(ctx->rtgeom_geos_errmsg, RTGEOM_GEOS_ERRMSG_MAXSIZE-1, "%s", msg) )
+	{
+		ctx->rtgeom_geos_errmsg[RTGEOM_GEOS_ERRMSG_MAXSIZE-1] = '\0';
+	}
+}
+
+void
+rtgeom_geos_ensure_init(const RTCTX *ctx)
+{
+  GEOSContextHandle_t h = GEOS_init_r();
+  ((RTCTX*)ctx)->gctx = h;
+  GEOSContext_setNoticeMessageHandler_r(h, rtgeom_geos_notice, (void*)ctx);
+  GEOSContext_setErrorMessageHandler_r(h, rtgeom_geos_error, (void*)ctx);
+}
+
+
+/*
+**  GEOS <==> PostGIS conversion functions
+**
+** Default conversion creates a GEOS point array, then iterates through the
+** PostGIS points, setting each value in the GEOS array one at a time.
+**
+*/
+
+/* Return a RTPOINTARRAY from a GEOSCoordSeq */
+RTPOINTARRAY *
+ptarray_from_GEOSCoordSeq(const RTCTX *ctx, const GEOSCoordSequence *cs, char want3d)
+{
+	uint32_t dims=2;
+	uint32_t size, i;
+	RTPOINTARRAY *pa;
+	RTPOINT4D point;
+
+	RTDEBUG(2, "ptarray_fromGEOSCoordSeq called");
+
+	if ( ! GEOSCoordSeq_getSize_r(ctx->gctx, cs, &size) )
+		rterror(ctx, "Exception thrown");
+
+	RTDEBUGF(4, " GEOSCoordSeq size: %d", size);
+
+	if ( want3d )
+	{
+		if ( ! GEOSCoordSeq_getDimensions_r(ctx->gctx, cs, &dims) )
+			rterror(ctx, "Exception thrown");
+
+		RTDEBUGF(4, " GEOSCoordSeq dimensions: %d", dims);
+
+		/* forget higher dimensions (if any) */
+		if ( dims > 3 ) dims = 3;
+	}
+
+	RTDEBUGF(4, " output dimensions: %d", dims);
+
+	pa = ptarray_construct(ctx, (dims==3), 0, size);
+
+	for (i=0; i<size; i++)
+	{
+		GEOSCoordSeq_getX_r(ctx->gctx, cs, i, &(point.x));
+		GEOSCoordSeq_getY_r(ctx->gctx, cs, i, &(point.y));
+		if ( dims >= 3 ) GEOSCoordSeq_getZ_r(ctx->gctx, cs, i, &(point.z));
+		ptarray_set_point4d(ctx, pa,i,&point);
+	}
+
+	return pa;
+}
+
+/* Return an RTGEOM from a Geometry */
+RTGEOM *
+GEOS2RTGEOM(const RTCTX *ctx, const GEOSGeometry *geom, char want3d)
+{
+	int type = GEOSGeomTypeId_r(ctx->gctx, geom) ;
+	int hasZ;
+	int SRID = GEOSGetSRID_r(ctx->gctx, geom);
+
+	/* GEOS's 0 is equivalent to our unknown as for SRID values */
+	if ( SRID == 0 ) SRID = SRID_UNKNOWN;
+
+	if ( want3d )
+	{
+		hasZ = GEOSHasZ_r(ctx->gctx, geom);
+		if ( ! hasZ )
+		{
+			RTDEBUG(3, "Geometry has no Z, won't provide one");
+
+			want3d = 0;
+		}
+	}
+
+/*
+	if ( GEOSisEmpty_r(ctx->gctx, geom) )
+	{
+		return (RTGEOM*)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, SRID, want3d, 0);
+	}
+*/
+
+	switch (type)
+	{
+		const GEOSCoordSequence *cs;
+		RTPOINTARRAY *pa, **ppaa;
+		const GEOSGeometry *g;
+		RTGEOM **geoms;
+		uint32_t i, ngeoms;
+
+	case GEOS_POINT:
+		RTDEBUG(4, "rtgeom_from_geometry: it's a Point");
+		cs = GEOSGeom_getCoordSeq_r(ctx->gctx, geom);
+		if ( GEOSisEmpty_r(ctx->gctx, geom) )
+		  return (RTGEOM*)rtpoint_construct_empty(ctx, SRID, want3d, 0);
+		pa = ptarray_from_GEOSCoordSeq(ctx, cs, want3d);
+		return (RTGEOM *)rtpoint_construct(ctx, SRID, NULL, pa);
+
+	case GEOS_LINESTRING:
+	case GEOS_LINEARRING:
+		RTDEBUG(4, "rtgeom_from_geometry: it's a LineString or LinearRing");
+		if ( GEOSisEmpty_r(ctx->gctx, geom) )
+		  return (RTGEOM*)rtline_construct_empty(ctx, SRID, want3d, 0);
+
+		cs = GEOSGeom_getCoordSeq_r(ctx->gctx, geom);
+		pa = ptarray_from_GEOSCoordSeq(ctx, cs, want3d);
+		return (RTGEOM *)rtline_construct(ctx, SRID, NULL, pa);
+
+	case GEOS_POLYGON:
+		RTDEBUG(4, "rtgeom_from_geometry: it's a Polygon");
+		if ( GEOSisEmpty_r(ctx->gctx, geom) )
+		  return (RTGEOM*)rtpoly_construct_empty(ctx, SRID, want3d, 0);
+		ngeoms = GEOSGetNumInteriorRings_r(ctx->gctx, geom);
+		ppaa = rtalloc(ctx, sizeof(RTPOINTARRAY *)*(ngeoms+1));
+		g = GEOSGetExteriorRing_r(ctx->gctx, geom);
+		cs = GEOSGeom_getCoordSeq_r(ctx->gctx, g);
+		ppaa[0] = ptarray_from_GEOSCoordSeq(ctx, cs, want3d);
+		for (i=0; i<ngeoms; i++)
+		{
+			g = GEOSGetInteriorRingN_r(ctx->gctx, geom, i);
+			cs = GEOSGeom_getCoordSeq_r(ctx->gctx, g);
+			ppaa[i+1] = ptarray_from_GEOSCoordSeq(ctx, cs,
+			                                      want3d);
+		}
+		return (RTGEOM *)rtpoly_construct(ctx, SRID, NULL,
+		                                  ngeoms+1, ppaa);
+
+	case GEOS_MULTIPOINT:
+	case GEOS_MULTILINESTRING:
+	case GEOS_MULTIPOLYGON:
+	case GEOS_GEOMETRYCOLLECTION:
+		RTDEBUG(4, "rtgeom_from_geometry: it's a Collection or Multi");
+
+		ngeoms = GEOSGetNumGeometries_r(ctx->gctx, geom);
+		geoms = NULL;
+		if ( ngeoms )
+		{
+			geoms = rtalloc(ctx, sizeof(RTGEOM *)*ngeoms);
+			for (i=0; i<ngeoms; i++)
+			{
+				g = GEOSGetGeometryN_r(ctx->gctx, geom, i);
+				geoms[i] = GEOS2RTGEOM(ctx, g, want3d);
+			}
+		}
+		return (RTGEOM *)rtcollection_construct(ctx, type,
+		                                        SRID, NULL, ngeoms, geoms);
+
+	default:
+		rterror(ctx, "GEOS2RTGEOM: unknown geometry type: %d", type);
+		return NULL;
+
+	}
+
+}
+
+static GEOSCoordSeq
+ptarray_to_GEOSCoordSeq(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	uint32_t dims = 2;
+	uint32_t i;
+	const RTPOINT3DZ *p3d;
+	const RTPOINT2D *p2d;
+	GEOSCoordSeq sq;
+
+	if ( RTFLAGS_GET_Z(pa->flags) ) 
+		dims = 3;
+
+	if ( ! (sq = GEOSCoordSeq_create_r(ctx->gctx, pa->npoints, dims)) ) 
+		rterror(ctx, "Error creating GEOS Coordinate Sequence");
+
+	for ( i=0; i < pa->npoints; i++ )
+	{
+		if ( dims == 3 )
+		{
+			p3d = rt_getPoint3dz_cp(ctx, pa, i);
+			p2d = (const RTPOINT2D *)p3d;
+			RTDEBUGF(4, "Point: %g,%g,%g", p3d->x, p3d->y, p3d->z);
+		}
+		else
+		{
+			p2d = rt_getPoint2d_cp(ctx, pa, i);
+			RTDEBUGF(4, "Point: %g,%g", p2d->x, p2d->y);
+		}
+
+#if RTGEOM_GEOS_VERSION < 33
+		/* Make sure we don't pass any infinite values down into GEOS */
+		/* GEOS 3.3+ is supposed to  handle this stuff OK */
+		if ( isinf(p2d->x) || isinf(p2d->y) || (dims == 3 && isinf(p3d->z)) )
+			rterror(ctx, "Infinite coordinate value found in geometry.");
+		if ( isnan(p2d->x) || isnan(p2d->y) || (dims == 3 && isnan(p3d->z)) )
+			rterror(ctx, "NaN coordinate value found in geometry.");
+#endif
+
+		GEOSCoordSeq_setX_r(ctx->gctx, sq, i, p2d->x);
+		GEOSCoordSeq_setY_r(ctx->gctx, sq, i, p2d->y);
+		
+		if ( dims == 3 ) 
+			GEOSCoordSeq_setZ_r(ctx->gctx, sq, i, p3d->z);
+	}
+	return sq;
+}
+
+static GEOSGeometry *
+ptarray_to_GEOSLinearRing(const RTCTX *ctx, const RTPOINTARRAY *pa, int autofix)
+{
+	GEOSCoordSeq sq;
+	GEOSGeom g;
+	RTPOINTARRAY *npa = 0;
+
+	if ( autofix )
+	{
+		/* check ring for being closed and fix if not */
+		if ( ! ptarray_is_closed_2d(ctx, pa) ) 
+		{
+			npa = ptarray_addPoint(ctx, pa, rt_getPoint_internal(ctx, pa, 0), RTFLAGS_NDIMS(pa->flags), pa->npoints);
+			pa = npa;
+		}
+		/* TODO: check ring for having at least 4 vertices */
+#if 0
+		while ( pa->npoints < 4 ) 
+		{
+			npa = ptarray_addPoint(ctx, npa, rt_getPoint_internal(ctx, pa, 0), RTFLAGS_NDIMS(pa->flags), pa->npoints);
+		}
+#endif
+	}
+
+	sq = ptarray_to_GEOSCoordSeq(ctx, pa);
+	if ( npa ) ptarray_free(ctx, npa);
+	g = GEOSGeom_createLinearRing_r(ctx->gctx, sq);
+	return g;
+}
+
+GEOSGeometry *
+GBOX2GEOS(const RTCTX *ctx, const RTGBOX *box)
+{
+	GEOSGeometry* envelope;
+	GEOSGeometry* ring;
+	GEOSCoordSequence* seq = GEOSCoordSeq_create_r(ctx->gctx, 5, 2);
+	if (!seq) 
+	{
+		return NULL;
+	}
+
+	GEOSCoordSeq_setX_r(ctx->gctx, seq, 0, box->xmin);
+	GEOSCoordSeq_setY_r(ctx->gctx, seq, 0, box->ymin);
+
+	GEOSCoordSeq_setX_r(ctx->gctx, seq, 1, box->xmax);
+	GEOSCoordSeq_setY_r(ctx->gctx, seq, 1, box->ymin);
+
+	GEOSCoordSeq_setX_r(ctx->gctx, seq, 2, box->xmax);
+	GEOSCoordSeq_setY_r(ctx->gctx, seq, 2, box->ymax);
+
+	GEOSCoordSeq_setX_r(ctx->gctx, seq, 3, box->xmin);
+	GEOSCoordSeq_setY_r(ctx->gctx, seq, 3, box->ymax);
+
+	GEOSCoordSeq_setX_r(ctx->gctx, seq, 4, box->xmin);
+	GEOSCoordSeq_setY_r(ctx->gctx, seq, 4, box->ymin);
+
+	ring = GEOSGeom_createLinearRing_r(ctx->gctx, seq);
+	if (!ring) 
+	{
+		GEOSCoordSeq_destroy_r(ctx->gctx, seq);
+		return NULL;
+	}
+
+	envelope = GEOSGeom_createPolygon_r(ctx->gctx, ring, NULL, 0);
+	if (!envelope) 
+	{
+		GEOSGeom_destroy_r(ctx->gctx, ring);
+		return NULL;
+	}
+
+	return envelope;
+}
+
+GEOSGeometry *
+RTGEOM2GEOS(const RTCTX *ctx, const RTGEOM *rtgeom, int autofix)
+{
+	GEOSCoordSeq sq;
+	GEOSGeom g, shell;
+	GEOSGeom *geoms = NULL;
+	/*
+	RTGEOM *tmp;
+	*/
+	uint32_t ngeoms, i;
+	int geostype;
+#if RTDEBUG_LEVEL >= 4
+	char *wkt;
+#endif
+
+	RTDEBUGF(4, "RTGEOM2GEOS got a %s", rttype_name(ctx, rtgeom->type));
+
+	if (rtgeom_has_arc(ctx, rtgeom))
+	{
+		RTGEOM *rtgeom_stroked = rtgeom_stroke(ctx, rtgeom, 32);
+		GEOSGeometry *g = RTGEOM2GEOS(ctx, rtgeom_stroked, autofix);
+		rtgeom_free(ctx, rtgeom_stroked);
+		return g;
+	}
+	
+	switch (rtgeom->type)
+	{
+		RTPOINT *rtp = NULL;
+		RTPOLY *rtpoly = NULL;
+		RTLINE *rtl = NULL;
+		RTCOLLECTION *rtc = NULL;
+#if RTGEOM_GEOS_VERSION < 33
+		RTPOINTARRAY *pa = NULL;
+#endif
+		
+	case RTPOINTTYPE:
+		rtp = (RTPOINT *)rtgeom;
+		
+		if ( rtgeom_is_empty(ctx, rtgeom) )
+		{
+#if RTGEOM_GEOS_VERSION < 33
+			pa = ptarray_construct_empty(ctx, rtgeom_has_z(ctx, rtgeom), rtgeom_has_m(ctx, rtgeom), 2);
+			sq = ptarray_to_GEOSCoordSeq(ctx, pa);
+			shell = GEOSGeom_createLinearRing_r(ctx->gctx, sq);
+			g = GEOSGeom_createPolygon_r(ctx->gctx, shell, NULL, 0);
+#else
+			g = GEOSGeom_createEmptyPolygon_r(ctx->gctx);
+#endif
+		}
+		else
+		{
+			sq = ptarray_to_GEOSCoordSeq(ctx, rtp->point);
+			g = GEOSGeom_createPoint_r(ctx->gctx, sq);
+		}
+		if ( ! g )
+		{
+			/* rtnotice(ctx, "Exception in RTGEOM2GEOS"); */
+			return NULL;
+		}
+		break;
+	case RTLINETYPE:
+		rtl = (RTLINE *)rtgeom;
+		/* TODO: if (autofix) */
+		if ( rtl->points->npoints == 1 ) {
+			/* Duplicate point, to make geos-friendly */
+			rtl->points = ptarray_addPoint(ctx, rtl->points,
+		                           rt_getPoint_internal(ctx, rtl->points, 0),
+		                           RTFLAGS_NDIMS(rtl->points->flags),
+		                           rtl->points->npoints);
+		}
+		sq = ptarray_to_GEOSCoordSeq(ctx, rtl->points);
+		g = GEOSGeom_createLineString_r(ctx->gctx, sq);
+		if ( ! g )
+		{
+			/* rtnotice(ctx, "Exception in RTGEOM2GEOS"); */
+			return NULL;
+		}
+		break;
+
+	case RTPOLYGONTYPE:
+		rtpoly = (RTPOLY *)rtgeom;
+		if ( rtgeom_is_empty(ctx, rtgeom) )
+		{
+#if RTGEOM_GEOS_VERSION < 33
+			RTPOINTARRAY *pa = ptarray_construct_empty(ctx, rtgeom_has_z(ctx, rtgeom), rtgeom_has_m(ctx, rtgeom), 2);
+			sq = ptarray_to_GEOSCoordSeq(ctx, pa);
+			shell = GEOSGeom_createLinearRing_r(ctx->gctx, sq);
+			g = GEOSGeom_createPolygon_r(ctx->gctx, shell, NULL, 0);
+#else
+			g = GEOSGeom_createEmptyPolygon_r(ctx->gctx);
+#endif
+		}
+		else
+		{
+			shell = ptarray_to_GEOSLinearRing(ctx, rtpoly->rings[0], autofix);
+			if ( ! shell ) return NULL;
+			/*rterror(ctx, "RTGEOM2GEOS: exception during polygon shell conversion"); */
+			ngeoms = rtpoly->nrings-1;
+			if ( ngeoms > 0 )
+				geoms = malloc(sizeof(GEOSGeom)*ngeoms);
+
+			for (i=1; i<rtpoly->nrings; ++i)
+			{
+				geoms[i-1] = ptarray_to_GEOSLinearRing(ctx, rtpoly->rings[i], autofix);
+				if ( ! geoms[i-1] )
+				{
+					--i;
+					while (i) GEOSGeom_destroy_r(ctx->gctx, geoms[--i]);
+					free(geoms);
+					GEOSGeom_destroy_r(ctx->gctx, shell);
+					return NULL;
+				}
+				/*rterror(ctx, "RTGEOM2GEOS: exception during polygon hole conversion"); */
+			}
+			g = GEOSGeom_createPolygon_r(ctx->gctx, shell, geoms, ngeoms);
+			if (geoms) free(geoms);
+		}
+		if ( ! g ) return NULL;
+		break;
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		if ( rtgeom->type == RTMULTIPOINTTYPE )
+			geostype = GEOS_MULTIPOINT;
+		else if ( rtgeom->type == RTMULTILINETYPE )
+			geostype = GEOS_MULTILINESTRING;
+		else if ( rtgeom->type == RTMULTIPOLYGONTYPE )
+			geostype = GEOS_MULTIPOLYGON;
+		else
+			geostype = GEOS_GEOMETRYCOLLECTION;
+
+		rtc = (RTCOLLECTION *)rtgeom;
+
+		ngeoms = rtc->ngeoms;
+		if ( ngeoms > 0 )
+			geoms = malloc(sizeof(GEOSGeom)*ngeoms);
+
+		for (i=0; i<ngeoms; ++i)
+		{
+			GEOSGeometry* g = RTGEOM2GEOS(ctx, rtc->geoms[i], 0);
+			if ( ! g )
+			{
+				while (i) GEOSGeom_destroy_r(ctx->gctx, geoms[--i]);
+				free(geoms);
+				return NULL;
+			}
+			geoms[i] = g;
+		}
+		g = GEOSGeom_createCollection_r(ctx->gctx, geostype, geoms, ngeoms);
+		if ( geoms ) free(geoms);
+		if ( ! g ) return NULL;
+		break;
+
+	default:
+		rterror(ctx, "Unknown geometry type: %d - %s", rtgeom->type, rttype_name(ctx, rtgeom->type));
+		return NULL;
+	}
+
+	GEOSSetSRID_r(ctx->gctx, g, rtgeom->srid);
+
+#if RTDEBUG_LEVEL >= 4
+	wkt = GEOSGeomToWKT_r(ctx->gctx, g);
+	RTDEBUGF(4, "RTGEOM2GEOS: GEOSGeom: %s", wkt);
+	free(wkt);
+#endif
+
+	return g;
+}
+
+const char*
+rtgeom_geos_version()
+{
+	const char *ver = GEOSversion();
+	return ver;
+}
+
+RTGEOM *
+rtgeom_normalize(const RTCTX *ctx, const RTGEOM *geom1)
+{
+	RTGEOM *result ;
+	GEOSGeometry *g1;
+	int is3d ;
+	int srid ;
+
+	srid = (int)(geom1->srid);
+	is3d = RTFLAGS_GET_Z(geom1->flags);
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ;
+	}
+
+	if ( -1 == GEOSNormalize_r(ctx->gctx, g1) )
+	{
+	  rterror(ctx, "Error in GEOSNormalize: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+	GEOSSetSRID_r(ctx->gctx, g1, srid); /* needed ? */
+	result = GEOS2RTGEOM(ctx, g1, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+
+	if (result == NULL)
+	{
+	  rterror(ctx, "Error performing intersection: GEOS2RTGEOM: %s",
+	                rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	return result ;
+}
+
+RTGEOM *
+rtgeom_intersection(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2)
+{
+	RTGEOM *result ;
+	GEOSGeometry *g1, *g2, *g3 ;
+	int is3d ;
+	int srid ;
+
+	/* A.Intersection(Empty) == Empty */
+	if ( rtgeom_is_empty(ctx, geom2) )
+		return rtgeom_clone_deep(ctx, geom2);
+
+	/* Empty.Intersection(A) == Empty */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	/* ensure srids are identical */
+	srid = (int)(geom1->srid);
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	RTDEBUG(3, "intersection() START");
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ;
+	}
+
+	g2 = RTGEOM2GEOS(ctx, geom2, 0);
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "Second argument geometry could not be converted to GEOS.");
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		return NULL ;
+	}
+
+	RTDEBUG(3, " constructed geometrys - calling geos");
+	RTDEBUGF(3, " g1 = %s", GEOSGeomToWKT_r(ctx->gctx, g1));
+	RTDEBUGF(3, " g2 = %s", GEOSGeomToWKT_r(ctx->gctx, g2));
+	/*RTDEBUGF(3, "g2 is valid = %i",GEOSisvalid_r(ctx->gctx, g2)); */
+	/*RTDEBUGF(3, "g1 is valid = %i",GEOSisvalid_r(ctx->gctx, g1)); */
+
+	g3 = GEOSIntersection_r(ctx->gctx, g1,g2);
+
+	RTDEBUG(3, " intersection finished");
+
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		rterror(ctx, "Error performing intersection: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3) ) ;
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	if (result == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "Error performing intersection: GEOS2RTGEOM: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	return result ;
+}
+
+RTGEOM *
+rtgeom_linemerge(const RTCTX *ctx, const RTGEOM *geom1)
+{
+	RTGEOM *result ;
+	GEOSGeometry *g1, *g3 ;
+	int is3d = RTFLAGS_GET_Z(geom1->flags);
+	int srid = geom1->srid;
+
+	/* Empty.Linemerge() == Empty */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return (RTGEOM*)rtcollection_construct_empty(ctx,  RTCOLLECTIONTYPE, srid, is3d,
+                                         rtgeom_has_m(ctx, geom1) );
+
+	rtgeom_geos_ensure_init(ctx);
+
+	RTDEBUG(3, "linemerge() START");
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ;
+	}
+
+	RTDEBUG(3, " constructed geometrys - calling geos");
+	RTDEBUGF(3, " g1 = %s", GEOSGeomToWKT_r(ctx->gctx, g1));
+	/*RTDEBUGF(3, "g1 is valid = %i",GEOSisvalid_r(ctx->gctx, g1)); */
+
+	g3 = GEOSLineMerge_r(ctx->gctx, g1);
+
+	RTDEBUG(3, " linemerge finished");
+
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "Error performing linemerge: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3) ) ;
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	if (result == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "Error performing linemerge: GEOS2RTGEOM: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	return result ;
+}
+
+RTGEOM *
+rtgeom_unaryunion(const RTCTX *ctx, const RTGEOM *geom1)
+{
+	RTGEOM *result ;
+	GEOSGeometry *g1, *g3 ;
+	int is3d = RTFLAGS_GET_Z(geom1->flags);
+	int srid = geom1->srid;
+
+	/* Empty.UnaryUnion() == Empty */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ;
+	}
+
+	g3 = GEOSUnaryUnion_r(ctx->gctx, g1);
+
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "Error performing unaryunion: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3) ) ;
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	if (result == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "Error performing unaryunion: GEOS2RTGEOM: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	return result ;
+}
+
+RTGEOM *
+rtgeom_difference(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2)
+{
+	GEOSGeometry *g1, *g2, *g3;
+	RTGEOM *result;
+	int is3d;
+	int srid;
+
+	/* A.Difference(Empty) == A */
+	if ( rtgeom_is_empty(ctx, geom2) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	/* Empty.Intersection(A) == Empty */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	/* ensure srids are identical */
+	srid = (int)(geom1->srid);
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = RTGEOM2GEOS(ctx, geom2, 0);
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "Second argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g3 = GEOSDifference_r(ctx->gctx, g1,g2);
+
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		rterror(ctx, "GEOSDifference: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3) ) ;
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	if (result == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "Error performing difference: GEOS2RTGEOM: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	/* compressType(result); */
+
+	return result;
+}
+
+RTGEOM *
+rtgeom_symdifference(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2)
+{
+	GEOSGeometry *g1, *g2, *g3;
+	RTGEOM *result;
+	int is3d;
+	int srid;
+
+	/* A.SymDifference(Empty) == A */
+	if ( rtgeom_is_empty(ctx, geom2) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	/* Empty.DymDifference(B) == B */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom2);
+
+	/* ensure srids are identical */
+	srid = (int)(geom1->srid);
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = RTGEOM2GEOS(ctx, geom2, 0);
+
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "Second argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		return NULL;
+	}
+
+	g3 = GEOSSymDifference_r(ctx->gctx, g1,g2);
+
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		rterror(ctx, "GEOSSymDifference: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL; /*never get here */
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3));
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	if (result == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "GEOS symdifference_r(ctx->gctx) threw an error (result postgis geometry formation)!");
+		return NULL ; /*never get here */
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	return result;
+}
+
+RTGEOM*
+rtgeom_union(const RTCTX *ctx, const RTGEOM *geom1, const RTGEOM *geom2)
+{
+	int is3d;
+	int srid;
+	GEOSGeometry *g1, *g2, *g3;
+	RTGEOM *result;
+
+	RTDEBUG(2, "in geomunion");
+
+	/* A.Union(empty) == A */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom2);
+
+	/* B.Union(empty) == B */
+	if ( rtgeom_is_empty(ctx, geom2) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+
+	/* ensure srids are identical */
+	srid = (int)(geom1->srid);
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 0);
+
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = RTGEOM2GEOS(ctx, geom2, 0);
+
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "Second argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	RTDEBUGF(3, "g1=%s", GEOSGeomToWKT_r(ctx->gctx, g1));
+	RTDEBUGF(3, "g2=%s", GEOSGeomToWKT_r(ctx->gctx, g2));
+
+	g3 = GEOSUnion_r(ctx->gctx, g1,g2);
+
+	RTDEBUGF(3, "g3=%s", GEOSGeomToWKT_r(ctx->gctx, g3));
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+
+	if (g3 == NULL)
+	{
+		rterror(ctx, "GEOSUnion: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL; /* never get here */
+	}
+
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	if (result == NULL)
+	{
+		rterror(ctx, "Error performing union: GEOS2RTGEOM: %s",
+		        rtgeom_get_last_geos_error(ctx));
+		return NULL; /*never get here */
+	}
+
+	return result;
+}
+
+RTGEOM *
+rtgeom_clip_by_rect(const RTCTX *ctx, const RTGEOM *geom1, double x0, double y0, double x1, double y1)
+{
+#if RTGEOM_GEOS_VERSION < 35
+	rterror(ctx, "The GEOS version this postgis binary "
+	        "was compiled against (%d) doesn't support "
+	        "'GEOSClipByRect' function _r(ctx->gctx, 3.3.5+ required)",
+	        RTGEOM_GEOS_VERSION);
+	return NULL;
+#else /* RTGEOM_GEOS_VERSION >= 35 */
+	RTGEOM *result ;
+	GEOSGeometry *g1, *g3 ;
+	int is3d ;
+
+	/* A.Intersection(Empty) == Empty */
+	if ( rtgeom_is_empty(ctx, geom1) )
+		return rtgeom_clone_deep(ctx, geom1);
+
+	is3d = RTFLAGS_GET_Z(geom1->flags);
+
+	rtgeom_geos_ensure_init(ctx);
+
+	RTDEBUG(3, "clip_by_rect() START");
+
+	g1 = RTGEOM2GEOS(ctx, geom1, 1); /* auto-fix structure */
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ;
+	}
+
+	RTDEBUG(3, " constructed geometrys - calling geos");
+	RTDEBUGF(3, " g1 = %s", GEOSGeomToWKT_r(ctx->gctx, g1));
+	/*RTDEBUGF(3, "g1 is valid = %i",GEOSisvalid_r(ctx->gctx, g1)); */
+
+	g3 = GEOSClipByRect_r(ctx->gctx, g1,x0,y0,x1,y1);
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+
+	RTDEBUG(3, " clip_by_rect finished");
+
+	if (g3 == NULL)
+	{
+		rtnotice(ctx, "Error performing rectangular clipping: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3) ) ;
+
+	result = GEOS2RTGEOM(ctx, g3, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	if (result == NULL)
+	{
+		rterror(ctx, "Error performing intersection: GEOS2RTGEOM: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL ; /* never get here */
+	}
+
+	result->srid = geom1->srid;
+
+	return result ;
+#endif /* RTGEOM_GEOS_VERSION >= 35 */
+}
+
+
+/* ------------ BuildArea stuff ---------------------------------------------------------------------{ */
+
+typedef struct Face_t {
+  const GEOSGeometry* geom;
+  GEOSGeometry* env;
+  double envarea;
+  struct Face_t* parent; /* if this face is an hole of another one, or NULL */
+} Face;
+
+static Face* newFace(const RTCTX *ctx, const GEOSGeometry* g);
+static void delFace(const RTCTX *ctx, Face* f);
+static unsigned int countParens(const RTCTX *ctx, const Face* f);
+static void findFaceHoles(const RTCTX *ctx, Face** faces, int nfaces);
+
+static Face*
+newFace(const RTCTX *ctx, const GEOSGeometry* g)
+{
+  Face* f = rtalloc(ctx, sizeof(Face));
+  f->geom = g;
+  f->env = GEOSEnvelope_r(ctx->gctx, f->geom);
+  GEOSArea_r(ctx->gctx, f->env, &f->envarea);
+  f->parent = NULL;
+  /* rtnotice(ctx, "Built Face with area %g and %d holes", f->envarea, GEOSGetNumInteriorRings_r(ctx->gctx, f->geom)); */
+  return f;
+}
+
+static unsigned int
+countParens(const RTCTX *ctx, const Face* f)
+{
+  unsigned int pcount = 0;
+  while ( f->parent ) {
+    ++pcount;
+    f = f->parent;
+  }
+  return pcount;
+}
+
+/* Destroy the face and release memory associated with it */
+static void
+delFace(const RTCTX *ctx, Face* f)
+{
+  GEOSGeom_destroy_r(ctx->gctx, f->env);
+  rtfree(ctx, f);
+}
+
+
+static int
+compare_by_envarea(const void* g1, const void* g2)
+{
+  Face* f1 = *(Face**)g1;
+  Face* f2 = *(Face**)g2;
+  double n1 = f1->envarea;
+  double n2 = f2->envarea;
+
+  if ( n1 < n2 ) return 1;
+  if ( n1 > n2 ) return -1;
+  return 0;
+}
+
+/* Find holes of each face */
+static void
+findFaceHoles(const RTCTX *ctx, Face** faces, int nfaces)
+{
+  int i, j, h;
+
+  /* We sort by envelope area so that we know holes are only
+   * after their shells */
+  qsort(faces, nfaces, sizeof(Face*), compare_by_envarea);
+  for (i=0; i<nfaces; ++i) {
+    Face* f = faces[i];
+    int nholes = GEOSGetNumInteriorRings_r(ctx->gctx, f->geom);
+    RTDEBUGF(2, "Scanning face %d with env area %g and %d holes", i, f->envarea, nholes);
+    for (h=0; h<nholes; ++h) {
+      const GEOSGeometry *hole = GEOSGetInteriorRingN_r(ctx->gctx, f->geom, h);
+      RTDEBUGF(2, "Looking for hole %d/%d of face %d among %d other faces", h+1, nholes, i, nfaces-i-1);
+      for (j=i+1; j<nfaces; ++j) {
+		const GEOSGeometry *f2er;
+        Face* f2 = faces[j];
+        if ( f2->parent ) continue; /* hole already assigned */
+        f2er = GEOSGetExteriorRing_r(ctx->gctx, f2->geom); 
+        /* TODO: can be optimized as the ring would have the
+         *       same vertices, possibly in different order.
+         *       maybe comparing number of points could already be
+         *       useful.
+         */
+        if ( GEOSEquals_r(ctx->gctx, f2er, hole) ) {
+          RTDEBUGF(2, "Hole %d/%d of face %d is face %d", h+1, nholes, i, j);
+          f2->parent = f;
+          break;
+        }
+      }
+    }
+  }
+}
+
+static GEOSGeometry*
+collectFacesWithEvenAncestors(const RTCTX *ctx, Face** faces, int nfaces)
+{
+  GEOSGeometry **geoms = rtalloc(ctx, sizeof(GEOSGeometry*)*nfaces);
+  GEOSGeometry *ret;
+  unsigned int ngeoms = 0;
+  int i;
+
+  for (i=0; i<nfaces; ++i) {
+    Face *f = faces[i];
+    if ( countParens(ctx, f) % 2 ) continue; /* we skip odd parents geoms */
+    geoms[ngeoms++] = GEOSGeom_clone_r(ctx->gctx, f->geom);
+  }
+
+  ret = GEOSGeom_createCollection_r(ctx->gctx, GEOS_MULTIPOLYGON, geoms, ngeoms);
+  rtfree(ctx, geoms);
+  return ret;
+}
+
+GEOSGeometry*
+RTGEOM_GEOS_buildArea(const RTCTX *ctx, const GEOSGeometry* geom_in)
+{
+  GEOSGeometry *tmp;
+  GEOSGeometry *geos_result, *shp;
+  GEOSGeometry const *vgeoms[1];
+  uint32_t i, ngeoms;
+  int srid = GEOSGetSRID_r(ctx->gctx, geom_in);
+  Face ** geoms;
+
+  vgeoms[0] = geom_in;
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Polygonizing");
+#endif
+  geos_result = GEOSPolygonize_r(ctx->gctx, vgeoms, 1);
+
+  RTDEBUGF(3, "GEOSpolygonize returned @ %p", geos_result);
+
+  /* Null return from GEOSpolygonize _r(ctx->gctx, an exception) */
+  if ( ! geos_result ) return 0;
+
+  /*
+   * We should now have a collection
+   */
+#if PARANOIA_LEVEL > 0
+  if ( GEOSGeometryTypeId_r(ctx->gctx, geos_result) != RTCOLLECTIONTYPE )
+  {
+    GEOSGeom_destroy_r(ctx->gctx, geos_result);
+    rterror(ctx, "Unexpected return from GEOSpolygonize");
+    return 0;
+  }
+#endif
+
+  ngeoms = GEOSGetNumGeometries_r(ctx->gctx, geos_result);
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Num geometries from polygonizer: %d", ngeoms);
+#endif
+
+
+  RTDEBUGF(3, "GEOSpolygonize: ngeoms in polygonize output: %d", ngeoms);
+  RTDEBUGF(3, "GEOSpolygonize: polygonized:%s",
+              rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, geos_result, 0)));
+
+  /*
+   * No geometries in collection, early out
+   */
+  if ( ngeoms == 0 )
+  {
+    GEOSSetSRID_r(ctx->gctx, geos_result, srid);
+    return geos_result;
+  }
+
+  /*
+   * Return first geometry if we only have one in collection,
+   * to avoid the unnecessary Geometry clone below.
+   */
+  if ( ngeoms == 1 )
+  {
+    tmp = (GEOSGeometry *)GEOSGetGeometryN_r(ctx->gctx, geos_result, 0);
+    if ( ! tmp )
+    {
+      GEOSGeom_destroy_r(ctx->gctx, geos_result);
+      return 0; /* exception */
+    }
+    shp = GEOSGeom_clone_r(ctx->gctx, tmp);
+    GEOSGeom_destroy_r(ctx->gctx, geos_result); /* only safe after the clone above */
+    GEOSSetSRID_r(ctx->gctx, shp, srid);
+    return shp;
+  }
+
+  RTDEBUGF(2, "Polygonize returned %d geoms", ngeoms);
+
+  /*
+   * Polygonizer returns a polygon for each face in the built topology.
+   *
+   * This means that for any face with holes we'll have other faces
+   * representing each hole. We can imagine a parent-child relationship
+   * between these faces.
+   *
+   * In order to maximize the number of visible rings in output we
+   * only use those faces which have an even number of parents.
+   *
+   * Example:
+   *
+   *   +---------------+
+   *   |     L0        |  L0 has no parents 
+   *   |  +---------+  |
+   *   |  |   L1    |  |  L1 is an hole of L0
+   *   |  |  +---+  |  |
+   *   |  |  |L2 |  |  |  L2 is an hole of L1 (which is an hole of L0)
+   *   |  |  |   |  |  |
+   *   |  |  +---+  |  |
+   *   |  +---------+  |
+   *   |               |
+   *   +---------------+
+   * 
+   * See http://trac.osgeo.org/postgis/ticket/1806
+   *
+   */
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Preparing face structures");
+#endif
+
+  /* Prepare face structures for later analysis */
+  geoms = rtalloc(ctx, sizeof(Face**)*ngeoms);
+  for (i=0; i<ngeoms; ++i)
+    geoms[i] = newFace(ctx, GEOSGetGeometryN_r(ctx->gctx, geos_result, i));
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Finding face holes");
+#endif
+
+  /* Find faces representing other faces holes */
+  findFaceHoles(ctx, geoms, ngeoms);
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Colletting even ancestor faces");
+#endif
+
+  /* Build a MultiPolygon composed only by faces with an
+   * even number of ancestors */
+  tmp = collectFacesWithEvenAncestors(ctx, geoms, ngeoms);
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Cleaning up");
+#endif
+
+  /* Cleanup face structures */
+  for (i=0; i<ngeoms; ++i) delFace(ctx, geoms[i]);
+  rtfree(ctx, geoms);
+
+  /* Faces referenced memory owned by geos_result.
+   * It is safe to destroy geos_result after deleting them. */
+  GEOSGeom_destroy_r(ctx->gctx, geos_result);
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Self-unioning");
+#endif
+
+  /* Run a single overlay operation to dissolve shared edges */
+  shp = GEOSUnionCascaded_r(ctx->gctx, tmp);
+  if ( ! shp )
+  {
+    GEOSGeom_destroy_r(ctx->gctx, tmp);
+    return 0; /* exception */
+  }
+
+#ifdef RTGEOM_PROFILE_BUILDAREA
+  rtnotice(ctx, "Final cleanup");
+#endif
+
+  GEOSGeom_destroy_r(ctx->gctx, tmp);
+
+  GEOSSetSRID_r(ctx->gctx, shp, srid);
+
+  return shp;
+}
+
+RTGEOM*
+rtgeom_buildarea(const RTCTX *ctx, const RTGEOM *geom)
+{
+	GEOSGeometry* geos_in;
+	GEOSGeometry* geos_out;
+	RTGEOM* geom_out;
+	int SRID = (int)(geom->srid);
+	int is3d = RTFLAGS_GET_Z(geom->flags);
+
+	/* Can't build an area from an empty! */
+	if ( rtgeom_is_empty(ctx, geom) )
+	{
+		return (RTGEOM*)rtpoly_construct_empty(ctx, SRID, is3d, 0);
+	}
+
+	RTDEBUG(3, "buildarea called");
+
+	RTDEBUGF(3, "ST_BuildArea got geom @ %p", geom);
+
+	rtgeom_geos_ensure_init(ctx);
+
+	geos_in = RTGEOM2GEOS(ctx, geom, 0);
+	
+	if ( 0 == geos_in )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	geos_out = RTGEOM_GEOS_buildArea(ctx, geos_in);
+	GEOSGeom_destroy_r(ctx->gctx, geos_in);
+
+	if ( ! geos_out ) /* exception thrown.. */
+	{
+		rterror(ctx, "RTGEOM_GEOS_buildArea: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* If no geometries are in result collection, return NULL */
+	if ( GEOSGetNumGeometries_r(ctx->gctx, geos_out) == 0 )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, geos_out);
+		return NULL;
+	}
+
+	geom_out = GEOS2RTGEOM(ctx, geos_out, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, geos_out);
+
+#if PARANOIA_LEVEL > 0
+	if ( geom_out == NULL )
+	{
+		rterror(ctx, "serialization error");
+		return NULL;
+	}
+
+#endif
+
+	return geom_out;
+}
+
+int
+rtgeom_is_simple(const RTCTX *ctx, const RTGEOM *geom)
+{
+	GEOSGeometry* geos_in;
+	int simple;
+
+	/* Empty is artays simple */
+	if ( rtgeom_is_empty(ctx, geom) )
+	{
+		return 1;
+	}
+
+	rtgeom_geos_ensure_init(ctx);
+
+	geos_in = RTGEOM2GEOS(ctx, geom, 0);
+	if ( 0 == geos_in )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return -1;
+	}
+	simple = GEOSisSimple_r(ctx->gctx, geos_in);
+	GEOSGeom_destroy_r(ctx->gctx, geos_in);
+
+	if ( simple == 2 ) /* exception thrown */
+	{
+		rterror(ctx, "rtgeom_is_simple: %s", rtgeom_get_last_geos_error(ctx));
+		return -1;
+	}
+
+	return simple ? 1 : 0;
+}
+
+/* ------------ end of BuildArea stuff ---------------------------------------------------------------------} */
+
+RTGEOM*
+rtgeom_geos_noop(const RTCTX *ctx, const RTGEOM* geom_in)
+{
+	GEOSGeometry *geosgeom;
+	RTGEOM* geom_out;
+
+	int is3d = RTFLAGS_GET_Z(geom_in->flags);
+
+	rtgeom_geos_ensure_init(ctx);
+	geosgeom = RTGEOM2GEOS(ctx, geom_in, 0);
+	if ( ! geosgeom ) {
+		rterror(ctx, "Geometry could not be converted to GEOS: %s",
+			rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	geom_out = GEOS2RTGEOM(ctx, geosgeom, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, geosgeom);
+	if ( ! geom_out ) {
+		rterror(ctx, "GEOS Geometry could not be converted to RTGEOM: %s",
+			rtgeom_get_last_geos_error(ctx));
+	}
+	return geom_out;
+	
+}
+
+RTGEOM*
+rtgeom_snap(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2, double tolerance)
+{
+#if RTGEOM_GEOS_VERSION < 33
+	rterror(ctx, "The GEOS version this rtgeom library "
+	        "was compiled against (%d) doesn't support "
+	        "'Snap' function (3.3.0+ required)",
+	        RTGEOM_GEOS_VERSION);
+	return NULL;
+#else /* RTGEOM_GEOS_VERSION >= 33 */
+
+	int srid, is3d;
+	GEOSGeometry *g1, *g2, *g3;
+	RTGEOM* out;
+
+	srid = geom1->srid;
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = (GEOSGeometry *)RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = (GEOSGeometry *)RTGEOM2GEOS(ctx, geom2, 0);
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "Second argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		return NULL;
+	}
+
+	g3 = GEOSSnap_r(ctx->gctx, g1, g2, tolerance);
+	if (g3 == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		rterror(ctx, "GEOSSnap: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+	out = GEOS2RTGEOM(ctx, g3, is3d);
+	if (out == NULL)
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g3);
+		rterror(ctx, "GEOSSnap_r(ctx->gctx) threw an error (result RTGEOM geometry formation)!");
+		return NULL;
+	}
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	return out;
+
+#endif /* RTGEOM_GEOS_VERSION >= 33 */
+}
+
+RTGEOM*
+rtgeom_sharedpaths(const RTCTX *ctx, const RTGEOM* geom1, const RTGEOM* geom2)
+{
+#if RTGEOM_GEOS_VERSION < 33
+	rterror(ctx, "The GEOS version this postgis binary "
+	        "was compiled against (%d) doesn't support "
+	        "'SharedPaths' function (3.3.0+ required)",
+	        RTGEOM_GEOS_VERSION);
+	return NULL;
+#else /* RTGEOM_GEOS_VERSION >= 33 */
+	GEOSGeometry *g1, *g2, *g3;
+	RTGEOM *out;
+	int is3d, srid;
+
+	srid = geom1->srid;
+	error_if_srid_mismatch(ctx, srid, (int)(geom2->srid));
+
+	is3d = (RTFLAGS_GET_Z(geom1->flags) || RTFLAGS_GET_Z(geom2->flags)) ;
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = (GEOSGeometry *)RTGEOM2GEOS(ctx, geom1, 0);
+	if ( 0 == g1 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "First argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = (GEOSGeometry *)RTGEOM2GEOS(ctx, geom2, 0);
+	if ( 0 == g2 )   /* exception thrown at construction */
+	{
+		rterror(ctx, "Second argument geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		return NULL;
+	}
+
+	g3 = GEOSSharedPaths_r(ctx->gctx, g1,g2);
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+
+	if (g3 == NULL)
+	{
+		rterror(ctx, "GEOSSharedPaths: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	GEOSSetSRID_r(ctx->gctx, g3, srid);
+	out = GEOS2RTGEOM(ctx, g3, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	if (out == NULL)
+	{
+		rterror(ctx, "GEOS2RTGEOM threw an error");
+		return NULL;
+	}
+
+	return out;
+#endif /* RTGEOM_GEOS_VERSION >= 33 */
+}
+
+RTGEOM*
+rtgeom_offsetcurve(const RTCTX *ctx, const RTLINE *rtline, double size, int quadsegs, int joinStyle, double mitreLimit)
+{
+#if RTGEOM_GEOS_VERSION < 32
+	rterror(ctx, "rtgeom_offsetcurve: GEOS 3.2 or higher required");
+#else
+	GEOSGeometry *g1, *g3;
+	RTGEOM *rtgeom_result;
+	RTGEOM *rtgeom_in = rtline_as_rtgeom(ctx, rtline);
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = (GEOSGeometry *)RTGEOM2GEOS(ctx, rtgeom_in, 0);
+	if ( ! g1 ) 
+	{
+		rterror(ctx, "rtgeom_offsetcurve: Geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+#if RTGEOM_GEOS_VERSION < 33
+	/* Size is artays positive for GEOSSingleSidedBuffer, and a flag determines left/right */
+	g3 = GEOSSingleSidedBuffer_r(ctx->gctx, g1, size < 0 ? -size : size,
+	                           quadsegs, joinStyle, mitreLimit,
+	                           size < 0 ? 0 : 1);
+#else
+	g3 = GEOSOffsetCurve_r(ctx->gctx, g1, size, quadsegs, joinStyle, mitreLimit);
+#endif
+	/* Don't need input geometry anymore */
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+
+	if (g3 == NULL)
+	{
+		rterror(ctx, "GEOSOffsetCurve: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3));
+
+	GEOSSetSRID_r(ctx->gctx, g3, rtgeom_get_srid(ctx, rtgeom_in));
+	rtgeom_result = GEOS2RTGEOM(ctx, g3, rtgeom_has_z(ctx, rtgeom_in));
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	if (rtgeom_result == NULL)
+	{
+		rterror(ctx, "rtgeom_offsetcurve: GEOS2RTGEOM returned null");
+		return NULL;
+	}
+
+	return rtgeom_result;
+	
+#endif /* RTGEOM_GEOS_VERSION < 32 */
+}
+
+RTTIN * rttin_from_geos(const RTCTX *ctx, const GEOSGeometry *geom, int want3d) {
+	int type = GEOSGeomTypeId_r(ctx->gctx, geom);
+	int hasZ;
+	int SRID = GEOSGetSRID_r(ctx->gctx, geom);
+
+	/* GEOS's 0 is equivalent to our unknown as for SRID values */
+	if ( SRID == 0 ) SRID = SRID_UNKNOWN;
+
+	if ( want3d ) {
+		hasZ = GEOSHasZ_r(ctx->gctx, geom);
+		if ( ! hasZ ) {
+			RTDEBUG(3, "Geometry has no Z, won't provide one");
+			want3d = 0;
+		}
+	}
+
+	switch (type) {
+		RTTRIANGLE **geoms;
+		uint32_t i, ngeoms;
+	case GEOS_GEOMETRYCOLLECTION:
+		RTDEBUG(4, "rtgeom_from_geometry: it's a Collection or Multi");
+
+		ngeoms = GEOSGetNumGeometries_r(ctx->gctx, geom);
+		geoms = NULL;
+		if ( ngeoms ) {
+			geoms = rtalloc(ctx, ngeoms * sizeof *geoms);
+			if (!geoms) {
+				rterror(ctx, "rttin_from_geos: can't allocate geoms");
+				return NULL;
+			}
+			for (i=0; i<ngeoms; i++) {
+				const GEOSGeometry *poly, *ring;
+				const GEOSCoordSequence *cs;
+				RTPOINTARRAY *pa;
+
+				poly = GEOSGetGeometryN_r(ctx->gctx, geom, i);
+				ring = GEOSGetExteriorRing_r(ctx->gctx, poly);
+				cs = GEOSGeom_getCoordSeq_r(ctx->gctx, ring);
+				pa = ptarray_from_GEOSCoordSeq(ctx, cs, want3d);
+
+				geoms[i] = rttriangle_construct(ctx, SRID, NULL, pa);
+			}
+		}
+		return (RTTIN *)rtcollection_construct(ctx, RTTINTYPE, SRID, NULL, ngeoms, (RTGEOM **)geoms);
+	case GEOS_POLYGON:
+	case GEOS_MULTIPOINT:
+	case GEOS_MULTILINESTRING:
+	case GEOS_MULTIPOLYGON:
+	case GEOS_LINESTRING:
+	case GEOS_LINEARRING:
+	case GEOS_POINT:
+		rterror(ctx, "rttin_from_geos: invalid geometry type for tin: %d", type);
+		break;
+
+	default:
+		rterror(ctx, "GEOS2RTGEOM: unknown geometry type: %d", type);
+		return NULL;
+	}
+
+	/* shouldn't get here */
+	return NULL;
+}
+/*
+ * output = 1 for edges, 2 for TIN, 0 for polygons
+ */
+RTGEOM* rtgeom_delaunay_triangulation(const RTCTX *ctx, const RTGEOM *rtgeom_in, double tolerance, int output) {
+#if RTGEOM_GEOS_VERSION < 34
+	rterror(ctx, "rtgeom_delaunay_triangulation: GEOS 3.4 or higher required");
+	return NULL;
+#else
+	GEOSGeometry *g1, *g3;
+	RTGEOM *rtgeom_result;
+
+	if (output < 0 || output > 2) {
+		rterror(ctx, "rtgeom_delaunay_triangulation: invalid output type specified %d", output);
+		return NULL;
+	}
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = (GEOSGeometry *)RTGEOM2GEOS(ctx, rtgeom_in, 0);
+	if ( ! g1 ) 
+	{
+		rterror(ctx, "rtgeom_delaunay_triangulation: Geometry could not be converted to GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* if output != 1 we want polys */
+	g3 = GEOSDelaunayTriangulation_r(ctx->gctx, g1, tolerance, output == 1);
+
+	/* Don't need input geometry anymore */
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+
+	if (g3 == NULL)
+	{
+		rterror(ctx, "GEOSDelaunayTriangulation: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* RTDEBUGF(3, "result: %s", GEOSGeomToWKT_r(ctx->gctx, g3)); */
+
+	GEOSSetSRID_r(ctx->gctx, g3, rtgeom_get_srid(ctx, rtgeom_in));
+
+	if (output == 2) {
+		rtgeom_result = (RTGEOM *)rttin_from_geos(ctx, g3, rtgeom_has_z(ctx, rtgeom_in));
+	} else {
+		rtgeom_result = GEOS2RTGEOM(ctx, g3, rtgeom_has_z(ctx, rtgeom_in));
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g3);
+
+	if (rtgeom_result == NULL) {
+		if (output != 2) {
+			rterror(ctx, "rtgeom_delaunay_triangulation: GEOS2RTGEOM returned null");
+		} else {
+			rterror(ctx, "rtgeom_delaunay_triangulation: rttin_from_geos returned null");
+		}
+		return NULL;
+	}
+
+	return rtgeom_result;
+	
+#endif /* RTGEOM_GEOS_VERSION < 34 */
+}
diff --git a/src/rtgeom_geos.h b/src/rtgeom_geos.h
new file mode 100644
index 0000000..46d9a0c
--- /dev/null
+++ b/src/rtgeom_geos.h
@@ -0,0 +1,45 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom.h"
+
+/*
+** Public prototypes for GEOS utility functions.
+*/
+RTGEOM *GEOS2RTGEOM(const RTCTX *ctx, const GEOSGeometry *geom, char want3d);
+GEOSGeometry * RTGEOM2GEOS(const RTCTX *ctx, const RTGEOM *g, int autofix);
+GEOSGeometry * GBOX2GEOS(const RTCTX *ctx, const RTGBOX *g);
+GEOSGeometry * RTGEOM_GEOS_buildArea(const RTCTX *ctx, const GEOSGeometry* geom_in);
+
+RTPOINTARRAY *ptarray_from_GEOSCoordSeq(const RTCTX *ctx, const GEOSCoordSequence *cs, char want3d);
+
+/* Return (read-only) last geos error message */
+const char *rtgeom_get_last_geos_error(const RTCTX *ctx);
+
+extern void rtgeom_geos_error(const char *msg, void *ctx);
+
+extern void rtgeom_geos_ensure_init(const RTCTX *ctx);
+
diff --git a/src/rtgeom_geos_clean.c b/src/rtgeom_geos_clean.c
new file mode 100644
index 0000000..5857e95
--- /dev/null
+++ b/src/rtgeom_geos_clean.c
@@ -0,0 +1,1059 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2009-2010 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom.h"
+#include "rtgeom_geos.h"
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/* #define RTGEOM_DEBUG_LEVEL 4 */
+/* #define PARANOIA_LEVEL 2 */
+#undef RTGEOM_PROFILE_MAKEVALID
+
+
+/*
+ * Return Nth vertex in GEOSGeometry as a POINT.
+ * May return NULL if the geometry has NO vertexex.
+ */
+static GEOSGeometry*
+RTGEOM_GEOS_getPointN(const RTCTX *ctx, const GEOSGeometry* g_in, uint32_t n)
+{
+	uint32_t dims;
+	const GEOSCoordSequence* seq_in;
+	GEOSCoordSeq seq_out;
+	double val;
+	uint32_t sz;
+	int gn;
+	GEOSGeometry* ret;
+
+	switch ( GEOSGeomTypeId_r(ctx->gctx, g_in) )
+	{
+	case GEOS_MULTIPOINT:
+	case GEOS_MULTILINESTRING:
+	case GEOS_MULTIPOLYGON:
+	case GEOS_GEOMETRYCOLLECTION:
+	{
+		for (gn=0; gn<GEOSGetNumGeometries_r(ctx->gctx, g_in); ++gn)
+		{
+			const GEOSGeometry* g = GEOSGetGeometryN_r(ctx->gctx, g_in, gn);
+			ret = RTGEOM_GEOS_getPointN(ctx, g,n);
+			if ( ret ) return ret;
+		}
+		break;
+	}
+
+	case GEOS_POLYGON:
+	{
+		ret = RTGEOM_GEOS_getPointN(ctx, GEOSGetExteriorRing_r(ctx->gctx, g_in), n);
+		if ( ret ) return ret;
+		for (gn=0; gn<GEOSGetNumInteriorRings_r(ctx->gctx, g_in); ++gn)
+		{
+			const GEOSGeometry* g = GEOSGetInteriorRingN_r(ctx->gctx, g_in, gn);
+			ret = RTGEOM_GEOS_getPointN(ctx, g, n);
+			if ( ret ) return ret;
+		}
+		break;
+	}
+
+	case GEOS_POINT:
+	case GEOS_LINESTRING:
+	case GEOS_LINEARRING:
+		break;
+
+	}
+
+	seq_in = GEOSGeom_getCoordSeq_r(ctx->gctx, g_in);
+	if ( ! seq_in ) return NULL;
+	if ( ! GEOSCoordSeq_getSize_r(ctx->gctx, seq_in, &sz) ) return NULL;
+	if ( ! sz ) return NULL;
+
+	if ( ! GEOSCoordSeq_getDimensions_r(ctx->gctx, seq_in, &dims) ) return NULL;
+
+	seq_out = GEOSCoordSeq_create_r(ctx->gctx, 1, dims);
+	if ( ! seq_out ) return NULL;
+
+	if ( ! GEOSCoordSeq_getX_r(ctx->gctx, seq_in, n, &val) ) return NULL;
+	if ( ! GEOSCoordSeq_setX_r(ctx->gctx, seq_out, n, val) ) return NULL;
+	if ( ! GEOSCoordSeq_getY_r(ctx->gctx, seq_in, n, &val) ) return NULL;
+	if ( ! GEOSCoordSeq_setY_r(ctx->gctx, seq_out, n, val) ) return NULL;
+	if ( dims > 2 )
+	{
+		if ( ! GEOSCoordSeq_getZ_r(ctx->gctx, seq_in, n, &val) ) return NULL;
+		if ( ! GEOSCoordSeq_setZ_r(ctx->gctx, seq_out, n, val) ) return NULL;
+	}
+
+	return GEOSGeom_createPoint_r(ctx->gctx, seq_out);
+}
+
+
+
+RTGEOM * rtcollection_make_geos_friendly(const RTCTX *ctx, RTCOLLECTION *g);
+RTGEOM * rtline_make_geos_friendly(const RTCTX *ctx, RTLINE *line);
+RTGEOM * rtpoly_make_geos_friendly(const RTCTX *ctx, RTPOLY *poly);
+RTPOINTARRAY* ring_make_geos_friendly(const RTCTX *ctx, RTPOINTARRAY* ring);
+
+/*
+ * Ensure the geometry is "structurally" valid
+ * (enough for GEOS to accept it)
+ * May return the input untouched (if already valid).
+ * May return geometries of lower dimension (on collapses)
+ */
+static RTGEOM *
+rtgeom_make_geos_friendly(const RTCTX *ctx, RTGEOM *geom)
+{
+	RTDEBUGF(2, "rtgeom_make_geos_friendly enter (type %d)", geom->type);
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+	case RTMULTIPOINTTYPE:
+		/* a point is artays valid */
+		return geom;
+		break;
+
+	case RTLINETYPE:
+		/* lines need at least 2 points */
+		return rtline_make_geos_friendly(ctx, (RTLINE *)geom);
+		break;
+
+	case RTPOLYGONTYPE:
+		/* polygons need all rings closed and with npoints > 3 */
+		return rtpoly_make_geos_friendly(ctx, (RTPOLY *)geom);
+		break;
+
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_make_geos_friendly(ctx, (RTCOLLECTION *)geom);
+		break;
+
+	case RTCIRCSTRINGTYPE:
+	case RTCOMPOUNDTYPE:
+	case RTCURVEPOLYTYPE:
+	case RTMULTISURFACETYPE:
+	case RTMULTICURVETYPE:
+	default:
+		rterror(ctx, "rtgeom_make_geos_friendly: unsupported input geometry type: %s (%d)", rttype_name(ctx, geom->type), geom->type);
+		break;
+	}
+	return 0;
+}
+
+/*
+ * Close the point array, if not already closed in 2d.
+ * Returns the input if already closed in 2d, or a newly
+ * constructed RTPOINTARRAY.
+ * TODO: move in ptarray.c
+ */
+RTPOINTARRAY* ptarray_close2d(const RTCTX *ctx, RTPOINTARRAY* ring);
+RTPOINTARRAY*
+ptarray_close2d(const RTCTX *ctx, RTPOINTARRAY* ring)
+{
+	RTPOINTARRAY* newring;
+
+	/* close the ring if not already closed (2d only) */
+	if ( ! ptarray_is_closed_2d(ctx, ring) )
+	{
+		/* close it up */
+		newring = ptarray_addPoint(ctx, ring,
+		                           rt_getPoint_internal(ctx, ring, 0),
+		                           RTFLAGS_NDIMS(ring->flags),
+		                           ring->npoints);
+		ring = newring;
+	}
+	return ring;
+}
+
+/* May return the same input or a new one (never zero) */
+RTPOINTARRAY*
+ring_make_geos_friendly(const RTCTX *ctx, RTPOINTARRAY* ring)
+{
+	RTPOINTARRAY* closedring;
+	RTPOINTARRAY* ring_in = ring;
+
+	/* close the ring if not already closed (2d only) */
+	closedring = ptarray_close2d(ctx, ring);
+	if (closedring != ring )
+	{
+		ring = closedring;
+	}
+
+	/* return 0 for collapsed ring (after closeup) */
+
+	while ( ring->npoints < 4 )
+	{
+		RTPOINTARRAY *oring = ring;
+		RTDEBUGF(4, "ring has %d points, adding another", ring->npoints);
+		/* let's add another... */
+		ring = ptarray_addPoint(ctx, ring,
+		                        rt_getPoint_internal(ctx, ring, 0),
+		                        RTFLAGS_NDIMS(ring->flags),
+		                        ring->npoints);
+		if ( oring != ring_in ) ptarray_free(ctx, oring);
+	}
+
+
+	return ring;
+}
+
+/* Make sure all rings are closed and have > 3 points.
+ * May return the input untouched.
+ */
+RTGEOM *
+rtpoly_make_geos_friendly(const RTCTX *ctx, RTPOLY *poly)
+{
+	RTGEOM* ret;
+	RTPOINTARRAY **new_rings;
+	int i;
+
+	/* If the polygon has no rings there's nothing to do */
+	if ( ! poly->nrings ) return (RTGEOM*)poly;
+
+	/* Allocate enough pointers for all rings */
+	new_rings = rtalloc(ctx, sizeof(RTPOINTARRAY*)*poly->nrings);
+
+	/* All rings must be closed and have > 3 points */
+	for (i=0; i<poly->nrings; i++)
+	{
+		RTPOINTARRAY* ring_in = poly->rings[i];
+		RTPOINTARRAY* ring_out = ring_make_geos_friendly(ctx, ring_in);
+
+		if ( ring_in != ring_out )
+		{
+			RTDEBUGF(3, "rtpoly_make_geos_friendly: ring %d cleaned, now has %d points", i, ring_out->npoints);
+			ptarray_free(ctx, ring_in);
+		}
+		else
+		{
+			RTDEBUGF(3, "rtpoly_make_geos_friendly: ring %d untouched", i);
+		}
+
+		assert ( ring_out );
+		new_rings[i] = ring_out;
+	}
+
+	rtfree(ctx, poly->rings);
+	poly->rings = new_rings;
+	ret = (RTGEOM*)poly;
+
+	return ret;
+}
+
+/* Need NO or >1 points. Duplicate first if only one. */
+RTGEOM *
+rtline_make_geos_friendly(const RTCTX *ctx, RTLINE *line)
+{
+	RTGEOM *ret;
+
+	if (line->points->npoints == 1) /* 0 is fine, 2 is fine */
+	{
+#if 1
+		/* Duplicate point */
+		line->points = ptarray_addPoint(ctx, line->points,
+		                                rt_getPoint_internal(ctx, line->points, 0),
+		                                RTFLAGS_NDIMS(line->points->flags),
+		                                line->points->npoints);
+		ret = (RTGEOM*)line;
+#else
+		/* Turn into a point */
+		ret = (RTGEOM*)rtpoint_construct(ctx, line->srid, 0, line->points);
+#endif
+		return ret;
+	}
+	else
+	{
+		return (RTGEOM*)line;
+		/* return rtline_clone(ctx, line); */
+	}
+}
+
+RTGEOM *
+rtcollection_make_geos_friendly(const RTCTX *ctx, RTCOLLECTION *g)
+{
+	RTGEOM **new_geoms;
+	uint32_t i, new_ngeoms=0;
+	RTCOLLECTION *ret;
+
+	/* enough space for all components */
+	new_geoms = rtalloc(ctx, sizeof(RTGEOM *)*g->ngeoms);
+
+	ret = rtalloc(ctx, sizeof(RTCOLLECTION));
+	memcpy(ret, g, sizeof(RTCOLLECTION));
+    ret->maxgeoms = g->ngeoms;
+
+	for (i=0; i<g->ngeoms; i++)
+	{
+		RTGEOM* newg = rtgeom_make_geos_friendly(ctx, g->geoms[i]);
+		if ( newg ) new_geoms[new_ngeoms++] = newg;
+	}
+
+	ret->bbox = NULL; /* recompute later... */
+
+	ret->ngeoms = new_ngeoms;
+	if ( new_ngeoms )
+	{
+		ret->geoms = new_geoms;
+	}
+	else
+	{
+		free(new_geoms);
+		ret->geoms = NULL;
+        ret->maxgeoms = 0;
+	}
+
+	return (RTGEOM*)ret;
+}
+
+/*
+ * Fully node given linework
+ */
+static GEOSGeometry*
+RTGEOM_GEOS_nodeLines(const RTCTX *ctx, const GEOSGeometry* lines)
+{
+	GEOSGeometry* noded;
+	GEOSGeometry* point;
+
+	/*
+	 * Union with first geometry point, obtaining full noding
+	 * and dissolving of duplicated repeated points
+	 *
+	 * TODO: substitute this with UnaryUnion?
+	 */
+
+	point = RTGEOM_GEOS_getPointN(ctx, lines, 0);
+	if ( ! point ) return NULL;
+
+	RTDEBUGF(3,
+	               "Boundary point: %s",
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, point, 0)));
+
+	noded = GEOSUnion_r(ctx->gctx, lines, point);
+	if ( NULL == noded )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, point);
+		return NULL;
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, point);
+
+	RTDEBUGF(3,
+	               "RTGEOM_GEOS_nodeLines: in[%s] out[%s]",
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, lines, 0)),
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, noded, 0)));
+
+	return noded;
+}
+
+#if RTGEOM_GEOS_VERSION >= 33
+/*
+ * We expect rtgeom_geos_ensure_init(ctx);
+ * Will return NULL on error (expect error handler being called by then)
+ *
+ */
+static GEOSGeometry*
+RTGEOM_GEOS_makeValidPolygon(const RTCTX *ctx, const GEOSGeometry* gin)
+{
+	GEOSGeom gout;
+	GEOSGeom geos_bound;
+	GEOSGeom geos_cut_edges, geos_area, collapse_points;
+	GEOSGeometry *vgeoms[3]; /* One for area, one for cut-edges */
+	unsigned int nvgeoms=0;
+
+	assert (GEOSGeomTypeId_r(ctx->gctx, gin) == GEOS_POLYGON ||
+	        GEOSGeomTypeId_r(ctx->gctx, gin) == GEOS_MULTIPOLYGON);
+
+	geos_bound = GEOSBoundary_r(ctx->gctx, gin);
+	if ( NULL == geos_bound )
+	{
+		return NULL;
+	}
+
+	RTDEBUGF(3,
+	               "Boundaries: %s",
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, geos_bound, 0)));
+
+	/* Use noded boundaries as initial "cut" edges */
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+  rtnotice(ctx, "ST_MakeValid: noding lines");
+#endif
+
+
+	geos_cut_edges = RTGEOM_GEOS_nodeLines(ctx, geos_bound);
+	if ( NULL == geos_cut_edges )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, geos_bound);
+		rtnotice(ctx, "RTGEOM_GEOS_nodeLines(ctx): %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* NOTE: the noding process may drop lines collapsing to points.
+	 *       We want to retrive any of those */
+	{
+		GEOSGeometry* pi;
+		GEOSGeometry* po;
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: extracting unique points from bounds");
+#endif
+
+		pi = GEOSGeom_extractUniquePoints_r(ctx->gctx, geos_bound);
+		if ( NULL == pi )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, geos_bound);
+			rtnotice(ctx, "GEOSGeom_extractUniquePoints_r(ctx->gctx): %s",
+			         rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		RTDEBUGF(3,
+		               "Boundaries input points %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, pi, 0)));
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: extracting unique points from cut_edges");
+#endif
+
+		po = GEOSGeom_extractUniquePoints_r(ctx->gctx, geos_cut_edges);
+		if ( NULL == po )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, geos_bound);
+			GEOSGeom_destroy_r(ctx->gctx, pi);
+			rtnotice(ctx, "GEOSGeom_extractUniquePoints_r(ctx->gctx): %s",
+			         rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		RTDEBUGF(3,
+		               "Boundaries output points %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, po, 0)));
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: find collapse points");
+#endif
+
+		collapse_points = GEOSDifference_r(ctx->gctx, pi, po);
+		if ( NULL == collapse_points )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, geos_bound);
+			GEOSGeom_destroy_r(ctx->gctx, pi);
+			GEOSGeom_destroy_r(ctx->gctx, po);
+			rtnotice(ctx, "GEOSDifference_r(ctx->gctx): %s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		RTDEBUGF(3,
+		               "Collapse points: %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, collapse_points, 0)));
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: cleanup(1)");
+#endif
+
+		GEOSGeom_destroy_r(ctx->gctx, pi);
+		GEOSGeom_destroy_r(ctx->gctx, po);
+	}
+	GEOSGeom_destroy_r(ctx->gctx, geos_bound);
+
+	RTDEBUGF(3,
+	               "Noded Boundaries: %s",
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, geos_cut_edges, 0)));
+
+	/* And use an empty geometry as initial "area" */
+	geos_area = GEOSGeom_createEmptyPolygon_r(ctx->gctx);
+	if ( ! geos_area )
+	{
+		rtnotice(ctx, "GEOSGeom_createEmptyPolygon_r(ctx->gctx): %s", rtgeom_get_last_geos_error(ctx));
+		GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+		return NULL;
+	}
+
+	/*
+	 * See if an area can be build with the remaining edges
+	 * and if it can, symdifference with the original area.
+	 * Iterate this until no more polygons can be created
+	 * with left-over edges.
+	 */
+	while (GEOSGetNumGeometries_r(ctx->gctx, geos_cut_edges))
+	{
+		GEOSGeometry* new_area=0;
+		GEOSGeometry* new_area_bound=0;
+		GEOSGeometry* symdif=0;
+		GEOSGeometry* new_cut_edges=0;
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: building area from %d edges", GEOSGetNumGeometries_r(ctx->gctx, geos_cut_edges)); 
+#endif
+
+		/*
+		 * ASSUMPTION: cut_edges should already be fully noded
+		 */
+
+		new_area = RTGEOM_GEOS_buildArea(ctx, geos_cut_edges);
+		if ( ! new_area )   /* must be an exception */
+		{
+			GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+			GEOSGeom_destroy_r(ctx->gctx, geos_area);
+			rtnotice(ctx, "RTGEOM_GEOS_buildArea(ctx) threw an error: %s",
+			         rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		if ( GEOSisEmpty_r(ctx->gctx, new_area) )
+		{
+			/* no more rings can be build with thes edges */
+			GEOSGeom_destroy_r(ctx->gctx, new_area);
+			break;
+		}
+
+		/*
+		 * We succeeded in building a ring !
+		 */
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: ring built with %d cut edges, saving boundaries", GEOSGetNumGeometries_r(ctx->gctx, geos_cut_edges)); 
+#endif
+
+		/*
+		 * Save the new ring boundaries first (to compute
+		 * further cut edges later)
+		 */
+		new_area_bound = GEOSBoundary_r(ctx->gctx, new_area);
+		if ( ! new_area_bound )
+		{
+			/* We did check for empty area already so
+			 * this must be some other error */
+			rtnotice(ctx, "GEOSBoundary_r(ctx->gctx, '%s') threw an error: %s",
+			         rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, new_area, 0)),
+			         rtgeom_get_last_geos_error(ctx));
+			GEOSGeom_destroy_r(ctx->gctx, new_area);
+			GEOSGeom_destroy_r(ctx->gctx, geos_area);
+			return NULL;
+		}
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: running SymDifference with new area"); 
+#endif
+
+		/*
+		 * Now symdif new and old area
+		 */
+		symdif = GEOSSymDifference_r(ctx->gctx, geos_area, new_area);
+		if ( ! symdif )   /* must be an exception */
+		{
+			GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+			GEOSGeom_destroy_r(ctx->gctx, new_area);
+			GEOSGeom_destroy_r(ctx->gctx, new_area_bound);
+			GEOSGeom_destroy_r(ctx->gctx, geos_area);
+			rtnotice(ctx, "GEOSSymDifference_r(ctx->gctx) threw an error: %s",
+			         rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		GEOSGeom_destroy_r(ctx->gctx, geos_area);
+		GEOSGeom_destroy_r(ctx->gctx, new_area);
+		geos_area = symdif;
+		symdif = 0;
+
+		/*
+		 * Now let's re-set geos_cut_edges with what's left
+		 * from the original boundary.
+		 * ASSUMPTION: only the previous cut-edges can be
+		 *             left, so we don't need to reconsider
+		 *             the whole original boundaries
+		 *
+		 * NOTE: this is an expensive operation. 
+		 *
+		 */
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+    rtnotice(ctx, "ST_MakeValid: computing new cut_edges (GEOSDifference)"); 
+#endif
+
+		new_cut_edges = GEOSDifference_r(ctx->gctx, geos_cut_edges, new_area_bound);
+		GEOSGeom_destroy_r(ctx->gctx, new_area_bound);
+		if ( ! new_cut_edges )   /* an exception ? */
+		{
+			/* cleanup and throw */
+			GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+			GEOSGeom_destroy_r(ctx->gctx, geos_area);
+			/* TODO: Shouldn't this be an rterror ? */
+			rtnotice(ctx, "GEOSDifference_r(ctx->gctx) threw an error: %s",
+			         rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+		geos_cut_edges = new_cut_edges;
+	}
+
+#ifdef RTGEOM_PROFILE_MAKEVALID
+  rtnotice(ctx, "ST_MakeValid: final checks");
+#endif
+
+	if ( ! GEOSisEmpty_r(ctx->gctx, geos_area) )
+	{
+		vgeoms[nvgeoms++] = geos_area;
+	}
+	else
+	{
+		GEOSGeom_destroy_r(ctx->gctx, geos_area);
+	}
+
+	if ( ! GEOSisEmpty_r(ctx->gctx, geos_cut_edges) )
+	{
+		vgeoms[nvgeoms++] = geos_cut_edges;
+	}
+	else
+	{
+		GEOSGeom_destroy_r(ctx->gctx, geos_cut_edges);
+	}
+
+	if ( ! GEOSisEmpty_r(ctx->gctx, collapse_points) )
+	{
+		vgeoms[nvgeoms++] = collapse_points;
+	}
+	else
+	{
+		GEOSGeom_destroy_r(ctx->gctx, collapse_points);
+	}
+
+	if ( 1 == nvgeoms )
+	{
+		/* Return cut edges */
+		gout = vgeoms[0];
+	}
+	else
+	{
+		/* Collect areas and lines (if any line) */
+		gout = GEOSGeom_createCollection_r(ctx->gctx, GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms);
+		if ( ! gout )   /* an exception again */
+		{
+			/* cleanup and throw */
+			/* TODO: Shouldn't this be an rterror ? */
+			rtnotice(ctx, "GEOSGeom_createCollection_r(ctx->gctx) threw an error: %s",
+			         rtgeom_get_last_geos_error(ctx));
+			/* TODO: cleanup! */
+			return NULL;
+		}
+	}
+
+	return gout;
+
+}
+
+static GEOSGeometry*
+RTGEOM_GEOS_makeValidLine(const RTCTX *ctx, const GEOSGeometry* gin)
+{
+	GEOSGeometry* noded;
+	noded = RTGEOM_GEOS_nodeLines(ctx, gin);
+	return noded;
+}
+
+static GEOSGeometry*
+RTGEOM_GEOS_makeValidMultiLine(const RTCTX *ctx, const GEOSGeometry* gin)
+{
+	GEOSGeometry** lines;
+	GEOSGeometry** points;
+	GEOSGeometry* mline_out=0;
+	GEOSGeometry* mpoint_out=0;
+	GEOSGeometry* gout=0;
+	uint32_t nlines=0, nlines_alloc;
+	uint32_t npoints=0;
+	uint32_t ngeoms=0, nsubgeoms;
+	uint32_t i, j;
+
+	ngeoms = GEOSGetNumGeometries_r(ctx->gctx, gin);
+
+	nlines_alloc = ngeoms;
+	lines = rtalloc(ctx, sizeof(GEOSGeometry*)*nlines_alloc);
+	points = rtalloc(ctx, sizeof(GEOSGeometry*)*ngeoms);
+
+	for (i=0; i<ngeoms; ++i)
+	{
+		const GEOSGeometry* g = GEOSGetGeometryN_r(ctx->gctx, gin, i);
+		GEOSGeometry* vg;
+		vg = RTGEOM_GEOS_makeValidLine(ctx, g);
+		if ( GEOSisEmpty_r(ctx->gctx, vg) )
+		{
+			/* we don't care about this one */
+			GEOSGeom_destroy_r(ctx->gctx, vg);
+		}
+		if ( GEOSGeomTypeId_r(ctx->gctx, vg) == GEOS_POINT )
+		{
+			points[npoints++] = vg;
+		}
+		else if ( GEOSGeomTypeId_r(ctx->gctx, vg) == GEOS_LINESTRING )
+		{
+			lines[nlines++] = vg;
+		}
+		else if ( GEOSGeomTypeId_r(ctx->gctx, vg) == GEOS_MULTILINESTRING )
+		{
+			nsubgeoms=GEOSGetNumGeometries_r(ctx->gctx, vg);
+			nlines_alloc += nsubgeoms;
+			lines = rtrealloc(ctx, lines, sizeof(GEOSGeometry*)*nlines_alloc);
+			for (j=0; j<nsubgeoms; ++j)
+			{
+				const GEOSGeometry* gc = GEOSGetGeometryN_r(ctx->gctx, vg, j);
+				/* NOTE: ownership of the cloned geoms will be
+				 *       taken by final collection */
+				lines[nlines++] = GEOSGeom_clone_r(ctx->gctx, gc);
+			}
+		}
+		else
+		{
+			/* NOTE: return from GEOSGeomType will leak
+			 * but we really don't expect this to happen */
+			rterror(ctx, "unexpected geom type returned "
+			        "by RTGEOM_GEOS_makeValid: %s",
+			        GEOSGeomType_r(ctx->gctx, vg));
+		}
+	}
+
+	if ( npoints )
+	{
+		if ( npoints > 1 )
+		{
+			mpoint_out = GEOSGeom_createCollection_r(ctx->gctx, GEOS_MULTIPOINT,
+			                                       points, npoints);
+		}
+		else
+		{
+			mpoint_out = points[0];
+		}
+	}
+
+	if ( nlines )
+	{
+		if ( nlines > 1 )
+		{
+			mline_out = GEOSGeom_createCollection_r(ctx->gctx, 
+			                GEOS_MULTILINESTRING, lines, nlines);
+		}
+		else
+		{
+			mline_out = lines[0];
+		}
+	}
+
+	rtfree(ctx, lines);
+
+	if ( mline_out && mpoint_out )
+	{
+		points[0] = mline_out;
+		points[1] = mpoint_out;
+		gout = GEOSGeom_createCollection_r(ctx->gctx, GEOS_GEOMETRYCOLLECTION,
+		                                 points, 2);
+	}
+	else if ( mline_out )
+	{
+		gout = mline_out;
+	}
+	else if ( mpoint_out )
+	{
+		gout = mpoint_out;
+	}
+
+	rtfree(ctx, points);
+
+	return gout;
+}
+
+static GEOSGeometry* RTGEOM_GEOS_makeValid(const RTCTX *ctx, const GEOSGeometry*);
+
+/*
+ * We expect rtgeom_geos_ensure_init(ctx);
+ * Will return NULL on error (expect error handler being called by then)
+ */
+static GEOSGeometry*
+RTGEOM_GEOS_makeValidCollection(const RTCTX *ctx, const GEOSGeometry* gin)
+{
+	int nvgeoms;
+	GEOSGeometry **vgeoms;
+	GEOSGeom gout;
+	unsigned int i;
+
+	nvgeoms = GEOSGetNumGeometries_r(ctx->gctx, gin);
+	if ( nvgeoms == -1 ) {
+		rterror(ctx, "GEOSGetNumGeometries: %s", rtgeom_get_last_geos_error(ctx));
+		return 0;
+	}
+
+	vgeoms = rtalloc(ctx,  sizeof(GEOSGeometry*) * nvgeoms );
+	if ( ! vgeoms ) {
+		rterror(ctx, "RTGEOM_GEOS_makeValidCollection: out of memory");
+		return 0;
+	}
+
+	for ( i=0; i<nvgeoms; ++i ) {
+		vgeoms[i] = RTGEOM_GEOS_makeValid(ctx,  GEOSGetGeometryN_r(ctx->gctx, gin, i) );
+		if ( ! vgeoms[i] ) {
+			while (i--) GEOSGeom_destroy_r(ctx->gctx, vgeoms[i]);
+			rtfree(ctx, vgeoms);
+			/* we expect rterror being called already by makeValid */
+			return NULL;
+		}
+	}
+
+	/* Collect areas and lines (if any line) */
+	gout = GEOSGeom_createCollection_r(ctx->gctx, GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms);
+	if ( ! gout )   /* an exception again */
+	{
+		/* cleanup and throw */
+		for ( i=0; i<nvgeoms; ++i ) GEOSGeom_destroy_r(ctx->gctx, vgeoms[i]);
+		rtfree(ctx, vgeoms);
+		rterror(ctx, "GEOSGeom_createCollection_r(ctx->gctx) threw an error: %s",
+		         rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	rtfree(ctx, vgeoms);
+
+	return gout;
+
+}
+
+
+static GEOSGeometry*
+RTGEOM_GEOS_makeValid(const RTCTX *ctx, const GEOSGeometry* gin)
+{
+	GEOSGeometry* gout;
+	char ret_char;
+
+	/*
+	 * Step 2: return what we got so far if already valid
+	 */
+
+	ret_char = GEOSisValid_r(ctx->gctx, gin);
+	if ( ret_char == 2 )
+	{
+		/* I don't think should ever happen */
+		rterror(ctx, "GEOSisValid_r(ctx->gctx): %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	else if ( ret_char )
+	{
+		RTDEBUGF(3,
+		               "Geometry [%s] is valid. ",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, gin, 0)));
+
+		/* It's valid at this step, return what we have */
+		return GEOSGeom_clone_r(ctx->gctx, gin);
+	}
+
+	RTDEBUGF(3,
+	               "Geometry [%s] is still not valid: %s. "
+	               "Will try to clean up further.",
+	               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, gin, 0)), rtgeom_get_last_geos_error(ctx));
+
+
+
+	/*
+	 * Step 3 : make what we got valid
+	 */
+
+	switch (GEOSGeomTypeId_r(ctx->gctx, gin))
+	{
+	case GEOS_MULTIPOINT:
+	case GEOS_POINT:
+		/* points are artays valid, but we might have invalid ordinate values */
+		rtnotice(ctx, "PUNTUAL geometry resulted invalid to GEOS -- dunno how to clean that up");
+		return NULL;
+		break;
+
+	case GEOS_LINESTRING:
+		gout = RTGEOM_GEOS_makeValidLine(ctx, gin);
+		if ( ! gout )  /* an exception or something */
+		{
+			/* cleanup and throw */
+			rterror(ctx, "%s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		break; /* we've done */
+
+	case GEOS_MULTILINESTRING:
+		gout = RTGEOM_GEOS_makeValidMultiLine(ctx, gin);
+		if ( ! gout )  /* an exception or something */
+		{
+			/* cleanup and throw */
+			rterror(ctx, "%s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		break; /* we've done */
+
+	case GEOS_POLYGON:
+	case GEOS_MULTIPOLYGON:
+	{
+		gout = RTGEOM_GEOS_makeValidPolygon(ctx, gin);
+		if ( ! gout )  /* an exception or something */
+		{
+			/* cleanup and throw */
+			rterror(ctx, "%s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		break; /* we've done */
+	}
+
+	case GEOS_GEOMETRYCOLLECTION:
+	{
+		gout = RTGEOM_GEOS_makeValidCollection(ctx, gin);
+		if ( ! gout )  /* an exception or something */
+		{
+			/* cleanup and throw */
+			rterror(ctx, "%s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		break; /* we've done */
+	}
+
+	default:
+	{
+		char* typname = GEOSGeomType_r(ctx->gctx, gin);
+		rtnotice(ctx, "ST_MakeValid: doesn't support geometry type: %s",
+		         typname);
+		GEOSFree_r(ctx->gctx, typname);
+		return NULL;
+		break;
+	}
+	}
+
+#if PARANOIA_LEVEL > 1
+	/*
+	 * Now check if every point of input is also found
+	 * in output, or abort by returning NULL
+	 *
+	 * Input geometry was rtgeom_in
+	 */
+	{
+			int loss;
+			GEOSGeometry *pi, *po, *pd;
+
+			/* TODO: handle some errors here...
+			 * Lack of exceptions is annoying indeed,
+			 * I'm getting old --strk;
+			 */
+			pi = GEOSGeom_extractUniquePoints_r(ctx->gctx, gin);
+			po = GEOSGeom_extractUniquePoints_r(ctx->gctx, gout);
+			pd = GEOSDifference_r(ctx->gctx, pi, po); /* input points - output points */
+			GEOSGeom_destroy_r(ctx->gctx, pi);
+			GEOSGeom_destroy_r(ctx->gctx, po);
+			loss = !GEOSisEmpty_r(ctx->gctx, pd);
+			GEOSGeom_destroy_r(ctx->gctx, pd);
+			if ( loss )
+			{
+				rtnotice(ctx, "Vertices lost in RTGEOM_GEOS_makeValid");
+				/* return NULL */
+			}
+	}
+#endif /* PARANOIA_LEVEL > 1 */
+
+
+	return gout;
+}
+
+/* Exported. Uses GEOS internally */
+RTGEOM*
+rtgeom_make_valid(const RTCTX *ctx, RTGEOM* rtgeom_in)
+{
+	int is3d;
+	GEOSGeom geosgeom;
+	GEOSGeometry* geosout;
+	RTGEOM *rtgeom_out;
+
+	is3d = RTFLAGS_GET_Z(rtgeom_in->flags);
+
+	/*
+	 * Step 1 : try to convert to GEOS, if impossible, clean that up first
+	 *          otherwise (adding only duplicates of existing points)
+	 */
+
+	rtgeom_geos_ensure_init(ctx);
+
+	rtgeom_out = rtgeom_in;
+	geosgeom = RTGEOM2GEOS(ctx, rtgeom_out, 0);
+	if ( ! geosgeom )
+	{
+		RTDEBUGF(4,
+		               "Original geom can't be converted to GEOS _r(ctx->gctx, %s)"
+		               " - will try cleaning that up first",
+		               rtgeom_get_last_geos_error(ctx));
+
+
+		rtgeom_out = rtgeom_make_geos_friendly(ctx, rtgeom_out);
+		if ( ! rtgeom_out )
+		{
+			rterror(ctx, "Could not make a valid geometry out of input");
+		}
+
+		/* try again as we did cleanup now */
+		/* TODO: invoke RTGEOM2GEOS directly with autoclean ? */
+		geosgeom = RTGEOM2GEOS(ctx, rtgeom_out, 0);
+		if ( ! geosgeom )
+		{
+			rterror(ctx, "Couldn't convert RTGEOM geom to GEOS: %s",
+			        rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+	}
+	else
+	{
+		RTDEBUG(4, "original geom converted to GEOS");
+		rtgeom_out = rtgeom_in;
+	}
+
+	geosout = RTGEOM_GEOS_makeValid(ctx, geosgeom);
+	GEOSGeom_destroy_r(ctx->gctx, geosgeom);
+	if ( ! geosout )
+	{
+		return NULL;
+	}
+
+	rtgeom_out = GEOS2RTGEOM(ctx, geosout, is3d);
+	GEOSGeom_destroy_r(ctx->gctx, geosout);
+
+	if ( rtgeom_is_collection(ctx, rtgeom_in) && ! rtgeom_is_collection(ctx, rtgeom_out) )
+	{{
+		RTGEOM **ogeoms = rtalloc(ctx, sizeof(RTGEOM*));
+		RTGEOM *ogeom;
+		RTDEBUG(3, "rtgeom_make_valid: forcing multi");
+		/* NOTE: this is safe because rtgeom_out is surely not rtgeom_in or
+		 * otherwise we couldn't have a collection and a non-collection */
+		assert(rtgeom_in != rtgeom_out);
+		ogeoms[0] = rtgeom_out;
+		ogeom = (RTGEOM *)rtcollection_construct(ctx, RTMULTITYPE[rtgeom_out->type],
+		                          rtgeom_out->srid, rtgeom_out->bbox, 1, ogeoms);
+		rtgeom_out->bbox = NULL;
+		rtgeom_out = ogeom;
+	}}
+
+	rtgeom_out->srid = rtgeom_in->srid;
+	return rtgeom_out;
+}
+
+#endif /* RTGEOM_GEOS_VERSION >= 33 */
+
diff --git a/src/rtgeom_geos_node.c b/src/rtgeom_geos_node.c
new file mode 100644
index 0000000..6b96efb
--- /dev/null
+++ b/src/rtgeom_geos_node.c
@@ -0,0 +1,269 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2011 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#include "rtgeom_geos.h"
+#include "librtgeom_internal.h"
+
+#include <string.h>
+#include <assert.h>
+
+static int
+rtgeom_ngeoms(const RTCTX *ctx, const RTGEOM* n)
+{
+	const RTCOLLECTION* c = rtgeom_as_rtcollection(ctx, n);
+	if ( c ) return c->ngeoms;
+	else return 1;
+}
+
+static const RTGEOM*
+rtgeom_subgeom(const RTCTX *ctx, const RTGEOM* g, int n)
+{
+	const RTCOLLECTION* c = rtgeom_as_rtcollection(ctx, g);
+	if ( c ) return rtcollection_getsubgeom(ctx, (RTCOLLECTION*)c, n);
+	else return g;
+}
+
+
+static void
+rtgeom_collect_endpoints(const RTCTX *ctx, const RTGEOM* rtg, RTMPOINT* col)
+{
+	int i, n;
+	RTLINE* l;
+
+	switch (rtg->type)
+	{
+		case RTMULTILINETYPE:
+			for ( i = 0,
+			        n = rtgeom_ngeoms(ctx, rtg);
+			      i < n; ++i )
+			{
+				rtgeom_collect_endpoints(ctx, 
+					rtgeom_subgeom(ctx, rtg, i),
+					col);
+			}
+			break;
+		case RTLINETYPE:
+			l = (RTLINE*)rtg;
+			col = rtmpoint_add_rtpoint(ctx, col,
+				rtline_get_rtpoint(ctx, l, 0));
+			col = rtmpoint_add_rtpoint(ctx, col,
+				rtline_get_rtpoint(ctx, l, l->points->npoints-1));
+			break;
+		default:
+			rterror(ctx, "rtgeom_collect_endpoints: invalid type %s",
+				rttype_name(ctx, rtg->type));
+			break;
+	}
+}
+
+static RTMPOINT*
+rtgeom_extract_endpoints(const RTCTX *ctx, const RTGEOM* rtg)
+{
+	RTMPOINT* col = rtmpoint_construct_empty(ctx, SRID_UNKNOWN,
+	                              RTFLAGS_GET_Z(rtg->flags),
+	                              RTFLAGS_GET_M(rtg->flags));
+	rtgeom_collect_endpoints(ctx, rtg, col);
+
+	return col;
+}
+
+/* Assumes rtgeom_geos_ensure_init(ctx) was called */
+/* May return RTPOINT or RTMPOINT */
+static RTGEOM*
+rtgeom_extract_unique_endpoints(const RTCTX *ctx, const RTGEOM* rtg)
+{
+#if RTGEOM_GEOS_VERSION < 33
+	rterror(ctx, "The GEOS version this postgis binary "
+	        "was compiled against (%d) doesn't support "
+	        "'GEOSUnaryUnion' function _r(ctx->gctx, 3.3.0+ required)",
+	        RTGEOM_GEOS_VERSION);
+	return NULL;
+#else /* RTGEOM_GEOS_VERSION >= 33 */
+	RTGEOM* ret;
+	GEOSGeometry *gepu;
+	RTMPOINT *epall = rtgeom_extract_endpoints(ctx, rtg);
+	GEOSGeometry *gepall = RTGEOM2GEOS(ctx, (RTGEOM*)epall, 1);
+	rtmpoint_free(ctx, epall);
+	if ( ! gepall ) {
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* UnaryUnion to remove duplicates */
+	/* TODO: do it all within pgis using indices */
+	gepu = GEOSUnaryUnion_r(ctx->gctx, gepall);
+	if ( ! gepu ) {
+		GEOSGeom_destroy_r(ctx->gctx, gepall);
+		rterror(ctx, "GEOSUnaryUnion: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	GEOSGeom_destroy_r(ctx->gctx, gepall);
+
+	ret = GEOS2RTGEOM(ctx, gepu, RTFLAGS_GET_Z(rtg->flags));
+	GEOSGeom_destroy_r(ctx->gctx, gepu);
+	if ( ! ret ) {
+		rterror(ctx, "Error during GEOS2RTGEOM");
+		return NULL;
+	}
+
+	return ret;
+#endif /* RTGEOM_GEOS_VERSION >= 33 */
+}
+
+/* exported */
+extern RTGEOM* rtgeom_node(const RTCTX *ctx, const RTGEOM* rtgeom_in);
+RTGEOM*
+rtgeom_node(const RTCTX *ctx, const RTGEOM* rtgeom_in)
+{
+#if RTGEOM_GEOS_VERSION < 33
+	rterror(ctx, "The GEOS version this postgis binary "
+	        "was compiled against (%d) doesn't support "
+	        "'GEOSUnaryUnion' function _r(ctx->gctx, 3.3.0+ required)",
+	        RTGEOM_GEOS_VERSION);
+	return NULL;
+#else /* RTGEOM_GEOS_VERSION >= 33 */
+	GEOSGeometry *g1, *gu, *gm;
+	RTGEOM *ep, *lines;
+	RTCOLLECTION *col, *tc;
+	int pn, ln, np, nl;
+
+	if ( rtgeom_dimension(ctx, rtgeom_in) != 1 ) {
+		rterror(ctx, "Noding geometries of dimension != 1 is unsupported");
+		return NULL;
+	}
+
+	rtgeom_geos_ensure_init(ctx);
+	g1 = RTGEOM2GEOS(ctx, rtgeom_in, 1);
+	if ( ! g1 ) {
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	ep = rtgeom_extract_unique_endpoints(ctx, rtgeom_in);
+	if ( ! ep ) {
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "Error extracting unique endpoints from input");
+		return NULL;
+	}
+
+	/* Unary union input to fully node */
+	gu = GEOSUnaryUnion_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	if ( ! gu ) {
+		rtgeom_free(ctx, ep);
+		rterror(ctx, "GEOSUnaryUnion: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* Linemerge (in case of overlaps) */
+	gm = GEOSLineMerge_r(ctx->gctx, gu);
+	GEOSGeom_destroy_r(ctx->gctx, gu);
+	if ( ! gm ) {
+		rtgeom_free(ctx, ep);
+		rterror(ctx, "GEOSLineMerge: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	lines = GEOS2RTGEOM(ctx, gm, RTFLAGS_GET_Z(rtgeom_in->flags));
+	GEOSGeom_destroy_r(ctx->gctx, gm);
+	if ( ! lines ) {
+		rtgeom_free(ctx, ep);
+		rterror(ctx, "Error during GEOS2RTGEOM");
+		return NULL;
+	}
+
+	/*
+	 * Reintroduce endpoints from input, using split-line-by-point.
+	 * Note that by now we can be sure that each point splits at 
+	 * most _one_ segment as any point shared by multiple segments
+	 * would already be a node. Also we can be sure that any of
+	 * the segments endpoints won't split any other segment.
+	 * We can use the above 2 assertions to early exit the loop.
+	 */
+
+	col = rtcollection_construct_empty(ctx, RTMULTILINETYPE, rtgeom_in->srid,
+	                              RTFLAGS_GET_Z(rtgeom_in->flags),
+	                              RTFLAGS_GET_M(rtgeom_in->flags));
+
+	np = rtgeom_ngeoms(ctx, ep);
+	for (pn=0; pn<np; ++pn) { /* for each point */
+
+		const RTPOINT* p = (RTPOINT*)rtgeom_subgeom(ctx, ep, pn);
+
+		nl = rtgeom_ngeoms(ctx, lines);
+		for (ln=0; ln<nl; ++ln) { /* for each line */
+
+			const RTLINE* l = (RTLINE*)rtgeom_subgeom(ctx, lines, ln);
+
+			int s = rtline_split_by_point_to(ctx, l, p, (RTMLINE*)col);
+
+			if ( ! s ) continue; /* not on this line */
+
+			if ( s == 1 ) {
+				/* found on this line, but not splitting it */
+				break;
+			}
+
+			/* splits this line */
+
+			/* replace this line with the two splits */
+			if ( rtgeom_is_collection(ctx, lines) ) {
+				tc = (RTCOLLECTION*)lines;
+				rtcollection_reserve(ctx, tc, nl + 1);
+				while (nl > ln+1) {
+					tc->geoms[nl] = tc->geoms[nl-1];
+					--nl;
+				}
+				rtgeom_free(ctx, tc->geoms[ln]);
+				tc->geoms[ln]   = col->geoms[0];
+				tc->geoms[ln+1] = col->geoms[1];
+				tc->ngeoms++;
+			} else {
+				rtgeom_free(ctx, lines);
+				/* transfer ownership rather than cloning */
+				lines = (RTGEOM*)rtcollection_clone_deep(ctx, col);
+				assert(col->ngeoms == 2);
+				rtgeom_free(ctx, col->geoms[0]);
+				rtgeom_free(ctx, col->geoms[1]);
+			}
+
+			/* reset the vector */
+			assert(col->ngeoms == 2);
+			col->ngeoms = 0;
+
+			break;
+		}
+
+	}
+
+	rtgeom_free(ctx, ep);
+	rtcollection_free(ctx, col);
+
+	lines->srid = rtgeom_in->srid;
+	return (RTGEOM*)lines;
+#endif /* RTGEOM_GEOS_VERSION >= 33 */
+}
+
diff --git a/src/rtgeom_geos_split.c b/src/rtgeom_geos_split.c
new file mode 100644
index 0000000..cf8ea32
--- /dev/null
+++ b/src/rtgeom_geos_split.c
@@ -0,0 +1,545 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011-2015 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+#include "rtgeom_geos.h"
+#include "librtgeom_internal.h"
+
+#include <string.h>
+#include <assert.h>
+
+static RTGEOM* rtline_split_by_line(const RTCTX *ctx, const RTLINE* rtgeom_in, const RTGEOM* blade_in);
+static RTGEOM* rtline_split_by_point(const RTCTX *ctx, const RTLINE* rtgeom_in, const RTPOINT* blade_in);
+static RTGEOM* rtline_split_by_mpoint(const RTCTX *ctx, const RTLINE* rtgeom_in, const RTMPOINT* blade_in);
+static RTGEOM* rtline_split(const RTCTX *ctx, const RTLINE* rtgeom_in, const RTGEOM* blade_in);
+static RTGEOM* rtpoly_split_by_line(const RTCTX *ctx, const RTPOLY* rtgeom_in, const RTLINE* blade_in);
+static RTGEOM* rtcollection_split(const RTCTX *ctx, const RTCOLLECTION* rtcoll_in, const RTGEOM* blade_in);
+static RTGEOM* rtpoly_split(const RTCTX *ctx, const RTPOLY* rtpoly_in, const RTGEOM* blade_in);
+
+/* Initializes and uses GEOS internally */
+static RTGEOM*
+rtline_split_by_line(const RTCTX *ctx, const RTLINE* rtline_in, const RTGEOM* blade_in)
+{
+	RTGEOM** components;
+	RTGEOM* diff;
+	RTCOLLECTION* out;
+	GEOSGeometry* gdiff; /* difference */
+	GEOSGeometry* g1;
+	GEOSGeometry* g2;
+	int ret;
+
+	/* ASSERT blade_in is LINE or MULTILINE */
+	assert (blade_in->type == RTLINETYPE ||
+	        blade_in->type == RTMULTILINETYPE ||
+	        blade_in->type == RTPOLYGONTYPE ||
+	        blade_in->type == RTMULTIPOLYGONTYPE );
+
+	/* Possible outcomes:
+	 *
+	 *  1. The lines do not cross or overlap
+	 *      -> Return a collection with single element
+	 *  2. The lines cross
+	 *      -> Return a collection of all elements resulting from the split
+	 */
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, (RTGEOM*)rtline_in, 0);
+	if ( ! g1 )
+	{
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	g2 = RTGEOM2GEOS(ctx, blade_in, 0);
+	if ( ! g2 )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* If blade is a polygon, pick its boundary */
+	if ( blade_in->type == RTPOLYGONTYPE || blade_in->type == RTMULTIPOLYGONTYPE )
+	{
+		gdiff = GEOSBoundary_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		if ( ! gdiff )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, g1);
+			rterror(ctx, "GEOSBoundary: %s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+		g2 = gdiff; gdiff = NULL;
+	}
+
+	/* If interior intersecton is linear we can't split */
+	ret = GEOSRelatePattern_r(ctx->gctx, g1, g2, "1********");
+	if ( 2 == ret )
+	{
+		rterror(ctx, "GEOSRelatePattern: %s", rtgeom_get_last_geos_error(ctx));
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		return NULL;
+	}
+	if ( ret )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		rterror(ctx, "Splitter line has linear intersection with input");
+		return NULL;
+	}
+
+
+	gdiff = GEOSDifference_r(ctx->gctx, g1,g2);
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+	if (gdiff == NULL)
+	{
+		rterror(ctx, "GEOSDifference: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	diff = GEOS2RTGEOM(ctx, gdiff, RTFLAGS_GET_Z(rtline_in->flags));
+	GEOSGeom_destroy_r(ctx->gctx, gdiff);
+	if (NULL == diff)
+	{
+		rterror(ctx, "GEOS2RTGEOM: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	out = rtgeom_as_rtcollection(ctx, diff);
+	if ( ! out )
+	{
+		components = rtalloc(ctx, sizeof(RTGEOM*)*1);
+		components[0] = diff;
+		out = rtcollection_construct(ctx, RTCOLLECTIONTYPE, rtline_in->srid,
+		                             NULL, 1, components);
+	}
+	else
+	{
+	  /* Set SRID */
+		rtgeom_set_srid(ctx, (RTGEOM*)out, rtline_in->srid);
+	  /* Force collection type */
+	  out->type = RTCOLLECTIONTYPE;
+	}
+
+
+	return (RTGEOM*)out;
+}
+
+static RTGEOM*
+rtline_split_by_point(const RTCTX *ctx, const RTLINE* rtline_in, const RTPOINT* blade_in)
+{
+	RTMLINE* out;
+
+	out = rtmline_construct_empty(ctx, rtline_in->srid,
+		RTFLAGS_GET_Z(rtline_in->flags),
+		RTFLAGS_GET_M(rtline_in->flags));
+	if ( rtline_split_by_point_to(ctx, rtline_in, blade_in, out) < 2 )
+	{
+		rtmline_add_rtline(ctx, out, rtline_clone_deep(ctx, rtline_in));
+	}
+
+	/* Turn multiline into collection */
+	out->type = RTCOLLECTIONTYPE;
+
+	return (RTGEOM*)out;
+}
+
+static RTGEOM*
+rtline_split_by_mpoint(const RTCTX *ctx, const RTLINE* rtline_in, const RTMPOINT* mp)
+{
+  RTMLINE* out;
+  int i, j;
+
+  out = rtmline_construct_empty(ctx, rtline_in->srid,
+          RTFLAGS_GET_Z(rtline_in->flags),
+          RTFLAGS_GET_M(rtline_in->flags));
+  rtmline_add_rtline(ctx, out, rtline_clone_deep(ctx, rtline_in));
+
+  for (i=0; i<mp->ngeoms; ++i)
+  {
+    for (j=0; j<out->ngeoms; ++j)
+    {
+      rtline_in = out->geoms[j];
+      RTPOINT *blade_in = mp->geoms[i];
+      int ret = rtline_split_by_point_to(ctx, rtline_in, blade_in, out);
+      if ( 2 == ret )
+      {
+        /* the point splits this line,
+         * 2 splits were added to collection.
+         * We'll move the latest added into
+         * the slot of the current one.
+         */
+        rtline_free(ctx, out->geoms[j]);
+        out->geoms[j] = out->geoms[--out->ngeoms];
+      }
+    }
+  }
+
+  /* Turn multiline into collection */
+  out->type = RTCOLLECTIONTYPE;
+
+  return (RTGEOM*)out;
+}
+
+int
+rtline_split_by_point_to(const RTCTX *ctx, const RTLINE* rtline_in, const RTPOINT* blade_in,
+                         RTMLINE* v)
+{
+	double loc, dist;
+	RTPOINT4D pt, pt_projected;
+	RTPOINTARRAY* pa1;
+	RTPOINTARRAY* pa2;
+	double vstol; /* vertex snap tolerance */
+
+	/* Possible outcomes:
+	 *
+	 *  1. The point is not on the line or on the boundary
+	 *      -> Leave collection untouched, return 0
+	 *  2. The point is on the boundary
+	 *      -> Leave collection untouched, return 1
+	 *  3. The point is in the line
+	 *      -> Push 2 elements on the collection:
+	 *         o start_point - cut_point
+	 *         o cut_point - last_point
+	 *      -> Return 2
+	 */
+
+	rt_getPoint4d_p(ctx, blade_in->point, 0, &pt);
+	loc = ptarray_locate_point(ctx, rtline_in->points, &pt, &dist, &pt_projected);
+
+	/* rtnotice(ctx, "Location: %g -- Distance: %g", loc, dist); */
+
+	if ( dist > 0 )   /* TODO: accept a tolerance ? */
+	{
+		/* No intersection */
+		return 0;
+	}
+
+	if ( loc == 0 || loc == 1 )
+	{
+		/* Intersection is on the boundary */
+		return 1;
+	}
+
+	/* There is a real intersection, let's get two substrings */
+
+	/* Compute vertex snap tolerance based on line length
+	 * TODO: take as parameter ? */
+	vstol = ptarray_length_2d(ctx, rtline_in->points) / 1e14;
+
+	pa1 = ptarray_substring(ctx, rtline_in->points, 0, loc, vstol);
+	pa2 = ptarray_substring(ctx, rtline_in->points, loc, 1, vstol);
+
+	/* NOTE: I've seen empty pointarrays with loc != 0 and loc != 1 */
+	if ( pa1->npoints == 0 || pa2->npoints == 0 ) {
+		ptarray_free(ctx, pa1);
+		ptarray_free(ctx, pa2);
+		/* Intersection is on the boundary */
+		return 1;
+	}
+
+	rtmline_add_rtline(ctx, v, rtline_construct(ctx, SRID_UNKNOWN, NULL, pa1));
+	rtmline_add_rtline(ctx, v, rtline_construct(ctx, SRID_UNKNOWN, NULL, pa2));
+	return 2;
+}
+
+static RTGEOM*
+rtline_split(const RTCTX *ctx, const RTLINE* rtline_in, const RTGEOM* blade_in)
+{
+	switch (blade_in->type)
+	{
+	case RTPOINTTYPE:
+		return rtline_split_by_point(ctx, rtline_in, (RTPOINT*)blade_in);
+	case RTMULTIPOINTTYPE:
+		return rtline_split_by_mpoint(ctx, rtline_in, (RTMPOINT*)blade_in);
+
+	case RTLINETYPE:
+	case RTMULTILINETYPE:
+	case RTPOLYGONTYPE:
+	case RTMULTIPOLYGONTYPE:
+		return rtline_split_by_line(ctx, rtline_in, blade_in);
+
+	default:
+		rterror(ctx, "Splitting a Line by a %s is unsupported",
+		        rttype_name(ctx, blade_in->type));
+		return NULL;
+	}
+	return NULL;
+}
+
+/* Initializes and uses GEOS internally */
+static RTGEOM*
+rtpoly_split_by_line(const RTCTX *ctx, const RTPOLY* rtpoly_in, const RTLINE* blade_in)
+{
+	RTCOLLECTION* out;
+	GEOSGeometry* g1;
+	GEOSGeometry* g2;
+	GEOSGeometry* g1_bounds;
+	GEOSGeometry* polygons;
+	const GEOSGeometry *vgeoms[1];
+	int i,n;
+	int hasZ = RTFLAGS_GET_Z(rtpoly_in->flags);
+
+
+	/* Possible outcomes:
+	 *
+	 *  1. The line does not split the polygon
+	 *      -> Return a collection with single element
+	 *  2. The line does split the polygon
+	 *      -> Return a collection of all elements resulting from the split
+	 */
+
+	rtgeom_geos_ensure_init(ctx);
+
+	g1 = RTGEOM2GEOS(ctx, (RTGEOM*)rtpoly_in, 0);
+	if ( NULL == g1 )
+	{
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+	g1_bounds = GEOSBoundary_r(ctx->gctx, g1);
+	if ( NULL == g1_bounds )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		rterror(ctx, "GEOSBoundary: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	g2 = RTGEOM2GEOS(ctx, (RTGEOM*)blade_in, 0);
+	if ( NULL == g2 )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+		rterror(ctx, "RTGEOM2GEOS: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	vgeoms[0] = GEOSUnion_r(ctx->gctx, g1_bounds, g2);
+	if ( NULL == vgeoms[0] )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+		rterror(ctx, "GEOSUnion: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+	/* debugging..
+		rtnotice(ctx, "Bounds poly: %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, g1_bounds, hasZ)));
+		rtnotice(ctx, "Line: %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, g2, hasZ)));
+
+		rtnotice(ctx, "Noded bounds: %s",
+		               rtgeom_to_ewkt(ctx, GEOS2RTGEOM(ctx, vgeoms[0], hasZ)));
+	*/
+
+	polygons = GEOSPolygonize_r(ctx->gctx, vgeoms, 1);
+	if ( NULL == polygons )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+		GEOSGeom_destroy_r(ctx->gctx, (GEOSGeometry*)vgeoms[0]);
+		rterror(ctx, "GEOSPolygonize: %s", rtgeom_get_last_geos_error(ctx));
+		return NULL;
+	}
+
+#if PARANOIA_LEVEL > 0
+	if ( GEOSGeometryTypeId_r(ctx->gctx, polygons) != RTCOLLECTIONTYPE )
+	{
+		GEOSGeom_destroy_r(ctx->gctx, g1);
+		GEOSGeom_destroy_r(ctx->gctx, g2);
+		GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+		GEOSGeom_destroy_r(ctx->gctx, (GEOSGeometry*)vgeoms[0]);
+		GEOSGeom_destroy_r(ctx->gctx, polygons);
+		rterror(ctx, "Unexpected return from GEOSpolygonize");
+		return 0;
+	}
+#endif
+
+	/* We should now have all polygons, just skip
+	 * the ones which are in holes of the original
+	 * geometries and return the rest in a collection
+	 */
+	n = GEOSGetNumGeometries_r(ctx->gctx, polygons);
+	out = rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, rtpoly_in->srid,
+				     hasZ, 0);
+	/* Allocate space for all polys */
+	out->geoms = rtrealloc(ctx, out->geoms, sizeof(RTGEOM*)*n);
+	assert(0 == out->ngeoms);
+	for (i=0; i<n; ++i)
+	{
+		GEOSGeometry* pos; /* point on surface */
+		const GEOSGeometry* p = GEOSGetGeometryN_r(ctx->gctx, polygons, i);
+		int contains;
+
+		pos = GEOSPointOnSurface_r(ctx->gctx, p);
+		if ( ! pos )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, g1);
+			GEOSGeom_destroy_r(ctx->gctx, g2);
+			GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+			GEOSGeom_destroy_r(ctx->gctx, (GEOSGeometry*)vgeoms[0]);
+			GEOSGeom_destroy_r(ctx->gctx, polygons);
+			rterror(ctx, "GEOSPointOnSurface: %s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		contains = GEOSContains_r(ctx->gctx, g1, pos);
+		if ( 2 == contains )
+		{
+			GEOSGeom_destroy_r(ctx->gctx, g1);
+			GEOSGeom_destroy_r(ctx->gctx, g2);
+			GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+			GEOSGeom_destroy_r(ctx->gctx, (GEOSGeometry*)vgeoms[0]);
+			GEOSGeom_destroy_r(ctx->gctx, polygons);
+			GEOSGeom_destroy_r(ctx->gctx, pos);
+			rterror(ctx, "GEOSContains: %s", rtgeom_get_last_geos_error(ctx));
+			return NULL;
+		}
+
+		GEOSGeom_destroy_r(ctx->gctx, pos);
+
+		if ( 0 == contains )
+		{
+			/* Original geometry doesn't contain
+			 * a point in this ring, must be an hole
+			 */
+			continue;
+		}
+
+		out->geoms[out->ngeoms++] = GEOS2RTGEOM(ctx, p, hasZ);
+	}
+
+	GEOSGeom_destroy_r(ctx->gctx, g1);
+	GEOSGeom_destroy_r(ctx->gctx, g2);
+	GEOSGeom_destroy_r(ctx->gctx, g1_bounds);
+	GEOSGeom_destroy_r(ctx->gctx, (GEOSGeometry*)vgeoms[0]);
+	GEOSGeom_destroy_r(ctx->gctx, polygons);
+
+	return (RTGEOM*)out;
+}
+
+static RTGEOM*
+rtcollection_split(const RTCTX *ctx, const RTCOLLECTION* rtcoll_in, const RTGEOM* blade_in)
+{
+	RTGEOM** split_vector=NULL;
+	RTCOLLECTION* out;
+	size_t split_vector_capacity;
+	size_t split_vector_size=0;
+	size_t i,j;
+
+	split_vector_capacity=8;
+	split_vector = rtalloc(ctx, split_vector_capacity * sizeof(RTGEOM*));
+	if ( ! split_vector )
+	{
+		rterror(ctx, "Out of virtual memory");
+		return NULL;
+	}
+
+	for (i=0; i<rtcoll_in->ngeoms; ++i)
+	{
+		RTCOLLECTION* col;
+		RTGEOM* split = rtgeom_split(ctx, rtcoll_in->geoms[i], blade_in);
+		/* an exception should prevent this from ever returning NULL */
+		if ( ! split ) return NULL;
+
+		col = rtgeom_as_rtcollection(ctx, split);
+		/* Output, if any, will artays be a collection */
+		assert(col);
+
+		/* Reallocate split_vector if needed */
+		if ( split_vector_size + col->ngeoms > split_vector_capacity )
+		{
+			/* NOTE: we could be smarter on reallocations here */
+			split_vector_capacity += col->ngeoms;
+			split_vector = rtrealloc(ctx, split_vector,
+			                         split_vector_capacity * sizeof(RTGEOM*));
+			if ( ! split_vector )
+			{
+				rterror(ctx, "Out of virtual memory");
+				return NULL;
+			}
+		}
+
+		for (j=0; j<col->ngeoms; ++j)
+		{
+			col->geoms[j]->srid = SRID_UNKNOWN; /* strip srid */
+			split_vector[split_vector_size++] = col->geoms[j];
+		}
+		rtfree(ctx, col->geoms);
+		rtfree(ctx, col);
+	}
+
+	/* Now split_vector has split_vector_size geometries */
+	out = rtcollection_construct(ctx, RTCOLLECTIONTYPE, rtcoll_in->srid,
+	                             NULL, split_vector_size, split_vector);
+
+	return (RTGEOM*)out;
+}
+
+static RTGEOM*
+rtpoly_split(const RTCTX *ctx, const RTPOLY* rtpoly_in, const RTGEOM* blade_in)
+{
+	switch (blade_in->type)
+	{
+	case RTLINETYPE:
+		return rtpoly_split_by_line(ctx, rtpoly_in, (RTLINE*)blade_in);
+	default:
+		rterror(ctx, "Splitting a Polygon by a %s is unsupported",
+		        rttype_name(ctx, blade_in->type));
+		return NULL;
+	}
+	return NULL;
+}
+
+/* exported */
+RTGEOM*
+rtgeom_split(const RTCTX *ctx, const RTGEOM* rtgeom_in, const RTGEOM* blade_in)
+{
+	switch (rtgeom_in->type)
+	{
+	case RTLINETYPE:
+		return rtline_split(ctx, (const RTLINE*)rtgeom_in, blade_in);
+
+	case RTPOLYGONTYPE:
+		return rtpoly_split(ctx, (const RTPOLY*)rtgeom_in, blade_in);
+
+	case RTMULTIPOLYGONTYPE:
+	case RTMULTILINETYPE:
+	case RTCOLLECTIONTYPE:
+		return rtcollection_split(ctx, (const RTCOLLECTION*)rtgeom_in, blade_in);
+
+	default:
+		rterror(ctx, "Splitting of %s geometries is unsupported",
+		        rttype_name(ctx, rtgeom_in->type));
+		return NULL;
+	}
+
+}
+
diff --git a/src/rtgeom_log.h b/src/rtgeom_log.h
new file mode 100644
index 0000000..7c182de
--- /dev/null
+++ b/src/rtgeom_log.h
@@ -0,0 +1,102 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Sandro Santilli <strk at keybit.net>
+ * Copyright 2008 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright 2007-2008 Mark Cave-Ayland
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#ifndef RTGEOM_LOG_H
+#define RTGEOM_LOG_H 1
+
+#include "librtgeom_internal.h"
+#include <stdarg.h>
+
+/*
+ * Debug macros
+ */
+#if RTGEOM_DEBUG_LEVEL > 0
+
+/* Display a notice at the given debug level */
+#define RTDEBUG(level, msg) \
+        do { \
+            if (RTGEOM_DEBUG_LEVEL >= level) \
+              rtdebug(const RTCTX *ctx, level, "[%s:%s:%d] " msg, __FILE__, __func__, __LINE__); \
+        } while (0);
+
+/* Display a formatted notice at the given debug level
+ * (like printf, with variadic arguments) */
+#define RTDEBUGF(level, msg, ...) \
+        do { \
+            if (RTGEOM_DEBUG_LEVEL >= level) \
+              rtdebug(const RTCTX *ctx, level, "[%s:%s:%d] " msg, \
+                __FILE__, __func__, __LINE__, __VA_ARGS__); \
+        } while (0);
+
+#else /* RTGEOM_DEBUG_LEVEL <= 0 */
+
+/* Empty prototype that can be optimised away by the compiler
+ * for non-debug builds */
+#define RTDEBUG(level, msg) \
+        ((void) 0)
+
+/* Empty prototype that can be optimised away by the compiler
+ * for non-debug builds */
+#define RTDEBUGF(level, msg, ...) \
+        ((void) 0)
+
+#endif /* RTGEOM_DEBUG_LEVEL <= 0 */
+
+/**
+ * Write a notice out to the notice handler.
+ *
+ * Uses standard printf() substitutions.
+ * Use for messages you artays want output.
+ * For debugging, use RTDEBUG() or RTDEBUGF().
+ * @ingroup logging
+ */
+void rtnotice(const RTCTX *ctx, const char *fmt, ...);
+
+/**
+ * Write a notice out to the error handler.
+ *
+ * Uses standard printf() substitutions.
+ * Use for errors you artays want output.
+ * For debugging, use RTDEBUG() or RTDEBUGF().
+ * @ingroup logging
+ */
+void rterror(const RTCTX *ctx, const char *fmt, ...);
+
+/**
+ * Write a debug message out. 
+ * Don't call this function directly, use the 
+ * macros, RTDEBUG() or RTDEBUGF(), for
+ * efficiency.
+ * @ingroup logging
+ */
+void rtdebug(const RTCTX *ctx, int level, const char *fmt, ...);
+
+
+
+#endif /* RTGEOM_LOG_H */
diff --git a/src/rtgeom_topo.c b/src/rtgeom_topo.c
new file mode 100644
index 0000000..d9f2b08
--- /dev/null
+++ b/src/rtgeom_topo.c
@@ -0,0 +1,5905 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk at keybit.net>
+ *
+ **********************************************************************/
+
+
+
+
+#include "rttopo_config.h"
+
+/*#define RTGEOM_DEBUG_LEVEL 1*/
+#include "rtgeom_log.h"
+
+#include "librtgeom_internal.h"
+#include "librtgeom_topo_internal.h"
+#include "rtgeom_geos.h"
+
+#include <stdio.h>
+#include <inttypes.h> /* for PRId64 */
+#include <errno.h>
+#include <math.h>
+
+#ifdef WIN32
+# define RTTFMT_ELEMID "lld"
+#else
+# define RTTFMT_ELEMID PRId64
+#endif
+
+/* TODO: move this to rtgeom_log.h */
+#define RTDEBUGG(level, geom, msg) \
+  if (RTGEOM_DEBUG_LEVEL >= level) \
+  do { \
+    size_t sz; \
+    char *wkt1 = rtgeom_to_wkt(iface->ctx, geom, RTWKT_EXTENDED, 15, &sz); \
+    /* char *wkt1 = rtgeom_to_hexwkb(iface->ctx, geom, RTWKT_EXTENDED, &sz); */ \
+    RTDEBUGF(level, msg ": %s", wkt1); \
+    rtfree(iface->ctx, wkt1); \
+  } while (0);
+
+
+/*********************************************************************
+ *
+ * Backend iface
+ *
+ ********************************************************************/
+
+RTT_BE_IFACE* rtt_CreateBackendIface(const RTCTX *ctx, const RTT_BE_DATA *data)
+{
+  RTT_BE_IFACE *iface = rtalloc(ctx, sizeof(RTT_BE_IFACE));
+  iface->data = data;
+  iface->cb = NULL;
+  iface->ctx = ctx;
+  return iface;
+}
+
+void rtt_BackendIfaceRegisterCallbacks(RTT_BE_IFACE *iface,
+                                       const RTT_BE_CALLBACKS* cb)
+{
+  iface->cb = cb;
+}
+
+void rtt_FreeBackendIface(RTT_BE_IFACE* iface)
+{
+  rtfree(iface->ctx, iface);
+}
+
+static void _rtt_EnsureGeos(const RTCTX *ctx)
+{
+  rtgeom_geos_ensure_init(ctx);
+}
+
+/*********************************************************************
+ *
+ * Backend wrappers
+ *
+ ********************************************************************/
+
+#define CHECKCB(be, method) do { \
+  if ( ! (be)->cb || ! (be)->cb->method ) \
+  rterror((be)->ctx, "Callback " # method " not registered by backend"); \
+} while (0)
+
+#define CB0(be, method) \
+  CHECKCB(be, method);\
+  return (be)->cb->method((be)->data)
+
+#define CB1(be, method, a1) \
+  CHECKCB(be, method);\
+  return (be)->cb->method((be)->data, a1)
+
+#define CBT0(to, method) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo)
+
+#define CBT1(to, method, a1) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1)
+
+#define CBT2(to, method, a1, a2) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2)
+
+#define CBT3(to, method, a1, a2, a3) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3)
+
+#define CBT4(to, method, a1, a2, a3, a4) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4)
+
+#define CBT5(to, method, a1, a2, a3, a4, a5) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5)
+
+#define CBT6(to, method, a1, a2, a3, a4, a5, a6) \
+  CHECKCB((to)->be_iface, method);\
+  return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5, a6)
+
+const char *
+rtt_be_lastErrorMessage(const RTT_BE_IFACE* be)
+{
+  CB0(be, lastErrorMessage);
+}
+
+RTT_BE_TOPOLOGY *
+rtt_be_loadTopologyByName(RTT_BE_IFACE *be, const char *name)
+{
+  CB1(be, loadTopologyByName, name);
+}
+
+static int
+rtt_be_topoGetSRID(RTT_TOPOLOGY *topo)
+{
+  CBT0(topo, topoGetSRID);
+}
+
+static double
+rtt_be_topoGetPrecision(RTT_TOPOLOGY *topo)
+{
+  CBT0(topo, topoGetPrecision);
+}
+
+static int
+rtt_be_topoHasZ(RTT_TOPOLOGY *topo)
+{
+  CBT0(topo, topoHasZ);
+}
+
+int
+rtt_be_freeTopology(RTT_TOPOLOGY *topo)
+{
+  CBT0(topo, freeTopology);
+}
+
+RTT_ISO_NODE*
+rtt_be_getNodeById(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getNodeById, ids, numelems, fields);
+}
+
+RTT_ISO_NODE*
+rtt_be_getNodeWithinDistance2D(RTT_TOPOLOGY* topo, RTPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit)
+{
+  CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit);
+}
+
+static RTT_ISO_NODE*
+rtt_be_getNodeWithinBox2D( const RTT_TOPOLOGY* topo,
+                           const RTGBOX* box, int* numelems, int fields,
+                           int limit )
+{
+  CBT4(topo, getNodeWithinBox2D, box, numelems, fields, limit);
+}
+
+static RTT_ISO_EDGE*
+rtt_be_getEdgeWithinBox2D( const RTT_TOPOLOGY* topo,
+                           const RTGBOX* box, int* numelems, int fields,
+                           int limit )
+{
+  CBT4(topo, getEdgeWithinBox2D, box, numelems, fields, limit);
+}
+
+static RTT_ISO_FACE*
+rtt_be_getFaceWithinBox2D( const RTT_TOPOLOGY* topo,
+                           const RTGBOX* box, int* numelems, int fields,
+                           int limit )
+{
+  CBT4(topo, getFaceWithinBox2D, box, numelems, fields, limit);
+}
+
+int
+rtt_be_insertNodes(RTT_TOPOLOGY* topo, RTT_ISO_NODE* node, int numelems)
+{
+  CBT2(topo, insertNodes, node, numelems);
+}
+
+static int
+rtt_be_insertFaces(RTT_TOPOLOGY* topo, RTT_ISO_FACE* face, int numelems)
+{
+  CBT2(topo, insertFaces, face, numelems);
+}
+
+static int
+rtt_be_deleteFacesById(const RTT_TOPOLOGY* topo, const RTT_ELEMID* ids, int numelems)
+{
+  CBT2(topo, deleteFacesById, ids, numelems);
+}
+
+static int
+rtt_be_deleteNodesById(const RTT_TOPOLOGY* topo, const RTT_ELEMID* ids, int numelems)
+{
+  CBT2(topo, deleteNodesById, ids, numelems);
+}
+
+RTT_ELEMID
+rtt_be_getNextEdgeId(RTT_TOPOLOGY* topo)
+{
+  CBT0(topo, getNextEdgeId);
+}
+
+RTT_ISO_EDGE*
+rtt_be_getEdgeById(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getEdgeById, ids, numelems, fields);
+}
+
+static RTT_ISO_FACE*
+rtt_be_getFaceById(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getFaceById, ids, numelems, fields);
+}
+
+static RTT_ISO_EDGE*
+rtt_be_getEdgeByNode(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields)
+{
+  CBT3(topo, getEdgeByNode, ids, numelems, fields);
+}
+
+static RTT_ISO_EDGE*
+rtt_be_getEdgeByFace(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields, const RTGBOX *box)
+{
+  CBT4(topo, getEdgeByFace, ids, numelems, fields, box);
+}
+
+static RTT_ISO_NODE*
+rtt_be_getNodeByFace(RTT_TOPOLOGY* topo, const RTT_ELEMID* ids,
+                   int* numelems, int fields, const RTGBOX *box)
+{
+  CBT4(topo, getNodeByFace, ids, numelems, fields, box);
+}
+
+RTT_ISO_EDGE*
+rtt_be_getEdgeWithinDistance2D(RTT_TOPOLOGY* topo, RTPOINT* pt,
+                               double dist, int* numelems, int fields,
+                               int limit)
+{
+  CBT5(topo, getEdgeWithinDistance2D, pt, dist, numelems, fields, limit);
+}
+
+int
+rtt_be_insertEdges(RTT_TOPOLOGY* topo, RTT_ISO_EDGE* edge, int numelems)
+{
+  CBT2(topo, insertEdges, edge, numelems);
+}
+
+int
+rtt_be_updateEdges(RTT_TOPOLOGY* topo,
+  const RTT_ISO_EDGE* sel_edge, int sel_fields,
+  const RTT_ISO_EDGE* upd_edge, int upd_fields,
+  const RTT_ISO_EDGE* exc_edge, int exc_fields
+)
+{
+  CBT6(topo, updateEdges, sel_edge, sel_fields,
+                          upd_edge, upd_fields,
+                          exc_edge, exc_fields);
+}
+
+static int
+rtt_be_updateNodes(RTT_TOPOLOGY* topo,
+  const RTT_ISO_NODE* sel_node, int sel_fields,
+  const RTT_ISO_NODE* upd_node, int upd_fields,
+  const RTT_ISO_NODE* exc_node, int exc_fields
+)
+{
+  CBT6(topo, updateNodes, sel_node, sel_fields,
+                          upd_node, upd_fields,
+                          exc_node, exc_fields);
+}
+
+static int
+rtt_be_updateFacesById(RTT_TOPOLOGY* topo,
+  const RTT_ISO_FACE* faces, int numfaces
+)
+{
+  CBT2(topo, updateFacesById, faces, numfaces);
+}
+
+static int
+rtt_be_updateEdgesById(RTT_TOPOLOGY* topo,
+  const RTT_ISO_EDGE* edges, int numedges, int upd_fields
+)
+{
+  CBT3(topo, updateEdgesById, edges, numedges, upd_fields);
+}
+
+static int
+rtt_be_updateNodesById(RTT_TOPOLOGY* topo,
+  const RTT_ISO_NODE* nodes, int numnodes, int upd_fields
+)
+{
+  CBT3(topo, updateNodesById, nodes, numnodes, upd_fields);
+}
+
+int
+rtt_be_deleteEdges(RTT_TOPOLOGY* topo,
+  const RTT_ISO_EDGE* sel_edge, int sel_fields
+)
+{
+  CBT2(topo, deleteEdges, sel_edge, sel_fields);
+}
+
+RTT_ELEMID
+rtt_be_getFaceContainingPoint(RTT_TOPOLOGY* topo, RTPOINT* pt)
+{
+  CBT1(topo, getFaceContainingPoint, pt);
+}
+
+
+int
+rtt_be_updateTopoGeomEdgeSplit(RTT_TOPOLOGY* topo, RTT_ELEMID split_edge, RTT_ELEMID new_edge1, RTT_ELEMID new_edge2)
+{
+  CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2);
+}
+
+static int
+rtt_be_updateTopoGeomFaceSplit(RTT_TOPOLOGY* topo, RTT_ELEMID split_face,
+                               RTT_ELEMID new_face1, RTT_ELEMID new_face2)
+{
+  CBT3(topo, updateTopoGeomFaceSplit, split_face, new_face1, new_face2);
+}
+
+static int
+rtt_be_checkTopoGeomRemEdge(RTT_TOPOLOGY* topo, RTT_ELEMID edge_id,
+                            RTT_ELEMID face_left, RTT_ELEMID face_right)
+{
+  CBT3(topo, checkTopoGeomRemEdge, edge_id, face_left, face_right);
+}
+
+static int
+rtt_be_checkTopoGeomRemNode(RTT_TOPOLOGY* topo, RTT_ELEMID node_id,
+                            RTT_ELEMID eid1, RTT_ELEMID eid2)
+{
+  CBT3(topo, checkTopoGeomRemNode, node_id, eid1, eid2);
+}
+
+static int
+rtt_be_updateTopoGeomFaceHeal(RTT_TOPOLOGY* topo,
+                             RTT_ELEMID face1, RTT_ELEMID face2,
+                             RTT_ELEMID newface)
+{
+  CBT3(topo, updateTopoGeomFaceHeal, face1, face2, newface);
+}
+
+static int
+rtt_be_updateTopoGeomEdgeHeal(RTT_TOPOLOGY* topo,
+                             RTT_ELEMID edge1, RTT_ELEMID edge2,
+                             RTT_ELEMID newedge)
+{
+  CBT3(topo, updateTopoGeomEdgeHeal, edge1, edge2, newedge);
+}
+
+static RTT_ELEMID*
+rtt_be_getRingEdges( RTT_TOPOLOGY* topo,
+                     RTT_ELEMID edge, int *numedges, int limit )
+{
+  CBT3(topo, getRingEdges, edge, numedges, limit);
+}
+
+
+/* wrappers of backend wrappers... */
+
+int
+rtt_be_ExistsCoincidentNode(RTT_TOPOLOGY* topo, RTPOINT* pt)
+{
+  int exists = 0;
+  rtt_be_getNodeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
+  if ( exists == -1 ) {
+    rterror(topo->be_iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return 0;
+  }
+  return exists;
+}
+
+int
+rtt_be_ExistsEdgeIntersectingPoint(RTT_TOPOLOGY* topo, RTPOINT* pt)
+{
+  int exists = 0;
+  rtt_be_getEdgeWithinDistance2D(topo, pt, 0, &exists, 0, -1);
+  if ( exists == -1 ) {
+    rterror(topo->be_iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return 0;
+  }
+  return exists;
+}
+
+/************************************************************************
+ *
+ * Utility functions
+ *
+ ************************************************************************/
+
+static void
+_rtt_release_faces(const RTCTX *ctx, RTT_ISO_FACE *faces, int num_faces)
+{
+  int i;
+  for ( i=0; i<num_faces; ++i ) {
+    if ( faces[i].mbr ) rtfree(ctx, faces[i].mbr);
+  }
+  rtfree(ctx, faces);
+}
+
+static void
+_rtt_release_edges(const RTCTX *ctx, RTT_ISO_EDGE *edges, int num_edges)
+{
+  int i;
+  for ( i=0; i<num_edges; ++i ) {
+    if ( edges[i].geom ) rtline_free(ctx, edges[i].geom);
+  }
+  rtfree(ctx, edges);
+}
+
+static void
+_rtt_release_nodes(const RTCTX *ctx, RTT_ISO_NODE *nodes, int num_nodes)
+{
+  int i;
+  for ( i=0; i<num_nodes; ++i ) {
+    if ( nodes[i].geom ) rtpoint_free(ctx, nodes[i].geom);
+  }
+  rtfree(ctx, nodes);
+}
+
+/************************************************************************
+ *
+ * API implementation
+ *
+ ************************************************************************/
+
+RTT_TOPOLOGY *
+rtt_LoadTopology( RTT_BE_IFACE *iface, const char *name )
+{
+  RTT_BE_TOPOLOGY* be_topo;
+  RTT_TOPOLOGY* topo;
+
+  be_topo = rtt_be_loadTopologyByName(iface, name);
+  if ( ! be_topo ) {
+    //rterror(iface->ctx, "Could not load topology from backend: %s",
+    rterror(iface->ctx, "%s", rtt_be_lastErrorMessage(iface));
+    return NULL;
+  }
+  topo = rtalloc(iface->ctx, sizeof(RTT_TOPOLOGY));
+  topo->be_iface = iface;
+  topo->be_topo = be_topo;
+  topo->srid = rtt_be_topoGetSRID(topo);
+  topo->hasZ = rtt_be_topoHasZ(topo);
+  topo->precision = rtt_be_topoGetPrecision(topo);
+
+  return topo;
+}
+
+void
+rtt_FreeTopology( RTT_TOPOLOGY* topo )
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  if ( ! rtt_be_freeTopology(topo) ) {
+    rtnotice(topo->be_iface->ctx, "Could not release backend topology memory: %s",
+            rtt_be_lastErrorMessage(topo->be_iface));
+  }
+  rtfree(iface->ctx, topo);
+}
+
+RTT_ELEMID
+rtt_AddIsoNode( RTT_TOPOLOGY* topo, RTT_ELEMID face,
+                RTPOINT* pt, int skipISOChecks )
+{
+  RTT_ELEMID foundInFace = -1;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  if ( ! skipISOChecks )
+  {
+    if ( rtt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - coincident node");
+      return -1;
+    }
+    if ( rtt_be_ExistsEdgeIntersectingPoint(topo, pt) ) /*x*/
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - edge crosses node.");
+      return -1;
+    }
+  }
+
+  if ( face == -1 || ! skipISOChecks )
+  {
+    foundInFace = rtt_be_getFaceContainingPoint(topo, pt); /*x*/
+    if ( foundInFace == -2 ) {
+      rterror(topo->be_iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    if ( foundInFace == -1 ) foundInFace = 0;
+  }
+
+  if ( face == -1 ) {
+    face = foundInFace;
+  }
+  else if ( ! skipISOChecks && foundInFace != face ) {
+#if 0
+    rterror(iface->ctx, "SQL/MM Spatial exception - within face %d (not %d)",
+            foundInFace, face);
+#else
+    rterror(topo->be_iface->ctx, "SQL/MM Spatial exception - not within face");
+#endif
+    return -1;
+  }
+
+  RTT_ISO_NODE node;
+  node.node_id = -1;
+  node.containing_face = face;
+  node.geom = pt;
+  if ( ! rtt_be_insertNodes(topo, &node, 1) )
+  {
+    rterror(topo->be_iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  return node.node_id;
+}
+
+/* Check that an edge does not cross an existing node or edge
+ *
+ * @param myself the id of an edge to skip, if any
+ *               (for ChangeEdgeGeom). Can use 0 for none.
+ *
+ * Return -1 on cross or error, 0 if everything is fine.
+ * Note that before returning -1, rterror is invoked...
+ */
+static int
+_rtt_CheckEdgeCrossing( RTT_TOPOLOGY* topo,
+                        RTT_ELEMID start_node, RTT_ELEMID end_node,
+                        const RTLINE *geom, RTT_ELEMID myself )
+{
+  int i, num_nodes, num_edges;
+  RTT_ISO_EDGE *edges;
+  RTT_ISO_NODE *nodes;
+  const RTGBOX *edgebox;
+  GEOSGeometry *edgegg;
+  const GEOSPreparedGeometry* prepared_edge;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  _rtt_EnsureGeos(iface->ctx);
+
+  edgegg = RTGEOM2GEOS(iface->ctx,  rtline_as_rtgeom(iface->ctx, geom), 0);
+  if ( ! edgegg ) {
+    rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+    return -1;
+  }
+  prepared_edge = GEOSPrepare_r(iface->ctx->gctx,  edgegg );
+  if ( ! prepared_edge ) {
+    rterror(iface->ctx, "Could not prepare edge geometry: %s", rtgeom_get_last_geos_error(iface->ctx));
+    return -1;
+  }
+  edgebox = rtgeom_get_bbox(iface->ctx,  rtline_as_rtgeom(iface->ctx, geom) );
+
+  /* loop over each node within the edge's gbox */
+  nodes = rtt_be_getNodeWithinBox2D( topo, edgebox, &num_nodes,
+                                            RTT_COL_NODE_ALL, 0 );
+  RTDEBUGF(1, "rtt_be_getNodeWithinBox2D returned %d nodes", num_nodes);
+  if ( num_nodes == -1 ) {
+    GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+    GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<num_nodes; ++i )
+  {
+    RTT_ISO_NODE* node = &(nodes[i]);
+    GEOSGeometry *nodegg;
+    int contains;
+    if ( node->node_id == start_node ) continue;
+    if ( node->node_id == end_node ) continue;
+    /* check if the edge contains this node (not on boundary) */
+    nodegg = RTGEOM2GEOS(iface->ctx,  rtpoint_as_rtgeom(iface->ctx, node->geom) , 0);
+    /* ST_RelateMatch(rec.relate, 'T********') */
+    contains = GEOSPreparedContains_r(iface->ctx->gctx,  prepared_edge, nodegg );
+    GEOSGeom_destroy_r(iface->ctx->gctx, nodegg);
+    if (contains == 2)
+    {
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      _rtt_release_nodes(iface->ctx, nodes, num_nodes);
+      rterror(iface->ctx, "GEOS exception on PreparedContains: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+    if ( contains )
+    {
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      _rtt_release_nodes(iface->ctx, nodes, num_nodes);
+      rterror(iface->ctx, "SQL/MM Spatial exception - geometry crosses a node");
+      return -1;
+    }
+  }
+  if ( nodes ) _rtt_release_nodes(iface->ctx, nodes, num_nodes);
+               /* may be NULL if num_nodes == 0 */
+
+  /* loop over each edge within the edge's gbox */
+  edges = rtt_be_getEdgeWithinBox2D( topo, edgebox, &num_edges, RTT_COL_EDGE_ALL, 0 );
+  RTDEBUGF(1, "rtt_be_getEdgeWithinBox2D returned %d edges", num_edges);
+  if ( num_edges == -1 ) {
+    GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+    GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<num_edges; ++i )
+  {
+    RTT_ISO_EDGE* edge = &(edges[i]);
+    RTT_ELEMID edge_id = edge->edge_id;
+    GEOSGeometry *eegg;
+    char *relate;
+    int match;
+
+    if ( edge_id == myself ) continue;
+
+    if ( ! edge->geom ) {
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      rterror(iface->ctx, "Edge %d has NULL geometry!", edge_id);
+      return -1;
+    }
+
+    eegg = RTGEOM2GEOS(iface->ctx,  rtline_as_rtgeom(iface->ctx, edge->geom), 0 );
+    if ( ! eegg ) {
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+
+    RTDEBUGF(2, "Edge %d converted to GEOS", edge_id);
+
+    /* check if the edge crosses our edge (not boundary-boundary) */
+
+    relate = GEOSRelateBoundaryNodeRule_r(iface->ctx->gctx, eegg, edgegg, 2);
+    if ( ! relate ) {
+      GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      rterror(iface->ctx, "GEOSRelateBoundaryNodeRule error: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+
+    RTDEBUGF(2, "Edge %d relate pattern is %s", edge_id, relate);
+
+    match = GEOSRelatePatternMatch_r(iface->ctx->gctx, relate, "F********");
+    if ( match ) {
+      /* error or no interior intersection */
+      GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+      GEOSFree_r(iface->ctx->gctx, relate);
+      if ( match == 2 ) {
+        _rtt_release_edges(iface->ctx, edges, num_edges);
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+        GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+        rterror(iface->ctx, "GEOSRelatePatternMatch error: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -1;
+      }
+      else continue; /* no interior intersection */
+    }
+
+    match = GEOSRelatePatternMatch_r(iface->ctx->gctx, relate, "1FFF*FFF2");
+    if ( match ) {
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+      GEOSFree_r(iface->ctx->gctx, relate);
+      if ( match == 2 ) {
+        rterror(iface->ctx, "GEOSRelatePatternMatch error: %s", rtgeom_get_last_geos_error(iface->ctx));
+      } else {
+        rterror(iface->ctx, "SQL/MM Spatial exception - coincident edge %" RTTFMT_ELEMID,
+                edge_id);
+      }
+      return -1;
+    }
+
+    match = GEOSRelatePatternMatch_r(iface->ctx->gctx, relate, "1********");
+    if ( match ) {
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+      GEOSFree_r(iface->ctx->gctx, relate);
+      if ( match == 2 ) {
+        rterror(iface->ctx, "GEOSRelatePatternMatch error: %s", rtgeom_get_last_geos_error(iface->ctx));
+      } else {
+        rterror(iface->ctx, "Spatial exception - geometry intersects edge %"
+                RTTFMT_ELEMID, edge_id);
+      }
+      return -1;
+    }
+
+    match = GEOSRelatePatternMatch_r(iface->ctx->gctx, relate, "T********");
+    if ( match ) {
+      _rtt_release_edges(iface->ctx, edges, num_edges);
+      GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+      GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+      GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+      GEOSFree_r(iface->ctx->gctx, relate);
+      if ( match == 2 ) {
+        rterror(iface->ctx, "GEOSRelatePatternMatch error: %s", rtgeom_get_last_geos_error(iface->ctx));
+      } else {
+        rterror(iface->ctx, "SQL/MM Spatial exception - geometry crosses edge %"
+                RTTFMT_ELEMID, edge_id);
+      }
+      return -1;
+    }
+
+    RTDEBUGF(2, "Edge %d analisys completed, it does no harm", edge_id);
+
+    GEOSFree_r(iface->ctx->gctx, relate);
+    GEOSGeom_destroy_r(iface->ctx->gctx, eegg);
+  }
+  if ( edges ) _rtt_release_edges(iface->ctx, edges, num_edges);
+              /* would be NULL if num_edges was 0 */
+
+  GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepared_edge);
+  GEOSGeom_destroy_r(iface->ctx->gctx, edgegg);
+
+  return 0;
+}
+
+
+RTT_ELEMID
+rtt_AddIsoEdge( RTT_TOPOLOGY* topo, RTT_ELEMID startNode,
+                RTT_ELEMID endNode, const RTLINE* geom )
+{
+  int num_nodes;
+  int i;
+  RTT_ISO_EDGE newedge;
+  RTT_ISO_NODE *endpoints;
+  RTT_ELEMID containing_face = -1;
+  RTT_ELEMID node_ids[2];
+  RTT_ISO_NODE updated_nodes[2];
+  int skipISOChecks = 0;
+  RTPOINT2D p1, p2;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  /* NOT IN THE SPECS:
+   * A closed edge is never isolated (as it forms a face)
+   */
+  if ( startNode == endNode )
+  {
+    rterror(iface->ctx, "Closed edges would not be isolated, try rtt_AddEdgeNewFaces");
+    return -1;
+  }
+
+  if ( ! skipISOChecks )
+  {
+    /* Acurve must be simple */
+    if ( ! rtgeom_is_simple(iface->ctx, rtline_as_rtgeom(iface->ctx, geom)) )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - curve not simple");
+      return -1;
+    }
+  }
+
+  /*
+   * Check for:
+   *    existence of nodes
+   *    nodes faces match
+   * Extract:
+   *    nodes face id
+   *    nodes geoms
+   */
+  num_nodes = 2;
+  node_ids[0] = startNode;
+  node_ids[1] = endNode;
+  endpoints = rtt_be_getNodeById( topo, node_ids, &num_nodes,
+                                             RTT_COL_NODE_ALL );
+  if ( num_nodes < 0 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  else if ( num_nodes < 2 )
+  {
+    if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+    rterror(iface->ctx, "SQL/MM Spatial exception - non-existent node");
+    return -1;
+  }
+  for ( i=0; i<num_nodes; ++i )
+  {
+    const RTT_ISO_NODE *n = &(endpoints[i]);
+    if ( n->containing_face == -1 )
+    {
+      _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+      rterror(iface->ctx, "SQL/MM Spatial exception - not isolated node");
+      return -1;
+    }
+    if ( containing_face == -1 ) containing_face = n->containing_face;
+    else if ( containing_face != n->containing_face )
+    {
+      _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+      rterror(iface->ctx, "SQL/MM Spatial exception - nodes in different faces");
+      return -1;
+    }
+
+    if ( ! skipISOChecks )
+    {
+      if ( n->node_id == startNode )
+      {
+        /* l) Check that start point of acurve match start node geoms. */
+        rt_getPoint2d_p(iface->ctx, geom->points, 0, &p1);
+        rt_getPoint2d_p(iface->ctx, n->geom->point, 0, &p2);
+        if ( ! p2d_same(iface->ctx, &p1, &p2) )
+        {
+          _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+          rterror(iface->ctx, "SQL/MM Spatial exception - "
+                  "start node not geometry start point.");
+          return -1;
+        }
+      }
+      else
+      {
+        /* m) Check that end point of acurve match end node geoms. */
+        rt_getPoint2d_p(iface->ctx, geom->points, geom->points->npoints-1, &p1);
+        rt_getPoint2d_p(iface->ctx, n->geom->point, 0, &p2);
+        if ( ! p2d_same(iface->ctx, &p1, &p2) )
+        {
+          _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+          rterror(iface->ctx, "SQL/MM Spatial exception - "
+                  "end node not geometry end point.");
+          return -1;
+        }
+      }
+    }
+  }
+
+  if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+
+  if ( ! skipISOChecks )
+  {
+    if ( _rtt_CheckEdgeCrossing( topo, startNode, endNode, geom, 0 ) )
+    {
+      /* would have called rterror already, leaking :( */
+      return -1;
+    }
+  }
+
+  /*
+   * All checks passed, time to prepare the new edge
+   */
+
+  newedge.edge_id = rtt_be_getNextEdgeId( topo );
+  if ( newedge.edge_id == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* TODO: this should likely be an exception instead ! */
+  if ( containing_face == -1 ) containing_face = 0;
+
+  newedge.start_node = startNode;
+  newedge.end_node = endNode;
+  newedge.face_left = newedge.face_right = containing_face;
+  newedge.next_left = -newedge.edge_id;
+  newedge.next_right = newedge.edge_id;
+  newedge.geom = (RTLINE *)geom; /* const cast.. */
+
+  int ret = rtt_be_insertEdges(topo, &newedge, 1);
+  if ( ret == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    rterror(iface->ctx, "Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  /*
+   * Update Node containing_face values
+   *
+   * the nodes anode and anothernode are no more isolated
+   * because now there is an edge connecting them
+   */ 
+  updated_nodes[0].node_id = startNode;
+  updated_nodes[0].containing_face = -1;
+  updated_nodes[1].node_id = endNode;
+  updated_nodes[1].containing_face = -1;
+  ret = rtt_be_updateNodesById(topo, updated_nodes, 2,
+                               RTT_COL_NODE_CONTAINING_FACE);
+  if ( ret == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  return newedge.edge_id;
+}
+
+static RTCOLLECTION *
+_rtt_EdgeSplit( RTT_TOPOLOGY* topo, RTT_ELEMID edge, RTPOINT* pt, int skipISOChecks, RTT_ISO_EDGE** oldedge )
+{
+  RTGEOM *split;
+  RTCOLLECTION *split_col;
+  int i;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  /* Get edge */
+  i = 1;
+  RTDEBUG(1, "calling rtt_be_getEdgeById");
+  *oldedge = rtt_be_getEdgeById(topo, &edge, &i, RTT_COL_EDGE_ALL);
+  RTDEBUGF(1, "rtt_be_getEdgeById returned %p", *oldedge);
+  if ( ! *oldedge )
+  {
+    RTDEBUGF(1, "rtt_be_getEdgeById returned NULL and set i=%d", i);
+    if ( i == -1 )
+    {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return NULL;
+    }
+    else if ( i == 0 )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge");
+      return NULL;
+    }
+    else
+    {
+      rterror(iface->ctx, "Backend coding error: getEdgeById callback returned NULL "
+              "but numelements output parameter has value %d "
+              "(expected 0 or 1)", i);
+      return NULL;
+    }
+  }
+
+
+  /*
+   *  - check if a coincident node already exists
+   */
+  if ( ! skipISOChecks )
+  {
+    RTDEBUG(1, "calling rtt_be_ExistsCoincidentNode");
+    if ( rtt_be_ExistsCoincidentNode(topo, pt) ) /*x*/
+    {
+      RTDEBUG(1, "rtt_be_ExistsCoincidentNode returned");
+      _rtt_release_edges(iface->ctx, *oldedge, 1);
+      rterror(iface->ctx, "SQL/MM Spatial exception - coincident node");
+      return NULL;
+    }
+    RTDEBUG(1, "rtt_be_ExistsCoincidentNode returned");
+  }
+
+  /* Split edge */
+  split = rtgeom_split(iface->ctx, (RTGEOM*)(*oldedge)->geom, (RTGEOM*)pt);
+  if ( ! split )
+  {
+    _rtt_release_edges(iface->ctx, *oldedge, 1);
+    rterror(iface->ctx, "could not split edge by point ?");
+    return NULL;
+  }
+  split_col = rtgeom_as_rtcollection(iface->ctx, split);
+  if ( ! split_col ) {
+    _rtt_release_edges(iface->ctx, *oldedge, 1);
+    rtgeom_free(iface->ctx, split);
+    rterror(iface->ctx, "rtgeom_as_rtcollection returned NULL");
+    return NULL;
+  }
+  if (split_col->ngeoms < 2) {
+    _rtt_release_edges(iface->ctx, *oldedge, 1);
+    rtgeom_free(iface->ctx, split);
+    rterror(iface->ctx, "SQL/MM Spatial exception - point not on edge");
+    return NULL;
+  }
+
+#if 0
+  {
+  size_t sz;
+  char *wkt = rtgeom_to_wkt(iface->ctx, (RTGEOM*)split_col, RTWKT_EXTENDED, 2, &sz);
+  RTDEBUGF(1, "returning split col: %s", wkt);
+  rtfree(iface->ctx, wkt);
+  }
+#endif
+  return split_col;
+}
+
+RTT_ELEMID
+rtt_ModEdgeSplit( RTT_TOPOLOGY* topo, RTT_ELEMID edge,
+                  RTPOINT* pt, int skipISOChecks )
+{
+  RTT_ISO_NODE node;
+  RTT_ISO_EDGE* oldedge = NULL;
+  RTCOLLECTION *split_col;
+  const RTGEOM *oldedge_geom;
+  const RTGEOM *newedge_geom;
+  RTT_ISO_EDGE newedge1;
+  RTT_ISO_EDGE seledge, updedge, excedge;
+  int ret;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  split_col = _rtt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
+  if ( ! split_col ) return -1; /* should have raised an exception */
+  oldedge_geom = split_col->geoms[0];
+  newedge_geom = split_col->geoms[1];
+  /* Make sure the SRID is set on the subgeom */
+  ((RTGEOM*)oldedge_geom)->srid = split_col->srid;
+  ((RTGEOM*)newedge_geom)->srid = split_col->srid;
+
+  /* Add new node, getting new id back */
+  node.node_id = -1;
+  node.containing_face = -1; /* means not-isolated */
+  node.geom = pt;
+  if ( ! rtt_be_insertNodes(topo, &node, 1) )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if (node.node_id == -1) {
+    /* should have been set by backend */
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend coding error: "
+            "insertNodes callback did not return node_id");
+    return -1;
+  }
+
+  /* Insert the new edge */
+  newedge1.edge_id = rtt_be_getNextEdgeId(topo);
+  if ( newedge1.edge_id == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  newedge1.start_node = node.node_id;
+  newedge1.end_node = oldedge->end_node;
+  newedge1.face_left = oldedge->face_left;
+  newedge1.face_right = oldedge->face_right;
+  newedge1.next_left = oldedge->next_left == -oldedge->edge_id ?
+      -newedge1.edge_id : oldedge->next_left;
+  newedge1.next_right = -oldedge->edge_id;
+  newedge1.geom = rtgeom_as_rtline(iface->ctx, newedge_geom);
+  /* rtgeom_split of a line should only return lines ... */
+  if ( ! newedge1.geom ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "first geometry in rtgeom_split output is not a line");
+    return -1;
+  }
+  ret = rtt_be_insertEdges(topo, &newedge1, 1);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  /* Update the old edge */
+  updedge.geom = rtgeom_as_rtline(iface->ctx, oldedge_geom);
+  /* rtgeom_split of a line should only return lines ... */
+  if ( ! updedge.geom ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "second geometry in rtgeom_split output is not a line");
+    return -1;
+  }
+  updedge.next_left = newedge1.edge_id;
+  updedge.end_node = node.node_id;
+  ret = rtt_be_updateEdges(topo,
+      oldedge, RTT_COL_EDGE_EDGE_ID,
+      &updedge, RTT_COL_EDGE_GEOM|RTT_COL_EDGE_NEXT_LEFT|RTT_COL_EDGE_END_NODE,
+      NULL, 0);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Edge being split (%d) disappeared during operations?", oldedge->edge_id);
+    return -1;
+  } else if ( ret > 1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "More than a single edge found with id %d !", oldedge->edge_id);
+    return -1;
+  }
+
+  /* Update all next edge references to match new layout (ST_ModEdgeSplit) */
+
+  updedge.next_right = -newedge1.edge_id;
+  excedge.edge_id = newedge1.edge_id;
+  seledge.next_right = -oldedge->edge_id;
+  seledge.start_node = oldedge->end_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_RIGHT|RTT_COL_EDGE_START_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_RIGHT,
+      &excedge, RTT_COL_EDGE_EDGE_ID);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  updedge.next_left = -newedge1.edge_id;
+  excedge.edge_id = newedge1.edge_id;
+  seledge.next_left = -oldedge->edge_id;
+  seledge.end_node = oldedge->end_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_LEFT|RTT_COL_EDGE_END_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_LEFT,
+      &excedge, RTT_COL_EDGE_EDGE_ID);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Update TopoGeometries composition */
+  ret = rtt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedge1.edge_id, -1);
+  if ( ! ret ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  _rtt_release_edges(iface->ctx, oldedge, 1);
+  rtcollection_free(iface->ctx, split_col);
+
+  /* return new node id */
+  return node.node_id;
+}
+
+RTT_ELEMID
+rtt_NewEdgesSplit( RTT_TOPOLOGY* topo, RTT_ELEMID edge,
+                   RTPOINT* pt, int skipISOChecks )
+{
+  RTT_ISO_NODE node;
+  RTT_ISO_EDGE* oldedge = NULL;
+  RTCOLLECTION *split_col;
+  const RTGEOM *oldedge_geom;
+  const RTGEOM *newedge_geom;
+  RTT_ISO_EDGE newedges[2];
+  RTT_ISO_EDGE seledge, updedge;
+  int ret;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  split_col = _rtt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge );
+  if ( ! split_col ) return -1; /* should have raised an exception */
+  oldedge_geom = split_col->geoms[0];
+  newedge_geom = split_col->geoms[1];
+  /* Make sure the SRID is set on the subgeom */
+  ((RTGEOM*)oldedge_geom)->srid = split_col->srid;
+  ((RTGEOM*)newedge_geom)->srid = split_col->srid;
+
+  /* Add new node, getting new id back */
+  node.node_id = -1;
+  node.containing_face = -1; /* means not-isolated */
+  node.geom = pt;
+  if ( ! rtt_be_insertNodes(topo, &node, 1) )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if (node.node_id == -1) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    /* should have been set by backend */
+    rterror(iface->ctx, "Backend coding error: "
+            "insertNodes callback did not return node_id");
+    return -1;
+  }
+
+  /* Delete the old edge */
+  seledge.edge_id = edge;
+  ret = rtt_be_deleteEdges(topo, &seledge, RTT_COL_EDGE_EDGE_ID);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Get new edges identifiers */
+  newedges[0].edge_id = rtt_be_getNextEdgeId(topo);
+  if ( newedges[0].edge_id == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  newedges[1].edge_id = rtt_be_getNextEdgeId(topo);
+  if ( newedges[1].edge_id == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Define the first new edge (to new node) */
+  newedges[0].start_node = oldedge->start_node;
+  newedges[0].end_node = node.node_id;
+  newedges[0].face_left = oldedge->face_left;
+  newedges[0].face_right = oldedge->face_right;
+  newedges[0].next_left = newedges[1].edge_id;
+  if ( oldedge->next_right == edge )
+    newedges[0].next_right = newedges[0].edge_id;
+  else if ( oldedge->next_right == -edge )
+    newedges[0].next_right = -newedges[1].edge_id;
+  else
+    newedges[0].next_right = oldedge->next_right;
+  newedges[0].geom = rtgeom_as_rtline(iface->ctx, oldedge_geom);
+  /* rtgeom_split of a line should only return lines ... */
+  if ( ! newedges[0].geom ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "first geometry in rtgeom_split output is not a line");
+    return -1;
+  }
+
+  /* Define the second new edge (from new node) */
+  newedges[1].start_node = node.node_id;
+  newedges[1].end_node = oldedge->end_node;
+  newedges[1].face_left = oldedge->face_left;
+  newedges[1].face_right = oldedge->face_right;
+  newedges[1].next_right = -newedges[0].edge_id;
+  if ( oldedge->next_left == -edge )
+    newedges[1].next_left = -newedges[1].edge_id;
+  else if ( oldedge->next_left == edge )
+    newedges[1].next_left = newedges[0].edge_id;
+  else
+    newedges[1].next_left = oldedge->next_left;
+  newedges[1].geom = rtgeom_as_rtline(iface->ctx, newedge_geom);
+  /* rtgeom_split of a line should only return lines ... */
+  if ( ! newedges[1].geom ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "second geometry in rtgeom_split output is not a line");
+    return -1;
+  }
+
+  /* Insert both new edges */
+  ret = rtt_be_insertEdges(topo, newedges, 2);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  /* Update all next edge references pointing to old edge id */
+
+  updedge.next_right = newedges[1].edge_id;
+  seledge.next_right = edge;
+  seledge.start_node = oldedge->start_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_RIGHT|RTT_COL_EDGE_START_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_RIGHT,
+      NULL, 0);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  updedge.next_right = -newedges[0].edge_id;
+  seledge.next_right = -edge;
+  seledge.start_node = oldedge->end_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_RIGHT|RTT_COL_EDGE_START_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_RIGHT,
+      NULL, 0);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  updedge.next_left = newedges[0].edge_id;
+  seledge.next_left = edge;
+  seledge.end_node = oldedge->start_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_LEFT|RTT_COL_EDGE_END_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_LEFT,
+      NULL, 0);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  updedge.next_left = -newedges[1].edge_id;
+  seledge.next_left = -edge;
+  seledge.end_node = oldedge->end_node;
+  ret = rtt_be_updateEdges(topo,
+      &seledge, RTT_COL_EDGE_NEXT_LEFT|RTT_COL_EDGE_END_NODE,
+      &updedge, RTT_COL_EDGE_NEXT_LEFT,
+      NULL, 0);
+  if ( ret == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_release(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Update TopoGeometries composition */
+  ret = rtt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedges[0].edge_id, newedges[1].edge_id);
+  if ( ! ret ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rtcollection_free(iface->ctx, split_col);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  _rtt_release_edges(iface->ctx, oldedge, 1);
+  rtcollection_free(iface->ctx, split_col);
+
+  /* return new node id */
+  return node.node_id;
+}
+
+/* Data structure used by AddEdgeX functions */
+typedef struct edgeend_t {
+  /* Signed identifier of next clockwise edge (+outgoing,-incoming) */
+  RTT_ELEMID nextCW;
+  /* Identifier of face between myaz and next CW edge */
+  RTT_ELEMID cwFace;
+  /* Signed identifier of next counterclockwise edge (+outgoing,-incoming) */
+  RTT_ELEMID nextCCW;
+  /* Identifier of face between myaz and next CCW edge */
+  RTT_ELEMID ccwFace;
+  int was_isolated;
+  double myaz; /* azimuth of edgeend geometry */
+} edgeend;
+
+/* 
+ * Get first distinct vertex from endpoint
+ * @param pa the pointarray to seek points in
+ * @param ref the point we want to search a distinct one
+ * @param from vertex index to start from
+ * @param dir  1 to go forward
+ *            -1 to go backward
+ * @return 0 if edge is collapsed (no distinct points)
+ */
+static int
+_rtt_FirstDistinctVertex2D(const RTCTX *ctx, const RTPOINTARRAY* pa, RTPOINT2D *ref, int from, int dir, RTPOINT2D *op)
+{
+  int i, toofar, inc;
+  RTPOINT2D fp;
+
+  if ( dir > 0 )
+  {
+    toofar = pa->npoints;
+    inc = 1;
+  }
+  else
+  {
+    toofar = -1;
+    inc = -1;
+  }
+
+  RTDEBUGF(1, "first point is index %d", from);
+  fp = *ref; /* rt_getPoint2d_p(ctx, pa, from, &fp); */
+  for ( i = from+inc; i != toofar; i += inc )
+  {
+    RTDEBUGF(1, "testing point %d", i);
+    rt_getPoint2d_p(ctx, pa, i, op); /* pick next point */
+    if ( p2d_same(ctx, op, &fp) ) continue; /* equal to startpoint */
+    /* this is a good one, neither same of start nor of end point */
+    return 1; /* found */
+  }
+
+  /* no distinct vertices found */
+  return 0;
+}
+
+
+/*
+ * Return non-zero on failure (rterror is invoked in that case)
+ * Possible failures:
+ *  -1 no two distinct vertices exist
+ *  -2 azimuth computation failed for first edge end
+ */
+static int
+_rtt_InitEdgeEndByLine(const RTCTX *ctx, edgeend *fee, edgeend *lee, RTLINE *edge,
+                                            RTPOINT2D *fp, RTPOINT2D *lp)
+{
+  RTPOINTARRAY *pa = edge->points;
+  RTPOINT2D pt;
+
+  fee->nextCW = fee->nextCCW =
+  lee->nextCW = lee->nextCCW = 0;
+  fee->cwFace = fee->ccwFace =
+  lee->cwFace = lee->ccwFace = -1;
+
+  /* Compute azimuth of first edge end */
+  RTDEBUG(1, "computing azimuth of first edge end");
+  if ( ! _rtt_FirstDistinctVertex2D(ctx, pa, fp, 0, 1, &pt) )
+  {
+    rterror(ctx, "Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+  if ( ! azimuth_pt_pt(ctx, fp, &pt, &(fee->myaz)) ) {
+    rterror(ctx, "error computing azimuth of first edgeend [%g %g,%g %g]",
+            fp->x, fp->y, pt.x, pt.y);
+    return -2;
+  }
+  RTDEBUGF(1, "azimuth of first edge end [%g %g,%g %g] is %g",
+            fp->x, fp->y, pt.x, pt.y, fee->myaz);
+
+  /* Compute azimuth of second edge end */
+  RTDEBUG(1, "computing azimuth of second edge end");
+  if ( ! _rtt_FirstDistinctVertex2D(ctx, pa, lp, pa->npoints-1, -1, &pt) )
+  {
+    rterror(ctx, "Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+  if ( ! azimuth_pt_pt(ctx, lp, &pt, &(lee->myaz)) ) {
+    rterror(ctx, "error computing azimuth of last edgeend [%g %g,%g %g]",
+            lp->x, lp->y, pt.x, pt.y);
+    return -2;
+  }
+  RTDEBUGF(1, "azimuth of last edge end [%g %g,%g %g] is %g",
+            lp->x, lp->y, pt.x, pt.y, lee->myaz);
+
+  return 0;
+}
+
+/*
+ * Find the first edges encountered going clockwise and counterclockwise
+ * around a node, starting from the given azimuth, and take
+ * note of the face on the both sides.
+ *
+ * @param topo the topology to act upon
+ * @param node the identifier of the node to analyze
+ * @param data input (myaz) / output (nextCW, nextCCW) parameter
+ * @param other edgeend, if also incident to given node (closed edge).
+ * @param myedge_id identifier of the edge id that data->myaz belongs to
+ * @return number of incident edges found
+ *
+ */
+static int
+_rtt_FindAdjacentEdges( RTT_TOPOLOGY* topo, RTT_ELEMID node, edgeend *data,
+                        edgeend *other, int myedge_id )
+{
+  RTT_ISO_EDGE *edges;
+  int numedges = 1;
+  int i;
+  double minaz, maxaz;
+  double az, azdif;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  data->nextCW = data->nextCCW = 0;
+  data->cwFace = data->ccwFace = -1;
+
+  if ( other ) {
+    azdif = other->myaz - data->myaz;
+    if ( azdif < 0 ) azdif += 2 * M_PI;
+    minaz = maxaz = azdif;
+    /* TODO: set nextCW/nextCCW/cwFace/ccwFace to other->something ? */
+    RTDEBUGF(1, "Other edge end has cwFace=%d and ccwFace=%d",
+                other->cwFace, other->ccwFace);
+  } else {
+    minaz = maxaz = -1;
+  }
+
+  RTDEBUGF(1, "Looking for edges incident to node %" RTTFMT_ELEMID
+              " and adjacent to azimuth %g", node, data->myaz);
+
+  /* Get incident edges */
+  edges = rtt_be_getEdgeByNode( topo, &node, &numedges, RTT_COL_EDGE_ALL );
+  if ( numedges == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return 0;
+  }
+
+  RTDEBUGF(1, "getEdgeByNode returned %d edges, minaz=%g, maxaz=%g",
+              numedges, minaz, maxaz);
+
+  /* For each incident edge-end (1 or 2): */
+  for ( i = 0; i < numedges; ++i )
+  {
+    RTT_ISO_EDGE *edge;
+    RTGEOM *g;
+    RTGEOM *cleangeom;
+    RTPOINT2D p1, p2;
+    RTPOINTARRAY *pa;
+
+    edge = &(edges[i]);
+
+    if ( edge->edge_id == myedge_id ) continue;
+
+    g = rtline_as_rtgeom(iface->ctx, edge->geom);
+    /* NOTE: remove_repeated_points call could be replaced by
+     * some other mean to pick two distinct points for endpoints */
+    cleangeom = rtgeom_remove_repeated_points(iface->ctx,  g, 0 );
+    pa = rtgeom_as_rtline(iface->ctx, cleangeom)->points;
+
+    if ( pa->npoints < 2 ) {{
+      RTT_ELEMID id = edge->edge_id;
+      _rtt_release_edges(iface->ctx, edges, numedges);
+      rtgeom_free(iface->ctx, cleangeom);
+      rterror(iface->ctx, "corrupted topology: edge %" RTTFMT_ELEMID
+              " does not have two distinct points", id);
+      return -1;
+    }}
+
+    if ( edge->start_node == node ) {
+      rt_getPoint2d_p(iface->ctx, pa, 0, &p1);
+      rt_getPoint2d_p(iface->ctx, pa, 1, &p2);
+      RTDEBUGF(1, "edge %" RTTFMT_ELEMID
+                  " starts on node %" RTTFMT_ELEMID
+                  ", edgeend is %g,%g-%g,%g",
+                  edge->edge_id, node, p1.x, p1.y, p2.x, p2.y);
+      if ( ! azimuth_pt_pt(iface->ctx, &p1, &p2, &az) ) {{
+        RTT_ELEMID id = edge->edge_id;
+        _rtt_release_edges(iface->ctx, edges, numedges);
+        rtgeom_free(iface->ctx, cleangeom);
+        rterror(iface->ctx, "error computing azimuth of edge %d first edgeend [%g,%g-%g,%g]",
+                id, p1.x, p1.y, p2.x, p2.y);
+        return -1;
+      }}
+      azdif = az - data->myaz;
+      RTDEBUGF(1, "azimuth of edge %" RTTFMT_ELEMID
+                  ": %g (diff: %g)", edge->edge_id, az, azdif);
+
+      if ( azdif < 0 ) azdif += 2 * M_PI;
+      if ( minaz == -1 ) {
+        minaz = maxaz = azdif;
+        data->nextCW = data->nextCCW = edge->edge_id; /* outgoing */
+        data->cwFace = edge->face_left;
+        data->ccwFace = edge->face_right;
+        RTDEBUGF(1, "new nextCW and nextCCW edge is %" RTTFMT_ELEMID
+                    ", outgoing, "
+                    "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                    " (face_right is new ccwFace, face_left is new cwFace)",
+                    edge->edge_id, edge->face_left,
+                    edge->face_right);
+      } else {
+        if ( azdif < minaz ) {
+          data->nextCW = edge->edge_id; /* outgoing */
+          data->cwFace = edge->face_left;
+          RTDEBUGF(1, "new nextCW edge is %" RTTFMT_ELEMID
+                      ", outgoing, "
+                      "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                      " (previous had minaz=%g, face_left is new cwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, minaz);
+          minaz = azdif;
+        }
+        else if ( azdif > maxaz ) {
+          data->nextCCW = edge->edge_id; /* outgoing */
+          data->ccwFace = edge->face_right;
+          RTDEBUGF(1, "new nextCCW edge is %" RTTFMT_ELEMID
+                      ", outgoing, "
+                      "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                      " (previous had maxaz=%g, face_right is new ccwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, maxaz);
+          maxaz = azdif;
+        }
+      }
+    }
+
+    if ( edge->end_node == node ) {
+      rt_getPoint2d_p(iface->ctx, pa, pa->npoints-1, &p1);
+      rt_getPoint2d_p(iface->ctx, pa, pa->npoints-2, &p2);
+      RTDEBUGF(1, "edge %" RTTFMT_ELEMID " ends on node %" RTTFMT_ELEMID
+                  ", edgeend is %g,%g-%g,%g",
+                  edge->edge_id, node, p1.x, p1.y, p2.x, p2.y);
+      if ( ! azimuth_pt_pt(iface->ctx, &p1, &p2, &az) ) {{
+        RTT_ELEMID id = edge->edge_id;
+        _rtt_release_edges(iface->ctx, edges, numedges);
+        rtgeom_free(iface->ctx, cleangeom);
+        rterror(iface->ctx, "error computing azimuth of edge %d last edgeend [%g,%g-%g,%g]",
+                id, p1.x, p1.y, p2.x, p2.y);
+        return -1;
+      }}
+      azdif = az - data->myaz;
+      RTDEBUGF(1, "azimuth of edge %" RTTFMT_ELEMID
+                  ": %g (diff: %g)", edge->edge_id, az, azdif);
+      if ( azdif < 0 ) azdif += 2 * M_PI;
+      if ( minaz == -1 ) {
+        minaz = maxaz = azdif;
+        data->nextCW = data->nextCCW = -edge->edge_id; /* incoming */
+        data->cwFace = edge->face_right;
+        data->ccwFace = edge->face_left;
+        RTDEBUGF(1, "new nextCW and nextCCW edge is %" RTTFMT_ELEMID
+                    ", incoming, "
+                    "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                    " (face_right is new cwFace, face_left is new ccwFace)",
+                    edge->edge_id, edge->face_left,
+                    edge->face_right);
+      } else {
+        if ( azdif < minaz ) {
+          data->nextCW = -edge->edge_id; /* incoming */
+          data->cwFace = edge->face_right;
+          RTDEBUGF(1, "new nextCW edge is %" RTTFMT_ELEMID
+                      ", incoming, "
+                      "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                      " (previous had minaz=%g, face_right is new cwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, minaz);
+          minaz = azdif;
+        }
+        else if ( azdif > maxaz ) {
+          data->nextCCW = -edge->edge_id; /* incoming */
+          data->ccwFace = edge->face_left;
+          RTDEBUGF(1, "new nextCCW edge is %" RTTFMT_ELEMID
+                      ", outgoing, from start point, "
+                      "with face_left %" RTTFMT_ELEMID " and face_right %" RTTFMT_ELEMID
+                      " (previous had maxaz=%g, face_left is new ccwFace)",
+                      edge->edge_id, edge->face_left,
+                      edge->face_right, maxaz);
+          maxaz = azdif;
+        }
+      }
+    }
+
+    rtgeom_free(iface->ctx, cleangeom);
+  }
+  if ( numedges ) _rtt_release_edges(iface->ctx, edges, numedges);
+
+  RTDEBUGF(1, "edges adjacent to azimuth %g"
+              " (incident to node %" RTTFMT_ELEMID ")"
+              ": CW:%" RTTFMT_ELEMID "(%g) CCW:%" RTTFMT_ELEMID "(%g)",
+              data->myaz, node, data->nextCW, minaz,
+              data->nextCCW, maxaz);
+
+  if ( myedge_id < 1 && numedges && data->cwFace != data->ccwFace )
+  {
+    if ( data->cwFace != -1 && data->ccwFace != -1 ) {
+      rterror(iface->ctx, "Corrupted topology: adjacent edges %" RTTFMT_ELEMID " and %" RTTFMT_ELEMID
+              " bind different face (%" RTTFMT_ELEMID " and %" RTTFMT_ELEMID ")",
+              data->nextCW, data->nextCCW,
+              data->cwFace, data->ccwFace);
+      return -1;
+    }
+  }
+
+  /* Return number of incident edges found */
+  return numedges;
+}
+
+/*
+ * Get a point internal to the line and write it into the "ip"
+ * parameter
+ *
+ * return 0 on failure (line is empty or collapsed), 1 otherwise
+ */
+static int
+_rtt_GetInteriorEdgePoint(const RTCTX *ctx, const RTLINE* edge, RTPOINT2D* ip)
+{
+  int i;
+  RTPOINT2D fp, lp, tp;
+  RTPOINTARRAY *pa = edge->points;
+
+  if ( pa->npoints < 2 ) return 0; /* empty or structurally collapsed */
+
+  rt_getPoint2d_p(ctx, pa, 0, &fp); /* save first point */
+  rt_getPoint2d_p(ctx, pa, pa->npoints-1, &lp); /* save last point */
+  for (i=1; i<pa->npoints-1; ++i)
+  {
+    rt_getPoint2d_p(ctx, pa, i, &tp); /* pick next point */
+    if ( p2d_same(ctx, &tp, &fp) ) continue; /* equal to startpoint */
+    if ( p2d_same(ctx, &tp, &lp) ) continue; /* equal to endpoint */
+    /* this is a good one, neither same of start nor of end point */
+    *ip = tp;
+    return 1; /* found */
+  }
+
+  /* no distinct vertex found */
+
+  /* interpolate if start point != end point */
+
+  if ( p2d_same(ctx, &fp, &lp) ) return 0; /* no distinct points in edge */
+ 
+  ip->x = fp.x + ( (lp.x - fp.x) * 0.5 );
+  ip->y = fp.y + ( (lp.y - fp.y) * 0.5 );
+
+  return 1;
+}
+
+/*
+ * Add a split face by walking on the edge side.
+ *
+ * @param topo the topology to act upon
+ * @param sedge edge id and walking side and direction
+ *              (forward,left:positive backward,right:negative)
+ * @param face the face in which the edge identifier is known to be
+ * @param mbr_only do not create a new face but update MBR of the current
+ *
+ * @return:
+ *    -1: if mbr_only was requested
+ *     0: if the edge does not form a ring
+ *    -1: if it is impossible to create a face on the requested side
+ *        ( new face on the side is the universe )
+ *    -2: error
+ *   >0 : id of newly added face
+ */
+static RTT_ELEMID
+_rtt_AddFaceSplit( RTT_TOPOLOGY* topo,
+                   RTT_ELEMID sedge, RTT_ELEMID face,
+                   int mbr_only )
+{
+  int numedges, numfaceedges, i, j;
+  int newface_outside;
+  int num_signed_edge_ids;
+  RTT_ELEMID *signed_edge_ids;
+  RTT_ELEMID *edge_ids;
+  RTT_ISO_EDGE *edges;
+  RTT_ISO_EDGE *ring_edges;
+  RTT_ISO_EDGE *forward_edges = NULL;
+  int forward_edges_count = 0;
+  RTT_ISO_EDGE *backward_edges = NULL;
+  int backward_edges_count = 0;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  signed_edge_ids = rtt_be_getRingEdges(topo, sedge,
+                                        &num_signed_edge_ids, 0);
+  if ( ! signed_edge_ids ) {
+    rterror(iface->ctx, "Backend error (no ring edges for edge %" RTTFMT_ELEMID "): %s",
+            sedge, rtt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  RTDEBUGF(1, "getRingEdges returned %d edges", num_signed_edge_ids);
+
+  /* You can't get to the other side of an edge forming a ring */
+  for (i=0; i<num_signed_edge_ids; ++i) {
+    if ( signed_edge_ids[i] == -sedge ) {
+      /* No split here */
+      RTDEBUG(1, "not a ring");
+      rtfree(iface->ctx,  signed_edge_ids );
+      return 0;
+    }
+  }
+
+  RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " split face %" RTTFMT_ELEMID " (mbr_only:%d)",
+           sedge, face, mbr_only);
+
+  /* Construct a polygon using edges of the ring */
+  numedges = 0;
+  edge_ids = rtalloc(iface->ctx, sizeof(RTT_ELEMID)*num_signed_edge_ids);
+  for (i=0; i<num_signed_edge_ids; ++i) {
+    int absid = llabs(signed_edge_ids[i]);
+    int found = 0;
+    /* Do not add the same edge twice */
+    for (j=0; j<numedges; ++j) {
+      if ( edge_ids[j] == absid ) {
+        found = 1;
+        break;
+      }
+    }
+    if ( ! found ) edge_ids[numedges++] = absid;
+  }
+  i = numedges;
+  ring_edges = rtt_be_getEdgeById(topo, edge_ids, &i,
+                                  RTT_COL_EDGE_EDGE_ID|RTT_COL_EDGE_GEOM);
+  rtfree(iface->ctx,  edge_ids );
+  if ( i == -1 )
+  {
+    rtfree(iface->ctx,  signed_edge_ids );
+    /* ring_edges should be NULL */
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  else if ( i != numedges )
+  {
+    rtfree(iface->ctx,  signed_edge_ids );
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    rterror(iface->ctx, "Unexpected error: %d edges found when expecting %d", i, numedges);
+    return -2;
+  }
+
+  /* Should now build a polygon with those edges, in the order
+   * given by GetRingEdges.
+   */
+  RTPOINTARRAY *pa = NULL;
+  for ( i=0; i<num_signed_edge_ids; ++i )
+  {
+    RTT_ELEMID eid = signed_edge_ids[i];
+    RTDEBUGF(1, "Edge %d in ring of edge %" RTTFMT_ELEMID " is edge %" RTTFMT_ELEMID,
+                i, sedge, eid);
+    RTT_ISO_EDGE *edge = NULL;
+    RTPOINTARRAY *epa;
+    for ( j=0; j<numedges; ++j )
+    {
+      if ( ring_edges[j].edge_id == llabs(eid) )
+      {
+        edge = &(ring_edges[j]);
+        break;
+      }
+    }
+    if ( edge == NULL )
+    {
+      rtfree(iface->ctx,  signed_edge_ids );
+      _rtt_release_edges(iface->ctx, ring_edges, numedges);
+      rterror(iface->ctx, "missing edge that was found in ring edges loop");
+      return -2;
+    }
+
+    if ( pa == NULL )
+    {
+      pa = ptarray_clone_deep(iface->ctx, edge->geom->points);
+      if ( eid < 0 ) ptarray_reverse(iface->ctx, pa);
+    }
+    else
+    {
+      if ( eid < 0 )
+      {
+        epa = ptarray_clone_deep(iface->ctx, edge->geom->points);
+        ptarray_reverse(iface->ctx, epa);
+        ptarray_append_ptarray(iface->ctx, pa, epa, 0);
+        ptarray_free(iface->ctx, epa);
+      }
+      else
+      {
+        /* avoid a clone here */
+        ptarray_append_ptarray(iface->ctx, pa, edge->geom->points, 0);
+      }
+    }
+  }
+  RTPOINTARRAY **points = rtalloc(iface->ctx, sizeof(RTPOINTARRAY*));
+  points[0] = pa;
+  /* NOTE: the ring may very well have collapsed components,
+   *       which would make it topologically invalid
+   */
+  RTPOLY* shell = rtpoly_construct(iface->ctx, 0, 0, 1, points);
+
+  int isccw = ptarray_isccw(iface->ctx, pa);
+  RTDEBUGF(1, "Ring of edge %" RTTFMT_ELEMID " is %sclockwise",
+              sedge, isccw ? "counter" : "");
+  const RTGBOX* shellbox = rtgeom_get_bbox(iface->ctx, rtpoly_as_rtgeom(iface->ctx, shell));
+
+  if ( face == 0 )
+  {
+    /* Edge split the universe face */
+    if ( ! isccw )
+    {
+      rtpoly_free(iface->ctx, shell);
+      rtfree(iface->ctx,  signed_edge_ids );
+      _rtt_release_edges(iface->ctx, ring_edges, numedges);
+      /* Face on the left side of this ring is the universe face.
+       * Next call (for the other side) should create the split face
+       */
+      RTDEBUG(1, "The left face of this clockwise ring is the universe, "
+                 "won't create a new face there");
+      return -1;
+    }
+  }
+
+  if ( mbr_only && face != 0 )
+  {
+    if ( isccw )
+    {{
+      RTT_ISO_FACE updface;
+      updface.face_id = face;
+      updface.mbr = (RTGBOX *)shellbox; /* const cast, we won't free it, later */
+      int ret = rtt_be_updateFacesById( topo, &updface, 1 );
+      if ( ret == -1 )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        _rtt_release_edges(iface->ctx, ring_edges, numedges);
+        rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox above */
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != 1 )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        _rtt_release_edges(iface->ctx, ring_edges, numedges);
+        rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox above */
+        rterror(iface->ctx, "Unexpected error: %d faces found when expecting 1", ret);
+        return -2;
+      }
+    }}
+    rtfree(iface->ctx,  signed_edge_ids );
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox above */
+    return -1; /* mbr only was requested */
+  }
+
+  RTT_ISO_FACE *oldface = NULL;
+  RTT_ISO_FACE newface;
+  newface.face_id = -1;
+  if ( face != 0 && ! isccw)
+  {{
+    /* Face created an hole in an outer face */
+    int nfaces = 1;
+    oldface = rtt_be_getFaceById(topo, &face, &nfaces, RTT_COL_FACE_ALL);
+    if ( nfaces == -1 )
+    {
+      rtfree(iface->ctx,  signed_edge_ids );
+      rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox */
+      _rtt_release_edges(iface->ctx, ring_edges, numedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -2;
+    }
+    if ( nfaces != 1 )
+    {
+      rtfree(iface->ctx,  signed_edge_ids );
+      rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox */
+      _rtt_release_edges(iface->ctx, ring_edges, numedges);
+      rterror(iface->ctx, "Unexpected error: %d faces found when expecting 1", nfaces);
+      return -2;
+    }
+    newface.mbr = oldface->mbr;
+  }}
+  else
+  {
+    newface.mbr = (RTGBOX *)shellbox; /* const cast, we won't free it, later */
+  }
+
+  /* Insert the new face */
+  int ret = rtt_be_insertFaces( topo, &newface, 1 );
+  if ( ret == -1 )
+  {
+    rtfree(iface->ctx,  signed_edge_ids );
+    rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox */
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  if ( ret != 1 )
+  {
+    rtfree(iface->ctx,  signed_edge_ids );
+    rtpoly_free(iface->ctx, shell); /* NOTE: owns shellbox */
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    rterror(iface->ctx, "Unexpected error: %d faces inserted when expecting 1", ret);
+    return -2;
+  }
+  if ( oldface ) {
+    newface.mbr = NULL; /* it is a reference to oldface mbr... */
+    _rtt_release_faces(iface->ctx, oldface, 1);
+  }
+
+  /* Update side location of new face edges */
+
+  /* We want the new face to be on the left, if possible */
+  if ( face != 0 && ! isccw ) { /* ring is clockwise in a real face */
+    /* face shrinked, must update all non-contained edges and nodes */
+    RTDEBUG(1, "New face is on the outside of the ring, updating rings in former shell");
+    newface_outside = 1;
+    /* newface is outside */
+  } else {
+    RTDEBUG(1, "New face is on the inside of the ring, updating forward edges in new ring");
+    newface_outside = 0;
+    /* newface is inside */
+  }
+
+  /* Update edges bounding the old face */
+  /* (1) fetch all edges where left_face or right_face is = oldface */
+  int fields = RTT_COL_EDGE_EDGE_ID |
+               RTT_COL_EDGE_FACE_LEFT |
+               RTT_COL_EDGE_FACE_RIGHT |
+               RTT_COL_EDGE_GEOM
+               ;
+  numfaceedges = 1;
+  edges = rtt_be_getEdgeByFace( topo, &face, &numfaceedges, fields, newface.mbr );
+  if ( numfaceedges == -1 ) {
+    rtfree(iface->ctx,  signed_edge_ids );
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  RTDEBUGF(1, "rtt_be_getEdgeByFace returned %d edges", numfaceedges);
+  GEOSGeometry *shellgg = 0;
+  const GEOSPreparedGeometry* prepshell = 0;
+  shellgg = RTGEOM2GEOS(iface->ctx,  rtpoly_as_rtgeom(iface->ctx, shell), 0);
+  if ( ! shellgg ) {
+    rtpoly_free(iface->ctx, shell);
+    rtfree(iface->ctx, signed_edge_ids);
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    _rtt_release_edges(iface->ctx, edges, numfaceedges);
+    rterror(iface->ctx, "Could not convert shell geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+    return -2;
+  }
+  prepshell = GEOSPrepare_r(iface->ctx->gctx,  shellgg );
+  if ( ! prepshell ) {
+    GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+    rtpoly_free(iface->ctx, shell);
+    rtfree(iface->ctx, signed_edge_ids);
+    _rtt_release_edges(iface->ctx, ring_edges, numedges);
+    _rtt_release_edges(iface->ctx, edges, numfaceedges);
+    rterror(iface->ctx, "Could not prepare shell geometry: %s", rtgeom_get_last_geos_error(iface->ctx));
+    return -2;
+  }
+
+  if ( numfaceedges )
+  {
+    forward_edges = rtalloc(iface->ctx, sizeof(RTT_ISO_EDGE)*numfaceedges);
+    forward_edges_count = 0;
+    backward_edges = rtalloc(iface->ctx, sizeof(RTT_ISO_EDGE)*numfaceedges);
+    backward_edges_count = 0;
+
+    /* (2) loop over the results and: */
+    for ( i=0; i<numfaceedges; ++i )
+    {
+      RTT_ISO_EDGE *e = &(edges[i]);
+      int found = 0;
+      int contains;
+      GEOSGeometry *egg;
+      RTPOINT *epgeom;
+      RTPOINT2D ep;
+
+      /* (2.1) skip edges whose ID is in the list of boundary edges ? */
+      for ( j=0; j<num_signed_edge_ids; ++j )
+      {
+        int seid = signed_edge_ids[j];
+        if ( seid == e->edge_id )
+        {
+          /* IDEA: remove entry from signed_edge_ids ? */
+          RTDEBUGF(1, "Edge %d is a forward edge of the new ring", e->edge_id);
+          forward_edges[forward_edges_count].edge_id = e->edge_id;
+          forward_edges[forward_edges_count++].face_left = newface.face_id;
+          found++;
+          if ( found == 2 ) break;
+        }
+        else if ( -seid == e->edge_id )
+        {
+          /* IDEA: remove entry from signed_edge_ids ? */
+          RTDEBUGF(1, "Edge %d is a backward edge of the new ring", e->edge_id);
+          backward_edges[backward_edges_count].edge_id = e->edge_id;
+          backward_edges[backward_edges_count++].face_right = newface.face_id;
+          found++;
+          if ( found == 2 ) break;
+        }
+      }
+      if ( found ) continue;
+
+      /* We need to check only a single point
+       * (to avoid collapsed elements of the shell polygon
+       * giving false positive).
+       * The point but must not be an endpoint.
+       */
+      if ( ! _rtt_GetInteriorEdgePoint(iface->ctx, e->geom, &ep) )
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+        GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+        rtfree(iface->ctx, signed_edge_ids);
+        rtpoly_free(iface->ctx, shell);
+        rtfree(iface->ctx, forward_edges); /* contents owned by ring_edges */
+        rtfree(iface->ctx, backward_edges); /* contents owned by ring_edges */
+        _rtt_release_edges(iface->ctx, ring_edges, numedges);
+        _rtt_release_edges(iface->ctx, edges, numfaceedges);
+        rterror(iface->ctx, "Could not find interior point for edge %d: %s",
+                e->edge_id, rtgeom_get_last_geos_error(iface->ctx));
+        return -2;
+      }
+
+      epgeom = rtpoint_make2d(iface->ctx, 0, ep.x, ep.y);
+      egg = RTGEOM2GEOS(iface->ctx,  rtpoint_as_rtgeom(iface->ctx, epgeom) , 0);
+      rtpoint_free(iface->ctx, epgeom);
+      if ( ! egg ) {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+        GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+        rtfree(iface->ctx, signed_edge_ids);
+        rtpoly_free(iface->ctx, shell);
+        rtfree(iface->ctx, forward_edges); /* contents owned by ring_edges */
+        rtfree(iface->ctx, backward_edges); /* contents owned by ring_edges */
+        _rtt_release_edges(iface->ctx, ring_edges, numedges);
+        _rtt_release_edges(iface->ctx, edges, numfaceedges);
+        rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s",
+                rtgeom_get_last_geos_error(iface->ctx));
+        return -2;
+      }
+      /* IDEA: can be optimized by computing this on our side rather
+       *       than on GEOS _r(iface->ctx->gctx, saves conversion of big edges) */
+      /* IDEA: check that bounding box shortcut is taken, or use
+       *       shellbox to do it here */
+      contains = GEOSPreparedContains_r(iface->ctx->gctx,  prepshell, egg );
+      GEOSGeom_destroy_r(iface->ctx->gctx, egg);
+      if ( contains == 2 )
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+        GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+        rtfree(iface->ctx, signed_edge_ids);
+        rtpoly_free(iface->ctx, shell);
+        rtfree(iface->ctx, forward_edges); /* contents owned by ring_edges */
+        rtfree(iface->ctx, backward_edges); /* contents owned by ring_edges */
+        _rtt_release_edges(iface->ctx, ring_edges, numedges);
+        _rtt_release_edges(iface->ctx, edges, numfaceedges);
+        rterror(iface->ctx, "GEOS exception on PreparedContains: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -2;
+      }
+      RTDEBUGF(1, "Edge %d %scontained in new ring", e->edge_id,
+                  (contains?"":"not "));
+
+      /* (2.2) skip edges (NOT, if newface_outside) contained in shell */
+      if ( newface_outside )
+      {
+        if ( contains )
+        {
+          RTDEBUGF(1, "Edge %d contained in an hole of the new face",
+                      e->edge_id);
+          continue;
+        }
+      }
+      else
+      {
+        if ( ! contains )
+        {
+          RTDEBUGF(1, "Edge %d not contained in the face shell",
+                      e->edge_id);
+          continue;
+        }
+      }
+
+      /* (2.3) push to forward_edges if left_face = oface */
+      if ( e->face_left == face )
+      {
+        RTDEBUGF(1, "Edge %d has new face on the left side", e->edge_id);
+        forward_edges[forward_edges_count].edge_id = e->edge_id;
+        forward_edges[forward_edges_count++].face_left = newface.face_id;
+      }
+
+      /* (2.4) push to backward_edges if right_face = oface */
+      if ( e->face_right == face )
+      {
+        RTDEBUGF(1, "Edge %d has new face on the right side", e->edge_id);
+        backward_edges[backward_edges_count].edge_id = e->edge_id;
+        backward_edges[backward_edges_count++].face_right = newface.face_id;
+      }
+    }
+
+    /* Update forward edges */
+    if ( forward_edges_count )
+    {
+      ret = rtt_be_updateEdgesById(topo, forward_edges,
+                                   forward_edges_count,
+                                   RTT_COL_EDGE_FACE_LEFT);
+      if ( ret == -1 )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != forward_edges_count )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        rterror(iface->ctx, "Unexpected error: %d edges updated when expecting %d",
+                ret, forward_edges_count);
+        return -2;
+      }
+    }
+
+    /* Update backward edges */
+    if ( backward_edges_count )
+    {
+      ret = rtt_be_updateEdgesById(topo, backward_edges,
+                                   backward_edges_count,
+                                   RTT_COL_EDGE_FACE_RIGHT);
+      if ( ret == -1 )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+      if ( ret != backward_edges_count )
+      {
+        rtfree(iface->ctx,  signed_edge_ids );
+        rterror(iface->ctx, "Unexpected error: %d edges updated when expecting %d",
+                ret, backward_edges_count);
+        return -2;
+      }
+    }
+
+    rtfree(iface->ctx, forward_edges);
+    rtfree(iface->ctx, backward_edges);
+
+  }
+
+  _rtt_release_edges(iface->ctx, ring_edges, numedges);
+  _rtt_release_edges(iface->ctx, edges, numfaceedges);
+
+  /* Update isolated nodes which are now in new face */
+  int numisonodes = 1;
+  fields = RTT_COL_NODE_NODE_ID | RTT_COL_NODE_GEOM;
+  RTT_ISO_NODE *nodes = rtt_be_getNodeByFace(topo, &face,
+                                             &numisonodes, fields, newface.mbr);
+  if ( numisonodes == -1 ) {
+    rtfree(iface->ctx,  signed_edge_ids );
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -2;
+  }
+  if ( numisonodes ) {
+    RTT_ISO_NODE *updated_nodes = rtalloc(iface->ctx, sizeof(RTT_ISO_NODE)*numisonodes);
+    int nodes_to_update = 0;
+    for (i=0; i<numisonodes; ++i)
+    {
+      RTT_ISO_NODE *n = &(nodes[i]);
+      GEOSGeometry *ngg;
+      ngg = RTGEOM2GEOS(iface->ctx,  rtpoint_as_rtgeom(iface->ctx, n->geom), 0 );
+      int contains;
+      if ( ! ngg ) {
+        _rtt_release_nodes(iface->ctx, nodes, numisonodes);
+        if ( prepshell ) GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+        if ( shellgg ) GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+        rtfree(iface->ctx, signed_edge_ids);
+        rtpoly_free(iface->ctx, shell);
+        rterror(iface->ctx, "Could not convert node geometry to GEOS: %s",
+                rtgeom_get_last_geos_error(iface->ctx));
+        return -2;
+      }
+      contains = GEOSPreparedContains_r(iface->ctx->gctx,  prepshell, ngg );
+      GEOSGeom_destroy_r(iface->ctx->gctx, ngg);
+      if ( contains == 2 )
+      {
+        _rtt_release_nodes(iface->ctx, nodes, numisonodes);
+        if ( prepshell ) GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+        if ( shellgg ) GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+        rtfree(iface->ctx, signed_edge_ids);
+        rtpoly_free(iface->ctx, shell);
+        rterror(iface->ctx, "GEOS exception on PreparedContains: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -2;
+      }
+      RTDEBUGF(1, "Node %d is %scontained in new ring, newface is %s",
+                  n->node_id, contains ? "" : "not ",
+                  newface_outside ? "outside" : "inside" );
+      if ( newface_outside )
+      {
+        if ( contains )
+        {
+          RTDEBUGF(1, "Node %d contained in an hole of the new face",
+                      n->node_id);
+          continue;
+        }
+      }
+      else
+      {
+        if ( ! contains )
+        {
+          RTDEBUGF(1, "Node %d not contained in the face shell",
+                      n->node_id);
+          continue;
+        }
+      }
+      updated_nodes[nodes_to_update].node_id = n->node_id;
+      updated_nodes[nodes_to_update++].containing_face =
+                                       newface.face_id;
+      RTDEBUGF(1, "Node %d will be updated", n->node_id);
+    }
+    _rtt_release_nodes(iface->ctx, nodes, numisonodes);
+    if ( nodes_to_update )
+    {
+      int ret = rtt_be_updateNodesById(topo, updated_nodes,
+                                       nodes_to_update,
+                                       RTT_COL_NODE_CONTAINING_FACE);
+      if ( ret == -1 ) {
+        rtfree(iface->ctx,  signed_edge_ids );
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -2;
+      }
+    }
+    rtfree(iface->ctx, updated_nodes);
+  }
+
+  GEOSPreparedGeom_destroy_r(iface->ctx->gctx, prepshell);
+  GEOSGeom_destroy_r(iface->ctx->gctx, shellgg);
+  rtfree(iface->ctx, signed_edge_ids);
+  rtpoly_free(iface->ctx, shell);
+
+  return newface.face_id;
+}
+
+static RTT_ELEMID
+_rtt_AddEdge( RTT_TOPOLOGY* topo,
+              RTT_ELEMID start_node, RTT_ELEMID end_node,
+              RTLINE *geom, int skipChecks, int modFace )
+{
+  RTT_ISO_EDGE newedge;
+  RTGEOM *cleangeom;
+  edgeend span; /* start point analisys */
+  edgeend epan; /* end point analisys */
+  RTPOINT2D p1, pn, p2;
+  RTPOINTARRAY *pa;
+  RTT_ELEMID node_ids[2];
+  const RTPOINT *start_node_geom = NULL;
+  const RTPOINT *end_node_geom = NULL;
+  int num_nodes;
+  RTT_ISO_NODE *endpoints;
+  int i;
+  int prev_left;
+  int prev_right;
+  RTT_ISO_EDGE seledge;
+  RTT_ISO_EDGE updedge;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  if ( ! skipChecks )
+  {
+    /* curve must be simple */
+    if ( ! rtgeom_is_simple(iface->ctx, rtline_as_rtgeom(iface->ctx, geom)) )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - curve not simple");
+      return -1;
+    }
+  }
+
+  newedge.start_node = start_node;
+  newedge.end_node = end_node;
+  newedge.geom = geom;
+  newedge.face_left = -1;
+  newedge.face_right = -1;
+  cleangeom = rtgeom_remove_repeated_points(iface->ctx,  rtline_as_rtgeom(iface->ctx, geom), 0 );
+
+  pa = rtgeom_as_rtline(iface->ctx, cleangeom)->points;
+  if ( pa->npoints < 2 ) {
+    rtgeom_free(iface->ctx, cleangeom);
+    rterror(iface->ctx, "Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+
+  /* Initialize endpoint info (some of that ) */
+  span.cwFace = span.ccwFace =
+  epan.cwFace = epan.ccwFace = -1;
+
+  /* Compute azimut of first edge end on start node */
+  rt_getPoint2d_p(iface->ctx, pa, 0, &p1);
+  rt_getPoint2d_p(iface->ctx, pa, 1, &pn);
+  if ( p2d_same(iface->ctx, &p1, &pn) ) {
+    rtgeom_free(iface->ctx, cleangeom);
+    /* Can still happen, for 2-point lines */
+    rterror(iface->ctx, "Invalid edge (no two distinct vertices exist)");
+    return -1;
+  }
+  if ( ! azimuth_pt_pt(iface->ctx, &p1, &pn, &span.myaz) ) {
+    rtgeom_free(iface->ctx, cleangeom);
+    rterror(iface->ctx, "error computing azimuth of first edgeend [%g,%g-%g,%g]",
+            p1.x, p1.y, pn.x, pn.y);
+    return -1;
+  }
+  RTDEBUGF(1, "edge's start node is %g,%g", p1.x, p1.y);
+
+  /* Compute azimuth of last edge end on end node */
+  rt_getPoint2d_p(iface->ctx, pa, pa->npoints-1, &p2);
+  rt_getPoint2d_p(iface->ctx, pa, pa->npoints-2, &pn);
+  rtgeom_free(iface->ctx, cleangeom);
+  if ( ! azimuth_pt_pt(iface->ctx, &p2, &pn, &epan.myaz) ) {
+    rterror(iface->ctx, "error computing azimuth of last edgeend [%g,%g-%g,%g]",
+            p2.x, p2.y, pn.x, pn.y);
+    return -1;
+  }
+  RTDEBUGF(1, "edge's end node is %g,%g", p2.x, p2.y);
+
+  /*
+   * Check endpoints existance, match with Curve geometry
+   * and get face information (if any)
+   */
+
+  if ( start_node != end_node ) {
+    num_nodes = 2;
+    node_ids[0] = start_node;
+    node_ids[1] = end_node;
+  } else {
+    num_nodes = 1;
+    node_ids[0] = start_node;
+  }
+
+  endpoints = rtt_be_getNodeById( topo, node_ids, &num_nodes, RTT_COL_NODE_ALL );
+  if ( num_nodes < 0 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<num_nodes; ++i )
+  {
+    RTT_ISO_NODE* node = &(endpoints[i]);
+    if ( node->containing_face != -1 )
+    {
+      if ( newedge.face_left == -1 )
+      {
+        newedge.face_left = newedge.face_right = node->containing_face;
+      }
+      else if ( newedge.face_left != node->containing_face )
+      {
+        _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+        rterror(iface->ctx, "SQL/MM Spatial exception - geometry crosses an edge"
+                " (endnodes in faces %" RTTFMT_ELEMID " and %" RTTFMT_ELEMID ")",
+                newedge.face_left, node->containing_face);
+      }
+    }
+
+    RTDEBUGF(1, "Node %d, with geom %p (looking for %d and %d)",
+             node->node_id, node->geom, start_node, end_node);
+    if ( node->node_id == start_node ) {
+      start_node_geom = node->geom;
+    } 
+    if ( node->node_id == end_node ) {
+      end_node_geom = node->geom;
+    } 
+  }
+
+  if ( ! skipChecks )
+  {
+    if ( ! start_node_geom )
+    {
+      if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent node");
+      return -1;
+    }
+    else
+    {
+      pa = start_node_geom->point;
+      rt_getPoint2d_p(iface->ctx, pa, 0, &pn);
+      if ( ! p2d_same(iface->ctx, &pn, &p1) )
+      {
+        if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+        rterror(iface->ctx, "SQL/MM Spatial exception"
+                " - start node not geometry start point."
+                //" - start node not geometry start point (%g,%g != %g,%g).", pn.x, pn.y, p1.x, p1.y
+        );
+        return -1;
+      }
+    }
+
+    if ( ! end_node_geom )
+    {
+      if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent node");
+      return -1;
+    }
+    else
+    {
+      pa = end_node_geom->point;
+      rt_getPoint2d_p(iface->ctx, pa, 0, &pn);
+      if ( ! p2d_same(iface->ctx, &pn, &p2) )
+      {
+        if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+        rterror(iface->ctx, "SQL/MM Spatial exception"
+                " - end node not geometry end point."
+                //" - end node not geometry end point (%g,%g != %g,%g).", pn.x, pn.y, p2.x, p2.y
+        );
+        return -1;
+      }
+    }
+
+    if ( num_nodes ) _rtt_release_nodes(iface->ctx, endpoints, num_nodes);
+
+    if ( _rtt_CheckEdgeCrossing( topo, start_node, end_node, geom, 0 ) )
+      return -1;
+
+  } /* ! skipChecks */
+
+  /*
+   * All checks passed, time to prepare the new edge
+   */
+
+  newedge.edge_id = rtt_be_getNextEdgeId( topo );
+  if ( newedge.edge_id == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Find adjacent edges to each endpoint */
+  int isclosed = start_node == end_node;
+  int found;
+  found = _rtt_FindAdjacentEdges( topo, start_node, &span,
+                                  isclosed ? &epan : NULL, -1 );
+  if ( found ) {
+    span.was_isolated = 0;
+    newedge.next_right = span.nextCW ? span.nextCW : -newedge.edge_id;
+    prev_left = span.nextCCW ? -span.nextCCW : newedge.edge_id;
+    RTDEBUGF(1, "New edge %d is connected on start node, "
+                "next_right is %d, prev_left is %d",
+                newedge.edge_id, newedge.next_right, prev_left);
+    if ( newedge.face_right == -1 ) {
+      newedge.face_right = span.cwFace;
+    }
+    if ( newedge.face_left == -1 ) {
+      newedge.face_left = span.ccwFace;
+    }
+  } else {
+    span.was_isolated = 1;
+    newedge.next_right = isclosed ? -newedge.edge_id : newedge.edge_id;
+    prev_left = isclosed ? newedge.edge_id : -newedge.edge_id;
+    RTDEBUGF(1, "New edge %d is isolated on start node, "
+                "next_right is %d, prev_left is %d",
+                newedge.edge_id, newedge.next_right, prev_left);
+  }
+
+  found = _rtt_FindAdjacentEdges( topo, end_node, &epan,
+                                  isclosed ? &span : NULL, -1 );
+  if ( found ) {
+    epan.was_isolated = 0;
+    newedge.next_left = epan.nextCW ? epan.nextCW : newedge.edge_id;
+    prev_right = epan.nextCCW ? -epan.nextCCW : -newedge.edge_id;
+    RTDEBUGF(1, "New edge %d is connected on end node, "
+                "next_left is %d, prev_right is %d",
+                newedge.edge_id, newedge.next_left, prev_right);
+    if ( newedge.face_right == -1 ) {
+      newedge.face_right = span.ccwFace;
+    }
+    if ( newedge.face_left == -1 ) {
+      newedge.face_left = span.cwFace;
+    }
+  } else {
+    epan.was_isolated = 1;
+    newedge.next_left = isclosed ? newedge.edge_id : -newedge.edge_id;
+    prev_right = isclosed ? -newedge.edge_id : newedge.edge_id;
+    RTDEBUGF(1, "New edge %d is isolated on end node, "
+                "next_left is %d, prev_right is %d",
+                newedge.edge_id, newedge.next_left, prev_right);
+  }
+
+  /*
+   * If we don't have faces setup by now we must have encountered
+   * a malformed topology (no containing_face on isolated nodes, no
+   * left/right faces on adjacent edges or mismatching values)
+   */
+  if ( newedge.face_left != newedge.face_right )
+  {
+    rterror(iface->ctx, "Left(%" RTTFMT_ELEMID ")/right(%" RTTFMT_ELEMID ")"
+            "faces mismatch: invalid topology ?",
+            newedge.face_left, newedge.face_right);
+    return -1;
+  }
+  else if ( newedge.face_left == -1 )
+  {
+    rterror(iface->ctx, "Could not derive edge face from linked primitives:"
+            " invalid topology ?");
+    return -1;
+  }
+
+  /*
+   * Insert the new edge, and update all linking
+   */
+
+  int ret = rtt_be_insertEdges(topo, &newedge, 1);
+  if ( ret == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  } else if ( ret == 0 ) {
+    rterror(iface->ctx, "Insertion of split edge failed (no reason)");
+    return -1;
+  }
+
+  int updfields;
+
+  /* Link prev_left to us
+   * (if it's not us already) */
+  if ( llabs(prev_left) != newedge.edge_id )
+  {
+    if ( prev_left > 0 )
+    {
+      /* its next_left_edge is us */
+      updfields = RTT_COL_EDGE_NEXT_LEFT;
+      updedge.next_left = newedge.edge_id;
+      seledge.edge_id = prev_left;
+    }
+    else
+    {
+      /* its next_right_edge is us */
+      updfields = RTT_COL_EDGE_NEXT_RIGHT;
+      updedge.next_right = newedge.edge_id;
+      seledge.edge_id = -prev_left;
+    }
+
+    ret = rtt_be_updateEdges(topo,
+        &seledge, RTT_COL_EDGE_EDGE_ID,
+        &updedge, updfields,
+        NULL, 0);
+    if ( ret == -1 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* Link prev_right to us 
+   * (if it's not us already) */
+  if ( llabs(prev_right) != newedge.edge_id )
+  {
+    if ( prev_right > 0 )
+    {
+      /* its next_left_edge is -us */
+      updfields = RTT_COL_EDGE_NEXT_LEFT;
+      updedge.next_left = -newedge.edge_id;
+      seledge.edge_id = prev_right;
+    }
+    else
+    {
+      /* its next_right_edge is -us */
+      updfields = RTT_COL_EDGE_NEXT_RIGHT;
+      updedge.next_right = -newedge.edge_id;
+      seledge.edge_id = -prev_right;
+    }
+
+    ret = rtt_be_updateEdges(topo,
+        &seledge, RTT_COL_EDGE_EDGE_ID,
+        &updedge, updfields,
+        NULL, 0);
+    if ( ret == -1 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* NOT IN THE SPECS...
+   * set containing_face = null for start_node and end_node
+   * if they where isolated
+   *
+   */
+  RTT_ISO_NODE updnode, selnode;
+  updnode.containing_face = -1;
+  if ( span.was_isolated )
+  {
+    selnode.node_id = start_node;
+    ret = rtt_be_updateNodes(topo,
+        &selnode, RTT_COL_NODE_NODE_ID,
+        &updnode, RTT_COL_NODE_CONTAINING_FACE,
+        NULL, 0);
+    if ( ret == -1 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+  if ( epan.was_isolated )
+  {
+    selnode.node_id = end_node;
+    ret = rtt_be_updateNodes(topo,
+        &selnode, RTT_COL_NODE_NODE_ID,
+        &updnode, RTT_COL_NODE_CONTAINING_FACE,
+        NULL, 0);
+    if ( ret == -1 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  int newface1 = -1;
+
+  if ( ! modFace )
+  {
+    newface1 = _rtt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 0 );
+    if ( newface1 == 0 ) {
+      RTDEBUG(1, "New edge does not split any face");
+      return newedge.edge_id; /* no split */
+    }
+  }
+
+  /* Check face splitting */
+  int newface = _rtt_AddFaceSplit( topo, newedge.edge_id,
+                                   newedge.face_left, 0 );
+  if ( modFace )
+  {
+    if ( newface == 0 ) {
+      RTDEBUG(1, "New edge does not split any face");
+      return newedge.edge_id; /* no split */
+    }
+
+    if ( newface < 0 )
+    {
+      /* face on the left is the universe face */
+      /* must be forming a maximal ring in universal face */
+      newface = _rtt_AddFaceSplit( topo, -newedge.edge_id,
+                                   newedge.face_left, 0 );
+      if ( newface < 0 ) return newedge.edge_id; /* no split */
+    }
+    else
+    {
+      _rtt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 1 );
+    }
+  }
+
+  /*
+   * Update topogeometries, if needed
+   */
+  if ( newedge.face_left != 0 )
+  {
+    ret = rtt_be_updateTopoGeomFaceSplit(topo, newedge.face_left,
+                                         newface, newface1);
+    if ( ret == 0 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+
+    if ( ! modFace )
+    {
+      /* drop old face from the face table */
+      ret = rtt_be_deleteFacesById(topo, &(newedge.face_left), 1);
+      if ( ret == -1 ) {
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+    }
+  }
+
+  return newedge.edge_id;
+}
+
+RTT_ELEMID
+rtt_AddEdgeModFace( RTT_TOPOLOGY* topo,
+                    RTT_ELEMID start_node, RTT_ELEMID end_node,
+                    RTLINE *geom, int skipChecks )
+{
+  return _rtt_AddEdge( topo, start_node, end_node, geom, skipChecks, 1 );
+}
+
+RTT_ELEMID
+rtt_AddEdgeNewFaces( RTT_TOPOLOGY* topo,
+                    RTT_ELEMID start_node, RTT_ELEMID end_node,
+                    RTLINE *geom, int skipChecks )
+{
+  return _rtt_AddEdge( topo, start_node, end_node, geom, skipChecks, 0 );
+}
+
+static RTGEOM *
+_rtt_FaceByEdges(RTT_TOPOLOGY *topo, RTT_ISO_EDGE *edges, int numfaceedges)
+{
+  RTGEOM *outg;
+  RTCOLLECTION *bounds;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTGEOM **geoms = rtalloc(iface->ctx,  sizeof(RTGEOM*) * numfaceedges );
+  int i, validedges = 0;
+
+  for ( i=0; i<numfaceedges; ++i )
+  {
+    /* NOTE: skipping edges with same face on both sides, although
+     *       correct, results in a failure to build faces from
+     *       invalid topologies as expected by legacy tests.
+     * TODO: update legacy tests expectances/unleash this skipping ?
+     */
+    /* if ( edges[i].face_left == edges[i].face_right ) continue; */
+    geoms[validedges++] = rtline_as_rtgeom(iface->ctx, edges[i].geom);
+  }
+  if ( ! validedges )
+  {
+    /* Face has no valid boundary edges, we'll return EMPTY, see
+     * https://trac.osgeo.org/postgis/ticket/3221 */
+    if ( numfaceedges ) rtfree(iface->ctx, geoms);
+    RTDEBUG(1, "_rtt_FaceByEdges returning empty polygon");
+    return rtpoly_as_rtgeom(iface->ctx, 
+            rtpoly_construct_empty(iface->ctx, topo->srid, topo->hasZ, 0)
+           );
+  }
+  bounds = rtcollection_construct(iface->ctx, RTMULTILINETYPE,
+                                  topo->srid,
+                                  NULL, /* gbox */
+                                  validedges,
+                                  geoms);
+  outg = rtgeom_buildarea(iface->ctx,  rtcollection_as_rtgeom(iface->ctx, bounds) );
+  rtcollection_release(iface->ctx, bounds);
+  rtfree(iface->ctx, geoms);
+#if 0
+  {
+  size_t sz;
+  char *wkt = rtgeom_to_wkt(iface->ctx, outg, RTWKT_EXTENDED, 2, &sz);
+  RTDEBUGF(1, "_rtt_FaceByEdges returning area: %s", wkt);
+  rtfree(iface->ctx, wkt);
+  }
+#endif
+  return outg;
+}
+
+RTGEOM*
+rtt_GetFaceGeometry(RTT_TOPOLOGY* topo, RTT_ELEMID faceid)
+{
+  int numfaceedges;
+  RTT_ISO_EDGE *edges;
+  RTT_ISO_FACE *face;
+  RTPOLY *out;
+  RTGEOM *outg;
+  int i;
+  int fields;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  if ( faceid == 0 )
+  {
+    rterror(iface->ctx, "SQL/MM Spatial exception - universal face has no geometry");
+    return NULL;
+  }
+
+  /* Construct the face geometry */
+  numfaceedges = 1;
+  fields = RTT_COL_EDGE_GEOM |
+           RTT_COL_EDGE_FACE_LEFT |
+           RTT_COL_EDGE_FACE_RIGHT
+           ;
+  edges = rtt_be_getEdgeByFace( topo, &faceid, &numfaceedges, fields, NULL );
+  if ( numfaceedges == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return NULL;
+  }
+
+  if ( numfaceedges == 0 )
+  {
+    i = 1;
+    face = rtt_be_getFaceById(topo, &faceid, &i, RTT_COL_FACE_FACE_ID);
+    if ( i == -1 ) {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return NULL;
+    }
+    if ( i == 0 ) {
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent face.");
+      return NULL;
+    }
+    rtfree(iface->ctx,  face );
+    if ( i > 1 ) {
+      rterror(iface->ctx, "Corrupted topology: multiple face records have face_id=%"
+              PRId64, faceid);
+      return NULL;
+    }
+    /* Face has no boundary edges, we'll return EMPTY, see
+     * https://trac.osgeo.org/postgis/ticket/3221 */
+    out = rtpoly_construct_empty(iface->ctx, topo->srid, topo->hasZ, 0);
+    return rtpoly_as_rtgeom(iface->ctx, out);
+  }
+
+  outg = _rtt_FaceByEdges( topo, edges, numfaceedges );
+  _rtt_release_edges(iface->ctx, edges, numfaceedges);
+
+  return outg;
+}
+
+/* Find which edge from the "edges" set defines the next
+ * portion of the given "ring".
+ *
+ * The edge might be either forward or backward.
+ *
+ * @param ring The ring to find definition of.
+ *             It is assumed it does not contain duplicated vertices.
+ * @param from offset of the ring point to start looking from
+ * @param edges array of edges to search into
+ * @param numedges number of edges in the edges array
+ *
+ * @return index of the edge defining the next ring portion or
+ *               -1 if no edge was found to be part of the ring
+ */
+static int
+_rtt_FindNextRingEdge(const RTCTX *ctx, const RTPOINTARRAY *ring, int from,
+                      const RTT_ISO_EDGE *edges, int numedges)
+{
+  int i;
+  RTPOINT2D p1;
+
+  /* Get starting ring point */
+  rt_getPoint2d_p(ctx, ring, from, &p1);
+
+  RTDEBUGF(1, "Ring's 'from' point (%d) is %g,%g", from, p1.x, p1.y);
+
+  /* find the edges defining the next portion of ring starting from
+   * vertex "from" */
+  for ( i=0; i<numedges; ++i )
+  {
+    const RTT_ISO_EDGE *isoe = &(edges[i]);
+    RTLINE *edge = isoe->geom;
+    RTPOINTARRAY *epa = edge->points;
+    RTPOINT2D p2, pt;
+    int match = 0;
+    int j;
+
+    /* Skip if the edge is a dangling one */
+    if ( isoe->face_left == isoe->face_right )
+    {
+      RTDEBUGF(3, "_rtt_FindNextRingEdge: edge %" RTTFMT_ELEMID
+                  " has same face (%" RTTFMT_ELEMID
+                  ") on both sides, skipping",
+                  isoe->edge_id, isoe->face_left);
+      continue;
+    }
+
+#if 0
+    size_t sz;
+    RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " is %s",
+                isoe->edge_id,
+                rtgeom_to_wkt(ctx, rtline_as_rtgeom(ctx, edge), RTWKT_EXTENDED, 2, &sz));
+#endif
+
+    /* ptarray_remove_repeated_points ? */
+
+    rt_getPoint2d_p(ctx, epa, 0, &p2);
+    RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " 'first' point is %g,%g",
+                isoe->edge_id, p2.x, p2.y);
+    RTDEBUGF(1, "Rings's 'from' point is still %g,%g", p1.x, p1.y);
+    if ( p2d_same(ctx, &p1, &p2) )
+    {
+      RTDEBUG(1, "p2d_same(ctx, p1,p2) returned true");
+      RTDEBUGF(1, "First point of edge %" RTTFMT_ELEMID
+                  " matches ring vertex %d", isoe->edge_id, from);
+      /* first point matches, let's check next non-equal one */
+      for ( j=1; j<epa->npoints; ++j )
+      {
+        rt_getPoint2d_p(ctx, epa, j, &p2);
+        RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " 'next' point %d is %g,%g",
+                    isoe->edge_id, j, p2.x, p2.y);
+        /* we won't check duplicated edge points */
+        if ( p2d_same(ctx, &p1, &p2) ) continue;
+        /* we assume there are no duplicated points in ring */
+        rt_getPoint2d_p(ctx, ring, from+1, &pt);
+        RTDEBUGF(1, "Ring's point %d is %g,%g",
+                    from+1, pt.x, pt.y);
+        match = p2d_same(ctx, &pt, &p2);
+        break; /* we want to check a single non-equal next vertex */
+      }
+#if RTGEOM_DEBUG_LEVEL > 0
+      if ( match ) {
+        RTDEBUGF(1, "Prev point of edge %" RTTFMT_ELEMID
+                    " matches ring vertex %d", isoe->edge_id, from+1);
+      } else {
+        RTDEBUGF(1, "Prev point of edge %" RTTFMT_ELEMID
+                    " does not match ring vertex %d", isoe->edge_id, from+1);
+      }
+#endif
+    }
+
+    if ( ! match )
+    {
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " did not match as forward",
+                 isoe->edge_id);
+      rt_getPoint2d_p(ctx, epa, epa->npoints-1, &p2);
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " 'last' point is %g,%g",
+                  isoe->edge_id, p2.x, p2.y);
+      if ( p2d_same(ctx, &p1, &p2) )
+      {
+        RTDEBUGF(1, "Last point of edge %" RTTFMT_ELEMID
+                    " matches ring vertex %d", isoe->edge_id, from);
+        /* last point matches, let's check next non-equal one */
+        for ( j=epa->npoints-2; j>=0; --j )
+        {
+          rt_getPoint2d_p(ctx, epa, j, &p2);
+          RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " 'prev' point %d is %g,%g",
+                      isoe->edge_id, j, p2.x, p2.y);
+          /* we won't check duplicated edge points */
+          if ( p2d_same(ctx, &p1, &p2) ) continue;
+          /* we assume there are no duplicated points in ring */
+          rt_getPoint2d_p(ctx, ring, from+1, &pt);
+          RTDEBUGF(1, "Ring's point %d is %g,%g",
+                      from+1, pt.x, pt.y);
+          match = p2d_same(ctx, &pt, &p2);
+          break; /* we want to check a single non-equal next vertex */
+        }
+      }
+#if RTGEOM_DEBUG_LEVEL > 0
+      if ( match ) {
+        RTDEBUGF(1, "Prev point of edge %" RTTFMT_ELEMID
+                    " matches ring vertex %d", isoe->edge_id, from+1);
+      } else {
+        RTDEBUGF(1, "Prev point of edge %" RTTFMT_ELEMID
+                    " does not match ring vertex %d", isoe->edge_id, from+1);
+      }
+#endif
+    }
+
+    if ( match ) return i;
+
+  }
+
+  return -1;
+}
+
+/* Reverse values in array between "from" (inclusive)
+ * and "to" (exclusive) indexes */
+static void
+_rtt_ReverseElemidArray(RTT_ELEMID *ary, int from, int to)
+{
+  RTT_ELEMID t;
+  while (from < to)
+  {
+    t = ary[from];
+    ary[from++] = ary[to];
+    ary[to--] = t;
+  }
+}
+
+/* Rotate values in array between "from" (inclusive)
+ * and "to" (exclusive) indexes, so that "rotidx" is
+ * the new value at "from" */
+static void
+_rtt_RotateElemidArray(RTT_ELEMID *ary, int from, int to, int rotidx)
+{
+  _rtt_ReverseElemidArray(ary, from, rotidx-1);
+  _rtt_ReverseElemidArray(ary, rotidx, to-1);
+  _rtt_ReverseElemidArray(ary, from, to-1);
+}
+
+
+int
+rtt_GetFaceEdges(RTT_TOPOLOGY* topo, RTT_ELEMID face_id, RTT_ELEMID **out )
+{
+  RTGEOM *face;
+  RTPOLY *facepoly;
+  RTT_ISO_EDGE *edges;
+  int numfaceedges;
+  int fields, i;
+  int nseid = 0; /* number of signed edge ids */
+  int prevseid;
+  RTT_ELEMID *seid; /* signed edge ids */
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  /* Get list of face edges */
+  numfaceedges = 1;
+  fields = RTT_COL_EDGE_EDGE_ID |
+           RTT_COL_EDGE_GEOM |
+           RTT_COL_EDGE_FACE_LEFT |
+           RTT_COL_EDGE_FACE_RIGHT
+           ;
+  edges = rtt_be_getEdgeByFace( topo, &face_id, &numfaceedges, fields, NULL );
+  if ( numfaceedges == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( ! numfaceedges ) return 0; /* no edges in output */
+
+  /* order edges by occurrence in face */
+
+  face = _rtt_FaceByEdges(topo, edges, numfaceedges);
+  if ( ! face )
+  {
+    /* _rtt_FaceByEdges should have already invoked rterror in this case */
+    _rtt_release_edges(iface->ctx, edges, numfaceedges);
+    return -1;
+  }
+
+  if ( rtgeom_is_empty(iface->ctx, face) )
+  {
+    /* no edges in output */
+    _rtt_release_edges(iface->ctx, edges, numfaceedges);
+    rtgeom_free(iface->ctx, face);
+    return 0;
+  }
+
+  /* force_lhr, if the face is not the universe */
+  /* _rtt_FaceByEdges seems to guaranteed RHR */
+  /* rtgeom_force_clockwise(iface->ctx, face); */
+  if ( face_id ) rtgeom_reverse(iface->ctx, face);
+
+#if 0
+  {
+  size_t sz;
+  char *wkt = rtgeom_to_wkt(iface->ctx, face, RTWKT_EXTENDED, 6, &sz);
+  RTDEBUGF(1, "Geometry of face %" RTTFMT_ELEMID " is: %s",
+              face_id, wkt);
+  rtfree(iface->ctx, wkt);
+  }
+#endif
+
+  facepoly = rtgeom_as_rtpoly(iface->ctx, face);
+  if ( ! facepoly )
+  {
+    _rtt_release_edges(iface->ctx, edges, numfaceedges);
+    rtgeom_free(iface->ctx, face);
+    rterror(iface->ctx, "Geometry of face %" RTTFMT_ELEMID " is not a polygon", face_id);
+    return -1;
+  }
+
+  nseid = prevseid = 0;
+  seid = rtalloc(iface->ctx,  sizeof(RTT_ELEMID) * numfaceedges );
+
+  /* for each ring of the face polygon... */
+  for ( i=0; i<facepoly->nrings; ++i )
+  {
+    const RTPOINTARRAY *ring = facepoly->rings[i];
+    int j = 0;
+    RTT_ISO_EDGE *nextedge;
+    RTLINE *nextline;
+
+    RTDEBUGF(1, "Ring %d has %d points", i, ring->npoints);
+
+    while ( j < ring->npoints-1 )
+    {
+      RTDEBUGF(1, "Looking for edge covering ring %d from vertex %d",
+                  i, j);
+
+      int edgeno = _rtt_FindNextRingEdge(iface->ctx, ring, j, edges, numfaceedges);
+      if ( edgeno == -1 )
+      {
+        /* should never happen */
+        _rtt_release_edges(iface->ctx, edges, numfaceedges);
+        rtgeom_free(iface->ctx, face);
+        rtfree(iface->ctx, seid);
+        rterror(iface->ctx, "No edge (among %d) found to be defining geometry of face %"
+                RTTFMT_ELEMID, numfaceedges, face_id);
+        return -1;
+      }
+
+      nextedge = &(edges[edgeno]);
+      nextline = nextedge->geom;
+
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID
+                  " covers ring %d from vertex %d to %d",
+                  nextedge->edge_id, i, j, j + nextline->points->npoints - 1);
+
+#if 0
+      {
+      size_t sz;
+      char *wkt = rtgeom_to_wkt(iface->ctx, rtline_as_rtgeom(iface->ctx, nextline), RTWKT_EXTENDED, 6, &sz);
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " is %s",
+                  nextedge->edge_id, wkt);
+      rtfree(iface->ctx, wkt);
+      }
+#endif
+
+      j += nextline->points->npoints - 1;
+
+      /* Add next edge to the output array */
+      seid[nseid++] = nextedge->face_left == face_id ?
+                          nextedge->edge_id :
+                         -nextedge->edge_id;
+
+      /* avoid checking again on next time turn */
+      nextedge->face_left = nextedge->face_right = -1;
+    }
+
+    /* now "scroll" the list of edges so that the one
+     * with smaller absolute edge_id is first */
+    /* Range is: [prevseid, nseid) -- [inclusive, exclusive) */
+    if ( (nseid - prevseid) > 1 )
+    {{
+      RTT_ELEMID minid = 0;
+      int minidx = 0;
+      RTDEBUGF(1, "Looking for smallest id among the %d edges "
+                  "composing ring %d", (nseid-prevseid), i);
+      for ( j=prevseid; j<nseid; ++j )
+      {
+        RTT_ELEMID id = llabs(seid[j]);
+        RTDEBUGF(1, "Abs id of edge in pos %d is %" RTTFMT_ELEMID, j, id);
+        if ( ! minid || id < minid )
+        {
+          minid = id;
+          minidx = j;
+        }
+      }
+      RTDEBUGF(1, "Smallest id is %" RTTFMT_ELEMID
+                  " at position %d", minid, minidx);
+      if ( minidx != prevseid )
+      {
+        _rtt_RotateElemidArray(seid, prevseid, nseid, minidx);
+      }
+    }}
+
+    prevseid = nseid;
+  }
+
+  rtgeom_free(iface->ctx, face);
+  _rtt_release_edges(iface->ctx, edges, numfaceedges);
+
+  *out = seid;
+  return nseid;
+}
+
+static GEOSGeometry *
+_rtt_EdgeMotionArea(const RTCTX *ctx, RTLINE *geom, int isclosed)
+{
+  GEOSGeometry *gg;
+  RTPOINT4D p4d;
+  RTPOINTARRAY *pa;
+  RTPOINTARRAY **pas;
+  RTPOLY *poly;
+  RTGEOM *g;
+
+  pas = rtalloc(ctx, sizeof(RTPOINTARRAY*));
+
+  _rtt_EnsureGeos(ctx);
+
+  if ( isclosed )
+  {
+    pas[0] = ptarray_clone_deep(ctx,  geom->points );
+    poly = rtpoly_construct(ctx, 0, 0, 1, pas);
+    gg = RTGEOM2GEOS(ctx,  rtpoly_as_rtgeom(ctx, poly), 0 );
+    rtpoly_free(ctx, poly); /* should also delete the pointarrays */
+  }
+  else
+  {
+    pa = geom->points;
+    rt_getPoint4d_p(ctx, pa, 0, &p4d);
+    pas[0] = ptarray_clone_deep(ctx,  pa );
+    /* don't bother dup check */
+    if ( RT_FAILURE == ptarray_append_point(ctx, pas[0], &p4d, RT_TRUE) )
+    {
+      ptarray_free(ctx, pas[0]);
+      rtfree(ctx, pas);
+      rterror(ctx, "Could not append point to pointarray");
+      return NULL;
+    }
+    poly = rtpoly_construct(ctx, 0, NULL, 1, pas);
+    /* make valid, in case the edge self-intersects on its first-last
+     * vertex segment */
+    g = rtgeom_make_valid(ctx, rtpoly_as_rtgeom(ctx, poly));
+    rtpoly_free(ctx, poly); /* should also delete the pointarrays */
+    if ( ! g )
+    {
+      rterror(ctx, "Could not make edge motion area valid");
+      return NULL;
+    }
+    gg = RTGEOM2GEOS(ctx, g, 0);
+    rtgeom_free(ctx, g);
+  }
+  if ( ! gg )
+  {
+    rterror(ctx, "Could not convert old edge area geometry to GEOS: %s",
+            rtgeom_get_last_geos_error(ctx));
+    return NULL;
+  }
+  return gg;
+}
+
+int
+rtt_ChangeEdgeGeom(RTT_TOPOLOGY* topo, RTT_ELEMID edge_id, RTLINE *geom)
+{
+  RTT_ISO_EDGE *oldedge;
+  RTT_ISO_EDGE newedge;
+  RTPOINT2D p1, p2, pt;
+  int i;
+  int isclosed = 0;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  /* curve must be simple */
+  if ( ! rtgeom_is_simple(iface->ctx, rtline_as_rtgeom(iface->ctx, geom)) )
+  {
+    rterror(iface->ctx, "SQL/MM Spatial exception - curve not simple");
+    return -1;
+  }
+
+  i = 1;
+  oldedge = rtt_be_getEdgeById(topo, &edge_id, &i, RTT_COL_EDGE_ALL);
+  if ( ! oldedge )
+  {
+    RTDEBUGF(1, "rtt_ChangeEdgeGeom: "
+                "rtt_be_getEdgeById returned NULL and set i=%d", i);
+    if ( i == -1 )
+    {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    else if ( i == 0 )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge %"
+              RTTFMT_ELEMID, edge_id);
+      return -1;
+    }
+    else
+    {
+      rterror(iface->ctx, "Backend coding error: getEdgeById callback returned NULL "
+              "but numelements output parameter has value %d "
+              "(expected 0 or 1)", i);
+      return -1;
+    }
+  }
+
+  RTDEBUGF(1, "rtt_ChangeEdgeGeom: "
+              "old edge has %d points, new edge has %d points",
+              oldedge->geom->points->npoints, geom->points->npoints);
+
+  /*
+   * e) Check StartPoint consistency
+   */
+  rt_getPoint2d_p(iface->ctx, oldedge->geom->points, 0, &p1);
+  rt_getPoint2d_p(iface->ctx, geom->points, 0, &pt);
+  if ( ! p2d_same(iface->ctx, &p1, &pt) )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "SQL/MM Spatial exception - "
+            "start node not geometry start point.");
+    return -1;
+  }
+
+  /*
+   * f) Check EndPoint consistency
+   */
+  if ( oldedge->geom->points->npoints < 2 )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Corrupted topology: edge %" RTTFMT_ELEMID
+            " has less than 2 vertices", oldedge->edge_id);
+    return -1;
+  }
+  rt_getPoint2d_p(iface->ctx, oldedge->geom->points, oldedge->geom->points->npoints-1, &p2);
+  if ( geom->points->npoints < 2 )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Invalid edge: less than 2 vertices");
+    return -1;
+  }
+  rt_getPoint2d_p(iface->ctx, geom->points, geom->points->npoints-1, &pt);
+  if ( ! p2d_same(iface->ctx, &pt, &p2) )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "SQL/MM Spatial exception - "
+            "end node not geometry end point.");
+    return -1;
+  }
+
+  /* Not in the specs:
+   * if the edge is closed, check we didn't change winding !
+   *       (should be part of isomorphism checking)
+   */
+  if ( oldedge->start_node == oldedge->end_node )
+  {
+    isclosed = 1;
+#if 1 /* TODO: this is actually bogus as a test */
+    /* check for valid edge (distinct vertices must exist) */
+    if ( ! _rtt_GetInteriorEdgePoint(iface->ctx, geom, &pt) )
+    {
+      _rtt_release_edges(iface->ctx, oldedge, 1);
+      rterror(iface->ctx, "Invalid edge (no two distinct vertices exist)");
+      return -1;
+    }
+#endif
+
+    if ( ptarray_isccw(iface->ctx, oldedge->geom->points) !=
+         ptarray_isccw(iface->ctx, geom->points) )
+    {
+      _rtt_release_edges(iface->ctx, oldedge, 1);
+      rterror(iface->ctx, "Edge twist at node POINT(%g %g)", p1.x, p1.y);
+      return -1;
+    }
+  }
+
+  if ( _rtt_CheckEdgeCrossing(topo, oldedge->start_node,
+                                    oldedge->end_node, geom, edge_id ) )
+  {
+    /* would have called rterror already, leaking :( */
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    return -1;
+  }
+
+  RTDEBUG(1, "rtt_ChangeEdgeGeom: "
+             "edge crossing check passed ");
+
+  /*
+   * Not in the specs:
+   * Check topological isomorphism
+   */
+
+  /* Check that the "motion range" doesn't include any node */
+  // 1. compute combined bbox of old and new edge
+  RTGBOX mbox; /* motion box */
+  rtgeom_add_bbox(iface->ctx, (RTGEOM*)oldedge->geom); /* just in case */
+  rtgeom_add_bbox(iface->ctx, (RTGEOM*)geom); /* just in case */
+  gbox_union(iface->ctx, oldedge->geom->bbox, geom->bbox, &mbox);
+  // 2. fetch all nodes in the combined box
+  RTT_ISO_NODE *nodes;
+  int numnodes;
+  nodes = rtt_be_getNodeWithinBox2D(topo, &mbox, &numnodes,
+                                          RTT_COL_NODE_ALL, 0);
+  RTDEBUGF(1, "rtt_be_getNodeWithinBox2D returned %d nodes", numnodes);
+  if ( numnodes == -1 ) {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  // 3. if any node beside endnodes are found:
+  if ( numnodes > ( 1 + isclosed ? 0 : 1 ) )
+  {{
+    GEOSGeometry *oarea, *narea;
+    const GEOSPreparedGeometry *oareap, *nareap;
+
+    _rtt_EnsureGeos(iface->ctx);
+
+    oarea = _rtt_EdgeMotionArea(iface->ctx, oldedge->geom, isclosed);
+    if ( ! oarea )
+    {
+      _rtt_release_edges(iface->ctx, oldedge, 1);
+      rterror(iface->ctx, "Could not compute edge motion area for old edge");
+      return -1;
+    }
+
+    narea = _rtt_EdgeMotionArea(iface->ctx, geom, isclosed);
+    if ( ! narea )
+    {
+      GEOSGeom_destroy_r(iface->ctx->gctx, oarea);
+      _rtt_release_edges(iface->ctx, oldedge, 1);
+      rterror(iface->ctx, "Could not compute edge motion area for new edge");
+      return -1;
+    }
+
+    //   3.2. bail out if any node is in one and not the other
+    oareap = GEOSPrepare_r(iface->ctx->gctx,  oarea );
+    nareap = GEOSPrepare_r(iface->ctx->gctx,  narea );
+    for (i=0; i<numnodes; ++i)
+    {
+      RTT_ISO_NODE *n = &(nodes[i]);
+      GEOSGeometry *ngg;
+      int ocont, ncont;
+      size_t sz;
+      char *wkt;
+      if ( n->node_id == oldedge->start_node ) continue;
+      if ( n->node_id == oldedge->end_node ) continue;
+      ngg = RTGEOM2GEOS(iface->ctx,  rtpoint_as_rtgeom(iface->ctx, n->geom) , 0);
+      ocont = GEOSPreparedContains_r(iface->ctx->gctx,  oareap, ngg );
+      ncont = GEOSPreparedContains_r(iface->ctx->gctx,  nareap, ngg );
+      GEOSGeom_destroy_r(iface->ctx->gctx, ngg);
+      if (ocont == 2 || ncont == 2)
+      {
+        _rtt_release_nodes(iface->ctx, nodes, numnodes);
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, oareap);
+        GEOSGeom_destroy_r(iface->ctx->gctx, oarea);
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, nareap);
+        GEOSGeom_destroy_r(iface->ctx->gctx, narea);
+        rterror(iface->ctx, "GEOS exception on PreparedContains: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -1;
+      }
+      if (ocont != ncont)
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, oareap);
+        GEOSGeom_destroy_r(iface->ctx->gctx, oarea);
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, nareap);
+        GEOSGeom_destroy_r(iface->ctx->gctx, narea);
+        wkt = rtgeom_to_wkt(iface->ctx, rtpoint_as_rtgeom(iface->ctx, n->geom), RTWKT_ISO, 6, &sz);
+        _rtt_release_nodes(iface->ctx, nodes, numnodes);
+        rterror(iface->ctx, "Edge motion collision at %s", wkt);
+        rtfree(iface->ctx, wkt); /* would not necessarely reach this point */
+        return -1;
+      }
+    }
+    GEOSPreparedGeom_destroy_r(iface->ctx->gctx, oareap);
+    GEOSGeom_destroy_r(iface->ctx->gctx, oarea);
+    GEOSPreparedGeom_destroy_r(iface->ctx->gctx, nareap);
+    GEOSGeom_destroy_r(iface->ctx->gctx, narea);
+  }}
+  if ( numnodes ) _rtt_release_nodes(iface->ctx, nodes, numnodes);
+
+  RTDEBUG(1, "nodes containment check passed");
+
+  /*
+   * Check edge adjacency before
+   * TODO: can be optimized to gather azimuths of all edge ends once
+   */
+
+  edgeend span_pre, epan_pre;
+  /* initialize span_pre.myaz and epan_pre.myaz with existing edge */
+  i = _rtt_InitEdgeEndByLine(iface->ctx, &span_pre, &epan_pre,
+                             oldedge->geom, &p1, &p2);
+  if ( i ) return -1; /* rterror should have been raised */
+  _rtt_FindAdjacentEdges( topo, oldedge->start_node, &span_pre,
+                                  isclosed ? &epan_pre : NULL, edge_id );
+  _rtt_FindAdjacentEdges( topo, oldedge->end_node, &epan_pre,
+                                  isclosed ? &span_pre : NULL, edge_id );
+
+  RTDEBUGF(1, "edges adjacent to old edge are %" RTTFMT_ELEMID
+              " and % (first point), %" RTTFMT_ELEMID
+              " and % (last point)" RTTFMT_ELEMID,
+              span_pre.nextCW, span_pre.nextCCW,
+              epan_pre.nextCW, epan_pre.nextCCW);
+
+  /* update edge geometry */
+  newedge.edge_id = edge_id;
+  newedge.geom = geom;
+  i = rtt_be_updateEdgesById(topo, &newedge, 1, RTT_COL_EDGE_GEOM);
+  if ( i == -1 )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( ! i )
+  {
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Unexpected error: %d edges updated when expecting 1", i);
+    return -1;
+  }
+
+  /*
+   * Check edge adjacency after
+   */
+  edgeend span_post, epan_post;
+  i = _rtt_InitEdgeEndByLine(iface->ctx, &span_post, &epan_post, geom, &p1, &p2);
+  if ( i ) return -1; /* rterror should have been raised */
+  /* initialize epan_post.myaz and epan_post.myaz */
+  i = _rtt_InitEdgeEndByLine(iface->ctx, &span_post, &epan_post,
+                             geom, &p1, &p2);
+  if ( i ) return -1; /* rterror should have been raised */
+  _rtt_FindAdjacentEdges( topo, oldedge->start_node, &span_post,
+                          isclosed ? &epan_post : NULL, edge_id );
+  _rtt_FindAdjacentEdges( topo, oldedge->end_node, &epan_post,
+                          isclosed ? &span_post : NULL, edge_id );
+
+  RTDEBUGF(1, "edges adjacent to new edge are %" RTTFMT_ELEMID
+              " and %" RTTFMT_ELEMID
+              " (first point), %" RTTFMT_ELEMID
+              " and % (last point)" RTTFMT_ELEMID,
+              span_pre.nextCW, span_pre.nextCCW,
+              epan_pre.nextCW, epan_pre.nextCCW);
+
+
+  /* Bail out if next CW or CCW edge on start node changed */
+  if ( span_pre.nextCW != span_post.nextCW ||
+       span_pre.nextCCW != span_post.nextCCW )
+  {{
+    RTT_ELEMID nid = oldedge->start_node;
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Edge changed disposition around start node %"
+            RTTFMT_ELEMID, nid);
+    return -1;
+  }}
+
+  /* Bail out if next CW or CCW edge on end node changed */
+  if ( epan_pre.nextCW != epan_post.nextCW ||
+       epan_pre.nextCCW != epan_post.nextCCW )
+  {{
+    RTT_ELEMID nid = oldedge->end_node;
+    _rtt_release_edges(iface->ctx, oldedge, 1);
+    rterror(iface->ctx, "Edge changed disposition around end node %"
+            RTTFMT_ELEMID, nid);
+    return -1;
+  }}
+
+  /*
+  -- Update faces MBR of left and right faces
+  -- TODO: think about ways to optimize this part, like see if
+  --       the old edge geometry partecipated in the definition
+  --       of the current MBR (for shrinking) or the new edge MBR
+  --       would be larger than the old face MBR...
+  --
+  */
+  int facestoupdate = 0;
+  RTT_ISO_FACE faces[2];
+  RTGEOM *nface1 = NULL;
+  RTGEOM *nface2 = NULL;
+  if ( oldedge->face_left != 0 )
+  {
+    nface1 = rtt_GetFaceGeometry(topo, oldedge->face_left);
+#if 0
+    {
+    size_t sz;
+    char *wkt = rtgeom_to_wkt(iface->ctx, nface1, RTWKT_EXTENDED, 2, &sz);
+    RTDEBUGF(1, "new geometry of face left (%d): %s", (int)oldedge->face_left, wkt);
+    rtfree(iface->ctx, wkt);
+    }
+#endif
+    rtgeom_add_bbox(iface->ctx, nface1);
+    faces[facestoupdate].face_id = oldedge->face_left;
+    /* ownership left to nface */
+    faces[facestoupdate++].mbr = nface1->bbox;
+  }
+  if ( oldedge->face_right != 0
+       /* no need to update twice the same face.. */
+       && oldedge->face_right != oldedge->face_left )
+  {
+    nface2 = rtt_GetFaceGeometry(topo, oldedge->face_right);
+#if 0
+    {
+    size_t sz;
+    char *wkt = rtgeom_to_wkt(iface->ctx, nface2, RTWKT_EXTENDED, 2, &sz);
+    RTDEBUGF(1, "new geometry of face right (%d): %s", (int)oldedge->face_right, wkt);
+    rtfree(iface->ctx, wkt);
+    }
+#endif
+    rtgeom_add_bbox(iface->ctx, nface2);
+    faces[facestoupdate].face_id = oldedge->face_right;
+    faces[facestoupdate++].mbr = nface2->bbox; /* ownership left to nface */
+  }
+  RTDEBUGF(1, "%d faces to update", facestoupdate);
+  if ( facestoupdate )
+  {
+    i = rtt_be_updateFacesById( topo, &(faces[0]), facestoupdate );
+    if ( i != facestoupdate )
+    {
+      if ( nface1 ) rtgeom_free(iface->ctx, nface1);
+      if ( nface2 ) rtgeom_free(iface->ctx, nface2);
+      _rtt_release_edges(iface->ctx, oldedge, 1);
+      if ( i == -1 )
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      else
+        rterror(iface->ctx, "Unexpected error: %d faces found when expecting 1", i);
+      return -1;
+    }
+  }
+  if ( nface1 ) rtgeom_free(iface->ctx, nface1);
+  if ( nface2 ) rtgeom_free(iface->ctx, nface2);
+
+  RTDEBUG(1, "all done, cleaning up edges");
+
+  _rtt_release_edges(iface->ctx, oldedge, 1);
+  return 0; /* success */
+}
+
+/* Only return CONTAINING_FACE in the node object */
+static RTT_ISO_NODE *
+_rtt_GetIsoNode(RTT_TOPOLOGY* topo, RTT_ELEMID nid)
+{
+  RTT_ISO_NODE *node;
+  int n = 1;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  node = rtt_be_getNodeById( topo, &nid, &n, RTT_COL_NODE_CONTAINING_FACE );
+  if ( n < 0 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return 0;
+  }
+  if ( n < 1 ) {
+    rterror(iface->ctx, "SQL/MM Spatial exception - non-existent node");
+    return 0;
+  }
+  if ( node->containing_face == -1 )
+  {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "SQL/MM Spatial exception - not isolated node");
+    return 0;
+  }
+
+  return node;
+}
+
+int
+rtt_MoveIsoNode(RTT_TOPOLOGY* topo, RTT_ELEMID nid, RTPOINT *pt)
+{
+  RTT_ISO_NODE *node;
+  int ret;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  node = _rtt_GetIsoNode( topo, nid );
+  if ( ! node ) return -1;
+
+  if ( rtt_be_ExistsCoincidentNode(topo, pt) )
+  {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "SQL/MM Spatial exception - coincident node");
+    return -1;
+  }
+
+  if ( rtt_be_ExistsEdgeIntersectingPoint(topo, pt) )
+  {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "SQL/MM Spatial exception - edge crosses node.");
+    return -1;
+  }
+
+  /* TODO: check that the new point is in the same containing face !
+   * See https://trac.osgeo.org/postgis/ticket/3232
+   */
+
+  node->node_id = nid;
+  node->geom = pt;
+  ret = rtt_be_updateNodesById(topo, node, 1,
+                               RTT_COL_NODE_GEOM);
+  if ( ret == -1 ) {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  rtfree(iface->ctx, node);
+  return 0;
+}
+
+int
+rtt_RemoveIsoNode(RTT_TOPOLOGY* topo, RTT_ELEMID nid)
+{
+  RTT_ISO_NODE *node;
+  int n = 1;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  node = _rtt_GetIsoNode( topo, nid );
+  if ( ! node ) return -1;
+
+  n = rtt_be_deleteNodesById( topo, &nid, n );
+  if ( n == -1 )
+  {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( n != 1 )
+  {
+    rtfree(iface->ctx, node);
+    rterror(iface->ctx, "Unexpected error: %d nodes deleted when expecting 1", n);
+    return -1;
+  }
+
+  /* TODO: notify to caller about node being removed ?
+   * See https://trac.osgeo.org/postgis/ticket/3231
+   */
+
+  rtfree(iface->ctx, node);
+  return 0; /* success */
+}
+
+int
+rtt_RemIsoEdge(RTT_TOPOLOGY* topo, RTT_ELEMID id)
+{
+  RTT_ISO_EDGE deledge;
+  RTT_ISO_EDGE *edge;
+  RTT_ELEMID nid[2];
+  RTT_ISO_NODE upd_node[2];
+  RTT_ELEMID containing_face;
+  int n = 1;
+  int i;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  edge = rtt_be_getEdgeById( topo, &id, &n, RTT_COL_EDGE_START_NODE|
+                                            RTT_COL_EDGE_END_NODE |
+                                            RTT_COL_EDGE_FACE_LEFT |
+                                            RTT_COL_EDGE_FACE_RIGHT );
+  if ( ! edge )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( ! n )
+  {
+    rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge");
+    return -1;
+  }
+  if ( n > 1 )
+  {
+    rtfree(iface->ctx, edge);
+    rterror(iface->ctx, "Corrupted topology: more than a single edge have id %"
+            RTTFMT_ELEMID, id);
+    return -1;
+  }
+
+  if ( edge[0].face_left != edge[0].face_right )
+  {
+    rtfree(iface->ctx, edge);
+    rterror(iface->ctx, "SQL/MM Spatial exception - not isolated edge");
+    return -1;
+  }
+  containing_face = edge[0].face_left;
+
+  nid[0] = edge[0].start_node;
+  nid[1] = edge[0].end_node;
+  rtfree(iface->ctx, edge);
+
+  n = 2;
+  edge = rtt_be_getEdgeByNode( topo, nid, &n, RTT_COL_EDGE_EDGE_ID );
+  if ( n == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<n; ++i )
+  {
+    if ( edge[i].edge_id == id ) continue;
+    rtfree(iface->ctx, edge);
+    rterror(iface->ctx, "SQL/MM Spatial exception - not isolated edge");
+    return -1;
+  }
+  if ( edge ) rtfree(iface->ctx, edge);
+
+  deledge.edge_id = id;
+  n = rtt_be_deleteEdges( topo, &deledge, RTT_COL_EDGE_EDGE_ID );
+  if ( n == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( n != 1 )
+  {
+    rterror(iface->ctx, "Unexpected error: %d edges deleted when expecting 1", n);
+    return -1;
+  }
+
+  upd_node[0].node_id = nid[0];
+  upd_node[0].containing_face = containing_face;
+  n = 1;
+  if ( nid[1] != nid[0] ) {
+    upd_node[1].node_id = nid[1];
+    upd_node[1].containing_face = containing_face;
+    ++n;
+  }
+  n = rtt_be_updateNodesById(topo, upd_node, n,
+                               RTT_COL_NODE_CONTAINING_FACE);
+  if ( n == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* TODO: notify to caller about edge being removed ?
+   * See https://trac.osgeo.org/postgis/ticket/3248
+   */
+
+  return 0; /* success */
+}
+
+/* Used by _rtt_RemEdge to update edge face ref on healing
+ *
+ * @param of old face id (never 0 as you cannot remove face 0)
+ * @param nf new face id
+ * @return 0 on success, -1 on backend error
+ */
+static int
+_rtt_UpdateEdgeFaceRef( RTT_TOPOLOGY *topo, RTT_ELEMID of, RTT_ELEMID nf)
+{
+  RTT_ISO_EDGE sel_edge, upd_edge;
+  int ret;
+
+  assert( of != 0 );
+
+  /* Update face_left for all edges still referencing old face */
+  sel_edge.face_left = of;
+  upd_edge.face_left = nf;
+  ret = rtt_be_updateEdges(topo, &sel_edge, RTT_COL_EDGE_FACE_LEFT,
+                                 &upd_edge, RTT_COL_EDGE_FACE_LEFT,
+                                 NULL, 0);
+  if ( ret == -1 ) return -1;
+
+  /* Update face_right for all edges still referencing old face */
+  sel_edge.face_right = of;
+  upd_edge.face_right = nf;
+  ret = rtt_be_updateEdges(topo, &sel_edge, RTT_COL_EDGE_FACE_RIGHT,
+                                 &upd_edge, RTT_COL_EDGE_FACE_RIGHT,
+                                 NULL, 0);
+  if ( ret == -1 ) return -1;
+
+  return 0;
+}
+
+/* Used by _rtt_RemEdge to update node face ref on healing
+ *
+ * @param of old face id (never 0 as you cannot remove face 0)
+ * @param nf new face id
+ * @return 0 on success, -1 on backend error
+ */
+static int
+_rtt_UpdateNodeFaceRef( RTT_TOPOLOGY *topo, RTT_ELEMID of, RTT_ELEMID nf)
+{
+  RTT_ISO_NODE sel, upd;
+  int ret;
+
+  assert( of != 0 );
+
+  /* Update face_left for all edges still referencing old face */
+  sel.containing_face = of;
+  upd.containing_face = nf;
+  ret = rtt_be_updateNodes(topo, &sel, RTT_COL_NODE_CONTAINING_FACE,
+                                 &upd, RTT_COL_NODE_CONTAINING_FACE,
+                                 NULL, 0);
+  if ( ret == -1 ) return -1;
+
+  return 0;
+}
+
+/* Used by rtt_RemEdgeModFace and rtt_RemEdgeNewFaces
+ *
+ * Returns -1 on error, identifier of the face that takes up the space
+ * previously occupied by the removed edge if modFace is 1, identifier of
+ * the created face (0 if none) if modFace is 0.
+ */
+static RTT_ELEMID
+_rtt_RemEdge(RTT_TOPOLOGY* topo, RTT_ELEMID edge_id, int modFace )
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  int i, nedges, nfaces, fields;
+  RTT_ISO_EDGE *edge = NULL;
+  RTT_ISO_EDGE *upd_edge = NULL;
+  RTT_ISO_EDGE upd_edge_left[2];
+  int nedge_left = 0;
+  RTT_ISO_EDGE upd_edge_right[2];
+  int nedge_right = 0;
+  RTT_ISO_NODE upd_node[2];
+  int nnode = 0;
+  RTT_ISO_FACE *faces = NULL;
+  RTT_ISO_FACE newface;
+  RTT_ELEMID node_ids[2];
+  RTT_ELEMID face_ids[2];
+  int fnode_edges = 0; /* number of edges on the first node (excluded
+                        * the one being removed ) */
+  int lnode_edges = 0; /* number of edges on the last node (excluded
+                        * the one being removed ) */
+
+  newface.face_id = 0;
+
+  i = 1;
+  edge = rtt_be_getEdgeById(topo, &edge_id, &i, RTT_COL_EDGE_ALL);
+  if ( ! edge )
+  {
+    RTDEBUGF(1, "rtt_be_getEdgeById returned NULL and set i=%d", i);
+    if ( i == -1 )
+    {
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    else if ( i == 0 )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge %"
+              RTTFMT_ELEMID, edge_id);
+      return -1;
+    }
+    else
+    {
+      rterror(iface->ctx, "Backend coding error: getEdgeById callback returned NULL "
+              "but numelements output parameter has value %d "
+              "(expected 0 or 1)", i);
+      return -1;
+    }
+  }
+
+  if ( ! rtt_be_checkTopoGeomRemEdge(topo, edge_id,
+                                     edge->face_left, edge->face_right) )
+  {
+    rterror(iface->ctx, "%s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  RTDEBUG(1, "Updating next_{right,left}_face of ring edges...");
+
+  /* Update edge linking */
+
+  nedges = 0;
+  node_ids[nedges++] = edge->start_node;
+  if ( edge->end_node != edge->start_node )
+  {
+    node_ids[nedges++] = edge->end_node;
+  }
+  fields = RTT_COL_EDGE_EDGE_ID | RTT_COL_EDGE_START_NODE |
+           RTT_COL_EDGE_END_NODE | RTT_COL_EDGE_NEXT_LEFT |
+           RTT_COL_EDGE_NEXT_RIGHT;
+  upd_edge = rtt_be_getEdgeByNode( topo, &(node_ids[0]), &nedges, fields );
+  if ( nedges == -1 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  nedge_left = nedge_right = 0;
+  for ( i=0; i<nedges; ++i )
+  {
+    RTT_ISO_EDGE *e = &(upd_edge[i]);
+    if ( e->edge_id == edge_id ) continue;
+    if ( e->start_node == edge->start_node || e->end_node == edge->start_node )
+    {
+      ++fnode_edges;
+    }
+    if ( e->start_node == edge->end_node || e->end_node == edge->end_node )
+    {
+      ++lnode_edges;
+    }
+    if ( e->next_left == -edge_id )
+    {
+      upd_edge_left[nedge_left].edge_id = e->edge_id;
+      upd_edge_left[nedge_left++].next_left =
+        edge->next_left != edge_id ? edge->next_left : edge->next_right;
+    }
+    else if ( e->next_left == edge_id )
+    {
+      upd_edge_left[nedge_left].edge_id = e->edge_id;
+      upd_edge_left[nedge_left++].next_left =
+        edge->next_right != -edge_id ? edge->next_right : edge->next_left;
+    }
+
+    if ( e->next_right == -edge_id )
+    {
+      upd_edge_right[nedge_right].edge_id = e->edge_id;
+      upd_edge_right[nedge_right++].next_right =
+        edge->next_left != edge_id ? edge->next_left : edge->next_right;
+    }
+    else if ( e->next_right == edge_id )
+    {
+      upd_edge_right[nedge_right].edge_id = e->edge_id;
+      upd_edge_right[nedge_right++].next_right =
+        edge->next_right != -edge_id ? edge->next_right : edge->next_left;
+    }
+  }
+
+  if ( nedge_left )
+  {
+    RTDEBUGF(1, "updating %d 'next_left' edges", nedge_left);
+    /* update edges in upd_edge_left set next_left */
+    i = rtt_be_updateEdgesById(topo, &(upd_edge_left[0]), nedge_left,
+                               RTT_COL_EDGE_NEXT_LEFT);
+    if ( i == -1 )
+    {
+      _rtt_release_edges(iface->ctx, edge, 1);
+      rtfree(iface->ctx, upd_edge);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+  if ( nedge_right )
+  {
+    RTDEBUGF(1, "updating %d 'next_right' edges", nedge_right);
+    /* update edges in upd_edge_right set next_right */
+    i = rtt_be_updateEdgesById(topo, &(upd_edge_right[0]), nedge_right,
+                               RTT_COL_EDGE_NEXT_RIGHT);
+    if ( i == -1 )
+    {
+      _rtt_release_edges(iface->ctx, edge, 1);
+      rtfree(iface->ctx, upd_edge);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+  RTDEBUGF(1, "releasing %d updateable edges in %p", nedges, upd_edge);
+  rtfree(iface->ctx, upd_edge);
+
+  /* Id of face that will take up all the space previously
+   * taken by left and right faces of the edge */
+  RTT_ELEMID floodface;
+
+  /* Find floodface, and update its mbr if != 0 */
+  if ( edge->face_left == edge->face_right )
+  {
+    floodface = edge->face_right;
+  }
+  else
+  {
+    /* Two faces healed */
+    if ( edge->face_left == 0 || edge->face_right == 0 )
+    {
+      floodface = 0;
+      RTDEBUG(1, "floodface is universe");
+    }
+    else
+    {
+      /* we choose right face as the face that will remain
+       * to be symmetric with ST_AddEdgeModFace */
+      floodface = edge->face_right;
+      RTDEBUGF(1, "floodface is %" RTTFMT_ELEMID, floodface);
+      /* update mbr of floodface as union of mbr of both faces */
+      face_ids[0] = edge->face_left;
+      face_ids[1] = edge->face_right;
+      nfaces = 2;
+      fields = RTT_COL_FACE_ALL;
+      faces = rtt_be_getFaceById(topo, face_ids, &nfaces, fields);
+      if ( nfaces == -1 ) {
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+      RTGBOX *box1=NULL;
+      RTGBOX *box2=NULL;
+      for ( i=0; i<nfaces; ++i )
+      {
+        if ( faces[i].face_id == edge->face_left )
+        {
+          if ( ! box1 ) box1 = faces[i].mbr;
+          else
+          {
+            i = edge->face_left;
+            _rtt_release_edges(iface->ctx, edge, 1);
+            _rtt_release_faces(iface->ctx, faces, nfaces);
+            rterror(iface->ctx, "corrupted topology: more than 1 face have face_id=%"
+                    RTTFMT_ELEMID, i);
+            return -1;
+          }
+        }
+        else if ( faces[i].face_id == edge->face_right )
+        {
+          if ( ! box2 ) box2 = faces[i].mbr;
+          else
+          {
+            i = edge->face_right;
+            _rtt_release_edges(iface->ctx, edge, 1);
+            _rtt_release_faces(iface->ctx, faces, nfaces);
+            rterror(iface->ctx, "corrupted topology: more than 1 face have face_id=%"
+                    RTTFMT_ELEMID, i);
+            return -1;
+          }
+        }
+        else
+        {
+          i = faces[i].face_id;
+          _rtt_release_edges(iface->ctx, edge, 1);
+          _rtt_release_faces(iface->ctx, faces, nfaces);
+          rterror(iface->ctx, "Backend coding error: getFaceById returned face "
+                  "with non-requested id %" RTTFMT_ELEMID, i);
+          return -1;
+        }
+      }
+      if ( ! box1 ) {
+        i = edge->face_left;
+        _rtt_release_edges(iface->ctx, edge, 1);
+        _rtt_release_faces(iface->ctx, faces, nfaces);
+        rterror(iface->ctx, "corrupted topology: no face have face_id=%"
+                RTTFMT_ELEMID " (left face for edge %"
+                RTTFMT_ELEMID ")", i, edge_id);
+        return -1;
+      }
+      if ( ! box2 ) {
+        i = edge->face_right;
+        _rtt_release_edges(iface->ctx, edge, 1);
+        _rtt_release_faces(iface->ctx, faces, nfaces);
+        rterror(iface->ctx, "corrupted topology: no face have face_id=%"
+                RTTFMT_ELEMID " (right face for edge %"
+                RTTFMT_ELEMID ")", i, edge_id);
+        return -1;
+      }
+      gbox_merge(iface->ctx, box2, box1); /* box1 is now the union of the two */
+      newface.mbr = box1;
+      if ( modFace )
+      {
+        newface.face_id = floodface;
+        i = rtt_be_updateFacesById( topo, &newface, 1 );
+        _rtt_release_faces(iface->ctx, faces, 2);
+        if ( i == -1 )
+        {
+          _rtt_release_edges(iface->ctx, edge, 1);
+          rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+          return -1;
+        }
+        if ( i != 1 )
+        {
+          _rtt_release_edges(iface->ctx, edge, 1);
+          rterror(iface->ctx, "Unexpected error: %d faces updated when expecting 1", i);
+          return -1;
+        }
+      }
+      else
+      {
+        /* New face replaces the old two faces */
+        newface.face_id = -1;
+        i = rtt_be_insertFaces( topo, &newface, 1 );
+        _rtt_release_faces(iface->ctx, faces, 2);
+        if ( i == -1 )
+        {
+          _rtt_release_edges(iface->ctx, edge, 1);
+          rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+          return -1;
+        }
+        if ( i != 1 )
+        {
+          _rtt_release_edges(iface->ctx, edge, 1);
+          rterror(iface->ctx, "Unexpected error: %d faces inserted when expecting 1", i);
+          return -1;
+        }
+        floodface = newface.face_id;
+      }
+    }
+
+    /* Update face references for edges and nodes still referencing
+     * the removed face(s) */
+
+    if ( edge->face_left != floodface )
+    {
+      if ( -1 == _rtt_UpdateEdgeFaceRef(topo, edge->face_left, floodface) )
+      {
+        _rtt_release_edges(iface->ctx, edge, 1);
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+      if ( -1 == _rtt_UpdateNodeFaceRef(topo, edge->face_left, floodface) )
+      {
+        _rtt_release_edges(iface->ctx, edge, 1);
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+    }
+
+    if ( edge->face_right != floodface )
+    {
+      if ( -1 == _rtt_UpdateEdgeFaceRef(topo, edge->face_right, floodface) )
+      {
+        _rtt_release_edges(iface->ctx, edge, 1);
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+      if ( -1 == _rtt_UpdateNodeFaceRef(topo, edge->face_right, floodface) )
+      {
+        _rtt_release_edges(iface->ctx, edge, 1);
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+    }
+
+    /* Update topogeoms on heal */
+    if ( ! rtt_be_updateTopoGeomFaceHeal(topo,
+                                  edge->face_right, edge->face_left,
+                                  floodface) )
+    {
+      _rtt_release_edges(iface->ctx, edge, 1);
+      rterror(iface->ctx, "%s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  } /* two faces healed */
+
+  /* Delete the edge */
+  i = rtt_be_deleteEdges(topo, edge, RTT_COL_EDGE_EDGE_ID);
+  if ( i == -1 ) {
+    _rtt_release_edges(iface->ctx, edge, 1);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* If any of the edge nodes remained isolated, set
+   * containing_face = floodface
+   */
+  if ( ! fnode_edges )
+  {
+    upd_node[nnode].node_id = edge->start_node;
+    upd_node[nnode].containing_face = floodface;
+    ++nnode;
+  }
+  if ( edge->end_node != edge->start_node && ! lnode_edges )
+  {
+    upd_node[nnode].node_id = edge->end_node;
+    upd_node[nnode].containing_face = floodface;
+    ++nnode;
+  }
+  if ( nnode )
+  {
+    i = rtt_be_updateNodesById(topo, upd_node, nnode,
+                               RTT_COL_NODE_CONTAINING_FACE);
+    if ( i == -1 ) {
+      _rtt_release_edges(iface->ctx, edge, 1);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  if ( edge->face_left != edge->face_right )
+  /* or there'd be no face to remove */
+  {
+    RTT_ELEMID ids[2];
+    int nids = 0;
+    if ( edge->face_right != floodface )
+      ids[nids++] = edge->face_right;
+    if ( edge->face_left != floodface )
+      ids[nids++] = edge->face_left;
+    i = rtt_be_deleteFacesById(topo, ids, nids);
+    if ( i == -1 ) {
+      _rtt_release_edges(iface->ctx, edge, 1);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  _rtt_release_edges(iface->ctx, edge, 1);
+  return modFace ? floodface : newface.face_id;
+}
+
+RTT_ELEMID
+rtt_RemEdgeModFace( RTT_TOPOLOGY* topo, RTT_ELEMID edge_id )
+{
+  return _rtt_RemEdge( topo, edge_id, 1 );
+}
+
+RTT_ELEMID
+rtt_RemEdgeNewFace( RTT_TOPOLOGY* topo, RTT_ELEMID edge_id )
+{
+  return _rtt_RemEdge( topo, edge_id, 0 );
+}
+
+static RTT_ELEMID
+_rtt_HealEdges( RTT_TOPOLOGY* topo, RTT_ELEMID eid1, RTT_ELEMID eid2,
+                int modEdge )
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTT_ELEMID ids[2];
+  RTT_ELEMID commonnode = -1;
+  int caseno = 0;
+  RTT_ISO_EDGE *node_edges;
+  int num_node_edges;
+  RTT_ISO_EDGE *edges;
+  RTT_ISO_EDGE *e1 = NULL;
+  RTT_ISO_EDGE *e2 = NULL;
+  RTT_ISO_EDGE newedge, updedge, seledge;
+  int nedges, i;
+  int e1freenode;
+  int e2sign, e2freenode;
+  RTPOINTARRAY *pa;
+  char buf[256];
+  char *ptr;
+  size_t bufleft = 256;
+
+  ptr = buf;
+
+  /* NOT IN THE SPECS: see if the same edge is given twice.. */
+  if ( eid1 == eid2 )
+  {
+    rterror(iface->ctx, "Cannot heal edge %" RTTFMT_ELEMID
+            " with itself, try with another", eid1);
+    return -1;
+  }
+  ids[0] = eid1;
+  ids[1] = eid2;
+  nedges = 2;
+  edges = rtt_be_getEdgeById(topo, ids, &nedges, RTT_COL_EDGE_ALL);
+  if ( nedges == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for ( i=0; i<nedges; ++i )
+  {
+    if ( edges[i].edge_id == eid1 ) {
+      if ( e1 ) {
+        _rtt_release_edges(iface->ctx, edges, nedges);
+        rterror(iface->ctx, "Corrupted topology: multiple edges have id %"
+                RTTFMT_ELEMID, eid1);
+        return -1;
+      }
+      e1 = &(edges[i]);
+    }
+    else if ( edges[i].edge_id == eid2 ) {
+      if ( e2 ) {
+        _rtt_release_edges(iface->ctx, edges, nedges);
+        rterror(iface->ctx, "Corrupted topology: multiple edges have id %"
+                RTTFMT_ELEMID, eid2);
+        return -1;
+      }
+      e2 = &(edges[i]);
+    }
+  }
+  if ( ! e1 )
+  {
+    if ( edges ) _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge %"
+            RTTFMT_ELEMID, eid1);
+    return -1;
+  }
+  if ( ! e2 )
+  {
+    if ( edges ) _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "SQL/MM Spatial exception - non-existent edge %"
+            RTTFMT_ELEMID, eid2);
+    return -1;
+  }
+
+  /* NOT IN THE SPECS: See if any of the two edges are closed. */
+  if ( e1->start_node == e1->end_node )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "Edge %" RTTFMT_ELEMID " is closed, cannot heal to edge %"
+            RTTFMT_ELEMID, eid1, eid2);
+    return -1;
+  }
+  if ( e2->start_node == e2->end_node )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "Edge %" RTTFMT_ELEMID " is closed, cannot heal to edge %"
+            RTTFMT_ELEMID, eid2, eid1);
+    return -1;
+  }
+
+  /* Find common node */
+
+  if ( e1->end_node == e2->start_node )
+  {
+    commonnode = e1->end_node;
+    caseno = 1;
+  }
+  else if ( e1->end_node == e2->end_node )
+  {
+    commonnode = e1->end_node;
+    caseno = 2;
+  }
+  /* Check if any other edge is connected to the common node, if found */
+  if ( commonnode != -1 )
+  {
+    num_node_edges = 1;
+    node_edges = rtt_be_getEdgeByNode( topo, &commonnode,
+                                       &num_node_edges, RTT_COL_EDGE_EDGE_ID );
+    if ( num_node_edges == -1 ) {
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    for (i=0; i<num_node_edges; ++i)
+    {
+      int r;
+      if ( node_edges[i].edge_id == eid1 ) continue;
+      if ( node_edges[i].edge_id == eid2 ) continue;
+      commonnode = -1;
+      /* append to string, for error message */
+      if ( bufleft ) {
+        r = snprintf(ptr, bufleft, "%s%" RTTFMT_ELEMID,
+                     ( ptr==buf ? "" : "," ), node_edges[i].edge_id);
+        if ( r >= bufleft )
+        {
+          bufleft = 0;
+          buf[252] = '.';
+          buf[253] = '.';
+          buf[254] = '.';
+          buf[255] = '\0';
+        }
+        else
+        {
+          bufleft -= r;
+          ptr += r;
+        }
+      }
+    }
+    rtfree(iface->ctx, node_edges);
+  }
+
+  if ( commonnode == -1 )
+  {
+    if ( e1->start_node == e2->start_node )
+    {
+      commonnode = e1->start_node;
+      caseno = 3;
+    }
+    else if ( e1->start_node == e2->end_node )
+    {
+      commonnode = e1->start_node;
+      caseno = 4;
+    }
+    /* Check if any other edge is connected to the common node, if found */
+    if ( commonnode != -1 )
+    {
+      num_node_edges = 1;
+      node_edges = rtt_be_getEdgeByNode( topo, &commonnode,
+                                         &num_node_edges, RTT_COL_EDGE_EDGE_ID );
+      if ( num_node_edges == -1 ) {
+        _rtt_release_edges(iface->ctx, edges, nedges);
+        rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+        return -1;
+      }
+      for (i=0; i<num_node_edges; ++i)
+      {
+        int r;
+        if ( node_edges[i].edge_id == eid1 ) continue;
+        if ( node_edges[i].edge_id == eid2 ) continue;
+        commonnode = -1;
+        /* append to string, for error message */
+        if ( bufleft ) {
+          r = snprintf(ptr, bufleft, "%s%" RTTFMT_ELEMID,
+                       ( ptr==buf ? "" : "," ), node_edges[i].edge_id);
+          if ( r >= bufleft )
+          {
+            bufleft = 0;
+            buf[252] = '.';
+            buf[253] = '.';
+            buf[254] = '.';
+            buf[255] = '\0';
+          }
+          else
+          {
+            bufleft -= r;
+            ptr += r;
+          }
+        }
+      }
+      if ( num_node_edges ) rtfree(iface->ctx, node_edges);
+    }
+  }
+
+  if ( commonnode == -1 )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    if ( ptr != buf )
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - other edges connected (%s)",
+              buf);
+    }
+    else
+    {
+      rterror(iface->ctx, "SQL/MM Spatial exception - non-connected edges");
+    }
+    return -1;
+  }
+
+  if ( ! rtt_be_checkTopoGeomRemNode(topo, commonnode,
+                                     eid1, eid2 ) )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "%s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* Construct the geometry of the new edge */
+  switch (caseno)
+  {
+    case 1: /* e1.end = e2.start */
+      pa = ptarray_clone_deep(iface->ctx, e1->geom->points);
+      //pa = ptarray_merge(iface->ctx, pa, e2->geom->points);
+      ptarray_append_ptarray(iface->ctx, pa, e2->geom->points, 0);
+      newedge.start_node = e1->start_node;
+      newedge.end_node = e2->end_node;
+      newedge.next_left = e2->next_left;
+      newedge.next_right = e1->next_right;
+      e1freenode = 1;
+      e2freenode = -1;
+      e2sign = 1;
+      break;
+    case 2: /* e1.end = e2.end */
+    {
+      RTPOINTARRAY *pa2;
+      pa2 = ptarray_clone_deep(iface->ctx, e2->geom->points);
+      ptarray_reverse(iface->ctx, pa2);
+      pa = ptarray_clone_deep(iface->ctx, e1->geom->points);
+      //pa = ptarray_merge(iface->ctx, e1->geom->points, pa);
+      ptarray_append_ptarray(iface->ctx, pa, pa2, 0);
+      ptarray_free(iface->ctx, pa2);
+      newedge.start_node = e1->start_node;
+      newedge.end_node = e2->start_node;
+      newedge.next_left = e2->next_right;
+      newedge.next_right = e1->next_right;
+      e1freenode = 1;
+      e2freenode = 1;
+      e2sign = -1;
+      break;
+    }
+    case 3: /* e1.start = e2.start */
+      pa = ptarray_clone_deep(iface->ctx, e2->geom->points);
+      ptarray_reverse(iface->ctx, pa);
+      //pa = ptarray_merge(iface->ctx, pa, e1->geom->points);
+      ptarray_append_ptarray(iface->ctx, pa, e1->geom->points, 0);
+      newedge.end_node = e1->end_node;
+      newedge.start_node = e2->end_node;
+      newedge.next_left = e1->next_left;
+      newedge.next_right = e2->next_left;
+      e1freenode = -1;
+      e2freenode = -1;
+      e2sign = -1;
+      break;
+    case 4: /* e1.start = e2.end */
+      pa = ptarray_clone_deep(iface->ctx, e2->geom->points);
+      //pa = ptarray_merge(iface->ctx, pa, e1->geom->points);
+      ptarray_append_ptarray(iface->ctx, pa, e1->geom->points, 0);
+      newedge.end_node = e1->end_node;
+      newedge.start_node = e2->start_node;
+      newedge.next_left = e1->next_left;
+      newedge.next_right = e2->next_right;
+      e1freenode = -1;
+      e2freenode = 1;
+      e2sign = 1;
+      break;
+    default:
+      pa = NULL;
+      e1freenode = 0;
+      e2freenode = 0;
+      e2sign = 0;
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Coding error: caseno=%d should never happen", caseno);
+      break;
+  }
+  newedge.geom = rtline_construct(iface->ctx, topo->srid, NULL, pa);
+
+  if ( modEdge )
+  {
+    /* Update data of the first edge */
+    newedge.edge_id = eid1;
+    i = rtt_be_updateEdgesById(topo, &newedge, 1,
+                               RTT_COL_EDGE_NEXT_LEFT|
+                               RTT_COL_EDGE_NEXT_RIGHT |
+                               RTT_COL_EDGE_START_NODE |
+                               RTT_COL_EDGE_END_NODE |
+                               RTT_COL_EDGE_GEOM);
+    if ( i == -1 )
+    {
+      rtline_free(iface->ctx, newedge.geom);
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+    else if ( i != 1 )
+    {
+      rtline_free(iface->ctx, newedge.geom);
+      if ( edges ) _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Unexpected error: %d edges updated when expecting 1", i);
+      return -1;
+    }
+  }
+  else
+  {
+    /* Add new edge */
+    newedge.edge_id = -1;
+    newedge.face_left = e1->face_left;
+    newedge.face_right = e1->face_right;
+    i = rtt_be_insertEdges(topo, &newedge, 1);
+    if ( i == -1 ) {
+      rtline_free(iface->ctx, newedge.geom);
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    } else if ( i == 0 ) {
+      rtline_free(iface->ctx, newedge.geom);
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Insertion of split edge failed (no reason)");
+      return -1;
+    }
+  }
+  rtline_free(iface->ctx, newedge.geom);
+
+  /*
+  -- Update next_left_edge/next_right_edge for
+  -- any edge having them still pointing at the edge being removed
+  -- (eid2 only when modEdge, or both otherwise)
+  --
+  -- NOTE:
+  -- e#freenode is 1 when edge# end node was the common node
+  -- and -1 otherwise. This gives the sign of possibly found references
+  -- to its "free" (non connected to other edge) endnode.
+  -- e2sign is -1 if edge1 direction is opposite to edge2 direction,
+  -- or 1 otherwise.
+  --
+  */
+
+  /* update edges connected to e2's boundary from their end node */
+  seledge.next_left = e2freenode * eid2;
+  updedge.next_left = e2freenode * newedge.edge_id * e2sign;
+  i = rtt_be_updateEdges(topo, &seledge, RTT_COL_EDGE_NEXT_LEFT,
+                               &updedge, RTT_COL_EDGE_NEXT_LEFT,
+                               NULL, 0);
+  if ( i == -1 )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /* update edges connected to e2's boundary from their start node */
+  seledge.next_right = e2freenode * eid2;
+  updedge.next_right = e2freenode * newedge.edge_id * e2sign;
+  i = rtt_be_updateEdges(topo, &seledge, RTT_COL_EDGE_NEXT_RIGHT,
+                               &updedge, RTT_COL_EDGE_NEXT_RIGHT,
+                               NULL, 0);
+  if ( i == -1 )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  if ( ! modEdge )
+  {
+    /* update edges connected to e1's boundary from their end node */
+    seledge.next_left = e1freenode * eid1;
+    updedge.next_left = e1freenode * newedge.edge_id;
+    i = rtt_be_updateEdges(topo, &seledge, RTT_COL_EDGE_NEXT_LEFT,
+                                 &updedge, RTT_COL_EDGE_NEXT_LEFT,
+                                 NULL, 0);
+    if ( i == -1 )
+    {
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+
+    /* update edges connected to e1's boundary from their start node */
+    seledge.next_right = e1freenode * eid1;
+    updedge.next_right = e1freenode * newedge.edge_id;
+    i = rtt_be_updateEdges(topo, &seledge, RTT_COL_EDGE_NEXT_RIGHT,
+                                 &updedge, RTT_COL_EDGE_NEXT_RIGHT,
+                                 NULL, 0);
+    if ( i == -1 )
+    {
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  /* delete the edges (only second on modEdge or both) */
+  i = rtt_be_deleteEdges(topo, e2, RTT_COL_EDGE_EDGE_ID);
+  if ( i == -1 )
+  {
+    _rtt_release_edges(iface->ctx, edges, nedges);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( ! modEdge ) {
+    i = rtt_be_deleteEdges(topo, e1, RTT_COL_EDGE_EDGE_ID);
+    if ( i == -1 )
+    {
+      _rtt_release_edges(iface->ctx, edges, nedges);
+      rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+      return -1;
+    }
+  }
+
+  _rtt_release_edges(iface->ctx, edges, nedges);
+
+  /* delete the common node */
+  i = rtt_be_deleteNodesById( topo, &commonnode, 1 );
+  if ( i == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  /*
+  --
+  -- NOT IN THE SPECS:
+  -- Drop composition rows involving second
+  -- edge, as the first edge took its space,
+  -- and all affected TopoGeom have been previously checked
+  -- for being composed by both edges.
+  */
+  if ( ! rtt_be_updateTopoGeomEdgeHeal(topo,
+                                eid1, eid2, newedge.edge_id) )
+  {
+    rterror(iface->ctx, "%s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  return modEdge ? commonnode : newedge.edge_id;
+}
+
+RTT_ELEMID
+rtt_ModEdgeHeal( RTT_TOPOLOGY* topo, RTT_ELEMID e1, RTT_ELEMID e2 )
+{
+  return _rtt_HealEdges( topo, e1, e2, 1 );
+}
+
+RTT_ELEMID
+rtt_NewEdgeHeal( RTT_TOPOLOGY* topo, RTT_ELEMID e1, RTT_ELEMID e2 )
+{
+  return _rtt_HealEdges( topo, e1, e2, 0 );
+}
+
+RTT_ELEMID
+rtt_GetNodeByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol)
+{
+  RTT_ISO_NODE *elem;
+  int num;
+  int flds = RTT_COL_NODE_NODE_ID|RTT_COL_NODE_GEOM; /* geom not needed */
+  RTT_ELEMID id = 0;
+  RTPOINT2D qp; /* query point */
+  const RTT_BE_IFACE *iface = topo->be_iface;
+
+  if ( ! rt_getPoint2d_p(iface->ctx, pt->point, 0, &qp) )
+  {
+    rterror(iface->ctx, "Empty query point");
+    return -1;
+  }
+  elem = rtt_be_getNodeWithinDistance2D(topo, pt, tol, &num, flds, 0);
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  else if ( num )
+  {
+    if ( num > 1 )
+    {
+      _rtt_release_nodes(iface->ctx, elem, num);
+      rterror(iface->ctx, "Two or more nodes found");
+      return -1;
+    }
+    id = elem[0].node_id;
+    _rtt_release_nodes(iface->ctx, elem, num);
+  }
+
+  return id;
+}
+
+RTT_ELEMID
+rtt_GetEdgeByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol)
+{
+  RTT_ISO_EDGE *elem;
+  int num, i;
+  int flds = RTT_COL_EDGE_EDGE_ID|RTT_COL_EDGE_GEOM; /* GEOM is not needed */
+  RTT_ELEMID id = 0;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTGEOM *qp = rtpoint_as_rtgeom(iface->ctx, pt); /* query point */
+
+  if ( rtgeom_is_empty(iface->ctx, qp) )
+  {
+    rterror(iface->ctx, "Empty query point");
+    return -1;
+  }
+  elem = rtt_be_getEdgeWithinDistance2D(topo, pt, tol, &num, flds, 0);
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for (i=0; i<num;++i)
+  {
+    RTT_ISO_EDGE *e = &(elem[i]);
+#if 0
+    RTGEOM* geom;
+    double dist;
+
+    if ( ! e->geom )
+    {
+      _rtt_release_edges(iface->ctx, elem, num);
+      rtnotice(iface->ctx, "Corrupted topology: edge %" RTTFMT_ELEMID
+               " has null geometry", e->edge_id);
+      continue;
+    }
+
+    /* Should we check for intersection not being on an endpoint
+     * as documented ? */
+    geom = rtline_as_rtgeom(iface->ctx, e->geom);
+    dist = rtgeom_mindistance2d_tolerance(iface->ctx, geom, qp, tol);
+    if ( dist > tol ) continue;
+#endif
+
+    if ( id )
+    {
+      _rtt_release_edges(iface->ctx, elem, num);
+      rterror(iface->ctx, "Two or more edges found");
+      return -1;
+    }
+    else id = e->edge_id;
+  }
+
+  if ( num ) _rtt_release_edges(iface->ctx, elem, num);
+
+  return id;
+}
+
+RTT_ELEMID
+rtt_GetFaceByPoint(RTT_TOPOLOGY *topo, RTPOINT *pt, double tol)
+{
+  RTT_ELEMID id = 0;
+  RTT_ISO_EDGE *elem;
+  int num, i;
+  int flds = RTT_COL_EDGE_EDGE_ID |
+             RTT_COL_EDGE_GEOM |
+             RTT_COL_EDGE_FACE_LEFT |
+             RTT_COL_EDGE_FACE_RIGHT;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTGEOM *qp = rtpoint_as_rtgeom(iface->ctx, pt);
+
+  id = rtt_be_getFaceContainingPoint(topo, pt);
+  if ( id == -2 ) {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+
+  if ( id > 0 )
+  {
+    return id;
+  }
+  id = 0; /* or it'll be -1 for not found */
+
+  RTDEBUG(1, "No face properly contains query point,"
+             " looking for edges");
+
+  /* Not in a face, may be in universe or on edge, let's check
+   * for distance */
+  /* NOTE: we never pass a tolerance of 0 to avoid ever using
+   * ST_Within, which doesn't include endpoints matches */
+  elem = rtt_be_getEdgeWithinDistance2D(topo, pt, tol?tol:1e-5, &num, flds, 0);
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  for (i=0; i<num; ++i)
+  {
+    RTT_ISO_EDGE *e = &(elem[i]);
+    RTT_ELEMID eface = 0;
+    RTGEOM* geom;
+    double dist;
+
+    if ( ! e->geom )
+    {
+      _rtt_release_edges(iface->ctx, elem, num);
+      rtnotice(iface->ctx, "Corrupted topology: edge %" RTTFMT_ELEMID
+               " has null geometry", e->edge_id);
+      continue;
+    }
+
+    /* don't consider dangling edges */
+    if ( e->face_left == e->face_right )
+    {
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID
+                  " is dangling, won't consider it", e->edge_id);
+      continue;
+    }
+
+    geom = rtline_as_rtgeom(iface->ctx, e->geom);
+    dist = rtgeom_mindistance2d_tolerance(iface->ctx, geom, qp, tol);
+
+    RTDEBUGF(1, "Distance from edge %" RTTFMT_ELEMID
+                " is %g (tol=%g)", e->edge_id, dist, tol);
+
+    /* we won't consider edges too far */
+    if ( dist > tol ) continue;
+    if ( e->face_left == 0 ) {
+      eface = e->face_right;
+    }
+    else if ( e->face_right == 0 ) {
+      eface = e->face_left;
+    }
+    else {
+      _rtt_release_edges(iface->ctx, elem, num);
+      rterror(iface->ctx, "Two or more faces found");
+      return -1;
+    }
+
+    if ( id && id != eface )
+    {
+      _rtt_release_edges(iface->ctx, elem, num);
+      rterror(iface->ctx, "Two or more faces found"
+#if 0 /* debugging */
+              " (%" RTTFMT_ELEMID
+              " and %" RTTFMT_ELEMID ")", id, eface
+#endif
+             );
+      return -1;
+    }
+    else id = eface;
+  }
+  if ( num ) _rtt_release_edges(iface->ctx, elem, num);
+
+  return id;
+}
+
+/* Return the smallest delta that can perturbate
+ * the maximum absolute value of a geometry ordinate
+ */
+static double
+_rtt_minTolerance(const RTCTX *ctx,  RTGEOM *g )
+{
+  const RTGBOX* gbox;
+  double max;
+  double ret;
+
+  gbox = rtgeom_get_bbox(ctx, g);
+  if ( ! gbox ) return 0; /* empty */
+  max = FP_ABS(gbox->xmin);
+  if ( max < FP_ABS(gbox->xmax) ) max = FP_ABS(gbox->xmax);
+  if ( max < FP_ABS(gbox->ymin) ) max = FP_ABS(gbox->ymin);
+  if ( max < FP_ABS(gbox->ymax) ) max = FP_ABS(gbox->ymax);
+
+  ret = 3.6 * pow(10,  - ( 15 - log10(max?max:1.0) ) );
+
+  return ret;
+}
+
+#define _RTT_MINTOLERANCE( topo, geom ) ( \
+  topo->precision ?  topo->precision : _rtt_minTolerance(topo->be_iface->ctx, geom) )
+
+typedef struct scored_pointer_t {
+  void *ptr;
+  double score;
+} scored_pointer;
+
+static int
+compare_scored_pointer(const void *si1, const void *si2)
+{
+	double a = ((scored_pointer *)si1)->score;
+	double b = ((scored_pointer *)si2)->score;
+	if ( a < b )
+		return -1;
+	else if ( a > b )
+		return 1;
+	else
+		return 0;
+}
+
+RTT_ELEMID
+rtt_AddPoint(RTT_TOPOLOGY* topo, RTPOINT* point, double tol)
+{
+  int num, i;
+  double mindist = FLT_MAX;
+  RTT_ISO_NODE *nodes, *nodes2;
+  RTT_ISO_EDGE *edges, *edges2;
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTGEOM *pt = rtpoint_as_rtgeom(iface->ctx, point);
+  int flds;
+  RTT_ELEMID id = 0;
+  scored_pointer *sorted;
+
+  /* Get tolerance, if 0 was given */
+  if ( ! tol ) tol = _RTT_MINTOLERANCE( topo, pt );
+
+  RTDEBUGG(1, pt, "Adding point");
+
+  /*
+  -- 1. Check if any existing node is closer than the given precision
+  --    and if so pick the closest
+  TODO: use WithinBox2D
+  */
+  flds = RTT_COL_NODE_NODE_ID|RTT_COL_NODE_GEOM;
+  nodes = rtt_be_getNodeWithinDistance2D(topo, point, tol, &num, flds, 0);
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( num )
+  {
+    RTDEBUGF(1, "New point is within %.15g units of %d nodes", tol, num);
+    /* Order by distance if there are more than a single return */
+    if ( num > 1 )
+    {{
+      sorted= rtalloc(iface->ctx, sizeof(scored_pointer)*num);
+      for (i=0; i<num; ++i)
+      {
+        sorted[i].ptr = nodes+i;
+        sorted[i].score = rtgeom_mindistance2d(iface->ctx, rtpoint_as_rtgeom(iface->ctx, nodes[i].geom), pt);
+        RTDEBUGF(1, "Node %" RTTFMT_ELEMID " distance: %.15g",
+          ((RTT_ISO_NODE*)(sorted[i].ptr))->node_id, sorted[i].score);
+      }
+      qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer);
+      nodes2 = rtalloc(iface->ctx, sizeof(RTT_ISO_NODE)*num);
+      for (i=0; i<num; ++i)
+      {
+        nodes2[i] = *((RTT_ISO_NODE*)sorted[i].ptr);
+      }
+      rtfree(iface->ctx, sorted);
+      rtfree(iface->ctx, nodes);
+      nodes = nodes2;
+    }}
+
+    for ( i=0; i<num; ++i )
+    {
+      RTT_ISO_NODE *n = &(nodes[i]);
+      RTGEOM *g = rtpoint_as_rtgeom(iface->ctx, n->geom);
+      double dist = rtgeom_mindistance2d(iface->ctx, g, pt);
+      /* TODO: move this check in the previous sort scan ... */
+      if ( dist >= tol ) continue; /* must be closer than tolerated */
+      if ( ! id || dist < mindist )
+      {
+        id = n->node_id;
+        mindist = dist;
+      }
+    }
+    if ( id )
+    {
+      /* found an existing node */
+      if ( nodes ) _rtt_release_nodes(iface->ctx, nodes, num);
+      return id;
+    }
+  }
+
+  _rtt_EnsureGeos(iface->ctx);
+
+  /*
+  -- 2. Check if any existing edge falls within tolerance
+  --    and if so split it by a point projected on it
+  TODO: use WithinBox2D
+  */
+  flds = RTT_COL_EDGE_EDGE_ID|RTT_COL_EDGE_GEOM;
+  edges = rtt_be_getEdgeWithinDistance2D(topo, point, tol, &num, flds, 0);
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( num )
+  {
+  RTDEBUGF(1, "New point is within %.15g units of %d edges", tol, num);
+
+  /* Order by distance if there are more than a single return */
+  if ( num > 1 )
+  {{
+    int j;
+    sorted = rtalloc(iface->ctx, sizeof(scored_pointer)*num);
+    for (i=0; i<num; ++i)
+    {
+      sorted[i].ptr = edges+i;
+      sorted[i].score = rtgeom_mindistance2d(iface->ctx, rtline_as_rtgeom(iface->ctx, edges[i].geom), pt);
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID " distance: %.15g",
+        ((RTT_ISO_EDGE*)(sorted[i].ptr))->edge_id, sorted[i].score);
+    }
+    qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer);
+    edges2 = rtalloc(iface->ctx, sizeof(RTT_ISO_EDGE)*num);
+    for (j=0, i=0; i<num; ++i)
+    {
+      if ( sorted[i].score == sorted[0].score )
+      {
+        edges2[j++] = *((RTT_ISO_EDGE*)sorted[i].ptr);
+      }
+      else
+      {
+        rtline_free(iface->ctx, ((RTT_ISO_EDGE*)sorted[i].ptr)->geom);
+      }
+    }
+    num = j;
+    rtfree(iface->ctx, sorted);
+    rtfree(iface->ctx, edges);
+    edges = edges2;
+  }}
+
+  for (i=0; i<num; ++i)
+  {
+    /* The point is on or near an edge, split the edge */
+    RTT_ISO_EDGE *e = &(edges[i]);
+    RTGEOM *g = rtline_as_rtgeom(iface->ctx, e->geom);
+    RTGEOM *prj;
+    int contains;
+    GEOSGeometry *prjg, *gg;
+
+    RTDEBUGF(1, "Splitting edge %" RTTFMT_ELEMID, e->edge_id);
+
+    /* project point to line, split edge by point */
+    prj = rtgeom_closest_point(iface->ctx, g, pt);
+    if ( rtgeom_has_z(iface->ctx, pt) )
+    {{
+      /*
+      -- This is a workaround for ClosestPoint lack of Z support:
+      -- http://trac.osgeo.org/postgis/ticket/2033
+      */
+      RTGEOM *tmp;
+      double z;
+      RTPOINT4D p4d;
+      RTPOINT *prjpt;
+      /* add Z to "prj" */
+      tmp = rtgeom_force_3dz(iface->ctx, prj);
+      prjpt = rtgeom_as_rtpoint(iface->ctx, tmp);
+      rt_getPoint4d_p(iface->ctx, point->point, 0, &p4d);
+      z = p4d.z;
+      rt_getPoint4d_p(iface->ctx, prjpt->point, 0, &p4d);
+      p4d.z = z;
+      ptarray_set_point4d(iface->ctx, prjpt->point, 0, &p4d);
+      rtgeom_free(iface->ctx, prj);
+      prj = tmp;
+    }}
+    prjg = RTGEOM2GEOS(iface->ctx, prj, 0);
+    if ( ! prjg ) {
+      rtgeom_free(iface->ctx, prj);
+      _rtt_release_edges(iface->ctx, edges, num);
+      rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+    gg = RTGEOM2GEOS(iface->ctx, g, 0);
+    if ( ! gg ) {
+      rtgeom_free(iface->ctx, prj);
+      _rtt_release_edges(iface->ctx, edges, num);
+      GEOSGeom_destroy_r(iface->ctx->gctx, prjg);
+      rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+    contains = GEOSContains_r(iface->ctx->gctx, gg, prjg);
+    GEOSGeom_destroy_r(iface->ctx->gctx, prjg);
+    GEOSGeom_destroy_r(iface->ctx->gctx, gg);
+    if ( contains == 2 )
+    {
+      rtgeom_free(iface->ctx, prj);
+      _rtt_release_edges(iface->ctx, edges, num);
+      rterror(iface->ctx, "GEOS exception on Contains: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    } 
+    if ( ! contains )
+    {{
+      double snaptol;
+      RTGEOM *snapedge;
+      RTLINE *snapline;
+      RTPOINT4D p1, p2;
+
+      RTDEBUGF(1, "Edge %" RTTFMT_ELEMID
+                  " does not contain projected point to it",
+                  e->edge_id);
+
+      /* In order to reduce the robustness issues, we'll pick
+       * an edge that contains the projected point, if possible */
+      if ( i+1 < num )
+      {
+        RTDEBUG(1, "But there's another to check");
+        rtgeom_free(iface->ctx, prj);
+        continue;
+      }
+
+      /*
+      -- The tolerance must be big enough for snapping to happen
+      -- and small enough to snap only to the projected point.
+      -- Unfortunately ST_Distance returns 0 because it also uses
+      -- a projected point internally, so we need another way.
+      */
+      snaptol = _rtt_minTolerance(iface->ctx, prj);
+      snapedge = rtgeom_snap(iface->ctx, g, prj, snaptol);
+      snapline = rtgeom_as_rtline(iface->ctx, snapedge);
+
+      RTDEBUGF(1, "Edge snapped with tolerance %g", snaptol);
+
+      /* TODO: check if snapping did anything ? */
+#if RTGEOM_DEBUG_LEVEL > 0
+      {
+      size_t sz;
+      char *wkt1 = rtgeom_to_wkt(iface->ctx, g, RTWKT_EXTENDED, 15, &sz);
+      char *wkt2 = rtgeom_to_wkt(iface->ctx, snapedge, RTWKT_EXTENDED, 15, &sz);
+      RTDEBUGF(1, "Edge %s snapped became %s", wkt1, wkt2);
+      rtfree(iface->ctx, wkt1);
+      rtfree(iface->ctx, wkt2);
+      }
+#endif
+
+
+      /*
+      -- Snapping currently snaps the first point below tolerance
+      -- so may possibly move first point. See ticket #1631
+      */
+      rt_getPoint4d_p(iface->ctx, e->geom->points, 0, &p1);
+      rt_getPoint4d_p(iface->ctx, snapline->points, 0, &p2);
+      RTDEBUGF(1, "Edge first point is %g %g, "
+                  "snapline first point is %g %g",
+                  p1.x, p1.y, p2.x, p2.y);
+      if ( p1.x != p2.x || p1.y != p2.y )
+      {
+        RTDEBUG(1, "Snapping moved first point, re-adding it");
+        if ( RT_SUCCESS != ptarray_insert_point(iface->ctx, snapline->points, &p1, 0) )
+        {
+          rtgeom_free(iface->ctx, prj);
+          rtgeom_free(iface->ctx, snapedge);
+          _rtt_release_edges(iface->ctx, edges, num);
+          rterror(iface->ctx, "GEOS exception on Contains: %s", rtgeom_get_last_geos_error(iface->ctx));
+          return -1;
+        }
+#if RTGEOM_DEBUG_LEVEL > 0
+        {
+        size_t sz;
+        char *wkt1 = rtgeom_to_wkt(iface->ctx, g, RTWKT_EXTENDED, 15, &sz);
+        RTDEBUGF(1, "Tweaked snapline became %s", wkt1);
+        rtfree(iface->ctx, wkt1);
+        }
+#endif
+      }
+#if RTGEOM_DEBUG_LEVEL > 0
+      else {
+        RTDEBUG(1, "Snapping did not move first point");
+      }
+#endif
+
+      if ( -1 == rtt_ChangeEdgeGeom( topo, e->edge_id, snapline ) )
+      {
+        /* TODO: should have invoked rterror already, leaking memory */
+        rtgeom_free(iface->ctx, prj);
+        rtgeom_free(iface->ctx, snapedge);
+        _rtt_release_edges(iface->ctx, edges, num);
+        rterror(iface->ctx, "rtt_ChangeEdgeGeom failed");
+        return -1;
+      }
+      rtgeom_free(iface->ctx, snapedge);
+    }}
+#if RTGEOM_DEBUG_LEVEL > 0
+    else
+    {{
+      size_t sz;
+      char *wkt1 = rtgeom_to_wkt(iface->ctx, g, RTWKT_EXTENDED, 15, &sz);
+      char *wkt2 = rtgeom_to_wkt(iface->ctx, prj, RTWKT_EXTENDED, 15, &sz);
+      RTDEBUGF(1, "Edge %s contains projected point %s", wkt1, wkt2);
+      rtfree(iface->ctx, wkt1);
+      rtfree(iface->ctx, wkt2);
+    }}
+#endif
+
+    /* TODO: pass 1 as last argument (skipChecks) ? */
+    id = rtt_ModEdgeSplit( topo, e->edge_id, rtgeom_as_rtpoint(iface->ctx, prj), 0 );
+    if ( -1 == id )
+    {
+      /* TODO: should have invoked rterror already, leaking memory */
+      rtgeom_free(iface->ctx, prj);
+      _rtt_release_edges(iface->ctx, edges, num);
+      rterror(iface->ctx, "rtt_ModEdgeSplit failed");
+      return -1;
+    }
+
+    rtgeom_free(iface->ctx, prj);
+    break; /* we only want to snap a single edge */
+  }
+  _rtt_release_edges(iface->ctx, edges, num);
+  }
+  else
+  {
+    /* The point is isolated, add it as such */
+    /* TODO: pass 1 as last argument (skipChecks) ? */
+    id = rtt_AddIsoNode(topo, -1, point, 0);
+    if ( -1 == id )
+    {
+      /* should have invoked rterror already, leaking memory */
+      rterror(iface->ctx, "rtt_AddIsoNode failed");
+      return -1;
+    }
+  }
+
+  return id;
+}
+
+/* Return identifier of an equal edge, 0 if none or -1 on error
+ * (and rterror gets called on error)
+ */
+static RTT_ELEMID
+_rtt_GetEqualEdge( RTT_TOPOLOGY *topo, RTLINE *edge )
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTT_ELEMID id;
+  RTT_ISO_EDGE *edges;
+  int num, i;
+  const RTGBOX *qbox = rtgeom_get_bbox(iface->ctx,  rtline_as_rtgeom(iface->ctx, edge) );
+  GEOSGeometry *edgeg;
+  const int flds = RTT_COL_EDGE_EDGE_ID|RTT_COL_EDGE_GEOM;
+
+  edges = rtt_be_getEdgeWithinBox2D( topo, qbox, &num, flds, 0 );
+  if ( num == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  if ( num )
+  {
+    _rtt_EnsureGeos(iface->ctx);
+
+    edgeg = RTGEOM2GEOS(iface->ctx,  rtline_as_rtgeom(iface->ctx, edge), 0 );
+    if ( ! edgeg )
+    {
+      _rtt_release_edges(iface->ctx, edges, num);
+      rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return -1;
+    }
+    for (i=0; i<num; ++i)
+    {
+      RTT_ISO_EDGE *e = &(edges[i]);
+      RTGEOM *g = rtline_as_rtgeom(iface->ctx, e->geom);
+      GEOSGeometry *gg;
+      int equals;
+      gg = RTGEOM2GEOS(iface->ctx,  g, 0 );
+      if ( ! gg )
+      {
+        GEOSGeom_destroy_r(iface->ctx->gctx, edgeg);
+        _rtt_release_edges(iface->ctx, edges, num);
+        rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -1;
+      }
+      equals = GEOSEquals_r(iface->ctx->gctx, gg, edgeg);
+      GEOSGeom_destroy_r(iface->ctx->gctx, gg);
+      if ( equals == 2 )
+      {
+        GEOSGeom_destroy_r(iface->ctx->gctx, edgeg);
+        _rtt_release_edges(iface->ctx, edges, num);
+        rterror(iface->ctx, "GEOSEquals exception: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return -1;
+      }
+      if ( equals )
+      {
+        id = e->edge_id;
+        GEOSGeom_destroy_r(iface->ctx->gctx, edgeg);
+        _rtt_release_edges(iface->ctx, edges, num);
+        return id;
+      }
+    }
+    GEOSGeom_destroy_r(iface->ctx->gctx, edgeg);
+    _rtt_release_edges(iface->ctx, edges, num);
+  }
+
+  return 0;
+}
+
+/*
+ * Add a pre-noded pre-split line edge. Used by rtt_AddLine
+ * Return edge id, 0 if none added (empty edge), -1 on error
+ */
+static RTT_ELEMID
+_rtt_AddLineEdge( RTT_TOPOLOGY* topo, RTLINE* edge, double tol )
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTCOLLECTION *col;
+  RTPOINT *start_point, *end_point;
+  RTGEOM *tmp;
+  RTT_ISO_NODE *node;
+  RTT_ELEMID nid[2]; /* start_node, end_node */
+  RTT_ELEMID id; /* edge id */
+  RTPOINT4D p4d;
+  int nn, i;
+
+  start_point = rtline_get_rtpoint(iface->ctx, edge, 0);
+  if ( ! start_point )
+  {
+    rtnotice(iface->ctx, "Empty component of noded line");
+    return 0; /* must be empty */
+  }
+  nid[0] = rtt_AddPoint( topo, start_point, tol );
+  rtpoint_free(iface->ctx, start_point); /* too late if rtt_AddPoint calls rterror */
+  if ( nid[0] == -1 ) return -1; /* rterror should have been called */
+
+  end_point = rtline_get_rtpoint(iface->ctx, edge, edge->points->npoints-1);
+  if ( ! end_point )
+  {
+    rterror(iface->ctx, "could not get last point of line "
+            "after successfully getting first point !?");
+    return -1;
+  }
+  nid[1] = rtt_AddPoint( topo, end_point, tol );
+  rtpoint_free(iface->ctx, end_point); /* too late if rtt_AddPoint calls rterror */
+  if ( nid[1] == -1 ) return -1; /* rterror should have been called */
+
+  /*
+    -- Added endpoints may have drifted due to tolerance, so
+    -- we need to re-snap the edge to the new nodes before adding it
+  */
+
+  nn = nid[0] == nid[1] ? 1 : 2;
+  node = rtt_be_getNodeById( topo, nid, &nn,
+                             RTT_COL_NODE_NODE_ID|RTT_COL_NODE_GEOM );
+  if ( nn == -1 )
+  {
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return -1;
+  }
+  start_point = NULL; end_point = NULL;
+  for (i=0; i<nn; ++i)
+  {
+    if ( node[i].node_id == nid[0] ) start_point = node[i].geom;
+    if ( node[i].node_id == nid[1] ) end_point = node[i].geom;
+  }
+  if ( ! start_point  || ! end_point )
+  {
+    if ( nn ) _rtt_release_nodes(iface->ctx, node, nn);
+    rterror(iface->ctx, "Could not find just-added nodes % " RTTFMT_ELEMID
+            " and %" RTTFMT_ELEMID, nid[0], nid[1]);
+    return -1;
+  }
+
+  /* snap */
+
+  rt_getPoint4d_p(iface->ctx,  start_point->point, 0, &p4d );
+  rtline_setPoint4d(iface->ctx, edge, 0, &p4d);
+
+  rt_getPoint4d_p(iface->ctx,  end_point->point, 0, &p4d );
+  rtline_setPoint4d(iface->ctx, edge, edge->points->npoints-1, &p4d);
+
+  if ( nn ) _rtt_release_nodes(iface->ctx, node, nn);
+
+  /* make valid, after snap (to handle collapses) */
+  tmp = rtgeom_make_valid(iface->ctx, rtline_as_rtgeom(iface->ctx, edge));
+
+  col = rtgeom_as_rtcollection(iface->ctx, tmp);
+  if ( col )
+  {{
+    RTGEOM *tmp2;
+
+    col = rtcollection_extract(iface->ctx, col, RTLINETYPE);
+
+    /* Check if the so-snapped edge collapsed (see #1650) */
+    if ( col->ngeoms == 0 )
+    {
+      rtcollection_free(iface->ctx, col);
+      rtgeom_free(iface->ctx, tmp);
+      RTDEBUG(1, "Made-valid snapped edge collapsed");
+      return 0;
+    }
+
+    tmp2 = rtgeom_clone_deep(iface->ctx,  col->geoms[0] );
+    rtgeom_free(iface->ctx, tmp);
+    tmp = tmp2;
+    edge = rtgeom_as_rtline(iface->ctx, tmp);
+    rtcollection_free(iface->ctx, col);
+    if ( ! edge )
+    {
+      /* should never happen */
+      rterror(iface->ctx, "rtcollection_extract(iface->ctx, RTLINETYPE) returned a non-line?");
+      return -1;
+    }
+  }}
+  else
+  {
+    edge = rtgeom_as_rtline(iface->ctx, tmp);
+    if ( ! edge )
+    {
+      RTDEBUGF(1, "Made-valid snapped edge collapsed to %s",
+                  rttype_name(iface->ctx, rtgeom_get_type(iface->ctx, tmp)));
+      rtgeom_free(iface->ctx, tmp);
+      return 0;
+    }
+  }
+
+  /* check if the so-snapped edge _now_ exists */
+  id = _rtt_GetEqualEdge ( topo, edge );
+  RTDEBUGF(1, "_rtt_GetEqualEdge returned %" RTTFMT_ELEMID, id);
+  if ( id == -1 )
+  {
+    rtgeom_free(iface->ctx, tmp); /* probably too late, due to internal rterror */
+    return -1;
+  }
+  if ( id ) 
+  {
+    rtgeom_free(iface->ctx, tmp); /* possibly takes "edge" down with it */
+    return id;
+  }
+
+  /* No previously existing edge was found, we'll add one */
+  /* TODO: skip checks, I guess ? */
+  id = rtt_AddEdgeModFace( topo, nid[0], nid[1], edge, 0 );
+  RTDEBUGF(1, "rtt_AddEdgeModFace returned %" RTTFMT_ELEMID, id);
+  if ( id == -1 )
+  {
+    rtgeom_free(iface->ctx, tmp); /* probably too late, due to internal rterror */
+    return -1;
+  }
+  rtgeom_free(iface->ctx, tmp); /* possibly takes "edge" down with it */
+
+  return id;
+}
+
+/* Simulate split-loop as it was implemented in pl/pgsql version
+ * of TopoGeo_addLinestring */
+static RTGEOM *
+_rtt_split_by_nodes(const RTCTX *ctx, const RTGEOM *g, const RTGEOM *nodes)
+{
+  RTCOLLECTION *col = rtgeom_as_rtcollection(ctx, nodes);
+  int i;
+  RTGEOM *bg;
+
+  bg = rtgeom_clone_deep(ctx, g);
+  if ( ! col->ngeoms ) return bg;
+
+  for (i=0; i<col->ngeoms; ++i)
+  {
+    RTGEOM *g2;
+    g2 = rtgeom_split(ctx, bg, col->geoms[i]);
+    rtgeom_free(ctx, bg);
+    bg = g2;
+  }
+  bg->srid = nodes->srid;
+
+  return bg;
+}
+
+RTT_ELEMID*
+rtt_AddLine(RTT_TOPOLOGY* topo, RTLINE* line, double tol, int* nedges)
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  RTGEOM *geomsbuf[1];
+  RTGEOM **geoms;
+  int ngeoms;
+  RTGEOM *noded;
+  RTCOLLECTION *col;
+  RTT_ELEMID *ids;
+  RTT_ISO_EDGE *edges;
+  RTT_ISO_NODE *nodes;
+  int num;
+  int i;
+  RTGBOX qbox;
+
+  *nedges = -1; /* error condition, by default */
+
+  /* Get tolerance, if 0 was given */
+  if ( ! tol ) tol = _RTT_MINTOLERANCE( topo, (RTGEOM*)line );
+  RTDEBUGF(1, "Working tolerance:%.15g", tol);
+  RTDEBUGF(1, "Input line has srid=%d", line->srid);
+
+  /* 1. Self-node */
+  noded = rtgeom_node(iface->ctx, (RTGEOM*)line);
+  if ( ! noded ) return NULL; /* should have called rterror already */
+  RTDEBUGG(1, noded, "Noded");
+
+  qbox = *rtgeom_get_bbox(iface->ctx,  rtline_as_rtgeom(iface->ctx, line) );
+  RTDEBUGF(1, "Line BOX is %.15g %.15g, %.15g %.15g", qbox.xmin, qbox.ymin,
+                                          qbox.xmax, qbox.ymax);
+  gbox_expand(iface->ctx, &qbox, tol);
+  RTDEBUGF(1, "BOX expanded by %g is %.15g %.15g, %.15g %.15g",
+              tol, qbox.xmin, qbox.ymin, qbox.xmax, qbox.ymax);
+
+  /* 2. Node to edges falling within tol distance */
+  edges = rtt_be_getEdgeWithinBox2D( topo, &qbox, &num, RTT_COL_EDGE_ALL, 0 );
+  if ( num == -1 )
+  {
+    rtgeom_free(iface->ctx, noded);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return NULL;
+  }
+  RTDEBUGF(1, "Line bbox intersects %d edges bboxes", num);
+  if ( num )
+  {{
+    /* collect those whose distance from us is < tol */
+    RTGEOM **nearby = rtalloc(iface->ctx, sizeof(RTGEOM *)*num);
+    int nn=0;
+    for (i=0; i<num; ++i)
+    {
+      RTT_ISO_EDGE *e = &(edges[i]);
+      RTGEOM *g = rtline_as_rtgeom(iface->ctx, e->geom);
+      double dist = rtgeom_mindistance2d(iface->ctx, g, noded);
+      if ( dist >= tol ) continue; /* must be closer than tolerated */
+      nearby[nn++] = g;
+    }
+    if ( nn )
+    {{
+      RTCOLLECTION *col;
+      RTGEOM *iedges; /* just an alias for col */
+      RTGEOM *snapped;
+      RTGEOM *set1, *set2;
+
+      RTDEBUGF(1, "Line intersects %d edges", nn);
+
+      col = rtcollection_construct(iface->ctx, RTCOLLECTIONTYPE, topo->srid,
+                                   NULL, nn, nearby);
+      iedges = rtcollection_as_rtgeom(iface->ctx, col);
+      RTDEBUGG(1, iedges, "Collected edges");
+      RTDEBUGF(1, "Snapping noded, with srid=%d "
+                  "to interesecting edges, with srid=%d",
+                  noded->srid, iedges->srid);
+      snapped = rtgeom_snap(iface->ctx, noded, iedges, tol);
+      rtgeom_free(iface->ctx, noded);
+      RTDEBUGG(1, snapped, "Snapped");
+      RTDEBUGF(1, "Diffing snapped, with srid=%d "
+                  "and interesecting edges, with srid=%d",
+                  snapped->srid, iedges->srid);
+      noded = rtgeom_difference(iface->ctx, snapped, iedges);
+      RTDEBUGG(1, noded, "Differenced");
+      RTDEBUGF(1, "Intersecting snapped, with srid=%d "
+                  "and interesecting edges, with srid=%d",
+                  snapped->srid, iedges->srid);
+      set1 = rtgeom_intersection(iface->ctx, snapped, iedges);
+      RTDEBUGG(1, set1, "Intersected");
+      rtgeom_free(iface->ctx, snapped);
+      RTDEBUGF(1, "Linemerging set1, with srid=%d", set1->srid);
+      set2 = rtgeom_linemerge(iface->ctx, set1);
+      RTDEBUGG(1, set2, "Linemerged");
+      RTDEBUGG(1, noded, "Noded");
+      rtgeom_free(iface->ctx, set1);
+      RTDEBUGF(1, "Unioning noded, with srid=%d "
+                  "and set2, with srid=%d", noded->srid, set2->srid);
+      set1 = rtgeom_union(iface->ctx, noded, set2);
+      rtgeom_free(iface->ctx, set2);
+      rtgeom_free(iface->ctx, noded);
+      noded = set1;
+      RTDEBUGG(1, set1, "Unioned");
+
+      /* will not release the geoms array */
+      rtcollection_release(iface->ctx, col);
+    }}
+    rtfree(iface->ctx, nearby);
+    _rtt_release_edges(iface->ctx, edges, num);
+  }}
+
+  /* 2.1. Node with existing nodes within tol
+   * TODO: check if we should be only considering _isolated_ nodes! */
+  nodes = rtt_be_getNodeWithinBox2D( topo, &qbox, &num, RTT_COL_NODE_ALL, 0 );
+  if ( num == -1 )
+  {
+    rtgeom_free(iface->ctx, noded);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return NULL;
+  }
+  RTDEBUGF(1, "Line bbox intersects %d nodes bboxes", num);
+  if ( num )
+  {{
+    /* collect those whose distance from us is < tol */
+    RTGEOM **nearby = rtalloc(iface->ctx, sizeof(RTGEOM *)*num);
+    int nn=0;
+    for (i=0; i<num; ++i)
+    {
+      RTT_ISO_NODE *n = &(nodes[i]);
+      RTGEOM *g = rtpoint_as_rtgeom(iface->ctx, n->geom);
+      double dist = rtgeom_mindistance2d(iface->ctx, g, noded);
+      if ( dist >= tol ) continue; /* must be closer than tolerated */
+      nearby[nn++] = g;
+    }
+    if ( nn )
+    {{
+      RTCOLLECTION *col;
+      RTGEOM *inodes; /* just an alias for col */
+      RTGEOM *tmp;
+
+      RTDEBUGF(1, "Line intersects %d nodes", nn);
+
+      col = rtcollection_construct(iface->ctx, RTMULTIPOINTTYPE, topo->srid,
+                                   NULL, nn, nearby);
+      inodes = rtcollection_as_rtgeom(iface->ctx, col);
+
+      RTDEBUGG(1, inodes, "Collected nodes");
+
+      /* TODO: consider snapping once against all elements
+       *      (rather than once with edges and once with nodes) */
+      tmp = rtgeom_snap(iface->ctx, noded, inodes, tol);
+      rtgeom_free(iface->ctx, noded);
+      noded = tmp;
+      RTDEBUGG(1, noded, "Node-snapped");
+
+      tmp = _rtt_split_by_nodes(iface->ctx, noded, inodes);
+          /* rtgeom_split(iface->ctx, noded, inodes); */
+      rtgeom_free(iface->ctx, noded);
+      noded = tmp;
+      RTDEBUGG(1, noded, "Node-split");
+
+      /* will not release the geoms array */
+      rtcollection_release(iface->ctx, col);
+
+      /*
+      -- re-node to account for ST_Snap introduced self-intersections
+      -- See http://trac.osgeo.org/postgis/ticket/1714
+      -- TODO: consider running UnaryUnion once after all noding
+      */
+      tmp = rtgeom_unaryunion(iface->ctx, noded);
+      rtgeom_free(iface->ctx, noded);
+      noded = tmp;
+      RTDEBUGG(1, noded, "Unary-unioned");
+
+    }}
+    rtfree(iface->ctx, nearby);
+    _rtt_release_nodes(iface->ctx, nodes, num);
+  }}
+
+  RTDEBUGG(1, noded, "Finally-noded");
+
+  /* 3. For each (now-noded) segment, insert an edge */
+  col = rtgeom_as_rtcollection(iface->ctx, noded);
+  if ( col )
+  {
+    RTDEBUG(1, "Noded line was a collection");
+    geoms = col->geoms;
+    ngeoms = col->ngeoms;
+  }
+  else
+  {
+    RTDEBUG(1, "Noded line was a single geom");
+    geomsbuf[0] = noded;
+    geoms = geomsbuf;
+    ngeoms = 1;
+  }
+
+  RTDEBUGF(1, "Line was split into %d edges", ngeoms);
+
+  /* TODO: refactor to first add all nodes (re-snapping edges if
+   * needed) and then check all edges for existing already
+   * ( so to save a DB scan for each edge to be added )
+   */
+  ids = rtalloc(iface->ctx, sizeof(RTT_ELEMID)*ngeoms);
+  num = 0;
+  for ( i=0; i<ngeoms; ++i )
+  {
+    RTT_ELEMID id;
+    RTGEOM *g = geoms[i];
+    g->srid = noded->srid;
+
+#if RTGEOM_DEBUG_LEVEL > 0
+    {
+      size_t sz;
+      char *wkt1 = rtgeom_to_wkt(iface->ctx, g, RTWKT_EXTENDED, 15, &sz);
+      RTDEBUGF(1, "Component %d of split line is: %s", i, wkt1);
+      rtfree(iface->ctx, wkt1);
+    }
+#endif
+
+    id = _rtt_AddLineEdge( topo, rtgeom_as_rtline(iface->ctx, g), tol );
+    RTDEBUGF(1, "_rtt_AddLineEdge returned %" RTTFMT_ELEMID, id);
+    if ( id < 0 )
+    {
+      rtgeom_free(iface->ctx, noded);
+      rtfree(iface->ctx, ids);
+      return NULL;
+    }
+    if ( ! id )
+    {
+      RTDEBUGF(1, "Component %d of split line collapsed", i);
+      continue;
+    }
+
+    RTDEBUGF(1, "Component %d of split line is edge %" RTTFMT_ELEMID,
+                  i, id);
+    ids[num++] = id; /* TODO: skip duplicates */
+  }
+
+  RTDEBUGG(1, noded, "Noded before free");
+  rtgeom_free(iface->ctx, noded);
+
+  /* TODO: XXX remove duplicated ids if not done before */
+
+  *nedges = num;
+  return ids;
+}
+
+RTT_ELEMID*
+rtt_AddPolygon(RTT_TOPOLOGY* topo, RTPOLY* poly, double tol, int* nfaces)
+{
+  const RTT_BE_IFACE *iface = topo->be_iface;
+  int i;
+  *nfaces = -1; /* error condition, by default */
+  int num;
+  RTT_ISO_FACE *faces;
+  int nfacesinbox;
+  RTT_ELEMID *ids = NULL;
+  RTGBOX qbox;
+  const GEOSPreparedGeometry *ppoly;
+  GEOSGeometry *polyg;
+
+  /* Get tolerance, if 0 was given */
+  if ( ! tol ) tol = _RTT_MINTOLERANCE( topo, (RTGEOM*)poly );
+  RTDEBUGF(1, "Working tolerance:%.15g", tol);
+
+  /* Add each ring as an edge */
+  for ( i=0; i<poly->nrings; ++i )
+  {
+    RTLINE *line;
+    RTPOINTARRAY *pa;
+    RTT_ELEMID *eids;
+    int nedges;
+
+    pa = ptarray_clone(iface->ctx, poly->rings[i]);
+    line = rtline_construct(iface->ctx, topo->srid, NULL, pa);
+    eids = rtt_AddLine( topo, line, tol, &nedges );
+    if ( nedges < 0 ) {
+      /* probably too late as rtt_AddLine invoked rterror */
+      rtline_free(iface->ctx, line);
+      rterror(iface->ctx, "Error adding ring %d of polygon", i);
+      return NULL;
+    }
+    rtline_free(iface->ctx, line);
+    rtfree(iface->ctx, eids);
+  }
+
+  /*
+  -- Find faces covered by input polygon
+  -- NOTE: potential snapping changed polygon edges
+  */
+  qbox = *rtgeom_get_bbox(iface->ctx,  rtpoly_as_rtgeom(iface->ctx, poly) );
+  gbox_expand(iface->ctx, &qbox, tol);
+  faces = rtt_be_getFaceWithinBox2D( topo, &qbox, &nfacesinbox,
+                                     RTT_COL_FACE_ALL, 0 );
+  if ( nfacesinbox == -1 )
+  {
+    rtfree(iface->ctx, ids);
+    rterror(iface->ctx, "Backend error: %s", rtt_be_lastErrorMessage(topo->be_iface));
+    return NULL;
+  }
+
+  num = 0;
+  if ( nfacesinbox )
+  {
+    polyg = RTGEOM2GEOS(iface->ctx, rtpoly_as_rtgeom(iface->ctx, poly), 0);
+    if ( ! polyg )
+    {
+      _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+      rterror(iface->ctx, "Could not convert poly geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+      return NULL;
+    }
+    ppoly = GEOSPrepare_r(iface->ctx->gctx, polyg);
+    ids = rtalloc(iface->ctx, sizeof(RTT_ELEMID)*nfacesinbox);
+    for ( i=0; i<nfacesinbox; ++i )
+    {
+      RTT_ISO_FACE *f = &(faces[i]);
+      RTGEOM *fg;
+      GEOSGeometry *fgg, *sp;
+      int covers;
+
+      /* check if a point on this face surface is covered by our polygon */
+      fg = rtt_GetFaceGeometry( topo, f->face_id );
+      if ( ! fg )
+      {
+        i = f->face_id; /* so we can destroy faces */
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, ppoly);
+        GEOSGeom_destroy_r(iface->ctx->gctx, polyg);
+        rtfree(iface->ctx, ids);
+        _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+        rterror(iface->ctx, "Could not get geometry of face %" RTTFMT_ELEMID, i);
+        return NULL;
+      }
+      /* check if a point on this face's surface is covered by our polygon */
+      fgg = RTGEOM2GEOS(iface->ctx, fg, 0);
+      rtgeom_free(iface->ctx, fg);
+      if ( ! fgg )
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, ppoly);
+        GEOSGeom_destroy_r(iface->ctx->gctx, polyg);
+        _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+        rterror(iface->ctx, "Could not convert edge geometry to GEOS: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return NULL;
+      }
+      sp = GEOSPointOnSurface_r(iface->ctx->gctx, fgg);
+      GEOSGeom_destroy_r(iface->ctx->gctx, fgg);
+      if ( ! sp )
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, ppoly);
+        GEOSGeom_destroy_r(iface->ctx->gctx, polyg);
+        _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+        rterror(iface->ctx, "Could not find point on face surface: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return NULL;
+      }
+      covers = GEOSPreparedCovers_r(iface->ctx->gctx,  ppoly, sp );
+      GEOSGeom_destroy_r(iface->ctx->gctx, sp);
+      if (covers == 2)
+      {
+        GEOSPreparedGeom_destroy_r(iface->ctx->gctx, ppoly);
+        GEOSGeom_destroy_r(iface->ctx->gctx, polyg);
+        _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+        rterror(iface->ctx, "PreparedCovers error: %s", rtgeom_get_last_geos_error(iface->ctx));
+        return NULL;
+      }
+      if ( ! covers )
+      {
+        continue; /* we're not composed by this face */
+      }
+
+      /* TODO: avoid duplicates ? */
+      ids[num++] = f->face_id;
+    }
+    GEOSPreparedGeom_destroy_r(iface->ctx->gctx, ppoly);
+    GEOSGeom_destroy_r(iface->ctx->gctx, polyg);
+    _rtt_release_faces(iface->ctx, faces, nfacesinbox);
+  }
+
+  /* possibly 0 if non face's surface point was found
+   * to be covered by input polygon */
+  *nfaces = num;
+
+  return ids;
+}
diff --git a/src/rthomogenize.c b/src/rthomogenize.c
new file mode 100644
index 0000000..a32e474
--- /dev/null
+++ b/src/rthomogenize.c
@@ -0,0 +1,273 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2010 Olivier Courtin <olivier.courtin at oslandia.com>
+ *
+ **********************************************************************/
+
+
+
+#include <stdlib.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+typedef struct {
+	int cnt[RTNUMTYPES];
+	RTCOLLECTION* buf[RTNUMTYPES];
+} HomogenizeBuffer;
+
+static void
+init_homogenizebuffer(const RTCTX *ctx, HomogenizeBuffer *buffer)
+{
+	int i;
+	for ( i = 0; i < RTNUMTYPES; i++ )
+	{
+		buffer->cnt[i] = 0;
+		buffer->buf[i] = NULL;
+	}
+}
+
+/*
+static void
+free_homogenizebuffer(HomogenizeBuffer *buffer)
+{
+	int i;
+	for ( i = 0; i < RTNUMTYPES; i++ )
+	{
+		if ( buffer->buf[i] )
+		{
+			rtcollection_free(ctx, buffer->buf[i]);
+		}
+	}
+}
+*/
+
+/*
+** Given a generic collection, return the "simplest" form.
+**
+** eg: GEOMETRYCOLLECTION(MULTILINESTRING()) => MULTILINESTRING()
+**
+**     GEOMETRYCOLLECTION(MULTILINESTRING(), MULTILINESTRING(), POINT())
+**      => GEOMETRYCOLLECTION(MULTILINESTRING(), POINT())
+**
+** In general, if the subcomponents are homogeneous, return a properly
+** typed collection.
+** Otherwise, return a generic collection, with the subtypes in minimal
+** typed collections.
+*/
+static void
+rtcollection_build_buffer(const RTCTX *ctx, const RTCOLLECTION *col, HomogenizeBuffer *buffer)
+{
+	int i;
+	
+	if ( ! col ) return;
+	if ( rtgeom_is_empty(ctx, rtcollection_as_rtgeom(ctx, col)) ) return;
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		RTGEOM *geom = col->geoms[i];
+		switch(geom->type)
+		{
+			case RTPOINTTYPE:
+			case RTLINETYPE:
+			case RTCIRCSTRINGTYPE:
+			case RTCOMPOUNDTYPE:
+			case RTTRIANGLETYPE:
+			case RTCURVEPOLYTYPE:
+			case RTPOLYGONTYPE:
+			{
+				/* Init if necessary */
+				if ( ! buffer->buf[geom->type] )
+				{
+					RTCOLLECTION *bufcol = rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, col->srid, RTFLAGS_GET_Z(col->flags), RTFLAGS_GET_M(col->flags));
+					bufcol->type = rttype_get_collectiontype(ctx, geom->type);
+					buffer->buf[geom->type] = bufcol;
+				}
+				/* Add sub-geom to buffer */
+				rtcollection_add_rtgeom(ctx, buffer->buf[geom->type], rtgeom_clone(ctx, geom));
+				/* Increment count for this singleton type */
+				buffer->cnt[geom->type] = buffer->cnt[geom->type] + 1;
+			}
+			default:
+			{
+				rtcollection_build_buffer(ctx, rtgeom_as_rtcollection(ctx, geom), buffer);
+			}
+		}
+	}
+	return;
+}
+
+static RTGEOM*
+rtcollection_homogenize(const RTCTX *ctx, const RTCOLLECTION *col)
+{
+	int i;
+	int ntypes = 0;
+	int type = 0;
+	RTGEOM *outgeom = NULL;
+	
+	HomogenizeBuffer buffer;
+
+	/* Sort all the parts into a buffer */
+	init_homogenizebuffer(ctx, &buffer);
+	rtcollection_build_buffer(ctx, col, &buffer);
+	
+	/* Check for homogeneity */
+	for ( i = 0; i < RTNUMTYPES; i++ )
+	{
+		if ( buffer.cnt[i] > 0 )
+		{
+			ntypes++;
+			type = i;
+		}
+	}
+	
+	/* No types? Huh. Return empty. */
+	if ( ntypes == 0 )
+	{
+		RTCOLLECTION *outcol;
+		outcol = rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, col->srid, RTFLAGS_GET_Z(col->flags), RTFLAGS_GET_M(col->flags));
+		outgeom = rtcollection_as_rtgeom(ctx, outcol);
+	}
+	/* One type, return homogeneous collection */
+	else if ( ntypes == 1 )
+	{
+		RTCOLLECTION *outcol;
+		outcol = buffer.buf[type];
+		if ( outcol->ngeoms == 1 )
+		{
+			outgeom = outcol->geoms[0];
+			outcol->ngeoms=0; rtcollection_free(ctx, outcol);
+		}
+		else
+		{
+			outgeom = rtcollection_as_rtgeom(ctx, outcol);
+		}
+		outgeom->srid = col->srid;
+	}
+	/* Bah, more than out type, return anonymous collection */
+	else if ( ntypes > 1 )
+	{
+		int j;
+		RTCOLLECTION *outcol;
+		outcol = rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, col->srid, RTFLAGS_GET_Z(col->flags), RTFLAGS_GET_M(col->flags));
+		for ( j = 0; j < RTNUMTYPES; j++ )
+		{
+			if ( buffer.buf[j] )
+			{
+				RTCOLLECTION *bcol = buffer.buf[j];
+				if ( bcol->ngeoms == 1 )
+				{
+					rtcollection_add_rtgeom(ctx, outcol, bcol->geoms[0]);
+					bcol->ngeoms=0; rtcollection_free(ctx, bcol);
+				}
+				else 
+				{
+					rtcollection_add_rtgeom(ctx, outcol, rtcollection_as_rtgeom(ctx, bcol));
+				}
+			}
+		}
+		outgeom = rtcollection_as_rtgeom(ctx, outcol);
+	}
+
+	return outgeom;
+}
+
+
+
+
+
+/*
+** Given a generic geometry, return the "simplest" form.
+**
+** eg:
+**     LINESTRING() => LINESTRING()
+**
+**     MULTILINESTRING(with a single line) => LINESTRING()
+**
+**     GEOMETRYCOLLECTION(MULTILINESTRING()) => MULTILINESTRING()
+**
+**     GEOMETRYCOLLECTION(MULTILINESTRING(), MULTILINESTRING(), POINT())
+**      => GEOMETRYCOLLECTION(MULTILINESTRING(), POINT())
+*/
+RTGEOM *
+rtgeom_homogenize(const RTCTX *ctx, const RTGEOM *geom)
+{
+	RTGEOM *hgeom;
+
+	/* EMPTY Geometry */
+	if (rtgeom_is_empty(ctx, geom)) 
+	{
+		if( rtgeom_is_collection(ctx, geom) )
+		{
+			return rtcollection_as_rtgeom(ctx, rtcollection_construct_empty(ctx, geom->type, geom->srid, rtgeom_has_z(ctx, geom), rtgeom_has_m(ctx, geom)));
+		}
+		
+		return rtgeom_clone(ctx, geom);
+	}
+
+	switch (geom->type)
+	{
+
+		/* Return simple geometries untouched */
+		case RTPOINTTYPE:
+		case RTLINETYPE:
+		case RTCIRCSTRINGTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTTRIANGLETYPE:
+		case RTCURVEPOLYTYPE:
+		case RTPOLYGONTYPE:
+			return rtgeom_clone(ctx, geom);
+
+		/* Process homogeneous geometries lightly */
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+		case RTPOLYHEDRALSURFACETYPE:
+		case RTTINTYPE:
+		{
+			RTCOLLECTION *col = (RTCOLLECTION*)geom;
+
+			/* Strip single-entry multi-geometries down to singletons */
+			if ( col->ngeoms == 1 )
+			{
+				hgeom = rtgeom_clone(ctx, (RTGEOM*)(col->geoms[0]));
+				hgeom->srid = geom->srid;
+				if (geom->bbox)
+					hgeom->bbox = gbox_copy(ctx, geom->bbox);
+				return hgeom;
+			}
+
+			/* Return proper multigeometry untouched */
+			return rtgeom_clone(ctx, geom);
+		}
+	
+		/* Work on anonymous collections separately */
+		case RTCOLLECTIONTYPE: 
+			return rtcollection_homogenize(ctx, (RTCOLLECTION *) geom);
+	}
+
+	/* Unknown type */
+	rterror(ctx, "rtgeom_homogenize: Geometry Type not supported (%i)",
+	        rttype_name(ctx, geom->type));
+
+	return NULL; /* Never get here! */
+}
diff --git a/src/rtin_geojson.c b/src/rtin_geojson.c
new file mode 100644
index 0000000..016245a
--- /dev/null
+++ b/src/rtin_geojson.c
@@ -0,0 +1,599 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2013 Sandro Santilli <strk at keybit.net>
+ * Copyright 2011 Kashif Rasul <kashif.rasul at gmail.com>
+ *
+ **********************************************************************/
+
+
+
+#include <assert.h>
+#include "librtgeom.h"
+#include "rtgeom_log.h"
+#include "rttopo_config.h"
+
+#if defined(HAVE_LIBJSON) || defined(HAVE_LIBJSON_C) /* --{ */
+
+#ifdef HAVE_LIBJSON_C
+#include <json-c/json.h>
+#include <json-c/json_object_private.h>
+#else
+#include <json/json.h>
+#include <json/json_object_private.h>
+#endif
+
+#ifndef JSON_C_VERSION
+/* Adds support for libjson < 0.10 */
+# define json_tokener_error_desc(x) json_tokener_errors[(x)]
+#endif
+
+#include <string.h>
+
+static void geojson_rterror(char *msg, int error_code)
+{
+	RTDEBUGF(3, "rtgeom_from_geojson ERROR %i", error_code);
+	rterror(ctx, "%s", msg);
+}
+
+/* Prototype */
+static RTGEOM* parse_geojson(json_object *geojson, int *hasz, int root_srid);
+
+static json_object*
+findMemberByName(json_object* poObj, const char* pszName )
+{
+	json_object* poTmp;
+	json_object_iter it;
+
+	poTmp = poObj;
+
+	if( NULL == pszName || NULL == poObj)
+		return NULL;
+
+	it.key = NULL;
+	it.val = NULL;
+	it.entry = NULL;
+
+	if( NULL != json_object_get_object(poTmp) )
+	{
+		if( NULL == json_object_get_object(poTmp)->head )
+		{
+			geojson_rterror("invalid GeoJSON representation", 2);
+			return NULL;
+		}
+
+		for( it.entry = json_object_get_object(poTmp)->head;
+		        ( it.entry ?
+		          ( it.key = (char*)it.entry->k,
+		            it.val = (json_object*)it.entry->v, it.entry) : 0);
+		        it.entry = it.entry->next)
+		{
+			if( strcasecmp((char *)it.key, pszName )==0 )
+				return it.val;
+		}
+	}
+
+	return NULL;
+}
+
+
+static int
+parse_geojson_coord(json_object *poObj, int *hasz, RTPOINTARRAY *pa)
+{
+	RTPOINT4D pt;
+
+	RTDEBUGF(3, "parse_geojson_coord called for object %s.", json_object_to_json_string( poObj ) );
+
+	if( json_type_array == json_object_get_type( poObj ) )
+	{
+
+		json_object* poObjCoord = NULL;
+		const int nSize = json_object_array_length( poObj );
+		RTDEBUGF(3, "parse_geojson_coord called for array size %d.", nSize );
+
+		if ( nSize < 2 )
+		{
+			geojson_rterror("Too few ordinates in GeoJSON", 4);
+			return RT_FAILURE;
+		}
+		
+		/* Read X coordinate */
+		poObjCoord = json_object_array_get_idx( poObj, 0 );
+		pt.x = json_object_get_double( poObjCoord );
+		RTDEBUGF(3, "parse_geojson_coord pt.x = %f.", pt.x );
+
+		/* Read Y coordinate */
+		poObjCoord = json_object_array_get_idx( poObj, 1 );
+		pt.y = json_object_get_double( poObjCoord );
+		RTDEBUGF(3, "parse_geojson_coord pt.y = %f.", pt.y );
+
+		if( nSize > 2 ) /* should this be >= 3 ? */
+		{
+			/* Read Z coordinate */
+			poObjCoord = json_object_array_get_idx( poObj, 2 );
+			pt.z = json_object_get_double( poObjCoord );
+			RTDEBUGF(3, "parse_geojson_coord pt.z = %f.", pt.z );
+			*hasz = RT_TRUE;
+		}
+		else if ( nSize == 2 )
+		{
+			*hasz = RT_FALSE;
+			/* Initialize Z coordinate, if required */
+			if ( RTFLAGS_GET_Z(pa->flags) ) pt.z = 0.0;
+		}
+		else 
+		{
+			/* TODO: should we account for nSize > 3 ? */
+			/* more than 3 coordinates, we're just dropping dimensions here... */
+		}
+
+		/* Initialize M coordinate, if required */
+		if ( RTFLAGS_GET_M(pa->flags) ) pt.m = 0.0;
+
+	}
+	else
+	{
+		/* If it's not an array, just don't handle it */
+		return RT_FAILURE;
+	}
+
+	return ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+}
+
+static RTGEOM*
+parse_geojson_point(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom;
+	RTPOINTARRAY *pa;
+	json_object* coords = NULL;
+
+	RTDEBUGF(3, "parse_geojson_point called with root_srid = %d.", root_srid );
+
+	coords = findMemberByName( geojson, "coordinates" );
+	if ( ! coords ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+		return NULL;
+	}
+	
+	pa = ptarray_construct_empty(ctx, 1, 0, 1);
+	parse_geojson_coord(coords, hasz, pa);
+
+	geom = (RTGEOM *) rtpoint_construct(ctx, root_srid, NULL, pa);
+	RTDEBUG(2, "parse_geojson_point finished.");
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson_linestring(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom;
+	RTPOINTARRAY *pa;
+	json_object* points = NULL;
+	int i = 0;
+
+	RTDEBUG(2, "parse_geojson_linestring called.");
+
+	points = findMemberByName( geojson, "coordinates" );
+	if ( ! points ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+	return NULL;
+	}
+
+	pa = ptarray_construct_empty(ctx, 1, 0, 1);
+
+	if( json_type_array == json_object_get_type( points ) )
+	{
+		const int nPoints = json_object_array_length( points );
+		for(i = 0; i < nPoints; ++i)
+		{
+			json_object* coords = NULL;
+			coords = json_object_array_get_idx( points, i );
+			parse_geojson_coord(coords, hasz, pa);
+		}
+	}
+
+	geom = (RTGEOM *) rtline_construct(ctx, root_srid, NULL, pa);
+
+	RTDEBUG(2, "parse_geojson_linestring finished.");
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson_polygon(json_object *geojson, int *hasz, int root_srid)
+{
+	RTPOINTARRAY **ppa = NULL;
+	json_object* rings = NULL;
+	json_object* points = NULL;
+	int i = 0, j = 0;
+	int nRings = 0, nPoints = 0;
+
+	rings = findMemberByName( geojson, "coordinates" );
+	if ( ! rings ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+		return NULL;
+	}
+
+	if ( json_type_array != json_object_get_type(rings) )
+	{
+		geojson_rterror("The 'coordinates' in GeoJSON are not an array", 4);
+		return NULL;
+	}
+
+	nRings = json_object_array_length( rings );
+
+	/* No rings => POLYGON EMPTY */
+	if ( ! nRings )
+	{
+		return (RTGEOM *)rtpoly_construct_empty(ctx, root_srid, 0, 0);
+	}
+	
+	for ( i = 0; i < nRings; i++ )
+	{
+		points = json_object_array_get_idx(rings, i);
+		if ( ! points || json_object_get_type(points) != json_type_array )
+		{
+			geojson_rterror("The 'coordinates' in GeoJSON ring are not an array", 4);
+			return NULL;
+		}
+		nPoints = json_object_array_length(points);
+		
+		/* Skip empty rings */
+		if ( nPoints == 0 ) continue;
+		
+		if ( ! ppa )
+			ppa = (RTPOINTARRAY**)rtalloc(ctx, sizeof(RTPOINTARRAY*) * nRings);
+		
+		ppa[i] = ptarray_construct_empty(ctx, 1, 0, 1);
+		for ( j = 0; j < nPoints; j++ )
+		{
+			json_object* coords = NULL;
+			coords = json_object_array_get_idx( points, j );
+			parse_geojson_coord(coords, hasz, ppa[i]);
+		}
+	}	
+	
+	/* All the rings were empty! */
+	if ( ! ppa )
+		return (RTGEOM *)rtpoly_construct_empty(ctx, root_srid, 0, 0);
+	
+	return (RTGEOM *) rtpoly_construct(ctx, root_srid, NULL, nRings, ppa);
+}
+
+static RTGEOM*
+parse_geojson_multipoint(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom;
+	int i = 0;
+	json_object* poObjPoints = NULL;
+
+	if (!root_srid)
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, root_srid, 1, 0);
+	}
+	else
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, -1, 1, 0);
+	}
+
+	poObjPoints = findMemberByName( geojson, "coordinates" );
+	if ( ! poObjPoints ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+		return NULL;
+	}
+
+	if( json_type_array == json_object_get_type( poObjPoints ) )
+	{
+		const int nPoints = json_object_array_length( poObjPoints );
+		for( i = 0; i < nPoints; ++i)
+		{
+			RTPOINTARRAY *pa;
+			json_object* poObjCoords = NULL;
+			poObjCoords = json_object_array_get_idx( poObjPoints, i );
+
+			pa = ptarray_construct_empty(ctx, 1, 0, 1);
+			parse_geojson_coord(poObjCoords, hasz, pa);
+
+			geom = (RTGEOM*)rtmpoint_add_rtpoint(ctx, (RTMPOINT*)geom,
+			                                     (RTPOINT*)rtpoint_construct(ctx, root_srid, NULL, pa));
+		}
+	}
+
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson_multilinestring(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom = NULL;
+	int i, j;
+	json_object* poObjLines = NULL;
+
+	if (!root_srid)
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTILINETYPE, root_srid, 1, 0);
+	}
+	else
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTILINETYPE, -1, 1, 0);
+	}
+
+	poObjLines = findMemberByName( geojson, "coordinates" );
+	if ( ! poObjLines ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+		return NULL;
+	}
+
+	if( json_type_array == json_object_get_type( poObjLines ) )
+	{
+		const int nLines = json_object_array_length( poObjLines );
+		for( i = 0; i < nLines; ++i)
+		{
+			RTPOINTARRAY *pa = NULL;
+			json_object* poObjLine = NULL;
+			poObjLine = json_object_array_get_idx( poObjLines, i );
+			pa = ptarray_construct_empty(ctx, 1, 0, 1);
+
+			if( json_type_array == json_object_get_type( poObjLine ) )
+			{
+				const int nPoints = json_object_array_length( poObjLine );
+				for(j = 0; j < nPoints; ++j)
+				{
+					json_object* coords = NULL;
+					coords = json_object_array_get_idx( poObjLine, j );
+					parse_geojson_coord(coords, hasz, pa);
+				}
+
+				geom = (RTGEOM*)rtmline_add_rtline(ctx, (RTMLINE*)geom,
+				                                   (RTLINE*)rtline_construct(ctx, root_srid, NULL, pa));
+			}
+		}
+	}
+
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson_multipolygon(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom = NULL;
+	int i, j, k;
+	json_object* poObjPolys = NULL;
+
+	if (!root_srid)
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTIPOLYGONTYPE, root_srid, 1, 0);
+	}
+	else
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTMULTIPOLYGONTYPE, -1, 1, 0);
+	}
+
+	poObjPolys = findMemberByName( geojson, "coordinates" );
+	if ( ! poObjPolys ) 
+	{
+		geojson_rterror("Unable to find 'coordinates' in GeoJSON string", 4);
+		return NULL;
+	}
+
+	if( json_type_array == json_object_get_type( poObjPolys ) )
+	{
+		const int nPolys = json_object_array_length( poObjPolys );
+
+		for(i = 0; i < nPolys; ++i)
+		{			
+			json_object* poObjPoly = json_object_array_get_idx( poObjPolys, i );
+
+			if( json_type_array == json_object_get_type( poObjPoly ) )
+			{
+				RTPOLY *rtpoly = rtpoly_construct_empty(ctx, geom->srid, rtgeom_has_z(ctx, geom), rtgeom_has_m(ctx, geom));
+				int nRings = json_object_array_length( poObjPoly );
+				
+				for(j = 0; j < nRings; ++j)
+				{
+					json_object* points = json_object_array_get_idx( poObjPoly, j );
+					
+					if( json_type_array == json_object_get_type( points ) )
+					{
+
+						RTPOINTARRAY *pa = ptarray_construct_empty(ctx, 1, 0, 1);
+
+						int nPoints = json_object_array_length( points );
+						for ( k=0; k < nPoints; k++ )
+						{
+							json_object* coords = json_object_array_get_idx( points, k );
+							parse_geojson_coord(coords, hasz, pa);
+						}
+						
+						rtpoly_add_ring(ctx, rtpoly, pa);
+					}
+				}
+				geom = (RTGEOM*)rtmpoly_add_rtpoly(ctx, (RTMPOLY*)geom, rtpoly);
+			}
+		}
+	}
+
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson_geometrycollection(json_object *geojson, int *hasz, int root_srid)
+{
+	RTGEOM *geom = NULL;
+	int i;
+	json_object* poObjGeoms = NULL;
+
+	if (!root_srid)
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, root_srid, 1, 0);
+	}
+	else
+	{
+		geom = (RTGEOM *)rtcollection_construct_empty(ctx, RTCOLLECTIONTYPE, -1, 1, 0);
+	}
+
+	poObjGeoms = findMemberByName( geojson, "geometries" );
+	if ( ! poObjGeoms ) 
+	{
+		geojson_rterror("Unable to find 'geometries' in GeoJSON string", 4);
+		return NULL;
+	}
+
+	if( json_type_array == json_object_get_type( poObjGeoms ) )
+	{
+		const int nGeoms = json_object_array_length( poObjGeoms );
+		json_object* poObjGeom = NULL;
+		for(i = 0; i < nGeoms; ++i )
+		{
+			poObjGeom = json_object_array_get_idx( poObjGeoms, i );
+			geom = (RTGEOM*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION *)geom,
+			                                        parse_geojson(poObjGeom, hasz, root_srid));
+		}
+	}
+
+	return geom;
+}
+
+static RTGEOM*
+parse_geojson(json_object *geojson, int *hasz, int root_srid)
+{
+	json_object* type = NULL;
+	const char* name;
+
+	if( NULL == geojson ) 
+	{
+		geojson_rterror("invalid GeoJSON representation", 2);
+		return NULL;
+	}
+
+	type = findMemberByName( geojson, "type" );
+	if( NULL == type ) 
+	{
+		geojson_rterror("unknown GeoJSON type", 3);
+		return NULL;
+	}
+
+	name = json_object_get_string( type );
+
+	if( strcasecmp( name, "Point" )==0 )
+		return parse_geojson_point(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "LineString" )==0 )
+		return parse_geojson_linestring(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "Polygon" )==0 )
+		return parse_geojson_polygon(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "MultiPoint" )==0 )
+		return parse_geojson_multipoint(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "MultiLineString" )==0 )
+		return parse_geojson_multilinestring(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "MultiPolygon" )==0 )
+		return parse_geojson_multipolygon(geojson, hasz, root_srid);
+
+	if( strcasecmp( name, "GeometryCollection" )==0 )
+		return parse_geojson_geometrycollection(geojson, hasz, root_srid);
+
+	rterror(ctx, "invalid GeoJson representation");
+	return NULL; /* Never reach */
+}
+
+#endif /* HAVE_LIBJSON or HAVE_LIBJSON_C --} */
+
+RTGEOM*
+rtgeom_from_geojson(const RTCTX *ctx, const char *geojson, char **srs)
+{
+#ifndef HAVE_LIBJSON
+	*srs = NULL;
+	rterror(ctx, "You need JSON-C for rtgeom_from_geojson");
+	return NULL;
+#else /* HAVE_LIBJSON */
+
+	/* size_t geojson_size = strlen(geojson); */
+
+	RTGEOM *rtgeom;
+	int hasz=RT_TRUE;
+	json_tokener* jstok = NULL;
+	json_object* poObj = NULL;
+	json_object* poObjSrs = NULL;
+	*srs = NULL;
+
+	/* Begin to Parse json */
+	jstok = json_tokener_new();
+	poObj = json_tokener_parse_ex(jstok, geojson, -1);
+	if( jstok->err != json_tokener_success)
+	{
+		char err[256];
+		snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset);
+		json_tokener_free(jstok);
+		json_object_put(poObj);
+		geojson_rterror(err, 1);
+		return NULL;
+	}
+	json_tokener_free(jstok);
+
+	poObjSrs = findMemberByName( poObj, "crs" );
+	if (poObjSrs != NULL)
+	{
+		json_object* poObjSrsType = findMemberByName( poObjSrs, "type" );
+		if (poObjSrsType != NULL)
+		{
+			json_object* poObjSrsProps = findMemberByName( poObjSrs, "properties" );
+			if ( poObjSrsProps )
+			{
+				json_object* poNameURL = findMemberByName( poObjSrsProps, "name" );
+				if ( poNameURL )
+				{
+					const char* pszName = json_object_get_string( poNameURL );
+					if ( pszName )
+					{
+						*srs = rtalloc(ctx, strlen(pszName) + 1);
+						strcpy(*srs, pszName);
+					}
+				}
+			}
+		}
+	}
+
+	rtgeom = parse_geojson(poObj, &hasz, 0);
+	json_object_put(poObj);
+
+	rtgeom_add_bbox(ctx, rtgeom);
+
+	if (!hasz)
+	{
+		RTGEOM *tmp = rtgeom_force_2d(ctx, rtgeom);
+		rtgeom_free(ctx, rtgeom);
+		rtgeom = tmp;
+
+		RTDEBUG(2, "geom_from_geojson called.");
+	}
+
+	return rtgeom;
+#endif /* HAVE_LIBJSON } */
+}
+
+
diff --git a/src/rtin_twkb.c b/src/rtin_twkb.c
new file mode 100644
index 0000000..c4c7dae
--- /dev/null
+++ b/src/rtin_twkb.c
@@ -0,0 +1,683 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2014 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+#include <math.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include "varint.h"
+
+#define TWKB_IN_MAXCOORDS 4
+
+/**
+* Used for passing the parse state between the parsing functions.
+*/
+typedef struct
+{
+	/* Pointers to the bytes */
+	uint8_t *twkb; /* Points to start of TWKB */
+	uint8_t *twkb_end; /* Points to end of TWKB */
+	uint8_t *pos; /* Current read position */
+
+	uint32_t check; /* Simple validity checks on geometries */
+	uint32_t rttype; /* Current type we are handling */
+
+	uint8_t has_bbox;
+	uint8_t has_size;
+	uint8_t has_idlist;
+	uint8_t has_z;
+	uint8_t has_m;
+	uint8_t is_empty;
+
+	/* Precision factors to convert ints to double */
+	double factor;
+	double factor_z;
+	double factor_m;
+
+	uint64_t size;
+
+	/* Info about current geometry */
+	uint8_t magic_byte; /* the magic byte contain info about if twkb contain id, size info, bboxes and precision */
+
+	int ndims; /* Number of dimensions */
+
+	int64_t *coords; /* An array to keep delta values from 4 dimensions */
+
+} twkb_parse_state;
+
+
+/**
+* Internal function declarations.
+*/
+RTGEOM* rtgeom_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s);
+
+
+/**********************************************************************/
+
+/**
+* Check that we are not about to read off the end of the RTWKB
+* array.
+*/
+static inline void twkb_parse_state_advance(const RTCTX *ctx, twkb_parse_state *s, size_t next)
+{
+	if( (s->pos + next) > s->twkb_end)
+	{
+		rterror(ctx, "%s: TWKB structure does not match expected size!", __func__);
+		// rtnotice(ctx, "TWKB structure does not match expected size!");
+	}
+
+	s->pos += next;
+}
+
+static inline int64_t twkb_parse_state_varint(const RTCTX *ctx, twkb_parse_state *s)
+{
+	size_t size;
+	int64_t val = varint_s64_decode(ctx, s->pos, s->twkb_end, &size);
+	twkb_parse_state_advance(ctx, s, size);
+	return val;
+}
+
+static inline uint64_t twkb_parse_state_uvarint(const RTCTX *ctx, twkb_parse_state *s)
+{
+	size_t size;
+	uint64_t val = varint_u64_decode(ctx, s->pos, s->twkb_end, &size);
+	twkb_parse_state_advance(ctx, s, size);
+	return val;
+}
+
+static inline double twkb_parse_state_double(const RTCTX *ctx, twkb_parse_state *s, double factor)
+{
+	size_t size;
+	int64_t val = varint_s64_decode(ctx, s->pos, s->twkb_end, &size);
+	twkb_parse_state_advance(ctx, s, size);
+	return val / factor;
+}
+
+static inline void twkb_parse_state_varint_skip(const RTCTX *ctx, twkb_parse_state *s)
+{
+	size_t size = varint_size(ctx, s->pos, s->twkb_end);
+
+	if ( ! size )
+		rterror(ctx, "%s: no varint to skip", __func__);
+
+	twkb_parse_state_advance(ctx, s, size);
+	return;
+}
+
+
+
+static uint32_t rttype_from_twkb_type(const RTCTX *ctx, uint8_t twkb_type)
+{
+	switch (twkb_type)
+	{
+		case 1:
+			return RTPOINTTYPE;
+		case 2:
+			return RTLINETYPE;
+		case 3:
+			return RTPOLYGONTYPE;
+		case 4:
+			return RTMULTIPOINTTYPE;
+		case 5:
+			return RTMULTILINETYPE;
+		case 6:
+			return RTMULTIPOLYGONTYPE;
+		case 7:
+			return RTCOLLECTIONTYPE;
+
+		default: /* Error! */
+			rterror(ctx, "Unknown RTWKB type");
+			return 0;
+	}
+	return 0;
+}
+
+/**
+* Byte
+* Read a byte and advance the parse state forward.
+*/
+static uint8_t byte_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	uint8_t val = *(s->pos);
+	twkb_parse_state_advance(ctx, s, RTWKB_BYTE_SIZE);
+	return val;
+}
+
+
+/**
+* RTPOINTARRAY
+* Read a dynamically sized point array and advance the parse state forward.
+*/
+static RTPOINTARRAY* ptarray_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s, uint32_t npoints)
+{
+	RTPOINTARRAY *pa = NULL;
+	uint32_t ndims = s->ndims;
+	int i;
+	double *dlist;
+
+	RTDEBUG(2,"Entering ptarray_from_twkb_state");
+	RTDEBUGF(4,"Pointarray has %d points", npoints);
+
+	/* Empty! */
+	if( npoints == 0 )
+		return ptarray_construct_empty(ctx, s->has_z, s->has_m, 0);
+
+	pa = ptarray_construct(ctx, s->has_z, s->has_m, npoints);
+	dlist = (double*)(pa->serialized_pointlist);
+	for( i = 0; i < npoints; i++ )
+	{
+		int j = 0;
+		/* X */
+		s->coords[j] += twkb_parse_state_varint(ctx, s);
+		dlist[ndims*i + j] = s->coords[j] / s->factor;
+		j++;
+		/* Y */
+		s->coords[j] += twkb_parse_state_varint(ctx, s);
+		dlist[ndims*i + j] = s->coords[j] / s->factor;
+		j++;
+		/* Z */
+		if ( s->has_z )
+		{
+			s->coords[j] += twkb_parse_state_varint(ctx, s);
+			dlist[ndims*i + j] = s->coords[j] / s->factor_z;
+			j++;
+		}
+		/* M */
+		if ( s->has_m )
+		{
+			s->coords[j] += twkb_parse_state_varint(ctx, s);
+			dlist[ndims*i + j] = s->coords[j] / s->factor_m;
+			j++;
+		}
+	}
+
+	return pa;
+}
+
+/**
+* POINT
+*/
+static RTPOINT* rtpoint_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	static uint32_t npoints = 1;
+	RTPOINTARRAY *pa;
+
+	RTDEBUG(2,"Entering rtpoint_from_twkb_state");
+
+	if ( s->is_empty )
+		return rtpoint_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	pa = ptarray_from_twkb_state(ctx, s, npoints);
+	return rtpoint_construct(ctx, SRID_UNKNOWN, NULL, pa);
+}
+
+/**
+* LINESTRING
+*/
+static RTLINE* rtline_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	uint32_t npoints;
+	RTPOINTARRAY *pa;
+
+	RTDEBUG(2,"Entering rtline_from_twkb_state");
+
+	if ( s->is_empty )
+		return rtline_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	/* Read number of points */
+	npoints = twkb_parse_state_uvarint(ctx, s);
+
+	if ( npoints == 0 )
+		return rtline_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	/* Read coordinates */
+	pa = ptarray_from_twkb_state(ctx, s, npoints);
+
+	if( pa == NULL )
+		return rtline_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 2 )
+	{
+		rterror(ctx, "%s must have at least two points", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	return rtline_construct(ctx, SRID_UNKNOWN, NULL, pa);
+}
+
+/**
+* POLYGON
+*/
+static RTPOLY* rtpoly_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	uint32_t nrings;
+	int i;
+	RTPOLY *poly;
+
+	RTDEBUG(2,"Entering rtpoly_from_twkb_state");
+
+	if ( s->is_empty )
+		return rtpoly_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	/* Read number of rings */
+	nrings = twkb_parse_state_uvarint(ctx, s);
+
+	/* Start w/ empty polygon */
+	poly = rtpoly_construct_empty(ctx, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	RTDEBUGF(4,"Polygon has %d rings", nrings);
+
+	/* Empty polygon? */
+	if( nrings == 0 )
+		return poly;
+
+	for( i = 0; i < nrings; i++ )
+	{
+		/* Ret number of points */
+		uint32_t npoints = twkb_parse_state_uvarint(ctx, s);
+		RTPOINTARRAY *pa = ptarray_from_twkb_state(ctx, s, npoints);
+
+		/* Skip empty rings */
+		if( pa == NULL )
+			continue;
+
+		/* Force first and last points to be the same. */
+		if( ! ptarray_is_closed_2d(ctx, pa) )
+		{
+			RTPOINT4D pt;
+			rt_getPoint4d_p(ctx, pa, 0, &pt);
+			ptarray_append_point(ctx, pa, &pt, RT_FALSE);
+		}
+
+		/* Check for at least four points. */
+		if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 4 )
+		{
+			RTDEBUGF(2, "%s must have at least four points in each ring", rttype_name(ctx, s->rttype));
+			rterror(ctx, "%s must have at least four points in each ring", rttype_name(ctx, s->rttype));
+			return NULL;
+		}
+
+		/* Add ring to polygon */
+		if ( rtpoly_add_ring(ctx, poly, pa) == RT_FAILURE )
+		{
+			RTDEBUG(2, "Unable to add ring to polygon");
+			rterror(ctx, "Unable to add ring to polygon");
+		}
+
+	}
+	return poly;
+}
+
+
+/**
+* MULTIPOINT
+*/
+static RTCOLLECTION* rtmultipoint_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	int ngeoms, i;
+	RTGEOM *geom = NULL;
+	RTCOLLECTION *col = rtcollection_construct_empty(ctx, s->rttype, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	RTDEBUG(2,"Entering rtmultipoint_from_twkb_state");
+
+	if ( s->is_empty )
+		return col;
+
+	/* Read number of geometries */
+	ngeoms = twkb_parse_state_uvarint(ctx, s);
+	RTDEBUGF(4,"Number of geometries %d", ngeoms);
+
+	/* It has an idlist, we need to skip that */
+	if ( s->has_idlist )
+	{
+		for ( i = 0; i < ngeoms; i++ )
+			twkb_parse_state_varint_skip(ctx, s);
+	}
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtpoint_as_rtgeom(ctx, rtpoint_from_twkb_state(ctx, s));
+		if ( rtcollection_add_rtgeom(ctx, col, geom) == NULL )
+		{
+			rterror(ctx, "Unable to add geometry (%p) to collection (%p)", geom, col);
+			return NULL;
+		}
+	}
+
+	return col;
+}
+
+/**
+* MULTILINESTRING
+*/
+static RTCOLLECTION* rtmultiline_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	int ngeoms, i;
+	RTGEOM *geom = NULL;
+	RTCOLLECTION *col = rtcollection_construct_empty(ctx, s->rttype, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	RTDEBUG(2,"Entering rtmultilinestring_from_twkb_state");
+
+	if ( s->is_empty )
+		return col;
+
+	/* Read number of geometries */
+	ngeoms = twkb_parse_state_uvarint(ctx, s);
+
+	RTDEBUGF(4,"Number of geometries %d",ngeoms);
+
+	/* It has an idlist, we need to skip that */
+	if ( s->has_idlist )
+	{
+		for ( i = 0; i < ngeoms; i++ )
+			twkb_parse_state_varint_skip(ctx, s);
+	}
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtline_as_rtgeom(ctx, rtline_from_twkb_state(ctx, s));
+		if ( rtcollection_add_rtgeom(ctx, col, geom) == NULL )
+		{
+			rterror(ctx, "Unable to add geometry (%p) to collection (%p)", geom, col);
+			return NULL;
+		}
+	}
+
+	return col;
+}
+
+/**
+* MULTIPOLYGON
+*/
+static RTCOLLECTION* rtmultipoly_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	int ngeoms, i;
+	RTGEOM *geom = NULL;
+	RTCOLLECTION *col = rtcollection_construct_empty(ctx, s->rttype, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	RTDEBUG(2,"Entering rtmultipolygon_from_twkb_state");
+
+	if ( s->is_empty )
+		return col;
+
+	/* Read number of geometries */
+	ngeoms = twkb_parse_state_uvarint(ctx, s);
+	RTDEBUGF(4,"Number of geometries %d",ngeoms);
+
+	/* It has an idlist, we need to skip that */
+	if ( s->has_idlist )
+	{
+		for ( i = 0; i < ngeoms; i++ )
+			twkb_parse_state_varint_skip(ctx, s);
+	}
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtpoly_as_rtgeom(ctx, rtpoly_from_twkb_state(ctx, s));
+		if ( rtcollection_add_rtgeom(ctx, col, geom) == NULL )
+		{
+			rterror(ctx, "Unable to add geometry (%p) to collection (%p)", geom, col);
+			return NULL;
+		}
+	}
+
+	return col;
+}
+
+
+/**
+* COLLECTION, RTMULTIPOINTTYPE, RTMULTILINETYPE, RTMULTIPOLYGONTYPE
+**/
+static RTCOLLECTION* rtcollection_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	int ngeoms, i;
+	RTGEOM *geom = NULL;
+	RTCOLLECTION *col = rtcollection_construct_empty(ctx, s->rttype, SRID_UNKNOWN, s->has_z, s->has_m);
+
+	RTDEBUG(2,"Entering rtcollection_from_twkb_state");
+
+	if ( s->is_empty )
+		return col;
+
+	/* Read number of geometries */
+	ngeoms = twkb_parse_state_uvarint(ctx, s);
+
+	RTDEBUGF(4,"Number of geometries %d",ngeoms);
+
+	/* It has an idlist, we need to skip that */
+	if ( s->has_idlist )
+	{
+		for ( i = 0; i < ngeoms; i++ )
+			twkb_parse_state_varint_skip(ctx, s);
+	}
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtgeom_from_twkb_state(ctx, s);
+		if ( rtcollection_add_rtgeom(ctx, col, geom) == NULL )
+		{
+			rterror(ctx, "Unable to add geometry (%p) to collection (%p)", geom, col);
+			return NULL;
+		}
+	}
+
+
+	return col;
+}
+
+
+static void header_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	RTDEBUG(2,"Entering magicbyte_from_twkb_state");
+
+	uint8_t extended_dims;
+
+	/* Read the first two bytes */
+	uint8_t type_precision = byte_from_twkb_state(ctx, s);
+	uint8_t metadata = byte_from_twkb_state(ctx, s);
+
+	/* Strip type and precision out of first byte */
+	uint8_t type = type_precision & 0x0F;
+	int8_t precision = unzigzag8(ctx, (type_precision & 0xF0) >> 4);
+
+	/* Convert TWKB type to internal type */
+	s->rttype = rttype_from_twkb_type(ctx, type);
+
+	/* Convert the precision into factor */
+	s->factor = pow(10, (double)precision);
+
+	/* Strip metadata flags out of second byte */
+	s->has_bbox   =  metadata & 0x01;
+	s->has_size   = (metadata & 0x02) >> 1;
+	s->has_idlist = (metadata & 0x04) >> 2;
+	extended_dims = (metadata & 0x08) >> 3;
+	s->is_empty   = (metadata & 0x10) >> 4;
+
+	/* Flag for higher dims means read a third byte */
+	if ( extended_dims )
+	{
+		int8_t precision_z, precision_m;
+
+		extended_dims = byte_from_twkb_state(ctx, s);
+
+		/* Strip Z/M presence and precision from ext byte */
+		s->has_z    = (extended_dims & 0x01);
+		s->has_m    = (extended_dims & 0x02) >> 1;
+		precision_z = (extended_dims & 0x1C) >> 2;
+		precision_m = (extended_dims & 0xE0) >> 5;
+
+		/* Convert the precision into factor */
+		s->factor_z = pow(10, (double)precision_z);
+		s->factor_m = pow(10, (double)precision_m);
+	}
+	else
+	{
+		s->has_z = 0;
+		s->has_m = 0;
+		s->factor_z = 0;
+		s->factor_m = 0;
+	}
+
+	/* Read the size, if there is one */
+	if ( s->has_size )
+	{
+		s->size = twkb_parse_state_uvarint(ctx, s);
+	}
+
+	/* Calculate the number of dimensions */
+	s->ndims = 2 + s->has_z + s->has_m;
+
+	return;
+}
+
+
+
+/**
+* Generic handling for TWKB geometries. The front of every TWKB geometry
+* (including those embedded in collections) is a type byte and metadata byte,
+* then optional size, bbox, etc. Read those, then switch to particular type
+* handling code.
+*/
+RTGEOM* rtgeom_from_twkb_state(const RTCTX *ctx, twkb_parse_state *s)
+{
+	RTGBOX bbox;
+	RTGEOM *geom = NULL;
+	uint32_t has_bbox = RT_FALSE;
+	int i;
+
+	/* Read the first two bytes, and optional */
+	/* extended precision info and optional size info */
+	header_from_twkb_state(ctx, s);
+
+	/* Just experienced a geometry header, so now we */
+	/* need to reset our coordinate deltas */
+	for ( i = 0; i < TWKB_IN_MAXCOORDS; i++ )
+	{
+		s->coords[i] = 0.0;
+	}
+
+	/* Read the bounding box, is there is one */
+	if ( s->has_bbox )
+	{
+		/* Initialize */
+		has_bbox = s->has_bbox;
+		memset(&bbox, 0, sizeof(RTGBOX));
+		bbox.flags = gflags(ctx, s->has_z, s->has_m, 0);
+
+		/* X */
+		bbox.xmin = twkb_parse_state_double(ctx, s, s->factor);
+		bbox.xmax = bbox.xmin + twkb_parse_state_double(ctx, s, s->factor);
+		/* Y */
+		bbox.ymin = twkb_parse_state_double(ctx, s, s->factor);
+		bbox.ymax = bbox.ymin + twkb_parse_state_double(ctx, s, s->factor);
+		/* Z */
+		if ( s->has_z )
+		{
+			bbox.zmin = twkb_parse_state_double(ctx, s, s->factor_z);
+			bbox.zmax = bbox.zmin + twkb_parse_state_double(ctx, s, s->factor_z);
+		}
+		/* M */
+		if ( s->has_z )
+		{
+			bbox.mmin = twkb_parse_state_double(ctx, s, s->factor_m);
+			bbox.mmax = bbox.mmin + twkb_parse_state_double(ctx, s, s->factor_m);
+		}
+	}
+
+	/* Switch to code for the particular type we're dealing with */
+	switch( s->rttype )
+	{
+		case RTPOINTTYPE:
+			geom = rtpoint_as_rtgeom(ctx, rtpoint_from_twkb_state(ctx, s));
+			break;
+		case RTLINETYPE:
+			geom = rtline_as_rtgeom(ctx, rtline_from_twkb_state(ctx, s));
+			break;
+		case RTPOLYGONTYPE:
+			geom = rtpoly_as_rtgeom(ctx, rtpoly_from_twkb_state(ctx, s));
+			break;
+		case RTMULTIPOINTTYPE:
+			geom = rtcollection_as_rtgeom(ctx, rtmultipoint_from_twkb_state(ctx, s));
+			break;
+		case RTMULTILINETYPE:
+			geom = rtcollection_as_rtgeom(ctx, rtmultiline_from_twkb_state(ctx, s));
+			break;
+		case RTMULTIPOLYGONTYPE:
+			geom = rtcollection_as_rtgeom(ctx, rtmultipoly_from_twkb_state(ctx, s));
+			break;
+		case RTCOLLECTIONTYPE:
+			geom = rtcollection_as_rtgeom(ctx, rtcollection_from_twkb_state(ctx, s));
+			break;
+		/* Unknown type! */
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]", rttype_name(ctx, s->rttype), s->rttype);
+			break;
+	}
+
+	if ( has_bbox )
+	{
+		geom->bbox = gbox_clone(ctx, &bbox);
+	}
+
+	return geom;
+}
+
+
+/**
+* RTWKB inputs *must* have a declared size, to prevent malformed RTWKB from reading
+* off the end of the memory segment (this stops a malevolent user from declaring
+* a one-ring polygon to have 10 rings, causing the RTWKB reader to walk off the
+* end of the memory).
+*
+* Check is a bitmask of: RT_PARSER_CHECK_MINPOINTS, RT_PARSER_CHECK_ODD,
+* RT_PARSER_CHECK_CLOSURE, RT_PARSER_CHECK_NONE, RT_PARSER_CHECK_ALL
+*/
+RTGEOM* rtgeom_from_twkb(const RTCTX *ctx, uint8_t *twkb, size_t twkb_size, char check)
+{
+	int64_t coords[TWKB_IN_MAXCOORDS] = {0, 0, 0, 0};
+	twkb_parse_state s;
+
+	RTDEBUG(2,"Entering rtgeom_from_twkb");
+	RTDEBUGF(4,"twkb_size: %d",(int) twkb_size);
+
+	/* Zero out the state */
+	memset(&s, 0, sizeof(twkb_parse_state));
+
+	/* Initialize the state appropriately */
+	s.twkb = s.pos = twkb;
+	s.twkb_end = twkb + twkb_size;
+	s.check = check;
+	s.coords = coords;
+
+	/* Handle the check catch-all values */
+	if ( check & RT_PARSER_CHECK_NONE )
+		s.check = 0;
+	else
+		s.check = check;
+
+
+	/* Read the rest of the geometry */
+	return rtgeom_from_twkb_state(ctx, &s);
+}
diff --git a/src/rtin_wkb.c b/src/rtin_wkb.c
new file mode 100644
index 0000000..6b5fa48
--- /dev/null
+++ b/src/rtin_wkb.c
@@ -0,0 +1,806 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "rttopo_config.h"
+/*#define RTGEOM_DEBUG_LEVEL 4*/
+#include "librtgeom_internal.h" /* NOTE: includes rtgeom_log.h */
+#include "rtgeom_log.h"
+#include <math.h>
+
+/**
+* Used for passing the parse state between the parsing functions.
+*/
+typedef struct 
+{
+	const uint8_t *wkb; /* Points to start of RTWKB */
+	size_t wkb_size; /* Expected size of RTWKB */
+	int swap_bytes; /* Do an endian flip? */
+	int check; /* Simple validity checks on geometries */
+	uint32_t rttype; /* Current type we are handling */
+	uint32_t srid; /* Current SRID we are handling */
+	int has_z; /* Z? */
+	int has_m; /* M? */
+	int has_srid; /* SRID? */
+	const uint8_t *pos; /* Current parse position */
+} wkb_parse_state;
+
+
+/**
+* Internal function declarations.
+*/
+RTGEOM* rtgeom_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s);
+
+
+
+/**********************************************************************/
+
+/* Our static character->number map. Anything > 15 is invalid */
+static uint8_t hex2char[256] = {
+    /* not Hex characters */
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    /* 0-9 */
+    0,1,2,3,4,5,6,7,8,9,20,20,20,20,20,20,
+    /* A-F */
+    20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20,
+    /* not Hex characters */
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+	/* a-f */
+    20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    /* not Hex characters (upper 128 characters) */
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,
+    20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20
+    };
+
+
+uint8_t* bytes_from_hexbytes(const RTCTX *ctx, const char *hexbuf, size_t hexsize)
+{
+	uint8_t *buf = NULL;
+	register uint8_t h1, h2;
+	int i;
+	
+	if( hexsize % 2 )
+		rterror(ctx, "Invalid hex string, length (%d) has to be a multiple of two!", hexsize);
+
+	buf = rtalloc(ctx, hexsize/2);
+	
+	if( ! buf )
+		rterror(ctx, "Unable to allocate memory buffer.");
+		
+	for( i = 0; i < hexsize/2; i++ )
+	{
+		h1 = hex2char[(int)hexbuf[2*i]];
+		h2 = hex2char[(int)hexbuf[2*i+1]];
+		if( h1 > 15 )
+			rterror(ctx, "Invalid hex character (%c) encountered", hexbuf[2*i]);
+		if( h2 > 15 )
+			rterror(ctx, "Invalid hex character (%c) encountered", hexbuf[2*i+1]);
+		/* First character is high bits, second is low bits */
+		buf[i] = ((h1 & 0x0F) << 4) | (h2 & 0x0F);
+	}
+	return buf;
+}
+
+
+/**********************************************************************/
+
+
+
+
+
+/**
+* Check that we are not about to read off the end of the RTWKB 
+* array.
+*/
+static inline void wkb_parse_state_check(const RTCTX *ctx, wkb_parse_state *s, size_t next)
+{
+	if( (s->pos + next) > (s->wkb + s->wkb_size) )
+		rterror(ctx, "RTWKB structure does not match expected size!");
+} 
+
+/**
+* Take in an unknown kind of wkb type number and ensure it comes out
+* as an extended RTWKB type number (with Z/M/SRID flags masked onto the 
+* high bits).
+*/
+static void rttype_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s, uint32_t wkb_type)
+{
+	uint32_t wkb_simple_type;
+	
+	RTDEBUG(4, "Entered function");
+	
+	s->has_z = RT_FALSE;
+	s->has_m = RT_FALSE;
+	s->has_srid = RT_FALSE;
+
+	/* If any of the higher bits are set, this is probably an extended type. */
+	if( wkb_type & 0xF0000000 )
+	{
+		if( wkb_type & RTWKBZOFFSET ) s->has_z = RT_TRUE;
+		if( wkb_type & RTWKBMOFFSET ) s->has_m = RT_TRUE;
+		if( wkb_type & RTWKBSRIDFLAG ) s->has_srid = RT_TRUE;
+		RTDEBUGF(4, "Extended type: has_z=%d has_m=%d has_srid=%d", s->has_z, s->has_m, s->has_srid);
+	}
+	
+	/* Mask off the flags */
+	wkb_type = wkb_type & 0x0FFFFFFF;
+	/* Strip out just the type number (1-12) from the ISO number (eg 3001-3012) */
+	wkb_simple_type = wkb_type % 1000;
+	
+	/* Extract the Z/M information from ISO style numbers */
+	if( wkb_type >= 3000 && wkb_type < 4000 )
+	{
+		s->has_z = RT_TRUE;
+		s->has_m = RT_TRUE;
+	}
+	else if ( wkb_type >= 2000 && wkb_type < 3000 )
+	{
+		s->has_m = RT_TRUE;
+	}
+	else if ( wkb_type >= 1000 && wkb_type < 2000 )
+	{
+		s->has_z = RT_TRUE;
+	}
+
+	switch (wkb_simple_type)
+	{
+		case RTWKB_POINT_TYPE: 
+			s->rttype = RTPOINTTYPE;
+			break;
+		case RTWKB_LINESTRING_TYPE: 
+			s->rttype = RTLINETYPE;
+			break;
+		case RTWKB_POLYGON_TYPE:
+			s->rttype = RTPOLYGONTYPE;
+			break;
+		case RTWKB_MULTIPOINT_TYPE:
+			s->rttype = RTMULTIPOINTTYPE;
+			break;
+		case RTWKB_MULTILINESTRING_TYPE:
+			s->rttype = RTMULTILINETYPE;
+			break;
+		case RTWKB_MULTIPOLYGON_TYPE:
+			s->rttype = RTMULTIPOLYGONTYPE;
+			break;
+		case RTWKB_GEOMETRYCOLLECTION_TYPE: 
+			s->rttype = RTCOLLECTIONTYPE;
+			break;
+		case RTWKB_CIRCULARSTRING_TYPE:
+			s->rttype = RTCIRCSTRINGTYPE;
+			break;
+		case RTWKB_COMPOUNDCURVE_TYPE:
+			s->rttype = RTCOMPOUNDTYPE;
+			break;
+		case RTWKB_CURVEPOLYGON_TYPE:
+			s->rttype = RTCURVEPOLYTYPE;
+			break;
+		case RTWKB_MULTICURVE_TYPE:
+			s->rttype = RTMULTICURVETYPE;
+			break;
+		case RTWKB_MULTISURFACE_TYPE: 
+			s->rttype = RTMULTISURFACETYPE;
+			break;
+		case RTWKB_POLYHEDRALSURFACE_TYPE:
+			s->rttype = RTPOLYHEDRALSURFACETYPE;
+			break;
+		case RTWKB_TIN_TYPE:
+			s->rttype = RTTINTYPE;
+			break;
+		case RTWKB_TRIANGLE_TYPE:
+			s->rttype = RTTRIANGLETYPE;
+			break;
+		
+		/* PostGIS 1.5 emits 13, 14 for CurvePolygon, MultiCurve */
+		/* These numbers aren't SQL/MM (numbers currently only */
+		/* go up to 12. We can handle the old data here (for now??) */
+		/* converting them into the rttypes that are intended. */
+		case RTWKB_CURVE_TYPE:
+			s->rttype = RTCURVEPOLYTYPE;
+			break;
+		case RTWKB_SURFACE_TYPE:
+			s->rttype = RTMULTICURVETYPE;
+			break;
+		
+		default: /* Error! */
+			rterror(ctx, "Unknown RTWKB type (%d)! Full RTWKB type number was (%d).", wkb_simple_type, wkb_type);
+			break;	
+	}
+
+	RTDEBUGF(4,"Got rttype %s (%u)", rttype_name(ctx, s->rttype), s->rttype);
+
+	return;
+}
+
+/**
+* Byte
+* Read a byte and advance the parse state forward.
+*/
+static char byte_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	char char_value = 0;
+	RTDEBUG(4, "Entered function");
+
+	wkb_parse_state_check(ctx, s, RTWKB_BYTE_SIZE);
+	RTDEBUG(4, "Passed state check");
+	
+	char_value = s->pos[0];
+	RTDEBUGF(4, "Read byte value: %x", char_value);
+	s->pos += RTWKB_BYTE_SIZE;
+	
+	return char_value;
+}
+
+/**
+* Int32
+* Read 4-byte integer and advance the parse state forward.
+*/
+static uint32_t integer_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	uint32_t i = 0;
+
+	wkb_parse_state_check(ctx, s, RTWKB_INT_SIZE);
+	
+	memcpy(&i, s->pos, RTWKB_INT_SIZE);
+	
+	/* Swap? Copy into a stack-allocated integer. */
+	if( s->swap_bytes )
+	{
+		int j = 0;
+		uint8_t tmp;
+		
+		for( j = 0; j < RTWKB_INT_SIZE/2; j++ )
+		{
+			tmp = ((uint8_t*)(&i))[j];
+			((uint8_t*)(&i))[j] = ((uint8_t*)(&i))[RTWKB_INT_SIZE - j - 1];
+			((uint8_t*)(&i))[RTWKB_INT_SIZE - j - 1] = tmp;
+		}
+	}
+
+	s->pos += RTWKB_INT_SIZE;
+	return i;
+}
+
+/**
+* Double
+* Read an 8-byte double and advance the parse state forward.
+*/
+static double double_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	double d = 0;
+
+	wkb_parse_state_check(ctx, s, RTWKB_DOUBLE_SIZE);
+
+	memcpy(&d, s->pos, RTWKB_DOUBLE_SIZE);
+
+	/* Swap? Copy into a stack-allocated integer. */
+	if( s->swap_bytes )
+	{
+		int i = 0;
+		uint8_t tmp;
+		
+		for( i = 0; i < RTWKB_DOUBLE_SIZE/2; i++ )
+		{
+			tmp = ((uint8_t*)(&d))[i];
+			((uint8_t*)(&d))[i] = ((uint8_t*)(&d))[RTWKB_DOUBLE_SIZE - i - 1];
+			((uint8_t*)(&d))[RTWKB_DOUBLE_SIZE - i - 1] = tmp;
+		}
+
+	}
+
+	s->pos += RTWKB_DOUBLE_SIZE;
+	return d;
+}
+
+/**
+* RTPOINTARRAY
+* Read a dynamically sized point array and advance the parse state forward.
+* First read the number of points, then read the points.
+*/
+static RTPOINTARRAY* ptarray_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	RTPOINTARRAY *pa = NULL;
+	size_t pa_size;
+	uint32_t ndims = 2;
+	uint32_t npoints = 0;
+
+	/* Calculate the size of this point array. */
+	npoints = integer_from_wkb_state(ctx, s);
+
+	RTDEBUGF(4,"Pointarray has %d points", npoints);
+
+	if( s->has_z ) ndims++;
+	if( s->has_m ) ndims++;
+	pa_size = npoints * ndims * RTWKB_DOUBLE_SIZE;
+
+	/* Empty! */
+	if( npoints == 0 )
+		return ptarray_construct(ctx, s->has_z, s->has_m, npoints);
+
+	/* Does the data we want to read exist? */
+	wkb_parse_state_check(ctx, s, pa_size);
+	
+	/* If we're in a native endianness, we can just copy the data directly! */
+	if( ! s->swap_bytes )
+	{
+		pa = ptarray_construct_copy_data(ctx, s->has_z, s->has_m, npoints, (uint8_t*)s->pos);
+		s->pos += pa_size;
+	}
+	/* Otherwise we have to read each double, separately. */
+	else
+	{
+		int i = 0;
+		double *dlist;
+		pa = ptarray_construct(ctx, s->has_z, s->has_m, npoints);
+		dlist = (double*)(pa->serialized_pointlist);
+		for( i = 0; i < npoints * ndims; i++ )
+		{
+			dlist[i] = double_from_wkb_state(ctx, s);
+		}
+	}
+
+	return pa;
+}
+
+/**
+* POINT
+* Read a RTWKB point, starting just after the endian byte, 
+* type number and optional srid number.
+* Advance the parse state forward appropriately.
+* RTWKB point has just a set of doubles, with the quantity depending on the 
+* dimension of the point, so this looks like a special case of the above
+* with only one point.
+*/
+static RTPOINT* rtpoint_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	static uint32_t npoints = 1;
+	RTPOINTARRAY *pa = NULL;
+	size_t pa_size;
+	uint32_t ndims = 2;
+	const RTPOINT2D *pt;
+
+	/* Count the dimensions. */
+	if( s->has_z ) ndims++;
+	if( s->has_m ) ndims++;
+	pa_size = ndims * RTWKB_DOUBLE_SIZE;
+
+	/* Does the data we want to read exist? */
+	wkb_parse_state_check(ctx, s, pa_size);
+
+	/* If we're in a native endianness, we can just copy the data directly! */
+	if( ! s->swap_bytes )
+	{
+		pa = ptarray_construct_copy_data(ctx, s->has_z, s->has_m, npoints, (uint8_t*)s->pos);
+		s->pos += pa_size;
+	}
+	/* Otherwise we have to read each double, separately */
+	else
+	{
+		int i = 0;
+		double *dlist;
+		pa = ptarray_construct(ctx, s->has_z, s->has_m, npoints);
+		dlist = (double*)(pa->serialized_pointlist);
+		for( i = 0; i < ndims; i++ )
+		{
+			dlist[i] = double_from_wkb_state(ctx, s);
+		}
+	}
+	
+	/* Check for POINT(NaN NaN) ==> POINT EMPTY */
+	pt = rt_getPoint2d_cp(ctx, pa, 0);
+	if ( isnan(pt->x) && isnan(pt->y) )
+	{
+		ptarray_free(ctx, pa);
+		return rtpoint_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+	}
+	else
+	{
+		return rtpoint_construct(ctx, s->srid, NULL, pa);
+	}
+}
+
+/**
+* LINESTRING
+* Read a RTWKB linestring, starting just after the endian byte, 
+* type number and optional srid number. Advance the parse state 
+* forward appropriately. 
+* There is only one pointarray in a linestring. Optionally
+* check for minimal following of rules (two point minimum).
+*/
+static RTLINE* rtline_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	RTPOINTARRAY *pa = ptarray_from_wkb_state(ctx, s);
+
+	if( pa == NULL || pa->npoints == 0 )
+		return rtline_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+
+	if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 2 )
+	{
+		rterror(ctx, "%s must have at least two points", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	return rtline_construct(ctx, s->srid, NULL, pa);
+}
+
+/**
+* CIRCULARSTRING
+* Read a RTWKB circularstring, starting just after the endian byte, 
+* type number and optional srid number. Advance the parse state 
+* forward appropriately. 
+* There is only one pointarray in a linestring. Optionally
+* check for minimal following of rules (three point minimum,
+* odd number of points).
+*/
+static RTCIRCSTRING* rtcircstring_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	RTPOINTARRAY *pa = ptarray_from_wkb_state(ctx, s);
+
+	if( pa == NULL || pa->npoints == 0 )
+		return rtcircstring_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+
+	if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 3 )
+	{
+		rterror(ctx, "%s must have at least three points", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	if( s->check & RT_PARSER_CHECK_ODD && ! (pa->npoints % 2) )
+	{
+		rterror(ctx, "%s must have an odd number of points", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	return rtcircstring_construct(ctx, s->srid, NULL, pa);	
+}
+
+/**
+* POLYGON
+* Read a RTWKB polygon, starting just after the endian byte, 
+* type number and optional srid number. Advance the parse state 
+* forward appropriately. 
+* First read the number of rings, then read each ring
+* (which are structured as point arrays)
+*/
+static RTPOLY* rtpoly_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	uint32_t nrings = integer_from_wkb_state(ctx, s);
+	int i = 0;
+	RTPOLY *poly = rtpoly_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+
+	RTDEBUGF(4,"Polygon has %d rings", nrings);
+	
+	/* Empty polygon? */
+	if( nrings == 0 )
+		return poly;
+
+	for( i = 0; i < nrings; i++ )
+	{
+		RTPOINTARRAY *pa = ptarray_from_wkb_state(ctx, s);
+		if( pa == NULL )
+			continue;
+
+		/* Check for at least four points. */
+		if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 4 )
+		{
+			RTDEBUGF(2, "%s must have at least four points in each ring", rttype_name(ctx, s->rttype));
+			rterror(ctx, "%s must have at least four points in each ring", rttype_name(ctx, s->rttype));
+			return NULL;
+		}
+
+		/* Check that first and last points are the same. */
+		if( s->check & RT_PARSER_CHECK_CLOSURE && ! ptarray_is_closed_2d(ctx, pa) )
+		{
+			RTDEBUGF(2, "%s must have closed rings", rttype_name(ctx, s->rttype));
+			rterror(ctx, "%s must have closed rings", rttype_name(ctx, s->rttype));
+			return NULL;
+		}
+		
+		/* Add ring to polygon */
+		if ( rtpoly_add_ring(ctx, poly, pa) == RT_FAILURE )
+		{
+			RTDEBUG(2, "Unable to add ring to polygon");
+			rterror(ctx, "Unable to add ring to polygon");
+		}
+
+	}
+	return poly;
+}
+
+/**
+* TRIANGLE
+* Read a RTWKB triangle, starting just after the endian byte, 
+* type number and optional srid number. Advance the parse state 
+* forward appropriately. 
+* Triangles are encoded like polygons in RTWKB, but more like linestrings
+* as rtgeometries.
+*/
+static RTTRIANGLE* rttriangle_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	uint32_t nrings = integer_from_wkb_state(ctx, s);
+	RTTRIANGLE *tri = rttriangle_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+	RTPOINTARRAY *pa = NULL;
+
+	/* Empty triangle? */
+	if( nrings == 0 )
+		return tri;
+
+	/* Should be only one ring. */
+	if ( nrings != 1 )
+		rterror(ctx, "Triangle has wrong number of rings: %d", nrings);
+
+	/* There's only one ring, we hope? */	
+	pa = ptarray_from_wkb_state(ctx, s);
+
+	/* If there's no points, return an empty triangle. */
+	if( pa == NULL )
+		return tri;
+
+	/* Check for at least four points. */
+	if( s->check & RT_PARSER_CHECK_MINPOINTS && pa->npoints < 4 )
+	{
+		RTDEBUGF(2, "%s must have at least four points", rttype_name(ctx, s->rttype));
+		rterror(ctx, "%s must have at least four points", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	if( s->check & RT_PARSER_CHECK_CLOSURE && ! ptarray_is_closed(ctx, pa) )
+	{
+		rterror(ctx, "%s must have closed rings", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	if( s->check & RT_PARSER_CHECK_ZCLOSURE && ! ptarray_is_closed_z(ctx, pa) )
+	{
+		rterror(ctx, "%s must have closed rings", rttype_name(ctx, s->rttype));
+		return NULL;
+	}
+
+	/* Empty TRIANGLE starts w/ empty RTPOINTARRAY, free it first */
+	if (tri->points)
+		ptarray_free(ctx, tri->points);
+	
+	tri->points = pa;	
+	return tri;
+}
+
+/**
+* RTCURVEPOLYTYPE
+*/
+static RTCURVEPOLY* rtcurvepoly_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	uint32_t ngeoms = integer_from_wkb_state(ctx, s);
+	RTCURVEPOLY *cp = rtcurvepoly_construct_empty(ctx, s->srid, s->has_z, s->has_m);
+	RTGEOM *geom = NULL;
+	int i;
+	
+	/* Empty collection? */
+	if ( ngeoms == 0 )
+		return cp;
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtgeom_from_wkb_state(ctx, s);
+		if ( rtcurvepoly_add_ring(ctx, cp, geom) == RT_FAILURE )
+			rterror(ctx, "Unable to add geometry (%p) to curvepoly (%p)", geom, cp);
+	}
+	
+	return cp;
+}
+
+/**
+* RTPOLYHEDRALSURFACETYPE
+*/
+
+/**
+* COLLECTION, RTMULTIPOINTTYPE, RTMULTILINETYPE, RTMULTIPOLYGONTYPE, RTCOMPOUNDTYPE,
+* RTMULTICURVETYPE, RTMULTISURFACETYPE, 
+* RTTINTYPE
+*/
+static RTCOLLECTION* rtcollection_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	uint32_t ngeoms = integer_from_wkb_state(ctx, s);
+	RTCOLLECTION *col = rtcollection_construct_empty(ctx, s->rttype, s->srid, s->has_z, s->has_m);
+	RTGEOM *geom = NULL;
+	int i;
+
+	RTDEBUGF(4,"Collection has %d components", ngeoms);
+	
+	/* Empty collection? */
+	if ( ngeoms == 0 )
+		return col;
+
+	/* Be strict in polyhedral surface closures */
+	if ( s->rttype == RTPOLYHEDRALSURFACETYPE )
+		s->check |= RT_PARSER_CHECK_ZCLOSURE;
+
+	for ( i = 0; i < ngeoms; i++ )
+	{
+		geom = rtgeom_from_wkb_state(ctx, s);
+		if ( rtcollection_add_rtgeom(ctx, col, geom) == NULL )
+		{
+			rterror(ctx, "Unable to add geometry (%p) to collection (%p)", geom, col);
+			return NULL;
+		}
+	}
+	
+	return col;
+}
+
+
+/**
+* GEOMETRY
+* Generic handling for RTWKB geometries. The front of every RTWKB geometry
+* (including those embedded in collections) is an endian byte, a type
+* number and an optional srid number. We handle all those here, then pass
+* to the appropriate handler for the specific type.
+*/
+RTGEOM* rtgeom_from_wkb_state(const RTCTX *ctx, wkb_parse_state *s)
+{
+	char wkb_little_endian;
+	uint32_t wkb_type;
+	
+	RTDEBUG(4,"Entered function");
+	
+	/* Fail when handed incorrect starting byte */
+	wkb_little_endian = byte_from_wkb_state(ctx, s);
+	if( wkb_little_endian != 1 && wkb_little_endian != 0 )
+	{
+		RTDEBUG(4,"Leaving due to bad first byte!");
+		rterror(ctx, "Invalid endian flag value encountered.");
+		return NULL;
+	}
+
+	/* Check the endianness of our input  */
+	s->swap_bytes = RT_FALSE;
+	if( getMachineEndian(ctx) == NDR ) /* Machine arch is little */
+	{
+		if ( ! wkb_little_endian )    /* Data is big! */
+			s->swap_bytes = RT_TRUE;
+	}
+	else                              /* Machine arch is big */
+	{
+		if ( wkb_little_endian )      /* Data is little! */
+			s->swap_bytes = RT_TRUE;
+	}
+
+	/* Read the type number */
+	wkb_type = integer_from_wkb_state(ctx, s);
+	RTDEBUGF(4,"Got RTWKB type number: 0x%X", wkb_type);
+	rttype_from_wkb_state(ctx, s, wkb_type);
+	
+	/* Read the SRID, if necessary */
+	if( s->has_srid )
+	{
+		s->srid = clamp_srid(ctx, integer_from_wkb_state(ctx, s));
+		/* TODO: warn on explicit UNKNOWN srid ? */
+		RTDEBUGF(4,"Got SRID: %u", s->srid);
+	}
+	
+	/* Do the right thing */
+	switch( s->rttype )
+	{
+		case RTPOINTTYPE:
+			return (RTGEOM*)rtpoint_from_wkb_state(ctx, s);
+			break;
+		case RTLINETYPE:
+			return (RTGEOM*)rtline_from_wkb_state(ctx, s);
+			break;
+		case RTCIRCSTRINGTYPE:
+			return (RTGEOM*)rtcircstring_from_wkb_state(ctx, s);
+			break;
+		case RTPOLYGONTYPE:
+			return (RTGEOM*)rtpoly_from_wkb_state(ctx, s);
+			break;
+		case RTTRIANGLETYPE:
+			return (RTGEOM*)rttriangle_from_wkb_state(ctx, s);
+			break;
+		case RTCURVEPOLYTYPE:
+			return (RTGEOM*)rtcurvepoly_from_wkb_state(ctx, s);
+			break;
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+		case RTPOLYHEDRALSURFACETYPE:
+		case RTTINTYPE:
+		case RTCOLLECTIONTYPE:
+			return (RTGEOM*)rtcollection_from_wkb_state(ctx, s);
+			break;
+
+		/* Unknown type! */
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]", rttype_name(ctx, s->rttype), s->rttype);
+	}
+
+	/* Return value to keep compiler happy. */
+	return NULL;
+	
+}
+
+/* TODO add check for SRID consistency */
+
+/**
+* RTWKB inputs *must* have a declared size, to prevent malformed RTWKB from reading
+* off the end of the memory segment (this stops a malevolent user from declaring
+* a one-ring polygon to have 10 rings, causing the RTWKB reader to walk off the 
+* end of the memory).
+*
+* Check is a bitmask of: RT_PARSER_CHECK_MINPOINTS, RT_PARSER_CHECK_ODD, 
+* RT_PARSER_CHECK_CLOSURE, RT_PARSER_CHECK_NONE, RT_PARSER_CHECK_ALL
+*/
+RTGEOM* rtgeom_from_wkb(const RTCTX *ctx, const uint8_t *wkb, const size_t wkb_size, const char check)
+{
+	wkb_parse_state s;
+	
+	/* Initialize the state appropriately */
+	s.wkb = wkb;
+	s.wkb_size = wkb_size;
+	s.swap_bytes = RT_FALSE;
+	s.check = check;
+	s.rttype = 0;
+	s.srid = SRID_UNKNOWN;
+	s.has_z = RT_FALSE;
+	s.has_m = RT_FALSE;
+	s.has_srid = RT_FALSE;
+	s.pos = wkb;
+	
+	/* Hand the check catch-all values */
+	if ( check & RT_PARSER_CHECK_NONE ) 
+		s.check = 0;
+	else
+		s.check = check;
+
+	return rtgeom_from_wkb_state(ctx, &s);
+}
+
+RTGEOM* rtgeom_from_hexwkb(const RTCTX *ctx, const char *hexwkb, const char check)
+{
+	int hexwkb_len;
+	uint8_t *wkb;
+	RTGEOM *rtgeom;
+	
+	if ( ! hexwkb )	
+	{
+		rterror(ctx, "rtgeom_from_hexwkb: null input");
+		return NULL;
+	}
+	
+	hexwkb_len = strlen(hexwkb);
+	wkb = bytes_from_hexbytes(ctx, hexwkb, hexwkb_len);
+	rtgeom = rtgeom_from_wkb(ctx, wkb, hexwkb_len/2, check);
+	rtfree(ctx, wkb);
+	return rtgeom;	
+}
diff --git a/src/rtiterator.c b/src/rtiterator.c
new file mode 100644
index 0000000..09a26f7
--- /dev/null
+++ b/src/rtiterator.c
@@ -0,0 +1,283 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2015 Daniel Baston <dbaston at gmail.com>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom.h"
+#include "rtgeom_log.h"
+
+struct LISTNODE
+{
+	struct LISTNODE* next;
+	void* item;
+};
+typedef struct LISTNODE LISTNODE;
+
+/* The RTPOINTITERATOR consists of two stacks of items to process: a stack
+ * of geometries, and a stack of RTPOINTARRAYs extracted from those geometries.
+ * The index "i" refers to the "next" point, which is found at the top of the
+ * pointarrays stack.
+ *
+ * When the pointarrays stack is depleted, we pull a geometry from the geometry
+ * stack to replenish it.
+ */
+struct RTPOINTITERATOR
+{
+	LISTNODE* geoms;
+	LISTNODE* pointarrays;
+	uint32_t i;
+	char allow_modification;
+};
+
+static LISTNODE*
+prepend_node(const RTCTX *ctx, void* g, LISTNODE* front)
+{
+	LISTNODE* n = rtalloc(ctx, sizeof(LISTNODE));
+	n->item = g;
+	n->next = front;
+
+	return n;
+}
+
+static LISTNODE*
+pop_node(const RTCTX *ctx, LISTNODE* i)
+{
+	LISTNODE* next = i->next;
+	rtfree(ctx, i);
+	return next;
+}
+
+static int
+add_rtgeom_to_stack(const RTCTX *ctx, RTPOINTITERATOR* s, RTGEOM* g)
+{
+	if (rtgeom_is_empty(ctx, g))
+		return RT_FAILURE;
+
+	s->geoms = prepend_node(ctx, g, s->geoms);
+	return RT_SUCCESS;
+}
+
+/** Return a pointer to the first of one or more LISTNODEs holding the RTPOINTARRAYs
+ *  of a geometry.  Will not handle GeometryCollections.
+ */
+static LISTNODE*
+extract_pointarrays_from_rtgeom(const RTCTX *ctx, RTGEOM* g)
+{
+	switch(rtgeom_get_type(ctx, g))
+	{
+	case RTPOINTTYPE:
+		return prepend_node(ctx, rtgeom_as_rtpoint(ctx, g)->point, NULL);
+	case RTLINETYPE:
+		return prepend_node(ctx, rtgeom_as_rtline(ctx, g)->points, NULL);
+	case RTTRIANGLETYPE:
+		return prepend_node(ctx, rtgeom_as_rttriangle(ctx, g)->points, NULL);
+	case RTCIRCSTRINGTYPE:
+		return prepend_node(ctx, rtgeom_as_rtcircstring(ctx, g)->points, NULL);
+	case RTPOLYGONTYPE:
+	{
+		LISTNODE* n = NULL;
+
+		RTPOLY* p = rtgeom_as_rtpoly(ctx, g);
+		int i;
+		for (i = p->nrings - 1; i >= 0; i--)
+		{
+			n = prepend_node(ctx, p->rings[i], n);
+		}
+
+		return n;
+	}
+	default:
+		rterror(ctx, "Unsupported geometry type for rtpointiterator");
+	}
+
+	return NULL;
+}
+
+/** Remove an RTCOLLECTION from the iterator stack, and add the components of the
+ *  RTCOLLECTIONs to the stack.
+ */
+static void
+unroll_collection(const RTCTX *ctx, RTPOINTITERATOR* s)
+{
+	int i;
+	RTCOLLECTION* c;
+
+	if (!s->geoms)
+	{
+		return;
+	}
+
+	c = (RTCOLLECTION*) s->geoms->item;
+	s->geoms = pop_node(ctx, s->geoms);
+
+	for (i = c->ngeoms - 1; i >= 0; i--)
+	{
+		RTGEOM* g = rtcollection_getsubgeom(ctx, c, i);
+
+		add_rtgeom_to_stack(ctx, s, g);
+	}
+}
+
+/** Unroll RTCOLLECTIONs from the top of the stack, as necessary, until the element at the
+ *  top of the stack is not a RTCOLLECTION.
+ */
+static void
+unroll_collections(const RTCTX *ctx, RTPOINTITERATOR* s)
+{
+	while(s->geoms && rtgeom_is_collection(ctx, s->geoms->item))
+	{
+		unroll_collection(ctx, s);
+	}
+}
+
+static int
+rtpointiterator_advance(const RTCTX *ctx, RTPOINTITERATOR* s)
+{
+	s->i += 1;
+
+	/* We've reached the end of our current RTPOINTARRAY.  Try to see if there
+	 * are any more RTPOINTARRAYS on the stack. */
+	if (s->pointarrays && s->i >= ((RTPOINTARRAY*) s->pointarrays->item)->npoints)
+	{
+		s->pointarrays = pop_node(ctx, s->pointarrays);
+		s->i = 0;
+	}
+
+	/* We don't have a current RTPOINTARRAY.  Pull a geometry from the stack, and
+	 * decompose it into its POINTARRARYs. */
+	if (!s->pointarrays)
+	{
+		RTGEOM* g;
+		unroll_collections(ctx, s);
+
+		if (!s->geoms)
+		{
+			return RT_FAILURE;
+		}
+
+		s->i = 0;
+		g = s->geoms->item;
+		s->pointarrays = extract_pointarrays_from_rtgeom(ctx, g);
+
+		s->geoms = pop_node(ctx, s->geoms);
+	}
+
+	if (!s->pointarrays)
+	{
+		return RT_FAILURE;
+	}
+	return RT_SUCCESS;
+}
+
+/* Public API implementation */
+
+int
+rtpointiterator_peek(const RTCTX *ctx, RTPOINTITERATOR* s, RTPOINT4D* p)
+{
+	if (!rtpointiterator_has_next(ctx, s))
+		return RT_FAILURE;
+
+	return rt_getPoint4d_p(ctx, s->pointarrays->item, s->i, p);
+}
+
+int
+rtpointiterator_has_next(const RTCTX *ctx, RTPOINTITERATOR* s)
+{
+	if (s->pointarrays && s->i < ((RTPOINTARRAY*) s->pointarrays->item)->npoints)
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+int
+rtpointiterator_next(const RTCTX *ctx, RTPOINTITERATOR* s, RTPOINT4D* p)
+{
+	if (!rtpointiterator_has_next(ctx, s))
+		return RT_FAILURE;
+
+	/* If p is NULL, just advance without reading */
+	if (p && !rtpointiterator_peek(ctx, s, p))
+		return RT_FAILURE;
+
+	rtpointiterator_advance(ctx, s);
+	return RT_SUCCESS;
+}
+
+int
+rtpointiterator_modify_next(const RTCTX *ctx, RTPOINTITERATOR* s, const RTPOINT4D* p)
+{
+	if (!rtpointiterator_has_next(ctx, s))
+		return RT_FAILURE;
+
+	if (!s->allow_modification)
+	{
+		rterror(ctx, "Cannot write to read-only iterator");
+		return RT_FAILURE;
+	}
+
+	ptarray_set_point4d(ctx, s->pointarrays->item, s->i, p);
+
+	rtpointiterator_advance(ctx, s);
+	return RT_SUCCESS;
+}
+
+RTPOINTITERATOR*
+rtpointiterator_create(const RTCTX *ctx, const RTGEOM* g)
+{
+	RTPOINTITERATOR* it = rtpointiterator_create_rw(ctx, (RTGEOM*) g);
+	it->allow_modification = RT_FALSE;
+
+	return it;
+}
+
+RTPOINTITERATOR*
+rtpointiterator_create_rw(const RTCTX *ctx, RTGEOM* g)
+{
+	RTPOINTITERATOR* it = rtalloc(ctx, sizeof(RTPOINTITERATOR));
+
+	it->geoms = NULL;
+	it->pointarrays = NULL;
+	it->i = 0;
+	it->allow_modification = RT_TRUE;
+
+	add_rtgeom_to_stack(ctx, it, g);
+	rtpointiterator_advance(ctx, it);
+
+	return it;
+}
+
+void
+rtpointiterator_destroy(const RTCTX *ctx, RTPOINTITERATOR* s)
+{
+	while (s->geoms != NULL)
+	{
+		s->geoms = pop_node(ctx, s->geoms);
+	}
+
+	while (s->pointarrays != NULL)
+	{
+		s->pointarrays = pop_node(ctx, s->pointarrays);
+	}
+
+	rtfree(ctx, s);
+}
diff --git a/src/rtline.c b/src/rtline.c
new file mode 100644
index 0000000..b1a1fea
--- /dev/null
+++ b/src/rtline.c
@@ -0,0 +1,595 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2012 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/* basic RTLINE functions */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+
+/*
+ * Construct a new RTLINE.  points will *NOT* be copied
+ * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
+ */
+RTLINE *
+rtline_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points)
+{
+	RTLINE *result;
+	result = (RTLINE*) rtalloc(ctx, sizeof(RTLINE));
+
+	RTDEBUG(2, "rtline_construct called.");
+
+	result->type = RTLINETYPE;
+	
+	result->flags = points->flags;
+	RTFLAGS_SET_BBOX(result->flags, bbox?1:0);
+
+	RTDEBUGF(3, "rtline_construct type=%d", result->type);
+
+	result->srid = srid;
+	result->points = points;
+	result->bbox = bbox;
+
+	return result;
+}
+
+RTLINE *
+rtline_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTLINE *result = rtalloc(ctx, sizeof(RTLINE));
+	result->type = RTLINETYPE;
+	result->flags = gflags(ctx, hasz,hasm,0);
+	result->srid = srid;
+	result->points = ptarray_construct_empty(ctx, hasz, hasm, 1);
+	result->bbox = NULL;
+	return result;
+}
+
+
+void rtline_free(const RTCTX *ctx, RTLINE  *line)
+{
+	if ( ! line ) return;
+	
+	if ( line->bbox )
+		rtfree(ctx, line->bbox);
+	if ( line->points )
+		ptarray_free(ctx, line->points);
+	rtfree(ctx, line);
+}
+
+
+void printRTLINE(const RTCTX *ctx, RTLINE *line)
+{
+	rtnotice(ctx, "RTLINE {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(line->flags));
+	rtnotice(ctx, "    srid = %i", (int)line->srid);
+	printPA(ctx, line->points);
+	rtnotice(ctx, "}");
+}
+
+/* @brief Clone RTLINE object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTLINE *
+rtline_clone(const RTCTX *ctx, const RTLINE *g)
+{
+	RTLINE *ret = rtalloc(ctx, sizeof(RTLINE));
+
+	RTDEBUGF(2, "rtline_clone called with %p", g);
+
+	memcpy(ret, g, sizeof(RTLINE));
+
+	ret->points = ptarray_clone(ctx, g->points);
+
+	if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	return ret;
+}
+
+/* Deep clone RTLINE object. RTPOINTARRAY *is* copied. */
+RTLINE *
+rtline_clone_deep(const RTCTX *ctx, const RTLINE *g)
+{
+	RTLINE *ret = rtalloc(ctx, sizeof(RTLINE));
+
+	RTDEBUGF(2, "rtline_clone_deep called with %p", g);
+	memcpy(ret, g, sizeof(RTLINE));
+
+	if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	if ( g->points ) ret->points = ptarray_clone_deep(ctx, g->points);
+	RTFLAGS_SET_READONLY(ret->flags,0);
+
+	return ret;
+}
+
+
+void
+rtline_release(const RTCTX *ctx, RTLINE *rtline)
+{
+	rtgeom_release(ctx, rtline_as_rtgeom(ctx, rtline));
+}
+
+void
+rtline_reverse(const RTCTX *ctx, RTLINE *line)
+{
+	if ( rtline_is_empty(ctx, line) ) return;
+	ptarray_reverse(ctx, line->points);
+}
+
+RTLINE *
+rtline_segmentize2d(const RTCTX *ctx, RTLINE *line, double dist)
+{
+	RTPOINTARRAY *segmentized = ptarray_segmentize2d(ctx, line->points, dist);
+	if ( ! segmentized ) return NULL;
+	return rtline_construct(ctx, line->srid, NULL, segmentized);
+}
+
+/* check coordinate equality  */
+char
+rtline_same(const RTCTX *ctx, const RTLINE *l1, const RTLINE *l2)
+{
+	return ptarray_same(ctx, l1->points, l2->points);
+}
+
+/*
+ * Construct a RTLINE from an array of point and line geometries
+ * RTLINE dimensions are large enough to host all input dimensions.
+ */
+RTLINE *
+rtline_from_rtgeom_array(const RTCTX *ctx, int srid, uint32_t ngeoms, RTGEOM **geoms)
+{
+ 	int i;
+	int hasz = RT_FALSE;
+	int hasm = RT_FALSE;
+	RTPOINTARRAY *pa;
+	RTLINE *line;
+	RTPOINT4D pt;
+
+	/*
+	 * Find output dimensions, check integrity
+	 */
+	for (i=0; i<ngeoms; i++)
+	{
+		if ( RTFLAGS_GET_Z(geoms[i]->flags) ) hasz = RT_TRUE;
+		if ( RTFLAGS_GET_M(geoms[i]->flags) ) hasm = RT_TRUE;
+		if ( hasz && hasm ) break; /* Nothing more to learn! */
+	}
+
+	/* ngeoms should be a guess about how many points we have in input */
+	pa = ptarray_construct_empty(ctx, hasz, hasm, ngeoms);
+	
+	for ( i=0; i < ngeoms; i++ )
+	{
+		RTGEOM *g = geoms[i];
+
+		if ( rtgeom_is_empty(ctx, g) ) continue;
+
+		if ( g->type == RTPOINTTYPE )
+		{
+			rtpoint_getPoint4d_p(ctx, (RTPOINT*)g, &pt);
+			ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+		}
+		else if ( g->type == RTLINETYPE )
+		{
+			ptarray_append_ptarray(ctx, pa, ((RTLINE*)g)->points, -1);
+		}
+		else
+		{
+			ptarray_free(ctx, pa);
+			rterror(ctx, "rtline_from_ptarray: invalid input type: %s", rttype_name(ctx, g->type));
+			return NULL;
+		}
+	}
+
+	if ( pa->npoints > 0 )
+		line = rtline_construct(ctx, srid, NULL, pa);
+	else  {
+		/* Is this really any different from the above ? */
+		ptarray_free(ctx, pa);
+		line = rtline_construct_empty(ctx, srid, hasz, hasm);
+	}
+	
+	return line;
+}
+
+/*
+ * Construct a RTLINE from an array of RTPOINTs
+ * RTLINE dimensions are large enough to host all input dimensions.
+ */
+RTLINE *
+rtline_from_ptarray(const RTCTX *ctx, int srid, uint32_t npoints, RTPOINT **points)
+{
+ 	int i;
+	int hasz = RT_FALSE;
+	int hasm = RT_FALSE;
+	RTPOINTARRAY *pa;
+	RTLINE *line;
+	RTPOINT4D pt;
+
+	/*
+	 * Find output dimensions, check integrity
+	 */
+	for (i=0; i<npoints; i++)
+	{
+		if ( points[i]->type != RTPOINTTYPE )
+		{
+			rterror(ctx, "rtline_from_ptarray: invalid input type: %s", rttype_name(ctx, points[i]->type));
+			return NULL;
+		}
+		if ( RTFLAGS_GET_Z(points[i]->flags) ) hasz = RT_TRUE;
+		if ( RTFLAGS_GET_M(points[i]->flags) ) hasm = RT_TRUE;
+		if ( hasz && hasm ) break; /* Nothing more to learn! */
+	}
+
+	pa = ptarray_construct_empty(ctx, hasz, hasm, npoints);
+	
+	for ( i=0; i < npoints; i++ )
+	{
+		if ( ! rtpoint_is_empty(ctx, points[i]) )
+		{
+			rtpoint_getPoint4d_p(ctx, points[i], &pt);
+			ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+		}
+	}
+
+	if ( pa->npoints > 0 )
+		line = rtline_construct(ctx, srid, NULL, pa);
+	else 
+		line = rtline_construct_empty(ctx, srid, hasz, hasm);
+	
+	return line;
+}
+
+/*
+ * Construct a RTLINE from a RTMPOINT
+ */
+RTLINE *
+rtline_from_rtmpoint(const RTCTX *ctx, int srid, const RTMPOINT *mpoint)
+{
+	uint32_t i;
+	RTPOINTARRAY *pa = NULL;
+	RTGEOM *rtgeom = (RTGEOM*)mpoint;
+	RTPOINT4D pt;
+
+	char hasz = rtgeom_has_z(ctx, rtgeom);
+	char hasm = rtgeom_has_m(ctx, rtgeom);
+	uint32_t npoints = mpoint->ngeoms;
+
+	if ( rtgeom_is_empty(ctx, rtgeom) ) 
+	{
+		return rtline_construct_empty(ctx, srid, hasz, hasm);
+	}
+
+	pa = ptarray_construct(ctx, hasz, hasm, npoints);
+
+	for (i=0; i < npoints; i++)
+	{
+		rt_getPoint4d_p(ctx, mpoint->geoms[i]->point, 0, &pt);
+		ptarray_set_point4d(ctx, pa, i, &pt);
+	}
+	
+	RTDEBUGF(3, "rtline_from_rtmpoint: constructed pointarray for %d points", mpoint->ngeoms);
+
+	return rtline_construct(ctx, srid, NULL, pa);
+}
+
+/**
+* Returns freshly allocated #RTPOINT that corresponds to the index where.
+* Returns NULL if the geometry is empty or the index invalid.
+*/
+RTPOINT*
+rtline_get_rtpoint(const RTCTX *ctx, const RTLINE *line, int where)
+{
+	RTPOINT4D pt;
+	RTPOINT *rtpoint;
+	RTPOINTARRAY *pa;
+
+	if ( rtline_is_empty(ctx, line) || where < 0 || where >= line->points->npoints )
+		return NULL;
+
+	pa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(line->flags), RTFLAGS_GET_M(line->flags), 1);
+	pt = rt_getPoint4d(ctx, line->points, where);
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+	rtpoint = rtpoint_construct(ctx, line->srid, NULL, pa);
+	return rtpoint;
+}
+
+
+int
+rtline_add_rtpoint(const RTCTX *ctx, RTLINE *line, RTPOINT *point, int where)
+{
+	RTPOINT4D pt;	
+	rt_getPoint4d_p(ctx, point->point, 0, &pt);
+
+	if ( ptarray_insert_point(ctx, line->points, &pt, where) != RT_SUCCESS )
+		return RT_FAILURE;
+
+	/* Update the bounding box */
+	if ( line->bbox )
+	{
+		rtgeom_drop_bbox(ctx, rtline_as_rtgeom(ctx, line));
+		rtgeom_add_bbox(ctx, rtline_as_rtgeom(ctx, line));
+	}
+	
+	return RT_SUCCESS;
+}
+
+
+
+RTLINE *
+rtline_removepoint(const RTCTX *ctx, RTLINE *line, uint32_t index)
+{
+	RTPOINTARRAY *newpa;
+	RTLINE *ret;
+
+	newpa = ptarray_removePoint(ctx, line->points, index);
+
+	ret = rtline_construct(ctx, line->srid, NULL, newpa);
+	rtgeom_add_bbox(ctx, (RTGEOM *) ret);
+
+	return ret;
+}
+
+/*
+ * Note: input will be changed, make sure you have permissions for this.
+ */
+void
+rtline_setPoint4d(const RTCTX *ctx, RTLINE *line, uint32_t index, RTPOINT4D *newpoint)
+{
+	ptarray_set_point4d(ctx, line->points, index, newpoint);
+	/* Update the box, if there is one to update */
+	if ( line->bbox )
+	{
+		rtgeom_drop_bbox(ctx, (RTGEOM*)line);
+		rtgeom_add_bbox(ctx, (RTGEOM*)line);
+	}
+}
+
+/**
+* Re-write the measure ordinate (or add one, if it isn't already there) interpolating
+* the measure between the supplied start and end values.
+*/
+RTLINE*
+rtline_measured_from_rtline(const RTCTX *ctx, const RTLINE *rtline, double m_start, double m_end)
+{
+	int i = 0;
+	int hasm = 0, hasz = 0;
+	int npoints = 0;
+	double length = 0.0;
+	double length_so_far = 0.0;
+	double m_range = m_end - m_start;
+	double m;
+	RTPOINTARRAY *pa = NULL;
+	RTPOINT3DZ p1, p2;
+
+	if ( rtline->type != RTLINETYPE )
+	{
+		rterror(ctx, "rtline_construct_from_rtline: only line types supported");
+		return NULL;
+	}
+
+	hasz = RTFLAGS_GET_Z(rtline->flags);
+	hasm = 1;
+
+	/* Null points or npoints == 0 will result in empty return geometry */
+	if ( rtline->points )
+	{
+		npoints = rtline->points->npoints;
+		length = ptarray_length_2d(ctx, rtline->points);
+		rt_getPoint3dz_p(ctx, rtline->points, 0, &p1);
+	}
+
+	pa = ptarray_construct(ctx, hasz, hasm, npoints);
+
+	for ( i = 0; i < npoints; i++ )
+	{
+		RTPOINT4D q;
+		RTPOINT2D a, b;
+		rt_getPoint3dz_p(ctx, rtline->points, i, &p2);
+		a.x = p1.x;
+		a.y = p1.y;
+		b.x = p2.x;
+		b.y = p2.y;
+		length_so_far += distance2d_pt_pt(ctx, &a, &b);
+		if ( length > 0.0 )
+			m = m_start + m_range * length_so_far / length;
+		/* #3172, support (valid) zero-length inputs */
+		else if ( length == 0.0 && npoints > 1 )
+			m = m_start + m_range * i / (npoints-1);
+		else
+			m = 0.0;
+		q.x = p2.x;
+		q.y = p2.y;
+		q.z = p2.z;
+		q.m = m;
+		ptarray_set_point4d(ctx, pa, i, &q);
+		p1 = p2;
+	}
+
+	return rtline_construct(ctx, rtline->srid, NULL, pa);
+}
+
+RTGEOM*
+rtline_remove_repeated_points(const RTCTX *ctx, const RTLINE *rtline, double tolerance)
+{
+	RTPOINTARRAY* npts = ptarray_remove_repeated_points_minpoints(ctx, rtline->points, tolerance, 2);
+
+	RTDEBUGF(3, "%s: npts %p", __func__, npts);
+
+	return (RTGEOM*)rtline_construct(ctx, rtline->srid,
+	                                 rtline->bbox ? gbox_copy(ctx, rtline->bbox) : 0,
+	                                 npts);
+}
+
+int
+rtline_is_closed(const RTCTX *ctx, const RTLINE *line)
+{
+	if (RTFLAGS_GET_Z(line->flags))
+		return ptarray_is_closed_3d(ctx, line->points);
+
+	return ptarray_is_closed_2d(ctx, line->points);
+}
+
+int
+rtline_is_trajectory(const RTCTX *ctx, const RTLINE *line)
+{
+  RTPOINT3DM p;
+  int i, n;
+  double m = -1 * FLT_MAX;
+
+  if ( ! RTFLAGS_GET_M(line->flags) ) {
+    rtnotice(ctx, "Line does not have M dimension");
+    return RT_FALSE;
+  }
+
+  n = line->points->npoints;
+  if ( n < 2 ) return RT_TRUE; /* empty or single-point are "good" */
+
+  for (i=0; i<n; ++i) {
+    rt_getPoint3dm_p(ctx, line->points, i, &p);
+    if ( p.m <= m ) {
+      rtnotice(ctx, "Measure of vertex %d (%g) not bigger than measure of vertex %d (%g)",
+        i, p.m, i-1, m);
+      return RT_FALSE;
+    }
+    m = p.m;
+  }
+
+  return RT_TRUE;
+}
+
+
+RTLINE*
+rtline_force_dims(const RTCTX *ctx, const RTLINE *line, int hasz, int hasm)
+{
+	RTPOINTARRAY *pdims = NULL;
+	RTLINE *lineout;
+	
+	/* Return 2D empty */
+	if( rtline_is_empty(ctx, line) )
+	{
+		lineout = rtline_construct_empty(ctx, line->srid, hasz, hasm);
+	}
+	else
+	{	
+		pdims = ptarray_force_dims(ctx, line->points, hasz, hasm);
+		lineout = rtline_construct(ctx, line->srid, NULL, pdims);
+	}
+	lineout->type = line->type;
+	return lineout;
+}
+
+int rtline_is_empty(const RTCTX *ctx, const RTLINE *line)
+{
+	if ( !line->points || line->points->npoints < 1 )
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+
+int rtline_count_vertices(const RTCTX *ctx, RTLINE *line)
+{
+	assert(line);
+	if ( ! line->points )
+		return 0;
+	return line->points->npoints;
+}
+
+RTLINE* rtline_simplify(const RTCTX *ctx, const RTLINE *iline, double dist, int preserve_collapsed)
+{
+	static const int minvertices = 2; /* TODO: allow setting this */
+	RTLINE *oline;
+	RTPOINTARRAY *pa;
+
+	RTDEBUG(2, "function called");
+
+	/* Skip empty case */
+	if( rtline_is_empty(ctx, iline) )
+		return NULL;
+
+	pa = ptarray_simplify(ctx, iline->points, dist, minvertices);
+	if ( ! pa ) return NULL;
+
+	/* Make sure single-point collapses have two points */
+	if ( pa->npoints == 1 )
+	{
+		/* Make sure single-point collapses have two points */
+		if ( preserve_collapsed )
+		{
+			RTPOINT4D pt;
+			rt_getPoint4d_p(ctx, pa, 0, &pt);		
+			ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+		}
+		/* Return null for collapse */
+		else 
+		{
+			ptarray_free(ctx, pa);
+			return NULL;
+		}
+	}
+
+	oline = rtline_construct(ctx, iline->srid, NULL, pa);
+	oline->type = iline->type;
+	return oline;
+}
+
+double rtline_length(const RTCTX *ctx, const RTLINE *line)
+{
+	if ( rtline_is_empty(ctx, line) )
+		return 0.0;
+	return ptarray_length(ctx, line->points);
+}
+
+double rtline_length_2d(const RTCTX *ctx, const RTLINE *line)
+{
+	if ( rtline_is_empty(ctx, line) )
+		return 0.0;
+	return ptarray_length_2d(ctx, line->points);
+}
+
+
+
+RTLINE* rtline_grid(const RTCTX *ctx, const RTLINE *line, const gridspec *grid)
+{
+	RTLINE *oline;
+	RTPOINTARRAY *opa;
+
+	opa = ptarray_grid(ctx, line->points, grid);
+
+	/* Skip line3d with less then 2 points */
+	if ( opa->npoints < 2 ) return NULL;
+
+	/* TODO: grid bounding box... */
+	oline = rtline_construct(ctx, line->srid, NULL, opa);
+
+	return oline;
+}
+
diff --git a/src/rtlinearreferencing.c b/src/rtlinearreferencing.c
new file mode 100644
index 0000000..b075f04
--- /dev/null
+++ b/src/rtlinearreferencing.c
@@ -0,0 +1,1401 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2011 Paul Ramsey
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include "measures3d.h"
+
+static int
+segment_locate_along(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2, double m, double offset, RTPOINT4D *pn)
+{
+	double m1 = p1->m;
+	double m2 = p2->m;
+	double mprop;
+
+	/* M is out of range, no new point generated. */
+	if ( (m < FP_MIN(m1,m2)) || (m > FP_MAX(m1,m2)) )
+	{
+		return RT_FALSE;
+	}
+
+	if( m1 == m2 )
+	{
+		/* Degenerate case: same M on both points.
+		   If they are the same point we just return one of them. */
+		if ( p4d_same(ctx, p1,p2) )
+		{
+			*pn = *p1;
+			return RT_TRUE;
+		}
+		/* If the points are different we can out.
+		   Correct behavior is probably an mprop of 0.5? */
+		rterror(ctx, "Zero measure-length line encountered!");
+		return RT_FALSE;
+	}
+
+	/* M is in range, new point to be generated. */
+	mprop = (m - m1) / (m2 - m1);
+	pn->x = p1->x + (p2->x - p1->x) * mprop;
+	pn->y = p1->y + (p2->y - p1->y) * mprop;
+	pn->z = p1->z + (p2->z - p1->z) * mprop;
+	pn->m = m;
+
+	/* Offset to the left or right, if necessary. */
+	if ( offset != 0.0 )
+	{
+		double theta = atan2(p2->y - p1->y, p2->x - p1->x);
+		pn->x -= sin(theta) * offset;
+		pn->y += cos(theta) * offset;
+	}
+
+	return RT_TRUE;
+}
+
+
+static RTPOINTARRAY*
+ptarray_locate_along(const RTCTX *ctx, const RTPOINTARRAY *pa, double m, double offset)
+{
+	int i;
+	RTPOINT4D p1, p2, pn;
+	RTPOINTARRAY *dpa = NULL;
+
+	/* Can't do anything with degenerate point arrays */
+	if ( ! pa || pa->npoints < 2 ) return NULL;
+
+	/* Walk through each segment in the point array */
+	for ( i = 1; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i-1, &p1);
+		rt_getPoint4d_p(ctx, pa, i, &p2);
+
+		/* No derived point? Move to next segment. */
+		if ( segment_locate_along(ctx, &p1, &p2, m, offset, &pn) == RT_FALSE )
+			continue;
+
+		/* No pointarray, make a fresh one */
+		if ( dpa == NULL )
+			dpa = ptarray_construct_empty(ctx, ptarray_has_z(ctx, pa), ptarray_has_m(ctx, pa), 8);
+
+		/* Add our new point to the array */
+		ptarray_append_point(ctx, dpa, &pn, 0);
+	}
+
+	return dpa;
+}
+
+static RTMPOINT*
+rtline_locate_along(const RTCTX *ctx, const RTLINE *rtline, double m, double offset)
+{
+	RTPOINTARRAY *opa = NULL;
+	RTMPOINT *mp = NULL;
+	RTGEOM *rtg = rtline_as_rtgeom(ctx, rtline);
+	int hasz, hasm, srid;
+
+	/* Return degenerates upwards */
+	if ( ! rtline ) return NULL;
+
+	/* Create empty return shell */
+	srid = rtgeom_get_srid(ctx, rtg);
+	hasz = rtgeom_has_z(ctx, rtg);
+	hasm = rtgeom_has_m(ctx, rtg);
+
+	if ( hasm )
+	{
+		/* Find points along */
+		opa = ptarray_locate_along(ctx, rtline->points, m, offset);
+	}
+	else
+	{
+		RTLINE *rtline_measured = rtline_measured_from_rtline(ctx, rtline, 0.0, 1.0);
+		opa = ptarray_locate_along(ctx, rtline_measured->points, m, offset);
+		rtline_free(ctx, rtline_measured);
+	}
+
+	/* Return NULL as EMPTY */
+	if ( ! opa )
+		return rtmpoint_construct_empty(ctx, srid, hasz, hasm);
+
+	/* Convert pointarray into a multipoint */
+	mp = rtmpoint_construct(ctx, srid, opa);
+	ptarray_free(ctx, opa);
+	return mp;
+}
+
+static RTMPOINT*
+rtmline_locate_along(const RTCTX *ctx, const RTMLINE *rtmline, double m, double offset)
+{
+	RTMPOINT *rtmpoint = NULL;
+	RTGEOM *rtg = rtmline_as_rtgeom(ctx, rtmline);
+	int i, j;
+
+	/* Return degenerates upwards */
+	if ( (!rtmline) || (rtmline->ngeoms < 1) ) return NULL;
+
+	/* Construct return */
+	rtmpoint = rtmpoint_construct_empty(ctx, rtgeom_get_srid(ctx, rtg), rtgeom_has_z(ctx, rtg), rtgeom_has_m(ctx, rtg));
+
+	/* Locate along each sub-line */
+	for ( i = 0; i < rtmline->ngeoms; i++ )
+	{
+		RTMPOINT *along = rtline_locate_along(ctx, rtmline->geoms[i], m, offset);
+		if ( along )
+		{
+			if ( ! rtgeom_is_empty(ctx, (RTGEOM*)along) )
+			{
+				for ( j = 0; j < along->ngeoms; j++ )
+				{
+					rtmpoint_add_rtpoint(ctx, rtmpoint, along->geoms[j]);
+				}
+			}
+			/* Free the containing geometry, but leave the sub-geometries around */
+			along->ngeoms = 0;
+			rtmpoint_free(ctx, along);
+		}
+	}
+	return rtmpoint;
+}
+
+static RTMPOINT*
+rtpoint_locate_along(const RTCTX *ctx, const RTPOINT *rtpoint, double m, double offset)
+{
+	double point_m = rtpoint_get_m(ctx, rtpoint);
+	RTGEOM *rtg = rtpoint_as_rtgeom(ctx, rtpoint);
+	RTMPOINT *r = rtmpoint_construct_empty(ctx, rtgeom_get_srid(ctx, rtg), rtgeom_has_z(ctx, rtg), rtgeom_has_m(ctx, rtg));
+	if ( FP_EQUALS(m, point_m) )
+	{
+		rtmpoint_add_rtpoint(ctx, r, rtpoint_clone(ctx, rtpoint));
+	}
+	return r;
+}
+
+static RTMPOINT*
+rtmpoint_locate_along(const RTCTX *ctx, const RTMPOINT *rtin, double m, double offset)
+{
+	RTGEOM *rtg = rtmpoint_as_rtgeom(ctx, rtin);
+	RTMPOINT *rtout = NULL;
+	int i;
+
+	/* Construct return */
+	rtout = rtmpoint_construct_empty(ctx, rtgeom_get_srid(ctx, rtg), rtgeom_has_z(ctx, rtg), rtgeom_has_m(ctx, rtg));
+
+	for ( i = 0; i < rtin->ngeoms; i++ )
+	{
+		double point_m = rtpoint_get_m(ctx, rtin->geoms[i]);
+		if ( FP_EQUALS(m, point_m) )
+		{
+			rtmpoint_add_rtpoint(ctx, rtout, rtpoint_clone(ctx, rtin->geoms[i]));
+		}
+	}
+
+	return rtout;
+}
+
+RTGEOM*
+rtgeom_locate_along(const RTCTX *ctx, const RTGEOM *rtin, double m, double offset)
+{
+	if ( ! rtin ) return NULL;
+
+	if ( ! rtgeom_has_m(ctx, rtin) )
+		rterror(ctx, "Input geometry does not have a measure dimension");
+
+	switch (rtin->type)
+	{
+	case RTPOINTTYPE:
+		return (RTGEOM*)rtpoint_locate_along(ctx, (RTPOINT*)rtin, m, offset);
+	case RTMULTIPOINTTYPE:
+		return (RTGEOM*)rtmpoint_locate_along(ctx, (RTMPOINT*)rtin, m, offset);
+	case RTLINETYPE:
+		return (RTGEOM*)rtline_locate_along(ctx, (RTLINE*)rtin, m, offset);
+	case RTMULTILINETYPE:
+		return (RTGEOM*)rtmline_locate_along(ctx, (RTMLINE*)rtin, m, offset);
+	/* Only line types supported right now */
+	/* TO DO: CurveString, CompoundCurve, MultiCurve */
+	/* TO DO: Point, MultiPoint */
+	default:
+		rterror(ctx, "Only linear geometries are supported, %s provided.",rttype_name(ctx, rtin->type));
+		return NULL;
+	}
+	return NULL;
+}
+
+/**
+* Given a RTPOINT4D and an ordinate number, return
+* the value of the ordinate.
+* @param p input point
+* @param ordinate number (1=x, 2=y, 3=z, 4=m)
+* @return d value at that ordinate
+*/
+double rtpoint_get_ordinate(const RTCTX *ctx, const RTPOINT4D *p, char ordinate)
+{
+	if ( ! p )
+	{
+		rterror(ctx, "Null input geometry.");
+		return 0.0;
+	}
+
+	if ( ! ( ordinate == 'X' || ordinate == 'Y' || ordinate == 'Z' || ordinate == 'M' ) )
+	{
+		rterror(ctx, "Cannot extract %c ordinate.", ordinate);
+		return 0.0;
+	}
+
+	if ( ordinate == 'X' )
+		return p->x;
+	if ( ordinate == 'Y' )
+		return p->y;
+	if ( ordinate == 'Z' )
+		return p->z;
+	if ( ordinate == 'M' )
+		return p->m;
+
+	/* X */
+	return p->x;
+
+}
+
+/**
+* Given a point, ordinate number and value, set that ordinate on the
+* point.
+*/
+void rtpoint_set_ordinate(const RTCTX *ctx, RTPOINT4D *p, char ordinate, double value)
+{
+	if ( ! p )
+	{
+		rterror(ctx, "Null input geometry.");
+		return;
+	}
+
+	if ( ! ( ordinate == 'X' || ordinate == 'Y' || ordinate == 'Z' || ordinate == 'M' ) )
+	{
+		rterror(ctx, "Cannot set %c ordinate.", ordinate);
+		return;
+	}
+
+	RTDEBUGF(4, "    setting ordinate %c to %g", ordinate, value);
+
+	switch ( ordinate )
+	{
+	case 'X':
+		p->x = value;
+		return;
+	case 'Y':
+		p->y = value;
+		return;
+	case 'Z':
+		p->z = value;
+		return;
+	case 'M':
+		p->m = value;
+		return;
+	}
+}
+
+/**
+* Given two points, a dimensionality, an ordinate, and an interpolation value
+* generate a new point that is proportionally between the input points,
+* using the values in the provided dimension as the scaling factors.
+*/
+int point_interpolate(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2, RTPOINT4D *p, int hasz, int hasm, char ordinate, double interpolation_value)
+{
+	static char* dims = "XYZM";
+	double p1_value = rtpoint_get_ordinate(ctx, p1, ordinate);
+	double p2_value = rtpoint_get_ordinate(ctx, p2, ordinate);
+	double proportion;
+	int i = 0;
+
+	if ( ! ( ordinate == 'X' || ordinate == 'Y' || ordinate == 'Z' || ordinate == 'M' ) )
+	{
+		rterror(ctx, "Cannot set %c ordinate.", ordinate);
+		return 0;
+	}
+
+	if ( FP_MIN(p1_value, p2_value) > interpolation_value ||
+	        FP_MAX(p1_value, p2_value) < interpolation_value )
+	{
+		rterror(ctx, "Cannot interpolate to a value (%g) not between the input points (%g, %g).", interpolation_value, p1_value, p2_value);
+		return 0;
+	}
+
+	proportion = fabs((interpolation_value - p1_value) / (p2_value - p1_value));
+
+	for ( i = 0; i < 4; i++ )
+	{
+		double newordinate = 0.0;
+		if ( dims[i] == 'Z' && ! hasz ) continue;
+		if ( dims[i] == 'M' && ! hasm ) continue;
+		p1_value = rtpoint_get_ordinate(ctx, p1, dims[i]);
+		p2_value = rtpoint_get_ordinate(ctx, p2, dims[i]);
+		newordinate = p1_value + proportion * (p2_value - p1_value);
+		rtpoint_set_ordinate(ctx, p, dims[i], newordinate);
+		RTDEBUGF(4, "   clip ordinate(%c) p1_value(%g) p2_value(%g) proportion(%g) newordinate(%g) ", dims[i], p1_value, p2_value, proportion, newordinate );
+	}
+
+	return 1;
+}
+
+
+/**
+* Clip an input POINT between two values, on any ordinate input.
+*/
+RTCOLLECTION*
+rtpoint_clip_to_ordinate_range(const RTCTX *ctx, const RTPOINT *point, char ordinate, double from, double to)
+{
+	RTCOLLECTION *rtgeom_out = NULL;
+	char hasz, hasm;
+	RTPOINT4D p4d;
+	double ordinate_value;
+
+	/* Nothing to do with NULL */
+	if ( ! point )
+		rterror(ctx, "Null input geometry.");
+
+	/* Ensure 'from' is less than 'to'. */
+	if ( to < from )
+	{
+		double t = from;
+		from = to;
+		to = t;
+	}
+
+	/* Read Z/M info */
+	hasz = rtgeom_has_z(ctx, rtpoint_as_rtgeom(ctx, point));
+	hasm = rtgeom_has_m(ctx, rtpoint_as_rtgeom(ctx, point));
+
+	/* Prepare return object */
+	rtgeom_out = rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, point->srid, hasz, hasm);
+
+	/* Test if ordinate is in range */
+	rtpoint_getPoint4d_p(ctx, point, &p4d);
+	ordinate_value = rtpoint_get_ordinate(ctx, &p4d, ordinate);
+	if ( from <= ordinate_value && to >= ordinate_value )
+	{
+		RTPOINT *rtp = rtpoint_clone(ctx, point);
+		rtcollection_add_rtgeom(ctx, rtgeom_out, rtpoint_as_rtgeom(ctx, rtp));
+	}
+
+	/* Set the bbox, if necessary */
+	if ( rtgeom_out->bbox )
+	{
+		rtgeom_drop_bbox(ctx, (RTGEOM*)rtgeom_out);
+		rtgeom_add_bbox(ctx, (RTGEOM*)rtgeom_out);
+	}
+
+	return rtgeom_out;
+}
+
+
+
+/**
+* Clip an input MULTIPOINT between two values, on any ordinate input.
+*/
+RTCOLLECTION*
+rtmpoint_clip_to_ordinate_range(const RTCTX *ctx, const RTMPOINT *mpoint, char ordinate, double from, double to)
+{
+	RTCOLLECTION *rtgeom_out = NULL;
+	char hasz, hasm;
+	int i;
+
+	/* Nothing to do with NULL */
+	if ( ! mpoint )
+		rterror(ctx, "Null input geometry.");
+
+	/* Ensure 'from' is less than 'to'. */
+	if ( to < from )
+	{
+		double t = from;
+		from = to;
+		to = t;
+	}
+
+	/* Read Z/M info */
+	hasz = rtgeom_has_z(ctx, rtmpoint_as_rtgeom(ctx, mpoint));
+	hasm = rtgeom_has_m(ctx, rtmpoint_as_rtgeom(ctx, mpoint));
+
+	/* Prepare return object */
+	rtgeom_out = rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, mpoint->srid, hasz, hasm);
+
+	/* For each point, is its ordinate value between from and to? */
+	for ( i = 0; i < mpoint->ngeoms; i ++ )
+	{
+		RTPOINT4D p4d;
+		double ordinate_value;
+
+		rtpoint_getPoint4d_p(ctx, mpoint->geoms[i], &p4d);
+		ordinate_value = rtpoint_get_ordinate(ctx, &p4d, ordinate);
+
+		if ( from <= ordinate_value && to >= ordinate_value )
+		{
+			RTPOINT *rtp = rtpoint_clone(ctx, mpoint->geoms[i]);
+			rtcollection_add_rtgeom(ctx, rtgeom_out, rtpoint_as_rtgeom(ctx, rtp));
+		}
+	}
+
+	/* Set the bbox, if necessary */
+	if ( rtgeom_out->bbox )
+	{
+		rtgeom_drop_bbox(ctx, (RTGEOM*)rtgeom_out);
+		rtgeom_add_bbox(ctx, (RTGEOM*)rtgeom_out);
+	}
+
+	return rtgeom_out;
+}
+
+/**
+* Clip an input MULTILINESTRING between two values, on any ordinate input.
+*/
+RTCOLLECTION*
+rtmline_clip_to_ordinate_range(const RTCTX *ctx, const RTMLINE *mline, char ordinate, double from, double to)
+{
+	RTCOLLECTION *rtgeom_out = NULL;
+
+	if ( ! mline )
+	{
+		rterror(ctx, "Null input geometry.");
+		return NULL;
+	}
+
+	if ( mline->ngeoms == 1)
+	{
+		rtgeom_out = rtline_clip_to_ordinate_range(ctx, mline->geoms[0], ordinate, from, to);
+	}
+	else
+	{
+		RTCOLLECTION *col;
+		char hasz = rtgeom_has_z(ctx, rtmline_as_rtgeom(ctx, mline));
+		char hasm = rtgeom_has_m(ctx, rtmline_as_rtgeom(ctx, mline));
+		int i, j;
+		char homogeneous = 1;
+		size_t geoms_size = 0;
+		rtgeom_out = rtcollection_construct_empty(ctx, RTMULTILINETYPE, mline->srid, hasz, hasm);
+		RTFLAGS_SET_Z(rtgeom_out->flags, hasz);
+		RTFLAGS_SET_M(rtgeom_out->flags, hasm);
+		for ( i = 0; i < mline->ngeoms; i ++ )
+		{
+			col = rtline_clip_to_ordinate_range(ctx, mline->geoms[i], ordinate, from, to);
+			if ( col )
+			{
+				/* Something was left after the clip. */
+				if ( rtgeom_out->ngeoms + col->ngeoms > geoms_size )
+				{
+					geoms_size += 16;
+					if ( rtgeom_out->geoms )
+					{
+						rtgeom_out->geoms = rtrealloc(ctx, rtgeom_out->geoms, geoms_size * sizeof(RTGEOM*));
+					}
+					else
+					{
+						rtgeom_out->geoms = rtalloc(ctx, geoms_size * sizeof(RTGEOM*));
+					}
+				}
+				for ( j = 0; j < col->ngeoms; j++ )
+				{
+					rtgeom_out->geoms[rtgeom_out->ngeoms] = col->geoms[j];
+					rtgeom_out->ngeoms++;
+				}
+				if ( col->type != mline->type )
+				{
+					homogeneous = 0;
+				}
+				/* Shallow free the struct, leaving the geoms behind. */
+				if ( col->bbox ) rtfree(ctx, col->bbox);
+				rtfree(ctx, col->geoms);
+				rtfree(ctx, col);
+			}
+		}
+		if ( rtgeom_out->bbox )
+		{
+			rtgeom_drop_bbox(ctx, (RTGEOM*)rtgeom_out);
+			rtgeom_add_bbox(ctx, (RTGEOM*)rtgeom_out);
+		}
+
+		if ( ! homogeneous )
+		{
+			rtgeom_out->type = RTCOLLECTIONTYPE;
+		}
+	}
+
+	if ( ! rtgeom_out || rtgeom_out->ngeoms == 0 ) /* Nothing left after clip. */
+	{
+		return NULL;
+	}
+
+	return rtgeom_out;
+
+}
+
+
+/**
+* Take in a LINESTRING and return a MULTILINESTRING of those portions of the
+* LINESTRING between the from/to range for the specified ordinate (XYZM)
+*/
+RTCOLLECTION*
+rtline_clip_to_ordinate_range(const RTCTX *ctx, const RTLINE *line, char ordinate, double from, double to)
+{
+
+	RTPOINTARRAY *pa_in = NULL;
+	RTCOLLECTION *rtgeom_out = NULL;
+	RTPOINTARRAY *dp = NULL;
+	int i;
+	int added_last_point = 0;
+	RTPOINT4D *p = NULL, *q = NULL, *r = NULL;
+	double ordinate_value_p = 0.0, ordinate_value_q = 0.0;
+	char hasz = rtgeom_has_z(ctx, rtline_as_rtgeom(ctx, line));
+	char hasm = rtgeom_has_m(ctx, rtline_as_rtgeom(ctx, line));
+	char dims = RTFLAGS_NDIMS(line->flags);
+
+	/* Null input, nothing we can do. */
+	if ( ! line )
+	{
+		rterror(ctx, "Null input geometry.");
+		return NULL;
+	}
+
+	/* Ensure 'from' is less than 'to'. */
+	if ( to < from )
+	{
+		double t = from;
+		from = to;
+		to = t;
+	}
+
+	RTDEBUGF(4, "from = %g, to = %g, ordinate = %c", from, to, ordinate);
+	RTDEBUGF(4, "%s", rtgeom_to_ewkt(ctx, (RTGEOM*)line));
+
+	/* Asking for an ordinate we don't have. Error. */
+	if ( (ordinate == 'Z' && ! hasz) || (ordinate == 'M' && ! hasm) )
+	{
+		rterror(ctx, "Cannot clip on ordinate %d in a %d-d geometry.", ordinate, dims);
+		return NULL;
+	}
+
+	/* Prepare our working point objects. */
+	p = rtalloc(ctx, sizeof(RTPOINT4D));
+	q = rtalloc(ctx, sizeof(RTPOINT4D));
+	r = rtalloc(ctx, sizeof(RTPOINT4D));
+
+	/* Construct a collection to hold our outputs. */
+	rtgeom_out = rtcollection_construct_empty(ctx, RTMULTILINETYPE, line->srid, hasz, hasm);
+
+	/* Get our input point array */
+	pa_in = line->points;
+
+	for ( i = 0; i < pa_in->npoints; i++ )
+	{
+		RTDEBUGF(4, "Point #%d", i);
+		RTDEBUGF(4, "added_last_point %d", added_last_point);
+		if ( i > 0 )
+		{
+			*q = *p;
+			ordinate_value_q = ordinate_value_p;
+		}
+		rt_getPoint4d_p(ctx, pa_in, i, p);
+		ordinate_value_p = rtpoint_get_ordinate(ctx, p, ordinate);
+		RTDEBUGF(4, " ordinate_value_p %g (current)", ordinate_value_p);
+		RTDEBUGF(4, " ordinate_value_q %g (previous)", ordinate_value_q);
+
+		/* Is this point inside the ordinate range? Yes. */
+		if ( ordinate_value_p >= from && ordinate_value_p <= to )
+		{
+			RTDEBUGF(4, " inside ordinate range (%g, %g)", from, to);
+
+			if ( ! added_last_point )
+			{
+				RTDEBUG(4,"  new ptarray required");
+				/* We didn't add the previous point, so this is a new segment.
+				*  Make a new point array. */
+				dp = ptarray_construct_empty(ctx, hasz, hasm, 32);
+
+				/* We're transiting into the range so add an interpolated
+				*  point at the range boundary.
+				*  If we're on a boundary and crossing from the far side,
+				*  we also need an interpolated point. */
+				if ( i > 0 && ( /* Don't try to interpolate if this is the first point */
+				            ( ordinate_value_p > from && ordinate_value_p < to ) || /* Inside */
+				            ( ordinate_value_p == from && ordinate_value_q > to ) || /* Hopping from above */
+				            ( ordinate_value_p == to && ordinate_value_q < from ) ) ) /* Hopping from below */
+				{
+					double interpolation_value;
+					(ordinate_value_q > to) ? (interpolation_value = to) : (interpolation_value = from);
+					point_interpolate(ctx, q, p, r, hasz, hasm, ordinate, interpolation_value);
+					ptarray_append_point(ctx, dp, r, RT_FALSE);
+					RTDEBUGF(4, "[0] interpolating between (%g, %g) with interpolation point (%g)", ordinate_value_q, ordinate_value_p, interpolation_value);
+				}
+			}
+			/* Add the current vertex to the point array. */
+			ptarray_append_point(ctx, dp, p, RT_FALSE);
+			if ( ordinate_value_p == from || ordinate_value_p == to )
+			{
+				added_last_point = 2; /* Added on boundary. */
+			}
+			else
+			{
+				added_last_point = 1; /* Added inside range. */
+			}
+		}
+		/* Is this point inside the ordinate range? No. */
+		else
+		{
+			RTDEBUGF(4, "  added_last_point (%d)", added_last_point);
+			if ( added_last_point == 1 )
+			{
+				/* We're transiting out of the range, so add an interpolated point
+				*  to the point array at the range boundary. */
+				double interpolation_value;
+				(ordinate_value_p > to) ? (interpolation_value = to) : (interpolation_value = from);
+				point_interpolate(ctx, q, p, r, hasz, hasm, ordinate, interpolation_value);
+				ptarray_append_point(ctx, dp, r, RT_FALSE);
+				RTDEBUGF(4, " [1] interpolating between (%g, %g) with interpolation point (%g)", ordinate_value_q, ordinate_value_p, interpolation_value);
+			}
+			else if ( added_last_point == 2 )
+			{
+				/* We're out and the last point was on the boundary.
+				*  If the last point was the near boundary, nothing to do.
+				*  If it was the far boundary, we need an interpolated point. */
+				if ( from != to && (
+				            (ordinate_value_q == from && ordinate_value_p > from) ||
+				            (ordinate_value_q == to && ordinate_value_p < to) ) )
+				{
+					double interpolation_value;
+					(ordinate_value_p > to) ? (interpolation_value = to) : (interpolation_value = from);
+					point_interpolate(ctx, q, p, r, hasz, hasm, ordinate, interpolation_value);
+					ptarray_append_point(ctx, dp, r, RT_FALSE);
+					RTDEBUGF(4, " [2] interpolating between (%g, %g) with interpolation point (%g)", ordinate_value_q, ordinate_value_p, interpolation_value);
+				}
+			}
+			else if ( i && ordinate_value_q < from && ordinate_value_p > to )
+			{
+				/* We just hopped over the whole range, from bottom to top,
+				*  so we need to add *two* interpolated points! */
+				dp = ptarray_construct(ctx, hasz, hasm, 2);
+				/* Interpolate lower point. */
+				point_interpolate(ctx, p, q, r, hasz, hasm, ordinate, from);
+				ptarray_set_point4d(ctx, dp, 0, r);
+				/* Interpolate upper point. */
+				point_interpolate(ctx, p, q, r, hasz, hasm, ordinate, to);
+				ptarray_set_point4d(ctx, dp, 1, r);
+			}
+			else if ( i && ordinate_value_q > to && ordinate_value_p < from )
+			{
+				/* We just hopped over the whole range, from top to bottom,
+				*  so we need to add *two* interpolated points! */
+				dp = ptarray_construct(ctx, hasz, hasm, 2);
+				/* Interpolate upper point. */
+				point_interpolate(ctx, p, q, r, hasz, hasm, ordinate, to);
+				ptarray_set_point4d(ctx, dp, 0, r);
+				/* Interpolate lower point. */
+				point_interpolate(ctx, p, q, r, hasz, hasm, ordinate, from);
+				ptarray_set_point4d(ctx, dp, 1, r);
+			}
+			/* We have an extant point-array, save it out to a multi-line. */
+			if ( dp )
+			{
+				RTDEBUG(4, "saving pointarray to multi-line (1)");
+
+				/* Only one point, so we have to make an rtpoint to hold this
+				*  and set the overall output type to a generic collection. */
+				if ( dp->npoints == 1 )
+				{
+					RTPOINT *opoint = rtpoint_construct(ctx, line->srid, NULL, dp);
+					rtgeom_out->type = RTCOLLECTIONTYPE;
+					rtgeom_out = rtcollection_add_rtgeom(ctx, rtgeom_out, rtpoint_as_rtgeom(ctx, opoint));
+
+				}
+				else
+				{
+					RTLINE *oline = rtline_construct(ctx, line->srid, NULL, dp);
+					rtgeom_out = rtcollection_add_rtgeom(ctx, rtgeom_out, rtline_as_rtgeom(ctx, oline));
+				}
+
+				/* Pointarray is now owned by rtgeom_out, so drop reference to it */
+				dp = NULL;
+			}
+			added_last_point = 0;
+
+		}
+	}
+
+	/* Still some points left to be saved out. */
+	if ( dp && dp->npoints > 0 )
+	{
+		RTDEBUG(4, "saving pointarray to multi-line (2)");
+		RTDEBUGF(4, "dp->npoints == %d", dp->npoints);
+		RTDEBUGF(4, "rtgeom_out->ngeoms == %d", rtgeom_out->ngeoms);
+
+		if ( dp->npoints == 1 )
+		{
+			RTPOINT *opoint = rtpoint_construct(ctx, line->srid, NULL, dp);
+			rtgeom_out->type = RTCOLLECTIONTYPE;
+			rtgeom_out = rtcollection_add_rtgeom(ctx, rtgeom_out, rtpoint_as_rtgeom(ctx, opoint));
+		}
+		else
+		{
+			RTLINE *oline = rtline_construct(ctx, line->srid, NULL, dp);
+			rtgeom_out = rtcollection_add_rtgeom(ctx, rtgeom_out, rtline_as_rtgeom(ctx, oline));
+		}
+
+		/* Pointarray is now owned by rtgeom_out, so drop reference to it */
+		dp = NULL;
+	}
+
+	rtfree(ctx, p);
+	rtfree(ctx, q);
+	rtfree(ctx, r);
+
+	if ( rtgeom_out->bbox && rtgeom_out->ngeoms > 0 )
+	{
+		rtgeom_drop_bbox(ctx, (RTGEOM*)rtgeom_out);
+		rtgeom_add_bbox(ctx, (RTGEOM*)rtgeom_out);
+	}
+
+	return rtgeom_out;
+
+}
+
+RTCOLLECTION*
+rtgeom_clip_to_ordinate_range(const RTCTX *ctx, const RTGEOM *rtin, char ordinate, double from, double to, double offset)
+{
+	RTCOLLECTION *out_col;
+	RTCOLLECTION *out_offset;
+	int i;
+
+	if ( ! rtin )
+		rterror(ctx, "rtgeom_clip_to_ordinate_range: null input geometry!");
+
+	switch ( rtin->type )
+	{
+	case RTLINETYPE:
+		out_col = rtline_clip_to_ordinate_range(ctx, (RTLINE*)rtin, ordinate, from, to);
+		break;
+	case RTMULTILINETYPE:
+		out_col = rtmline_clip_to_ordinate_range(ctx, (RTMLINE*)rtin, ordinate, from, to);
+		break;
+	case RTMULTIPOINTTYPE:
+		out_col = rtmpoint_clip_to_ordinate_range(ctx, (RTMPOINT*)rtin, ordinate, from, to);
+		break;
+	case RTPOINTTYPE:
+		out_col = rtpoint_clip_to_ordinate_range(ctx, (RTPOINT*)rtin, ordinate, from, to);
+		break;
+	default:
+		rterror(ctx, "This function does not accept %s geometries.", rttype_name(ctx, rtin->type));
+		return NULL;;
+	}
+
+	/* Stop if result is NULL */
+	if ( out_col == NULL )
+		rterror(ctx, "rtgeom_clip_to_ordinate_range clipping routine returned NULL");
+
+	/* Return if we aren't going to offset the result */
+	if ( FP_EQUALS(offset, 0.0) || rtgeom_is_empty(ctx, rtcollection_as_rtgeom(ctx, out_col)) )
+		return out_col;
+
+	/* Construct a collection to hold our outputs. */
+	/* Things get ugly: GEOS offset drops Z's and M's so we have to drop ours */
+	out_offset = rtcollection_construct_empty(ctx, RTMULTILINETYPE, rtin->srid, 0, 0);
+
+	/* Try and offset the linear portions of the return value */
+	for ( i = 0; i < out_col->ngeoms; i++ )
+	{
+		int type = out_col->geoms[i]->type;
+		if ( type == RTPOINTTYPE )
+		{
+			rtnotice(ctx, "rtgeom_clip_to_ordinate_range cannot offset a clipped point");
+			continue;
+		}
+		else if ( type == RTLINETYPE )
+		{
+			/* rtgeom_offsetcurve(ctx, line, offset, quadsegs, joinstyle (round), mitrelimit) */
+			RTGEOM *rtoff = rtgeom_offsetcurve(ctx, rtgeom_as_rtline(ctx, out_col->geoms[i]), offset, 8, 1, 5.0);
+			if ( ! rtoff )
+			{
+				rterror(ctx, "rtgeom_offsetcurve returned null");
+			}
+			rtcollection_add_rtgeom(ctx, out_offset, rtoff);
+		}
+		else
+		{
+			rterror(ctx, "rtgeom_clip_to_ordinate_range found an unexpected type (%s) in the offset routine",rttype_name(ctx, type));
+		}
+	}
+
+	return out_offset;
+}
+
+RTCOLLECTION*
+rtgeom_locate_between(const RTCTX *ctx, const RTGEOM *rtin, double from, double to, double offset)
+{
+	if ( ! rtgeom_has_m(ctx, rtin) )
+		rterror(ctx, "Input geometry does not have a measure dimension");
+
+	return rtgeom_clip_to_ordinate_range(ctx, rtin, 'M', from, to, offset);
+}
+
+double
+rtgeom_interpolate_point(const RTCTX *ctx, const RTGEOM *rtin, const RTPOINT *rtpt)
+{
+	RTPOINT4D p, p_proj;
+	double ret = 0.0;
+
+	if ( ! rtin )
+		rterror(ctx, "rtgeom_interpolate_point: null input geometry!");
+
+	if ( ! rtgeom_has_m(ctx, rtin) )
+		rterror(ctx, "Input geometry does not have a measure dimension");
+
+	if ( rtgeom_is_empty(ctx, rtin) || rtpoint_is_empty(ctx, rtpt) )
+		rterror(ctx, "Input geometry is empty");
+
+	switch ( rtin->type )
+	{
+	case RTLINETYPE:
+	{
+		RTLINE *rtline = rtgeom_as_rtline(ctx, rtin);
+		rtpoint_getPoint4d_p(ctx, rtpt, &p);
+		ret = ptarray_locate_point(ctx, rtline->points, &p, NULL, &p_proj);
+		ret = p_proj.m;
+		break;
+	}
+	default:
+		rterror(ctx, "This function does not accept %s geometries.", rttype_name(ctx, rtin->type));
+	}
+	return ret;
+}
+
+/*
+ * Time of closest point of approach
+ *
+ * Given two vectors (p1-p2 and q1-q2) and
+ * a time range (t1-t2) return the time in which
+ * a point p is closest to a point q on their
+ * respective vectors, and the actual points
+ *
+ * Here we use algorithm from softsurfer.com
+ * that can be found here
+ * http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
+ *
+ * @param p0 start of first segment, will be set to actual
+ *           closest point of approach on segment.
+ * @param p1 end of first segment
+ * @param q0 start of second segment, will be set to actual
+ *           closest point of approach on segment.
+ * @param q1 end of second segment
+ * @param t0 start of travel time
+ * @param t1 end of travel time
+ *
+ * @return time of closest point of approach
+ *
+ */
+static double
+segments_tcpa(const RTCTX *ctx, RTPOINT4D* p0, const RTPOINT4D* p1,
+              RTPOINT4D* q0, const RTPOINT4D* q1,
+              double t0, double t1)
+{
+	RTPOINT3DZ pv; /* velocity of p, aka u */
+	RTPOINT3DZ qv; /* velocity of q, aka v */
+	RTPOINT3DZ dv; /* velocity difference */
+	RTPOINT3DZ w0; /* vector between first points */
+
+	/*
+	  rtnotice(ctx, "FROM %g,%g,%g,%g -- %g,%g,%g,%g",
+	    p0->x, p0->y, p0->z, p0->m,
+	    p1->x, p1->y, p1->z, p1->m);
+	  rtnotice(ctx, "  TO %g,%g,%g,%g -- %g,%g,%g,%g",
+	    q0->x, q0->y, q0->z, q0->m,
+	    q1->x, q1->y, q1->z, q1->m);
+	*/
+
+	/* PV aka U */
+	pv.x = ( p1->x - p0->x );
+	pv.y = ( p1->y - p0->y );
+	pv.z = ( p1->z - p0->z );
+	/*rtnotice(ctx, "PV:  %g, %g, %g", pv.x, pv.y, pv.z);*/
+
+	/* QV aka V */
+	qv.x = ( q1->x - q0->x );
+	qv.y = ( q1->y - q0->y );
+	qv.z = ( q1->z - q0->z );
+	/*rtnotice(ctx, "QV:  %g, %g, %g", qv.x, qv.y, qv.z);*/
+
+	dv.x = pv.x - qv.x;
+	dv.y = pv.y - qv.y;
+	dv.z = pv.z - qv.z;
+	/*rtnotice(ctx, "DV:  %g, %g, %g", dv.x, dv.y, dv.z);*/
+
+	double dv2 = DOT(dv,dv);
+	/*rtnotice(ctx, "DOT: %g", dv2);*/
+
+	if ( dv2 == 0.0 )
+	{
+		/* Distance is the same at any time, we pick the earliest */
+		return t0;
+	}
+
+	/* Distance at any given time, with t0 */
+	w0.x = ( p0->x - q0->x );
+	w0.y = ( p0->y - q0->y );
+	w0.z = ( p0->z - q0->z );
+
+	/*rtnotice(ctx, "W0:  %g, %g, %g", w0.x, w0.y, w0.z);*/
+
+	/* Check that at distance dt w0 is distance */
+
+	/* This is the fraction of measure difference */
+	double t = -DOT(w0,dv) / dv2;
+	/*rtnotice(ctx, "CLOSEST TIME (fraction): %g", t);*/
+
+	if ( t > 1.0 )
+	{
+		/* Getting closer as we move to the end */
+		/*rtnotice(ctx, "Converging");*/
+		t = 1;
+	}
+	else if ( t < 0.0 )
+	{
+		/*rtnotice(ctx, "Diverging");*/
+		t = 0;
+	}
+
+	/* Interpolate the actual points now */
+
+	p0->x += pv.x * t;
+	p0->y += pv.y * t;
+	p0->z += pv.z * t;
+
+	q0->x += qv.x * t;
+	q0->y += qv.y * t;
+	q0->z += qv.z * t;
+
+	t = t0 + (t1 - t0) * t;
+	/*rtnotice(ctx, "CLOSEST TIME (real): %g", t);*/
+
+	return t;
+}
+
+static int
+ptarray_collect_mvals(const RTCTX *ctx, const RTPOINTARRAY *pa, double tmin, double tmax, double *mvals)
+{
+	RTPOINT4D pbuf;
+	int i, n=0;
+	for (i=0; i<pa->npoints; ++i)
+	{
+		rt_getPoint4d_p(ctx, pa, i, &pbuf); /* could be optimized */
+		if ( pbuf.m >= tmin && pbuf.m <= tmax )
+			mvals[n++] = pbuf.m;
+	}
+	return n;
+}
+
+static int
+compare_double(const void *pa, const void *pb)
+{
+	double a = *((double *)pa);
+	double b = *((double *)pb);
+	if ( a < b )
+		return -1;
+	else if ( a > b )
+		return 1;
+	else
+		return 0;
+}
+
+/* Return number of elements in unique array */
+static int
+uniq(const RTCTX *ctx, double *vals, int nvals)
+{
+	int i, last=0;
+	for (i=1; i<nvals; ++i)
+	{
+		// rtnotice(ctx, "(I%d):%g", i, vals[i]);
+		if ( vals[i] != vals[last] )
+		{
+			vals[++last] = vals[i];
+			// rtnotice(ctx, "(O%d):%g", last, vals[last]);
+		}
+	}
+	return last+1;
+}
+
+/*
+ * Find point at a given measure
+ *
+ * The function assumes measures are linear so that artays a single point
+ * is returned for a single measure.
+ *
+ * @param pa the point array to perform search on
+ * @param m the measure to search for
+ * @param p the point to write result into
+ * @param from the segment number to start from
+ *
+ * @return the segment number the point was found into
+ *         or -1 if given measure was out of the known range.
+ */
+static int
+ptarray_locate_along_linear(const RTCTX *ctx, const RTPOINTARRAY *pa, double m, RTPOINT4D *p, int from)
+{
+	int i = from;
+	RTPOINT4D p1, p2;
+
+	/* Walk through each segment in the point array */
+	rt_getPoint4d_p(ctx, pa, i, &p1);
+	for ( i = from+1; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p2);
+
+		if ( segment_locate_along(ctx, &p1, &p2, m, 0, p) == RT_TRUE )
+			return i-1; /* found */
+
+		p1 = p2;
+	}
+
+	return -1; /* not found */
+}
+
+double
+rtgeom_tcpa(const RTCTX *ctx, const RTGEOM *g1, const RTGEOM *g2, double *mindist)
+{
+	RTLINE *l1, *l2;
+	int i;
+	const RTGBOX *gbox1, *gbox2;
+	double tmin, tmax;
+	double *mvals;
+	int nmvals = 0;
+	double mintime;
+	double mindist2 = FLT_MAX; /* minimum distance, squared */
+
+	if ( ! rtgeom_has_m(ctx, g1) || ! rtgeom_has_m(ctx, g2) )
+	{
+		rterror(ctx, "Both input geometries must have a measure dimension");
+		return -1;
+	}
+
+	l1 = rtgeom_as_rtline(ctx, g1);
+	l2 = rtgeom_as_rtline(ctx, g2);
+
+	if ( ! l1 || ! l2 )
+	{
+		rterror(ctx, "Both input geometries must be linestrings");
+		return -1;
+	}
+
+	if ( l1->points->npoints < 2 || l2->points->npoints < 2 )
+	{
+		rterror(ctx, "Both input lines must have at least 2 points");
+		return -1;
+	}
+
+	/* WARNING: these ranges may be wider than real ones */
+	gbox1 = rtgeom_get_bbox(ctx, g1);
+	gbox2 = rtgeom_get_bbox(ctx, g2);
+
+	assert(gbox1); /* or the npoints check above would have failed */
+	assert(gbox2); /* or the npoints check above would have failed */
+
+	/*
+	 * Find overlapping M range
+	 * WARNING: may be larger than the real one
+	 */
+
+	tmin = FP_MAX(gbox1->mmin, gbox2->mmin);
+	tmax = FP_MIN(gbox1->mmax, gbox2->mmax);
+
+	if ( tmax < tmin )
+	{
+		RTDEBUG(1, "Inputs never exist at the same time");
+		return -2;
+	}
+
+	// rtnotice(ctx, "Min:%g, Max:%g", tmin, tmax);
+
+	/*
+	 * Collect M values in common time range from inputs
+	 */
+
+	mvals = rtalloc(ctx,  sizeof(double) *
+	                 ( l1->points->npoints + l2->points->npoints ) );
+
+	/* TODO: also clip the lines ? */
+	nmvals  = ptarray_collect_mvals(ctx, l1->points, tmin, tmax, mvals);
+	nmvals += ptarray_collect_mvals(ctx, l2->points, tmin, tmax, mvals + nmvals);
+
+	/* Sort values in ascending order */
+	qsort(mvals, nmvals, sizeof(double), compare_double);
+
+	/* Remove duplicated values */
+	nmvals = uniq(ctx, mvals, nmvals);
+
+	if ( nmvals < 2 )
+	{
+		{
+			/* there's a single time, must be that one... */
+			double t0 = mvals[0];
+			RTPOINT4D p0, p1;
+			RTDEBUGF(1, "Inputs only exist both at a single time (%g)", t0);
+			if ( mindist )
+			{
+				if ( -1 == ptarray_locate_along_linear(ctx, l1->points, t0, &p0, 0) )
+				{
+					rtfree(ctx, mvals);
+					rterror(ctx, "Could not find point with M=%g on first geom", t0);
+					return -1;
+				}
+				if ( -1 == ptarray_locate_along_linear(ctx, l2->points, t0, &p1, 0) )
+				{
+					rtfree(ctx, mvals);
+					rterror(ctx, "Could not find point with M=%g on second geom", t0);
+					return -1;
+				}
+				*mindist = distance3d_pt_pt(ctx, (POINT3D*)&p0, (POINT3D*)&p1);
+			}
+			rtfree(ctx, mvals);
+			return t0;
+		}
+	}
+
+	/*
+	 * For each consecutive pair of measures, compute time of closest point
+	 * approach and actual distance between points at that time
+	 */
+	mintime = tmin;
+	for (i=1; i<nmvals; ++i)
+	{
+		double t0 = mvals[i-1];
+		double t1 = mvals[i];
+		double t;
+		RTPOINT4D p0, p1, q0, q1;
+		int seg;
+		double dist2;
+
+		// rtnotice(ctx, "T %g-%g", t0, t1);
+
+		seg = ptarray_locate_along_linear(ctx, l1->points, t0, &p0, 0);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z);
+
+		seg = ptarray_locate_along_linear(ctx, l1->points, t1, &p1, seg);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z);
+
+		seg = ptarray_locate_along_linear(ctx, l2->points, t0, &q0, 0);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z);
+
+		seg = ptarray_locate_along_linear(ctx, l2->points, t1, &q1, seg);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z);
+
+		t = segments_tcpa(ctx, &p0, &p1, &q0, &q1, t0, t1);
+
+		/*
+		rtnotice(ctx, "Closest points: %g,%g,%g and %g,%g,%g at time %g",
+		p0.x, p0.y, p0.z,
+		q0.x, q0.y, q0.z, t);
+		*/
+
+		dist2 = ( q0.x - p0.x ) * ( q0.x - p0.x ) +
+		        ( q0.y - p0.y ) * ( q0.y - p0.y ) +
+		        ( q0.z - p0.z ) * ( q0.z - p0.z );
+		if ( dist2 < mindist2 )
+		{
+			mindist2 = dist2;
+			mintime = t;
+			// rtnotice(ctx, "MINTIME: %g", mintime);
+		}
+	}
+
+	/*
+	 * Release memory
+	 */
+
+	rtfree(ctx, mvals);
+
+	if ( mindist )
+	{
+		*mindist = sqrt(mindist2);
+	}
+	/*rtnotice(ctx, "MINDIST: %g", sqrt(mindist2));*/
+
+	return mintime;
+}
+
+int
+rtgeom_cpa_within(const RTCTX *ctx, const RTGEOM *g1, const RTGEOM *g2, double maxdist)
+{
+	RTLINE *l1, *l2;
+	int i;
+	const RTGBOX *gbox1, *gbox2;
+	double tmin, tmax;
+	double *mvals;
+	int nmvals = 0;
+	double maxdist2 = maxdist * maxdist;
+	int within = RT_FALSE;
+
+	if ( ! rtgeom_has_m(ctx, g1) || ! rtgeom_has_m(ctx, g2) )
+	{
+		rterror(ctx, "Both input geometries must have a measure dimension");
+		return RT_FALSE;
+	}
+
+	l1 = rtgeom_as_rtline(ctx, g1);
+	l2 = rtgeom_as_rtline(ctx, g2);
+
+	if ( ! l1 || ! l2 )
+	{
+		rterror(ctx, "Both input geometries must be linestrings");
+		return RT_FALSE;
+	}
+
+	if ( l1->points->npoints < 2 || l2->points->npoints < 2 )
+	{
+		/* TODO: return distance between these two points */
+		rterror(ctx, "Both input lines must have at least 2 points");
+		return RT_FALSE;
+	}
+
+	/* WARNING: these ranges may be wider than real ones */
+	gbox1 = rtgeom_get_bbox(ctx, g1);
+	gbox2 = rtgeom_get_bbox(ctx, g2);
+
+	assert(gbox1); /* or the npoints check above would have failed */
+	assert(gbox2); /* or the npoints check above would have failed */
+
+	/*
+	 * Find overlapping M range
+	 * WARNING: may be larger than the real one
+	 */
+
+	tmin = FP_MAX(gbox1->mmin, gbox2->mmin);
+	tmax = FP_MIN(gbox1->mmax, gbox2->mmax);
+
+	if ( tmax < tmin )
+	{
+		RTDEBUG(1, "Inputs never exist at the same time");
+		return RT_FALSE;
+	}
+
+	// rtnotice(ctx, "Min:%g, Max:%g", tmin, tmax);
+
+	/*
+	 * Collect M values in common time range from inputs
+	 */
+
+	mvals = rtalloc(ctx,  sizeof(double) *
+	                 ( l1->points->npoints + l2->points->npoints ) );
+
+	/* TODO: also clip the lines ? */
+	nmvals  = ptarray_collect_mvals(ctx, l1->points, tmin, tmax, mvals);
+	nmvals += ptarray_collect_mvals(ctx, l2->points, tmin, tmax, mvals + nmvals);
+
+	/* Sort values in ascending order */
+	qsort(mvals, nmvals, sizeof(double), compare_double);
+
+	/* Remove duplicated values */
+	nmvals = uniq(ctx, mvals, nmvals);
+
+	if ( nmvals < 2 )
+	{
+		/* there's a single time, must be that one... */
+		double t0 = mvals[0];
+		RTPOINT4D p0, p1;
+		RTDEBUGF(1, "Inputs only exist both at a single time (%g)", t0);
+		if ( -1 == ptarray_locate_along_linear(ctx, l1->points, t0, &p0, 0) )
+		{
+			rtnotice(ctx, "Could not find point with M=%g on first geom", t0);
+			return RT_FALSE;
+		}
+		if ( -1 == ptarray_locate_along_linear(ctx, l2->points, t0, &p1, 0) )
+		{
+			rtnotice(ctx, "Could not find point with M=%g on second geom", t0);
+			return RT_FALSE;
+		}
+		if ( distance3d_pt_pt(ctx, (POINT3D*)&p0, (POINT3D*)&p1) <= maxdist )
+			within = RT_TRUE;
+		rtfree(ctx, mvals);
+		return within;
+	}
+
+	/*
+	 * For each consecutive pair of measures, compute time of closest point
+	 * approach and actual distance between points at that time
+	 */
+	for (i=1; i<nmvals; ++i)
+	{
+		double t0 = mvals[i-1];
+		double t1 = mvals[i];
+#if RTGEOM_DEBUG_LEVEL >= 1
+		double t;
+#endif
+		RTPOINT4D p0, p1, q0, q1;
+		int seg;
+		double dist2;
+
+		// rtnotice(ctx, "T %g-%g", t0, t1);
+
+		seg = ptarray_locate_along_linear(ctx, l1->points, t0, &p0, 0);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z);
+
+		seg = ptarray_locate_along_linear(ctx, l1->points, t1, &p1, seg);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z);
+
+		seg = ptarray_locate_along_linear(ctx, l2->points, t0, &q0, 0);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z);
+
+		seg = ptarray_locate_along_linear(ctx, l2->points, t1, &q1, seg);
+		if ( -1 == seg ) continue; /* possible, if RTGBOX is approximated */
+		// rtnotice(ctx, "Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z);
+
+#if RTGEOM_DEBUG_LEVEL >= 1
+		t =
+#endif
+		segments_tcpa(ctx, &p0, &p1, &q0, &q1, t0, t1);
+
+		/*
+		rtnotice(ctx, "Closest points: %g,%g,%g and %g,%g,%g at time %g",
+		p0.x, p0.y, p0.z,
+		q0.x, q0.y, q0.z, t);
+		*/
+
+		dist2 = ( q0.x - p0.x ) * ( q0.x - p0.x ) +
+		        ( q0.y - p0.y ) * ( q0.y - p0.y ) +
+		        ( q0.z - p0.z ) * ( q0.z - p0.z );
+		if ( dist2 <= maxdist2 )
+		{
+			RTDEBUGF(1, "Within distance %g at time %g, breaking", sqrt(dist2), t);
+			within = RT_TRUE;
+			break;
+		}
+	}
+
+	/*
+	 * Release memory
+	 */
+
+	rtfree(ctx, mvals);
+
+	return within;
+}
diff --git a/src/rtmcurve.c b/src/rtmcurve.c
new file mode 100644
index 0000000..6d5667d
--- /dev/null
+++ b/src/rtmcurve.c
@@ -0,0 +1,34 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+
+
+
+
diff --git a/src/rtmline.c b/src/rtmline.c
new file mode 100644
index 0000000..133123b
--- /dev/null
+++ b/src/rtmline.c
@@ -0,0 +1,129 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+
+void
+rtmline_release(const RTCTX *ctx, RTMLINE *rtmline)
+{
+	rtgeom_release(ctx, rtmline_as_rtgeom(ctx, rtmline));
+}
+
+RTMLINE *
+rtmline_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTMLINE *ret = (RTMLINE*)rtcollection_construct_empty(ctx, RTMULTILINETYPE, srid, hasz, hasm);
+	return ret;
+}
+
+
+
+RTMLINE* rtmline_add_rtline(const RTCTX *ctx, RTMLINE *mobj, const RTLINE *obj)
+{
+	return (RTMLINE*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION*)mobj, (RTGEOM*)obj);
+}
+
+/**
+* Re-write the measure ordinate (or add one, if it isn't already there) interpolating
+* the measure between the supplied start and end values.
+*/
+RTMLINE*
+rtmline_measured_from_rtmline(const RTCTX *ctx, const RTMLINE *rtmline, double m_start, double m_end)
+{
+	int i = 0;
+	int hasm = 0, hasz = 0;
+	double length = 0.0, length_so_far = 0.0;
+	double m_range = m_end - m_start;
+	RTGEOM **geoms = NULL;
+
+	if ( rtmline->type != RTMULTILINETYPE )
+	{
+		rterror(ctx, "rtmline_measured_from_lmwline: only multiline types supported");
+		return NULL;
+	}
+
+	hasz = RTFLAGS_GET_Z(rtmline->flags);
+	hasm = 1;
+
+	/* Calculate the total length of the mline */
+	for ( i = 0; i < rtmline->ngeoms; i++ )
+	{
+		RTLINE *rtline = (RTLINE*)rtmline->geoms[i];
+		if ( rtline->points && rtline->points->npoints > 1 )
+		{
+			length += ptarray_length_2d(ctx, rtline->points);
+		}
+	}
+
+	if ( rtgeom_is_empty(ctx, (RTGEOM*)rtmline) )
+	{
+		return (RTMLINE*)rtcollection_construct_empty(ctx, RTMULTILINETYPE, rtmline->srid, hasz, hasm);
+	}
+
+	geoms = rtalloc(ctx, sizeof(RTGEOM*) * rtmline->ngeoms);
+
+	for ( i = 0; i < rtmline->ngeoms; i++ )
+	{
+		double sub_m_start, sub_m_end;
+		double sub_length = 0.0;
+		RTLINE *rtline = (RTLINE*)rtmline->geoms[i];
+
+		if ( rtline->points && rtline->points->npoints > 1 )
+		{
+			sub_length = ptarray_length_2d(ctx, rtline->points);
+		}
+
+		sub_m_start = (m_start + m_range * length_so_far / length);
+		sub_m_end = (m_start + m_range * (length_so_far + sub_length) / length);
+
+		geoms[i] = (RTGEOM*)rtline_measured_from_rtline(ctx, rtline, sub_m_start, sub_m_end);
+
+		length_so_far += sub_length;
+	}
+
+	return (RTMLINE*)rtcollection_construct(ctx, rtmline->type, rtmline->srid, NULL, rtmline->ngeoms, geoms);
+}
+
+void rtmline_free(const RTCTX *ctx, RTMLINE *mline)
+{
+	int i;
+	if ( ! mline ) return;
+	
+	if ( mline->bbox )
+		rtfree(ctx, mline->bbox);
+
+	for ( i = 0; i < mline->ngeoms; i++ )
+		if ( mline->geoms && mline->geoms[i] )
+			rtline_free(ctx, mline->geoms[i]);
+
+	if ( mline->geoms )
+		rtfree(ctx, mline->geoms);
+
+	rtfree(ctx, mline);
+}
diff --git a/src/rtmpoint.c b/src/rtmpoint.c
new file mode 100644
index 0000000..041b96b
--- /dev/null
+++ b/src/rtmpoint.c
@@ -0,0 +1,123 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+void
+rtmpoint_release(const RTCTX *ctx, RTMPOINT *rtmpoint)
+{
+	rtgeom_release(ctx, rtmpoint_as_rtgeom(ctx, rtmpoint));
+}
+
+RTMPOINT *
+rtmpoint_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTMPOINT *ret = (RTMPOINT*)rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, srid, hasz, hasm);
+	return ret;
+}
+
+RTMPOINT* rtmpoint_add_rtpoint(const RTCTX *ctx, RTMPOINT *mobj, const RTPOINT *obj)
+{
+	RTDEBUG(4, "Called");
+	return (RTMPOINT*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION*)mobj, (RTGEOM*)obj);
+}
+
+RTMPOINT *
+rtmpoint_construct(const RTCTX *ctx, int srid, const RTPOINTARRAY *pa)
+{
+	int i;
+	int hasz = ptarray_has_z(ctx, pa);
+	int hasm = ptarray_has_m(ctx, pa);
+	RTMPOINT *ret = (RTMPOINT*)rtcollection_construct_empty(ctx, RTMULTIPOINTTYPE, srid, hasz, hasm);
+	
+	for ( i = 0; i < pa->npoints; i++ )
+	{
+		RTPOINT *rtp;
+		RTPOINT4D p;
+		rt_getPoint4d_p(ctx, pa, i, &p);		
+		rtp = rtpoint_make(ctx, srid, hasz, hasm, &p);
+		rtmpoint_add_rtpoint(ctx, ret, rtp);
+	}
+	
+	return ret;
+}
+
+
+void rtmpoint_free(const RTCTX *ctx, RTMPOINT *mpt)
+{
+	int i;
+
+	if ( ! mpt ) return;
+	
+	if ( mpt->bbox )
+		rtfree(ctx, mpt->bbox);
+
+	for ( i = 0; i < mpt->ngeoms; i++ )
+		if ( mpt->geoms && mpt->geoms[i] )
+			rtpoint_free(ctx, mpt->geoms[i]);
+
+	if ( mpt->geoms )
+		rtfree(ctx, mpt->geoms);
+
+	rtfree(ctx, mpt);
+}
+
+RTGEOM*
+rtmpoint_remove_repeated_points(const RTCTX *ctx, const RTMPOINT *mpoint, double tolerance)
+{
+	uint32_t nnewgeoms;
+	uint32_t i, j;
+	RTGEOM **newgeoms;
+
+	newgeoms = rtalloc(ctx, sizeof(RTGEOM *)*mpoint->ngeoms);
+	nnewgeoms = 0;
+	for (i=0; i<mpoint->ngeoms; ++i)
+	{
+		/* Brute force, may be optimized by building an index */
+		int seen=0;
+		for (j=0; j<nnewgeoms; ++j)
+		{
+			if ( rtpoint_same(ctx, (RTPOINT*)newgeoms[j],
+			                  (RTPOINT*)mpoint->geoms[i]) )
+			{
+				seen=1;
+				break;
+			}
+		}
+		if ( seen ) continue;
+		newgeoms[nnewgeoms++] = (RTGEOM*)rtpoint_clone(ctx, mpoint->geoms[i]);
+	}
+
+	return (RTGEOM*)rtcollection_construct(ctx, mpoint->type,
+	                                       mpoint->srid, mpoint->bbox ? gbox_copy(ctx, mpoint->bbox) : NULL,
+	                                       nnewgeoms, newgeoms);
+
+}
+
diff --git a/src/rtmpoly.c b/src/rtmpoly.c
new file mode 100644
index 0000000..598791f
--- /dev/null
+++ b/src/rtmpoly.c
@@ -0,0 +1,70 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+void
+rtmpoly_release(const RTCTX *ctx, RTMPOLY *rtmpoly)
+{
+	rtgeom_release(ctx, rtmpoly_as_rtgeom(ctx, rtmpoly));
+}
+
+RTMPOLY *
+rtmpoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTMPOLY *ret = (RTMPOLY*)rtcollection_construct_empty(ctx, RTMULTIPOLYGONTYPE, srid, hasz, hasm);
+	return ret;
+}
+
+
+RTMPOLY* rtmpoly_add_rtpoly(const RTCTX *ctx, RTMPOLY *mobj, const RTPOLY *obj)
+{
+	return (RTMPOLY*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION*)mobj, (RTGEOM*)obj);
+}
+
+
+void rtmpoly_free(const RTCTX *ctx, RTMPOLY *mpoly)
+{
+	int i;
+	if ( ! mpoly ) return;
+	if ( mpoly->bbox )
+		rtfree(ctx, mpoly->bbox);
+
+	for ( i = 0; i < mpoly->ngeoms; i++ )
+		if ( mpoly->geoms && mpoly->geoms[i] )
+			rtpoly_free(ctx, mpoly->geoms[i]);
+
+	if ( mpoly->geoms )
+		rtfree(ctx, mpoly->geoms);
+
+	rtfree(ctx, mpoly);
+}
+
diff --git a/src/rtmsurface.c b/src/rtmsurface.c
new file mode 100644
index 0000000..dd37a34
--- /dev/null
+++ b/src/rtmsurface.c
@@ -0,0 +1,34 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+
diff --git a/src/rtout_encoded_polyline.c b/src/rtout_encoded_polyline.c
new file mode 100644
index 0000000..fb8f805
--- /dev/null
+++ b/src/rtout_encoded_polyline.c
@@ -0,0 +1,130 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+* Copyright 2014 Kashif Rasul <kashif.rasul at gmail.com> and
+ *
+ **********************************************************************/
+
+
+
+#include "stringbuffer.h"
+#include "librtgeom_internal.h"
+
+static char * rtline_to_encoded_polyline(const RTCTX *ctx, const RTLINE*, int precision);
+static char * rtmmpoint_to_encoded_polyline(const RTCTX *ctx, const RTMPOINT*, int precision);
+static char * pointarray_to_encoded_polyline(const RTCTX *ctx, const RTPOINTARRAY*, int precision);
+
+/* takes a GEOMETRY and returns an Encoded Polyline representation */
+extern char *
+rtgeom_to_encoded_polyline(const RTCTX *ctx, const RTGEOM *geom, int precision)
+{
+	int type = geom->type;
+	switch (type)
+	{
+	case RTLINETYPE:
+		return rtline_to_encoded_polyline(ctx, (RTLINE*)geom, precision);
+	case RTMULTIPOINTTYPE:
+		return rtmmpoint_to_encoded_polyline(ctx, (RTMPOINT*)geom, precision);
+	default:
+		rterror(ctx, "rtgeom_to_encoded_polyline: '%s' geometry type not supported", rttype_name(ctx, type));
+		return NULL;
+	}
+}
+
+static
+char * rtline_to_encoded_polyline(const RTCTX *ctx, const RTLINE *line, int precision)
+{
+	return pointarray_to_encoded_polyline(ctx, line->points, precision);
+}
+
+static
+char * rtmmpoint_to_encoded_polyline(const RTCTX *ctx, const RTMPOINT *mpoint, int precision)
+{
+	RTLINE *line = rtline_from_rtmpoint(ctx, mpoint->srid, mpoint);
+	char *encoded_polyline = rtline_to_encoded_polyline(ctx, line, precision);
+
+	rtline_free(ctx, line);
+	return encoded_polyline;
+}
+
+static
+char * pointarray_to_encoded_polyline(const RTCTX *ctx, const RTPOINTARRAY *pa, int precision)
+{
+	int i;
+	const RTPOINT2D *prevPoint;
+	int *delta = rtalloc(ctx, 2*sizeof(int)*pa->npoints);
+	char *encoded_polyline = NULL;
+	stringbuffer_t *sb;
+	double scale = pow(10,precision);
+
+	/* Take the double value and multiply it by 1x10^percision, rounding the result */
+	prevPoint = rt_getPoint2d_cp(ctx, pa, 0);
+	delta[0] = round(prevPoint->y*scale);
+	delta[1] = round(prevPoint->x*scale);
+
+	/*  points only include the offset from the previous point */
+	for (i=1; i<pa->npoints; i++)
+	{
+		const RTPOINT2D *point = rt_getPoint2d_cp(ctx, pa, i);
+		delta[2*i] = round(point->y*scale) - round(prevPoint->y*scale);
+		delta[(2*i)+1] = round(point->x*scale) - round(prevPoint->x*scale);
+		prevPoint = point;
+	}
+
+	/* value to binary: a negative value must be calculated using its two's complement */
+	for (i=0; i<pa->npoints*2; i++)
+	{
+		/* Left-shift the binary value one bit */
+		delta[i] <<= 1;
+		/* if value is negative, invert this encoding */
+		if (delta[i] < 0) {
+			delta[i] = ~(delta[i]);
+		}
+	}
+
+	sb = stringbuffer_create(ctx);
+	for (i=0; i<pa->npoints*2; i++)
+	{
+		int numberToEncode = delta[i];
+
+		while (numberToEncode >= 0x20) {
+			/* Place the 5-bit chunks into reverse order or
+			 each value with 0x20 if another bit chunk follows and add 63*/
+			int nextValue = (0x20 | (numberToEncode & 0x1f)) + 63;
+			stringbuffer_aprintf(ctx, sb, "%c", (char)nextValue);
+			if(92 == nextValue)
+				stringbuffer_aprintf(ctx, sb, "%c", (char)nextValue);
+
+			/* Break the binary value out into 5-bit chunks */
+			numberToEncode >>= 5;
+		}
+
+		numberToEncode += 63;
+		stringbuffer_aprintf(ctx, sb, "%c", (char)numberToEncode);
+		if(92 == numberToEncode)
+			stringbuffer_aprintf(ctx, sb, "%c", (char)numberToEncode);
+	}
+
+	rtfree(ctx, delta);
+	encoded_polyline = stringbuffer_getstringcopy(ctx, sb);
+	stringbuffer_destroy(ctx, sb);
+
+	return encoded_polyline;
+}
diff --git a/src/rtout_geojson.c b/src/rtout_geojson.c
new file mode 100644
index 0000000..aa354cb
--- /dev/null
+++ b/src/rtout_geojson.c
@@ -0,0 +1,788 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2003 Refractions Research Inc.
+ * Copyright 2009-2010 Olivier Courtin <olivier.courtin at oslandia.com>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include <string.h>	/* strlen */
+#include <assert.h>
+
+static char * asgeojson_point(const RTCTX *ctx, const RTPOINT *point, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_line(const RTCTX *ctx, const RTLINE *line, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_poly(const RTCTX *ctx, const RTPOLY *poly, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_multipoint(const RTCTX *ctx, const RTMPOINT *mpoint, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_multiline(const RTCTX *ctx, const RTMLINE *mline, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_multipolygon(const RTCTX *ctx, const RTMPOLY *mpoly, char *srs, RTGBOX *bbox, int precision);
+static char * asgeojson_collection(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, RTGBOX *bbox, int precision);
+static size_t asgeojson_geom_size(const RTCTX *ctx, const RTGEOM *geom, RTGBOX *bbox, int precision);
+static size_t asgeojson_geom_buf(const RTCTX *ctx, const RTGEOM *geom, char *output, RTGBOX *bbox, int precision);
+
+static size_t pointArray_to_geojson(const RTCTX *ctx, RTPOINTARRAY *pa, char *buf, int precision);
+static size_t pointArray_geojson_size(const RTCTX *ctx, RTPOINTARRAY *pa, int precision);
+
+/**
+ * Takes a GEOMETRY and returns a GeoJson representation
+ */
+char *
+rtgeom_to_geojson(const RTCTX *ctx, const RTGEOM *geom, char *srs, int precision, int has_bbox)
+{
+	int type = geom->type;
+	RTGBOX *bbox = NULL;
+	RTGBOX tmp;
+
+	if ( precision > OUT_MAX_DOUBLE_PRECISION ) precision = OUT_MAX_DOUBLE_PRECISION;
+
+	if (has_bbox) 
+	{
+		/* Whether these are geography or geometry, 
+		   the GeoJSON expects a cartesian bounding box */
+		rtgeom_calculate_gbox_cartesian(ctx, geom, &tmp);
+		bbox = &tmp;
+	}		
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		return asgeojson_point(ctx, (RTPOINT*)geom, srs, bbox, precision);
+	case RTLINETYPE:
+		return asgeojson_line(ctx, (RTLINE*)geom, srs, bbox, precision);
+	case RTPOLYGONTYPE:
+		return asgeojson_poly(ctx, (RTPOLY*)geom, srs, bbox, precision);
+	case RTMULTIPOINTTYPE:
+		return asgeojson_multipoint(ctx, (RTMPOINT*)geom, srs, bbox, precision);
+	case RTMULTILINETYPE:
+		return asgeojson_multiline(ctx, (RTMLINE*)geom, srs, bbox, precision);
+	case RTMULTIPOLYGONTYPE:
+		return asgeojson_multipolygon(ctx, (RTMPOLY*)geom, srs, bbox, precision);
+	case RTCOLLECTIONTYPE:
+		return asgeojson_collection(ctx, (RTCOLLECTION*)geom, srs, bbox, precision);
+	default:
+		rterror(ctx, "rtgeom_to_geojson: '%s' geometry type not supported",
+		        rttype_name(ctx, type));
+	}
+
+	/* Never get here */
+	return NULL;
+}
+
+
+
+/**
+ * Handle SRS
+ */
+static size_t
+asgeojson_srs_size(const RTCTX *ctx, char *srs)
+{
+	int size;
+
+	size = sizeof("'crs':{'type':'name',");
+	size += sizeof("'properties':{'name':''}},");
+	size += strlen(srs) * sizeof(char);
+
+	return size;
+}
+
+static size_t
+asgeojson_srs_buf(const RTCTX *ctx, char *output, char *srs)
+{
+	char *ptr = output;
+
+	ptr += sprintf(ptr, "\"crs\":{\"type\":\"name\",");
+	ptr += sprintf(ptr, "\"properties\":{\"name\":\"%s\"}},", srs);
+
+	return (ptr-output);
+}
+
+
+
+/**
+ * Handle Bbox
+ */
+static size_t
+asgeojson_bbox_size(const RTCTX *ctx, int hasz, int precision)
+{
+	int size;
+
+	if (!hasz)
+	{
+		size = sizeof("\"bbox\":[,,,],");
+		size +=	2 * 2 * (OUT_MAX_DIGS_DOUBLE + precision);
+	}
+	else
+	{
+		size = sizeof("\"bbox\":[,,,,,],");
+		size +=	2 * 3 * (OUT_MAX_DIGS_DOUBLE + precision);
+	}
+
+	return size;
+}
+
+static size_t
+asgeojson_bbox_buf(const RTCTX *ctx, char *output, RTGBOX *bbox, int hasz, int precision)
+{
+	char *ptr = output;
+
+	if (!hasz)
+		ptr += sprintf(ptr, "\"bbox\":[%.*f,%.*f,%.*f,%.*f],",
+		               precision, bbox->xmin, precision, bbox->ymin,
+		               precision, bbox->xmax, precision, bbox->ymax);
+	else
+		ptr += sprintf(ptr, "\"bbox\":[%.*f,%.*f,%.*f,%.*f,%.*f,%.*f],",
+		               precision, bbox->xmin, precision, bbox->ymin, precision, bbox->zmin,
+		               precision, bbox->xmax, precision, bbox->ymax, precision, bbox->zmax);
+
+	return (ptr-output);
+}
+
+
+
+/**
+ * Point Geometry
+ */
+
+static size_t
+asgeojson_point_size(const RTCTX *ctx, const RTPOINT *point, char *srs, RTGBOX *bbox, int precision)
+{
+	int size;
+
+	size = pointArray_geojson_size(ctx, point->point, precision);
+	size += sizeof("{'type':'Point',");
+	size += sizeof("'coordinates':}");
+
+	if ( rtpoint_is_empty(ctx, point) )
+		size += 2; /* [] */
+
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(point->flags), precision);
+
+	return size;
+}
+
+static size_t
+asgeojson_point_buf(const RTCTX *ctx, const RTPOINT *point, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	char *ptr = output;
+
+	ptr += sprintf(ptr, "{\"type\":\"Point\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(point->flags), precision);
+
+	ptr += sprintf(ptr, "\"coordinates\":");
+	if ( rtpoint_is_empty(ctx, point) )
+		ptr += sprintf(ptr, "[]");
+	ptr += pointArray_to_geojson(ctx, point->point, ptr, precision);
+	ptr += sprintf(ptr, "}");
+
+	return (ptr-output);
+}
+
+static char *
+asgeojson_point(const RTCTX *ctx, const RTPOINT *point, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_point_size(ctx, point, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_point_buf(ctx, point, srs, output, bbox, precision);
+	return output;
+}
+
+
+
+/**
+ * Line Geometry
+ */
+
+static size_t
+asgeojson_line_size(const RTCTX *ctx, const RTLINE *line, char *srs, RTGBOX *bbox, int precision)
+{
+	int size;
+
+	size = sizeof("{'type':'LineString',");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(line->flags), precision);
+	size += sizeof("'coordinates':[]}");
+	size += pointArray_geojson_size(ctx, line->points, precision);
+
+	return size;
+}
+
+static size_t
+asgeojson_line_buf(const RTCTX *ctx, const RTLINE *line, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "{\"type\":\"LineString\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(line->flags), precision);
+	ptr += sprintf(ptr, "\"coordinates\":[");
+	ptr += pointArray_to_geojson(ctx, line->points, ptr, precision);
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr-output);
+}
+
+static char *
+asgeojson_line(const RTCTX *ctx, const RTLINE *line, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_line_size(ctx, line, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_line_buf(ctx, line, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+/**
+ * Polygon Geometry
+ */
+
+static size_t
+asgeojson_poly_size(const RTCTX *ctx, const RTPOLY *poly, char *srs, RTGBOX *bbox, int precision)
+{
+	size_t size;
+	int i;
+
+	size = sizeof("{\"type\":\"Polygon\",");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(poly->flags), precision);
+	size += sizeof("\"coordinates\":[");
+	for (i=0, size=0; i<poly->nrings; i++)
+	{
+		size += pointArray_geojson_size(ctx, poly->rings[i], precision);
+		size += sizeof("[]");
+	}
+	size += sizeof(",") * i;
+	size += sizeof("]}");
+
+	return size;
+}
+
+static size_t
+asgeojson_poly_buf(const RTCTX *ctx, const RTPOLY *poly, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	int i;
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "{\"type\":\"Polygon\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(poly->flags), precision);
+	ptr += sprintf(ptr, "\"coordinates\":[");
+	for (i=0; i<poly->nrings; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");
+		ptr += sprintf(ptr, "[");
+		ptr += pointArray_to_geojson(ctx, poly->rings[i], ptr, precision);
+		ptr += sprintf(ptr, "]");
+	}
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr-output);
+}
+
+static char *
+asgeojson_poly(const RTCTX *ctx, const RTPOLY *poly, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_poly_size(ctx, poly, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_poly_buf(ctx, poly, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+/**
+ * Multipoint Geometry
+ */
+
+static size_t
+asgeojson_multipoint_size(const RTCTX *ctx, const RTMPOINT *mpoint, char *srs, RTGBOX *bbox, int precision)
+{
+	RTPOINT * point;
+	int size;
+	int i;
+
+	size = sizeof("{'type':'MultiPoint',");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(mpoint->flags), precision);
+	size += sizeof("'coordinates':[]}");
+
+	for (i=0; i<mpoint->ngeoms; i++)
+	{
+		point = mpoint->geoms[i];
+		size += pointArray_geojson_size(ctx, point->point, precision);
+	}
+	size += sizeof(",") * i;
+
+	return size;
+}
+
+static size_t
+asgeojson_multipoint_buf(const RTCTX *ctx, const RTMPOINT *mpoint, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	RTPOINT *point;
+	int i;
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "{\"type\":\"MultiPoint\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(mpoint->flags), precision);
+	ptr += sprintf(ptr, "\"coordinates\":[");
+
+	for (i=0; i<mpoint->ngeoms; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");
+		point = mpoint->geoms[i];
+		ptr += pointArray_to_geojson(ctx, point->point, ptr, precision);
+	}
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr - output);
+}
+
+static char *
+asgeojson_multipoint(const RTCTX *ctx, const RTMPOINT *mpoint, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_multipoint_size(ctx, mpoint, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_multipoint_buf(ctx, mpoint, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+/**
+ * Multiline Geometry
+ */
+
+static size_t
+asgeojson_multiline_size(const RTCTX *ctx, const RTMLINE *mline, char *srs, RTGBOX *bbox, int precision)
+{
+	RTLINE * line;
+	int size;
+	int i;
+
+	size = sizeof("{'type':'MultiLineString',");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(mline->flags), precision);
+	size += sizeof("'coordinates':[]}");
+
+	for (i=0 ; i<mline->ngeoms; i++)
+	{
+		line = mline->geoms[i];
+		size += pointArray_geojson_size(ctx, line->points, precision);
+		size += sizeof("[]");
+	}
+	size += sizeof(",") * i;
+
+	return size;
+}
+
+static size_t
+asgeojson_multiline_buf(const RTCTX *ctx, const RTMLINE *mline, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	RTLINE *line;
+	int i;
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "{\"type\":\"MultiLineString\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(mline->flags), precision);
+	ptr += sprintf(ptr, "\"coordinates\":[");
+
+	for (i=0; i<mline->ngeoms; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");
+		ptr += sprintf(ptr, "[");
+		line = mline->geoms[i];
+		ptr += pointArray_to_geojson(ctx, line->points, ptr, precision);
+		ptr += sprintf(ptr, "]");
+	}
+
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr - output);
+}
+
+static char *
+asgeojson_multiline(const RTCTX *ctx, const RTMLINE *mline, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_multiline_size(ctx, mline, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_multiline_buf(ctx, mline, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+/**
+ * MultiPolygon Geometry
+ */
+
+static size_t
+asgeojson_multipolygon_size(const RTCTX *ctx, const RTMPOLY *mpoly, char *srs, RTGBOX *bbox, int precision)
+{
+	RTPOLY *poly;
+	int size;
+	int i, j;
+
+	size = sizeof("{'type':'MultiPolygon',");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(mpoly->flags), precision);
+	size += sizeof("'coordinates':[]}");
+
+	for (i=0; i < mpoly->ngeoms; i++)
+	{
+		poly = mpoly->geoms[i];
+		for (j=0 ; j <poly->nrings ; j++)
+		{
+			size += pointArray_geojson_size(ctx, poly->rings[j], precision);
+			size += sizeof("[]");
+		}
+		size += sizeof("[]");
+	}
+	size += sizeof(",") * i;
+	size += sizeof("]}");
+
+	return size;
+}
+
+static size_t
+asgeojson_multipolygon_buf(const RTCTX *ctx, const RTMPOLY *mpoly, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	RTPOLY *poly;
+	int i, j;
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "{\"type\":\"MultiPolygon\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(mpoly->flags), precision);
+	ptr += sprintf(ptr, "\"coordinates\":[");
+	for (i=0; i<mpoly->ngeoms; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");
+		ptr += sprintf(ptr, "[");
+		poly = mpoly->geoms[i];
+		for (j=0 ; j < poly->nrings ; j++)
+		{
+			if (j) ptr += sprintf(ptr, ",");
+			ptr += sprintf(ptr, "[");
+			ptr += pointArray_to_geojson(ctx, poly->rings[j], ptr, precision);
+			ptr += sprintf(ptr, "]");
+		}
+		ptr += sprintf(ptr, "]");
+	}
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr - output);
+}
+
+static char *
+asgeojson_multipolygon(const RTCTX *ctx, const RTMPOLY *mpoly, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_multipolygon_size(ctx, mpoly, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_multipolygon_buf(ctx, mpoly, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+/**
+ * Collection Geometry
+ */
+
+static size_t
+asgeojson_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, RTGBOX *bbox, int precision)
+{
+	int i;
+	int size;
+	RTGEOM *subgeom;
+
+	size = sizeof("{'type':'GeometryCollection',");
+	if (srs) size += asgeojson_srs_size(ctx, srs);
+	if (bbox) size += asgeojson_bbox_size(ctx, RTFLAGS_GET_Z(col->flags), precision);
+	size += sizeof("'geometries':");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		size += asgeojson_geom_size(ctx, subgeom, NULL, precision);
+	}
+	size += sizeof(",") * i;
+	size += sizeof("]}");
+
+	return size;
+}
+
+static size_t
+asgeojson_collection_buf(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, char *output, RTGBOX *bbox, int precision)
+{
+	int i;
+	char *ptr=output;
+	RTGEOM *subgeom;
+
+	ptr += sprintf(ptr, "{\"type\":\"GeometryCollection\",");
+	if (srs) ptr += asgeojson_srs_buf(ctx, ptr, srs);
+	if (col->ngeoms && bbox) ptr += asgeojson_bbox_buf(ctx, ptr, bbox, RTFLAGS_GET_Z(col->flags), precision);
+	ptr += sprintf(ptr, "\"geometries\":[");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");
+		subgeom = col->geoms[i];
+		ptr += asgeojson_geom_buf(ctx, subgeom, ptr, NULL, precision);
+	}
+
+	ptr += sprintf(ptr, "]}");
+
+	return (ptr - output);
+}
+
+static char *
+asgeojson_collection(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, RTGBOX *bbox, int precision)
+{
+	char *output;
+	int size;
+
+	size = asgeojson_collection_size(ctx, col, srs, bbox, precision);
+	output = rtalloc(ctx, size);
+	asgeojson_collection_buf(ctx, col, srs, output, bbox, precision);
+
+	return output;
+}
+
+
+
+static size_t
+asgeojson_geom_size(const RTCTX *ctx, const RTGEOM *geom, RTGBOX *bbox, int precision)
+{
+	int type = geom->type;
+	size_t size = 0;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		size = asgeojson_point_size(ctx, (RTPOINT*)geom, NULL, bbox, precision);
+		break;
+
+	case RTLINETYPE:
+		size = asgeojson_line_size(ctx, (RTLINE*)geom, NULL, bbox, precision);
+		break;
+
+	case RTPOLYGONTYPE:
+		size = asgeojson_poly_size(ctx, (RTPOLY*)geom, NULL, bbox, precision);
+		break;
+
+	case RTMULTIPOINTTYPE:
+		size = asgeojson_multipoint_size(ctx, (RTMPOINT*)geom, NULL, bbox, precision);
+		break;
+
+	case RTMULTILINETYPE:
+		size = asgeojson_multiline_size(ctx, (RTMLINE*)geom, NULL, bbox, precision);
+		break;
+
+	case RTMULTIPOLYGONTYPE:
+		size = asgeojson_multipolygon_size(ctx, (RTMPOLY*)geom, NULL, bbox, precision);
+		break;
+
+	default:
+		rterror(ctx, "GeoJson: geometry not supported.");
+	}
+
+	return size;
+}
+
+
+static size_t
+asgeojson_geom_buf(const RTCTX *ctx, const RTGEOM *geom, char *output, RTGBOX *bbox, int precision)
+{
+	int type = geom->type;
+	char *ptr=output;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		ptr += asgeojson_point_buf(ctx, (RTPOINT*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	case RTLINETYPE:
+		ptr += asgeojson_line_buf(ctx, (RTLINE*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	case RTPOLYGONTYPE:
+		ptr += asgeojson_poly_buf(ctx, (RTPOLY*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	case RTMULTIPOINTTYPE:
+		ptr += asgeojson_multipoint_buf(ctx, (RTMPOINT*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	case RTMULTILINETYPE:
+		ptr += asgeojson_multiline_buf(ctx, (RTMLINE*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	case RTMULTIPOLYGONTYPE:
+		ptr += asgeojson_multipolygon_buf(ctx, (RTMPOLY*)geom, NULL, ptr, bbox, precision);
+		break;
+
+	default:
+		if (bbox) rtfree(ctx, bbox);
+		rterror(ctx, "GeoJson: geometry not supported.");
+	}
+
+	return (ptr-output);
+}
+
+/*
+ * Print an ordinate value using at most the given number of decimal digits
+ *
+ * The actual number of printed decimal digits may be less than the
+ * requested ones if out of significant digits.
+ *
+ * The function will not write more than maxsize bytes, including the
+ * terminating NULL. Returns the number of bytes that would have been
+ * written if there was enough space (excluding terminating NULL).
+ * So a return of ``bufsize'' or more means that the string was
+ * truncated and misses a terminating NULL.
+ *
+ * TODO: export ?
+ *
+ */
+static int
+rtprint_double(const RTCTX *ctx, double d, int maxdd, char *buf, size_t bufsize)
+{
+  double ad = fabs(d);
+  int ndd = ad < 1 ? 0 : floor(log10(ad))+1; /* non-decimal digits */
+  if (fabs(d) < OUT_MAX_DOUBLE)
+  {
+    if ( maxdd > (OUT_MAX_DOUBLE_PRECISION - ndd) )  maxdd -= ndd;
+    return snprintf(buf, bufsize, "%.*f", maxdd, d);
+  }
+  else
+  {
+    return snprintf(buf, bufsize, "%g", d);
+  }
+}
+
+
+
+static size_t
+pointArray_to_geojson(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int precision)
+{
+	int i;
+	char *ptr;
+#define BUFSIZE OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION
+	char x[BUFSIZE+1];
+	char y[BUFSIZE+1];
+	char z[BUFSIZE+1];
+
+	assert ( precision <= OUT_MAX_DOUBLE_PRECISION );
+
+  /* Ensure a terminating NULL at the end of buffers
+   * so that we don't need to check for truncation
+   * inprint_double */
+  x[BUFSIZE] = '\0';
+  y[BUFSIZE] = '\0';
+  z[BUFSIZE] = '\0';
+
+	ptr = output;
+
+  /* TODO: rewrite this loop to be simpler and possibly quicker */
+	if (!RTFLAGS_GET_Z(pa->flags))
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT2D *pt;
+			pt = rt_getPoint2d_cp(ctx, pa, i);
+
+			rtprint_double(ctx, pt->x, precision, x, BUFSIZE);
+			trim_trailing_zeros(ctx, x);
+			rtprint_double(ctx, pt->y, precision, y, BUFSIZE);
+			trim_trailing_zeros(ctx, y);
+
+			if ( i ) ptr += sprintf(ptr, ",");
+			ptr += sprintf(ptr, "[%s,%s]", x, y);
+		}
+	}
+	else
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT3DZ *pt;
+			pt = rt_getPoint3dz_cp(ctx, pa, i);
+
+			rtprint_double(ctx, pt->x, precision, x, BUFSIZE);
+			trim_trailing_zeros(ctx, x);
+			rtprint_double(ctx, pt->y, precision, y, BUFSIZE);
+			trim_trailing_zeros(ctx, y);
+			rtprint_double(ctx, pt->z, precision, z, BUFSIZE);
+			trim_trailing_zeros(ctx, z);
+
+			if ( i ) ptr += sprintf(ptr, ",");
+			ptr += sprintf(ptr, "[%s,%s,%s]", x, y, z);
+		}
+	}
+
+	return (ptr-output);
+}
+
+
+
+/**
+ * Returns maximum size of rendered pointarray in bytes.
+ */
+static size_t
+pointArray_geojson_size(const RTCTX *ctx, RTPOINTARRAY *pa, int precision)
+{
+	assert ( precision <= OUT_MAX_DOUBLE_PRECISION );
+	if (RTFLAGS_NDIMS(pa->flags) == 2)
+		return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(","))
+		       * 2 * pa->npoints + sizeof(",[]");
+
+	return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(",,"))
+	       * 3 * pa->npoints + sizeof(",[]");
+}
diff --git a/src/rtout_gml.c b/src/rtout_gml.c
new file mode 100644
index 0000000..05bac29
--- /dev/null
+++ b/src/rtout_gml.c
@@ -0,0 +1,1994 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011 Sandro Santilli <strk at keybit.net>
+ * Copyright 2010-2012 Oslandia
+ * Copyright 2001-2003 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/**
+* @file GML output routines.
+*
+**********************************************************************/
+
+
+#include <string.h>
+#include "librtgeom_internal.h"
+
+
+static size_t asgml2_point_size(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, const char *prefix);
+static char * asgml2_point(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, const char *prefix);
+static size_t asgml2_line_size(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, const char *prefix);
+static char * asgml2_line(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, const char *prefix);
+static size_t asgml2_poly_size(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, const char *prefix);
+static char * asgml2_poly(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, const char *prefix);
+static size_t asgml2_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, const char *prefix);
+static char * asgml2_multi(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, const char *prefix);
+static size_t asgml2_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, const char *prefix);
+static char * asgml2_collection(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, const char *prefix);
+static size_t pointArray_toGML2(const RTCTX *ctx, RTPOINTARRAY *pa, char *buf, int precision);
+
+static size_t asgml3_point_size(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_point(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static size_t asgml3_line_size(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_line(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_circstring(const RTCTX *ctx,  const RTCIRCSTRING *circ, const char *srs, int precision, int opts, const char *prefix, const char *id );
+static size_t asgml3_poly_size(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_poly(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, int opts, int is_patch, const char *prefix, const char *id);
+static char * asgml3_curvepoly(const RTCTX *ctx, const RTCURVEPOLY* poly, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static size_t asgml3_triangle_size(const RTCTX *ctx, const RTTRIANGLE *triangle, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_triangle(const RTCTX *ctx, const RTTRIANGLE *triangle, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static size_t asgml3_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_multi(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_psurface(const RTCTX *ctx, const RTPSURFACE *psur, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_tin(const RTCTX *ctx, const RTTIN *tin, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static size_t asgml3_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_collection(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static char * asgml3_compound(const RTCTX *ctx, const RTCOMPOUND *col, const char *srs, int precision, int opts, const char *prefix, const char *id );
+static char * asgml3_multicurve(const RTCTX *ctx,  const RTMCURVE* cur, const char *srs, int precision, int opts, const char *prefix, const char *id );
+static char * asgml3_multisurface(const RTCTX *ctx, const RTMSURFACE *sur, const char *srs, int precision, int opts, const char *prefix, const char *id);
+static size_t pointArray_toGML3(const RTCTX *ctx, RTPOINTARRAY *pa, char *buf, int precision, int opts);
+
+
+static size_t pointArray_GMLsize(const RTCTX *ctx, RTPOINTARRAY *pa, int precision);
+
+static char *
+gbox_to_gml2(const RTCTX *ctx, const RTGBOX *bbox, const char *srs, int precision, const char *prefix)
+{
+	int size;
+	RTPOINT4D pt;
+	RTPOINTARRAY *pa;
+	char *ptr, *output;
+	size_t prefixlen = strlen(prefix);
+
+	if ( ! bbox )
+	{
+		size = ( sizeof("<Box>/") + (prefixlen*2) ) * 2;
+		if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+
+		ptr = output = rtalloc(ctx, size);
+
+		ptr += sprintf(ptr, "<%sBox", prefix);
+
+		if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+
+		ptr += sprintf(ptr, "/>");
+
+		return output;
+	}
+
+	pa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(bbox->flags), 0, 2);
+
+	pt.x = bbox->xmin;
+	pt.y = bbox->ymin;
+	if (RTFLAGS_GET_Z(bbox->flags)) pt.z = bbox->zmin;
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+
+	pt.x = bbox->xmax;
+	pt.y = bbox->ymax;
+	if (RTFLAGS_GET_Z(bbox->flags)) pt.z = bbox->zmax;
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+
+	size = pointArray_GMLsize(ctx, pa, precision);
+	size += ( sizeof("<Box><coordinates>/") + (prefixlen*2) ) * 2;
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+
+	ptr = output = rtalloc(ctx, size);
+
+	if ( srs ) ptr += sprintf(ptr, "<%sBox srsName=\"%s\">", prefix, srs);
+	else       ptr += sprintf(ptr, "<%sBox>", prefix);
+
+	ptr += sprintf(ptr, "<%scoordinates>", prefix);
+	ptr += pointArray_toGML2(ctx, pa, ptr, precision);
+	ptr += sprintf(ptr, "</%scoordinates></%sBox>", prefix, prefix);
+
+	ptarray_free(ctx, pa);
+
+	return output;
+}
+
+static char *
+gbox_to_gml3(const RTCTX *ctx, const RTGBOX *bbox, const char *srs, int precision, int opts, const char *prefix)
+{
+	int size;
+	RTPOINT4D pt;
+	RTPOINTARRAY *pa;
+	char *ptr, *output;
+	size_t prefixlen = strlen(prefix);
+	int dimension = 2;
+
+	if ( ! bbox )
+	{
+		size = ( sizeof("<Envelope>/") + (prefixlen*2) ) * 2;
+		if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+
+		ptr = output = rtalloc(ctx, size);
+
+		ptr += sprintf(ptr, "<%sEnvelope", prefix);
+		if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+
+		ptr += sprintf(ptr, "/>");
+
+		return output;
+	}
+
+	if (RTFLAGS_GET_Z(bbox->flags)) dimension = 3;
+
+	pa = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(bbox->flags), 0, 1);
+
+	pt.x = bbox->xmin;
+	pt.y = bbox->ymin;
+	if (RTFLAGS_GET_Z(bbox->flags)) pt.z = bbox->zmin;
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+
+	size = pointArray_GMLsize(ctx, pa, precision) * 2;
+	size += ( sizeof("<Envelope><lowerCorner><upperCorner>//") + (prefixlen*3) ) * 2;
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+	if ( IS_DIMS(opts) ) size += sizeof(" srsDimension=. .");
+
+	ptr = output = rtalloc(ctx, size);
+
+	ptr += sprintf(ptr, "<%sEnvelope", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if ( IS_DIMS(opts) ) ptr += sprintf(ptr, " srsDimension=\"%d\"", dimension);
+	ptr += sprintf(ptr, ">");
+
+	ptr += sprintf(ptr, "<%slowerCorner>", prefix);
+	ptr += pointArray_toGML3(ctx, pa, ptr, precision, opts);
+	ptr += sprintf(ptr, "</%slowerCorner>", prefix);
+
+	ptarray_remove_point(ctx, pa, 0);
+	pt.x = bbox->xmax;
+	pt.y = bbox->ymax;
+	if (RTFLAGS_GET_Z(bbox->flags)) pt.z = bbox->zmax;
+	ptarray_append_point(ctx, pa, &pt, RT_TRUE);
+
+	ptr += sprintf(ptr, "<%supperCorner>", prefix);
+	ptr += pointArray_toGML3(ctx, pa, ptr, precision, opts);
+	ptr += sprintf(ptr, "</%supperCorner>", prefix);
+
+	ptr += sprintf(ptr, "</%sEnvelope>", prefix);
+
+	ptarray_free(ctx, pa);
+
+	return output;
+}
+
+
+extern char *
+rtgeom_extent_to_gml2(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, const char *prefix)
+{
+	const RTGBOX* bbox = rtgeom_get_bbox(ctx, geom);
+	/*
+		if ( ! bbox ) {
+			rterror(ctx, "rtgeom_extent_to_gml2: empty geometry doesn't have a bounding box");
+			return NULL;
+		}
+	*/
+	char *ret = gbox_to_gml2(ctx, bbox, srs, precision, prefix);
+	return ret;
+}
+
+
+extern char *
+rtgeom_extent_to_gml3(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, int opts, const char *prefix)
+{
+	const RTGBOX* bbox = rtgeom_get_bbox(ctx, geom);
+	/*
+		if ( ! bbox ) {
+			rterror(ctx, "rtgeom_extent_to_gml3: empty geometry doesn't have a bounding box");
+			return NULL;
+		}
+	*/
+	return gbox_to_gml3(ctx, bbox, srs, precision, opts, prefix);
+}
+
+
+/**
+ *  @brief VERSION GML 2
+ *  	takes a GEOMETRY and returns a GML2 representation
+ */
+extern char *
+rtgeom_to_gml2(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, const char* prefix)
+{
+	int type = geom->type;
+
+	/* Return null for empty (#1377) */
+	if ( rtgeom_is_empty(ctx, geom) )
+		return NULL;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		return asgml2_point(ctx, (RTPOINT*)geom, srs, precision, prefix);
+
+	case RTLINETYPE:
+		return asgml2_line(ctx, (RTLINE*)geom, srs, precision, prefix);
+
+	case RTPOLYGONTYPE:
+		return asgml2_poly(ctx, (RTPOLY*)geom, srs, precision, prefix);
+
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+		return asgml2_multi(ctx, (RTCOLLECTION*)geom, srs, precision, prefix);
+
+	case RTCOLLECTIONTYPE:
+		return asgml2_collection(ctx, (RTCOLLECTION*)geom, srs, precision, prefix);
+
+	case RTTRIANGLETYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+		rterror(ctx, "Cannot convert %s to GML2. Try ST_AsGML(3, <geometry>) to generate GML3.", rttype_name(ctx, type));
+		return NULL;
+
+	default:
+		rterror(ctx, "rtgeom_to_gml2: '%s' geometry type not supported", rttype_name(ctx, type));
+		return NULL;
+	}
+}
+
+static size_t
+asgml2_point_size(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, const char* prefix)
+{
+	int size;
+	size_t prefixlen = strlen(prefix);
+
+	size = pointArray_GMLsize(ctx, point->point, precision);
+	size += ( sizeof("<point><coordinates>/") + (prefixlen*2) ) * 2;
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+	return size;
+}
+
+static size_t
+asgml2_point_buf(const RTCTX *ctx, const RTPOINT *point, const char *srs, char *output, int precision, const char* prefix)
+{
+	char *ptr = output;
+
+	ptr += sprintf(ptr, "<%sPoint", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if ( rtpoint_is_empty(ctx, point) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+	ptr += sprintf(ptr, "<%scoordinates>", prefix);
+	ptr += pointArray_toGML2(ctx, point->point, ptr, precision);
+	ptr += sprintf(ptr, "</%scoordinates></%sPoint>", prefix, prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml2_point(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, const char *prefix)
+{
+	char *output;
+	int size;
+
+	size = asgml2_point_size(ctx, point, srs, precision, prefix);
+	output = rtalloc(ctx, size);
+	asgml2_point_buf(ctx, point, srs, output, precision, prefix);
+	return output;
+}
+
+static size_t
+asgml2_line_size(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, const char *prefix)
+{
+	int size;
+	size_t prefixlen = strlen(prefix);
+
+	size = pointArray_GMLsize(ctx, line->points, precision);
+	size += ( sizeof("<linestring><coordinates>/") + (prefixlen*2) ) * 2;
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+	return size;
+}
+
+static size_t
+asgml2_line_buf(const RTCTX *ctx, const RTLINE *line, const char *srs, char *output, int precision,
+                const char *prefix)
+{
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "<%sLineString", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+
+	if ( rtline_is_empty(ctx, line) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	ptr += sprintf(ptr, "<%scoordinates>", prefix);
+	ptr += pointArray_toGML2(ctx, line->points, ptr, precision);
+	ptr += sprintf(ptr, "</%scoordinates></%sLineString>", prefix, prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml2_line(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, const char *prefix)
+{
+	char *output;
+	int size;
+
+	size = asgml2_line_size(ctx, line, srs, precision, prefix);
+	output = rtalloc(ctx, size);
+	asgml2_line_buf(ctx, line, srs, output, precision, prefix);
+	return output;
+}
+
+static size_t
+asgml2_poly_size(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, const char *prefix)
+{
+	size_t size;
+	int i;
+	size_t prefixlen = strlen(prefix);
+
+	size = sizeof("<polygon></polygon>") + prefixlen*2;
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+	if ( rtpoly_is_empty(ctx, poly) )
+		return size;
+	size += ( sizeof("<outerboundaryis><linearring><coordinates>/") + ( prefixlen*3) ) * 2;
+	size += ( sizeof("<innerboundaryis><linearring><coordinates>/") + ( prefixlen*2) ) * 2 * poly->nrings;
+
+	for (i=0; i<poly->nrings; i++)
+		size += pointArray_GMLsize(ctx, poly->rings[i], precision);
+
+	return size;
+}
+
+static size_t
+asgml2_poly_buf(const RTCTX *ctx, const RTPOLY *poly, const char *srs, char *output, int precision,
+                const char *prefix)
+{
+	int i;
+	char *ptr=output;
+
+	ptr += sprintf(ptr, "<%sPolygon", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if ( rtpoly_is_empty(ctx, poly) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+	ptr += sprintf(ptr, "<%souterBoundaryIs><%sLinearRing><%scoordinates>",
+	               prefix, prefix, prefix);
+	ptr += pointArray_toGML2(ctx, poly->rings[0], ptr, precision);
+	ptr += sprintf(ptr, "</%scoordinates></%sLinearRing></%souterBoundaryIs>", prefix, prefix, prefix);
+	for (i=1; i<poly->nrings; i++)
+	{
+		ptr += sprintf(ptr, "<%sinnerBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix);
+		ptr += pointArray_toGML2(ctx, poly->rings[i], ptr, precision);
+		ptr += sprintf(ptr, "</%scoordinates></%sLinearRing></%sinnerBoundaryIs>", prefix, prefix, prefix);
+	}
+	ptr += sprintf(ptr, "</%sPolygon>", prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml2_poly(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, const char *prefix)
+{
+	char *output;
+	int size;
+
+	size = asgml2_poly_size(ctx, poly, srs, precision, prefix);
+	output = rtalloc(ctx, size);
+	asgml2_poly_buf(ctx, poly, srs, output, precision, prefix);
+	return output;
+}
+
+/*
+ * Compute max size required for GML version of this
+ * inspected geometry. Will recurse when needed.
+ * Don't call this with single-geoms inspected.
+ */
+static size_t
+asgml2_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision,
+                  const char *prefix)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+	RTGEOM *subgeom;
+
+	/* the longest possible multi version */
+	size = sizeof("<MultiLineString></MultiLineString>");
+	size += 2*prefixlen;
+
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			size += ( sizeof("<pointMember>/") + prefixlen ) * 2;
+			size += asgml2_point_size(ctx, (RTPOINT*)subgeom, 0, precision, prefix);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			size += ( sizeof("<lineStringMember>/") + prefixlen ) * 2;
+			size += asgml2_line_size(ctx, (RTLINE*)subgeom, 0, precision, prefix);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			size += ( sizeof("<polygonMember>/") + prefixlen ) * 2;
+			size += asgml2_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, prefix);
+		}
+	}
+
+	return size;
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asgml2_multi_buf(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, char *output,
+                 int precision, const char *prefix)
+{
+	int type = col->type;
+	char *ptr, *gmltype;
+	int i;
+	RTGEOM *subgeom;
+
+	ptr = output;
+	gmltype="";
+
+	if 	(type == RTMULTIPOINTTYPE)   gmltype = "MultiPoint";
+	else if (type == RTMULTILINETYPE)	   gmltype = "MultiLineString";
+	else if (type == RTMULTIPOLYGONTYPE) gmltype = "MultiPolygon";
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%s%s", prefix, gmltype);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+
+	if (!col->ngeoms)
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			ptr += sprintf(ptr, "<%spointMember>", prefix);
+			ptr += asgml2_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, prefix);
+			ptr += sprintf(ptr, "</%spointMember>", prefix);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			ptr += sprintf(ptr, "<%slineStringMember>", prefix);
+			ptr += asgml2_line_buf(ctx, (RTLINE*)subgeom, 0, ptr, precision, prefix);
+			ptr += sprintf(ptr, "</%slineStringMember>", prefix);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			ptr += sprintf(ptr, "<%spolygonMember>", prefix);
+			ptr += asgml2_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, prefix);
+			ptr += sprintf(ptr, "</%spolygonMember>", prefix);
+		}
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%s%s>", prefix, gmltype);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml2_multi(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision,
+             const char *prefix)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml2_multi_size(ctx, col, srs, precision, prefix);
+	gml = rtalloc(ctx, size);
+	asgml2_multi_buf(ctx, col, srs, gml, precision, prefix);
+	return gml;
+}
+
+
+/*
+ * Don't call this with single-geoms!
+ */
+static size_t
+asgml2_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision,
+                       const char *prefix)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+	RTGEOM *subgeom;
+
+	size = sizeof("<MultiGeometry></MultiGeometry>");
+	size += (prefixlen * 2);
+
+	if ( srs ) size += strlen(srs) + sizeof(" srsName=..");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+
+		size += ( sizeof("<geometryMember>/") + prefixlen ) * 2;
+		if ( subgeom->type == RTPOINTTYPE)
+		{
+			size += asgml2_point_size(ctx, (RTPOINT*)subgeom, 0, precision, prefix);
+		}
+		else if ( subgeom->type == RTLINETYPE)
+		{
+			size += asgml2_line_size(ctx, (RTLINE*)subgeom, 0, precision, prefix);
+		}
+		else if ( subgeom->type == RTPOLYGONTYPE)
+		{
+			size += asgml2_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, prefix);
+		}
+		else if ( rtgeom_is_collection(ctx, subgeom) )
+		{
+			size += asgml2_collection_size(ctx, (RTCOLLECTION*)subgeom, 0, precision, prefix);
+		}
+		else
+			rterror(ctx, "asgml2_collection_size: Unable to process geometry type!");
+	}
+
+
+	return size;
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asgml2_collection_buf(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, char *output, int precision, const char *prefix)
+{
+	char *ptr;
+	int i;
+	RTGEOM *subgeom;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%sMultiGeometry", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+
+	if (!col->ngeoms)
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+
+		ptr += sprintf(ptr, "<%sgeometryMember>", prefix);
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			ptr += asgml2_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, prefix);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			ptr += asgml2_line_buf(ctx, (RTLINE*)subgeom, 0, ptr, precision, prefix);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			ptr += asgml2_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, prefix);
+		}
+		else if (rtgeom_is_collection(ctx, subgeom))
+		{
+			if (subgeom->type == RTCOLLECTIONTYPE)
+				ptr += asgml2_collection_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, prefix);
+			else
+				ptr += asgml2_multi_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, prefix);
+		}
+		ptr += sprintf(ptr, "</%sgeometryMember>", prefix);
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%sMultiGeometry>", prefix);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml2_collection(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision,
+                  const char *prefix)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml2_collection_size(ctx, col, srs, precision, prefix);
+	gml = rtalloc(ctx, size);
+	asgml2_collection_buf(ctx, col, srs, gml, precision, prefix);
+	return gml;
+}
+
+
+static size_t
+pointArray_toGML2(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int precision)
+{
+	int i;
+	char *ptr;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char z[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+
+	ptr = output;
+
+	if ( ! RTFLAGS_GET_Z(pa->flags) )
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT2D *pt;
+			pt = rt_getPoint2d_cp(ctx, pa, i);
+
+			if (fabs(pt->x) < OUT_MAX_DOUBLE)
+				sprintf(x, "%.*f", precision, pt->x);
+			else
+				sprintf(x, "%g", pt->x);
+			trim_trailing_zeros(ctx, x);
+
+			if (fabs(pt->y) < OUT_MAX_DOUBLE)
+				sprintf(y, "%.*f", precision, pt->y);
+			else
+				sprintf(y, "%g", pt->y);
+			trim_trailing_zeros(ctx, y);
+
+			if ( i ) ptr += sprintf(ptr, " ");
+			ptr += sprintf(ptr, "%s,%s", x, y);
+		}
+	}
+	else
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT3DZ *pt;
+			pt = rt_getPoint3dz_cp(ctx, pa, i);
+
+			if (fabs(pt->x) < OUT_MAX_DOUBLE)
+				sprintf(x, "%.*f", precision, pt->x);
+			else
+				sprintf(x, "%g", pt->x);
+			trim_trailing_zeros(ctx, x);
+
+			if (fabs(pt->y) < OUT_MAX_DOUBLE)
+				sprintf(y, "%.*f", precision, pt->y);
+			else
+				sprintf(y, "%g", pt->y);
+			trim_trailing_zeros(ctx, y);
+
+			if (fabs(pt->z) < OUT_MAX_DOUBLE)
+				sprintf(z, "%.*f", precision, pt->z);
+			else
+				sprintf(z, "%g", pt->z);
+			trim_trailing_zeros(ctx, z);
+
+			if ( i ) ptr += sprintf(ptr, " ");
+			ptr += sprintf(ptr, "%s,%s,%s", x, y, z);
+		}
+	}
+
+	return ptr-output;
+}
+
+
+/*
+ * VERSION GML 3.1.1
+ */
+
+
+/* takes a GEOMETRY and returns a GML representation */
+extern char *
+rtgeom_to_gml3(const RTCTX *ctx, const RTGEOM *geom, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int type = geom->type;
+
+	/* Return null for empty (#1377) */
+	if ( rtgeom_is_empty(ctx, geom) )
+		return NULL;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		return asgml3_point(ctx, (RTPOINT*)geom, srs, precision, opts, prefix, id);
+
+	case RTLINETYPE:
+		return asgml3_line(ctx, (RTLINE*)geom, srs, precision, opts, prefix, id);
+
+	case RTCIRCSTRINGTYPE:
+		return asgml3_circstring(ctx, (RTCIRCSTRING*)geom, srs, precision, opts, prefix, id );
+
+	case RTPOLYGONTYPE:
+		return asgml3_poly(ctx, (RTPOLY*)geom, srs, precision, opts, 0, prefix, id);
+
+	case RTCURVEPOLYTYPE:
+		return asgml3_curvepoly(ctx, (RTCURVEPOLY*)geom, srs, precision, opts, prefix, id);
+
+	case RTTRIANGLETYPE:
+		return asgml3_triangle(ctx, (RTTRIANGLE*)geom, srs, precision, opts, prefix, id);
+
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+		return asgml3_multi(ctx, (RTCOLLECTION*)geom, srs, precision, opts, prefix, id);
+
+	case RTPOLYHEDRALSURFACETYPE:
+		return asgml3_psurface(ctx, (RTPSURFACE*)geom, srs, precision, opts, prefix, id);
+
+	case RTTINTYPE:
+		return asgml3_tin(ctx, (RTTIN*)geom, srs, precision, opts, prefix, id);
+
+	case RTCOLLECTIONTYPE:
+		return asgml3_collection(ctx, (RTCOLLECTION*)geom, srs, precision, opts, prefix, id);
+
+	case RTCOMPOUNDTYPE:
+		return asgml3_compound(ctx,  (RTCOMPOUND*)geom, srs, precision, opts, prefix, id );
+
+	case RTMULTICURVETYPE:
+		return asgml3_multicurve(ctx,  (RTMCURVE*)geom, srs, precision, opts, prefix, id );
+
+	case RTMULTISURFACETYPE:
+		return asgml3_multisurface(ctx,  (RTMSURFACE*)geom, srs, precision, opts, prefix, id );
+
+	default:
+		rterror(ctx, "rtgeom_to_gml3: '%s' geometry type not supported", rttype_name(ctx, type));
+		return NULL;
+	}
+}
+
+static size_t
+asgml3_point_size(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int size;
+	size_t prefixlen = strlen(prefix);
+
+	size = pointArray_GMLsize(ctx, point->point, precision);
+	size += ( sizeof("<point><pos>/") + (prefixlen*2) ) * 2;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	if (IS_DIMS(opts)) size += sizeof(" srsDimension='x'");
+	return size;
+}
+
+static size_t
+asgml3_point_buf(const RTCTX *ctx, const RTPOINT *point, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr = output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(point->flags)) dimension = 3;
+
+	ptr += sprintf(ptr, "<%sPoint", prefix);
+	if ( srs ) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if ( id )  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+	if ( rtpoint_is_empty(ctx, point) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+
+	ptr += sprintf(ptr, ">");
+	if (IS_DIMS(opts)) ptr += sprintf(ptr, "<%spos srsDimension=\"%d\">", prefix, dimension);
+	else         ptr += sprintf(ptr, "<%spos>", prefix);
+	ptr += pointArray_toGML3(ctx, point->point, ptr, precision, opts);
+	ptr += sprintf(ptr, "</%spos></%sPoint>", prefix, prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml3_point(const RTCTX *ctx, const RTPOINT *point, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *output;
+	int size;
+
+	size = asgml3_point_size(ctx, point, srs, precision, opts, prefix, id);
+	output = rtalloc(ctx, size);
+	asgml3_point_buf(ctx, point, srs, output, precision, opts, prefix, id);
+	return output;
+}
+
+
+static size_t
+asgml3_line_size(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int size;
+	size_t prefixlen = strlen(prefix);
+
+	size = pointArray_GMLsize(ctx, line->points, precision);
+	if ( opts & RT_GML_SHORTLINE )
+	{
+		size += (
+		            sizeof("<LineString><posList>/") +
+		            ( prefixlen * 2 )
+		        ) * 2;
+	}
+	else
+	{
+		size += (
+		            sizeof("<Curve><segments><LineStringSegment><posList>/") +
+		            ( prefixlen * 4 )
+		        ) * 2;
+	}
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	if (IS_DIMS(opts)) size += sizeof(" srsDimension='x'");
+	return size;
+}
+
+static size_t
+asgml3_line_buf(const RTCTX *ctx, const RTLINE *line, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr=output;
+	int dimension=2;
+	int shortline = ( opts & RT_GML_SHORTLINE );
+
+	if (RTFLAGS_GET_Z(line->flags)) dimension = 3;
+
+	if ( shortline )
+	{
+		ptr += sprintf(ptr, "<%sLineString", prefix);
+	}
+	else
+	{
+		ptr += sprintf(ptr, "<%sCurve", prefix);
+	}
+
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+
+	if ( rtline_is_empty(ctx, line) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	if ( ! shortline )
+	{
+		ptr += sprintf(ptr, "<%ssegments>", prefix);
+		ptr += sprintf(ptr, "<%sLineStringSegment>", prefix);
+	}
+
+	if (IS_DIMS(opts))
+	{
+		ptr += sprintf(ptr, "<%sposList srsDimension=\"%d\">",
+		               prefix, dimension);
+	}
+	else
+	{
+		ptr += sprintf(ptr, "<%sposList>", prefix);
+	}
+
+	ptr += pointArray_toGML3(ctx, line->points, ptr, precision, opts);
+
+	ptr += sprintf(ptr, "</%sposList>", prefix);
+
+	if ( shortline )
+	{
+		ptr += sprintf(ptr, "</%sLineString>", prefix);
+	}
+	else
+	{
+		ptr += sprintf(ptr, "</%sLineStringSegment>", prefix);
+		ptr += sprintf(ptr, "</%ssegments>", prefix);
+		ptr += sprintf(ptr, "</%sCurve>", prefix);
+	}
+
+	return (ptr-output);
+}
+
+static char *
+asgml3_line(const RTCTX *ctx, const RTLINE *line, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *output;
+	int size;
+
+	size = asgml3_line_size(ctx, line, srs, precision, opts, prefix, id);
+	output = rtalloc(ctx, size);
+	asgml3_line_buf(ctx, line, srs, output, precision, opts, prefix, id);
+	return output;
+}
+
+
+static size_t
+asgml3_circstring_size(const RTCTX *ctx, const RTCIRCSTRING *circ, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int size = pointArray_GMLsize(ctx,  circ->points, precision );
+	size_t prefixlen = strlen(prefix);
+	size += 2 * ( sizeof( "<Curve><segments>/" ) + 2 * prefixlen );
+	size += 2 * ( sizeof( "<ArcString><posList>/" ) + 2 * prefixlen );
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	if (IS_DIMS(opts)) size += sizeof(" srsDimension='x'");
+	return size;
+}
+
+static size_t
+asgml3_circstring_buf(const RTCTX *ctx, const RTCIRCSTRING *circ, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char* ptr = output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(circ->flags))
+	{
+		dimension = 3;
+	}
+
+	ptr += sprintf(ptr, "<%sCurve", prefix);
+	if (srs)
+	{
+		ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	}
+	if (id)
+	{
+		ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+	}
+	ptr += sprintf(ptr, ">");
+	ptr += sprintf(ptr, "<%ssegments>", prefix);
+	ptr += sprintf(ptr, "<%sArcString>", prefix);
+	ptr += sprintf(ptr, "<%sposList", prefix);
+
+	if (IS_DIMS(opts))
+	{
+		ptr += sprintf(ptr, " srsDimension=\"%d\"", dimension);
+	}
+	ptr += sprintf(ptr, ">");
+
+	ptr += pointArray_toGML3(ctx, circ->points, ptr, precision, opts);
+	ptr += sprintf(ptr, "</%sposList>", prefix);
+	ptr += sprintf(ptr, "</%sArcString>", prefix);
+	ptr += sprintf(ptr, "</%ssegments>", prefix);
+	ptr += sprintf(ptr, "</%sCurve>", prefix);
+	return (ptr-output);
+}
+
+static char *
+asgml3_circstring(const RTCTX *ctx,  const RTCIRCSTRING *circ, const char *srs, int precision, int opts, const char *prefix, const char *id )
+{
+	char *output;
+	int size;
+
+	size = asgml3_circstring_size(ctx, circ, srs, precision, opts, prefix, id);
+	output = rtalloc(ctx,  size );
+	asgml3_circstring_buf(ctx, circ, srs, output, precision, opts, prefix, id);
+	return output;
+}
+
+
+static size_t
+asgml3_poly_size(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+	int i;
+
+	size = ( sizeof("<PolygonPatch><exterior><LinearRing>///") + (prefixlen*3) ) * 2;
+	size += ( sizeof("<interior><LinearRing>//") + (prefixlen*2) ) * 2 * (poly->nrings - 1);
+	size += ( sizeof("<posList></posList>") + (prefixlen*2) ) * poly->nrings;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	if (IS_DIMS(opts)) size += sizeof(" srsDimension='x'") * poly->nrings;
+
+	for (i=0; i<poly->nrings; i++)
+		size += pointArray_GMLsize(ctx, poly->rings[i], precision);
+
+	return size;
+}
+
+static size_t
+asgml3_poly_buf(const RTCTX *ctx, const RTPOLY *poly, const char *srs, char *output, int precision, int opts, int is_patch, const char *prefix, const char *id)
+{
+	int i;
+	char *ptr=output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(poly->flags)) dimension = 3;
+	if (is_patch)
+	{
+		ptr += sprintf(ptr, "<%sPolygonPatch", prefix);
+
+	}
+	else
+	{
+		ptr += sprintf(ptr, "<%sPolygon", prefix);
+	}
+
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+
+	if ( rtpoly_is_empty(ctx, poly) )
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	ptr += sprintf(ptr, "<%sexterior><%sLinearRing>", prefix, prefix);
+	if (IS_DIMS(opts)) ptr += sprintf(ptr, "<%sposList srsDimension=\"%d\">", prefix, dimension);
+	else         ptr += sprintf(ptr, "<%sposList>", prefix);
+
+	ptr += pointArray_toGML3(ctx, poly->rings[0], ptr, precision, opts);
+	ptr += sprintf(ptr, "</%sposList></%sLinearRing></%sexterior>",
+	               prefix, prefix, prefix);
+	for (i=1; i<poly->nrings; i++)
+	{
+		ptr += sprintf(ptr, "<%sinterior><%sLinearRing>", prefix, prefix);
+		if (IS_DIMS(opts)) ptr += sprintf(ptr, "<%sposList srsDimension=\"%d\">", prefix, dimension);
+		else         ptr += sprintf(ptr, "<%sposList>", prefix);
+		ptr += pointArray_toGML3(ctx, poly->rings[i], ptr, precision, opts);
+		ptr += sprintf(ptr, "</%sposList></%sLinearRing></%sinterior>",
+		               prefix, prefix, prefix);
+	}
+	if (is_patch) ptr += sprintf(ptr, "</%sPolygonPatch>", prefix);
+	else ptr += sprintf(ptr, "</%sPolygon>", prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml3_poly(const RTCTX *ctx, const RTPOLY *poly, const char *srs, int precision, int opts, int is_patch, const char *prefix, const char *id)
+{
+	char *output;
+	int size;
+
+	size = asgml3_poly_size(ctx, poly, srs, precision, opts, prefix, id);
+	output = rtalloc(ctx, size);
+	asgml3_poly_buf(ctx, poly, srs, output, precision, opts, is_patch, prefix, id);
+	return output;
+}
+
+static size_t 
+asgml3_compound_size(const RTCTX *ctx, const RTCOMPOUND *col, const char *srs, int precision, int opts, const char *prefix, const char *id )
+{
+	int i;
+	size_t size;
+	RTGEOM *subgeom;
+	size_t prefixlen = strlen(prefix);
+
+	size = ( sizeof( "<Curve></Curve>" ) + 2 * prefixlen );
+
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+
+	size += ( sizeof("<segments></segments>") + 2 * prefixlen );
+
+	for(i= 0; i < col->ngeoms; ++i )
+	{
+		subgeom = col->geoms[i];
+		if ( subgeom->type == RTLINETYPE )
+		{
+
+			size += sizeof( "<LineStringSegment></LineStringSegment" ) + 2 * prefixlen;
+			size += sizeof( "<posList></posList" ) + 2 * prefixlen;
+			size += pointArray_GMLsize(ctx,  ((RTLINE*)subgeom)->points, precision );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			size += sizeof( "<ArcString><posList></ArcString></posList>") + 4 * prefixlen;
+			size += pointArray_GMLsize(ctx,  ((RTCIRCSTRING*)subgeom)->points, precision );
+		}
+		else
+		{
+			continue;
+		}
+		if (IS_DIMS(opts))
+		{
+			size += sizeof(" srsDimension='x'");
+		}
+	}
+	return size;
+}
+
+static size_t 
+asgml3_compound_buf(const RTCTX *ctx, const RTCOMPOUND *col, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	RTGEOM *subgeom;
+	int i;
+	char* ptr = output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(col->flags))
+	{
+		dimension = 3;
+	}
+
+	ptr += sprintf( ptr, "<%sCurve", prefix );
+	if (srs)
+	{
+		ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	}
+	if (id)
+	{
+		ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id );
+	}
+	ptr += sprintf( ptr, ">" );
+	ptr += sprintf( ptr, "<%ssegments>", prefix );
+
+	for( i = 0; i < col->ngeoms; ++i )
+	{
+		subgeom = col->geoms[i];
+		if( subgeom->type != RTLINETYPE && subgeom->type != RTCIRCSTRINGTYPE )
+		{
+			continue;
+		}
+
+		if ( subgeom->type == RTLINETYPE )
+		{
+			ptr += sprintf( ptr, "<%sLineStringSegment><%sposList", prefix, prefix );
+			if (IS_DIMS(opts))
+			{
+				ptr += sprintf(ptr, " srsDimension=\"%d\"", dimension);
+			}
+			ptr += sprintf(ptr, ">");
+			ptr += pointArray_toGML3(ctx, ((RTCIRCSTRING*)subgeom)->points, ptr, precision, opts);
+			ptr += sprintf( ptr, "</%sposList></%sLineStringSegment>", prefix, prefix );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			ptr += sprintf( ptr, "<%sArcString><%sposList" , prefix, prefix );
+			if (IS_DIMS(opts))
+			{
+				ptr += sprintf(ptr, " srsDimension=\"%d\"", dimension);
+			}
+			ptr += sprintf(ptr, ">");
+			ptr += pointArray_toGML3(ctx, ((RTLINE*)subgeom)->points, ptr, precision, opts);
+			ptr += sprintf( ptr, "</%sposList></%sArcString>", prefix, prefix );
+		}
+	}
+
+	ptr += sprintf( ptr, "</%ssegments>", prefix );
+	ptr += sprintf( ptr, "</%sCurve>", prefix );
+	return ( ptr - output );
+}
+
+static char *
+asgml3_compound(const RTCTX *ctx, const RTCOMPOUND *col, const char *srs, int precision, int opts, const char *prefix, const char *id )
+{
+	char* gml;
+	size_t size;
+
+	size = asgml3_compound_size(ctx,  col, srs, precision, opts, prefix, id );
+	gml = rtalloc(ctx,  size );
+	asgml3_compound_buf(ctx,  col, srs, gml, precision, opts, prefix, id );
+	return gml;
+}
+
+static size_t asgml3_curvepoly_size(const RTCTX *ctx, const RTCURVEPOLY* poly, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	size_t prefixlen = strlen(prefix);
+	RTGEOM* subgeom;
+	size_t size = sizeof( "<Polygon></Polygon" ) + 2 * prefixlen;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	int i;
+
+	for( i = 0; i < poly->nrings; ++i )
+	{
+		if( i == 0 )
+		{
+			size += sizeof( "<exterior></exterior>" ) + 2 * prefixlen;
+		}
+		else
+		{
+			size += sizeof( "<interior></interior>" ) + 2 * prefixlen;
+		}
+		subgeom = poly->rings[i];
+
+		if ( subgeom->type == RTLINETYPE )
+		{
+			size += sizeof("<LinearRing></LinearRing>") + 2 * prefixlen;
+			size += sizeof("<posList></posList") + 2 * prefixlen;
+			if (IS_DIMS(opts))
+			{
+				size += sizeof(" srsDimension='x'");
+			}
+			size += pointArray_GMLsize(ctx,  ((RTLINE*)subgeom)->points, precision );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			size += sizeof("<Ring></Ring>") + 2 * prefixlen;
+			size += sizeof("<CurveMember></CurveMember>") + 2 * prefixlen;
+			size += asgml3_circstring_size(ctx, (RTCIRCSTRING*)subgeom, srs, precision, opts, prefix, id);
+		}
+		else if( subgeom->type == RTCOMPOUNDTYPE )
+		{
+			size += sizeof("<Ring></Ring>") + 2 * prefixlen;
+			size += sizeof("<curveMember></curveMember>") + 2 * prefixlen;
+			size += asgml3_compound_size(ctx,  (RTCOMPOUND*)subgeom, srs, precision, opts, prefix, id );
+		}
+	}
+	return size;
+}
+
+static size_t asgml3_curvepoly_buf(const RTCTX *ctx, const RTCURVEPOLY* poly, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	int i;
+	RTGEOM* subgeom;
+	char *ptr=output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(poly->flags))
+	{
+		dimension = 3;
+	}
+
+	ptr += sprintf( ptr, "<%sPolygon", prefix );
+	if (srs)
+	{
+		ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	}
+	if (id)
+	{
+		ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id );
+	}
+	ptr += sprintf(ptr, ">");
+
+	for( i = 0; i < poly->nrings; ++i )
+	{
+		if( i == 0 )
+		{
+			ptr += sprintf( ptr, "<%sexterior>", prefix);
+		}
+		else
+		{
+			ptr += sprintf( ptr, "<%sinterior>", prefix);
+		}
+
+		subgeom = poly->rings[i];
+		if ( subgeom->type == RTLINETYPE )
+		{
+			ptr += sprintf( ptr, "<%sLinearRing>", prefix );
+			ptr += sprintf( ptr, "<%sposList", prefix );
+			if (IS_DIMS(opts))
+			{
+				ptr += sprintf(ptr, " srsDimension=\"%d\"", dimension);
+			}
+			ptr += sprintf( ptr, ">" );
+			ptr += pointArray_toGML3(ctx, ((RTLINE*)subgeom)->points, ptr, precision, opts);
+			ptr += sprintf( ptr, "</%sposList>", prefix );
+			ptr += sprintf( ptr, "</%sLinearRing>", prefix );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			ptr += sprintf( ptr, "<%sRing>", prefix );
+			ptr += sprintf( ptr, "<%scurveMember>", prefix );
+			ptr += asgml3_circstring_buf(ctx,  (RTCIRCSTRING*)subgeom, srs, ptr, precision, opts, prefix, id );
+			ptr += sprintf( ptr, "</%scurveMember>", prefix );
+			ptr += sprintf( ptr, "</%sRing>", prefix );
+		}
+		else if( subgeom->type == RTCOMPOUNDTYPE )
+		{
+			ptr += sprintf( ptr, "<%sRing>", prefix );
+			ptr += sprintf( ptr, "<%scurveMember>", prefix );
+			ptr += asgml3_compound_buf(ctx,  (RTCOMPOUND*)subgeom, srs, ptr, precision, opts, prefix, id );
+			ptr += sprintf( ptr, "</%scurveMember>", prefix );
+			ptr += sprintf( ptr, "</%sRing>", prefix );
+		}
+
+		if( i == 0 )
+		{
+			ptr += sprintf( ptr, "</%sexterior>", prefix);
+		}
+		else
+		{
+			ptr += sprintf( ptr, "</%sinterior>", prefix);
+		}
+	}
+
+	ptr += sprintf( ptr, "</%sPolygon>", prefix );
+	return (ptr - output);
+}
+
+static char* asgml3_curvepoly(const RTCTX *ctx, const RTCURVEPOLY* poly, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char* gml;
+	size_t size;
+
+	size = asgml3_curvepoly_size(ctx,  poly, srs, precision, opts, prefix, id );
+	gml = rtalloc(ctx,  size );
+	asgml3_curvepoly_buf(ctx,  poly, srs, gml, precision, opts, prefix, id );
+	return gml;
+}
+
+
+static size_t
+asgml3_triangle_size(const RTCTX *ctx, const RTTRIANGLE *triangle, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+
+	size =  ( sizeof("<Triangle><exterior><LinearRing>///") + (prefixlen*3) ) * 2;
+	size +=   sizeof("<posList></posList>") + (prefixlen*2);
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(prefix) + strlen(id) + sizeof(" id=..");
+	if (IS_DIMS(opts)) size += sizeof(" srsDimension='x'");
+
+	size += pointArray_GMLsize(ctx, triangle->points, precision);
+
+	return size;
+}
+
+static size_t
+asgml3_triangle_buf(const RTCTX *ctx, const RTTRIANGLE *triangle, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr=output;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(triangle->flags)) dimension = 3;
+	ptr += sprintf(ptr, "<%sTriangle", prefix);
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+	ptr += sprintf(ptr, ">");
+
+	ptr += sprintf(ptr, "<%sexterior><%sLinearRing>", prefix, prefix);
+	if (IS_DIMS(opts)) ptr += sprintf(ptr, "<%sposList srsDimension=\"%d\">", prefix, dimension);
+	else         ptr += sprintf(ptr, "<%sposList>", prefix);
+
+	ptr += pointArray_toGML3(ctx, triangle->points, ptr, precision, opts);
+	ptr += sprintf(ptr, "</%sposList></%sLinearRing></%sexterior>",
+	               prefix, prefix, prefix);
+
+	ptr += sprintf(ptr, "</%sTriangle>", prefix);
+
+	return (ptr-output);
+}
+
+static char *
+asgml3_triangle(const RTCTX *ctx, const RTTRIANGLE *triangle, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *output;
+	int size;
+
+	size = asgml3_triangle_size(ctx, triangle, srs, precision, opts, prefix, id);
+	output = rtalloc(ctx, size);
+	asgml3_triangle_buf(ctx, triangle, srs, output, precision, opts, prefix, id);
+	return output;
+}
+
+
+/*
+ * Compute max size required for GML version of this
+ * inspected geometry. Will recurse when needed.
+ * Don't call this with single-geoms inspected.
+ */
+static size_t
+asgml3_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+	RTGEOM *subgeom;
+
+	/* the longest possible multi version */
+	size = sizeof("<MultiLineString></MultiLineString>") + prefixlen*2;
+
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			size += ( sizeof("<pointMember>/") + prefixlen ) * 2;
+			size += asgml3_point_size(ctx, (RTPOINT*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			size += ( sizeof("<curveMember>/") + prefixlen ) * 2;
+			size += asgml3_line_size(ctx, (RTLINE*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			size += ( sizeof("<surfaceMember>/") + prefixlen ) * 2;
+			size += asgml3_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, opts, prefix, id);
+		}
+	}
+
+	return size;
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asgml3_multi_buf(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	int type = col->type;
+	char *ptr, *gmltype;
+	int i;
+	RTGEOM *subgeom;
+
+	ptr = output;
+	gmltype="";
+
+	if 	(type == RTMULTIPOINTTYPE)   gmltype = "MultiPoint";
+	else if (type == RTMULTILINETYPE)    gmltype = "MultiCurve";
+	else if (type == RTMULTIPOLYGONTYPE) gmltype = "MultiSurface";
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%s%s", prefix, gmltype);
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+
+	if (!col->ngeoms)
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			ptr += sprintf(ptr, "<%spointMember>", prefix);
+			ptr += asgml3_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, opts, prefix, id);
+			ptr += sprintf(ptr, "</%spointMember>", prefix);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			ptr += sprintf(ptr, "<%scurveMember>", prefix);
+			ptr += asgml3_line_buf(ctx, (RTLINE*)subgeom, 0, ptr, precision, opts, prefix, id);
+			ptr += sprintf(ptr, "</%scurveMember>", prefix);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			ptr += sprintf(ptr, "<%ssurfaceMember>", prefix);
+			ptr += asgml3_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, opts, 0, prefix, id);
+			ptr += sprintf(ptr, "</%ssurfaceMember>", prefix);
+		}
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%s%s>", prefix, gmltype);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml3_multi(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml3_multi_size(ctx, col, srs, precision, opts, prefix, id);
+	gml = rtalloc(ctx, size);
+	asgml3_multi_buf(ctx, col, srs, gml, precision, opts, prefix, id);
+	return gml;
+}
+
+
+static size_t
+asgml3_psurface_size(const RTCTX *ctx, const RTPSURFACE *psur, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+
+	size = (sizeof("<PolyhedralSurface><polygonPatches>/") + prefixlen*2) * 2;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		size += asgml3_poly_size(ctx, psur->geoms[i], 0, precision, opts, prefix, id);
+	}
+
+	return size;
+}
+
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asgml3_psurface_buf(const RTCTX *ctx, const RTPSURFACE *psur, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr;
+	int i;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%sPolyhedralSurface", prefix);
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+	ptr += sprintf(ptr, "><%spolygonPatches>", prefix);
+
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		ptr += asgml3_poly_buf(ctx, psur->geoms[i], 0, ptr, precision, opts, 1, prefix, id);
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%spolygonPatches></%sPolyhedralSurface>",
+	               prefix, prefix);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml3_psurface(const RTCTX *ctx, const RTPSURFACE *psur, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml3_psurface_size(ctx, psur, srs, precision, opts, prefix, id);
+	gml = rtalloc(ctx, size);
+	asgml3_psurface_buf(ctx, psur, srs, gml, precision, opts, prefix, id);
+	return gml;
+}
+
+
+static size_t
+asgml3_tin_size(const RTCTX *ctx, const RTTIN *tin, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+
+	size = (sizeof("<Tin><trianglePatches>/") + prefixlen*2) * 2;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		size += asgml3_triangle_size(ctx, tin->geoms[i], 0, precision, opts, prefix, id);
+	}
+
+	return size;
+}
+
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asgml3_tin_buf(const RTCTX *ctx, const RTTIN *tin, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr;
+	int i;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%sTin", prefix);
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+	else	 ptr += sprintf(ptr, "><%strianglePatches>", prefix);
+
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		ptr += asgml3_triangle_buf(ctx, tin->geoms[i], 0, ptr, precision,
+		                           opts, prefix, id);
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%strianglePatches></%sTin>", prefix, prefix);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml3_tin(const RTCTX *ctx, const RTTIN *tin, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml3_tin_size(ctx, tin, srs, precision, opts, prefix, id);
+	gml = rtalloc(ctx, size);
+	asgml3_tin_buf(ctx, tin, srs, gml, precision, opts, prefix, id);
+	return gml;
+}
+
+static size_t
+asgml3_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	int i;
+	size_t size;
+	size_t prefixlen = strlen(prefix);
+	RTGEOM *subgeom;
+
+	size = sizeof("<MultiGeometry></MultiGeometry>") + prefixlen*2;
+
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		size += ( sizeof("<geometryMember>/") + prefixlen ) * 2;
+		if ( subgeom->type == RTPOINTTYPE )
+		{
+			size += asgml3_point_size(ctx, (RTPOINT*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else if ( subgeom->type == RTLINETYPE )
+		{
+			size += asgml3_line_size(ctx, (RTLINE*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else if ( subgeom->type == RTPOLYGONTYPE )
+		{
+			size += asgml3_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else if ( rtgeom_is_collection(ctx, subgeom) )
+		{
+			size += asgml3_multi_size(ctx, (RTCOLLECTION*)subgeom, 0, precision, opts, prefix, id);
+		}
+		else
+			rterror(ctx, "asgml3_collection_size: unknown geometry type");
+	}
+
+	return size;
+}
+
+static size_t
+asgml3_collection_buf(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char *ptr;
+	int i;
+	RTGEOM *subgeom;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<%sMultiGeometry", prefix);
+	if (srs) ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	if (id)  ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id);
+
+	if (!col->ngeoms)
+	{
+		ptr += sprintf(ptr, "/>");
+		return (ptr-output);
+	}
+	ptr += sprintf(ptr, ">");
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		ptr += sprintf(ptr, "<%sgeometryMember>", prefix);
+		if ( subgeom->type == RTPOINTTYPE )
+		{
+			ptr += asgml3_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, opts, prefix, id);
+		}
+		else if ( subgeom->type == RTLINETYPE )
+		{
+			ptr += asgml3_line_buf(ctx, (RTLINE*)subgeom, 0, ptr, precision, opts, prefix, id);
+		}
+		else if ( subgeom->type == RTPOLYGONTYPE )
+		{
+			ptr += asgml3_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, opts, 0, prefix, id);
+		}
+		else if ( rtgeom_is_collection(ctx, subgeom) )
+		{
+			if ( subgeom->type == RTCOLLECTIONTYPE )
+				ptr += asgml3_collection_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, opts, prefix, id);
+			else
+				ptr += asgml3_multi_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, opts, prefix, id);
+		}
+		else
+			rterror(ctx, "asgml3_collection_buf: unknown geometry type");
+
+		ptr += sprintf(ptr, "</%sgeometryMember>", prefix);
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "</%sMultiGeometry>", prefix);
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asgml3_collection(const RTCTX *ctx, const RTCOLLECTION *col, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char *gml;
+	size_t size;
+
+	size = asgml3_collection_size(ctx, col, srs, precision, opts, prefix, id);
+	gml = rtalloc(ctx, size);
+	asgml3_collection_buf(ctx, col, srs, gml, precision, opts, prefix, id);
+	return gml;
+}
+
+static size_t asgml3_multicurve_size(const RTCTX *ctx,  const RTMCURVE* cur, const char *srs, int precision, int opts, const char *prefix, const char *id )
+{
+	size_t prefixlen = strlen(prefix);
+	size_t size = sizeof( "<MultiCurve></MultiCurve>" ) + 2 * prefixlen;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	RTGEOM* subgeom;
+	int i;
+
+	for( i = 0; i < cur->ngeoms; ++i )
+	{
+		size += sizeof( "<curveMember></curveMember>" ) + 2 * prefixlen;
+		subgeom = cur->geoms[i];
+		if ( subgeom->type == RTLINETYPE )
+		{
+			size += asgml3_line_size(ctx,  (RTLINE*)subgeom, srs, precision, opts, prefix, id );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			size += asgml3_circstring_size(ctx,  (RTCIRCSTRING*)subgeom, srs, precision, opts, prefix, id );
+		}
+		else if( subgeom->type == RTCOMPOUNDTYPE )
+		{
+			size += asgml3_compound_size(ctx,  (RTCOMPOUND*)subgeom, srs, precision, opts, prefix, id );
+		}
+	}
+	return size;
+}
+
+static size_t asgml3_multicurve_buf(const RTCTX *ctx,  const RTMCURVE* cur, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id )
+{
+	char* ptr = output;
+	RTGEOM* subgeom;
+	int i;
+
+	ptr += sprintf(ptr, "<%sMultiCurve", prefix );
+	if (srs)
+	{
+		ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	}
+	if (id)
+	{
+		ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id );
+	}
+	ptr += sprintf( ptr, ">");
+
+	for( i = 0; i < cur->ngeoms; ++i )
+	{
+		ptr += sprintf(ptr, "<%scurveMember>", prefix );
+		subgeom = cur->geoms[i];
+		if ( subgeom->type == RTLINETYPE )
+		{
+			ptr += asgml3_line_buf(ctx,  (RTLINE*)subgeom, srs, ptr, precision, opts, prefix, id );
+		}
+		else if( subgeom->type == RTCIRCSTRINGTYPE )
+		{
+			ptr += asgml3_circstring_buf(ctx,  (RTCIRCSTRING*)subgeom, srs, ptr, precision, opts, prefix, id );
+		}
+		else if( subgeom->type == RTCOMPOUNDTYPE )
+		{
+			ptr += asgml3_compound_buf(ctx,  (RTCOMPOUND*)subgeom, srs, ptr, precision, opts, prefix, id );
+		}
+		ptr += sprintf(ptr, "</%scurveMember>", prefix );
+	}
+	ptr += sprintf(ptr, "</%sMultiCurve>", prefix );
+	return (ptr - output);
+}
+
+static char * asgml3_multicurve(const RTCTX *ctx,  const RTMCURVE* cur, const char *srs, int precision, int opts, const char *prefix, const char *id )
+{
+	char* gml;
+	size_t size =asgml3_multicurve_size(ctx,  cur, srs, precision, opts, prefix, id );
+	gml = rtalloc(ctx,  size );
+	asgml3_multicurve_buf(ctx,  cur, srs, gml, precision, opts, prefix, id );
+	return gml;
+}
+
+static size_t asgml3_multisurface_size(const RTCTX *ctx, const RTMSURFACE *sur, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	size_t prefixlen = strlen(prefix);
+	size_t size = sizeof( "<MultiSurface></MultiSurface>" ) + 2 * prefixlen;
+	if (srs) size += strlen(srs) + sizeof(" srsName=..");
+	if (id)  size += strlen(id) + strlen(prefix) + sizeof(" id=..");
+	RTGEOM* subgeom;
+	int i;
+
+	for( i = 0; i < sur->ngeoms; ++i )
+	{
+		subgeom = sur->geoms[i];
+		if( subgeom->type == RTPOLYGONTYPE )
+		{
+			size += asgml3_poly_size(ctx,  (RTPOLY*)sur->geoms[i], srs, precision, opts, prefix, id );
+		}
+		else if( subgeom->type == RTCURVEPOLYTYPE )
+		{
+			size += asgml3_curvepoly_size(ctx,  (RTCURVEPOLY*)sur->geoms[i], srs, precision, opts, prefix, id );
+		}
+	}
+	return size;
+}
+
+static size_t asgml3_multisurface_buf(const RTCTX *ctx, const RTMSURFACE *sur, const char *srs, char *output, int precision, int opts, const char *prefix, const char *id)
+{
+	char* ptr = output;
+	int i;
+	RTGEOM* subgeom;
+
+	ptr += sprintf( ptr, "<%sMultiSurface", prefix );
+	if (srs)
+	{
+		ptr += sprintf(ptr, " srsName=\"%s\"", srs);
+	}
+	if (id)
+	{
+		ptr += sprintf(ptr, " %sid=\"%s\"", prefix, id );
+	}
+	ptr += sprintf( ptr, ">" );
+
+	for( i = 0; i < sur->ngeoms; ++i )
+	{
+		subgeom = sur->geoms[i];
+		if( subgeom->type == RTPOLYGONTYPE )
+		{
+			ptr += asgml3_poly_buf(ctx,  (RTPOLY*)sur->geoms[i], srs, ptr, precision, opts, 0, prefix, id );
+		}
+		else if( subgeom->type == RTCURVEPOLYTYPE )
+		{
+			ptr += asgml3_curvepoly_buf(ctx,  (RTCURVEPOLY*)sur->geoms[i], srs, ptr, precision, opts, prefix, id );
+		}
+	}
+	ptr += sprintf( ptr, "</%sMultiSurface>", prefix );
+	return ptr - output;
+}
+
+static char * asgml3_multisurface(const RTCTX *ctx, const RTMSURFACE *sur, const char *srs, int precision, int opts, const char *prefix, const char *id)
+{
+	char* gml;
+	size_t size = asgml3_multisurface_size(ctx,  sur, srs, precision, opts, prefix, id );
+	gml = rtalloc(ctx,  size );
+	asgml3_multisurface_buf(ctx,  sur, srs, gml, precision, opts, prefix, id );
+	return gml;
+}
+
+
+/* In GML3, inside <posList> or <pos>, coordinates are separated by a space separator
+ * In GML3 also, lat/lon are reversed for geocentric data
+ */
+static size_t
+pointArray_toGML3(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int precision, int opts)
+{
+	int i;
+	char *ptr;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char z[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+
+	ptr = output;
+
+	if ( ! RTFLAGS_GET_Z(pa->flags) )
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT2D *pt;
+			pt = rt_getPoint2d_cp(ctx, pa, i);
+
+			if (fabs(pt->x) < OUT_MAX_DOUBLE)
+				sprintf(x, "%.*f", precision, pt->x);
+			else
+				sprintf(x, "%g", pt->x);
+			trim_trailing_zeros(ctx, x);
+
+			if (fabs(pt->y) < OUT_MAX_DOUBLE)
+				sprintf(y, "%.*f", precision, pt->y);
+			else
+				sprintf(y, "%g", pt->y);
+			trim_trailing_zeros(ctx, y);
+
+			if ( i ) ptr += sprintf(ptr, " ");
+			if (IS_DEGREE(opts))
+				ptr += sprintf(ptr, "%s %s", y, x);
+			else
+				ptr += sprintf(ptr, "%s %s", x, y);
+		}
+	}
+	else
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			const RTPOINT3DZ *pt;
+			pt = rt_getPoint3dz_cp(ctx, pa, i);
+
+			if (fabs(pt->x) < OUT_MAX_DOUBLE)
+				sprintf(x, "%.*f", precision, pt->x);
+			else
+				sprintf(x, "%g", pt->x);
+			trim_trailing_zeros(ctx, x);
+
+			if (fabs(pt->y) < OUT_MAX_DOUBLE)
+				sprintf(y, "%.*f", precision, pt->y);
+			else
+				sprintf(y, "%g", pt->y);
+			trim_trailing_zeros(ctx, y);
+
+			if (fabs(pt->z) < OUT_MAX_DOUBLE)
+				sprintf(z, "%.*f", precision, pt->z);
+			else
+				sprintf(z, "%g", pt->z);
+			trim_trailing_zeros(ctx, z);
+
+			if ( i ) ptr += sprintf(ptr, " ");
+			if (IS_DEGREE(opts))
+				ptr += sprintf(ptr, "%s %s %s", y, x, z);
+			else
+				ptr += sprintf(ptr, "%s %s %s", x, y, z);
+		}
+	}
+
+	return ptr-output;
+}
+
+
+
+/*
+ * Returns maximum size of rendered pointarray in bytes.
+ */
+static size_t
+pointArray_GMLsize(const RTCTX *ctx, RTPOINTARRAY *pa, int precision)
+{
+	if (RTFLAGS_NDIMS(pa->flags) == 2)
+		return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(", ")) * 2 * pa->npoints;
+
+	return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(", ")) * 3 * pa->npoints;
+}
diff --git a/src/rtout_kml.c b/src/rtout_kml.c
new file mode 100644
index 0000000..58ef9a8
--- /dev/null
+++ b/src/rtout_kml.c
@@ -0,0 +1,198 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2006 Corporacion Autonoma Regional de Santander 
+ * Copyright 2010 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "stringbuffer.h"
+
+static int rtgeom_to_kml2_sb(const RTCTX *ctx, const RTGEOM *geom, int precision, const char *prefix, stringbuffer_t *sb);
+static int rtpoint_to_kml2_sb(const RTCTX *ctx, const RTPOINT *point, int precision, const char *prefix, stringbuffer_t *sb);
+static int rtline_to_kml2_sb(const RTCTX *ctx, const RTLINE *line, int precision, const char *prefix, stringbuffer_t *sb);
+static int rtpoly_to_kml2_sb(const RTCTX *ctx, const RTPOLY *poly, int precision, const char *prefix, stringbuffer_t *sb);
+static int rtcollection_to_kml2_sb(const RTCTX *ctx, const RTCOLLECTION *col, int precision, const char *prefix, stringbuffer_t *sb);
+static int ptarray_to_kml2_sb(const RTCTX *ctx, const RTPOINTARRAY *pa, int precision, stringbuffer_t *sb);
+
+/*
+* KML 2.2.0
+*/
+
+/* takes a GEOMETRY and returns a KML representation */
+char*
+rtgeom_to_kml2(const RTCTX *ctx, const RTGEOM *geom, int precision, const char *prefix)
+{
+	stringbuffer_t *sb;
+	int rv;
+	char *kml;
+
+	/* Can't do anything with empty */
+	if( rtgeom_is_empty(ctx, geom) )
+		return NULL;
+
+	sb = stringbuffer_create(ctx);
+	rv = rtgeom_to_kml2_sb(ctx, geom, precision, prefix, sb);
+	
+	if ( rv == RT_FAILURE )
+	{
+		stringbuffer_destroy(ctx, sb);
+		return NULL;
+	}
+	
+	kml = stringbuffer_getstringcopy(ctx, sb);
+	stringbuffer_destroy(ctx, sb);
+	
+	return kml;
+}
+
+static int 
+rtgeom_to_kml2_sb(const RTCTX *ctx, const RTGEOM *geom, int precision, const char *prefix, stringbuffer_t *sb)
+{
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		return rtpoint_to_kml2_sb(ctx, (RTPOINT*)geom, precision, prefix, sb);
+
+	case RTLINETYPE:
+		return rtline_to_kml2_sb(ctx, (RTLINE*)geom, precision, prefix, sb);
+
+	case RTPOLYGONTYPE:
+		return rtpoly_to_kml2_sb(ctx, (RTPOLY*)geom, precision, prefix, sb);
+
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+		return rtcollection_to_kml2_sb(ctx, (RTCOLLECTION*)geom, precision, prefix, sb);
+
+	default:
+		rterror(ctx, "rtgeom_to_kml2: '%s' geometry type not supported", rttype_name(ctx, geom->type));
+		return RT_FAILURE;
+	}
+}
+
+static int 
+ptarray_to_kml2_sb(const RTCTX *ctx, const RTPOINTARRAY *pa, int precision, stringbuffer_t *sb)
+{
+	int i, j;
+	int dims = RTFLAGS_GET_Z(pa->flags) ? 3 : 2;
+	RTPOINT4D pt;
+	double *d;
+	
+	for ( i = 0; i < pa->npoints; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &pt);
+		d = (double*)(&pt);
+		if ( i ) stringbuffer_append(ctx, sb," ");
+		for (j = 0; j < dims; j++)
+		{
+			if ( j ) stringbuffer_append(ctx, sb,",");
+			if( fabs(d[j]) < OUT_MAX_DOUBLE )
+			{
+				if ( stringbuffer_aprintf(ctx, sb, "%.*f", precision, d[j]) < 0 ) return RT_FAILURE;
+			}
+			else 
+			{
+				if ( stringbuffer_aprintf(ctx, sb, "%g", d[j]) < 0 ) return RT_FAILURE;
+			}
+			stringbuffer_trim_trailing_zeroes(ctx, sb);
+		}
+	}
+	return RT_SUCCESS;
+}
+
+
+static int 
+rtpoint_to_kml2_sb(const RTCTX *ctx, const RTPOINT *point, int precision, const char *prefix, stringbuffer_t *sb)
+{
+	/* Open point */
+	if ( stringbuffer_aprintf(ctx, sb, "<%sPoint><%scoordinates>", prefix, prefix) < 0 ) return RT_FAILURE;
+	/* Coordinate array */
+	if ( ptarray_to_kml2_sb(ctx, point->point, precision, sb) == RT_FAILURE ) return RT_FAILURE;
+	/* Close point */
+	if ( stringbuffer_aprintf(ctx, sb, "</%scoordinates></%sPoint>", prefix, prefix) < 0 ) return RT_FAILURE;
+	return RT_SUCCESS;
+}
+
+static int 
+rtline_to_kml2_sb(const RTCTX *ctx, const RTLINE *line, int precision, const char *prefix, stringbuffer_t *sb)
+{
+	/* Open linestring */
+	if ( stringbuffer_aprintf(ctx, sb, "<%sLineString><%scoordinates>", prefix, prefix) < 0 ) return RT_FAILURE;
+	/* Coordinate array */
+	if ( ptarray_to_kml2_sb(ctx, line->points, precision, sb) == RT_FAILURE ) return RT_FAILURE;
+	/* Close linestring */
+	if ( stringbuffer_aprintf(ctx, sb, "</%scoordinates></%sLineString>", prefix, prefix) < 0 ) return RT_FAILURE;
+	
+	return RT_SUCCESS;
+}
+
+static int 
+rtpoly_to_kml2_sb(const RTCTX *ctx, const RTPOLY *poly, int precision, const char *prefix, stringbuffer_t *sb)
+{
+	int i, rv;
+	
+	/* Open polygon */
+	if ( stringbuffer_aprintf(ctx, sb, "<%sPolygon>", prefix) < 0 ) return RT_FAILURE;
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		/* Inner or outer ring opening tags */
+		if( i )
+			rv = stringbuffer_aprintf(ctx, sb, "<%sinnerBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix);
+		else
+			rv = stringbuffer_aprintf(ctx, sb, "<%souterBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix);		
+		if ( rv < 0 ) return RT_FAILURE;
+		
+		/* Coordinate array */
+		if ( ptarray_to_kml2_sb(ctx, poly->rings[i], precision, sb) == RT_FAILURE ) return RT_FAILURE;
+		
+		/* Inner or outer ring closing tags */
+		if( i )
+			rv = stringbuffer_aprintf(ctx, sb, "</%scoordinates></%sLinearRing></%sinnerBoundaryIs>", prefix, prefix, prefix);
+		else
+			rv = stringbuffer_aprintf(ctx, sb, "</%scoordinates></%sLinearRing></%souterBoundaryIs>", prefix, prefix, prefix);		
+		if ( rv < 0 ) return RT_FAILURE;
+	}
+	/* Close polygon */
+	if ( stringbuffer_aprintf(ctx, sb, "</%sPolygon>", prefix) < 0 ) return RT_FAILURE;
+
+	return RT_SUCCESS;
+}
+
+static int 
+rtcollection_to_kml2_sb(const RTCTX *ctx, const RTCOLLECTION *col, int precision, const char *prefix, stringbuffer_t *sb)
+{
+	int i, rv;
+		
+	/* Open geometry */
+	if ( stringbuffer_aprintf(ctx, sb, "<%sMultiGeometry>", prefix) < 0 ) return RT_FAILURE;
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		rv = rtgeom_to_kml2_sb(ctx, col->geoms[i], precision, prefix, sb);
+		if ( rv == RT_FAILURE ) return RT_FAILURE;		
+	}
+	/* Close geometry */
+	if ( stringbuffer_aprintf(ctx, sb, "</%sMultiGeometry>", prefix) < 0 ) return RT_FAILURE;
+
+	return RT_SUCCESS;
+}
diff --git a/src/rtout_svg.c b/src/rtout_svg.c
new file mode 100644
index 0000000..9eea60b
--- /dev/null
+++ b/src/rtout_svg.c
@@ -0,0 +1,670 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2003 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/** @file
+*
+* SVG output routines.
+* Originally written by: Klaus F�rster <klaus at svg.cc>
+* Refactored by: Olivier Courtin (Camptocamp)
+*
+* BNF SVG Path: <http://www.w3.org/TR/SVG/paths.html#PathDataBNF>
+**********************************************************************/
+
+#include "librtgeom_internal.h"
+
+static char * assvg_point(const RTCTX *ctx, const RTPOINT *point, int relative, int precision);
+static char * assvg_line(const RTCTX *ctx, const RTLINE *line, int relative, int precision);
+static char * assvg_polygon(const RTCTX *ctx, const RTPOLY *poly, int relative, int precision);
+static char * assvg_multipoint(const RTCTX *ctx, const RTMPOINT *mpoint, int relative, int precision);
+static char * assvg_multiline(const RTCTX *ctx, const RTMLINE *mline, int relative, int precision);
+static char * assvg_multipolygon(const RTCTX *ctx, const RTMPOLY *mpoly, int relative, int precision);
+static char * assvg_collection(const RTCTX *ctx, const RTCOLLECTION *col, int relative, int precision);
+
+static size_t assvg_geom_size(const RTCTX *ctx, const RTGEOM *geom, int relative, int precision);
+static size_t assvg_geom_buf(const RTCTX *ctx, const RTGEOM *geom, char *output, int relative, int precision);
+static size_t pointArray_svg_size(const RTCTX *ctx, RTPOINTARRAY *pa, int precision);
+static size_t pointArray_svg_rel(const RTCTX *ctx, RTPOINTARRAY *pa, char * output, int close_ring, int precision);
+static size_t pointArray_svg_abs(const RTCTX *ctx, RTPOINTARRAY *pa, char * output, int close_ring, int precision);
+
+
+/**
+ * Takes a GEOMETRY and returns a SVG representation
+ */
+char *
+rtgeom_to_svg(const RTCTX *ctx, const RTGEOM *geom, int precision, int relative)
+{
+	char *ret = NULL;
+	int type = geom->type;
+
+	/* Empty string for empties */
+	if( rtgeom_is_empty(ctx, geom) )
+	{
+		ret = rtalloc(ctx, 1);
+		ret[0] = '\0';
+		return ret;
+	}
+	
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		ret = assvg_point(ctx, (RTPOINT*)geom, relative, precision);
+		break;
+	case RTLINETYPE:
+		ret = assvg_line(ctx, (RTLINE*)geom, relative, precision);
+		break;
+	case RTPOLYGONTYPE:
+		ret = assvg_polygon(ctx, (RTPOLY*)geom, relative, precision);
+		break;
+	case RTMULTIPOINTTYPE:
+		ret = assvg_multipoint(ctx, (RTMPOINT*)geom, relative, precision);
+		break;
+	case RTMULTILINETYPE:
+		ret = assvg_multiline(ctx, (RTMLINE*)geom, relative, precision);
+		break;
+	case RTMULTIPOLYGONTYPE:
+		ret = assvg_multipolygon(ctx, (RTMPOLY*)geom, relative, precision);
+		break;
+	case RTCOLLECTIONTYPE:
+		ret = assvg_collection(ctx, (RTCOLLECTION*)geom, relative, precision);
+		break;
+
+	default:
+		rterror(ctx, "rtgeom_to_svg: '%s' geometry type not supported",
+		        rttype_name(ctx, type));
+	}
+
+	return ret;
+}
+
+
+/**
+ * Point Geometry
+ */
+
+static size_t
+assvg_point_size(const RTCTX *ctx, const RTPOINT *point, int circle, int precision)
+{
+	size_t size;
+
+	size = (OUT_MAX_DIGS_DOUBLE + precision) * 2;
+	if (circle) size += sizeof("cx='' cy=''");
+	else size += sizeof("x='' y=''");
+
+	return size;
+}
+
+static size_t
+assvg_point_buf(const RTCTX *ctx, const RTPOINT *point, char * output, int circle, int precision)
+{
+	char *ptr=output;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	RTPOINT2D pt;
+
+	rt_getPoint2d_p(ctx, point->point, 0, &pt);
+
+	if (fabs(pt.x) < OUT_MAX_DOUBLE)
+		sprintf(x, "%.*f", precision, pt.x);
+	else
+		sprintf(x, "%g", pt.x);
+	trim_trailing_zeros(ctx, x);
+
+	/* SVG Y axis is reversed, an no need to transform 0 into -0 */
+	if (fabs(pt.y) < OUT_MAX_DOUBLE)
+		sprintf(y, "%.*f", precision, fabs(pt.y) ? pt.y * -1 : pt.y);
+	else
+		sprintf(y, "%g", fabs(pt.y) ? pt.y * -1 : pt.y);
+	trim_trailing_zeros(ctx, y);
+
+	if (circle) ptr += sprintf(ptr, "x=\"%s\" y=\"%s\"", x, y);
+	else ptr += sprintf(ptr, "cx=\"%s\" cy=\"%s\"", x, y);
+
+	return (ptr-output);
+}
+
+static char *
+assvg_point(const RTCTX *ctx, const RTPOINT *point, int circle, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_point_size(ctx, point, circle, precision);
+	output = rtalloc(ctx, size);
+	assvg_point_buf(ctx, point, output, circle, precision);
+
+	return output;
+}
+
+
+/**
+ * Line Geometry
+ */
+
+static size_t
+assvg_line_size(const RTCTX *ctx, const RTLINE *line, int relative, int precision)
+{
+	size_t size;
+
+	size = sizeof("M ");
+	size += pointArray_svg_size(ctx, line->points, precision);
+
+	return size;
+}
+
+static size_t
+assvg_line_buf(const RTCTX *ctx, const RTLINE *line, char * output, int relative, int precision)
+{
+	char *ptr=output;
+
+	/* Start path with SVG MoveTo */
+	ptr += sprintf(ptr, "M ");
+	if (relative)
+		ptr += pointArray_svg_rel(ctx, line->points, ptr, 1, precision);
+	else
+		ptr += pointArray_svg_abs(ctx, line->points, ptr, 1, precision);
+
+	return (ptr-output);
+}
+
+static char *
+assvg_line(const RTCTX *ctx, const RTLINE *line, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_line_size(ctx, line, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_line_buf(ctx, line, output, relative, precision);
+
+	return output;
+}
+
+
+/**
+ * Polygon Geometry
+ */
+
+static size_t
+assvg_polygon_size(const RTCTX *ctx, const RTPOLY *poly, int relative, int precision)
+{
+	int i;
+	size_t size=0;
+
+	for (i=0; i<poly->nrings; i++)
+		size += pointArray_svg_size(ctx, poly->rings[i], precision) + sizeof(" ");
+	size += sizeof("M  Z") * poly->nrings;
+
+	return size;
+}
+
+static size_t
+assvg_polygon_buf(const RTCTX *ctx, const RTPOLY *poly, char * output, int relative, int precision)
+{
+	int i;
+	char *ptr=output;
+
+	for (i=0; i<poly->nrings; i++)
+	{
+		if (i) ptr += sprintf(ptr, " ");	/* Space beetween each ring */
+		ptr += sprintf(ptr, "M ");		/* Start path with SVG MoveTo */
+
+		if (relative)
+		{
+			ptr += pointArray_svg_rel(ctx, poly->rings[i], ptr, 0, precision);
+			ptr += sprintf(ptr, " z");	/* SVG closepath */
+		}
+		else
+		{
+			ptr += pointArray_svg_abs(ctx, poly->rings[i], ptr, 0, precision);
+			ptr += sprintf(ptr, " Z");	/* SVG closepath */
+		}
+	}
+
+	return (ptr-output);
+}
+
+static char *
+assvg_polygon(const RTCTX *ctx, const RTPOLY *poly, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_polygon_size(ctx, poly, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_polygon_buf(ctx, poly, output, relative, precision);
+
+	return output;
+}
+
+
+/**
+ * Multipoint Geometry
+ */
+
+static size_t
+assvg_multipoint_size(const RTCTX *ctx, const RTMPOINT *mpoint, int relative, int precision)
+{
+	const RTPOINT *point;
+	size_t size=0;
+	int i;
+
+	for (i=0 ; i<mpoint->ngeoms ; i++)
+	{
+		point = mpoint->geoms[i];
+		size += assvg_point_size(ctx, point, relative, precision);
+	}
+	size += sizeof(",") * --i;  /* Arbitrary comma separator */
+
+	return size;
+}
+
+static size_t
+assvg_multipoint_buf(const RTCTX *ctx, const RTMPOINT *mpoint, char *output, int relative, int precision)
+{
+	const RTPOINT *point;
+	int i;
+	char *ptr=output;
+
+	for (i=0 ; i<mpoint->ngeoms ; i++)
+	{
+		if (i) ptr += sprintf(ptr, ",");  /* Arbitrary comma separator */
+		point = mpoint->geoms[i];
+		ptr += assvg_point_buf(ctx, point, ptr, relative, precision);
+	}
+
+	return (ptr-output);
+}
+
+static char *
+assvg_multipoint(const RTCTX *ctx, const RTMPOINT *mpoint, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_multipoint_size(ctx, mpoint, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_multipoint_buf(ctx, mpoint, output, relative, precision);
+
+	return output;
+}
+
+
+/**
+ * Multiline Geometry
+ */
+
+static size_t
+assvg_multiline_size(const RTCTX *ctx, const RTMLINE *mline, int relative, int precision)
+{
+	const RTLINE *line;
+	size_t size=0;
+	int i;
+
+	for (i=0 ; i<mline->ngeoms ; i++)
+	{
+		line = mline->geoms[i];
+		size += assvg_line_size(ctx, line, relative, precision);
+	}
+	size += sizeof(" ") * --i;   /* SVG whitespace Separator */
+
+	return size;
+}
+
+static size_t
+assvg_multiline_buf(const RTCTX *ctx, const RTMLINE *mline, char *output, int relative, int precision)
+{
+	const RTLINE *line;
+	int i;
+	char *ptr=output;
+
+	for (i=0 ; i<mline->ngeoms ; i++)
+	{
+		if (i) ptr += sprintf(ptr, " ");  /* SVG whitespace Separator */
+		line = mline->geoms[i];
+		ptr += assvg_line_buf(ctx, line, ptr, relative, precision);
+	}
+
+	return (ptr-output);
+}
+
+static char *
+assvg_multiline(const RTCTX *ctx, const RTMLINE *mline, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_multiline_size(ctx, mline, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_multiline_buf(ctx, mline, output, relative, precision);
+
+	return output;
+}
+
+
+/*
+ * Multipolygon Geometry
+ */
+
+static size_t
+assvg_multipolygon_size(const RTCTX *ctx, const RTMPOLY *mpoly, int relative, int precision)
+{
+	const RTPOLY *poly;
+	size_t size=0;
+	int i;
+
+	for (i=0 ; i<mpoly->ngeoms ; i++)
+	{
+		poly = mpoly->geoms[i];
+		size += assvg_polygon_size(ctx, poly, relative, precision);
+	}
+	size += sizeof(" ") * --i;   /* SVG whitespace Separator */
+
+	return size;
+}
+
+static size_t
+assvg_multipolygon_buf(const RTCTX *ctx, const RTMPOLY *mpoly, char *output, int relative, int precision)
+{
+	const RTPOLY *poly;
+	int i;
+	char *ptr=output;
+
+	for (i=0 ; i<mpoly->ngeoms ; i++)
+	{
+		if (i) ptr += sprintf(ptr, " ");  /* SVG whitespace Separator */
+		poly = mpoly->geoms[i];
+		ptr += assvg_polygon_buf(ctx, poly, ptr, relative, precision);
+	}
+
+	return (ptr-output);
+}
+
+static char *
+assvg_multipolygon(const RTCTX *ctx, const RTMPOLY *mpoly, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_multipolygon_size(ctx, mpoly, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_multipolygon_buf(ctx, mpoly, output, relative, precision);
+
+	return output;
+}
+
+
+/**
+* Collection Geometry
+*/
+
+static size_t
+assvg_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, int relative, int precision)
+{
+	int i = 0;
+	size_t size=0;
+	const RTGEOM *subgeom;
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		size += assvg_geom_size(ctx, subgeom, relative, precision);
+	}
+
+	if ( i ) /* We have some geometries, so add space for delimiters. */
+		size += sizeof(";") * --i;
+
+	if (size == 0) size++; /* GEOMETRYCOLLECTION EMPTY, space for null terminator */
+
+	return size;
+}
+
+static size_t
+assvg_collection_buf(const RTCTX *ctx, const RTCOLLECTION *col, char *output, int relative, int precision)
+{
+	int i;
+	char *ptr=output;
+	const RTGEOM *subgeom;
+
+	/* EMPTY GEOMETRYCOLLECTION */
+	if (col->ngeoms == 0) *ptr = '\0';
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		if (i) ptr += sprintf(ptr, ";");
+		subgeom = col->geoms[i];
+		ptr += assvg_geom_buf(ctx, subgeom, ptr, relative, precision);
+	}
+
+	return (ptr - output);
+}
+
+static char *
+assvg_collection(const RTCTX *ctx, const RTCOLLECTION *col, int relative, int precision)
+{
+	char *output;
+	int size;
+
+	size = assvg_collection_size(ctx, col, relative, precision);
+	output = rtalloc(ctx, size);
+	assvg_collection_buf(ctx, col, output, relative, precision);
+
+	return output;
+}
+
+
+static size_t
+assvg_geom_buf(const RTCTX *ctx, const RTGEOM *geom, char *output, int relative, int precision)
+{
+    int type = geom->type;
+	char *ptr=output;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		ptr += assvg_point_buf(ctx, (RTPOINT*)geom, ptr, relative, precision);
+		break;
+
+	case RTLINETYPE:
+		ptr += assvg_line_buf(ctx, (RTLINE*)geom, ptr, relative, precision);
+		break;
+
+	case RTPOLYGONTYPE:
+		ptr += assvg_polygon_buf(ctx, (RTPOLY*)geom, ptr, relative, precision);
+		break;
+
+	case RTMULTIPOINTTYPE:
+		ptr += assvg_multipoint_buf(ctx, (RTMPOINT*)geom, ptr, relative, precision);
+		break;
+
+	case RTMULTILINETYPE:
+		ptr += assvg_multiline_buf(ctx, (RTMLINE*)geom, ptr, relative, precision);
+		break;
+
+	case RTMULTIPOLYGONTYPE:
+		ptr += assvg_multipolygon_buf(ctx, (RTMPOLY*)geom, ptr, relative, precision);
+		break;
+
+	default:
+		rterror(ctx, "assvg_geom_buf: '%s' geometry type not supported.",
+		        rttype_name(ctx, type));
+	}
+
+	return (ptr-output);
+}
+
+
+static size_t
+assvg_geom_size(const RTCTX *ctx, const RTGEOM *geom, int relative, int precision)
+{
+    int type = geom->type;
+	size_t size = 0;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		size = assvg_point_size(ctx, (RTPOINT*)geom, relative, precision);
+		break;
+
+	case RTLINETYPE:
+		size = assvg_line_size(ctx, (RTLINE*)geom, relative, precision);
+		break;
+
+	case RTPOLYGONTYPE:
+		size = assvg_polygon_size(ctx, (RTPOLY*)geom, relative, precision);
+		break;
+
+	case RTMULTIPOINTTYPE:
+		size = assvg_multipoint_size(ctx, (RTMPOINT*)geom, relative, precision);
+		break;
+
+	case RTMULTILINETYPE:
+		size = assvg_multiline_size(ctx, (RTMLINE*)geom, relative, precision);
+		break;
+
+	case RTMULTIPOLYGONTYPE:
+		size = assvg_multipolygon_size(ctx, (RTMPOLY*)geom, relative, precision);
+		break;
+
+	default:
+		rterror(ctx, "assvg_geom_size: '%s' geometry type not supported.",
+		        rttype_name(ctx, type));
+	}
+
+	return size;
+}
+
+
+static size_t
+pointArray_svg_rel(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int close_ring, int precision)
+{
+	int i, end;
+	char *ptr;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	RTPOINT2D pt, lpt;
+
+	ptr = output;
+
+	if (close_ring) end = pa->npoints;
+	else end = pa->npoints - 1;
+
+	/* Starting point */
+	rt_getPoint2d_p(ctx, pa, 0, &pt);
+
+	if (fabs(pt.x) < OUT_MAX_DOUBLE)
+		sprintf(x, "%.*f", precision, pt.x);
+	else
+		sprintf(x, "%g", pt.x);
+	trim_trailing_zeros(ctx, x);
+
+	if (fabs(pt.y) < OUT_MAX_DOUBLE)
+		sprintf(y, "%.*f", precision, fabs(pt.y) ? pt.y * -1 : pt.y);
+	else
+		sprintf(y, "%g", fabs(pt.y) ? pt.y * -1 : pt.y);
+	trim_trailing_zeros(ctx, y);
+
+	ptr += sprintf(ptr,"%s %s l", x, y);
+
+	/* All the following ones */
+	for (i=1 ; i < end ; i++)
+	{
+		lpt = pt;
+
+		rt_getPoint2d_p(ctx, pa, i, &pt);
+		if (fabs(pt.x -lpt.x) < OUT_MAX_DOUBLE)
+			sprintf(x, "%.*f", precision, pt.x -lpt.x);
+		else
+			sprintf(x, "%g", pt.x -lpt.x);
+		trim_trailing_zeros(ctx, x);
+
+		/* SVG Y axis is reversed, an no need to transform 0 into -0 */
+		if (fabs(pt.y -lpt.y) < OUT_MAX_DOUBLE)
+			sprintf(y, "%.*f", precision,
+			        fabs(pt.y -lpt.y) ? (pt.y - lpt.y) * -1: (pt.y - lpt.y));
+		else
+			sprintf(y, "%g",
+			        fabs(pt.y -lpt.y) ? (pt.y - lpt.y) * -1: (pt.y - lpt.y));
+		trim_trailing_zeros(ctx, y);
+
+		ptr += sprintf(ptr," %s %s", x, y);
+	}
+
+	return (ptr-output);
+}
+
+
+/**
+ * Returns maximum size of rendered pointarray in bytes.
+ */
+static size_t
+pointArray_svg_abs(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int close_ring, int precision)
+{
+	int i, end;
+	char *ptr;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	RTPOINT2D pt;
+
+	ptr = output;
+
+	if (close_ring) end = pa->npoints;
+	else end = pa->npoints - 1;
+
+	for (i=0 ; i < end ; i++)
+	{
+		rt_getPoint2d_p(ctx, pa, i, &pt);
+
+		if (fabs(pt.x) < OUT_MAX_DOUBLE)
+			sprintf(x, "%.*f", precision, pt.x);
+		else
+			sprintf(x, "%g", pt.x);
+		trim_trailing_zeros(ctx, x);
+
+		/* SVG Y axis is reversed, an no need to transform 0 into -0 */
+		if (fabs(pt.y) < OUT_MAX_DOUBLE)
+			sprintf(y, "%.*f", precision, fabs(pt.y) ? pt.y * -1:pt.y);
+		else
+			sprintf(y, "%g", fabs(pt.y) ? pt.y * -1:pt.y);
+		trim_trailing_zeros(ctx, y);
+
+		if (i == 1) ptr += sprintf(ptr, " L ");
+		else if (i) ptr += sprintf(ptr, " ");
+		ptr += sprintf(ptr,"%s %s", x, y);
+	}
+
+	return (ptr-output);
+}
+
+
+/**
+ * Returns maximum size of rendered pointarray in bytes.
+ */
+static size_t
+pointArray_svg_size(const RTCTX *ctx, RTPOINTARRAY *pa, int precision)
+{
+	return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(" "))
+	       * 2 * pa->npoints + sizeof(" L ");
+}
diff --git a/src/rtout_twkb.c b/src/rtout_twkb.c
new file mode 100644
index 0000000..4a4d9bd
--- /dev/null
+++ b/src/rtout_twkb.c
@@ -0,0 +1,616 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2013 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+#include "rtout_twkb.h"
+
+/*
+* GeometryType, and dimensions
+*/
+static uint8_t rtgeom_twkb_type(const RTCTX *ctx, const RTGEOM *geom)
+{
+	uint8_t twkb_type = 0;
+
+	RTDEBUGF(2, "Entered  rtgeom_twkb_type",0);
+
+	switch ( geom->type )
+	{
+		case RTPOINTTYPE:
+			twkb_type = RTWKB_POINT_TYPE;
+			break;
+		case RTLINETYPE:
+			twkb_type = RTWKB_LINESTRING_TYPE;
+			break;
+		case RTPOLYGONTYPE:
+			twkb_type = RTWKB_POLYGON_TYPE;
+			break;
+		case RTMULTIPOINTTYPE:
+			twkb_type = RTWKB_MULTIPOINT_TYPE;
+			break;
+		case RTMULTILINETYPE:
+			twkb_type = RTWKB_MULTILINESTRING_TYPE;
+			break;
+		case RTMULTIPOLYGONTYPE:
+			twkb_type = RTWKB_MULTIPOLYGON_TYPE;
+			break;
+		case RTCOLLECTIONTYPE:
+			twkb_type = RTWKB_GEOMETRYCOLLECTION_TYPE;
+			break;
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]",
+				rttype_name(ctx, geom->type), geom->type);
+	}
+	return twkb_type;
+}
+
+
+/**
+* Calculates the size of the bbox in varints in the form:
+* xmin, xdelta, ymin, ydelta
+*/
+static size_t sizeof_bbox(const RTCTX *ctx, TWKB_STATE *ts, int ndims)
+{
+	int i;
+	uint8_t buf[16];
+	size_t size = 0;
+	RTDEBUGF(2, "Entered %s", __func__);
+	for ( i = 0; i < ndims; i++ )
+	{
+		size += varint_s64_encode_buf(ctx, ts->bbox_min[i], buf);
+		size += varint_s64_encode_buf(ctx, (ts->bbox_max[i] - ts->bbox_min[i]), buf);
+	}
+	return size;
+}
+/**
+* Writes the bbox in varints in the form:
+* xmin, xdelta, ymin, ydelta
+*/
+static void write_bbox(const RTCTX *ctx, TWKB_STATE *ts, int ndims)
+{
+	int i;
+	RTDEBUGF(2, "Entered %s", __func__);
+	for ( i = 0; i < ndims; i++ )
+	{
+		bytebuffer_append_varint(ctx, ts->header_buf, ts->bbox_min[i]);
+		bytebuffer_append_varint(ctx, ts->header_buf, (ts->bbox_max[i] - ts->bbox_min[i]));
+	}
+}
+
+
+/**
+* Stores a pointarray as varints in the buffer
+* @register_npoints, controls whether an npoints entry is added to the buffer (used to skip npoints for point types)
+* @dimension, states the dimensionality of object this array is part of (0 = point, 1 = linear, 2 = areal)
+*/
+static int ptarray_to_twkb_buf(const RTCTX *ctx, const RTPOINTARRAY *pa, TWKB_GLOBALS *globals, TWKB_STATE *ts, int register_npoints, int minpoints)
+{
+	int ndims = RTFLAGS_NDIMS(pa->flags);
+	int i, j;
+	bytebuffer_t b;
+	bytebuffer_t *b_p;
+	int64_t nextdelta[MAX_N_DIMS];
+	int npoints = 0;
+	size_t npoints_offset = 0;
+
+	RTDEBUGF(2, "Entered %s", __func__);
+
+	/* Dispense with the empty case right away */
+	if ( pa->npoints == 0 && register_npoints )	
+	{		
+		RTDEBUGF(4, "Register npoints:%d", pa->npoints);
+		bytebuffer_append_uvarint(ctx, ts->geom_buf, pa->npoints);
+		return 0;
+	}
+
+	/* If npoints is more than 127 it is unpredictable how many bytes npoints will need */
+	/* Then we have to store the deltas in a temp buffer to later add them after npoints */
+	/* If noints is below 128 we know 1 byte will be needed */
+	/* Then we can make room for that 1 byte at once and write to */
+	/* ordinary buffer */
+	if( pa->npoints > 127 )
+	{
+		/* Independent buffer to hold the coordinates, so we can put the npoints */
+		/* into the stream once we know how many points we actually have */
+		bytebuffer_init_with_size(ctx, &b, 3 * ndims * pa->npoints);
+		b_p = &b;
+	}
+	else
+	{
+		/* We give an alias to our ordinary buffer */
+		b_p = ts->geom_buf;
+		if ( register_npoints )		
+		{		
+			/* We do not store a pointer to the place where we want the npoints value */
+			/* Instead we store how far from the beginning of the buffer we want the value */
+			/* That is because we otherwise will get in trouble if the buffer is reallocated */			
+			npoints_offset = b_p->writecursor - b_p->buf_start;
+			
+			/* We just move the cursor 1 step to make room for npoints byte */
+			/* We use the function append_byte even if we have no value yet, */
+			/* since that gives us the check for big enough buffer and moves the cursor */
+			bytebuffer_append_byte(ctx, b_p, 0);
+		}
+	}
+	
+	for ( i = 0; i < pa->npoints; i++ )
+	{
+		double *dbl_ptr = (double*)rt_getPoint_internal(ctx, pa, i);
+		int diff = 0;
+
+		/* Write this coordinate to the buffer as a varint */
+		for ( j = 0; j < ndims; j++ )
+		{
+			/* To get the relative coordinate we don't get the distance */
+			/* from the last point but instead the distance from our */
+			/* last accumulated point. This is important to not build up an */
+			/* accumulated error when rounding the coordinates */
+			nextdelta[j] = (int64_t) llround(globals->factor[j] * dbl_ptr[j]) - ts->accum_rels[j];
+			RTDEBUGF(4, "deltavalue: %d, ", nextdelta[j]);
+			diff += llabs(nextdelta[j]);
+		}
+		
+		/* Skipping the first point is not allowed */
+		/* If the sum(abs()) of all the deltas was zero, */
+		/* then this was a duplicate point, so we can ignore it */
+		if ( i > minpoints && diff == 0 )
+			continue;
+		
+		/* We really added a point, so... */
+		npoints++;
+		
+		/* Write this vertex to the temporary buffer as varints */
+		for ( j = 0; j < ndims; j++ )
+		{
+			ts->accum_rels[j] += nextdelta[j];
+			bytebuffer_append_varint(ctx, b_p, nextdelta[j]);
+		}
+
+		/* See if this coordinate expands the bounding box */
+		if( globals->variant & TWKB_BBOX )
+		{
+			for ( j = 0; j < ndims; j++ )
+			{
+				if( ts->accum_rels[j] > ts->bbox_max[j] )
+					ts->bbox_max[j] = ts->accum_rels[j];
+
+				if( ts->accum_rels[j] < ts->bbox_min[j] )
+					ts->bbox_min[j] = ts->accum_rels[j];
+			}
+		}
+
+	}	
+
+	if ( pa->npoints > 127 )
+	{		
+		/* Now write the temporary results into the main buffer */
+		/* First the npoints */
+		if ( register_npoints )	
+			bytebuffer_append_uvarint(ctx, ts->geom_buf, npoints);
+		/* Now the coordinates */
+		bytebuffer_append_bytebuffer(ctx, ts->geom_buf, b_p);
+		
+		/* Clear our temporary buffer */
+		rtfree(ctx, b.buf_start);
+	}
+	else
+	{
+		/* If we didn't use a temp buffer, we just write that npoints value */
+		/* to where it belongs*/
+		if ( register_npoints )	
+			varint_u64_encode_buf(ctx, npoints, b_p->buf_start + npoints_offset);
+	}
+	
+	return 0;
+}
+
+/******************************************************************
+* POINTS
+*******************************************************************/
+
+static int rtpoint_to_twkb_buf(const RTCTX *ctx, const RTPOINT *pt, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	RTDEBUGF(2, "Entered %s", __func__);
+
+	/* Set the coordinates (don't write npoints) */
+	ptarray_to_twkb_buf(ctx, pt->point, globals, ts, 0, 1);
+	return 0;
+}
+
+/******************************************************************
+* LINESTRINGS
+*******************************************************************/
+
+static int rtline_to_twkb_buf(const RTCTX *ctx, const RTLINE *line, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	RTDEBUGF(2, "Entered %s", __func__);
+
+	/* Set the coordinates (do write npoints) */
+	ptarray_to_twkb_buf(ctx, line->points, globals, ts, 1, 2);
+	return 0;
+}
+
+/******************************************************************
+* POLYGONS
+*******************************************************************/
+
+static int rtpoly_to_twkb_buf(const RTCTX *ctx, const RTPOLY *poly, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	int i;
+
+	/* Set the number of rings */
+	bytebuffer_append_uvarint(ctx, ts->geom_buf, (uint64_t) poly->nrings);
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		/* Set the coordinates (do write npoints) */
+		ptarray_to_twkb_buf(ctx, poly->rings[i], globals, ts, 1, 4);
+	}
+
+	return 0;
+}
+
+
+
+/******************************************************************
+* MULTI-GEOMETRYS (MultiPoint, MultiLinestring, MultiPolygon)
+*******************************************************************/
+
+static int rtmulti_to_twkb_buf(const RTCTX *ctx, const RTCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	int i;
+	int nempty = 0;
+
+	RTDEBUGF(2, "Entered %s", __func__);
+	RTDEBUGF(4, "Number of geometries in multi is %d", col->ngeoms);
+
+	/* Deal with special case for MULTIPOINT: skip any empty points */
+	if ( col->type == RTMULTIPOINTTYPE )
+	{
+		for ( i = 0; i < col->ngeoms; i++ )
+			if ( rtgeom_is_empty(ctx, col->geoms[i]) )
+				nempty++;
+	}
+
+	/* Set the number of geometries */
+	bytebuffer_append_uvarint(ctx, ts->geom_buf, (uint64_t) (col->ngeoms - nempty));
+
+	/* We've been handed an idlist, so write it in */
+	if ( ts->idlist )
+	{
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			/* Skip empty points in multipoints, we can't represent them */
+			if ( col->type == RTMULTIPOINTTYPE && rtgeom_is_empty(ctx, col->geoms[i]) )
+				continue;
+			
+			bytebuffer_append_varint(ctx, ts->geom_buf, ts->idlist[i]);
+		}
+		
+		/* Empty it out to nobody else uses it now */
+		ts->idlist = NULL;
+	}
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		/* Skip empty points in multipoints, we can't represent them */
+		if ( col->type == RTMULTIPOINTTYPE && rtgeom_is_empty(ctx, col->geoms[i]) )
+			continue;
+
+		rtgeom_to_twkb_buf(ctx, col->geoms[i], globals, ts);
+	}
+	return 0;
+}
+
+/******************************************************************
+* GEOMETRYCOLLECTIONS
+*******************************************************************/
+
+static int rtcollection_to_twkb_buf(const RTCTX *ctx, const RTCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	int i;
+
+	RTDEBUGF(2, "Entered %s", __func__);
+	RTDEBUGF(4, "Number of geometries in collection is %d", col->ngeoms);
+
+	/* Set the number of geometries */
+	bytebuffer_append_uvarint(ctx, ts->geom_buf, (uint64_t) col->ngeoms);
+
+	/* We've been handed an idlist, so write it in */
+	if ( ts->idlist )
+	{
+		for ( i = 0; i < col->ngeoms; i++ )
+			bytebuffer_append_varint(ctx, ts->geom_buf, ts->idlist[i]);
+		
+		/* Empty it out to nobody else uses it now */
+		ts->idlist = NULL;
+	}
+
+	/* Write in the sub-geometries */
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		rtgeom_write_to_buffer(ctx, col->geoms[i], globals, ts);
+	}
+	return 0;
+}
+
+
+/******************************************************************
+* Handle whole TWKB
+*******************************************************************/
+
+static int rtgeom_to_twkb_buf(const RTCTX *ctx, const RTGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *ts)
+{
+	RTDEBUGF(2, "Entered %s", __func__);
+
+	switch ( geom->type )
+	{
+		case RTPOINTTYPE:
+		{
+			RTDEBUGF(4,"Type found is Point, %d", geom->type);
+			return rtpoint_to_twkb_buf(ctx, (RTPOINT*) geom, globals, ts);
+		}
+		case RTLINETYPE:
+		{
+			RTDEBUGF(4,"Type found is Linestring, %d", geom->type);
+			return rtline_to_twkb_buf(ctx, (RTLINE*) geom, globals, ts);
+		}
+		/* Polygon has 'nrings' and 'rings' elements */
+		case RTPOLYGONTYPE:
+		{
+			RTDEBUGF(4,"Type found is Polygon, %d", geom->type);
+			return rtpoly_to_twkb_buf(ctx, (RTPOLY*)geom, globals, ts);
+		}
+
+		/* All these Collection types have 'ngeoms' and 'geoms' elements */
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		{
+			RTDEBUGF(4,"Type found is Multi, %d", geom->type);
+			return rtmulti_to_twkb_buf(ctx, (RTCOLLECTION*)geom, globals, ts);
+		}
+		case RTCOLLECTIONTYPE:
+		{
+			RTDEBUGF(4,"Type found is collection, %d", geom->type);
+			return rtcollection_to_twkb_buf(ctx, (RTCOLLECTION*) geom, globals, ts);
+		}
+		/* Unknown type! */
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]", rttype_name(ctx, (geom)->type), (geom)->type);
+	}
+
+	return 0;
+}
+
+
+static int rtgeom_write_to_buffer(const RTCTX *ctx, const RTGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *parent_state)
+{
+	int i, is_empty, has_z, has_m, ndims;
+	size_t bbox_size = 0, optional_precision_byte = 0;
+	uint8_t flag = 0, type_prec = 0;
+
+	TWKB_STATE child_state;
+	memset(&child_state, 0, sizeof(TWKB_STATE));
+	child_state.header_buf = bytebuffer_create_with_size(ctx, 16);
+	child_state.geom_buf = bytebuffer_create_with_size(ctx, 64);
+	child_state.idlist = parent_state->idlist;
+
+	/* Read dimensionality from input */
+	has_z = rtgeom_has_z(ctx, geom);
+	has_m = rtgeom_has_m(ctx, geom);
+	ndims = rtgeom_ndims(ctx, geom);
+	is_empty = rtgeom_is_empty(ctx, geom);
+
+	/* Do we need extended precision? If we have a Z or M we do. */
+	optional_precision_byte = (has_z || has_m);
+
+	/* Both X and Y dimension use the same precision */
+	globals->factor[0] = pow(10, globals->prec_xy);
+	globals->factor[1] = globals->factor[0];
+
+	/* Z and M dimensions have their own precisions */
+	if ( has_z )
+		globals->factor[2] = pow(10, globals->prec_z);
+	if ( has_m )
+		globals->factor[2 + has_z] = pow(10, globals->prec_m);
+
+	/* Reset stats */
+	for ( i = 0; i < MAX_N_DIMS; i++ )
+	{
+		/* Reset bbox calculation */
+		child_state.bbox_max[i] = INT64_MIN;
+		child_state.bbox_min[i] = INT64_MAX;
+		/* Reset acumulated delta values to get absolute values on next point */
+		child_state.accum_rels[i] = 0;
+	}
+
+	/* RTTYPE/PRECISION BYTE */
+	if ( abs(globals->prec_xy) > 7 )
+		rterror(ctx, "%s: X/Z precision cannot be greater than 7 or less than -7", __func__);
+	
+	/* Read the TWKB type number from the geometry */
+	RTTYPE_PREC_SET_TYPE(type_prec, rtgeom_twkb_type(ctx, geom));
+	/* Zig-zag the precision value before encoding it since it is a signed value */
+	TYPE_PREC_SET_PREC(type_prec, zigzag8(ctx, globals->prec_xy));
+	/* Write the type and precision byte */
+	bytebuffer_append_byte(ctx, child_state.header_buf, type_prec);
+
+	/* METADATA BYTE */
+	/* Set first bit if we are going to store bboxes */
+	FIRST_BYTE_SET_BBOXES(flag, (globals->variant & TWKB_BBOX) && ! is_empty);
+	/* Set second bit if we are going to store resulting size */
+	FIRST_BYTE_SET_SIZES(flag, globals->variant & TWKB_SIZE);
+	/* There will be no ID-list (for now) */
+	FIRST_BYTE_SET_IDLIST(flag, parent_state->idlist && ! is_empty);
+	/* Are there higher dimensions */
+	FIRST_BYTE_SET_EXTENDED(flag, optional_precision_byte);
+	/* Empty? */
+	FIRST_BYTE_SET_EMPTY(flag, is_empty);
+	/* Write the header byte */
+	bytebuffer_append_byte(ctx, child_state.header_buf, flag);
+
+	/* EXTENDED PRECISION BYTE (OPTIONAL) */
+	/* If needed, write the extended dim byte */
+	if( optional_precision_byte )
+	{
+		uint8_t flag = 0;
+
+		if ( has_z && ( globals->prec_z > 7 || globals->prec_z < 0 ) )
+			rterror(ctx, "%s: Z precision cannot be negative or greater than 7", __func__);
+
+		if ( has_m && ( globals->prec_m > 7 || globals->prec_m < 0 ) )
+			rterror(ctx, "%s: M precision cannot be negative or greater than 7", __func__);
+
+		HIGHER_DIM_SET_HASZ(flag, has_z);
+		HIGHER_DIM_SET_HASM(flag, has_m);
+		HIGHER_DIM_SET_PRECZ(flag, globals->prec_z);
+		HIGHER_DIM_SET_PRECM(flag, globals->prec_m);
+		bytebuffer_append_byte(ctx, child_state.header_buf, flag);
+	}
+
+	/* It the geometry is empty, we're almost done */
+	if ( is_empty )
+	{
+		/* If this output is sized, write the size of */
+		/* all following content, which is zero because */
+		/* there is none */
+		if ( globals->variant & TWKB_SIZE )
+			bytebuffer_append_byte(ctx, child_state.header_buf, 0);
+
+		bytebuffer_append_bytebuffer(ctx, parent_state->geom_buf, child_state.header_buf);
+		bytebuffer_destroy(ctx, child_state.header_buf);
+		bytebuffer_destroy(ctx, child_state.geom_buf);
+		return 0;
+	}
+
+	/* Write the TWKB into the output buffer */
+	rtgeom_to_twkb_buf(ctx, geom, globals, &child_state);
+
+	/*If we have a header_buf, we know that this function is called inside a collection*/
+	/*and then we have to merge the bboxes of the included geometries*/
+	/*and put the result to the parent (the collection)*/
+	if( (globals->variant & TWKB_BBOX) && parent_state->header_buf )
+	{
+		RTDEBUG(4,"Merge bboxes");
+		for ( i = 0; i < ndims; i++ )
+		{
+			if(child_state.bbox_min[i]<parent_state->bbox_min[i])
+				parent_state->bbox_min[i] = child_state.bbox_min[i];
+			if(child_state.bbox_max[i]>parent_state->bbox_max[i])
+				parent_state->bbox_max[i] = child_state.bbox_max[i];
+		}
+	}
+	
+	/* Did we have a box? If so, how big? */
+	bbox_size = 0;
+	if( globals->variant & TWKB_BBOX )
+	{
+		RTDEBUG(4,"We want boxes and will calculate required size");
+		bbox_size = sizeof_bbox(ctx, &child_state, ndims);
+	}
+
+	/* Write the size if wanted */
+	if( globals->variant & TWKB_SIZE )
+	{
+		/* Here we have to add what we know will be written to header */
+		/* buffer after size value is written */
+		size_t size_to_register = bytebuffer_getlength(ctx, child_state.geom_buf);
+		size_to_register += bbox_size;
+		bytebuffer_append_uvarint(ctx, child_state.header_buf, size_to_register);
+	}
+
+	if( globals->variant & TWKB_BBOX )
+		write_bbox(ctx, &child_state, ndims);
+
+	bytebuffer_append_bytebuffer(ctx, parent_state->geom_buf,child_state.header_buf);
+	bytebuffer_append_bytebuffer(ctx, parent_state->geom_buf,child_state.geom_buf);
+
+	bytebuffer_destroy(ctx, child_state.header_buf);
+	bytebuffer_destroy(ctx, child_state.geom_buf);
+	return 0;
+}
+
+
+/**
+* Convert RTGEOM to a char* in TWKB format. Caller is responsible for freeing
+* the returned array.
+*/
+uint8_t*
+rtgeom_to_twkb_with_idlist(const RTCTX *ctx, const RTGEOM *geom, int64_t *idlist, uint8_t variant,
+               int8_t precision_xy, int8_t precision_z, int8_t precision_m,
+               size_t *twkb_size)
+{
+	RTDEBUGF(2, "Entered %s", __func__);
+	RTDEBUGF(2, "variant value %x", variant);
+
+	TWKB_GLOBALS tg;
+	TWKB_STATE ts;
+
+	uint8_t *twkb;
+
+	memset(&ts, 0, sizeof(TWKB_STATE));
+	memset(&tg, 0, sizeof(TWKB_GLOBALS));
+	
+	tg.variant = variant;
+	tg.prec_xy = precision_xy;
+	tg.prec_z = precision_z;
+	tg.prec_m = precision_m;
+
+	if ( idlist && ! rtgeom_is_collection(ctx, geom) )
+	{
+		rterror(ctx, "Only collections can support ID lists");
+		return NULL;
+	}
+
+	if ( ! geom )
+	{
+		RTDEBUG(4,"Cannot convert NULL into TWKB.");
+		rterror(ctx, "Cannot convert NULL into TWKB");
+		return NULL;
+	}
+	
+	ts.idlist = idlist;
+	ts.header_buf = NULL;
+	ts.geom_buf = bytebuffer_create(ctx);
+	rtgeom_write_to_buffer(ctx, geom, &tg, &ts);
+
+	if ( twkb_size )
+		*twkb_size = bytebuffer_getlength(ctx, ts.geom_buf);
+
+	twkb = ts.geom_buf->buf_start;
+	rtfree(ctx, ts.geom_buf);
+	return twkb;
+}
+
+
+uint8_t*
+rtgeom_to_twkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant,
+               int8_t precision_xy, int8_t precision_z, int8_t precision_m,
+               size_t *twkb_size)
+{
+	return rtgeom_to_twkb_with_idlist(ctx, geom, NULL, variant, precision_xy, precision_z, precision_m, twkb_size);
+}
+
+
diff --git a/src/rtout_twkb.h b/src/rtout_twkb.h
new file mode 100644
index 0000000..a731e95
--- /dev/null
+++ b/src/rtout_twkb.h
@@ -0,0 +1,107 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2013 Nicklas Avén
+ * Copyright 2013 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ * Copyright 2013 Nicklas Avén
+ *
+ * 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 "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include <limits.h>
+#include "bytebuffer.h"
+
+/* Maximum number of geometry dimmensions that internal arrays can hold */
+#define MAX_N_DIMS 4
+
+#define MAX_BBOX_SIZE 64
+#define MAX_SIZE_SIZE 8
+
+
+/**
+* Header true/false flags
+*/
+
+#define FIRST_BYTE_SET_BBOXES(flag, bool)   ((flag) = ((bool) ? (flag) | 0x01 : (flag) & (~0x01)))
+#define FIRST_BYTE_SET_SIZES(flag, bool)    ((flag) = ((bool) ? (flag) | 0x02 : (flag) & (~0x02)))
+#define FIRST_BYTE_SET_IDLIST(flag, bool)   ((flag) = ((bool) ? (flag) | 0x04 : (flag) & (~0x04)))
+#define FIRST_BYTE_SET_EXTENDED(flag, bool) ((flag) = ((bool) ? (flag) | 0x08 : (flag) & (~0x08)))
+#define FIRST_BYTE_SET_EMPTY(flag, bool)    ((flag) = ((bool) ? (flag) | 0x10 : (flag) & (~0x10)))
+
+
+/**
+* Macros for manipulating the 'type_precision' int. An int8_t used as follows:
+* Type 4 bits
+* Precision 4 bits
+*/
+
+#define RTTYPE_PREC_SET_TYPE(flag, type) ((flag) = ((flag) & 0xF0) | (((type) & 0x0F)))
+#define TYPE_PREC_SET_PREC(flag, prec) ((flag) = ((flag) & 0x0F) | (((prec) & 0x0F) << 4))
+
+#define HIGHER_DIM_SET_HASZ(flag, bool) ((flag) = ((bool) ? (flag) | 0x01 : (flag) & (~0x01)))
+#define HIGHER_DIM_SET_HASM(flag, bool) ((flag) = ((bool) ? (flag) | 0x02 : (flag) & (~0x02)))
+
+#define HIGHER_DIM_SET_PRECZ(flag, prec) ((flag) = ((flag) & 0xE3) | (((prec) & 0x07) << 2))
+#define HIGHER_DIM_SET_PRECM(flag, prec) ((flag) = ((flag) & 0x1F) | (((prec) & 0x07) << 5))
+
+typedef struct
+{
+	/* Options defined at start */
+	uint8_t variant;
+	int8_t prec_xy;
+	int8_t prec_z;
+	int8_t prec_m;
+	float factor[4]; /*What factor to multiply the coordiinates with to get the requested precision*/
+} TWKB_GLOBALS;
+
+typedef struct
+{
+	uint8_t variant;  /*options that change at runtime*/
+	bytebuffer_t *header_buf;
+	bytebuffer_t *geom_buf;
+	int hasz;
+	int hasm;
+	const int64_t *idlist;
+	int64_t bbox_min[MAX_N_DIMS];
+	int64_t bbox_max[MAX_N_DIMS];
+	int64_t accum_rels[MAX_N_DIMS]; /*Holds the acculmulated relative values*/
+} TWKB_STATE;
+
+static int rtgeom_to_twkb_buf(const RTCTX *ctx, const RTGEOM *geom, TWKB_GLOBALS *global_values, TWKB_STATE *ts);
+
+static int rtpoint_to_twkb_buf(const RTCTX *ctx, const RTPOINT *line, TWKB_GLOBALS *global_values, TWKB_STATE *ts);
+static int rtline_to_twkb_buf(const RTCTX *ctx, const RTLINE *line, TWKB_GLOBALS *global_values, TWKB_STATE *ts);
+static int rtpoly_to_twkb_buf(const RTCTX *ctx, const RTPOLY *poly, TWKB_GLOBALS *global_values, TWKB_STATE *ts);
+static int rtcollection_to_twkb_buf(const RTCTX *ctx, const RTCOLLECTION *col, TWKB_GLOBALS *global_values, TWKB_STATE *ts);
+static int rtgeom_write_to_buffer(const RTCTX *ctx, const RTGEOM *geom, TWKB_GLOBALS *global_values, TWKB_STATE *parent_state);
+
diff --git a/src/rtout_wkb.c b/src/rtout_wkb.c
new file mode 100644
index 0000000..7f6d74d
--- /dev/null
+++ b/src/rtout_wkb.c
@@ -0,0 +1,853 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include <math.h>
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+static uint8_t* rtgeom_to_wkb_buf(const RTCTX *ctx, const RTGEOM *geom, uint8_t *buf, uint8_t variant);
+static size_t rtgeom_to_wkb_size(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant);
+
+/*
+* Look-up table for hex writer
+*/
+static char *hexchr = "0123456789ABCDEF";
+
+char* hexbytes_from_bytes(const RTCTX *ctx, uint8_t *bytes, size_t size) 
+{
+	char *hex;
+	int i;
+	if ( ! bytes || ! size )
+	{
+		rterror(ctx, "hexbutes_from_bytes: invalid input");
+		return NULL;
+	}
+	hex = rtalloc(ctx, size * 2 + 1);
+	hex[2*size] = '\0';
+	for( i = 0; i < size; i++ )
+	{
+		/* Top four bits to 0-F */
+		hex[2*i] = hexchr[bytes[i] >> 4];
+		/* Bottom four bits to 0-F */
+		hex[2*i+1] = hexchr[bytes[i] & 0x0F];
+	}
+	return hex;
+}
+
+/*
+* Optional SRID
+*/
+static int rtgeom_wkb_needs_srid(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant)
+{
+	/* Sub-components of collections inherit their SRID from the parent.
+	   We force that behavior with the RTWKB_NO_SRID flag */
+	if ( variant & RTWKB_NO_SRID )
+		return RT_FALSE;
+		
+	/* We can only add an SRID if the geometry has one, and the 
+	   RTWKB form is extended */	
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_has_srid(ctx, geom) )
+		return RT_TRUE;
+		
+	/* Everything else doesn't get an SRID */
+	return RT_FALSE;
+}
+
+/*
+* GeometryType
+*/
+static uint32_t rtgeom_wkb_type(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant)
+{
+	uint32_t wkb_type = 0;
+
+	switch ( geom->type )
+	{
+	case RTPOINTTYPE:
+		wkb_type = RTWKB_POINT_TYPE;
+		break;
+	case RTLINETYPE:
+		wkb_type = RTWKB_LINESTRING_TYPE;
+		break;
+	case RTPOLYGONTYPE:
+		wkb_type = RTWKB_POLYGON_TYPE;
+		break;
+	case RTMULTIPOINTTYPE:
+		wkb_type = RTWKB_MULTIPOINT_TYPE;
+		break;
+	case RTMULTILINETYPE:
+		wkb_type = RTWKB_MULTILINESTRING_TYPE;
+		break;
+	case RTMULTIPOLYGONTYPE:
+		wkb_type = RTWKB_MULTIPOLYGON_TYPE;
+		break;
+	case RTCOLLECTIONTYPE:
+		wkb_type = RTWKB_GEOMETRYCOLLECTION_TYPE;
+		break;
+	case RTCIRCSTRINGTYPE:
+		wkb_type = RTWKB_CIRCULARSTRING_TYPE;
+		break;
+	case RTCOMPOUNDTYPE:
+		wkb_type = RTWKB_COMPOUNDCURVE_TYPE;
+		break;
+	case RTCURVEPOLYTYPE:
+		wkb_type = RTWKB_CURVEPOLYGON_TYPE;
+		break;
+	case RTMULTICURVETYPE:
+		wkb_type = RTWKB_MULTICURVE_TYPE;
+		break;
+	case RTMULTISURFACETYPE:
+		wkb_type = RTWKB_MULTISURFACE_TYPE;
+		break;
+	case RTPOLYHEDRALSURFACETYPE:
+		wkb_type = RTWKB_POLYHEDRALSURFACE_TYPE;
+		break;
+	case RTTINTYPE:
+		wkb_type = RTWKB_TIN_TYPE;
+		break;
+	case RTTRIANGLETYPE:
+		wkb_type = RTWKB_TRIANGLE_TYPE;
+		break;
+	default:
+		rterror(ctx, "Unsupported geometry type: %s [%d]",
+			rttype_name(ctx, geom->type), geom->type);
+	}
+
+	if ( variant & RTWKB_EXTENDED )
+	{
+		if ( RTFLAGS_GET_Z(geom->flags) )
+			wkb_type |= RTWKBZOFFSET;
+		if ( RTFLAGS_GET_M(geom->flags) )
+			wkb_type |= RTWKBMOFFSET;
+/*		if ( geom->srid != SRID_UNKNOWN && ! (variant & RTWKB_NO_SRID) ) */
+		if ( rtgeom_wkb_needs_srid(ctx, geom, variant) )
+			wkb_type |= RTWKBSRIDFLAG;
+	}
+	else if ( variant & RTWKB_ISO )
+	{
+		/* Z types are in the 1000 range */
+		if ( RTFLAGS_GET_Z(geom->flags) )
+			wkb_type += 1000;
+		/* M types are in the 2000 range */
+		if ( RTFLAGS_GET_M(geom->flags) )
+			wkb_type += 2000;
+		/* ZM types are in the 1000 + 2000 = 3000 range, see above */
+	}
+	return wkb_type;
+}
+
+/*
+* Endian
+*/
+static uint8_t* endian_to_wkb_buf(const RTCTX *ctx, uint8_t *buf, uint8_t variant)
+{
+	if ( variant & RTWKB_HEX )
+	{
+		buf[0] = '0';
+		buf[1] = ((variant & RTWKB_NDR) ? '1' : '0');
+		return buf + 2;
+	}
+	else
+	{
+		buf[0] = ((variant & RTWKB_NDR) ? 1 : 0);
+		return buf + 1;
+	}
+}
+
+/*
+* SwapBytes?
+*/
+static inline int wkb_swap_bytes(const RTCTX *ctx, uint8_t variant)
+{
+	/* If requested variant matches machine arch, we don't have to swap! */
+	if ( ((variant & RTWKB_NDR) && (getMachineEndian(ctx) == NDR)) ||
+	     ((! (variant & RTWKB_NDR)) && (getMachineEndian(ctx) == XDR)) )
+	{
+		return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+/*
+* Integer32
+*/
+static uint8_t* integer_to_wkb_buf(const RTCTX *ctx, const int ival, uint8_t *buf, uint8_t variant)
+{
+	char *iptr = (char*)(&ival);
+	int i = 0;
+
+	if ( sizeof(int) != RTWKB_INT_SIZE )
+	{
+		rterror(ctx, "Machine int size is not %d bytes!", RTWKB_INT_SIZE);
+	}
+	RTDEBUGF(4, "Writing value '%u'", ival);
+	if ( variant & RTWKB_HEX )
+	{
+		int swap = wkb_swap_bytes(ctx, variant);
+		/* Machine/request arch mismatch, so flip byte order */
+		for ( i = 0; i < RTWKB_INT_SIZE; i++ )
+		{
+			int j = (swap ? RTWKB_INT_SIZE - 1 - i : i);
+			uint8_t b = iptr[j];
+			/* Top four bits to 0-F */
+			buf[2*i] = hexchr[b >> 4];
+			/* Bottom four bits to 0-F */
+			buf[2*i+1] = hexchr[b & 0x0F];
+		}
+		return buf + (2 * RTWKB_INT_SIZE);
+	}
+	else
+	{
+		/* Machine/request arch mismatch, so flip byte order */
+		if ( wkb_swap_bytes(ctx, variant) )
+		{
+			for ( i = 0; i < RTWKB_INT_SIZE; i++ )
+			{
+				buf[i] = iptr[RTWKB_INT_SIZE - 1 - i];
+			}
+		}
+		/* If machine arch and requested arch match, don't flip byte order */
+		else
+		{
+			memcpy(buf, iptr, RTWKB_INT_SIZE);
+		}
+		return buf + RTWKB_INT_SIZE;
+	}
+}
+
+/*
+* Float64
+*/
+static uint8_t* double_to_wkb_buf(const RTCTX *ctx, const double d, uint8_t *buf, uint8_t variant)
+{
+	char *dptr = (char*)(&d);
+	int i = 0;
+
+	if ( sizeof(double) != RTWKB_DOUBLE_SIZE )
+	{
+		rterror(ctx, "Machine double size is not %d bytes!", RTWKB_DOUBLE_SIZE);
+	}
+
+	if ( variant & RTWKB_HEX )
+	{
+		int swap =  wkb_swap_bytes(ctx, variant);
+		/* Machine/request arch mismatch, so flip byte order */
+		for ( i = 0; i < RTWKB_DOUBLE_SIZE; i++ )
+		{
+			int j = (swap ? RTWKB_DOUBLE_SIZE - 1 - i : i);
+			uint8_t b = dptr[j];
+			/* Top four bits to 0-F */
+			buf[2*i] = hexchr[b >> 4];
+			/* Bottom four bits to 0-F */
+			buf[2*i+1] = hexchr[b & 0x0F];
+		}
+		return buf + (2 * RTWKB_DOUBLE_SIZE);
+	}
+	else
+	{
+		/* Machine/request arch mismatch, so flip byte order */
+		if ( wkb_swap_bytes(ctx, variant) )
+		{
+			for ( i = 0; i < RTWKB_DOUBLE_SIZE; i++ )
+			{
+				buf[i] = dptr[RTWKB_DOUBLE_SIZE - 1 - i];
+			}
+		}
+		/* If machine arch and requested arch match, don't flip byte order */
+		else
+		{
+			memcpy(buf, dptr, RTWKB_DOUBLE_SIZE);
+		}
+		return buf + RTWKB_DOUBLE_SIZE;
+	}
+}
+
+
+/*
+* Empty
+*/
+static size_t empty_to_wkb_size(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant)
+{
+	/* endian byte + type integer */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE;
+
+	/* optional srid integer */
+	if ( rtgeom_wkb_needs_srid(ctx, geom, variant) )
+		size += RTWKB_INT_SIZE;
+
+	/* Represent POINT EMPTY as POINT(NaN NaN) */
+	if ( geom->type == RTPOINTTYPE )
+	{
+		const RTPOINT *pt = (RTPOINT*)geom;
+		size += RTWKB_DOUBLE_SIZE * RTFLAGS_NDIMS(pt->point->flags);		
+	}
+	/* num-elements */
+	else
+	{
+		size += RTWKB_INT_SIZE;
+	}
+
+	return size;
+}
+
+static uint8_t* empty_to_wkb_buf(const RTCTX *ctx, const RTGEOM *geom, uint8_t *buf, uint8_t variant)
+{
+	uint32_t wkb_type = rtgeom_wkb_type(ctx, geom, variant);
+
+	/* Set the endian flag */
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, wkb_type, buf, variant);
+
+	/* Set the SRID if necessary */
+	if ( rtgeom_wkb_needs_srid(ctx, geom, variant) )
+		buf = integer_to_wkb_buf(ctx, geom->srid, buf, variant);
+
+	/* Represent POINT EMPTY as POINT(NaN NaN) */
+	if ( geom->type == RTPOINTTYPE )
+	{
+		const RTPOINT *pt = (RTPOINT*)geom;
+		static double nn = NAN;
+		int i;
+		for ( i = 0; i < RTFLAGS_NDIMS(pt->point->flags); i++ )
+		{
+			buf = double_to_wkb_buf(ctx, nn, buf, variant);
+		}
+	}
+	/* Everything else is flagged as empty using num-elements == 0 */
+	else
+	{
+		/* Set nrings/npoints/ngeoms to zero */
+		buf = integer_to_wkb_buf(ctx, 0, buf, variant);
+	}
+	
+	return buf;
+}
+
+/*
+* RTPOINTARRAY
+*/
+static size_t ptarray_to_wkb_size(const RTCTX *ctx, const RTPOINTARRAY *pa, uint8_t variant)
+{
+	int dims = 2;
+	size_t size = 0;
+
+	if ( variant & (RTWKB_ISO | RTWKB_EXTENDED) )
+		dims = RTFLAGS_NDIMS(pa->flags);
+
+	/* Include the npoints if it's not a POINT type) */
+	if ( ! ( variant & RTWKB_NO_NPOINTS ) )
+		size += RTWKB_INT_SIZE;
+
+	/* size of the double list */
+	size += pa->npoints * dims * RTWKB_DOUBLE_SIZE;
+
+	return size;
+}
+
+static uint8_t* ptarray_to_wkb_buf(const RTCTX *ctx, const RTPOINTARRAY *pa, uint8_t *buf, uint8_t variant)
+{
+	int dims = 2;
+	int pa_dims = RTFLAGS_NDIMS(pa->flags);
+	int i, j;
+	double *dbl_ptr;
+
+	/* SFSQL is artays 2-d. Extended and ISO use all available dimensions */
+	if ( (variant & RTWKB_ISO) || (variant & RTWKB_EXTENDED) )
+		dims = pa_dims;
+
+	/* Set the number of points (if it's not a POINT type) */
+	if ( ! ( variant & RTWKB_NO_NPOINTS ) )
+		buf = integer_to_wkb_buf(ctx, pa->npoints, buf, variant);
+
+	/* Bulk copy the coordinates when: dimensionality matches, output format */
+	/* is not hex, and output endian matches internal endian. */
+	if ( pa->npoints && (dims == pa_dims) && ! wkb_swap_bytes(ctx, variant) && ! (variant & RTWKB_HEX)  )
+	{
+		size_t size = pa->npoints * dims * RTWKB_DOUBLE_SIZE;
+		memcpy(buf, rt_getPoint_internal(ctx, pa, 0), size);
+		buf += size;
+	}
+	/* Copy coordinates one-by-one otherwise */
+	else 
+	{
+		for ( i = 0; i < pa->npoints; i++ )
+		{
+			RTDEBUGF(4, "Writing point #%d", i);
+			dbl_ptr = (double*)rt_getPoint_internal(ctx, pa, i);
+			for ( j = 0; j < dims; j++ )
+			{
+				RTDEBUGF(4, "Writing dimension #%d (buf = %p)", j, buf);
+				buf = double_to_wkb_buf(ctx, dbl_ptr[j], buf, variant);
+			}
+		}
+	}
+	RTDEBUGF(4, "Done (buf = %p)", buf);
+	return buf;
+}
+
+/*
+* POINT
+*/
+static size_t rtpoint_to_wkb_size(const RTCTX *ctx, const RTPOINT *pt, uint8_t variant)
+{
+	/* Endian flag + type number */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE;
+
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)pt) )
+		return empty_to_wkb_size(ctx, (RTGEOM*)pt, variant);
+
+	/* Extended RTWKB needs space for optional SRID integer */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)pt, variant) )
+		size += RTWKB_INT_SIZE;
+
+	/* Points */
+	size += ptarray_to_wkb_size(ctx, pt->point, variant | RTWKB_NO_NPOINTS);
+	return size;
+}
+
+static uint8_t* rtpoint_to_wkb_buf(const RTCTX *ctx, const RTPOINT *pt, uint8_t *buf, uint8_t variant)
+{
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)pt) )
+		return empty_to_wkb_buf(ctx, (RTGEOM*)pt, buf, variant);
+
+	/* Set the endian flag */
+	RTDEBUGF(4, "Entering function, buf = %p", buf);
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+	RTDEBUGF(4, "Endian set, buf = %p", buf);
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, rtgeom_wkb_type(ctx, (RTGEOM*)pt, variant), buf, variant);
+	RTDEBUGF(4, "Type set, buf = %p", buf);
+	/* Set the optional SRID for extended variant */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)pt, variant) )
+	{
+		buf = integer_to_wkb_buf(ctx, pt->srid, buf, variant);
+		RTDEBUGF(4, "SRID set, buf = %p", buf);
+	}
+	/* Set the coordinates */
+	buf = ptarray_to_wkb_buf(ctx, pt->point, buf, variant | RTWKB_NO_NPOINTS);
+	RTDEBUGF(4, "Pointarray set, buf = %p", buf);
+	return buf;
+}
+
+/*
+* LINESTRING, CIRCULARSTRING
+*/
+static size_t rtline_to_wkb_size(const RTCTX *ctx, const RTLINE *line, uint8_t variant)
+{
+	/* Endian flag + type number */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE;
+
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)line) )
+		return empty_to_wkb_size(ctx, (RTGEOM*)line, variant);
+
+	/* Extended RTWKB needs space for optional SRID integer */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)line, variant) )
+		size += RTWKB_INT_SIZE;
+
+	/* Size of point array */
+	size += ptarray_to_wkb_size(ctx, line->points, variant);
+	return size;
+}
+
+static uint8_t* rtline_to_wkb_buf(const RTCTX *ctx, const RTLINE *line, uint8_t *buf, uint8_t variant)
+{
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)line) )
+		return empty_to_wkb_buf(ctx, (RTGEOM*)line, buf, variant);
+
+	/* Set the endian flag */
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, rtgeom_wkb_type(ctx, (RTGEOM*)line, variant), buf, variant);
+	/* Set the optional SRID for extended variant */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)line, variant) )
+		buf = integer_to_wkb_buf(ctx, line->srid, buf, variant);
+	/* Set the coordinates */
+	buf = ptarray_to_wkb_buf(ctx, line->points, buf, variant);
+	return buf;
+}
+
+/*
+* TRIANGLE
+*/
+static size_t rttriangle_to_wkb_size(const RTCTX *ctx, const RTTRIANGLE *tri, uint8_t variant)
+{
+	/* endian flag + type number + number of rings */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE + RTWKB_INT_SIZE;
+
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)tri) )
+		return empty_to_wkb_size(ctx, (RTGEOM*)tri, variant);
+
+	/* Extended RTWKB needs space for optional SRID integer */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)tri, variant) )
+		size += RTWKB_INT_SIZE;
+
+	/* How big is this point array? */
+	size += ptarray_to_wkb_size(ctx, tri->points, variant);
+
+	return size;
+}
+
+static uint8_t* rttriangle_to_wkb_buf(const RTCTX *ctx, const RTTRIANGLE *tri, uint8_t *buf, uint8_t variant)
+{
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)tri) )
+		return empty_to_wkb_buf(ctx, (RTGEOM*)tri, buf, variant);
+
+	/* Set the endian flag */
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+	
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, rtgeom_wkb_type(ctx, (RTGEOM*)tri, variant), buf, variant);
+	
+	/* Set the optional SRID for extended variant */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)tri, variant) )
+		buf = integer_to_wkb_buf(ctx, tri->srid, buf, variant);
+
+	/* Set the number of rings (only one, it's a triangle, buddy) */
+	buf = integer_to_wkb_buf(ctx, 1, buf, variant);
+	
+	/* Write that ring */
+	buf = ptarray_to_wkb_buf(ctx, tri->points, buf, variant);
+
+	return buf;
+}
+
+/*
+* POLYGON
+*/
+static size_t rtpoly_to_wkb_size(const RTCTX *ctx, const RTPOLY *poly, uint8_t variant)
+{
+	/* endian flag + type number + number of rings */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE + RTWKB_INT_SIZE;
+	int i = 0;
+	
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)poly) )
+		return empty_to_wkb_size(ctx, (RTGEOM*)poly, variant);
+
+	/* Extended RTWKB needs space for optional SRID integer */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)poly, variant) )
+		size += RTWKB_INT_SIZE;
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		/* Size of ring point array */
+		size += ptarray_to_wkb_size(ctx, poly->rings[i], variant);
+	}
+
+	return size;
+}
+
+static uint8_t* rtpoly_to_wkb_buf(const RTCTX *ctx, const RTPOLY *poly, uint8_t *buf, uint8_t variant)
+{
+	int i;
+
+	/* Only process empty at this level in the EXTENDED case */
+	if ( (variant & RTWKB_EXTENDED) && rtgeom_is_empty(ctx, (RTGEOM*)poly) )
+		return empty_to_wkb_buf(ctx, (RTGEOM*)poly, buf, variant);
+
+	/* Set the endian flag */
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, rtgeom_wkb_type(ctx, (RTGEOM*)poly, variant), buf, variant);
+	/* Set the optional SRID for extended variant */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)poly, variant) )
+		buf = integer_to_wkb_buf(ctx, poly->srid, buf, variant);
+	/* Set the number of rings */
+	buf = integer_to_wkb_buf(ctx, poly->nrings, buf, variant);
+
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		buf = ptarray_to_wkb_buf(ctx, poly->rings[i], buf, variant);
+	}
+
+	return buf;
+}
+
+
+/*
+* MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION
+* MULTICURVE, COMPOUNDCURVE, MULTISURFACE, CURVEPOLYGON, TIN, 
+* POLYHEDRALSURFACE
+*/
+static size_t rtcollection_to_wkb_size(const RTCTX *ctx, const RTCOLLECTION *col, uint8_t variant)
+{
+	/* Endian flag + type number + number of subgeoms */
+	size_t size = RTWKB_BYTE_SIZE + RTWKB_INT_SIZE + RTWKB_INT_SIZE;
+	int i = 0;
+
+	/* Extended RTWKB needs space for optional SRID integer */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)col, variant) )
+		size += RTWKB_INT_SIZE;
+
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		/* size of subgeom */
+		size += rtgeom_to_wkb_size(ctx, (RTGEOM*)col->geoms[i], variant | RTWKB_NO_SRID);
+	}
+
+	return size;
+}
+
+static uint8_t* rtcollection_to_wkb_buf(const RTCTX *ctx, const RTCOLLECTION *col, uint8_t *buf, uint8_t variant)
+{
+	int i;
+
+	/* Set the endian flag */
+	buf = endian_to_wkb_buf(ctx, buf, variant);
+	/* Set the geometry type */
+	buf = integer_to_wkb_buf(ctx, rtgeom_wkb_type(ctx, (RTGEOM*)col, variant), buf, variant);
+	/* Set the optional SRID for extended variant */
+	if ( rtgeom_wkb_needs_srid(ctx, (RTGEOM*)col, variant) )
+		buf = integer_to_wkb_buf(ctx, col->srid, buf, variant);
+	/* Set the number of sub-geometries */
+	buf = integer_to_wkb_buf(ctx, col->ngeoms, buf, variant);
+
+	/* Write the sub-geometries. Sub-geometries do not get SRIDs, they
+	   inherit from their parents. */
+	for ( i = 0; i < col->ngeoms; i++ )
+	{
+		buf = rtgeom_to_wkb_buf(ctx, col->geoms[i], buf, variant | RTWKB_NO_SRID);
+	}
+
+	return buf;
+}
+
+/*
+* GEOMETRY
+*/
+static size_t rtgeom_to_wkb_size(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant)
+{
+	size_t size = 0;
+
+	if ( geom == NULL )
+		return 0;
+
+	/* Short circuit out empty geometries */
+	if ( (!(variant & RTWKB_EXTENDED)) && rtgeom_is_empty(ctx, geom) )
+	{
+		return empty_to_wkb_size(ctx, geom, variant);
+	}
+
+	switch ( geom->type )
+	{
+		case RTPOINTTYPE:
+			size += rtpoint_to_wkb_size(ctx, (RTPOINT*)geom, variant);
+			break;
+
+		/* LineString and CircularString both have points elements */
+		case RTCIRCSTRINGTYPE:
+		case RTLINETYPE:
+			size += rtline_to_wkb_size(ctx, (RTLINE*)geom, variant);
+			break;
+
+		/* Polygon has nrings and rings elements */
+		case RTPOLYGONTYPE:
+			size += rtpoly_to_wkb_size(ctx, (RTPOLY*)geom, variant);
+			break;
+
+		/* Triangle has one ring of three points */
+		case RTTRIANGLETYPE:
+			size += rttriangle_to_wkb_size(ctx, (RTTRIANGLE*)geom, variant);
+			break;
+
+		/* All these Collection types have ngeoms and geoms elements */
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTCURVEPOLYTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+		case RTCOLLECTIONTYPE:
+		case RTPOLYHEDRALSURFACETYPE:
+		case RTTINTYPE:
+			size += rtcollection_to_wkb_size(ctx, (RTCOLLECTION*)geom, variant);
+			break;
+
+		/* Unknown type! */
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]", rttype_name(ctx, geom->type), geom->type);
+	}
+
+	return size;
+}
+
+/* TODO handle the TRIANGLE type properly */
+
+static uint8_t* rtgeom_to_wkb_buf(const RTCTX *ctx, const RTGEOM *geom, uint8_t *buf, uint8_t variant)
+{
+
+	/* Do not simplify empties when outputting to canonical form */
+	if ( rtgeom_is_empty(ctx, geom) & ! (variant & RTWKB_EXTENDED) )
+		return empty_to_wkb_buf(ctx, geom, buf, variant);
+
+	switch ( geom->type )
+	{
+		case RTPOINTTYPE:
+			return rtpoint_to_wkb_buf(ctx, (RTPOINT*)geom, buf, variant);
+
+		/* LineString and CircularString both have 'points' elements */
+		case RTCIRCSTRINGTYPE:
+		case RTLINETYPE:
+			return rtline_to_wkb_buf(ctx, (RTLINE*)geom, buf, variant);
+
+		/* Polygon has 'nrings' and 'rings' elements */
+		case RTPOLYGONTYPE:
+			return rtpoly_to_wkb_buf(ctx, (RTPOLY*)geom, buf, variant);
+
+		/* Triangle has one ring of three points */
+		case RTTRIANGLETYPE:
+			return rttriangle_to_wkb_buf(ctx, (RTTRIANGLE*)geom, buf, variant);
+
+		/* All these Collection types have 'ngeoms' and 'geoms' elements */
+		case RTMULTIPOINTTYPE:
+		case RTMULTILINETYPE:
+		case RTMULTIPOLYGONTYPE:
+		case RTCOMPOUNDTYPE:
+		case RTCURVEPOLYTYPE:
+		case RTMULTICURVETYPE:
+		case RTMULTISURFACETYPE:
+		case RTCOLLECTIONTYPE:
+		case RTPOLYHEDRALSURFACETYPE:
+		case RTTINTYPE:
+			return rtcollection_to_wkb_buf(ctx, (RTCOLLECTION*)geom, buf, variant);
+
+		/* Unknown type! */
+		default:
+			rterror(ctx, "Unsupported geometry type: %s [%d]", rttype_name(ctx, geom->type), geom->type);
+	}
+	/* Return value to keep compiler happy. */
+	return 0;
+}
+
+/**
+* Convert RTGEOM to a char* in RTWKB format. Caller is responsible for freeing
+* the returned array.
+*
+* @param variant. Unsigned bitmask value. Accepts one of: RTWKB_ISO, RTWKB_EXTENDED, RTWKB_SFSQL.
+* Accepts any of: RTWKB_NDR, RTWKB_HEX. For example: Variant = ( RTWKB_ISO | RTWKB_NDR ) would
+* return the little-endian ISO form of RTWKB. For Example: Variant = ( RTWKB_EXTENDED | RTWKB_HEX )
+* would return the big-endian extended form of RTWKB, as hex-encoded ASCII (the "canonical form").
+* @param size_out If supplied, will return the size of the returned memory segment,
+* including the null terminator in the case of ASCII.
+*/
+uint8_t* rtgeom_to_wkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, size_t *size_out)
+{
+	size_t buf_size;
+	uint8_t *buf = NULL;
+	uint8_t *wkb_out = NULL;
+
+	/* Initialize output size */
+	if ( size_out ) *size_out = 0;
+
+	if ( geom == NULL )
+	{
+		RTDEBUG(4,"Cannot convert NULL into RTWKB.");
+		rterror(ctx, "Cannot convert NULL into RTWKB.");
+		return NULL;
+	}
+
+	/* Calculate the required size of the output buffer */
+	buf_size = rtgeom_to_wkb_size(ctx, geom, variant);
+	RTDEBUGF(4, "RTWKB output size: %d", buf_size);
+
+	if ( buf_size == 0 )
+	{
+		RTDEBUG(4,"Error calculating output RTWKB buffer size.");
+		rterror(ctx, "Error calculating output RTWKB buffer size.");
+		return NULL;
+	}
+
+	/* Hex string takes twice as much space as binary + a null character */
+	if ( variant & RTWKB_HEX )
+	{
+		buf_size = 2 * buf_size + 1;
+		RTDEBUGF(4, "Hex RTWKB output size: %d", buf_size);
+	}
+
+	/* If neither or both variants are specified, choose the native order */
+	if ( ! (variant & RTWKB_NDR || variant & RTWKB_XDR) ||
+	       (variant & RTWKB_NDR && variant & RTWKB_XDR) )
+	{
+		if ( getMachineEndian(ctx) == NDR ) 
+			variant = variant | RTWKB_NDR;
+		else
+			variant = variant | RTWKB_XDR;
+	}
+
+	/* Allocate the buffer */
+	buf = rtalloc(ctx, buf_size);
+
+	if ( buf == NULL )
+	{
+		RTDEBUGF(4,"Unable to allocate %d bytes for RTWKB output buffer.", buf_size);
+		rterror(ctx, "Unable to allocate %d bytes for RTWKB output buffer.", buf_size);
+		return NULL;
+	}
+
+	/* Retain a pointer to the front of the buffer for later */
+	wkb_out = buf;
+
+	/* Write the RTWKB into the output buffer */
+	buf = rtgeom_to_wkb_buf(ctx, geom, buf, variant);
+
+	/* Null the last byte if this is a hex output */
+	if ( variant & RTWKB_HEX )
+	{
+		*buf = '\0';
+		buf++;
+	}
+
+	RTDEBUGF(4,"buf (%p) - wkb_out (%p) = %d", buf, wkb_out, buf - wkb_out);
+
+	/* The buffer pointer should now land at the end of the allocated buffer space. Let's check. */
+	if ( buf_size != (buf - wkb_out) )
+	{
+		RTDEBUG(4,"Output RTWKB is not the same size as the allocated buffer.");
+		rterror(ctx, "Output RTWKB is not the same size as the allocated buffer.");
+		rtfree(ctx, wkb_out);
+		return NULL;
+	}
+
+	/* Report output size */
+	if ( size_out ) *size_out = buf_size;
+
+	return wkb_out;
+}
+
+char* rtgeom_to_hexwkb(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, size_t *size_out)
+{
+	return (char*)rtgeom_to_wkb(ctx, geom, variant | RTWKB_HEX, size_out);
+}
+
diff --git a/src/rtout_wkt.c b/src/rtout_wkt.c
new file mode 100644
index 0000000..6686f99
--- /dev/null
+++ b/src/rtout_wkt.c
@@ -0,0 +1,694 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include "stringbuffer.h"
+
+static void rtgeom_to_wkt_sb(const RTCTX *ctx, const RTGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant);
+
+
+/*
+* ISO format uses both Z and M qualifiers.
+* Extended format only uses an M qualifier for 3DM variants, where it is not
+* clear what the third dimension represents.
+* SFSQL format never has more than two dimensions, so no qualifiers.
+*/
+static void dimension_qualifiers_to_wkt_sb(const RTCTX *ctx, const RTGEOM *geom, stringbuffer_t *sb, uint8_t variant)
+{
+
+	/* Extended RTWKT: POINTM(0 0 0) */
+#if 0
+	if ( (variant & RTWKT_EXTENDED) && ! (variant & RTWKT_IS_CHILD) && RTFLAGS_GET_M(geom->flags) && (!RTFLAGS_GET_Z(geom->flags)) )
+#else
+	if ( (variant & RTWKT_EXTENDED) && RTFLAGS_GET_M(geom->flags) && (!RTFLAGS_GET_Z(geom->flags)) )
+#endif
+	{
+		stringbuffer_append(ctx, sb, "M"); /* "M" */
+		return;
+	}
+
+	/* ISO RTWKT: POINT ZM (0 0 0 0) */
+	if ( (variant & RTWKT_ISO) && (RTFLAGS_NDIMS(geom->flags) > 2) )
+	{
+		stringbuffer_append(ctx, sb, " ");
+		if ( RTFLAGS_GET_Z(geom->flags) )
+			stringbuffer_append(ctx, sb, "Z");
+		if ( RTFLAGS_GET_M(geom->flags) )
+			stringbuffer_append(ctx, sb, "M");
+		stringbuffer_append(ctx, sb, " ");
+	}
+}
+
+/*
+* Write an empty token out, padding with a space if
+* necessary. 
+*/
+static void empty_to_wkt_sb(const RTCTX *ctx, stringbuffer_t *sb)
+{
+	if ( ! strchr(" ,(", stringbuffer_lastchar(ctx, sb)) ) /* "EMPTY" */
+	{ 
+		stringbuffer_append(ctx, sb, " "); 
+	}
+	stringbuffer_append(ctx, sb, "EMPTY"); 
+}
+
+/*
+* Point array is a list of coordinates. Depending on output mode,
+* we may suppress some dimensions. ISO and Extended formats include
+* all dimensions. Standard OGC output only includes X/Y coordinates.
+*/
+static void ptarray_to_wkt_sb(const RTCTX *ctx, const RTPOINTARRAY *ptarray, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	/* OGC only includes X/Y */
+	int dimensions = 2;
+	int i, j;
+
+	/* ISO and extended formats include all dimensions */
+	if ( variant & ( RTWKT_ISO | RTWKT_EXTENDED ) )
+		dimensions = RTFLAGS_NDIMS(ptarray->flags);
+
+	/* Opening paren? */
+	if ( ! (variant & RTWKT_NO_PARENS) )
+		stringbuffer_append(ctx, sb, "(");
+
+	/* Digits and commas */
+	for (i = 0; i < ptarray->npoints; i++)
+	{
+		double *dbl_ptr = (double*)rt_getPoint_internal(ctx, ptarray, i);
+
+		/* Commas before ever coord but the first */
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+
+		for (j = 0; j < dimensions; j++)
+		{
+			/* Spaces before every ordinate but the first */
+			if ( j > 0 )
+				stringbuffer_append(ctx, sb, " ");
+			stringbuffer_aprintf(ctx, sb, "%.*g", precision, dbl_ptr[j]);
+		}
+	}
+
+	/* Closing paren? */
+	if ( ! (variant & RTWKT_NO_PARENS) )
+		stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* A four-dimensional point will have different outputs depending on variant.
+*   ISO: POINT ZM (0 0 0 0)
+*   Extended: POINT(0 0 0 0)
+*   OGC: POINT(0 0)
+* A three-dimensional m-point will have different outputs too.
+*   ISO: POINT M (0 0 0)
+*   Extended: POINTM(0 0 0)
+*   OGC: POINT(0 0)
+*/
+static void rtpoint_to_wkt_sb(const RTCTX *ctx, const RTPOINT *pt, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "POINT"); /* "POINT" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)pt, sb, variant);
+	}
+
+	if ( rtpoint_is_empty(ctx, pt) )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	ptarray_to_wkt_sb(ctx, pt->point, sb, precision, variant);
+}
+
+/*
+* LINESTRING(0 0 0, 1 1 1)
+*/
+static void rtline_to_wkt_sb(const RTCTX *ctx, const RTLINE *line, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "LINESTRING"); /* "LINESTRING" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)line, sb, variant);
+	}
+	if ( rtline_is_empty(ctx, line) )
+	{  
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	ptarray_to_wkt_sb(ctx, line->points, sb, precision, variant);
+}
+
+/*
+* POLYGON(0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)
+*/
+static void rtpoly_to_wkt_sb(const RTCTX *ctx, const RTPOLY *poly, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "POLYGON"); /* "POLYGON" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)poly, sb, variant);
+	}
+	if ( rtpoly_is_empty(ctx, poly) )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "(");
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		ptarray_to_wkt_sb(ctx, poly->rings[i], sb, precision, variant);
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* CIRCULARSTRING
+*/
+static void rtcircstring_to_wkt_sb(const RTCTX *ctx, const RTCIRCSTRING *circ, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "CIRCULARSTRING"); /* "CIRCULARSTRING" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)circ, sb, variant);
+	}
+	if ( rtcircstring_is_empty(ctx, circ) )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	ptarray_to_wkt_sb(ctx, circ->points, sb, precision, variant);
+}
+
+
+/*
+* Multi-points do not wrap their sub-members in parens, unlike other multi-geometries.
+*   MULTPOINT(0 0, 1 1) instead of MULTIPOINT((0 0),(1 1))
+*/
+static void rtmpoint_to_wkt_sb(const RTCTX *ctx, const RTMPOINT *mpoint, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "MULTIPOINT"); /* "MULTIPOINT" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)mpoint, sb, variant);
+	}
+	if ( mpoint->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < mpoint->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* We don't want type strings or parens on our subgeoms */
+		rtpoint_to_wkt_sb(ctx, mpoint->geoms[i], sb, precision, variant | RTWKT_NO_PARENS | RTWKT_NO_TYPE );
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* MULTILINESTRING
+*/
+static void rtmline_to_wkt_sb(const RTCTX *ctx, const RTMLINE *mline, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "MULTILINESTRING"); /* "MULTILINESTRING" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)mline, sb, variant);
+	}
+	if ( mline->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < mline->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* We don't want type strings on our subgeoms */
+		rtline_to_wkt_sb(ctx, mline->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* MULTIPOLYGON
+*/
+static void rtmpoly_to_wkt_sb(const RTCTX *ctx, const RTMPOLY *mpoly, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "MULTIPOLYGON"); /* "MULTIPOLYGON" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)mpoly, sb, variant);
+	}
+	if ( mpoly->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < mpoly->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* We don't want type strings on our subgeoms */
+		rtpoly_to_wkt_sb(ctx, mpoly->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* Compound curves provide type information for their curved sub-geometries
+* but not their linestring sub-geometries.
+*   COMPOUNDCURVE((0 0, 1 1), CURVESTRING(1 1, 2 2, 3 3))
+*/
+static void rtcompound_to_wkt_sb(const RTCTX *ctx, const RTCOMPOUND *comp, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "COMPOUNDCURVE"); /* "COMPOUNDCURVE" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)comp, sb, variant);
+	}
+	if ( comp->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < comp->ngeoms; i++ )
+	{
+		int type = comp->geoms[i]->type;
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* Linestring subgeoms don't get type identifiers */
+		if ( type == RTLINETYPE )
+		{
+			rtline_to_wkt_sb(ctx, (RTLINE*)comp->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+		}
+		/* But circstring subgeoms *do* get type identifiers */
+		else if ( type == RTCIRCSTRINGTYPE )
+		{
+			rtcircstring_to_wkt_sb(ctx, (RTCIRCSTRING*)comp->geoms[i], sb, precision, variant );
+		}
+		else
+		{
+			rterror(ctx, "rtcompound_to_wkt_sb: Unknown type received %d - %s", type, rttype_name(ctx, type));
+		}
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* Curve polygons provide type information for their curved rings
+* but not their linestring rings.
+*   CURVEPOLYGON((0 0, 1 1, 0 1, 0 0), CURVESTRING(0 0, 1 1, 0 1, 0.5 1, 0 0))
+*/
+static void rtcurvepoly_to_wkt_sb(const RTCTX *ctx, const RTCURVEPOLY *cpoly, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "CURVEPOLYGON"); /* "CURVEPOLYGON" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)cpoly, sb, variant);
+	}
+	if ( cpoly->nrings < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < cpoly->nrings; i++ )
+	{
+		int type = cpoly->rings[i]->type;
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		switch (type)
+		{
+		case RTLINETYPE:
+			/* Linestring subgeoms don't get type identifiers */
+			rtline_to_wkt_sb(ctx, (RTLINE*)cpoly->rings[i], sb, precision, variant | RTWKT_NO_TYPE );
+			break;
+		case RTCIRCSTRINGTYPE:
+			/* But circstring subgeoms *do* get type identifiers */
+			rtcircstring_to_wkt_sb(ctx, (RTCIRCSTRING*)cpoly->rings[i], sb, precision, variant );
+			break;
+		case RTCOMPOUNDTYPE:
+			/* And compoundcurve subgeoms *do* get type identifiers */
+			rtcompound_to_wkt_sb(ctx, (RTCOMPOUND*)cpoly->rings[i], sb, precision, variant );
+			break;
+		default:
+			rterror(ctx, "rtcurvepoly_to_wkt_sb: Unknown type received %d - %s", type, rttype_name(ctx, type));
+		}
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+
+/*
+* Multi-curves provide type information for their curved sub-geometries
+* but not their linear sub-geometries.
+*   MULTICURVE((0 0, 1 1), CURVESTRING(0 0, 1 1, 2 2))
+*/
+static void rtmcurve_to_wkt_sb(const RTCTX *ctx, const RTMCURVE *mcurv, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "MULTICURVE"); /* "MULTICURVE" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)mcurv, sb, variant);
+	}
+	if ( mcurv->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < mcurv->ngeoms; i++ )
+	{
+		int type = mcurv->geoms[i]->type;
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		switch (type)
+		{
+		case RTLINETYPE:
+			/* Linestring subgeoms don't get type identifiers */
+			rtline_to_wkt_sb(ctx, (RTLINE*)mcurv->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+			break;
+		case RTCIRCSTRINGTYPE:
+			/* But circstring subgeoms *do* get type identifiers */
+			rtcircstring_to_wkt_sb(ctx, (RTCIRCSTRING*)mcurv->geoms[i], sb, precision, variant );
+			break;
+		case RTCOMPOUNDTYPE:
+			/* And compoundcurve subgeoms *do* get type identifiers */
+			rtcompound_to_wkt_sb(ctx, (RTCOMPOUND*)mcurv->geoms[i], sb, precision, variant );
+			break;
+		default:
+			rterror(ctx, "rtmcurve_to_wkt_sb: Unknown type received %d - %s", type, rttype_name(ctx, type));
+		}
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+
+/*
+* Multi-surfaces provide type information for their curved sub-geometries
+* but not their linear sub-geometries.
+*   MULTISURFACE(((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0)))
+*/
+static void rtmsurface_to_wkt_sb(const RTCTX *ctx, const RTMSURFACE *msurf, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "MULTISURFACE"); /* "MULTISURFACE" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)msurf, sb, variant);
+	}
+	if ( msurf->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+	for ( i = 0; i < msurf->ngeoms; i++ )
+	{
+		int type = msurf->geoms[i]->type;
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		switch (type)
+		{
+		case RTPOLYGONTYPE:
+			/* Linestring subgeoms don't get type identifiers */
+			rtpoly_to_wkt_sb(ctx, (RTPOLY*)msurf->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+			break;
+		case RTCURVEPOLYTYPE:
+			/* But circstring subgeoms *do* get type identifiers */
+			rtcurvepoly_to_wkt_sb(ctx, (RTCURVEPOLY*)msurf->geoms[i], sb, precision, variant);
+			break;
+		default:
+			rterror(ctx, "rtmsurface_to_wkt_sb: Unknown type received %d - %s", type, rttype_name(ctx, type));
+		}
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* Geometry collections provide type information for all their curved sub-geometries
+* but not their linear sub-geometries.
+*   GEOMETRYCOLLECTION(POLYGON((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0)))
+*/
+static void rtcollection_to_wkt_sb(const RTCTX *ctx, const RTCOLLECTION *collection, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "GEOMETRYCOLLECTION"); /* "GEOMETRYCOLLECTION" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)collection, sb, variant);
+	}
+	if ( collection->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+	stringbuffer_append(ctx, sb, "(");
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are children */
+	for ( i = 0; i < collection->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		rtgeom_to_wkt_sb(ctx, (RTGEOM*)collection->geoms[i], sb, precision, variant );
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* TRIANGLE 
+*/
+static void rttriangle_to_wkt_sb(const RTCTX *ctx, const RTTRIANGLE *tri, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "TRIANGLE"); /* "TRIANGLE" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)tri, sb, variant);
+	}
+	if ( rttriangle_is_empty(ctx, tri) )
+	{  
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "("); /* Triangles have extraneous brackets */
+	ptarray_to_wkt_sb(ctx, tri->points, sb, precision, variant);
+	stringbuffer_append(ctx, sb, ")"); 
+}
+
+/*
+* TIN
+*/
+static void rttin_to_wkt_sb(const RTCTX *ctx, const RTTIN *tin, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "TIN"); /* "TIN" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)tin, sb, variant);
+	}
+	if ( tin->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	stringbuffer_append(ctx, sb, "(");
+	for ( i = 0; i < tin->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* We don't want type strings on our subgeoms */
+		rttriangle_to_wkt_sb(ctx, tin->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+	}
+	stringbuffer_append(ctx, sb, ")");
+}
+
+/*
+* POLYHEDRALSURFACE
+*/
+static void rtpsurface_to_wkt_sb(const RTCTX *ctx, const RTPSURFACE *psurf, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	int i = 0;
+
+	if ( ! (variant & RTWKT_NO_TYPE) )
+	{
+		stringbuffer_append(ctx, sb, "POLYHEDRALSURFACE"); /* "POLYHEDRALSURFACE" */
+		dimension_qualifiers_to_wkt_sb(ctx, (RTGEOM*)psurf, sb, variant);
+	}
+	if ( psurf->ngeoms < 1 )
+	{
+		empty_to_wkt_sb(ctx, sb);
+		return;
+	}
+
+	variant = variant | RTWKT_IS_CHILD; /* Inform the sub-geometries they are childre */
+
+	stringbuffer_append(ctx, sb, "(");
+	for ( i = 0; i < psurf->ngeoms; i++ )
+	{
+		if ( i > 0 )
+			stringbuffer_append(ctx, sb, ",");
+		/* We don't want type strings on our subgeoms */
+		rtpoly_to_wkt_sb(ctx, psurf->geoms[i], sb, precision, variant | RTWKT_NO_TYPE );
+	}
+	stringbuffer_append(ctx, sb, ")");	
+}
+
+
+/*
+* Generic GEOMETRY
+*/
+static void rtgeom_to_wkt_sb(const RTCTX *ctx, const RTGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant)
+{
+	RTDEBUGF(4, "rtgeom_to_wkt_sb: type %s, hasz %d, hasm %d",
+		rttype_name(ctx, geom->type), (geom->type),
+		RTFLAGS_GET_Z(geom->flags)?1:0, RTFLAGS_GET_M(geom->flags)?1:0);
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+		rtpoint_to_wkt_sb(ctx, (RTPOINT*)geom, sb, precision, variant);
+		break;
+	case RTLINETYPE:
+		rtline_to_wkt_sb(ctx, (RTLINE*)geom, sb, precision, variant);
+		break;
+	case RTPOLYGONTYPE:
+		rtpoly_to_wkt_sb(ctx, (RTPOLY*)geom, sb, precision, variant);
+		break;
+	case RTMULTIPOINTTYPE:
+		rtmpoint_to_wkt_sb(ctx, (RTMPOINT*)geom, sb, precision, variant);
+		break;
+	case RTMULTILINETYPE:
+		rtmline_to_wkt_sb(ctx, (RTMLINE*)geom, sb, precision, variant);
+		break;
+	case RTMULTIPOLYGONTYPE:
+		rtmpoly_to_wkt_sb(ctx, (RTMPOLY*)geom, sb, precision, variant);
+		break;
+	case RTCOLLECTIONTYPE:
+		rtcollection_to_wkt_sb(ctx, (RTCOLLECTION*)geom, sb, precision, variant);
+		break;
+	case RTCIRCSTRINGTYPE:
+		rtcircstring_to_wkt_sb(ctx, (RTCIRCSTRING*)geom, sb, precision, variant);
+		break;
+	case RTCOMPOUNDTYPE:
+		rtcompound_to_wkt_sb(ctx, (RTCOMPOUND*)geom, sb, precision, variant);
+		break;
+	case RTCURVEPOLYTYPE:
+		rtcurvepoly_to_wkt_sb(ctx, (RTCURVEPOLY*)geom, sb, precision, variant);
+		break;
+	case RTMULTICURVETYPE:
+		rtmcurve_to_wkt_sb(ctx, (RTMCURVE*)geom, sb, precision, variant);
+		break;
+	case RTMULTISURFACETYPE:
+		rtmsurface_to_wkt_sb(ctx, (RTMSURFACE*)geom, sb, precision, variant);
+		break;
+	case RTTRIANGLETYPE:
+		rttriangle_to_wkt_sb(ctx, (RTTRIANGLE*)geom, sb, precision, variant);
+		break;
+	case RTTINTYPE:
+		rttin_to_wkt_sb(ctx, (RTTIN*)geom, sb, precision, variant);
+		break;
+	case RTPOLYHEDRALSURFACETYPE:
+		rtpsurface_to_wkt_sb(ctx, (RTPSURFACE*)geom, sb, precision, variant);
+		break;
+	default:
+		rterror(ctx, "rtgeom_to_wkt_sb: Type %d - %s unsupported.",
+		        geom->type, rttype_name(ctx, geom->type));
+	}
+}
+
+/**
+* RTWKT emitter function. Allocates a new *char and fills it with the RTWKT
+* representation. If size_out is not NULL, it will be set to the size of the
+* allocated *char.
+*
+* @param variant Bitmasked value, accepts one of RTWKT_ISO, RTWKT_SFSQL, RTWKT_EXTENDED.
+* @param precision Number of significant digits in the output doubles.
+* @param size_out If supplied, will return the size of the returned string,
+* including the null terminator.
+*/
+char* rtgeom_to_wkt(const RTCTX *ctx, const RTGEOM *geom, uint8_t variant, int precision, size_t *size_out)
+{
+	stringbuffer_t *sb;
+	char *str = NULL;
+	if ( geom == NULL )
+		return NULL;
+	sb = stringbuffer_create(ctx);
+	/* Extended mode starts with an "SRID=" section for geoms that have one */
+	if ( (variant & RTWKT_EXTENDED) && rtgeom_has_srid(ctx, geom) )
+	{
+		stringbuffer_aprintf(ctx, sb, "SRID=%d;", geom->srid);
+	}
+	rtgeom_to_wkt_sb(ctx, geom, sb, precision, variant);
+	if ( stringbuffer_getstring(ctx, sb) == NULL )
+	{
+		rterror(ctx, "Uh oh");
+		return NULL;
+	}
+	str = stringbuffer_getstringcopy(ctx, sb);
+	if ( size_out )
+		*size_out = stringbuffer_getlength(ctx, sb) + 1;
+	stringbuffer_destroy(ctx, sb);
+	return str;
+}
+
diff --git a/src/rtout_x3d.c b/src/rtout_x3d.c
new file mode 100644
index 0000000..39bb33f
--- /dev/null
+++ b/src/rtout_x3d.c
@@ -0,0 +1,929 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2011-2015 Arrival 3D
+ *
+ **********************************************************************/
+
+
+/**
+* @file X3D output routines.
+*
+**********************************************************************/
+
+
+#include <string.h>
+#include "librtgeom_internal.h"
+
+/** defid is the id of the coordinate can be used to hold other elements DEF='abc' transform='' etc. **/
+static size_t asx3d3_point_size(const RTCTX *ctx, const RTPOINT *point, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_point(const RTCTX *ctx, const RTPOINT *point, char *srs, int precision, int opts, const char *defid);
+static size_t asx3d3_line_size(const RTCTX *ctx, const RTLINE *line, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_line(const RTCTX *ctx, const RTLINE *line, char *srs, int precision, int opts, const char *defid);
+static size_t asx3d3_poly_size(const RTCTX *ctx, const RTPOLY *poly, char *srs, int precision, int opts, const char *defid);
+static size_t asx3d3_triangle_size(const RTCTX *ctx, const RTTRIANGLE *triangle, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_triangle(const RTCTX *ctx, const RTTRIANGLE *triangle, char *srs, int precision, int opts, const char *defid);
+static size_t asx3d3_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precisioSn, int opts, const char *defid);
+static char * asx3d3_multi(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_psurface(const RTCTX *ctx, const RTPSURFACE *psur, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_tin(const RTCTX *ctx, const RTTIN *tin, char *srs, int precision, int opts, const char *defid);
+static size_t asx3d3_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid);
+static char * asx3d3_collection(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid);
+static size_t pointArray_toX3D3(const RTCTX *ctx, RTPOINTARRAY *pa, char *buf, int precision, int opts, int is_closed);
+
+static size_t pointArray_X3Dsize(const RTCTX *ctx, RTPOINTARRAY *pa, int precision);
+
+
+/*
+ * VERSION X3D 3.0.2 http://www.web3d.org/specifications/x3d-3.0.dtd
+ */
+
+
+/* takes a GEOMETRY and returns an X3D representation */
+extern char *
+rtgeom_to_x3d3(const RTCTX *ctx, const RTGEOM *geom, char *srs, int precision, int opts, const char *defid)
+{
+	int type = geom->type;
+
+	switch (type)
+	{
+	case RTPOINTTYPE:
+		return asx3d3_point(ctx, (RTPOINT*)geom, srs, precision, opts, defid);
+
+	case RTLINETYPE:
+		return asx3d3_line(ctx, (RTLINE*)geom, srs, precision, opts, defid);
+
+	case RTPOLYGONTYPE:
+	{
+		/** We might change this later, but putting a polygon in an indexed face set
+		* seems like the simplest way to go so treat just like a mulitpolygon
+		*/
+		RTCOLLECTION *tmp = (RTCOLLECTION*)rtgeom_as_multi(ctx, geom);
+		char *ret = asx3d3_multi(ctx, tmp, srs, precision, opts, defid);
+		rtcollection_free(ctx, tmp);
+		return ret;
+	}
+
+	case RTTRIANGLETYPE:
+		return asx3d3_triangle(ctx, (RTTRIANGLE*)geom, srs, precision, opts, defid);
+
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+		return asx3d3_multi(ctx, (RTCOLLECTION*)geom, srs, precision, opts, defid);
+
+	case RTPOLYHEDRALSURFACETYPE:
+		return asx3d3_psurface(ctx, (RTPSURFACE*)geom, srs, precision, opts, defid);
+
+	case RTTINTYPE:
+		return asx3d3_tin(ctx, (RTTIN*)geom, srs, precision, opts, defid);
+
+	case RTCOLLECTIONTYPE:
+		return asx3d3_collection(ctx, (RTCOLLECTION*)geom, srs, precision, opts, defid);
+
+	default:
+		rterror(ctx, "rtgeom_to_x3d3: '%s' geometry type not supported", rttype_name(ctx, type));
+		return NULL;
+	}
+}
+
+static size_t
+asx3d3_point_size(const RTCTX *ctx, const RTPOINT *point, char *srs, int precision, int opts, const char *defid)
+{
+	int size;
+	/* size_t defidlen = strlen(defid); */
+
+	size = pointArray_X3Dsize(ctx, point->point, precision);
+	/* size += ( sizeof("<point><pos>/") + (defidlen*2) ) * 2; */
+	/* if (srs)     size += strlen(srs) + sizeof(" srsName=.."); */
+	return size;
+}
+
+static size_t
+asx3d3_point_buf(const RTCTX *ctx, const RTPOINT *point, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr = output;
+	/* int dimension=2; */
+
+	/* if (RTFLAGS_GET_Z(point->flags)) dimension = 3; */
+	/*	if ( srs )
+		{
+			ptr += sprintf(ptr, "<%sPoint srsName=\"%s\">", defid, srs);
+		}
+		else*/
+	/* ptr += sprintf(ptr, "%s", defid); */
+
+	/* ptr += sprintf(ptr, "<%spos>", defid); */
+	ptr += pointArray_toX3D3(ctx, point->point, ptr, precision, opts, 0);
+	/* ptr += sprintf(ptr, "</%spos></%sPoint>", defid, defid); */
+
+	return (ptr-output);
+}
+
+static char *
+asx3d3_point(const RTCTX *ctx, const RTPOINT *point, char *srs, int precision, int opts, const char *defid)
+{
+	char *output;
+	int size;
+
+	size = asx3d3_point_size(ctx, point, srs, precision, opts, defid);
+	output = rtalloc(ctx, size);
+	asx3d3_point_buf(ctx, point, srs, output, precision, opts, defid);
+	return output;
+}
+
+
+static size_t
+asx3d3_line_size(const RTCTX *ctx, const RTLINE *line, char *srs, int precision, int opts, const char *defid)
+{
+	int size;
+	size_t defidlen = strlen(defid);
+
+	size = pointArray_X3Dsize(ctx, line->points, precision)*2;
+	
+	if ( X3D_USE_GEOCOORDS(opts) ) {
+			size += (
+	            sizeof("<LineSet vertexCount=''><GeoCoordinate geoSystem='\"GD\" \"WE\" \"longitude_first\"' point='' /></LineSet>")  + defidlen
+	        ) * 2;
+	}
+	else {
+		size += (
+		            sizeof("<LineSet vertexCount=''><Coordinate point='' /></LineSet>")  + defidlen
+		        ) * 2;
+	}
+
+	/* if (srs)     size += strlen(srs) + sizeof(" srsName=.."); */
+	return size;
+}
+
+static size_t
+asx3d3_line_buf(const RTCTX *ctx, const RTLINE *line, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr=output;
+	/* int dimension=2; */
+	RTPOINTARRAY *pa;
+
+
+	/* if (RTFLAGS_GET_Z(line->flags)) dimension = 3; */
+
+	pa = line->points;
+	ptr += sprintf(ptr, "<LineSet %s vertexCount='%d'>", defid, pa->npoints);
+
+	if ( X3D_USE_GEOCOORDS(opts) ) ptr += sprintf(ptr, "<GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='", ( (opts & RT_X3D_FLIP_XY) ? "latitude_first" : "longitude_first") );
+	else
+		ptr += sprintf(ptr, "<Coordinate point='");
+	ptr += pointArray_toX3D3(ctx, line->points, ptr, precision, opts, rtline_is_closed(ctx, (RTLINE *) line));
+
+	ptr += sprintf(ptr, "' />");
+
+	ptr += sprintf(ptr, "</LineSet>");
+	return (ptr-output);
+}
+
+static size_t
+asx3d3_line_coords(const RTCTX *ctx, const RTLINE *line, char *output, int precision, int opts)
+{
+	char *ptr=output;
+	/* ptr += sprintf(ptr, ""); */
+	ptr += pointArray_toX3D3(ctx, line->points, ptr, precision, opts, rtline_is_closed(ctx, line));
+	return (ptr-output);
+}
+
+/* Calculate the coordIndex property of the IndexedLineSet for the multilinestring */
+static size_t
+asx3d3_mline_coordindex(const RTCTX *ctx, const RTMLINE *mgeom, char *output)
+{
+	char *ptr=output;
+	RTLINE *geom;
+	int i, j, k, si;
+	RTPOINTARRAY *pa;
+	int np;
+
+	j = 0;
+	for (i=0; i < mgeom->ngeoms; i++)
+	{
+		geom = (RTLINE *) mgeom->geoms[i];
+		pa = geom->points;
+		np = pa->npoints;
+		si = j;  /* start index of first point of linestring */
+		for (k=0; k < np ; k++)
+		{
+			if (k)
+			{
+				ptr += sprintf(ptr, " ");
+			}
+			/** if the linestring is closed, we put the start point index
+			*   for the last vertex to denote use first point
+			*    and don't increment the index **/
+			if (!rtline_is_closed(ctx, geom) || k < (np -1) )
+			{
+				ptr += sprintf(ptr, "%d", j);
+				j += 1;
+			}
+			else
+			{
+				ptr += sprintf(ptr,"%d", si);
+			}
+		}
+		if (i < (mgeom->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " -1 "); /* separator for each linestring */
+		}
+	}
+	return (ptr-output);
+}
+
+/* Calculate the coordIndex property of the IndexedLineSet for a multipolygon
+    This is not ideal -- would be really nice to just share this function with psurf,
+    but I'm not smart enough to do that yet*/
+static size_t
+asx3d3_mpoly_coordindex(const RTCTX *ctx, const RTMPOLY *psur, char *output)
+{
+	char *ptr=output;
+	RTPOLY *patch;
+	int i, j, k, l;
+	int np;
+	j = 0;
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		patch = (RTPOLY *) psur->geoms[i];
+		for (l=0; l < patch->nrings; l++)
+		{
+			np = patch->rings[l]->npoints - 1;
+			for (k=0; k < np ; k++)
+			{
+				if (k)
+				{
+					ptr += sprintf(ptr, " ");
+				}
+				ptr += sprintf(ptr, "%d", (j + k));
+			}
+			j += k;
+			if (l < (patch->nrings - 1) )
+			{
+				/** @todo TODO: Decide the best way to render holes
+				*  Evidentally according to my X3D expert the X3D consortium doesn't really
+				*  support holes and it's an issue of argument among many that feel it should. He thinks CAD x3d extensions to spec might.
+				*  What he has done and others developing X3D exports to simulate a hole is to cut around it.
+				*  So if you have a donut, you would cut it into half and have 2 solid polygons.  Not really sure the best way to handle this.
+				*  For now will leave it as polygons stacked on top of each other -- which is what we are doing here and perhaps an option
+				*  to color differently.  It's not ideal but the alternative sounds complicated.
+				**/
+				ptr += sprintf(ptr, " -1 "); /* separator for each inner ring. Ideally we should probably triangulate and cut around as others do */
+			}
+		}
+		if (i < (psur->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " -1 "); /* separator for each subgeom */
+		}
+	}
+	return (ptr-output);
+}
+
+/** Return the linestring as an X3D LineSet */
+static char *
+asx3d3_line(const RTCTX *ctx, const RTLINE *line, char *srs, int precision, int opts, const char *defid)
+{
+	char *output;
+	int size;
+
+	size = sizeof("<LineSet><CoordIndex ='' /></LineSet>") + asx3d3_line_size(ctx, line, srs, precision, opts, defid);
+	output = rtalloc(ctx, size);
+	asx3d3_line_buf(ctx, line, srs, output, precision, opts, defid);
+	return output;
+}
+
+/** Compute the string space needed for the IndexedFaceSet representation of the polygon **/
+static size_t
+asx3d3_poly_size(const RTCTX *ctx, const RTPOLY *poly,  char *srs, int precision, int opts, const char *defid)
+{
+	size_t size;
+	size_t defidlen = strlen(defid);
+	int i;
+
+	size = ( sizeof("<IndexedFaceSet></IndexedFaceSet>") + (defidlen*3) ) * 2 + 6 * (poly->nrings - 1);
+
+	for (i=0; i<poly->nrings; i++)
+		size += pointArray_X3Dsize(ctx, poly->rings[i], precision);
+
+	return size;
+}
+
+/** Compute the X3D coordinates of the polygon **/
+static size_t
+asx3d3_poly_buf(const RTCTX *ctx, const RTPOLY *poly, char *srs, char *output, int precision, int opts, int is_patch, const char *defid)
+{
+	int i;
+	char *ptr=output;
+
+	ptr += pointArray_toX3D3(ctx, poly->rings[0], ptr, precision, opts, 1);
+	for (i=1; i<poly->nrings; i++)
+	{
+		ptr += sprintf(ptr, " "); /* inner ring points start */
+		ptr += pointArray_toX3D3(ctx, poly->rings[i], ptr, precision, opts,1);
+	}
+	return (ptr-output);
+}
+
+static size_t
+asx3d3_triangle_size(const RTCTX *ctx, const RTTRIANGLE *triangle, char *srs, int precision, int opts, const char *defid)
+{
+	size_t size;
+	size_t defidlen = strlen(defid);
+
+	/** 6 for the 3 sides and space to separate each side **/
+	size = sizeof("<IndexedTriangleSet index=''></IndexedTriangleSet>") + defidlen + 6;
+	size += pointArray_X3Dsize(ctx, triangle->points, precision);
+
+	return size;
+}
+
+static size_t
+asx3d3_triangle_buf(const RTCTX *ctx, const RTTRIANGLE *triangle, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr=output;
+	ptr += pointArray_toX3D3(ctx, triangle->points, ptr, precision, opts, 1);
+
+	return (ptr-output);
+}
+
+static char *
+asx3d3_triangle(const RTCTX *ctx, const RTTRIANGLE *triangle, char *srs, int precision, int opts, const char *defid)
+{
+	char *output;
+	int size;
+
+	size = asx3d3_triangle_size(ctx, triangle, srs, precision, opts, defid);
+	output = rtalloc(ctx, size);
+	asx3d3_triangle_buf(ctx, triangle, srs, output, precision, opts, defid);
+	return output;
+}
+
+
+/**
+ * Compute max size required for X3D version of this
+ * inspected geometry. Will recurse when needed.
+ * Don't call this with single-geoms inspected.
+ */
+static size_t
+asx3d3_multi_size(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid)
+{
+	int i;
+	size_t size;
+	size_t defidlen = strlen(defid);
+	RTGEOM *subgeom;
+
+	/* the longest possible multi version needs to hold DEF=defid and coordinate breakout */
+	if ( X3D_USE_GEOCOORDS(opts) )
+		size = sizeof("<PointSet><GeoCoordinate geoSystem='\"GD\" \"WE\" \"longitude_first\"' point='' /></PointSet>");
+	else
+		size = sizeof("<PointSet><Coordinate point='' /></PointSet>") + defidlen;
+	
+
+	/* if ( srs ) size += strlen(srs) + sizeof(" srsName=.."); */
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			/* size += ( sizeof("point=''") + defidlen ) * 2; */
+			size += asx3d3_point_size(ctx, (RTPOINT*)subgeom, 0, precision, opts, defid);
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			/* size += ( sizeof("<curveMember>/") + defidlen ) * 2; */
+			size += asx3d3_line_size(ctx, (RTLINE*)subgeom, 0, precision, opts, defid);
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			/* size += ( sizeof("<surfaceMember>/") + defidlen ) * 2; */
+			size += asx3d3_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, opts, defid);
+		}
+	}
+
+	return size;
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asx3d3_multi_buf(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr, *x3dtype;
+	int i;
+	int dimension=2;
+
+	if (RTFLAGS_GET_Z(col->flags)) dimension = 3;
+	RTGEOM *subgeom;
+	ptr = output;
+	x3dtype="";
+
+
+	switch (col->type)
+	{
+        case RTMULTIPOINTTYPE:
+            x3dtype = "PointSet";
+            if ( dimension == 2 ){ /** Use Polypoint2D instead **/
+                x3dtype = "Polypoint2D";   
+                ptr += sprintf(ptr, "<%s %s point='", x3dtype, defid);
+            }
+            else {
+                ptr += sprintf(ptr, "<%s %s>", x3dtype, defid);
+            }
+            break;
+        case RTMULTILINETYPE:
+            x3dtype = "IndexedLineSet";
+            ptr += sprintf(ptr, "<%s %s coordIndex='", x3dtype, defid);
+            ptr += asx3d3_mline_coordindex(ctx, (const RTMLINE *)col, ptr);
+            ptr += sprintf(ptr, "'>");
+            break;
+        case RTMULTIPOLYGONTYPE:
+            x3dtype = "IndexedFaceSet";
+            ptr += sprintf(ptr, "<%s %s coordIndex='", x3dtype, defid);
+            ptr += asx3d3_mpoly_coordindex(ctx, (const RTMPOLY *)col, ptr);
+            ptr += sprintf(ptr, "'>");
+            break;
+        default:
+            rterror(ctx, "asx3d3_multi_buf: '%s' geometry type not supported", rttype_name(ctx, col->type));
+            return 0;
+    }
+    if (dimension == 3){
+		if ( X3D_USE_GEOCOORDS(opts) ) 
+			ptr += sprintf(ptr, "<GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='", ((opts & RT_X3D_FLIP_XY) ? "latitude_first" : "longitude_first") );
+		else
+        	ptr += sprintf(ptr, "<Coordinate point='");
+    }
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		if (subgeom->type == RTPOINTTYPE)
+		{
+			ptr += asx3d3_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, opts, defid);
+			ptr += sprintf(ptr, " ");
+		}
+		else if (subgeom->type == RTLINETYPE)
+		{
+			ptr += asx3d3_line_coords(ctx, (RTLINE*)subgeom, ptr, precision, opts);
+			ptr += sprintf(ptr, " ");
+		}
+		else if (subgeom->type == RTPOLYGONTYPE)
+		{
+			ptr += asx3d3_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, opts, 0, defid);
+			ptr += sprintf(ptr, " ");
+		}
+	}
+
+	/* Close outmost tag */
+	if (dimension == 3){
+	    ptr += sprintf(ptr, "' /></%s>", x3dtype);
+	}
+	else { ptr += sprintf(ptr, "' />"); }    
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asx3d3_multi(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid)
+{
+	char *x3d;
+	size_t size;
+
+	size = asx3d3_multi_size(ctx, col, srs, precision, opts, defid);
+	x3d = rtalloc(ctx, size);
+	asx3d3_multi_buf(ctx, col, srs, x3d, precision, opts, defid);
+	return x3d;
+}
+
+
+static size_t
+asx3d3_psurface_size(const RTCTX *ctx, const RTPSURFACE *psur, char *srs, int precision, int opts, const char *defid)
+{
+	int i;
+	size_t size;
+	size_t defidlen = strlen(defid);
+
+	if ( X3D_USE_GEOCOORDS(opts) ) size = sizeof("<IndexedFaceSet coordIndex=''><GeoCoordinate geoSystem='\"GD\" \"WE\" \"longitude_first\"' point='' />") + defidlen;
+	else size = sizeof("<IndexedFaceSet coordIndex=''><Coordinate point='' />") + defidlen;
+	
+
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		size += asx3d3_poly_size(ctx, psur->geoms[i], 0, precision, opts, defid)*5; /** need to make space for coordIndex values too including -1 separating each poly**/
+	}
+
+	return size;
+}
+
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asx3d3_psurface_buf(const RTCTX *ctx, const RTPSURFACE *psur, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr;
+	int i;
+	int j;
+	int k;
+	int np;
+	RTPOLY *patch;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	ptr += sprintf(ptr, "<IndexedFaceSet %s coordIndex='",defid);
+
+	j = 0;
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		patch = (RTPOLY *) psur->geoms[i];
+		np = patch->rings[0]->npoints - 1;
+		for (k=0; k < np ; k++)
+		{
+			if (k)
+			{
+				ptr += sprintf(ptr, " ");
+			}
+			ptr += sprintf(ptr, "%d", (j + k));
+		}
+		if (i < (psur->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " -1 "); /* separator for each subgeom */
+		}
+		j += k;
+	}
+
+	if ( X3D_USE_GEOCOORDS(opts) ) 
+		ptr += sprintf(ptr, "'><GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='", ( (opts & RT_X3D_FLIP_XY) ? "latitude_first" : "longitude_first") );
+	else ptr += sprintf(ptr, "'><Coordinate point='");
+
+	for (i=0; i<psur->ngeoms; i++)
+	{
+		ptr += asx3d3_poly_buf(ctx, psur->geoms[i], 0, ptr, precision, opts, 1, defid);
+		if (i < (psur->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " "); /* only add a trailing space if its not the last polygon in the set */
+		}
+	}
+
+	/* Close outmost tag */
+	ptr += sprintf(ptr, "' /></IndexedFaceSet>");
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asx3d3_psurface(const RTCTX *ctx, const RTPSURFACE *psur, char *srs, int precision, int opts, const char *defid)
+{
+	char *x3d;
+	size_t size;
+
+	size = asx3d3_psurface_size(ctx, psur, srs, precision, opts, defid);
+	x3d = rtalloc(ctx, size);
+	asx3d3_psurface_buf(ctx, psur, srs, x3d, precision, opts, defid);
+	return x3d;
+}
+
+
+static size_t
+asx3d3_tin_size(const RTCTX *ctx, const RTTIN *tin, char *srs, int precision, int opts, const char *defid)
+{
+	int i;
+	size_t size;
+	size_t defidlen = strlen(defid);
+	/* int dimension=2; */
+
+	/** Need to make space for size of additional attributes,
+	** the coordIndex has a value for each edge for each triangle plus a space to separate so we need at least that much extra room ***/
+	size = sizeof("<IndexedTriangleSet coordIndex=''></IndexedTriangleSet>") + defidlen + tin->ngeoms*12;
+
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		size += (asx3d3_triangle_size(ctx, tin->geoms[i], 0, precision, opts, defid) * 20); /** 3 is to make space for coordIndex **/
+	}
+
+	return size;
+}
+
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static size_t
+asx3d3_tin_buf(const RTCTX *ctx, const RTTIN *tin, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr;
+	int i;
+	int k;
+	/* int dimension=2; */
+
+	ptr = output;
+
+	ptr += sprintf(ptr, "<IndexedTriangleSet %s index='",defid);
+	k = 0;
+	/** Fill in triangle index **/
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		ptr += sprintf(ptr, "%d %d %d", k, (k+1), (k+2));
+		if (i < (tin->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " ");
+		}
+		k += 3;
+	}
+
+	if ( X3D_USE_GEOCOORDS(opts) ) ptr += sprintf(ptr, "'><GeoCoordinate geoSystem='\"GD\" \"WE\" \"%s\"' point='", ( (opts & RT_X3D_FLIP_XY) ? "latitude_first" : "longitude_first") );
+	else ptr += sprintf(ptr, "'><Coordinate point='");
+	
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		ptr += asx3d3_triangle_buf(ctx, tin->geoms[i], 0, ptr, precision,
+		                           opts, defid);
+		if (i < (tin->ngeoms - 1) )
+		{
+			ptr += sprintf(ptr, " ");
+		}
+	}
+
+	/* Close outmost tag */
+
+	ptr += sprintf(ptr, "'/></IndexedTriangleSet>");
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asx3d3_tin(const RTCTX *ctx, const RTTIN *tin, char *srs, int precision, int opts, const char *defid)
+{
+	char *x3d;
+	size_t size;
+
+	size = asx3d3_tin_size(ctx, tin, srs, precision, opts, defid);
+	x3d = rtalloc(ctx, size);
+	asx3d3_tin_buf(ctx, tin, srs, x3d, precision, opts, defid);
+	return x3d;
+}
+
+static size_t
+asx3d3_collection_size(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid)
+{
+	int i;
+	size_t size;
+	size_t defidlen = strlen(defid);
+	RTGEOM *subgeom;
+
+	/* size = sizeof("<MultiGeometry></MultiGeometry>") + defidlen*2; */
+	size = defidlen*2;
+
+	/** if ( srs )
+		size += strlen(srs) + sizeof(" srsName=.."); **/
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		size += ( sizeof("<Shape />") + defidlen ) * 2; /** for collections we need to wrap each in a shape tag to make valid **/
+		if ( subgeom->type == RTPOINTTYPE )
+		{
+			size += asx3d3_point_size(ctx, (RTPOINT*)subgeom, 0, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTLINETYPE )
+		{
+			size += asx3d3_line_size(ctx, (RTLINE*)subgeom, 0, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTPOLYGONTYPE )
+		{
+			size += asx3d3_poly_size(ctx, (RTPOLY*)subgeom, 0, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTTINTYPE )
+		{
+			size += asx3d3_tin_size(ctx, (RTTIN*)subgeom, 0, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTPOLYHEDRALSURFACETYPE )
+		{
+			size += asx3d3_psurface_size(ctx, (RTPSURFACE*)subgeom, 0, precision, opts, defid);
+		}
+		else if ( rtgeom_is_collection(ctx, subgeom) )
+		{
+			size += asx3d3_multi_size(ctx, (RTCOLLECTION*)subgeom, 0, precision, opts, defid);
+		}
+		else
+			rterror(ctx, "asx3d3_collection_size: unknown geometry type");
+	}
+
+	return size;
+}
+
+static size_t
+asx3d3_collection_buf(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, char *output, int precision, int opts, const char *defid)
+{
+	char *ptr;
+	int i;
+	RTGEOM *subgeom;
+
+	ptr = output;
+
+	/* Open outmost tag */
+	/** @TODO: decide if we need outtermost tags, this one was just a copy from gml so is wrong **/
+#ifdef PGIS_X3D_OUTERMOST_TAGS
+	if ( srs )
+	{
+		ptr += sprintf(ptr, "<%sMultiGeometry srsName=\"%s\">", defid, srs);
+	}
+	else
+	{
+		ptr += sprintf(ptr, "<%sMultiGeometry>", defid);
+	}
+#endif
+
+	for (i=0; i<col->ngeoms; i++)
+	{
+		subgeom = col->geoms[i];
+		ptr += sprintf(ptr, "<Shape%s>", defid);
+		if ( subgeom->type == RTPOINTTYPE )
+		{
+			ptr += asx3d3_point_buf(ctx, (RTPOINT*)subgeom, 0, ptr, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTLINETYPE )
+		{
+			ptr += asx3d3_line_buf(ctx, (RTLINE*)subgeom, 0, ptr, precision, opts, defid);
+		}
+		else if ( subgeom->type == RTPOLYGONTYPE )
+		{
+			ptr += asx3d3_poly_buf(ctx, (RTPOLY*)subgeom, 0, ptr, precision, opts, 0, defid);
+		}
+		else if ( subgeom->type == RTTINTYPE )
+		{
+			ptr += asx3d3_tin_buf(ctx, (RTTIN*)subgeom, srs, ptr, precision, opts,  defid);
+			
+		}
+		else if ( subgeom->type == RTPOLYHEDRALSURFACETYPE )
+		{
+			ptr += asx3d3_psurface_buf(ctx, (RTPSURFACE*)subgeom, srs, ptr, precision, opts,  defid);
+			
+		}
+		else if ( rtgeom_is_collection(ctx, subgeom) )
+		{
+			if ( subgeom->type == RTCOLLECTIONTYPE )
+				ptr += asx3d3_collection_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, opts, defid);
+			else
+				ptr += asx3d3_multi_buf(ctx, (RTCOLLECTION*)subgeom, 0, ptr, precision, opts, defid);
+		}
+		else
+			rterror(ctx, "asx3d3_collection_buf: unknown geometry type");
+
+		ptr += printf(ptr, "</Shape>");
+	}
+
+	/* Close outmost tag */
+#ifdef PGIS_X3D_OUTERMOST_TAGS
+	ptr += sprintf(ptr, "</%sMultiGeometry>", defid);
+#endif
+
+	return (ptr-output);
+}
+
+/*
+ * Don't call this with single-geoms inspected!
+ */
+static char *
+asx3d3_collection(const RTCTX *ctx, const RTCOLLECTION *col, char *srs, int precision, int opts, const char *defid)
+{
+	char *x3d;
+	size_t size;
+
+	size = asx3d3_collection_size(ctx, col, srs, precision, opts, defid);
+	x3d = rtalloc(ctx, size);
+	asx3d3_collection_buf(ctx, col, srs, x3d, precision, opts, defid);
+	return x3d;
+}
+
+
+/** In X3D3, coordinates are separated by a space separator
+ */
+static size_t
+pointArray_toX3D3(const RTCTX *ctx, RTPOINTARRAY *pa, char *output, int precision, int opts, int is_closed)
+{
+	int i;
+	char *ptr;
+	char x[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char y[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+	char z[OUT_MAX_DIGS_DOUBLE+OUT_MAX_DOUBLE_PRECISION+1];
+
+	ptr = output;
+
+	if ( ! RTFLAGS_GET_Z(pa->flags) )
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			/** Only output the point if it is not the last point of a closed object or it is a non-closed type **/
+			if ( !is_closed || i < (pa->npoints - 1) )
+			{
+				RTPOINT2D pt;
+				rt_getPoint2d_p(ctx, pa, i, &pt);
+
+				if (fabs(pt.x) < OUT_MAX_DOUBLE)
+					sprintf(x, "%.*f", precision, pt.x);
+				else
+					sprintf(x, "%g", pt.x);
+				trim_trailing_zeros(ctx, x);
+
+				if (fabs(pt.y) < OUT_MAX_DOUBLE)
+					sprintf(y, "%.*f", precision, pt.y);
+				else
+					sprintf(y, "%g", pt.y);
+				trim_trailing_zeros(ctx, y);
+
+				if ( i )
+					ptr += sprintf(ptr, " ");
+					
+				if ( ( opts & RT_X3D_FLIP_XY) )
+					ptr += sprintf(ptr, "%s %s", y, x);
+				else
+					ptr += sprintf(ptr, "%s %s", x, y);
+			}
+		}
+	}
+	else
+	{
+		for (i=0; i<pa->npoints; i++)
+		{
+			/** Only output the point if it is not the last point of a closed object or it is a non-closed type **/
+			if ( !is_closed || i < (pa->npoints - 1) )
+			{
+				RTPOINT4D pt;
+				rt_getPoint4d_p(ctx, pa, i, &pt);
+
+				if (fabs(pt.x) < OUT_MAX_DOUBLE)
+					sprintf(x, "%.*f", precision, pt.x);
+				else
+					sprintf(x, "%g", pt.x);
+				trim_trailing_zeros(ctx, x);
+
+				if (fabs(pt.y) < OUT_MAX_DOUBLE)
+					sprintf(y, "%.*f", precision, pt.y);
+				else
+					sprintf(y, "%g", pt.y);
+				trim_trailing_zeros(ctx, y);
+
+				if (fabs(pt.z) < OUT_MAX_DOUBLE)
+					sprintf(z, "%.*f", precision, pt.z);
+				else
+					sprintf(z, "%g", pt.z);
+				trim_trailing_zeros(ctx, z);
+
+				if ( i )
+					ptr += sprintf(ptr, " ");
+
+				if ( ( opts & RT_X3D_FLIP_XY) )
+					ptr += sprintf(ptr, "%s %s %s", y, x, z);
+				else
+					ptr += sprintf(ptr, "%s %s %s", x, y, z);
+			}
+		}
+	}
+
+	return ptr-output;
+}
+
+
+
+/**
+ * Returns maximum size of rendered pointarray in bytes.
+ */
+static size_t
+pointArray_X3Dsize(const RTCTX *ctx, RTPOINTARRAY *pa, int precision)
+{
+	if (RTFLAGS_NDIMS(pa->flags) == 2)
+		return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(" "))
+		       * 2 * pa->npoints;
+
+	return (OUT_MAX_DIGS_DOUBLE + precision + sizeof(" ")) * 3 * pa->npoints;
+}
diff --git a/src/rtpoint.c b/src/rtpoint.c
new file mode 100644
index 0000000..25c1f32
--- /dev/null
+++ b/src/rtpoint.c
@@ -0,0 +1,288 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "rttopo_config.h"
+/*#define RTGEOM_DEBUG_LEVEL 4*/
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+/*
+ * Convenience functions to hide the RTPOINTARRAY
+ * TODO: obsolete this
+ */
+int
+rtpoint_getPoint2d_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT2D *out)
+{
+	return rt_getPoint2d_p(ctx, point->point, 0, out);
+}
+
+/* convenience functions to hide the RTPOINTARRAY */
+int
+rtpoint_getPoint3dz_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT3DZ *out)
+{
+	return rt_getPoint3dz_p(ctx, point->point,0,out);
+}
+int
+rtpoint_getPoint3dm_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT3DM *out)
+{
+	return rt_getPoint3dm_p(ctx, point->point,0,out);
+}
+int
+rtpoint_getPoint4d_p(const RTCTX *ctx, const RTPOINT *point, RTPOINT4D *out)
+{
+	return rt_getPoint4d_p(ctx, point->point,0,out);
+}
+
+double
+rtpoint_get_x(const RTCTX *ctx, const RTPOINT *point)
+{
+	RTPOINT4D pt;
+	if ( rtpoint_is_empty(ctx, point) )
+		rterror(ctx, "rtpoint_get_x called with empty geometry");
+	rt_getPoint4d_p(ctx, point->point, 0, &pt);
+	return pt.x;
+}
+
+double
+rtpoint_get_y(const RTCTX *ctx, const RTPOINT *point)
+{
+	RTPOINT4D pt;
+	if ( rtpoint_is_empty(ctx, point) )
+		rterror(ctx, "rtpoint_get_y called with empty geometry");
+	rt_getPoint4d_p(ctx, point->point, 0, &pt);
+	return pt.y;
+}
+
+double
+rtpoint_get_z(const RTCTX *ctx, const RTPOINT *point)
+{
+	RTPOINT4D pt;
+	if ( rtpoint_is_empty(ctx, point) )
+		rterror(ctx, "rtpoint_get_z called with empty geometry");
+	if ( ! RTFLAGS_GET_Z(point->flags) )
+		rterror(ctx, "rtpoint_get_z called without z dimension");
+	rt_getPoint4d_p(ctx, point->point, 0, &pt);
+	return pt.z;
+}
+
+double
+rtpoint_get_m(const RTCTX *ctx, const RTPOINT *point)
+{
+	RTPOINT4D pt;
+	if ( rtpoint_is_empty(ctx, point) )
+		rterror(ctx, "rtpoint_get_m called with empty geometry");
+	if ( ! RTFLAGS_GET_M(point->flags) )
+		rterror(ctx, "rtpoint_get_m called without m dimension");
+	rt_getPoint4d_p(ctx, point->point, 0, &pt);
+	return pt.m;
+}
+
+/*
+ * Construct a new point.  point will not be copied
+ * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
+ */
+RTPOINT *
+rtpoint_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *point)
+{
+	RTPOINT *result;
+	uint8_t flags = 0;
+
+	if (point == NULL)
+		return NULL; /* error */
+
+	result = rtalloc(ctx, sizeof(RTPOINT));
+	result->type = RTPOINTTYPE;
+	RTFLAGS_SET_Z(flags, RTFLAGS_GET_Z(point->flags));
+	RTFLAGS_SET_M(flags, RTFLAGS_GET_M(point->flags));
+	RTFLAGS_SET_BBOX(flags, bbox?1:0);
+	result->flags = flags;
+	result->srid = srid;
+	result->point = point;
+	result->bbox = bbox;
+
+	return result;
+}
+
+RTPOINT *
+rtpoint_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTPOINT *result = rtalloc(ctx, sizeof(RTPOINT));
+	result->type = RTPOINTTYPE;
+	result->flags = gflags(ctx, hasz, hasm, 0);
+	result->srid = srid;
+	result->point = ptarray_construct(ctx, hasz, hasm, 0);
+	result->bbox = NULL;
+	return result;
+}
+
+RTPOINT *
+rtpoint_make2d(const RTCTX *ctx, int srid, double x, double y)
+{
+	RTPOINT4D p = {x, y, 0.0, 0.0};
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, 0, 0, 1);
+
+	ptarray_append_point(ctx, pa, &p, RT_TRUE);
+	return rtpoint_construct(ctx, srid, NULL, pa);
+}
+
+RTPOINT *
+rtpoint_make3dz(const RTCTX *ctx, int srid, double x, double y, double z)
+{
+	RTPOINT4D p = {x, y, z, 0.0};
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, 1, 0, 1);
+
+	ptarray_append_point(ctx, pa, &p, RT_TRUE);
+
+	return rtpoint_construct(ctx, srid, NULL, pa);
+}
+
+RTPOINT *
+rtpoint_make3dm(const RTCTX *ctx, int srid, double x, double y, double m)
+{
+	RTPOINT4D p = {x, y, 0.0, m};
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, 0, 1, 1);
+
+	ptarray_append_point(ctx, pa, &p, RT_TRUE);
+
+	return rtpoint_construct(ctx, srid, NULL, pa);
+}
+
+RTPOINT *
+rtpoint_make4d(const RTCTX *ctx, int srid, double x, double y, double z, double m)
+{
+	RTPOINT4D p = {x, y, z, m};
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, 1, 1, 1);
+
+	ptarray_append_point(ctx, pa, &p, RT_TRUE);
+
+	return rtpoint_construct(ctx, srid, NULL, pa);
+}
+
+RTPOINT *
+rtpoint_make(const RTCTX *ctx, int srid, int hasz, int hasm, const RTPOINT4D *p)
+{
+	RTPOINTARRAY *pa = ptarray_construct_empty(ctx, hasz, hasm, 1);
+	ptarray_append_point(ctx, pa, p, RT_TRUE);
+	return rtpoint_construct(ctx, srid, NULL, pa);
+}
+
+void rtpoint_free(const RTCTX *ctx, RTPOINT *pt)
+{
+	if ( ! pt ) return;
+	
+	if ( pt->bbox )
+		rtfree(ctx, pt->bbox);
+	if ( pt->point )
+		ptarray_free(ctx, pt->point);
+	rtfree(ctx, pt);
+}
+
+void printRTPOINT(const RTCTX *ctx, RTPOINT *point)
+{
+	rtnotice(ctx, "RTPOINT {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(point->flags));
+	rtnotice(ctx, "    BBOX = %i", RTFLAGS_GET_BBOX(point->flags) ? 1 : 0 );
+	rtnotice(ctx, "    SRID = %i", (int)point->srid);
+	printPA(ctx, point->point);
+	rtnotice(ctx, "}");
+}
+
+/* @brief Clone RTPOINT object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTPOINT *
+rtpoint_clone(const RTCTX *ctx, const RTPOINT *g)
+{
+	RTPOINT *ret = rtalloc(ctx, sizeof(RTPOINT));
+
+	RTDEBUG(2, "rtpoint_clone called");
+
+	memcpy(ret, g, sizeof(RTPOINT));
+
+	ret->point = ptarray_clone(ctx, g->point);
+
+	if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	return ret;
+}
+
+
+
+void
+rtpoint_release(const RTCTX *ctx, RTPOINT *rtpoint)
+{
+	rtgeom_release(ctx, rtpoint_as_rtgeom(ctx, rtpoint));
+}
+
+
+/* check coordinate equality  */
+char
+rtpoint_same(const RTCTX *ctx, const RTPOINT *p1, const RTPOINT *p2)
+{
+	return ptarray_same(ctx, p1->point, p2->point);
+}
+
+
+RTPOINT*
+rtpoint_force_dims(const RTCTX *ctx, const RTPOINT *point, int hasz, int hasm)
+{
+	RTPOINTARRAY *pdims = NULL;
+	RTPOINT *pointout;
+	
+	/* Return 2D empty */
+	if( rtpoint_is_empty(ctx, point) )
+	{
+		pointout = rtpoint_construct_empty(ctx, point->srid, hasz, hasm);
+	}
+	else
+	{
+		/* Artays we duplicate the ptarray and return */
+		pdims = ptarray_force_dims(ctx, point->point, hasz, hasm);
+		pointout = rtpoint_construct(ctx, point->srid, NULL, pdims);
+	}
+	pointout->type = point->type;
+	return pointout;
+}
+
+int rtpoint_is_empty(const RTCTX *ctx, const RTPOINT *point)
+{
+	if ( ! point->point || point->point->npoints < 1 )
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+
+RTPOINT *
+rtpoint_grid(const RTCTX *ctx, const RTPOINT *point, const gridspec *grid)
+{
+	RTPOINTARRAY *opa = ptarray_grid(ctx, point->point, grid);
+	return rtpoint_construct(ctx, point->srid, NULL, opa);
+}
+
diff --git a/src/rtpoly.c b/src/rtpoly.c
new file mode 100644
index 0000000..11a39aa
--- /dev/null
+++ b/src/rtpoly.c
@@ -0,0 +1,596 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2012 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+/* basic RTPOLY manipulation */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+#define CHECK_POLY_RINGS_ZM 1
+
+/* construct a new RTPOLY.  arrays (points/points per ring) will NOT be copied
+ * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
+ */
+RTPOLY*
+rtpoly_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, uint32_t nrings, RTPOINTARRAY **points)
+{
+	RTPOLY *result;
+	int hasz, hasm;
+#ifdef CHECK_POLY_RINGS_ZM
+	char zm;
+	uint32_t i;
+#endif
+
+	if ( nrings < 1 ) rterror(ctx, "rtpoly_construct: need at least 1 ring");
+
+	hasz = RTFLAGS_GET_Z(points[0]->flags);
+	hasm = RTFLAGS_GET_M(points[0]->flags);
+
+#ifdef CHECK_POLY_RINGS_ZM
+	zm = RTFLAGS_GET_ZM(points[0]->flags);
+	for (i=1; i<nrings; i++)
+	{
+		if ( zm != RTFLAGS_GET_ZM(points[i]->flags) )
+			rterror(ctx, "rtpoly_construct: mixed dimensioned rings");
+	}
+#endif
+
+	result = (RTPOLY*) rtalloc(ctx, sizeof(RTPOLY));
+	result->type = RTPOLYGONTYPE;
+	result->flags = gflags(ctx, hasz, hasm, 0);
+	RTFLAGS_SET_BBOX(result->flags, bbox?1:0);
+	result->srid = srid;
+	result->nrings = nrings;
+	result->maxrings = nrings;
+	result->rings = points;
+	result->bbox = bbox;
+
+	return result;
+}
+
+RTPOLY*
+rtpoly_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTPOLY *result = rtalloc(ctx, sizeof(RTPOLY));
+	result->type = RTPOLYGONTYPE;
+	result->flags = gflags(ctx, hasz,hasm,0);
+	result->srid = srid;
+	result->nrings = 0;
+	result->maxrings = 1; /* Allocate room for ring, just in case. */
+	result->rings = rtalloc(ctx, result->maxrings * sizeof(RTPOINTARRAY*));
+	result->bbox = NULL;
+	return result;
+}
+
+void rtpoly_free(const RTCTX *ctx, RTPOLY  *poly)
+{
+	int t;
+
+	if( ! poly ) return;
+
+	if ( poly->bbox )
+		rtfree(ctx, poly->bbox);
+
+	for (t=0; t<poly->nrings; t++)
+	{
+		if ( poly->rings[t] )
+			ptarray_free(ctx, poly->rings[t]);
+	}
+
+	if ( poly->rings )
+		rtfree(ctx, poly->rings);
+
+	rtfree(ctx, poly);
+}
+
+void printRTPOLY(const RTCTX *ctx, RTPOLY *poly)
+{
+	int t;
+	rtnotice(ctx, "RTPOLY {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(poly->flags));
+	rtnotice(ctx, "    SRID = %i", (int)poly->srid);
+	rtnotice(ctx, "    nrings = %i", (int)poly->nrings);
+	for (t=0; t<poly->nrings; t++)
+	{
+		rtnotice(ctx, "    RING # %i :",t);
+		printPA(ctx, poly->rings[t]);
+	}
+	rtnotice(ctx, "}");
+}
+
+/* @brief Clone RTLINE object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTPOLY *
+rtpoly_clone(const RTCTX *ctx, const RTPOLY *g)
+{
+	int i;
+	RTPOLY *ret = rtalloc(ctx, sizeof(RTPOLY));
+	memcpy(ret, g, sizeof(RTPOLY));
+	ret->rings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*g->nrings);
+	for ( i = 0; i < g->nrings; i++ ) {
+		ret->rings[i] = ptarray_clone(ctx, g->rings[i]);
+	}
+	if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	return ret;
+}
+
+/* Deep clone RTPOLY object. RTPOINTARRAY are copied, as is ring array */
+RTPOLY *
+rtpoly_clone_deep(const RTCTX *ctx, const RTPOLY *g)
+{
+	int i;
+	RTPOLY *ret = rtalloc(ctx, sizeof(RTPOLY));
+	memcpy(ret, g, sizeof(RTPOLY));
+	if ( g->bbox ) ret->bbox = gbox_copy(ctx, g->bbox);
+	ret->rings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*g->nrings);
+	for ( i = 0; i < ret->nrings; i++ )
+	{
+		ret->rings[i] = ptarray_clone_deep(ctx, g->rings[i]);
+	}
+	RTFLAGS_SET_READONLY(ret->flags,0);
+	return ret;
+}
+
+/**
+* Add a ring to a polygon. Point array will be referenced, not copied.
+*/
+int
+rtpoly_add_ring(const RTCTX *ctx, RTPOLY *poly, RTPOINTARRAY *pa) 
+{
+	if( ! poly || ! pa ) 
+		return RT_FAILURE;
+		
+	/* We have used up our storage, add some more. */
+	if( poly->nrings >= poly->maxrings ) 
+	{
+		int new_maxrings = 2 * (poly->nrings + 1);
+		poly->rings = rtrealloc(ctx, poly->rings, new_maxrings * sizeof(RTPOINTARRAY*));
+		poly->maxrings = new_maxrings;
+	}
+	
+	/* Add the new ring entry. */
+	poly->rings[poly->nrings] = pa;
+	poly->nrings++;
+	
+	return RT_SUCCESS;
+}
+
+void
+rtpoly_force_clockwise(const RTCTX *ctx, RTPOLY *poly)
+{
+	int i;
+
+	/* No-op empties */
+	if ( rtpoly_is_empty(ctx, poly) )
+		return;
+
+	/* External ring */
+	if ( ptarray_isccw(ctx, poly->rings[0]) )
+		ptarray_reverse(ctx, poly->rings[0]);
+
+	/* Internal rings */
+	for (i=1; i<poly->nrings; i++)
+		if ( ! ptarray_isccw(ctx, poly->rings[i]) )
+			ptarray_reverse(ctx, poly->rings[i]);
+
+}
+
+void
+rtpoly_release(const RTCTX *ctx, RTPOLY *rtpoly)
+{
+	rtgeom_release(ctx, rtpoly_as_rtgeom(ctx, rtpoly));
+}
+
+void
+rtpoly_reverse(const RTCTX *ctx, RTPOLY *poly)
+{
+	int i;
+	if ( rtpoly_is_empty(ctx, poly) ) return;
+	for (i=0; i<poly->nrings; i++)
+		ptarray_reverse(ctx, poly->rings[i]);
+}
+
+RTPOLY *
+rtpoly_segmentize2d(const RTCTX *ctx, RTPOLY *poly, double dist)
+{
+	RTPOINTARRAY **newrings;
+	uint32_t i;
+
+	newrings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*poly->nrings);
+	for (i=0; i<poly->nrings; i++)
+	{
+		newrings[i] = ptarray_segmentize2d(ctx, poly->rings[i], dist);
+		if ( ! newrings[i] ) {
+			while (i--) ptarray_free(ctx, newrings[i]);
+			rtfree(ctx, newrings);
+			return NULL;
+		}
+	}
+	return rtpoly_construct(ctx, poly->srid, NULL,
+	                        poly->nrings, newrings);
+}
+
+/*
+ * check coordinate equality
+ * ring and coordinate order is considered
+ */
+char
+rtpoly_same(const RTCTX *ctx, const RTPOLY *p1, const RTPOLY *p2)
+{
+	uint32_t i;
+
+	if ( p1->nrings != p2->nrings ) return 0;
+	for (i=0; i<p1->nrings; i++)
+	{
+		if ( ! ptarray_same(ctx, p1->rings[i], p2->rings[i]) )
+			return 0;
+	}
+	return 1;
+}
+
+/*
+ * Construct a polygon from a RTLINE being
+ * the shell and an array of RTLINE (possibly NULL) being holes.
+ * Pointarrays from intput geoms are cloned.
+ * SRID must be the same for each input line.
+ * Input lines must have at least 4 points, and be closed.
+ */
+RTPOLY *
+rtpoly_from_rtlines(const RTCTX *ctx, const RTLINE *shell,
+                    uint32_t nholes, const RTLINE **holes)
+{
+	uint32_t nrings;
+	RTPOINTARRAY **rings = rtalloc(ctx, (nholes+1)*sizeof(RTPOINTARRAY *));
+	int srid = shell->srid;
+	RTPOLY *ret;
+
+	if ( shell->points->npoints < 4 )
+		rterror(ctx, "rtpoly_from_rtlines: shell must have at least 4 points");
+	if ( ! ptarray_is_closed_2d(ctx, shell->points) )
+		rterror(ctx, "rtpoly_from_rtlines: shell must be closed");
+	rings[0] = ptarray_clone_deep(ctx, shell->points);
+
+	for (nrings=1; nrings<=nholes; nrings++)
+	{
+		const RTLINE *hole = holes[nrings-1];
+
+		if ( hole->srid != srid )
+			rterror(ctx, "rtpoly_from_rtlines: mixed SRIDs in input lines");
+
+		if ( hole->points->npoints < 4 )
+			rterror(ctx, "rtpoly_from_rtlines: holes must have at least 4 points");
+		if ( ! ptarray_is_closed_2d(ctx, hole->points) )
+			rterror(ctx, "rtpoly_from_rtlines: holes must be closed");
+
+		rings[nrings] = ptarray_clone_deep(ctx, hole->points);
+	}
+
+	ret = rtpoly_construct(ctx, srid, NULL, nrings, rings);
+	return ret;
+}
+
+RTGEOM*
+rtpoly_remove_repeated_points(const RTCTX *ctx, const RTPOLY *poly, double tolerance)
+{
+	uint32_t i;
+	RTPOINTARRAY **newrings;
+
+	newrings = rtalloc(ctx, sizeof(RTPOINTARRAY *)*poly->nrings);
+	for (i=0; i<poly->nrings; i++)
+	{
+		newrings[i] = ptarray_remove_repeated_points_minpoints(ctx, poly->rings[i], tolerance, 4);
+	}
+
+	return (RTGEOM*)rtpoly_construct(ctx, poly->srid,
+	                                 poly->bbox ? gbox_copy(ctx, poly->bbox) : NULL,
+	                                 poly->nrings, newrings);
+
+}
+
+
+RTPOLY*
+rtpoly_force_dims(const RTCTX *ctx, const RTPOLY *poly, int hasz, int hasm)
+{
+	RTPOLY *polyout;
+	
+	/* Return 2D empty */
+	if( rtpoly_is_empty(ctx, poly) )
+	{
+		polyout = rtpoly_construct_empty(ctx, poly->srid, hasz, hasm);
+	}
+	else
+	{
+		RTPOINTARRAY **rings = NULL;
+		int i;
+		rings = rtalloc(ctx, sizeof(RTPOINTARRAY*) * poly->nrings);
+		for( i = 0; i < poly->nrings; i++ )
+		{
+			rings[i] = ptarray_force_dims(ctx, poly->rings[i], hasz, hasm);
+		}
+		polyout = rtpoly_construct(ctx, poly->srid, NULL, poly->nrings, rings);
+	}
+	polyout->type = poly->type;
+	return polyout;
+}
+
+int rtpoly_is_empty(const RTCTX *ctx, const RTPOLY *poly)
+{
+	if ( (poly->nrings < 1) || (!poly->rings) || (!poly->rings[0]) || (poly->rings[0]->npoints < 1) )
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+int rtpoly_count_vertices(const RTCTX *ctx, RTPOLY *poly)
+{
+	int i = 0;
+	int v = 0; /* vertices */
+	assert(poly);
+	for ( i = 0; i < poly->nrings; i ++ )
+	{
+		v += poly->rings[i]->npoints;
+	}
+	return v;
+}
+
+RTPOLY* rtpoly_simplify(const RTCTX *ctx, const RTPOLY *ipoly, double dist, int preserve_collapsed)
+{
+	int i;
+	RTPOLY *opoly = rtpoly_construct_empty(ctx, ipoly->srid, RTFLAGS_GET_Z(ipoly->flags), RTFLAGS_GET_M(ipoly->flags));
+
+	RTDEBUGF(2, "%s: simplifying polygon with %d rings", __func__, ipoly->nrings);
+
+	if ( rtpoly_is_empty(ctx, ipoly) )
+	{
+		rtpoly_free(ctx, opoly);
+		return NULL;
+	}
+
+	for ( i = 0; i < ipoly->nrings; i++ )
+	{
+		RTPOINTARRAY *opts;
+		int minvertices = 0;
+
+		/* We'll still let holes collapse, but if we're preserving */
+		/* and this is a shell, we ensure it is kept */
+		if ( preserve_collapsed && i == 0 )
+			minvertices = 4; 
+			
+		opts = ptarray_simplify(ctx, ipoly->rings[i], dist, minvertices);
+
+		RTDEBUGF(3, "ring%d simplified from %d to %d points", i, ipoly->rings[i]->npoints, opts->npoints);
+
+		/* Less points than are needed to form a closed ring, we can't use this */
+		if ( opts->npoints < 4 )
+		{
+			RTDEBUGF(3, "ring%d skipped (% pts)", i, opts->npoints);
+			ptarray_free(ctx, opts);
+			if ( i ) continue;
+			else break; /* Don't scan holes if shell is collapsed */
+		}
+
+		/* Add ring to simplified polygon */
+		if( rtpoly_add_ring(ctx, opoly, opts) == RT_FAILURE )
+		{
+			rtpoly_free(ctx, opoly);
+			return NULL;
+		}
+	}
+
+	RTDEBUGF(3, "simplified polygon with %d rings", ipoly->nrings);
+	opoly->type = ipoly->type;
+
+	if( rtpoly_is_empty(ctx, opoly) )
+	{
+		rtpoly_free(ctx, opoly);
+		return NULL;
+	}
+
+	return opoly;
+}
+
+/**
+* Find the area of the outer ring - sum (area of inner rings).
+*/
+double
+rtpoly_area(const RTCTX *ctx, const RTPOLY *poly)
+{
+	double poly_area = 0.0;
+	int i;
+	
+	if ( ! poly ) 
+		rterror(ctx, "rtpoly_area called with null polygon pointer!");
+
+	for ( i=0; i < poly->nrings; i++ )
+	{
+		RTPOINTARRAY *ring = poly->rings[i];
+		double ringarea = 0.0;
+
+		/* Empty or messed-up ring. */
+		if ( ring->npoints < 3 ) 
+			continue; 
+		
+		ringarea = fabs(ptarray_signed_area(ctx, ring));
+		if ( i == 0 ) /* Outer ring, positive area! */
+			poly_area += ringarea; 
+		else /* Inner ring, negative area! */
+			poly_area -= ringarea; 
+	}
+
+	return poly_area;
+}
+
+
+/**
+ * Compute the sum of polygon rings length.
+ * Could use a more numerically stable calculator...
+ */
+double
+rtpoly_perimeter(const RTCTX *ctx, const RTPOLY *poly)
+{
+	double result=0.0;
+	int i;
+
+	RTDEBUGF(2, "in rtgeom_polygon_perimeter (%d rings)", poly->nrings);
+
+	for (i=0; i<poly->nrings; i++)
+		result += ptarray_length(ctx, poly->rings[i]);
+
+	return result;
+}
+
+/**
+ * Compute the sum of polygon rings length (forcing 2d computation).
+ * Could use a more numerically stable calculator...
+ */
+double
+rtpoly_perimeter_2d(const RTCTX *ctx, const RTPOLY *poly)
+{
+	double result=0.0;
+	int i;
+
+	RTDEBUGF(2, "in rtgeom_polygon_perimeter (%d rings)", poly->nrings);
+
+	for (i=0; i<poly->nrings; i++)
+		result += ptarray_length_2d(ctx, poly->rings[i]);
+
+	return result;
+}
+
+int
+rtpoly_is_closed(const RTCTX *ctx, const RTPOLY *poly)
+{
+	int i = 0;
+	
+	if ( poly->nrings == 0 ) 
+		return RT_TRUE;
+		
+	for ( i = 0; i < poly->nrings; i++ )
+	{
+		if (RTFLAGS_GET_Z(poly->flags))
+		{
+			if ( ! ptarray_is_closed_3d(ctx, poly->rings[i]) )
+				return RT_FALSE;
+		}
+		else
+		{	
+			if ( ! ptarray_is_closed_2d(ctx, poly->rings[i]) )
+				return RT_FALSE;
+		}
+	}
+	
+	return RT_TRUE;
+}
+
+int 
+rtpoly_startpoint(const RTCTX *ctx, const RTPOLY* poly, RTPOINT4D* pt)
+{
+	if ( poly->nrings < 1 )
+		return RT_FAILURE;
+	return ptarray_startpoint(ctx, poly->rings[0], pt);
+}
+
+int
+rtpoly_contains_point(const RTCTX *ctx, const RTPOLY *poly, const RTPOINT2D *pt)
+{
+	int i;
+	
+	if ( rtpoly_is_empty(ctx, poly) )
+		return RT_FALSE;
+	
+	if ( ptarray_contains_point(ctx, poly->rings[0], pt) == RT_OUTSIDE )
+		return RT_FALSE;
+	
+	for ( i = 1; i < poly->nrings; i++ )
+	{
+		if ( ptarray_contains_point(ctx, poly->rings[i], pt) == RT_INSIDE )
+			return RT_FALSE;
+	}
+	return RT_TRUE;
+}
+
+
+
+RTPOLY* rtpoly_grid(const RTCTX *ctx, const RTPOLY *poly, const gridspec *grid)
+{
+	RTPOLY *opoly;
+	int ri;
+
+#if 0
+	/*
+	 * TODO: control this assertion
+	 * it is assumed that, since the grid size will be a pixel,
+	 * a visible ring should show at least a white pixel inside,
+	 * thus, for a square, that would be grid_xsize*grid_ysize
+	 */
+	double minvisiblearea = grid->xsize * grid->ysize;
+#endif
+
+	RTDEBUGF(3, "rtpoly_grid: applying grid to polygon with %d rings", poly->nrings);
+
+	opoly = rtpoly_construct_empty(ctx, poly->srid, rtgeom_has_z(ctx, (RTGEOM*)poly), rtgeom_has_m(ctx, (RTGEOM*)poly));
+
+	for (ri=0; ri<poly->nrings; ri++)
+	{
+		RTPOINTARRAY *ring = poly->rings[ri];
+		RTPOINTARRAY *newring;
+
+		newring = ptarray_grid(ctx, ring, grid);
+
+		/* Skip ring if not composed by at least 4 pts (3 segments) */
+		if ( newring->npoints < 4 )
+		{
+			ptarray_free(ctx, newring);
+
+			RTDEBUGF(3, "grid_polygon3d: ring%d skipped ( <4 pts )", ri);
+
+			if ( ri ) continue;
+			else break; /* this is the external ring, no need to work on holes */
+		}
+		
+		if ( ! rtpoly_add_ring(ctx, opoly, newring) )
+		{
+			rterror(ctx, "rtpoly_grid, memory error");
+			return NULL;
+		}
+	}
+
+	RTDEBUGF(3, "rtpoly_grid: simplified polygon with %d rings", opoly->nrings);
+
+	if ( ! opoly->nrings ) 
+	{
+		rtpoly_free(ctx, opoly);
+		return NULL;
+	}
+
+	return opoly;
+}
diff --git a/src/rtprint.c b/src/rtprint.c
new file mode 100644
index 0000000..17ee2b5
--- /dev/null
+++ b/src/rtprint.c
@@ -0,0 +1,425 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+
+/* Ensures the given lat and lon are in the "normal" range:
+ * -90 to +90 for lat, -180 to +180 for lon. */
+static void rtprint_normalize_latlon(const RTCTX *ctx, 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 * rtdouble_to_dms(const RTCTX *ctx, 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.*/
+	static int NUM_PIECES = 9;
+	static 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. */
+		rterror(ctx, "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)
+				{
+					rterror(ctx, "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)
+				{
+					rterror(ctx, "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)
+				{
+					rterror(ctx, "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)
+			{
+				rterror(ctx, "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
+				{
+					rterror(ctx, "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)
+				{
+					rterror(ctx, "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)
+					{
+						rterror(ctx, "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)
+		{
+			rterror(ctx, "Internal error, somehow needed more pieces than it should.");
+		}
+	}
+	if (deg_piece < 0)
+	{
+		rterror(ctx, "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)
+		{
+			rterror(ctx, "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)
+	{
+		rterror(ctx, "Bad format, degrees (DD.DDD) number of digits was greater than our working limit.");
+	}
+	if(deg_piece >= 0) 
+	{
+		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)
+		{
+			rterror(ctx, "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)
+		{
+			rterror(ctx, "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*)rtalloc(ctx, 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.
+ * NOTE2: returned string is rtalloc'ed, caller is responsible to rtfree it up
+ */
+static char * rtdoubles_to_latlon(const RTCTX *ctx, 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. */
+	rtprint_normalize_latlon(ctx, &lat, &lon);
+	/* This is somewhat inefficient as the format is parsed twice. */
+	lat_text = rtdouble_to_dms(ctx, lat, "N", "S", format);
+	lon_text = rtdouble_to_dms(ctx, lon, "E", "W", format);
+
+	/* lat + lon + a space between + the null terminator. */
+	result = (char*)rtalloc(ctx, strlen(lat_text) + strlen(lon_text) + 2);
+	sprintf(result, "%s %s", lat_text, lon_text);
+	rtfree(ctx, lat_text);
+	rtfree(ctx, 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.
+ * NOTE2: returned string is rtalloc'ed, caller is responsible to rtfree it up
+ */
+char* rtpoint_to_latlon(const RTCTX *ctx, const RTPOINT * pt, const char *format)
+{
+	const RTPOINT2D *p;
+	if (NULL == pt)
+	{
+		rterror(ctx, "Cannot convert a null point into formatted text.");
+	}
+	if (rtgeom_is_empty(ctx, (RTGEOM *)pt))
+	{
+		rterror(ctx, "Cannot convert an empty point into formatted text.");
+	}
+	p = rt_getPoint2d_cp(ctx, pt->point, 0);
+	return rtdoubles_to_latlon(ctx, p->y, p->x, format);
+}
diff --git a/src/rtpsurface.c b/src/rtpsurface.c
new file mode 100644
index 0000000..f311545
--- /dev/null
+++ b/src/rtpsurface.c
@@ -0,0 +1,205 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+RTPSURFACE* rtpsurface_add_rtpoly(const RTCTX *ctx, RTPSURFACE *mobj, const RTPOLY *obj)
+{
+	return (RTPSURFACE*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION*)mobj, (RTGEOM*)obj);
+}
+
+
+void rtpsurface_free(const RTCTX *ctx, RTPSURFACE *psurf)
+{
+	int i;
+	if ( ! psurf ) return;
+	if ( psurf->bbox )
+		rtfree(ctx, psurf->bbox);
+
+	for ( i = 0; i < psurf->ngeoms; i++ )
+		if ( psurf->geoms && psurf->geoms[i] )
+			rtpoly_free(ctx, psurf->geoms[i]);
+
+	if ( psurf->geoms )
+		rtfree(ctx, psurf->geoms);
+
+	rtfree(ctx, psurf);
+}
+
+
+void printRTPSURFACE(const RTCTX *ctx, RTPSURFACE *psurf)
+{
+	int i, j;
+	RTPOLY *patch;
+
+	if (psurf->type != RTPOLYHEDRALSURFACETYPE)
+		rterror(ctx, "printRTPSURFACE called with something else than a POLYHEDRALSURFACE");
+
+	rtnotice(ctx, "RTPSURFACE {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(psurf->flags));
+	rtnotice(ctx, "    SRID = %i", (int)psurf->srid);
+	rtnotice(ctx, "    ngeoms = %i", (int)psurf->ngeoms);
+
+	for (i=0; i<psurf->ngeoms; i++)
+	{
+		patch = (RTPOLY *) psurf->geoms[i];
+		for (j=0; j<patch->nrings; j++)
+		{
+			rtnotice(ctx, "    RING # %i :",j);
+			printPA(ctx, patch->rings[j]);
+		}
+	}
+	rtnotice(ctx, "}");
+}
+
+
+
+
+/*
+ * TODO rewrite all this stuff to be based on a truly topological model
+ */
+
+struct struct_psurface_arcs
+{
+	double ax, ay, az;
+	double bx, by, bz;
+	int cnt, face;
+};
+typedef struct struct_psurface_arcs *psurface_arcs;
+
+/* We supposed that the geometry is valid
+   we could have wrong result if not */
+int rtpsurface_is_closed(const RTCTX *ctx, const RTPSURFACE *psurface)
+{
+	int i, j, k;
+	int narcs, carc;
+	int found;
+	psurface_arcs arcs;
+	RTPOINT4D pa, pb;
+	RTPOLY *patch;
+
+	/* If surface is not 3D, it's can't be closed */
+	if (!RTFLAGS_GET_Z(psurface->flags)) return 0;
+
+	/* If surface is less than 4 faces hard to be closed too */
+	if (psurface->ngeoms < 4) return 0;
+
+	/* Max theorical arcs number if no one is shared ... */
+	for (i=0, narcs=0 ; i < psurface->ngeoms ; i++)
+	{
+		patch = (RTPOLY *) psurface->geoms[i];
+		narcs += patch->rings[0]->npoints - 1;
+	}
+
+	arcs = rtalloc(ctx, sizeof(struct struct_psurface_arcs) * narcs);
+	for (i=0, carc=0; i < psurface->ngeoms ; i++)
+	{
+
+		patch = (RTPOLY *) psurface->geoms[i];
+		for (j=0; j < patch->rings[0]->npoints - 1; j++)
+		{
+
+			rt_getPoint4d_p(ctx, patch->rings[0], j,   &pa);
+			rt_getPoint4d_p(ctx, patch->rings[0], j+1, &pb);
+
+			/* remove redundant points if any */
+			if (pa.x == pb.x && pa.y == pb.y && pa.z == pb.z) continue;
+
+			/* Make sure to order the 'lower' point first */
+			if ( (pa.x > pb.x) ||
+			        (pa.x == pb.x && pa.y > pb.y) ||
+			        (pa.x == pb.x && pa.y == pb.y && pa.z > pb.z) )
+			{
+				pa = pb;
+				rt_getPoint4d_p(ctx, patch->rings[0], j, &pb);
+			}
+
+			for (found=0, k=0; k < carc ; k++)
+			{
+
+				if (  ( arcs[k].ax == pa.x && arcs[k].ay == pa.y &&
+				        arcs[k].az == pa.z && arcs[k].bx == pb.x &&
+				        arcs[k].by == pb.y && arcs[k].bz == pb.z &&
+				        arcs[k].face != i) )
+				{
+					arcs[k].cnt++;
+					found = 1;
+
+					/* Look like an invalid PolyhedralSurface
+					      anyway not a closed one */
+					if (arcs[k].cnt > 2)
+					{
+						rtfree(ctx, arcs);
+						return 0;
+					}
+				}
+			}
+
+			if (!found)
+			{
+				arcs[carc].cnt=1;
+				arcs[carc].face=i;
+				arcs[carc].ax = pa.x;
+				arcs[carc].ay = pa.y;
+				arcs[carc].az = pa.z;
+				arcs[carc].bx = pb.x;
+				arcs[carc].by = pb.y;
+				arcs[carc].bz = pb.z;
+				carc++;
+
+				/* Look like an invalid PolyhedralSurface
+				      anyway not a closed one */
+				if (carc > narcs)
+				{
+					rtfree(ctx, arcs);
+					return 0;
+				}
+			}
+		}
+	}
+
+	/* A polyhedron is closed if each edge
+	       is shared by exactly 2 faces */
+	for (k=0; k < carc ; k++)
+	{
+		if (arcs[k].cnt != 2)
+		{
+			rtfree(ctx, arcs);
+			return 0;
+		}
+	}
+	rtfree(ctx, arcs);
+
+	/* Invalid Polyhedral case */
+	if (carc < psurface->ngeoms) return 0;
+
+	return 1;
+}
diff --git a/src/rtspheroid.c b/src/rtspheroid.c
new file mode 100644
index 0000000..3c9ffd0
--- /dev/null
+++ b/src/rtspheroid.c
@@ -0,0 +1,701 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ * Copyright (C) 2009 David Skea <David.Skea at gov.bc.ca>
+ *
+ **********************************************************************/
+
+
+
+#include "librtgeom_internal.h"
+#include "rtgeodetic.h"
+#include "rtgeom_log.h"
+
+/* GeographicLib */
+#if PROJ_GEODESIC
+#include <geodesic.h>
+#endif
+
+/**
+* Initialize spheroid object based on major and minor axis
+*/
+void spheroid_init(const RTCTX *ctx, SPHEROID *s, double a, double b)
+{
+	s->a = a;
+	s->b = b;
+	s->f = (a - b) / a;
+	s->e_sq = (a*a - b*b)/(a*a);
+	s->radius = (2.0 * a + b ) / 3.0;
+}
+
+#if ! PROJ_GEODESIC
+static double spheroid_mu2(const RTCTX *ctx, double alpha, const SPHEROID *s)
+{
+	double b2 = POW2(s->b);
+	return POW2(cos(alpha)) * (POW2(s->a) - b2) / b2;
+}
+
+static double spheroid_big_a(const RTCTX *ctx, double u2)
+{
+	return 1.0 + (u2 / 16384.0) * (4096.0 + u2 * (-768.0 + u2 * (320.0 - 175.0 * u2)));
+}
+
+static double spheroid_big_b(const RTCTX *ctx, double u2)
+{
+	return (u2 / 1024.0) * (256.0 + u2 * (-128.0 + u2 * (74.0 - 47.0 * u2)));
+}
+#endif /* ! PROJ_GEODESIC */
+
+
+#if PROJ_GEODESIC
+
+/**
+* Computes the shortest distance along the surface of the spheroid
+* between two points, using the inverse geodesic problem from
+* GeographicLib (Karney 2013).
+*
+* @param a - location of first point
+* @param b - location of second point
+* @param s - spheroid to calculate on
+* @return spheroidal distance between a and b in spheroid units
+*/
+double spheroid_distance(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
+{
+	struct geod_geodesic gd;
+	geod_init(&gd, spheroid->a, spheroid->f);
+	double lat1 = a->lat * 180.0 / M_PI;
+	double lon1 = a->lon * 180.0 / M_PI;
+	double lat2 = b->lat * 180.0 / M_PI;
+	double lon2 = b->lon * 180.0 / M_PI;
+	double s12; /* return distance */
+	geod_inverse(&gd, lat1, lon1, lat2, lon2, &s12, 0, 0);
+	return s12;
+}
+
+/**
+* Computes the forward azimuth of the geodesic joining two points on
+* the spheroid, using the inverse geodesic problem (Karney 2013).
+*
+* @param r - location of first point
+* @param s - location of second point
+* @return azimuth of line joining r to s (but not reverse)
+*/
+double spheroid_direction(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
+{
+	struct geod_geodesic gd;
+	geod_init(&gd, spheroid->a, spheroid->f);
+	double lat1 = a->lat * 180.0 / M_PI;
+	double lon1 = a->lon * 180.0 / M_PI;
+	double lat2 = b->lat * 180.0 / M_PI;
+	double lon2 = b->lon * 180.0 / M_PI;
+	double azi1; /* return azimuth */
+	geod_inverse(&gd, lat1, lon1, lat2, lon2, 0, &azi1, 0);
+	return azi1 * M_PI / 180.0;
+}
+
+/**
+* Given a location, an azimuth and a distance, computes the location of
+* the projected point. Using the direct geodesic problem from
+* GeographicLib (Karney 2013).
+*
+* @param r - location of first point
+* @param distance - distance in meters
+* @param azimuth - azimuth in radians
+* @return g - location of projected point
+*/
+int spheroid_project(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g)
+{
+	struct geod_geodesic gd;
+	geod_init(&gd, spheroid->a, spheroid->f);
+	double lat1 = r->lat * 180.0 / M_PI;
+	double lon1 = r->lon * 180.0 / M_PI;
+	double lat2, lon2; /* return projected position */
+	geod_direct(&gd, lat1, lon1, azimuth * 180.0 / M_PI, distance, &lat2, &lon2, 0);
+	g->lat = lat2 * M_PI / 180.0;
+	g->lon = lon2 * M_PI / 180.0;
+	return RT_SUCCESS;
+}
+
+
+static double ptarray_area_spheroid(const RTCTX *ctx, const RTPOINTARRAY *pa, const SPHEROID *spheroid)
+{
+	/* Return zero on non-sensical inputs */
+	if ( ! pa || pa->npoints < 4 )
+		return 0.0;
+
+	struct geod_geodesic gd;
+	geod_init(&gd, spheroid->a, spheroid->f);
+	struct geod_polygon poly;
+	geod_polygon_init(&poly, 0);
+	int i;
+	double area; /* returned polygon area */
+	RTPOINT2D p; /* long/lat units are degrees */
+
+	/* Pass points from point array; don't close the linearring */
+	for ( i = 0; i < pa->npoints - 1; i++ )
+	{
+		rt_getPoint2d_p(ctx, pa, i, &p);
+		geod_polygon_addpoint(&gd, &poly, p.y, p.x);
+		RTDEBUGF(4, "geod_polygon_addpoint %d: %.12g %.12g", i, p.y, p.x);
+	}
+	i = geod_polygon_compute(&gd, &poly, 0, 1, &area, 0);
+	if ( i != pa->npoints - 1 )
+	{
+		rterror(ctx, "ptarray_area_spheroid: different number of points %d vs %d",
+				i, pa->npoints - 1);
+	}
+	RTDEBUGF(4, "geod_polygon_compute area: %.12g", area);
+	return fabs(area);
+}
+
+/* Above use GeographicLib */
+#else /* ! PROJ_GEODESIC */
+/* Below use pre-version 2.2 geodesic functions */
+
+/**
+* Computes the shortest distance along the surface of the spheroid
+* between two points. Based on Vincenty's formula for the geodetic
+* inverse problem as described in "Geocentric Datum of Australia
+* Technical Manual", Chapter 4. Tested against:
+* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html
+* and
+* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp
+*
+* @param a - location of first point.
+* @param b - location of second point.
+* @param s - spheroid to calculate on
+* @return spheroidal distance between a and b in spheroid units.
+*/
+double spheroid_distance(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
+{
+	double lambda = (b->lon - a->lon);
+	double f = spheroid->f;
+	double omf = 1 - spheroid->f;
+	double u1, u2;
+	double cos_u1, cos_u2;
+	double sin_u1, sin_u2;
+	double big_a, big_b, delta_sigma;
+	double alpha, sin_alpha, cos_alphasq, c;
+	double sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqrsin_sigma, last_lambda, omega;
+	double cos_lambda, sin_lambda;
+	double distance;
+	int i = 0;
+
+	/* Same point => zero distance */
+	if ( geographic_point_equals(ctx, a, b) )
+	{
+		return 0.0;
+	}
+
+	u1 = atan(omf * tan(a->lat));
+	cos_u1 = cos(u1);
+	sin_u1 = sin(u1);
+	u2 = atan(omf * tan(b->lat));
+	cos_u2 = cos(u2);
+	sin_u2 = sin(u2);
+
+	omega = lambda;
+	do
+	{
+		cos_lambda = cos(lambda);
+		sin_lambda = sin(lambda);
+		sqrsin_sigma = POW2(cos_u2 * sin_lambda) +
+		               POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda));
+		sin_sigma = sqrt(sqrsin_sigma);
+		cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda;
+		sigma = atan2(sin_sigma, cos_sigma);
+		sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin(sigma);
+
+		/* Numerical stability issue, ensure asin is not NaN */
+		if ( sin_alpha > 1.0 )
+			alpha = M_PI_2;
+		else if ( sin_alpha < -1.0 )
+			alpha = -1.0 * M_PI_2;
+		else
+			alpha = asin(sin_alpha);
+
+		cos_alphasq = POW2(cos(alpha));
+		cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq);
+
+		/* Numerical stability issue, cos2 is in range */
+		if ( cos2_sigma_m > 1.0 )
+			cos2_sigma_m = 1.0;
+		if ( cos2_sigma_m < -1.0 )
+			cos2_sigma_m = -1.0;
+
+		c = (f / 16.0) * cos_alphasq * (4.0 + f * (4.0 - 3.0 * cos_alphasq));
+		last_lambda = lambda;
+		lambda = omega + (1.0 - c) * f * sin(alpha) * (sigma + c * sin(sigma) *
+		         (cos2_sigma_m + c * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m))));
+		i++;
+	}
+	while ( (i < 999) && (lambda != 0.0) && (fabs((last_lambda - lambda)/lambda) > 1.0e-9) );
+
+	u2 = spheroid_mu2(ctx, alpha, spheroid);
+	big_a = spheroid_big_a(ctx, u2);
+	big_b = spheroid_big_b(ctx, u2);
+	delta_sigma = big_b * sin_sigma * (cos2_sigma_m + (big_b / 4.0) * (cos_sigma * (-1.0 + 2.0 * POW2(cos2_sigma_m)) -
+	                                   (big_b / 6.0) * cos2_sigma_m * (-3.0 + 4.0 * sqrsin_sigma) * (-3.0 + 4.0 * POW2(cos2_sigma_m))));
+
+	distance = spheroid->b * big_a * (sigma - delta_sigma);
+
+	/* Algorithm failure, distance == NaN, fallback to sphere */
+	if ( distance != distance )
+	{
+		rterror(ctx, "spheroid_distance returned NaN: (%.20g %.20g) (%.20g %.20g) a = %.20g b = %.20g",a->lat, a->lon, b->lat, b->lon, spheroid->a, spheroid->b);
+		return spheroid->radius * sphere_distance(ctx, a, b);
+	}
+
+	return distance;
+}
+
+/**
+* Computes the direction of the geodesic joining two points on
+* the spheroid. Based on Vincenty's formula for the geodetic
+* inverse problem as described in "Geocentric Datum of Australia
+* Technical Manual", Chapter 4. Tested against:
+* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html
+* and
+* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp
+*
+* @param r - location of first point
+* @param s - location of second point
+* @return azimuth of line joining r and s
+*/
+double spheroid_direction(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid)
+{
+	int i = 0;
+	double lambda = s->lon - r->lon;
+	double omf = 1 - spheroid->f;
+	double u1 = atan(omf * tan(r->lat));
+	double cos_u1 = cos(u1);
+	double sin_u1 = sin(u1);
+	double u2 = atan(omf * tan(s->lat));
+	double cos_u2 = cos(u2);
+	double sin_u2 = sin(u2);
+
+	double omega = lambda;
+	double alpha, sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqr_sin_sigma, last_lambda;
+	double sin_alpha, cos_alphasq, C, alphaFD;
+	do
+	{
+		sqr_sin_sigma = POW2(cos_u2 * sin(lambda)) +
+		                POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda)));
+		sin_sigma = sqrt(sqr_sin_sigma);
+		cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos(lambda);
+		sigma = atan2(sin_sigma, cos_sigma);
+		sin_alpha = cos_u1 * cos_u2 * sin(lambda) / sin(sigma);
+
+		/* Numerical stability issue, ensure asin is not NaN */
+		if ( sin_alpha > 1.0 )
+			alpha = M_PI_2;
+		else if ( sin_alpha < -1.0 )
+			alpha = -1.0 * M_PI_2;
+		else
+			alpha = asin(sin_alpha);
+
+		cos_alphasq = POW2(cos(alpha));
+		cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq);
+
+		/* Numerical stability issue, cos2 is in range */
+		if ( cos2_sigma_m > 1.0 )
+			cos2_sigma_m = 1.0;
+		if ( cos2_sigma_m < -1.0 )
+			cos2_sigma_m = -1.0;
+
+		C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq));
+		last_lambda = lambda;
+		lambda = omega + (1.0 - C) * spheroid->f * sin(alpha) * (sigma + C * sin(sigma) *
+		         (cos2_sigma_m + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m))));
+		i++;
+	}
+	while ( (i < 999) && (lambda != 0) && (fabs((last_lambda - lambda) / lambda) > 1.0e-9) );
+
+	alphaFD = atan2((cos_u2 * sin(lambda)),
+	                (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda)));
+	if (alphaFD < 0.0)
+	{
+		alphaFD = alphaFD + 2.0 * M_PI;
+	}
+	if (alphaFD > 2.0 * M_PI)
+	{
+		alphaFD = alphaFD - 2.0 * M_PI;
+	}
+	return alphaFD;
+}
+
+
+/**
+* Given a location, an azimuth and a distance, computes the
+* location of the projected point. Based on Vincenty's formula
+* for the geodetic direct problem as described in "Geocentric
+* Datum of Australia Technical Manual", Chapter 4. Tested against:
+* http://mascot.gdbc.gov.bc.ca/mascot/util1b.html
+* and
+* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_direct.jsp
+*
+* @param r - location of first point.
+* @param distance - distance in meters.
+* @param azimuth - azimuth in radians.
+* @return s - location of projected point.
+*/
+int spheroid_project(const RTCTX *ctx, const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g)
+{
+	double omf = 1 - spheroid->f;
+	double tan_u1 = omf * tan(r->lat);
+	double u1 = atan(tan_u1);
+	double sigma, last_sigma, delta_sigma, two_sigma_m;
+	double sigma1, sin_alpha, alpha, cos_alphasq;
+	double u2, A, B;
+	double lat2, lambda, lambda2, C, omega;
+	int i = 0;
+
+	if (azimuth < 0.0)
+	{
+		azimuth = azimuth + M_PI * 2.0;
+	}
+	if (azimuth > (M_PI * 2.0))
+	{
+		azimuth = azimuth - M_PI * 2.0;
+	}
+
+	sigma1 = atan2(tan_u1, cos(azimuth));
+	sin_alpha = cos(u1) * sin(azimuth);
+	alpha = asin(sin_alpha);
+	cos_alphasq = 1.0 - POW2(sin_alpha);
+
+	u2 = spheroid_mu2(ctx, alpha, spheroid);
+	A = spheroid_big_a(ctx, u2);
+	B = spheroid_big_b(ctx, u2);
+
+	sigma = (distance / (spheroid->b * A));
+	do
+	{
+		two_sigma_m = 2.0 * sigma1 + sigma;
+		delta_sigma = B * sin(sigma) * (cos(two_sigma_m) + (B / 4.0) * (cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m)) - (B / 6.0) * cos(two_sigma_m) * (-3.0 + 4.0 * POW2(sin(sigma))) * (-3.0 + 4.0 * POW2(cos(two_sigma_m))))));
+		last_sigma = sigma;
+		sigma = (distance / (spheroid->b * A)) + delta_sigma;
+		i++;
+	}
+	while (i < 999 && fabs((last_sigma - sigma) / sigma) > 1.0e-9);
+
+	lat2 = atan2((sin(u1) * cos(sigma) + cos(u1) * sin(sigma) *
+	              cos(azimuth)), (omf * sqrt(POW2(sin_alpha) +
+	                                         POW2(sin(u1) * sin(sigma) - cos(u1) * cos(sigma) *
+	                                              cos(azimuth)))));
+	lambda = atan2((sin(sigma) * sin(azimuth)), (cos(u1) * cos(sigma) -
+	               sin(u1) * sin(sigma) * cos(azimuth)));
+	C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq));
+	omega = lambda - (1.0 - C) * spheroid->f * sin_alpha * (sigma + C * sin(sigma) *
+	        (cos(two_sigma_m) + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m)))));
+	lambda2 = r->lon + omega;
+	g->lat = lat2;
+	g->lon = lambda2;
+	return RT_SUCCESS;
+}
+
+
+static inline double spheroid_prime_vertical_radius_of_curvature(const RTCTX *ctx, double latitude, const SPHEROID *spheroid)
+{
+	return spheroid->a / (sqrt(1.0 - spheroid->e_sq * POW2(sin(latitude))));
+}
+
+static inline double spheroid_parallel_arc_length(const RTCTX *ctx, double latitude, double deltaLongitude, const SPHEROID *spheroid)
+{
+	return spheroid_prime_vertical_radius_of_curvature(ctx, latitude, spheroid)
+	       * cos(latitude)
+	       * deltaLongitude;
+}
+
+
+/**
+* Computes the area on the spheroid of a box bounded by meridians and
+* parallels. The box is defined by two points, the South West corner
+* and the North East corner. Formula based on Bagratuni 1967.
+*
+* @param southWestCorner - lower left corner of bounding box.
+* @param northEastCorner - upper right corner of bounding box.
+* @return area in square meters.
+*/
+static double spheroid_boundingbox_area(const RTCTX *ctx, const GEOGRAPHIC_POINT *southWestCorner, const GEOGRAPHIC_POINT *northEastCorner, const SPHEROID *spheroid)
+{
+	double z0 = (northEastCorner->lon - southWestCorner->lon) * POW2(spheroid->b) / 2.0;
+	double e = sqrt(spheroid->e_sq);
+	double sinPhi1 = sin(southWestCorner->lat);
+	double sinPhi2 = sin(northEastCorner->lat);
+	double t1p1 = sinPhi1 / (1.0 - spheroid->e_sq * sinPhi1 * sinPhi1);
+	double t1p2 = sinPhi2 / (1.0 - spheroid->e_sq * sinPhi2 * sinPhi2);
+	double oneOver2e = 1.0 / (2.0 * e);
+	double t2p1 = oneOver2e * log((1.0 + e * sinPhi1) / (1.0 - e * sinPhi1));
+	double t2p2 = oneOver2e * log((1.0 + e * sinPhi2) / (1.0 - e * sinPhi2));
+	return z0 * (t1p2 + t2p2) - z0 * (t1p1 + t2p1);
+}
+
+/**
+* This function doesn't work for edges crossing the dateline or in the southern
+* hemisphere. Points are pre-conditioned in ptarray_area_spheroid.
+*/
+static double spheroid_striparea(const RTCTX *ctx, const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, double latitude_min, const SPHEROID *spheroid)
+{
+	GEOGRAPHIC_POINT A, B, mL, nR;
+	double deltaLng, baseArea, topArea;
+	double bE, tE, ratio, sign;
+
+	A = *a;
+	B = *b;
+
+	mL.lat = latitude_min;
+	mL.lon = FP_MIN(A.lon, B.lon);
+	nR.lat = FP_MIN(A.lat, B.lat);
+	nR.lon = FP_MAX(A.lon, B.lon);
+	RTDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon);
+	RTDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon);
+	baseArea = spheroid_boundingbox_area(ctx, &mL, &nR, spheroid);
+	RTDEBUGF(4, "baseArea %.12g", baseArea);
+
+	mL.lat = FP_MIN(A.lat, B.lat);
+	mL.lon = FP_MIN(A.lon, B.lon);
+	nR.lat = FP_MAX(A.lat, B.lat);
+	nR.lon = FP_MAX(A.lon, B.lon);
+	RTDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon);
+	RTDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon);
+	topArea = spheroid_boundingbox_area(ctx, &mL, &nR, spheroid);
+	RTDEBUGF(4, "topArea %.12g", topArea);
+
+	deltaLng = B.lon - A.lon;
+	RTDEBUGF(4, "deltaLng %.12g", deltaLng);
+	bE = spheroid_parallel_arc_length(ctx, A.lat, deltaLng, spheroid);
+	tE = spheroid_parallel_arc_length(ctx, B.lat, deltaLng, spheroid);
+	RTDEBUGF(4, "bE %.12g", bE);
+	RTDEBUGF(4, "tE %.12g", tE);
+
+	ratio = (bE + tE)/tE;
+	sign = signum(B.lon - A.lon);
+	return (baseArea + topArea / ratio) * sign;
+}
+
+static double ptarray_area_spheroid(const RTCTX *ctx, const RTPOINTARRAY *pa, const SPHEROID *spheroid)
+{
+	GEOGRAPHIC_POINT a, b;
+	RTPOINT2D p;
+	int i;
+	double area = 0.0;
+	RTGBOX gbox2d;
+	int in_south = RT_FALSE;
+	double delta_lon_tolerance;
+	double latitude_min;
+
+	gbox2d.flags = gflags(ctx, 0, 0, 0);
+
+	/* Return zero on non-sensical inputs */
+	if ( ! pa || pa->npoints < 4 )
+		return 0.0;
+
+	/* Get the raw min/max values for the latitudes */
+	ptarray_calculate_gbox_cartesian(ctx, pa, &gbox2d);
+
+	if ( signum(gbox2d.ymin) != signum(gbox2d.ymax) )
+		rterror(ctx, "ptarray_area_spheroid: cannot handle ptarray that crosses equator");
+
+	/* Geodetic bbox < 0.0 implies geometry is entirely in southern hemisphere */
+	if ( gbox2d.ymax < 0.0 )
+		in_south = RT_TRUE;
+
+	RTDEBUGF(4, "gbox2d.ymax %.12g", gbox2d.ymax);
+
+	/* Tolerance for strip area calculation */
+	if ( in_south )
+	{
+		delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymin) / 8.0) - 2.0) / 10000.0;
+		latitude_min = deg2rad(fabs(gbox2d.ymax));
+	}
+	else
+	{
+		delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymax) / 8.0) - 2.0) / 10000.0;
+		latitude_min = deg2rad(gbox2d.ymin);
+	}
+
+	/* Initialize first point */
+	rt_getPoint2d_p(ctx, pa, 0, &p);
+	geographic_point_init(ctx, p.x, p.y, &a);
+
+	for ( i = 1; i < pa->npoints; i++ )
+	{
+		GEOGRAPHIC_POINT a1, b1;
+		double strip_area = 0.0;
+		double delta_lon = 0.0;
+		RTDEBUGF(4, "edge #%d", i);
+
+		rt_getPoint2d_p(ctx, pa, i, &p);
+		geographic_point_init(ctx, p.x, p.y, &b);
+
+		a1 = a;
+		b1 = b;
+
+		/* Flip into north if in south */
+		if ( in_south )
+		{
+			a1.lat = -1.0 * a1.lat;
+			b1.lat = -1.0 * b1.lat;
+		}
+
+		RTDEBUGF(4, "in_south %d", in_south);
+
+		RTDEBUGF(4, "crosses_dateline(ctx, a, b) %d", crosses_dateline(ctx, &a, &b) );
+
+		if ( crosses_dateline(ctx, &a, &b) )
+		{
+			double shift;
+
+			if ( a1.lon > 0.0 )
+				shift = (M_PI - a1.lon) + 0.088; /* About 5deg more */
+			else
+				shift = (M_PI - b1.lon) + 0.088; /* About 5deg more */
+
+			RTDEBUGF(4, "shift: %.8g", shift);
+			RTDEBUGF(4, "before shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon);
+			point_shift(ctx, &a1, shift);
+			point_shift(ctx, &b1, shift);
+			RTDEBUGF(4, "after shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon);
+
+		}
+
+
+		delta_lon = fabs(b1.lon - a1.lon);
+
+		RTDEBUGF(4, "a1(%.18g %.18g) b1(%.18g %.18g)", a1.lat, a1.lon, b1.lat, b1.lon);
+		RTDEBUGF(4, "delta_lon %.18g", delta_lon);
+		RTDEBUGF(4, "delta_lon_tolerance %.18g", delta_lon_tolerance);
+
+		if ( delta_lon > 0.0 )
+		{
+			if ( delta_lon < delta_lon_tolerance )
+			{
+				strip_area = spheroid_striparea(ctx, &a1, &b1, latitude_min, spheroid);
+				RTDEBUGF(4, "strip_area %.12g", strip_area);
+				area += strip_area;
+			}
+			else
+			{
+				GEOGRAPHIC_POINT p, q;
+				double step = floor(delta_lon / delta_lon_tolerance);
+				double distance = spheroid_distance(ctx, &a1, &b1, spheroid);
+				double pDistance = 0.0;
+				int j = 0;
+				RTDEBUGF(4, "step %.18g", step);
+				RTDEBUGF(4, "distance %.18g", distance);
+				step = distance / step;
+				RTDEBUGF(4, "step %.18g", step);
+				p = a1;
+				while (pDistance < (distance - step * 1.01))
+				{
+					double azimuth = spheroid_direction(ctx, &p, &b1, spheroid);
+					j++;
+					RTDEBUGF(4, "  iteration %d", j);
+					RTDEBUGF(4, "  azimuth %.12g", azimuth);
+					pDistance = pDistance + step;
+					RTDEBUGF(4, "  pDistance %.12g", pDistance);
+					spheroid_project(ctx, &p, spheroid, step, azimuth, &q);
+					strip_area = spheroid_striparea(ctx, &p, &q, latitude_min, spheroid);
+					RTDEBUGF(4, "  strip_area %.12g", strip_area);
+					area += strip_area;
+					RTDEBUGF(4, "  area %.12g", area);
+					p.lat = q.lat;
+					p.lon = q.lon;
+				}
+				strip_area = spheroid_striparea(ctx, &p, &b1, latitude_min, spheroid);
+				area += strip_area;
+			}
+		}
+
+		/* B gets incremented in the next loop, so we save the value here */
+		a = b;
+	}
+	return fabs(area);
+}
+#endif /* else ! PROJ_GEODESIC */
+
+/**
+* Calculate the area of an RTGEOM. Anything except POLYGON, MULTIPOLYGON
+* and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons
+* calculate external ring area and subtract internal ring area. A RTGBOX is
+* required to check relationship to equator an outside point.
+* WARNING: Does NOT WORK for polygons over equator or pole.
+*/
+double rtgeom_area_spheroid(const RTCTX *ctx, const RTGEOM *rtgeom, const SPHEROID *spheroid)
+{
+	int type;
+
+	assert(rtgeom);
+
+	/* No area in nothing */
+	if ( rtgeom_is_empty(ctx, rtgeom) )
+		return 0.0;
+
+	/* Read the geometry type number */
+	type = rtgeom->type;
+
+	/* Anything but polygons and collections returns zero */
+	if ( ! ( type == RTPOLYGONTYPE || type == RTMULTIPOLYGONTYPE || type == RTCOLLECTIONTYPE ) )
+		return 0.0;
+
+	/* Actually calculate area */
+	if ( type == RTPOLYGONTYPE )
+	{
+		RTPOLY *poly = (RTPOLY*)rtgeom;
+		int i;
+		double area = 0.0;
+
+		/* Just in case there's no rings */
+		if ( poly->nrings < 1 )
+			return 0.0;
+
+		/* First, the area of the outer ring */
+		area += ptarray_area_spheroid(ctx, poly->rings[0], spheroid);
+
+		/* Subtract areas of inner rings */
+		for ( i = 1; i < poly->nrings; i++ )
+		{
+			area -=  ptarray_area_spheroid(ctx, poly->rings[i], spheroid);
+		}
+		return area;
+	}
+
+	/* Recurse into sub-geometries to get area */
+	if ( type == RTMULTIPOLYGONTYPE || type == RTCOLLECTIONTYPE )
+	{
+		RTCOLLECTION *col = (RTCOLLECTION*)rtgeom;
+		int i;
+		double area = 0.0;
+
+		for ( i = 0; i < col->ngeoms; i++ )
+		{
+			area += rtgeom_area_spheroid(ctx, col->geoms[i], spheroid);
+		}
+		return area;
+	}
+
+	/* Shouldn't get here. */
+	return 0.0;
+}
+
+
+
diff --git a/src/rtstroke.c b/src/rtstroke.c
new file mode 100644
index 0000000..3986826
--- /dev/null
+++ b/src/rtstroke.c
@@ -0,0 +1,873 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "librtgeom_internal.h"
+
+/* #define RTGEOM_DEBUG_LEVEL 4 */
+
+#include "rtgeom_log.h"
+
+
+RTMLINE* rtmcurve_stroke(const RTCTX *ctx, const RTMCURVE *mcurve, uint32_t perQuad);
+RTMPOLY* rtmsurface_stroke(const RTCTX *ctx, const RTMSURFACE *msurface, uint32_t perQuad);
+RTCOLLECTION* rtcollection_stroke(const RTCTX *ctx, const RTCOLLECTION *collection, uint32_t perQuad);
+
+RTGEOM* pta_unstroke(const RTCTX *ctx, const RTPOINTARRAY *points, int type, int srid);
+RTGEOM* rtline_unstroke(const RTCTX *ctx, const RTLINE *line);
+RTGEOM* rtpolygon_unstroke(const RTCTX *ctx, const RTPOLY *poly);
+RTGEOM* rtmline_unstroke(const RTCTX *ctx, const RTMLINE *mline);
+RTGEOM* rtmpolygon_unstroke(const RTCTX *ctx, const RTMPOLY *mpoly);
+RTGEOM* rtgeom_unstroke(const RTCTX *ctx, const RTGEOM *geom);
+
+
+/*
+ * Determines (recursively in the case of collections) whether the geometry
+ * contains at least on arc geometry or segment.
+ */
+int
+rtgeom_has_arc(const RTCTX *ctx, const RTGEOM *geom)
+{
+	RTCOLLECTION *col;
+	int i;
+
+	RTDEBUG(2, "rtgeom_has_arc called.");
+
+	switch (geom->type)
+	{
+	case RTPOINTTYPE:
+	case RTLINETYPE:
+	case RTPOLYGONTYPE:
+	case RTTRIANGLETYPE:
+	case RTMULTIPOINTTYPE:
+	case RTMULTILINETYPE:
+	case RTMULTIPOLYGONTYPE:
+	case RTPOLYHEDRALSURFACETYPE:
+	case RTTINTYPE:
+		return RT_FALSE;
+	case RTCIRCSTRINGTYPE:
+		return RT_TRUE;
+	/* It's a collection that MAY contain an arc */
+	default:
+		col = (RTCOLLECTION *)geom;
+		for (i=0; i<col->ngeoms; i++)
+		{
+			if (rtgeom_has_arc(ctx, col->geoms[i]) == RT_TRUE) 
+				return RT_TRUE;
+		}
+		return RT_FALSE;
+	}
+}
+
+
+
+/*******************************************************************************
+ * Begin curve segmentize functions
+ ******************************************************************************/
+
+static double interpolate_arc(const RTCTX *ctx, double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3)
+{
+	RTDEBUGF(4,"angle %.05g a1 %.05g a2 %.05g a3 %.05g zm1 %.05g zm2 %.05g zm3 %.05g",angle,a1,a2,a3,zm1,zm2,zm3);
+	/* Counter-clockwise sweep */
+	if ( a1 < a2 )
+	{
+		if ( angle <= a2 )
+			return zm1 + (zm2-zm1) * (angle-a1) / (a2-a1);
+		else
+			return zm2 + (zm3-zm2) * (angle-a2) / (a3-a2);
+	}
+	/* Clockwise sweep */
+	else
+	{
+		if ( angle >= a2 )
+			return zm1 + (zm2-zm1) * (a1-angle) / (a1-a2);
+		else
+			return zm2 + (zm3-zm2) * (a2-angle) / (a2-a3);
+	}
+}
+
+static RTPOINTARRAY *
+rtcircle_stroke(const RTCTX *ctx, const RTPOINT4D *p1, const RTPOINT4D *p2, const RTPOINT4D *p3, uint32_t perQuad)
+{
+	RTPOINT2D center;
+	RTPOINT2D *t1 = (RTPOINT2D*)p1;
+	RTPOINT2D *t2 = (RTPOINT2D*)p2;
+	RTPOINT2D *t3 = (RTPOINT2D*)p3;
+	RTPOINT4D pt;
+	int p2_side = 0;
+	int clockwise = RT_TRUE;
+	double radius; /* Arc radius */
+	double increment; /* Angle per segment */
+	double a1, a2, a3, angle;
+	RTPOINTARRAY *pa;
+	int is_circle = RT_FALSE;
+
+	RTDEBUG(2, "rtcircle_calculate_gbox called.");
+
+	radius = rt_arc_center(ctx, t1, t2, t3, &center);
+	p2_side = rt_segment_side(ctx, t1, t3, t2);
+
+	/* Matched start/end points imply circle */
+	if ( p1->x == p3->x && p1->y == p3->y )
+		is_circle = RT_TRUE;
+	
+	/* Negative radius signals straight line, p1/p2/p3 are colinear */
+	if ( (radius < 0.0 || p2_side == 0) && ! is_circle )
+	    return NULL;
+		
+	/* The side of the p1/p3 line that p2 falls on dictates the sweep  
+	   direction from p1 to p3. */
+	if ( p2_side == -1 )
+		clockwise = RT_TRUE;
+	else
+		clockwise = RT_FALSE;
+		
+	increment = fabs(M_PI_2 / perQuad);
+	
+	/* Angles of each point that defines the arc section */
+	a1 = atan2(p1->y - center.y, p1->x - center.x);
+	a2 = atan2(p2->y - center.y, p2->x - center.x);
+	a3 = atan2(p3->y - center.y, p3->x - center.x);
+
+	/* p2 on left side => clockwise sweep */
+	if ( clockwise )
+	{
+		increment *= -1;
+		/* Adjust a3 down so we can decrement from a1 to a3 cleanly */
+		if ( a3 > a1 )
+			a3 -= 2.0 * M_PI;
+		if ( a2 > a1 )
+			a2 -= 2.0 * M_PI;
+	}
+	/* p2 on right side => counter-clockwise sweep */
+	else
+	{
+		/* Adjust a3 up so we can increment from a1 to a3 cleanly */
+		if ( a3 < a1 )
+			a3 += 2.0 * M_PI;
+		if ( a2 < a1 )
+			a2 += 2.0 * M_PI;
+	}
+	
+	/* Override angles for circle case */
+	if( is_circle )
+	{
+		a3 = a1 + 2.0 * M_PI;
+		a2 = a1 + M_PI;
+		increment = fabs(increment);
+		clockwise = RT_FALSE;
+	}
+	
+	/* Initialize point array */
+	pa = ptarray_construct_empty(ctx, 1, 1, 32);
+
+	/* Sweep from a1 to a3 */
+	ptarray_append_point(ctx, pa, p1, RT_FALSE);
+	for ( angle = a1 + increment; clockwise ? angle > a3 : angle < a3; angle += increment ) 
+	{
+		pt.x = center.x + radius * cos(angle);
+		pt.y = center.y + radius * sin(angle);
+		pt.z = interpolate_arc(ctx, angle, a1, a2, a3, p1->z, p2->z, p3->z);
+		pt.m = interpolate_arc(ctx, angle, a1, a2, a3, p1->m, p2->m, p3->m);
+		ptarray_append_point(ctx, pa, &pt, RT_FALSE);
+	}	
+	return pa;
+}
+
+RTLINE *
+rtcircstring_stroke(const RTCTX *ctx, const RTCIRCSTRING *icurve, uint32_t perQuad)
+{
+	RTLINE *oline;
+	RTPOINTARRAY *ptarray;
+	RTPOINTARRAY *tmp;
+	uint32_t i, j;
+	RTPOINT4D p1, p2, p3, p4;
+
+	RTDEBUGF(2, "rtcircstring_stroke called., dim = %d", icurve->points->flags);
+
+	ptarray = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(icurve->points->flags), RTFLAGS_GET_M(icurve->points->flags), 64);
+
+	for (i = 2; i < icurve->points->npoints; i+=2)
+	{
+		RTDEBUGF(3, "rtcircstring_stroke: arc ending at point %d", i);
+
+		rt_getPoint4d_p(ctx, icurve->points, i - 2, &p1);
+		rt_getPoint4d_p(ctx, icurve->points, i - 1, &p2);
+		rt_getPoint4d_p(ctx, icurve->points, i, &p3);
+		tmp = rtcircle_stroke(ctx, &p1, &p2, &p3, perQuad);
+
+		if (tmp)
+		{
+			RTDEBUGF(3, "rtcircstring_stroke: generated %d points", tmp->npoints);
+
+			for (j = 0; j < tmp->npoints; j++)
+			{
+				rt_getPoint4d_p(ctx, tmp, j, &p4);
+				ptarray_append_point(ctx, ptarray, &p4, RT_TRUE);
+			}
+			ptarray_free(ctx, tmp);
+		}
+		else
+		{
+			RTDEBUG(3, "rtcircstring_stroke: points are colinear, returning curve points as line");
+
+			for (j = i - 2 ; j < i ; j++)
+			{
+				rt_getPoint4d_p(ctx, icurve->points, j, &p4);
+				ptarray_append_point(ctx, ptarray, &p4, RT_TRUE);
+			}
+		}
+
+	}
+	rt_getPoint4d_p(ctx, icurve->points, icurve->points->npoints-1, &p1);
+	ptarray_append_point(ctx, ptarray, &p1, RT_TRUE);
+		
+	oline = rtline_construct(ctx, icurve->srid, NULL, ptarray);
+	return oline;
+}
+
+RTLINE *
+rtcompound_stroke(const RTCTX *ctx, const RTCOMPOUND *icompound, uint32_t perQuad)
+{
+	RTGEOM *geom;
+	RTPOINTARRAY *ptarray = NULL, *ptarray_out = NULL;
+	RTLINE *tmp = NULL;
+	uint32_t i, j;
+	RTPOINT4D p;
+
+	RTDEBUG(2, "rtcompound_stroke called.");
+
+	ptarray = ptarray_construct_empty(ctx, RTFLAGS_GET_Z(icompound->flags), RTFLAGS_GET_M(icompound->flags), 64);
+
+	for (i = 0; i < icompound->ngeoms; i++)
+	{
+		geom = icompound->geoms[i];
+		if (geom->type == RTCIRCSTRINGTYPE)
+		{
+			tmp = rtcircstring_stroke(ctx, (RTCIRCSTRING *)geom, perQuad);
+			for (j = 0; j < tmp->points->npoints; j++)
+			{
+				rt_getPoint4d_p(ctx, tmp->points, j, &p);
+				ptarray_append_point(ctx, ptarray, &p, RT_TRUE);
+			}
+			rtline_free(ctx, tmp);
+		}
+		else if (geom->type == RTLINETYPE)
+		{
+			tmp = (RTLINE *)geom;
+			for (j = 0; j < tmp->points->npoints; j++)
+			{
+				rt_getPoint4d_p(ctx, tmp->points, j, &p);
+				ptarray_append_point(ctx, ptarray, &p, RT_TRUE);
+			}
+		}
+		else
+		{
+			rterror(ctx, "Unsupported geometry type %d found.",
+			        geom->type, rttype_name(ctx, geom->type));
+			return NULL;
+		}
+	}
+	ptarray_out = ptarray_remove_repeated_points(ctx, ptarray, 0.0);
+	ptarray_free(ctx, ptarray);
+	return rtline_construct(ctx, icompound->srid, NULL, ptarray_out);
+}
+
+RTPOLY *
+rtcurvepoly_stroke(const RTCTX *ctx, const RTCURVEPOLY *curvepoly, uint32_t perQuad)
+{
+	RTPOLY *ogeom;
+	RTGEOM *tmp;
+	RTLINE *line;
+	RTPOINTARRAY **ptarray;
+	int i;
+
+	RTDEBUG(2, "rtcurvepoly_stroke called.");
+
+	ptarray = rtalloc(ctx, sizeof(RTPOINTARRAY *)*curvepoly->nrings);
+
+	for (i = 0; i < curvepoly->nrings; i++)
+	{
+		tmp = curvepoly->rings[i];
+		if (tmp->type == RTCIRCSTRINGTYPE)
+		{
+			line = rtcircstring_stroke(ctx, (RTCIRCSTRING *)tmp, perQuad);
+			ptarray[i] = ptarray_clone_deep(ctx, line->points);
+			rtline_free(ctx, line);
+		}
+		else if (tmp->type == RTLINETYPE)
+		{
+			line = (RTLINE *)tmp;
+			ptarray[i] = ptarray_clone_deep(ctx, line->points);
+		}
+		else if (tmp->type == RTCOMPOUNDTYPE)
+		{
+			line = rtcompound_stroke(ctx, (RTCOMPOUND *)tmp, perQuad);
+			ptarray[i] = ptarray_clone_deep(ctx, line->points);
+			rtline_free(ctx, line);
+		}
+		else
+		{
+			rterror(ctx, "Invalid ring type found in CurvePoly.");
+			return NULL;
+		}
+	}
+
+	ogeom = rtpoly_construct(ctx, curvepoly->srid, NULL, curvepoly->nrings, ptarray);
+	return ogeom;
+}
+
+RTMLINE *
+rtmcurve_stroke(const RTCTX *ctx, const RTMCURVE *mcurve, uint32_t perQuad)
+{
+	RTMLINE *ogeom;
+	RTGEOM **lines;
+	int i;
+
+	RTDEBUGF(2, "rtmcurve_stroke called, geoms=%d, dim=%d.", mcurve->ngeoms, RTFLAGS_NDIMS(mcurve->flags));
+
+	lines = rtalloc(ctx, sizeof(RTGEOM *)*mcurve->ngeoms);
+
+	for (i = 0; i < mcurve->ngeoms; i++)
+	{
+		const RTGEOM *tmp = mcurve->geoms[i];
+		if (tmp->type == RTCIRCSTRINGTYPE)
+		{
+			lines[i] = (RTGEOM *)rtcircstring_stroke(ctx, (RTCIRCSTRING *)tmp, perQuad);
+		}
+		else if (tmp->type == RTLINETYPE)
+		{
+			lines[i] = (RTGEOM *)rtline_construct(ctx, mcurve->srid, NULL, ptarray_clone_deep(ctx, ((RTLINE *)tmp)->points));
+		}
+		else if (tmp->type == RTCOMPOUNDTYPE)
+		{
+			lines[i] = (RTGEOM *)rtcompound_stroke(ctx, (RTCOMPOUND *)tmp, perQuad);
+		}
+		else
+		{
+			rterror(ctx, "Unsupported geometry found in MultiCurve.");
+			return NULL;
+		}
+	}
+
+	ogeom = (RTMLINE *)rtcollection_construct(ctx, RTMULTILINETYPE, mcurve->srid, NULL, mcurve->ngeoms, lines);
+	return ogeom;
+}
+
+RTMPOLY *
+rtmsurface_stroke(const RTCTX *ctx, const RTMSURFACE *msurface, uint32_t perQuad)
+{
+	RTMPOLY *ogeom;
+	RTGEOM *tmp;
+	RTPOLY *poly;
+	RTGEOM **polys;
+	RTPOINTARRAY **ptarray;
+	int i, j;
+
+	RTDEBUG(2, "rtmsurface_stroke called.");
+
+	polys = rtalloc(ctx, sizeof(RTGEOM *)*msurface->ngeoms);
+
+	for (i = 0; i < msurface->ngeoms; i++)
+	{
+		tmp = msurface->geoms[i];
+		if (tmp->type == RTCURVEPOLYTYPE)
+		{
+			polys[i] = (RTGEOM *)rtcurvepoly_stroke(ctx, (RTCURVEPOLY *)tmp, perQuad);
+		}
+		else if (tmp->type == RTPOLYGONTYPE)
+		{
+			poly = (RTPOLY *)tmp;
+			ptarray = rtalloc(ctx, sizeof(RTPOINTARRAY *)*poly->nrings);
+			for (j = 0; j < poly->nrings; j++)
+			{
+				ptarray[j] = ptarray_clone_deep(ctx, poly->rings[j]);
+			}
+			polys[i] = (RTGEOM *)rtpoly_construct(ctx, msurface->srid, NULL, poly->nrings, ptarray);
+		}
+	}
+	ogeom = (RTMPOLY *)rtcollection_construct(ctx, RTMULTIPOLYGONTYPE, msurface->srid, NULL, msurface->ngeoms, polys);
+	return ogeom;
+}
+
+RTCOLLECTION *
+rtcollection_stroke(const RTCTX *ctx, const RTCOLLECTION *collection, uint32_t perQuad)
+{
+	RTCOLLECTION *ocol;
+	RTGEOM *tmp;
+	RTGEOM **geoms;
+	int i;
+
+	RTDEBUG(2, "rtcollection_stroke called.");
+
+	geoms = rtalloc(ctx, sizeof(RTGEOM *)*collection->ngeoms);
+
+	for (i=0; i<collection->ngeoms; i++)
+	{
+		tmp = collection->geoms[i];
+		switch (tmp->type)
+		{
+		case RTCIRCSTRINGTYPE:
+			geoms[i] = (RTGEOM *)rtcircstring_stroke(ctx, (RTCIRCSTRING *)tmp, perQuad);
+			break;
+		case RTCOMPOUNDTYPE:
+			geoms[i] = (RTGEOM *)rtcompound_stroke(ctx, (RTCOMPOUND *)tmp, perQuad);
+			break;
+		case RTCURVEPOLYTYPE:
+			geoms[i] = (RTGEOM *)rtcurvepoly_stroke(ctx, (RTCURVEPOLY *)tmp, perQuad);
+			break;
+		case RTCOLLECTIONTYPE:
+			geoms[i] = (RTGEOM *)rtcollection_stroke(ctx, (RTCOLLECTION *)tmp, perQuad);
+			break;
+		default:
+			geoms[i] = rtgeom_clone(ctx, tmp);
+			break;
+		}
+	}
+	ocol = rtcollection_construct(ctx, RTCOLLECTIONTYPE, collection->srid, NULL, collection->ngeoms, geoms);
+	return ocol;
+}
+
+RTGEOM *
+rtgeom_stroke(const RTCTX *ctx, const RTGEOM *geom, uint32_t perQuad)
+{
+	RTGEOM * ogeom = NULL;
+	switch (geom->type)
+	{
+	case RTCIRCSTRINGTYPE:
+		ogeom = (RTGEOM *)rtcircstring_stroke(ctx, (RTCIRCSTRING *)geom, perQuad);
+		break;
+	case RTCOMPOUNDTYPE:
+		ogeom = (RTGEOM *)rtcompound_stroke(ctx, (RTCOMPOUND *)geom, perQuad);
+		break;
+	case RTCURVEPOLYTYPE:
+		ogeom = (RTGEOM *)rtcurvepoly_stroke(ctx, (RTCURVEPOLY *)geom, perQuad);
+		break;
+	case RTMULTICURVETYPE:
+		ogeom = (RTGEOM *)rtmcurve_stroke(ctx, (RTMCURVE *)geom, perQuad);
+		break;
+	case RTMULTISURFACETYPE:
+		ogeom = (RTGEOM *)rtmsurface_stroke(ctx, (RTMSURFACE *)geom, perQuad);
+		break;
+	case RTCOLLECTIONTYPE:
+		ogeom = (RTGEOM *)rtcollection_stroke(ctx, (RTCOLLECTION *)geom, perQuad);
+		break;
+	default:
+		ogeom = rtgeom_clone(ctx, geom);
+	}
+	return ogeom;
+}
+
+/**
+ * Return ABC angle in radians
+ * TODO: move to rtalgorithm
+ */
+static double
+rt_arc_angle(const RTCTX *ctx, const RTPOINT2D *a, const RTPOINT2D *b, const RTPOINT2D *c)
+{
+  RTPOINT2D ab, cb;
+
+  ab.x = b->x - a->x;
+  ab.y = b->y - a->y;
+
+  cb.x = b->x - c->x;
+  cb.y = b->y - c->y;
+
+  double dot = (ab.x * cb.x + ab.y * cb.y); /* dot product */
+  double cross = (ab.x * cb.y - ab.y * cb.x); /* cross product */
+
+  double alpha = atan2(cross, dot);
+
+  return alpha;
+}
+
+/**
+* Returns RT_TRUE if b is on the arc formed by a1/a2/a3, but not within
+* that portion already described by a1/a2/a3
+*/
+static int pt_continues_arc(const RTCTX *ctx, const RTPOINT4D *a1, const RTPOINT4D *a2, const RTPOINT4D *a3, const RTPOINT4D *b)
+{
+	RTPOINT2D center;
+	RTPOINT2D *t1 = (RTPOINT2D*)a1;
+	RTPOINT2D *t2 = (RTPOINT2D*)a2;
+	RTPOINT2D *t3 = (RTPOINT2D*)a3;
+	RTPOINT2D *tb = (RTPOINT2D*)b;
+	double radius = rt_arc_center(ctx, t1, t2, t3, &center);
+	double b_distance, diff;
+
+	/* Co-linear a1/a2/a3 */
+	if ( radius < 0.0 )
+		return RT_FALSE;
+
+	b_distance = distance2d_pt_pt(ctx, tb, &center);
+	diff = fabs(radius - b_distance);
+	RTDEBUGF(4, "circle_radius=%g, b_distance=%g, diff=%g, percentage=%g", radius, b_distance, diff, diff/radius);
+	
+	/* Is the point b on the circle? */
+	if ( diff < EPSILON_SQLMM ) 
+	{
+		int a2_side = rt_segment_side(ctx, t1, t3, t2);
+		int b_side  = rt_segment_side(ctx, t1, t3, tb);
+		double angle1 = rt_arc_angle(ctx, t1, t2, t3);
+		double angle2 = rt_arc_angle(ctx, t2, t3, tb);
+
+		/* Is the angle similar to the previous one ? */
+		diff = fabs(angle1 - angle2);
+		RTDEBUGF(4, " angle1: %g, angle2: %g, diff:%g", angle1, angle2, diff);
+		if ( diff > EPSILON_SQLMM ) 
+		{
+			return RT_FALSE;
+		}
+
+		/* Is the point b on the same side of a1/a3 as the mid-point a2 is? */
+		/* If not, it's in the unbounded part of the circle, so it continues the arc, return true. */
+		if ( b_side != a2_side )
+			return RT_TRUE;
+	}
+	return RT_FALSE;
+}
+
+static RTGEOM*
+linestring_from_pa(const RTCTX *ctx, const RTPOINTARRAY *pa, int srid, int start, int end)
+{
+	int i = 0, j = 0;
+	RTPOINT4D p;
+	RTPOINTARRAY *pao = ptarray_construct(ctx, ptarray_has_z(ctx, pa), ptarray_has_m(ctx, pa), end-start+2);
+	RTDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end);
+	for( i = start; i < end + 2; i++ )
+	{
+		rt_getPoint4d_p(ctx, pa, i, &p);
+		ptarray_set_point4d(ctx, pao, j++, &p);	
+	}
+	return rtline_as_rtgeom(ctx, rtline_construct(ctx, srid, NULL, pao));
+}
+
+static RTGEOM*
+circstring_from_pa(const RTCTX *ctx, const RTPOINTARRAY *pa, int srid, int start, int end)
+{
+	
+	RTPOINT4D p0, p1, p2;
+	RTPOINTARRAY *pao = ptarray_construct(ctx, ptarray_has_z(ctx, pa), ptarray_has_m(ctx, pa), 3);
+	RTDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end);
+	rt_getPoint4d_p(ctx, pa, start, &p0);
+	ptarray_set_point4d(ctx, pao, 0, &p0);	
+	rt_getPoint4d_p(ctx, pa, (start+end+1)/2, &p1);
+	ptarray_set_point4d(ctx, pao, 1, &p1);	
+	rt_getPoint4d_p(ctx, pa, end+1, &p2);
+	ptarray_set_point4d(ctx, pao, 2, &p2);	
+	return rtcircstring_as_rtgeom(ctx, rtcircstring_construct(ctx, srid, NULL, pao));
+}
+
+static RTGEOM*
+geom_from_pa(const RTCTX *ctx, const RTPOINTARRAY *pa, int srid, int is_arc, int start, int end)
+{
+	RTDEBUGF(4, "srid=%d, is_arc=%d, start=%d, end=%d", srid, is_arc, start, end);
+	if ( is_arc )
+		return circstring_from_pa(ctx, pa, srid, start, end);
+	else
+		return linestring_from_pa(ctx, pa, srid, start, end);
+}
+
+RTGEOM*
+pta_unstroke(const RTCTX *ctx, const RTPOINTARRAY *points, int type, int srid)
+{
+	int i = 0, j, k;
+	RTPOINT4D a1, a2, a3, b;
+	RTPOINT4D first, center;
+	char *edges_in_arcs;
+	int found_arc = RT_FALSE;
+	int current_arc = 1;
+	int num_edges;
+	int edge_type; /* non-zero if edge is part of an arc */
+	int start, end;
+	RTCOLLECTION *outcol;
+	/* Minimum number of edges, per quadrant, required to define an arc */
+	const unsigned int min_quad_edges = 2;
+
+	/* Die on null input */
+	if ( ! points )
+		rterror(ctx, "pta_unstroke called with null pointarray");
+
+	/* Null on empty input? */
+	if ( points->npoints == 0 )
+		return NULL;
+	
+	/* We can't desegmentize anything shorter than four points */
+	if ( points->npoints < 4 )
+	{
+		/* Return a linestring here*/
+		rterror(ctx, "pta_unstroke needs implementation for npoints < 4");
+	}
+	
+	/* Allocate our result array of vertices that are part of arcs */
+	num_edges = points->npoints - 1;
+	edges_in_arcs = rtalloc(ctx, num_edges + 1);
+	memset(edges_in_arcs, 0, num_edges + 1);
+	
+	/* We make a candidate arc of the first two edges, */
+	/* And then see if the next edge follows it */
+	while( i < num_edges-2 )
+	{
+		unsigned int arc_edges;
+		double num_quadrants;
+		double angle;
+
+		found_arc = RT_FALSE;
+		/* Make candidate arc */
+		rt_getPoint4d_p(ctx, points, i  , &a1);
+		rt_getPoint4d_p(ctx, points, i+1, &a2);
+		rt_getPoint4d_p(ctx, points, i+2, &a3);
+		memcpy(&first, &a1, sizeof(RTPOINT4D));
+
+		for( j = i+3; j < num_edges+1; j++ )
+		{
+			RTDEBUGF(4, "i=%d, j=%d", i, j);
+			rt_getPoint4d_p(ctx, points, j, &b);
+			/* Does this point fall on our candidate arc? */
+			if ( pt_continues_arc(ctx, &a1, &a2, &a3, &b) )
+			{
+				/* Yes. Mark this edge and the two preceding it as arc components */
+				RTDEBUGF(4, "pt_continues_arc #%d", current_arc);
+				found_arc = RT_TRUE;
+				for ( k = j-1; k > j-4; k-- )
+					edges_in_arcs[k] = current_arc;
+			}
+			else
+			{
+				/* No. So we're done with this candidate arc */
+				RTDEBUG(4, "pt_continues_arc = false");
+				current_arc++;
+				break;
+			}
+
+			memcpy(&a1, &a2, sizeof(RTPOINT4D));
+			memcpy(&a2, &a3, sizeof(RTPOINT4D));
+			memcpy(&a3,  &b, sizeof(RTPOINT4D));
+		}
+		/* Jump past all the edges that were added to the arc */
+		if ( found_arc )
+		{
+			/* Check if an arc was composed by enough edges to be
+			 * really considered an arc
+			 * See http://trac.osgeo.org/postgis/ticket/2420
+			 */
+			arc_edges = j - 1 - i;
+			RTDEBUGF(4, "arc defined by %d edges found", arc_edges);
+			if ( first.x == b.x && first.y == b.y ) {
+				RTDEBUG(4, "arc is a circle");
+				num_quadrants = 4;
+			}
+			else {
+				rt_arc_center(ctx, (RTPOINT2D*)&first, (RTPOINT2D*)&b, (RTPOINT2D*)&a1, (RTPOINT2D*)&center);
+				angle = rt_arc_angle(ctx, (RTPOINT2D*)&first, (RTPOINT2D*)&center, (RTPOINT2D*)&b);
+        int p2_side = rt_segment_side(ctx, (RTPOINT2D*)&first, (RTPOINT2D*)&a1, (RTPOINT2D*)&b);
+        if ( p2_side >= 0 ) angle = -angle; 
+
+				if ( angle < 0 ) angle = 2 * M_PI + angle;
+				num_quadrants = ( 4 * angle ) / ( 2 * M_PI );
+				RTDEBUGF(4, "arc angle (%g %g, %g %g, %g %g) is %g (side is %d), quandrants:%g", first.x, first.y, center.x, center.y, b.x, b.y, angle, p2_side, num_quadrants);
+			}
+			/* a1 is first point, b is last point */
+			if ( arc_edges < min_quad_edges * num_quadrants ) {
+				RTDEBUGF(4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants);
+				for ( k = j-1; k >= i; k-- )
+					edges_in_arcs[k] = 0;
+			}
+
+			i = j-1;
+		}
+		else
+		{
+			/* Mark this edge as a linear edge */
+			edges_in_arcs[i] = 0;
+			i = i+1;
+		}
+	}
+	
+#if RTGEOM_DEBUG_LEVEL > 3
+	{
+		char *edgestr = rtalloc(ctx, num_edges+1);
+		for ( i = 0; i < num_edges; i++ )
+		{
+			if ( edges_in_arcs[i] )
+				edgestr[i] = 48 + edges_in_arcs[i];
+			else
+				edgestr[i] = '.';
+		}
+		edgestr[num_edges] = 0;
+		RTDEBUGF(3, "edge pattern %s", edgestr);
+		rtfree(ctx, edgestr);
+	}
+#endif
+
+	start = 0;
+	edge_type = edges_in_arcs[0];
+	outcol = rtcollection_construct_empty(ctx, RTCOMPOUNDTYPE, srid, ptarray_has_z(ctx, points), ptarray_has_m(ctx, points));
+	for( i = 1; i < num_edges; i++ )
+	{
+		if( edge_type != edges_in_arcs[i] )
+		{
+			end = i - 1;
+			rtcollection_add_rtgeom(ctx, outcol, geom_from_pa(ctx, points, srid, edge_type, start, end));
+			start = i;
+			edge_type = edges_in_arcs[i];
+		}
+	}
+	rtfree(ctx, edges_in_arcs); /* not needed anymore */
+
+	/* Roll out last item */
+	end = num_edges - 1;
+	rtcollection_add_rtgeom(ctx, outcol, geom_from_pa(ctx, points, srid, edge_type, start, end));
+	
+	/* Strip down to singleton if only one entry */
+	if ( outcol->ngeoms == 1 )
+	{
+		RTGEOM *outgeom = outcol->geoms[0];
+		outcol->ngeoms = 0; rtcollection_free(ctx, outcol);
+		return outgeom;
+	}
+	return rtcollection_as_rtgeom(ctx, outcol);
+}
+
+
+RTGEOM *
+rtline_unstroke(const RTCTX *ctx, const RTLINE *line)
+{
+	RTDEBUG(2, "rtline_unstroke called.");
+
+	if ( line->points->npoints < 4 ) return rtline_as_rtgeom(ctx, rtline_clone(ctx, line));
+	else return pta_unstroke(ctx, line->points, line->flags, line->srid);
+}
+
+RTGEOM *
+rtpolygon_unstroke(const RTCTX *ctx, const RTPOLY *poly)
+{
+	RTGEOM **geoms;
+	int i, hascurve = 0;
+
+	RTDEBUG(2, "rtpolygon_unstroke called.");
+
+	geoms = rtalloc(ctx, sizeof(RTGEOM *)*poly->nrings);
+	for (i=0; i<poly->nrings; i++)
+	{
+		geoms[i] = pta_unstroke(ctx, poly->rings[i], poly->flags, poly->srid);
+		if (geoms[i]->type == RTCIRCSTRINGTYPE || geoms[i]->type == RTCOMPOUNDTYPE)
+		{
+			hascurve = 1;
+		}
+	}
+	if (hascurve == 0)
+	{
+		for (i=0; i<poly->nrings; i++)
+		{
+			rtfree(ctx, geoms[i]); /* TODO: should this be rtgeom_free instead ? */
+		}
+		return rtgeom_clone(ctx, (RTGEOM *)poly);
+	}
+
+	return (RTGEOM *)rtcollection_construct(ctx, RTCURVEPOLYTYPE, poly->srid, NULL, poly->nrings, geoms);
+}
+
+RTGEOM *
+rtmline_unstroke(const RTCTX *ctx, const RTMLINE *mline)
+{
+	RTGEOM **geoms;
+	int i, hascurve = 0;
+
+	RTDEBUG(2, "rtmline_unstroke called.");
+
+	geoms = rtalloc(ctx, sizeof(RTGEOM *)*mline->ngeoms);
+	for (i=0; i<mline->ngeoms; i++)
+	{
+		geoms[i] = rtline_unstroke(ctx, (RTLINE *)mline->geoms[i]);
+		if (geoms[i]->type == RTCIRCSTRINGTYPE || geoms[i]->type == RTCOMPOUNDTYPE)
+		{
+			hascurve = 1;
+		}
+	}
+	if (hascurve == 0)
+	{
+		for (i=0; i<mline->ngeoms; i++)
+		{
+			rtfree(ctx, geoms[i]); /* TODO: should this be rtgeom_free instead ? */
+		}
+		return rtgeom_clone(ctx, (RTGEOM *)mline);
+	}
+	return (RTGEOM *)rtcollection_construct(ctx, RTMULTICURVETYPE, mline->srid, NULL, mline->ngeoms, geoms);
+}
+
+RTGEOM * 
+rtmpolygon_unstroke(const RTCTX *ctx, const RTMPOLY *mpoly)
+{
+	RTGEOM **geoms;
+	int i, hascurve = 0;
+
+	RTDEBUG(2, "rtmpoly_unstroke called.");
+
+	geoms = rtalloc(ctx, sizeof(RTGEOM *)*mpoly->ngeoms);
+	for (i=0; i<mpoly->ngeoms; i++)
+	{
+		geoms[i] = rtpolygon_unstroke(ctx, (RTPOLY *)mpoly->geoms[i]);
+		if (geoms[i]->type == RTCURVEPOLYTYPE)
+		{
+			hascurve = 1;
+		}
+	}
+	if (hascurve == 0)
+	{
+		for (i=0; i<mpoly->ngeoms; i++)
+		{
+			rtfree(ctx, geoms[i]); /* TODO: should this be rtgeom_free instead ? */
+		}
+		return rtgeom_clone(ctx, (RTGEOM *)mpoly);
+	}
+	return (RTGEOM *)rtcollection_construct(ctx, RTMULTISURFACETYPE, mpoly->srid, NULL, mpoly->ngeoms, geoms);
+}
+
+RTGEOM *
+rtgeom_unstroke(const RTCTX *ctx, const RTGEOM *geom)
+{
+	RTDEBUG(2, "rtgeom_unstroke called.");
+
+	switch (geom->type)
+	{
+	case RTLINETYPE:
+		return rtline_unstroke(ctx, (RTLINE *)geom);
+	case RTPOLYGONTYPE:
+		return rtpolygon_unstroke(ctx, (RTPOLY *)geom);
+	case RTMULTILINETYPE:
+		return rtmline_unstroke(ctx, (RTMLINE *)geom);
+	case RTMULTIPOLYGONTYPE:
+		return rtmpolygon_unstroke(ctx, (RTMPOLY *)geom);
+	default:
+		return rtgeom_clone(ctx, geom);
+	}
+}
+
diff --git a/src/rttin.c b/src/rttin.c
new file mode 100644
index 0000000..0e49545
--- /dev/null
+++ b/src/rttin.c
@@ -0,0 +1,189 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2001-2006 Refractions Research Inc.
+ *
+ **********************************************************************/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+RTTIN* rttin_add_rttriangle(const RTCTX *ctx, RTTIN *mobj, const RTTRIANGLE *obj)
+{
+	return (RTTIN*)rtcollection_add_rtgeom(ctx, (RTCOLLECTION*)mobj, (RTGEOM*)obj);
+}
+
+void rttin_free(const RTCTX *ctx, RTTIN *tin)
+{
+	int i;
+	if ( ! tin ) return;
+	if ( tin->bbox )
+		rtfree(ctx, tin->bbox);
+
+	for ( i = 0; i < tin->ngeoms; i++ )
+		if ( tin->geoms && tin->geoms[i] )
+			rttriangle_free(ctx, tin->geoms[i]);
+
+	if ( tin->geoms )
+		rtfree(ctx, tin->geoms);
+
+	rtfree(ctx, tin);
+}
+
+
+void printRTTIN(const RTCTX *ctx, RTTIN *tin)
+{
+	int i;
+	RTTRIANGLE *triangle;
+
+	if (tin->type != RTTINTYPE)
+		rterror(ctx, "printRTTIN called with something else than a TIN");
+
+	rtnotice(ctx, "RTTIN {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(tin->flags));
+	rtnotice(ctx, "    SRID = %i", (int)tin->srid);
+	rtnotice(ctx, "    ngeoms = %i", (int)tin->ngeoms);
+
+	for (i=0; i<tin->ngeoms; i++)
+	{
+		triangle = (RTTRIANGLE *) tin->geoms[i];
+		printPA(ctx, triangle->points);
+	}
+	rtnotice(ctx, "}");
+}
+
+
+/*
+ * TODO rewrite all this stuff to be based on a truly topological model
+ */
+
+struct struct_tin_arcs
+{
+	double ax, ay, az;
+	double bx, by, bz;
+	int cnt, face;
+};
+typedef struct struct_tin_arcs *tin_arcs;
+
+/* We supposed that the geometry is valid
+   we could have wrong result if not */
+int rttin_is_closed(const RTCTX *ctx, const RTTIN *tin)
+{
+	int i, j, k;
+	int narcs, carc;
+	int found;
+	tin_arcs arcs;
+	RTPOINT4D pa, pb;
+	RTTRIANGLE *patch;
+
+	/* If surface is not 3D, it's can't be closed */
+	if (!RTFLAGS_GET_Z(tin->flags)) return 0;
+
+	/* Max theorical arcs number if no one is shared ... */
+	narcs = 3 * tin->ngeoms;
+
+	arcs = rtalloc(ctx, sizeof(struct struct_tin_arcs) * narcs);
+	for (i=0, carc=0; i < tin->ngeoms ; i++)
+	{
+
+		patch = (RTTRIANGLE *) tin->geoms[i];
+		for (j=0; j < 3 ; j++)
+		{
+
+			rt_getPoint4d_p(ctx, patch->points, j,   &pa);
+			rt_getPoint4d_p(ctx, patch->points, j+1, &pb);
+
+			/* Make sure to order the 'lower' point first */
+			if ( (pa.x > pb.x) ||
+			        (pa.x == pb.x && pa.y > pb.y) ||
+			        (pa.x == pb.x && pa.y == pb.y && pa.z > pb.z) )
+			{
+				pa = pb;
+				rt_getPoint4d_p(ctx, patch->points, j, &pb);
+			}
+
+			for (found=0, k=0; k < carc ; k++)
+			{
+
+				if (  ( arcs[k].ax == pa.x && arcs[k].ay == pa.y &&
+				        arcs[k].az == pa.z && arcs[k].bx == pb.x &&
+				        arcs[k].by == pb.y && arcs[k].bz == pb.z &&
+				        arcs[k].face != i) )
+				{
+					arcs[k].cnt++;
+					found = 1;
+
+					/* Look like an invalid TIN
+					      anyway not a closed one */
+					if (arcs[k].cnt > 2)
+					{
+						rtfree(ctx, arcs);
+						return 0;
+					}
+				}
+			}
+
+			if (!found)
+			{
+				arcs[carc].cnt=1;
+				arcs[carc].face=i;
+				arcs[carc].ax = pa.x;
+				arcs[carc].ay = pa.y;
+				arcs[carc].az = pa.z;
+				arcs[carc].bx = pb.x;
+				arcs[carc].by = pb.y;
+				arcs[carc].bz = pb.z;
+				carc++;
+
+				/* Look like an invalid TIN
+				      anyway not a closed one */
+				if (carc > narcs)
+				{
+					rtfree(ctx, arcs);
+					return 0;
+				}
+			}
+		}
+	}
+
+	/* A TIN is closed if each edge
+	       is shared by exactly 2 faces */
+	for (k=0; k < carc ; k++)
+	{
+		if (arcs[k].cnt != 2)
+		{
+			rtfree(ctx, arcs);
+			return 0;
+		}
+	}
+	rtfree(ctx, arcs);
+
+	/* Invalid TIN case */
+	if (carc < tin->ngeoms) return 0;
+
+	return 1;
+}
diff --git a/src/rttopo_config.h.in b/src/rttopo_config.h.in
new file mode 100644
index 0000000..38736fe
--- /dev/null
+++ b/src/rttopo_config.h.in
@@ -0,0 +1,27 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+#undef LIBRTGEOM_VERSION
+#undef RTGEOM_DEBUG_LEVEL
+#undef RTGEOM_GEOS_VERSION
diff --git a/src/rttree.c b/src/rttree.c
new file mode 100644
index 0000000..a172ba8
--- /dev/null
+++ b/src/rttree.c
@@ -0,0 +1,249 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+#include "rttree.h"
+
+
+/**
+* Internal nodes have their point references set to NULL.
+*/
+static int rect_node_is_leaf(const RTCTX *ctx, const RECT_NODE *node)
+{
+	return (node->p1 != NULL);
+}
+
+/**
+* Recurse from top of node tree and free all children.
+* does not free underlying point array.
+*/
+void rect_tree_free(const RTCTX *ctx, RECT_NODE *node)
+{
+	if ( node->left_node )
+	{
+		rect_tree_free(ctx, node->left_node);
+		node->left_node = 0;
+	}
+	if ( node->right_node )
+	{
+		rect_tree_free(ctx, node->right_node);
+		node->right_node = 0;
+	}
+	rtfree(ctx, node);
+}
+
+/* 0 => no containment */
+int rect_tree_contains_point(const RTCTX *ctx, const RECT_NODE *node, const RTPOINT2D *pt, int *on_boundary)
+{
+	if ( FP_CONTAINS_INCL(node->ymin, pt->y, node->ymax) )
+	{
+		if ( rect_node_is_leaf(ctx, node) )
+		{
+			double side = rt_segment_side(ctx, node->p1, node->p2, pt);
+			if ( side == 0 )
+				*on_boundary = RT_TRUE;
+			return (side < 0 ? -1 : 1 );
+		}
+		else
+		{
+			return rect_tree_contains_point(ctx, node->left_node, pt, on_boundary) +
+			       rect_tree_contains_point(ctx, node->right_node, pt, on_boundary);
+		}
+	}
+	/* printf("NOT in measure range\n"); */
+	return 0;
+}
+
+int rect_tree_intersects_tree(const RTCTX *ctx, const RECT_NODE *n1, const RECT_NODE *n2)
+{
+	RTDEBUGF(4,"n1 (%.9g %.9g,%.9g %.9g) vs n2 (%.9g %.9g,%.9g %.9g)",n1->xmin,n1->ymin,n1->xmax,n1->ymax,n2->xmin,n2->ymin,n2->xmax,n2->ymax);
+	/* There can only be an edge intersection if the rectangles overlap */
+	if ( ! ( FP_GT(n1->xmin, n2->xmax) || FP_GT(n2->xmin, n1->xmax) || FP_GT(n1->ymin, n2->ymax) || FP_GT(n2->ymin, n1->ymax) ) )
+	{
+		RTDEBUG(4," interaction found");
+		/* We can only test for a true intersection if the nodes are both leaf nodes */
+		if ( rect_node_is_leaf(ctx, n1) && rect_node_is_leaf(ctx, n2) )
+		{
+			RTDEBUG(4,"  leaf node test");
+			/* Check for true intersection */
+			if ( rt_segment_intersects(ctx, n1->p1, n1->p2, n2->p1, n2->p2) )
+				return RT_TRUE;
+			else
+				return RT_FALSE;
+		}
+		else
+		{
+			RTDEBUG(4,"  internal node found, recursing");
+			/* Recurse to children */
+			if ( rect_node_is_leaf(ctx, n1) )
+			{
+				if ( rect_tree_intersects_tree(ctx, n2->left_node, n1) || rect_tree_intersects_tree(ctx, n2->right_node, n1) )
+					return RT_TRUE;
+				else
+					return RT_FALSE;
+			}
+			else
+			{
+				if ( rect_tree_intersects_tree(ctx, n1->left_node, n2) || rect_tree_intersects_tree(ctx, n1->right_node, n2) )
+					return RT_TRUE;
+				else
+					return RT_FALSE;
+			}
+		}
+	}
+	else
+	{
+		RTDEBUG(4," no interaction found");
+		return RT_FALSE;
+	}
+}
+
+
+/**
+* Create a new leaf node, calculating a measure value for each point on the
+* edge and storing pointers back to the end points for later.
+*/
+RECT_NODE* rect_node_leaf_new(const RTCTX *ctx, const RTPOINTARRAY *pa, int i)
+{
+	RTPOINT2D *p1, *p2;
+	RECT_NODE *node;
+
+	p1 = (RTPOINT2D*)rt_getPoint_internal(ctx, pa, i);
+	p2 = (RTPOINT2D*)rt_getPoint_internal(ctx, pa, i+1);
+
+	/* Zero length edge, doesn't get a node */
+	if ( FP_EQUALS(p1->x, p2->x) && FP_EQUALS(p1->y, p2->y) )
+		return NULL;
+
+	node = rtalloc(ctx, sizeof(RECT_NODE));
+	node->p1 = p1;
+	node->p2 = p2;
+	node->xmin = FP_MIN(p1->x,p2->x);
+	node->xmax = FP_MAX(p1->x,p2->x);
+	node->ymin = FP_MIN(p1->y,p2->y);
+	node->ymax = FP_MAX(p1->y,p2->y);
+	node->left_node = NULL;
+	node->right_node = NULL;
+	return node;
+}
+
+/**
+* Create a new internal node, calculating the new measure range for the node,
+* and storing pointers to the child nodes.
+*/
+RECT_NODE* rect_node_internal_new(const RTCTX *ctx, RECT_NODE *left_node, RECT_NODE *right_node)
+{
+	RECT_NODE *node = rtalloc(ctx, sizeof(RECT_NODE));
+	node->p1 = NULL;
+	node->p2 = NULL;
+	node->xmin = FP_MIN(left_node->xmin, right_node->xmin);
+	node->xmax = FP_MAX(left_node->xmax, right_node->xmax);
+	node->ymin = FP_MIN(left_node->ymin, right_node->ymin);
+	node->ymax = FP_MAX(left_node->ymax, right_node->ymax);
+	node->left_node = left_node;
+	node->right_node = right_node;
+	return node;
+}
+
+/**
+* Build a tree of nodes from a point array, one node per edge, and each
+* with an associated measure range along a one-dimensional space. We
+* can then search that space as a range tree.
+*/
+RECT_NODE* rect_tree_new(const RTCTX *ctx, const RTPOINTARRAY *pa)
+{
+	int num_edges, num_children, num_parents;
+	int i, j;
+	RECT_NODE **nodes;
+	RECT_NODE *node;
+	RECT_NODE *tree;
+
+	if ( pa->npoints < 2 )
+	{
+		return NULL;
+	}
+
+	/*
+	** First create a flat list of nodes, one per edge.
+	** For each vertex, transform into our one-dimensional measure.
+	** Hopefully, when projected, the points turn into a fairly
+	** uniformly distributed collection of measures.
+	*/
+	num_edges = pa->npoints - 1;
+	nodes = rtalloc(ctx, sizeof(RECT_NODE*) * pa->npoints);
+	j = 0;
+	for ( i = 0; i < num_edges; i++ )
+	{
+		node = rect_node_leaf_new(ctx, pa, i);
+		if ( node ) /* Not zero length? */
+		{
+			nodes[j] = node;
+			j++;
+		}
+	}
+
+	/*
+	** If we sort the nodelist first, we'll get a more balanced tree
+	** in the end, but at the cost of sorting. For now, we just
+	** build the tree knowing that point arrays tend to have a
+	** reasonable amount of sorting already.
+	*/
+
+	num_children = j;
+	num_parents = num_children / 2;
+	while ( num_parents > 0 )
+	{
+		j = 0;
+		while ( j < num_parents )
+		{
+			/*
+			** Each new parent includes pointers to the children, so even though
+			** we are over-writing their place in the list, we still have references
+			** to them via the tree.
+			*/
+			nodes[j] = rect_node_internal_new(ctx, nodes[2*j], nodes[(2*j)+1]);
+			j++;
+		}
+		/* Odd number of children, just copy the last node up a level */
+		if ( num_children % 2 )
+		{
+			nodes[j] = nodes[num_children - 1];
+			num_parents++;
+		}
+		num_children = num_parents;
+		num_parents = num_children / 2;
+	}
+
+	/* Take a reference to the head of the tree*/
+	tree = nodes[0];
+
+	/* Free the old list structure, leaving the tree in place */
+	rtfree(ctx, nodes);
+
+	return tree;
+
+}
+
diff --git a/src/rttree.h b/src/rttree.h
new file mode 100644
index 0000000..a38e707
--- /dev/null
+++ b/src/rttree.h
@@ -0,0 +1,43 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+typedef struct rect_node
+{
+	double xmin;
+	double xmax;
+	double ymin;
+	double ymax;
+	struct rect_node *left_node;
+	struct rect_node *right_node;
+	RTPOINT2D *p1;
+	RTPOINT2D *p2;
+} RECT_NODE;	
+
+int rect_tree_contains_point(const RTCTX *ctx, const RECT_NODE *tree, const RTPOINT2D *pt, int *on_boundary);
+int rect_tree_intersects_tree(const RTCTX *ctx, const RECT_NODE *tree1, const RECT_NODE *tree2);
+void rect_tree_free(const RTCTX *ctx, RECT_NODE *node);
+RECT_NODE* rect_node_leaf_new(const RTCTX *ctx, const RTPOINTARRAY *pa, int i);
+RECT_NODE* rect_node_internal_new(const RTCTX *ctx, RECT_NODE *left_node, RECT_NODE *right_node);
+RECT_NODE* rect_tree_new(const RTCTX *ctx, const RTPOINTARRAY *pa);
diff --git a/src/rttriangle.c b/src/rttriangle.c
new file mode 100644
index 0000000..4524c3d
--- /dev/null
+++ b/src/rttriangle.c
@@ -0,0 +1,225 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2010 - Oslandia
+ *
+ **********************************************************************/
+
+
+
+/* basic RTTRIANGLE manipulation */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+
+
+/* construct a new RTTRIANGLE.
+ * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
+ */
+RTTRIANGLE*
+rttriangle_construct(const RTCTX *ctx, int srid, RTGBOX *bbox, RTPOINTARRAY *points)
+{
+	RTTRIANGLE *result;
+
+	result = (RTTRIANGLE*) rtalloc(ctx, sizeof(RTTRIANGLE));
+	result->type = RTTRIANGLETYPE;
+
+	result->flags = points->flags;
+	RTFLAGS_SET_BBOX(result->flags, bbox?1:0);
+	
+	result->srid = srid;
+	result->points = points;
+	result->bbox = bbox;
+
+	return result;
+}
+
+RTTRIANGLE*
+rttriangle_construct_empty(const RTCTX *ctx, int srid, char hasz, char hasm)
+{
+	RTTRIANGLE *result = rtalloc(ctx, sizeof(RTTRIANGLE));
+	result->type = RTTRIANGLETYPE;
+	result->flags = gflags(ctx, hasz,hasm,0);
+	result->srid = srid;
+	result->points = ptarray_construct_empty(ctx, hasz, hasm, 1);
+	result->bbox = NULL;
+	return result;
+}
+
+void rttriangle_free(const RTCTX *ctx, RTTRIANGLE  *triangle)
+{
+	if ( ! triangle ) return;
+	
+	if (triangle->bbox)
+		rtfree(ctx, triangle->bbox);
+		
+	if (triangle->points)
+		ptarray_free(ctx, triangle->points);
+		
+	rtfree(ctx, triangle);
+}
+
+void printRTTRIANGLE(const RTCTX *ctx, RTTRIANGLE *triangle)
+{
+	if (triangle->type != RTTRIANGLETYPE)
+                rterror(ctx, "printRTTRIANGLE called with something else than a Triangle");
+
+	rtnotice(ctx, "RTTRIANGLE {");
+	rtnotice(ctx, "    ndims = %i", (int)RTFLAGS_NDIMS(triangle->flags));
+	rtnotice(ctx, "    SRID = %i", (int)triangle->srid);
+	printPA(ctx, triangle->points);
+	rtnotice(ctx, "}");
+}
+
+/* @brief Clone RTTRIANGLE object. Serialized point lists are not copied.
+ *
+ * @see ptarray_clone 
+ */
+RTTRIANGLE *
+rttriangle_clone(const RTCTX *ctx, const RTTRIANGLE *g)
+{
+	RTDEBUGF(2, "rttriangle_clone called with %p", g);
+	return (RTTRIANGLE *)rtline_clone(ctx, (const RTLINE *)g);
+}
+
+void
+rttriangle_force_clockwise(const RTCTX *ctx, RTTRIANGLE *triangle)
+{
+	if ( ptarray_isccw(ctx, triangle->points) )
+		ptarray_reverse(ctx, triangle->points);
+}
+
+void
+rttriangle_reverse(const RTCTX *ctx, RTTRIANGLE *triangle)
+{
+	if( rttriangle_is_empty(ctx, triangle) ) return;
+	ptarray_reverse(ctx, triangle->points);
+}
+
+void
+rttriangle_release(const RTCTX *ctx, RTTRIANGLE *rttriangle)
+{
+	rtgeom_release(ctx, rttriangle_as_rtgeom(ctx, rttriangle));
+}
+
+/* check coordinate equality  */
+char
+rttriangle_same(const RTCTX *ctx, const RTTRIANGLE *t1, const RTTRIANGLE *t2)
+{
+	char r = ptarray_same(ctx, t1->points, t2->points);
+	RTDEBUGF(5, "returning %d", r);
+	return r;
+}
+
+/*
+ * Construct a triangle from a RTLINE being
+ * the shell
+ * Pointarray from intput geom are cloned.
+ * Input line must have 4 points, and be closed.
+ */
+RTTRIANGLE *
+rttriangle_from_rtline(const RTCTX *ctx, const RTLINE *shell)
+{
+	RTTRIANGLE *ret;
+	RTPOINTARRAY *pa;
+
+	if ( shell->points->npoints != 4 )
+		rterror(ctx, "rttriangle_from_rtline: shell must have exactly 4 points");
+
+	if (   (!RTFLAGS_GET_Z(shell->flags) && !ptarray_is_closed_2d(ctx, shell->points)) ||
+	        (RTFLAGS_GET_Z(shell->flags) && !ptarray_is_closed_3d(ctx, shell->points)) )
+		rterror(ctx, "rttriangle_from_rtline: shell must be closed");
+
+	pa = ptarray_clone_deep(ctx, shell->points);
+	ret = rttriangle_construct(ctx, shell->srid, NULL, pa);
+
+	if (rttriangle_is_repeated_points(ctx, ret))
+		rterror(ctx, "rttriangle_from_rtline: some points are repeated in triangle");
+
+	return ret;
+}
+
+char
+rttriangle_is_repeated_points(const RTCTX *ctx, RTTRIANGLE *triangle)
+{
+	char ret;
+	RTPOINTARRAY *pa;
+
+	pa = ptarray_remove_repeated_points(ctx, triangle->points, 0.0);
+	ret = ptarray_same(ctx, pa, triangle->points);
+	ptarray_free(ctx, pa);
+
+	return ret;
+}
+
+int rttriangle_is_empty(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	if ( !triangle->points || triangle->points->npoints < 1 )
+		return RT_TRUE;
+	return RT_FALSE;
+}
+
+/**
+ * Find the area of the outer ring 
+ */
+double
+rttriangle_area(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	double area=0.0;
+	int i;
+	RTPOINT2D p1;
+	RTPOINT2D p2;
+
+	if (! triangle->points->npoints) return area; /* empty triangle */
+
+	for (i=0; i < triangle->points->npoints-1; i++)
+	{
+		rt_getPoint2d_p(ctx, triangle->points, i, &p1);
+		rt_getPoint2d_p(ctx, triangle->points, i+1, &p2);
+		area += ( p1.x * p2.y ) - ( p1.y * p2.x );
+	}
+
+	area  /= 2.0;
+
+	return fabs(area);
+}
+
+
+double
+rttriangle_perimeter(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	if( triangle->points ) 
+		return ptarray_length(ctx, triangle->points);
+	else 
+		return 0.0;
+}
+
+double
+rttriangle_perimeter_2d(const RTCTX *ctx, const RTTRIANGLE *triangle)
+{
+	if( triangle->points ) 
+		return ptarray_length_2d(ctx, triangle->points);
+	else 
+		return 0.0;
+}
diff --git a/src/rtutil.c b/src/rtutil.c
new file mode 100644
index 0000000..bbb82ae
--- /dev/null
+++ b/src/rtutil.c
@@ -0,0 +1,401 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * ^copyright^
+ *
+ **********************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h> /* for tolower */
+
+/* Global variables */
+#include "rttopo_config.h"
+#include "librtgeom_internal.h"
+#include "rtgeom_log.h"
+
+/* Default allocators */
+static void * default_allocator(size_t size);
+static void default_freeor(void *mem);
+static void * default_reallocator(void *mem, size_t size);
+
+#define RT_MSG_MAXLEN 256
+
+static char *rtgeomTypeName[] =
+{
+	"Unknown",
+	"Point",
+	"LineString",
+	"Polygon",
+	"MultiPoint",
+	"MultiLineString",
+	"MultiPolygon",
+	"GeometryCollection",
+	"CircularString",
+	"CompoundCurve",
+	"CurvePolygon",
+	"MultiCurve",
+	"MultiSurface",
+	"PolyhedralSurface",
+	"Triangle",
+	"Tin"
+};
+
+/*
+ * Default rtnotice/rterror handlers
+ *
+ * Since variadic functions cannot pass their parameters directly, we need
+ * wrappers for these functions to convert the arguments into a va_list
+ * structure.
+ */
+
+void
+rtnotice(const RTCTX *ctx, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	/* Call the supplied function */
+	(*ctx->notice_logger)(fmt, ap, ctx->notice_logger_arg);
+
+	va_end(ap);
+}
+
+void
+rterror(const RTCTX *ctx, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	/* Call the supplied function */
+	(*ctx->error_logger)(fmt, ap, ctx->error_logger_arg);
+
+	va_end(ap);
+}
+
+void
+rtdebug(const RTCTX *ctx, int level, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	/* Call the supplied function */
+	(*ctx->debug_logger)(level, fmt, ap, ctx->debug_logger_arg);
+
+	va_end(ap);
+}
+
+/*
+ * Default allocators
+ *
+ * We include some default allocators that use malloc/free/realloc
+ * along with stdout/stderr since this is the most common use case
+ *
+ */
+
+static void *
+default_allocator(size_t size)
+{
+	void *mem = malloc(size);
+	return mem;
+}
+
+static void
+default_freeor(void *mem)
+{
+	free(mem);
+}
+
+static void *
+default_reallocator(void *mem, size_t size)
+{
+	void *ret = realloc(mem, size);
+	return ret;
+}
+
+static void
+default_noticereporter(const char *fmt, va_list ap, void *arg)
+{
+	char msg[RT_MSG_MAXLEN+1];
+	vsnprintf (msg, RT_MSG_MAXLEN, fmt, ap);
+	msg[RT_MSG_MAXLEN]='\0';
+	printf("%s\n", msg);
+}
+
+static void
+default_debuglogger(int level, const char *fmt, va_list ap, void *arg)
+{
+	char msg[RT_MSG_MAXLEN+1];
+	if ( RTGEOM_DEBUG_LEVEL >= level )
+	{
+		/* Space pad the debug output */
+		int i;
+		for ( i = 0; i < level; i++ )
+			msg[i] = ' ';
+		vsnprintf(msg+i, RT_MSG_MAXLEN-i, fmt, ap);
+		msg[RT_MSG_MAXLEN]='\0';
+		printf("%s\n", msg);
+	}
+}
+
+static void
+default_errorreporter(const char *fmt, va_list ap, void *arg)
+{
+	char msg[RT_MSG_MAXLEN+1];
+	vsnprintf (msg, RT_MSG_MAXLEN, fmt, ap);
+	msg[RT_MSG_MAXLEN]='\0';
+	fprintf(stderr, "%s\n", msg);
+	exit(1);
+}
+
+RTCTX *
+rtgeom_init(rtallocator allocator,
+                   rtreallocator reallocator,
+                   rtfreeor freeor)
+{
+  RTCTX *ctx = allocator(sizeof(RTCTX));
+
+  memset(ctx, '\0', sizeof(RTCTX));
+
+  ctx->rtalloc_var = default_allocator;
+  ctx->rtrealloc_var = default_reallocator;
+  ctx->rtfree_var = default_freeor;
+
+	if ( allocator ) ctx->rtalloc_var = allocator;
+	if ( reallocator ) ctx->rtrealloc_var = reallocator;
+	if ( freeor ) ctx->rtfree_var = freeor;
+
+  ctx->notice_logger = default_noticereporter;
+  ctx->error_logger = default_errorreporter;
+  ctx->debug_logger = default_debuglogger;
+
+  return ctx;
+}
+
+void
+rtgeom_set_error_logger(RTCTX *ctx, rtreporter logger, void *arg)
+{
+  ctx->error_logger = logger;
+  ctx->error_logger_arg = arg;
+}
+
+void
+rtgeom_set_notice_logger(RTCTX *ctx, rtreporter logger, void *arg)
+{
+  ctx->notice_logger = logger;
+  ctx->notice_logger_arg = arg;
+}
+
+void
+rtgeom_set_debug_logger(RTCTX *ctx, rtdebuglogger logger, void *arg)
+{
+  ctx->debug_logger = logger;
+  ctx->debug_logger_arg = arg;
+}
+
+const char* 
+rttype_name(const RTCTX *ctx, uint8_t type)
+{
+	if ( type > 15 )
+	{
+		/* assert(0); */
+		return "Invalid type";
+	}
+	return rtgeomTypeName[(int ) type];
+}
+
+void *
+rtalloc(const RTCTX *ctx, size_t size)
+{
+	void *mem = ctx->rtalloc_var(size);
+	RTDEBUGF(5, "rtalloc: %d@%p", size, mem);
+	return mem;
+}
+
+void *
+rtrealloc(const RTCTX *ctx, void *mem, size_t size)
+{
+	RTDEBUGF(5, "rtrealloc: %d@%p", size, mem);
+	return ctx->rtrealloc_var(mem, size);
+}
+
+void
+rtfree(const RTCTX *ctx, void *mem)
+{
+	ctx->rtfree_var(mem);
+}
+
+/*
+ * Removes trailing zeros and dot for a %f formatted number.
+ * Modifies input.
+ */
+void
+trim_trailing_zeros(const RTCTX *ctx, char *str)
+{
+	char *ptr, *totrim=NULL;
+	int len;
+	int i;
+
+	RTDEBUGF(3, "input: %s", str);
+
+	ptr = strchr(str, '.');
+	if ( ! ptr ) return; /* no dot, no decimal digits */
+
+	RTDEBUGF(3, "ptr: %s", ptr);
+
+	len = strlen(ptr);
+	for (i=len-1; i; i--)
+	{
+		if ( ptr[i] != '0' ) break;
+		totrim=&ptr[i];
+	}
+	if ( totrim )
+	{
+		if ( ptr == totrim-1 ) *ptr = '\0';
+		else *totrim = '\0';
+	}
+
+	RTDEBUGF(3, "output: %s", str);
+}
+
+/*
+ * Returns a new string which contains a maximum of maxlength characters starting
+ * from startpos and finishing at endpos (0-based indexing). If the string is
+ * truncated then the first or last characters are replaced by "..." as
+ * appropriate.
+ *
+ * The caller should specify start or end truncation by setting the truncdirection
+ * parameter as follows:
+ *    0 - start truncation (i.e. characters are removed from the beginning)
+ *    1 - end trunctation (i.e. characters are removed from the end)
+ */
+
+char * rtmessage_truncate(const RTCTX *ctx, char *str, int startpos, int endpos, int maxlength, int truncdirection)
+{
+	char *output;
+	char *outstart;
+
+	/* Allocate space for new string */
+	output = rtalloc(ctx, maxlength + 4);
+	output[0] = '\0';
+
+	/* Start truncation */
+	if (truncdirection == 0)
+	{
+		/* Calculate the start position */
+		if (endpos - startpos < maxlength)
+		{
+			outstart = str + startpos;
+			strncat(output, outstart, endpos - startpos + 1);
+		}
+		else
+		{
+			if (maxlength >= 3)
+			{
+				/* Add "..." prefix */
+				outstart = str + endpos + 1 - maxlength + 3;
+				strncat(output, "...", 3);
+				strncat(output, outstart, maxlength - 3);
+			}
+			else
+			{
+				/* maxlength is too small; just output "..." */
+				strncat(output, "...", 3);
+			}
+		}
+	}
+
+	/* End truncation */
+	if (truncdirection == 1)
+	{
+		/* Calculate the end position */
+		if (endpos - startpos < maxlength)
+		{
+			outstart = str + startpos;
+			strncat(output, outstart, endpos - startpos + 1);
+		}
+		else
+		{
+			if (maxlength >= 3)
+			{
+				/* Add "..." suffix */
+				outstart = str + startpos;
+				strncat(output, outstart, maxlength - 3);
+				strncat(output, "...", 3);
+			}
+			else
+			{
+				/* maxlength is too small; just output "..." */
+				strncat(output, "...", 3);
+			}
+		}
+	}
+
+	return output;
+}
+
+
+char
+getMachineEndian(const RTCTX *ctx)
+{
+	static int endian_check_int = 1; /* dont modify this!!! */
+
+	return *((char *) &endian_check_int); /* 0 = big endian | xdr,
+	                                       * 1 = little endian | ndr
+	                                       */
+}
+
+
+void
+error_if_srid_mismatch(const RTCTX *ctx, int srid1, int srid2)
+{
+	if ( srid1 != srid2 )
+	{
+		rterror(ctx, "Operation on mixed SRID geometries");
+	}
+}
+
+int
+clamp_srid(const RTCTX *ctx, int srid)
+{
+	int newsrid = srid;
+
+	if ( newsrid <= 0 ) {
+		if ( newsrid != SRID_UNKNOWN ) {
+			newsrid = SRID_UNKNOWN;
+			rtnotice(ctx, "SRID value %d converted to the officially unknown SRID value %d", srid, newsrid);
+		}
+	} else if ( srid > SRID_MAXIMUM ) {
+    newsrid = SRID_USER_MAXIMUM + 1 +
+      /* -1 is to reduce likelyhood of clashes */
+      /* NOTE: must match implementation in postgis_restore.pl */
+      ( srid % ( SRID_MAXIMUM - SRID_USER_MAXIMUM - 1 ) );
+		rtnotice(ctx, "SRID value %d > SRID_MAXIMUM converted to %d", srid, newsrid);
+	}
+	
+	return newsrid;
+}
+
diff --git a/src/stringbuffer.c b/src/stringbuffer.c
new file mode 100644
index 0000000..6e4edaa
--- /dev/null
+++ b/src/stringbuffer.c
@@ -0,0 +1,343 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2002 Thamer Alharbash
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+
+#include "librtgeom_internal.h"
+#include "stringbuffer.h"
+
+/**
+* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free.
+*/
+stringbuffer_t* 
+stringbuffer_create(const RTCTX *ctx)
+{
+	return stringbuffer_create_with_size(ctx, STRINGBUFFER_STARTSIZE);
+}
+
+/**
+* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free.
+*/
+stringbuffer_t* 
+stringbuffer_create_with_size(const RTCTX *ctx, size_t size)
+{
+	stringbuffer_t *s;
+
+	s = rtalloc(ctx, sizeof(stringbuffer_t));
+	s->str_start = rtalloc(ctx, size);
+	s->str_end = s->str_start;
+	s->capacity = size;
+	memset(s->str_start,0,size);
+	return s;
+}
+
+/**
+* Free the stringbuffer_t and all memory managed within it.
+*/
+void 
+stringbuffer_destroy(const RTCTX *ctx, stringbuffer_t *s)
+{
+	if ( s->str_start ) rtfree(ctx, s->str_start);
+	if ( s ) rtfree(ctx, s);
+}
+
+/**
+* Reset the stringbuffer_t. Useful for starting a fresh string
+* without the expense of freeing and re-allocating a new
+* stringbuffer_t.
+*/
+void 
+stringbuffer_clear(const RTCTX *ctx, stringbuffer_t *s)
+{
+	s->str_start[0] = '\0';
+	s->str_end = s->str_start;
+}
+
+/**
+* If necessary, expand the stringbuffer_t internal buffer to accomodate the
+* specified additional size.
+*/
+static inline void 
+stringbuffer_makeroom(const RTCTX *ctx, stringbuffer_t *s, size_t size_to_add)
+{
+	size_t current_size = (s->str_end - s->str_start);
+	size_t capacity = s->capacity;
+	size_t required_size = current_size + size_to_add;
+
+	while (capacity < required_size)
+		capacity *= 2;
+
+	if ( capacity > s->capacity )
+	{
+		s->str_start = rtrealloc(ctx, s->str_start, capacity);
+		s->capacity = capacity;
+		s->str_end = s->str_start + current_size;
+	}
+}
+
+/**
+* Return the last character in the buffer.
+*/
+char 
+stringbuffer_lastchar(const RTCTX *ctx, stringbuffer_t *s)
+{
+	if( s->str_end == s->str_start ) 
+		return 0;
+	
+	return *(s->str_end - 1);
+}
+
+/**
+* Append the specified string to the stringbuffer_t.
+*/
+void 
+stringbuffer_append(const RTCTX *ctx, stringbuffer_t *s, const char *a)
+{
+	int alen = strlen(a); /* Length of string to append */
+	int alen0 = alen + 1; /* Length including null terminator */
+	stringbuffer_makeroom(ctx, s, alen0);
+	memcpy(s->str_end, a, alen0);
+	s->str_end += alen;
+}
+
+/**
+* Returns a reference to the internal string being managed by
+* the stringbuffer. The current string will be null-terminated
+* within the internal string.
+*/
+const char* 
+stringbuffer_getstring(const RTCTX *ctx, stringbuffer_t *s)
+{
+	return s->str_start;
+}
+
+/**
+* Returns a newly allocated string large enough to contain the
+* current state of the string. Caller is responsible for
+* freeing the return value.
+*/
+char* 
+stringbuffer_getstringcopy(const RTCTX *ctx, stringbuffer_t *s)
+{
+	size_t size = (s->str_end - s->str_start) + 1;
+	char *str = rtalloc(ctx, size);
+	memcpy(str, s->str_start, size);
+	str[size - 1] = '\0';
+	return str;
+}
+
+/**
+* Returns the length of the current string, not including the
+* null terminator (same behavior as strlen()).
+*/
+int 
+stringbuffer_getlength(const RTCTX *ctx, stringbuffer_t *s)
+{
+	return (s->str_end - s->str_start);
+}
+
+/**
+* Clear the stringbuffer_t and re-start it with the specified string.
+*/
+void 
+stringbuffer_set(const RTCTX *ctx, stringbuffer_t *s, const char *str)
+{
+	stringbuffer_clear(ctx, s);
+	stringbuffer_append(ctx, s, str);
+}
+
+/**
+* Copy the contents of src into dst.
+*/
+void 
+stringbuffer_copy(const RTCTX *ctx, stringbuffer_t *dst, stringbuffer_t *src)
+{
+	stringbuffer_set(ctx, dst, stringbuffer_getstring(ctx, src));
+}
+
+/**
+* Appends a formatted string to the current string buffer,
+* using the format and argument list provided. Returns -1 on error,
+* check errno for reasons, documented in the printf man page.
+*/
+static int 
+stringbuffer_avprintf(const RTCTX *ctx, stringbuffer_t *s, const char *fmt, va_list ap)
+{
+	int maxlen = (s->capacity - (s->str_end - s->str_start));
+	int len = 0; /* Length of the output */
+	va_list ap2;
+
+	/* Make a copy of the variadic arguments, in case we need to print twice */
+	/* Print to our buffer */
+	va_copy(ap2, ap);
+	len = vsnprintf(s->str_end, maxlen, fmt, ap2);
+	va_end(ap2);
+
+	/* Propogate errors up */
+	if ( len < 0 ) 
+		#if defined(__MINGW64_VERSION_MAJOR)
+		len = _vscprintf(fmt, ap2);/**Assume windows flaky vsnprintf that returns -1 if initial buffer to small and add more space **/
+		#else
+		return len;
+		#endif
+
+	/* We didn't have enough space! */
+	/* Either Unix vsnprint returned write length larger than our buffer */
+	/*     or Windows vsnprintf returned an error code. */
+	if ( len >= maxlen )
+	{
+		stringbuffer_makeroom(ctx, s, len + 1);
+		maxlen = (s->capacity - (s->str_end - s->str_start));
+
+		/* Try to print a second time */
+		len = vsnprintf(s->str_end, maxlen, fmt, ap);
+
+		/* Printing error? Error! */
+		if ( len < 0 ) return len;
+		/* Too long still? Error! */
+		if ( len >= maxlen ) return -1;
+	}
+
+	/* Move end pointer forward and return. */
+	s->str_end += len;
+	return len;
+}
+
+/**
+* Appends a formatted string to the current string buffer,
+* using the format and argument list provided.
+* Returns -1 on error, check errno for reasons,
+* as documented in the printf man page.
+*/
+int 
+stringbuffer_aprintf(const RTCTX *ctx, stringbuffer_t *s, const char *fmt, ...)
+{
+	int r;
+	va_list ap;
+	va_start(ap, fmt);
+	r = stringbuffer_avprintf(ctx, s, fmt, ap);
+	va_end(ap);
+	return r;
+}
+
+/**
+* Trims whitespace off the end of the stringbuffer. Returns
+* the number of characters trimmed.
+*/
+int 
+stringbuffer_trim_trailing_white(const RTCTX *ctx, stringbuffer_t *s)
+{
+	char *ptr = s->str_end;
+	int dist = 0;
+	
+	/* Roll backwards until we hit a non-space. */
+	while( ptr > s->str_start )
+	{	
+		ptr--;
+		if( (*ptr == ' ') || (*ptr == '\t') )
+		{
+			continue;
+		}
+		else
+		{
+			ptr++;
+			dist = s->str_end - ptr;
+			*ptr = '\0';
+			s->str_end = ptr;
+			return dist;
+		}
+	}
+	return dist;	
+}
+
+/**
+* Trims zeroes off the end of the last number in the stringbuffer.
+* The number has to be the very last thing in the buffer. Only the
+* last number will be trimmed. Returns the number of characters
+* trimmed.
+* 
+* eg: 1.22000 -> 1.22
+*     1.0 -> 1
+*     0.0 -> 0
+*/
+int 
+stringbuffer_trim_trailing_zeroes(const RTCTX *ctx, stringbuffer_t *s)
+{
+	char *ptr = s->str_end;
+	char *decimal_ptr = NULL;
+	int dist;
+	
+	if ( s->str_end - s->str_start < 2) 
+		return 0;
+
+	/* Roll backwards to find the decimal for this number */
+	while( ptr > s->str_start )
+	{	
+		ptr--;
+		if ( *ptr == '.' )
+		{
+			decimal_ptr = ptr;
+			break;
+		}
+		if ( (*ptr >= '0') && (*ptr <= '9' ) )
+			continue;
+		else
+			break;
+	}
+
+	/* No decimal? Nothing to trim! */
+	if ( ! decimal_ptr )
+		return 0;
+	
+	ptr = s->str_end;
+	
+	/* Roll backwards again, with the decimal as stop point, trimming contiguous zeroes */
+	while( ptr >= decimal_ptr )
+	{
+		ptr--;
+		if ( *ptr == '0' )
+			continue;
+		else
+			break;
+	}
+	
+	/* Huh, we get anywhere. Must not have trimmed anything. */
+	if ( ptr == s->str_end )
+		return 0;
+
+	/* If we stopped at the decimal, we want to null that out. 
+	   It we stopped on a numeral, we want to preserve that, so push the 
+	   pointer forward one space. */
+	if ( *ptr != '.' )
+		ptr++;
+
+	/* Add null terminator re-set the end of the stringbuffer. */
+	*ptr = '\0';
+	dist = s->str_end - ptr;
+	s->str_end = ptr;
+	return dist;
+}
+
diff --git a/src/stringbuffer.h b/src/stringbuffer.h
new file mode 100644
index 0000000..8cfecaa
--- /dev/null
+++ b/src/stringbuffer.h
@@ -0,0 +1,62 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2002 Thamer Alharbash
+ * Copyright 2009 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ **********************************************************************/
+
+
+
+#ifndef _STRINGBUFFER_H
+#define _STRINGBUFFER_H 1
+
+#include "librtgeom_internal.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+#define STRINGBUFFER_STARTSIZE 128
+
+typedef struct
+{
+	size_t capacity;
+	char *str_end;
+	char *str_start;
+}
+stringbuffer_t;
+
+extern stringbuffer_t *stringbuffer_create_with_size(const RTCTX *ctx, size_t size);
+extern stringbuffer_t *stringbuffer_create(const RTCTX *ctx);
+extern void stringbuffer_destroy(const RTCTX *ctx, stringbuffer_t *sb);
+extern void stringbuffer_clear(const RTCTX *ctx, stringbuffer_t *sb);
+void stringbuffer_set(const RTCTX *ctx, stringbuffer_t *sb, const char *s);
+void stringbuffer_copy(const RTCTX *ctx, stringbuffer_t *sb, stringbuffer_t *src);
+extern void stringbuffer_append(const RTCTX *ctx, stringbuffer_t *sb, const char *s);
+extern int stringbuffer_aprintf(const RTCTX *ctx, stringbuffer_t *sb, const char *fmt, ...);
+extern const char *stringbuffer_getstring(const RTCTX *ctx, stringbuffer_t *sb);
+extern char *stringbuffer_getstringcopy(const RTCTX *ctx, stringbuffer_t *sb);
+extern int stringbuffer_getlength(const RTCTX *ctx, stringbuffer_t *sb);
+extern char stringbuffer_lastchar(const RTCTX *ctx, stringbuffer_t *s);
+extern int stringbuffer_trim_trailing_white(const RTCTX *ctx, stringbuffer_t *s);
+extern int stringbuffer_trim_trailing_zeroes(const RTCTX *ctx, stringbuffer_t *s);
+
+#endif /* _STRINGBUFFER_H */
diff --git a/src/varint.c b/src/varint.c
new file mode 100644
index 0000000..cce9a50
--- /dev/null
+++ b/src/varint.c
@@ -0,0 +1,208 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2014 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2013 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+#include "varint.h"
+#include "rtgeom_log.h"
+#include "librtgeom.h"
+
+/* -------------------------------------------------------------------------------- */
+
+static size_t 
+_varint_u64_encode_buf(const RTCTX *ctx, uint64_t val, uint8_t *buf)
+{
+	uint8_t grp;	
+	uint64_t q = val;
+	uint8_t *ptr = buf;
+	while (1) 
+	{
+		/* We put the 7 least significant bits in grp */
+		grp = 0x7f & q; 
+		/* We rightshift our input value 7 bits */
+		/* which means that the 7 next least significant bits */
+		/* becomes the 7 least significant */
+		q = q >> 7;	
+		/* Check if, after our rightshifting, we still have */
+		/* anything to read in our input value. */
+		if ( q > 0 )
+		{
+			/* In the next line quite a lot is happening. */
+			/* Since there is more to read in our input value */
+			/* we signal that by setting the most siginicant bit */
+			/* in our byte to 1. */
+			/* Then we put that byte in our buffer and move the pointer */
+			/* forward one step */
+			*ptr = 0x80 | grp;
+			ptr++;
+		}
+		else
+		{
+			/* The same as above, but since there is nothing more */
+			/* to read in our input value we leave the most significant bit unset */
+			*ptr = grp;
+			ptr++;
+			return ptr - buf;
+		}
+	}
+	/* This cannot happen */
+	rterror(ctx, "%s: Got out of infinite loop. Consciousness achieved.", __func__);
+	return (size_t)0;
+}
+
+
+size_t
+varint_u64_encode_buf(const RTCTX *ctx, uint64_t val, uint8_t *buf)
+{
+	return _varint_u64_encode_buf(ctx, val, buf);
+}
+
+
+size_t
+varint_u32_encode_buf(const RTCTX *ctx, uint32_t val, uint8_t *buf)
+{
+	return _varint_u64_encode_buf(ctx, (uint64_t)val, buf);
+}
+
+size_t
+varint_s64_encode_buf(const RTCTX *ctx, int64_t val, uint8_t *buf)
+{
+	return _varint_u64_encode_buf(ctx, zigzag64(ctx, val), buf);
+}
+
+size_t
+varint_s32_encode_buf(const RTCTX *ctx, int32_t val, uint8_t *buf)
+{
+	return _varint_u64_encode_buf(ctx, (uint64_t)zigzag32(ctx, val), buf);
+}
+
+/* Read from signed 64bit varint */
+int64_t 
+varint_s64_decode(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end, size_t *size)
+{	
+	return unzigzag64(ctx, varint_u64_decode(ctx, the_start, the_end, size));
+}
+
+/* Read from unsigned 64bit varint */
+uint64_t 
+varint_u64_decode(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end, size_t *size)
+{
+	uint64_t nVal = 0;
+	int nShift = 0;
+	uint8_t nByte;
+	const uint8_t *ptr = the_start;
+
+	/* Check so we don't read beyond the twkb */
+	while( ptr < the_end )
+	{
+		nByte = *ptr;
+		/* Hibit is set, so this isn't the last byte */
+		if (nByte & 0x80)
+		{
+			/* We get here when there is more to read in the input varInt */
+			/* Here we take the least significant 7 bits of the read */
+			/* byte and put it in the most significant place in the result variable. */
+			nVal |= ((uint64_t)(nByte & 0x7f)) << nShift; 
+			/* move the "cursor" of the input buffer step (8 bits) */
+			ptr++; 
+			/* move the cursor in the resulting variable (7 bits) */
+			nShift += 7;
+		}
+		else
+		{
+			/* move the "cursor" one step */
+			ptr++; 
+			/* Move the last read byte to the most significant */
+			/* place in the result and return the whole result */
+			*size = ptr - the_start;
+			return nVal | ((uint64_t)nByte << nShift);
+		}
+	}
+	rterror(ctx, "%s: varint extends past end of buffer", __func__);
+	return 0;
+}
+
+size_t 
+varint_size(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end)
+{
+	const uint8_t *ptr = the_start;
+
+	/* Check so we don't read beyond the twkb */
+	while( ptr < the_end )
+	{
+		/* Hibit is set, this isn't the last byte */
+		if (*ptr & 0x80)
+		{
+			ptr++;
+		}
+		else
+		{
+			ptr++;
+			return ptr - the_start;
+		}
+	}
+	return 0;
+}
+
+uint64_t zigzag64(const RTCTX *ctx, int64_t val)
+{
+	return (val << 1) ^ (val >> 63);
+}
+
+uint32_t zigzag32(const RTCTX *ctx, int32_t val)
+{
+	return (val << 1) ^ (val >> 31);
+}
+	
+uint8_t zigzag8(const RTCTX *ctx, int8_t val)
+{
+	return (val << 1) ^ (val >> 7);
+}
+	
+int64_t unzigzag64(const RTCTX *ctx, uint64_t val)
+{
+        if ( val & 0x01 ) 
+            return -1 * (int64_t)((val+1) >> 1);
+        else
+            return (int64_t)(val >> 1);
+}
+	
+int32_t unzigzag32(const RTCTX *ctx, uint32_t val)
+{
+        if ( val & 0x01 ) 
+            return -1 * (int32_t)((val+1) >> 1);
+        else
+            return (int32_t)(val >> 1);
+}
+	
+int8_t unzigzag8(const RTCTX *ctx, uint8_t val)
+{
+        if ( val & 0x01 ) 
+            return -1 * (int8_t)((val+1) >> 1);
+        else
+            return (int8_t)(val >> 1);
+}
+	
+
diff --git a/src/varint.h b/src/varint.h
new file mode 100644
index 0000000..11745ae
--- /dev/null
+++ b/src/varint.h
@@ -0,0 +1,56 @@
+/**********************************************************************
+ *
+ * rttopo - topology library
+ * http://gitlab.com/rttopo/rttopo
+ *
+ * rttopo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * rttopo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with rttopo.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright (C) 2014 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2013 Nicklas Avén
+ *
+ **********************************************************************/
+
+
+
+#ifndef _LIBRTGEOM_VARINT_H
+#define _LIBRTGEOM_VARINT_H 1
+
+#include "librtgeom_internal.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+
+/* NEW SIGNATURES */
+
+size_t varint_u32_encode_buf(const RTCTX *ctx, uint32_t val, uint8_t *buf);
+size_t varint_s32_encode_buf(const RTCTX *ctx, int32_t val, uint8_t *buf);
+size_t varint_u64_encode_buf(const RTCTX *ctx, uint64_t val, uint8_t *buf);
+size_t varint_s64_encode_buf(const RTCTX *ctx, int64_t val, uint8_t *buf);
+int64_t varint_s64_decode(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end, size_t *size);
+uint64_t varint_u64_decode(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end, size_t *size);
+
+size_t varint_size(const RTCTX *ctx, const uint8_t *the_start, const uint8_t *the_end);
+
+uint64_t zigzag64(const RTCTX *ctx, int64_t val);
+uint32_t zigzag32(const RTCTX *ctx, int32_t val);
+uint8_t zigzag8(const RTCTX *ctx, int8_t val);
+int64_t unzigzag64(const RTCTX *ctx, uint64_t val);
+int32_t unzigzag32(const RTCTX *ctx, uint32_t val);
+int8_t unzigzag8(const RTCTX *ctx, uint8_t val);
+
+#endif /* !defined _LIBRTGEOM_VARINT_H  */
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/librttopo.git



More information about the Pkg-grass-devel mailing list