envelope.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. """
  2. The GDAL/OGR library uses an Envelope structure to hold the bounding
  3. box information for a geometry. The envelope (bounding box) contains
  4. two pairs of coordinates, one for the lower left coordinate and one
  5. for the upper right coordinate:
  6. +----------o Upper right; (max_x, max_y)
  7. | |
  8. | |
  9. | |
  10. Lower left (min_x, min_y) o----------+
  11. """
  12. from ctypes import Structure, c_double
  13. from django.contrib.gis.gdal.error import GDALException
  14. # The OGR definition of an Envelope is a C structure containing four doubles.
  15. # See the 'ogr_core.h' source file for more information:
  16. # https://gdal.org/doxygen/ogr__core_8h_source.html
  17. class OGREnvelope(Structure):
  18. "Represent the OGREnvelope C Structure."
  19. _fields_ = [
  20. ("MinX", c_double),
  21. ("MaxX", c_double),
  22. ("MinY", c_double),
  23. ("MaxY", c_double),
  24. ]
  25. class Envelope:
  26. """
  27. The Envelope object is a C structure that contains the minimum and
  28. maximum X, Y coordinates for a rectangle bounding box. The naming
  29. of the variables is compatible with the OGR Envelope structure.
  30. """
  31. def __init__(self, *args):
  32. """
  33. The initialization function may take an OGREnvelope structure, 4-element
  34. tuple or list, or 4 individual arguments.
  35. """
  36. if len(args) == 1:
  37. if isinstance(args[0], OGREnvelope):
  38. # OGREnvelope (a ctypes Structure) was passed in.
  39. self._envelope = args[0]
  40. elif isinstance(args[0], (tuple, list)):
  41. # A tuple was passed in.
  42. if len(args[0]) != 4:
  43. raise GDALException(
  44. "Incorrect number of tuple elements (%d)." % len(args[0])
  45. )
  46. else:
  47. self._from_sequence(args[0])
  48. else:
  49. raise TypeError("Incorrect type of argument: %s" % type(args[0]))
  50. elif len(args) == 4:
  51. # Individual parameters passed in.
  52. # Thanks to ww for the help
  53. self._from_sequence([float(a) for a in args])
  54. else:
  55. raise GDALException("Incorrect number (%d) of arguments." % len(args))
  56. # Checking the x,y coordinates
  57. if self.min_x > self.max_x:
  58. raise GDALException("Envelope minimum X > maximum X.")
  59. if self.min_y > self.max_y:
  60. raise GDALException("Envelope minimum Y > maximum Y.")
  61. def __eq__(self, other):
  62. """
  63. Return True if the envelopes are equivalent; can compare against
  64. other Envelopes and 4-tuples.
  65. """
  66. if isinstance(other, Envelope):
  67. return (
  68. (self.min_x == other.min_x)
  69. and (self.min_y == other.min_y)
  70. and (self.max_x == other.max_x)
  71. and (self.max_y == other.max_y)
  72. )
  73. elif isinstance(other, tuple) and len(other) == 4:
  74. return (
  75. (self.min_x == other[0])
  76. and (self.min_y == other[1])
  77. and (self.max_x == other[2])
  78. and (self.max_y == other[3])
  79. )
  80. else:
  81. raise GDALException("Equivalence testing only works with other Envelopes.")
  82. def __str__(self):
  83. "Return a string representation of the tuple."
  84. return str(self.tuple)
  85. def _from_sequence(self, seq):
  86. "Initialize the C OGR Envelope structure from the given sequence."
  87. self._envelope = OGREnvelope()
  88. self._envelope.MinX = seq[0]
  89. self._envelope.MinY = seq[1]
  90. self._envelope.MaxX = seq[2]
  91. self._envelope.MaxY = seq[3]
  92. def expand_to_include(self, *args):
  93. """
  94. Modify the envelope to expand to include the boundaries of
  95. the passed-in 2-tuple (a point), 4-tuple (an extent) or
  96. envelope.
  97. """
  98. # We provide a number of different signatures for this method,
  99. # and the logic here is all about converting them into a
  100. # 4-tuple single parameter which does the actual work of
  101. # expanding the envelope.
  102. if len(args) == 1:
  103. if isinstance(args[0], Envelope):
  104. return self.expand_to_include(args[0].tuple)
  105. elif hasattr(args[0], "x") and hasattr(args[0], "y"):
  106. return self.expand_to_include(
  107. args[0].x, args[0].y, args[0].x, args[0].y
  108. )
  109. elif isinstance(args[0], (tuple, list)):
  110. # A tuple was passed in.
  111. if len(args[0]) == 2:
  112. return self.expand_to_include(
  113. (args[0][0], args[0][1], args[0][0], args[0][1])
  114. )
  115. elif len(args[0]) == 4:
  116. (minx, miny, maxx, maxy) = args[0]
  117. if minx < self._envelope.MinX:
  118. self._envelope.MinX = minx
  119. if miny < self._envelope.MinY:
  120. self._envelope.MinY = miny
  121. if maxx > self._envelope.MaxX:
  122. self._envelope.MaxX = maxx
  123. if maxy > self._envelope.MaxY:
  124. self._envelope.MaxY = maxy
  125. else:
  126. raise GDALException(
  127. "Incorrect number of tuple elements (%d)." % len(args[0])
  128. )
  129. else:
  130. raise TypeError("Incorrect type of argument: %s" % type(args[0]))
  131. elif len(args) == 2:
  132. # An x and an y parameter were passed in
  133. return self.expand_to_include((args[0], args[1], args[0], args[1]))
  134. elif len(args) == 4:
  135. # Individual parameters passed in.
  136. return self.expand_to_include(args)
  137. else:
  138. raise GDALException("Incorrect number (%d) of arguments." % len(args[0]))
  139. @property
  140. def min_x(self):
  141. "Return the value of the minimum X coordinate."
  142. return self._envelope.MinX
  143. @property
  144. def min_y(self):
  145. "Return the value of the minimum Y coordinate."
  146. return self._envelope.MinY
  147. @property
  148. def max_x(self):
  149. "Return the value of the maximum X coordinate."
  150. return self._envelope.MaxX
  151. @property
  152. def max_y(self):
  153. "Return the value of the maximum Y coordinate."
  154. return self._envelope.MaxY
  155. @property
  156. def ur(self):
  157. "Return the upper-right coordinate."
  158. return (self.max_x, self.max_y)
  159. @property
  160. def ll(self):
  161. "Return the lower-left coordinate."
  162. return (self.min_x, self.min_y)
  163. @property
  164. def tuple(self):
  165. "Return a tuple representing the envelope."
  166. return (self.min_x, self.min_y, self.max_x, self.max_y)
  167. @property
  168. def wkt(self):
  169. "Return WKT representing a Polygon for this envelope."
  170. # TODO: Fix significant figures.
  171. return "POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))" % (
  172. self.min_x,
  173. self.min_y,
  174. self.min_x,
  175. self.max_y,
  176. self.max_x,
  177. self.max_y,
  178. self.max_x,
  179. self.min_y,
  180. self.min_x,
  181. self.min_y,
  182. )