1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

gis: geos: Added GeoJSON input/output support for GEOSGeometry (requires GDAL 1.5+); Polygons may now be instantiated with tuples of coordinates instead of only LinearRing instances; added extent property; added coords property alias for tuple (and added tuple for GeometryCollection); made small optimizations to KML and coordinate array generators. Small GDAL protochange that didn't make last commit: get_envelope is now generated w/the previously unused env_func.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7114 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-02-14 16:33:16 +00:00
parent 6a692e8031
commit ccf9baca86
6 changed files with 150 additions and 68 deletions

View File

@ -105,7 +105,5 @@ geom_transform = void_output(lgdal.OGR_G_Transform, [c_void_p, c_void_p])
geom_transform_to = void_output(lgdal.OGR_G_TransformTo, [c_void_p, c_void_p]) geom_transform_to = void_output(lgdal.OGR_G_TransformTo, [c_void_p, c_void_p])
# For retrieving the envelope of the geometry. # For retrieving the envelope of the geometry.
get_envelope = lgdal.OGR_G_GetEnvelope get_envelope = env_func(lgdal.OGR_G_GetEnvelope, [c_void_p, POINTER(OGREnvelope)])
get_envelope.restype = None
get_envelope.argtypes = [c_void_p, POINTER(OGREnvelope)]
get_envelope.errcheck = check_envelope

View File

