polygon.py 6.6 KB

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