__init__.py 15 KB

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