mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: geos: Memory-management refactor, changes include:
(1) Moved GEOSPointer to its own module, and now tracks children geometries. (2) Fixed 'deep-indexing' issues for nested rings in GeometryCollections/MultiPolygons (e.g., mpoly[0][1] = new_ring). (3) Added simplify() -- simplifies geometries using the Douglas-Peucker algorithm. (4) Conformed docstrings to Django coding style. (5) GEOSGeometry no longer uses `parent` and `input_type` keywords. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6024 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
cb64f5375c
commit
f4203ef757
@ -33,6 +33,7 @@ from django.contrib.gis.geos.base import GEOSGeometry
|
||||
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
|
||||
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.libgeos import geos_version
|
||||
|
||||
def fromstr(wkt_or_hex, **kwargs):
|
||||
"Given a string value (wkt or hex), returns a GEOSGeometry object."
|
||||
|
@ -1,8 +1,7 @@
|
||||
"""
|
||||
This module contains the 'base' GEOSGeometry object -- all GEOS geometries
|
||||
inherit from this object.
|
||||
inherit from this object.
|
||||
"""
|
||||
|
||||
# ctypes and types dependencies.
|
||||
from ctypes import \
|
||||
byref, string_at, create_string_buffer, pointer, \
|
||||
@ -11,11 +10,10 @@ from types import StringType, UnicodeType, IntType, FloatType
|
||||
|
||||
# Python and GEOS-related dependencies.
|
||||
import re
|
||||
from warnings import warn
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY, ISQLQuote
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||
if HAS_NUMPY: from numpy import ndarray, array
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY, ISQLQuote
|
||||
from django.contrib.gis.geos.pointer import GEOSPointer, NULL_GEOM
|
||||
|
||||
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
|
||||
# to prevent potentially malicious input from reaching the underlying C
|
||||
@ -26,28 +24,23 @@ wkt_regex = re.compile(r'^(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTIL
|
||||
class GEOSGeometry(object):
|
||||
"A class that, generally, encapsulates a GEOS geometry."
|
||||
|
||||
# Initially, all geometries use a NULL pointer.
|
||||
_ptr = NULL_GEOM
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, geo_input, input_type=False, parent=None, srid=None):
|
||||
"""The constructor for GEOS geometry objects. May take the following
|
||||
strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form).
|
||||
|
||||
The `input_type` keyword has been deprecated -- geometry type is now auto-detected.
|
||||
|
||||
The `parent` keyword is for internal use only, and indicates to the garbage collector
|
||||
not to delete this geometry because it was spawned from a parent (e.g., the exterior
|
||||
ring from a polygon). Its value is the GEOSPointer of the parent geometry.
|
||||
def __init__(self, geo_input, srid=None):
|
||||
"""
|
||||
The base constructor for GEOS geometry objects, and may take the following
|
||||
string inputs: WKT and HEXEWKB (a PostGIS-specific canonical form).
|
||||
|
||||
# Initially, setting the pointer to NULL
|
||||
self._ptr = GEOSPointer(0)
|
||||
The `srid` keyword is used to specify the Source Reference Identifier
|
||||
(SRID) number for this Geometry. If not set, the SRID will be None.
|
||||
"""
|
||||
|
||||
if isinstance(geo_input, UnicodeType):
|
||||
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
|
||||
geo_input = geo_input.encode('ascii')
|
||||
|
||||
if isinstance(geo_input, StringType):
|
||||
if input_type: warn('input_type keyword is deprecated')
|
||||
|
||||
if hex_regex.match(geo_input):
|
||||
# If the regex matches, the geometry is in HEX form.
|
||||
sz = c_size_t(len(geo_input))
|
||||
@ -58,47 +51,41 @@ class GEOSGeometry(object):
|
||||
g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input))
|
||||
else:
|
||||
raise GEOSException, 'given string input "%s" unrecognized as WKT or HEXEWKB.' % geo_input
|
||||
|
||||
elif isinstance(geo_input, (IntType, GEOSPointer)):
|
||||
# When the input is either a raw pointer value (an integer), or a GEOSPointer object.
|
||||
# When the input is either a memory address (an integer), or a
|
||||
# GEOSPointer object.
|
||||
g = geo_input
|
||||
else:
|
||||
# Invalid geometry type.
|
||||
raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input))
|
||||
|
||||
if bool(g):
|
||||
# If we have a GEOSPointer object, just set the '_ptr' attribute with input
|
||||
if isinstance(g, GEOSPointer): self._ptr = g
|
||||
else: self._ptr.set(g) # Otherwise, set with the address
|
||||
# Setting the pointer object with a valid pointer.
|
||||
self._ptr = GEOSPointer(g)
|
||||
else:
|
||||
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
|
||||
|
||||
# Setting the 'parent' flag -- when the object is labeled with this flag
|
||||
# it will not be destroyed by __del__(). This is used for child geometries spawned from
|
||||
# parent geometries (e.g., LinearRings from a Polygon, Points from a MultiPoint, etc.).
|
||||
if isinstance(parent, GEOSPointer):
|
||||
self._parent = parent
|
||||
else:
|
||||
self._parent = GEOSPointer(0)
|
||||
|
||||
# Setting the SRID, if given.
|
||||
if srid and isinstance(srid, int): self.srid = srid
|
||||
|
||||
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
|
||||
self.__class__ = GEOS_CLASSES[self.geom_type]
|
||||
|
||||
# Getting the coordinate sequence for the geometry (will be None on geometries that
|
||||
# do not have coordinate sequences)
|
||||
self._get_cs()
|
||||
# Setting the coordinate sequence for the geometry (will be None on
|
||||
# geometries that do not have coordinate sequences)
|
||||
self._set_cs()
|
||||
|
||||
# Extra setup needed for Geometries that may be parents.
|
||||
# _populate() needs to be called for parent Geometries.
|
||||
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
|
||||
|
||||
def __del__(self):
|
||||
"Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent."
|
||||
#print 'base: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||
# Only calling destroy on valid pointers not spawned from a parent
|
||||
if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
|
||||
"""
|
||||
Destroys this Geometry; in other words, frees the memory used by the
|
||||
GEOS C++ object -- but only if the pointer is not a child Geometry
|
||||
(e.g., don't delete the LinearRings spawned from a Polygon).
|
||||
"""
|
||||
#print 'base: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid)
|
||||
if not self._ptr.child: self._ptr.destroy()
|
||||
|
||||
def __str__(self):
|
||||
"WKT is used for the string representation."
|
||||
@ -156,21 +143,30 @@ class GEOSGeometry(object):
|
||||
|
||||
# g1 ^= g2
|
||||
def __ixor__(self, other):
|
||||
"Reassigns this Geometry to the symmetric difference of this Geometry and the other."
|
||||
"""
|
||||
Reassigns this Geometry to the symmetric difference of this Geometry
|
||||
and the other.
|
||||
"""
|
||||
return self.sym_difference(other)
|
||||
|
||||
#### Internal GEOSPointer-related routines. ####
|
||||
def _nullify(self):
|
||||
"""During initialization of geometries from other geometries, this routine is
|
||||
used to nullify any parent geometries (since they will now be missing memory
|
||||
components) and to nullify the geometry itself to prevent future access.
|
||||
Only the address (an integer) of the current geometry is returned for use in
|
||||
initializing the new geometry."""
|
||||
"""
|
||||
Returns the address of this Geometry, and nullifies any related pointers.
|
||||
This function is called if this Geometry is used in the initialization
|
||||
of another Geometry.
|
||||
"""
|
||||
# First getting the memory address of the geometry.
|
||||
address = self._ptr()
|
||||
|
||||
# If the geometry is a child geometry, then the parent geometry pointer is
|
||||
# nullified.
|
||||
if self._parent: self._parent.nullify()
|
||||
if self._ptr.child:
|
||||
p = self._ptr.parent
|
||||
# If we have a grandchild (a LinearRing from a MultiPolygon or
|
||||
# GeometryCollection), then nullify the collection as well.
|
||||
if p.child: p.parent.nullify()
|
||||
p.nullify()
|
||||
|
||||
# Nullifying the geometry pointer
|
||||
self._ptr.nullify()
|
||||
@ -178,7 +174,7 @@ class GEOSGeometry(object):
|
||||
return address
|
||||
|
||||
def _reassign(self, new_geom):
|
||||
"Internal routine for reassigning internal pointer to a new geometry."
|
||||
"Reassigns the internal pointer to that of the new Geometry."
|
||||
# Only can re-assign when given a pointer or a geometry.
|
||||
if not isinstance(new_geom, (GEOSPointer, GEOSGeometry)):
|
||||
raise TypeError, 'cannot reassign geometry on given type: %s' % type(new_geom)
|
||||
@ -186,8 +182,8 @@ class GEOSGeometry(object):
|
||||
|
||||
# Re-assigning the internal GEOSPointer to the new geometry, nullifying
|
||||
# the new Geometry in the process.
|
||||
if isinstance(new_geom, GEOSGeometry): self._ptr.set(new_geom._nullify())
|
||||
else: self._ptr = new_geom
|
||||
if isinstance(new_geom, GEOSPointer): self._ptr = new_geom
|
||||
else: self._ptr = GEOSPointer(new_geom._nullify())
|
||||
|
||||
# The new geometry class may be different from the original, so setting
|
||||
# the __class__ and populating the internal geometry or ring dictionary.
|
||||
@ -200,10 +196,10 @@ class GEOSGeometry(object):
|
||||
if proto == ISQLQuote:
|
||||
return self
|
||||
else:
|
||||
raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
|
||||
raise GEOSException, 'Error implementing psycopg2 protocol. Is psycopg2 installed?'
|
||||
|
||||
def getquoted(self):
|
||||
"Returns a properly quoted string for use in PostgresSQL/PostGIS."
|
||||
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
||||
# Using ST_GeomFromText(), corresponds to SQL/MM ISO standard.
|
||||
return "ST_GeomFromText('%s', %s)" % (self.wkt, self.srid or -1)
|
||||
|
||||
@ -217,10 +213,11 @@ class GEOSGeometry(object):
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_cs(self):
|
||||
"Gets the coordinate sequence for this Geometry."
|
||||
def _set_cs(self):
|
||||
"Sets the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True)
|
||||
if not self._ptr.coordseq_valid:
|
||||
self._ptr.set_coordseq(lgeos.GEOSGeom_getCoordSeq(self._ptr()))
|
||||
self._cs = GEOSCoordSeq(self._ptr, self.hasz)
|
||||
else:
|
||||
self._cs = None
|
||||
@ -272,16 +269,20 @@ class GEOSGeometry(object):
|
||||
|
||||
## Internal for GEOS unary & binary predicate functions ##
|
||||
def _unary_predicate(self, func):
|
||||
"""Returns the result, or raises an exception for the given unary
|
||||
predicate function."""
|
||||
"""
|
||||
Returns the result, or raises an exception for the given unary predicate
|
||||
function.
|
||||
"""
|
||||
val = func(self._ptr())
|
||||
if val == 0: return False
|
||||
elif val == 1: return True
|
||||
else: raise GEOSException, '%s: exception occurred.' % func.__name__
|
||||
|
||||
def _binary_predicate(self, func, other, *args):
|
||||
"""Returns the result, or raises an exception for the given binary
|
||||
predicate function."""
|
||||
"""
|
||||
Returns the result, or raises an exception for the given binary
|
||||
predicate function.
|
||||
"""
|
||||
if not isinstance(other, GEOSGeometry):
|
||||
raise TypeError, 'Binary predicate operation ("%s") requires another GEOSGeometry instance.' % func.__name__
|
||||
val = func(self._ptr(), other._ptr(), *args)
|
||||
@ -292,7 +293,10 @@ class GEOSGeometry(object):
|
||||
#### Unary predicates ####
|
||||
@property
|
||||
def empty(self):
|
||||
"Returns a boolean indicating whether the set of points in this Geometry are empty."
|
||||
"""
|
||||
Returns a boolean indicating whether the set of points in this Geometry
|
||||
are empty.
|
||||
"""
|
||||
return self._unary_predicate(lgeos.GEOSisEmpty)
|
||||
|
||||
@property
|
||||
@ -317,20 +321,26 @@ class GEOSGeometry(object):
|
||||
|
||||
#### Binary predicates. ####
|
||||
def relate_pattern(self, other, pattern):
|
||||
"""Returns true if the elements in the DE-9IM intersection matrix for
|
||||
the two Geometries match the elements in pattern."""
|
||||
"""
|
||||
Returns true if the elements in the DE-9IM intersection matrix for the
|
||||
two Geometries match the elements in pattern.
|
||||
"""
|
||||
if len(pattern) > 9:
|
||||
raise GEOSException, 'invalid intersection matrix pattern'
|
||||
return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern))
|
||||
|
||||
def disjoint(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FF*FF****."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FF*FF****.
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSDisjoint, other)
|
||||
|
||||
def touches(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FT*******, F**T***** or F***T****."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FT*******, F**T***** or F***T****.
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSTouches, other)
|
||||
|
||||
def intersects(self, other):
|
||||
@ -338,14 +348,18 @@ class GEOSGeometry(object):
|
||||
return self._binary_predicate(lgeos.GEOSIntersects, other)
|
||||
|
||||
def crosses(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T****** (for a point and a curve,a point and an area or a line and
|
||||
an area) 0******** (for two curves)."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T****** (for a point and a curve,a point and an area or a line and
|
||||
an area) 0******** (for two curves).
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSCrosses, other)
|
||||
|
||||
def within(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**F***."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**F***.
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSWithin, other)
|
||||
|
||||
def contains(self, other):
|
||||
@ -353,18 +367,24 @@ class GEOSGeometry(object):
|
||||
return self._binary_predicate(lgeos.GEOSContains, other)
|
||||
|
||||
def overlaps(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves)."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSOverlaps, other)
|
||||
|
||||
def equals(self, other):
|
||||
"""Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**FFF*."""
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**FFF*.
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSEquals, other)
|
||||
|
||||
def equals_exact(self, other, tolerance=0):
|
||||
"""Returns true if the two Geometries are exactly equal, up to a
|
||||
specified tolerance."""
|
||||
"""
|
||||
Returns true if the two Geometries are exactly equal, up to a
|
||||
specified tolerance.
|
||||
"""
|
||||
return self._binary_predicate(lgeos.GEOSEqualsExact, other,
|
||||
c_double(tolerance))
|
||||
|
||||
@ -383,12 +403,12 @@ class GEOSGeometry(object):
|
||||
#### Output Routines ####
|
||||
@property
|
||||
def wkt(self):
|
||||
"Returns the WKT of the Geometry."
|
||||
"Returns the WKT (Well-Known Text) of the Geometry."
|
||||
return string_at(lgeos.GEOSGeomToWKT(self._ptr()))
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
"Returns the WKBHEX of the Geometry."
|
||||
"Returns the HEXEWKB of the Geometry."
|
||||
sz = c_size_t()
|
||||
h = lgeos.GEOSGeomToHEX_buf(self._ptr(), byref(sz))
|
||||
return string_at(h, sz.value)
|
||||
@ -401,21 +421,26 @@ class GEOSGeometry(object):
|
||||
|
||||
#### Topology Routines ####
|
||||
def _unary_topology(self, func, *args):
|
||||
"""Returns a GEOSGeometry for the given unary (takes only one Geomtery
|
||||
as a paramter) topological operation."""
|
||||
"""
|
||||
Returns a GEOSGeometry for the given unary (takes only one Geomtery
|
||||
as a paramter) topological operation.
|
||||
"""
|
||||
return GEOSGeometry(func(self._ptr(), *args), srid=self.srid)
|
||||
|
||||
def _binary_topology(self, func, other, *args):
|
||||
"""Returns a GEOSGeometry for the given binary (takes two Geometries
|
||||
as parameters) topological operation."""
|
||||
"""
|
||||
Returns a GEOSGeometry for the given binary (takes two Geometries
|
||||
as parameters) topological operation.
|
||||
"""
|
||||
return GEOSGeometry(func(self._ptr(), other._ptr(), *args), srid=self.srid)
|
||||
|
||||
def buffer(self, width, quadsegs=8):
|
||||
"""Returns a geometry that represents all points whose distance from this
|
||||
Geometry is less than or equal to distance. Calculations are in the
|
||||
Spatial Reference System of this Geometry. The optional third parameter sets
|
||||
the number of segment used to approximate a quarter circle (defaults to 8).
|
||||
(Text from PostGIS documentation at ch. 6.1.3)
|
||||
"""
|
||||
Returns a geometry that represents all points whose distance from this
|
||||
Geometry is less than or equal to distance. Calculations are in the
|
||||
Spatial Reference System of this Geometry. The optional third parameter sets
|
||||
the number of segment used to approximate a quarter circle (defaults to 8).
|
||||
(Text from PostGIS documentation at ch. 6.1.3)
|
||||
"""
|
||||
if not isinstance(width, (FloatType, IntType)):
|
||||
raise TypeError, 'width parameter must be a float'
|
||||
@ -430,9 +455,11 @@ class GEOSGeometry(object):
|
||||
|
||||
@property
|
||||
def centroid(self):
|
||||
"""The centroid is equal to the centroid of the set of component Geometries
|
||||
of highest dimension (since the lower-dimension geometries contribute zero
|
||||
"weight" to the centroid)."""
|
||||
"""
|
||||
The centroid is equal to the centroid of the set of component Geometries
|
||||
of highest dimension (since the lower-dimension geometries contribute zero
|
||||
"weight" to the centroid).
|
||||
"""
|
||||
return self._unary_topology(lgeos.GEOSGetCentroid)
|
||||
|
||||
@property
|
||||
@ -442,8 +469,10 @@ class GEOSGeometry(object):
|
||||
|
||||
@property
|
||||
def convex_hull(self):
|
||||
"""Returns the smallest convex Polygon that contains all the points
|
||||
in the Geometry."""
|
||||
"""
|
||||
Returns the smallest convex Polygon that contains all the points
|
||||
in the Geometry.
|
||||
"""
|
||||
return self._unary_topology(lgeos.GEOSConvexHull)
|
||||
|
||||
@property
|
||||
@ -451,8 +480,16 @@ class GEOSGeometry(object):
|
||||
"Computes an interior point of this Geometry."
|
||||
return self._unary_topology(lgeos.GEOSPointOnSurface)
|
||||
|
||||
def simplify(self, tolerance=0.0):
|
||||
"""
|
||||
Returns the Geometry, simplified using the Douglas-Peucker algorithm
|
||||
to the specified tolerance (higher tolerance => less points). If no
|
||||
tolerance provided, defaults to 0.
|
||||
"""
|
||||
return self._unary_topology(lgeos.GEOSSimplify, c_double(tolerance))
|
||||
|
||||
def relate(self, other):
|
||||
"Returns the DE-9IM intersection matrix for this geometry and the other."
|
||||
"Returns the DE-9IM intersection matrix for this Geometry and the other."
|
||||
return string_at(lgeos.GEOSRelate(self._ptr(), other._ptr()))
|
||||
|
||||
def difference(self, other):
|
||||
@ -461,8 +498,10 @@ class GEOSGeometry(object):
|
||||
return self._binary_topology(lgeos.GEOSDifference, other)
|
||||
|
||||
def sym_difference(self, other):
|
||||
"""Returns a set combining the points in this Geometry not in other,
|
||||
and the points in other not in this Geometry."""
|
||||
"""
|
||||
Returns a set combining the points in this Geometry not in other,
|
||||
and the points in other not in this Geometry.
|
||||
"""
|
||||
return self._binary_topology(lgeos.GEOSSymDifference, other)
|
||||
|
||||
def intersection(self, other):
|
||||
@ -483,9 +522,11 @@ class GEOSGeometry(object):
|
||||
else: return a.value
|
||||
|
||||
def distance(self, other):
|
||||
"""Returns the distance between the closest points on this Geometry
|
||||
and the other. Units will be in those of the coordinate system. of
|
||||
the Geometry."""
|
||||
"""
|
||||
Returns the distance between the closest points on this Geometry
|
||||
and the other. Units will be in those of the coordinate system of
|
||||
the Geometry.
|
||||
"""
|
||||
if not isinstance(other, GEOSGeometry):
|
||||
raise TypeError, 'distance() works only on other GEOS Geometries.'
|
||||
dist = c_double()
|
||||
@ -495,8 +536,10 @@ class GEOSGeometry(object):
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""Returns the length of this Geometry (e.g., 0 for point, or the
|
||||
circumfrence of a Polygon)."""
|
||||
"""
|
||||
Returns the length of this Geometry (e.g., 0 for point, or the
|
||||
circumfrence of a Polygon).
|
||||
"""
|
||||
l = c_double()
|
||||
status = lgeos.GEOSLength(self._ptr(), byref(l))
|
||||
if status != 1: return None
|
||||
|
@ -1,13 +1,14 @@
|
||||
"""
|
||||
This module houses the Geometry Collection objects:
|
||||
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
||||
This module houses the Geometry Collection objects:
|
||||
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
||||
"""
|
||||
from ctypes import c_int, c_uint, byref, cast
|
||||
from types import TupleType, ListType
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, GEOM_PTR
|
||||
from django.contrib.gis.geos.base import GEOSGeometry
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
|
||||
from django.contrib.gis.geos.libgeos import lgeos, get_pointer_arr, GEOM_PTR
|
||||
from django.contrib.gis.geos.pointer import GEOSPointer
|
||||
|
||||
class GeometryCollection(GEOSGeometry):
|
||||
_allowed = (Point, LineString, LinearRing, Polygon)
|
||||
@ -15,16 +16,14 @@ class GeometryCollection(GEOSGeometry):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Initializes a Geometry Collection from a sequence of Geometry objects."
|
||||
# Setting up the collection for creation
|
||||
self._ptr = GEOSPointer(0) # Initially NULL
|
||||
self._geoms = {}
|
||||
self._parent = None
|
||||
|
||||
# Checking the arguments
|
||||
if not args:
|
||||
raise TypeError, 'Must provide at least one Geometry to initialize %s.' % self.__class__.__name__
|
||||
|
||||
if len(args) == 1: # If only one geometry provided or a list of geometries is provided
|
||||
if len(args) == 1:
|
||||
# If only one geometry provided or a list of geometries is provided
|
||||
# in the first argument.
|
||||
if isinstance(args[0], (TupleType, ListType)):
|
||||
init_geoms = args[0]
|
||||
else:
|
||||
@ -36,40 +35,47 @@ class GeometryCollection(GEOSGeometry):
|
||||
if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
|
||||
raise TypeError, 'Invalid Geometry type encountered in the arguments.'
|
||||
|
||||
# Creating the geometry pointer array
|
||||
# Creating the geometry pointer array, and populating each element in
|
||||
# the array with the address of the Geometry returned by _nullify().
|
||||
ngeom = len(init_geoms)
|
||||
geoms = get_pointer_arr(ngeom)
|
||||
|
||||
# Incrementing through each input geometry.
|
||||
for i in xrange(ngeom):
|
||||
geoms[i] = cast(init_geoms[i]._nullify(), GEOM_PTR)
|
||||
|
||||
# Calling the parent class, using the pointer returned from GEOS createCollection()
|
||||
super(GeometryCollection, self).__init__(lgeos.GEOSGeom_createCollection(c_int(self._typeid), byref(geoms), c_uint(ngeom)), **kwargs)
|
||||
# Calling the parent class, using the pointer returned from the
|
||||
# GEOS createCollection() factory.
|
||||
addr = lgeos.GEOSGeom_createCollection(c_int(self._typeid),
|
||||
byref(geoms), c_uint(ngeom))
|
||||
super(GeometryCollection, self).__init__(addr, **kwargs)
|
||||
|
||||
def __del__(self):
|
||||
"Overloaded deletion method for Geometry Collections."
|
||||
#print 'collection: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||
#print 'collection: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid)
|
||||
# If this geometry is still valid, it hasn't been modified by others.
|
||||
if self._ptr.valid:
|
||||
# Nullifying pointers to internal geometries, preventing any attempted future access.
|
||||
for k in self._geoms: self._geoms[k].nullify()
|
||||
# Nullifying pointers to internal Geometries, preventing any
|
||||
# attempted future access.
|
||||
for g in self._ptr: g.nullify()
|
||||
else:
|
||||
# Internal memory has become part of other Geometry objects, must delete the
|
||||
# internal objects which are still valid individually, since calling destructor
|
||||
# on entire geometry will result in an attempted deletion of NULL pointers for
|
||||
# the missing components.
|
||||
for k in self._geoms:
|
||||
if self._geoms[k].valid:
|
||||
lgeos.GEOSGeom_destroy(self._geoms[k].address)
|
||||
self._geoms[k].nullify()
|
||||
# Internal memory has become part of other Geometry objects; must
|
||||
# delete the internal objects which are still valid individually,
|
||||
# because calling the destructor on the entire geometry will result
|
||||
# in an attempted deletion of NULL pointers for the missing
|
||||
# components (which may crash Python).
|
||||
for g in self._ptr:
|
||||
if len(g) > 0:
|
||||
# The collection geometry is a Polygon, destroy any leftover
|
||||
# LinearRings.
|
||||
for r in g: r.destroy()
|
||||
g.destroy()
|
||||
|
||||
super(GeometryCollection, self).__del__()
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Returns the Geometry from this Collection at the given index (0-based)."
|
||||
# Checking the index and returning the corresponding GEOS geometry.
|
||||
self._checkindex(index)
|
||||
return GEOSGeometry(self._geoms[index], parent=self._ptr, srid=self.srid)
|
||||
return GEOSGeometry(self._ptr[index], srid=self.srid)
|
||||
|
||||
def __setitem__(self, index, geom):
|
||||
"Sets the Geometry at the specified index."
|
||||
@ -105,14 +111,23 @@ class GeometryCollection(GEOSGeometry):
|
||||
def _nullify(self):
|
||||
"Overloaded from base method to nullify geometry references in this Collection."
|
||||
# Nullifying the references to the internal Geometry objects from this Collection.
|
||||
for k in self._geoms: self._geoms[k].nullify()
|
||||
for g in self._ptr: g.nullify()
|
||||
return super(GeometryCollection, self)._nullify()
|
||||
|
||||
def _populate(self):
|
||||
"Populates the internal child geometry dictionary."
|
||||
self._geoms = {}
|
||||
for i in xrange(self.num_geom):
|
||||
self._geoms[i] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(i)))
|
||||
"Internal routine that populates the internal children geometries list."
|
||||
ptr_list = []
|
||||
for i in xrange(len(self)):
|
||||
# Getting the geometry pointer for the geometry at the index.
|
||||
geom_ptr = lgeos.GEOSGetGeometryN(self._ptr(), c_int(i))
|
||||
|
||||
# Adding the coordinate sequence to the list, or using None if the
|
||||
# collection Geometry doesn't support coordinate sequences.
|
||||
if lgeos.GEOSGeomTypeId(geom_ptr) in (0, 1, 2):
|
||||
ptr_list.append((geom_ptr, lgeos.GEOSGeom_getCoordSeq(geom_ptr)))
|
||||
else:
|
||||
ptr_list.append((geom_ptr, None))
|
||||
self._ptr.set_children(ptr_list)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
|
@ -1,15 +1,15 @@
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
|
||||
"""
|
||||
This module houses the GEOSCoordSeq object, and is used internally
|
||||
by GEOSGeometry to house the actual coordinates of the Point,
|
||||
LineString, and LinearRing geometries.
|
||||
"""
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
from django.contrib.gis.geos.libgeos import lgeos, HAS_NUMPY
|
||||
from django.contrib.gis.geos.pointer import GEOSPointer
|
||||
from ctypes import c_double, c_int, c_uint, byref
|
||||
from types import ListType, TupleType
|
||||
if HAS_NUMPY: from numpy import ndarray
|
||||
|
||||
"""
|
||||
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."
|
||||
|
||||
@ -31,18 +31,18 @@ class GEOSCoordSeq(object):
|
||||
return int(self.size)
|
||||
|
||||
def __str__(self):
|
||||
"The string representation of the coordinate sequence."
|
||||
"Returns 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."
|
||||
"Returns the coordinate sequence value at the given 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."
|
||||
"Sets the coordinate sequence value at the given index."
|
||||
# Checking the input value
|
||||
if isinstance(value, (ListType, TupleType)):
|
||||
pass
|
||||
@ -66,7 +66,7 @@ class GEOSCoordSeq(object):
|
||||
|
||||
#### Internal Routines ####
|
||||
def _checkindex(self, index):
|
||||
"Checks the index."
|
||||
"Checks the given index."
|
||||
sz = self.size
|
||||
if (sz < 1) or (index < 0) or (index >= sz):
|
||||
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
|
||||
@ -78,7 +78,7 @@ class GEOSCoordSeq(object):
|
||||
|
||||
#### Ordinate getting and setting routines ####
|
||||
def getOrdinate(self, dimension, index):
|
||||
"Gets the value for the given dimension and index."
|
||||
"Returns the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
|
||||
@ -152,7 +152,10 @@ class GEOSCoordSeq(object):
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"Inherits this from the parent geometry."
|
||||
"""
|
||||
Returns whether this coordinate sequence is 3D. This property value is
|
||||
inherited from the parent Geometry.
|
||||
"""
|
||||
return self._z
|
||||
|
||||
### Other Methods ###
|
||||
|
@ -1,15 +1,20 @@
|
||||
"""
|
||||
This module houses the GEOS exceptions, specifically, GEOSException and
|
||||
GEOSGeometryIndexError.
|
||||
"""
|
||||
|
||||
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
|
||||
"""
|
||||
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.
|
||||
# will be propagated, unless the exception has an attribute
|
||||
# `silent_variable_failure` whose value is True." -- Django template docs.
|
||||
silent_variable_failure = True
|
||||
|
@ -3,36 +3,33 @@
|
||||
geometry classes. All geometry classes in this module inherit from
|
||||
GEOSGeometry.
|
||||
"""
|
||||
|
||||
from ctypes import c_double, c_int, c_uint, byref, cast
|
||||
from types import FloatType, IntType, ListType, TupleType
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, GEOM_PTR, HAS_NUMPY
|
||||
from django.contrib.gis.geos.base import GEOSGeometry
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||
from django.contrib.gis.geos.libgeos import lgeos, get_pointer_arr, GEOM_PTR, HAS_NUMPY
|
||||
from django.contrib.gis.geos.pointer import GEOSPointer
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||
if HAS_NUMPY: from numpy import ndarray, array
|
||||
|
||||
class Point(GEOSGeometry):
|
||||
|
||||
def __init__(self, x, y=None, z=None, srid=None):
|
||||
"""The Point object may be initialized with either a tuple, or individual
|
||||
parameters. For example:
|
||||
"""
|
||||
The Point object may be initialized with either a tuple, or individual
|
||||
parameters. For example:
|
||||
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
|
||||
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
|
||||
"""
|
||||
|
||||
# Setting-up for Point Creation
|
||||
self._ptr = GEOSPointer(0) # Initially NULL
|
||||
self._parent = None
|
||||
|
||||
if isinstance(x, (TupleType, ListType)):
|
||||
# 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)
|
||||
if ndim < 2 or ndim > 3:
|
||||
raise TypeError, 'Invalid sequence parameter: %s' % str(x)
|
||||
coords = x
|
||||
elif isinstance(x, (IntType, FloatType)) and isinstance(y, (IntType, FloatType)):
|
||||
# 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)):
|
||||
ndim = 3
|
||||
coords = [x, y, z]
|
||||
@ -42,22 +39,17 @@ class Point(GEOSGeometry):
|
||||
else:
|
||||
raise TypeError, 'Invalid parameters given for Point initialization.'
|
||||
|
||||
# Creating the coordinate sequence
|
||||
# Creating the coordinate sequence, and setting X, Y, [Z]
|
||||
cs = create_cs(c_uint(1), c_uint(ndim))
|
||||
|
||||
# Setting the X
|
||||
status = lgeos.GEOSCoordSeq_setX(cs, c_uint(0), c_double(coords[0]))
|
||||
if not status: raise GEOSException, 'Could not set X during Point initialization.'
|
||||
|
||||
# Setting the Y
|
||||
status = lgeos.GEOSCoordSeq_setY(cs, c_uint(0), c_double(coords[1]))
|
||||
if not status: raise GEOSException, 'Could not set Y during Point initialization.'
|
||||
|
||||
# Setting the Z
|
||||
if ndim == 3:
|
||||
status = lgeos.GEOSCoordSeq_setZ(cs, c_uint(0), c_double(coords[2]))
|
||||
|
||||
# Initializing from the geometry, and getting a Python object
|
||||
# Initializing using the address returned from the GEOS
|
||||
# createPoint factory.
|
||||
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs), srid=srid)
|
||||
|
||||
def __len__(self):
|
||||
@ -125,9 +117,10 @@ class LineString(GEOSGeometry):
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initializes on the given sequence -- may take lists, tuples, NumPy arrays
|
||||
of X,Y pairs, or Point objects. If Point objects are used, ownership is
|
||||
_not_ transferred to the LineString object.
|
||||
"""
|
||||
Initializes on the given sequence -- may take lists, tuples, NumPy arrays
|
||||
of X,Y pairs, or Point objects. If Point objects are used, ownership is
|
||||
_not_ transferred to the LineString object.
|
||||
|
||||
Examples:
|
||||
ls = LineString((1, 1), (2, 2))
|
||||
@ -135,11 +128,7 @@ class LineString(GEOSGeometry):
|
||||
ls = LineString(array([(1, 1), (2, 2)]))
|
||||
ls = LineString(Point(1, 1), Point(2, 2))
|
||||
"""
|
||||
# Setting up for LineString creation
|
||||
self._ptr = GEOSPointer(0) # Initially NULL
|
||||
self._parent = None
|
||||
|
||||
# If only one argument was provided, then set the coords array appropriately
|
||||
# If only one argument provided, set the coords array appropriately
|
||||
if len(args) == 1: coords = args[0]
|
||||
else: coords = args
|
||||
|
||||
@ -166,10 +155,9 @@ class LineString(GEOSGeometry):
|
||||
else:
|
||||
raise TypeError, 'Invalid initialization input for LineStrings.'
|
||||
|
||||
# Creating the coordinate sequence
|
||||
# Creating a coordinate sequence object because it is easier to
|
||||
# set the points using GEOSCoordSeq.__setitem__().
|
||||
cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim))), z=bool(ndim==3))
|
||||
|
||||
# Setting each point in the coordinate sequence
|
||||
for i in xrange(ncoords):
|
||||
if numpy_coords: cs[i] = coords[i,:]
|
||||
elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
|
||||
@ -184,7 +172,8 @@ class LineString(GEOSGeometry):
|
||||
# If SRID was passed in with the keyword arguments
|
||||
srid = kwargs.get('srid', None)
|
||||
|
||||
# Calling the base geometry initialization with the returned pointer from the function.
|
||||
# Calling the base geometry initialization with the returned pointer
|
||||
# from the function.
|
||||
super(LineString, self).__init__(func(cs._ptr.coordseq()), srid=srid)
|
||||
|
||||
def __getitem__(self, index):
|
||||
@ -214,9 +203,11 @@ class LineString(GEOSGeometry):
|
||||
return self._cs.tuple
|
||||
|
||||
def _listarr(self, func):
|
||||
"""Internal routine that returns a sequence (list) corresponding with
|
||||
the given function. Will return a numpy array if possible."""
|
||||
lst = [func(i) for i in xrange(len(self))] # constructing the list, using the function
|
||||
"""
|
||||
Internal routine that returns a sequence (list) corresponding with
|
||||
the given function. Will return a numpy array if possible.
|
||||
"""
|
||||
lst = [func(i) for i in xrange(len(self))]
|
||||
if HAS_NUMPY: return array(lst) # ARRRR!
|
||||
else: return lst
|
||||
|
||||
@ -251,16 +242,16 @@ class LinearRing(LineString):
|
||||
class Polygon(GEOSGeometry):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initializes on an exterior ring and a sequence of holes (both instances of LinearRings.
|
||||
All LinearRing instances used for creation will become owned by this Polygon.
|
||||
|
||||
Examples, where shell, hole1, and hole2 are valid LinearRing geometries:
|
||||
poly = Polygon(shell, hole1, hole2)
|
||||
poly = Polygon(shell, (hole1, hole2))
|
||||
"""
|
||||
self._ptr = GEOSPointer(0) # Initially NULL
|
||||
self._parent = None
|
||||
self._rings = {}
|
||||
Initializes on an exterior ring and a sequence of holes (both
|
||||
instances of LinearRings. All LinearRing instances used for creation
|
||||
will become owned by this Polygon.
|
||||
|
||||
Below are some examples of initialization, where shell, hole1, and
|
||||
hole2 are valid LinearRing geometries:
|
||||
>>> poly = Polygon(shell, hole1, hole2)
|
||||
>>> poly = Polygon(shell, (hole1, hole2))
|
||||
"""
|
||||
if not args:
|
||||
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
|
||||
|
||||
@ -293,26 +284,28 @@ class Polygon(GEOSGeometry):
|
||||
|
||||
def __del__(self):
|
||||
"Overloaded deletion method for Polygons."
|
||||
#print 'polygon: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||
# If this geometry is still valid, it hasn't been modified by others.
|
||||
if self._ptr.valid:
|
||||
# Nulling the pointers to internal rings, preventing any attempted future access
|
||||
for k in self._rings: self._rings[k].nullify()
|
||||
elif not self._parent:
|
||||
# Internal memory has become part of other objects; must delete the
|
||||
# internal objects which are still valid individually, since calling
|
||||
# destructor on entire geometry will result in an attempted deletion
|
||||
# of NULL pointers for the missing components. Not performed on
|
||||
# children Polygons from MultiPolygon or GeometryCollection objects.
|
||||
for k in self._rings:
|
||||
if self._rings[k].valid:
|
||||
lgeos.GEOSGeom_destroy(self._rings[k].address)
|
||||
self._rings[k].nullify()
|
||||
#print 'polygon: Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._ptr.parent, self._ptr.valid)
|
||||
# Not performed on children Polygons from MultiPolygon or GeometryCollection objects.
|
||||
if not self._ptr.child:
|
||||
# If this geometry is still valid, it hasn't been modified by others.
|
||||
if self._ptr.valid:
|
||||
# Nulling the pointers to internal rings, preventing any
|
||||
# attempted future access.
|
||||
for r in self._ptr: r.nullify()
|
||||
else:
|
||||
# Internal memory has become part of other Geometry objects; must
|
||||
# delete the internal objects which are still valid individually,
|
||||
# because calling the destructor on entire geometry will result
|
||||
# in an attempted deletion of NULL pointers for the missing
|
||||
# components (which may crash Python).
|
||||
for r in self._ptr: r.destroy()
|
||||
super(Polygon, self).__del__()
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Returns the ring at the specified index. The first index, 0, will always
|
||||
return the exterior ring. Indices > 0 will return the interior ring."""
|
||||
"""
|
||||
Returns the ring at the specified index. The first index, 0, will always
|
||||
return the exterior ring. Indices > 0 will return the interior ring.
|
||||
"""
|
||||
if index == 0:
|
||||
return self.exterior_ring
|
||||
else:
|
||||
@ -353,31 +346,36 @@ class Polygon(GEOSGeometry):
|
||||
def _nullify(self):
|
||||
"Overloaded from base method to nullify ring references as well."
|
||||
# Nullifying the references to the internal rings of this Polygon.
|
||||
for k in self._rings: self._rings[k].nullify()
|
||||
for r in self._ptr: r.nullify()
|
||||
return super(Polygon, self)._nullify()
|
||||
|
||||
def _populate(self):
|
||||
"Populates the internal rings dictionary."
|
||||
# Getting the exterior ring first for the 0th index.
|
||||
self._rings = {0 : GEOSPointer(lgeos.GEOSGetExteriorRing(self._ptr()))}
|
||||
|
||||
# Getting the interior rings.
|
||||
for i in xrange(self.num_interior_rings):
|
||||
self._rings[i+1] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i)))
|
||||
"Internal routine for populating the internal ring pointers."
|
||||
# Only populate if there aren't already children pointers.
|
||||
if len(self._ptr) == 0:
|
||||
# Getting the exterior ring pointer address.
|
||||
ring_list = [lgeos.GEOSGetExteriorRing(self._ptr())]
|
||||
# Getting the interior ring pointer addresses.
|
||||
ring_list += [lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(i)) for i in xrange(self.num_interior_rings)]
|
||||
# Getting the coordinate sequence pointer address for each of the rings.
|
||||
ptr_list = [(ring_ptr, lgeos.GEOSGeom_getCoordSeq(ring_ptr)) for ring_ptr in ring_list]
|
||||
# Setting the children pointers.
|
||||
self._ptr.set_children(ptr_list)
|
||||
|
||||
def get_interior_ring(self, ring_i):
|
||||
"""Gets the interior ring at the specified index,
|
||||
0 is for the first interior ring, not the exterior ring."""
|
||||
"""
|
||||
Gets the interior ring at the specified index, 0 is for the first
|
||||
interior ring, not the exterior ring.
|
||||
"""
|
||||
# Returning the ring from the internal ring dictionary (have to add one
|
||||
# to index since all internal rings come after the exterior ring)
|
||||
self._checkindex(ring_i+1)
|
||||
return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr, srid=self.srid)
|
||||
return GEOSGeometry(self._ptr[ring_i+1], srid=self.srid)
|
||||
|
||||
#### Polygon Properties ####
|
||||
@property
|
||||
def num_interior_rings(self):
|
||||
"Returns the number of interior rings."
|
||||
|
||||
# Getting the number of rings
|
||||
n = lgeos.GEOSGetNumInteriorRings(self._ptr())
|
||||
|
||||
@ -387,7 +385,7 @@ class Polygon(GEOSGeometry):
|
||||
|
||||
def get_ext_ring(self):
|
||||
"Gets the exterior ring of the Polygon."
|
||||
return GEOSGeometry(self._rings[0], parent=self._ptr, srid=self.srid)
|
||||
return GEOSGeometry(self._ptr[0], srid=self.srid)
|
||||
|
||||
def set_ext_ring(self, ring):
|
||||
"Sets the exterior ring of the Polygon."
|
||||
|
@ -1,15 +1,15 @@
|
||||
"""
|
||||
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).
|
||||
as the notice and error handler function callbacks (get called
|
||||
when an error occurs in GEOS).
|
||||
|
||||
This module also houses GEOS Pointer utilities, including the
|
||||
GEOSPointer class, get_pointer_arr(), GEOM_PTR, and init_from_geom().
|
||||
This module also houses GEOS Pointer utilities, including
|
||||
get_pointer_arr(), and GEOM_PTR.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from ctypes import c_char_p, c_int, pointer, CDLL, CFUNCTYPE, POINTER, Structure
|
||||
import os, sys
|
||||
from ctypes import c_char_p, c_int, string_at, CDLL, CFUNCTYPE, POINTER, Structure
|
||||
import atexit, os, sys
|
||||
|
||||
# NumPy supported?
|
||||
try:
|
||||
@ -18,7 +18,7 @@ try:
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
# Are psycopg2 and GeoDjango models being used?
|
||||
# Is psycopg2 available?
|
||||
try:
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
except (ImportError, EnvironmentError):
|
||||
@ -67,11 +67,11 @@ 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:
|
||||
# is wrapped:
|
||||
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
|
||||
lgeos.initGEOS(notice_h, error_h)
|
||||
|
||||
#### GEOS Geometry Pointer object, related C data structures, and functions. ####
|
||||
#### GEOS Geometry C data structures, and utility functions. ####
|
||||
|
||||
# Opaque GEOS geometry structure
|
||||
class GEOSGeom_t(Structure):
|
||||
@ -81,87 +81,16 @@ class GEOSGeom_t(Structure):
|
||||
# Pointer to opaque geometry structure
|
||||
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||
|
||||
# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection GEOS routines
|
||||
# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
|
||||
# GEOS routines
|
||||
def get_pointer_arr(n):
|
||||
"Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
|
||||
GeomArr = GEOM_PTR * n
|
||||
return GeomArr()
|
||||
|
||||
class GEOSPointer(object):
|
||||
"""The GEOSPointer provides a layer of abstraction in accessing the values returned by
|
||||
GEOS geometry creation routines. Memory addresses (integers) are kept in a C pointer,
|
||||
which allows parent geometries to be 'nullified' if their children's memory is used
|
||||
in construction of another geometry. Related coordinate sequence pointers are kept
|
||||
in this object for the same reason."""
|
||||
def geos_version():
|
||||
"Returns the string version of GEOS."
|
||||
return string_at(lgeos.GEOSversion())
|
||||
|
||||
### Python 'magic' routines ###
|
||||
def __init__(self, address, coordseq=0):
|
||||
"Initializes on an address (an integer)."
|
||||
if isinstance(address, int):
|
||||
self._geom = pointer(c_int(address))
|
||||
self._coordseq = pointer(c_int(coordseq))
|
||||
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 (an integer) will be returned."""
|
||||
if self.valid: return self.address
|
||||
else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
|
||||
|
||||
def __nonzero__(self):
|
||||
"Returns True when the GEOSPointer is valid."
|
||||
return self.valid
|
||||
|
||||
def __str__(self):
|
||||
return str(self.address)
|
||||
|
||||
def __repr__(self):
|
||||
return 'GEOSPointer(%s)' % self.address
|
||||
|
||||
### GEOSPointer Properties ###
|
||||
@property
|
||||
def address(self):
|
||||
"Returns the address of the GEOSPointer (represented as an integer)."
|
||||
return self._geom.contents.value
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"Tests whether this GEOSPointer is valid."
|
||||
if bool(self.address): return True
|
||||
else: return False
|
||||
|
||||
### Coordinate Sequence routines and properties ###
|
||||
def coordseq(self):
|
||||
"If the coordinate sequence pointer is NULL (0), an exception will be raised."
|
||||
if self.coordseq_valid: return self.coordseq_address
|
||||
else: raise GEOSException, 'GEOS coordinate sequence pointer invalid (was this geometry or the parent geometry deleted or modified?)'
|
||||
|
||||
@property
|
||||
def coordseq_address(self):
|
||||
"Returns the address of the related coordinate sequence."
|
||||
return self._coordseq.contents.value
|
||||
|
||||
@property
|
||||
def coordseq_valid(self):
|
||||
"Returns True if the coordinate sequence address is valid, False otherwise."
|
||||
if bool(self.coordseq_address): return True
|
||||
else: return False
|
||||
|
||||
### GEOSPointer Methods ###
|
||||
def set(self, address, coordseq=False):
|
||||
"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).'
|
||||
if coordseq:
|
||||
self._coordseq.contents = c_int(address)
|
||||
else:
|
||||
self._geom.contents = c_int(address)
|
||||
|
||||
def nullify(self):
|
||||
"""Nullify this geometry pointer (set the address to 0). This does not delete
|
||||
any memory, rather, it sets the GEOS pointer to a NULL address, to prevent
|
||||
access to addressses of deleted objects."""
|
||||
# Nullifying both the geometry and coordinate sequence pointer.
|
||||
self.set(0)
|
||||
self.set(0, coordseq=True)
|
||||
# Calling the finishGEOS() upon exit of the interpreter.
|
||||
atexit.register(lgeos.finishGEOS)
|
||||
|
265
django/contrib/gis/geos/pointer.py
Normal file
265
django/contrib/gis/geos/pointer.py
Normal file
@ -0,0 +1,265 @@
|
||||
"""
|
||||
This module houses the GEOSPointer class, used by GEOS Geometry objects
|
||||
internally for memory management. Do not modify unless you _really_
|
||||
know what you're doing.
|
||||
"""
|
||||
from ctypes import cast, c_int, c_void_p, pointer, POINTER, Structure
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import lgeos
|
||||
|
||||
# This C data structure houses the memory addresses (integers) of the
|
||||
# pointers returned from the GEOS C routines.
|
||||
class GEOSStruct(Structure): pass
|
||||
GEOSStruct._fields_ = [("geom", POINTER(c_int)), # for the geometry
|
||||
("cs", POINTER(c_int)), # for geometries w/coordinate sequences
|
||||
("parent", POINTER(GEOSStruct)), # points to the GEOSStruct of the parent
|
||||
("child", c_void_p), # points to an array of GEOSStructs
|
||||
("nchild", c_int), # the number of children
|
||||
]
|
||||
|
||||
class GEOSPointer(object):
|
||||
"""
|
||||
The GEOSPointer provides a layer of abstraction in accessing the values
|
||||
returned by GEOS geometry creation routines. Memory addresses (integers)
|
||||
are kept in a C pointer, which allows parent geometries to be 'nullified'
|
||||
when a child's memory is used in construction of another geometry.
|
||||
|
||||
This object is also used to store pointers for any associated coordinate
|
||||
sequence and may store the pointers for any children geometries.
|
||||
"""
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, address, coordseq=0):
|
||||
"""
|
||||
Initializes on an address (an integer), another GEOSPointer, or a
|
||||
GEOSStruct object.
|
||||
"""
|
||||
if isinstance(address, int):
|
||||
self._struct = GEOSStruct()
|
||||
# Integer addresses passed in, use the 'set' methods.
|
||||
self.set(address)
|
||||
if coordseq: self.set_coordseq(coordseq)
|
||||
elif isinstance(address, GEOSPointer):
|
||||
# Initializing from another GEOSPointer
|
||||
self._struct = address._struct
|
||||
elif isinstance(address, GEOSStruct):
|
||||
# GEOSStruct passed directly in as a parameter
|
||||
self._struct = address
|
||||
else:
|
||||
raise TypeError, 'GEOSPointer object must initialize with an integer.'
|
||||
|
||||
def __call__(self):
|
||||
"""
|
||||
Returns the address value (an integer) of the GEOS Geometry pointer.
|
||||
If the pointer is NULL, a GEOSException will be raised, thus preventing
|
||||
an invalid memory address from being passed to a C routine.
|
||||
"""
|
||||
if self.valid: return self.address
|
||||
else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Returns a GEOSpointer object at the given child index."
|
||||
n_child = len(self)
|
||||
if n_child:
|
||||
if index < 0 or index >= n_child:
|
||||
raise IndexError, 'invalid child index'
|
||||
else:
|
||||
CHILD_PTR = POINTER(GEOSStruct * len(self))
|
||||
return GEOSPointer(cast(self._struct.child, CHILD_PTR).contents[index])
|
||||
else:
|
||||
raise GEOSException, 'This GEOSPointer is not a parent'
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterates over the children Geometry pointers, return as GEOSPointer
|
||||
objects to the caller.
|
||||
"""
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of children Geometry pointers (or 0 if no children)."
|
||||
return self._struct.nchild
|
||||
|
||||
def __nonzero__(self):
|
||||
"Returns True when the GEOSPointer is valid."
|
||||
return self.valid
|
||||
|
||||
def __str__(self):
|
||||
"Returns the string representation of this GEOSPointer."
|
||||
# First getting the address for the Geometry pointer.
|
||||
if self.valid: geom_addr = self.address
|
||||
else: geom_addr = 0
|
||||
# If there's a coordinate sequence, construct accoringly.
|
||||
if self.coordseq_valid:
|
||||
return 'GEOSPointer(%s, %s)' % (geom_addr, self.coordseq_address)
|
||||
else:
|
||||
return 'GEOSPointer(%s)' % geom_addr
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
#### GEOSPointer Properties ####
|
||||
@property
|
||||
def address(self):
|
||||
"Returns the address of the GEOSPointer (represented as an integer)."
|
||||
return self._struct.geom.contents.value
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"Tests whether this GEOSPointer is valid."
|
||||
return bool(self._struct.geom)
|
||||
|
||||
#### Parent & Child Properties ####
|
||||
@property
|
||||
def parent(self):
|
||||
"Returns the GEOSPointer for the parent or None."
|
||||
if self.child:
|
||||
return GEOSPointer(self._struct.parent.contents)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def child(self):
|
||||
"Returns True if the GEOSPointer has a parent."
|
||||
return bool(self._struct.parent)
|
||||
|
||||
#### Coordinate Sequence routines and properties ####
|
||||
def coordseq(self):
|
||||
"""
|
||||
If the coordinate sequence pointer is NULL or 0, an exception will
|
||||
be raised.
|
||||
"""
|
||||
if self.coordseq_valid: return self.coordseq_address
|
||||
else: raise GEOSException, 'GEOS coordinate sequence pointer invalid (was this geometry or the parent geometry deleted or modified?)'
|
||||
|
||||
@property
|
||||
def coordseq_address(self):
|
||||
"Returns the address of the related coordinate sequence."
|
||||
return self._struct.cs.contents.value
|
||||
|
||||
@property
|
||||
def coordseq_valid(self):
|
||||
"Returns True if the coordinate sequence address is valid, False otherwise."
|
||||
return bool(self._struct.cs)
|
||||
|
||||
#### GEOSPointer Methods ####
|
||||
def destroy(self):
|
||||
"""
|
||||
Calls GEOSGeom_destroy on the address of this pointer, and nullifies
|
||||
this pointer. Use VERY carefully, as trying to destroy an address that
|
||||
no longer holds a valid GEOS Geometry may crash Python.
|
||||
"""
|
||||
if self.valid:
|
||||
# ONLY on valid geometries.
|
||||
lgeos.GEOSGeom_destroy(self.address)
|
||||
self.nullify()
|
||||
|
||||
def set(self, address):
|
||||
"""
|
||||
Sets the address of this pointer with the given address (represented
|
||||
as an integer). Using 0 or None will set the pointer to NULL.
|
||||
"""
|
||||
if address in (0, None):
|
||||
self._struct.geom = None
|
||||
else:
|
||||
self._struct.geom.contents = c_int(address)
|
||||
|
||||
def set_coordseq(self, address):
|
||||
"""
|
||||
Sets the address of the coordinate sequence associated with
|
||||
this pointer.
|
||||
"""
|
||||
if address in (0, None):
|
||||
self._struct.cs = None
|
||||
else:
|
||||
self._struct.cs.contents = c_int(address)
|
||||
|
||||
def set_children(self, ptr_list):
|
||||
"""
|
||||
Sets children pointers with the given pointer list (of integers).
|
||||
Alternatively, a list of tuples for the geometry and coordinate
|
||||
sequence pointers of the children may be used.
|
||||
"""
|
||||
# The number of children geometries is the number of pointers (or
|
||||
# tuples) passed in via the `ptr_list`.
|
||||
n_child = len(ptr_list)
|
||||
|
||||
# Determining whether coordinate sequences pointers were passed in.
|
||||
if isinstance(ptr_list[0], (tuple, list)):
|
||||
self._child_cs = True
|
||||
else:
|
||||
self._child_cs = False
|
||||
|
||||
# Dynamically creating the C types for the children array (CHILD_ARR),
|
||||
# initializing with the created type, and creating a parent pointer
|
||||
# for the children.
|
||||
CHILD_ARR = GEOSStruct * n_child
|
||||
children = CHILD_ARR()
|
||||
parent = pointer(self._struct)
|
||||
|
||||
# Incrementing through each of the children, and setting the
|
||||
# pointers in the array of GEOSStructs.
|
||||
for i in xrange(n_child):
|
||||
if self._child_cs:
|
||||
geom_ptr, cs_ptr = ptr_list[i]
|
||||
if cs_ptr is not None:
|
||||
children[i].cs.contents = c_int(cs_ptr)
|
||||
else:
|
||||
geom_ptr = ptr_list[i]
|
||||
children[i].geom.contents = c_int(geom_ptr)
|
||||
children[i].parent = parent
|
||||
|
||||
# Casting the CHILD_ARR to the contents of the void pointer, and
|
||||
# setting the number of children within the struct (used by __len__).
|
||||
self._struct.child = cast(pointer(children), c_void_p)
|
||||
self._struct.nchild = c_int(n_child)
|
||||
|
||||
def nullify(self):
|
||||
"""
|
||||
Nullify the geometry and coordinate sequence pointers (sets the
|
||||
pointers to NULL). This does not delete any memory (destroy()
|
||||
handles that), rather, it sets the GEOS pointers to a NULL address,
|
||||
preventing access to deleted objects.
|
||||
"""
|
||||
# Nullifying both the geometry and coordinate sequence pointer.
|
||||
self.set(None)
|
||||
self.set_coordseq(None)
|
||||
|
||||
def summary(self):
|
||||
"""
|
||||
Returns a summary string containing information about the pointer,
|
||||
including the geometry address, any associated coordinate sequence
|
||||
address, and child geometry addresses.
|
||||
"""
|
||||
sum = '%s\n' % str(self)
|
||||
for p1 in self:
|
||||
sum += ' * %s\n' % p1
|
||||
for p2 in p1: sum += ' - %s\n' % p2
|
||||
if bool(self._struct.parent):
|
||||
sum += 'Parent: %s\n' % self.parent
|
||||
return sum
|
||||
|
||||
class NullGEOSPointer(GEOSPointer):
|
||||
"The NullGEOSPointer is always NULL, and cannot be set to anything else."
|
||||
def __init__(self):
|
||||
self._struct = GEOSStruct()
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def coordseq_valid(self):
|
||||
return False
|
||||
|
||||
def set(self, *args):
|
||||
pass
|
||||
|
||||
def set_coordseq(self, *args):
|
||||
pass
|
||||
|
||||
def set_children(self, *args):
|
||||
pass
|
||||
|
||||
NULL_GEOM = NullGEOSPointer()
|
@ -13,13 +13,13 @@ class GEOSTest(unittest.TestCase):
|
||||
def test01a_wkt(self):
|
||||
"Testing WKT output."
|
||||
for g in wkt_out:
|
||||
geom = GEOSGeometry(g.wkt)
|
||||
geom = fromstr(g.wkt)
|
||||
self.assertEqual(g.ewkt, geom.wkt)
|
||||
|
||||
def test01b_hex(self):
|
||||
"Testing HEX output."
|
||||
for g in hex_wkt:
|
||||
geom = GEOSGeometry(g.wkt)
|
||||
geom = fromstr(g.wkt)
|
||||
self.assertEqual(g.hex, geom.hex)
|
||||
|
||||
def test01c_kml(self):
|
||||
@ -34,22 +34,22 @@ class GEOSTest(unittest.TestCase):
|
||||
print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
|
||||
for err in errors:
|
||||
if err.hex:
|
||||
self.assertRaises(GEOSException, GEOSGeometry, err.wkt)
|
||||
self.assertRaises(GEOSException, fromstr, err.wkt)
|
||||
else:
|
||||
self.assertRaises(GEOSException, GEOSGeometry, err.wkt)
|
||||
self.assertRaises(GEOSException, fromstr, err.wkt)
|
||||
print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
|
||||
|
||||
def test02a_points(self):
|
||||
"Testing Point objects."
|
||||
prev = GEOSGeometry('POINT(0 0)')
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for p in points:
|
||||
# Creating the point from the WKT
|
||||
pnt = GEOSGeometry(p.wkt)
|
||||
pnt = fromstr(p.wkt)
|
||||
self.assertEqual(pnt.geom_type, 'Point')
|
||||
self.assertEqual(pnt.geom_typeid, 0)
|
||||
self.assertEqual(p.x, pnt.x)
|
||||
self.assertEqual(p.y, pnt.y)
|
||||
self.assertEqual(True, pnt == GEOSGeometry(p.wkt))
|
||||
self.assertEqual(True, pnt == fromstr(p.wkt))
|
||||
self.assertEqual(False, pnt == prev)
|
||||
|
||||
# Making sure that the point's X, Y components are what we expect
|
||||
@ -97,7 +97,7 @@ class GEOSTest(unittest.TestCase):
|
||||
def test02b_multipoints(self):
|
||||
"Testing MultiPoint objects."
|
||||
for mp in multipoints:
|
||||
mpnt = GEOSGeometry(mp.wkt)
|
||||
mpnt = fromstr(mp.wkt)
|
||||
self.assertEqual(mpnt.geom_type, 'MultiPoint')
|
||||
self.assertEqual(mpnt.geom_typeid, 4)
|
||||
|
||||
@ -115,9 +115,9 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
def test03a_linestring(self):
|
||||
"Testing LineString objects."
|
||||
prev = GEOSGeometry('POINT(0 0)')
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for l in linestrings:
|
||||
ls = GEOSGeometry(l.wkt)
|
||||
ls = fromstr(l.wkt)
|
||||
self.assertEqual(ls.geom_type, 'LineString')
|
||||
self.assertEqual(ls.geom_typeid, 1)
|
||||
self.assertEqual(ls.empty, False)
|
||||
@ -127,7 +127,7 @@ class GEOSTest(unittest.TestCase):
|
||||
if hasattr(l, 'tup'):
|
||||
self.assertEqual(l.tup, ls.tuple)
|
||||
|
||||
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
|
||||
self.assertEqual(True, ls == fromstr(l.wkt))
|
||||
self.assertEqual(False, ls == prev)
|
||||
self.assertRaises(GEOSGeometryIndexError, ls.__getitem__, len(ls))
|
||||
prev = ls
|
||||
@ -141,16 +141,16 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
def test03b_multilinestring(self):
|
||||
"Testing MultiLineString objects."
|
||||
prev = GEOSGeometry('POINT(0 0)')
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for l in multilinestrings:
|
||||
ml = GEOSGeometry(l.wkt)
|
||||
ml = fromstr(l.wkt)
|
||||
self.assertEqual(ml.geom_type, 'MultiLineString')
|
||||
self.assertEqual(ml.geom_typeid, 5)
|
||||
|
||||
self.assertAlmostEqual(l.centroid[0], ml.centroid.x, 9)
|
||||
self.assertAlmostEqual(l.centroid[1], ml.centroid.y, 9)
|
||||
|
||||
self.assertEqual(True, ml == GEOSGeometry(l.wkt))
|
||||
self.assertEqual(True, ml == fromstr(l.wkt))
|
||||
self.assertEqual(False, ml == prev)
|
||||
prev = ml
|
||||
|
||||
@ -166,7 +166,7 @@ class GEOSTest(unittest.TestCase):
|
||||
def test04_linearring(self):
|
||||
"Testing LinearRing objects."
|
||||
for rr in linearrings:
|
||||
lr = GEOSGeometry(rr.wkt)
|
||||
lr = fromstr(rr.wkt)
|
||||
self.assertEqual(lr.geom_type, 'LinearRing')
|
||||
self.assertEqual(lr.geom_typeid, 2)
|
||||
self.assertEqual(rr.n_p, len(lr))
|
||||
@ -181,10 +181,10 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
def test05a_polygons(self):
|
||||
"Testing Polygon objects."
|
||||
prev = GEOSGeometry('POINT(0 0)')
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for p in polygons:
|
||||
# Creating the Polygon, testing its properties.
|
||||
poly = GEOSGeometry(p.wkt)
|
||||
poly = fromstr(p.wkt)
|
||||
self.assertEqual(poly.geom_type, 'Polygon')
|
||||
self.assertEqual(poly.geom_typeid, 3)
|
||||
self.assertEqual(poly.empty, False)
|
||||
@ -199,7 +199,7 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
|
||||
|
||||
# Testing the geometry equivalence
|
||||
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
|
||||
self.assertEqual(True, poly == fromstr(p.wkt))
|
||||
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
|
||||
self.assertEqual(True, poly != prev)
|
||||
|
||||
@ -222,9 +222,8 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertEqual(r.geom_typeid, 2)
|
||||
|
||||
# Testing polygon construction.
|
||||
self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
|
||||
self.assertRaises(TypeError, Polygon, 'foo')
|
||||
|
||||
self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3])
|
||||
self.assertRaises(TypeError, Polygon.__init__, 'foo')
|
||||
rings = tuple(r.clone() for r in poly)
|
||||
self.assertEqual(poly, Polygon(rings[0], rings[1:]))
|
||||
self.assertEqual(poly.wkt, Polygon(*tuple(r.clone() for r in poly)).wkt)
|
||||
@ -246,9 +245,9 @@ class GEOSTest(unittest.TestCase):
|
||||
def test05b_multipolygons(self):
|
||||
"Testing MultiPolygon objects."
|
||||
print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
|
||||
prev = GEOSGeometry('POINT (0 0)')
|
||||
prev = fromstr('POINT (0 0)')
|
||||
for mp in multipolygons:
|
||||
mpoly = GEOSGeometry(mp.wkt)
|
||||
mpoly = fromstr(mp.wkt)
|
||||
self.assertEqual(mpoly.geom_type, 'MultiPolygon')
|
||||
self.assertEqual(mpoly.geom_typeid, 6)
|
||||
self.assertEqual(mp.valid, mpoly.valid)
|
||||
@ -266,14 +265,14 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
|
||||
|
||||
def test06_memory_hijinks(self):
|
||||
"Testing Geometry __del__() in different scenarios"
|
||||
def test06a_memory_hijinks(self):
|
||||
"Testing Geometry __del__() on rings and polygons."
|
||||
#### 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)
|
||||
poly = fromstr(polygons[1].wkt)
|
||||
ring1 = poly[0]
|
||||
ring2 = poly[1]
|
||||
|
||||
@ -292,6 +291,8 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertRaises(GEOSException, str, ring1)
|
||||
self.assertRaises(GEOSException, str, ring2)
|
||||
|
||||
def test06b_memory_hijinks(self):
|
||||
"Testing Geometry __del__() on collections."
|
||||
#### Memory issues with geometries from Geometry Collections
|
||||
mp = fromstr('MULTIPOINT(85 715, 235 1400, 4620 1711)')
|
||||
|
||||
@ -315,16 +316,17 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
# Should raise GEOSException when trying to get geometries from the multipoint
|
||||
# after it has been deleted.
|
||||
parr1 = [ptr for ptr in mp._ptr]
|
||||
parr2 = [p._ptr for p in mp]
|
||||
del mp
|
||||
|
||||
for p in pts:
|
||||
self.assertRaises(GEOSException, str, p) # tests p's geometry pointer
|
||||
self.assertRaises(GEOSException, p.get_coords) # tests p's coordseq pointer
|
||||
|
||||
# Now doing this with a GeometryCollection
|
||||
polywkt = polygons[3].wkt # a 'real life' polygon.
|
||||
lrwkt = linearrings[0].wkt # a 'real life' linear ring
|
||||
poly = fromstr(polywkt)
|
||||
linring = fromstr(lrwkt)
|
||||
poly = fromstr(polygons[3].wkt) # a 'real life' polygon.
|
||||
linring = fromstr(linearrings[0].wkt) # a 'real life' linear ring
|
||||
|
||||
# Pulling out the shell and cloning our initial geometries for later comparison.
|
||||
shell = poly.shell
|
||||
@ -338,33 +340,42 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertRaises(GEOSException, str, shell)
|
||||
self.assertRaises(GEOSException, str, linring)
|
||||
|
||||
r1 = gc[1] # pulling out the ring
|
||||
# Deep-indexing delete should be 'harmless.'
|
||||
tmpr1 = gc[0][0]
|
||||
del tmpr1
|
||||
|
||||
r1 = gc[0][0] # pulling out shell from polygon -- deep indexing
|
||||
r2 = gc[1] # pulling out the ring
|
||||
pnt = gc[2] # pulling the point from the geometry collection
|
||||
|
||||
# Now lets create a MultiPolygon from the geometry collection components
|
||||
mpoly = MultiPolygon(gc[0], Polygon(gc[1]))
|
||||
self.assertEqual(polyc.wkt, mpoly[0].wkt)
|
||||
self.assertEqual(linringc.wkt, mpoly[1][0].wkt) # deep-indexed ring
|
||||
|
||||
# Should no longer be able to access the geometry collection directly
|
||||
self.assertRaises(GEOSException, len, gc)
|
||||
|
||||
# BUT, should still be able to access the Point we obtained earlier, but
|
||||
# not the linear ring (since it is now part of the MultiPolygon.
|
||||
# BUT, should still be able to access the Point we obtained earlier,
|
||||
# however, not the linear ring (since it is now part of the
|
||||
# MultiPolygon).
|
||||
self.assertEqual(5, pnt.x)
|
||||
self.assertEqual(23, pnt.y)
|
||||
|
||||
# __len__ is called on the coordinate sequence pointer -- make sure its nullified as well
|
||||
self.assertRaises(GEOSException, len, r1)
|
||||
self.assertRaises(GEOSException, str, r1)
|
||||
for tmpr in [r1, r2]:
|
||||
# __len__ is called on the coordinate sequence pointer --
|
||||
# making sure its nullified as well
|
||||
self.assertRaises(GEOSException, len, tmpr)
|
||||
self.assertRaises(GEOSException, str, tmpr)
|
||||
|
||||
# Can't access point after deletion of parent geometry.
|
||||
del gc
|
||||
self.assertRaises(GEOSException, str, pnt)
|
||||
|
||||
# Cleaning up.
|
||||
del polyc
|
||||
del mpoly
|
||||
del polyc, mpoly
|
||||
|
||||
def test06c_memory_hijinks(self):
|
||||
"Testing __init__ using other Geometries as parameters."
|
||||
#### Memory issues with creating geometries from coordinate sequences within other geometries
|
||||
|
||||
# Creating the initial polygon from the following tuples, and then pulling out
|
||||
@ -391,15 +402,39 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
# Deleting the first polygon, and ensuring that
|
||||
# the second hole is now gone for good.
|
||||
del poly1
|
||||
del poly1, poly2
|
||||
self.assertRaises(GEOSException, str, hole2)
|
||||
|
||||
#### Testing creating Geometries w/"deep-indexed" rings ####
|
||||
mpoly = MultiPolygon(Polygon(LinearRing(ext_tup), LinearRing(itup1), LinearRing(itup2)),
|
||||
Polygon(LinearRing(itup1)))
|
||||
|
||||
r0 = mpoly[0][1] # itup1, left alone
|
||||
r1 = mpoly[0][0] # ext_tup
|
||||
r2 = mpoly[1][0] # itup1
|
||||
r3 = mpoly[0][2] # itup2
|
||||
|
||||
# Using the rings of the multipolygon to create a new Polygon, should
|
||||
# no longer be able to access the MultiPolygon or the ring objects
|
||||
# used in initialization.
|
||||
p1 = Polygon(r1, r2, r3)
|
||||
for g in [mpoly, r1, r2, r3]:
|
||||
self.assertRaises(GEOSException, len, g)
|
||||
self.assertRaises(GEOSException, str, g)
|
||||
|
||||
# However, the middle ring of the first Polygon was not used.
|
||||
self.assertEqual(r0.tuple, itup1)
|
||||
self.assertEqual(r0, p1[1])
|
||||
|
||||
# This deletes the leftover ring (or whenever mpoly is garbage collected)
|
||||
del mpoly
|
||||
|
||||
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)
|
||||
poly = fromstr(p.wkt)
|
||||
cs = poly.exterior_ring.coord_seq
|
||||
|
||||
self.assertEqual(p.ext_ring_cs, cs.tuple) # done in the Polygon test too.
|
||||
@ -423,13 +458,13 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
def test09_relate_pattern(self):
|
||||
"Testing relate() and relate_pattern()."
|
||||
g = GEOSGeometry('POINT (0 0)')
|
||||
g = fromstr('POINT (0 0)')
|
||||
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
|
||||
|
||||
for i in xrange(len(relate_geoms)):
|
||||
g_tup = relate_geoms[i]
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
a = fromstr(g_tup[0].wkt)
|
||||
b = fromstr(g_tup[1].wkt)
|
||||
pat = g_tup[2]
|
||||
result = g_tup[3]
|
||||
self.assertEqual(result, a.relate_pattern(b, pat))
|
||||
@ -439,9 +474,9 @@ class GEOSTest(unittest.TestCase):
|
||||
"Testing intersects() and intersection()."
|
||||
for i in xrange(len(topology_geoms)):
|
||||
g_tup = topology_geoms[i]
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
i1 = GEOSGeometry(intersect_geoms[i].wkt)
|
||||
a = fromstr(g_tup[0].wkt)
|
||||
b = fromstr(g_tup[1].wkt)
|
||||
i1 = fromstr(intersect_geoms[i].wkt)
|
||||
self.assertEqual(True, a.intersects(b))
|
||||
i2 = a.intersection(b)
|
||||
self.assertEqual(i1, i2)
|
||||
@ -453,9 +488,9 @@ class GEOSTest(unittest.TestCase):
|
||||
"Testing union()."
|
||||
for i in xrange(len(topology_geoms)):
|
||||
g_tup = topology_geoms[i]
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
u1 = GEOSGeometry(union_geoms[i].wkt)
|
||||
a = fromstr(g_tup[0].wkt)
|
||||
b = fromstr(g_tup[1].wkt)
|
||||
u1 = fromstr(union_geoms[i].wkt)
|
||||
u2 = a.union(b)
|
||||
self.assertEqual(u1, u2)
|
||||
self.assertEqual(u1, a | b) # __or__ is union operator
|
||||
@ -466,9 +501,9 @@ class GEOSTest(unittest.TestCase):
|
||||
"Testing difference()."
|
||||
for i in xrange(len(topology_geoms)):
|
||||
g_tup = topology_geoms[i]
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
d1 = GEOSGeometry(diff_geoms[i].wkt)
|
||||
a = fromstr(g_tup[0].wkt)
|
||||
b = fromstr(g_tup[1].wkt)
|
||||
d1 = fromstr(diff_geoms[i].wkt)
|
||||
d2 = a.difference(b)
|
||||
self.assertEqual(d1, d2)
|
||||
self.assertEqual(d1, a - b) # __sub__ is difference operator
|
||||
@ -479,9 +514,9 @@ class GEOSTest(unittest.TestCase):
|
||||
"Testing sym_difference()."
|
||||
for i in xrange(len(topology_geoms)):
|
||||
g_tup = topology_geoms[i]
|
||||
a = GEOSGeometry(g_tup[0].wkt)
|
||||
b = GEOSGeometry(g_tup[1].wkt)
|
||||
d1 = GEOSGeometry(sdiff_geoms[i].wkt)
|
||||
a = fromstr(g_tup[0].wkt)
|
||||
b = fromstr(g_tup[1].wkt)
|
||||
d1 = fromstr(sdiff_geoms[i].wkt)
|
||||
d2 = a.sym_difference(b)
|
||||
self.assertEqual(d1, d2)
|
||||
self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
|
||||
@ -492,10 +527,10 @@ class GEOSTest(unittest.TestCase):
|
||||
"Testing buffer()."
|
||||
for i in xrange(len(buffer_geoms)):
|
||||
g_tup = buffer_geoms[i]
|
||||
g = GEOSGeometry(g_tup[0].wkt)
|
||||
g = fromstr(g_tup[0].wkt)
|
||||
|
||||
# The buffer we expect
|
||||
exp_buf = GEOSGeometry(g_tup[1].wkt)
|
||||
exp_buf = fromstr(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]))
|
||||
@ -575,8 +610,8 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertNotEqual(pnt, mp[i])
|
||||
del mp
|
||||
|
||||
# Multipolygons involve much more memory management because each
|
||||
# polygon w/in the collection has its own rings.
|
||||
# MultiPolygons involve much more memory management because each
|
||||
# Polygon w/in the collection has its own rings.
|
||||
for tg in multipolygons:
|
||||
mpoly = fromstr(tg.wkt)
|
||||
for i in xrange(len(mpoly)):
|
||||
@ -592,12 +627,17 @@ class GEOSTest(unittest.TestCase):
|
||||
self.assertRaises(GEOSException, str, tmp)
|
||||
self.assertEqual(mpoly[i], new)
|
||||
self.assertNotEqual(poly, mpoly[i])
|
||||
del mpoly
|
||||
|
||||
# Extreme (!!) __setitem__
|
||||
mpoly[0][0][0] = (3.14, 2.71)
|
||||
self.assertEqual((3.14, 2.71), mpoly[0][0][0])
|
||||
# Doing it more slowly..
|
||||
self.assertEqual((3.14, 2.71), mpoly[0].shell[0])
|
||||
|
||||
del mpoly
|
||||
|
||||
def test17_threed(self):
|
||||
"Testing three-dimensional geometries."
|
||||
|
||||
# Testing a 3D Point
|
||||
pnt = Point(2, 3, 8)
|
||||
self.assertEqual((2.,3.,8.), pnt.coords)
|
||||
@ -632,7 +672,6 @@ class GEOSTest(unittest.TestCase):
|
||||
|
||||
def test19_length(self):
|
||||
"Testing the length property."
|
||||
|
||||
# Points have 0 length.
|
||||
pnt = Point(0, 0)
|
||||
self.assertEqual(0.0, pnt.length)
|
||||
|
Loading…
x
Reference in New Issue
Block a user