123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- from ctypes import byref, c_uint
-
- from django.contrib.gis.geos import prototypes as capi
- from django.contrib.gis.geos.geometry import GEOSGeometry
- from django.contrib.gis.geos.libgeos import GEOM_PTR
- from django.contrib.gis.geos.linestring import LinearRing
-
-
- class Polygon(GEOSGeometry):
- _minlength = 1
-
- def __init__(self, *args, **kwargs):
- """
- Initialize 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:
- >>> from django.contrib.gis.geos import LinearRing, Polygon
- >>> shell = hole1 = hole2 = LinearRing()
- >>> 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:
- super().__init__(self._create_polygon(0, None), **kwargs)
- return
-
- # Getting the ext_ring and init_holes parameters from the argument list
- ext_ring, *init_holes = args
- 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 not init_holes[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().__init__(polygon, **kwargs)
-
- def __iter__(self):
- "Iterate over each ring in the polygon."
- for i in range(len(self)):
- yield self[i]
-
- def __len__(self):
- "Return the number of rings in this Polygon."
- return self.num_interior_rings + 1
-
- @classmethod
- def from_bbox(cls, bbox):
- "Construct a Polygon from a bounding box (4-tuple)."
- x0, y0, x1, y1 = bbox
- for z in bbox:
- if not isinstance(z, (float, int)):
- return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %
- (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0))
- return Polygon(((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.
- if not length:
- return capi.create_empty_polygon()
-
- 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 = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
- 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')):
- "Try 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):
- """
- Return 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!
- Return 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):
- "Return the number of interior rings."
- # Getting the number of rings
- return capi.get_nrings(self.ptr)
-
- def _get_ext_ring(self):
- "Get the exterior ring of the Polygon."
- return self[0]
-
- def _set_ext_ring(self, ring):
- "Set 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):
- "Get the tuple for each ring in this Polygon."
- return tuple(self[i].tuple for i in range(len(self)))
- coords = tuple
-
- @property
- def kml(self):
- "Return the KML representation of this Polygon."
- inner_kml = ''.join(
- "<innerBoundaryIs>%s</innerBoundaryIs>" % self[i + 1].kml
- for i in range(self.num_interior_rings)
- )
- return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)
|