[php-geos] 01/03: Imported Upstream version 1.0.0~rc1

Bas Couwenberg sebastic at debian.org
Tue Aug 30 12:17:36 UTC 2016


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

sebastic pushed a commit to branch master
in repository php-geos.

commit 644609655248eaefae856f1d457b2d78d424d596
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Aug 30 11:51:22 2016 +0200

    Imported Upstream version 1.0.0~rc1
---
 .drone.yml                            |   15 +
 .gitignore                            |   72 +
 COPYING                               |   17 +
 CREDITS                               |    3 +
 LGPL-2                                |  502 +++++
 MIT-LICENSE                           |   22 +
 Makefile.frag                         |    3 +
 NEWS                                  |    4 +
 README.md                             |   52 +
 TODO                                  |    9 +
 autogen.sh                            |    3 +
 config.m4                             |  101 +
 docker/build-test/Dockerfile.alpine   |   22 +
 docker/build-test/Dockerfile.trisquel |   12 +
 docker/build-test/Makefile            |   19 +
 geos.c                                | 3433 +++++++++++++++++++++++++++++++++
 package.xml                           |   74 +
 php_geos.h                            |   55 +
 phpunit.xml                           |    7 +
 test/crashme.php                      |   25 +
 tests/000_General.phpt                |   51 +
 tests/001_Geometry.phpt               | 1935 +++++++++++++++++++
 tests/002_WKTWriter.phpt              |  209 ++
 tests/003_WKTReader.phpt              |  113 ++
 tests/004_WKBWriter.phpt              |  170 ++
 tests/README.md                       |   14 +
 tests/TestHelper.php                  |  141 ++
 27 files changed, 7083 insertions(+)

diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..b5bdb23
--- /dev/null
+++ b/.drone.yml
@@ -0,0 +1,15 @@
+# See http://readme.drone.io/usage/overview
+pipeline:
+  build:
+    image: docker.kbt.io/php-geos/build-test:trisquel
+    commands:
+      - export PATH=${PHP_BIN}:${PATH}
+      - php --version
+      - ./autogen.sh
+      - ./configure
+      - make
+      - make check
+matrix:
+  PHP_BIN:
+    - /opt/php-7.0.7/bin/
+    - /usr/bin/ # this is php5
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d531376
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,72 @@
+*~
+*.swp
+*.lo
+*.la
+.deps
+.libs
+Makefile
+Makefile.fragments
+Makefile.global
+Makefile.objects
+acinclude.m4
+aclocal.m4
+autom4te.cache
+build
+config.cache
+config.guess
+config.h
+config.h.in
+config.log
+config.nice
+config.status
+config.sub
+configure
+configure.in
+conftest
+conftest.c
+include
+install-sh
+libtool
+ltmain.sh
+missing
+mkinstalldirs
+modules
+scan_makefile_in.awk
+*.dsw
+*.plg
+*.opt
+*.ncb
+Release
+Release_inline
+Debug
+Release_TS
+Release_TSDbg
+Release_TS_inline
+Debug_TS
+memcached*.tgz
+run-tests.php
+cscope.out
+php_memcached.loT
+tests/*.log
+tests/*.mem
+tests/*.out
+tests/*.diff
+tests/*.php
+tests/*.exp
+tests/*.sh
+tests/*/*.log
+tests/*/*.mem
+tests/*/*.out
+tests/*/*.diff
+tests/*/*.php
+tests/*/*.exp
+tests/*/*.sh
+tests/*/*/*.log
+tests/*/*/*.mem
+tests/*/*/*.out
+tests/*/*/*.diff
+tests/*/*/*.php
+tests/*/*/*.exp
+tests/*/*/*.sh
+tmp-php.ini
+!/docker/**/Makefile
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5799769
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,17 @@
+
+This PHP extension is covered under the following licenses:
+
+- geos.c and php_geos.h have been extracted from the GEOS library
+  and are covered under the LGPL 2.1 are are copyright
+
+    Sandro Santilli <strk at kbt.io>
+
+  See the LGPL-2 file for details.
+
+- the remainder of the system including the "PECL-ization" of
+  the extension are covered under the MIT license and is
+  copyright
+
+    J Smith <dark.panda at gmail.com> <jay at php.net>
+
+  See the MIT-LICENSE file for details.
diff --git a/CREDITS b/CREDITS
new file mode 100644
index 0000000..0c6410d
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,3 @@
+geos
+Sandro Santilli <strk at kbt.io> - GEOS
+J Smith <dark.panda at gmail.com> <jay at php.net> - PHP packaging
diff --git a/LGPL-2 b/LGPL-2
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/LGPL-2
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 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.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, 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 library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+  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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+  If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be 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.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+  9. 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 Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+

