c_libraries_interface.py 45 KB

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