You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

io.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import threading
  2. from ctypes import POINTER, Structure, byref, c_byte, c_char_p, c_int, c_size_t
  3. from django.contrib.gis.geos.base import GEOSBase
  4. from django.contrib.gis.geos.libgeos import (
  5. GEOM_PTR, GEOSFuncFactory, geos_version_tuple,
  6. )
  7. from django.contrib.gis.geos.prototypes.errcheck import (
  8. check_geom, check_sized_string, check_string,
  9. )
  10. from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
  11. from django.utils.encoding import force_bytes
  12. # ### The WKB/WKT Reader/Writer structures and pointers ###
  13. class WKTReader_st(Structure):
  14. pass
  15. class WKTWriter_st(Structure):
  16. pass
  17. class WKBReader_st(Structure):
  18. pass
  19. class WKBWriter_st(Structure):
  20. pass
  21. WKT_READ_PTR = POINTER(WKTReader_st)
  22. WKT_WRITE_PTR = POINTER(WKTWriter_st)
  23. WKB_READ_PTR = POINTER(WKBReader_st)
  24. WKB_WRITE_PTR = POINTER(WKBReader_st)
  25. # WKTReader routines
  26. wkt_reader_create = GEOSFuncFactory('GEOSWKTReader_create', restype=WKT_READ_PTR)
  27. wkt_reader_destroy = GEOSFuncFactory('GEOSWKTReader_destroy', argtypes=[WKT_READ_PTR])
  28. wkt_reader_read = GEOSFuncFactory(
  29. 'GEOSWKTReader_read', argtypes=[WKT_READ_PTR, c_char_p], restype=GEOM_PTR, errcheck=check_geom
  30. )
  31. # WKTWriter routines
  32. wkt_writer_create = GEOSFuncFactory('GEOSWKTWriter_create', restype=WKT_WRITE_PTR)
  33. wkt_writer_destroy = GEOSFuncFactory('GEOSWKTWriter_destroy', argtypes=[WKT_WRITE_PTR])
  34. wkt_writer_write = GEOSFuncFactory(
  35. 'GEOSWKTWriter_write', argtypes=[WKT_WRITE_PTR, GEOM_PTR], restype=geos_char_p, errcheck=check_string
  36. )
  37. wkt_writer_get_outdim = GEOSFuncFactory(
  38. 'GEOSWKTWriter_getOutputDimension', argtypes=[WKT_WRITE_PTR], restype=c_int
  39. )
  40. wkt_writer_set_outdim = GEOSFuncFactory(
  41. 'GEOSWKTWriter_setOutputDimension', argtypes=[WKT_WRITE_PTR, c_int]
  42. )
  43. wkt_writer_set_trim = GEOSFuncFactory('GEOSWKTWriter_setTrim', argtypes=[WKT_WRITE_PTR, c_byte])
  44. wkt_writer_set_precision = GEOSFuncFactory('GEOSWKTWriter_setRoundingPrecision', argtypes=[WKT_WRITE_PTR, c_int])
  45. # WKBReader routines
  46. wkb_reader_create = GEOSFuncFactory('GEOSWKBReader_create', restype=WKB_READ_PTR)
  47. wkb_reader_destroy = GEOSFuncFactory('GEOSWKBReader_destroy', argtypes=[WKB_READ_PTR])
  48. class WKBReadFunc(GEOSFuncFactory):
  49. # Although the function definitions take `const unsigned char *`
  50. # as their parameter, we use c_char_p here so the function may
  51. # take Python strings directly as parameters. Inside Python there
  52. # is not a difference between signed and unsigned characters, so
  53. # it is not a problem.
  54. argtypes = [WKB_READ_PTR, c_char_p, c_size_t]
  55. restype = GEOM_PTR
  56. errcheck = staticmethod(check_geom)
  57. wkb_reader_read = WKBReadFunc('GEOSWKBReader_read')
  58. wkb_reader_read_hex = WKBReadFunc('GEOSWKBReader_readHEX')
  59. # WKBWriter routines
  60. wkb_writer_create = GEOSFuncFactory('GEOSWKBWriter_create', restype=WKB_WRITE_PTR)
  61. wkb_writer_destroy = GEOSFuncFactory('GEOSWKBWriter_destroy', argtypes=[WKB_WRITE_PTR])
  62. # WKB Writing prototypes.
  63. class WKBWriteFunc(GEOSFuncFactory):
  64. argtypes = [WKB_WRITE_PTR, GEOM_PTR, POINTER(c_size_t)]
  65. restype = c_uchar_p
  66. errcheck = staticmethod(check_sized_string)
  67. wkb_writer_write = WKBWriteFunc('GEOSWKBWriter_write')
  68. wkb_writer_write_hex = WKBWriteFunc('GEOSWKBWriter_writeHEX')
  69. # WKBWriter property getter/setter prototypes.
  70. class WKBWriterGet(GEOSFuncFactory):
  71. argtypes = [WKB_WRITE_PTR]
  72. restype = c_int
  73. class WKBWriterSet(GEOSFuncFactory):
  74. argtypes = [WKB_WRITE_PTR, c_int]
  75. wkb_writer_get_byteorder = WKBWriterGet('GEOSWKBWriter_getByteOrder')
  76. wkb_writer_set_byteorder = WKBWriterSet('GEOSWKBWriter_setByteOrder')
  77. wkb_writer_get_outdim = WKBWriterGet('GEOSWKBWriter_getOutputDimension')
  78. wkb_writer_set_outdim = WKBWriterSet('GEOSWKBWriter_setOutputDimension')
  79. wkb_writer_get_include_srid = WKBWriterGet('GEOSWKBWriter_getIncludeSRID', restype=c_byte)
  80. wkb_writer_set_include_srid = WKBWriterSet('GEOSWKBWriter_setIncludeSRID', argtypes=[WKB_WRITE_PTR, c_byte])
  81. # ### Base I/O Class ###
  82. class IOBase(GEOSBase):
  83. "Base class for GEOS I/O objects."
  84. def __init__(self):
  85. # Getting the pointer with the constructor.
  86. self.ptr = self._constructor()
  87. # Loading the real destructor function at this point as doing it in
  88. # __del__ is too late (import error).
  89. self.destructor.func
  90. # ### Base WKB/WKT Reading and Writing objects ###
  91. # Non-public WKB/WKT reader classes for internal use because
  92. # their `read` methods return _pointers_ instead of GEOSGeometry
  93. # objects.
  94. class _WKTReader(IOBase):
  95. _constructor = wkt_reader_create
  96. ptr_type = WKT_READ_PTR
  97. destructor = wkt_reader_destroy
  98. def read(self, wkt):
  99. if not isinstance(wkt, (bytes, str)):
  100. raise TypeError
  101. return wkt_reader_read(self.ptr, force_bytes(wkt))
  102. class _WKBReader(IOBase):
  103. _constructor = wkb_reader_create
  104. ptr_type = WKB_READ_PTR
  105. destructor = wkb_reader_destroy
  106. def read(self, wkb):
  107. "Return a _pointer_ to C GEOS Geometry object from the given WKB."
  108. if isinstance(wkb, memoryview):
  109. wkb_s = bytes(wkb)
  110. return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
  111. elif isinstance(wkb, (bytes, str)):
  112. return wkb_reader_read_hex(self.ptr, wkb, len(wkb))
  113. else:
  114. raise TypeError
  115. # ### WKB/WKT Writer Classes ###
  116. class WKTWriter(IOBase):
  117. _constructor = wkt_writer_create
  118. ptr_type = WKT_WRITE_PTR
  119. destructor = wkt_writer_destroy
  120. _trim = False
  121. _precision = None
  122. def __init__(self, dim=2, trim=False, precision=None):
  123. super().__init__()
  124. if bool(trim) != self._trim:
  125. self.trim = trim
  126. if precision is not None:
  127. self.precision = precision
  128. self.outdim = dim
  129. def write(self, geom):
  130. "Return the WKT representation of the given geometry."
  131. return wkt_writer_write(self.ptr, geom.ptr)
  132. @property
  133. def outdim(self):
  134. return wkt_writer_get_outdim(self.ptr)
  135. @outdim.setter
  136. def outdim(self, new_dim):
  137. if new_dim not in (2, 3):
  138. raise ValueError('WKT output dimension must be 2 or 3')
  139. wkt_writer_set_outdim(self.ptr, new_dim)
  140. @property
  141. def trim(self):
  142. return self._trim
  143. @trim.setter
  144. def trim(self, flag):
  145. if bool(flag) != self._trim:
  146. self._trim = bool(flag)
  147. wkt_writer_set_trim(self.ptr, self._trim)
  148. @property
  149. def precision(self):
  150. return self._precision
  151. @precision.setter
  152. def precision(self, precision):
  153. if (not isinstance(precision, int) or precision < 0) and precision is not None:
  154. raise AttributeError('WKT output rounding precision must be non-negative integer or None.')
  155. if precision != self._precision:
  156. self._precision = precision
  157. wkt_writer_set_precision(self.ptr, -1 if precision is None else precision)
  158. class WKBWriter(IOBase):
  159. _constructor = wkb_writer_create
  160. ptr_type = WKB_WRITE_PTR
  161. destructor = wkb_writer_destroy
  162. geos_version = geos_version_tuple()
  163. def __init__(self, dim=2):
  164. super().__init__()
  165. self.outdim = dim
  166. def _handle_empty_point(self, geom):
  167. from django.contrib.gis.geos import Point
  168. if isinstance(geom, Point) and geom.empty:
  169. if self.srid:
  170. # PostGIS uses POINT(NaN NaN) for WKB representation of empty
  171. # points. Use it for EWKB as it's a PostGIS specific format.
  172. # https://trac.osgeo.org/postgis/ticket/3181
  173. geom = Point(float('NaN'), float('NaN'), srid=geom.srid)
  174. else:
  175. raise ValueError('Empty point is not representable in WKB.')
  176. return geom
  177. def write(self, geom):
  178. "Return the WKB representation of the given geometry."
  179. from django.contrib.gis.geos import Polygon
  180. geom = self._handle_empty_point(geom)
  181. wkb = wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t()))
  182. if self.geos_version < (3, 6, 1) and isinstance(geom, Polygon) and geom.empty:
  183. # Fix GEOS output for empty polygon.
  184. # See https://trac.osgeo.org/geos/ticket/680.
  185. wkb = wkb[:-8] + b'\0' * 4
  186. return memoryview(wkb)
  187. def write_hex(self, geom):
  188. "Return the HEXEWKB representation of the given geometry."
  189. from django.contrib.gis.geos.polygon import Polygon
  190. geom = self._handle_empty_point(geom)
  191. wkb = wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
  192. if self.geos_version < (3, 6, 1) and isinstance(geom, Polygon) and geom.empty:
  193. wkb = wkb[:-16] + b'0' * 8
  194. return wkb
  195. # ### WKBWriter Properties ###
  196. # Property for getting/setting the byteorder.
  197. def _get_byteorder(self):
  198. return wkb_writer_get_byteorder(self.ptr)
  199. def _set_byteorder(self, order):
  200. if order not in (0, 1):
  201. raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
  202. wkb_writer_set_byteorder(self.ptr, order)
  203. byteorder = property(_get_byteorder, _set_byteorder)
  204. # Property for getting/setting the output dimension.
  205. @property
  206. def outdim(self):
  207. return wkb_writer_get_outdim(self.ptr)
  208. @outdim.setter
  209. def outdim(self, new_dim):
  210. if new_dim not in (2, 3):
  211. raise ValueError('WKB output dimension must be 2 or 3')
  212. wkb_writer_set_outdim(self.ptr, new_dim)
  213. # Property for getting/setting the include srid flag.
  214. @property
  215. def srid(self):
  216. return bool(wkb_writer_get_include_srid(self.ptr))
  217. @srid.setter
  218. def srid(self, include):
  219. wkb_writer_set_include_srid(self.ptr, bool(include))
  220. # `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer
  221. # objects that are local to the thread. The `GEOSGeometry` internals
  222. # access these instances by calling the module-level functions, defined
  223. # below.
  224. class ThreadLocalIO(threading.local):
  225. wkt_r = None
  226. wkt_w = None
  227. wkb_r = None
  228. wkb_w = None
  229. ewkb_w = None
  230. thread_context = ThreadLocalIO()
  231. # These module-level routines return the I/O object that is local to the
  232. # thread. If the I/O object does not exist yet it will be initialized.
  233. def wkt_r():
  234. thread_context.wkt_r = thread_context.wkt_r or _WKTReader()
  235. return thread_context.wkt_r
  236. def wkt_w(dim=2, trim=False, precision=None):
  237. if not thread_context.wkt_w:
  238. thread_context.wkt_w = WKTWriter(dim=dim, trim=trim, precision=precision)
  239. else:
  240. thread_context.wkt_w.outdim = dim
  241. thread_context.wkt_w.trim = trim
  242. thread_context.wkt_w.precision = precision
  243. return thread_context.wkt_w
  244. def wkb_r():
  245. thread_context.wkb_r = thread_context.wkb_r or _WKBReader()
  246. return thread_context.wkb_r
  247. def wkb_w(dim=2):
  248. if not thread_context.wkb_w:
  249. thread_context.wkb_w = WKBWriter(dim=dim)
  250. else:
  251. thread_context.wkb_w.outdim = dim
  252. return thread_context.wkb_w
  253. def ewkb_w(dim=2):
  254. if not thread_context.ewkb_w:
  255. thread_context.ewkb_w = WKBWriter(dim=dim)
  256. thread_context.ewkb_w.srid = True
  257. else:
  258. thread_context.ewkb_w.outdim = dim
  259. return thread_context.ewkb_w