__init__.py 15 KB

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