c_libraries_interface.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268
  1. # -*- coding: utf-8 -*-
  2. """@package grass.pygrass.massages
  3. @brief Temporal Framework GRASS C-library interface
  4. Fast and exit-safe interface to GRASS C-library functions
  5. using ctypes and multiprocessing
  6. (C) 2013 by the GRASS Development Team
  7. This program is free software under the GNU General Public
  8. License (>=v2). Read the file COPYING that comes with GRASS
  9. for details.
  10. @author Soeren Gebbert
  11. """
  12. import sys
  13. from multiprocessing import Process, Lock, Pipe, Queue
  14. import logging
  15. from ctypes import *
  16. from core import *
  17. import core as corefunc
  18. import grass.lib.gis as libgis
  19. import grass.lib.raster as libraster
  20. import grass.lib.vector as libvector
  21. import grass.lib.date as libdate
  22. import grass.lib.raster3d as libraster3d
  23. import grass.lib.temporal as libtgis
  24. import signal, os
  25. ###############################################################################
  26. class RPCDefs(object):
  27. # Function identifier and index
  28. STOP=0
  29. HAS_TIMESTAMP=1
  30. WRITE_TIMESTAMP=2
  31. READ_TIMESTAMP=3
  32. REMOVE_TIMESTAMP=4
  33. READ_MAP_INFO=5
  34. MAP_EXISTS=6
  35. READ_MAP_INFO=7
  36. AVAILABLE_MAPSETS = 8
  37. GET_DRIVER_NAME = 9
  38. GET_DATABASE_NAME = 10
  39. G_MAPSET = 11
  40. G_LOCATION = 12
  41. G_GISDBASE = 13
  42. G_FATAL_ERROR = 14
  43. TYPE_RASTER=0
  44. TYPE_RASTER3D=1
  45. TYPE_VECTOR=2
  46. ###############################################################################
  47. def _fatal_error(lock, conn, data):
  48. """Calls G_fatal_error()"""
  49. libgis.G_fatal_error("Fatal Error in C library server")
  50. def _get_mapset(lock, conn, data):
  51. """Return the current mapset
  52. :param lock: A multiprocessing.Lock instance
  53. :param conn: A multiprocessing.Pipe instance used to send True or False
  54. :param data: The mapset as list entry 1 [function_id]
  55. :returns: Name of the current mapset
  56. """
  57. mapset = libgis.G_mapset()
  58. conn.send(mapset)
  59. def _get_location(lock, conn, data):
  60. """Return the current location
  61. :param lock: A multiprocessing.Lock instance
  62. :param conn: A multiprocessing.Pipe instance used to send True or False
  63. :param data: The mapset as list entry 1 [function_id]
  64. :returns: Name of the location
  65. """
  66. location = libgis.G_location()
  67. conn.send(location)
  68. def _get_gisdbase(lock, conn, data):
  69. """Return the current gisdatabase
  70. :param lock: A multiprocessing.Lock instance
  71. :param conn: A multiprocessing.Pipe instance used to send True or False
  72. :param data: The mapset as list entry 1 [function_id]
  73. :returns: Name of the gisdatabase
  74. """
  75. gisdbase = libgis.G_gisdbase()
  76. conn.send(gisdbase)
  77. def _get_driver_name(lock, conn, data):
  78. """Return the temporal database driver of a specific mapset
  79. :param lock: A multiprocessing.Lock instance
  80. :param conn: A multiprocessing.Pipe instance used to send True or False
  81. :param data: The mapset as list entry 1 [function_id, mapset]
  82. :returns: Name of the driver or None if no temporal database present
  83. """
  84. mapset = data[1]
  85. if not mapset:
  86. mapset = libgis.G_mapset()
  87. drstring = libtgis.tgis_get_mapset_driver_name(mapset)
  88. conn.send(drstring)
  89. ###############################################################################
  90. def _get_database_name(lock, conn, data):
  91. """Return the temporal database name of a specific mapset
  92. :param lock: A multiprocessing.Lock instance
  93. :param conn: A multiprocessing.Pipe instance used to send True or False
  94. :param data: The mapset as list entry 1 [function_id, mapset]
  95. :returns: Name of the database or None if no temporal database present
  96. """
  97. mapset = data[1]
  98. if not mapset:
  99. mapset = libgis.G_mapset()
  100. dbstring = libtgis.tgis_get_mapset_database_name(mapset)
  101. if dbstring:
  102. # We substitute GRASS variables if they are located in the database string
  103. # This behavior is in conjunction with db.connect
  104. dbstring = dbstring.replace("$GISDBASE", libgis.G_gisdbase())
  105. dbstring = dbstring.replace("$LOCATION_NAME", libgis.G_location())
  106. dbstring = dbstring.replace("$MAPSET", libgis.G_mapset())
  107. conn.send(dbstring)
  108. ###############################################################################
  109. def _available_mapsets(lock, conn, data):
  110. """Return all available mapsets the user can access as a list of strings
  111. :param lock: A multiprocessing.Lock instance
  112. :param conn: A multiprocessing.Pipe instance used to send True or False
  113. :param data: The list of data entries [function_id]
  114. :returns: Names of available mapsets as list of strings
  115. """
  116. mapsets = libgis.G_get_available_mapsets()
  117. count = 0
  118. mapset_list = []
  119. while mapsets[count]:
  120. char_list = ""
  121. mapset = mapsets[count]
  122. if libgis.G__mapset_permissions(mapset) > 0:
  123. count += 1
  124. c = 0
  125. while mapset[c] != "\x00":
  126. char_list += mapset[c]
  127. c += 1
  128. mapset_list.append(char_list)
  129. # We need to sort the mapset list, but the first one should be
  130. # the current mapset
  131. current_mapset = libgis.G_mapset()
  132. mapset_list.remove(current_mapset)
  133. mapset_list.sort()
  134. mapset_list.reverse()
  135. mapset_list.append(current_mapset)
  136. mapset_list.reverse()
  137. conn.send(mapset_list)
  138. def _has_timestamp(lock, conn, data):
  139. """Check if the file based GRASS timestamp is present and send
  140. True or False using the provided pipe.
  141. :param lock: A multiprocessing.Lock instance
  142. :param conn: A multiprocessing.Pipe instance used to send True or False
  143. :param data: The list of data entries [function_id, maptype, name, mapset, layer]
  144. """
  145. maptype = data[1]
  146. name = data[2]
  147. mapset = data[3]
  148. layer= data[4]
  149. check = False
  150. if maptype == RPCDefs.TYPE_RASTER:
  151. if libgis.G_has_raster_timestamp(name, mapset) == 1:
  152. check = True
  153. elif maptype == RPCDefs.TYPE_VECTOR:
  154. if libgis.G_has_vector_timestamp(name, layer, mapset) == 1:
  155. check = True
  156. elif maptype == RPCDefs.TYPE_RASTER3D:
  157. if libgis.G_has_raster3d_timestamp(name, mapset) == 1:
  158. check = True
  159. conn.send(check)
  160. ###############################################################################
  161. def _read_timestamp(lock, conn, data):
  162. """Read the file based GRASS timestamp and send
  163. the result using the provided pipe.
  164. The tuple to be send via pipe: (return value of G_read_*_timestamp, timestamps).
  165. Please have a look at the documentation of G_read_raster_timestamp,
  166. G_read_vector_timestamp and G_read_raster3d_timestamp for the return
  167. values description.
  168. The timestamps to be send are tuples of values:
  169. - relative time (start, end, unit), start and end are of type
  170. integer, unit is of type string.
  171. - absolute time (start, end), start and end are of type datetime
  172. The end time may be None in case of a time instance.
  173. :param lock: A multiprocessing.Lock instance
  174. :param conn: A multiprocessing.Pipe instance used to send the result
  175. :param data: The list of data entries [function_id, maptype, name, mapset, layer]
  176. """
  177. maptype = data[1]
  178. name = data[2]
  179. mapset = data[3]
  180. layer= data[4]
  181. check = False
  182. ts = libgis.TimeStamp()
  183. if maptype == RPCDefs.TYPE_RASTER:
  184. check = libgis.G_read_raster_timestamp(name, mapset, byref(ts))
  185. elif maptype == RPCDefs.TYPE_VECTOR:
  186. check = libgis.G_read_vector_timestamp(name, layer, mapset, byref(ts))
  187. elif maptype == RPCDefs.TYPE_RASTER3D:
  188. check = libgis.G_read_raster3d_timestamp(name, mapset, byref(ts))
  189. dates = _convert_timestamp_from_grass(ts)
  190. conn.send((check, dates))
  191. ###############################################################################
  192. def _write_timestamp(lock, conn, data):
  193. """Write the file based GRASS timestamp
  194. the return values of the called C-functions using the provided pipe.
  195. The value to be send via pipe is the return value of G_write_*_timestamp.
  196. Please have a look at the documentation of G_write_raster_timestamp,
  197. G_write_vector_timestamp and G_write_raster3d_timestamp for the return
  198. values description.
  199. :param lock: A multiprocessing.Lock instance
  200. :param conn: A multiprocessing.Pipe instance used to send True or False
  201. :param data: The list of data entries [function_id, maptype, name, mapset, layer, timestring]
  202. """
  203. maptype = data[1]
  204. name = data[2]
  205. mapset = data[3]
  206. layer= data[4]
  207. timestring = data[5]
  208. check = -3
  209. ts = libgis.TimeStamp()
  210. check = libgis.G_scan_timestamp(byref(ts), timestring)
  211. if check != 1:
  212. logging.error("Unable to convert the timestamp: "+ timestring)
  213. return -2
  214. if maptype == RPCDefs.TYPE_RASTER:
  215. check = libgis.G_write_raster_timestamp(name, byref(ts))
  216. elif maptype == RPCDefs.TYPE_VECTOR:
  217. check = libgis.G_write_vector_timestamp(name, layer, byref(ts))
  218. elif maptype == RPCDefs.TYPE_RASTER3D:
  219. check = libgis.G_write_raster3d_timestamp(name, byref(ts))
  220. conn.send(check)
  221. ###############################################################################
  222. def _remove_timestamp(lock, conn, data):
  223. """Remove the file based GRASS timestamp
  224. the return values of the called C-functions using the provided pipe.
  225. The value to be send via pipe is the return value of G_remove_*_timestamp.
  226. Please have a look at the documentation of G_remove_raster_timestamp,
  227. G_remove_vector_timestamp and G_remove_raster3d_timestamp for the return
  228. values description.
  229. :param lock: A multiprocessing.Lock instance
  230. :param conn: A multiprocessing.Pipe instance used to send True or False
  231. :param data: The list of data entries [function_id, maptype, name, mapset, layer]
  232. """
  233. maptype = data[1]
  234. name = data[2]
  235. mapset = data[3]
  236. layer= data[4]
  237. check = False
  238. if maptype == RPCDefs.TYPE_RASTER:
  239. check = libgis.G_remove_raster_timestamp(name, mapset)
  240. elif maptype == RPCDefs.TYPE_VECTOR:
  241. check = libgis.G_remove_vector_timestamp(name, layer, mapset)
  242. elif maptype == RPCDefs.TYPE_RASTER3D:
  243. check = libgis.G_remove_raster3d_timestamp(name, mapset)
  244. conn.send(check)
  245. ###############################################################################
  246. def _map_exists(lock, conn, data):
  247. """Check if a map exists in the spatial database
  248. The value to be send via pipe is True in case the map exists and False
  249. if not.
  250. :param lock: A multiprocessing.Lock instance
  251. :param conn: A multiprocessing.Pipe instance used to send True or False
  252. :param data: The list of data entries [function_id, maptype, name, mapset]
  253. """
  254. maptype = data[1]
  255. name = data[2]
  256. mapset = data[3]
  257. check = False
  258. if maptype == RPCDefs.TYPE_RASTER:
  259. mapset = libgis.G_find_raster(name, mapset)
  260. elif maptype == RPCDefs.TYPE_VECTOR:
  261. mapset = libgis.G_find_vector(name, mapset)
  262. elif maptype == RPCDefs.TYPE_RASTER3D:
  263. mapset = libgis.G_find_raster3d(name, mapset)
  264. if mapset:
  265. check = True
  266. conn.send(check)
  267. ###############################################################################
  268. def _read_map_info(lock, conn, data):
  269. """Read map specific metadata from the spatial database using C-library
  270. functions
  271. :param lock: A multiprocessing.Lock instance
  272. :param conn: A multiprocessing.Pipe instance used to send True or False
  273. :param data: The list of data entries [function_id, maptype, name, mapset]
  274. """
  275. maptype = data[1]
  276. name = data[2]
  277. mapset = data[3]
  278. if maptype == RPCDefs.TYPE_RASTER:
  279. kvp = _read_raster_info(name, mapset)
  280. elif maptype == RPCDefs.TYPE_VECTOR:
  281. kvp = _read_vector_info(name, mapset)
  282. elif maptype == RPCDefs.TYPE_RASTER3D:
  283. kvp = _read_raster3d_info(name, mapset)
  284. conn.send(kvp)
  285. ###############################################################################
  286. def _read_raster_info(name, mapset):
  287. """Read the raster map info from the file system and store the content
  288. into a dictionary
  289. This method uses the ctypes interface to the gis and raster libraries
  290. to read the map metadata information
  291. :param name: The name of the map
  292. :param mapset: The mapset of the map
  293. :returns: The key value pairs of the map specific metadata, or None in case of an error
  294. """
  295. kvp = {}
  296. if not libgis.G_find_raster(name, mapset):
  297. return None
  298. # Read the region information
  299. region = libgis.Cell_head()
  300. libraster.Rast_get_cellhd(name, mapset, byref(region))
  301. kvp["north"] = region.north
  302. kvp["south"] = region.south
  303. kvp["east"] = region.east
  304. kvp["west"] = region.west
  305. kvp["nsres"] = region.ns_res
  306. kvp["ewres"] = region.ew_res
  307. kvp["rows"] = region.cols
  308. kvp["cols"] = region.rows
  309. maptype = libraster.Rast_map_type(name, mapset)
  310. if maptype == libraster.DCELL_TYPE:
  311. kvp["datatype"] = "DCELL"
  312. elif maptype == libraster.FCELL_TYPE:
  313. kvp["datatype"] = "FCELL"
  314. elif maptype == libraster.CELL_TYPE:
  315. kvp["datatype"] = "CELL"
  316. # Read range
  317. if libraster.Rast_map_is_fp(name, mapset):
  318. range = libraster.FPRange()
  319. libraster.Rast_init_fp_range(byref(range))
  320. ret = libraster.Rast_read_fp_range(name, mapset, byref(range))
  321. if ret < 0:
  322. logging.error(_("Unable to read range file"))
  323. return None
  324. if ret == 2:
  325. kvp["min"] = None
  326. kvp["max"] = None
  327. else:
  328. min = libgis.DCELL()
  329. max = libgis.DCELL()
  330. libraster.Rast_get_fp_range_min_max(
  331. byref(range), byref(min), byref(max))
  332. kvp["min"] = min.value
  333. kvp["max"] = max.value
  334. else:
  335. range = libraster.Range()
  336. libraster.Rast_init_range(byref(range))
  337. ret = libraster.Rast_read_range(name, mapset, byref(range))
  338. if ret < 0:
  339. logging.error(_("Unable to read range file"))
  340. return None
  341. if ret == 2:
  342. kvp["min"] = None
  343. kvp["max"] = None
  344. else:
  345. min = libgis.CELL()
  346. max = libgis.CELL()
  347. libraster.Rast_get_range_min_max(
  348. byref(range), byref(min), byref(max))
  349. kvp["min"] = min.value
  350. kvp["max"] = max.value
  351. return kvp
  352. ###############################################################################
  353. def _read_raster3d_info(name, mapset):
  354. """Read the 3D raster map info from the file system and store the content
  355. into a dictionary
  356. This method uses the ctypes interface to the gis and raster3d libraries
  357. to read the map metadata information
  358. :param name: The name of the map
  359. :param mapset: The mapset of the map
  360. :returns: The key value pairs of the map specific metadata, or None in case of an error
  361. """
  362. kvp = {}
  363. if not libgis.G_find_raster3d(name, mapset):
  364. return None
  365. # Read the region information
  366. region = libraster3d.RASTER3D_Region()
  367. libraster3d.Rast3d_read_region_map(name, mapset, byref(region))
  368. kvp["north"] = region.north
  369. kvp["south"] = region.south
  370. kvp["east"] = region.east
  371. kvp["west"] = region.west
  372. kvp["nsres"] = region.ns_res
  373. kvp["ewres"] = region.ew_res
  374. kvp["tbres"] = region.tb_res
  375. kvp["rows"] = region.cols
  376. kvp["cols"] = region.rows
  377. kvp["depths"] = region.depths
  378. kvp["top"] = region.top
  379. kvp["bottom"] = region.bottom
  380. # We need to open the map, this function returns a void pointer
  381. # but we may need the correct type which is RASTER3D_Map, hence
  382. # the casting
  383. g3map = cast(libraster3d.Rast3d_open_cell_old(name, mapset,
  384. libraster3d.RASTER3D_DEFAULT_WINDOW,
  385. libraster3d.RASTER3D_TILE_SAME_AS_FILE,
  386. libraster3d.RASTER3D_NO_CACHE),
  387. POINTER(libraster3d.RASTER3D_Map))
  388. if not g3map:
  389. logging.error(_("Unable to open 3D raster map <%s>" % (name)))
  390. return None
  391. maptype = libraster3d.Rast3d_file_type_map(g3map)
  392. if maptype == libraster.DCELL_TYPE:
  393. kvp["datatype"] = "DCELL"
  394. elif maptype == libraster.FCELL_TYPE:
  395. kvp["datatype"] = "FCELL"
  396. # Read range
  397. min = libgis.DCELL()
  398. max = libgis.DCELL()
  399. ret = libraster3d.Rast3d_range_load(g3map)
  400. if not ret:
  401. logging.error(_("Unable to load range of 3D raster map <%s>" %
  402. (name)))
  403. return None
  404. libraster3d.Rast3d_range_min_max(g3map, byref(min), byref(max))
  405. if min.value != min.value:
  406. kvp["min"] = None
  407. else:
  408. kvp["min"] = float(min.value)
  409. if max.value != max.value:
  410. kvp["max"] = None
  411. else:
  412. kvp["max"] = float(max.value)
  413. if not libraster3d.Rast3d_close(g3map):
  414. logging.error(_("Unable to close 3D raster map <%s>" % (name)))
  415. return None
  416. return kvp
  417. ###############################################################################
  418. def _read_vector_info(name, mapset):
  419. """Read the vector map info from the file system and store the content
  420. into a dictionary
  421. This method uses the ctypes interface to the vector libraries
  422. to read the map metadata information
  423. :param name: The name of the map
  424. :param mapset: The mapset of the map
  425. :returns: The key value pairs of the map specific metadata, or None in case of an error
  426. """
  427. kvp = {}
  428. if not libgis.G_find_vector(name, mapset):
  429. return None
  430. # The vector map structure
  431. Map = libvector.Map_info()
  432. # We open the maps always in topology mode first
  433. libvector.Vect_set_open_level(2)
  434. with_topo = True
  435. # Code lend from v.info main.c
  436. if libvector.Vect_open_old_head2(byref(Map), name, mapset, "1") < 2:
  437. # force level 1, open fully
  438. # NOTE: number of points, lines, boundaries, centroids,
  439. # faces, kernels is still available
  440. libvector.Vect_set_open_level(1) # no topology
  441. with_topo = False
  442. if libvector.Vect_open_old2(byref(Map), name, mapset, "1") < 1:
  443. logging.error(_("Unable to open vector map <%s>" %
  444. (libvector.Vect_get_full_name(byref(Map)))))
  445. return None
  446. # Release the vector spatial index memory when closed
  447. libvector.Vect_set_release_support(byref(Map))
  448. # Read the extent information
  449. bbox = libvector.bound_box()
  450. libvector.Vect_get_map_box(byref(Map), byref(bbox))
  451. kvp["north"] = bbox.N
  452. kvp["south"] = bbox.S
  453. kvp["east"] = bbox.E
  454. kvp["west"] = bbox.W
  455. kvp["top"] = bbox.T
  456. kvp["bottom"] = bbox.B
  457. kvp["map3d"] = bool(libvector.Vect_is_3d(byref(Map)))
  458. # Read number of features
  459. if with_topo:
  460. kvp["points"] = libvector.Vect_get_num_primitives(
  461. byref(Map), libvector.GV_POINT)
  462. kvp["lines"] = libvector.Vect_get_num_primitives(
  463. byref(Map), libvector.GV_LINE)
  464. kvp["boundaries"] = libvector.Vect_get_num_primitives(
  465. byref(Map), libvector.GV_BOUNDARY)
  466. kvp["centroids"] = libvector.Vect_get_num_primitives(
  467. byref(Map), libvector.GV_CENTROID)
  468. kvp["faces"] = libvector.Vect_get_num_primitives(
  469. byref(Map), libvector.GV_FACE)
  470. kvp["kernels"] = libvector.Vect_get_num_primitives(
  471. byref(Map), libvector.GV_KERNEL)
  472. # Summarize the primitives
  473. kvp["primitives"] = kvp["points"] + kvp["lines"] + \
  474. kvp["boundaries"] + kvp["centroids"]
  475. if kvp["map3d"]:
  476. kvp["primitives"] += kvp["faces"] + kvp["kernels"]
  477. # Read topology information
  478. kvp["nodes"] = libvector.Vect_get_num_nodes(byref(Map))
  479. kvp["areas"] = libvector.Vect_get_num_areas(byref(Map))
  480. kvp["islands"] = libvector.Vect_get_num_islands(byref(Map))
  481. kvp["holes"] = libvector.Vect_get_num_holes(byref(Map))
  482. kvp["volumes"] = libvector.Vect_get_num_primitives(
  483. byref(Map), libvector.GV_VOLUME)
  484. else:
  485. kvp["points"] = None
  486. kvp["lines"] = None
  487. kvp["boundaries"] = None
  488. kvp["centroids"] = None
  489. kvp["faces"] = None
  490. kvp["kernels"] = None
  491. kvp["primitives"] = None
  492. kvp["nodes"] = None
  493. kvp["areas"] = None
  494. kvp["islands"] = None
  495. kvp["holes"] = None
  496. kvp["volumes"] = None
  497. libvector.Vect_close(byref(Map))
  498. return kvp
  499. ###############################################################################
  500. def _convert_timestamp_from_grass(ts):
  501. """Convert a GRASS file based timestamp into the temporal framework
  502. format datetime or integer.
  503. A tuple of two datetime objects (start, end) is returned in case of absolute time.
  504. In case of relative time a tuple with start time, end time and the
  505. relative unit (start, end, unit) will be returned.
  506. Note:
  507. The end time will be set to None in case of a time instance.
  508. :param ts grass.lib.gis.TimeStamp object created by G_read_*_timestamp
  509. """
  510. dt1 = libgis.DateTime()
  511. dt2 = libgis.DateTime()
  512. count = c_int()
  513. libgis.G_get_timestamps(byref(ts),
  514. byref(dt1),
  515. byref(dt2),
  516. byref(count))
  517. if dt1.mode == libdate.DATETIME_ABSOLUTE:
  518. pdt1 = None
  519. pdt2 = None
  520. if count.value >= 1:
  521. pdt1 = datetime(int(dt1.year), int(dt1.month), int(dt1.day),
  522. int(dt1.hour), int(dt1.minute),
  523. int(dt1.second))
  524. if count.value == 2:
  525. pdt2 = datetime(int(dt2.year), int(dt2.month), int(dt2.day),
  526. int(dt2.hour), int(dt2.minute),
  527. int(dt2.second))
  528. # ATTENTION: We ignore the time zone
  529. # TODO: Write time zone support
  530. return (pdt1, pdt2)
  531. else:
  532. unit = None
  533. start = None
  534. end = None
  535. if count.value >= 1:
  536. if dt1.year > 0:
  537. unit = "years"
  538. start = dt1.year
  539. elif dt1.month > 0:
  540. unit = "months"
  541. start = dt1.month
  542. elif dt1.day > 0:
  543. unit = "days"
  544. start = dt1.day
  545. elif dt1.hour > 0:
  546. unit = "hours"
  547. start = dt1.hour
  548. elif dt1.minute > 0:
  549. unit = "minutes"
  550. start = dt1.minute
  551. elif dt1.second > 0:
  552. unit = "seconds"
  553. start = dt1.second
  554. if count.value == 2:
  555. if dt2.year > 0:
  556. end = dt2.year
  557. elif dt2.month > 0:
  558. end = dt2.month
  559. elif dt2.day > 0:
  560. end = dt2.day
  561. elif dt2.hour > 0:
  562. end = dt2.hour
  563. elif dt2.minute > 0:
  564. end = dt2.minute
  565. elif dt2.second > 0:
  566. end = dt2.second
  567. return (start, end, unit)
  568. ###############################################################################
  569. def _stop(lock, conn, data):
  570. libgis.G_debug(1, "Stop C-interface server")
  571. conn.close()
  572. lock.release()
  573. sys.exit()
  574. ###############################################################################
  575. # Global server connection
  576. server_connection = None
  577. server_lock = None
  578. def c_library_server(lock, conn):
  579. """The GRASS C-libraries server function designed to be a target for
  580. multiprocessing.Process
  581. :param lock: A multiprocessing.Lock
  582. :param conn: A multiprocessing.Pipe
  583. """
  584. # Crerate the function array
  585. functions = [0]*15
  586. functions[RPCDefs.STOP] = _stop
  587. functions[RPCDefs.HAS_TIMESTAMP] = _has_timestamp
  588. functions[RPCDefs.WRITE_TIMESTAMP] = _write_timestamp
  589. functions[RPCDefs.READ_TIMESTAMP] = _read_timestamp
  590. functions[RPCDefs.REMOVE_TIMESTAMP] = _remove_timestamp
  591. functions[RPCDefs.READ_MAP_INFO] = _read_map_info
  592. functions[RPCDefs.MAP_EXISTS] = _map_exists
  593. functions[RPCDefs.AVAILABLE_MAPSETS] = _available_mapsets
  594. functions[RPCDefs.GET_DRIVER_NAME] = _get_driver_name
  595. functions[RPCDefs.GET_DATABASE_NAME] = _get_database_name
  596. functions[RPCDefs.G_MAPSET] = _get_mapset
  597. functions[RPCDefs.G_LOCATION] = _get_location
  598. functions[RPCDefs.G_GISDBASE] = _get_gisdbase
  599. functions[RPCDefs.G_FATAL_ERROR] = _fatal_error
  600. libgis.G_gisinit("c_library_server")
  601. libgis.G_debug(1, "Start C-interface server")
  602. while True:
  603. # Avoid busy waiting
  604. conn.poll(4)
  605. data = conn.recv()
  606. lock.acquire()
  607. functions[data[0]](lock, conn, data)
  608. lock.release()
  609. class CLibrariesInterface(object):
  610. """Fast and exit-safe interface to GRASS C-libraries functions
  611. This class implements a fast and exit-safe interface to the GRASS
  612. gis, raster, 3D raster and vector C-libraries functions.
  613. The C-libraries functions are called via ctypes in a subprocess
  614. using a pipe (multiprocessing.Pipe) to transfer the text messages.
  615. Hence, the process that uses the CLibrariesInterface will not be
  616. exited, if a G_fatal_error() was invoked in the subprocess.
  617. In this case the CLibrariesInterface object will simply start a
  618. new subprocess and restarts the pipeline.
  619. Usage:
  620. @code
  621. >>> import grass.script as gscript
  622. >>> import grass.temporal as tgis
  623. >>> gscript.use_temp_region()
  624. >>> gscript.run_command("g.region", n=80.0, s=0.0, e=120.0, w=0.0,
  625. ... t=1.0, b=0.0, res=10.0, res3=10.0)
  626. 0
  627. >>> tgis.init()
  628. >>> gscript.run_command("r.mapcalc", expression="test = 1", overwrite=True, quiet=True)
  629. 0
  630. >>> gscript.run_command("r3.mapcalc", expression="test = 1", overwrite=True, quiet=True)
  631. 0
  632. >>> gscript.run_command("v.random", output="test", n=10, overwrite=True, quiet=True)
  633. 0
  634. >>> gscript.run_command("r.timestamp", map="test", date='12 Mar 1995 10:34:40', overwrite=True, quiet=True)
  635. 0
  636. >>> gscript.run_command("r3.timestamp", map="test", date='12 Mar 1995 10:34:40', overwrite=True, quiet=True)
  637. 0
  638. >>> gscript.run_command("v.timestamp", map="test", date='12 Mar 1995 10:34:40', overwrite=True, quiet=True)
  639. 0
  640. # Check mapsets
  641. >>> ciface = tgis.CLibrariesInterface()
  642. >>> mapsets = ciface.available_mapsets()
  643. >>> mapsets[0] == tgis.get_current_mapset()
  644. True
  645. # Raster map
  646. >>> ciface = tgis.CLibrariesInterface()
  647. >>> check = ciface.raster_map_exists("test", tgis.get_current_mapset())
  648. >>> print check
  649. True
  650. >>> ciface.read_raster_info("test", tgis.get_current_mapset())
  651. {'rows': 12, 'north': 80.0, 'min': 1, 'datatype': 'CELL', 'max': 1, 'ewres': 10.0, 'cols': 8, 'west': 0.0, 'east': 120.0, 'nsres': 10.0, 'south': 0.0}
  652. >>> check = ciface.has_raster_timestamp("test", tgis.get_current_mapset())
  653. >>> print check
  654. True
  655. >>> if check:
  656. ... res = ciface.read_raster_timestamp("test", tgis.get_current_mapset())
  657. ... if res[0]:
  658. ... print str(res[1][0]), str(res[1][0])
  659. ... ciface.remove_raster_timestamp("test", tgis.get_current_mapset())
  660. 1995-03-12 10:34:40 1995-03-12 10:34:40
  661. 1
  662. >>> ciface.has_raster_timestamp("test", tgis.get_current_mapset())
  663. False
  664. >>> ciface.write_raster_timestamp("test", tgis.get_current_mapset(), "13 Jan 1999 14:30:05")
  665. 1
  666. >>> ciface.has_raster_timestamp("test", tgis.get_current_mapset())
  667. True
  668. # 3D raster map
  669. >>> check = ciface.raster3d_map_exists("test", tgis.get_current_mapset())
  670. >>> print check
  671. True
  672. >>> ciface.read_raster3d_info("test", tgis.get_current_mapset())
  673. {'tbres': 1.0, 'rows': 12, 'north': 80.0, 'bottom': 0.0, 'datatype': 'DCELL', 'max': 1.0, 'top': 1.0, 'min': 1.0, 'cols': 8, 'depths': 1, 'west': 0.0, 'ewres': 10.0, 'east': 120.0, 'nsres': 10.0, 'south': 0.0}
  674. >>> check = ciface.has_raster3d_timestamp("test", tgis.get_current_mapset())
  675. >>> print check
  676. True
  677. >>> if check:
  678. ... res = ciface.read_raster3d_timestamp("test", tgis.get_current_mapset())
  679. ... if res[0]:
  680. ... print str(res[1][0]), str(res[1][0])
  681. ... ciface.remove_raster3d_timestamp("test", tgis.get_current_mapset())
  682. 1995-03-12 10:34:40 1995-03-12 10:34:40
  683. 1
  684. >>> ciface.has_raster3d_timestamp("test", tgis.get_current_mapset())
  685. False
  686. >>> ciface.write_raster3d_timestamp("test", tgis.get_current_mapset(), "13 Jan 1999 14:30:05")
  687. 1
  688. >>> ciface.has_raster3d_timestamp("test", tgis.get_current_mapset())
  689. True
  690. # Vector map
  691. >>> check = ciface.vector_map_exists("test", tgis.get_current_mapset())
  692. >>> print check
  693. True
  694. >>> kvp = ciface.read_vector_info("test", tgis.get_current_mapset())
  695. >>> print kvp['points']
  696. 10
  697. >>> check = ciface.has_vector_timestamp("test", tgis.get_current_mapset(), None)
  698. >>> print check
  699. True
  700. >>> if check:
  701. ... res = ciface.read_vector_timestamp("test", tgis.get_current_mapset())
  702. ... if res[0]:
  703. ... print str(res[1][0]), str(res[1][0])
  704. ... ciface.remove_vector_timestamp("test", tgis.get_current_mapset())
  705. 1995-03-12 10:34:40 1995-03-12 10:34:40
  706. 1
  707. >>> ciface.has_vector_timestamp("test", tgis.get_current_mapset())
  708. False
  709. >>> ciface.write_vector_timestamp("test", tgis.get_current_mapset(), "13 Jan 1999 14:30:05")
  710. 1
  711. >>> ciface.has_vector_timestamp("test", tgis.get_current_mapset())
  712. True
  713. >>> ciface.get_driver_name()
  714. 'sqlite'
  715. >>> ciface.get_database_name().split("/")[-1]
  716. 'sqlite.db'
  717. >>> mapset = ciface.get_mapset()
  718. >>> location = ciface.get_location()
  719. >>> gisdbase = ciface.get_gisdbase()
  720. >>> gscript.del_temp_region()
  721. @endcode
  722. """
  723. def __init__(self):
  724. self.client_conn = None
  725. self.server_conn = None
  726. self.queue = None
  727. self.server = None
  728. self.start_server()
  729. def start_server(self):
  730. self.client_conn, self.server_conn = Pipe(True)
  731. self.lock = Lock()
  732. self.server = Process(target=c_library_server, args=(self.lock,
  733. self.server_conn))
  734. self.server.daemon = True
  735. self.server.start()
  736. def _check_restart_server(self):
  737. """Restart the server if it was terminated
  738. """
  739. if self.server.is_alive() is True:
  740. return
  741. self.client_conn.close()
  742. self.server_conn.close()
  743. self.start_server()
  744. logging.warning("Needed to restart the libgis server")
  745. def raster_map_exists(self, name, mapset):
  746. """Check if a raster map exists in the spatial database
  747. :param name: The name of the map
  748. :param mapset: The mapset of the map
  749. :returns: True if exists, False if not
  750. """
  751. self._check_restart_server()
  752. self.client_conn.send([RPCDefs.MAP_EXISTS, RPCDefs.TYPE_RASTER,
  753. name, mapset, None])
  754. return self.client_conn.recv()
  755. def read_raster_info(self, name, mapset):
  756. """Read the raster map info from the file system and store the content
  757. into a dictionary
  758. :param name: The name of the map
  759. :param mapset: The mapset of the map
  760. :returns: The key value pairs of the map specific metadata, or None in case of an error
  761. """
  762. self._check_restart_server()
  763. self.client_conn.send([RPCDefs.READ_MAP_INFO, RPCDefs.TYPE_RASTER,
  764. name, mapset, None])
  765. return self.client_conn.recv()
  766. def has_raster_timestamp(self, name, mapset):
  767. """Check if a file based raster timetamp exists
  768. :param name: The name of the map
  769. :param mapset: The mapset of the map
  770. :returns: True if exists, False if not
  771. """
  772. self._check_restart_server()
  773. self.client_conn.send([RPCDefs.HAS_TIMESTAMP, RPCDefs.TYPE_RASTER,
  774. name, mapset, None])
  775. return self.client_conn.recv()
  776. def remove_raster_timestamp(self, name, mapset):
  777. """Remove a file based raster timetamp
  778. Please have a look at the documentation G_remove_raster_timestamp
  779. for the return values description.
  780. :param name: The name of the map
  781. :param mapset: The mapset of the map
  782. :returns: The return value of G_remove_raster_timestamp
  783. """
  784. self._check_restart_server()
  785. self.client_conn.send([RPCDefs.REMOVE_TIMESTAMP, RPCDefs.TYPE_RASTER,
  786. name, mapset, None])
  787. return self.client_conn.recv()
  788. def read_raster_timestamp(self, name, mapset):
  789. """Read a file based raster timetamp
  790. Please have a look at the documentation G_read_raster_timestamp
  791. for the return values description.
  792. The timestamps to be send are tuples of values:
  793. - relative time (start, end, unit), start and end are of type
  794. integer, unit is of type string.
  795. - absolute time (start, end), start and end are of type datetime
  796. The end time may be None in case of a time instance.
  797. :param name: The name of the map
  798. :param mapset: The mapset of the map
  799. :returns: The return value of G_read_raster_timestamp
  800. """
  801. self._check_restart_server()
  802. self.client_conn.send([RPCDefs.READ_TIMESTAMP, RPCDefs.TYPE_RASTER,
  803. name, mapset, None])
  804. return self.client_conn.recv()
  805. def write_raster_timestamp(self, name, mapset, timestring):
  806. """Write a file based raster timetamp
  807. Please have a look at the documentation G_write_raster_timestamp
  808. for the return values description.
  809. Note:
  810. Only timestamps of maps from the current mapset can written.
  811. :param name: The name of the map
  812. :param mapset: The mapset of the map
  813. :param timestring: A GRASS datetime C-library compatible string
  814. :returns: The return value of G_write_raster_timestamp
  815. """
  816. self._check_restart_server()
  817. self.client_conn.send([RPCDefs.WRITE_TIMESTAMP, RPCDefs.TYPE_RASTER,
  818. name, mapset, None, timestring])
  819. return self.client_conn.recv()
  820. def raster3d_map_exists(self, name, mapset):
  821. """Check if a 3D raster map exists in the spatial database
  822. :param name: The name of the map
  823. :param mapset: The mapset of the map
  824. :returns: True if exists, False if not
  825. """
  826. self._check_restart_server()
  827. self.client_conn.send([RPCDefs.MAP_EXISTS, RPCDefs.TYPE_RASTER3D,
  828. name, mapset, None])
  829. return self.client_conn.recv()
  830. def read_raster3d_info(self, name, mapset):
  831. """Read the 3D raster map info from the file system and store the content
  832. into a dictionary
  833. :param name: The name of the map
  834. :param mapset: The mapset of the map
  835. :returns: The key value pairs of the map specific metadata, or None in case of an error
  836. """
  837. self._check_restart_server()
  838. self.client_conn.send([RPCDefs.READ_MAP_INFO, RPCDefs.TYPE_RASTER3D,
  839. name, mapset, None])
  840. return self.client_conn.recv()
  841. def has_raster3d_timestamp(self, name, mapset):
  842. """Check if a file based 3D raster timetamp exists
  843. :param name: The name of the map
  844. :param mapset: The mapset of the map
  845. :returns: True if exists, False if not
  846. """
  847. self._check_restart_server()
  848. self.client_conn.send([RPCDefs.HAS_TIMESTAMP, RPCDefs.TYPE_RASTER3D,
  849. name, mapset, None])
  850. return self.client_conn.recv()
  851. def remove_raster3d_timestamp(self, name, mapset):
  852. """Remove a file based 3D raster timetamp
  853. Please have a look at the documentation G_remove_raster3d_timestamp
  854. for the return values description.
  855. :param name: The name of the map
  856. :param mapset: The mapset of the map
  857. :returns: The return value of G_remove_raster3d_timestamp
  858. """
  859. self._check_restart_server()
  860. self.client_conn.send([RPCDefs.REMOVE_TIMESTAMP, RPCDefs.TYPE_RASTER3D,
  861. name, mapset, None])
  862. return self.client_conn.recv()
  863. def read_raster3d_timestamp(self, name, mapset):
  864. """Read a file based 3D raster timetamp
  865. Please have a look at the documentation G_read_raster3d_timestamp
  866. for the return values description.
  867. The timestamps to be send are tuples of values:
  868. - relative time (start, end, unit), start and end are of type
  869. integer, unit is of type string.
  870. - absolute time (start, end), start and end are of type datetime
  871. The end time may be None in case of a time instance.
  872. :param name: The name of the map
  873. :param mapset: The mapset of the map
  874. :returns: The return value of G_read_raster3d_timestamp
  875. """
  876. self._check_restart_server()
  877. self.client_conn.send([RPCDefs.READ_TIMESTAMP, RPCDefs.TYPE_RASTER3D,
  878. name, mapset, None])
  879. return self.client_conn.recv()
  880. def write_raster3d_timestamp(self, name, mapset, timestring):
  881. """Write a file based 3D raster timetamp
  882. Please have a look at the documentation G_write_raster3d_timestamp
  883. for the return values description.
  884. Note:
  885. Only timestamps of maps from the current mapset can written.
  886. :param name: The name of the map
  887. :param mapset: The mapset of the map
  888. :param timestring: A GRASS datetime C-library compatible string
  889. :returns: The return value of G_write_raster3d_timestamp
  890. """
  891. self._check_restart_server()
  892. self.client_conn.send([RPCDefs.WRITE_TIMESTAMP, RPCDefs.TYPE_RASTER3D,
  893. name, mapset, None, timestring])
  894. return self.client_conn.recv()
  895. def vector_map_exists(self, name, mapset):
  896. """Check if a vector map exists in the spatial database
  897. :param name: The name of the map
  898. :param mapset: The mapset of the map
  899. :returns: True if exists, False if not
  900. """
  901. self._check_restart_server()
  902. self.client_conn.send([RPCDefs.MAP_EXISTS, RPCDefs.TYPE_VECTOR,
  903. name, mapset, None])
  904. return self.client_conn.recv()
  905. def read_vector_info(self, name, mapset):
  906. """Read the vector map info from the file system and store the content
  907. into a dictionary
  908. :param name: The name of the map
  909. :param mapset: The mapset of the map
  910. :returns: The key value pairs of the map specific metadata, or None in case of an error
  911. """
  912. self._check_restart_server()
  913. self.client_conn.send([RPCDefs.READ_MAP_INFO, RPCDefs.TYPE_VECTOR,
  914. name, mapset, None])
  915. return self.client_conn.recv()
  916. def has_vector_timestamp(self, name, mapset, layer=None):
  917. """Check if a file based vector timetamp exists
  918. :param name: The name of the map
  919. :param mapset: The mapset of the map
  920. :param layer: The layer of the vector map
  921. :returns: True if exists, False if not
  922. """
  923. self._check_restart_server()
  924. self.client_conn.send([RPCDefs.HAS_TIMESTAMP, RPCDefs.TYPE_VECTOR,
  925. name, mapset, layer])
  926. return self.client_conn.recv()
  927. def remove_vector_timestamp(self, name, mapset, layer=None):
  928. """Remove a file based vector timetamp
  929. Please have a look at the documentation G_remove_vector_timestamp
  930. for the return values description.
  931. :param name: The name of the map
  932. :param mapset: The mapset of the map
  933. :param layer The layer of the vector map
  934. :returns: The return value of G_remove_vector_timestamp
  935. """
  936. self._check_restart_server()
  937. self.client_conn.send([RPCDefs.REMOVE_TIMESTAMP, RPCDefs.TYPE_VECTOR,
  938. name, mapset, layer])
  939. return self.client_conn.recv()
  940. def read_vector_timestamp(self, name, mapset, layer=None):
  941. """Read a file based vector timetamp
  942. Please have a look at the documentation G_read_vector_timestamp
  943. for the return values description.
  944. The timestamps to be send are tuples of values:
  945. - relative time (start, end, unit), start and end are of type
  946. integer, unit is of type string.
  947. - absolute time (start, end), start and end are of type datetime
  948. The end time may be None in case of a time instance.
  949. :param name: The name of the map
  950. :param mapset: The mapset of the map
  951. :param layer: The layer of the vector map
  952. :returns: The return value ofG_read_vector_timestamp and the timestamps
  953. """
  954. self._check_restart_server()
  955. self.client_conn.send([RPCDefs.READ_TIMESTAMP, RPCDefs.TYPE_VECTOR,
  956. name, mapset, layer])
  957. return self.client_conn.recv()
  958. def write_vector_timestamp(self, name, mapset, timestring, layer=None):
  959. """Write a file based vector timestamp
  960. Please have a look at the documentation G_write_vector_timestamp
  961. for the return values description.
  962. Note:
  963. Only timestamps pf maps from the current mapset can written.
  964. :param name: The name of the map
  965. :param mapset: The mapset of the map
  966. :param timestring: A GRASS datetime C-library compatible string
  967. :param layer: The layer of the vector map
  968. :returns: The return value of G_write_vector_timestamp
  969. """
  970. self._check_restart_server()
  971. self.client_conn.send([RPCDefs.WRITE_TIMESTAMP, RPCDefs.TYPE_VECTOR,
  972. name, mapset, layer, timestring])
  973. return self.client_conn.recv()
  974. def available_mapsets(self):
  975. """Return all available mapsets the user can access as a list of strings
  976. :returns: Names of available mapsets as list of strings
  977. """
  978. self._check_restart_server()
  979. self.client_conn.send([RPCDefs.AVAILABLE_MAPSETS, ])
  980. return self.client_conn.recv()
  981. def get_driver_name(self, mapset=None):
  982. """Return the temporal database driver of a specific mapset
  983. :param mapset: Name of the mapset
  984. :returns: Name of the driver or None if no temporal database present
  985. """
  986. self._check_restart_server()
  987. self.client_conn.send([RPCDefs.GET_DRIVER_NAME, mapset])
  988. return self.client_conn.recv()
  989. def get_database_name(self, mapset=None):
  990. """Return the temporal database name of a specific mapset
  991. :param mapset: Name of the mapset
  992. :returns: Name of the database or None if no temporal database present
  993. """
  994. self._check_restart_server()
  995. self.client_conn.send([RPCDefs.GET_DATABASE_NAME, mapset])
  996. return self.client_conn.recv()
  997. def get_mapset(self):
  998. """Return the current mapset
  999. :returns: Name of the current mapset
  1000. """
  1001. self._check_restart_server()
  1002. self.client_conn.send([RPCDefs.G_MAPSET,])
  1003. return self.client_conn.recv()
  1004. def get_location(self):
  1005. """Return the location
  1006. :returns: Name of the location
  1007. """
  1008. self._check_restart_server()
  1009. self.client_conn.send([RPCDefs.G_LOCATION,])
  1010. return self.client_conn.recv()
  1011. def get_gisdbase(self):
  1012. """Return the gisdatabase
  1013. :returns: Name of the gisdatabase
  1014. """
  1015. self._check_restart_server()
  1016. self.client_conn.send([RPCDefs.G_GISDBASE,])
  1017. return self.client_conn.recv()
  1018. def fatal_error(self, mapset=None):
  1019. """Return the temporal database name of a specific mapset
  1020. :param mapset: Name of the mapset
  1021. :returns: Name of the database or None if no temporal database present
  1022. """
  1023. self._check_restart_server()
  1024. self.client_conn.send([RPCDefs.G_FATAL_ERROR])
  1025. def stop(self):
  1026. """Stop the messenger server and close the pipe
  1027. This method should be called at exit using the package atexit
  1028. """
  1029. if self.server is not None and self.server.is_alive():
  1030. self.client_conn.send([0,])
  1031. self.server.join(5)
  1032. self.server.terminate()
  1033. if self.client_conn is not None:
  1034. self.client_conn.close()
  1035. if __name__ == "__main__":
  1036. import doctest
  1037. doctest.testmod()