@ -5,7 +5,7 @@
# Python, ctypes and types dependencies. # Python, ctypes and types dependencies.
import re import re
from ctypes import addressof, byref, c_double, c_size_t from ctypes import addressof, byref, c_double, c_size_t
from types import StringType, UnicodeType, IntType, FloatType, BufferType from types import UnicodeType
# GEOS-related dependencies. # GEOS-related dependencies.
from django.contrib.gis.geos.coordseq import GEOSCoordSeq from django.contrib.gis.geos.coordseq import GEOSCoordSeq
@ -20,16 +20,17 @@ from django.contrib.gis.geos.prototypes import *
# Trying to import GDAL libraries, if available. Have to place in # Trying to import GDAL libraries, if available. Have to place in
# try/except since this package may be used outside GeoDjango. # try/except since this package may be used outside GeoDjango.
try: try:
from django.contrib.gis.gdal import OGRGeometry, SpatialReference from django.contrib.gis.gdal import OGRGeometry, SpatialReference, GEOJSON
HAS_GDAL=True HAS_GDAL = True
except: except:
HAS_GDAL=False HAS_GDAL, GEOJSON = False, False
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure # Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
# to prevent potentially malicious input from reaching the underlying C # to prevent potentially malicious input from reaching the underlying C
# library. Not a substitute for good web security programming practices. # library. Not a substitute for good web security programming practices.
hex_regex = re.compile(r'^[0-9A-F]+$', re.I) hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I) wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I)
json_regex = re.compile(r'^\{.+\}$')
class GEOSGeometry(object): class GEOSGeometry(object):
"A class that, generally, encapsulates a GEOS geometry." "A class that, generally, encapsulates a GEOS geometry."
@ -50,24 +51,29 @@ class GEOSGeometry(object):
The `srid` keyword is used to specify the Source Reference Identifier The `srid` keyword is used to specify the Source Reference Identifier
(SRID) number for this Geometry. If not set, the SRID will be None. (SRID) number for this Geometry. If not set, the SRID will be None.
""" """
if isinstance(geo_input, basestring):
if isinstance(geo_input, UnicodeType): if isinstance(geo_input, UnicodeType):
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more. # Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
geo_input = geo_input.encode('ascii') geo_input = geo_input.encode('ascii')
if isinstance(geo_input, StringType):
if hex_regex.match(geo_input): wkt_m = wkt_regex.match(geo_input)
# If the regex matches, the geometry is in HEX form. if wkt_m:
# Handling WKT input.
if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
g = from_wkt(wkt_m.group('wkt'))
elif hex_regex.match(geo_input):
# Handling HEXEWKB input.
g = from_hex(geo_input, len(geo_input)) g = from_hex(geo_input, len(geo_input))
else: elif GEOJSON and json_regex.match(geo_input):
m = wkt_regex.match(geo_input) # Handling GeoJSON input.
if m: wkb_input = str(OGRGeometry(geo_input).wkb)
if m.group('srid'): srid = int(m.group('srid')) g = from_wkb(wkb_input, len(wkb_input))
g = from_wkt(m.group('wkt'))
else: else:
raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.') raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
elif isinstance(geo_input, GEOM_PTR): elif isinstance(geo_input, GEOM_PTR):
# When the input is a pointer to a geomtry (GEOM_PTR). # When the input is a pointer to a geomtry (GEOM_PTR).
g = geo_input g = geo_input
elif isinstance(geo_input, BufferType): elif isinstance(geo_input, buffer):
# When the input is a buffer (WKB). # When the input is a buffer (WKB).
wkb_input = str(geo_input) wkb_input = str(geo_input)
g = from_wkb(wkb_input, len(wkb_input)) g = from_wkb(wkb_input, len(wkb_input))
@ -191,6 +197,7 @@ class GEOSGeometry(object):
@property @property
def coord_seq(self): def coord_seq(self):
"Returns a clone of the coordinate sequence for this Geometry." "Returns a clone of the coordinate sequence for this Geometry."
if self.has_cs:
return self._cs.clone() return self._cs.clone()
#### Geometry Info #### #### Geometry Info ####
@ -307,7 +314,7 @@ class GEOSGeometry(object):
Returns true if the elements in the DE-9IM intersection matrix for the Returns true if the elements in the DE-9IM intersection matrix for the
two Geometries match the elements in pattern. two Geometries match the elements in pattern.
""" """
if not isinstance(pattern, StringType) or len(pattern) > 9: if not isinstance(pattern, str) or len(pattern) > 9:
raise GEOSException('invalid intersection matrix pattern') raise GEOSException('invalid intersection matrix pattern')
return geos_relatepattern(self.ptr, other.ptr, pattern) return geos_relatepattern(self.ptr, other.ptr, pattern)
@ -360,6 +367,15 @@ class GEOSGeometry(object):
# str(self.wkb).encode('hex') # str(self.wkb).encode('hex')
return to_hex(self.ptr, byref(c_size_t())) return to_hex(self.ptr, byref(c_size_t()))
@property
def json(self):
"""
Returns GeoJSON representation of this Geometry if GDAL 1.5+
is installed.
"""
if GEOJSON: return self.ogr.json
geojson = json
@property @property
def wkb(self): def wkb(self):
"Returns the WKB of the Geometry as a buffer." "Returns the WKB of the Geometry as a buffer."
@ -521,6 +537,21 @@ class GEOSGeometry(object):
raise TypeError('distance() works only on other GEOS Geometries.') raise TypeError('distance() works only on other GEOS Geometries.')
return geos_distance(self.ptr, other.ptr, byref(c_double())) return geos_distance(self.ptr, other.ptr, byref(c_double()))
@property
def extent(self):
"""
Returns the extent of this geometry as a 4-tuple, consisting of
(xmin, ymin, xmax, ymax).
"""
env = self.envelope
if isinstance(env, Point):
xmin, ymin = env.tuple
xmax, ymax = xmin, ymin
else:
xmin, ymin = env[0][0]
xmax, ymax = env[0][2]
return (xmin, ymin, xmax, ymax)
@property @property
def length(self): def length(self):
""" """

View File

@ -85,9 +85,13 @@ class GeometryCollection(GEOSGeometry):
@property @property
def kml(self): def kml(self):
"Returns the KML for this Geometry Collection." "Returns the KML for this Geometry Collection."
kml = '<MultiGeometry>' return '<MultiGeometry>%s</MultiGeometry>' % ''.join([g.kml for g in self])
for g in self: kml += g.kml
return kml + '</MultiGeometry>' @property
def tuple(self):
"Returns a tuple of all the coordinates in this Geometry Collection"
return tuple([g.tuple for g in self])
coords = tuple
# MultiPoint, MultiLineString, and MultiPolygon class definitions. # MultiPoint, MultiLineString, and MultiPolygon class definitions.
class MultiPoint(GeometryCollection): class MultiPoint(GeometryCollection):

View File

