1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

gis: GEOS library has been re-factored to, changes include:

(1) allow write-access to Geometries (for future lazy-geometries)
 (2) Point, LineString, LinearRing have their own constructors (e.g., p = Point(5, 17))
 (3) improved ctypes memory management (as part of writability enhancements)
 (4) improved tests and comments
 (5) numpy support


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5637 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-07-09 06:16:06 +00:00
parent c31d20858d
commit 342dbc6738
8 changed files with 922 additions and 483 deletions

View File

@ -0,0 +1,162 @@
from django.contrib.gis.geos.libgeos import lgeos
from django.contrib.gis.geos.GEOSError import GEOSException, GEOSGeometryIndexError
from ctypes import c_double, c_int, c_uint, byref
"""
This module houses the GEOSCoordSeq object, and is used internally
by GEOSGeometry to house the actual coordinates of the Point,
LineString, and LinearRing geometries.
"""
class GEOSCoordSeq(object):
"The internal representation of a list of coordinates inside a Geometry."
#### Python 'magic' routines ####
def __init__(self, ptr, z=False):
"Initializes from a GEOS pointer."
self._ptr = ptr
self._z = z
def __iter__(self):
"Iterates over each point in the coordinate sequence."
for i in xrange(self.size):
yield self.__getitem__(i)
def __len__(self):
"Returns the number of points in the coordinate sequence."
return int(self.size)
def __str__(self):
"The string representation of the coordinate sequence."
return str(self.tuple)
def __getitem__(self, index):
"Can use the index [] operator to get coordinate sequence at an index."
coords = [self.getX(index), self.getY(index)]
if self.dims == 3 and self._z:
coords.append(self.getZ(index))
return tuple(coords)
def __setitem__(self, index, value):
"Can use the index [] operator to set coordinate sequence at an index."
if self.dims == 3 and self._z:
n_args = 3
set_3d = True
else:
n_args = 2
set_3d = False
if len(value) != n_args:
raise TypeError, 'Dimension of value does not match.'
self.setX(index, value[0])
self.setY(index, value[1])
if set_3d: self.setZ(index, value[2])
#### Internal Routines ####
def _checkindex(self, index):
"Checks the index."
sz = self.size
if (sz < 1) or (index < 0) or (index >= sz):
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
def _checkdim(self, dim):
"Checks the given dimension."
if dim < 0 or dim > 2:
raise GEOSException, 'invalid ordinate dimension "%d"' % dim
#### Ordinate getting and setting routines ####
def getOrdinate(self, dimension, index):
"Gets the value for the given dimension and index."
self._checkindex(index)
self._checkdim(dimension)
# Wrapping the dimension, index
dim = c_uint(dimension)
idx = c_uint(index)
# 'd' is the value of the point, passed in by reference
d = c_double()
status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr(), idx, dim, byref(d))
if status == 0:
raise GEOSException, 'could not retrieve %th ordinate at index: %s' % (str(dimension), str(index))
return d.value
def setOrdinate(self, dimension, index, value):
"Sets the value for the given dimension and index."
self._checkindex(index)
self._checkdim(dimension)
# Wrapping the dimension, index
dim = c_uint(dimension)
idx = c_uint(index)
# Setting the ordinate
status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr(), idx, dim, c_double(value))
if status == 0:
raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index)
def getX(self, index):
"Get the X value at the index."
return self.getOrdinate(0, index)
def setX(self, index, value):
"Set X with the value at the given index."
self.setOrdinate(0, index, value)
def getY(self, index):
"Get the Y value at the given index."
return self.getOrdinate(1, index)
def setY(self, index, value):
"Set Y with the value at the given index."
self.setOrdinate(1, index, value)
def getZ(self, index):
"Get Z with the value at the given index."
return self.getOrdinate(2, index)
def setZ(self, index, value):
"Set Z with the value at the given index."
self.setOrdinate(2, index, value)
### Dimensions ###
@property
def size(self):
"Returns the size of this coordinate sequence."
n = c_uint(0)
status = lgeos.GEOSCoordSeq_getSize(self._ptr(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq size.'
return n.value
@property
def dims(self):
"Returns the dimensions of this coordinate sequence."
n = c_uint(0)
status = lgeos.GEOSCoordSeq_getDimensions(self._ptr(), byref(n))
if status == 0:
raise GEOSException, 'Could not get CoordSeq dimensions.'
return n.value
@property
def hasz(self):
"Inherits this from the parent geometry."
return self._z
### Other Methods ###
@property
def clone(self):
"Clones this coordinate sequence."
pass
@property
def tuple(self):
"Returns a tuple version of this coordinate sequence."
n = self.size
if n == 1:
return self.__getitem__(0)
else:
return tuple(self.__getitem__(i) for i in xrange(n))
# ctypes prototype for the Coordinate Sequence creation factory
create_cs = lgeos.GEOSCoordSeq_create
create_cs.argtypes = [c_uint, c_uint]

View File

@ -0,0 +1,15 @@
class GEOSException(Exception):
"The base GEOS exception, indicates a GEOS-related error."
pass
class GEOSGeometryIndexError(GEOSException, KeyError):
"""This exception is raised when an invalid index is encountered, and has
the 'silent_variable_feature' attribute set to true. This ensures that
django's templates proceed to use the next lookup type gracefully when
an Exception is raised. Fixes ticket #4740.
"""
# "If, during the method lookup, a method raises an exception, the exception
# will be propagated, unless the exception has an attribute silent_variable_failure
# whose value is True." -- django template docs.
silent_variable_failure = True

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
Copyright (c) 2007, Justin Bronn
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of GEOSGeometry nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,4 +1,36 @@
from GEOSGeometry import GEOSGeometry, GEOSException
"""
The goal of this module is to be a ctypes wrapper around the GEOS library
that will work on both *NIX and Windows systems. Specifically, this uses
the GEOS C api.
I have several motivations for doing this:
(1) The GEOS SWIG wrapper is no longer maintained, and requires the
installation of SWIG.
(2) The PCL implementation is over 2K+ lines of C and would make
PCL a requisite package for the GeoDjango application stack.
(3) Windows and Mac compatibility becomes substantially easier, and does not
require the additional compilation of PCL or GEOS and SWIG -- all that
is needed is a Win32 or Mac compiled GEOS C library (dll or dylib)
in a location that Python can read (e.g. 'C:\Python25').
In summary, I wanted to wrap GEOS in a more maintainable and portable way using
only Python and the excellent ctypes library (now standard in Python 2.5).
In the spirit of loose coupling, this library does not require Django or
GeoDjango. Only the GEOS C library and ctypes are needed for the platform
of your choice.
For more information about GEOS:
http://geos.refractions.net
For more info about PCL and the discontinuation of the Python GEOS
library see Sean Gillies' writeup (and subsequent update) at:
http://zcologia.com/news/150/geometries-for-python/
http://zcologia.com/news/429/geometries-for-python-update/
"""
from GEOSGeometry import GEOSGeometry, Point, LineString, LinearRing, HAS_NUMPY
from GEOSError import GEOSException
def hex_to_wkt(hex):
"Converts HEXEWKB into WKT."

View File

@ -0,0 +1,106 @@
"""
This module houses the ctypes initialization procedures, as well
as the notice and error handler function callbacks (get called
when an error occurs in GEOS).
"""
from django.contrib.gis.geos.GEOSError import GEOSException
from ctypes import \
c_char_p, c_int, c_size_t, c_ubyte, pointer, addressof, \
CDLL, CFUNCTYPE, POINTER, Structure
import os, sys
# NumPy supported?
try:
from numpy import array, ndarray
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
# Setting the appropriate name for the GEOS-C library, depending on which
# OS and POSIX platform we're running.
if os.name == 'nt':
# Windows NT library
lib_name = 'libgeos_c-1.dll'
elif os.name == 'posix':
platform = os.uname()[0] # Using os.uname()
if platform in ('Linux', 'SunOS'):
# Linux or Solaris shared library
lib_name = 'libgeos_c.so'
elif platform == 'Darwin':
# Mac OSX Shared Library (Thanks Matt!)
lib_name = 'libgeos_c.dylib'
else:
raise GEOSException, 'Unknown POSIX platform "%s"' % platform
else:
raise GEOSException, 'Unsupported OS "%s"' % os.name
# Getting the GEOS C library. The C interface (CDLL) is used for
# both *NIX and Windows.
# See the GEOS C API source code for more details on the library function calls:
# http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
lgeos = CDLL(lib_name)
# The notice and error handler C function callback definitions.
# Supposed to mimic the GEOS message handler (C below):
# "typedef void (*GEOSMessageHandler)(const char *fmt, ...);"
NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
def notice_h(fmt, list, output_h=sys.stdout):
output_h.write('GEOS_NOTICE: %s\n' % (fmt % list))
notice_h = NOTICEFUNC(notice_h)
ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
def error_h(fmt, lst, output_h=sys.stderr):
try:
err_msg = fmt % lst
except:
err_msg = fmt
output_h.write('GEOS_ERROR: %s\n' % err_msg)
error_h = ERRORFUNC(error_h)
# The initGEOS routine should be called first, however, that routine takes
# the notice and error functions as parameters. Here is the C code that
# we need to wrap:
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
lgeos.initGEOS(notice_h, error_h)
class GEOSPointer(object):
"""The GEOSPointer provides a layer of abstraction to accessing the values returned by
GEOS geometry creation routines."""
### Python 'magic' routines ###
def __init__(self, ptr):
"Given a ctypes pointer(c_int)"
if isinstance(ptr, int):
self._ptr = pointer(c_int(ptr))
else:
raise TypeError, 'GEOSPointer object must initialize with an integer.'
def __call__(self):
"""If the pointer is NULL, then an exception will be raised, otherwise the
address value (a GEOSGeom_ptr) will be returned."""
if self.valid: return self.address
else: raise GEOSException, 'GEOS pointer no longer valid (was the parent geometry deleted?)'
### GEOSPointer Properties ###
@property
def address(self):
"Returns the address of the GEOSPointer (represented as an integer)."
return self._ptr.contents.value
@property
def valid(self):
"Tests whether this GEOSPointer is valid."
if bool(self.address): return True
else: return False
### GEOSPointer Methods ###
def set(self, address):
"Sets this pointer with the new address (represented as an integer)"
if not isinstance(address, int):
raise TypeError, 'GEOSPointer must be set with an address (an integer).'
self._ptr.contents = c_int(address)
def nullify(self):
"Nullify this geometry pointer (set the address to 0)."
self.set(0)

View File

@ -90,7 +90,11 @@ multipoints = (TestGeom('MULTIPOINT(10 10, 20 20 )', n_p=2, points=((10., 10.),
# LineStrings
linestrings = (TestGeom('LINESTRING (60 180, 120 100, 180 180)', n_p=3, centroid=(120, 140), tup=((60, 180), (120, 100), (180, 180))),
TestGeom('LINESTRING (0 0, 5 5, 10 5, 10 10)', n_p=4, centroid=(6.1611652351681556, 4.6966991411008934), tup=((0, 0), (5, 5), (10, 5), (10, 10)),)
TestGeom('LINESTRING (0 0, 5 5, 10 5, 10 10)', n_p=4, centroid=(6.1611652351681556, 4.6966991411008934), tup=((0, 0), (5, 5), (10, 5), (10, 10)),),
)
# Linear Rings
linearrings = (TestGeom('LINEARRING (649899.3065171393100172 4176512.3807915160432458, 649902.7294133581453934 4176512.7834989596158266, 649906.5550170192727819 4176514.3942507002502680, 649910.5820134161040187 4176516.0050024418160319, 649914.4076170771149918 4176518.0184616246260703, 649917.2264131171396002 4176519.4278986593708396, 649920.0452871860470623 4176521.6427505780011415, 649922.0587463703704998 4176522.8507948759943247, 649924.2735982896992937 4176524.4616246484220028, 649926.2870574744883925 4176525.4683542405255139, 649927.8978092158213258 4176526.8777912775985897, 649929.3072462501004338 4176528.0858355751261115, 649930.1126611357321963 4176529.4952726080082357, 649927.4951798024121672 4176506.9444361114874482, 649899.3065171393100172 4176512.3807915160432458)', n_p=15),
)
# MultiLineStrings
@ -128,5 +132,12 @@ relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),
TestGeom('MULTILINESTRING((90 20, 170 100, 170 140), (130 140, 130 60, 90 20, 20 90, 90 20))'),
'FF10F0102', True,),
)
#((TestGeom('POLYGON ((545 317, 617 379, 581 321, 545 317))')),
# (TestGeom('POLYGON ((484 290, 558 359, 543 309, 484 290))'))),
buffer_geoms = ( (TestGeom('POINT(0 0)'),
TestGeom('POLYGON ((5 0,4.903926402016153 -0.97545161008064,4.619397662556435 -1.913417161825447,4.157348061512728 -2.777851165098009,3.53553390593274 -3.535533905932735,2.777851165098015 -4.157348061512724,1.913417161825454 -4.619397662556431,0.975451610080648 -4.903926402016151,0.000000000000008 -5.0,-0.975451610080632 -4.903926402016154,-1.913417161825439 -4.619397662556437,-2.777851165098002 -4.157348061512732,-3.53553390593273 -3.535533905932746,-4.157348061512719 -2.777851165098022,-4.619397662556429 -1.913417161825462,-4.903926402016149 -0.975451610080656,-5.0 -0.000000000000016,-4.903926402016156 0.975451610080624,-4.619397662556441 1.913417161825432,-4.157348061512737 2.777851165097995,-3.535533905932752 3.535533905932723,-2.777851165098029 4.157348061512714,-1.913417161825468 4.619397662556426,-0.975451610080661 4.903926402016149,-0.000000000000019 5.0,0.975451610080624 4.903926402016156,1.913417161825434 4.61939766255644,2.777851165097998 4.157348061512735,3.535533905932727 3.535533905932748,4.157348061512719 2.777851165098022,4.619397662556429 1.91341716182546,4.90392640201615 0.975451610080652,5 0))'),
5.0, 8),
(TestGeom('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'),
TestGeom('POLYGON ((-2 0,-2 10,-1.961570560806461 10.390180644032258,-1.847759065022573 10.765366864730179,-1.662939224605091 11.111140466039204,-1.414213562373095 11.414213562373096,-1.111140466039204 11.662939224605092,-0.765366864730179 11.847759065022574,-0.390180644032256 11.961570560806461,0 12,10 12,10.390180644032256 11.961570560806461,10.765366864730179 11.847759065022574,11.111140466039204 11.66293922460509,11.414213562373096 11.414213562373096,11.66293922460509 11.111140466039204,11.847759065022574 10.765366864730179,11.961570560806461 10.390180644032256,12 10,12 0,11.961570560806461 -0.390180644032256,11.847759065022574 -0.76536686473018,11.66293922460509 -1.111140466039204,11.414213562373096 -1.414213562373095,11.111140466039204 -1.66293922460509,10.765366864730179 -1.847759065022573,10.390180644032256 -1.961570560806461,10 -2,0.0 -2.0,-0.390180644032255 -1.961570560806461,-0.765366864730177 -1.847759065022575,-1.1111404660392 -1.662939224605093,-1.41421356237309 -1.4142135623731,-1.662939224605086 -1.111140466039211,-1.84775906502257 -0.765366864730189,-1.961570560806459 -0.390180644032268,-2 0))'),
2.0, 8),
)

View File

@ -1,39 +1,39 @@
import unittest
from copy import copy
from django.contrib.gis.geos import GEOSGeometry, GEOSException
from django.contrib.gis.geos import GEOSGeometry, GEOSException, Point, LineString, LinearRing, HAS_NUMPY
from geometries import *
class GeosTest2(unittest.TestCase):
if HAS_NUMPY:
from numpy import array
def test0100_wkt(self):
class GEOSTest(unittest.TestCase):
def test01a_wkt(self):
"Testing WKT output."
for g in wkt_out:
geom = GEOSGeometry(g.wkt)
self.assertEqual(g.ewkt, geom.wkt)
def test0101_hex(self):
def test01b_hex(self):
"Testing HEX output."
for g in hex_wkt:
geom = GEOSGeometry(g.wkt)
self.assertEqual(g.hex, geom.hex)
def test0102_errors(self):
def test01c_errors(self):
"Testing the Error handlers."
print "\nBEGIN - expecting ParseError; safe to ignore.\n"
print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
for err in errors:
if err.hex:
self.assertRaises(GEOSException, GEOSGeometry, err.wkt, 'hex')
else:
self.assertRaises(GEOSException, GEOSGeometry, err.wkt)
print "\nEND - expecting ParseError; safe to ignore.\n"
print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
def test02_points(self):
def test02a_points(self):
"Testing Point objects."
prev = GEOSGeometry('POINT(0 0)')
for p in points:
# Creating the point from the WKT
pnt = GEOSGeometry(p.wkt)
self.assertEqual(pnt.geom_type, 'Point')
self.assertEqual(pnt.geom_typeid, 0)
@ -41,22 +41,39 @@ class GeosTest2(unittest.TestCase):
self.assertEqual(p.y, pnt.y)
self.assertEqual(True, pnt == GEOSGeometry(p.wkt))
self.assertEqual(False, pnt == prev)
prev = pnt
# Making sure that the point's X, Y components are what we expect
self.assertAlmostEqual(p.x, pnt.tuple[0], 9)
self.assertAlmostEqual(p.y, pnt.tuple[1], 9)
# Testing the third dimension, and getting the tuple arguments
if hasattr(p, 'z'):
self.assertEqual(True, pnt.hasz)
self.assertEqual(p.z, pnt.z)
self.assertEqual(p.z, pnt.tuple[2], 9)
tup_args = (p.x, p.y, p.z)
else:
self.assertEqual(False, pnt.hasz)
self.assertEqual(None, pnt.z)
tup_args = (p.x, p.y)
# Centroid operation on point should be point itself
self.assertEqual(p.centroid, pnt.centroid.tuple)
def test02_multipoints(self):
# Now testing the different constructors
pnt2 = Point(tup_args) # e.g., Point((1, 2))
pnt3 = Point(*tup_args) # e.g., Point(1, 2)
self.assertEqual(True, pnt == pnt2)
self.assertEqual(True, pnt == pnt3)
# Now testing setting the x and y
pnt.y = 3.14
pnt.x = 2.71
self.assertEqual(3.14, pnt.y)
self.assertEqual(2.71, pnt.x)
prev = pnt # setting the previous geometry
def test02b_multipoints(self):
"Testing MultiPoint objects."
for mp in multipoints:
mpnt = GEOSGeometry(mp.wkt)
@ -73,81 +90,38 @@ class GeosTest2(unittest.TestCase):
self.assertEqual(p.geom_typeid, 0)
self.assertEqual(p.empty, False)
self.assertEqual(p.valid, True)
def test03_polygons(self):
"Testing Polygon objects."
prev = GEOSGeometry('POINT(0 0)')
for p in polygons:
poly = GEOSGeometry(p.wkt)
self.assertEqual(poly.geom_type, 'Polygon')
self.assertEqual(poly.geom_typeid, 3)
self.assertEqual(poly.empty, False)
self.assertEqual(poly.ring, False)
self.assertEqual(p.n_i, poly.num_interior_rings)
self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__
self.assertEqual(p.n_p, poly.num_points)
# Area & Centroid
self.assertAlmostEqual(p.area, poly.area, 9)
self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9)
self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
# Testing the geometry equivalence
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
self.assertEqual(False, poly == prev)
prev = poly
# Testing the exterior ring
ring = poly.exterior_ring
self.assertEqual(ring.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2)
if p.ext_ring_cs:
self.assertEqual(p.ext_ring_cs, ring.tuple)
self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__
def test03_multipolygons(self):
"Testing MultiPolygon objects."
prev = GEOSGeometry('POINT (0 0)')
for mp in multipolygons:
mpoly = GEOSGeometry(mp.wkt)
self.assertEqual(mpoly.geom_type, 'MultiPolygon')
self.assertEqual(mpoly.geom_typeid, 6)
self.assertEqual(mp.valid, mpoly.valid)
if mp.valid:
self.assertEqual(mp.num_geom, mpoly.num_geom)
self.assertEqual(mp.n_p, mpoly.num_coords)
self.assertEqual(mp.num_geom, len(mpoly))
for p in mpoly:
self.assertEqual(p.geom_type, 'Polygon')
self.assertEqual(p.geom_typeid, 3)
self.assertEqual(p.valid, True)
def test04_linestring(self):
def test03a_linestring(self):
"Testing LineString objects."
prev = GEOSGeometry('POINT(0 0)')
for l in linestrings:
ls = GEOSGeometry(l.wkt)
self.assertEqual(ls.geom_type, 'LineString')
self.assertEqual(ls.geom_typeid, 1)
self.assertEqual(ls.empty, False)
self.assertEqual(ls.ring, False)
self.assertEqual(l.centroid, ls.centroid.tuple)
if hasattr(l, 'centroid'):
self.assertEqual(l.centroid, ls.centroid.tuple)
if hasattr(l, 'tup'):
self.assertEqual(l.tup, ls.tuple)
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
self.assertEqual(False, ls == prev)
prev = ls
def test04_multilinestring(self):
# Creating a LineString from a tuple, list, and numpy array
ls2 = LineString(ls.tuple)
self.assertEqual(ls, ls2)
ls3 = LineString([list(tup) for tup in ls.tuple])
self.assertEqual(ls, ls3)
if HAS_NUMPY:
ls4 = LineString(array(ls.tuple))
self.assertEqual(ls, ls4)
def test03b_multilinestring(self):
"Testing MultiLineString objects."
prev = GEOSGeometry('POINT(0 0)')
for l in multilinestrings:
ml = GEOSGeometry(l.wkt)
self.assertEqual(ml.geom_type, 'MultiLineString')
@ -165,14 +139,152 @@ class GeosTest2(unittest.TestCase):
self.assertEqual(ls.geom_typeid, 1)
self.assertEqual(ls.empty, False)
#def test05_linearring(self):
# "Testing LinearRing objects."
# pass
def test04a_linearring(self):
"Testing LinearRing objects."
for rr in linearrings:
lr = GEOSGeometry(rr.wkt)
self.assertEqual(lr.geom_type, 'LinearRing')
self.assertEqual(lr.geom_typeid, 2)
self.assertEqual(rr.n_p, len(lr))
self.assertEqual(True, lr.valid)
self.assertEqual(False, lr.empty)
# Creating a LinearRing from a tuple, list, and numpy array
lr2 = LinearRing(lr.tuple)
self.assertEqual(lr, lr2)
lr3 = LinearRing([list(tup) for tup in lr.tuple])
self.assertEqual(lr, lr3)
if HAS_NUMPY:
lr4 = LineString(array(lr.tuple))
self.assertEqual(lr, lr4)
def test05a_polygons(self):
"Testing Polygon objects."
prev = GEOSGeometry('POINT(0 0)')
for p in polygons:
# Creating the Polygon, testing its properties.
poly = GEOSGeometry(p.wkt)
self.assertEqual(poly.geom_type, 'Polygon')
self.assertEqual(poly.geom_typeid, 3)
self.assertEqual(poly.empty, False)
self.assertEqual(poly.ring, False)
self.assertEqual(p.n_i, poly.num_interior_rings)
self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__
self.assertEqual(p.n_p, poly.num_points)
# Area & Centroid
self.assertAlmostEqual(p.area, poly.area, 9)
self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9)
self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
# Testing the geometry equivalence
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
# Testing the exterior ring
ring = poly.exterior_ring
self.assertEqual(ring.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2)
if p.ext_ring_cs:
self.assertEqual(p.ext_ring_cs, ring.tuple)
self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__
# Testing __iter__
for r in poly:
self.assertEqual(ring.geom_type, 'LinearRing')
self.assertEqual(ring.geom_typeid, 2)
# Setting the second point of the first ring (which should set the
# first point of the polygon).
prev = poly.clone() # Using clone() to get a copy of the current polygon
self.assertEqual(True, poly == prev) # They clone should be equal to the first
newval = (poly[0][1][0] + 5.0, poly[0][1][1] + 5.0) # really testing __getitem__ ([ring][point][tuple])
try:
poly[0][1] = ('cannot assign with', 'string values')
except TypeError:
pass
poly[0][1] = newval # setting the second point in the polygon with the newvalue (based on the old)
self.assertEqual(newval, poly[0][1]) # The point in the polygon should be the
self.assertEqual(False, poly == prev) # Even different from the clone we just made
def test05b_multipolygons(self):
"Testing MultiPolygon objects."
print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
prev = GEOSGeometry('POINT (0 0)')
for mp in multipolygons:
mpoly = GEOSGeometry(mp.wkt)
self.assertEqual(mpoly.geom_type, 'MultiPolygon')
self.assertEqual(mpoly.geom_typeid, 6)
self.assertEqual(mp.valid, mpoly.valid)
if mp.valid:
self.assertEqual(mp.num_geom, mpoly.num_geom)
self.assertEqual(mp.n_p, mpoly.num_coords)
self.assertEqual(mp.num_geom, len(mpoly))
for p in mpoly:
self.assertEqual(p.geom_type, 'Polygon')
self.assertEqual(p.geom_typeid, 3)
self.assertEqual(p.valid, True)
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
def test06_memory_hijinks(self):
"Testing Geometry __del__() in different scenarios"
#### Memory issues with rings and polygons
# These tests are needed to ensure sanity with writable geometries.
# Getting a polygon with interior rings, and pulling out the interior rings
poly = GEOSGeometry(polygons[1].wkt)
ring1 = poly[0]
ring2 = poly[1]
# These deletes should be 'harmless' since they are done on child geometries
del ring1
del ring2
ring1 = poly[0]
ring2 = poly[1]
# Deleting the polygon
del poly
# Ensuring that trying to access the deleted memory (by getting the string
# representation of the ring of a deleted polygon) raises a GEOSException
# instead of something worse..
self.assertRaises(GEOSException, str, ring1)
self.assertRaises(GEOSException, str, ring2)
#### Memory issues with geometries from Geometry Collections
mp = GEOSGeometry('MULTIPOINT(85 715, 235 1400, 4620 1711)')
# Getting the points
pts = [p for p in mp]
# More 'harmless' child geometry deletes
for p in pts: del p
# Cloning for comparisons
clones = [p.clone() for p in pts]
for i in xrange(len(clones)):
# Testing equivalence before & after modification
self.assertEqual(True, pts[i] == clones[i]) # before
pts[i].x = 3.14159
pts[i].y = 2.71828
self.assertEqual(False, pts[i] == clones[i]) # after
self.assertEqual(3.14159, mp[i].x) # parent x,y should be modified
self.assertEqual(2.71828, mp[i].y)
# Should raise GEOSException when trying to get geometries from the multipoint
# after it has been deleted.
del mp
for p in pts:
self.assertRaises(GEOSException, str, p)
def test08_coord_seq(self):
"Testing Coordinate Sequence objects."
for p in polygons:
if p.ext_ring_cs:
# Constructing the polygon and getting the coordinate sequence
poly = GEOSGeometry(p.wkt)
cs = poly.exterior_ring.coord_seq
@ -181,15 +293,19 @@ class GeosTest2(unittest.TestCase):
# Checks __getitem__ and __setitem__
for i in xrange(len(p.ext_ring_cs)):
c1 = p.ext_ring_cs[i]
c2 = cs[i]
c1 = p.ext_ring_cs[i] # Expected value
c2 = cs[i] # Value from coordseq
self.assertEqual(c1, c2)
# Constructing the test value to set the coordinate sequence with
if len(c1) == 2: tset = (5, 23)
else: tset = (5, 23, 8)
cs[i] = tset
# Making sure every set point matches what we expect
for j in range(len(tset)):
cs[i] = tset
self.assertEqual(tset, cs[i])
self.assertEqual(tset[j], cs[i][j])
def test09_relate_pattern(self):
"Testing relate() and relate_pattern()."
@ -208,7 +324,6 @@ class GeosTest2(unittest.TestCase):
def test10_intersection(self):
"Testing intersects() and intersection()."
for i in xrange(len(topology_geoms)):
g_tup = topology_geoms[i]
a = GEOSGeometry(g_tup[0].wkt)
@ -239,10 +354,36 @@ class GeosTest2(unittest.TestCase):
d2 = a.difference(b)
self.assertEqual(d1, d2)
def test13_buffer(self):
"Testing buffer()."
for i in xrange(len(buffer_geoms)):
g_tup = buffer_geoms[i]
g = GEOSGeometry(g_tup[0].wkt)
# The buffer we expect
exp_buf = GEOSGeometry(g_tup[1].wkt)
# Can't use a floating-point for the number of quadsegs.
self.assertRaises(TypeError, g.buffer, g_tup[2], float(g_tup[3]))
# Constructing our buffer
buf = g.buffer(g_tup[2], g_tup[3])
self.assertEqual(exp_buf.num_coords, buf.num_coords)
self.assertEqual(len(exp_buf), len(buf))
# Now assuring that each point in the buffer is almost equal
for j in xrange(len(exp_buf)):
exp_ring = exp_buf[j]
buf_ring = buf[j]
self.assertEqual(len(exp_ring), len(buf_ring))
for k in xrange(len(exp_ring)):
# Asserting the X, Y of each point are almost equal (due to floating point imprecision)
self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9)
self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GeosTest2))
s.addTest(unittest.makeSuite(GEOSTest))
return s
def run(verbosity=2):