polygon.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. from ctypes import byref, c_uint
  2. from django.contrib.gis.geos import prototypes as capi
  3. from django.contrib.gis.geos.geometry import GEOSGeometry
  4. from django.contrib.gis.geos.libgeos import GEOM_PTR, get_pointer_arr
  5. from django.contrib.gis.geos.linestring import LinearRing
  6. from django.utils import six
  7. from django.utils.six.moves import range
  8. class Polygon(GEOSGeometry):
  9. _minlength = 1
  10. def __init__(self, *args, **kwargs):
  11. """
  12. Initializes on an exterior ring and a sequence of holes (both
  13. instances may be either LinearRing instances, or a tuple/list
  14. that may be constructed into a LinearRing).
  15. Examples of initialization, where shell, hole1, and hole2 are
  16. valid LinearRing geometries:
  17. >>> from django.contrib.gis.geos import LinearRing, Polygon
  18. >>> shell = hole1 = hole2 = LinearRing()
  19. >>> poly = Polygon(shell, hole1, hole2)
  20. >>> poly = Polygon(shell, (hole1, hole2))
  21. >>> # Example where a tuple parameters are used:
  22. >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
  23. ... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
  24. """
  25. if not args:
  26. super(Polygon, self).__init__(self._create_polygon(0, None), **kwargs)
  27. return
  28. # Getting the ext_ring and init_holes parameters from the argument list
  29. ext_ring = args[0]
  30. init_holes = args[1:]
  31. n_holes = len(init_holes)
  32. # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
  33. if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
  34. if len(init_holes[0]) == 0:
  35. init_holes = ()
  36. n_holes = 0
  37. elif isinstance(init_holes[0][0], LinearRing):
  38. init_holes = init_holes[0]
  39. n_holes = len(init_holes)
  40. polygon = self._create_polygon(n_holes + 1, (ext_ring,) + init_holes)
  41. super(Polygon, self).__init__(polygon, **kwargs)
  42. def __iter__(self):
  43. "Iterates over each ring in the polygon."
  44. for i in range(len(self)):
  45. yield self[i]
  46. def __len__(self):
  47. "Returns the number of rings in this Polygon."
  48. return self.num_interior_rings + 1
  49. @classmethod
  50. def from_bbox(cls, bbox):
  51. "Constructs a Polygon from a bounding box (4-tuple)."
  52. x0, y0, x1, y1 = bbox
  53. for z in bbox:
  54. if not isinstance(z, six.integer_types + (float,)):
  55. return GEOSGeometry('POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %
  56. (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0))
  57. return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0)))
  58. # ### These routines are needed for list-like operation w/ListMixin ###
  59. def _create_polygon(self, length, items):
  60. # Instantiate LinearRing objects if necessary, but don't clone them yet
  61. # _construct_ring will throw a TypeError if a parameter isn't a valid ring
  62. # If we cloned the pointers here, we wouldn't be able to clean up
  63. # in case of error.
  64. if not length:
  65. return capi.create_empty_polygon()
  66. rings = []
  67. for r in items:
  68. if isinstance(r, GEOM_PTR):
  69. rings.append(r)
  70. else:
  71. rings.append(self._construct_ring(r))
  72. shell = self._clone(rings.pop(0))
  73. n_holes = length - 1
  74. if n_holes:
  75. holes = get_pointer_arr(n_holes)
  76. for i, r in enumerate(rings):
  77. holes[i] = self._clone(r)
  78. holes_param = byref(holes)
  79. else:
  80. holes_param = None
  81. return capi.create_polygon(shell, holes_param, c_uint(n_holes))
  82. def _clone(self, g):
  83. if isinstance(g, GEOM_PTR):
  84. return capi.geom_clone(g)
  85. else:
  86. return capi.geom_clone(g.ptr)
  87. def _construct_ring(self, param, msg=(
  88. 'Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')):
  89. "Helper routine for trying to construct a ring from the given parameter."
  90. if isinstance(param, LinearRing):
  91. return param
  92. try:
  93. ring = LinearRing(param)
  94. return ring
  95. except TypeError:
  96. raise TypeError(msg)
  97. def _set_list(self, length, items):
  98. # Getting the current pointer, replacing with the newly constructed
  99. # geometry, and destroying the old geometry.
  100. prev_ptr = self.ptr
  101. srid = self.srid
  102. self.ptr = self._create_polygon(length, items)
  103. if srid:
  104. self.srid = srid
  105. capi.destroy_geom(prev_ptr)
  106. def _get_single_internal(self, index):
  107. """
  108. Returns the ring at the specified index. The first index, 0, will
  109. always return the exterior ring. Indices > 0 will return the
  110. interior ring at the given index (e.g., poly[1] and poly[2] would
  111. return the first and second interior ring, respectively).
  112. CAREFUL: Internal/External are not the same as Interior/Exterior!
  113. _get_single_internal returns a pointer from the existing geometries for use
  114. internally by the object's methods. _get_single_external returns a clone
  115. of the same geometry for use by external code.
  116. """
  117. if index == 0:
  118. return capi.get_extring(self.ptr)
  119. else:
  120. # Getting the interior ring, have to subtract 1 from the index.
  121. return capi.get_intring(self.ptr, index - 1)
  122. def _get_single_external(self, index):
  123. return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid)
  124. _set_single = GEOSGeometry._set_single_rebuild
  125. _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
  126. # #### Polygon Properties ####
  127. @property
  128. def num_interior_rings(self):
  129. "Returns the number of interior rings."
  130. # Getting the number of rings
  131. return capi.get_nrings(self.ptr)
  132. def _get_ext_ring(self):
  133. "Gets the exterior ring of the Polygon."
  134. return self[0]
  135. def _set_ext_ring(self, ring):
  136. "Sets the exterior ring of the Polygon."
  137. self[0] = ring
  138. # Properties for the exterior ring/shell.
  139. exterior_ring = property(_get_ext_ring, _set_ext_ring)
  140. shell = exterior_ring
  141. @property
  142. def tuple(self):
  143. "Gets the tuple for each ring in this Polygon."
  144. return tuple(self[i].tuple for i in range(len(self)))
  145. coords = tuple
  146. @property
  147. def kml(self):
  148. "Returns the KML representation of this Polygon."
  149. inner_kml = ''.join(
  150. "<innerBoundaryIs>%s</innerBoundaryIs>" % self[i + 1].kml
  151. for i in range(self.num_interior_rings)
  152. )
  153. return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)