__init__.py 15 KB

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