__init__.py 15 KB

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