Refactored the GEOS interface. Improvements include:
* Geometries now allow list-like manipulation, e.g., can add, insert, delete vertexes (or other geometries in collections) like Python lists. Thanks, Aryeh Leib Taurog. * Added support for GEOS prepared geometries via `prepared` property. Prepared geometries significantly speed up certain operations. * Added support for GEOS cascaded union as `MultiPolygon.cascaded_union` property. * Added support for GEOS line merge as `merged` property on `LineString`, and `MultiLineString` geometries. Thanks, Paul Smith. * No longer use the deprecated C API for serialization to/from WKB and WKT. Now use the GEOS I/O classes, which are now exposed as `WKTReader`, `WKTWriter`, `WKBReader`, and `WKBWriter` (which supports 3D and SRID inclusion) * Moved each type of geometry to their own module, eliminating the cluttered `geometries.py`. * Internally, all C API methods are explicitly called from a module rather than a star import. Fixed #9557, #9877, #10222 git-svn-id: http://code.djangoproject.com/svn/django/trunk@10131 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
4246c832b6
commit
66e1670efa
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2007, Justin Bronn
|
||||
Copyright (c) 2007-2009 Justin Bronn
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
|
@ -1,69 +1,14 @@
|
|||
"""
|
||||
The goal of this module is to be a ctypes wrapper around the GEOS library
|
||||
that will work on both *NIX and Windows systems. Specifically, this uses
|
||||
the GEOS C api.
|
||||
|
||||
I have several motivations for doing this:
|
||||
(1) The GEOS SWIG wrapper is no longer maintained, and requires the
|
||||
installation of SWIG.
|
||||
(2) The PCL implementation is over 2K+ lines of C and would make
|
||||
PCL a requisite package for the GeoDjango application stack.
|
||||
(3) Windows and Mac compatibility becomes substantially easier, and does not
|
||||
require the additional compilation of PCL or GEOS and SWIG -- all that
|
||||
is needed is a Win32 or Mac compiled GEOS C library (dll or dylib)
|
||||
in a location that Python can read (e.g. 'C:\Python25').
|
||||
|
||||
In summary, I wanted to wrap GEOS in a more maintainable and portable way using
|
||||
only Python and the excellent ctypes library (now standard in Python 2.5).
|
||||
|
||||
In the spirit of loose coupling, this library does not require Django or
|
||||
GeoDjango. Only the GEOS C library and ctypes are needed for the platform
|
||||
of your choice.
|
||||
|
||||
For more information about GEOS:
|
||||
http://geos.refractions.net
|
||||
|
||||
For more info about PCL and the discontinuation of the Python GEOS
|
||||
library see Sean Gillies' writeup (and subsequent update) at:
|
||||
http://zcologia.com/news/150/geometries-for-python/
|
||||
http://zcologia.com/news/429/geometries-for-python-update/
|
||||
The GeoDjango GEOS module. Please consult the GeoDjango documentation
|
||||
for more details:
|
||||
http://geodjango.org/docs/geos.html
|
||||
"""
|
||||
from django.contrib.gis.geos.base import GEOSGeometry, wkt_regex, hex_regex
|
||||
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon, HAS_NUMPY
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.geos.linestring import LineString, LinearRing
|
||||
from django.contrib.gis.geos.polygon import Polygon
|
||||
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
from django.contrib.gis.geos.libgeos import geos_version, geos_version_info
|
||||
|
||||
def fromfile(file_name):
|
||||
"""
|
||||
Given a string file name, returns a GEOSGeometry. The file may contain WKB,
|
||||
WKT, or HEX.
|
||||
"""
|
||||
fh = open(file_name, 'rb')
|
||||
buf = fh.read()
|
||||
fh.close()
|
||||
if wkt_regex.match(buf) or hex_regex.match(buf):
|
||||
return GEOSGeometry(buf)
|
||||
else:
|
||||
return GEOSGeometry(buffer(buf))
|
||||
|
||||
def fromstr(wkt_or_hex, **kwargs):
|
||||
"Given a string value (wkt or hex), returns a GEOSGeometry object."
|
||||
return GEOSGeometry(wkt_or_hex, **kwargs)
|
||||
|
||||
def hex_to_wkt(hex):
|
||||
"Converts HEXEWKB into WKT."
|
||||
return GEOSGeometry(hex).wkt
|
||||
|
||||
def wkt_to_hex(wkt):
|
||||
"Converts WKT into HEXEWKB."
|
||||
return GEOSGeometry(wkt).hex
|
||||
|
||||
def centroid(input):
|
||||
"Returns the centroid of the geometry (given in HEXEWKB)."
|
||||
return GEOSGeometry(input).centroid.wkt
|
||||
|
||||
def area(input):
|
||||
"Returns the area of the geometry (given in HEXEWKB)."
|
||||
return GEOSGeometry(input).area
|
||||
|
||||
from django.contrib.gis.geos.io import WKTReader, WKTWriter, WKBReader, WKBWriter
|
||||
from django.contrib.gis.geos.factory import fromfile, fromstr
|
||||
from django.contrib.gis.geos.libgeos import geos_version, geos_version_info, GEOS_PREPARE
|
||||
|
|
|
@ -1,608 +1,54 @@
|
|||
"""
|
||||
This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
|
||||
inherit from this object.
|
||||
"""
|
||||
# Python, ctypes and types dependencies.
|
||||
import re
|
||||
from ctypes import addressof, byref, c_double, c_size_t
|
||||
from types import UnicodeType
|
||||
|
||||
# GEOS-related dependencies.
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR
|
||||
|
||||
# All other functions in this module come from the ctypes
|
||||
# prototypes module -- which handles all interaction with
|
||||
# the underlying GEOS library.
|
||||
from django.contrib.gis.geos.prototypes import *
|
||||
from ctypes import c_void_p
|
||||
from types import NoneType
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
|
||||
# Trying to import GDAL libraries, if available. Have to place in
|
||||
# try/except since this package may be used outside GeoDjango.
|
||||
try:
|
||||
from django.contrib.gis.gdal import OGRGeometry, SpatialReference, GEOJSON
|
||||
from django.contrib.gis.gdal.geometries import json_regex
|
||||
HAS_GDAL = True
|
||||
except:
|
||||
HAS_GDAL, GEOJSON = False, False
|
||||
from django.contrib.gis import gdal
|
||||
except ImportError:
|
||||
# A 'dummy' gdal module.
|
||||
class GDALInfo(object):
|
||||
HAS_GDAL = False
|
||||
GEOJSON = False
|
||||
gdal = GDALInfo()
|
||||
|
||||
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
|
||||
# to prevent potentially malicious input from reaching the underlying C
|
||||
# library. Not a substitute for good web security programming practices.
|
||||
hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
|
||||
wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I)
|
||||
# NumPy supported?
|
||||
try:
|
||||
import numpy
|
||||
except ImportError:
|
||||
numpy = False
|
||||
|
||||
class GEOSGeometry(object):
|
||||
"A class that, generally, encapsulates a GEOS geometry."
|
||||
|
||||
# Initially, the geometry pointer is NULL
|
||||
class GEOSBase(object):
|
||||
"""
|
||||
Base object for GEOS objects that has a pointer access property
|
||||
that controls access to the underlying C pointer.
|
||||
"""
|
||||
# Initially the pointer is NULL.
|
||||
_ptr = None
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, geo_input, srid=None):
|
||||
"""
|
||||
The base constructor for GEOS geometry objects, and may take the
|
||||
following inputs:
|
||||
# Default allowed pointer type.
|
||||
ptr_type = c_void_p
|
||||
|
||||
* string: WKT
|
||||
* string: HEXEWKB (a PostGIS-specific canonical form)
|
||||
* buffer: WKB
|
||||
# Pointer access property.
|
||||
def _get_ptr(self):
|
||||
# Raise an exception if the pointer isn't valid don't
|
||||
# want to be passing NULL pointers to routines --
|
||||
# that's very bad.
|
||||
if self._ptr: return self._ptr
|
||||
else: raise GEOSException('NULL GEOS %s pointer encountered.' % self.__class__.__name__)
|
||||
|
||||
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, basestring):
|
||||
if isinstance(geo_input, UnicodeType):
|
||||
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
|
||||
geo_input = geo_input.encode('ascii')
|
||||
|
||||
wkt_m = wkt_regex.match(geo_input)
|
||||
if wkt_m:
|
||||
# Handling WKT input.
|
||||
if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
|
||||
g = from_wkt(wkt_m.group('wkt'))
|
||||
elif hex_regex.match(geo_input):
|
||||
# Handling HEXEWKB input.
|
||||
g = from_hex(geo_input, len(geo_input))
|
||||
elif GEOJSON and json_regex.match(geo_input):
|
||||
# Handling GeoJSON input.
|
||||
wkb_input = str(OGRGeometry(geo_input).wkb)
|
||||
g = from_wkb(wkb_input, len(wkb_input))
|
||||
else:
|
||||
raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
|
||||
elif isinstance(geo_input, GEOM_PTR):
|
||||
# When the input is a pointer to a geomtry (GEOM_PTR).
|
||||
g = geo_input
|
||||
elif isinstance(geo_input, buffer):
|
||||
# When the input is a buffer (WKB).
|
||||
wkb_input = str(geo_input)
|
||||
g = from_wkb(wkb_input, len(wkb_input))
|
||||
else:
|
||||
# Invalid geometry type.
|
||||
raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))
|
||||
|
||||
if bool(g):
|
||||
# Setting the pointer object with a valid pointer.
|
||||
self._ptr = g
|
||||
else:
|
||||
raise GEOSException('Could not initialize GEOS Geometry with given input.')
|
||||
|
||||
# Post-initialization setup.
|
||||
self._post_init(srid)
|
||||
|
||||
def _post_init(self, srid):
|
||||
"Helper routine for performing post-initialization setup."
|
||||
# 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_typeid]
|
||||
|
||||
# Setting the coordinate sequence for the geometry (will be None on
|
||||
# geometries that do not have coordinate sequences)
|
||||
self._set_cs()
|
||||
|
||||
@property
|
||||
def ptr(self):
|
||||
"""
|
||||
Property for controlling access to the GEOS geometry pointer. Using
|
||||
this raises an exception when the pointer is NULL, thus preventing
|
||||
the C library from attempting to access an invalid memory location.
|
||||
"""
|
||||
if self._ptr:
|
||||
return self._ptr
|
||||
else:
|
||||
raise GEOSException('NULL GEOS pointer encountered; was this geometry modified?')
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Destroys this Geometry; in other words, frees the memory used by the
|
||||
GEOS C++ object.
|
||||
"""
|
||||
if self._ptr: destroy_geom(self._ptr)
|
||||
|
||||
def __copy__(self):
|
||||
"""
|
||||
Returns a clone because the copy of a GEOSGeometry may contain an
|
||||
invalid pointer location if the original is garbage collected.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __deepcopy__(self, memodict):
|
||||
"""
|
||||
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
|
||||
thus, the protocol routine needs to be implemented to return correct
|
||||
copies (clones) of these GEOS objects, which use C pointers.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __str__(self):
|
||||
"WKT is used for the string representation."
|
||||
return self.wkt
|
||||
|
||||
def __repr__(self):
|
||||
"Short-hand representation because WKT may be very large."
|
||||
return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
|
||||
|
||||
# Pickling support
|
||||
def __getstate__(self):
|
||||
# The pickled state is simply a tuple of the WKB (in string form)
|
||||
# and the SRID.
|
||||
return str(self.wkb), self.srid
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Instantiating from the tuple state that was pickled.
|
||||
wkb, srid = state
|
||||
ptr = from_wkb(wkb, len(wkb))
|
||||
if not ptr: raise GEOSException('Invalid Geometry loaded from pickled state.')
|
||||
def _set_ptr(self, ptr):
|
||||
# Only allow the pointer to be set with pointers of the
|
||||
# compatible type or None (NULL).
|
||||
if isinstance(ptr, int):
|
||||
self._ptr = self.ptr_type(ptr)
|
||||
elif isinstance(ptr, (self.ptr_type, NoneType)):
|
||||
self._ptr = ptr
|
||||
self._post_init(srid)
|
||||
|
||||
# Comparison operators
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Equivalence testing, a Geometry may be compared with another Geometry
|
||||
or a WKT representation.
|
||||
"""
|
||||
if isinstance(other, basestring):
|
||||
return self.wkt == other
|
||||
elif isinstance(other, GEOSGeometry):
|
||||
return self.equals_exact(other)
|
||||
else:
|
||||
return False
|
||||
raise TypeError('Incompatible pointer type')
|
||||
|
||||
def __ne__(self, other):
|
||||
"The not equals operator."
|
||||
return not (self == 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 ####
|
||||
@property
|
||||
def has_cs(self):
|
||||
"Returns True if this Geometry has a coordinate sequence, False if not."
|
||||
# Only these geometries are allowed to have coordinate sequences.
|
||||
if isinstance(self, (Point, LineString, LinearRing)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _set_cs(self):
|
||||
"Sets the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
self._cs = GEOSCoordSeq(get_cs(self.ptr), self.hasz)
|
||||
else:
|
||||
self._cs = None
|
||||
|
||||
@property
|
||||
def coord_seq(self):
|
||||
"Returns a clone of the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
return self._cs.clone()
|
||||
|
||||
#### Geometry Info ####
|
||||
@property
|
||||
def geom_type(self):
|
||||
"Returns a string representing the Geometry type, e.g. 'Polygon'"
|
||||
return geos_type(self.ptr)
|
||||
|
||||
@property
|
||||
def geom_typeid(self):
|
||||
"Returns an integer representing the Geometry type."
|
||||
return geos_typeid(self.ptr)
|
||||
|
||||
@property
|
||||
def num_geom(self):
|
||||
"Returns the number of geometries in the Geometry."
|
||||
return get_num_geoms(self.ptr)
|
||||
|
||||
@property
|
||||
def num_coords(self):
|
||||
"Returns the number of coordinates in the Geometry."
|
||||
return get_num_coords(self.ptr)
|
||||
|
||||
@property
|
||||
def num_points(self):
|
||||
"Returns the number points, or coordinates, in the Geometry."
|
||||
return self.num_coords
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
|
||||
return get_dims(self.ptr)
|
||||
|
||||
def normalize(self):
|
||||
"Converts this Geometry to normal form (or canonical form)."
|
||||
return geos_normalize(self.ptr)
|
||||
|
||||
#### Unary predicates ####
|
||||
@property
|
||||
def empty(self):
|
||||
"""
|
||||
Returns a boolean indicating whether the set of points in this Geometry
|
||||
are empty.
|
||||
"""
|
||||
return geos_isempty(self.ptr)
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"Returns whether the geometry has a 3D dimension."
|
||||
return geos_hasz(self.ptr)
|
||||
|
||||
@property
|
||||
def ring(self):
|
||||
"Returns whether or not the geometry is a ring."
|
||||
return geos_isring(self.ptr)
|
||||
|
||||
@property
|
||||
def simple(self):
|
||||
"Returns false if the Geometry not simple."
|
||||
return geos_issimple(self.ptr)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"This property tests the validity of this Geometry."
|
||||
return geos_isvalid(self.ptr)
|
||||
|
||||
#### Binary predicates. ####
|
||||
def contains(self, other):
|
||||
"Returns true if other.within(this) returns true."
|
||||
return geos_contains(self.ptr, other.ptr)
|
||||
|
||||
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).
|
||||
"""
|
||||
return geos_crosses(self.ptr, other.ptr)
|
||||
|
||||
def disjoint(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FF*FF****.
|
||||
"""
|
||||
return geos_disjoint(self.ptr, other.ptr)
|
||||
|
||||
def equals(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**FFF*.
|
||||
"""
|
||||
return geos_equals(self.ptr, other.ptr)
|
||||
|
||||
def equals_exact(self, other, tolerance=0):
|
||||
"""
|
||||
Returns true if the two Geometries are exactly equal, up to a
|
||||
specified tolerance.
|
||||
"""
|
||||
return geos_equalsexact(self.ptr, other.ptr, float(tolerance))
|
||||
|
||||
def intersects(self, other):
|
||||
"Returns true if disjoint returns false."
|
||||
return geos_intersects(self.ptr, other.ptr)
|
||||
|
||||
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).
|
||||
"""
|
||||
return geos_overlaps(self.ptr, other.ptr)
|
||||
|
||||
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.
|
||||
"""
|
||||
if not isinstance(pattern, str) or len(pattern) > 9:
|
||||
raise GEOSException('invalid intersection matrix pattern')
|
||||
return geos_relatepattern(self.ptr, other.ptr, pattern)
|
||||
|
||||
def touches(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FT*******, F**T***** or F***T****.
|
||||
"""
|
||||
return geos_touches(self.ptr, other.ptr)
|
||||
|
||||
def within(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**F***.
|
||||
"""
|
||||
return geos_within(self.ptr, other.ptr)
|
||||
|
||||
#### SRID Routines ####
|
||||
def get_srid(self):
|
||||
"Gets the SRID for the geometry, returns None if no SRID is set."
|
||||
s = geos_get_srid(self.ptr)
|
||||
if s == 0: return None
|
||||
else: return s
|
||||
|
||||
def set_srid(self, srid):
|
||||
"Sets the SRID for the geometry."
|
||||
geos_set_srid(self.ptr, srid)
|
||||
srid = property(get_srid, set_srid)
|
||||
|
||||
#### Output Routines ####
|
||||
@property
|
||||
def ewkt(self):
|
||||
"Returns the EWKT (WKT + SRID) of the Geometry."
|
||||
if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
|
||||
else: return self.wkt
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
"Returns the WKT (Well-Known Text) of the Geometry."
|
||||
return to_wkt(self.ptr)
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
"""
|
||||
Returns the HEX of the Geometry -- please note that the SRID is not
|
||||
included in this representation, because the GEOS C library uses
|
||||
-1 by default, even if the SRID is set.
|
||||
"""
|
||||
# A possible faster, all-python, implementation:
|
||||
# str(self.wkb).encode('hex')
|
||||
return to_hex(self.ptr, byref(c_size_t()))
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""
|
||||
Returns GeoJSON representation of this Geometry if GDAL 1.5+
|
||||
is installed.
|
||||
"""
|
||||
if GEOJSON: return self.ogr.json
|
||||
geojson = json
|
||||
|
||||
@property
|
||||
def wkb(self):
|
||||
"Returns the WKB of the Geometry as a buffer."
|
||||
bin = to_wkb(self.ptr, byref(c_size_t()))
|
||||
return buffer(bin)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Returns the KML representation of this Geometry."
|
||||
gtype = self.geom_type
|
||||
return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
|
||||
|
||||
#### GDAL-specific output routines ####
|
||||
@property
|
||||
def ogr(self):
|
||||
"Returns the OGR Geometry for this Geometry."
|
||||
if HAS_GDAL:
|
||||
if self.srid:
|
||||
return OGRGeometry(self.wkb, self.srid)
|
||||
else:
|
||||
return OGRGeometry(self.wkb)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def srs(self):
|
||||
"Returns the OSR SpatialReference for SRID of this Geometry."
|
||||
if HAS_GDAL and self.srid:
|
||||
return SpatialReference(self.srid)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def crs(self):
|
||||
"Alias for `srs` property."
|
||||
return self.srs
|
||||
|
||||
def transform(self, ct, clone=False):
|
||||
"""
|
||||
Requires GDAL. Transforms the geometry according to the given
|
||||
transformation object, which may be an integer SRID, and WKT or
|
||||
PROJ.4 string. By default, the geometry is transformed in-place and
|
||||
nothing is returned. However if the `clone` keyword is set, then this
|
||||
geometry will not be modified and a transformed clone will be returned
|
||||
instead.
|
||||
"""
|
||||
srid = self.srid
|
||||
if HAS_GDAL and srid:
|
||||
g = OGRGeometry(self.wkb, srid)
|
||||
g.transform(ct)
|
||||
wkb = str(g.wkb)
|
||||
ptr = from_wkb(wkb, len(wkb))
|
||||
if clone:
|
||||
# User wants a cloned transformed geometry returned.
|
||||
return GEOSGeometry(ptr, srid=g.srid)
|
||||
if ptr:
|
||||
# Reassigning pointer, and performing post-initialization setup
|
||||
# again due to the reassignment.
|
||||
destroy_geom(self.ptr)
|
||||
self._ptr = ptr
|
||||
self._post_init(g.srid)
|
||||
else:
|
||||
raise GEOSException('Transformed WKB was invalid.')
|
||||
|
||||
#### Topology Routines ####
|
||||
def _topology(self, gptr):
|
||||
"Helper routine to return Geometry from the given pointer."
|
||||
return GEOSGeometry(gptr, srid=self.srid)
|
||||
|
||||
@property
|
||||
def boundary(self):
|
||||
"Returns the boundary as a newly allocated Geometry object."
|
||||
return self._topology(geos_boundary(self.ptr))
|
||||
|
||||
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)
|
||||
"""
|
||||
return self._topology(geos_buffer(self.ptr, width, quadsegs))
|
||||
|
||||
@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).
|
||||
"""
|
||||
return self._topology(geos_centroid(self.ptr))
|
||||
|
||||
@property
|
||||
def convex_hull(self):
|
||||
"""
|
||||
Returns the smallest convex Polygon that contains all the points
|
||||
in the Geometry.
|
||||
"""
|
||||
return self._topology(geos_convexhull(self.ptr))
|
||||
|
||||
def difference(self, other):
|
||||
"""
|
||||
Returns a Geometry representing the points making up this Geometry
|
||||
that do not make up other.
|
||||
"""
|
||||
return self._topology(geos_difference(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def envelope(self):
|
||||
"Return the envelope for this geometry (a polygon)."
|
||||
return self._topology(geos_envelope(self.ptr))
|
||||
|
||||
def intersection(self, other):
|
||||
"Returns a Geometry representing the points shared by this Geometry and other."
|
||||
return self._topology(geos_intersection(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def point_on_surface(self):
|
||||
"Computes an interior point of this Geometry."
|
||||
return self._topology(geos_pointonsurface(self.ptr))
|
||||
|
||||
def relate(self, other):
|
||||
"Returns the DE-9IM intersection matrix for this Geometry and the other."
|
||||
return geos_relate(self.ptr, other.ptr)
|
||||
|
||||
def simplify(self, tolerance=0.0, preserve_topology=False):
|
||||
"""
|
||||
Returns the Geometry, simplified using the Douglas-Peucker algorithm
|
||||
to the specified tolerance (higher tolerance => less points). If no
|
||||
tolerance provided, defaults to 0.
|
||||
|
||||
By default, this function does not preserve topology - e.g. polygons can
|
||||
be split, collapse to lines or disappear holes can be created or
|
||||
disappear, and lines can cross. By specifying preserve_topology=True,
|
||||
the result will have the same dimension and number of components as the
|
||||
input. This is significantly slower.
|
||||
"""
|
||||
if preserve_topology:
|
||||
return self._topology(geos_preservesimplify(self.ptr, tolerance))
|
||||
else:
|
||||
return self._topology(geos_simplify(self.ptr, tolerance))
|
||||
|
||||
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.
|
||||
"""
|
||||
return self._topology(geos_symdifference(self.ptr, other.ptr))
|
||||
|
||||
def union(self, other):
|
||||
"Returns a Geometry representing all the points in this Geometry and other."
|
||||
return self._topology(geos_union(self.ptr, other.ptr))
|
||||
|
||||
#### Other Routines ####
|
||||
@property
|
||||
def area(self):
|
||||
"Returns the area of the Geometry."
|
||||
return geos_area(self.ptr, byref(c_double()))
|
||||
|
||||
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.
|
||||
"""
|
||||
if not isinstance(other, GEOSGeometry):
|
||||
raise TypeError('distance() works only on other GEOS Geometries.')
|
||||
return geos_distance(self.ptr, other.ptr, byref(c_double()))
|
||||
|
||||
@property
|
||||
def extent(self):
|
||||
"""
|
||||
Returns the extent of this geometry as a 4-tuple, consisting of
|
||||
(xmin, ymin, xmax, ymax).
|
||||
"""
|
||||
env = self.envelope
|
||||
if isinstance(env, Point):
|
||||
xmin, ymin = env.tuple
|
||||
xmax, ymax = xmin, ymin
|
||||
else:
|
||||
xmin, ymin = env[0][0]
|
||||
xmax, ymax = env[0][2]
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""
|
||||
Returns the length of this Geometry (e.g., 0 for point, or the
|
||||
circumfrence of a Polygon).
|
||||
"""
|
||||
return geos_length(self.ptr, byref(c_double()))
|
||||
|
||||
def clone(self):
|
||||
"Clones this Geometry."
|
||||
return GEOSGeometry(geom_clone(self.ptr), srid=self.srid)
|
||||
|
||||
# Class mapping dictionary
|
||||
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
|
||||
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||
GEOS_CLASSES = {0 : Point,
|
||||
1 : LineString,
|
||||
2 : LinearRing,
|
||||
3 : Polygon,
|
||||
4 : MultiPoint,
|
||||
5 : MultiLineString,
|
||||
6 : MultiPolygon,
|
||||
7 : GeometryCollection,
|
||||
}
|
||||
# Property for controlling access to the GEOS object pointers. Using
|
||||
# this raises an exception when the pointer is NULL, thus preventing
|
||||
# the C library from attempting to access an invalid memory location.
|
||||
ptr = property(_get_ptr, _set_ptr)
|
||||
|
|
|
@ -3,16 +3,17 @@
|
|||
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
||||
"""
|
||||
from ctypes import c_int, c_uint, byref
|
||||
from types import TupleType, ListType
|
||||
from django.contrib.gis.geos.base import GEOSGeometry
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
|
||||
from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
|
||||
from django.contrib.gis.geos.prototypes import create_collection, destroy_geom, geom_clone, geos_typeid, get_cs, get_geomn
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, GEOS_PREPARE
|
||||
from django.contrib.gis.geos.linestring import LineString, LinearRing
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.geos.polygon import Polygon
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
|
||||
class GeometryCollection(GEOSGeometry):
|
||||
_allowed = (Point, LineString, LinearRing, Polygon)
|
||||
_typeid = 7
|
||||
_minlength = 0
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Initializes a Geometry Collection from a sequence of Geometry objects."
|
||||
|
@ -24,7 +25,7 @@ class GeometryCollection(GEOSGeometry):
|
|||
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)):
|
||||
if isinstance(args[0], (tuple, list)):
|
||||
init_geoms = args[0]
|
||||
else:
|
||||
init_geoms = args
|
||||
|
@ -32,55 +33,55 @@ class GeometryCollection(GEOSGeometry):
|
|||
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.')
|
||||
# this is moved to list mixin super class
|
||||
self._check_allowed(init_geoms)
|
||||
|
||||
# Creating the geometry pointer array.
|
||||
ngeoms = len(init_geoms)
|
||||
geoms = get_pointer_arr(ngeoms)
|
||||
for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i].ptr)
|
||||
super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs)
|
||||
|
||||
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(geom_clone(get_geomn(self.ptr, index)), srid=self.srid)
|
||||
|
||||
def __setitem__(self, index, geom):
|
||||
"Sets the Geometry at the specified index."
|
||||
self._checkindex(index)
|
||||
if not isinstance(geom, self._allowed):
|
||||
raise TypeError('Incompatible Geometry for collection.')
|
||||
|
||||
ngeoms = len(self)
|
||||
geoms = get_pointer_arr(ngeoms)
|
||||
for i in xrange(ngeoms):
|
||||
if i == index:
|
||||
geoms[i] = geom_clone(geom.ptr)
|
||||
else:
|
||||
geoms[i] = geom_clone(get_geomn(self.ptr, i))
|
||||
|
||||
# Creating a new collection, and destroying the contents of the previous poiner.
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms))
|
||||
if srid: self.srid = srid
|
||||
destroy_geom(prev_ptr)
|
||||
collection = self._create_collection(len(init_geoms), iter(init_geoms))
|
||||
super(GeometryCollection, self).__init__(collection, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterates over each Geometry in the Collection."
|
||||
for i in xrange(len(self)):
|
||||
yield self.__getitem__(i)
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of geometries in this Collection."
|
||||
return self.num_geom
|
||||
|
||||
def _checkindex(self, index):
|
||||
"Checks the given geometry index."
|
||||
if index < 0 or index >= self.num_geom:
|
||||
raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index))
|
||||
### Methods for compatibility with ListMixin ###
|
||||
@classmethod
|
||||
def _create_collection(cls, length, items):
|
||||
# Creating the geometry pointer array.
|
||||
geoms = get_pointer_arr(length)
|
||||
for i, g in enumerate(items):
|
||||
# this is a little sloppy, but makes life easier
|
||||
# allow GEOSGeometry types (python wrappers) or pointer types
|
||||
geoms[i] = capi.geom_clone(getattr(g, 'ptr', g))
|
||||
|
||||
return capi.create_collection(c_int(cls._typeid), byref(geoms), c_uint(length))
|
||||
|
||||
def _getitem_internal(self, index):
|
||||
return capi.get_geomn(self.ptr, index)
|
||||
|
||||
def _getitem_external(self, index):
|
||||
"Returns the Geometry from this Collection at the given index (0-based)."
|
||||
# Checking the index and returning the corresponding GEOS geometry.
|
||||
return GEOSGeometry(capi.geom_clone(self._getitem_internal(index)), srid=self.srid)
|
||||
|
||||
def _set_collection(self, length, items):
|
||||
"Create a new collection, and destroy the contents of the previous pointer."
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self.ptr = self._create_collection(length, items)
|
||||
if srid: self.srid = srid
|
||||
capi.destroy_geom(prev_ptr)
|
||||
|
||||
# Because GeometryCollections need to be rebuilt upon the changing of a
|
||||
# component geometry, these routines are set to their counterparts that
|
||||
# rebuild the entire geometry.
|
||||
_set_single = GEOSGeometry._set_single_rebuild
|
||||
_assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
|
@ -97,9 +98,31 @@ class GeometryCollection(GEOSGeometry):
|
|||
class MultiPoint(GeometryCollection):
|
||||
_allowed = Point
|
||||
_typeid = 4
|
||||
|
||||
class MultiLineString(GeometryCollection):
|
||||
_allowed = (LineString, LinearRing)
|
||||
_typeid = 5
|
||||
|
||||
@property
|
||||
def merged(self):
|
||||
"""
|
||||
Returns a LineString representing the line merge of this
|
||||
MultiLineString.
|
||||
"""
|
||||
return self._topology(capi.geos_linemerge(self.ptr))
|
||||
|
||||
class MultiPolygon(GeometryCollection):
|
||||
_allowed = Polygon
|
||||
_typeid = 6
|
||||
|
||||
@property
|
||||
def cascaded_union(self):
|
||||
"Returns a cascaded union of this MultiPolygon."
|
||||
if GEOS_PREPARE:
|
||||
return GEOSGeometry(capi.geos_cascaded_union(self.ptr), self.srid)
|
||||
else:
|
||||
raise GEOSException('The cascaded union operation requires GEOS 3.1+.')
|
||||
|
||||
# Setting the allowed types here since GeometryCollection is defined before
|
||||
# its subclasses.
|
||||
GeometryCollection._allowed = (Point, LineString, LinearRing, Polygon, MultiPoint, MultiLineString, MultiPolygon)
|
||||
|
|
|
@ -4,15 +4,16 @@
|
|||
LineString, and LinearRing geometries.
|
||||
"""
|
||||
from ctypes import c_double, c_uint, byref
|
||||
from types import ListType, TupleType
|
||||
from django.contrib.gis.geos.base import GEOSBase, numpy
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR, HAS_NUMPY
|
||||
from django.contrib.gis.geos.prototypes import cs_clone, cs_getdims, cs_getordinate, cs_getsize, cs_setordinate
|
||||
if HAS_NUMPY: from numpy import ndarray
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
|
||||
class GEOSCoordSeq(object):
|
||||
class GEOSCoordSeq(GEOSBase):
|
||||
"The internal representation of a list of coordinates inside a Geometry."
|
||||
|
||||
ptr_type = CS_PTR
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, ptr, z=False):
|
||||
"Initializes from a GEOS pointer."
|
||||
|
@ -44,9 +45,9 @@ class GEOSCoordSeq(object):
|
|||
def __setitem__(self, index, value):
|
||||
"Sets the coordinate sequence value at the given index."
|
||||
# Checking the input value
|
||||
if isinstance(value, (ListType, TupleType)):
|
||||
if isinstance(value, (list, tuple)):
|
||||
pass
|
||||
elif HAS_NUMPY and isinstance(value, ndarray):
|
||||
elif numpy and isinstance(value, numpy.ndarray):
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Must set coordinate with a sequence (list, tuple, or numpy array).')
|
||||
|
@ -76,27 +77,18 @@ class GEOSCoordSeq(object):
|
|||
if dim < 0 or dim > 2:
|
||||
raise GEOSException('invalid ordinate dimension "%d"' % dim)
|
||||
|
||||
@property
|
||||
def ptr(self):
|
||||
"""
|
||||
Property for controlling access to coordinate sequence pointer,
|
||||
preventing attempted access to a NULL memory location.
|
||||
"""
|
||||
if self._ptr: return self._ptr
|
||||
else: raise GEOSException('NULL coordinate sequence pointer encountered.')
|
||||
|
||||
#### Ordinate getting and setting routines ####
|
||||
def getOrdinate(self, dimension, index):
|
||||
"Returns the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
return cs_getordinate(self.ptr, index, dimension, byref(c_double()))
|
||||
return capi.cs_getordinate(self.ptr, index, dimension, byref(c_double()))
|
||||
|
||||
def setOrdinate(self, dimension, index, value):
|
||||
"Sets the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
cs_setordinate(self.ptr, index, dimension, value)
|
||||
capi.cs_setordinate(self.ptr, index, dimension, value)
|
||||
|
||||
def getX(self, index):
|
||||
"Get the X value at the index."
|
||||
|
@ -126,12 +118,12 @@ class GEOSCoordSeq(object):
|
|||
@property
|
||||
def size(self):
|
||||
"Returns the size of this coordinate sequence."
|
||||
return cs_getsize(self.ptr, byref(c_uint()))
|
||||
return capi.cs_getsize(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Returns the dimensions of this coordinate sequence."
|
||||
return cs_getdims(self.ptr, byref(c_uint()))
|
||||
return capi.cs_getdims(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
|
@ -144,7 +136,7 @@ class GEOSCoordSeq(object):
|
|||
### Other Methods ###
|
||||
def clone(self):
|
||||
"Clones this coordinate sequence."
|
||||
return GEOSCoordSeq(cs_clone(self.ptr), self.hasz)
|
||||
return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex
|
||||
|
||||
def fromfile(file_h):
|
||||
"""
|
||||
Given a string file name, returns a GEOSGeometry. The file may contain WKB,
|
||||
WKT, or HEX.
|
||||
"""
|
||||
# If given a file name, get a real handle.
|
||||
if isinstance(file_h, basestring):
|
||||
file_h = open(file_h, 'rb')
|
||||
|
||||
# Reading in the file's contents,
|
||||
buf = file_h.read()
|
||||
|
||||
# If we get WKB need to wrap in buffer(), so run through regexes.
|
||||
if wkt_regex.match(buf) or hex_regex.match(buf):
|
||||
return GEOSGeometry(buf)
|
||||
else:
|
||||
return GEOSGeometry(buffer(buf))
|
||||
|
||||
def fromstr(string, **kwargs):
|
||||
"Given a string value, returns a GEOSGeometry object."
|
||||
return GEOSGeometry(string, **kwargs)
|
|
@ -1,391 +0,0 @@
|
|||
"""
|
||||
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_uint, byref
|
||||
from django.contrib.gis.geos.base import GEOSGeometry
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY
|
||||
from django.contrib.gis.geos.prototypes import *
|
||||
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:
|
||||
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
|
||||
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
|
||||
"""
|
||||
|
||||
if isinstance(x, (tuple, list)):
|
||||
# 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, (int, float, long)) and isinstance(y, (int, float, long)):
|
||||
# Here X, Y, and (optionally) Z were passed in individually, as parameters.
|
||||
if isinstance(z, (int, float, long)):
|
||||
ndim = 3
|
||||
coords = [x, y, z]
|
||||
else:
|
||||
ndim = 2
|
||||
coords = [x, y]
|
||||
else:
|
||||
raise TypeError('Invalid parameters given for Point initialization.')
|
||||
|
||||
# Creating the coordinate sequence, and setting X, Y, [Z]
|
||||
cs = create_cs(c_uint(1), c_uint(ndim))
|
||||
cs_setx(cs, 0, coords[0])
|
||||
cs_sety(cs, 0, coords[1])
|
||||
if ndim == 3: cs_setz(cs, 0, coords[2])
|
||||
|
||||
# Initializing using the address returned from the GEOS
|
||||
# createPoint factory.
|
||||
super(Point, self).__init__(create_point(cs), srid=srid)
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of dimensions for this Point (either 0, 2 or 3)."
|
||||
if self.empty: return 0
|
||||
if self.hasz: return 3
|
||||
else: return 2
|
||||
|
||||
def get_x(self):
|
||||
"Returns the X component of the Point."
|
||||
return self._cs.getOrdinate(0, 0)
|
||||
|
||||
def set_x(self, value):
|
||||
"Sets the X component of the Point."
|
||||
self._cs.setOrdinate(0, 0, value)
|
||||
|
||||
def get_y(self):
|
||||
"Returns the Y component of the Point."
|
||||
return self._cs.getOrdinate(1, 0)
|
||||
|
||||
def set_y(self, value):
|
||||
"Sets the Y component of the Point."
|
||||
self._cs.setOrdinate(1, 0, value)
|
||||
|
||||
def get_z(self):
|
||||
"Returns the Z component of the Point."
|
||||
if self.hasz:
|
||||
return self._cs.getOrdinate(2, 0)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_z(self, value):
|
||||
"Sets the Z component of the Point."
|
||||
if self.hasz:
|
||||
self._cs.setOrdinate(2, 0, value)
|
||||
else:
|
||||
raise GEOSException('Cannot set Z on 2D Point.')
|
||||
|
||||
# X, Y, Z properties
|
||||
x = property(get_x, set_x)
|
||||
y = property(get_y, set_y)
|
||||
z = property(get_z, set_z)
|
||||
|
||||
### Tuple setting and retrieval routines. ###
|
||||
def get_coords(self):
|
||||
"Returns a tuple of the point."
|
||||
return self._cs.tuple
|
||||
|
||||
def set_coords(self, tup):
|
||||
"Sets the coordinates of the point with the given tuple."
|
||||
self._cs[0] = tup
|
||||
|
||||
# The tuple and coords properties
|
||||
tuple = property(get_coords, set_coords)
|
||||
coords = tuple
|
||||
|
||||
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.
|
||||
|
||||
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))
|
||||
"""
|
||||
# If only one argument provided, set the coords array appropriately
|
||||
if len(args) == 1: coords = args[0]
|
||||
else: coords = args
|
||||
|
||||
if isinstance(coords, (tuple, list)):
|
||||
# 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)
|
||||
if coords: ndim = len(coords[0])
|
||||
else: raise TypeError('Cannot initialize on empty sequence.')
|
||||
self._checkdim(ndim)
|
||||
# Incrementing through each of the coordinates and verifying
|
||||
for i in xrange(1, ncoords):
|
||||
if not isinstance(coords[i], (tuple, list, Point)):
|
||||
raise TypeError('each coordinate should be a sequence (list or tuple)')
|
||||
if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.')
|
||||
numpy_coords = False
|
||||
elif HAS_NUMPY and isinstance(coords, ndarray):
|
||||
shape = coords.shape # Using numpy's shape.
|
||||
if len(shape) != 2: raise TypeError('Too many dimensions.')
|
||||
self._checkdim(shape[1])
|
||||
ncoords = shape[0]
|
||||
ndim = shape[1]
|
||||
numpy_coords = True
|
||||
else:
|
||||
raise TypeError('Invalid initialization input for LineStrings.')
|
||||
|
||||
# Creating a coordinate sequence object because it is easier to
|
||||
# set the points using GEOSCoordSeq.__setitem__().
|
||||
cs = GEOSCoordSeq(create_cs(ncoords, ndim), z=bool(ndim==3))
|
||||
for i in xrange(ncoords):
|
||||
if numpy_coords: cs[i] = coords[i,:]
|
||||
elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
|
||||
else: cs[i] = coords[i]
|
||||
|
||||
# Getting the correct initialization function
|
||||
if kwargs.get('ring', False):
|
||||
func = create_linearring
|
||||
else:
|
||||
func = create_linestring
|
||||
|
||||
# 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.
|
||||
super(LineString, self).__init__(func(cs.ptr), srid=srid)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Gets the point at the specified index."
|
||||
return self._cs[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
|
||||
self._cs[index] = value
|
||||
|
||||
def __iter__(self):
|
||||
"Allows iteration over this LineString."
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of points in this LineString."
|
||||
return len(self._cs)
|
||||
|
||||
def _checkdim(self, dim):
|
||||
if dim not in (2, 3): raise TypeError('Dimension mismatch.')
|
||||
|
||||
#### Sequence Properties ####
|
||||
@property
|
||||
def tuple(self):
|
||||
"Returns a tuple version of the geometry from the coordinate sequence."
|
||||
return self._cs.tuple
|
||||
coords = 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))]
|
||||
if HAS_NUMPY: return array(lst) # ARRRR!
|
||||
else: return lst
|
||||
|
||||
@property
|
||||
def array(self):
|
||||
"Returns a numpy array for the LineString."
|
||||
return self._listarr(self._cs.__getitem__)
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"Returns a list or numpy array of the X variable."
|
||||
return self._listarr(self._cs.getX)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"Returns a list or numpy array of the Y variable."
|
||||
return self._listarr(self._cs.getY)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"Returns a list or numpy array of the Z variable."
|
||||
if not self.hasz: return None
|
||||
else: return self._listarr(self._cs.getZ)
|
||||
|
||||
# LinearRings are LineStrings used within Polygons.
|
||||
class LinearRing(LineString):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Overriding the initialization function to set the ring keyword."
|
||||
kwargs['ring'] = True # Setting the ring keyword argument to True
|
||||
super(LinearRing, self).__init__(*args, **kwargs)
|
||||
|
||||
class Polygon(GEOSGeometry):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initializes on an exterior ring and a sequence of holes (both
|
||||
instances may be either LinearRing instances, or a tuple/list
|
||||
that may be constructed into a LinearRing).
|
||||
|
||||
Examples of initialization, where shell, hole1, and hole2 are
|
||||
valid LinearRing geometries:
|
||||
>>> poly = Polygon(shell, hole1, hole2)
|
||||
>>> poly = Polygon(shell, (hole1, hole2))
|
||||
|
||||
Example where a tuple parameters are used:
|
||||
>>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
|
||||
((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
|
||||
"""
|
||||
if not args:
|
||||
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:]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
# If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
|
||||
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \
|
||||
(len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)):
|
||||
init_holes = init_holes[0]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
# Ensuring the exterior ring and holes parameters are LinearRing objects
|
||||
# or may be instantiated into LinearRings.
|
||||
ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.')
|
||||
holes_list = [] # Create new list, cause init_holes is a tuple.
|
||||
for i in xrange(n_holes):
|
||||
holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'))
|
||||
|
||||
# Why another loop? Because if a TypeError is raised, cloned pointers will
|
||||
# be around that can't be cleaned up.
|
||||
holes = get_pointer_arr(n_holes)
|
||||
for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr)
|
||||
|
||||
# Getting the shell pointer address.
|
||||
shell = geom_clone(ext_ring.ptr)
|
||||
|
||||
# Calling with the GEOS createPolygon factory.
|
||||
super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
|
||||
|
||||
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 at the given index (e.g., poly[1] and poly[2] would
|
||||
return the first and second interior ring, respectively).
|
||||
"""
|
||||
if index == 0:
|
||||
return self.exterior_ring
|
||||
else:
|
||||
# Getting the interior ring, have to subtract 1 from the index.
|
||||
return self.get_interior_ring(index-1)
|
||||
|
||||
def __setitem__(self, index, ring):
|
||||
"Sets the ring at the specified index with the given ring."
|
||||
# Checking the index and ring parameters.
|
||||
self._checkindex(index)
|
||||
if not isinstance(ring, LinearRing):
|
||||
raise TypeError('must set Polygon index with a LinearRing object')
|
||||
|
||||
# Getting the shell
|
||||
if index == 0:
|
||||
shell = geom_clone(ring.ptr)
|
||||
else:
|
||||
shell = geom_clone(get_extring(self.ptr))
|
||||
|
||||
# Getting the interior rings (holes)
|
||||
nholes = len(self)-1
|
||||
if nholes > 0:
|
||||
holes = get_pointer_arr(nholes)
|
||||
for i in xrange(nholes):
|
||||
if i == (index-1):
|
||||
holes[i] = geom_clone(ring.ptr)
|
||||
else:
|
||||
holes[i] = geom_clone(get_intring(self.ptr, i))
|
||||
holes_param = byref(holes)
|
||||
else:
|
||||
holes_param = None
|
||||
|
||||
# Getting the current pointer, replacing with the newly constructed
|
||||
# geometry, and destroying the old geometry.
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self._ptr = create_polygon(shell, holes_param, c_uint(nholes))
|
||||
if srid: self.srid = srid
|
||||
destroy_geom(prev_ptr)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterates over each ring in the polygon."
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of rings in this Polygon."
|
||||
return self.num_interior_rings + 1
|
||||
|
||||
def _checkindex(self, index):
|
||||
"Internal routine for checking the given ring index."
|
||||
if index < 0 or index >= len(self):
|
||||
raise GEOSIndexError('invalid Polygon ring index: %s' % index)
|
||||
|
||||
def _construct_ring(self, param, msg=''):
|
||||
"Helper routine for trying to construct a ring from the given parameter."
|
||||
if isinstance(param, LinearRing): return param
|
||||
try:
|
||||
ring = LinearRing(param)
|
||||
return ring
|
||||
except TypeError:
|
||||
raise TypeError(msg)
|
||||
|
||||
def get_interior_ring(self, ring_i):
|
||||
"""
|
||||
Gets the interior ring at the specified index, 0 is for the first
|
||||
interior ring, not the exterior ring.
|
||||
"""
|
||||
self._checkindex(ring_i+1)
|
||||
return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid)
|
||||
|
||||
#### Polygon Properties ####
|
||||
@property
|
||||
def num_interior_rings(self):
|
||||
"Returns the number of interior rings."
|
||||
# Getting the number of rings
|
||||
return get_nrings(self.ptr)
|
||||
|
||||
def get_ext_ring(self):
|
||||
"Gets the exterior ring of the Polygon."
|
||||
return GEOSGeometry(geom_clone(get_extring(self.ptr)), srid=self.srid)
|
||||
|
||||
def set_ext_ring(self, ring):
|
||||
"Sets the exterior ring of the Polygon."
|
||||
self[0] = ring
|
||||
|
||||
# properties for the exterior ring/shell
|
||||
exterior_ring = property(get_ext_ring, set_ext_ring)
|
||||
shell = exterior_ring
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Gets the tuple for each ring in this Polygon."
|
||||
return tuple([self[i].tuple for i in xrange(len(self))])
|
||||
coords = tuple
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Returns the KML representation of this Polygon."
|
||||
inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml
|
||||
for i in xrange(self.num_interior_rings)])
|
||||
return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)
|
|
@ -0,0 +1,622 @@
|
|||
"""
|
||||
This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
|
||||
inherit from this object.
|
||||
"""
|
||||
# Python, ctypes and types dependencies.
|
||||
import re
|
||||
from ctypes import addressof, byref, c_double, c_size_t
|
||||
|
||||
# super-class for mutable list behavior
|
||||
from django.contrib.gis.geos.mutable_list import ListMixin
|
||||
|
||||
# GEOS-related dependencies.
|
||||
from django.contrib.gis.geos.base import GEOSBase, gdal
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
|
||||
from django.contrib.gis.geos.mutable_list import ListMixin
|
||||
|
||||
# All other functions in this module come from the ctypes
|
||||
# prototypes module -- which handles all interaction with
|
||||
# the underlying GEOS library.
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos import io
|
||||
|
||||
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
|
||||
# to prevent potentially malicious input from reaching the underlying C
|
||||
# library. Not a substitute for good web security programming practices.
|
||||
hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
|
||||
wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I)
|
||||
|
||||
class GEOSGeometry(GEOSBase, ListMixin):
|
||||
"A class that, generally, encapsulates a GEOS geometry."
|
||||
|
||||
# Raise GEOSIndexError instead of plain IndexError
|
||||
# (see ticket #4740 and GEOSIndexError docstring)
|
||||
_IndexError = GEOSIndexError
|
||||
|
||||
ptr_type = GEOM_PTR
|
||||
|
||||
#### Python 'magic' routines ####
|
||||
def __init__(self, geo_input, srid=None):
|
||||
"""
|
||||
The base constructor for GEOS geometry objects, and may take the
|
||||
following inputs:
|
||||
|
||||
* strings:
|
||||
- WKT
|
||||
- HEXEWKB (a PostGIS-specific canonical form)
|
||||
- GeoJSON (requires GDAL)
|
||||
* buffer:
|
||||
- WKB
|
||||
|
||||
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, basestring):
|
||||
if isinstance(geo_input, unicode):
|
||||
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
|
||||
geo_input = geo_input.encode('ascii')
|
||||
|
||||
wkt_m = wkt_regex.match(geo_input)
|
||||
if wkt_m:
|
||||
# Handling WKT input.
|
||||
if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
|
||||
g = io.wkt_r.read(wkt_m.group('wkt'))
|
||||
elif hex_regex.match(geo_input):
|
||||
# Handling HEXEWKB input.
|
||||
g = io.wkb_r.read(geo_input)
|
||||
elif gdal.GEOJSON and gdal.geometries.json_regex.match(geo_input):
|
||||
# Handling GeoJSON input.
|
||||
g = io.wkb_r.read(gdal.OGRGeometry(geo_input).wkb)
|
||||
else:
|
||||
raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
|
||||
elif isinstance(geo_input, GEOM_PTR):
|
||||
# When the input is a pointer to a geomtry (GEOM_PTR).
|
||||
g = geo_input
|
||||
elif isinstance(geo_input, buffer):
|
||||
# When the input is a buffer (WKB).
|
||||
g = io.wkb_r.read(geo_input)
|
||||
elif isinstance(geo_input, GEOSGeometry):
|
||||
g = capi.geom_clone(geo_input.ptr)
|
||||
else:
|
||||
# Invalid geometry type.
|
||||
raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))
|
||||
|
||||
if bool(g):
|
||||
# Setting the pointer object with a valid pointer.
|
||||
self.ptr = g
|
||||
else:
|
||||
raise GEOSException('Could not initialize GEOS Geometry with given input.')
|
||||
|
||||
# Post-initialization setup.
|
||||
self._post_init(srid)
|
||||
|
||||
def _post_init(self, srid):
|
||||
"Helper routine for performing post-initialization setup."
|
||||
# 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_typeid]
|
||||
|
||||
# Setting the coordinate sequence for the geometry (will be None on
|
||||
# geometries that do not have coordinate sequences)
|
||||
self._set_cs()
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
Destroys this Geometry; in other words, frees the memory used by the
|
||||
GEOS C++ object.
|
||||
"""
|
||||
if self._ptr: capi.destroy_geom(self._ptr)
|
||||
|
||||
def __copy__(self):
|
||||
"""
|
||||
Returns a clone because the copy of a GEOSGeometry may contain an
|
||||
invalid pointer location if the original is garbage collected.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __deepcopy__(self, memodict):
|
||||
"""
|
||||
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
|
||||
thus, the protocol routine needs to be implemented to return correct
|
||||
copies (clones) of these GEOS objects, which use C pointers.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __str__(self):
|
||||
"WKT is used for the string representation."
|
||||
return self.wkt
|
||||
|
||||
def __repr__(self):
|
||||
"Short-hand representation because WKT may be very large."
|
||||
return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
|
||||
|
||||
# Pickling support
|
||||
def __getstate__(self):
|
||||
# The pickled state is simply a tuple of the WKB (in string form)
|
||||
# and the SRID.
|
||||
return str(self.wkb), self.srid
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Instantiating from the tuple state that was pickled.
|
||||
wkb, srid = state
|
||||
ptr = capi.from_wkb(wkb, len(wkb))
|
||||
if not ptr: raise GEOSException('Invalid Geometry loaded from pickled state.')
|
||||
self.ptr = ptr
|
||||
self._post_init(srid)
|
||||
|
||||
# Comparison operators
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Equivalence testing, a Geometry may be compared with another Geometry
|
||||
or a WKT representation.
|
||||
"""
|
||||
if isinstance(other, basestring):
|
||||
return self.wkt == other
|
||||
elif isinstance(other, GEOSGeometry):
|
||||
return self.equals_exact(other)
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
"The not equals operator."
|
||||
return not (self == 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 ####
|
||||
@property
|
||||
def has_cs(self):
|
||||
"Returns True if this Geometry has a coordinate sequence, False if not."
|
||||
# Only these geometries are allowed to have coordinate sequences.
|
||||
if isinstance(self, (Point, LineString, LinearRing)):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _set_cs(self):
|
||||
"Sets the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
|
||||
else:
|
||||
self._cs = None
|
||||
|
||||
@property
|
||||
def coord_seq(self):
|
||||
"Returns a clone of the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
return self._cs.clone()
|
||||
|
||||
#### Geometry Info ####
|
||||
@property
|
||||
def geom_type(self):
|
||||
"Returns a string representing the Geometry type, e.g. 'Polygon'"
|
||||
return capi.geos_type(self.ptr)
|
||||
|
||||
@property
|
||||
def geom_typeid(self):
|
||||
"Returns an integer representing the Geometry type."
|
||||
return capi.geos_typeid(self.ptr)
|
||||
|
||||
@property
|
||||
def num_geom(self):
|
||||
"Returns the number of geometries in the Geometry."
|
||||
return capi.get_num_geoms(self.ptr)
|
||||
|
||||
@property
|
||||
def num_coords(self):
|
||||
"Returns the number of coordinates in the Geometry."
|
||||
return capi.get_num_coords(self.ptr)
|
||||
|
||||
@property
|
||||
def num_points(self):
|
||||
"Returns the number points, or coordinates, in the Geometry."
|
||||
return self.num_coords
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
|
||||
return capi.get_dims(self.ptr)
|
||||
|
||||
def normalize(self):
|
||||
"Converts this Geometry to normal form (or canonical form)."
|
||||
return capi.geos_normalize(self.ptr)
|
||||
|
||||
#### Unary predicates ####
|
||||
@property
|
||||
def empty(self):
|
||||
"""
|
||||
Returns a boolean indicating whether the set of points in this Geometry
|
||||
are empty.
|
||||
"""
|
||||
return capi.geos_isempty(self.ptr)
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"Returns whether the geometry has a 3D dimension."
|
||||
return capi.geos_hasz(self.ptr)
|
||||
|
||||
@property
|
||||
def ring(self):
|
||||
"Returns whether or not the geometry is a ring."
|
||||
return capi.geos_isring(self.ptr)
|
||||
|
||||
@property
|
||||
def simple(self):
|
||||
"Returns false if the Geometry not simple."
|
||||
return capi.geos_issimple(self.ptr)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"This property tests the validity of this Geometry."
|
||||
return capi.geos_isvalid(self.ptr)
|
||||
|
||||
#### Binary predicates. ####
|
||||
def contains(self, other):
|
||||
"Returns true if other.within(this) returns true."
|
||||
return capi.geos_contains(self.ptr, other.ptr)
|
||||
|
||||
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).
|
||||
"""
|
||||
return capi.geos_crosses(self.ptr, other.ptr)
|
||||
|
||||
def disjoint(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FF*FF****.
|
||||
"""
|
||||
return capi.geos_disjoint(self.ptr, other.ptr)
|
||||
|
||||
def equals(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**FFF*.
|
||||
"""
|
||||
return capi.geos_equals(self.ptr, other.ptr)
|
||||
|
||||
def equals_exact(self, other, tolerance=0):
|
||||
"""
|
||||
Returns true if the two Geometries are exactly equal, up to a
|
||||
specified tolerance.
|
||||
"""
|
||||
return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
|
||||
|
||||
def intersects(self, other):
|
||||
"Returns true if disjoint returns false."
|
||||
return capi.geos_intersects(self.ptr, other.ptr)
|
||||
|
||||
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).
|
||||
"""
|
||||
return capi.geos_overlaps(self.ptr, other.ptr)
|
||||
|
||||
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.
|
||||
"""
|
||||
if not isinstance(pattern, basestring) or len(pattern) > 9:
|
||||
raise GEOSException('invalid intersection matrix pattern')
|
||||
return capi.geos_relatepattern(self.ptr, other.ptr, pattern)
|
||||
|
||||
def touches(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FT*******, F**T***** or F***T****.
|
||||
"""
|
||||
return capi.geos_touches(self.ptr, other.ptr)
|
||||
|
||||
def within(self, other):
|
||||
"""
|
||||
Returns true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**F***.
|
||||
"""
|
||||
return capi.geos_within(self.ptr, other.ptr)
|
||||
|
||||
#### SRID Routines ####
|
||||
def get_srid(self):
|
||||
"Gets the SRID for the geometry, returns None if no SRID is set."
|
||||
s = capi.geos_get_srid(self.ptr)
|
||||
if s == 0: return None
|
||||
else: return s
|
||||
|
||||
def set_srid(self, srid):
|
||||
"Sets the SRID for the geometry."
|
||||
capi.geos_set_srid(self.ptr, srid)
|
||||
srid = property(get_srid, set_srid)
|
||||
|
||||
#### Output Routines ####
|
||||
@property
|
||||
def ewkt(self):
|
||||
"Returns the EWKT (WKT + SRID) of the Geometry."
|
||||
if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
|
||||
else: return self.wkt
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
"Returns the WKT (Well-Known Text) of the Geometry."
|
||||
return io.wkt_w.write(self.ptr)
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
"""
|
||||
Returns the HEX of the Geometry -- please note that the SRID is not
|
||||
included in this representation, because the GEOS C library uses
|
||||
-1 by default, even if the SRID is set.
|
||||
"""
|
||||
# A possible faster, all-python, implementation:
|
||||
# str(self.wkb).encode('hex')
|
||||
return io.wkb_w.write_hex(self.ptr)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""
|
||||
Returns GeoJSON representation of this Geometry if GDAL 1.5+
|
||||
is installed.
|
||||
"""
|
||||
if gdal.GEOJSON:
|
||||
return self.ogr.json
|
||||
else:
|
||||
raise GEOSException('GeoJSON output only supported on GDAL 1.5+.')
|
||||
geojson = json
|
||||
|
||||
@property
|
||||
def wkb(self):
|
||||
"Returns the WKB of the Geometry as a buffer."
|
||||
return io.wkb_w.write(self.ptr)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Returns the KML representation of this Geometry."
|
||||
gtype = self.geom_type
|
||||
return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
|
||||
|
||||
@property
|
||||
def prepared(self):
|
||||
"""
|
||||
Returns a PreparedGeometry corresponding to this geometry -- it is
|
||||
optimized for the contains, intersects, and covers operations.
|
||||
"""
|
||||
if GEOS_PREPARE:
|
||||
return PreparedGeometry(self)
|
||||
else:
|
||||
raise GEOSException('GEOS 3.1+ required for prepared geometry support.')
|
||||
|
||||
#### GDAL-specific output routines ####
|
||||
@property
|
||||
def ogr(self):
|
||||
"Returns the OGR Geometry for this Geometry."
|
||||
if gdal.HAS_GDAL:
|
||||
if self.srid:
|
||||
return gdal.OGRGeometry(self.wkb, self.srid)
|
||||
else:
|
||||
return gdal.OGRGeometry(self.wkb)
|
||||
else:
|
||||
raise GEOSException('GDAL required to convert to an OGRGeometry.')
|
||||
|
||||
@property
|
||||
def srs(self):
|
||||
"Returns the OSR SpatialReference for SRID of this Geometry."
|
||||
if gdal.HAS_GDAL:
|
||||
if self.srid:
|
||||
return gdal.SpatialReference(self.srid)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
raise GEOSException('GDAL required to return a SpatialReference object.')
|
||||
|
||||
@property
|
||||
def crs(self):
|
||||
"Alias for `srs` property."
|
||||
return self.srs
|
||||
|
||||
def transform(self, ct, clone=False):
|
||||
"""
|
||||
Requires GDAL. Transforms the geometry according to the given
|
||||
transformation object, which may be an integer SRID, and WKT or
|
||||
PROJ.4 string. By default, the geometry is transformed in-place and
|
||||
nothing is returned. However if the `clone` keyword is set, then this
|
||||
geometry will not be modified and a transformed clone will be returned
|
||||
instead.
|
||||
"""
|
||||
srid = self.srid
|
||||
if gdal.HAS_GDAL and srid:
|
||||
# Creating an OGR Geometry, which is then transformed.
|
||||
g = gdal.OGRGeometry(self.wkb, srid)
|
||||
g.transform(ct)
|
||||
# Getting a new GEOS pointer
|
||||
ptr = io.wkb_r.read(g.wkb)
|
||||
if clone:
|
||||
# User wants a cloned transformed geometry returned.
|
||||
return GEOSGeometry(ptr, srid=g.srid)
|
||||
if ptr:
|
||||
# Reassigning pointer, and performing post-initialization setup
|
||||
# again due to the reassignment.
|
||||
capi.destroy_geom(self.ptr)
|
||||
self.ptr = ptr
|
||||
self._post_init(g.srid)
|
||||
else:
|
||||
raise GEOSException('Transformed WKB was invalid.')
|
||||
|
||||
#### Topology Routines ####
|
||||
def _topology(self, gptr):
|
||||
"Helper routine to return Geometry from the given pointer."
|
||||
return GEOSGeometry(gptr, srid=self.srid)
|
||||
|
||||
@property
|
||||
def boundary(self):
|
||||
"Returns the boundary as a newly allocated Geometry object."
|
||||
return self._topology(capi.geos_boundary(self.ptr))
|
||||
|
||||
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)
|
||||
"""
|
||||
return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
|
||||
|
||||
@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).
|
||||
"""
|
||||
return self._topology(capi.geos_centroid(self.ptr))
|
||||
|
||||
@property
|
||||
def convex_hull(self):
|
||||
"""
|
||||
Returns the smallest convex Polygon that contains all the points
|
||||
in the Geometry.
|
||||
"""
|
||||
return self._topology(capi.geos_convexhull(self.ptr))
|
||||
|
||||
def difference(self, other):
|
||||
"""
|
||||
Returns a Geometry representing the points making up this Geometry
|
||||
that do not make up other.
|
||||
"""
|
||||
return self._topology(capi.geos_difference(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def envelope(self):
|
||||
"Return the envelope for this geometry (a polygon)."
|
||||
return self._topology(capi.geos_envelope(self.ptr))
|
||||
|
||||
def intersection(self, other):
|
||||
"Returns a Geometry representing the points shared by this Geometry and other."
|
||||
return self._topology(capi.geos_intersection(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def point_on_surface(self):
|
||||
"Computes an interior point of this Geometry."
|
||||
return self._topology(capi.geos_pointonsurface(self.ptr))
|
||||
|
||||
def relate(self, other):
|
||||
"Returns the DE-9IM intersection matrix for this Geometry and the other."
|
||||
return capi.geos_relate(self.ptr, other.ptr)
|
||||
|
||||
def simplify(self, tolerance=0.0, preserve_topology=False):
|
||||
"""
|
||||
Returns the Geometry, simplified using the Douglas-Peucker algorithm
|
||||
to the specified tolerance (higher tolerance => less points). If no
|
||||
tolerance provided, defaults to 0.
|
||||
|
||||
By default, this function does not preserve topology - e.g. polygons can
|
||||
be split, collapse to lines or disappear holes can be created or
|
||||
disappear, and lines can cross. By specifying preserve_topology=True,
|
||||
the result will have the same dimension and number of components as the
|
||||
input. This is significantly slower.
|
||||
"""
|
||||
if preserve_topology:
|
||||
return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
|
||||
else:
|
||||
return self._topology(capi.geos_simplify(self.ptr, tolerance))
|
||||
|
||||
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.
|
||||
"""
|
||||
return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
|
||||
|
||||
def union(self, other):
|
||||
"Returns a Geometry representing all the points in this Geometry and other."
|
||||
return self._topology(capi.geos_union(self.ptr, other.ptr))
|
||||
|
||||
#### Other Routines ####
|
||||
@property
|
||||
def area(self):
|
||||
"Returns the area of the Geometry."
|
||||
return capi.geos_area(self.ptr, byref(c_double()))
|
||||
|
||||
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.
|
||||
"""
|
||||
if not isinstance(other, GEOSGeometry):
|
||||
raise TypeError('distance() works only on other GEOS Geometries.')
|
||||
return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
|
||||
|
||||
@property
|
||||
def extent(self):
|
||||
"""
|
||||
Returns the extent of this geometry as a 4-tuple, consisting of
|
||||
(xmin, ymin, xmax, ymax).
|
||||
"""
|
||||
env = self.envelope
|
||||
if isinstance(env, Point):
|
||||
xmin, ymin = env.tuple
|
||||
xmax, ymax = xmin, ymin
|
||||
else:
|
||||
xmin, ymin = env[0][0]
|
||||
xmax, ymax = env[0][2]
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""
|
||||
Returns the length of this Geometry (e.g., 0 for point, or the
|
||||
circumfrence of a Polygon).
|
||||
"""
|
||||
return capi.geos_length(self.ptr, byref(c_double()))
|
||||
|
||||
def clone(self):
|
||||
"Clones this Geometry."
|
||||
return GEOSGeometry(capi.geom_clone(self.ptr), srid=self.srid)
|
||||
|
||||
# Class mapping dictionary. Has to be at the end to avoid import
|
||||
# conflicts with GEOSGeometry.
|
||||
from django.contrib.gis.geos.linestring import LineString, LinearRing
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.geos.polygon import Polygon
|
||||
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
|
||||
GEOS_CLASSES = {0 : Point,
|
||||
1 : LineString,
|
||||
2 : LinearRing,
|
||||
3 : Polygon,
|
||||
4 : MultiPoint,
|
||||
5 : MultiLineString,
|
||||
6 : MultiPolygon,
|
||||
7 : GeometryCollection,
|
||||
}
|
||||
|
||||
# If supported, import the PreparedGeometry class.
|
||||
if GEOS_PREPARE:
|
||||
from django.contrib.gis.geos.prepared import PreparedGeometry
|
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
Module that holds classes for performing I/O operations on GEOS geometry
|
||||
objects. Specifically, this has Python implementations of WKB/WKT
|
||||
reader and writer classes.
|
||||
"""
|
||||
from ctypes import byref, c_size_t
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR
|
||||
from django.contrib.gis.geos.prototypes import io as capi
|
||||
|
||||
class IOBase(GEOSBase):
|
||||
"Base class for IO objects that that have `destroy` method."
|
||||
def __init__(self):
|
||||
# Getting the pointer with the constructor.
|
||||
self.ptr = self.constructor()
|
||||
|
||||
def __del__(self):
|
||||
# Cleaning up with the appropriate destructor.
|
||||
if self._ptr: self.destructor(self._ptr)
|
||||
|
||||
def _get_geom_ptr(self, geom):
|
||||
if hasattr(geom, 'ptr'): geom = geom.ptr
|
||||
if not isinstance(geom, GEOM_PTR): raise TypeError
|
||||
return geom
|
||||
|
||||
### WKT Reading and Writing objects ###
|
||||
class WKTReader(IOBase):
|
||||
constructor = capi.wkt_reader_create
|
||||
destructor = capi.wkt_reader_destroy
|
||||
ptr_type = capi.WKT_READ_PTR
|
||||
|
||||
def read(self, wkt, ptr=False):
|
||||
if not isinstance(wkt, basestring): raise TypeError
|
||||
return capi.wkt_reader_read(self.ptr, wkt)
|
||||
|
||||
class WKTWriter(IOBase):
|
||||
constructor = capi.wkt_writer_create
|
||||
destructor = capi.wkt_writer_destroy
|
||||
ptr_type = capi.WKT_WRITE_PTR
|
||||
|
||||
def write(self, geom):
|
||||
return capi.wkt_writer_write(self.ptr, self._get_geom_ptr(geom))
|
||||
|
||||
### WKB Reading and Writing objects ###
|
||||
class WKBReader(IOBase):
|
||||
constructor = capi.wkb_reader_create
|
||||
destructor = capi.wkb_reader_destroy
|
||||
ptr_type = capi.WKB_READ_PTR
|
||||
|
||||
def read(self, wkb):
|
||||
if isinstance(wkb, buffer):
|
||||
wkb_s = str(wkb)
|
||||
return capi.wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
|
||||
elif isinstance(wkb, basestring):
|
||||
return capi.wkb_reader_read_hex(self.ptr, wkb, len(wkb))
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
class WKBWriter(IOBase):
|
||||
constructor = capi.wkb_writer_create
|
||||
destructor = capi.wkb_writer_destroy
|
||||
ptr_type = capi.WKB_READ_PTR
|
||||
|
||||
def write(self, geom):
|
||||
return buffer(capi.wkb_writer_write(self.ptr, self._get_geom_ptr(geom), byref(c_size_t())))
|
||||
|
||||
def write_hex(self, geom):
|
||||
return capi.wkb_writer_write_hex(self.ptr, self._get_geom_ptr(geom), byref(c_size_t()))
|
||||
|
||||
### WKBWriter Properties ###
|
||||
|
||||
# Property for getting/setting the byteorder.
|
||||
def _get_byteorder(self):
|
||||
return capi.wkb_writer_get_byteorder(self.ptr)
|
||||
|
||||
def _set_byteorder(self, order):
|
||||
if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
|
||||
capi.wkb_writer_set_byteorder(self.ptr, order)
|
||||
|
||||
byteorder = property(_get_byteorder, _set_byteorder)
|
||||
|
||||
# Property for getting/setting the output dimension.
|
||||
def _get_outdim(self):
|
||||
return capi.wkb_writer_get_outdim(self.ptr)
|
||||
|
||||
def _set_outdim(self, new_dim):
|
||||
if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
|
||||
capi.wkb_writer_set_outdim(self.ptr, new_dim)
|
||||
|
||||
outdim = property(_get_outdim, _set_outdim)
|
||||
|
||||
# Property for getting/setting the include srid flag.
|
||||
def _get_include_srid(self):
|
||||
return bool(ord(capi.wkb_writer_get_include_srid(self.ptr)))
|
||||
|
||||
def _set_include_srid(self, include):
|
||||
if bool(include): flag = chr(1)
|
||||
else: flag = chr(0)
|
||||
capi.wkb_writer_set_include_srid(self.ptr, flag)
|
||||
|
||||
srid = property(_get_include_srid, _set_include_srid)
|
||||
|
||||
# Instances of the WKT and WKB reader/writer objects.
|
||||
wkt_r = WKTReader()
|
||||
wkt_w = WKTWriter()
|
||||
wkb_r = WKBReader()
|
||||
wkb_w = WKBWriter()
|
||||
|
|
@ -11,13 +11,6 @@ from ctypes import c_char_p, Structure, CDLL, CFUNCTYPE, POINTER
|
|||
from ctypes.util import find_library
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
|
||||
# NumPy supported?
|
||||
try:
|
||||
from numpy import array, ndarray
|
||||
HAS_NUMPY = True
|
||||
except ImportError:
|
||||
HAS_NUMPY = False
|
||||
|
||||
# Custom library path set?
|
||||
try:
|
||||
from django.conf import settings
|
||||
|
@ -88,10 +81,12 @@ lgeos.initGEOS(notice_h, error_h)
|
|||
|
||||
# Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
|
||||
class GEOSGeom_t(Structure): pass
|
||||
class GEOSPrepGeom_t(Structure): pass
|
||||
class GEOSCoordSeq_t(Structure): pass
|
||||
|
||||
# Pointers to opaque GEOS geometry structures.
|
||||
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||
PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
|
||||
CS_PTR = POINTER(GEOSCoordSeq_t)
|
||||
|
||||
# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
|
||||
|
@ -109,7 +104,7 @@ geos_version.restype = c_char_p
|
|||
|
||||
# Regular expression should be able to parse version strings such as
|
||||
# '3.0.0rc4-CAPI-1.3.3', or '3.0.0-CAPI-1.4.1'
|
||||
version_regex = re.compile(r'^(?P<version>\d+\.\d+\.\d+)(rc(?P<release_candidate>\d+))?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)$')
|
||||
version_regex = re.compile(r'^(?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.\d+)(rc(?P<release_candidate>\d+))?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)$')
|
||||
def geos_version_info():
|
||||
"""
|
||||
Returns a dictionary containing the various version metadata parsed from
|
||||
|
@ -120,7 +115,14 @@ def geos_version_info():
|
|||
ver = geos_version()
|
||||
m = version_regex.match(ver)
|
||||
if not m: raise GEOSException('Could not parse version info string "%s"' % ver)
|
||||
return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version'))
|
||||
return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor'))
|
||||
|
||||
# Version numbers and whether or not prepared geometry support is available.
|
||||
_verinfo = geos_version_info()
|
||||
GEOS_MAJOR_VERSION = int(_verinfo['major'])
|
||||
GEOS_MINOR_VERSION = int(_verinfo['minor'])
|
||||
del _verinfo
|
||||
GEOS_PREPARE = GEOS_MAJOR_VERSION > 3 or GEOS_MAJOR_VERSION == 3 and GEOS_MINOR_VERSION >= 1
|
||||
|
||||
# Calling the finishGEOS() upon exit of the interpreter.
|
||||
atexit.register(lgeos.finishGEOS)
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
from django.contrib.gis.geos.base import numpy
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
|
||||
class LineString(GEOSGeometry):
|
||||
_init_func = capi.create_linestring
|
||||
_minlength = 2
|
||||
|
||||
#### 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.
|
||||
|
||||
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))
|
||||
"""
|
||||
# If only one argument provided, set the coords array appropriately
|
||||
if len(args) == 1: coords = args[0]
|
||||
else: coords = args
|
||||
|
||||
if isinstance(coords, (tuple, list)):
|
||||
# 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)
|
||||
if coords: ndim = len(coords[0])
|
||||
else: raise TypeError('Cannot initialize on empty sequence.')
|
||||
self._checkdim(ndim)
|
||||
# Incrementing through each of the coordinates and verifying
|
||||
for i in xrange(1, ncoords):
|
||||
if not isinstance(coords[i], (tuple, list, Point)):
|
||||
raise TypeError('each coordinate should be a sequence (list or tuple)')
|
||||
if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.')
|
||||
numpy_coords = False
|
||||
elif numpy and isinstance(coords, numpy.ndarray):
|
||||
shape = coords.shape # Using numpy's shape.
|
||||
if len(shape) != 2: raise TypeError('Too many dimensions.')
|
||||
self._checkdim(shape[1])
|
||||
ncoords = shape[0]
|
||||
ndim = shape[1]
|
||||
numpy_coords = True
|
||||
else:
|
||||
raise TypeError('Invalid initialization input for LineStrings.')
|
||||
|
||||
# Creating a coordinate sequence object because it is easier to
|
||||
# set the points using GEOSCoordSeq.__setitem__().
|
||||
cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim==3))
|
||||
|
||||
for i in xrange(ncoords):
|
||||
if numpy_coords: cs[i] = coords[i,:]
|
||||
elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
|
||||
else: cs[i] = coords[i]
|
||||
|
||||
# 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.
|
||||
super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)
|
||||
|
||||
def __iter__(self):
|
||||
"Allows iteration over this LineString."
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of points in this LineString."
|
||||
return len(self._cs)
|
||||
|
||||
def _getitem_external(self, index):
|
||||
self._checkindex(index)
|
||||
return self._cs[index]
|
||||
_getitem_internal = _getitem_external
|
||||
|
||||
def _set_collection(self, length, items):
|
||||
ndim = self._cs.dims #
|
||||
hasz = self._cs.hasz # I don't understand why these are different
|
||||
|
||||
# create a new coordinate sequence and populate accordingly
|
||||
cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
|
||||
for i, c in enumerate(items):
|
||||
cs[i] = c
|
||||
|
||||
ptr = self._init_func(cs.ptr)
|
||||
if ptr:
|
||||
capi.destroy_geom(self.ptr)
|
||||
self.ptr = ptr
|
||||
self._post_init(self.srid)
|
||||
else:
|
||||
# can this happen?
|
||||
raise GEOSException('Geometry resulting from slice deletion was invalid.')
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._checkindex(index)
|
||||
self._cs[index] = value
|
||||
|
||||
def _checkdim(self, dim):
|
||||
if dim not in (2, 3): raise TypeError('Dimension mismatch.')
|
||||
|
||||
#### Sequence Properties ####
|
||||
@property
|
||||
def tuple(self):
|
||||
"Returns a tuple version of the geometry from the coordinate sequence."
|
||||
return self._cs.tuple
|
||||
coords = 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))]
|
||||
if numpy: return numpy.array(lst) # ARRRR!
|
||||
else: return lst
|
||||
|
||||
@property
|
||||
def array(self):
|
||||
"Returns a numpy array for the LineString."
|
||||
return self._listarr(self._cs.__getitem__)
|
||||
|
||||
@property
|
||||
def merged(self):
|
||||
"Returns the line merge of this LineString."
|
||||
return self._topology(capi.geos_linemerge(self.ptr))
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"Returns a list or numpy array of the X variable."
|
||||
return self._listarr(self._cs.getX)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"Returns a list or numpy array of the Y variable."
|
||||
return self._listarr(self._cs.getY)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"Returns a list or numpy array of the Z variable."
|
||||
if not self.hasz: return None
|
||||
else: return self._listarr(self._cs.getZ)
|
||||
|
||||
# LinearRings are LineStrings used within Polygons.
|
||||
class LinearRing(LineString):
|
||||
_minLength = 4
|
||||
_init_func = capi.create_linearring
|
|
@ -0,0 +1,236 @@
|
|||
# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
|
||||
# Released under the New BSD license.
|
||||
"""
|
||||
This module contains a base type which provides list-style mutations
|
||||
This is akin to UserList, but without specific data storage methods.
|
||||
Possible candidate for a more general position in the source tree,
|
||||
perhaps django.utils
|
||||
|
||||
Author: Aryeh Leib Taurog.
|
||||
"""
|
||||
class ListMixin(object):
|
||||
"""
|
||||
A base class which provides complete list interface
|
||||
derived classes should implement the following:
|
||||
|
||||
function _getitem_external(self, i):
|
||||
Return single item with index i for general use
|
||||
|
||||
function _getitem_internal(self, i):
|
||||
Same as above, but for use within the class [Optional]
|
||||
|
||||
function _set_collection(self, length, items):
|
||||
Recreate the entire object
|
||||
|
||||
function _set_single(self, i, value):
|
||||
Set the single item at index i to value [Optional]
|
||||
If left undefined, all mutations will result in rebuilding
|
||||
the object using _set_collection.
|
||||
|
||||
function __len__(self):
|
||||
Return the length
|
||||
|
||||
function __iter__(self):
|
||||
Return an iterator for the object
|
||||
|
||||
int _minlength:
|
||||
The minimum legal length [Optional]
|
||||
|
||||
int _maxlength:
|
||||
The maximum legal length [Optional]
|
||||
|
||||
iterable _allowed:
|
||||
A list of allowed item types [Optional]
|
||||
|
||||
class _IndexError:
|
||||
The type of exception to be raise on invalid index [Optional]
|
||||
"""
|
||||
|
||||
_minlength = 0
|
||||
_maxlength = None
|
||||
_IndexError = IndexError
|
||||
|
||||
### Special methods for Python list interface ###
|
||||
def __getitem__(self, index):
|
||||
"Gets the coordinates of the point(s) at the specified index/slice."
|
||||
if isinstance(index, slice):
|
||||
return [self._getitem_external(i) for i in xrange(*index.indices(len(self)))]
|
||||
else:
|
||||
index = self._checkindex(index)
|
||||
return self._getitem_external(index)
|
||||
|
||||
def __delitem__(self, index):
|
||||
"Delete the point(s) at the specified index/slice."
|
||||
if not isinstance(index, (int, long, slice)):
|
||||
raise TypeError("%s is not a legal index" % index)
|
||||
|
||||
# calculate new length and dimensions
|
||||
origLen = len(self)
|
||||
if isinstance(index, (int, long)):
|
||||
index = self._checkindex(index)
|
||||
indexRange = [index]
|
||||
else:
|
||||
indexRange = range(*index.indices(origLen))
|
||||
|
||||
newLen = origLen - len(indexRange)
|
||||
newItems = ( self._getitem_internal(i)
|
||||
for i in xrange(origLen)
|
||||
if i not in indexRange )
|
||||
|
||||
self._rebuild(newLen, newItems)
|
||||
|
||||
def __setitem__(self, index, val):
|
||||
"Sets the Geometry at the specified index."
|
||||
if isinstance(index, slice):
|
||||
self._set_slice(index, val)
|
||||
else:
|
||||
index = self._checkindex(index)
|
||||
self._check_allowed((val,))
|
||||
self._set_single(index, val)
|
||||
|
||||
### Public list interface Methods ###
|
||||
def append(self, val):
|
||||
"Standard list append method"
|
||||
self[len(self):] = [val]
|
||||
|
||||
def extend(self, vals):
|
||||
"Standard list extend method"
|
||||
self[len(self):] = vals
|
||||
|
||||
def insert(self, index, val):
|
||||
"Standard list insert method"
|
||||
if not isinstance(index, (int, long)):
|
||||
raise TypeError("%s is not a legal index" % index)
|
||||
self[index:index] = [val]
|
||||
|
||||
def pop(self, index=-1):
|
||||
"Standard list pop method"
|
||||
result = self[index]
|
||||
del self[index]
|
||||
return result
|
||||
|
||||
def index(self, val):
|
||||
"Standard list index method"
|
||||
for i in xrange(0, len(self)):
|
||||
if self[i] == val: return i
|
||||
raise ValueError('%s not found in object' % str(val))
|
||||
|
||||
def remove(self, val):
|
||||
"Standard list remove method"
|
||||
del self[self.index(val)]
|
||||
|
||||
def count(self, val):
|
||||
"Standard list count method"
|
||||
count = 0
|
||||
for i in self:
|
||||
if val == i: count += 1
|
||||
return count
|
||||
|
||||
### Private API routines unique to ListMixin ###
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not hasattr(self, '_getitem_internal'):
|
||||
self._getitem_internal = self._getitem_external
|
||||
|
||||
if hasattr(self, '_set_single'):
|
||||
self._canSetSingle = True
|
||||
else:
|
||||
self._canSetSingle = False
|
||||
self._set_single = self._set_single_rebuild
|
||||
self._assign_extended_slice = self._assign_extended_slice_rebuild
|
||||
|
||||
super(ListMixin, self).__init__(*args, **kwargs)
|
||||
|
||||
def _rebuild(self, newLen, newItems):
|
||||
if newLen < self._minlength:
|
||||
raise ValueError('Must have at least %d items' % self._minlength)
|
||||
if self._maxlength is not None and newLen > self._maxlength:
|
||||
raise ValueError('Cannot have more than %d items' % self._maxlength)
|
||||
|
||||
self._set_collection(newLen, newItems)
|
||||
|
||||
def _set_single_rebuild(self, index, value):
|
||||
self._set_slice(slice(index, index + 1, 1), [value])
|
||||
|
||||
def _checkindex(self, index, correct=True):
|
||||
length = len(self)
|
||||
if 0 <= index < length:
|
||||
return index
|
||||
if correct and -length <= index < 0:
|
||||
return index + length
|
||||
raise self._IndexError('invalid index: %s' % str(index))
|
||||
|
||||
def _check_allowed(self, items):
|
||||
if hasattr(self, '_allowed'):
|
||||
if False in [isinstance(val, self._allowed) for val in items]:
|
||||
raise TypeError('Invalid type encountered in the arguments.')
|
||||
|
||||
def _set_slice(self, index, values):
|
||||
"Assign values to a slice of the object"
|
||||
try:
|
||||
iter(values)
|
||||
except TypeError:
|
||||
raise TypeError('can only assign an iterable to a slice')
|
||||
|
||||
self._check_allowed(values)
|
||||
|
||||
origLen = len(self)
|
||||
valueList = list(values)
|
||||
start, stop, step = index.indices(origLen)
|
||||
|
||||
# CAREFUL: index.step and step are not the same!
|
||||
# step will never be None
|
||||
if index.step is None:
|
||||
self._assign_simple_slice(start, stop, valueList)
|
||||
else:
|
||||
self._assign_extended_slice(start, stop, step, valueList)
|
||||
|
||||
def _assign_extended_slice_rebuild(self, start, stop, step, valueList):
|
||||
'Assign an extended slice by rebuilding entire list'
|
||||
indexList = range(start, stop, step)
|
||||
# extended slice, only allow assigning slice of same size
|
||||
if len(valueList) != len(indexList):
|
||||
raise ValueError('attempt to assign sequence of size %d '
|
||||
'to extended slice of size %d'
|
||||
% (len(valueList), len(indexList)))
|
||||
|
||||
# we're not changing the length of the sequence
|
||||
newLen = len(self)
|
||||
newVals = dict(zip(indexList, valueList))
|
||||
def newItems():
|
||||
for i in xrange(newLen):
|
||||
if i in newVals:
|
||||
yield newVals[i]
|
||||
else:
|
||||
yield self._getitem_internal(i)
|
||||
|
||||
self._rebuild(newLen, newItems())
|
||||
|
||||
def _assign_extended_slice(self, start, stop, step, valueList):
|
||||
'Assign an extended slice by re-assigning individual items'
|
||||
indexList = range(start, stop, step)
|
||||
# extended slice, only allow assigning slice of same size
|
||||
if len(valueList) != len(indexList):
|
||||
raise ValueError('attempt to assign sequence of size %d '
|
||||
'to extended slice of size %d'
|
||||
% (len(valueList), len(indexList)))
|
||||
|
||||
for i, val in zip(indexList, valueList):
|
||||
self._set_single(i, val)
|
||||
|
||||
def _assign_simple_slice(self, start, stop, valueList):
|
||||
'Assign a simple slice; Can assign slice of any length'
|
||||
origLen = len(self)
|
||||
stop = max(start, stop)
|
||||
newLen = origLen - stop + start + len(valueList)
|
||||
def newItems():
|
||||
for i in xrange(origLen + 1):
|
||||
if i == start:
|
||||
for val in valueList:
|
||||
yield val
|
||||
|
||||
if i < origLen:
|
||||
if i < start or i >= stop:
|
||||
yield self._getitem_internal(i)
|
||||
|
||||
self._rebuild(newLen, newItems())
|
|
@ -0,0 +1,138 @@
|
|||
from ctypes import c_uint
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
|
||||
class Point(GEOSGeometry):
|
||||
_minlength = 2
|
||||
|
||||
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:
|
||||
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
|
||||
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
|
||||
"""
|
||||
if isinstance(x, (tuple, list)):
|
||||
# Here a tuple or list was passed in under the `x` parameter.
|
||||
ndim = len(x)
|
||||
coords = x
|
||||
elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)):
|
||||
# Here X, Y, and (optionally) Z were passed in individually, as parameters.
|
||||
if isinstance(z, (int, float, long)):
|
||||
ndim = 3
|
||||
coords = [x, y, z]
|
||||
else:
|
||||
ndim = 2
|
||||
coords = [x, y]
|
||||
else:
|
||||
raise TypeError('Invalid parameters given for Point initialization.')
|
||||
|
||||
point = self._create_point(ndim, coords)
|
||||
|
||||
# Initializing using the address returned from the GEOS
|
||||
# createPoint factory.
|
||||
super(Point, self).__init__(point, srid=srid)
|
||||
|
||||
@classmethod
|
||||
def _create_point(self, ndim, coords):
|
||||
"""
|
||||
Create a coordinate sequence, set X, Y, [Z], and create point
|
||||
"""
|
||||
if ndim < 2 or ndim > 3:
|
||||
raise TypeError('Invalid point dimension: %s' % str(ndim))
|
||||
|
||||
cs = capi.create_cs(c_uint(1), c_uint(ndim))
|
||||
i = iter(coords)
|
||||
capi.cs_setx(cs, 0, i.next())
|
||||
capi.cs_sety(cs, 0, i.next())
|
||||
if ndim == 3: capi.cs_setz(cs, 0, i.next())
|
||||
|
||||
return capi.create_point(cs)
|
||||
|
||||
def _getitem_external(self, index):
|
||||
return self._cs.getOrdinate(index, 0)
|
||||
|
||||
def _set_collection(self, length, items):
|
||||
ptr = self._create_point(length, items)
|
||||
if ptr:
|
||||
capi.destroy_geom(self.ptr)
|
||||
self._ptr = ptr
|
||||
self._set_cs()
|
||||
else:
|
||||
# can this happen?
|
||||
raise GEOSException('Geometry resulting from slice deletion was invalid.')
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._cs.setOrdinate(index, 0, value)
|
||||
|
||||
def __iter__(self):
|
||||
"Allows iteration over coordinates of this Point."
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of dimensions for this Point (either 0, 2 or 3)."
|
||||
if self.empty: return 0
|
||||
if self.hasz: return 3
|
||||
else: return 2
|
||||
|
||||
def _getitem_external(self, index):
|
||||
self._checkindex(index)
|
||||
if index == 0:
|
||||
return self.x
|
||||
elif index == 1:
|
||||
return self.y
|
||||
elif index == 2:
|
||||
return self.z
|
||||
_getitem_internal = _getitem_external
|
||||
|
||||
def get_x(self):
|
||||
"Returns the X component of the Point."
|
||||
return self._cs.getOrdinate(0, 0)
|
||||
|
||||
def set_x(self, value):
|
||||
"Sets the X component of the Point."
|
||||
self._cs.setOrdinate(0, 0, value)
|
||||
|
||||
def get_y(self):
|
||||
"Returns the Y component of the Point."
|
||||
return self._cs.getOrdinate(1, 0)
|
||||
|
||||
def set_y(self, value):
|
||||
"Sets the Y component of the Point."
|
||||
self._cs.setOrdinate(1, 0, value)
|
||||
|
||||
def get_z(self):
|
||||
"Returns the Z component of the Point."
|
||||
if self.hasz:
|
||||
return self._cs.getOrdinate(2, 0)
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_z(self, value):
|
||||
"Sets the Z component of the Point."
|
||||
if self.hasz:
|
||||
self._cs.setOrdinate(2, 0, value)
|
||||
else:
|
||||
raise GEOSException('Cannot set Z on 2D Point.')
|
||||
|
||||
# X, Y, Z properties
|
||||
x = property(get_x, set_x)
|
||||
y = property(get_y, set_y)
|
||||
z = property(get_z, set_z)
|
||||
|
||||
### Tuple setting and retrieval routines. ###
|
||||
def get_coords(self):
|
||||
"Returns a tuple of the point."
|
||||
return self._cs.tuple
|
||||
|
||||
def set_coords(self, tup):
|
||||
"Sets the coordinates of the point with the given tuple."
|
||||
self._cs[0] = tup
|
||||
|
||||
# The tuple and coords properties
|
||||
tuple = property(get_coords, set_coords)
|
||||
coords = tuple
|
|
@ -0,0 +1,172 @@
|
|||
from ctypes import c_uint, byref
|
||||
from django.contrib.gis.geos.error import GEOSIndexError
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
|
||||
from django.contrib.gis.geos.linestring import LinearRing
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
|
||||
class Polygon(GEOSGeometry):
|
||||
_minlength = 1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initializes on an exterior ring and a sequence of holes (both
|
||||
instances may be either LinearRing instances, or a tuple/list
|
||||
that may be constructed into a LinearRing).
|
||||
|
||||
Examples of initialization, where shell, hole1, and hole2 are
|
||||
valid LinearRing geometries:
|
||||
>>> poly = Polygon(shell, hole1, hole2)
|
||||
>>> poly = Polygon(shell, (hole1, hole2))
|
||||
|
||||
Example where a tuple parameters are used:
|
||||
>>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
|
||||
((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
|
||||
"""
|
||||
if not args:
|
||||
raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
|
||||
|
||||
# Getting the ext_ring and init_holes parameters from the argument list
|
||||
ext_ring = args[0]
|
||||
init_holes = args[1:]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
# If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
|
||||
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
|
||||
if len(init_holes[0]) == 0:
|
||||
init_holes = ()
|
||||
n_holes = 0
|
||||
elif isinstance(init_holes[0][0], LinearRing):
|
||||
init_holes = init_holes[0]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
polygon = self._create_polygon(n_holes + 1, (ext_ring,) + init_holes)
|
||||
super(Polygon, self).__init__(polygon, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterates over each ring in the polygon."
|
||||
for i in xrange(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Returns the number of rings in this Polygon."
|
||||
return self.num_interior_rings + 1
|
||||
|
||||
@classmethod
|
||||
def from_bbox(cls, bbox):
|
||||
"Constructs a Polygon from a bounding box (4-tuple)."
|
||||
x0, y0, x1, y1 = bbox
|
||||
return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
|
||||
x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
|
||||
|
||||
### These classmethods and routines are needed for list-like operation w/ListMixin ###
|
||||
@classmethod
|
||||
def _create_polygon(cls, length, items):
|
||||
# Instantiate LinearRing objects if necessary, but don't clone them yet
|
||||
# _construct_ring will throw a TypeError if a parameter isn't a valid ring
|
||||
# If we cloned the pointers here, we wouldn't be able to clean up
|
||||
# in case of error.
|
||||
rings = []
|
||||
for r in items:
|
||||
if isinstance(r, GEOM_PTR):
|
||||
rings.append(r)
|
||||
else:
|
||||
rings.append(cls._construct_ring(r))
|
||||
|
||||
shell = cls._clone(rings.pop(0))
|
||||
|
||||
n_holes = length - 1
|
||||
if n_holes:
|
||||
holes = get_pointer_arr(n_holes)
|
||||
for i, r in enumerate(rings):
|
||||
holes[i] = cls._clone(r)
|
||||
holes_param = byref(holes)
|
||||
else:
|
||||
holes_param = None
|
||||
|
||||
return capi.create_polygon(shell, holes_param, c_uint(n_holes))
|
||||
|
||||
@classmethod
|
||||
def _clone(cls, g):
|
||||
if isinstance(g, GEOM_PTR):
|
||||
return capi.geom_clone(g)
|
||||
else:
|
||||
return capi.geom_clone(g.ptr)
|
||||
|
||||
@classmethod
|
||||
def _construct_ring(cls, param, msg='Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'):
|
||||
"Helper routine for trying to construct a ring from the given parameter."
|
||||
if isinstance(param, LinearRing): return param
|
||||
try:
|
||||
ring = LinearRing(param)
|
||||
return ring
|
||||
except TypeError:
|
||||
raise TypeError(msg)
|
||||
|
||||
def _set_collection(self, length, items):
|
||||
# Getting the current pointer, replacing with the newly constructed
|
||||
# geometry, and destroying the old geometry.
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self.ptr = self._create_polygon(length, items)
|
||||
if srid: self.srid = srid
|
||||
capi.destroy_geom(prev_ptr)
|
||||
|
||||
def _getitem_internal(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 at the given index (e.g., poly[1] and poly[2] would
|
||||
return the first and second interior ring, respectively).
|
||||
|
||||
CAREFUL: Internal/External are not the same as Interior/Exterior!
|
||||
_getitem_internal returns a pointer from the existing geometries for use
|
||||
internally by the object's methods. _getitem_external returns a clone
|
||||
of the same geometry for use by external code.
|
||||
"""
|
||||
if index == 0:
|
||||
return capi.get_extring(self.ptr)
|
||||
else:
|
||||
# Getting the interior ring, have to subtract 1 from the index.
|
||||
return capi.get_intring(self.ptr, index-1)
|
||||
|
||||
def _getitem_external(self, index):
|
||||
return GEOSGeometry(capi.geom_clone(self._getitem_internal(index)), srid=self.srid)
|
||||
|
||||
# Because Polygonss need to be rebuilt upon the changing of a
|
||||
# component geometry, these routines are set to their counterparts that
|
||||
# rebuild the entire geometry.
|
||||
_set_single = GEOSGeometry._set_single_rebuild
|
||||
_assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
|
||||
|
||||
#### Polygon Properties ####
|
||||
@property
|
||||
def num_interior_rings(self):
|
||||
"Returns the number of interior rings."
|
||||
# Getting the number of rings
|
||||
return capi.get_nrings(self.ptr)
|
||||
|
||||
def _get_ext_ring(self):
|
||||
"Gets the exterior ring of the Polygon."
|
||||
return self[0]
|
||||
|
||||
def _set_ext_ring(self, ring):
|
||||
"Sets the exterior ring of the Polygon."
|
||||
self[0] = ring
|
||||
|
||||
# Properties for the exterior ring/shell.
|
||||
exterior_ring = property(_get_ext_ring, _set_ext_ring)
|
||||
shell = exterior_ring
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Gets the tuple for each ring in this Polygon."
|
||||
return tuple([self[i].tuple for i in xrange(len(self))])
|
||||
coords = tuple
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Returns the KML representation of this Polygon."
|
||||
inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml
|
||||
for i in xrange(self.num_interior_rings)])
|
||||
return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)
|
|
@ -0,0 +1,30 @@
|
|||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.prototypes import prepared as capi
|
||||
|
||||
class PreparedGeometry(GEOSBase):
|
||||
"""
|
||||
A geometry that is prepared for performing certain operations.
|
||||
At the moment this includes the contains covers, and intersects
|
||||
operations.
|
||||
"""
|
||||
ptr_type = capi.PREPGEOM_PTR
|
||||
|
||||
def __init__(self, geom):
|
||||
if not isinstance(geom, GEOSGeometry): raise TypeError
|
||||
self.ptr = capi.geos_prepare(geom.ptr)
|
||||
|
||||
def __del__(self):
|
||||
if self._ptr: capi.prepared_destroy(self._ptr)
|
||||
|
||||
def contains(self, other):
|
||||
return capi.prepared_contains(self.ptr, other.ptr)
|
||||
|
||||
def contains_properly(self, other):
|
||||
return capi.prepared_contains_properly(self.ptr, other.ptr)
|
||||
|
||||
def covers(self, other):
|
||||
return capi.prepared_covers(self.ptr, other.ptr)
|
||||
|
||||
def intersects(self, other):
|
||||
return capi.prepared_intersects(self.ptr, other.ptr)
|
|
@ -27,7 +27,4 @@ from django.contrib.gis.geos.prototypes.predicates import geos_hasz, geos_isempt
|
|||
geos_intersects, geos_overlaps, geos_relatepattern, geos_touches, geos_within
|
||||
|
||||
# Topology routines
|
||||
from django.contrib.gis.geos.prototypes.topology import \
|
||||
geos_boundary, geos_buffer, geos_centroid, geos_convexhull, geos_difference, \
|
||||
geos_envelope, geos_intersection, geos_pointonsurface, geos_preservesimplify, \
|
||||
geos_simplify, geos_symdifference, geos_union, geos_relate
|
||||
from django.contrib.gis.geos.prototypes.topology import *
|
||||
|
|
|
@ -48,22 +48,31 @@ def check_predicate(result, func, cargs):
|
|||
raise GEOSException('Error encountered on GEOS C predicate function "%s".' % func.__name__)
|
||||
|
||||
def check_sized_string(result, func, cargs):
|
||||
"Error checking for routines that return explicitly sized strings."
|
||||
"""
|
||||
Error checking for routines that return explicitly sized strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result:
|
||||
raise GEOSException('Invalid string pointer returned by GEOS C function "%s"' % func.__name__)
|
||||
# A c_size_t object is passed in by reference for the second
|
||||
# argument on these routines, and its needed to determine the
|
||||
# correct size.
|
||||
s = string_at(result, last_arg_byref(cargs))
|
||||
# Freeing the memory allocated within GEOS
|
||||
libc.free(result)
|
||||
return s
|
||||
|
||||
def check_string(result, func, cargs):
|
||||
"Error checking for routines that return strings."
|
||||
"""
|
||||
Error checking for routines that return strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result: raise GEOSException('Error encountered checking string return value in GEOS C function "%s".' % func.__name__)
|
||||
# Getting the string value at the pointer address.
|
||||
s = string_at(result)
|
||||
# Freeing the memory allocated by the GEOS library.
|
||||
# Freeing the memory allocated within GEOS
|
||||
libc.free(result)
|
||||
return s
|
||||
|
||||
|
@ -73,4 +82,3 @@ def check_zero(result, func, cargs):
|
|||
raise GEOSException('Error encountered in GEOS C function "%s".' % func.__name__)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from ctypes import c_char_p, c_int, c_size_t, c_ubyte, c_uint, POINTER
|
||||
from django.contrib.gis.geos.libgeos import lgeos, CS_PTR, GEOM_PTR
|
||||
from django.contrib.gis.geos.libgeos import lgeos, CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
|
||||
from django.contrib.gis.geos.prototypes.errcheck import \
|
||||
check_geom, check_minus_one, check_sized_string, check_string, check_zero
|
||||
|
||||
|
@ -55,8 +55,6 @@ def int_from_geom(func, zero=False):
|
|||
|
||||
def string_from_geom(func):
|
||||
"Argument is a Geometry, return type is a string."
|
||||
# We do _not_ specify an argument type because we want just an
|
||||
# address returned from the function.
|
||||
func.argtypes = [GEOM_PTR]
|
||||
func.restype = geos_char_p
|
||||
func.errcheck = check_string
|
||||
|
@ -64,13 +62,7 @@ def string_from_geom(func):
|
|||
|
||||
### ctypes prototypes ###
|
||||
|
||||
# TODO: Tell all users to use GEOS 3.0.0, instead of the release
|
||||
# candidates, and use the new Reader and Writer APIs (e.g.,
|
||||
# GEOSWKT[Reader|Writer], GEOSWKB[Reader|Writer]). A good time
|
||||
# to do this will be when Refractions releases a Windows PostGIS
|
||||
# installer using GEOS 3.0.0.
|
||||
|
||||
# Creation routines from WKB, HEX, WKT
|
||||
# Deprecated creation routines from WKB, HEX, WKT
|
||||
from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
|
||||
from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
|
||||
from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
from ctypes import c_char_p, c_int, c_char, c_size_t, Structure, POINTER
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string, check_sized_string
|
||||
from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
|
||||
|
||||
### The WKB/WKT Reader/Writer structures and pointers ###
|
||||
class WKTReader_st(Structure): pass
|
||||
class WKTWriter_st(Structure): pass
|
||||
class WKBReader_st(Structure): pass
|
||||
class WKBWriter_st(Structure): pass
|
||||
|
||||
WKT_READ_PTR = POINTER(WKTReader_st)
|
||||
WKT_WRITE_PTR = POINTER(WKTWriter_st)
|
||||
WKB_READ_PTR = POINTER(WKBReader_st)
|
||||
WKB_WRITE_PTR = POINTER(WKBReader_st)
|
||||
|
||||
### WKTReader routines ###
|
||||
wkt_reader_create = lgeos.GEOSWKTReader_create
|
||||
wkt_reader_create.restype = WKT_READ_PTR
|
||||
|
||||
wkt_reader_destroy = lgeos.GEOSWKTReader_destroy
|
||||
wkt_reader_destroy.argtypes = [WKT_READ_PTR]
|
||||
|
||||
wkt_reader_read = lgeos.GEOSWKTReader_read
|
||||
wkt_reader_read.argtypes = [WKT_READ_PTR, c_char_p]
|
||||
wkt_reader_read.restype = GEOM_PTR
|
||||
wkt_reader_read.errcheck = check_geom
|
||||
|
||||
### WKTWriter routines ###
|
||||
wkt_writer_create = lgeos.GEOSWKTWriter_create
|
||||
wkt_writer_create.restype = WKT_WRITE_PTR
|
||||
|
||||
wkt_writer_destroy = lgeos.GEOSWKTWriter_destroy
|
||||
wkt_writer_destroy.argtypes = [WKT_WRITE_PTR]
|
||||
|
||||
wkt_writer_write = lgeos.GEOSWKTWriter_write
|
||||
wkt_writer_write.argtypes = [WKT_WRITE_PTR, GEOM_PTR]
|
||||
wkt_writer_write.restype = geos_char_p
|
||||
wkt_writer_write.errcheck = check_string
|
||||
|
||||
### WKBReader routines ###
|
||||
wkb_reader_create = lgeos.GEOSWKBReader_create
|
||||
wkb_reader_create.restype = WKB_READ_PTR
|
||||
|
||||
wkb_reader_destroy = lgeos.GEOSWKBReader_destroy
|
||||
wkb_reader_destroy.argtypes = [WKB_READ_PTR]
|
||||
|
||||
def wkb_read_func(func):
|
||||
# Although the function definitions take `const unsigned char *`
|
||||
# as their parameter, we use c_char_p here so the function may
|
||||
# take Python strings directly as parameters. Inside Python there
|
||||
# is not a difference between signed and unsigned characters, so
|
||||
# it is not a problem.
|
||||
func.argtypes = [WKB_READ_PTR, c_char_p, c_size_t]
|
||||
func.restype = GEOM_PTR
|
||||
func.errcheck = check_geom
|
||||
return func
|
||||
|
||||
wkb_reader_read = wkb_read_func(lgeos.GEOSWKBReader_read)
|
||||
wkb_reader_read_hex = wkb_read_func(lgeos.GEOSWKBReader_readHEX)
|
||||
|
||||
### WKBWriter routines ###
|
||||
wkb_writer_create = lgeos.GEOSWKBWriter_create
|
||||
wkb_writer_create.restype = WKB_WRITE_PTR
|
||||
|
||||
wkb_writer_destroy = lgeos.GEOSWKBWriter_destroy
|
||||
wkb_writer_destroy.argtypes = [WKB_WRITE_PTR]
|
||||
|
||||
# WKB Writing prototypes.
|
||||
def wkb_write_func(func):
|
||||
func.argtypes = [WKB_WRITE_PTR, GEOM_PTR, POINTER(c_size_t)]
|
||||
func.restype = c_uchar_p
|
||||
func.errcheck = check_sized_string
|
||||
return func
|
||||
|
||||
wkb_writer_write = wkb_write_func(lgeos.GEOSWKBWriter_write)
|
||||
wkb_writer_write_hex = wkb_write_func(lgeos.GEOSWKBWriter_writeHEX)
|
||||
|
||||
# WKBWriter property getter/setter prototypes.
|
||||
def wkb_writer_get(func, restype=c_int):
|
||||
func.argtypes = [WKB_WRITE_PTR]
|
||||
func.restype = restype
|
||||
return func
|
||||
|
||||
def wkb_writer_set(func, argtype=c_int):
|
||||
func.argtypes = [WKB_WRITE_PTR, argtype]
|
||||
return func
|
||||
|
||||
wkb_writer_get_byteorder = wkb_writer_get(lgeos.GEOSWKBWriter_getByteOrder)
|
||||
wkb_writer_set_byteorder = wkb_writer_set(lgeos.GEOSWKBWriter_setByteOrder)
|
||||
wkb_writer_get_outdim = wkb_writer_get(lgeos.GEOSWKBWriter_getOutputDimension)
|
||||
wkb_writer_set_outdim = wkb_writer_set(lgeos.GEOSWKBWriter_setOutputDimension)
|
||||
wkb_writer_get_include_srid = wkb_writer_get(lgeos.GEOSWKBWriter_getIncludeSRID, restype=c_char)
|
||||
wkb_writer_set_include_srid = wkb_writer_set(lgeos.GEOSWKBWriter_setIncludeSRID, argtype=c_char)
|
|
@ -0,0 +1,24 @@
|
|||
from ctypes import c_char
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, PREPGEOM_PTR
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_predicate
|
||||
|
||||
# Prepared geometry constructor and destructors.
|
||||
geos_prepare = lgeos.GEOSPrepare
|
||||
geos_prepare.argtypes = [GEOM_PTR]
|
||||
geos_prepare.restype = PREPGEOM_PTR
|
||||
|
||||
prepared_destroy = lgeos.GEOSPreparedGeom_destroy
|
||||
prepared_destroy.argtpes = [PREPGEOM_PTR]
|
||||
prepared_destroy.restype = None
|
||||
|
||||
# Prepared geometry binary predicate support.
|
||||
def prepared_predicate(func):
|
||||
func.argtypes= [PREPGEOM_PTR, GEOM_PTR]
|
||||
func.restype = c_char
|
||||
func.errcheck = check_predicate
|
||||
return func
|
||||
|
||||
prepared_contains = prepared_predicate(lgeos.GEOSPreparedContains)
|
||||
prepared_contains_properly = prepared_predicate(lgeos.GEOSPreparedContainsProperly)
|
||||
prepared_covers = prepared_predicate(lgeos.GEOSPreparedCovers)
|
||||
prepared_intersects = prepared_predicate(lgeos.GEOSPreparedIntersects)
|
|
@ -2,8 +2,13 @@
|
|||
This module houses the GEOS ctypes prototype functions for the
|
||||
topological operations on geometries.
|
||||
"""
|
||||
__all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull',
|
||||
'geos_difference', 'geos_envelope', 'geos_intersection',
|
||||
'geos_linemerge', 'geos_pointonsurface', 'geos_preservesimplify',
|
||||
'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
|
||||
|
||||
from ctypes import c_char_p, c_double, c_int
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
|
||||
|
||||
def topology(func, *args):
|
||||
|
@ -23,6 +28,7 @@ geos_convexhull = topology(lgeos.GEOSConvexHull)
|
|||
geos_difference = topology(lgeos.GEOSDifference, GEOM_PTR)
|
||||
geos_envelope = topology(lgeos.GEOSEnvelope)
|
||||
geos_intersection = topology(lgeos.GEOSIntersection, GEOM_PTR)
|
||||
geos_linemerge = topology(lgeos.GEOSLineMerge)
|
||||
geos_pointonsurface = topology(lgeos.GEOSPointOnSurface)
|
||||
geos_preservesimplify = topology(lgeos.GEOSTopologyPreserveSimplify, c_double)
|
||||
geos_simplify = topology(lgeos.GEOSSimplify, c_double)
|
||||
|
@ -33,3 +39,10 @@ geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
|
|||
geos_relate = lgeos.GEOSRelate
|
||||
geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
|
||||
geos_relate.errcheck = check_string
|
||||
|
||||
# Routines only in GEOS 3.1+
|
||||
if GEOS_PREPARE:
|
||||
geos_cascaded_union = lgeos.GEOSUnionCascaded
|
||||
geos_cascaded_union.argtypes = [GEOM_PTR]
|
||||
geos_cascaded_union.restype = GEOM_PTR
|
||||
__all__.append('geos_cascaded_union')
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
GEOS Testing module.
|
||||
"""
|
||||
from unittest import TestSuite, TextTestRunner
|
||||
import test_geos, test_geos_mutation, test_mutable_list
|
||||
|
||||
test_suites = [
|
||||
test_geos.suite(),
|
||||
test_geos_mutation.suite(),
|
||||
test_mutable_list.suite(),
|
||||
]
|
||||
|
||||
def suite():
|
||||
"Builds a test suite for the GEOS tests."
|
||||
s = TestSuite()
|
||||
map(s.addTest, test_suites)
|
||||
return s
|
||||
|
||||
def run(verbosity=1):
|
||||
"Runs the GEOS tests."
|
||||
TextTestRunner(verbosity=verbosity).run(suite())
|
||||
|
||||
if __name__ == '__main__':
|
||||
run(2)
|
|
@ -0,0 +1,184 @@
|
|||
from django.contrib.gis.geos import *
|
||||
from random import random
|
||||
|
||||
SEQ_LENGTH = 10
|
||||
SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
|
||||
SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
|
||||
SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
|
||||
|
||||
def seqrange(): return xrange(*SEQ_RANGE)
|
||||
|
||||
def random_coord(dim = 2, # coordinate dimensions
|
||||
rng = (-50,50), # coordinate range
|
||||
num_type = float,
|
||||
round_coords = True):
|
||||
|
||||
if round_coords:
|
||||
num = lambda: num_type(round(random() * (rng[1]-rng[0]) + rng[0]))
|
||||
else:
|
||||
num = lambda: num_type(random() * (rng[1]-rng[0]) + rng[0])
|
||||
|
||||
return tuple( num() for axis in xrange(dim) )
|
||||
|
||||
def random_list(length = SEQ_LENGTH, ring = False, **kwargs):
|
||||
result = [ random_coord(**kwargs) for index in xrange(length) ]
|
||||
if ring:
|
||||
result[-1] = result[0]
|
||||
|
||||
return result
|
||||
|
||||
random_list.single = random_coord
|
||||
|
||||
def random_coll(count = SEQ_LENGTH, **kwargs):
|
||||
return [ tuple(random_list(**kwargs)) for i in xrange(count) ]
|
||||
|
||||
random_coll.single = random_list
|
||||
|
||||
class PyMutTestGeom:
|
||||
"The Test Geometry class container."
|
||||
def __init__(self, geom_type, coords_fcn=random_list, subtype=tuple, **kwargs):
|
||||
self.geom_type = geom_type
|
||||
self.subtype = subtype
|
||||
self.coords_fcn = coords_fcn
|
||||
self.fcn_args = kwargs
|
||||
self.coords = self.coords_fcn(**kwargs)
|
||||
self.geom = self.make_geom()
|
||||
|
||||
def newitem(self, **kwargs):
|
||||
a = self.coords_fcn.single(**kwargs)
|
||||
return self.subtype(a), tuple(a)
|
||||
|
||||
@property
|
||||
def tuple_coords(self):
|
||||
return tuple(self.coords)
|
||||
|
||||
def make_geom(self):
|
||||
return self.geom_type(map(self.subtype,self.coords))
|
||||
|
||||
|
||||
def slice_geometries(ring=True):
|
||||
testgeoms = [
|
||||
PyMutTestGeom(LineString),
|
||||
PyMutTestGeom(MultiPoint, subtype=Point),
|
||||
PyMutTestGeom(MultiLineString, coords_fcn=random_coll, subtype=LineString),
|
||||
]
|
||||
if ring:
|
||||
testgeoms.append(PyMutTestGeom(LinearRing, ring=True))
|
||||
|
||||
return testgeoms
|
||||
|
||||
def getslice_functions():
|
||||
def gs_01(x): x[0:4],
|
||||
def gs_02(x): x[5:-1],
|
||||
def gs_03(x): x[6:2:-1],
|
||||
def gs_04(x): x[:],
|
||||
def gs_05(x): x[:3],
|
||||
def gs_06(x): x[::2],
|
||||
def gs_07(x): x[::-4],
|
||||
def gs_08(x): x[7:7],
|
||||
def gs_09(x): x[20:],
|
||||
|
||||
# don't really care about ringy-ness here
|
||||
return mark_ring(vars(), 'gs_')
|
||||
|
||||
def delslice_functions():
|
||||
def ds_01(x): del x[0:4]
|
||||
def ds_02(x): del x[5:-1]
|
||||
def ds_03(x): del x[6:2:-1]
|
||||
def ds_04(x): del x[:] # should this be allowed?
|
||||
def ds_05(x): del x[:3]
|
||||
def ds_06(x): del x[1:9:2]
|
||||
def ds_07(x): del x[::-4]
|
||||
def ds_08(x): del x[7:7]
|
||||
def ds_09(x): del x[-7:-2]
|
||||
|
||||
return mark_ring(vars(), 'ds_')
|
||||
|
||||
def setslice_extended_functions(g):
|
||||
a = g.coords_fcn(3, rng=(100,150))
|
||||
def maptype(x,a):
|
||||
if isinstance(x, list): return a
|
||||
else: return map(g.subtype, a)
|
||||
|
||||
def sse_00(x): x[:3:1] = maptype(x, a)
|
||||
def sse_01(x): x[0:3:1] = maptype(x, a)
|
||||
def sse_02(x): x[2:5:1] = maptype(x, a)
|
||||
def sse_03(x): x[-3::1] = maptype(x, a)
|
||||
def sse_04(x): x[-4:-1:1] = maptype(x, a)
|
||||
def sse_05(x): x[8:5:-1] = maptype(x, a)
|
||||
def sse_06(x): x[-6:-9:-1] = maptype(x, a)
|
||||
def sse_07(x): x[:8:3] = maptype(x, a)
|
||||
def sse_08(x): x[1::3] = maptype(x, a)
|
||||
def sse_09(x): x[-2::-3] = maptype(x, a)
|
||||
def sse_10(x): x[7:1:-2] = maptype(x, a)
|
||||
def sse_11(x): x[2:8:2] = maptype(x, a)
|
||||
|
||||
return mark_ring(vars(), 'sse_')
|
||||
|
||||
def setslice_simple_functions(g):
|
||||
a = g.coords_fcn(3, rng=(100,150))
|
||||
def maptype(x,a):
|
||||
if isinstance(x, list): return a
|
||||
else: return map(g.subtype, a)
|
||||
|
||||
def ss_00(x): x[:0] = maptype(x, a)
|
||||
def ss_01(x): x[:1] = maptype(x, a)
|
||||
def ss_02(x): x[:2] = maptype(x, a)
|
||||
def ss_03(x): x[:3] = maptype(x, a)
|
||||
def ss_04(x): x[-4:] = maptype(x, a)
|
||||
def ss_05(x): x[-3:] = maptype(x, a)
|
||||
def ss_06(x): x[-2:] = maptype(x, a)
|
||||
def ss_07(x): x[-1:] = maptype(x, a)
|
||||
def ss_08(x): x[5:] = maptype(x, a)
|
||||
def ss_09(x): x[:] = maptype(x, a)
|
||||
def ss_10(x): x[4:4] = maptype(x, a)
|
||||
def ss_11(x): x[4:5] = maptype(x, a)
|
||||
def ss_12(x): x[4:7] = maptype(x, a)
|
||||
def ss_13(x): x[4:8] = maptype(x, a)
|
||||
def ss_14(x): x[10:] = maptype(x, a)
|
||||
def ss_15(x): x[20:30] = maptype(x, a)
|
||||
def ss_16(x): x[-13:-8] = maptype(x, a)
|
||||
def ss_17(x): x[-13:-9] = maptype(x, a)
|
||||
def ss_18(x): x[-13:-10] = maptype(x, a)
|
||||
def ss_19(x): x[-13:-11] = maptype(x, a)
|
||||
|
||||
return mark_ring(vars(), 'ss_')
|
||||
|
||||
def test_geos_functions():
|
||||
|
||||
return (
|
||||
lambda x: x.num_coords,
|
||||
lambda x: x.empty,
|
||||
lambda x: x.valid,
|
||||
lambda x: x.simple,
|
||||
lambda x: x.ring,
|
||||
lambda x: x.boundary,
|
||||
lambda x: x.convex_hull,
|
||||
lambda x: x.extend,
|
||||
lambda x: x.area,
|
||||
lambda x: x.length,
|
||||
)
|
||||
|
||||
def mark_ring(locals, name_pat, length=SEQ_LENGTH):
|
||||
'''
|
||||
Accepts an array of functions which perform slice modifications
|
||||
and labels each function as to whether or not it preserves ring-ness
|
||||
'''
|
||||
func_array = [ val for name, val in locals.items()
|
||||
if hasattr(val, '__call__')
|
||||
and name.startswith(name_pat) ]
|
||||
|
||||
for i in xrange(len(func_array)):
|
||||
a = range(length)
|
||||
a[-1] = a[0]
|
||||
func_array[i](a)
|
||||
ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
|
||||
func_array[i].ring = ring
|
||||
|
||||
return func_array
|
||||
|
||||
def getcoords(o):
|
||||
if hasattr(o, 'coords'):
|
||||
return o.coords
|
||||
else:
|
||||
return o
|
|
@ -1,12 +1,9 @@
|
|||
import random, unittest, sys
|
||||
from ctypes import ArgumentError
|
||||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.geos.base import HAS_GDAL
|
||||
from django.contrib.gis.geos.base import gdal, numpy
|
||||
from django.contrib.gis.tests.geometries import *
|
||||
|
||||
if HAS_NUMPY: from numpy import array
|
||||
if HAS_GDAL: from django.contrib.gis.gdal import OGRGeometry, SpatialReference, CoordTransform, GEOJSON
|
||||
|
||||
class GEOSTest(unittest.TestCase):
|
||||
|
||||
@property
|
||||
|
@ -49,6 +46,10 @@ class GEOSTest(unittest.TestCase):
|
|||
g = fromstr(err.wkt)
|
||||
except (GEOSException, ValueError):
|
||||
pass
|
||||
|
||||
# Bad WKB
|
||||
self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
|
||||
|
||||
print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
|
||||
|
||||
class NotAGeometry(object):
|
||||
|
@ -58,8 +59,6 @@ class GEOSTest(unittest.TestCase):
|
|||
self.assertRaises(TypeError, GEOSGeometry, NotAGeometry())
|
||||
# None
|
||||
self.assertRaises(TypeError, GEOSGeometry, None)
|
||||
# Bad WKB
|
||||
self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
|
||||
|
||||
def test01e_wkb(self):
|
||||
"Testing WKB output."
|
||||
|
@ -99,7 +98,7 @@ class GEOSTest(unittest.TestCase):
|
|||
|
||||
def test01i_json(self):
|
||||
"Testing GeoJSON input/output (via GDAL)."
|
||||
if not HAS_GDAL or not GEOJSON: return
|
||||
if not gdal or not gdal.GEOJSON: return
|
||||
for g in json_geoms:
|
||||
geom = GEOSGeometry(g.wkt)
|
||||
if not hasattr(g, 'not_equal'):
|
||||
|
@ -107,7 +106,24 @@ class GEOSTest(unittest.TestCase):
|
|||
self.assertEqual(g.json, geom.geojson)
|
||||
self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
|
||||
|
||||
def test01j_eq(self):
|
||||
def test01k_fromfile(self):
|
||||
"Testing the fromfile() factory."
|
||||
from StringIO import StringIO
|
||||
ref_pnt = GEOSGeometry('POINT(5 23)')
|
||||
|
||||
wkt_f = StringIO()
|
||||
wkt_f.write(ref_pnt.wkt)
|
||||
wkb_f = StringIO()
|
||||
wkb_f.write(str(ref_pnt.wkb))
|
||||
|
||||
# Other tests use `fromfile()` on string filenames so those
|
||||
# aren't tested here.
|
||||
for fh in (wkt_f, wkb_f):
|
||||
fh.seek(0)
|
||||
pnt = fromfile(fh)
|
||||
self.assertEqual(ref_pnt, pnt)
|
||||
|
||||
def test01k_eq(self):
|
||||
"Testing equivalence."
|
||||
p = fromstr('POINT(5 23)')
|
||||
self.assertEqual(p, p.wkt)
|
||||
|
@ -220,7 +236,7 @@ class GEOSTest(unittest.TestCase):
|
|||
self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
|
||||
self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
|
||||
self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
|
||||
if HAS_NUMPY: self.assertEqual(ls, LineString(array(ls.tuple))) # as numpy array
|
||||
if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array
|
||||
|
||||
def test03b_multilinestring(self):
|
||||
"Testing MultiLineString objects."
|
||||
|
@ -260,10 +276,16 @@ class GEOSTest(unittest.TestCase):
|
|||
self.assertEqual(lr, LinearRing(lr.tuple))
|
||||
self.assertEqual(lr, LinearRing(*lr.tuple))
|
||||
self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
|
||||
if HAS_NUMPY: self.assertEqual(lr, LinearRing(array(lr.tuple)))
|
||||
if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple)))
|
||||
|
||||
def test05a_polygons(self):
|
||||
"Testing Polygon objects."
|
||||
|
||||
# Testing `from_bbox` class method
|
||||
bbox = (-180, -90, 180, 90)
|
||||
p = Polygon.from_bbox( bbox )
|
||||
self.assertEqual(bbox, p.extent)
|
||||
|
||||
prev = fromstr('POINT(0 0)')
|
||||
for p in polygons:
|
||||
# Creating the Polygon, testing its properties.
|
||||
|
@ -297,7 +319,7 @@ class GEOSTest(unittest.TestCase):
|
|||
# Testing __getitem__ and __setitem__ on invalid indices
|
||||
self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly))
|
||||
self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False)
|
||||
self.assertRaises(GEOSIndexError, poly.__getitem__, -1)
|
||||
self.assertRaises(GEOSIndexError, poly.__getitem__, -1 * len(poly) - 1)
|
||||
|
||||
# Testing __iter__
|
||||
for r in poly:
|
||||
|
@ -643,7 +665,7 @@ class GEOSTest(unittest.TestCase):
|
|||
mpoly = MultiPolygon(poly.clone(), poly)
|
||||
self.assertEqual(8.0, mpoly.length)
|
||||
|
||||
def test20_emptyCollections(self):
|
||||
def test20a_emptyCollections(self):
|
||||
"Testing empty geometries and collections."
|
||||
gc1 = GeometryCollection([])
|
||||
gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
|
||||
|
@ -681,16 +703,35 @@ class GEOSTest(unittest.TestCase):
|
|||
else:
|
||||
self.assertRaises(GEOSIndexError, g.__getitem__, 0)
|
||||
|
||||
def test20b_collections_of_collections(self):
|
||||
"Testing GeometryCollection handling of other collections."
|
||||
# Creating a GeometryCollection WKT string composed of other
|
||||
# collections and polygons.
|
||||
coll = [mp.wkt for mp in multipolygons if mp.valid]
|
||||
coll.extend([mls.wkt for mls in multilinestrings])
|
||||
coll.extend([p.wkt for p in polygons])
|
||||
coll.extend([mp.wkt for mp in multipoints])
|
||||
gc_wkt = 'GEOMETRYCOLLECTION(%s)' % ','.join(coll)
|
||||
|
||||
# Should construct ok from WKT
|
||||
gc1 = GEOSGeometry(gc_wkt)
|
||||
|
||||
# Should also construct ok from individual geometry arguments.
|
||||
gc2 = GeometryCollection(*tuple(g for g in gc1))
|
||||
|
||||
# And, they should be equal.
|
||||
self.assertEqual(gc1, gc2)
|
||||
|
||||
def test21_test_gdal(self):
|
||||
"Testing `ogr` and `srs` properties."
|
||||
if not HAS_GDAL: return
|
||||
if not gdal.HAS_GDAL: return
|
||||
g1 = fromstr('POINT(5 23)')
|
||||
self.assertEqual(True, isinstance(g1.ogr, OGRGeometry))
|
||||
self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry))
|
||||
self.assertEqual(g1.srs, None)
|
||||
|
||||
g2 = fromstr('LINESTRING(0 0, 5 5, 23 23)', srid=4326)
|
||||
self.assertEqual(True, isinstance(g2.ogr, OGRGeometry))
|
||||
self.assertEqual(True, isinstance(g2.srs, SpatialReference))
|
||||
self.assertEqual(True, isinstance(g2.ogr, gdal.OGRGeometry))
|
||||
self.assertEqual(True, isinstance(g2.srs, gdal.SpatialReference))
|
||||
self.assertEqual(g2.hex, g2.ogr.hex)
|
||||
self.assertEqual('WGS 84', g2.srs.name)
|
||||
|
||||
|
@ -705,7 +746,7 @@ class GEOSTest(unittest.TestCase):
|
|||
|
||||
def test23_transform(self):
|
||||
"Testing `transform` method."
|
||||
if not HAS_GDAL: return
|
||||
if not gdal.HAS_GDAL: return
|
||||
orig = GEOSGeometry('POINT (-104.609 38.255)', 4326)
|
||||
trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774)
|
||||
|
||||
|
@ -713,8 +754,8 @@ class GEOSTest(unittest.TestCase):
|
|||
# for transformations.
|
||||
t1, t2, t3 = orig.clone(), orig.clone(), orig.clone()
|
||||
t1.transform(trans.srid)
|
||||
t2.transform(SpatialReference('EPSG:2774'))
|
||||
ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774))
|
||||
t2.transform(gdal.SpatialReference('EPSG:2774'))
|
||||
ct = gdal.CoordTransform(gdal.SpatialReference('WGS84'), gdal.SpatialReference(2774))
|
||||
t3.transform(ct)
|
||||
|
||||
# Testing use of the `clone` keyword.
|
||||
|
@ -767,6 +808,33 @@ class GEOSTest(unittest.TestCase):
|
|||
self.assertEqual(geom, tmpg)
|
||||
if not no_srid: self.assertEqual(geom.srid, tmpg.srid)
|
||||
|
||||
def test26_prepared(self):
|
||||
"Testing PreparedGeometry support."
|
||||
if not GEOS_PREPARE: return
|
||||
# Creating a simple multipolygon and getting a prepared version.
|
||||
mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))')
|
||||
prep = mpoly.prepared
|
||||
|
||||
# A set of test points.
|
||||
pnts = [Point(5, 5), Point(7.5, 7.5), Point(2.5, 7.5)]
|
||||
covers = [True, True, False] # No `covers` op for regular GEOS geoms.
|
||||
for pnt, c in zip(pnts, covers):
|
||||
# Results should be the same (but faster)
|
||||
self.assertEqual(mpoly.contains(pnt), prep.contains(pnt))
|
||||
self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt))
|
||||
self.assertEqual(c, prep.covers(pnt))
|
||||
|
||||
def test26_line_merge(self):
|
||||
"Testing line merge support"
|
||||
ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'),
|
||||
fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'),
|
||||
)
|
||||
ref_merged = (fromstr('LINESTRING(1 1, 3 3)'),
|
||||
fromstr('LINESTRING (1 1, 3 3, 4 2)'),
|
||||
)
|
||||
for geom, merged in zip(ref_geoms, ref_merged):
|
||||
self.assertEqual(merged, geom.merged)
|
||||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(GEOSTest))
|
|
@ -0,0 +1,135 @@
|
|||
# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
|
||||
# Modified from original contribution by Aryeh Leib Taurog, which was
|
||||
# released under the New BSD license.
|
||||
import unittest
|
||||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.geos.error import GEOSIndexError
|
||||
import copy
|
||||
|
||||
def getItem(o,i): return o[i]
|
||||
def delItem(o,i): del o[i]
|
||||
def setItem(o,i,v): o[i] = v
|
||||
|
||||
def api_get_distance(x): return x.distance(Point(-200,-200))
|
||||
def api_get_buffer(x): return x.buffer(10)
|
||||
def api_get_geom_typeid(x): return x.geom_typeid
|
||||
def api_get_num_coords(x): return x.num_coords
|
||||
def api_get_centroid(x): return x.centroid
|
||||
def api_get_empty(x): return x.empty
|
||||
def api_get_valid(x): return x.valid
|
||||
def api_get_simple(x): return x.simple
|
||||
def api_get_ring(x): return x.ring
|
||||
def api_get_boundary(x): return x.boundary
|
||||
def api_get_convex_hull(x): return x.convex_hull
|
||||
def api_get_extent(x): return x.extent
|
||||
def api_get_area(x): return x.area
|
||||
def api_get_length(x): return x.length
|
||||
|
||||
geos_function_tests = [ val for name, val in vars().items()
|
||||
if hasattr(val, '__call__')
|
||||
and name.startswith('api_get_') ]
|
||||
|
||||
class GEOSMutationTest(unittest.TestCase):
|
||||
"""
|
||||
Tests Pythonic Mutability of Python GEOS geometry wrappers
|
||||
get/set/delitem on a slice, normal list methods
|
||||
"""
|
||||
|
||||
def test00_GEOSIndexException(self):
|
||||
'Testing Geometry GEOSIndexError'
|
||||
p = Point(1,2)
|
||||
for i in range(-2,2): p._checkindex(i)
|
||||
self.assertRaises(GEOSIndexError, p._checkindex, 2)
|
||||
self.assertRaises(GEOSIndexError, p._checkindex, -3)
|
||||
|
||||
def test01_PointMutations(self):
|
||||
'Testing Point mutations'
|
||||
for p in (Point(1,2,3), fromstr('POINT (1 2 3)')):
|
||||
self.assertEqual(p._getitem_external(1), 2.0, 'Point _getitem_external')
|
||||
|
||||
# _set_single
|
||||
p._set_single(0,100)
|
||||
self.assertEqual(p.coords, (100.0,2.0,3.0), 'Point _set_single')
|
||||
|
||||
# _set_collection
|
||||
p._set_collection(2,(50,3141))
|
||||
self.assertEqual(p.coords, (50.0,3141.0), 'Point _set_collection')
|
||||
|
||||
def test02_PointExceptions(self):
|
||||
'Testing Point exceptions'
|
||||
self.assertRaises(TypeError, Point, range(1))
|
||||
self.assertRaises(TypeError, Point, range(4))
|
||||
|
||||
def test03_PointApi(self):
|
||||
'Testing Point API'
|
||||
q = Point(4,5,3)
|
||||
for p in (Point(1,2,3), fromstr('POINT (1 2 3)')):
|
||||
p[0:2] = [4,5]
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(q), f(p), 'Point ' + f.__name__)
|
||||
|
||||
def test04_LineStringMutations(self):
|
||||
'Testing LineString mutations'
|
||||
for ls in (LineString((1,0),(4,1),(6,-1)),
|
||||
fromstr('LINESTRING (1 0,4 1,6 -1)')):
|
||||
self.assertEqual(ls._getitem_external(1), (4.0,1.0), 'LineString _getitem_external')
|
||||
|
||||
# _set_single
|
||||
ls._set_single(0,(-50,25))
|
||||
self.assertEqual(ls.coords, ((-50.0,25.0),(4.0,1.0),(6.0,-1.0)), 'LineString _set_single')
|
||||
|
||||
# _set_collection
|
||||
ls._set_collection(2, ((-50.0,25.0),(6.0,-1.0)))
|
||||
self.assertEqual(ls.coords, ((-50.0,25.0),(6.0,-1.0)), 'LineString _set_collection')
|
||||
|
||||
lsa = LineString(ls.coords)
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(ls), 'LineString ' + f.__name__)
|
||||
|
||||
def test05_Polygon(self):
|
||||
'Testing Polygon mutations'
|
||||
for pg in (Polygon(((1,0),(4,1),(6,-1),(8,10),(1,0)),
|
||||
((5,4),(6,4),(6,3),(5,4))),
|
||||
fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')):
|
||||
self.assertEqual(pg._getitem_external(0),
|
||||
LinearRing((1,0),(4,1),(6,-1),(8,10),(1,0)),
|
||||
'Polygon _getitem_external(0)')
|
||||
self.assertEqual(pg._getitem_external(1),
|
||||
LinearRing((5,4),(6,4),(6,3),(5,4)),
|
||||
'Polygon _getitem_external(1)')
|
||||
|
||||
# _set_collection
|
||||
pg._set_collection(2, (((1,2),(10,0),(12,9),(-1,15),(1,2)),
|
||||
((4,2),(5,2),(5,3),(4,2))))
|
||||
self.assertEqual(pg.coords,
|
||||
(((1.0,2.0),(10.0,0.0),(12.0,9.0),(-1.0,15.0),(1.0,2.0)),
|
||||
((4.0,2.0),(5.0,2.0),(5.0,3.0),(4.0,2.0))),
|
||||
'Polygon _set_collection')
|
||||
|
||||
lsa = Polygon(*pg.coords)
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(pg), 'Polygon ' + f.__name__)
|
||||
|
||||
def test06_Collection(self):
|
||||
'Testing Collection mutations'
|
||||
for mp in (MultiPoint(*map(Point,((3,4),(-1,2),(5,-4),(2,8)))),
|
||||
fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)')):
|
||||
self.assertEqual(mp._getitem_external(2), Point(5,-4), 'Collection _getitem_external')
|
||||
|
||||
mp._set_collection(3, map(Point,((5,5),(3,-2),(8,1))))
|
||||
self.assertEqual(mp.coords, ((5.0,5.0),(3.0,-2.0),(8.0,1.0)), 'Collection _set_collection')
|
||||
|
||||
lsa = MultiPoint(*map(Point,((5,5),(3,-2),(8,1))))
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(mp), 'MultiPoint ' + f.__name__)
|
||||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(GEOSMutationTest))
|
||||
return s
|
||||
|
||||
def run(verbosity=2):
|
||||
unittest.TextTestRunner(verbosity=verbosity).run(suite())
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
|
@ -0,0 +1,329 @@
|
|||
# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
|
||||
# Modified from original contribution by Aryeh Leib Taurog, which was
|
||||
# released under the New BSD license.
|
||||
import unittest
|
||||
from django.contrib.gis.geos.mutable_list import ListMixin
|
||||
|
||||
class UserListA(ListMixin):
|
||||
_mytype = tuple
|
||||
def __init__(self, i_list, *args, **kwargs):
|
||||
self._list = self._mytype(i_list)
|
||||
super(UserListA, self).__init__(*args, **kwargs)
|
||||
|
||||
def __len__(self): return len(self._list)
|
||||
|
||||
def __iter__(self): return iter(self._list)
|
||||
|
||||
def __str__(self): return str(self._list)
|
||||
|
||||
def __repr__(self): return repr(self._list)
|
||||
|
||||
def _set_collection(self, length, items):
|
||||
# this would work:
|
||||
# self._list = self._mytype(items)
|
||||
# but then we wouldn't be testing length parameter
|
||||
itemList = ['x'] * length
|
||||
for i, v in enumerate(items):
|
||||
itemList[i] = v
|
||||
|
||||
self._list = self._mytype(itemList)
|
||||
|
||||
def _getitem_external(self, index):
|
||||
return self._list[index]
|
||||
|
||||
_getitem_internal = _getitem_external
|
||||
_set_single = ListMixin._set_single_rebuild
|
||||
_assign_extended_slice = ListMixin._assign_extended_slice_rebuild
|
||||
|
||||
class UserListB(UserListA):
|
||||
_mytype = list
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._list[index] = value
|
||||
|
||||
def nextRange(length):
|
||||
nextRange.start += 100
|
||||
return range(nextRange.start, nextRange.start + length)
|
||||
|
||||
nextRange.start = 0
|
||||
|
||||
class ListMixinTest(unittest.TestCase):
|
||||
"""
|
||||
Tests base class ListMixin by comparing a list clone which is
|
||||
a ListMixin subclass with a real Python list.
|
||||
"""
|
||||
limit = 3
|
||||
listType = UserListA
|
||||
|
||||
@classmethod
|
||||
def lists_of_len(cls, length=None):
|
||||
if length is None: length = cls.limit
|
||||
pl = range(length)
|
||||
return pl, cls.listType(pl)
|
||||
|
||||
@classmethod
|
||||
def limits_plus(cls, b):
|
||||
return range(-cls.limit - b, cls.limit + b)
|
||||
|
||||
@classmethod
|
||||
def step_range(cls):
|
||||
return range(-1 - cls.limit, 0) + range(1, 1 + cls.limit)
|
||||
|
||||
def test01_getslice(self):
|
||||
'Testing slice retrieval'
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(1):
|
||||
self.assertEqual(pl[i:], ul[i:], 'slice [%d:]' % (i))
|
||||
self.assertEqual(pl[:i], ul[:i], 'slice [:%d]' % (i))
|
||||
|
||||
for j in self.limits_plus(1):
|
||||
self.assertEqual(pl[i:j], ul[i:j], 'slice [%d:%d]' % (i,j))
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[i:j:k], ul[i:j:k], 'slice [%d:%d:%d]' % (i,j,k))
|
||||
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[i::k], ul[i::k], 'slice [%d::%d]' % (i,k))
|
||||
self.assertEqual(pl[:i:k], ul[:i:k], 'slice [:%d:%d]' % (i,k))
|
||||
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[::k], ul[::k], 'slice [::%d]' % (k))
|
||||
|
||||
def test02_setslice(self):
|
||||
'Testing slice assignment'
|
||||
def setfcn(x,i,j,k,L): x[i:j:k] = range(L)
|
||||
pl, ul = self.lists_of_len()
|
||||
for slen in range(self.limit + 1):
|
||||
ssl = nextRange(slen)
|
||||
ul[:] = ssl
|
||||
pl[:] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:]')
|
||||
|
||||
for i in self.limits_plus(1):
|
||||
ssl = nextRange(slen)
|
||||
ul[i:] = ssl
|
||||
pl[i:] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:]' % (i))
|
||||
|
||||
ssl = nextRange(slen)
|
||||
ul[:i] = ssl
|
||||
pl[:i] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:%d]' % (i))
|
||||
|
||||
for j in self.limits_plus(1):
|
||||
ssl = nextRange(slen)
|
||||
ul[i:j] = ssl
|
||||
pl[i:j] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:%d]' % (i, j))
|
||||
|
||||
for k in self.step_range():
|
||||
ssl = nextRange( len(ul[i:j:k]) )
|
||||
ul[i:j:k] = ssl
|
||||
pl[i:j:k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:%d:%d]' % (i, j, k))
|
||||
|
||||
sliceLen = len(ul[i:j:k])
|
||||
self.assertRaises(ValueError, setfcn, ul, i, j, k, sliceLen + 1)
|
||||
if sliceLen > 2:
|
||||
self.assertRaises(ValueError, setfcn, ul, i, j, k, sliceLen - 1)
|
||||
|
||||
for k in self.step_range():
|
||||
ssl = nextRange( len(ul[i::k]) )
|
||||
ul[i::k] = ssl
|
||||
pl[i::k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d::%d]' % (i, k))
|
||||
|
||||
ssl = nextRange( len(ul[:i:k]) )
|
||||
ul[:i:k] = ssl
|
||||
pl[:i:k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:%d:%d]' % (i, k))
|
||||
|
||||
for k in self.step_range():
|
||||
ssl = nextRange(len(ul[::k]))
|
||||
ul[::k] = ssl
|
||||
pl[::k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [::%d]' % (k))
|
||||
|
||||
|
||||
def test03_delslice(self):
|
||||
'Testing delete slice'
|
||||
for Len in range(self.limit):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:]
|
||||
del ul[:]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:]')
|
||||
for i in range(-Len - 1, Len + 1):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:]
|
||||
del ul[i:]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:]' % (i))
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:i]
|
||||
del ul[:i]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:%d]' % (i))
|
||||
for j in range(-Len - 1, Len + 1):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:j]
|
||||
del ul[i:j]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j))
|
||||
for k in range(-Len - 1,0) + range(1,Len):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:j:k]
|
||||
del ul[i:j:k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k))
|
||||
|
||||
for k in range(-Len - 1,0) + range(1,Len):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:i:k]
|
||||
del ul[:i:k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:%d:%d]' % (i,k))
|
||||
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i::k]
|
||||
del ul[i::k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k))
|
||||
|
||||
for k in range(-Len - 1,0) + range(1,Len):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[::k]
|
||||
del ul[::k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [::%d]' % (k))
|
||||
|
||||
def test04_get_set_del_single(self):
|
||||
'Testing get/set/delete single item'
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(0):
|
||||
self.assertEqual(pl[i], ul[i], 'get single item [%d]' % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl[i] = 100
|
||||
ul[i] = 100
|
||||
self.assertEqual(pl[:], ul[:], 'set single item [%d]' % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
del pl[i]
|
||||
del ul[i]
|
||||
self.assertEqual(pl[:], ul[:], 'del single item [%d]' % i)
|
||||
|
||||
def test05_out_of_range_exceptions(self):
|
||||
'Testing out of range exceptions'
|
||||
def setfcn(x, i): x[i] = 20
|
||||
def getfcn(x, i): return x[i]
|
||||
def delfcn(x, i): del x[i]
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in (-1 - self.limit, self.limit):
|
||||
self.assertRaises(IndexError, setfcn, ul, i) # 'set index %d' % i)
|
||||
self.assertRaises(IndexError, getfcn, ul, i) # 'get index %d' % i)
|
||||
self.assertRaises(IndexError, delfcn, ul, i) # 'del index %d' % i)
|
||||
|
||||
def test06_list_methods(self):
|
||||
'Testing list methods'
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.append(40)
|
||||
ul.append(40)
|
||||
self.assertEqual(pl[:], ul[:], 'append')
|
||||
|
||||
pl.extend(range(50,55))
|
||||
ul.extend(range(50,55))
|
||||
self.assertEqual(pl[:], ul[:], 'extend')
|
||||
|
||||
for i in self.limits_plus(1):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.insert(i,50)
|
||||
ul.insert(i,50)
|
||||
self.assertEqual(pl[:], ul[:], 'insert at %d' % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(pl.pop(i), ul.pop(i), 'popped value at %d' % i)
|
||||
self.assertEqual(pl[:], ul[:], 'after pop at %d' % i)
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(pl.pop(), ul.pop(i), 'popped value')
|
||||
self.assertEqual(pl[:], ul[:], 'after pop')
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
def popfcn(x, i): x.pop(i)
|
||||
self.assertRaises(IndexError, popfcn, ul, self.limit)
|
||||
self.assertRaises(IndexError, popfcn, ul, -1 - self.limit)
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
for val in range(self.limit):
|
||||
self.assertEqual(pl.index(val), ul.index(val), 'index of %d' % val)
|
||||
|
||||
for val in self.limits_plus(2):
|
||||
self.assertEqual(pl.count(val), ul.count(val), 'count %d' % val)
|
||||
|
||||
for val in range(self.limit):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.remove(val)
|
||||
ul.remove(val)
|
||||
self.assertEqual(pl[:], ul[:], 'after remove val %d' % val)
|
||||
|
||||
def indexfcn(x, v): return x.index(v)
|
||||
def removefcn(x, v): return x.remove(v)
|
||||
self.assertRaises(ValueError, indexfcn, ul, 40)
|
||||
self.assertRaises(ValueError, removefcn, ul, 40)
|
||||
|
||||
def test07_allowed_types(self):
|
||||
'Testing type-restricted list'
|
||||
pl, ul = self.lists_of_len()
|
||||
ul._allowed = (int, long)
|
||||
ul[1] = 50
|
||||
ul[:2] = [60, 70, 80]
|
||||
def setfcn(x, i, v): x[i] = v
|
||||
self.assertRaises(TypeError, setfcn, ul, 2, 'hello')
|
||||
self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), ('hello','goodbye'))
|
||||
|
||||
def test08_min_length(self):
|
||||
'Testing length limits'
|
||||
pl, ul = self.lists_of_len()
|
||||
ul._minlength = 1
|
||||
def delfcn(x,i): del x[:i]
|
||||
def setfcn(x,i): x[:i] = []
|
||||
for i in range(self.limit - ul._minlength + 1, self.limit + 1):
|
||||
self.assertRaises(ValueError, delfcn, ul, i)
|
||||
self.assertRaises(ValueError, setfcn, ul, i)
|
||||
del ul[:ul._minlength]
|
||||
|
||||
ul._maxlength = 4
|
||||
for i in range(0, ul._maxlength - len(ul)):
|
||||
ul.append(i)
|
||||
self.assertRaises(ValueError, ul.append, 10)
|
||||
|
||||
def test09_iterable_check(self):
|
||||
'Testing error on assigning non-iterable to slice'
|
||||
pl, ul = self.lists_of_len(self.limit + 1)
|
||||
def setfcn(x, i, v): x[i] = v
|
||||
self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), 2)
|
||||
|
||||
def test10_checkindex(self):
|
||||
'Testing index check'
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(0):
|
||||
if i < 0:
|
||||
self.assertEqual(ul._checkindex(i), i + self.limit, '_checkindex(neg index)')
|
||||
else:
|
||||
self.assertEqual(ul._checkindex(i), i, '_checkindex(pos index)')
|
||||
|
||||
for i in (-self.limit - 1, self.limit):
|
||||
self.assertRaises(IndexError, ul._checkindex, i)
|
||||
|
||||
ul._IndexError = TypeError
|
||||
self.assertRaises(TypeError, ul._checkindex, -self.limit - 1)
|
||||
|
||||
class ListMixinTestSingle(ListMixinTest):
|
||||
listType = UserListB
|
||||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(ListMixinTest))
|
||||
s.addTest(unittest.makeSuite(ListMixinTestSingle))
|
||||
return s
|
||||
|
||||
def run(verbosity=2):
|
||||
unittest.TextTestRunner(verbosity=verbosity).run(suite())
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
|
@ -16,8 +16,8 @@ def geo_suite():
|
|||
s = unittest.TestSuite()
|
||||
|
||||
# Adding the GEOS tests. (__future__)
|
||||
#from django.contrib.gis.geos import tests as geos_tests
|
||||
#s.addTest(geos_tests.suite())
|
||||
from django.contrib.gis.geos import tests as geos_tests
|
||||
s.addTest(geos_tests.suite())
|
||||
|
||||
# Test apps that require use of a spatial database (e.g., creation of models)
|
||||
test_apps = ['geoapp', 'relatedapp']
|
||||
|
@ -27,7 +27,6 @@ def geo_suite():
|
|||
# Tests that do not require setting up and tearing down a spatial database
|
||||
# and are modules in `django.contrib.gis.tests`.
|
||||
test_suite_names = [
|
||||
'test_geos',
|
||||
'test_measure',
|
||||
]
|
||||
|
||||
|
@ -216,8 +215,8 @@ class _DeprecatedTestModule(object):
|
|||
self.mod, DeprecationWarning)
|
||||
self.tests.run()
|
||||
|
||||
#from django.contrib.gis.geos import tests as _tests
|
||||
#test_geos = _DeprecatedTestModule(_tests, 'geos')
|
||||
from django.contrib.gis.geos import tests as _tests
|
||||
test_geos = _DeprecatedTestModule(_tests, 'geos')
|
||||
|
||||
from django.contrib.gis.gdal import HAS_GDAL
|
||||
if HAS_GDAL:
|
||||
|
|
Loading…
Reference in New Issue