__init__.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. # -*- coding: utf-8 -*-
  2. """
  3. Fast and exit-safe interface to PyGRASS Raster and Vector layer
  4. using multiprocessing
  5. (C) 2015 by the GRASS Development Team
  6. This program is free software under the GNU General Public
  7. License (>=v2). Read the file COPYING that comes with GRASS
  8. for details.
  9. :authors: Soeren Gebbert
  10. """
  11. import time
  12. import threading
  13. import sys
  14. from multiprocessing import Process, Lock, Pipe
  15. from ctypes import *
  16. from grass.exceptions import FatalError
  17. from grass.pygrass.vector import *
  18. from grass.pygrass.raster import *
  19. import grass.lib.gis as libgis
  20. from base import RPCServerBase
  21. from grass.pygrass.gis.region import Region
  22. import logging
  23. ###############################################################################
  24. ###############################################################################
  25. class RPCDefs(object):
  26. # Function identifier and index
  27. STOP = 0
  28. GET_VECTOR_TABLE_AS_DICT = 1
  29. GET_VECTOR_FEATURES_AS_WKB = 2
  30. GET_RASTER_IMAGE_AS_NP = 3
  31. G_FATAL_ERROR = 14
  32. def _get_raster_image_as_np(lock, conn, data):
  33. """Convert a raster map into an image and return
  34. a numpy array with RGB or Gray values.
  35. :param lock: A multiprocessing.Lock instance
  36. :param conn: A multiprocessing.Pipe instance used to send True or False
  37. :param data: The list of data entries [function_id, raster_name, extent, color]
  38. """
  39. array = None
  40. try:
  41. raster_name = data[1]
  42. extent = data[2]
  43. color = data[3]
  44. rast = RasterRow(raster_name)
  45. if rast.exist():
  46. reg = Region()
  47. reg.from_rast(raster_name)
  48. if extent is not None:
  49. if "north" in extent:
  50. reg.north = extent["north"]
  51. if "south" in extent:
  52. reg.south = extent["south"]
  53. if "east" in extent:
  54. reg.east = extent["east"]
  55. if "west" in extent:
  56. reg.west = extent["west"]
  57. if "rows" in extent:
  58. reg.rows = extent["rows"]
  59. if "cols" in extent:
  60. reg.cols = extent["cols"]
  61. reg.adjust()
  62. array = raster2numpy_img(raster_name, reg, color)
  63. except:
  64. raise
  65. finally:
  66. conn.send(array)
  67. def _get_vector_table_as_dict(lock, conn, data):
  68. """Get the table of a vector map layer as dictionary
  69. :param lock: A multiprocessing.Lock instance
  70. :param conn: A multiprocessing.Pipe instance used to send True or False
  71. :param data: The list of data entries [function_id, name, where]
  72. """
  73. ret = None
  74. try:
  75. name = data[1]
  76. where = data[2]
  77. layer = VectorTopo(name)
  78. if layer.exist() is True:
  79. layer.open("r")
  80. columns = None
  81. table = None
  82. if layer.table is not None:
  83. columns = layer.table.columns
  84. table = layer.table_to_dict(where=where)
  85. layer.close()
  86. ret = {}
  87. ret["table"] = table
  88. ret["columns"] = columns
  89. except:
  90. raise
  91. finally:
  92. conn.send(ret)
  93. def _get_vector_features_as_wkb_list(lock, conn, data):
  94. """Return vector layer features as wkb list
  95. supported feature types:
  96. point, centroid, line, boundary, area
  97. :param lock: A multiprocessing.Lock instance
  98. :param conn: A multiprocessing.Pipe instance used to send True or False
  99. :param data: The list of data entries [function_id,name,extent,
  100. feature_type, field]
  101. """
  102. wkb_list = None
  103. try:
  104. name = data[1]
  105. extent = data[2]
  106. feature_type = data[3]
  107. field = data[4]
  108. bbox = None
  109. layer = VectorTopo(name)
  110. if layer.exist() is True:
  111. if extent is not None:
  112. bbox = basic.Bbox(north=extent["north"],
  113. south=extent["south"],
  114. east=extent["east"],
  115. west=extent["west"])
  116. layer.open("r")
  117. if feature_type.lower() == "area":
  118. wkb_list = layer.areas_to_wkb_list(bbox=bbox, field=field)
  119. else:
  120. wkb_list = layer.features_to_wkb_list(bbox=bbox,
  121. feature_type=feature_type,
  122. field=field)
  123. layer.close()
  124. except:
  125. raise
  126. finally:
  127. conn.send(wkb_list)
  128. ###############################################################################
  129. def _fatal_error(lock, conn, data):
  130. """Calls G_fatal_error()"""
  131. libgis.G_fatal_error("Fatal Error in C library server")
  132. ###############################################################################
  133. def _stop(lock, conn, data):
  134. conn.close()
  135. lock.release()
  136. sys.exit()
  137. ###############################################################################
  138. def data_provider_server(lock, conn):
  139. """The PyGRASS data provider server designed to be a target for
  140. multiprocessing.Process
  141. :param lock: A multiprocessing.Lock
  142. :param conn: A multiprocessing.Pipe
  143. """
  144. def error_handler(data):
  145. """This function will be called in case of a fatal error in libgis"""
  146. #sys.stderr.write("Error handler was called\n")
  147. # We send an exeption that will be handled in
  148. # the parent process, then close the pipe
  149. # and release any possible lock
  150. conn.send(FatalError())
  151. conn.close()
  152. lock.release()
  153. CALLBACK = CFUNCTYPE(c_void_p, c_void_p)
  154. CALLBACK.restype = c_void_p
  155. CALLBACK.argtypes = c_void_p
  156. cerror_handler = CALLBACK(error_handler)
  157. libgis.G_add_error_handler(cerror_handler, None)
  158. # Crerate the function array
  159. functions = [0]*15
  160. functions[RPCDefs.GET_VECTOR_TABLE_AS_DICT] = _get_vector_table_as_dict
  161. functions[RPCDefs.GET_VECTOR_FEATURES_AS_WKB] = _get_vector_features_as_wkb_list
  162. functions[RPCDefs.GET_RASTER_IMAGE_AS_NP] = _get_raster_image_as_np
  163. functions[RPCDefs.STOP] = _stop
  164. functions[RPCDefs.G_FATAL_ERROR] = _fatal_error
  165. while True:
  166. # Avoid busy waiting
  167. conn.poll(None)
  168. data = conn.recv()
  169. lock.acquire()
  170. functions[data[0]](lock, conn, data)
  171. lock.release()
  172. test_vector_name="data_provider_vector_map"
  173. test_raster_name="data_provider_raster_map"
  174. class DataProvider(RPCServerBase):
  175. """Fast and exit-safe interface to PyGRASS data delivery functions
  176. """
  177. def __init__(self):
  178. RPCServerBase.__init__(self)
  179. def start_server(self):
  180. """This function must be re-implemented in the subclasses
  181. """
  182. self.client_conn, self.server_conn = Pipe(True)
  183. self.lock = Lock()
  184. self.server = Process(target=data_provider_server, args=(self.lock,
  185. self.server_conn))
  186. self.server.daemon = True
  187. self.server.start()
  188. def get_raster_image_as_np(self, name, extent=None, color="RGB"):
  189. """Return the attribute table of a vector map as dictionary.
  190. See documentation of: pygrass.raster.raster2numpy_img
  191. Usage:
  192. .. code-block:: python
  193. >>> from grass.pygrass.rpc import DataProvider
  194. >>> provider = DataProvider()
  195. >>> ret = provider.get_raster_image_as_np(name=test_raster_name)
  196. >>> len(ret)
  197. 64
  198. >>> extent = {"north":30, "south":10, "east":30, "west":10,
  199. ... "rows":2, "cols":2}
  200. >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
  201. ... extent=extent)
  202. >>> len(ret)
  203. 16
  204. >>> ret # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  205. array([169, 255, 0, 255, 255, 0, 46, 255, 208, 255,
  206. 0, 255, 255, 0, 84, 255], dtype=uint8)
  207. >>> extent = {"rows":3, "cols":1}
  208. >>> ret = provider.get_raster_image_as_np(name=test_raster_name,
  209. ... extent=extent)
  210. >>> len(ret)
  211. 12
  212. >>> ret # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  213. array([255, 0, 7, 255, 255, 0, 84, 255, 255,
  214. 0, 123, 255], dtype=uint8)
  215. >>> provider.stop()
  216. ..
  217. """
  218. self.check_server()
  219. self.client_conn.send([RPCDefs.GET_RASTER_IMAGE_AS_NP,
  220. name, extent, color])
  221. return self.safe_receive("get_raster_image_as_np")
  222. def get_vector_table_as_dict(self, name, where=None):
  223. """Return the attribute table of a vector map as dictionary.
  224. See documentation of: pygrass.vector.VectorTopo::table_to_dict
  225. Usage:
  226. .. code-block:: python
  227. >>> from grass.pygrass.rpc import DataProvider
  228. >>> provider = DataProvider()
  229. >>> ret = provider.get_vector_table_as_dict(name=test_vector_name)
  230. >>> ret["table"]
  231. {1: [1, u'point', 1.0], 2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}
  232. >>> ret["columns"]
  233. Columns([(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')])
  234. >>> ret = provider.get_vector_table_as_dict(name=test_vector_name,
  235. ... where="value > 1")
  236. >>> ret["table"]
  237. {2: [2, u'line', 2.0], 3: [3, u'centroid', 3.0]}
  238. >>> ret["columns"]
  239. Columns([(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')])
  240. >>> provider.get_vector_table_as_dict(name="no_map",
  241. ... where="value > 1")
  242. >>> provider.stop()
  243. ..
  244. """
  245. self.check_server()
  246. self.client_conn.send([RPCDefs.GET_VECTOR_TABLE_AS_DICT,
  247. name, where])
  248. return self.safe_receive("get_vector_table_as_dict")
  249. def get_vector_features_as_wkb_list(self, name, extent=None,
  250. feature_type="point", field=1):
  251. """Return the features of a vector map as wkb list.
  252. :param extent: A dictionary of {"north":double, "south":double,
  253. "east":double, "west":double}
  254. :param feature_type: point, centroid, line, boundary or area
  255. See documentation: pygrass.vector.VectorTopo::features_to_wkb_list
  256. pygrass.vector.VectorTopo::areas_to_wkb_list
  257. Usage:
  258. .. code-block:: python
  259. >>> from grass.pygrass.rpc import DataProvider
  260. >>> provider = DataProvider()
  261. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  262. ... extent=None,
  263. ... feature_type="point")
  264. >>> for entry in wkb:
  265. ... f_id, cat, string = entry
  266. ... print(f_id, cat, len(string))
  267. 1 1 21
  268. 2 1 21
  269. 3 1 21
  270. >>> extent = {"north":6.6, "south":5.5, "east":14.5, "west":13.5}
  271. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  272. ... extent=extent,
  273. ... feature_type="point")
  274. >>> for entry in wkb:
  275. ... f_id, cat, string = entry
  276. ... print(f_id, cat, len(string))
  277. 3 1 21
  278. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  279. ... extent=None,
  280. ... feature_type="line")
  281. >>> for entry in wkb:
  282. ... f_id, cat, string = entry
  283. ... print(f_id, cat, len(string))
  284. 4 2 57
  285. 5 2 57
  286. 6 2 57
  287. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  288. ... extent=None,
  289. ... feature_type="centroid")
  290. >>> for entry in wkb:
  291. ... f_id, cat, string = entry
  292. ... print(f_id, cat, len(string))
  293. 19 3 21
  294. 18 3 21
  295. 20 3 21
  296. 21 3 21
  297. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  298. ... extent=None,
  299. ... feature_type="area")
  300. >>> for entry in wkb:
  301. ... f_id, cat, string = entry
  302. ... print(f_id, cat, len(string))
  303. 1 3 225
  304. 2 3 141
  305. 3 3 93
  306. 4 3 141
  307. >>> wkb = provider.get_vector_features_as_wkb_list(name=test_vector_name,
  308. ... extent=None,
  309. ... feature_type="boundary")
  310. >>> for entry in wkb:
  311. ... f_id, cat, string = entry
  312. ... print(f_id, cat, len(string))
  313. 10 None 41
  314. 7 None 41
  315. 8 None 41
  316. 9 None 41
  317. 11 None 89
  318. 12 None 41
  319. 14 None 41
  320. 13 None 41
  321. 17 None 41
  322. 15 None 41
  323. 16 None 41
  324. >>> provider.stop()
  325. ..
  326. """
  327. self.check_server()
  328. self.client_conn.send([RPCDefs.GET_VECTOR_FEATURES_AS_WKB,
  329. name, extent, feature_type, field])
  330. return self.safe_receive("get_vector_features_as_wkb_list")
  331. if __name__ == "__main__":
  332. import doctest
  333. from grass.pygrass import utils
  334. from grass.pygrass.modules import Module
  335. Module("g.region", n=40, s=0, e=40, w=0, res=10)
  336. Module("r.mapcalc", expression="%s = row() + (10 * col())"%(test_raster_name),
  337. overwrite=True)
  338. utils.create_test_vector_map(test_vector_name)
  339. doctest.testmod()
  340. """Remove the generated maps, if exist"""
  341. mset = utils.get_mapset_raster(test_raster_name, mapset='')
  342. if mset:
  343. Module("g.remove", flags='f', type='raster', name=test_raster_name)
  344. mset = utils.get_mapset_vector(test_vector_name, mapset='')
  345. if mset:
  346. Module("g.remove", flags='f', type='vector', name=test_vector_name)