@ -153,14 +153,12 @@ class GEOSCoordSeq(object):
# a Z dimension. # a Z dimension.
if self.hasz: substr = '%s,%s,%s ' if self.hasz: substr = '%s,%s,%s '
else: substr = '%s,%s,0 ' else: substr = '%s,%s,0 '
kml = '<coordinates>' return '<coordinates>%s</coordinates>' % \
for i in xrange(len(self)): ''.join([substr % self[i] for i in xrange(len(self))]).strip()
kml += substr % self[i]
return kml.strip() + '</coordinates>'
@property @property
def tuple(self): def tuple(self):
"Returns a tuple version of this coordinate sequence." "Returns a tuple version of this coordinate sequence."
n = self.size n = self.size
if n == 1: return self[0] if n == 1: return self[0]
else: return tuple(self[i] for i in xrange(n)) else: return tuple([self[i] for i in xrange(n)])

View File

@ -4,7 +4,6 @@
GEOSGeometry. GEOSGeometry.
""" """
from ctypes import c_uint, byref from ctypes import c_uint, byref
from types import FloatType, IntType, ListType, TupleType
from django.contrib.gis.geos.base import GEOSGeometry from django.contrib.gis.geos.base import GEOSGeometry
from django.contrib.gis.geos.coordseq import GEOSCoordSeq from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
@ -24,15 +23,15 @@ class Point(GEOSGeometry):
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
""" """
if isinstance(x, (TupleType, ListType)): if isinstance(x, (tuple, list)):
# Here a tuple or list was passed in under the `x` parameter. # Here a tuple or list was passed in under the `x` parameter.
ndim = len(x) ndim = len(x)
if ndim < 2 or ndim > 3: if ndim < 2 or ndim > 3:
raise TypeError('Invalid sequence parameter: %s' % str(x)) raise TypeError('Invalid sequence parameter: %s' % str(x))
coords = x coords = x
elif isinstance(x, (IntType, FloatType)) and isinstance(y, (IntType, FloatType)): elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)):
# Here X, Y, and (optionally) Z were passed in individually, as parameters. # Here X, Y, and (optionally) Z were passed in individually, as parameters.
if isinstance(z, (IntType, FloatType)): if isinstance(z, (int, float, long)):
ndim = 3 ndim = 3
coords = [x, y, z] coords = [x, y, z]
else: else:
@ -103,7 +102,7 @@ class Point(GEOSGeometry):
# The tuple and coords properties # The tuple and coords properties
tuple = property(get_coords, set_coords) tuple = property(get_coords, set_coords)
coords = property(get_coords, set_coords) coords = tuple
class LineString(GEOSGeometry): class LineString(GEOSGeometry):
@ -124,7 +123,7 @@ class LineString(GEOSGeometry):
if len(args) == 1: coords = args[0] if len(args) == 1: coords = args[0]
else: coords = args else: coords = args
if isinstance(coords, (TupleType, ListType)): if isinstance(coords, (tuple, list)):
# Getting the number of coords and the number of dimensions -- which # Getting the number of coords and the number of dimensions -- which
# must stay the same, e.g., no LineString((1, 2), (1, 2, 3)). # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
ncoords = len(coords) ncoords = len(coords)
@ -133,7 +132,7 @@ class LineString(GEOSGeometry):
self._checkdim(ndim) self._checkdim(ndim)
# Incrementing through each of the coordinates and verifying # Incrementing through each of the coordinates and verifying
for i in xrange(1, ncoords): for i in xrange(1, ncoords):
if not isinstance(coords[i], (TupleType, ListType, Point)): if not isinstance(coords[i], (tuple, list, Point)):
raise TypeError('each coordinate should be a sequence (list or tuple)') raise TypeError('each coordinate should be a sequence (list or tuple)')
if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.') if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.')
numpy_coords = False numpy_coords = False
@ -193,6 +192,7 @@ class LineString(GEOSGeometry):
def tuple(self): def tuple(self):
"Returns a tuple version of the geometry from the coordinate sequence." "Returns a tuple version of the geometry from the coordinate sequence."
return self._cs.tuple return self._cs.tuple
coords = tuple
def _listarr(self, func): def _listarr(self, func):
""" """
@ -236,12 +236,17 @@ class Polygon(GEOSGeometry):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
Initializes on an exterior ring and a sequence of holes (both Initializes on an exterior ring and a sequence of holes (both
instances of LinearRings. instances may be either LinearRing instances, or a tuple/list
that may be constructed into a LinearRing).
Below are some examples of initialization, where shell, hole1, and Examples of initialization, where shell, hole1, and hole2 are
hole2 are valid LinearRing geometries: valid LinearRing geometries:
>>> poly = Polygon(shell, hole1, hole2) >>> poly = Polygon(shell, hole1, hole2)
>>> poly = Polygon(shell, (hole1, hole2)) >>> poly = Polygon(shell, (hole1, hole2))
Example where a tuple parameters are used:
>>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
""" """
if not args: if not args:
raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.') raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.')
@ -249,32 +254,38 @@ class Polygon(GEOSGeometry):
# Getting the ext_ring and init_holes parameters from the argument list # Getting the ext_ring and init_holes parameters from the argument list
ext_ring = args[0] ext_ring = args[0]
init_holes = args[1:] init_holes = args[1:]
if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)): n_holes = len(init_holes)
# If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \
(len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)):
init_holes = init_holes[0] init_holes = init_holes[0]
n_holes = len(init_holes)
# Ensuring the exterior ring parameter is a LinearRing object # Ensuring the exterior ring and holes parameters are LinearRing objects
if not isinstance(ext_ring, LinearRing): # or may be instantiated into LinearRings.
raise TypeError('First argument for Polygon initialization must be a LinearRing.') ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.')
holes_list = [] # Create new list, cause init_holes is a tuple.
for i in xrange(n_holes):
holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'))
# Making sure all of the holes are LinearRing objects # Why another loop? Because if a TypeError is raised, cloned pointers will
if False in [isinstance(hole, LinearRing) for hole in init_holes]: # be around that can't be cleaned up.
raise TypeError('Holes parameter must be a sequence of LinearRings.') holes = get_pointer_arr(n_holes)
for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr)
# Getting the holes array. # Getting the shell pointer address.
nholes = len(init_holes)
holes = get_pointer_arr(nholes)
for i in xrange(nholes): holes[i] = geom_clone(init_holes[i].ptr)
# Getting the shell pointer address,
shell = geom_clone(ext_ring.ptr) shell = geom_clone(ext_ring.ptr)
# Calling with the GEOS createPolygon factory. # Calling with the GEOS createPolygon factory.
super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(nholes)), **kwargs) super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
def __getitem__(self, index): def __getitem__(self, index):
""" """
Returns the ring at the specified index. The first index, 0, will always Returns the ring at the specified index. The first index, 0, will
return the exterior ring. Indices > 0 will return the interior ring. always return the exterior ring. Indices > 0 will return the
interior ring at the given index (e.g., poly[1] and poly[2] would
return the first and second interior ring, respectively).
""" """
if index == 0: if index == 0:
return self.exterior_ring return self.exterior_ring
@ -330,6 +341,15 @@ class Polygon(GEOSGeometry):
if index < 0 or index >= len(self): if index < 0 or index >= len(self):
raise GEOSIndexError('invalid Polygon ring index: %s' % index) raise GEOSIndexError('invalid Polygon ring index: %s' % index)
def _construct_ring(self, param, msg=''):
"Helper routine for trying to construct a ring from the given parameter."
if isinstance(param, LinearRing): return param
try:
ring = LinearRing(param)
return ring
except TypeError:
raise TypeError(msg)
def get_interior_ring(self, ring_i): def get_interior_ring(self, ring_i):
""" """
Gets the interior ring at the specified index, 0 is for the first Gets the interior ring at the specified index, 0 is for the first
@ -355,18 +375,17 @@ class Polygon(GEOSGeometry):
# properties for the exterior ring/shell # properties for the exterior ring/shell
exterior_ring = property(get_ext_ring, set_ext_ring) exterior_ring = property(get_ext_ring, set_ext_ring)
shell = property(get_ext_ring, set_ext_ring) shell = exterior_ring
@property @property
def tuple(self): def tuple(self):
"Gets the tuple for each ring in this Polygon." "Gets the tuple for each ring in this Polygon."
return tuple(self[i].tuple for i in xrange(len(self))) return tuple([self[i].tuple for i in xrange(len(self))])
coords = tuple
@property @property
def kml(self): def kml(self):
"Returns the KML representation of this Polygon." "Returns the KML representation of this Polygon."
inner_kml = '' inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml
if self.num_interior_rings > 0: for i in xrange(self.num_interior_rings)])
for i in xrange(self.num_interior_rings):
inner_kml += "<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml
return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)