+  11. 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 Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  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 library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; 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.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/MIT-LICENSE b/MIT-LICENSE
new file mode 100644
index 0000000..a547fa8
--- /dev/null
+++ b/MIT-LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2016 J Smith <dark.panda at gmail.com> <jay at php.net>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile.frag b/Makefile.frag
new file mode 100644
index 0000000..7328668
--- /dev/null
+++ b/Makefile.frag
@@ -0,0 +1,3 @@
+
+check:
+	REPORT_EXIT_STATUS=1 NO_INTERACTION=1 make test
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..24235ff
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,4 @@
+1.0.0 - 201608XX
+
+ First stand-alone release
+ (previous versions were shipped as part of GEOS since 20100618)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1de3193
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+PHP module for GEOS
+===================
+
+[![build status]
+(https://drone.osgeo.kbt.io/api/badges/geos/php-geos/status.svg?branch=master)]
+(https://drone.osgeo.kbt.io/geos/php-geos?branch=master, alt=build status)
+
+The code in this directory provides a PHP module to make use
+of functionalities of the [GEOS library] (http://geos.osgeo.org).
+
+The bindings are linked to the C-API, for betters stability.
+
+# Building
+
+## Requirements
+
+You need:
+
+  - PHP development files
+    ( often found in packages named something like 'php5-dev' )
+
+  - GEOS development files
+    ( often found in packages named something like 'libgeos-dev' )
+
+## Procedure
+
+    git clone https://git.osgeo.org/gogs/geos/php-geos.git
+    cd php-geos
+    ./autogen.sh
+    ./configure
+    make # generates modules/geos.so
+
+# Testing
+
+Automated testing is executed on 'make check'.
+
+You'll need phpunit installed for this to work. To install:
+
+    pear install --force --alldeps phpunit/phpunit
+
+# Installing
+
+As root (or owner with write access to the directory
+returned by php-config --extension-dir), run:
+
+    make install
+
+# Using
+
+ ... TODO ...
+ (reference some automatically built doc?)
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a9631a8
--- /dev/null
+++ b/TODO
@@ -0,0 +1,9 @@
+In order of priority
+
+- Find a way to have GEOSGeometry contents shown on var_dump
+- Documentation !! (doxygen-based?)
+- Add interface for algorithms (Orientation Index) ?
+- Add interfaces for prepared geometries ?
+- Add interfaces for STRTree ?
+- Add interfaces for GEOSCoordSeq ?
+- Add Geometry Constructors ?
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..4b2f22f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+phpize
diff --git a/config.m4 b/config.m4
new file mode 100644
index 0000000..71b1461
--- /dev/null
+++ b/config.m4
@@ -0,0 +1,101 @@
+dnl
+dnl $ Id: $
+dnl vim:se ts=2 sw=2 et:
+
+PHP_ARG_ENABLE(geos, whether to enable geos support,
+[  --enable-geos               Enable geos support])
+
+PHP_ARG_WITH(geos-config, for geos-config,
+[  --with-geos-config[=DIR]    Use geos-config])
+
+if test -z "$PHP_DEBUG"; then
+  AC_ARG_ENABLE(debug,
+  [  --enable-debug          compile with debugging symbols],[
+    PHP_DEBUG=$enableval
+  ],[    PHP_DEBUG=no
+  ])
+fi
+
+if test "$PHP_GEOS" != "no"; then
+  if test "$PHP_GEOS_CONFIG" != "yes"; then
+    if test -x "$PHP_GEOS_CONFIG"; then
+      GEOS_CONFIG="$PHP_GEOS_CONFIG"
+    fi
+  else
+    for i in /opt/local/bin /opt/bin /usr/local/bin /usr/bin ""; do
+      if test -x $i/geos-config; then
+        GEOS_CONFIG="$i/geos-config"
+        break;
+      fi
+    done
+  fi
+
+  if test -n "$GEOS_CONFIG"; then
+    GEOS_VERSION=`$GEOS_CONFIG --version`
+    GEOS_INCLUDE=`$GEOS_CONFIG --includes`
+    GEOS_LDFLAGS=`$GEOS_CONFIG --ldflags`
+    AC_MSG_RESULT([Using GEOS version $GEOS_VERSION])
+    AC_DEFINE(HAVE_GEOS_C_H,1,[Whether to have geos_c.h])
+  fi
+
+  if test -z "$GEOS_INCLUDE"; then
+    AC_MSG_ERROR(Cannot find geos_c.h. Please specify correct GEOS installation path)
+  fi
+
+  if test -z "$GEOS_LDFLAGS"; then
+    AC_MSG_ERROR(Cannot find geos_c.so. Please specify correct GEOS installation path)
+  fi
+
+  old_CFLAGS=$CFLAGS
+  CFLAGS="-I$GEOS_INCLUDE $CFLAGS"
+
+  old_LDFLAGS=$LDFLAGS
+  LDFLAGS="$GEOS_LDFLAGS $LDFLAGS"
+
+  AC_CHECK_HEADER(geos_c.h,, AC_MSG_ERROR(Can't find GEOS includes))
+  AC_CHECK_LIB(geos_c, initGEOS_r,, AC_MSG_ERROR([Unable to build the GEOS: a newer libgeos is required]))
+  AC_CHECK_LIB(geos_c, finishGEOS_r,, AC_MSG_ERROR([Unable to build the GEOS: a newer libgeos is required]))
+  AC_CHECK_LIB(geos_c, GEOSClipByRect_r, AC_DEFINE(HAVE_GEOS_CLIP_BY_RECT,1,[Whether we have GEOSClipByRect_r]))
+  AC_CHECK_LIB(geos_c, GEOSCoveredBy_r, AC_DEFINE(HAVE_GEOS_COVERED_BY,1,[Whether we have GEOSCoveredBy_r]))
+  AC_CHECK_LIB(geos_c, GEOSCovers_r, AC_DEFINE(HAVE_GEOS_COVERS,1,[Whether we have GEOSCovers_r]))
+  AC_CHECK_LIB(geos_c, GEOSDelaunayTriangulation_r, AC_DEFINE(HAVE_GEOS_DELAUNAY_TRIANGULATION,1,[Whether we have GEOSDelaunayTriangulation_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetEndPoint_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_END_POINT,1,[Whether we have GEOSGeomGetEndPoint_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetNumPoints_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_NUM_POINTS,1,[Whether we have GEOSGeomGetNumPoints_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetPointN_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_POINT_N,1,[Whether we have GEOSGeomGetPointN_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetStartPoint_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_START_POINT,1,[Whether we have GEOSGeomGetStartPoint_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetX_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_X,1,[Whether we have GEOSGeomGetX_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeomGetY_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_Y,1,[Whether we have GEOSGeomGetY_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeom_extractUniquePoints_r, AC_DEFINE(HAVE_GEOS_GEOM_EXTRACT_UNIQUE_POINTS,1,[Whether we have GEOSGeom_extractUniquePoints_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeom_getCoordinateDimension_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_COORDINATE_DIMENSION,1,[Whether we have GEOSGeom_getCoordinateDimension_r]))
+  AC_CHECK_LIB(geos_c, GEOSNode_r, AC_DEFINE(HAVE_GEOS_NODE,1,[Whether we have GEOSNode_r]))
+  AC_CHECK_LIB(geos_c, GEOSOffsetCurve_r, AC_DEFINE(HAVE_GEOS_OFFSET_CURVE,1,[Whether we have GEOSOffsetCurve_r]))
+  AC_CHECK_LIB(geos_c, GEOSPolygonize_full_r, AC_DEFINE(HAVE_GEOS_POLYGONIZE_FULL,1,[Whether we have GEOSPolygonize_full_r]))
+  AC_CHECK_LIB(geos_c, GEOSRelateBoundaryNodeRule_r, AC_DEFINE(HAVE_GEOS_RELATE_BOUNDARY_NODE_RULE,1,[Whether we have GEOSRelateBoundaryNodeRule_r]))
+  AC_CHECK_LIB(geos_c, GEOSRelatePatternMatch_r, AC_DEFINE(HAVE_GEOS_RELATE_PATTERN_MATCH,1,[Whether we have GEOSRelatePatternMatch_r]))
+  AC_CHECK_LIB(geos_c, GEOSSharedPaths_r, AC_DEFINE(HAVE_GEOS_SHARED_PATHS,1,[Whether we have GEOSSharedPaths_r]))
+  AC_CHECK_LIB(geos_c, GEOSSnap_r, AC_DEFINE(HAVE_GEOS_SNAP,1,[Whether we have GEOSSnap_r]))
+  AC_CHECK_LIB(geos_c, GEOSUnaryUnion_r, AC_DEFINE(HAVE_GEOS_UNARY_UNION,1,[Whether we have GEOSUnaryUnion_r]))
+  AC_CHECK_LIB(geos_c, GEOSVoronoiDiagram_r, AC_DEFINE(HAVE_GEOS_VORONOI_DIAGRAM,1,[Whether we have GEOSVoronoiDiagram_r]))
+  AC_CHECK_LIB(geos_c, GEOSisClosed_r, AC_DEFINE(HAVE_GEOS_IS_CLOSED,1,[Whether we have GEOSisClosed_r]))
+  AC_CHECK_LIB(geos_c, GEOSisValidDetail_r, AC_DEFINE(HAVE_GEOS_IS_VALID_DETAIL,1,[Whether we have GEOSisValidDetail_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeom_setPrecision_r, AC_DEFINE(HAVE_GEOS_GEOM_SET_PRECISION,1,[Whether we have GEOSGeom_setPrecision_r]))
+  AC_CHECK_LIB(geos_c, GEOSGeom_getPrecision_r, AC_DEFINE(HAVE_GEOS_GEOM_GET_PRECISION,1,[Whether we have GEOSGeom_getPrecision_r]))
+
+  AC_CHECK_LIB(geos_c, GEOSWKTWriter_setTrim_r, AC_DEFINE(HAVE_GEOS_WKT_WRITER_SET_TRIM,1,[Whether we have GEOSWKTWriter_setTrim_r]))
+  AC_CHECK_LIB(geos_c, GEOSWKTWriter_setRoundingPrecision_r, AC_DEFINE(HAVE_GEOS_WKT_WRITER_SET_ROUNDING_PRECISION,1,[Whether we have GEOSWKTWriter_setRoundingPrecision_r]))
+  AC_CHECK_LIB(geos_c, GEOSWKTWriter_setOutputDimension_r, AC_DEFINE(HAVE_GEOS_WKT_WRITER_SET_OUTPUT_DIMENSION,1,[Whether we have GEOSWKTWriter_setOutputDimension_r]))
+  AC_CHECK_LIB(geos_c, GEOSWKTWriter_getOutputDimension_r, AC_DEFINE(HAVE_GEOS_WKT_WRITER_GET_OUTPUT_DIMENSION,1,[Whether we have GEOSWKTWriter_getOutputDimension_r]))
+  AC_CHECK_LIB(geos_c, GEOSWKTWriter_setOld3D_r, AC_DEFINE(HAVE_GEOS_WKT_WRITER_SET_OLD_3D,1,[Whether we have GEOSWKTWriter_setOld3D_r]))
+
+  AC_TRY_COMPILE(geos_c.h, GEOS_PREC_NO_TOPO, AC_DEFINE(HAVE_GEOS_PREC_NO_TOPO,1,[Whether we have GEOS_PREC_NO_TOPO]))
+  AC_TRY_COMPILE(geos_c.h, GEOS_PREC_KEEP_COLLAPSED, AC_DEFINE(HAVE_GEOS_PREC_KEEP_COLLAPSED,1,[Whether we have GEOS_PREC_KEEP_COLLAPSED]))
+
+  CFLAGS=$old_CFLAGS
+  LDFLAGS=$old_LDFLAGS
+
+  PHP_ADD_LIBRARY(geos_c, 1, GEOS_SHARED_LIBADD)
+  PHP_ADD_MAKEFILE_FRAGMENT(Makefile.frag)
+  PHP_SUBST(GEOS_SHARED_LIBADD)
+  PHP_ADD_INCLUDE($GEOS_INCLUDE, 1)
+  PHP_NEW_EXTENSION(geos, geos.c, $ext_shared,,)
+fi
diff --git a/docker/build-test/Dockerfile.alpine b/docker/build-test/Dockerfile.alpine
new file mode 100644
index 0000000..1f6c755
--- /dev/null
+++ b/docker/build-test/Dockerfile.alpine
@@ -0,0 +1,22 @@
+FROM index.docker.io/alpine:latest
+RUN apk add --update make                  && rm -rf /var/cache/apk/*
+RUN apk add --update gcc                   && rm -rf /var/cache/apk/*
+# Splitting install of php5-dev in steps to reduce memory requirements
+RUN apk add --update ncurses-terminfo-base && rm -rf /var/cache/apk/*
+RUN apk add --update ncurses-terminfo      && rm -rf /var/cache/apk/*
+RUN apk add --update pcre                  && rm -rf /var/cache/apk/*
+RUN apk add --update php5-common           && rm -rf /var/cache/apk/*
+RUN apk add --update readline              && rm -rf /var/cache/apk/*
+RUN apk add --update libxml2               && rm -rf /var/cache/apk/*
+RUN apk add --update libpcre16             && rm -rf /var/cache/apk/*
+RUN apk add --update libpcre32             && rm -rf /var/cache/apk/*
+RUN apk add --update pcre-dev              && rm -rf /var/cache/apk/*
+RUN apk add --update php5-cli              && rm -rf /var/cache/apk/*
+RUN apk add --update php5-dev              && rm -rf /var/cache/apk/*
+
+# Autoconf brings in perl
+RUN apk add --update autoconf              && rm -rf /var/cache/apk/*
+
+#RUN apk add --update autoconf && rm -rf /var/cache/apk/*
+#RUN apk add --update git
+
diff --git a/docker/build-test/Dockerfile.trisquel b/docker/build-test/Dockerfile.trisquel
new file mode 100644
index 0000000..c1e73b9
--- /dev/null
+++ b/docker/build-test/Dockerfile.trisquel
@@ -0,0 +1,12 @@
+FROM kpengboy/trisquel
+RUN apt-get update -qq && apt-get install -y build-essential
+RUN apt-get update -qq && apt-get install -y autoconf
+RUN apt-get update -qq && apt-get install -y php5-dev
+RUN apt-get update -qq && apt-get install -y libgeos-dev
+RUN curl -o php-7.0.7.tar.bz2 http://ar2.php.net/distributions/php-7.0.7.tar.bz2
+RUN tar xjf php-7.0.7.tar.bz2
+RUN apt-get update -qq && apt-get install -y libxml2-dev
+RUN cd php-7.0.7 && \
+    ./configure --prefix=/opt/php-7.0.7 --enable-debug && \
+    make install
+RUN cd .. && rm -rf php-7.0.7*
diff --git a/docker/build-test/Makefile b/docker/build-test/Makefile
new file mode 100644
index 0000000..e28c853
--- /dev/null
+++ b/docker/build-test/Makefile
@@ -0,0 +1,19 @@
+DOCKER=docker
+REGISTRY=docker.kbt.io
+NAME=$(REGISTRY)/php-geos/build-test
+
+all: alpine trisquel
+
+push: alpine-push trisquel-push
+
+alpine:
+	$(DOCKER) build -t $(NAME):alpine - < Dockerfile.alpine
+
+alpine-push:
+	$(DOCKER) push $(NAME):alpine
+
+trisquel:
+	$(DOCKER) build -t $(NAME):trisquel - < Dockerfile.trisquel
+
+trisquel-push:
+	$(DOCKER) push $(NAME):trisquel
diff --git a/geos.c b/geos.c
new file mode 100755
index 0000000..3f449f6
--- /dev/null
+++ b/geos.c
@@ -0,0 +1,3433 @@
+/***********************************************************************
+ *
+ *    GEOS - Geometry Engine Open Source
+ *    http://trac.osgeo.org/geos
+ *
+ *    Copyright (C) 2010 Sandro Santilli <strk at kbt.io>
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser 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 St, Fifth Floor,
+ *    Boston, MA  02110-1301  USA
+ *
+ ***********************************************************************/
+
+/* PHP stuff */
+#include "php.h"
+#include "ext/standard/info.h" /* for php_info_... */
+#include "Zend/zend_exceptions.h" /* for zend_throw_exception_object */
+
+/* GEOS stuff */
+#include "geos_c.h"
+
+/* Own stuff */
+#include "php_geos.h"
+
+static ZEND_DECLARE_MODULE_GLOBALS(geos);
+static PHP_GINIT_FUNCTION(geos);
+
+PHP_MINIT_FUNCTION(geos);
+PHP_MSHUTDOWN_FUNCTION(geos);
+PHP_RINIT_FUNCTION(geos);
+PHP_RSHUTDOWN_FUNCTION(geos);
+PHP_MINFO_FUNCTION(geos);
+PHP_FUNCTION(GEOSVersion);
+PHP_FUNCTION(GEOSPolygonize);
+PHP_FUNCTION(GEOSLineMerge);
+
+#ifdef HAVE_GEOS_SHARED_PATHS
+PHP_FUNCTION(GEOSSharedPaths);
+#endif
+
+#ifdef HAVE_GEOS_RELATE_PATTERN_MATCH
+PHP_FUNCTION(GEOSRelateMatch);
+#endif
+
+#if PHP_VERSION_ID < 50399
+#define zend_function_entry function_entry
+#endif
+
+#if PHP_VERSION_ID >= 70000
+# define GEOS_PHP_DTOR_OBJECT zend_object
+# define zend_object_value zend_object *
+# define zend_uint size_t
+# define MAKE_STD_ZVAL(x) x = emalloc(sizeof(zval))
+# define GEOS_PHP_RETURN_STRING(x) { RETVAL_STRING((x)); efree((x)); return; }
+# define GEOS_PHP_RETURN_STRINGL(x,s) { RETVAL_STRINGL((x),(s)); efree((x)); return; }
+# define GEOS_PHP_ADD_ASSOC_ARRAY(a,k,v) { add_assoc_string((a), (k), (v)); efree((v)); }
+# define GEOS_PHP_ADD_ASSOC_ZVAL(a,k,v) { add_assoc_zval((a), (k), (v)); efree((v)); }
+# define GEOS_PHP_HASH_GET_CUR_KEY(s,k,i) zend_hash_get_current_key((s), (k), (i))
+# define GEOS_PHP_HASH_GET_CUR_DATA(h,d) ( d = zend_hash_get_current_data((h)) )
+# define GEOS_PHP_ZVAL zval *
+#else /* PHP_VERSION_ID < 70000 */
+# define GEOS_PHP_DTOR_OBJECT void
+# define GEOS_PHP_RETURN_STRING(x) RETURN_STRING((x),0)
+# define GEOS_PHP_RETURN_STRINGL(x,s) RETURN_STRINGL((x),(s),0)
+# define GEOS_PHP_ADD_ASSOC_ARRAY(a,k,v) add_assoc_string((a), (k), (v), 0)
+# define GEOS_PHP_ADD_ASSOC_ZVAL(a,k,v) add_assoc_zval((a), (k), (v))
+# define GEOS_PHP_HASH_GET_CUR_KEY(s,k,i) zend_hash_get_current_key((s), (k), (i), 0)
+# define zend_string char
+# define ZSTR_VAL(x) (x)
+# define GEOS_PHP_HASH_GET_CUR_DATA(h,d) zend_hash_get_current_data((h),(void**)&(d))
+# define GEOS_PHP_ZVAL zval **
+#endif
+
+
+static zend_function_entry geos_functions[] = {
+    PHP_FE(GEOSVersion, NULL)
+    PHP_FE(GEOSPolygonize, NULL)
+    PHP_FE(GEOSLineMerge, NULL)
+
+#   ifdef HAVE_GEOS_SHARED_PATHS
+    PHP_FE(GEOSSharedPaths, NULL)
+#   endif
+
+#   ifdef HAVE_GEOS_RELATE_PATTERN_MATCH
+    PHP_FE(GEOSRelateMatch, NULL)
+#   endif
+    {NULL, NULL, NULL}
+};
+
+zend_module_entry geos_module_entry = {
+    STANDARD_MODULE_HEADER,
+    PHP_GEOS_EXTNAME,
+    geos_functions,
+    PHP_MINIT(geos),              /* module init function */
+    PHP_MSHUTDOWN(geos),          /* module shutdown function */
+    PHP_RINIT(geos),              /* request init function */
+    PHP_RSHUTDOWN(geos),          /* request shutdown function */
+    PHP_MINFO(geos),              /* module info function */
+    PHP_GEOS_VERSION,
+    PHP_MODULE_GLOBALS(geos),     /* globals descriptor */
+    PHP_GINIT(geos),              /* globals ctor */
+    NULL,                         /* globals dtor */
+    NULL,                         /* post deactivate */
+    STANDARD_MODULE_PROPERTIES_EX
+};
+
+#ifdef COMPILE_DL_GEOS
+ZEND_GET_MODULE(geos)
+#endif
+
+/* -- Utility functions ---------------------- */
+
+static void noticeHandler(const char *fmt, ...)
+{
+    TSRMLS_FETCH();
+    char message[256];
+    va_list args;
+    va_start(args, fmt);
+    vsnprintf(message, sizeof(message) - 1, fmt, args);
+    va_end(args);
+
+    php_error_docref(NULL TSRMLS_CC, E_NOTICE, "%s", message);
+}
+
+static void errorHandler(const char *fmt, ...)
+{
+    TSRMLS_FETCH();
+    char message[256];
+    va_list args;
+    va_start(args, fmt);
+    vsnprintf(message, sizeof(message) - 1, fmt, args);
+    va_end(args);
+
+    /* TODO: use a GEOSException ? */
+    zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C),
+        1 TSRMLS_CC, "%s", message);
+
+}
+
+typedef struct Proxy_t {
+#if PHP_VERSION_ID >= 70000
+    int id;
+    void* relay;
+    zend_object std;
+#else
+    zend_object std;
+    void* relay;
+#endif
+} Proxy;
+
+#if PHP_VERSION_ID >= 70000
+static inline Proxy *php_geos_fetch_object(zend_object *obj) {
+  return (Proxy *)((char *) obj - XtOffsetOf(Proxy, std));
+}
+# define Z_GEOS_OBJ_P(zv) (Proxy *)((char *) (Z_OBJ_P(zv)) - XtOffsetOf(Proxy, std))
+#else
+# ifdef Z_OBJ
+#  define Z_GEOS_OBJ_P(zv) (Proxy*)Z_OBJ(*val TSRMLS_CC)
+# else
+#  define Z_GEOS_OBJ_P(zv) (Proxy*)zend_object_store_get_object(val TSRMLS_CC)
+# endif
+#endif
+
+static void
+setRelay(zval* val, void* obj) {
+    TSRMLS_FETCH();
+
+    Proxy* proxy = Z_GEOS_OBJ_P(val);
+
+    proxy->relay = obj;
+}
+
+static inline void *
+getRelay(zval* val, zend_class_entry* ce) {
+    TSRMLS_FETCH();
+
+    Proxy* proxy = Z_GEOS_OBJ_P(val);
+
+    if ( proxy->std.ce != ce ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+            "Relay object is not an %s", ce->name);
+    }
+    if ( ! proxy->relay ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+            "Relay object for object of type %s is not set", ce->name);
+    }
+    return proxy->relay;
+}
+
+static long getZvalAsLong(GEOS_PHP_ZVAL val)
+{
+    long ret;
+    zval tmp;
+
+#if PHP_VERSION_ID >= 70000
+    tmp = *val;
+#else
+    tmp = **val;
+#endif
+    zval_copy_ctor(&tmp);
+    convert_to_long(&tmp);
+    ret = Z_LVAL(tmp);
+    zval_dtor(&tmp);
+    return ret;
+}
+
+static long getZvalAsDouble(GEOS_PHP_ZVAL val)
+{
+    double ret;
+    zval tmp;
+
+#if PHP_VERSION_ID >= 70000
+    tmp = *val;
+#else
+    tmp = **val;
+#endif
+    zval_copy_ctor(&tmp);
+    convert_to_double(&tmp);
+    ret = Z_DVAL(tmp);
+    zval_dtor(&tmp);
+    return ret;
+}
+
+static zend_object_value
+Gen_create_obj (zend_class_entry *type,
+    void (*dtor)(GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC),
+    zend_object_handlers* handlers)
+{
+    TSRMLS_FETCH();
+
+#if PHP_VERSION_ID >= 70000
+
+    Proxy *obj = (Proxy *) ecalloc(1, sizeof(Proxy) + zend_object_properties_size(type));
+
+    zend_object_std_init(&obj->std, type TSRMLS_CC);
+    object_properties_init(&obj->std, type);
+
+    obj->std.handlers = handlers;
+
+    /* TODO: install the destructor ? (dtor) ! */
+    /* TODO: do not allocate a full Proxy if we're going to use an object */
+
+    return &obj->std;
+
+#else /* PHP_VERSION_ID < 70000 */
+
+    zend_object_value retval;
+
+    Proxy *obj = (Proxy *)ecalloc(1, sizeof(Proxy));
+
+    obj->std.ce = type;
+
+    ALLOC_HASHTABLE(obj->std.properties);
+    zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
+#if PHP_VERSION_ID < 50399
+    zend_hash_copy(obj->std.properties, &type->default_properties,
+        (copy_ctor_func_t)zval_add_ref, NULL, sizeof(zval *));
+#else
+    object_properties_init(&(obj->std), type);
+#endif
+
+    retval.handle = zend_objects_store_put(obj, NULL, dtor, NULL TSRMLS_CC);
+    retval.handlers = handlers;
+
+    return retval;
+
+#endif /* PHP_VERSION_ID < 70000 */
+}
+
+
+/* -- class GEOSGeometry -------------------- */
+
+PHP_METHOD(Geometry, __construct);
+PHP_METHOD(Geometry, __toString);
+PHP_METHOD(Geometry, project);
+PHP_METHOD(Geometry, interpolate);
+PHP_METHOD(Geometry, buffer);
+
+#ifdef HAVE_GEOS_OFFSET_CURVE
+PHP_METHOD(Geometry, offsetCurve);
+#endif
+
+PHP_METHOD(Geometry, envelope);
+PHP_METHOD(Geometry, intersection);
+PHP_METHOD(Geometry, convexHull);
+PHP_METHOD(Geometry, difference);
+PHP_METHOD(Geometry, symDifference);
+PHP_METHOD(Geometry, boundary);
+PHP_METHOD(Geometry, union); /* also does union cascaded */
+PHP_METHOD(Geometry, pointOnSurface);
+PHP_METHOD(Geometry, centroid);
+PHP_METHOD(Geometry, relate);
+
+#ifdef HAVE_GEOS_RELATE_BOUNDARY_NODE_RULE
+PHP_METHOD(Geometry, relateBoundaryNodeRule);
+#endif
+
+PHP_METHOD(Geometry, simplify); /* also does topology-preserving */
+PHP_METHOD(Geometry, normalize);
+
+#ifdef HAVE_GEOS_GEOM_SET_PRECISION
+PHP_METHOD(Geometry, setPrecision);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_PRECISION
+PHP_METHOD(Geometry, getPrecision);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_EXTRACT_UNIQUE_POINTS
+PHP_METHOD(Geometry, extractUniquePoints);
+#endif
+
+PHP_METHOD(Geometry, disjoint);
+PHP_METHOD(Geometry, touches);
+PHP_METHOD(Geometry, intersects);
+PHP_METHOD(Geometry, crosses);
+PHP_METHOD(Geometry, within);
+PHP_METHOD(Geometry, contains);
+PHP_METHOD(Geometry, overlaps);
+
+#ifdef HAVE_GEOS_COVERS
+PHP_METHOD(Geometry, covers);
+#endif
+
+#ifdef HAVE_GEOS_COVERED_BY
+PHP_METHOD(Geometry, coveredBy);
+#endif
+
+PHP_METHOD(Geometry, equals);
+PHP_METHOD(Geometry, equalsExact);
+PHP_METHOD(Geometry, isEmpty);
+
+#ifdef HAVE_GEOS_IS_VALID_DETAIL
+PHP_METHOD(Geometry, checkValidity);
+#endif
+
+PHP_METHOD(Geometry, isSimple);
+PHP_METHOD(Geometry, isRing);
+PHP_METHOD(Geometry, hasZ);
+
+#ifdef HAVE_GEOS_IS_CLOSED
+PHP_METHOD(Geometry, isClosed);
+#endif
+
+PHP_METHOD(Geometry, typeName);
+PHP_METHOD(Geometry, typeId);
+PHP_METHOD(Geometry, getSRID);
+PHP_METHOD(Geometry, setSRID);
+PHP_METHOD(Geometry, numGeometries);
+PHP_METHOD(Geometry, geometryN);
+PHP_METHOD(Geometry, numInteriorRings);
+
+#ifdef HAVE_GEOS_GEOM_GET_NUM_POINTS
+PHP_METHOD(Geometry, numPoints);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_X
+PHP_METHOD(Geometry, getX);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_Y
+PHP_METHOD(Geometry, getY);
+#endif
+
+PHP_METHOD(Geometry, interiorRingN);
+PHP_METHOD(Geometry, exteriorRing);
+PHP_METHOD(Geometry, numCoordinates);
+PHP_METHOD(Geometry, dimension);
+
+#ifdef HAVE_GEOS_GEOM_GET_COORDINATE_DIMENSION
+PHP_METHOD(Geometry, coordinateDimension);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_POINT_N
+PHP_METHOD(Geometry, pointN);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_START_POINT
+PHP_METHOD(Geometry, startPoint);
+#endif
+
+#ifdef HAVE_GEOS_GEOM_GET_END_POINT
+PHP_METHOD(Geometry, endPoint);
+#endif
+
+PHP_METHOD(Geometry, area);
+PHP_METHOD(Geometry, length);
+PHP_METHOD(Geometry, distance);
+PHP_METHOD(Geometry, hausdorffDistance);
+
+#ifdef HAVE_GEOS_SNAP
+PHP_METHOD(Geometry, snapTo);
+#endif
+
+#ifdef HAVE_GEOS_NODE
+PHP_METHOD(Geometry, node);
+#endif
+
+#ifdef HAVE_GEOS_DELAUNAY_TRIANGULATION
+PHP_METHOD(Geometry, delaunayTriangulation);
+#endif
+
+#ifdef HAVE_GEOS_VORONOI_DIAGRAM
+PHP_METHOD(Geometry, voronoiDiagram);
+#endif
+
+#ifdef HAVE_GEOS_CLIP_BY_RECT
+PHP_METHOD(Geometry, clipByRect);
+#endif
+
+static zend_function_entry Geometry_methods[] = {
+    PHP_ME(Geometry, __construct, NULL, 0)
+    PHP_ME(Geometry, __toString, NULL, 0)
+    PHP_ME(Geometry, project, NULL, 0)
+    PHP_ME(Geometry, interpolate, NULL, 0)
+    PHP_ME(Geometry, buffer, NULL, 0)
+
+#   ifdef HAVE_GEOS_OFFSET_CURVE
+    PHP_ME(Geometry, offsetCurve, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, envelope, NULL, 0)
+    PHP_ME(Geometry, intersection, NULL, 0)
+    PHP_ME(Geometry, convexHull, NULL, 0)
+    PHP_ME(Geometry, difference, NULL, 0)
+    PHP_ME(Geometry, symDifference, NULL, 0)
+    PHP_ME(Geometry, boundary, NULL, 0)
+    PHP_ME(Geometry, union, NULL, 0)
+    PHP_ME(Geometry, pointOnSurface, NULL, 0)
+    PHP_ME(Geometry, centroid, NULL, 0)
+    PHP_ME(Geometry, relate, NULL, 0)
+
+#   ifdef HAVE_GEOS_RELATE_BOUNDARY_NODE_RULE
+    PHP_ME(Geometry, relateBoundaryNodeRule, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, simplify, NULL, 0)
+    PHP_ME(Geometry, normalize, NULL, 0)
+
+#   ifdef HAVE_GEOS_GEOM_SET_PRECISION
+    PHP_ME(Geometry, setPrecision, NULL, 0)
+#   endif
+
+#   if HAVE_GEOS_GEOM_GET_PRECISION
+    PHP_ME(Geometry, getPrecision, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_EXTRACT_UNIQUE_POINTS
+    PHP_ME(Geometry, extractUniquePoints, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, disjoint, NULL, 0)
+    PHP_ME(Geometry, touches, NULL, 0)
+    PHP_ME(Geometry, intersects, NULL, 0)
+    PHP_ME(Geometry, crosses, NULL, 0)
+    PHP_ME(Geometry, within, NULL, 0)
+    PHP_ME(Geometry, contains, NULL, 0)
+    PHP_ME(Geometry, overlaps, NULL, 0)
+
+#   ifdef HAVE_GEOS_COVERS
+    PHP_ME(Geometry, covers, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_COVERED_BY
+    PHP_ME(Geometry, coveredBy, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, equals, NULL, 0)
+    PHP_ME(Geometry, equalsExact, NULL, 0)
+    PHP_ME(Geometry, isEmpty, NULL, 0)
+
+#   ifdef HAVE_GEOS_IS_VALID_DETAIL
+    PHP_ME(Geometry, checkValidity, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, isSimple, NULL, 0)
+    PHP_ME(Geometry, isRing, NULL, 0)
+    PHP_ME(Geometry, hasZ, NULL, 0)
+
+#   ifdef HAVE_GEOS_IS_CLOSED
+    PHP_ME(Geometry, isClosed, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, typeName, NULL, 0)
+    PHP_ME(Geometry, typeId, NULL, 0)
+    PHP_ME(Geometry, getSRID, NULL, 0)
+    PHP_ME(Geometry, setSRID, NULL, 0)
+    PHP_ME(Geometry, numGeometries, NULL, 0)
+    PHP_ME(Geometry, geometryN, NULL, 0)
+    PHP_ME(Geometry, numInteriorRings, NULL, 0)
+
+#   ifdef HAVE_GEOS_GEOM_GET_NUM_POINTS
+    PHP_ME(Geometry, numPoints, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_GET_X
+    PHP_ME(Geometry, getX, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_GET_Y
+    PHP_ME(Geometry, getY, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, interiorRingN, NULL, 0)
+    PHP_ME(Geometry, exteriorRing, NULL, 0)
+    PHP_ME(Geometry, numCoordinates, NULL, 0)
+    PHP_ME(Geometry, dimension, NULL, 0)
+
+#   ifdef HAVE_GEOS_GEOM_GET_COORDINATE_DIMENSION
+    PHP_ME(Geometry, coordinateDimension, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_GET_POINT_N
+    PHP_ME(Geometry, pointN, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_GET_START_POINT
+    PHP_ME(Geometry, startPoint, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_GEOM_GET_END_POINT
+    PHP_ME(Geometry, endPoint, NULL, 0)
+#   endif
+
+    PHP_ME(Geometry, area, NULL, 0)
+    PHP_ME(Geometry, length, NULL, 0)
+    PHP_ME(Geometry, distance, NULL, 0)
+    PHP_ME(Geometry, hausdorffDistance, NULL, 0)
+
+#   if HAVE_GEOS_SNAP
+    PHP_ME(Geometry, snapTo, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_NODE
+    PHP_ME(Geometry, node, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_DELAUNAY_TRIANGULATION
+    PHP_ME(Geometry, delaunayTriangulation, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_VORONOI_DIAGRAM
+    PHP_ME(Geometry, voronoiDiagram, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_CLIP_BY_RECT
+    PHP_ME(Geometry, clipByRect, NULL, 0)
+#   endif
+
+    {NULL, NULL, NULL}
+};
+
+static zend_class_entry *Geometry_ce_ptr;
+
+static zend_object_handlers Geometry_object_handlers;
+
+/* Geometry serializer */
+
+static GEOSWKBWriter* Geometry_serializer = 0;
+
+static GEOSWKBWriter* getGeometrySerializer()
+{
+    TSRMLS_FETCH();
+
+    if ( ! Geometry_serializer ) {
+        Geometry_serializer = GEOSWKBWriter_create_r(GEOS_G(handle));
+        GEOSWKBWriter_setIncludeSRID_r(GEOS_G(handle), Geometry_serializer, 1);
+        GEOSWKBWriter_setOutputDimension_r(GEOS_G(handle), Geometry_serializer, 3);
+    }
+    return Geometry_serializer;
+}
+
+static void delGeometrySerializer()
+{
+    TSRMLS_FETCH();
+
+    if ( Geometry_serializer ) {
+        GEOSWKBWriter_destroy_r(GEOS_G(handle), Geometry_serializer);
+    }
+}
+
+/* Geometry deserializer */
+
+static GEOSWKBReader* Geometry_deserializer = 0;
+
+static GEOSWKBReader* getGeometryDeserializer()
+{
+    TSRMLS_FETCH();
+
+    if ( ! Geometry_deserializer ) {
+        Geometry_deserializer = GEOSWKBReader_create_r(GEOS_G(handle));
+    }
+    return Geometry_deserializer;
+}
+
+static void delGeometryDeserializer()
+{
+    TSRMLS_FETCH();
+
+    if ( Geometry_deserializer ) {
+        GEOSWKBReader_destroy_r(GEOS_G(handle), Geometry_deserializer);
+    }
+}
+
+/* Serializer function for GEOSGeometry */
+
+static int
+Geometry_serialize(zval *object, unsigned char **buffer, zend_uint *buf_len,
+        zend_serialize_data *data TSRMLS_DC)
+{
+    GEOSWKBWriter *serializer;
+    GEOSGeometry *geom;
+    char* ret;
+    size_t retsize;
+
+
+    serializer = getGeometrySerializer();
+    geom = (GEOSGeometry*)getRelay(object, Geometry_ce_ptr);
+
+    /* NOTE: we might be fine using binary here */
+    ret = (char*)GEOSWKBWriter_writeHEX_r(GEOS_G(handle), serializer, geom, &retsize);
+    /* we'll probably get an exception if ret is null */
+    if ( ! ret ) return FAILURE;
+
+    *buffer = (unsigned char*)estrndup(ret, retsize);
+    GEOSFree_r(GEOS_G(handle), ret);
+
+    *buf_len = retsize;
+
+    return SUCCESS;
+}
+
+static int
+Geometry_deserialize(GEOS_PHP_ZVAL object, zend_class_entry *ce, const unsigned char *buf,
+        zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC)
+{
+    GEOSWKBReader* deserializer;
+    GEOSGeometry* geom;
+
+    deserializer = getGeometryDeserializer();
+    geom = GEOSWKBReader_readHEX_r(GEOS_G(handle), deserializer, buf, buf_len);
+
+    /* check zend_class_entry being what we expect! */
+    if ( ce != Geometry_ce_ptr ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+                "Geometry_deserialize called with unexpected zend_class_entry");
+        return FAILURE;
+    }
+#if PHP_VERSION_ID >= 70000
+    object_init_ex(object, ce);
+    setRelay(object, geom);
+#else
+    object_init_ex(*object, ce);
+    setRelay(*object, geom);
+#endif
+
+    return SUCCESS;
+}
+
+/*
+ * Push components of the given geometry
+ * to the given array zval.
+ * Components geometries are cloned.
+ * NOTE: collection components are not descended into
+ */
+static void
+dumpGeometry(GEOSGeometry* g, zval* array)
+{
+    TSRMLS_FETCH();
+    int ngeoms, i;
+
+    ngeoms = GEOSGetNumGeometries_r(GEOS_G(handle), g);
+    for (i=0; i<ngeoms; ++i)
+    {
+        zval *tmp;
+        GEOSGeometry* cc;
+        const GEOSGeometry* c = GEOSGetGeometryN_r(GEOS_G(handle), g, i);
+        if ( ! c ) continue; /* should get an exception */
+        /* we _need_ to clone as this one is owned by 'g' */
+        cc = GEOSGeom_clone_r(GEOS_G(handle), c);
+        if ( ! cc ) continue; /* should get an exception */
+
+        MAKE_STD_ZVAL(tmp);
+        object_init_ex(tmp, Geometry_ce_ptr);
+        setRelay(tmp, cc);
+        add_next_index_zval(array, tmp);
+#if PHP_VERSION_ID >= 70000
+        efree(tmp);
+#endif
+    }
+}
+
+
+static void
+Geometry_dtor (GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC)
+{
+#if PHP_VERSION_ID < 70000
+    Proxy *obj = (Proxy *)object;
+#else
+    Proxy *obj = php_geos_fetch_object(object);
+#endif
+
+    GEOSGeom_destroy_r(GEOS_G(handle), (GEOSGeometry*)obj->relay);
+
+#if PHP_VERSION_ID >= 70000
+    //zend_object_std_dtor(&obj->std);
+#else
+    zend_hash_destroy(obj->std.properties);
+    FREE_HASHTABLE(obj->std.properties);
+
+    efree(obj);
+#endif
+}
+
+static zend_object_value
+Geometry_create_obj (zend_class_entry *type TSRMLS_DC)
+{
+    return Gen_create_obj(type, Geometry_dtor, &Geometry_object_handlers);
+}
+
+
+PHP_METHOD(Geometry, __construct)
+{
+    php_error_docref(NULL TSRMLS_CC, E_ERROR,
+            "GEOSGeometry can't be constructed using new, check WKTReader");
+
+}
+
+PHP_METHOD(Geometry, __toString)
+{
+    GEOSGeometry *geom;
+    GEOSWKTWriter *writer;
+    char *wkt;
+    char *ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+    writer = GEOSWKTWriter_create_r(GEOS_G(handle));
+    /* NOTE: if we get an exception before reaching
+     *       GEOSWKTWriter_destory below we'll be leaking memory.
+     *       One fix could be storing the object in a refcounted
+     *       zval.
+     */
+#   ifdef HAVE_GEOS_WKT_WRITER_SET_TRIM
+    GEOSWKTWriter_setTrim_r(GEOS_G(handle), writer, 1);
+#   endif
+
+    wkt = GEOSWKTWriter_write_r(GEOS_G(handle), writer, geom);
+    /* we'll probably get an exception if wkt is null */
+    if ( ! wkt ) RETURN_NULL();
+
+    GEOSWKTWriter_destroy_r(GEOS_G(handle), writer);
+
+
+    ret = estrdup(wkt);
+    GEOSFree_r(GEOS_G(handle), wkt);
+
+    GEOS_PHP_RETURN_STRING(ret);
+}
+
+PHP_METHOD(Geometry, project)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    zval *zobj;
+    zend_bool normalized = 0;
+    double ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|b", &zobj,
+            &normalized) == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    if ( normalized ) {
+        ret = GEOSProjectNormalized_r(GEOS_G(handle), this, other);
+    } else {
+        ret = GEOSProject_r(GEOS_G(handle), this, other);
+    }
+    if ( ret < 0 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(ret);
+}
+
+PHP_METHOD(Geometry, interpolate)
+{
+    GEOSGeometry *this;
+    double dist;
+    GEOSGeometry *ret;
+    zend_bool normalized = 0;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|b",
+            &dist, &normalized) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( normalized ) {
+        ret = GEOSInterpolateNormalized_r(GEOS_G(handle), this, dist);
+    } else {
+        ret = GEOSInterpolate_r(GEOS_G(handle), this, dist);
+    }
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::buffer(dist, [<styleArray>])
+ *
+ * styleArray keys supported:
+ *  'quad_segs'
+ *       Type: int
+ *       Number of segments used to approximate
+ *       a quarter circle (defaults to 8).
+ *  'endcap'
+ *       Type: long
+ *       Endcap style (defaults to GEOSBUF_CAP_ROUND)
+ *  'join'
+ *       Type: long
+ *       Join style (defaults to GEOSBUF_JOIN_ROUND)
+ *  'mitre_limit'
+ *       Type: double
+ *       mitre ratio limit (only affects joins with GEOSBUF_JOIN_MITRE style)
+ *       'miter_limit' is also accepted as a synonym for 'mitre_limit'.
+ *  'single_sided'
+ *       Type: bool
+ *       If true buffer lines only on one side, so that the input line
+ *       will be a portion of the boundary of the returned polygon.
+ *       Only applies to lineal input. Defaults to false.
+ */
+PHP_METHOD(Geometry, buffer)
+{
+    GEOSGeometry *this;
+    double dist;
+    GEOSGeometry *ret;
+    GEOSBufferParams *params;
+    static const double default_mitreLimit = 5.0;
+    static const int default_endCapStyle = GEOSBUF_CAP_ROUND;
+    static const int default_joinStyle = GEOSBUF_JOIN_ROUND;
+    static const int default_quadSegs = 8;
+    long int quadSegs = default_quadSegs;
+    long int endCapStyle = default_endCapStyle;
+    long int joinStyle = default_joinStyle;
+    double mitreLimit = default_mitreLimit;
+    long singleSided = 0;
+    zval *style_val = NULL;
+    GEOS_PHP_ZVAL data;
+    HashTable *style;
+    zend_string *key;
+    ulong index;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|a",
+            &dist, &style_val) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    params = GEOSBufferParams_create_r(GEOS_G(handle));
+
+    if ( style_val )
+    {
+        style = HASH_OF(style_val);
+        while(GEOS_PHP_HASH_GET_CUR_KEY(style, &key, &index)
+              == HASH_KEY_IS_STRING)
+        {
+            if(!strcmp(ZSTR_VAL(key), "quad_segs"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                quadSegs = getZvalAsLong(data);
+                GEOSBufferParams_setQuadrantSegments_r(GEOS_G(handle), params, quadSegs);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "endcap"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                endCapStyle = getZvalAsLong(data);
+                GEOSBufferParams_setEndCapStyle_r(GEOS_G(handle), params, endCapStyle);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "join"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                joinStyle = getZvalAsLong(data);
+                GEOSBufferParams_setJoinStyle_r(GEOS_G(handle), params, joinStyle);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "mitre_limit"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                mitreLimit = getZvalAsDouble(data);
+                GEOSBufferParams_setMitreLimit_r(GEOS_G(handle), params, mitreLimit);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "single_sided"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                singleSided = getZvalAsLong(data);
+                GEOSBufferParams_setSingleSided_r(GEOS_G(handle), params, singleSided);
+            }
+
+            zend_hash_move_forward(style);
+        }
+    }
+
+    ret = GEOSBufferWithParams_r(GEOS_G(handle), this, params, dist);
+    GEOSBufferParams_destroy_r(GEOS_G(handle), params);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::offsetCurve(dist, [<styleArray>])
+ *
+ * styleArray keys supported:
+ *  'quad_segs'
+ *       Type: int
+ *       Number of segments used to approximate
+ *       a quarter circle (defaults to 8).
+ *  'join'
+ *       Type: long
+ *       Join style (defaults to GEOSBUF_JOIN_ROUND)
+ *  'mitre_limit'
+ *       Type: double
+ *       mitre ratio limit (only affects joins with GEOSBUF_JOIN_MITRE style)
+ *       'miter_limit' is also accepted as a synonym for 'mitre_limit'.
+ */
+#ifdef HAVE_GEOS_OFFSET_CURVE
+PHP_METHOD(Geometry, offsetCurve)
+{
+    GEOSGeometry *this;
+    double dist;
+    GEOSGeometry *ret;
+    static const double default_mitreLimit = 5.0;
+    static const int default_joinStyle = GEOSBUF_JOIN_ROUND;
+    static const int default_quadSegs = 8;
+    long int quadSegs = default_quadSegs;
+    long int joinStyle = default_joinStyle;
+    double mitreLimit = default_mitreLimit;
+    zval *style_val = NULL;
+    GEOS_PHP_ZVAL data;
+    HashTable *style;
+    zend_string *key;
+    ulong index;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|a",
+            &dist, &style_val) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( style_val )
+    {
+        style = HASH_OF(style_val);
+        while(GEOS_PHP_HASH_GET_CUR_KEY(style, &key, &index)
+              == HASH_KEY_IS_STRING)
+        {
+            if(!strcmp(ZSTR_VAL(key), "quad_segs"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                quadSegs = getZvalAsLong(data);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "join"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                joinStyle = getZvalAsLong(data);
+            }
+            else if(!strcmp(ZSTR_VAL(key), "mitre_limit"))
+            {
+                GEOS_PHP_HASH_GET_CUR_DATA(style, data);
+                mitreLimit = getZvalAsDouble(data);
+            }
+
+            zend_hash_move_forward(style);
+        }
+    }
+
+    ret = GEOSOffsetCurve_r(GEOS_G(handle), this, dist, quadSegs, joinStyle, mitreLimit);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+PHP_METHOD(Geometry, envelope)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSEnvelope_r(GEOS_G(handle), this);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+PHP_METHOD(Geometry, intersection)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    GEOSGeometry *ret;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSIntersection_r(GEOS_G(handle), this, other);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::clipByRect(xmin,ymin,xmax,ymax)
+ */
+#ifdef HAVE_GEOS_CLIP_BY_RECT
+PHP_METHOD(Geometry, clipByRect)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+    double xmin,ymin,xmax,ymax;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "dddd",
+            &xmin, &ymin, &xmax, &ymax) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    ret = GEOSClipByRect_r(GEOS_G(handle), this, xmin, ymin, xmax, ymax);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+PHP_METHOD(Geometry, convexHull)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSConvexHull_r(GEOS_G(handle), this);
+    if ( ret == NULL ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+PHP_METHOD(Geometry, difference)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    GEOSGeometry *ret;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSDifference_r(GEOS_G(handle), this, other);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+PHP_METHOD(Geometry, symDifference)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    GEOSGeometry *ret;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSSymDifference_r(GEOS_G(handle), this, other);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+PHP_METHOD(Geometry, boundary)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSBoundary_r(GEOS_G(handle), this);
+    if ( ret == NULL ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::union(otherGeom)
+ * GEOSGeometry::union()
+ */
+PHP_METHOD(Geometry, union)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    GEOSGeometry *ret;
+    zval *zobj = NULL;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( zobj ) {
+        other = getRelay(zobj, Geometry_ce_ptr);
+        ret = GEOSUnion_r(GEOS_G(handle), this, other);
+    } else {
+#       ifdef HAVE_GEOS_UNARY_UNION
+        ret = GEOSUnaryUnion_r(GEOS_G(handle), this);
+#       else
+        ret = GEOSUnionCascaded_r(GEOS_G(handle), this);
+#       endif
+    }
+
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::pointOnSurface()
+ */
+PHP_METHOD(Geometry, pointOnSurface)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSPointOnSurface_r(GEOS_G(handle), this);
+    if ( ret == NULL ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::centroid()
+ */
+PHP_METHOD(Geometry, centroid)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGetCentroid_r(GEOS_G(handle), this);
+    if ( ret == NULL ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry::relate(otherGeom)
+ * GEOSGeometry::relate(otherGeom, pattern)
+ */
+PHP_METHOD(Geometry, relate)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    zval *zobj;
+    char* pat = NULL;
+#if PHP_VERSION_ID >= 70000
+    size_t patlen;
+#else
+    int patlen;
+#endif
+    int retInt;
+    zend_bool retBool;
+    char* retStr;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|s",
+        &zobj, &pat, &patlen) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    if ( ! pat ) {
+        /* we'll compute it */
+        pat = GEOSRelate_r(GEOS_G(handle), this, other);
+        if ( ! pat ) RETURN_NULL(); /* should get an exception first */
+        retStr = estrdup(pat);
+        GEOSFree_r(GEOS_G(handle), pat);
+        GEOS_PHP_RETURN_STRING(retStr);
+    } else {
+        retInt = GEOSRelatePattern_r(GEOS_G(handle), this, other, pat);
+        if ( retInt == 2 ) RETURN_NULL(); /* should get an exception first */
+        retBool = retInt;
+        RETURN_BOOL(retBool);
+    }
+
+}
+
+/**
+ * GEOSGeometry::relateBoundaryNodeRule(otherGeom, rule)
+ */
+#ifdef HAVE_GEOS_RELATE_BOUNDARY_NODE_RULE
+PHP_METHOD(Geometry, relateBoundaryNodeRule)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    zval *zobj;
+    char* pat;
+    long int bnr = GEOSRELATE_BNR_OGC;
+    char* retStr;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ol",
+        &zobj, &bnr) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    /* we'll compute it */
+    pat = GEOSRelateBoundaryNodeRule_r(GEOS_G(handle), this, other, bnr);
+    if ( ! pat ) RETURN_NULL(); /* should get an exception first */
+    retStr = estrdup(pat);
+    GEOSFree_r(GEOS_G(handle), pat);
+    GEOS_PHP_RETURN_STRING(retStr);
+}
+#endif
+
+/**
+ * GEOSGeometry GEOSGeometry::simplify(tolerance)
+ * GEOSGeometry GEOSGeometry::simplify(tolerance, preserveTopology)
+ */
+PHP_METHOD(Geometry, simplify)
+{
+    GEOSGeometry *this;
+    double tolerance;
+    zend_bool preserveTopology = 0;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|b",
+            &tolerance, &preserveTopology) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( preserveTopology ) {
+        ret = GEOSTopologyPreserveSimplify_r(GEOS_G(handle), this, tolerance);
+    } else {
+        ret = GEOSSimplify_r(GEOS_G(handle), this, tolerance);
+    }
+
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::setPrecision(gridsize, [flags])
+ */
+#ifdef HAVE_GEOS_GEOM_SET_PRECISION
+PHP_METHOD(Geometry, setPrecision)
+{
+    GEOSGeometry *this;
+    double gridSize;
+    long int flags = 0;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "d|l",
+            &gridSize, &flags) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    ret = GEOSGeom_setPrecision_r(GEOS_G(handle), this, gridSize, flags);
+
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+/**
+ * double GEOSGeometry::getPrecision()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_PRECISION
+PHP_METHOD(Geometry, getPrecision)
+{
+    GEOSGeometry *geom;
+    double prec;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    prec = GEOSGeom_getPrecision_r(GEOS_G(handle), geom);
+    if ( prec < 0 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(prec);
+}
+#endif
+
+/**
+ * GEOSGeometry GEOSGeometry::normalize()
+ */
+PHP_METHOD(Geometry, normalize)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeom_clone_r(GEOS_G(handle), this);
+
+    if ( ! ret ) RETURN_NULL();
+
+    GEOSNormalize_r(GEOS_G(handle), ret); /* exception should be gotten automatically */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::extractUniquePoints()
+ */
+#ifdef HAVE_GEOS_GEOM_EXTRACT_UNIQUE_POINTS
+PHP_METHOD(Geometry, extractUniquePoints)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeom_extractUniquePoints_r(GEOS_G(handle), this);
+    if ( ret == NULL ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+/**
+ * bool GEOSGeometry::disjoint(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, disjoint)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSDisjoint_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::touches(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, touches)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSTouches_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::intersects(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, intersects)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSIntersects_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::crosses(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, crosses)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSCrosses_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::within(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, within)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSWithin_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::contains(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, contains)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSContains_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::overlaps(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, overlaps)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSOverlaps_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::covers(GEOSGeometry)
+ */
+#ifdef HAVE_GEOS_COVERS
+PHP_METHOD(Geometry, covers)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSCovers_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+#endif
+
+/**
+ * bool GEOSGeometry::coveredBy(GEOSGeometry)
+ */
+#ifdef HAVE_GEOS_COVERED_BY
+PHP_METHOD(Geometry, coveredBy)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+            == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSCoveredBy_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+#endif
+
+/**
+ * bool GEOSGeometry::equals(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, equals)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o",
+        &zobj) == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSEquals_r(GEOS_G(handle), this, other);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::equalsExact(GEOSGeometry)
+ * bool GEOSGeometry::equalsExact(GEOSGeometry, double tolerance)
+ */
+PHP_METHOD(Geometry, equalsExact)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    int ret;
+    double tolerance = 0;
+    zend_bool retBool;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o|d",
+        &zobj, &tolerance) == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSEqualsExact_r(GEOS_G(handle), this, other, tolerance);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::isEmpty()
+ */
+PHP_METHOD(Geometry, isEmpty)
+{
+    GEOSGeometry *this;
+    int ret;
+    zend_bool retBool;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSisEmpty_r(GEOS_G(handle), this);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * array GEOSGeometry::checkValidity()
+ */
+#ifdef HAVE_GEOS_IS_VALID_DETAIL
+PHP_METHOD(Geometry, checkValidity)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *location = NULL;
+    int ret;
+    char *reason = NULL;
+    zend_bool retBool;
+    char *reasonVal = NULL;
+    zval *locationVal = NULL;
+    long int flags = 0;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l",
+        &flags) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    ret = GEOSisValidDetail_r(GEOS_G(handle), this, flags, &reason, &location);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    if ( reason ) {
+        reasonVal = estrdup(reason);
+        GEOSFree_r(GEOS_G(handle), reason);
+    }
+
+    if ( location ) {
+        MAKE_STD_ZVAL(locationVal);
+        object_init_ex(locationVal, Geometry_ce_ptr);
+        setRelay(locationVal, location);
+    }
+
+    retBool = ret;
+
+    /* return value is an array */
+    array_init(return_value);
+    add_assoc_bool(return_value, "valid", retBool);
+    if ( reasonVal ) GEOS_PHP_ADD_ASSOC_ARRAY(return_value, "reason", reasonVal);
+    if ( locationVal ) GEOS_PHP_ADD_ASSOC_ZVAL(return_value, "location", locationVal);
+
+}
+#endif
+
+/**
+ * bool GEOSGeometry::isSimple()
+ */
+PHP_METHOD(Geometry, isSimple)
+{
+    GEOSGeometry *this;
+    int ret;
+    zend_bool retBool;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSisSimple_r(GEOS_G(handle), this);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::isRing()
+ */
+PHP_METHOD(Geometry, isRing)
+{
+    GEOSGeometry *this;
+    int ret;
+    zend_bool retBool;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSisRing_r(GEOS_G(handle), this);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::hasZ()
+ */
+PHP_METHOD(Geometry, hasZ)
+{
+    GEOSGeometry *this;
+    int ret;
+    zend_bool retBool;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSHasZ_r(GEOS_G(handle), this);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * bool GEOSGeometry::isClosed()
+ */
+#ifdef HAVE_GEOS_IS_CLOSED
+PHP_METHOD(Geometry, isClosed)
+{
+    GEOSGeometry *this;
+    int ret;
+    zend_bool retBool;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSisClosed_r(GEOS_G(handle), this);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+#endif
+
+/**
+ * string GEOSGeometry::typeName()
+ */
+PHP_METHOD(Geometry, typeName)
+{
+    GEOSGeometry *this;
+    char *typ;
+    char *typVal;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    /* TODO: define constant strings instead... */
+
+    typ = GEOSGeomType_r(GEOS_G(handle), this);
+    if ( ! typ ) RETURN_NULL(); /* should get an exception first */
+
+    typVal = estrdup(typ);
+    GEOSFree_r(GEOS_G(handle), typ);
+
+    GEOS_PHP_RETURN_STRING(typVal);
+}
+
+/**
+ * long GEOSGeometry::typeId()
+ */
+PHP_METHOD(Geometry, typeId)
+{
+    GEOSGeometry *this;
+    long typ;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    /* TODO: define constant strings instead... */
+
+    typ = GEOSGeomTypeId_r(GEOS_G(handle), this);
+    if ( typ == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(typ);
+}
+
+/**
+ * long GEOSGeometry::getSRID()
+ */
+PHP_METHOD(Geometry, getSRID)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGetSRID_r(GEOS_G(handle), geom);
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * void GEOSGeometry::setSRID(long)
+ */
+PHP_METHOD(Geometry, setSRID)
+{
+    GEOSGeometry *geom;
+    long int srid;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
+        &srid) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    GEOSSetSRID_r(GEOS_G(handle), geom, srid);
+}
+
+/**
+ * long GEOSGeometry::numGeometries()
+ */
+PHP_METHOD(Geometry, numGeometries)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGetNumGeometries_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::geometryN()
+ */
+PHP_METHOD(Geometry, geometryN)
+{
+    GEOSGeometry *geom;
+    const GEOSGeometry *c;
+    GEOSGeometry *cc;
+    long int num;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
+        &num) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( num >= GEOSGetNumGeometries_r(GEOS_G(handle), geom) ) RETURN_NULL();
+    c = GEOSGetGeometryN_r(GEOS_G(handle), geom, num);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+    cc = GEOSGeom_clone_r(GEOS_G(handle), c);
+    if ( ! cc ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, cc);
+}
+
+/**
+ * long GEOSGeometry::numInteriorRings()
+ */
+PHP_METHOD(Geometry, numInteriorRings)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGetNumInteriorRings_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * long GEOSGeometry::numPoints()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_NUM_POINTS
+PHP_METHOD(Geometry, numPoints)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeomGetNumPoints_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+#endif
+
+/**
+ * double GEOSGeometry::getX()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_X
+PHP_METHOD(Geometry, getX)
+{
+    GEOSGeometry *geom;
+    int ret;
+    double x;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeomGetX_r(GEOS_G(handle), geom, &x);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(x);
+}
+#endif
+
+/**
+ * double GEOSGeometry::getY()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_Y
+PHP_METHOD(Geometry, getY)
+{
+    GEOSGeometry *geom;
+    int ret;
+    double y;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeomGetY_r(GEOS_G(handle), geom, &y);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(y);
+}
+#endif
+
+/**
+ * GEOSGeometry GEOSGeometry::interiorRingN()
+ */
+PHP_METHOD(Geometry, interiorRingN)
+{
+    GEOSGeometry *geom;
+    const GEOSGeometry *c;
+    GEOSGeometry *cc;
+    long int num;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
+        &num) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( num >= GEOSGetNumInteriorRings_r(GEOS_G(handle), geom) ) RETURN_NULL();
+    c = GEOSGetInteriorRingN_r(GEOS_G(handle), geom, num);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+    cc = GEOSGeom_clone_r(GEOS_G(handle), c);
+    if ( ! cc ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, cc);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::exteriorRing()
+ */
+PHP_METHOD(Geometry, exteriorRing)
+{
+    GEOSGeometry *geom;
+    const GEOSGeometry *c;
+    GEOSGeometry *cc;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    c = GEOSGetExteriorRing_r(GEOS_G(handle), geom);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+    cc = GEOSGeom_clone_r(GEOS_G(handle), c);
+    if ( ! cc ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, cc);
+}
+
+/**
+ * long GEOSGeometry::numCoordinates()
+ */
+PHP_METHOD(Geometry, numCoordinates)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGetNumCoordinates_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * long GEOSGeometry::dimension()
+ * 0:puntual 1:lineal 2:areal
+ */
+PHP_METHOD(Geometry, dimension)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeom_getDimensions_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * long GEOSGeometry::coordinateDimension()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_COORDINATE_DIMENSION
+PHP_METHOD(Geometry, coordinateDimension)
+{
+    GEOSGeometry *geom;
+    long int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSGeom_getCoordinateDimension_r(GEOS_G(handle), geom);
+    if ( ret == -1 ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_LONG(ret);
+}
+#endif
+
+/**
+ * GEOSGeometry GEOSGeometry::pointN()
+ */
+#ifdef HAVE_GEOS_GEOM_GET_POINT_N
+PHP_METHOD(Geometry, pointN)
+{
+    GEOSGeometry *geom;
+    GEOSGeometry *c;
+    long int num;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l",
+        &num) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( num >= GEOSGeomGetNumPoints_r(GEOS_G(handle), geom) ) RETURN_NULL();
+    c = GEOSGeomGetPointN_r(GEOS_G(handle), geom, num);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, c);
+}
+#endif
+
+/**
+ * GEOSGeometry GEOSGeometry::startPoint()
+ */
+PHP_METHOD(Geometry, startPoint)
+{
+    GEOSGeometry *geom;
+    GEOSGeometry *c;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    c = GEOSGeomGetStartPoint_r(GEOS_G(handle), geom);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, c);
+}
+
+/**
+ * GEOSGeometry GEOSGeometry::endPoint()
+ */
+PHP_METHOD(Geometry, endPoint)
+{
+    GEOSGeometry *geom;
+    GEOSGeometry *c;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    c = GEOSGeomGetEndPoint_r(GEOS_G(handle), geom);
+    if ( ! c ) RETURN_NULL(); /* should get an exception first */
+
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, c);
+}
+
+/**
+ * double GEOSGeometry::area()
+ */
+PHP_METHOD(Geometry, area)
+{
+    GEOSGeometry *geom;
+    double area;
+    int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSArea_r(GEOS_G(handle), geom, &area);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(area);
+}
+
+/**
+ * double GEOSGeometry::length()
+ */
+PHP_METHOD(Geometry, length)
+{
+    GEOSGeometry *geom;
+    double length;
+    int ret;
+
+    geom = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSLength_r(GEOS_G(handle), geom, &length);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(length);
+}
+
+/**
+ * double GEOSGeometry::distance(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, distance)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    zval *zobj;
+    double dist;
+    int ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o",
+        &zobj) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSDistance_r(GEOS_G(handle), this, other, &dist);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(dist);
+}
+
+/**
+ * double GEOSGeometry::hausdorffDistance(GEOSGeometry)
+ */
+PHP_METHOD(Geometry, hausdorffDistance)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    zval *zobj;
+    double dist;
+    int ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o",
+        &zobj) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSHausdorffDistance_r(GEOS_G(handle), this, other, &dist);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    RETURN_DOUBLE(dist);
+}
+
+#ifdef HAVE_GEOS_SNAP
+PHP_METHOD(Geometry, snapTo)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *other;
+    GEOSGeometry *ret;
+    double tolerance;
+    zval *zobj;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "od", &zobj,
+            &tolerance) == FAILURE) {
+        RETURN_NULL();
+    }
+    other = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = GEOSSnap_r(GEOS_G(handle), this, other, tolerance);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+#ifdef HAVE_GEOS_NODE
+PHP_METHOD(Geometry, node)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    ret = GEOSNode_r(GEOS_G(handle), this);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+
+
+/* -- class GEOSWKTReader -------------------- */
+
+PHP_METHOD(WKTReader, __construct);
+PHP_METHOD(WKTReader, read);
+
+static zend_function_entry WKTReader_methods[] = {
+    PHP_ME(WKTReader, __construct, NULL, 0)
+    PHP_ME(WKTReader, read, NULL, 0)
+    {NULL, NULL, NULL}
+};
+
+static zend_class_entry *WKTReader_ce_ptr;
+
+static zend_object_handlers WKTReader_object_handlers;
+
+static void
+WKTReader_dtor (GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC)
+{
+#if PHP_VERSION_ID < 70000
+    Proxy *obj = (Proxy *)object;
+#else
+    Proxy *obj = php_geos_fetch_object(object);
+#endif
+
+    GEOSWKTReader *reader = (GEOSWKTReader*)obj->relay;
+    if (reader) {
+        GEOSWKTReader_destroy_r(GEOS_G(handle), reader);
+    }
+
+#if PHP_VERSION_ID < 70000
+    zend_hash_destroy(obj->std.properties);
+    FREE_HASHTABLE(obj->std.properties);
+
+    efree(obj);
+#endif
+}
+
+static zend_object_value
+WKTReader_create_obj (zend_class_entry *type TSRMLS_DC)
+{
+    return Gen_create_obj(type, WKTReader_dtor, &WKTReader_object_handlers);
+}
+
+
+PHP_METHOD(WKTReader, __construct)
+{
+    GEOSWKTReader* obj;
+    zval *object = getThis();
+
+    obj = GEOSWKTReader_create_r(GEOS_G(handle));
+    if ( ! obj ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+                "GEOSWKTReader_create() failed (didn't initGEOS?)");
+    }
+
+    setRelay(object, obj);
+}
+
+PHP_METHOD(WKTReader, read)
+{
+    GEOSWKTReader *reader;
+    GEOSGeometry *geom;
+    zend_string *wkt;
+#if PHP_VERSION_ID < 70000
+    int wktlen;
+#endif
+
+    reader = (GEOSWKTReader*)getRelay(getThis(), WKTReader_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+#if PHP_VERSION_ID >= 70000
+            "S", &wkt
+#else
+            "s", &wkt, &wktlen
+#endif
+       ) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    geom = GEOSWKTReader_read_r(GEOS_G(handle), reader, ZSTR_VAL(wkt));
+    /* we'll probably get an exception if geom is null */
+    if ( ! geom ) RETURN_NULL();
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, geom);
+
+}
+
+/* -- class GEOSWKTWriter -------------------- */
+
+PHP_METHOD(WKTWriter, __construct);
+PHP_METHOD(WKTWriter, write);
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_TRIM
+PHP_METHOD(WKTWriter, setTrim);
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_ROUNDING_PRECISION
+PHP_METHOD(WKTWriter, setRoundingPrecision);
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_OUTPUT_DIMENSION
+PHP_METHOD(WKTWriter, setOutputDimension);
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_GET_OUTPUT_DIMENSION
+PHP_METHOD(WKTWriter, getOutputDimension);
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_OLD_3D
+PHP_METHOD(WKTWriter, setOld3D);
+#endif
+
+static zend_function_entry WKTWriter_methods[] = {
+    PHP_ME(WKTWriter, __construct, NULL, 0)
+    PHP_ME(WKTWriter, write, NULL, 0)
+
+#   ifdef HAVE_GEOS_WKT_WRITER_SET_TRIM
+    PHP_ME(WKTWriter, setTrim, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_WKT_WRITER_SET_ROUNDING_PRECISION
+    PHP_ME(WKTWriter, setRoundingPrecision, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_WKT_WRITER_SET_OUTPUT_DIMENSION
+    PHP_ME(WKTWriter, setOutputDimension, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_WKT_WRITER_GET_OUTPUT_DIMENSION
+    PHP_ME(WKTWriter, getOutputDimension, NULL, 0)
+#   endif
+
+#   ifdef HAVE_GEOS_WKT_WRITER_SET_OLD_3D
+    PHP_ME(WKTWriter, setOld3D, NULL, 0)
+#   endif
+
+    {NULL, NULL, NULL}
+};
+
+static zend_class_entry *WKTWriter_ce_ptr;
+
+static zend_object_handlers WKTWriter_object_handlers;
+
+static void
+WKTWriter_dtor (GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC)
+{
+#if PHP_VERSION_ID < 70000
+    Proxy *obj = (Proxy *)object;
+#else
+    Proxy *obj = php_geos_fetch_object(object);
+#endif
+
+    GEOSWKTWriter_destroy_r(GEOS_G(handle), (GEOSWKTWriter*)obj->relay);
+
+#if PHP_VERSION_ID >= 70000
+    //zend_object_std_dtor(&obj->std);
+#else
+    zend_hash_destroy(obj->std.properties);
+    FREE_HASHTABLE(obj->std.properties);
+
+    efree(obj);
+#endif
+}
+
+static zend_object_value
+WKTWriter_create_obj (zend_class_entry *type TSRMLS_DC)
+{
+    return Gen_create_obj(type, WKTWriter_dtor, &WKTWriter_object_handlers);
+}
+
+PHP_METHOD(WKTWriter, __construct)
+{
+    GEOSWKTWriter* obj;
+    zval *object = getThis();
+
+    obj = GEOSWKTWriter_create_r(GEOS_G(handle));
+    if ( ! obj ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+                "GEOSWKTWriter_create() failed (didn't initGEOS?)");
+    }
+
+    setRelay(object, obj);
+}
+
+PHP_METHOD(WKTWriter, write)
+{
+    GEOSWKTWriter *writer;
+    zval *zobj;
+    GEOSGeometry *geom;
+    char* wkt;
+    char* retstr;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    geom = getRelay(zobj, Geometry_ce_ptr);
+
+    wkt = GEOSWKTWriter_write_r(GEOS_G(handle), writer, geom);
+    /* we'll probably get an exception if wkt is null */
+    if ( ! wkt ) RETURN_NULL();
+
+    retstr = estrdup(wkt);
+    GEOSFree_r(GEOS_G(handle), wkt);
+
+    GEOS_PHP_RETURN_STRING(retstr);
+}
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_TRIM
+PHP_METHOD(WKTWriter, setTrim)
+{
+    GEOSWKTWriter *writer;
+    zend_bool trimval;
+    char trim;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &trimval)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    trim = trimval;
+    GEOSWKTWriter_setTrim_r(GEOS_G(handle), writer, trim);
+}
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_ROUNDING_PRECISION
+PHP_METHOD(WKTWriter, setRoundingPrecision)
+{
+    GEOSWKTWriter *writer;
+    long int prec;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &prec)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    GEOSWKTWriter_setRoundingPrecision_r(GEOS_G(handle), writer, prec);
+}
+#endif
+
+/**
+ * void GEOSWKTWriter::setOutputDimension()
+ */
+#ifdef HAVE_GEOS_WKT_WRITER_SET_OUTPUT_DIMENSION
+PHP_METHOD(WKTWriter, setOutputDimension)
+{
+    GEOSWKTWriter *writer;
+    long int dim;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &dim)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    GEOSWKTWriter_setOutputDimension_r(GEOS_G(handle), writer, dim);
+}
+#endif
+
+/**
+ * long GEOSWKTWriter::getOutputDimension()
+ */
+#ifdef HAVE_GEOS_WKT_WRITER_GET_OUTPUT_DIMENSION
+PHP_METHOD(WKTWriter, getOutputDimension)
+{
+    GEOSWKTWriter *writer;
+    long int ret;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    ret = GEOSWKTWriter_getOutputDimension_r(GEOS_G(handle), writer);
+
+    RETURN_LONG(ret);
+}
+#endif
+
+#ifdef HAVE_GEOS_WKT_WRITER_SET_OLD_3D
+PHP_METHOD(WKTWriter, setOld3D)
+{
+    GEOSWKTWriter *writer;
+    zend_bool bval;
+    int val;
+
+    writer = (GEOSWKTWriter*)getRelay(getThis(), WKTWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &bval)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    val = bval;
+    GEOSWKTWriter_setOld3D_r(GEOS_G(handle), writer, val);
+}
+#endif
+
+/* -- class GEOSWKBWriter -------------------- */
+
+PHP_METHOD(WKBWriter, __construct);
+PHP_METHOD(WKBWriter, getOutputDimension);
+PHP_METHOD(WKBWriter, setOutputDimension);
+PHP_METHOD(WKBWriter, getByteOrder);
+PHP_METHOD(WKBWriter, setByteOrder);
+PHP_METHOD(WKBWriter, setIncludeSRID);
+PHP_METHOD(WKBWriter, getIncludeSRID);
+PHP_METHOD(WKBWriter, write);
+PHP_METHOD(WKBWriter, writeHEX);
+
+static zend_function_entry WKBWriter_methods[] = {
+    PHP_ME(WKBWriter, __construct, NULL, 0)
+    PHP_ME(WKBWriter, getOutputDimension, NULL, 0)
+    PHP_ME(WKBWriter, setOutputDimension, NULL, 0)
+    PHP_ME(WKBWriter, getByteOrder, NULL, 0)
+    PHP_ME(WKBWriter, setByteOrder, NULL, 0)
+    PHP_ME(WKBWriter, getIncludeSRID, NULL, 0)
+    PHP_ME(WKBWriter, setIncludeSRID, NULL, 0)
+    PHP_ME(WKBWriter, write, NULL, 0)
+    PHP_ME(WKBWriter, writeHEX, NULL, 0)
+    {NULL, NULL, NULL}
+};
+
+static zend_class_entry *WKBWriter_ce_ptr;
+
+static zend_object_handlers WKBWriter_object_handlers;
+
+static void
+WKBWriter_dtor (GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC)
+{
+#if PHP_VERSION_ID < 70000
+    Proxy *obj = (Proxy *)object;
+#else
+    Proxy *obj = php_geos_fetch_object(object);
+#endif
+
+    GEOSWKBWriter_destroy_r(GEOS_G(handle), (GEOSWKBWriter*)obj->relay);
+
+#if PHP_VERSION_ID >= 70000
+    //zend_object_std_dtor(&obj->std);
+#else
+    zend_hash_destroy(obj->std.properties);
+    FREE_HASHTABLE(obj->std.properties);
+
+    efree(obj);
+#endif
+}
+
+static zend_object_value
+WKBWriter_create_obj (zend_class_entry *type TSRMLS_DC)
+{
+    return Gen_create_obj(type, WKBWriter_dtor, &WKBWriter_object_handlers);
+}
+
+/**
+ * GEOSWKBWriter w = new GEOSWKBWriter()
+ */
+PHP_METHOD(WKBWriter, __construct)
+{
+    GEOSWKBWriter* obj;
+    zval *object = getThis();
+
+    obj = GEOSWKBWriter_create_r(GEOS_G(handle));
+    if ( ! obj ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+                "GEOSWKBWriter_create() failed (didn't initGEOS?)");
+    }
+
+    setRelay(object, obj);
+}
+
+/**
+ * long GEOSWKBWriter::getOutputDimension();
+ */
+PHP_METHOD(WKBWriter, getOutputDimension)
+{
+    GEOSWKBWriter *writer;
+    long int ret;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    ret = GEOSWKBWriter_getOutputDimension_r(GEOS_G(handle), writer);
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * void GEOSWKBWriter::setOutputDimension(dims);
+ */
+PHP_METHOD(WKBWriter, setOutputDimension)
+{
+    GEOSWKBWriter *writer;
+    long int dim;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &dim)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    GEOSWKBWriter_setOutputDimension_r(GEOS_G(handle), writer, dim);
+
+}
+
+/**
+ * string GEOSWKBWriter::write(GEOSGeometry)
+ */
+PHP_METHOD(WKBWriter, write)
+{
+    GEOSWKBWriter *writer;
+    zval *zobj;
+    GEOSGeometry *geom;
+    char *ret;
+    size_t retsize;
+    char* retstr;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    geom = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = (char*)GEOSWKBWriter_write_r(GEOS_G(handle), writer, geom, &retsize);
+    /* we'll probably get an exception if ret is null */
+    if ( ! ret ) RETURN_NULL();
+
+    retstr = estrndup(ret, retsize);
+    GEOSFree_r(GEOS_G(handle), ret);
+
+    GEOS_PHP_RETURN_STRINGL(retstr, retsize);
+}
+
+/**
+ * string GEOSWKBWriter::writeHEX(GEOSGeometry)
+ */
+PHP_METHOD(WKBWriter, writeHEX)
+{
+    GEOSWKBWriter *writer;
+    zval *zobj;
+    GEOSGeometry *geom;
+    char *ret;
+    size_t retsize; /* useless... */
+    char* retstr;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    geom = getRelay(zobj, Geometry_ce_ptr);
+
+    ret = (char*)GEOSWKBWriter_writeHEX_r(GEOS_G(handle), writer, geom, &retsize);
+    /* we'll probably get an exception if ret is null */
+    if ( ! ret ) RETURN_NULL();
+
+    retstr = estrndup(ret, retsize);
+    GEOSFree_r(GEOS_G(handle), ret);
+
+    GEOS_PHP_RETURN_STRING(retstr);
+}
+
+/**
+ * long GEOSWKBWriter::getByteOrder();
+ */
+PHP_METHOD(WKBWriter, getByteOrder)
+{
+    GEOSWKBWriter *writer;
+    long int ret;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    ret = GEOSWKBWriter_getByteOrder_r(GEOS_G(handle), writer);
+
+    RETURN_LONG(ret);
+}
+
+/**
+ * void GEOSWKBWriter::setByteOrder(dims);
+ */
+PHP_METHOD(WKBWriter, setByteOrder)
+{
+    GEOSWKBWriter *writer;
+    long int dim;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &dim)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    GEOSWKBWriter_setByteOrder_r(GEOS_G(handle), writer, dim);
+
+}
+
+/**
+ * bool GEOSWKBWriter::getIncludeSRID();
+ */
+PHP_METHOD(WKBWriter, getIncludeSRID)
+{
+    GEOSWKBWriter *writer;
+    int ret;
+    zend_bool retBool;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    ret = GEOSWKBWriter_getIncludeSRID_r(GEOS_G(handle), writer);
+    retBool = ret;
+
+    RETURN_BOOL(retBool);
+}
+
+/**
+ * void GEOSWKBWriter::setIncludeSRID(bool);
+ */
+PHP_METHOD(WKBWriter, setIncludeSRID)
+{
+    GEOSWKBWriter *writer;
+    int inc;
+    zend_bool incVal;
+
+    writer = (GEOSWKBWriter*)getRelay(getThis(), WKBWriter_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &incVal)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    inc = incVal;
+    GEOSWKBWriter_setIncludeSRID_r(GEOS_G(handle), writer, inc);
+}
+
+/* -- class GEOSWKBReader -------------------- */
+
+PHP_METHOD(WKBReader, __construct);
+PHP_METHOD(WKBReader, read);
+PHP_METHOD(WKBReader, readHEX);
+
+static zend_function_entry WKBReader_methods[] = {
+    PHP_ME(WKBReader, __construct, NULL, 0)
+    PHP_ME(WKBReader, read, NULL, 0)
+    PHP_ME(WKBReader, readHEX, NULL, 0)
+    {NULL, NULL, NULL}
+};
+
+static zend_class_entry *WKBReader_ce_ptr;
+
+static zend_object_handlers WKBReader_object_handlers;
+
+static void
+WKBReader_dtor (GEOS_PHP_DTOR_OBJECT *object TSRMLS_DC)
+{
+#if PHP_VERSION_ID < 70000
+    Proxy *obj = (Proxy *)object;
+#else
+    Proxy *obj = php_geos_fetch_object(object);
+#endif
+
+    GEOSWKBReader_destroy_r(GEOS_G(handle), (GEOSWKBReader*)obj->relay);
+
+#if PHP_VERSION_ID >= 70000
+    //zend_object_std_dtor(&obj->std);
+#else
+    zend_hash_destroy(obj->std.properties);
+    FREE_HASHTABLE(obj->std.properties);
+
+    efree(obj);
+#endif
+}
+
+static zend_object_value
+WKBReader_create_obj (zend_class_entry *type TSRMLS_DC)
+{
+    return Gen_create_obj(type, WKBReader_dtor, &WKBReader_object_handlers);
+}
+
+
+PHP_METHOD(WKBReader, __construct)
+{
+    GEOSWKBReader* obj;
+    zval *object = getThis();
+
+    obj = GEOSWKBReader_create_r(GEOS_G(handle));
+    if ( ! obj ) {
+        php_error_docref(NULL TSRMLS_CC, E_ERROR,
+                "GEOSWKBReader_create() failed (didn't initGEOS?)");
+    }
+
+    setRelay(object, obj);
+}
+
+PHP_METHOD(WKBReader, read)
+{
+    GEOSWKBReader *reader;
+    GEOSGeometry *geom;
+    zend_string* wkb;
+    int wkblen;
+
+    reader = (GEOSWKBReader*)getRelay(getThis(), WKBReader_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
+#if PHP_VERSION_ID >= 70000
+            "S", &wkb
+#else
+            "s", &wkb, &wkblen
+#endif
+       ) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+#if PHP_VERSION_ID >= 70000
+    wkblen = strlen(ZSTR_VAL(wkb));
+#endif
+
+    geom = GEOSWKBReader_read_r(GEOS_G(handle), reader, (unsigned char*)ZSTR_VAL(wkb), wkblen);
+    /* we'll probably get an exception if geom is null */
+    if ( ! geom ) RETURN_NULL();
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, geom);
+
+}
+
+PHP_METHOD(WKBReader, readHEX)
+{
+    GEOSWKBReader *reader;
+    GEOSGeometry *geom;
+    unsigned char* wkb;
+#if PHP_VERSION_ID >= 70000
+    size_t wkblen;
+#else
+    int wkblen;
+#endif
+
+    reader = (GEOSWKBReader*)getRelay(getThis(), WKBReader_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
+        &wkb, &wkblen) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    geom = GEOSWKBReader_readHEX_r(GEOS_G(handle), reader, wkb, wkblen);
+    /* we'll probably get an exception if geom is null */
+    if ( ! geom ) RETURN_NULL();
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, geom);
+
+}
+
+
+/* -- Free functions ------------------------- */
+
+/**
+ * string GEOSVersion()
+ */
+PHP_FUNCTION(GEOSVersion)
+{
+    char *str;
+
+    str = estrdup(GEOSversion());
+    GEOS_PHP_RETURN_STRING(str);
+}
+
+/**
+ * array GEOSPolygonize(GEOSGeometry $geom)
+ *
+ * The returned array contains the following elements:
+ *
+ *  - 'rings'
+ *      Type: array of GEOSGeometry
+ *      Rings that can be formed by the costituent
+ *      linework of geometry.
+ *  - 'cut_edges' (optional)
+ *      Type: array of GEOSGeometry
+ *      Edges which are connected at both ends but
+ *      which do not form part of polygon.
+ *  - 'dangles'
+ *      Type: array of GEOSGeometry
+ *      Edges which have one or both ends which are
+ *      not incident on another edge endpoint
+ *  - 'invalid_rings'
+ *      Type: array of GEOSGeometry
+ *      Edges which form rings which are invalid
+ *      (e.g. the component lines contain a self-intersection)
+ *
+ */
+PHP_FUNCTION(GEOSPolygonize)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *rings;
+    GEOSGeometry *cut_edges;
+    GEOSGeometry *dangles;
+    GEOSGeometry *invalid_rings;
+    zval *array_elem;
+    zval *zobj;
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+    this = getRelay(zobj, Geometry_ce_ptr);
+
+    rings = GEOSPolygonize_full_r(GEOS_G(handle), this, &cut_edges, &dangles, &invalid_rings);
+    if ( ! rings ) RETURN_NULL(); /* should get an exception first */
+
+    /* return value should be an array */
+    array_init(return_value);
+
+    MAKE_STD_ZVAL(array_elem);
+    array_init(array_elem);
+    dumpGeometry(rings, array_elem);
+    GEOSGeom_destroy_r(GEOS_G(handle), rings);
+    GEOS_PHP_ADD_ASSOC_ZVAL(return_value, "rings", array_elem);
+
+    MAKE_STD_ZVAL(array_elem);
+    array_init(array_elem);
+    dumpGeometry(cut_edges, array_elem);
+    GEOSGeom_destroy_r(GEOS_G(handle), cut_edges);
+    GEOS_PHP_ADD_ASSOC_ZVAL(return_value, "cut_edges", array_elem);
+
+    MAKE_STD_ZVAL(array_elem);
+    array_init(array_elem);
+    dumpGeometry(dangles, array_elem);
+    GEOSGeom_destroy_r(GEOS_G(handle), dangles);
+    GEOS_PHP_ADD_ASSOC_ZVAL(return_value, "dangles", array_elem);
+
+    MAKE_STD_ZVAL(array_elem);
+    array_init(array_elem);
+    dumpGeometry(invalid_rings, array_elem);
+    GEOSGeom_destroy_r(GEOS_G(handle), invalid_rings);
+    GEOS_PHP_ADD_ASSOC_ZVAL(return_value, "invalid_rings", array_elem);
+
+}
+
+/**
+ * array GEOSLineMerge(GEOSGeometry $geom)
+ */
+PHP_FUNCTION(GEOSLineMerge)
+{
+    GEOSGeometry *geom_in;
+    GEOSGeometry *geom_out;
+    zval *zobj;
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &zobj)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+    geom_in = getRelay(zobj, Geometry_ce_ptr);
+
+    geom_out = GEOSLineMerge_r(GEOS_G(handle), geom_in);
+    if ( ! geom_out ) RETURN_NULL(); /* should get an exception first */
+
+    /* return value should be an array */
+    array_init(return_value);
+    dumpGeometry(geom_out, return_value);
+    GEOSGeom_destroy_r(GEOS_G(handle), geom_out);
+}
+
+/**
+ * GEOSGeometry GEOSSharedPaths(GEOSGeometry $geom1, GEOSGeometry *geom2)
+ */
+#ifdef HAVE_GEOS_SHARED_PATHS
+PHP_FUNCTION(GEOSSharedPaths)
+{
+    GEOSGeometry *geom_in_1;
+    GEOSGeometry *geom_in_2;
+    GEOSGeometry *geom_out;
+    zval *zobj1, *zobj2;
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "oo", &zobj1, &zobj2)
+        == FAILURE)
+    {
+        RETURN_NULL();
+    }
+    geom_in_1 = getRelay(zobj1, Geometry_ce_ptr);
+    geom_in_2 = getRelay(zobj2, Geometry_ce_ptr);
+
+    geom_out = GEOSSharedPaths_r(GEOS_G(handle), geom_in_1, geom_in_2);
+    if ( ! geom_out ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, geom_out);
+}
+#endif
+
+/**
+ * GEOSGeometry::delaunayTriangulation([<tolerance>], [<onlyEdges>])
+ *
+ *  'tolerance'
+ *       Type: double
+ *       snapping tolerance to use for improved robustness
+ *  'onlyEdges'
+ *       Type: boolean
+ *       if true will return a MULTILINESTRING, otherwise (the default)
+ *       it will return a GEOMETRYCOLLECTION containing triangular POLYGONs.
+ */
+#ifdef HAVE_GEOS_DELAUNAY_TRIANGULATION
+PHP_METHOD(Geometry, delaunayTriangulation)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+    double tolerance = 0.0;
+    zend_bool edgeonly = 0;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|db",
+            &tolerance, &edgeonly) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    ret = GEOSDelaunayTriangulation_r(GEOS_G(handle), this, tolerance, edgeonly ? 1 : 0);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+/**
+ * GEOSGeometry::voronoiDiagram([<tolerance>], [<onlyEdges>], [<extent>])
+ *
+ *  'tolerance'
+ *       Type: double
+ *       snapping tolerance to use for improved robustness
+ *  'onlyEdges'
+ *       Type: boolean
+ *       if true will return a MULTILINESTRING, otherwise (the default)
+ *       it will return a GEOMETRYCOLLECTION containing POLYGONs.
+ *  'extent'
+ *       Type: geometry
+ *       Clip returned diagram by the extent of the given geometry
+ */
+#ifdef HAVE_GEOS_VORONOI_DIAGRAM
+PHP_METHOD(Geometry, voronoiDiagram)
+{
+    GEOSGeometry *this;
+    GEOSGeometry *ret;
+    zval *zobj = 0;
+    GEOSGeometry *env = 0;
+    double tolerance = 0.0;
+    zend_bool edgeonly = 0;
+
+    this = (GEOSGeometry*)getRelay(getThis(), Geometry_ce_ptr);
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|dbo",
+            &tolerance, &edgeonly, &zobj) == FAILURE) {
+        RETURN_NULL();
+    }
+
+    if ( zobj ) env = getRelay(zobj, Geometry_ce_ptr);
+    ret = GEOSVoronoiDiagram_r(GEOS_G(handle), this, env, tolerance, edgeonly ? 1 : 0);
+    if ( ! ret ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    object_init_ex(return_value, Geometry_ce_ptr);
+    setRelay(return_value, ret);
+}
+#endif
+
+/**
+ * bool GEOSRelateMatch(string matrix, string pattern)
+ */
+#ifdef HAVE_GEOS_RELATE_PATTERN_MATCH
+PHP_FUNCTION(GEOSRelateMatch)
+{
+    char* mat = NULL;
+    char* pat = NULL;
+#if PHP_VERSION_ID >= 70000
+    size_t matlen;
+    size_t patlen;
+#else
+    int matlen;
+    int patlen;
+#endif
+    int ret;
+    zend_bool retBool;
+
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
+        &mat, &matlen, &pat, &patlen) == FAILURE)
+    {
+        RETURN_NULL();
+    }
+
+    ret = GEOSRelatePatternMatch_r(GEOS_G(handle), mat, pat);
+    if ( ret == 2 ) RETURN_NULL(); /* should get an exception first */
+
+    /* return_value is a zval */
+    retBool = ret;
+    RETURN_BOOL(retBool);
+}
+#endif
+
+/* ------ Initialization / Deinitialization / Meta ------------------ */
+
+/* per-module initialization */
+PHP_MINIT_FUNCTION(geos)
+{
+    zend_class_entry ce;
+
+    /* WKTReader */
+    INIT_CLASS_ENTRY(ce, "GEOSWKTReader", WKTReader_methods);
+    WKTReader_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
+    WKTReader_ce_ptr->create_object = WKTReader_create_obj;
+    memcpy(&WKTReader_object_handlers,
+        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+    WKTReader_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 70000
+    WKTReader_object_handlers.offset = XtOffsetOf(Proxy, std);
+    WKTReader_object_handlers.free_obj = WKTReader_dtor;
+#endif
+
+    /* WKTWriter */
+    INIT_CLASS_ENTRY(ce, "GEOSWKTWriter", WKTWriter_methods);
+    WKTWriter_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
+    WKTWriter_ce_ptr->create_object = WKTWriter_create_obj;
+    memcpy(&WKTWriter_object_handlers,
+        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+    WKTWriter_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 70000
+    WKTWriter_object_handlers.offset = XtOffsetOf(Proxy, std);
+    WKTWriter_object_handlers.free_obj = WKTWriter_dtor;
+#endif
+
+    /* Geometry */
+    INIT_CLASS_ENTRY(ce, "GEOSGeometry", Geometry_methods);
+    Geometry_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
+    Geometry_ce_ptr->create_object = Geometry_create_obj;
+    memcpy(&Geometry_object_handlers,
+        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+    Geometry_object_handlers.clone_obj = NULL;
+    /* Geometry serialization */
+    Geometry_ce_ptr->serialize = Geometry_serialize;
+    Geometry_ce_ptr->unserialize = Geometry_deserialize;
+#if PHP_VERSION_ID >= 70000
+    Geometry_object_handlers.offset = XtOffsetOf(Proxy, std);
+    Geometry_object_handlers.free_obj = Geometry_dtor;
+#endif
+
+    /* WKBWriter */
+    INIT_CLASS_ENTRY(ce, "GEOSWKBWriter", WKBWriter_methods);
+    WKBWriter_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
+    WKBWriter_ce_ptr->create_object = WKBWriter_create_obj;
+    memcpy(&WKBWriter_object_handlers,
+        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+    WKBWriter_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 70000
+    WKBWriter_object_handlers.offset = XtOffsetOf(Proxy, std);
+    WKBWriter_object_handlers.free_obj = WKBWriter_dtor;
+#endif
+
+    /* WKBReader */
+    INIT_CLASS_ENTRY(ce, "GEOSWKBReader", WKBReader_methods);
+    WKBReader_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
+    WKBReader_ce_ptr->create_object = WKBReader_create_obj;
+    memcpy(&WKBReader_object_handlers,
+        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+    WKBReader_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 70000
+    WKBReader_object_handlers.offset = XtOffsetOf(Proxy, std);
+    WKBReader_object_handlers.free_obj = WKBReader_dtor;
+#endif
+
+
+    /* Constants */
+    REGISTER_LONG_CONSTANT("GEOSBUF_CAP_ROUND",  GEOSBUF_CAP_ROUND,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSBUF_CAP_FLAT",   GEOSBUF_CAP_FLAT,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSBUF_CAP_SQUARE", GEOSBUF_CAP_SQUARE,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSBUF_JOIN_ROUND", GEOSBUF_JOIN_ROUND,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSBUF_JOIN_MITRE", GEOSBUF_JOIN_MITRE,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSBUF_JOIN_BEVEL", GEOSBUF_JOIN_BEVEL,
+        CONST_CS|CONST_PERSISTENT);
+
+    REGISTER_LONG_CONSTANT("GEOS_POINT", GEOS_POINT,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_LINESTRING", GEOS_LINESTRING,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_LINEARRING", GEOS_LINEARRING,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_POLYGON", GEOS_POLYGON,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_MULTIPOINT", GEOS_MULTIPOINT,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_MULTILINESTRING", GEOS_MULTILINESTRING,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_MULTIPOLYGON", GEOS_MULTIPOLYGON,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOS_GEOMETRYCOLLECTION", GEOS_GEOMETRYCOLLECTION,
+        CONST_CS|CONST_PERSISTENT);
+
+    REGISTER_LONG_CONSTANT("GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE",
+        GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE,
+        CONST_CS|CONST_PERSISTENT);
+
+#   ifdef HAVE_GEOS_PREC_NO_TOPO
+    REGISTER_LONG_CONSTANT("GEOS_PREC_NO_TOPO", GEOS_PREC_NO_TOPO,
+        CONST_CS|CONST_PERSISTENT);
+#   endif
+
+#   ifdef HAVE_GEOS_PREC_KEEP_COLLAPSED
+    REGISTER_LONG_CONSTANT("GEOS_PREC_KEEP_COLLAPSED", GEOS_PREC_KEEP_COLLAPSED,
+        CONST_CS|CONST_PERSISTENT);
+#   endif
+
+    REGISTER_LONG_CONSTANT("GEOSRELATE_BNR_MOD2", GEOSRELATE_BNR_MOD2,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSRELATE_BNR_OGC", GEOSRELATE_BNR_OGC,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSRELATE_BNR_ENDPOINT", GEOSRELATE_BNR_ENDPOINT,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSRELATE_BNR_MULTIVALENT_ENDPOINT",
+        GEOSRELATE_BNR_MULTIVALENT_ENDPOINT,
+        CONST_CS|CONST_PERSISTENT);
+    REGISTER_LONG_CONSTANT("GEOSRELATE_BNR_MONOVALENT_ENDPOINT",
+        GEOSRELATE_BNR_MONOVALENT_ENDPOINT,
+        CONST_CS|CONST_PERSISTENT);
+
+    return SUCCESS;
+}
+
+/* per-module shutdown */
+PHP_MSHUTDOWN_FUNCTION(geos)
+{
+    delGeometrySerializer();
+    delGeometryDeserializer();
+    return SUCCESS;
+}
+
+/* per-request initialization */
+PHP_RINIT_FUNCTION(geos)
+{
+    GEOS_G(handle) = initGEOS_r(noticeHandler, errorHandler);
+    return SUCCESS;
+}
+
+/* pre-request destruction */
+PHP_RSHUTDOWN_FUNCTION(geos)
+{
+    finishGEOS_r(GEOS_G(handle));
+    return SUCCESS;
+}
+
+/* global initialization */
+PHP_GINIT_FUNCTION(geos)
+{
+    geos_globals->handle = NULL;
+}
+
+/* module info */
+PHP_MINFO_FUNCTION(geos)
+{
+    php_info_print_table_start();
+    php_info_print_table_row(2,
+        "GEOS - Geometry Engine Open Source", "enabled");
+    php_info_print_table_row(2,
+        "Version", PHP_GEOS_VERSION);
+    php_info_print_table_row(2,
+        "GEOS Version", GEOSversion());
+    php_info_print_table_end();
+}
diff --git a/package.xml b/package.xml
new file mode 100644
index 0000000..604ae3c
--- /dev/null
+++ b/package.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<package packagerversion="1.4.11" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+  <name>geos</name>
+  <channel>pecl.php.net</channel>
+  <summary>PHP extension for interfacing with the GEOS library</summary>
+  <description>
+    This extension provides a PECL package for the GEOS library.
+  </description>
+  <lead>
+    <name>J Smith</name>
+    <user>jay</user>
+    <email>jay at php.net</email>
+    <active>yes</active>
+  </lead>
+  <lead>
+    <name>Sandro Santilli</name>
+    <email>strk at kbt.io</email>
+  </lead>
+  <date>2016-01-19</date>
+  <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+  </version>
+  <stability>
+    <release>stable</release>
+    <api>stable</api>
+  </stability>
+  <license uri="">Mixed</license>
+  <notes>
+- First release
+  </notes>
+  <contents>
+    <dir name="/">
+      <file role="src" name="geos.c"/>
+      <file role="src" name="php_geos.h"/>
+      <file role="src" name="config.m4"/>
+      <file role="doc" name="COPYING"/>
+      <file role="doc" name="CREDITS"/>
+      <file role="doc" name="LGPL-2"/>
+      <file role="doc" name="MIT-LICENSE"/>
+      <dir name="/">
+        <file role="test" name="test.php"/>
+        <file role="test" name="crashme.php"/>
+      </dir>
+    </dir>
+  </contents>
+  <dependencies>
+    <required>
+      <php>
+        <min>5.6.0</min>
+      </php>
+      <pearinstaller>
+        <min>1.4.0b1</min>
+      </pearinstaller>
+    </required>
+  </dependencies>
+  <providesextension>geos</providesextension>
+  <extsrcrelease>
+    <configureoption name="with-geos-config" default="no" prompt="geos-config directory"/>
+  </extsrcrelease>
+  <changelog>
+    <release>
+      <stability><release>stable</release><api>stable</api></stability>
+      <version><release>0.0.1</release><api>0.0.1</api></version>
+      <date>2016-01-19</date>
+      <notes>
+- First release
+      </notes>
+    </release>
+  </changelog>
+</package>
diff --git a/php_geos.h b/php_geos.h
new file mode 100644
index 0000000..5350541
--- /dev/null
+++ b/php_geos.h
@@ -0,0 +1,55 @@
+/***********************************************************************
+ *
+ *    GEOS - Geometry Engine Open Source
+ *    http://trac.osgeo.org/geos
+ *
+ *    Copyright (C) 2010 Sandro Santilli <strk at kbt.io>
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation; either
+ *    version 2.1 of the License, or (at your option) any later version.
+ *
+ *    This library 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
+ *    Lesser 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 St, Fifth Floor,
+ *    Boston, MA  02110-1301  USA
+ *
+ ***********************************************************************/
+
+#ifndef PHP_GEOS_H
+#define PHP_GEOS_H
+
+/* TODO: generate from ./configure ? */
+#define PHP_GEOS_VERSION "0.0"
+#define PHP_GEOS_EXTNAME "geos"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "php.h"
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+extern zend_module_entry geos_module_entry;
+#define phpext_geos_ptr &geos_module_entry;
+
+#ifdef ZTS
+#define GEOS_G(v) TSRMG(geos_globals_id, zend_geos_globals *, v)
+#else
+#define GEOS_G(v) (geos_globals.v)
+#endif
+
+ZEND_BEGIN_MODULE_GLOBALS(geos)
+GEOSContextHandle_t handle;
+ZEND_END_MODULE_GLOBALS(geos)
+
+#endif /* PHP_GEOS_H */
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..d456448
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,7 @@
+<phpunit>
+  <testsuites>
+    <testsuite name="PHPT tests">
+      <directory suffix=".phpt">tests</directory>
+    </testsuite>
+  </testsuites>
+</phpunit>
diff --git a/test/crashme.php b/test/crashme.php
new file mode 100644
index 0000000..a0d0a42
--- /dev/null
+++ b/test/crashme.php
@@ -0,0 +1,25 @@
+<?php
+
+#
+# This file contains code attempting to segfault the extension
+#
+# Run with:
+# php -n -d enable_dl=On -d extension_dir=.. test.php
+#
+
+dl("geos.so");
+
+class Point extends GEOSWKTReader {
+    public function __construct()
+    {
+        parent::__construct();
+    }
+    public function test() {
+        return GEOSGeometry::numGeometries();
+    }
+};
+
+$p = new Point();
+$p->test();
+
+
diff --git a/tests/000_General.phpt b/tests/000_General.phpt
new file mode 100644
index 0000000..02ebe39
--- /dev/null
+++ b/tests/000_General.phpt
@@ -0,0 +1,51 @@
+--TEST--
+General tests
+--SKIPIF--
+<?php if (!extension_loaded('geos')) { print "geos extension not loaded\n"; exit(1); } ?>
+--FILE--
+<?php
+
+require './tests/TestHelper.php';
+
+class GeneralTest extends GEOSTest
+{
+    public function testGEOSVersion()
+    {
+        $this->assertContains('-CAPI-', GEOSVersion());
+    }
+
+    public function testConstants()
+    {
+        $this->assertEquals(1, GEOSBUF_CAP_ROUND);
+        $this->assertEquals(2, GEOSBUF_CAP_FLAT);
+        $this->assertEquals(3, GEOSBUF_CAP_SQUARE);
+
+        $this->assertEquals(1, GEOSBUF_JOIN_ROUND);
+        $this->assertEquals(2, GEOSBUF_JOIN_MITRE);
+        $this->assertEquals(3, GEOSBUF_JOIN_BEVEL);
+
+        $this->assertEquals(0, GEOS_POINT);
+        $this->assertEquals(1, GEOS_LINESTRING);
+        $this->assertEquals(2, GEOS_LINEARRING);
+        $this->assertEquals(3, GEOS_POLYGON);
+        $this->assertEquals(4, GEOS_MULTIPOINT);
+        $this->assertEquals(5, GEOS_MULTILINESTRING);
+        $this->assertEquals(6, GEOS_MULTIPOLYGON);
+        $this->assertEquals(7, GEOS_GEOMETRYCOLLECTION);
+
+        $this->assertEquals(1, GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE);
+
+        $this->assertEquals(1, GEOSRELATE_BNR_MOD2);
+        $this->assertEquals(1, GEOSRELATE_BNR_OGC);
+        $this->assertEquals(2, GEOSRELATE_BNR_ENDPOINT);
+        $this->assertEquals(3, GEOSRELATE_BNR_MULTIVALENT_ENDPOINT);
+        $this->assertEquals(4, GEOSRELATE_BNR_MONOVALENT_ENDPOINT);
+    }
+}
+
+GeneralTest::run();
+
+?>
+--EXPECT--
+GeneralTest->testGEOSVersion	OK
+GeneralTest->testConstants	OK
diff --git a/tests/001_Geometry.phpt b/tests/001_Geometry.phpt
new file mode 100644
index 0000000..ef7a0cb
--- /dev/null
+++ b/tests/001_Geometry.phpt
@@ -0,0 +1,1935 @@
+--TEST--
+Geometry tests
+--SKIPIF--
+<?php if (!extension_loaded('geos')) { print "geos extension not loaded\n"; exit(1); } ?>
+--FILE--
+<?php
+
+require './tests/TestHelper.php';
+
+class GeometryTest extends GEOSTest
+{
+    public function testGeometry_serialization()
+    {
+        $reader = new GEOSWKTReader();
+
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setTrim')) {
+            $writer->setTrim(TRUE);
+        }
+
+        if (method_exists(GEOSWKTWriter::class, 'setOutputDimension')) {
+            $writer->setOutputDimension(3);
+        }
+
+        $g = $reader->read('POINT(6 7 8)');
+        $g->setSRID(54);
+
+        $a = array('geom' => $g, 'name' => 'test geometry');
+        $srl = serialize($a);
+        $a2 = unserialize($srl);
+
+        $this->assertEquals('POINT Z (6 7 8)', $writer->write($a['geom']));
+
+    }
+
+    public function testGeometry_project()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(1 2)');
+        $g2 = $reader->read('POINT(3 4)');
+
+        /* The method only accept lineal geometries */
+        try {
+            $prj = $g->project($g2);
+            $this->assertTrue(FALSE); # this is just to fail if we get here
+        } catch (Exception $e) {
+            $this->assertContains('lineal', $e->getMessage());
+        }
+
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $g2 = $reader->read('POINT(0 0)');
+        $prj = $g->project($g2);
+        $this->assertEquals(0, $prj);
+        $prj = $g->project($g2, TRUE);
+        $this->assertEquals(0, $prj);
+
+        $g2 = $reader->read('POINT(10 0)');
+        $prj = $g->project($g2);
+        $this->assertEquals(10, $prj);
+        $prj = $g->project($g2, TRUE);
+        $this->assertEquals(1, $prj);
+
+        $g2 = $reader->read('POINT(5 0)');
+        $prj = $g->project($g2);
+        $this->assertEquals(5, $prj);
+        $prj = $g->project($g2, TRUE);
+        $this->assertEquals(0.5, $prj);
+
+        $g = $reader->read('MULTILINESTRING((0 0, 10 0),(20 10, 20 20))');
+
+        $g2 = $reader->read('POINT(20 0)');
+        $prj = $g->project($g2);
+        $this->assertEquals(10, $prj);
+        $prj = $g->project($g2, TRUE);
+        $this->assertEquals(0.5, $prj);
+
+        $g2 = $reader->read('POINT(20 5)');
+        $prj = $g->project($g2);
+        $this->assertEquals(10, $prj);
+        $prj = $g->project($g2, TRUE);
+        $this->assertEquals(0.5, $prj);
+
+
+    }
+
+    public function testGeometry_interpolate()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setTrim')) {
+            $writer->setTrim(TRUE);
+        }
+
+        /* The method only accept LineString geometries */
+        $g = $reader->read('POINT(1 2)');
+        try {
+            $prj = $g->interpolate(0);
+            $this->assertTrue(FALSE); # this is just to fail if we get here
+        } catch (Exception $e) {
+            $this->assertContains('LineString', $e->getMessage());
+        }
+
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $prj = $g->interpolate(0);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (0 0)', $writer->write($prj));
+        $prj = $g->interpolate(0, TRUE);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (0 0)', $writer->write($prj));
+
+        $prj = $g->interpolate(5);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (5 0)', $writer->write($prj));
+        $prj = $g->interpolate(0.5, TRUE);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (5 0)', $writer->write($prj));
+
+        /* return closest on longer distance */
+        $prj = $g->interpolate(20);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (10 0)', $writer->write($prj));
+        $prj = $g->interpolate(2, TRUE);
+        $this->assertNotNull($prj);
+        $this->assertEquals('POINT (10 0)', $writer->write($prj));
+
+    }
+
+    public function testGeometry_buffer()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->buffer(0);
+        $this->assertEquals('POLYGON EMPTY', $writer->write($b));
+
+        $b = $g->buffer(10);
+        $this->assertEquals('POLYGON ((10 0, 10 -2, 9 -4, 8 -6, 7 -7, 6 -8, 4 -9, 2 -10, 0 -10, -2 -10, -4 -9, -6 -8, -7 -7, -8 -6, -9 -4, -10 -2, -10 -0, -10 2, -9 4, -8 6, -7 7, -6 8, -4 9, -2 10, -0 10, 2 10, 4 9, 6 8, 7 7, 8 6, 9 4, 10 2, 10 0))', $writer->write($b));
+
+        # One segment per quadrant
+        $b = $g->buffer(10, array('quad_segs' => 1));
+        $this->assertEquals('POLYGON ((10 0, 0 -10, -10 -0, -0 10, 10 0))', $writer->write($b));
+
+        /* End cap styles */
+
+        $g = $reader->read('LINESTRING(0 0, 100 0)');
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 1,
+            'endcap' => GEOSBUF_CAP_ROUND
+        ));
+        $this->assertEquals('POLYGON ((100 10, 110 0, 100 -10, 0 -10, -10 0, 0 10, 100 10))', $writer->write($b));
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 1,
+            'endcap' => GEOSBUF_CAP_FLAT
+        ));
+        $this->assertEquals('POLYGON ((100 10, 100 -10, 0 -10, 0 10, 100 10))', $writer->write($b));
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 1,
+            'endcap' => GEOSBUF_CAP_SQUARE
+        ));
+        $this->assertEquals('POLYGON ((100 10, 110 10, 110 -10, 0 -10, -10 -10, -10 10, 100 10))', $writer->write($b));
+
+        /* Join styles */
+
+        $g = $reader->read('LINESTRING(0 0, 100 0, 100 100)');
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_ROUND
+        ));
+        $this->assertEquals('POLYGON ((90 10, 90 100, 93 107, 100 110, 107 107, 110 100, 110 0, 107 -7, 100 -10, 0 -10, -7 -7, -10 0, -7 7, 0 10, 90 10))', $writer->write($b));
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_BEVEL
+        ));
+        $this->assertEquals('POLYGON ((90 10, 90 100, 93 107, 100 110, 107 107, 110 100, 110 0, 100 -10, 0 -10, -7 -7, -10 0, -7 7, 0 10, 90 10))', $writer->write($b));
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_MITRE
+        ));
+        $this->assertEquals('POLYGON ((90 10, 90 100, 93 107, 100 110, 107 107, 110 100, 110 -10, 0 -10, -7 -7, -10 0, -7 7, 0 10, 90 10))', $writer->write($b));
+
+        $b = $g->buffer(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_MITRE,
+            'mitre_limit' => 1.0
+        ));
+        $this->assertEquals('POLYGON ((90 10, 90 100, 93 107, 100 110, 107 107, 110 100, 109 -5, 105 -9, 0 -10, -7 -7, -10 0, -7 7, 0 10, 90 10))', $writer->write($b));
+
+        /* Check that elements of the passed style array are not
+         * type-converted (buffer op will need to type-convert
+         * internally)
+         */
+        $ary = array('a' => 1);
+
+        $myStyle = array(
+            'quad_segs' => "a string",
+            'join' => "1",
+            'endcap' => $ary,
+            'mitre_limit' => 2 /* an int.. */
+        );
+        $this->assertEquals('string', gettype($myStyle['quad_segs']));
+        $this->assertEquals('string', gettype($myStyle['join']));
+        $this->assertEquals('array', gettype($myStyle['endcap']));
+        $this->assertEquals('integer', gettype($myStyle['mitre_limit']));
+        $b = $g->buffer(10, $myStyle);
+        $this->assertEquals('string', gettype($myStyle['quad_segs']));
+        $this->assertEquals('string', gettype($myStyle['join']));
+        $this->assertEquals('array', gettype($myStyle['endcap']));
+        $this->assertEquals('integer', gettype($myStyle['mitre_limit']));
+
+        /* Single-sided buffering */
+
+        $g = $reader->read('LINESTRING(0 0, 100 0)');
+
+        $b = $g->buffer(10, array(
+            'single_sided' => true
+        ));
+        $this->assertEquals('POLYGON ((100 0, 0 0, 0 10, 100 10, 100 0))', $writer->write($b));
+
+        $b = $g->buffer(-10, array(
+            'single_sided' => true
+        ));
+        $this->assertEquals('POLYGON ((0 0, 100 0, 100 -10, 0 -10, 0 0))', $writer->write($b));
+    }
+
+    public function testGeometry_offsetCurve()
+    {
+        if (!method_exists(GEOSGeometry::class, 'offsetCurve')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        $writer->setRoundingPrecision(0);
+
+        /* Join styles */
+
+        $g = $reader->read('LINESTRING(0 0, 100 0, 100 100)');
+
+        /* left, round join */
+        $b = $g->offsetCurve(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_ROUND
+        ));
+        $this->assertEquals('LINESTRING (0 10, 90 10, 90 100)', $writer->write($b));
+
+        /* right, round join */
+        $b = $g->offsetCurve(-10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_ROUND
+        ));
+        $this->assertEquals('LINESTRING (110 100, 110 0, 107 -7, 100 -10, 0 -10)', $writer->write($b));
+
+        /* left, bevel join */
+        $b = $g->offsetCurve(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_BEVEL
+        ));
+        $this->assertEquals('LINESTRING (0 10, 90 10, 90 100)', $writer->write($b));
+
+        /* right, bevel join */
+        $b = $g->offsetCurve(-10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_BEVEL
+        ));
+        $this->assertEquals('LINESTRING (110 100, 110 0, 100 -10, 0 -10)', $writer->write($b));
+
+          /* left, mitre join */
+        $b = $g->offsetCurve(10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_MITRE
+        ));
+        $this->assertEquals('LINESTRING (0 10, 90 10, 90 100)', $writer->write($b));
+
+        /* right, mitre join */
+        $b = $g->offsetCurve(-10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_MITRE
+        ));
+        $this->assertEquals('LINESTRING (110 100, 110 -10, 0 -10)', $writer->write($b));
+
+        /* right, mitre join limited */
+        $b = $g->offsetCurve(-10, array(
+            'quad_segs' => 2,
+            'join' => GEOSBUF_JOIN_MITRE,
+            'mitre_limit' => 1.0
+        ));
+        $this->assertEquals('LINESTRING (110 100, 109 -5, 105 -9, 0 -10)', $writer->write($b));
+    }
+
+    public function testGeometry_envelope()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->envelope();
+        $this->assertEquals('POINT (0 0)', $writer->write($b));
+
+        $g = $reader->read('LINESTRING(0 0, 10 10)');
+        $b = $g->envelope();
+        $this->assertEquals('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))', $writer->write($b));
+    }
+
+    public function testGeometry_intersection()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        /* POINT - POINT */
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'POINT (0 0)', $writer->write($gi));
+        $g2 = $reader->read('POINT(1 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+
+        /* POINT - LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('POINT(5 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'POINT (5 0)', $writer->write($gi));
+        $g2 = $reader->read('POINT(12 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+
+        /* LINE - LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'POINT (5 0)', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(5 0, 20 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'LINESTRING (5 0, 10 0)', $writer->write($gi));
+
+        /* LINE - POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'LINESTRING (5 0, 5 10)', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(10 0, 20 0)');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'POINT (10 0)', $writer->write($gi));
+
+        /* POLY - POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POLYGON((5 -5, 5 5, 15 5, 15 -5, 5 -5))');
+        $gi = $g->intersection($g2);
+        $this->assertTrue($gi->equals($reader->read('POLYGON ((10 5, 10 0, 5 0, 5 5, 10 5))')));
+        $g2 = $reader->read('POLYGON((10 0, 20 0, 20 -5, 10 -5, 10 0))');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'POINT (10 0)', $writer->write($gi));
+        $g2 = $reader->read('POLYGON((8 0, 20 0, 20 -5, 10 -5, 8 0))');
+        $gi = $g->intersection($g2);
+        $this->assertEquals( 'LINESTRING (8 0, 10 0)', $writer->write($gi));
+    }
+
+    public function testGeometry_clipByRect()
+    {
+        if (!method_exists(GEOSGeometry::class, 'clipByRect')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setRoundingPrecision(0);
+
+        /* POINT */
+        $g = $reader->read('POINT(0 0)');
+        $gi = $g->clipByRect(-1,-1,1,1);
+        $this->assertEquals( 'POINT (0 0)', $writer->write($gi));
+        $gi = $g->clipByRect(1,1,2,2);
+        $this->assertEquals( 'GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+
+        /* LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $gi = $g->clipByRect(1,-1,2,1);
+        $this->assertEquals( 'LINESTRING (1 0, 2 0)', $writer->write($gi));
+
+        /* POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $gi = $g->clipByRect(1,1,5,5);
+        $this->assertEquals( 'POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1))', $writer->write($gi));
+        $gi = $g->clipByRect(-1,-1,5,5);
+        $this->assertEquals( 'POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))', $writer->write($gi));
+    }
+
+    public function testGeometry_convexHull()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->convexHull();
+        $this->assertEquals('POINT (0 0)', $writer->write($b));
+
+        $g = $reader->read('LINESTRING(0 0, 10 10)');
+        $b = $g->convexHull();
+        $this->assertEquals('LINESTRING (0 0, 10 10)', $writer->write($b));
+
+        $g = $reader->read('POLYGON((0 0, 0 10, 5 5, 10 10, 10 0, 0 0))');
+        $b = $g->convexHull();
+        $this->assertEquals('POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))', $writer->write($b));
+    }
+
+    public function testGeometry_difference()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        /* POINT - POINT */
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals( 'GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+        $g2 = $reader->read('POINT(1 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals('POINT (0 0)', $writer->write($gi));
+
+        /* LINE - POINT */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('POINT(5 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals('LINESTRING (0 0, 10 0)', $writer->write($gi));
+
+        /* POINT - LINE */
+        $g = $reader->read('POINT(5 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(0 1, 10 1)');
+        $gi = $g->difference($g2);
+        $this->assertEquals( 'POINT (5 0)', $writer->write($gi));
+
+        /* LINE - LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->difference($g2);
+        $this->assertEquals( 'MULTILINESTRING ((0 0, 5 0), (5 0, 10 0))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(5 0, 20 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals( 'LINESTRING (0 0, 5 0)', $writer->write($gi));
+
+        /* POLY - LINE */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->difference($g2);
+        $this->assertEquals('POLYGON ((5 0, 0 0, 0 10, 5 10, 10 10, 10 0, 5 0))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(10 0, 20 0)');
+        $gi = $g->difference($g2);
+        $this->assertEquals('POLYGON ((10 0, 0 0, 0 10, 10 10, 10 0))', $writer->write($gi));
+
+        /* POLY - POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POLYGON((5 -5, 5 5, 15 5, 15 -5, 5 -5))');
+        $gi = $g->difference($g2);
+        $this->assertEquals('POLYGON ((5 0, 0 0, 0 10, 10 10, 10 5, 5 5, 5 0))', $writer->write($gi));
+    }
+
+    public function testGeometry_symdifference()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        /* POINT - POINT */
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals( 'GEOMETRYCOLLECTION EMPTY', $writer->write($gi));
+        $g2 = $reader->read('POINT(1 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals( 'MULTIPOINT (0 0, 1 0)', $writer->write($gi));
+
+        /* LINE - POINT */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('POINT(5 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('LINESTRING (0 0, 10 0)', $writer->write($gi));
+
+        /* POINT - LINE */
+        $g = $reader->read('POINT(5 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals( 'LINESTRING (0 0, 10 0)', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(0 1, 10 1)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (POINT (5 0), LINESTRING (0 1, 10 1))', $writer->write($gi));
+
+        /* LINE - LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('MULTILINESTRING ((0 0, 5 0), (5 0, 10 0), (5 -10, 5 0), (5 0, 5 10))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(5 0, 20 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('MULTILINESTRING ((0 0, 5 0), (10 0, 20 0))', $writer->write($gi));
+
+        /* POLY - LINE */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (LINESTRING (5 -10, 5 0), POLYGON ((5 0, 0 0, 0 10, 5 10, 10 10, 10 0, 5 0)))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(10 0, 20 0)');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (LINESTRING (10 0, 20 0), POLYGON ((10 0, 0 0, 0 10, 10 10, 10 0)))', $writer->write($gi));
+
+        /* POLY - POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POLYGON((5 -5, 5 5, 15 5, 15 -5, 5 -5))');
+        $gi = $g->symDifference($g2);
+        $this->assertEquals('MULTIPOLYGON (((5 0, 0 0, 0 10, 10 10, 10 5, 5 5, 5 0)), ((5 0, 10 0, 10 5, 15 5, 15 -5, 5 -5, 5 0)))', $writer->write($gi));
+    }
+
+    public function testGeometry_boundary()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->boundary();
+        $this->assertEquals('GEOMETRYCOLLECTION EMPTY', $writer->write($b));
+
+        $g = $reader->read('LINESTRING(0 0, 10 10)');
+        $b = $g->boundary();
+        $this->assertEquals('MULTIPOINT (0 0, 10 10)', $writer->write($b));
+
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),( 5 5, 5 6, 6 6, 6 5, 5 5))');
+        $b = $g->boundary();
+        $this->assertEquals('MULTILINESTRING ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 6, 6 6, 6 5, 5 5))', $writer->write($b));
+    }
+
+    public function testGeometry_union()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        /* POINT - POINT */
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals( 'POINT (0 0)', $writer->write($gi));
+        $g2 = $reader->read('POINT(1 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals( 'MULTIPOINT (0 0, 1 0)', $writer->write($gi));
+
+        /* LINE - POINT */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('POINT(5 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals('LINESTRING (0 0, 10 0)', $writer->write($gi));
+
+        /* POINT - LINE */
+        $g = $reader->read('POINT(5 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals( 'LINESTRING (0 0, 10 0)', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(0 1, 10 1)');
+        $gi = $g->union($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (POINT (5 0), LINESTRING (0 1, 10 1))', $writer->write($gi));
+
+        /* LINE - LINE */
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->union($g2);
+        $this->assertEquals('MULTILINESTRING ((0 0, 5 0), (5 0, 10 0), (5 -10, 5 0), (5 0, 5 10))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(5 0, 20 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals('MULTILINESTRING ((0 0, 5 0), (5 0, 10 0), (10 0, 20 0))', $writer->write($gi));
+
+        /* POLY - LINE */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('LINESTRING(5 -10, 5 10)');
+        $gi = $g->union($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (LINESTRING (5 -10, 5 0), POLYGON ((5 0, 0 0, 0 10, 5 10, 10 10, 10 0, 5 0)))', $writer->write($gi));
+        $g2 = $reader->read('LINESTRING(10 0, 20 0)');
+        $gi = $g->union($g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (LINESTRING (10 0, 20 0), POLYGON ((10 0, 0 0, 0 10, 10 10, 10 0)))', $writer->write($gi));
+
+        /* POLY - POLY */
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POLYGON((5 -5, 5 5, 15 5, 15 -5, 5 -5))');
+        $gi = $g->union($g2);
+        $this->assertEquals('POLYGON ((5 0, 0 0, 0 10, 10 10, 10 5, 15 5, 15 -5, 5 -5, 5 0))', $writer->write($gi));
+    }
+
+    public function testGeometry_unaryunion()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('MULTIPOLYGON(
+                 ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                 ((10 10, 10 14, 14 14, 14 10, 10 10),
+                  (11 11, 11 12, 12 12, 12 11, 11 11)),
+                 ((0 0, 11 0, 11 11, 0 11, 0 0))
+                ))');
+
+        $gu = $g->union();
+        $this->assertEquals('POLYGON ((1 0, 0 0, 0 1, 0 11, 10 11, 10 14, 14 14, 14 10, 11 10, 11 0, 1 0), (11 11, 12 11, 12 12, 11 12, 11 11))', $writer->write($gu));
+
+        $g = $reader->read('MULTILINESTRING(
+                 (0 0, 1 0, 1 1, 0 1, 0 0),
+                 (10 10, 10 14, 14 14, 14 10, 10 10),
+                  (11 11, 11 12, 12 12, 12 11, 11 11),
+                 (0 0, 11 0, 11 11, 0 11, 0 0)
+                )');
+
+        $gu = $g->union();
+        $this->assertEquals('MULTILINESTRING ((0 0, 1 0), (1 0, 1 1, 0 1), (0 1, 0 0), (1 0, 11 0, 11 10), (11 10, 11 11), (11 11, 10 11), (10 11, 0 11, 0 1), (11 11, 11 12, 12 12, 12 11, 11 11), (10 10, 10 11), (10 11, 10 14, 14 14, 14 10, 11 10), (11 10, 10 10))', $writer->write($gu));
+
+        $g = $reader->read('MULTIPOINT(
+                 0 0, 1 0, 1 1, 0 1, 0 0,
+                 10 10, 10 14, 14 14, 14 10, 10 10,
+                  11 11, 11 12, 12 12, 12 11, 11 11,
+                 0 0, 11 0, 11 11, 0 11, 0 0
+                )');
+
+        $gu = $g->union();
+        $this->assertEquals('MULTIPOINT (0 0, 0 1, 0 11, 1 0, 1 1, 10 10, 10 14, 11 0, 11 11, 11 12, 12 11, 12 12, 14 10, 14 14)', $writer->write($gu));
+
+        $g = $reader->read('GEOMETRYCOLLECTION(
+                MULTIPOLYGON(
+                 ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                 ((10 10, 10 14, 14 14, 14 10, 10 10),
+                  (11 11, 11 12, 12 12, 12 11, 11 11)),
+                 ((0 0, 11 0, 11 11, 0 11, 0 0))
+                ),
+                MULTILINESTRING(
+                 (0 0, 1 0, 1 1, 0 1, 0 0),
+                 (10 10, 10 14, 14 14, 14 10, 10 10),
+                  (11 11, 11 12, 12 12, 12 11, 11 11),
+                 (0 0, 11 0, 11 11, 0 11, 0 0),(-8 8, -8 6)
+                ),
+                MULTIPOINT(
+                 0 0, 1 0, 1 1, 0 1, 0 0,
+                 10 10, 10 14, 14 14, 14 10, 10 10,
+                  11 11, 11 12, 12 12, 12 11, 11 11,
+                 0 0, 11 0, 11 11, 0 11, 0 0, -10 -10
+                ))');
+
+        $gu = $g->union();
+        $this->assertEquals('GEOMETRYCOLLECTION (POINT (-10 -10), LINESTRING (-8 8, -8 6), POLYGON ((1 0, 0 0, 0 1, 0 11, 10 11, 10 14, 14 14, 14 10, 11 10, 11 0, 1 0), (11 12, 11 11, 12 11, 12 12, 11 12)))', $writer->write($gu));
+    }
+
+    public function testGeometry_pointOnSurface()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->pointOnSurface();
+        $this->assertEquals('POINT (0 0)', $writer->write($b));
+
+        $g = $reader->read('LINESTRING(0 0, 5 5, 10 10)');
+        $b = $g->pointOnSurface();
+        $this->assertEquals('POINT (5 5)', $writer->write($b));
+
+        $g = $reader->read('POLYGON((0 0, 0 10, 5 5, 10 10, 10 0, 0 0))');
+        $b = $g->pointOnSurface();
+        $this->assertEquals('POINT (1 8)', $writer->write($b));
+    }
+
+    public function testGeometry_centroid()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $b = $g->centroid();
+        $this->assertEquals('POINT (0 0)', $writer->write($b));
+
+        $g = $reader->read('LINESTRING(0 0, 10 10)');
+        $b = $g->centroid();
+        $this->assertEquals('POINT (5 5)', $writer->write($b));
+
+        $g = $reader->read('POLYGON((0 0, 0 10, 5 5, 10 10, 10 0, 0 0))');
+        $b = $g->centroid();
+        $this->assertEquals('POINT (5 4)', $writer->write($b));
+    }
+
+    public function testGeometry_relate()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+        $ret = $g->relate($g2);
+        $this->assertEquals('0FFFFFFF2', $ret);
+        $ret = $g->relate($g2, '0FFFFFFF2');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, '0*******T');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, '0*******1');
+        $this->assertEquals(FALSE, $ret);
+
+        $g = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(1 0)');
+        $ret = $g->relate($g2);
+        $this->assertEquals('FF0FFF0F2', $ret);
+        $ret = $g->relate($g2, 'FF0FFF0F2');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, 'F*******2');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, 'T*******2');
+        $this->assertEquals(FALSE, $ret);
+
+        $g = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POINT(1 0)');
+        $ret = $g->relate($g2);
+        $this->assertEquals('FF20F1FF2', $ret);
+        $ret = $g->relate($g2, 'FF20F1FF2');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, 'F****T**T');
+        $this->assertEquals(TRUE, $ret);
+        $ret = $g->relate($g2, 'T*******2');
+        $this->assertEquals(FALSE, $ret);
+
+    }
+
+    public function testGeometry_relateBoundaryNodeRule()
+    {
+        if (!method_exists(GEOSGeometry::class, 'relateBoundaryNodeRule')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('LINESTRING(0 0, 2 4, 5 5, 0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+
+        $ret = $g->relateBoundaryNodeRule($g2, GEOSRELATE_BNR_OGC);
+        $this->assertEquals('0F1FFFFF2', $ret);
+
+        $ret = $g->relateBoundaryNodeRule($g2, GEOSRELATE_BNR_ENDPOINT);
+        $this->assertEquals('FF10FFFF2', $ret);
+
+    }
+
+    public function testGeometry_polygonize()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('GEOMETRYCOLLECTION(
+            LINESTRING(0 0, 10 10),
+            LINESTRING(185 221, 100 100),
+            LINESTRING(185 221, 88 275, 180 316),
+            LINESTRING(185 221, 292 281, 180 316),
+            LINESTRING(189 98, 83 187, 185 221),
+            LINESTRING(189 98, 325 168, 185 221))
+            )');
+
+        $g2 = $reader->read('POINT(0 0)');
+
+        $ret = GEOSPolygonize($g);
+
+        $this->assertEquals('array', gettype($ret));
+        $this->assertEquals('array', gettype($ret['rings']));
+        $this->assertEquals('array', gettype($ret['cut_edges']));
+        $this->assertEquals('array', gettype($ret['dangles']));
+        $this->assertEquals('array', gettype($ret['invalid_rings']));
+
+        $this->assertEquals(2, count($ret['rings']));
+        $this->assertEquals('POLYGON ((185 221, 88 275, 180 316, 292 281, 185 221))', # JTS-confirmed!
+            $writer->write($ret['rings'][0]));
+        $this->assertEquals('POLYGON ((189 98, 83 187, 185 221, 325 168, 189 98))', # JTS-confirmed !
+            $writer->write($ret['rings'][1]));
+
+        $this->assertEquals(0, count($ret['cut_edges']));
+
+        $this->assertEquals(0, count($ret['invalid_rings']));
+
+        /*
+         * FIXME: the duplicated dangle (0 0, 10 10) is unexpected
+         */
+
+        $this->assertEquals(2, count($ret['dangles']));
+        $this->assertEquals('LINESTRING (185 221, 100 100)', # JTS-confirmed !
+            $writer->write($ret['dangles'][0]));
+        $this->assertEquals('LINESTRING (0 0, 10 10)', # JTS-confirmed !
+            $writer->write($ret['dangles'][1]));
+
+
+        ###########################################################
+
+        $g = $g->union($g2); /* Now make sure linestrings are noded */
+
+        $ret = GEOSPolygonize($g);
+
+        $this->assertEquals('array', gettype($ret));
+        $this->assertEquals('array', gettype($ret['rings']));
+        $this->assertEquals('array', gettype($ret['cut_edges']));
+        $this->assertEquals('array', gettype($ret['dangles']));
+        $this->assertEquals('array', gettype($ret['invalid_rings']));
+
+        $this->assertEquals(2, count($ret['dangles']));
+        $this->assertEquals('LINESTRING (132 146, 100 100)', $writer->write($ret['dangles'][0]));
+        $this->assertEquals('LINESTRING (0 0, 10 10)', $writer->write($ret['dangles'][1]));
+
+        $this->assertEquals(0, count($ret['invalid_rings']));
+
+    // TODO: test a polygonize run with cut lines and invalid_rings
+
+    }
+
+    public function testGeometry_lineMerge()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('MULTILINESTRING(
+            (0 0, 10 10),
+            (10 10, 10 0),
+            (5 0, 10 0),
+            (5 -5, 5 0)
+            )');
+
+        $ret = GEOSLineMerge($g);
+
+        $this->assertEquals('array', gettype($ret));
+        $this->assertEquals('1', count($ret));
+
+        $this->assertEquals('LINESTRING (0 0, 10 10, 10 0, 5 0, 5 -5)', $writer->write($ret[0]));
+
+    }
+
+    public function testGeometry_sharedPaths()
+    {
+        if (!function_exists('GEOSSharedPaths')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        /* LINE - LINE */
+        $g1 = $reader->read('LINESTRING(0 0, 50 0)');
+        $g2 = $reader->read('MULTILINESTRING((5 0, 15 0),(40 0, 30 0))');
+        $gs = GEOSSharedPaths($g1, $g2);
+        $this->assertEquals('GEOMETRYCOLLECTION (MULTILINESTRING ((5 0, 15 0)), MULTILINESTRING ((30 0, 40 0)))', $writer->write($gs));
+    }
+
+    public function testGeometry_simplify()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('LINESTRING(0 0, 3 4, 5 10, 10 0, 10 9, 5 11, 0 9)');
+        $gs = $g->simplify(2);
+        $this->assertEquals( 'LINESTRING (0 0, 5 10, 10 0, 10 9, 0 9)', $writer->write($gs));
+        $gs = $g->simplify(2, TRUE);
+        $this->assertEquals( 'LINESTRING (0 0, 5 10, 10 0, 10 9, 5 11, 0 9)', $writer->write($gs));
+    }
+
+    public function testGeometry_extractUniquePoints()
+    {
+        if (!method_exists(GEOSGeometry::class, 'extractUniquePoints')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read(
+    'GEOMETRYCOLLECTION (
+        MULTIPOLYGON (
+            ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            ((10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11))
+        ),
+        POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)),
+        MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)),
+        LINESTRING (0 0, 2 3),
+        MULTIPOINT (0 0, 2 3),
+        POINT (9 0),
+        POINT(1 0)),
+        LINESTRING EMPTY
+    ');
+
+        $gs = $g->extractUniquePoints();
+        if ( ! $gs ) RETURN_NULL(); /* should get an exception before */
+
+        $this->assertEquals('MULTIPOINT (0 0, 1 0, 1 1, 0 1, 10 10, 10 14, 14 14, 14 10, 11 11, 11 12, 12 12, 12 11, 2 3, 3 4, 9 0)', $writer->write($gs));
+    }
+
+    public function testGeometry_relationalOps()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g1 = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('POINT(0 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) ); /* no bounds, can't touch */
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertTrue( $g1->within($g2) );
+        $this->assertTrue( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertTrue( $g1->equals($g2) );
+        $this->assertTrue( $g1->equalsExact($g2) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertTrue( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertTrue( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('POINT(0 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertTrue( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 10) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertTrue( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('POINT(5 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertTrue( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 10) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertTrue( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('LINESTRING(5 -5, 5 5)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertTrue( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertFalse( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('LINESTRING(5 0, 15 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertTrue( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertFalse( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('LINESTRING(0 0, 5 0, 10 0)');
+        $g2 = $reader->read('LINESTRING(0 0, 10 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertTrue( $g1->within($g2) );
+        $this->assertTrue( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertTrue( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertTrue( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertTrue( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POLYGON((5 -5, 5 5, 15 5, 15 -5, 5 -5))');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertTrue( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertFalse( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POINT(15 15)');
+
+        $this->assertTrue( $g1->disjoint($g2) );
+        $this->assertFalse( $g1->touches($g2) );
+        $this->assertFalse( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertFalse( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertFalse( $g1->coveredBy($g2) );
+        }
+
+        $g1 = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))');
+        $g2 = $reader->read('POINT(5 0)');
+
+        $this->assertFalse( $g1->disjoint($g2) );
+        $this->assertTrue( $g1->touches($g2) );
+        $this->assertTrue( $g1->intersects($g2) );
+        $this->assertFalse( $g1->crosses($g2) );
+        $this->assertFalse( $g1->within($g2) );
+        $this->assertFalse( $g1->contains($g2) );
+        $this->assertFalse( $g1->overlaps($g2) );
+        $this->assertFalse( $g1->equals($g2) );
+        $this->assertFalse( $g1->equalsExact($g2, 1) );
+
+        if (method_exists(GEOSGeometry::class, 'covers')) {
+            $this->assertTrue( $g1->covers($g2) );
+        }
+
+        if (method_exists(GEOSGeometry::class, 'coveredBy')) {
+            $this->assertFalse( $g1->coveredBy($g2) );
+        }
+
+    }
+
+    public function testGeometry_isEmpty()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g1 = $reader->read('POINT(0 0)');
+        $this->assertFalse( $g1->isEmpty() );
+
+        $g1 = $reader->read('POINT EMPTY');
+        $this->assertTrue( $g1->isEmpty() );
+
+        $g1 = $reader->read('LINESTRING(0 0, 10 0)');
+        $this->assertFalse( $g1->isEmpty() );
+
+        $g1 = $reader->read('LINESTRING EMPTY');
+        $this->assertTrue( $g1->isEmpty() );
+
+        $g1 = $reader->read('POLYGON((0 0, 10 0, 10 10, 0 0))');
+        $this->assertFalse( $g1->isEmpty() );
+
+        $g1 = $reader->read('POLYGON EMPTY');
+        $this->assertTrue( $g1->isEmpty() );
+
+        $g1 = $reader->read('GEOMETRYCOLLECTION(POINT(0 0))');
+        $this->assertFalse( $g1->isEmpty() );
+
+        $g1 = $reader->read('GEOMETRYCOLLECTION EMPTY');
+        $this->assertTrue( $g1->isEmpty() );
+    }
+
+    public function testGeometry_checkValidity()
+    {
+        if (!method_exists(GEOSGeometry::class, 'checkValidity')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POINT(0 0)');
+        $val = $g->checkValidity();
+        $this->assertType( 'array', $val );
+        $this->assertTrue( $val['valid'] );
+        $this->assertFalse( isset($val['reason']) );
+        $this->assertFalse( isset($val['location']) );
+
+        $g = $reader->read('POINT(0 NaN)');
+        $val = $g->checkValidity();
+        $this->assertType( 'array', $val );
+        $this->assertFalse( $val['valid'] );
+        $this->assertEquals( 'Invalid Coordinate', $val['reason'] );
+        $this->assertEquals( 'POINT (0 nan)',
+            $writer->write($val['location']) );
+
+        $g = $reader->read(
+           'POLYGON((0 0, -10 10, 10 10, 0 0, 4 5, -4 5, 0 0)))'
+        );
+        $val = $g->checkValidity();
+        $this->assertType( 'array', $val );
+        $this->assertFalse( $val['valid'] );
+        $this->assertEquals( 'Ring Self-intersection', $val['reason'] );
+        $this->assertEquals( 'POINT (0 0)',
+            $writer->write($val['location']) );
+
+        $g = $reader->read(
+           'POLYGON((0 0, -10 10, 10 10, 0 0, 4 5, -4 5, 0 0)))'
+        );
+        $flags = GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE;
+        $val = $g->checkValidity($flags);
+        $this->assertType( 'array', $val );
+        $this->assertTrue( $val['valid'] );
+        $this->assertFalse( isset($val['reason']) );
+        $this->assertFalse( isset($val['location']) );
+    }
+
+    public function testGeometry_isSimple()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertTrue( $g->isSimple() );
+
+        $g = $reader->read('LINESTRING(0 0, 10 0)');
+        $this->assertTrue( $g->isSimple() );
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 5, 5 -5)');
+        $this->assertFalse( $g->isSimple() );
+    }
+
+    public function testGeometry_isRing()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertFalse( $g->isRing() );
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 5, 5 -5)');
+        $this->assertFalse( $g->isRing() );
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 5, 0 0)');
+        $this->assertTrue( $g->isRing() );
+    }
+
+    public function testGeometry_hasZ()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertFalse( $g->hasZ() );
+
+        $g = $reader->read('POINT(0 0 0)');
+        $this->assertTrue( $g->hasZ() );
+
+    }
+
+    public function testGeometry_isClosed()
+    {
+        if (!method_exists(GEOSGeometry::class, 'isClosed')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        try  {
+            $this->assertFalse( $g->isClosed() );
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('LineString', $e->getMessage());
+        }
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 5, 5 -5)');
+        $this->assertFalse( $g->isClosed() );
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 5, 0 0)');
+        $this->assertTrue( $g->isClosed() );
+    }
+
+    public function testGeometry_type()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals('Point', $g->typeName());
+        $this->assertEquals(GEOS_POINT, $g->typeId());
+
+        $g = $reader->read('MULTIPOINT (0 1, 2 3)');
+        $this->assertEquals('MultiPoint', $g->typeName());
+        $this->assertEquals(GEOS_MULTIPOINT, $g->typeId());
+
+        $g = $reader->read('LINESTRING (0 0, 2 3)');
+        $this->assertEquals('LineString', $g->typeName());
+        $this->assertEquals(GEOS_LINESTRING, $g->typeId());
+
+        $g = $reader->read('MULTILINESTRING ((0 1, 2 3), (10 10, 3 4))');
+        $this->assertEquals('MultiLineString', $g->typeName());
+        $this->assertEquals(GEOS_MULTILINESTRING, $g->typeId());
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals('Polygon', $g->typeName());
+        $this->assertEquals(GEOS_POLYGON, $g->typeId());
+
+        $g = $reader->read('MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((10 10, 10 14, 14 14, 14 10, 10 10), (11 11, 11 12, 12 12, 12 11, 11 11)))');
+        $this->assertEquals('MultiPolygon', $g->typeName());
+        $this->assertEquals(GEOS_MULTIPOLYGON, $g->typeId());
+
+        $g = $reader->read('GEOMETRYCOLLECTION (MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((10 10, 10 14, 14 14, 14 10, 10 10), (11 11, 11 12, 12 12, 12 11, 11 11))), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)), MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)), LINESTRING (0 0, 2 3), MULTIPOINT (0 0, 2 3), POINT (9 0))');
+        $this->assertEquals('GeometryCollection', $g->typeName());
+        $this->assertEquals(GEOS_GEOMETRYCOLLECTION, $g->typeId());
+    }
+
+    public function testGeometry_srid()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals(0, $g->getSRID());
+        $g->setSRID(2);
+        $this->assertEquals(2, $g->getSRID());
+    }
+
+    public function testGeometry_numGeometries()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals(1, $g->numGeometries());
+
+        $g = $reader->read('MULTIPOINT (0 1, 2 3)');
+        $this->assertEquals(2, $g->numGeometries());
+
+        $g = $reader->read('LINESTRING (0 0, 2 3)');
+        $this->assertEquals(1, $g->numGeometries());
+
+        $g = $reader->read('MULTILINESTRING ((0 1, 2 3), (10 10, 3 4))');
+        $this->assertEquals(2, $g->numGeometries());
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals(1, $g->numGeometries());
+
+        $g = $reader->read('MULTIPOLYGON (
+            ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            ((10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11)))');
+        $this->assertEquals(2, $g->numGeometries());
+
+        $g = $reader->read('GEOMETRYCOLLECTION (
+            MULTIPOLYGON (
+                ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                ((10 10, 10 14, 14 14, 14 10, 10 10),
+                    (11 11, 11 12, 12 12, 12 11, 11 11))
+            ),
+            POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)),
+            LINESTRING (0 0, 2 3),
+            MULTIPOINT (0 0, 2 3),
+            POINT (9 0))');
+        $this->assertEquals(6, $g->numGeometries());
+    }
+
+    public function testGeometry_geometryN()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $c = $g->geometryN(0);
+        $this->assertTrue( $g->equalsExact($c) );
+
+        $g = $reader->read('MULTIPOINT (0 1, 2 3)');
+        $this->assertEquals($reader->read('POINT(0 1)'), $g->geometryN(0));
+        $this->assertEquals($reader->read('POINT(2 3)'), $g->geometryN(1));
+
+        $c = $g->geometryN(2);
+        $this->assertNull( $c );
+
+    }
+
+    public function testGeometry_numInteriorRings()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals(0, $g->numInteriorRings());
+
+        $g = $reader->read('POLYGON (
+            (10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11))');
+        $this->assertEquals(1, $g->numInteriorRings());
+
+        $g = $reader->read('POLYGON (
+            (10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11),
+                (13 11, 13 12, 13.5 12, 13.5 11, 13 11))');
+        $this->assertEquals(2, $g->numInteriorRings());
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->numInteriorRings();
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'Polygon', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_numPoints()
+    {
+        if (!method_exists(GEOSGeometry::class, 'numPoints')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('LINESTRING (0 0, 1 0, 1 1, 0 1)');
+        $this->assertEquals(4, $g->numPoints());
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->numPoints();
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'LineString', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_getXY()
+    {
+        if (!method_exists(GEOSGeometry::class, 'getX')) {
+            return;
+        }
+
+        if (!method_exists(GEOSGeometry::class, 'getY')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT (1 2)');
+        $this->assertEquals(1, $g->getX());
+        $this->assertEquals(2, $g->getY());
+
+        $g = $reader->read('LINESTRING (0 0, 1 1)');
+        try {
+            $g->getX();
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'Point', $e->getMessage() );
+        }
+
+        try {
+            $g->getY();
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'Point', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_interiorRingN()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POLYGON (
+            (10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11))');
+        $r = $g->interiorRingN(0);
+        $this->assertEquals('LINEARRING (11 11, 11 12, 12 12, 12 11, 11 11)',
+            $writer->write($r) );
+
+        $g = $reader->read('POLYGON (
+            (10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11),
+                (13 11, 13 12, 13.5 12, 13.5 11, 13 11))');
+        $r = $g->interiorRingN(0);
+        $this->assertEquals('LINEARRING (11 11, 11 12, 12 12, 12 11, 11 11)',
+            $writer->write($r) );
+        $r = $g->interiorRingN(1);
+        $this->assertEquals('LINEARRING (13 11, 13 12, 14 12, 14 11, 13 11)',
+            $writer->write($r) );
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertNull($g->interiorRingN(0));
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->interiorRingN(0);
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'Polygon', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_exteriorRing()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POLYGON (
+            (10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11))');
+        $r = $g->exteriorRing();
+        $this->assertEquals('LINEARRING (10 10, 10 14, 14 14, 14 10, 10 10)',
+            $writer->write($r) );
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->exteriorRing(0);
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'Polygon', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_numCoordinates()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals(1, $g->numCoordinates());
+
+        $g = $reader->read('MULTIPOINT (0 1, 2 3)');
+        $this->assertEquals(2, $g->numCoordinates());
+
+        $g = $reader->read('LINESTRING (0 0, 2 3)');
+        $this->assertEquals(2, $g->numCoordinates());
+
+        $g = $reader->read('MULTILINESTRING ((0 1, 2 3), (10 10, 3 4))');
+        $this->assertEquals(4, $g->numCoordinates());
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals(5, $g->numCoordinates());
+
+        $g = $reader->read('MULTIPOLYGON (
+            ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            ((10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11)))');
+        $this->assertEquals(15, $g->numCoordinates());
+
+        $g = $reader->read('GEOMETRYCOLLECTION (
+            MULTIPOLYGON (
+                ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                ((10 10, 10 14, 14 14, 14 10, 10 10),
+                    (11 11, 11 12, 12 12, 12 11, 11 11))
+            ),
+            POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)),
+            LINESTRING (0 0, 2 3),
+            MULTIPOINT (0 0, 2 3),
+            POINT (9 0))');
+        $this->assertEquals(29, $g->numCoordinates());
+    }
+
+    public function testGeometry_dimension()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals(0, $g->dimension());
+
+        $g = $reader->read('MULTIPOINT (0 1, 2 3)');
+        $this->assertEquals(0, $g->dimension());
+
+        $g = $reader->read('LINESTRING (0 0, 2 3)');
+        $this->assertEquals(1, $g->dimension());
+
+        $g = $reader->read('MULTILINESTRING ((0 1, 2 3), (10 10, 3 4))');
+        $this->assertEquals(1, $g->dimension());
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals(2, $g->dimension());
+
+        $g = $reader->read('MULTIPOLYGON (
+            ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            ((10 10, 10 14, 14 14, 14 10, 10 10),
+                (11 11, 11 12, 12 12, 12 11, 11 11)))');
+        $this->assertEquals(2, $g->dimension());
+
+        $g = $reader->read('GEOMETRYCOLLECTION (
+            MULTIPOLYGON (
+                ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                ((10 10, 10 14, 14 14, 14 10, 10 10),
+                    (11 11, 11 12, 12 12, 12 11, 11 11))
+            ),
+            POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)),
+            MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)),
+            LINESTRING (0 0, 2 3),
+            MULTIPOINT (0 0, 2 3),
+            POINT (9 0))');
+        $this->assertEquals(2, $g->dimension());
+    }
+
+    public function testGeometry_coordinateDimension()
+    {
+        if (!method_exists(GEOSGeometry::class, 'coordinateDimension')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(0 0)');
+        $this->assertEquals(2, $g->coordinateDimension());
+
+        $g = $reader->read('POINT(0 0 0)');
+        $this->assertEquals(3, $g->coordinateDimension());
+
+    }
+
+    public function testGeometry_pointN()
+    {
+        if (!method_exists(GEOSGeometry::class, 'pointN')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setRoundingPrecision(0);
+
+        $g = $reader->read('LINESTRING (10 10, 10 14, 14 14, 14 10)');
+        $this->assertEquals('POINT (10 10)', $writer->write($g->pointN(0)) );
+        $this->assertEquals('POINT (10 14)', $writer->write($g->pointN(1)) );
+        $this->assertEquals('POINT (14 14)', $writer->write($g->pointN(2)) );
+        $this->assertEquals('POINT (14 10)', $writer->write($g->pointN(3)) );
+        $this->assertNull( $g->pointN(4) );
+
+        $g = $reader->read('LINEARRING (11 11, 11 12, 12 11, 11 11)');
+        $this->assertEquals('POINT (11 11)', $writer->write($g->pointN(0)) );
+        $this->assertEquals('POINT (11 12)', $writer->write($g->pointN(1)) );
+        $this->assertEquals('POINT (12 11)', $writer->write($g->pointN(2)) );
+        $this->assertEquals('POINT (11 11)', $writer->write($g->pointN(3)) );
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->pointN(0);
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'LineString', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_startendPoint()
+    {
+        if (!method_exists(GEOSGeometry::class, 'startPoint')) {
+            return;
+        }
+
+        if (!method_exists(GEOSGeometry::class, 'endPoint')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('LINESTRING (10 10, 10 14, 14 14, 14 10)');
+        $this->assertEquals('POINT (10 10)', $writer->write($g->startPoint()) );
+        $this->assertEquals('POINT (14 10)', $writer->write($g->endPoint()) );
+        $this->assertNull( $g->pointN(4) );
+
+        $g = $reader->read('LINEARRING (11 11, 11 12, 12 11, 11 11)');
+        $this->assertEquals('POINT (11 11)', $writer->write($g->startPoint()) );
+        $this->assertEquals('POINT (11 11)', $writer->write($g->endPoint()) );
+
+        $g = $reader->read('POINT (0 0)');
+        try {
+            $g->pointN(0);
+            $this->assertTrue( FALSE );
+        } catch (Exception $e) {
+            $this->assertContains( 'LineString', $e->getMessage() );
+        }
+
+    }
+
+    public function testGeometry_area()
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            $writer->setRoundingPrecision(0);
+        }
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals( 1.0, $g->area() );
+
+        $g = $reader->read('POINT (0 0)');
+        $this->assertEquals( 0.0, $g->area() );
+
+        $g = $reader->read('LINESTRING (0 0 , 10 0)');
+        $this->assertEquals( 0.0, $g->area() );
+
+    }
+
+    public function testGeometry_length()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertEquals( 4.0, $g->length() );
+
+        $g = $reader->read('POINT (0 0)');
+        $this->assertEquals( 0.0, $g->length() );
+
+        $g = $reader->read('LINESTRING (0 0 , 10 0)');
+        $this->assertEquals( 10.0, $g->length() );
+
+    }
+
+    public function testGeometry_distance()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+
+        $g2 = $reader->read('POINT(0.5 0.5)');
+        $this->assertEquals( 0.0, $g->distance($g2) );
+
+        $g2 = $reader->read('POINT (-1 0)');
+        $this->assertEquals( 1.0, $g->distance($g2) );
+
+        $g2 = $reader->read('LINESTRING (3 0 , 10 0)');
+        $this->assertEquals( 2.0, $g->distance($g2) );
+
+    }
+
+    public function testGeometry_hausdorffDistance()
+    {
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+
+        $g2 = $reader->read('POINT(0 10)');
+        $this->assertEquals( 10.0, round($g->hausdorffDistance($g2)) );
+
+        $g2 = $reader->read('POINT (-1 0)');
+        $this->assertEquals( 2.0, round($g->hausdorffDistance($g2)) );
+
+        $g2 = $reader->read('LINESTRING (3 0 , 10 0)');
+        $this->assertEquals( 9.0, round($g->hausdorffDistance($g2)) );
+
+    }
+
+    public function testGeometry_delaunayTriangulation()
+    {
+        if (!method_exists(GEOSGeometry::class, 'delaunayTriangulation')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setRoundingPrecision(0);
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+
+        $b = $g->delaunayTriangulation();
+        $this->assertEquals('GEOMETRYCOLLECTION (POLYGON ((0 1, 0 0, 1 0, 0 1)), POLYGON ((0 1, 1 0, 1 1, 0 1)))', $writer->write($b));
+
+        $b = $g->delaunayTriangulation(0,true);
+        $this->assertEquals('MULTILINESTRING ((0 1, 1 1), (0 0, 0 1), (0 0, 1 0), (1 0, 1 1), (0 1, 1 0))', $writer->write($b));
+
+    }
+
+    public function testGeometry_voronoiDiagram()
+    {
+        if (!method_exists(GEOSGeometry::class, 'voronoiDiagram')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setRoundingPrecision(0);
+
+        $g = $reader->read('MULTIPOINT(0 0, 100 0, 100 100, 0 100)');
+
+        $b = $g->voronoiDiagram();
+        $this->assertEquals('GEOMETRYCOLLECTION (POLYGON ((50 50, 50 200, 200 200, 200 50, 50 50)), POLYGON ((50 -100, 50 50, 200 50, 200 -100, 50 -100)), POLYGON ((-100 50, -100 200, 50 200, 50 50, -100 50)), POLYGON ((-100 -100, -100 50, 50 50, 50 -100, -100 -100)))', $writer->write($b->normalize()));
+
+        $b = $g->voronoiDiagram(0, 1);
+        $this->assertEquals('MULTILINESTRING ((50 50, 200 50), (50 50, 50 200), (50 -100, 50 50), (-100 50, 50 50))', $writer->write($b->normalize()));
+
+        $b = $g->voronoiDiagram(0, 1, $g->buffer(1000));
+        $this->assertEquals('MULTILINESTRING ((50 50, 1100 50), (50 50, 50 1100), (50 -1000, 50 50), (-1000 50, 50 50))', $writer->write($b->normalize()));
+
+    }
+
+    public function testGeometry_snapTo()
+    {
+        if (!method_exists(GEOSGeometry::class, 'snapTo')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setTrim(true);
+
+        $g = $reader->read('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))');
+
+        $g2 = $reader->read('POINT(0.1 0)');
+
+        $snapped = $g->snapTo($g2, 0);
+        $this->assertEquals('POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))', $writer->write($snapped));
+
+        $snapped = $g->snapTo($g2, 0.5);
+        $this->assertEquals('POLYGON ((0.1 0, 1 0, 1 1, 0 1, 0.1 0))', $writer->write($snapped));
+    }
+
+    public function testGeometry_node()
+    {
+        if (!method_exists(GEOSGeometry::class, 'node')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKTWriter();
+        $writer->setTrim(true);
+
+        $g = $reader->read('LINESTRING(0 0, 10 0, 5 -5, 5 5)');
+
+        $noded = $g->node();
+        $this->assertEquals('MULTILINESTRING ((0 0, 5 0), (5 0, 10 0, 5 -5, 5 0), (5 0, 5 5))', $writer->write($noded));
+
+    }
+}
+
+GeometryTest::run();
+
+?>
+--EXPECT--
+GeometryTest->testGeometry_serialization	OK
+GeometryTest->testGeometry_project	OK
+GeometryTest->testGeometry_interpolate	OK
+GeometryTest->testGeometry_buffer	OK
+GeometryTest->testGeometry_offsetCurve	OK
+GeometryTest->testGeometry_envelope	OK
+GeometryTest->testGeometry_intersection	OK
+GeometryTest->testGeometry_clipByRect	OK
+GeometryTest->testGeometry_convexHull	OK
+GeometryTest->testGeometry_difference	OK
+GeometryTest->testGeometry_symdifference	OK
+GeometryTest->testGeometry_boundary	OK
+GeometryTest->testGeometry_union	OK
+GeometryTest->testGeometry_unaryunion	OK
+GeometryTest->testGeometry_pointOnSurface	OK
+GeometryTest->testGeometry_centroid	OK
+GeometryTest->testGeometry_relate	OK
+GeometryTest->testGeometry_relateBoundaryNodeRule	OK
+GeometryTest->testGeometry_polygonize	OK
+GeometryTest->testGeometry_lineMerge	OK
+GeometryTest->testGeometry_sharedPaths	OK
+GeometryTest->testGeometry_simplify	OK
+GeometryTest->testGeometry_extractUniquePoints	OK
+GeometryTest->testGeometry_relationalOps	OK
+GeometryTest->testGeometry_isEmpty	OK
+GeometryTest->testGeometry_checkValidity	OK
+GeometryTest->testGeometry_isSimple	OK
+GeometryTest->testGeometry_isRing	OK
+GeometryTest->testGeometry_hasZ	OK
+GeometryTest->testGeometry_isClosed	OK
+GeometryTest->testGeometry_type	OK
+GeometryTest->testGeometry_srid	OK
+GeometryTest->testGeometry_numGeometries	OK
+GeometryTest->testGeometry_geometryN	OK
+GeometryTest->testGeometry_numInteriorRings	OK
+GeometryTest->testGeometry_numPoints	OK
+GeometryTest->testGeometry_getXY	OK
+GeometryTest->testGeometry_interiorRingN	OK
+GeometryTest->testGeometry_exteriorRing	OK
+GeometryTest->testGeometry_numCoordinates	OK
+GeometryTest->testGeometry_dimension	OK
+GeometryTest->testGeometry_coordinateDimension	OK
+GeometryTest->testGeometry_pointN	OK
+GeometryTest->testGeometry_startendPoint	OK
+GeometryTest->testGeometry_area	OK
+GeometryTest->testGeometry_length	OK
+GeometryTest->testGeometry_distance	OK
+GeometryTest->testGeometry_hausdorffDistance	OK
+GeometryTest->testGeometry_delaunayTriangulation	OK
+GeometryTest->testGeometry_voronoiDiagram	OK
+GeometryTest->testGeometry_snapTo	OK
+GeometryTest->testGeometry_node	OK
diff --git a/tests/002_WKTWriter.phpt b/tests/002_WKTWriter.phpt
new file mode 100644
index 0000000..646cf81
--- /dev/null
+++ b/tests/002_WKTWriter.phpt
@@ -0,0 +1,209 @@
+--TEST--
+WKTWriter tests
+--SKIPIF--
+<?php if (!extension_loaded('geos')) { print "geos extension not loaded\n"; exit(1); } ?>
+--FILE--
+<?php
+
+require './tests/TestHelper.php';
+
+class WKTWriterTest extends GEOSTest
+{
+    public function testWKTWriter__construct()
+    {
+        $writer = new GEOSWKTWriter();
+        $this->assertNotNull($writer);
+    }
+
+    public function testWKTWriter_write()
+    {
+        $writer = new GEOSWKTWriter();
+        $reader = new GEOSWKTReader();
+
+        try {
+            $writer->write(1);
+            $this->assertTrue(FALSE); # this is just to fail if we get here
+        } catch (Exception $e) {
+            $this->assertContains('expects parameter 1', $e->getMessage());
+        }
+
+        $g = $reader->read('POINT(6 7)');
+
+        $this->assertEquals('POINT (6.0000000000000000 7.0000000000000000)',
+            $writer->write($g));
+    }
+
+    public function testWKTWriter_setTrim()
+    {
+        if (!method_exists(GEOSWKTWriter::class, 'setTrim')) {
+            return;
+        }
+
+        $writer = new GEOSWKTWriter();
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(6 7)');
+        $this->assertNotNull($g);
+
+        $writer->setTrim(TRUE);
+        $this->assertEquals('POINT (6 7)',
+            $writer->write($g));
+
+        $writer->setTrim(FALSE);
+        $this->assertEquals('POINT (6.0000000000000000 7.0000000000000000)',
+            $writer->write($g));
+
+    }
+
+    public function testWKT_roundTrip()
+    {
+        $r = new GEOSWKTReader();
+        $w = new GEOSWKTWriter();
+
+        if (method_exists(GEOSWKTWriter::class, 'setTrim')) {
+            $w->setTrim(TRUE);
+        }
+
+        $in[] = 'POINT (0 0)';
+        $in[] = 'POINT EMPTY';
+        $in[] = 'MULTIPOINT (0 1, 2 3)';
+        $in[] = 'MULTIPOINT EMPTY';
+        $in[] = 'LINESTRING (0 0, 2 3)';
+        $in[] = 'LINESTRING EMPTY';
+        $in[] = 'MULTILINESTRING ((0 1, 2 3), (10 10, 3 4))';
+        $in[] = 'MULTILINESTRING EMPTY';
+        $in[] = 'POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))';
+        $in[] = 'POLYGON EMPTY';
+        $in[] = 'MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((10 10, 10 14, 14 14, 14 10, 10 10), (11 11, 11 12, 12 12, 12 11, 11 11)))';
+        $in[] = 'MULTIPOLYGON EMPTY';
+        $in[] = 'GEOMETRYCOLLECTION (MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((10 10, 10 14, 14 14, 14 10, 10 10), (11 11, 11 12, 12 12, 12 11, 11 11))), POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0)), MULTILINESTRING ((0 0, 2 3), (10 10, 3 4)), LINESTRING (0 0, 2 3), MULTIPOINT (0 0, 2 3), POINT (9 0))';
+        $in[] = 'GEOMETRYCOLLECTION EMPTY';
+
+        foreach ($in as $i) {
+            $this->assertEquals($i, $w->write($r->read($i)));
+        }
+
+    }
+
+    public function testWKTWriter_setRoundingPrecision()
+    {
+        if (!method_exists(GEOSWKTWriter::class, 'setRoundingPrecision')) {
+            return;
+        }
+
+        $writer = new GEOSWKTWriter();
+        $reader = new GEOSWKTReader();
+
+        $g = $reader->read('POINT(6.123456 7.123456)');
+
+        $this->assertEquals('POINT (6.1234560000000000 7.1234560000000000)',
+            $writer->write($g));
+
+        $writer->setRoundingPrecision(2);
+        $this->assertEquals('POINT (6.12 7.12)', $writer->write($g));
+
+        $writer->setRoundingPrecision(5); /* rounds */
+        $this->assertEquals('POINT (6.12346 7.12346)', $writer->write($g));
+
+        $writer->setRoundingPrecision(1);
+        $this->assertEquals('POINT (6.1 7.1)', $writer->write($g));
+
+        $writer->setRoundingPrecision(0);
+        $this->assertEquals('POINT (6 7)', $writer->write($g));
+
+    }
+
+    public function testWKTWriter_getOutputDimension()
+    {
+        if (!method_exists(GEOSWKTWriter::class, 'getOutputDimension')) {
+            return;
+        }
+
+        $writer = new GEOSWKTWriter();
+        $this->assertEquals(2, $writer->getOutputDimension());
+    }
+
+    public function testWKTWriter_setOutputDimension()
+    {
+        if (!method_exists(GEOSWKTWriter::class, 'setOutputDimension')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $g3d = $reader->read('POINT(1 2 3)');
+        $g2d = $reader->read('POINT(3 2)');
+
+        $writer = new GEOSWKTWriter();
+        $writer->setTrim(TRUE);
+
+        # Only 2d by default
+        $this->assertEquals('POINT (1 2)', $writer->write($g3d));
+
+        # 3d if requested _and_ available
+        $writer->setOutputDimension(3);
+        $this->assertEquals('POINT Z (1 2 3)', $writer->write($g3d));
+        $this->assertEquals('POINT (3 2)', $writer->write($g2d));
+
+        # 1 is invalid
+        try {
+            $writer->setOutputDimension(1);
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('must be 2 or 3', $e->getMessage());
+        }
+
+        # 4 is invalid
+        try {
+            $writer->setOutputDimension(4);
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('must be 2 or 3', $e->getMessage());
+        }
+
+    }
+
+    public function testWKTWriter_setOld3D()
+    {
+        if (!method_exists(GEOSWKTWriter::class, 'setOld3D')) {
+            return;
+        }
+
+        $reader = new GEOSWKTReader();
+        $g3d = $reader->read('POINT(1 2 3)');
+
+        $writer = new GEOSWKTWriter();
+        $writer->setTrim(TRUE);
+
+        # New 3d WKT by default
+        $writer->setOutputDimension(3);
+        $this->assertEquals('POINT Z (1 2 3)', $writer->write($g3d));
+
+        # Switch to old
+        $writer->setOld3D(TRUE);
+        $this->assertEquals('POINT (1 2 3)', $writer->write($g3d));
+
+        # Old3d flag is not reset when changing dimensions
+        $writer->setOutputDimension(2);
+        $this->assertEquals('POINT (1 2)', $writer->write($g3d));
+        $writer->setOutputDimension(3);
+        $this->assertEquals('POINT (1 2 3)', $writer->write($g3d));
+
+        # Likewise, dimensions spec is not reset when changing old3d flag
+        $writer->setOld3D(FALSE);
+        $this->assertEquals('POINT Z (1 2 3)', $writer->write($g3d));
+
+    }
+}
+
+WKTWriterTest::run();
+
+?>
+--EXPECT--
+WKTWriterTest->testWKTWriter__construct	OK
+WKTWriterTest->testWKTWriter_write	OK
+WKTWriterTest->testWKTWriter_setTrim	OK
+WKTWriterTest->testWKT_roundTrip	OK
+WKTWriterTest->testWKTWriter_setRoundingPrecision	OK
+WKTWriterTest->testWKTWriter_getOutputDimension	OK
+WKTWriterTest->testWKTWriter_setOutputDimension	OK
+WKTWriterTest->testWKTWriter_setOld3D	OK
diff --git a/tests/003_WKTReader.phpt b/tests/003_WKTReader.phpt
new file mode 100644
index 0000000..7bcbf54
--- /dev/null
+++ b/tests/003_WKTReader.phpt
@@ -0,0 +1,113 @@
+--TEST--
+WKTReader tests
+--SKIPIF--
+<?php if (!extension_loaded('geos')) { print "geos extension not loaded\n"; exit(1); } ?>
+--FILE--
+<?php
+
+require './tests/TestHelper.php';
+
+class WKTReaderTest extends GEOSTest
+{
+    public function testWKTReader__construct()
+    {
+        $reader = new GEOSWKTReader();
+        $this->assertNotNull($reader);
+    }
+
+    public function testWKTReader_read()
+    {
+        $reader = new GEOSWKTReader();
+
+        /* Good WKT */
+        $geom = $reader->read('POINT(0 0)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('POINT(0 0 0)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('POINT Z (0 0 0)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('POINT EMPTY');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOINT(0 0 1, 2 3 4)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOINT Z (0 0 1, 2 3 4)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOINT((0 0), (2 3))');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOINT EMPTY');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('LINESTRING(0 0 1, 2 3 4)');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('LINESTRING EMPTY');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTILINESTRING((0 0 1, 2 3 4),
+                                               (10 10 2, 3 4 5))');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTILINESTRING Z ((0 0 1, 2 3 4),
+                                               (10 10 2, 3 4 5))');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('POLYGON EMPTY');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOLYGON(
+                                ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                                ((10 10, 10 14, 14 14, 14 10, 10 10),
+                                    (11 11, 11 12, 12 12, 12 11, 11 11))
+                               )');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('MULTIPOLYGON EMPTY');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('GEOMETRYCOLLECTION(
+                MULTIPOLYGON(
+                 ((0 0, 1 0, 1 1, 0 1, 0 0)),
+                 ((10 10, 10 14, 14 14, 14 10, 10 10),
+                  (11 11, 11 12, 12 12, 12 11, 11 11))
+                ),
+                POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)),
+                MULTILINESTRING((0 0, 2 3), (10 10, 3 4)),
+                LINESTRING(0 0, 2 3),
+                MULTIPOINT(0 0, 2 3),
+                POINT(9 0)
+        )');
+        $this->assertNotNull($geom);
+        $geom = $reader->read('GEOMETRYCOLLECTION EMPTY');
+        $this->assertNotNull($geom);
+    }
+
+    public function testBogusWKT()
+    {
+        $reader = new GEOSWKTReader();
+
+        /* BOGUS WKT */
+        try {
+            $reader->read("MULTIDOT(0 1 2 3)");
+            $this->assertTrue(FALSE); # this is just to fail if we get here
+        } catch (Exception $e) {
+            $this->assertContains('ParseException', $e->getMessage());
+        }
+    }
+
+    public function testNoArgumentsToRead()
+    {
+        $reader = new GEOSWKTReader();
+
+        /* BOGUS call (#448) */
+        try {
+            $reader->read();
+            $this->assertTrue(FALSE); # this is just to fail if we get here
+        } catch (Exception $e) {
+            $this->assertContains('expects exactly 1 parameter',
+                                  $e->getMessage());
+        }
+    }
+}
+
+WKTReaderTest::run();
+
+?>
+--EXPECT--
+WKTReaderTest->testWKTReader__construct	OK
+WKTReaderTest->testWKTReader_read	OK
+WKTReaderTest->testBogusWKT	OK
+WKTReaderTest->testNoArgumentsToRead	OK
diff --git a/tests/004_WKBWriter.phpt b/tests/004_WKBWriter.phpt
new file mode 100644
index 0000000..d2b3c85
--- /dev/null
+++ b/tests/004_WKBWriter.phpt
@@ -0,0 +1,170 @@
+--TEST--
+WKBWriter tests
+--SKIPIF--
+<?php if (!extension_loaded('geos')) { print "geos extension not loaded\n"; exit(1); } ?>
+--FILE--
+<?php
+
+require './tests/TestHelper.php';
+
+class WKBWriterTest extends GEOSTest
+{
+    public function testWKBWriter__construct()
+    {
+        $writer = new GEOSWKBWriter();
+        $this->assertNotNull($writer);
+    }
+
+    public function testWKBWriter_getOutputDimension()
+    {
+        $writer = new GEOSWKBWriter();
+        $this->assertEquals(2, $writer->getOutputDimension());
+    }
+
+    public function testWKBWriter_setOutputDimension()
+    {
+        $writer = new GEOSWKBWriter();
+        $writer->setOutputDimension(3);
+        $this->assertEquals(3, $writer->getOutputDimension());
+        $writer->setOutputDimension(2);
+        $this->assertEquals(2, $writer->getOutputDimension());
+
+        # 1 is invalid
+        try {
+            $writer->setOutputDimension(1);
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('must be 2 or 3', $e->getMessage());
+        }
+
+        # 4 is invalid
+        try {
+            $writer->setOutputDimension(4);
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('must be 2 or 3', $e->getMessage());
+        }
+    }
+
+    public function testWKBWriter_getsetByteOrder()
+    {
+        $writer = new GEOSWKBWriter();
+
+        /* Machine-dependent */
+        $bo = $writer->getByteOrder();
+
+        $obo = $bo ? 0 : 1;
+        $writer->setByteOrder($obo);
+        $this->assertEquals($obo, $writer->getByteOrder());
+
+        # Anything different from 0 (BIG_ENDIAN) or 1 (LITTLE_ENDIAN)
+        # is invalid
+        try {
+            $writer->setByteOrder(5);
+            $this->assertTrue(FALSE);
+        } catch (Exception $e) {
+            $this->assertContains('LITTLE (1) or BIG (0)', $e->getMessage());
+        }
+    }
+
+    public function testWKBWriter_getsetIncludeSRID()
+    {
+        $writer = new GEOSWKBWriter();
+
+        $this->assertEquals(FALSE, $writer->getIncludeSRID());
+        $writer->setIncludeSRID(TRUE);
+        $this->assertEquals(TRUE, $writer->getIncludeSRID());
+        $writer->setIncludeSRID(FALSE);
+        $this->assertEquals(FALSE, $writer->getIncludeSRID());
+    }
+
+    /**
+     * @dataProvider providerWKBWriter_write
+     *
+     * @param integer $byteOrder       The byte order: 0 for BIG endian, 1 for LITTLE endian.
+     * @param integer $inputDimension  The input dimension: 2 or 3.
+     * @param integer $outputDimension The output dimension: 2 or 3.
+     * @param boolean $includeSrid     Whether to include the SRID in the output.
+     * @param string  $wkb             The expected HEX WKB output.
+     */
+    public function runWKBWriter_write($byteOrder, $inputDimension, $outputDimension, $includeSrid, $wkb)
+    {
+        $reader = new GEOSWKTReader();
+        $writer = new GEOSWKBWriter();
+
+        $writer->setByteOrder($byteOrder);
+        $writer->setOutputDimension($outputDimension);
+        $writer->setIncludeSRID($includeSrid);
+
+        if ($inputDimension === 3) {
+            $g = $reader->read('POINT(6 7 8)');
+            $g->setSRID(53);
+        } else {
+            $g = $reader->read('POINT(6 7)');
+            $g->setSRID(43);
+        }
+
+        $this->assertEquals(hex2bin($wkb), $writer->write($g));
+        $this->assertEquals($wkb, $writer->writeHEX($g));
+    }
+
+    public function testWKBWriter_write()
+    {
+        // 2D input
+        $this->runWKBWriter_write(1, 2, 2, false, '010100000000000000000018400000000000001C40');        // 2D LITTLE endian
+        $this->runWKBWriter_write(1, 2, 2, true, '01010000202B00000000000000000018400000000000001C40'); // 2D LITTLE endian + SRID
+        $this->runWKBWriter_write(0, 2, 2, false, '00000000014018000000000000401C000000000000');        // 2D BIG endian
+        $this->runWKBWriter_write(0, 2, 2, true, '00200000010000002B4018000000000000401C000000000000'); // 2D BIG endian + SRID
+        $this->runWKBWriter_write(1, 2, 3, false, '010100000000000000000018400000000000001C40');        // 3D LITTLE endian
+        $this->runWKBWriter_write(1, 2, 3, true, '01010000202B00000000000000000018400000000000001C40'); // 3D LITTLE endian + SRID
+        $this->runWKBWriter_write(0, 2, 3, false, '00000000014018000000000000401C000000000000');        // 3D BIG endian
+        $this->runWKBWriter_write(0, 2, 3, true, '00200000010000002B4018000000000000401C000000000000'); // 3D BIG endian + SRID
+
+        // 3D input
+        $this->runWKBWriter_write(1, 3, 2, false, '010100000000000000000018400000000000001C40');                        // 2D LITTLE endian
+        $this->runWKBWriter_write(1, 3, 2, true, '01010000203500000000000000000018400000000000001C40');                 // 2D LITTLE endian + SRID
+        $this->runWKBWriter_write(0, 3, 2, false, '00000000014018000000000000401C000000000000');                        // 2D BIG endian
+        $this->runWKBWriter_write(0, 3, 2, true, '0020000001000000354018000000000000401C000000000000');                 // 2D BIG endian + SRID
+        $this->runWKBWriter_write(1, 3, 3, false, '010100008000000000000018400000000000001C400000000000002040');        // 3D LITTLE endian
+        $this->runWKBWriter_write(1, 3, 3, true, '01010000A03500000000000000000018400000000000001C400000000000002040'); // 3D LITTLE endian + SRID
+        $this->runWKBWriter_write(0, 3, 3, false, '00800000014018000000000000401C0000000000004020000000000000');        // 3D BIG endian
+        $this->runWKBWriter_write(0, 3, 3, true, '00A0000001000000354018000000000000401C0000000000004020000000000000'); // 3D BIG endian + SRID
+    }
+
+    public function testInvalidWriteThrowsException()
+    {
+        $writer = new GEOSWKBWriter();
+
+        try {
+            $writer->write(1);
+            $this->assertTrue(false);
+        } catch (ErrorException $e) {
+            $this->assertContains('expects parameter 1 to be object, integer given', $e->getMessage());
+        }
+    }
+
+    public function testInvalidWriteHEXThrowsException()
+    {
+        $writer = new GEOSWKBWriter();
+
+        try {
+            $writer->writeHEX(1);
+            $this->assertTrue(false);
+        } catch (ErrorException $e) {
+            $this->assertContains('expects parameter 1 to be object, integer given', $e->getMessage());
+        }
+    }
+}
+
+WKBWriterTest::run();
+
+?>
+--EXPECT--
+WKBWriterTest->testWKBWriter__construct	OK
+WKBWriterTest->testWKBWriter_getOutputDimension	OK
+WKBWriterTest->testWKBWriter_setOutputDimension	OK
+WKBWriterTest->testWKBWriter_getsetByteOrder	OK
+WKBWriterTest->testWKBWriter_getsetIncludeSRID	OK
+WKBWriterTest->testWKBWriter_write	OK
+WKBWriterTest->testInvalidWriteThrowsException	OK
+WKBWriterTest->testInvalidWriteHEXThrowsException	OK
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..57f54fd
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,14 @@
+Tests can be run manually from the top-level build dir using:
+
+    php -n -d extension=modules/geos.so tests/001_Geometry.phpt
+
+If you want to use valgrind, it is recommended to disable Zend
+memory management:
+
+    export USE_ZEND_ALLOC=0
+
+And avoid unload of modules:
+
+    export ZEND_DONT_UNLOAD_MODULES=1
+
+Read more on https://bugs.php.net/bugs-getting-valgrind-log.php
diff --git a/tests/TestHelper.php b/tests/TestHelper.php
new file mode 100644
index 0000000..c6dd050
--- /dev/null
+++ b/tests/TestHelper.php
@@ -0,0 +1,141 @@
+<?php
+
+# hex2bin for PHP < 5.4
+# https://gist.github.com/mcrumley/5672621
+if (!function_exists('hex2bin')) {
+    function hex2bin($str) {
+        $map = array(
+            '00'=>"\x00", '10'=>"\x10", '20'=>"\x20", '30'=>"\x30", '40'=>"\x40", '50'=>"\x50", '60'=>"\x60", '70'=>"\x70",
+            '01'=>"\x01", '11'=>"\x11", '21'=>"\x21", '31'=>"\x31", '41'=>"\x41", '51'=>"\x51", '61'=>"\x61", '71'=>"\x71",
+            '02'=>"\x02", '12'=>"\x12", '22'=>"\x22", '32'=>"\x32", '42'=>"\x42", '52'=>"\x52", '62'=>"\x62", '72'=>"\x72",
+            '03'=>"\x03", '13'=>"\x13", '23'=>"\x23", '33'=>"\x33", '43'=>"\x43", '53'=>"\x53", '63'=>"\x63", '73'=>"\x73",
+            '04'=>"\x04", '14'=>"\x14", '24'=>"\x24", '34'=>"\x34", '44'=>"\x44", '54'=>"\x54", '64'=>"\x64", '74'=>"\x74",
+            '05'=>"\x05", '15'=>"\x15", '25'=>"\x25", '35'=>"\x35", '45'=>"\x45", '55'=>"\x55", '65'=>"\x65", '75'=>"\x75",
+            '06'=>"\x06", '16'=>"\x16", '26'=>"\x26", '36'=>"\x36", '46'=>"\x46", '56'=>"\x56", '66'=>"\x66", '76'=>"\x76",
+            '07'=>"\x07", '17'=>"\x17", '27'=>"\x27", '37'=>"\x37", '47'=>"\x47", '57'=>"\x57", '67'=>"\x67", '77'=>"\x77",
+            '08'=>"\x08", '18'=>"\x18", '28'=>"\x28", '38'=>"\x38", '48'=>"\x48", '58'=>"\x58", '68'=>"\x68", '78'=>"\x78",
+            '09'=>"\x09", '19'=>"\x19", '29'=>"\x29", '39'=>"\x39", '49'=>"\x49", '59'=>"\x59", '69'=>"\x69", '79'=>"\x79",
+            '0a'=>"\x0a", '1a'=>"\x1a", '2a'=>"\x2a", '3a'=>"\x3a", '4a'=>"\x4a", '5a'=>"\x5a", '6a'=>"\x6a", '7a'=>"\x7a",
+            '0b'=>"\x0b", '1b'=>"\x1b", '2b'=>"\x2b", '3b'=>"\x3b", '4b'=>"\x4b", '5b'=>"\x5b", '6b'=>"\x6b", '7b'=>"\x7b",
+            '0c'=>"\x0c", '1c'=>"\x1c", '2c'=>"\x2c", '3c'=>"\x3c", '4c'=>"\x4c", '5c'=>"\x5c", '6c'=>"\x6c", '7c'=>"\x7c",
+            '0d'=>"\x0d", '1d'=>"\x1d", '2d'=>"\x2d", '3d'=>"\x3d", '4d'=>"\x4d", '5d'=>"\x5d", '6d'=>"\x6d", '7d'=>"\x7d",
+            '0e'=>"\x0e", '1e'=>"\x1e", '2e'=>"\x2e", '3e'=>"\x3e", '4e'=>"\x4e", '5e'=>"\x5e", '6e'=>"\x6e", '7e'=>"\x7e",
+            '0f'=>"\x0f", '1f'=>"\x1f", '2f'=>"\x2f", '3f'=>"\x3f", '4f'=>"\x4f", '5f'=>"\x5f", '6f'=>"\x6f", '7f'=>"\x7f",
+
+            '80'=>"\x80", '90'=>"\x90", 'a0'=>"\xa0", 'b0'=>"\xb0", 'c0'=>"\xc0", 'd0'=>"\xd0", 'e0'=>"\xe0", 'f0'=>"\xf0",
+            '81'=>"\x81", '91'=>"\x91", 'a1'=>"\xa1", 'b1'=>"\xb1", 'c1'=>"\xc1", 'd1'=>"\xd1", 'e1'=>"\xe1", 'f1'=>"\xf1",
+            '82'=>"\x82", '92'=>"\x92", 'a2'=>"\xa2", 'b2'=>"\xb2", 'c2'=>"\xc2", 'd2'=>"\xd2", 'e2'=>"\xe2", 'f2'=>"\xf2",
+            '83'=>"\x83", '93'=>"\x93", 'a3'=>"\xa3", 'b3'=>"\xb3", 'c3'=>"\xc3", 'd3'=>"\xd3", 'e3'=>"\xe3", 'f3'=>"\xf3",
+            '84'=>"\x84", '94'=>"\x94", 'a4'=>"\xa4", 'b4'=>"\xb4", 'c4'=>"\xc4", 'd4'=>"\xd4", 'e4'=>"\xe4", 'f4'=>"\xf4",
+            '85'=>"\x85", '95'=>"\x95", 'a5'=>"\xa5", 'b5'=>"\xb5", 'c5'=>"\xc5", 'd5'=>"\xd5", 'e5'=>"\xe5", 'f5'=>"\xf5",
+            '86'=>"\x86", '96'=>"\x96", 'a6'=>"\xa6", 'b6'=>"\xb6", 'c6'=>"\xc6", 'd6'=>"\xd6", 'e6'=>"\xe6", 'f6'=>"\xf6",
+            '87'=>"\x87", '97'=>"\x97", 'a7'=>"\xa7", 'b7'=>"\xb7", 'c7'=>"\xc7", 'd7'=>"\xd7", 'e7'=>"\xe7", 'f7'=>"\xf7",
+            '88'=>"\x88", '98'=>"\x98", 'a8'=>"\xa8", 'b8'=>"\xb8", 'c8'=>"\xc8", 'd8'=>"\xd8", 'e8'=>"\xe8", 'f8'=>"\xf8",
+            '89'=>"\x89", '99'=>"\x99", 'a9'=>"\xa9", 'b9'=>"\xb9", 'c9'=>"\xc9", 'd9'=>"\xd9", 'e9'=>"\xe9", 'f9'=>"\xf9",
+            '8a'=>"\x8a", '9a'=>"\x9a", 'aa'=>"\xaa", 'ba'=>"\xba", 'ca'=>"\xca", 'da'=>"\xda", 'ea'=>"\xea", 'fa'=>"\xfa",
+            '8b'=>"\x8b", '9b'=>"\x9b", 'ab'=>"\xab", 'bb'=>"\xbb", 'cb'=>"\xcb", 'db'=>"\xdb", 'eb'=>"\xeb", 'fb'=>"\xfb",
+            '8c'=>"\x8c", '9c'=>"\x9c", 'ac'=>"\xac", 'bc'=>"\xbc", 'cc'=>"\xcc", 'dc'=>"\xdc", 'ec'=>"\xec", 'fc'=>"\xfc",
+            '8d'=>"\x8d", '9d'=>"\x9d", 'ad'=>"\xad", 'bd'=>"\xbd", 'cd'=>"\xcd", 'dd'=>"\xdd", 'ed'=>"\xed", 'fd'=>"\xfd",
+            '8e'=>"\x8e", '9e'=>"\x9e", 'ae'=>"\xae", 'be'=>"\xbe", 'ce'=>"\xce", 'de'=>"\xde", 'ee'=>"\xee", 'fe'=>"\xfe",
+            '8f'=>"\x8f", '9f'=>"\x9f", 'af'=>"\xaf", 'bf'=>"\xbf", 'cf'=>"\xcf", 'df'=>"\xdf", 'ef'=>"\xef", 'ff'=>"\xff",
+        );
+
+        $strlen = strlen($str);
+
+        if ($strlen % 2 !== 0) {
+            user_error('Hexadecimal input string must have an even length', E_USER_WARNING);
+            return false;
+        }
+
+        if (strspn($str, '0123456789ABCDEFabcdef') !== $strlen) {
+            return false;
+        }
+
+        return strtr(strtolower($str), $map);
+    }
+}
+
+function exception_error_handler($errno, $errstr, $errfile, $errline ) {
+    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
+}
+set_error_handler("exception_error_handler");
+
+class GEOSTest
+{
+    static public function run()
+    {
+        $instance = new static();
+
+        foreach (get_class_methods($instance) as $method) {
+            if (strpos($method, 'test') === 0) {
+                $class = get_class($instance);
+
+                try {
+                    $instance->$method();
+                    print "{$class}->{$method}\tOK" . PHP_EOL;
+                } catch (Exception $e) {
+                    print "{$class}->{$method}\tERROR:" . PHP_EOL;
+                    print $e->getMessage() . "\n";
+                    debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+                    throw $e;
+                }
+            }
+        }
+    }
+
+    public function assertContains($expected, $actual)
+    {
+        if (strpos($actual, $expected) === false) {
+            throw new Exception("Expected '{$actual}' to contain '{$expected}'.");
+        }
+    }
+
+    public function assertEquals($expected, $actual)
+    {
+        if ($actual != $expected) {
+            throw new Exception("Expected {$actual} to equal {$expected}.");
+        }
+    }
+
+    public function assertNull($actual)
+    {
+        if (!is_null($actual)) {
+            throw new Exception("Expected null.");
+        }
+    }
+
+    public function assertNotNull($actual)
+    {
+        if (is_null($actual)) {
+            throw new Exception("Expected not null.");
+        }
+    }
+
+    public function assertTrue($expected)
+    {
+        if ($expected !== true) {
+            throw new Exception("Expected true");
+        }
+    }
+
+    public function assertFalse($expected)
+    {
+        if ($expected !== false) {
+            throw new Exception("Expected false");
+        }
+    }
+
+    public function assertType($expectedType, $value)
+    {
+        $validType = false;
+
+        switch ($expectedType) {
+            case 'array':
+                $validType = is_array($value);
+        }
+
+        if (!$validType) {
+            throw new Exception("Expected type '{$expectedType}.'");
+        }
+    }
+}

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



More information about the Pkg-grass-devel mailing list