mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: big changes within in GEOS internals:
(1) All Geometry types and Collections now have their own constructor (e.g., Polygon(LinearRing(..)), MultiPoint(Point(..), Point(..))) (2) Memory management improved, laying the foundation for fully mutable geometries. (3) Added set-like operations (|, &, -, ^) (4) Added & improved tests. (5) docstring changes git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5742 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
8162e3543c
commit
e922445186
@ -30,8 +30,13 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from base import GEOSGeometry
|
from base import GEOSGeometry
|
||||||
from geometries import Point, LineString, LinearRing, HAS_NUMPY
|
from geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
|
||||||
from error import GEOSException
|
from collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||||
|
from error import GEOSException, GEOSGeometryIndexError
|
||||||
|
|
||||||
|
def fromstr(wkt_or_hex):
|
||||||
|
"Given a string value (wkt or hex), returns a GEOSGeometry object."
|
||||||
|
return GEOSGeometry(wkt_or_hex)
|
||||||
|
|
||||||
def hex_to_wkt(hex):
|
def hex_to_wkt(hex):
|
||||||
"Converts HEXEWKB into WKT."
|
"Converts HEXEWKB into WKT."
|
||||||
|
@ -1,35 +1,38 @@
|
|||||||
# Trying not to pollute the namespace.
|
"""
|
||||||
|
This module contains the 'base' GEOSGeometry object -- all GEOS geometries
|
||||||
|
inherit from this object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ctypes and types dependencies.
|
||||||
from ctypes import \
|
from ctypes import \
|
||||||
byref, string_at, create_string_buffer, pointer, \
|
byref, string_at, create_string_buffer, pointer, \
|
||||||
c_char_p, c_double, c_int, c_size_t
|
c_char_p, c_double, c_int, c_size_t
|
||||||
from types import StringType, IntType, FloatType, TupleType, ListType
|
from types import StringType, IntType, FloatType
|
||||||
|
|
||||||
# Getting GEOS-related dependencies.
|
# Python and GEOS-related dependencies.
|
||||||
import re
|
import re
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
|
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
|
||||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||||
|
if HAS_NUMPY: from numpy import ndarray, array
|
||||||
|
|
||||||
if HAS_NUMPY:
|
# Regular expression for recognizing HEXEWKB.
|
||||||
from numpy import ndarray, array
|
hex_regex = re.compile(r'^[0-9A-Fa-f]+$')
|
||||||
|
|
||||||
# For recognizing HEXEWKB.
|
|
||||||
hex_regex = re.compile(r'^[0-9A-Fa-f]+')
|
|
||||||
|
|
||||||
class GEOSGeometry(object):
|
class GEOSGeometry(object):
|
||||||
"A class that, generally, encapsulates a GEOS geometry."
|
"A class that, generally, encapsulates a GEOS geometry."
|
||||||
|
|
||||||
#### Python 'magic' routines ####
|
#### Python 'magic' routines ####
|
||||||
def __init__(self, geo_input, input_type=False, child=False):
|
def __init__(self, geo_input, input_type=False, parent=False):
|
||||||
"""The constructor for GEOS geometry objects. May take the following
|
"""The constructor for GEOS geometry objects. May take the following
|
||||||
strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form).
|
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 `input_type` keyword has been deprecated -- geometry type is now auto-detected.
|
||||||
|
|
||||||
The `child` keyword is for internal use only, and indicates to the garbage collector
|
The `parent` keyword is for internal use only, and indicates to the garbage collector
|
||||||
not to delete this geometry if it was spawned from a parent (e.g., the exterior
|
not to delete this geometry because it was spawned from a parent (e.g., the exterior
|
||||||
ring from a polygon).
|
ring from a polygon). Its value is the GEOSPointer of the parent geometry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Initially, setting the pointer to NULL
|
# Initially, setting the pointer to NULL
|
||||||
@ -55,84 +58,126 @@ class GEOSGeometry(object):
|
|||||||
raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input))
|
raise TypeError, 'Improper geometry input type: %s' % str(type(geo_input))
|
||||||
|
|
||||||
if bool(g):
|
if bool(g):
|
||||||
# If we have a GEOSPointer object, just set the '_ptr' attribute with g
|
# If we have a GEOSPointer object, just set the '_ptr' attribute with input
|
||||||
if isinstance(g, GEOSPointer): self._ptr = g
|
if isinstance(g, GEOSPointer): self._ptr = g
|
||||||
else: self._ptr.set(g) # Otherwise, set the address
|
else: self._ptr.set(g) # Otherwise, set with the address
|
||||||
else:
|
else:
|
||||||
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
|
raise GEOSException, 'Could not initialize GEOS Geometry with given input.'
|
||||||
|
|
||||||
# Setting the 'child' flag -- when the object is labeled with this flag
|
# 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 from
|
# 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.).
|
# parent geometries (e.g., LinearRings from a Polygon, Points from a MultiPoint, etc.).
|
||||||
self._child = child
|
if isinstance(parent, GEOSPointer):
|
||||||
|
self._parent = parent
|
||||||
|
else:
|
||||||
|
self._parent = GEOSPointer(0)
|
||||||
|
|
||||||
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
|
# Setting the class type (e.g., 'Point', 'Polygon', etc.)
|
||||||
self.__class__ = GEOS_CLASSES[self.geom_type]
|
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()
|
||||||
|
|
||||||
# Extra setup needed for Geometries that may be parents.
|
# Extra setup needed for Geometries that may be parents.
|
||||||
if isinstance(self, GeometryCollection): self._geoms = {}
|
if isinstance(self, (Polygon, GeometryCollection)): self._populate()
|
||||||
if isinstance(self, Polygon): self._rings = {}
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"Destroys this geometry -- only if the pointer is valid and this is not a child geometry."
|
"Destroys this geometry -- only if the pointer is valid and whether or not it belongs to a parent."
|
||||||
#print 'Deleting %s (child=%s, valid=%s)' % (self.geom_type, self._child, self._ptr.valid)
|
#print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||||
if self._ptr.valid and not self._child: lgeos.GEOSGeom_destroy(self._ptr())
|
# Only calling destroy on valid pointers not spawned from a parent
|
||||||
|
if self._ptr.valid and not self._parent: lgeos.GEOSGeom_destroy(self._ptr())
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"WKT is used for the string representation."
|
"WKT is used for the string representation."
|
||||||
return self.wkt
|
return self.wkt
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s object>' % self.geom_type
|
||||||
|
|
||||||
|
# Comparison operators
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"Equivalence testing."
|
"Equivalence testing."
|
||||||
return self.equals(other)
|
return self.equals(other)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
"The not equals operator."
|
||||||
|
return not self.equals(other)
|
||||||
|
|
||||||
|
### Geometry set-like operations ###
|
||||||
|
# Thanks to Sean Gillies for inspiration:
|
||||||
|
# http://lists.gispython.org/pipermail/community/2007-July/001034.html
|
||||||
|
# g = g1 | g2
|
||||||
|
def __or__(self, other):
|
||||||
|
"Returns the union of this Geometry and the other."
|
||||||
|
return self.union(other)
|
||||||
|
|
||||||
|
# g = g1 & g2
|
||||||
|
def __and__(self, other):
|
||||||
|
"Returns the intersection of this Geometry and the other."
|
||||||
|
return self.intersection(other)
|
||||||
|
|
||||||
|
# g = g1 - g2
|
||||||
|
def __sub__(self, other):
|
||||||
|
"Return the difference this Geometry and the other."
|
||||||
|
return self.difference(other)
|
||||||
|
|
||||||
|
# g = g1 ^ g2
|
||||||
|
def __xor__(self, other):
|
||||||
|
"Return the symmetric difference of this Geometry and the other."
|
||||||
|
return self.sym_difference(other)
|
||||||
|
|
||||||
#### Coordinate Sequence Routines ####
|
#### Coordinate Sequence Routines ####
|
||||||
def _cache_cs(self):
|
@property
|
||||||
"Caches the coordinate sequence for this Geometry."
|
def has_cs(self):
|
||||||
if not hasattr(self, '_cs'):
|
"Returns True if this Geometry has a coordinate sequence, False if not."
|
||||||
# Only these geometries are allowed to have coordinate sequences.
|
# Only these geometries are allowed to have coordinate sequences.
|
||||||
if self.geom_type in ('LineString', 'LinearRing', 'Point'):
|
if isinstance(self, (Point, LineString, LinearRing)):
|
||||||
self._cs = GEOSCoordSeq(GEOSPointer(lgeos.GEOSGeom_getCoordSeq(self._ptr())), self.hasz)
|
return True
|
||||||
else:
|
else:
|
||||||
self._cs = None
|
return False
|
||||||
|
|
||||||
|
def _get_cs(self):
|
||||||
|
"Gets the coordinate sequence for this Geometry."
|
||||||
|
if self.has_cs:
|
||||||
|
self._ptr.set(lgeos.GEOSGeom_getCoordSeq(self._ptr()), coordseq=True)
|
||||||
|
self._cs = GEOSCoordSeq(self._ptr, self.hasz)
|
||||||
|
else:
|
||||||
|
self._cs = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coord_seq(self):
|
def coord_seq(self):
|
||||||
"Returns the coordinate sequence for the geometry."
|
"Returns the coordinate sequence for this Geometry."
|
||||||
# Getting the coordinate sequence for the geometry
|
|
||||||
self._cache_cs()
|
|
||||||
|
|
||||||
# Returning a GEOSCoordSeq wrapped around the pointer.
|
|
||||||
return self._cs
|
return self._cs
|
||||||
|
|
||||||
#### Geometry Info ####
|
#### Geometry Info ####
|
||||||
@property
|
@property
|
||||||
def geom_type(self):
|
def geom_type(self):
|
||||||
"Returns a string representing the geometry type, e.g. 'Polygon'"
|
"Returns a string representing the Geometry type, e.g. 'Polygon'"
|
||||||
return string_at(lgeos.GEOSGeomType(self._ptr()))
|
return string_at(lgeos.GEOSGeomType(self._ptr()))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def geom_typeid(self):
|
def geom_typeid(self):
|
||||||
"Returns an integer representing the geometry type."
|
"Returns an integer representing the Geometry type."
|
||||||
return lgeos.GEOSGeomTypeId(self._ptr())
|
return lgeos.GEOSGeomTypeId(self._ptr())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def num_geom(self):
|
def num_geom(self):
|
||||||
"Returns the number of geometries in the geometry."
|
"Returns the number of geometries in the Geometry."
|
||||||
n = lgeos.GEOSGetNumGeometries(self._ptr())
|
n = lgeos.GEOSGetNumGeometries(self._ptr())
|
||||||
if n == -1: raise GEOSException, 'Error getting number of geometries.'
|
if n == -1: raise GEOSException, 'Error getting number of geometries.'
|
||||||
else: return n
|
else: return n
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def num_coords(self):
|
def num_coords(self):
|
||||||
"Returns the number of coordinates in the geometry."
|
"Returns the number of coordinates in the Geometry."
|
||||||
n = lgeos.GEOSGetNumCoordinates(self._ptr())
|
n = lgeos.GEOSGetNumCoordinates(self._ptr())
|
||||||
if n == -1: raise GEOSException, 'Error getting number of coordinates.'
|
if n == -1: raise GEOSException, 'Error getting number of coordinates.'
|
||||||
else: return n
|
else: return n
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def num_points(self):
|
def num_points(self):
|
||||||
"Returns the number points, or coordinates, in the geometry."
|
"Returns the number points, or coordinates, in the Geometry."
|
||||||
return self.num_coords
|
return self.num_coords
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -145,6 +190,7 @@ class GEOSGeometry(object):
|
|||||||
status = lgeos.GEOSNormalize(self._ptr())
|
status = lgeos.GEOSNormalize(self._ptr())
|
||||||
if status == -1: raise GEOSException, 'failed to normalize geometry'
|
if status == -1: raise GEOSException, 'failed to normalize geometry'
|
||||||
|
|
||||||
|
## Internal for GEOS unary & binary predicate functions ##
|
||||||
def _unary_predicate(self, func):
|
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())
|
val = func(self._ptr())
|
||||||
@ -164,12 +210,12 @@ class GEOSGeometry(object):
|
|||||||
#### Unary predicates ####
|
#### Unary predicates ####
|
||||||
@property
|
@property
|
||||||
def empty(self):
|
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)
|
return self._unary_predicate(lgeos.GEOSisEmpty)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
"This property tests the validity of this geometry."
|
"This property tests the validity of this Geometry."
|
||||||
return self._unary_predicate(lgeos.GEOSisValid)
|
return self._unary_predicate(lgeos.GEOSisValid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -190,17 +236,17 @@ class GEOSGeometry(object):
|
|||||||
#### Binary predicates. ####
|
#### Binary predicates. ####
|
||||||
def relate_pattern(self, other, pattern):
|
def relate_pattern(self, other, pattern):
|
||||||
"""Returns true if the elements in the DE-9IM intersection matrix for
|
"""Returns true if the elements in the DE-9IM intersection matrix for
|
||||||
the two Geometrys match the elements in pattern."""
|
the two Geometries match the elements in pattern."""
|
||||||
if len(pattern) > 9:
|
if len(pattern) > 9:
|
||||||
raise GEOSException, 'invalid intersection matrix pattern'
|
raise GEOSException, 'invalid intersection matrix pattern'
|
||||||
return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern))
|
return self._binary_predicate(lgeos.GEOSRelatePattern, other, c_char_p(pattern))
|
||||||
|
|
||||||
def disjoint(self, other):
|
def disjoint(self, other):
|
||||||
"Returns true if the DE-9IM intersection matrix for the two Geometrys 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)
|
return self._binary_predicate(lgeos.GEOSDisjoint, other)
|
||||||
|
|
||||||
def touches(self, other):
|
def touches(self, other):
|
||||||
"Returns true if the DE-9IM intersection matrix for the two Geometrys 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)
|
return self._binary_predicate(lgeos.GEOSTouches, other)
|
||||||
|
|
||||||
def intersects(self, other):
|
def intersects(self, other):
|
||||||
@ -208,12 +254,12 @@ class GEOSGeometry(object):
|
|||||||
return self._binary_predicate(lgeos.GEOSIntersects, other)
|
return self._binary_predicate(lgeos.GEOSIntersects, other)
|
||||||
|
|
||||||
def crosses(self, other):
|
def crosses(self, other):
|
||||||
"""Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T****** (for a point and a curve,
|
"""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)."""
|
a point and an area or a line and an area) 0******** (for two curves)."""
|
||||||
return self._binary_predicate(lgeos.GEOSCrosses, other)
|
return self._binary_predicate(lgeos.GEOSCrosses, other)
|
||||||
|
|
||||||
def within(self, other):
|
def within(self, other):
|
||||||
"Returns true if the DE-9IM intersection matrix for the two Geometrys 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)
|
return self._binary_predicate(lgeos.GEOSWithin, other)
|
||||||
|
|
||||||
def contains(self, other):
|
def contains(self, other):
|
||||||
@ -221,16 +267,16 @@ class GEOSGeometry(object):
|
|||||||
return self._binary_predicate(lgeos.GEOSContains, other)
|
return self._binary_predicate(lgeos.GEOSContains, other)
|
||||||
|
|
||||||
def overlaps(self, other):
|
def overlaps(self, other):
|
||||||
"""Returns true if the DE-9IM intersection matrix for the two Geometrys is T*T***T** (for two points
|
"""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)."""
|
or two surfaces) 1*T***T** (for two curves)."""
|
||||||
return self._binary_predicate(lgeos.GEOSOverlaps, other)
|
return self._binary_predicate(lgeos.GEOSOverlaps, other)
|
||||||
|
|
||||||
def equals(self, other):
|
def equals(self, other):
|
||||||
"Returns true if the DE-9IM intersection matrix for the two Geometrys 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)
|
return self._binary_predicate(lgeos.GEOSEquals, other)
|
||||||
|
|
||||||
def equals_exact(self, other, tolerance=0):
|
def equals_exact(self, other, tolerance=0):
|
||||||
"Returns true if the two Geometrys are exactly equal, up to a specified tolerance."
|
"Returns true if the two Geometries are exactly equal, up to a specified tolerance."
|
||||||
tol = c_double(tolerance)
|
tol = c_double(tolerance)
|
||||||
return self._binary_predicate(lgeos.GEOSEqualsExact, other, tol)
|
return self._binary_predicate(lgeos.GEOSEqualsExact, other, tol)
|
||||||
|
|
||||||
@ -290,7 +336,7 @@ class GEOSGeometry(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def centroid(self):
|
def centroid(self):
|
||||||
"""The centroid is equal to the centroid of the set of component Geometrys
|
"""The centroid is equal to the centroid of the set of component Geometries
|
||||||
of highest dimension (since the lower-dimension geometries contribute zero
|
of highest dimension (since the lower-dimension geometries contribute zero
|
||||||
"weight" to the centroid)."""
|
"weight" to the centroid)."""
|
||||||
return self._unary_topology(lgeos.GEOSGetCentroid)
|
return self._unary_topology(lgeos.GEOSGetCentroid)
|
||||||
@ -344,7 +390,7 @@ class GEOSGeometry(object):
|
|||||||
def clone(self):
|
def clone(self):
|
||||||
"Clones this Geometry."
|
"Clones this Geometry."
|
||||||
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()))
|
return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr()))
|
||||||
|
|
||||||
# Class mapping dictionary
|
# Class mapping dictionary
|
||||||
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
|
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
|
||||||
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||||
@ -352,8 +398,8 @@ GEOS_CLASSES = {'Point' : Point,
|
|||||||
'Polygon' : Polygon,
|
'Polygon' : Polygon,
|
||||||
'LineString' : LineString,
|
'LineString' : LineString,
|
||||||
'LinearRing' : LinearRing,
|
'LinearRing' : LinearRing,
|
||||||
'GeometryCollection' : GeometryCollection,
|
|
||||||
'MultiPoint' : MultiPoint,
|
'MultiPoint' : MultiPoint,
|
||||||
'MultiLineString' : MultiLineString,
|
'MultiLineString' : MultiLineString,
|
||||||
'MultiPolygon' : MultiPolygon,
|
'MultiPolygon' : MultiPolygon,
|
||||||
|
'GeometryCollection' : GeometryCollection,
|
||||||
}
|
}
|
||||||
|
@ -2,33 +2,94 @@
|
|||||||
This module houses the Geometry Collection objects:
|
This module houses the Geometry Collection objects:
|
||||||
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
||||||
"""
|
"""
|
||||||
from ctypes import c_int
|
from ctypes import c_int, c_uint, byref, cast
|
||||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer
|
from types import TupleType, ListType
|
||||||
|
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, init_from_geom, get_pointer_arr, GEOM_PTR
|
||||||
from django.contrib.gis.geos.base import GEOSGeometry
|
from django.contrib.gis.geos.base import GEOSGeometry
|
||||||
from django.contrib.gis.geos.error import GEOSException
|
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||||
|
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
|
||||||
|
|
||||||
|
def init_from_poly(poly):
|
||||||
|
"Internal routine used for initializing Geometry Collections from Polygons."
|
||||||
|
# Constructing a new Polygon to take control of the rings.
|
||||||
|
p = Polygon(*tuple(ring for ring in poly))
|
||||||
|
|
||||||
|
# If this Polygon came from a GeometryCollection, it is a child
|
||||||
|
# and the parent geometry pointer is nullified.
|
||||||
|
if poly._parent: poly._parent.nullify()
|
||||||
|
|
||||||
|
# Nullifying the polygon pointer
|
||||||
|
poly._ptr.nullify()
|
||||||
|
|
||||||
|
# Returning the address of the new Polygon.
|
||||||
|
return p._ptr()
|
||||||
|
|
||||||
class GeometryCollection(GEOSGeometry):
|
class GeometryCollection(GEOSGeometry):
|
||||||
|
_allowed = (Point, LineString, LinearRing, Polygon)
|
||||||
|
_typeid = 7
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
self._ptr = GEOSPointer(0) # Initially NULL
|
||||||
|
self._geoms = {}
|
||||||
|
self._parent = False
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
raise TypeError, 'Must provide at least one LinearRing to initialize Polygon.'
|
||||||
|
|
||||||
|
if len(args) == 1: # If only one geometry provided or a list of geometries is provided
|
||||||
|
if isinstance(args[0], (TupleType, ListType)):
|
||||||
|
init_geoms = args[0]
|
||||||
|
else:
|
||||||
|
init_geoms = args
|
||||||
|
else:
|
||||||
|
init_geoms = args
|
||||||
|
|
||||||
|
# Ensuring that only the permitted geometries are allowed in this collection
|
||||||
|
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
|
||||||
|
ngeom = len(init_geoms)
|
||||||
|
geoms = get_pointer_arr(ngeom)
|
||||||
|
|
||||||
|
# Incrementing through each input geometry.
|
||||||
|
for i in xrange(ngeom):
|
||||||
|
if isinstance(init_geoms[i], Polygon):
|
||||||
|
# Special care is taken when importing from Polygons
|
||||||
|
geoms[i] = cast(init_from_poly(init_geoms[i]), GEOM_PTR)
|
||||||
|
else:
|
||||||
|
geoms[i] = cast(init_from_geom(init_geoms[i]), 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)))
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"Override the GEOSGeometry delete routine to safely take care of any spawned geometries."
|
"Overloaded deletion method for Geometry Collections."
|
||||||
# Nullifying the pointers to internal geometries, preventing any attempted future access
|
#print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||||
for k in self._geoms: self._geoms[k].nullify()
|
# If this geometry is still valid, it hasn't been modified by others.
|
||||||
super(GeometryCollection, self).__del__() # Calling the parent __del__() method.
|
if self._ptr.valid:
|
||||||
|
# Nullifying pointers to internal geometries, preventing any attempted future access.
|
||||||
|
for k in self._geoms: self._geoms[k].nullify()
|
||||||
|
super(GeometryCollection, self).__del__()
|
||||||
|
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()
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"For indexing on the multiple geometries."
|
"For indexing on the multiple geometries."
|
||||||
|
# Checking the index and returning the corresponding GEOS geometry.
|
||||||
self._checkindex(index)
|
self._checkindex(index)
|
||||||
|
return GEOSGeometry(self._geoms[index], parent=self._ptr)
|
||||||
# Setting an entry in the _geoms dictionary for the requested geometry.
|
|
||||||
if not index in self._geoms:
|
|
||||||
self._geoms[index] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(index)))
|
|
||||||
|
|
||||||
# Cloning the GEOS Geometry first, before returning it.
|
|
||||||
return GEOSGeometry(self._geoms[index], child=True)
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"For iteration on the multiple geometries."
|
"For iteration on the multiple geometries."
|
||||||
for i in xrange(self.__len__()):
|
for i in xrange(len(self)):
|
||||||
yield self.__getitem__(i)
|
yield self.__getitem__(i)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
@ -40,7 +101,20 @@ class GeometryCollection(GEOSGeometry):
|
|||||||
if index < 0 or index >= self.num_geom:
|
if index < 0 or index >= self.num_geom:
|
||||||
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
|
raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index)
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
|
|
||||||
# MultiPoint, MultiLineString, and MultiPolygon class definitions.
|
# MultiPoint, MultiLineString, and MultiPolygon class definitions.
|
||||||
class MultiPoint(GeometryCollection): pass
|
class MultiPoint(GeometryCollection):
|
||||||
class MultiLineString(GeometryCollection): pass
|
_allowed = Point
|
||||||
class MultiPolygon(GeometryCollection): pass
|
_typeid = 4
|
||||||
|
class MultiLineString(GeometryCollection):
|
||||||
|
_allowed = (LineString, LinearRing)
|
||||||
|
_typeid = 5
|
||||||
|
class MultiPolygon(GeometryCollection):
|
||||||
|
_allowed = Polygon
|
||||||
|
_typeid = 6
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from django.contrib.gis.geos.libgeos import lgeos
|
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
|
||||||
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||||
from ctypes import c_double, c_int, c_uint, byref
|
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
|
This module houses the GEOSCoordSeq object, and is used internally
|
||||||
@ -14,6 +16,8 @@ class GEOSCoordSeq(object):
|
|||||||
#### Python 'magic' routines ####
|
#### Python 'magic' routines ####
|
||||||
def __init__(self, ptr, z=False):
|
def __init__(self, ptr, z=False):
|
||||||
"Initializes from a GEOS pointer."
|
"Initializes from a GEOS pointer."
|
||||||
|
if not isinstance(ptr, GEOSPointer):
|
||||||
|
raise TypeError, 'Coordinate sequence should initialize with a GEOSPointer.'
|
||||||
self._ptr = ptr
|
self._ptr = ptr
|
||||||
self._z = z
|
self._z = z
|
||||||
|
|
||||||
@ -39,6 +43,14 @@ class GEOSCoordSeq(object):
|
|||||||
|
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
"Can use the index [] operator to set coordinate sequence at an index."
|
"Can use the index [] operator to set coordinate sequence at an index."
|
||||||
|
# Checking the input value
|
||||||
|
if isinstance(value, (ListType, TupleType)):
|
||||||
|
pass
|
||||||
|
elif HAS_NUMPY and isinstance(value, ndarray):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise TypeError, 'Must set coordinate with a sequence (list, tuple, or numpy array).'
|
||||||
|
# Checking the dims of the input
|
||||||
if self.dims == 3 and self._z:
|
if self.dims == 3 and self._z:
|
||||||
n_args = 3
|
n_args = 3
|
||||||
set_3d = True
|
set_3d = True
|
||||||
@ -47,6 +59,7 @@ class GEOSCoordSeq(object):
|
|||||||
set_3d = False
|
set_3d = False
|
||||||
if len(value) != n_args:
|
if len(value) != n_args:
|
||||||
raise TypeError, 'Dimension of value does not match.'
|
raise TypeError, 'Dimension of value does not match.'
|
||||||
|
# Setting the X, Y, Z
|
||||||
self.setX(index, value[0])
|
self.setX(index, value[0])
|
||||||
self.setY(index, value[1])
|
self.setY(index, value[1])
|
||||||
if set_3d: self.setZ(index, value[2])
|
if set_3d: self.setZ(index, value[2])
|
||||||
@ -73,11 +86,11 @@ class GEOSCoordSeq(object):
|
|||||||
dim = c_uint(dimension)
|
dim = c_uint(dimension)
|
||||||
idx = c_uint(index)
|
idx = c_uint(index)
|
||||||
|
|
||||||
# 'd' is the value of the point, passed in by reference
|
# 'd' is the value of the ordinate, passed in by reference
|
||||||
d = c_double()
|
d = c_double()
|
||||||
status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr(), idx, dim, byref(d))
|
status = lgeos.GEOSCoordSeq_getOrdinate(self._ptr.coordseq(), idx, dim, byref(d))
|
||||||
if status == 0:
|
if status == 0:
|
||||||
raise GEOSException, 'could not retrieve %th ordinate at index: %s' % (str(dimension), str(index))
|
raise GEOSException, 'could not retrieve %sth ordinate at index: %s' % (dimension, index)
|
||||||
return d.value
|
return d.value
|
||||||
|
|
||||||
def setOrdinate(self, dimension, index, value):
|
def setOrdinate(self, dimension, index, value):
|
||||||
@ -90,7 +103,7 @@ class GEOSCoordSeq(object):
|
|||||||
idx = c_uint(index)
|
idx = c_uint(index)
|
||||||
|
|
||||||
# Setting the ordinate
|
# Setting the ordinate
|
||||||
status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr(), idx, dim, c_double(value))
|
status = lgeos.GEOSCoordSeq_setOrdinate(self._ptr.coordseq(), idx, dim, c_double(value))
|
||||||
if status == 0:
|
if status == 0:
|
||||||
raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index)
|
raise GEOSException, 'Could not set the ordinate for (dim, index): (%d, %d)' % (dimension, index)
|
||||||
|
|
||||||
@ -123,7 +136,7 @@ class GEOSCoordSeq(object):
|
|||||||
def size(self):
|
def size(self):
|
||||||
"Returns the size of this coordinate sequence."
|
"Returns the size of this coordinate sequence."
|
||||||
n = c_uint(0)
|
n = c_uint(0)
|
||||||
status = lgeos.GEOSCoordSeq_getSize(self._ptr(), byref(n))
|
status = lgeos.GEOSCoordSeq_getSize(self._ptr.coordseq(), byref(n))
|
||||||
if status == 0:
|
if status == 0:
|
||||||
raise GEOSException, 'Could not get CoordSeq size.'
|
raise GEOSException, 'Could not get CoordSeq size.'
|
||||||
return n.value
|
return n.value
|
||||||
@ -132,7 +145,7 @@ class GEOSCoordSeq(object):
|
|||||||
def dims(self):
|
def dims(self):
|
||||||
"Returns the dimensions of this coordinate sequence."
|
"Returns the dimensions of this coordinate sequence."
|
||||||
n = c_uint(0)
|
n = c_uint(0)
|
||||||
status = lgeos.GEOSCoordSeq_getDimensions(self._ptr(), byref(n))
|
status = lgeos.GEOSCoordSeq_getDimensions(self._ptr.coordseq(), byref(n))
|
||||||
if status == 0:
|
if status == 0:
|
||||||
raise GEOSException, 'Could not get CoordSeq dimensions.'
|
raise GEOSException, 'Could not get CoordSeq dimensions.'
|
||||||
return n.value
|
return n.value
|
||||||
@ -146,7 +159,7 @@ class GEOSCoordSeq(object):
|
|||||||
@property
|
@property
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"Clones this coordinate sequence."
|
"Clones this coordinate sequence."
|
||||||
pass
|
return GEOSCoordSeq(GEOSPointer(0, lgeos.GEOSCoordSeq_clone(self._ptr.coordseq())), self.hasz)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tuple(self):
|
def tuple(self):
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
from ctypes import c_double, c_int, c_uint
|
"""
|
||||||
|
This module houses the Point, LineString, LinearRing, and Polygon OGC
|
||||||
|
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 types import FloatType, IntType, ListType, TupleType
|
||||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs
|
||||||
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY
|
from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, get_pointer_arr, init_from_geom, GEOM_PTR, HAS_NUMPY
|
||||||
from django.contrib.gis.geos.base import GEOSGeometry
|
from django.contrib.gis.geos.base import GEOSGeometry
|
||||||
from django.contrib.gis.geos.error import GEOSException
|
from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError
|
||||||
|
if HAS_NUMPY: from numpy import ndarray, array
|
||||||
if HAS_NUMPY:
|
|
||||||
from numpy import ndarray, array
|
|
||||||
|
|
||||||
class Point(GEOSGeometry):
|
class Point(GEOSGeometry):
|
||||||
|
|
||||||
@ -17,6 +21,8 @@ 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self._ptr = GEOSPointer(0) # Initially NULL
|
||||||
|
|
||||||
if isinstance(x, (TupleType, ListType)):
|
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)
|
ndim = len(x)
|
||||||
@ -52,14 +58,17 @@ class Point(GEOSGeometry):
|
|||||||
# Initializing from the geometry, and getting a Python object
|
# Initializing from the geometry, and getting a Python object
|
||||||
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs))
|
super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"Returns the number of dimensions for this Point (either 2 or 3)."
|
||||||
|
if self.hasz: return 3
|
||||||
|
else: return 2
|
||||||
|
|
||||||
def _getOrdinate(self, dim, idx):
|
def _getOrdinate(self, dim, idx):
|
||||||
"The coordinate sequence getOrdinate() wrapper."
|
"The coordinate sequence getOrdinate() wrapper."
|
||||||
self._cache_cs()
|
|
||||||
return self._cs.getOrdinate(dim, idx)
|
return self._cs.getOrdinate(dim, idx)
|
||||||
|
|
||||||
def _setOrdinate(self, dim, idx, value):
|
def _setOrdinate(self, dim, idx, value):
|
||||||
"The coordinate sequence setOrdinate() wrapper."
|
"The coordinate sequence setOrdinate() wrapper."
|
||||||
self._cache_cs()
|
|
||||||
self._cs.setOrdinate(dim, idx, value)
|
self._cs.setOrdinate(dim, idx, value)
|
||||||
|
|
||||||
def get_x(self):
|
def get_x(self):
|
||||||
@ -100,38 +109,50 @@ class Point(GEOSGeometry):
|
|||||||
### Tuple setting and retrieval routines. ###
|
### Tuple setting and retrieval routines. ###
|
||||||
def get_tuple(self):
|
def get_tuple(self):
|
||||||
"Returns a tuple of the point."
|
"Returns a tuple of the point."
|
||||||
self._cache_cs()
|
|
||||||
return self._cs.tuple
|
return self._cs.tuple
|
||||||
|
|
||||||
def set_tuple(self):
|
def set_tuple(self, tup):
|
||||||
"Sets the tuple for this point object."
|
"Sets the coordinates of the point with the given tuple."
|
||||||
pass
|
self._cs[0] = tup
|
||||||
|
|
||||||
# The tuple property
|
# The tuple property
|
||||||
tuple = property(get_tuple, set_tuple)
|
tuple = property(get_tuple, set_tuple)
|
||||||
|
|
||||||
class LineString(GEOSGeometry):
|
class LineString(GEOSGeometry):
|
||||||
|
|
||||||
#### Python 'magic' routines ####
|
#### Python 'magic' routines ####
|
||||||
def __init__(self, coords, ring=False):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initializes on the given sequence, may take lists, tuples, or NumPy arrays
|
"""Initializes on the given sequence -- may take lists, tuples, NumPy arrays
|
||||||
of X,Y pairs."""
|
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))
|
||||||
|
ls = LineString([(1, 1), (2, 2)])
|
||||||
|
ls = LineString(array([(1, 1), (2, 2)]))
|
||||||
|
ls = LineString(Point(1, 1), Point(2, 2))
|
||||||
|
"""
|
||||||
|
self._ptr = GEOSPointer(0) # Initially NULL
|
||||||
|
|
||||||
|
# If only one argument was provided, then set the coords array appropriately
|
||||||
|
if len(args) == 1: coords = args[0]
|
||||||
|
else: coords = args
|
||||||
|
|
||||||
if isinstance(coords, (TupleType, ListType)):
|
if isinstance(coords, (TupleType, ListType)):
|
||||||
|
# Getting the number of coords and the number of dimensions -- which
|
||||||
|
# must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
|
||||||
ncoords = len(coords)
|
ncoords = len(coords)
|
||||||
first = True
|
if coords: ndim = len(coords[0])
|
||||||
for coord in coords:
|
else: raise TypeError, 'Cannot initialize on empty sequence.'
|
||||||
if not isinstance(coord, (TupleType, ListType)):
|
self._checkdim(ndim)
|
||||||
|
# Incrementing through each of the coordinates and verifying
|
||||||
|
for i in xrange(1, ncoords):
|
||||||
|
if not isinstance(coords[i], (TupleType, ListType, Point)):
|
||||||
raise TypeError, 'each coordinate should be a sequence (list or tuple)'
|
raise TypeError, 'each coordinate should be a sequence (list or tuple)'
|
||||||
if first:
|
if len(coords[i]) != ndim: raise TypeError, 'Dimension mismatch.'
|
||||||
ndim = len(coord)
|
|
||||||
self._checkdim(ndim)
|
|
||||||
first = False
|
|
||||||
else:
|
|
||||||
if len(coord) != ndim: raise TypeError, 'Dimension mismatch.'
|
|
||||||
numpy_coords = False
|
numpy_coords = False
|
||||||
elif HAS_NUMPY and isinstance(coords, ndarray):
|
elif HAS_NUMPY and isinstance(coords, ndarray):
|
||||||
shape = coords.shape
|
shape = coords.shape # Using numpy's shape.
|
||||||
if len(shape) != 2: raise TypeError, 'Too many dimensions.'
|
if len(shape) != 2: raise TypeError, 'Too many dimensions.'
|
||||||
self._checkdim(shape[1])
|
self._checkdim(shape[1])
|
||||||
ncoords = shape[0]
|
ncoords = shape[0]
|
||||||
@ -141,40 +162,38 @@ class LineString(GEOSGeometry):
|
|||||||
raise TypeError, 'Invalid initialization input for LineStrings.'
|
raise TypeError, 'Invalid initialization input for LineStrings.'
|
||||||
|
|
||||||
# Creating the coordinate sequence
|
# Creating the coordinate sequence
|
||||||
cs = GEOSCoordSeq(GEOSPointer(create_cs(c_uint(ncoords), c_uint(ndim))))
|
cs = GEOSCoordSeq(GEOSPointer(0, create_cs(c_uint(ncoords), c_uint(ndim))))
|
||||||
|
|
||||||
# Setting each point in the coordinate sequence
|
# Setting each point in the coordinate sequence
|
||||||
for i in xrange(ncoords):
|
for i in xrange(ncoords):
|
||||||
if numpy_coords: cs[i] = coords[i,:]
|
if numpy_coords: cs[i] = coords[i,:]
|
||||||
|
elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
|
||||||
else: cs[i] = coords[i]
|
else: cs[i] = coords[i]
|
||||||
|
|
||||||
# Getting the initialization function
|
# Getting the initialization function
|
||||||
if ring:
|
if kwargs.get('ring', False):
|
||||||
func = lgeos.GEOSGeom_createLinearRing
|
func = lgeos.GEOSGeom_createLinearRing
|
||||||
else:
|
else:
|
||||||
func = lgeos.GEOSGeom_createLineString
|
func = lgeos.GEOSGeom_createLineString
|
||||||
|
|
||||||
# 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()))
|
super(LineString, self).__init__(func(cs._ptr.coordseq()))
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"Gets the point at the specified index."
|
"Gets the point at the specified index."
|
||||||
self._cache_cs()
|
|
||||||
return self._cs[index]
|
return self._cs[index]
|
||||||
|
|
||||||
def __setitem__(self, index, value):
|
def __setitem__(self, index, value):
|
||||||
"Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
|
"Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
|
||||||
self._cache_cs()
|
|
||||||
self._cs[index] = value
|
self._cs[index] = value
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"Allows iteration over this LineString."
|
"Allows iteration over this LineString."
|
||||||
for i in xrange(self.__len__()):
|
for i in xrange(self.__len__()):
|
||||||
yield self.__getitem__(index)
|
yield self.__getitem__(i)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"Returns the number of points in this LineString."
|
"Returns the number of points in this LineString."
|
||||||
self._cache_cs()
|
|
||||||
return len(self._cs)
|
return len(self._cs)
|
||||||
|
|
||||||
def _checkdim(self, dim):
|
def _checkdim(self, dim):
|
||||||
@ -184,55 +203,103 @@ class LineString(GEOSGeometry):
|
|||||||
@property
|
@property
|
||||||
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."
|
||||||
self._cache_cs()
|
|
||||||
return self._cs.tuple
|
return self._cs.tuple
|
||||||
|
|
||||||
def _listarr(self, func):
|
def _listarr(self, func):
|
||||||
"""Internal routine that returns a sequence (list) corresponding with
|
"""Internal routine that returns a sequence (list) corresponding with
|
||||||
the given function. Will return a numpy array if possible."""
|
the given function. Will return a numpy array if possible."""
|
||||||
lst = [func(i) for i in xrange(self.__len__())] # constructing the list, using the function
|
lst = [func(i) for i in xrange(len(self))] # constructing the list, using the function
|
||||||
if HAS_NUMPY: return array(lst) # ARRRR!
|
if HAS_NUMPY: return array(lst) # ARRRR!
|
||||||
else: return lst
|
else: return lst
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def array(self):
|
def array(self):
|
||||||
"Returns a numpy array for the LineString."
|
"Returns a numpy array for the LineString."
|
||||||
self._cache_cs()
|
|
||||||
return self._listarr(self._cs.__getitem__)
|
return self._listarr(self._cs.__getitem__)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def x(self):
|
def x(self):
|
||||||
"Returns a list or numpy array of the X variable."
|
"Returns a list or numpy array of the X variable."
|
||||||
self._cache_cs()
|
|
||||||
return self._listarr(self._cs.getX)
|
return self._listarr(self._cs.getX)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def y(self):
|
def y(self):
|
||||||
"Returns a list or numpy array of the Y variable."
|
"Returns a list or numpy array of the Y variable."
|
||||||
self._cache_cs()
|
|
||||||
return self._listarr(self._cs.getY)
|
return self._listarr(self._cs.getY)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def z(self):
|
def z(self):
|
||||||
"Returns a list or numpy array of the Z variable."
|
"Returns a list or numpy array of the Z variable."
|
||||||
self._cache_cs()
|
|
||||||
if not self.hasz: return None
|
if not self.hasz: return None
|
||||||
else: return self._listarr(self._cs.getZ)
|
else: return self._listarr(self._cs.getZ)
|
||||||
|
|
||||||
# LinearRings are LineStrings used within Polygons.
|
# LinearRings are LineStrings used within Polygons.
|
||||||
class LinearRing(LineString):
|
class LinearRing(LineString):
|
||||||
def __init__(self, coords):
|
def __init__(self, *args):
|
||||||
"Overriding the initialization function to set the ring keyword."
|
"Overriding the initialization function to set the ring keyword."
|
||||||
super(LinearRing, self).__init__(coords, ring=True)
|
kwargs = {'ring' : True}
|
||||||
|
super(LinearRing, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
class Polygon(GEOSGeometry):
|
class Polygon(GEOSGeometry):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""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._rings = {}
|
||||||
|
if not args:
|
||||||
|
raise TypeError, 'Must provide at list one LinearRing instance to initialize Polygon.'
|
||||||
|
|
||||||
|
# Getting the ext_ring and init_holes parameters from the argument list
|
||||||
|
ext_ring = args[0]
|
||||||
|
init_holes = args[1:]
|
||||||
|
if len(init_holes) == 1 and isinstance(init_holes[0], (TupleType, ListType)):
|
||||||
|
init_holes = init_holes[0]
|
||||||
|
|
||||||
|
# Ensuring the exterior ring parameter is a LinearRing object
|
||||||
|
if not isinstance(ext_ring, LinearRing):
|
||||||
|
raise TypeError, 'First argument for Polygon initialization must be a LinearRing.'
|
||||||
|
|
||||||
|
# Making sure all of the holes are LinearRing objects
|
||||||
|
if False in [isinstance(hole, LinearRing) for hole in init_holes]:
|
||||||
|
raise TypeError, 'Holes parameter must be a sequence of LinearRings.'
|
||||||
|
|
||||||
|
# Getting the holes
|
||||||
|
nholes = len(init_holes)
|
||||||
|
holes = get_pointer_arr(nholes)
|
||||||
|
for i in xrange(nholes):
|
||||||
|
# Casting to the Geometry Pointer type
|
||||||
|
holes[i] = cast(init_from_geom(init_holes[i]), GEOM_PTR)
|
||||||
|
|
||||||
|
# Getting the shell pointer address,
|
||||||
|
shell = init_from_geom(ext_ring)
|
||||||
|
|
||||||
|
# Calling with the GEOS createPolygon factory.
|
||||||
|
super(Polygon, self).__init__(lgeos.GEOSGeom_createPolygon(shell, byref(holes), c_uint(nholes)))
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"Override the GEOSGeometry delete routine to safely take care of any spawned rings."
|
"Overloaded deletion method for Polygons."
|
||||||
# Nullifying the pointers to internal rings, preventing any attempted future access
|
#print 'Deleting %s (parent=%s, valid=%s)' % (self.__class__.__name__, self._parent, self._ptr.valid)
|
||||||
for k in self._rings: self._rings[k].nullify()
|
# If this geometry is still valid, it hasn't been modified by others.
|
||||||
super(Polygon, self).__del__() # Calling the parent __del__() method.
|
if self._ptr.valid:
|
||||||
|
# Nulling the pointers to internal rings, preventing any attempted future access
|
||||||
|
for k in self._rings: self._rings[k].nullify()
|
||||||
|
super(Polygon, self).__del__()
|
||||||
|
else:
|
||||||
|
# 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.
|
||||||
|
for k in self._rings:
|
||||||
|
if self._rings[k].valid:
|
||||||
|
lgeos.GEOSGeom_destroy(self._rings[k].address)
|
||||||
|
self._rings[k].nullify()
|
||||||
|
|
||||||
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 always
|
||||||
return the exterior ring. Indices > 0 will return the interior ring."""
|
return the exterior ring. Indices > 0 will return the interior ring."""
|
||||||
@ -247,13 +314,22 @@ class Polygon(GEOSGeometry):
|
|||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"Iterates over each ring in the polygon."
|
"Iterates over each ring in the polygon."
|
||||||
for i in xrange(self.__len__()):
|
for i in xrange(len(self)):
|
||||||
yield self.__getitem__(i)
|
yield self.__getitem__(i)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"Returns the number of rings in this Polygon."
|
"Returns the number of rings in this Polygon."
|
||||||
return self.num_interior_rings + 1
|
return self.num_interior_rings + 1
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
def get_interior_ring(self, ring_i):
|
def get_interior_ring(self, ring_i):
|
||||||
"""Gets the interior ring at the specified index,
|
"""Gets the interior ring at the specified index,
|
||||||
0 is for the first interior ring, not the exterior ring."""
|
0 is for the first interior ring, not the exterior ring."""
|
||||||
@ -262,13 +338,9 @@ class Polygon(GEOSGeometry):
|
|||||||
if ring_i < 0 or ring_i >= self.num_interior_rings:
|
if ring_i < 0 or ring_i >= self.num_interior_rings:
|
||||||
raise IndexError, 'ring index out of range'
|
raise IndexError, 'ring index out of range'
|
||||||
|
|
||||||
# Placing the ring in internal rings dictionary.
|
# Returning the ring from the internal ring dictionary (have to
|
||||||
idx = ring_i+1 # the index for the polygon is +1 because of the exterior ring
|
# add one to the index)
|
||||||
if not idx in self._rings:
|
return GEOSGeometry(self._rings[ring_i+1], parent=self._ptr)
|
||||||
self._rings[idx] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(ring_i)))
|
|
||||||
|
|
||||||
# Returning the ring at the given index.
|
|
||||||
return GEOSGeometry(self._rings[idx], child=True)
|
|
||||||
|
|
||||||
#### Polygon Properties ####
|
#### Polygon Properties ####
|
||||||
@property
|
@property
|
||||||
@ -282,17 +354,18 @@ class Polygon(GEOSGeometry):
|
|||||||
if n == -1: raise GEOSException, 'Error getting the number of interior rings.'
|
if n == -1: raise GEOSException, 'Error getting the number of interior rings.'
|
||||||
else: return n
|
else: return n
|
||||||
|
|
||||||
@property
|
def get_ext_ring(self):
|
||||||
def exterior_ring(self):
|
|
||||||
"Gets the exterior ring of the Polygon."
|
"Gets the exterior ring of the Polygon."
|
||||||
# Returns exterior ring
|
return GEOSGeometry(self._rings[0], parent=self._ptr)
|
||||||
self._rings[0] = GEOSPointer(lgeos.GEOSGetExteriorRing((self._ptr())))
|
|
||||||
return GEOSGeometry(self._rings[0], child=True)
|
|
||||||
|
|
||||||
@property
|
def set_ext_ring(self):
|
||||||
def shell(self):
|
"Sets the exterior ring of the Polygon."
|
||||||
"Gets the shell (exterior ring) of the Polygon."
|
# Sets the exterior ring
|
||||||
return self.exterior_ring
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# properties for the exterior ring/shell
|
||||||
|
exterior_ring = property(get_ext_ring)
|
||||||
|
shell = property(get_ext_ring)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tuple(self):
|
def tuple(self):
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.gis.geos.error import GEOSException
|
from django.contrib.gis.geos.error import GEOSException
|
||||||
from ctypes import \
|
from ctypes import c_char_p, c_int, pointer, CDLL, CFUNCTYPE, POINTER, Structure
|
||||||
c_char_p, c_int, c_size_t, c_ubyte, pointer, addressof, \
|
|
||||||
CDLL, CFUNCTYPE, POINTER, Structure
|
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
||||||
# NumPy supported?
|
# NumPy supported?
|
||||||
@ -64,29 +62,55 @@ error_h = ERRORFUNC(error_h)
|
|||||||
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
|
# "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
|
||||||
lgeos.initGEOS(notice_h, error_h)
|
lgeos.initGEOS(notice_h, error_h)
|
||||||
|
|
||||||
|
#### GEOS Geometry Pointer utilities. ####
|
||||||
|
|
||||||
|
# Opaque GEOS geometry structure
|
||||||
|
class GEOSGeom_t(Structure):
|
||||||
|
"Opaque structure used when arrays of geometries are needed as parameters."
|
||||||
|
pass
|
||||||
|
# Pointer to opaque geometry structure
|
||||||
|
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
#### GEOS Pointer object and routines ####
|
||||||
class GEOSPointer(object):
|
class GEOSPointer(object):
|
||||||
"""The GEOSPointer provides a layer of abstraction to accessing the values returned by
|
"""The GEOSPointer provides a layer of abstraction in accessing the values returned by
|
||||||
GEOS geometry creation routines."""
|
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."""
|
||||||
|
|
||||||
### Python 'magic' routines ###
|
### Python 'magic' routines ###
|
||||||
def __init__(self, ptr):
|
def __init__(self, address, coordseq=0):
|
||||||
"Given a ctypes pointer(c_int)"
|
"Initializes on an address (an integer)."
|
||||||
if isinstance(ptr, int):
|
if isinstance(address, int):
|
||||||
self._ptr = pointer(c_int(ptr))
|
self._geom = pointer(c_int(address))
|
||||||
|
self._coordseq = pointer(c_int(coordseq))
|
||||||
else:
|
else:
|
||||||
raise TypeError, 'GEOSPointer object must initialize with an integer.'
|
raise TypeError, 'GEOSPointer object must initialize with an integer.'
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""If the pointer is NULL, then an exception will be raised, otherwise the
|
"""If the pointer is NULL, then an exception will be raised, otherwise the
|
||||||
address value (a GEOSGeom_ptr) will be returned."""
|
address value (an integer) will be returned."""
|
||||||
if self.valid: return self.address
|
if self.valid: return self.address
|
||||||
else: raise GEOSException, 'GEOS pointer no longer valid (was the parent geometry deleted?)'
|
else: raise GEOSException, 'GEOS pointer no longer valid (was this geometry or the parent geometry deleted or modified?)'
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
"Returns True when the GEOSPointer is valid."
|
||||||
|
return self.valid
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.address)
|
||||||
|
|
||||||
### GEOSPointer Properties ###
|
### GEOSPointer Properties ###
|
||||||
@property
|
@property
|
||||||
def address(self):
|
def address(self):
|
||||||
"Returns the address of the GEOSPointer (represented as an integer)."
|
"Returns the address of the GEOSPointer (represented as an integer)."
|
||||||
return self._ptr.contents.value
|
return self._geom.contents.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
@ -94,13 +118,55 @@ class GEOSPointer(object):
|
|||||||
if bool(self.address): return True
|
if bool(self.address): return True
|
||||||
else: return False
|
else: return False
|
||||||
|
|
||||||
|
### Coordinate Sequence 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 ###
|
### GEOSPointer Methods ###
|
||||||
def set(self, address):
|
def set(self, address, coordseq=False):
|
||||||
"Sets this pointer with the new address (represented as an integer)"
|
"Sets this pointer with the new address (represented as an integer)"
|
||||||
if not isinstance(address, int):
|
if not isinstance(address, int):
|
||||||
raise TypeError, 'GEOSPointer must be set with an address (an integer).'
|
raise TypeError, 'GEOSPointer must be set with an address (an integer).'
|
||||||
self._ptr.contents = c_int(address)
|
if coordseq:
|
||||||
|
self._coordseq.contents = c_int(address)
|
||||||
|
else:
|
||||||
|
self._geom.contents = c_int(address)
|
||||||
|
|
||||||
def nullify(self):
|
def nullify(self):
|
||||||
"Nullify this geometry pointer (set the address to 0)."
|
"""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)
|
||||||
|
self.set(0, coordseq=True)
|
||||||
|
|
||||||
|
def init_from_geom(geom):
|
||||||
|
"""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."""
|
||||||
|
# First getting the memory address of the geometry.
|
||||||
|
address = geom._ptr()
|
||||||
|
|
||||||
|
# If the geometry is a child geometry, then the parent geometry pointer is
|
||||||
|
# nullified.
|
||||||
|
if geom._parent: geom._parent.nullify()
|
||||||
|
|
||||||
|
# Nullifying the geometry pointer
|
||||||
|
geom._ptr.nullify()
|
||||||
|
|
||||||
|
return address
|
||||||
|
@ -109,17 +109,26 @@ multilinestrings = (TestGeom('MULTILINESTRING ((0 0, 0 100), (100 0, 100 100))',
|
|||||||
topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0, -5.0 0.0))'),
|
topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0, -5.0 0.0))'),
|
||||||
TestGeom('POLYGON ((0.0 -5.0, 0.0 5.0, 10.0 5.0, 10.0 -5.0, 0.0 -5.0))')
|
TestGeom('POLYGON ((0.0 -5.0, 0.0 5.0, 10.0 5.0, 10.0 -5.0, 0.0 -5.0))')
|
||||||
),
|
),
|
||||||
|
(TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'),
|
||||||
|
TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'),
|
intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'),
|
||||||
|
TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))')
|
||||||
)
|
)
|
||||||
|
|
||||||
union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'),
|
union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'),
|
||||||
|
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'),
|
||||||
)
|
)
|
||||||
|
|
||||||
diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'),
|
diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'),
|
||||||
|
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sdiff_geoms = ( TestGeom('MULTIPOLYGON (((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0)),((0 0,5 0,5 5,10 5,10 -5,0 -5,0 0)))'),
|
||||||
|
TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'),
|
||||||
|
)
|
||||||
|
|
||||||
relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),
|
relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'),
|
||||||
TestGeom('MULTIPOINT(80 170, 140 120, 200 80, 80 70)'),
|
TestGeom('MULTIPOINT(80 170, 140 120, 200 80, 80 70)'),
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException, Point, LineString, LinearRing, HAS_NUMPY
|
from django.contrib.gis.geos import \
|
||||||
|
GEOSException, GEOSGeometryIndexError, \
|
||||||
|
GEOSGeometry, Point, LineString, LinearRing, Polygon, \
|
||||||
|
MultiPoint, MultiLineString, MultiPolygon, GeometryCollection, \
|
||||||
|
fromstr, HAS_NUMPY
|
||||||
from geometries import *
|
from geometries import *
|
||||||
|
|
||||||
if HAS_NUMPY:
|
if HAS_NUMPY: from numpy import array
|
||||||
from numpy import array
|
|
||||||
|
|
||||||
class GEOSTest(unittest.TestCase):
|
class GEOSTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -52,10 +55,12 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(p.z, pnt.z)
|
self.assertEqual(p.z, pnt.z)
|
||||||
self.assertEqual(p.z, pnt.tuple[2], 9)
|
self.assertEqual(p.z, pnt.tuple[2], 9)
|
||||||
tup_args = (p.x, p.y, p.z)
|
tup_args = (p.x, p.y, p.z)
|
||||||
|
set_tup = (2.71, 3.14, 5.23)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(False, pnt.hasz)
|
self.assertEqual(False, pnt.hasz)
|
||||||
self.assertEqual(None, pnt.z)
|
self.assertEqual(None, pnt.z)
|
||||||
tup_args = (p.x, p.y)
|
tup_args = (p.x, p.y)
|
||||||
|
set_tup = (2.71, 3.14)
|
||||||
|
|
||||||
# Centroid operation on point should be point itself
|
# Centroid operation on point should be point itself
|
||||||
self.assertEqual(p.centroid, pnt.centroid.tuple)
|
self.assertEqual(p.centroid, pnt.centroid.tuple)
|
||||||
@ -71,6 +76,11 @@ class GEOSTest(unittest.TestCase):
|
|||||||
pnt.x = 2.71
|
pnt.x = 2.71
|
||||||
self.assertEqual(3.14, pnt.y)
|
self.assertEqual(3.14, pnt.y)
|
||||||
self.assertEqual(2.71, pnt.x)
|
self.assertEqual(2.71, pnt.x)
|
||||||
|
|
||||||
|
# Setting via the tuple property
|
||||||
|
pnt.tuple = set_tup
|
||||||
|
self.assertEqual(set_tup, pnt.tuple)
|
||||||
|
|
||||||
prev = pnt # setting the previous geometry
|
prev = pnt # setting the previous geometry
|
||||||
|
|
||||||
def test02b_multipoints(self):
|
def test02b_multipoints(self):
|
||||||
@ -83,6 +93,7 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
|
self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
|
||||||
self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
|
self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
|
||||||
|
|
||||||
|
self.assertRaises(GEOSGeometryIndexError, mpnt.__getitem__, len(mpnt))
|
||||||
self.assertEqual(mp.centroid, mpnt.centroid.tuple)
|
self.assertEqual(mp.centroid, mpnt.centroid.tuple)
|
||||||
self.assertEqual(mp.points, tuple(m.tuple for m in mpnt))
|
self.assertEqual(mp.points, tuple(m.tuple for m in mpnt))
|
||||||
for p in mpnt:
|
for p in mpnt:
|
||||||
@ -107,17 +118,15 @@ class GEOSTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
|
self.assertEqual(True, ls == GEOSGeometry(l.wkt))
|
||||||
self.assertEqual(False, ls == prev)
|
self.assertEqual(False, ls == prev)
|
||||||
|
self.assertRaises(GEOSGeometryIndexError, ls.__getitem__, len(ls))
|
||||||
prev = ls
|
prev = ls
|
||||||
|
|
||||||
# Creating a LineString from a tuple, list, and numpy array
|
# Creating a LineString from a tuple, list, and numpy array
|
||||||
ls2 = LineString(ls.tuple)
|
self.assertEqual(ls, LineString(ls.tuple)) # tuple
|
||||||
self.assertEqual(ls, ls2)
|
self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
|
||||||
ls3 = LineString([list(tup) for tup in ls.tuple])
|
self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
|
||||||
self.assertEqual(ls, ls3)
|
self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
|
||||||
if HAS_NUMPY:
|
if HAS_NUMPY: self.assertEqual(ls, LineString(array(ls.tuple))) # as numpy array
|
||||||
ls4 = LineString(array(ls.tuple))
|
|
||||||
self.assertEqual(ls, ls4)
|
|
||||||
|
|
||||||
def test03b_multilinestring(self):
|
def test03b_multilinestring(self):
|
||||||
"Testing MultiLineString objects."
|
"Testing MultiLineString objects."
|
||||||
@ -139,7 +148,11 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(ls.geom_typeid, 1)
|
self.assertEqual(ls.geom_typeid, 1)
|
||||||
self.assertEqual(ls.empty, False)
|
self.assertEqual(ls.empty, False)
|
||||||
|
|
||||||
def test04a_linearring(self):
|
self.assertRaises(GEOSGeometryIndexError, ml.__getitem__, len(ml))
|
||||||
|
self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
|
||||||
|
self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
|
||||||
|
|
||||||
|
def test04_linearring(self):
|
||||||
"Testing LinearRing objects."
|
"Testing LinearRing objects."
|
||||||
for rr in linearrings:
|
for rr in linearrings:
|
||||||
lr = GEOSGeometry(rr.wkt)
|
lr = GEOSGeometry(rr.wkt)
|
||||||
@ -150,13 +163,10 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(False, lr.empty)
|
self.assertEqual(False, lr.empty)
|
||||||
|
|
||||||
# Creating a LinearRing from a tuple, list, and numpy array
|
# Creating a LinearRing from a tuple, list, and numpy array
|
||||||
lr2 = LinearRing(lr.tuple)
|
self.assertEqual(lr, LinearRing(lr.tuple))
|
||||||
self.assertEqual(lr, lr2)
|
self.assertEqual(lr, LinearRing(*lr.tuple))
|
||||||
lr3 = LinearRing([list(tup) for tup in lr.tuple])
|
self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
|
||||||
self.assertEqual(lr, lr3)
|
if HAS_NUMPY: self.assertEqual(lr, LineString(array(lr.tuple)))
|
||||||
if HAS_NUMPY:
|
|
||||||
lr4 = LineString(array(lr.tuple))
|
|
||||||
self.assertEqual(lr, lr4)
|
|
||||||
|
|
||||||
def test05a_polygons(self):
|
def test05a_polygons(self):
|
||||||
"Testing Polygon objects."
|
"Testing Polygon objects."
|
||||||
@ -180,6 +190,7 @@ class GEOSTest(unittest.TestCase):
|
|||||||
# Testing the geometry equivalence
|
# Testing the geometry equivalence
|
||||||
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
|
self.assertEqual(True, poly == GEOSGeometry(p.wkt))
|
||||||
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
|
self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
|
||||||
|
self.assertEqual(True, poly != prev)
|
||||||
|
|
||||||
# Testing the exterior ring
|
# Testing the exterior ring
|
||||||
ring = poly.exterior_ring
|
ring = poly.exterior_ring
|
||||||
@ -194,6 +205,15 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(ring.geom_type, 'LinearRing')
|
self.assertEqual(ring.geom_type, 'LinearRing')
|
||||||
self.assertEqual(ring.geom_typeid, 2)
|
self.assertEqual(ring.geom_typeid, 2)
|
||||||
|
|
||||||
|
# Testing polygon construction.
|
||||||
|
self.assertRaises(TypeError, Polygon, 0, [1, 2, 3])
|
||||||
|
self.assertRaises(TypeError, Polygon, '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)
|
||||||
|
self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
|
||||||
|
|
||||||
# Setting the second point of the first ring (which should set the
|
# Setting the second point of the first ring (which should set the
|
||||||
# first point of the polygon).
|
# first point of the polygon).
|
||||||
prev = poly.clone() # Using clone() to get a copy of the current polygon
|
prev = poly.clone() # Using clone() to get a copy of the current polygon
|
||||||
@ -221,10 +241,13 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(mp.num_geom, mpoly.num_geom)
|
self.assertEqual(mp.num_geom, mpoly.num_geom)
|
||||||
self.assertEqual(mp.n_p, mpoly.num_coords)
|
self.assertEqual(mp.n_p, mpoly.num_coords)
|
||||||
self.assertEqual(mp.num_geom, len(mpoly))
|
self.assertEqual(mp.num_geom, len(mpoly))
|
||||||
|
self.assertRaises(GEOSGeometryIndexError, mpoly.__getitem__, len(mpoly))
|
||||||
for p in mpoly:
|
for p in mpoly:
|
||||||
self.assertEqual(p.geom_type, 'Polygon')
|
self.assertEqual(p.geom_type, 'Polygon')
|
||||||
self.assertEqual(p.geom_typeid, 3)
|
self.assertEqual(p.geom_typeid, 3)
|
||||||
self.assertEqual(p.valid, True)
|
self.assertEqual(p.valid, True)
|
||||||
|
self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
|
||||||
|
|
||||||
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
|
print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
|
||||||
|
|
||||||
def test06_memory_hijinks(self):
|
def test06_memory_hijinks(self):
|
||||||
@ -254,7 +277,7 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertRaises(GEOSException, str, ring2)
|
self.assertRaises(GEOSException, str, ring2)
|
||||||
|
|
||||||
#### Memory issues with geometries from Geometry Collections
|
#### Memory issues with geometries from Geometry Collections
|
||||||
mp = GEOSGeometry('MULTIPOINT(85 715, 235 1400, 4620 1711)')
|
mp = fromstr('MULTIPOINT(85 715, 235 1400, 4620 1711)')
|
||||||
|
|
||||||
# Getting the points
|
# Getting the points
|
||||||
pts = [p for p in mp]
|
pts = [p for p in mp]
|
||||||
@ -278,7 +301,82 @@ class GEOSTest(unittest.TestCase):
|
|||||||
# after it has been deleted.
|
# after it has been deleted.
|
||||||
del mp
|
del mp
|
||||||
for p in pts:
|
for p in pts:
|
||||||
self.assertRaises(GEOSException, str, p)
|
self.assertRaises(GEOSException, str, p) # tests p's geometry pointer
|
||||||
|
self.assertRaises(GEOSException, p.get_tuple) # 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)
|
||||||
|
|
||||||
|
# Pulling out the shell and cloning our initial geometries for later comparison.
|
||||||
|
shell = poly.shell
|
||||||
|
polyc = poly.clone()
|
||||||
|
linringc = linring.clone()
|
||||||
|
|
||||||
|
gc = GeometryCollection(poly, linring, Point(5, 23))
|
||||||
|
|
||||||
|
# Should no longer be able to access these variables
|
||||||
|
self.assertRaises(GEOSException, str, poly)
|
||||||
|
self.assertRaises(GEOSException, str, shell)
|
||||||
|
self.assertRaises(GEOSException, str, linring)
|
||||||
|
|
||||||
|
r1 = 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)
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Can't access point after deletion of parent geometry.
|
||||||
|
del gc
|
||||||
|
self.assertRaises(GEOSException, str, pnt)
|
||||||
|
|
||||||
|
# Cleaning up.
|
||||||
|
del polyc
|
||||||
|
del mpoly
|
||||||
|
|
||||||
|
#### Memory issues with creating geometries from coordinate sequences within other geometries
|
||||||
|
|
||||||
|
# Creating the initial polygon from the following tuples, and then pulling out
|
||||||
|
# the individual rings.
|
||||||
|
ext_tup = ((0, 0), (0, 7), (7, 7), (7, 0), (0, 0))
|
||||||
|
itup1 = ((1, 1), (1, 2), (2, 2), (2, 1), (1, 1))
|
||||||
|
itup2 = ((4, 4), (4, 5), (5, 5), (5, 4), (4, 4))
|
||||||
|
poly1 = Polygon(LinearRing(ext_tup), LinearRing(itup1), LinearRing(itup2))
|
||||||
|
shell = poly1.shell
|
||||||
|
hole1 = poly1[1]
|
||||||
|
hole2 = poly1[2]
|
||||||
|
|
||||||
|
# Creating a Polygon from the shell and one of the holes
|
||||||
|
poly2 = Polygon(shell, hole1)
|
||||||
|
|
||||||
|
# We should no longer be able to access the original Polygon, its
|
||||||
|
# shell or its first internal ring.
|
||||||
|
self.assertRaises(GEOSException, str, poly1)
|
||||||
|
self.assertRaises(GEOSException, str, shell)
|
||||||
|
self.assertRaises(GEOSException, str, hole1)
|
||||||
|
|
||||||
|
# BUT, the second hole is still accessible.
|
||||||
|
self.assertEqual(itup2, hole2.tuple)
|
||||||
|
|
||||||
|
# Deleting the first polygon, and ensuring that
|
||||||
|
# the second hole is now gone for good.
|
||||||
|
del poly1
|
||||||
|
self.assertRaises(GEOSException, str, hole2)
|
||||||
|
|
||||||
def test08_coord_seq(self):
|
def test08_coord_seq(self):
|
||||||
"Testing Coordinate Sequence objects."
|
"Testing Coordinate Sequence objects."
|
||||||
@ -309,7 +407,6 @@ class GEOSTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test09_relate_pattern(self):
|
def test09_relate_pattern(self):
|
||||||
"Testing relate() and relate_pattern()."
|
"Testing relate() and relate_pattern()."
|
||||||
|
|
||||||
g = GEOSGeometry('POINT (0 0)')
|
g = GEOSGeometry('POINT (0 0)')
|
||||||
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
|
self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
|
||||||
|
|
||||||
@ -333,6 +430,7 @@ class GEOSTest(unittest.TestCase):
|
|||||||
self.assertEqual(True, a.intersects(b))
|
self.assertEqual(True, a.intersects(b))
|
||||||
i2 = a.intersection(b)
|
i2 = a.intersection(b)
|
||||||
self.assertEqual(i1, i2)
|
self.assertEqual(i1, i2)
|
||||||
|
self.assertEqual(i1, a & b)
|
||||||
|
|
||||||
def test11_union(self):
|
def test11_union(self):
|
||||||
"Testing union()."
|
"Testing union()."
|
||||||
@ -343,6 +441,7 @@ class GEOSTest(unittest.TestCase):
|
|||||||
u1 = GEOSGeometry(union_geoms[i].wkt)
|
u1 = GEOSGeometry(union_geoms[i].wkt)
|
||||||
u2 = a.union(b)
|
u2 = a.union(b)
|
||||||
self.assertEqual(u1, u2)
|
self.assertEqual(u1, u2)
|
||||||
|
self.assertEqual(u1, a | b) # Union ('|') operator
|
||||||
|
|
||||||
def test12_difference(self):
|
def test12_difference(self):
|
||||||
"Testing difference()."
|
"Testing difference()."
|
||||||
@ -353,8 +452,20 @@ class GEOSTest(unittest.TestCase):
|
|||||||
d1 = GEOSGeometry(diff_geoms[i].wkt)
|
d1 = GEOSGeometry(diff_geoms[i].wkt)
|
||||||
d2 = a.difference(b)
|
d2 = a.difference(b)
|
||||||
self.assertEqual(d1, d2)
|
self.assertEqual(d1, d2)
|
||||||
|
self.assertEqual(d1, a - b) # Difference ('-') operator
|
||||||
|
|
||||||
def test13_buffer(self):
|
def test13_symdifference(self):
|
||||||
|
"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)
|
||||||
|
d2 = a.sym_difference(b)
|
||||||
|
self.assertEqual(d1, d2)
|
||||||
|
self.assertEqual(d1, a ^ b) # Symmetric difference ('^') operator
|
||||||
|
|
||||||
|
def test14_buffer(self):
|
||||||
"Testing buffer()."
|
"Testing buffer()."
|
||||||
for i in xrange(len(buffer_geoms)):
|
for i in xrange(len(buffer_geoms)):
|
||||||
g_tup = buffer_geoms[i]
|
g_tup = buffer_geoms[i]
|
||||||
|
@ -228,7 +228,7 @@ class LayerMapping:
|
|||||||
"A class that maps OGR Layers to Django Models."
|
"A class that maps OGR Layers to Django Models."
|
||||||
|
|
||||||
def __init__(self, model, data, mapping, layer=0, source_srs=None):
|
def __init__(self, model, data, mapping, layer=0, source_srs=None):
|
||||||
"Takes the Django model, the mapping (dictionary), and the SHP file."
|
"Takes the Django model, the data source, and the mapping (dictionary)"
|
||||||
|
|
||||||
# Getting the field names and types from the model
|
# Getting the field names and types from the model
|
||||||
fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
|
fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user