View File

@ -5,7 +5,7 @@ from django.contrib.gis.geos.base import HAS_GDAL
from django.contrib.gis.tests.geometries import * from django.contrib.gis.tests.geometries import *
if HAS_NUMPY: from numpy import array if HAS_NUMPY: from numpy import array
if HAS_GDAL: from django.contrib.gis.gdal import OGRGeometry, SpatialReference, CoordTransform if HAS_GDAL: from django.contrib.gis.gdal import OGRGeometry, SpatialReference, CoordTransform, GEOJSON
class GEOSTest(unittest.TestCase): class GEOSTest(unittest.TestCase):
@ -85,7 +85,16 @@ class GEOSTest(unittest.TestCase):
self.assertEqual(srid, poly.shell.srid) self.assertEqual(srid, poly.shell.srid)
self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export
def test01i_eq(self): def test01i_json(self):
"Testing GeoJSON input/output (via GDAL)."
if not HAS_GDAL or not GEOJSON: return
for g in json_geoms:
geom = GEOSGeometry(g.wkt)
self.assertEqual(g.json, geom.json)
self.assertEqual(g.json, geom.geojson)
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
def test01j_eq(self):
"Testing equivalence with WKT." "Testing equivalence with WKT."
p = fromstr('POINT(5 23)') p = fromstr('POINT(5 23)')
self.assertEqual(p, p.wkt) self.assertEqual(p, p.wkt)
@ -268,7 +277,7 @@ class GEOSTest(unittest.TestCase):
# Testing __getitem__ and __setitem__ on invalid indices # Testing __getitem__ and __setitem__ on invalid indices
self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly)) self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly))
#self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False) self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False)
self.assertRaises(GEOSIndexError, poly.__getitem__, -1) self.assertRaises(GEOSIndexError, poly.__getitem__, -1)
# Testing __iter__ # Testing __iter__
@ -279,8 +288,16 @@ class GEOSTest(unittest.TestCase):
# Testing polygon construction. # Testing polygon construction.
self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3]) self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3])
self.assertRaises(TypeError, Polygon.__init__, 'foo') self.assertRaises(TypeError, Polygon.__init__, 'foo')
# Polygon(shell, (hole1, ... holeN))
rings = tuple(r for r in poly) rings = tuple(r for r in poly)
self.assertEqual(poly, Polygon(rings[0], rings[1:])) self.assertEqual(poly, Polygon(rings[0], rings[1:]))
# Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN)
ring_tuples = tuple(r.tuple for r in poly)
self.assertEqual(poly, Polygon(*ring_tuples))
# Constructing with tuples of LinearRings.
self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt) self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt)
self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt) self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
@ -686,12 +703,27 @@ class GEOSTest(unittest.TestCase):
t2.transform(SpatialReference('EPSG:2774')) t2.transform(SpatialReference('EPSG:2774'))
ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774)) ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774))
t3.transform(ct) t3.transform(ct)
for p in (t1, t2, t3):
prec = 3 prec = 3
for p in (t1, t2, t3):
self.assertAlmostEqual(trans.x, p.x, prec) self.assertAlmostEqual(trans.x, p.x, prec)
self.assertAlmostEqual(trans.y, p.y, prec) self.assertAlmostEqual(trans.y, p.y, prec)
def test24_extent(self):
"Testing `extent` method."
# The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50))
self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent)
pnt = Point(5.23, 17.8)
# Extent of points is just the point itself repeated.
self.assertEqual((5.23, 17.8, 5.23, 17.8), pnt.extent)
# Testing on the 'real world' Polygon.
poly = fromstr(polygons[3].wkt)
ring = poly.shell
x, y = ring.x, ring.y
xmin, ymin = min(x), min(y)
xmax, ymax = max(x), max(y)
self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GEOSTest)) s.addTest(unittest.makeSuite(GEOSTest))