| 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 routines are needed for list-like operation w/ListMixin ### |
| def _create_polygon(self, 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(self._construct_ring(r)) |
| |
| shell = self._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] = self._clone(r) |
| holes_param = byref(holes) |
| else: |
| holes_param = None |
| |
| return capi.create_polygon(shell, holes_param, c_uint(n_holes)) |
| |
| def _clone(self, g): |
| if isinstance(g, GEOM_PTR): |
| return capi.geom_clone(g) |
| else: |
| return capi.geom_clone(g.ptr) |
| |
| def _construct_ring(self, 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_list(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 _get_single_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! |
| _get_single_internal returns a pointer from the existing geometries for use |
| internally by the object's methods. _get_single_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 _get_single_external(self, index): |
| return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) |
| |
| _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) |