vector.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. """
  2. Vector related functions to be used in Python scripts.
  3. Usage:
  4. ::
  5. from grass.script import vector as grass
  6. grass.vector_db(map)
  7. (C) 2008-2010 by the GRASS Development Team
  8. This program is free software under the GNU General Public
  9. License (>=v2). Read the file COPYING that comes with GRASS
  10. for details.
  11. .. sectionauthor:: Glynn Clements
  12. .. sectionauthor:: Martin Landa <landa.martin gmail.com>
  13. """
  14. import os
  15. import types
  16. import copy
  17. import __builtin__
  18. from utils import parse_key_val
  19. from core import *
  20. def vector_db(map, **args):
  21. """Return the database connection details for a vector map
  22. (interface to `v.db.connect -g`). Example:
  23. >>> vector_db('geology') # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  24. {1: {'layer': 1, ... 'table': 'geology'}}
  25. :param str map: vector map
  26. :param args: other v.db.connect's arguments
  27. :return: dictionary
  28. """
  29. s = read_command('v.db.connect', quiet=True, flags='g', map=map, sep=';',
  30. **args)
  31. result = {}
  32. for l in s.splitlines():
  33. f = l.split(';')
  34. if len(f) != 5:
  35. continue
  36. if '/' in f[0]:
  37. f1 = f[0].split('/')
  38. layer = f1[0]
  39. name = f1[1]
  40. else:
  41. layer = f[0]
  42. name = ''
  43. result[int(layer)] = {
  44. 'layer' : int(layer),
  45. 'name' : name,
  46. 'table' : f[1],
  47. 'key' : f[2],
  48. 'database' : f[3],
  49. 'driver' : f[4] }
  50. return result
  51. def vector_layer_db(map, layer):
  52. """Return the database connection details for a vector map layer.
  53. If db connection for given layer is not defined, fatal() is called.
  54. :param str map: map name
  55. :param layer: layer number
  56. :return: parsed output
  57. """
  58. try:
  59. f = vector_db(map)[int(layer)]
  60. except KeyError:
  61. fatal(_("Database connection not defined for layer %s") % layer)
  62. return f
  63. # run "v.info -c ..." and parse output
  64. def vector_columns(map, layer=None, getDict=True, **args):
  65. """Return a dictionary (or a list) of the columns for the
  66. database table connected to a vector map (interface to `v.info -c`).
  67. >>> vector_columns('geology', getDict=True) # doctest: +NORMALIZE_WHITESPACE
  68. {'PERIMETER': {'index': 2, 'type': 'DOUBLE PRECISION'}, 'GEOL250_':
  69. {'index': 3, 'type': 'INTEGER'}, 'SHAPE_area': {'index': 6, 'type':
  70. 'DOUBLE PRECISION'}, 'onemap_pro': {'index': 1, 'type': 'DOUBLE
  71. PRECISION'}, 'SHAPE_len': {'index': 7, 'type': 'DOUBLE PRECISION'},
  72. 'cat': {'index': 0, 'type': 'INTEGER'}, 'GEOL250_ID': {'index': 4, 'type':
  73. 'INTEGER'}, 'GEO_NAME': {'index': 5, 'type': 'CHARACTER'}}
  74. >>> vector_columns('geology', getDict=False) # doctest: +NORMALIZE_WHITESPACE
  75. ['cat',
  76. 'onemap_pro',
  77. 'PERIMETER',
  78. 'GEOL250_',
  79. 'GEOL250_ID',
  80. 'GEO_NAME',
  81. 'SHAPE_area',
  82. 'SHAPE_len']
  83. :param str map: map name
  84. :param layer: layer number or name (None for all layers)
  85. :param bool getDict: True to return dictionary of columns otherwise list
  86. of column names is returned
  87. :param args: (v.info's arguments)
  88. :return: dictionary/list of columns
  89. """
  90. s = read_command('v.info', flags='c', map=map, layer=layer, quiet=True,
  91. **args)
  92. if getDict:
  93. result = dict()
  94. else:
  95. result = list()
  96. i = 0
  97. for line in s.splitlines():
  98. ctype, cname = line.split('|')
  99. if getDict:
  100. result[cname] = {'type': ctype, 'index': i}
  101. else:
  102. result.append(cname)
  103. i += 1
  104. return result
  105. def vector_history(map):
  106. """Set the command history for a vector map to the command used to
  107. invoke the script (interface to `v.support`).
  108. :param str map: mapname
  109. :return: v.support output
  110. """
  111. run_command('v.support', map=map, cmdhist=os.environ['CMDLINE'])
  112. def vector_info_topo(map):
  113. """Return information about a vector map (interface to `v.info -t`).
  114. Example:
  115. >>> vector_info_topo('geology') # doctest: +NORMALIZE_WHITESPACE
  116. {'lines': 0, 'centroids': 1832, 'boundaries': 3649, 'points': 0,
  117. 'primitives': 5481, 'islands': 907, 'nodes': 2724, 'map3d': False,
  118. 'areas': 1832}
  119. :param str map: map name
  120. :return: parsed output
  121. """
  122. s = read_command('v.info', flags='t', map=map)
  123. ret = parse_key_val(s, val_type=int)
  124. if 'map3d' in ret:
  125. ret['map3d'] = bool(ret['map3d'])
  126. return ret
  127. def vector_info(map):
  128. """Return information about a vector map (interface to
  129. `v.info`). Example:
  130. >>> vector_info('geology') # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  131. {'comment': '', 'projection': 'Lambert Conformal Conic' ... 'south': 10875.8272320917}
  132. :param str map: map name
  133. :return: parsed vector info
  134. """
  135. s = read_command('v.info', flags='get', map=map)
  136. kv = parse_key_val(s)
  137. for k in ['north', 'south', 'east', 'west', 'top', 'bottom']:
  138. kv[k] = float(kv[k])
  139. for k in ['level', 'num_dblinks']:
  140. kv[k] = int(kv[k])
  141. for k in ['nodes', 'points', 'lines', 'boundaries', 'centroids', 'areas',
  142. 'islands', 'primitives']:
  143. kv[k] = int(kv[k])
  144. if 'map3d' in kv:
  145. kv['map3d'] = bool(int(kv['map3d']))
  146. if kv['map3d']:
  147. for k in ['faces', 'kernels', 'volumes', 'holes']:
  148. kv[k] = int(kv[k])
  149. return kv
  150. def vector_db_select(map, layer=1, **kwargs):
  151. """Get attribute data of selected vector map layer.
  152. Function returns list of columns and dictionary of values ordered by
  153. key column value. Example:
  154. >>> print vector_db_select('geology')['columns']
  155. ['cat', 'onemap_pro', 'PERIMETER', 'GEOL250_', 'GEOL250_ID', 'GEO_NAME', 'SHAPE_area', 'SHAPE_len']
  156. >>> print vector_db_select('geology')['values'][3]
  157. ['3', '579286.875', '3335.55835', '4', '3', 'Zml', '579286.829631', '3335.557182']
  158. >>> print vector_db_select('geology', columns = 'GEO_NAME')['values'][3]
  159. ['Zml']
  160. :param str map: map name
  161. :param str layer: layer number
  162. :param kwargs: v.db.select options
  163. :return: dictionary ('columns' and 'values')
  164. """
  165. try:
  166. key = vector_db(map=map)[layer]['key']
  167. except KeyError:
  168. error(_('Missing layer %(layer)d in vector map <%(map)s>') % \
  169. {'layer': layer, 'map': map})
  170. return {'columns': [], 'values': {}}
  171. include_key = True
  172. if 'columns' in kwargs:
  173. if key not in kwargs['columns'].split(','):
  174. # add key column if missing
  175. include_key = False
  176. debug("Adding key column to the output")
  177. kwargs['columns'] += ',' + key
  178. ret = read_command('v.db.select', map=map, layer=layer, **kwargs)
  179. if not ret:
  180. error(_('vector_db_select() failed'))
  181. return {'columns': [], 'values': {}}
  182. columns = []
  183. values = {}
  184. for line in ret.splitlines():
  185. if not columns:
  186. columns = line.split('|')
  187. key_index = columns.index(key)
  188. # discard key column
  189. if not include_key:
  190. columns = columns[:-1]
  191. continue
  192. value = line.split('|')
  193. key_value = int(value[key_index])
  194. if not include_key:
  195. # discard key column
  196. values[key_value] = value[:-1]
  197. else:
  198. values[key_value] = value
  199. return {'columns': columns, 'values': values}
  200. def vector_what(map, coord, distance=0.0, ttype=None):
  201. """Query vector map at given locations
  202. To query one vector map at one location
  203. ::
  204. print grass.vector_what(map='archsites', coord=(595743, 4925281),
  205. distance=250)
  206. [{'Category': 8, 'Map': 'archsites', 'Layer': 1, 'Key_column': 'cat',
  207. 'Database': '/home/martin/grassdata/spearfish60/PERMANENT/dbf/',
  208. 'Mapset': 'PERMANENT', 'Driver': 'dbf',
  209. 'Attributes': {'str1': 'No_Name', 'cat': '8'},
  210. 'Table': 'archsites', 'Type': 'Point', 'Id': 8}]
  211. To query one vector map with multiple layers (no additional parameters
  212. required)
  213. ::
  214. for q in grass.vector_what(map='some_map', distance=100.0,
  215. coord=(596532.357143,4920486.21429)):
  216. print q['Map'], q['Layer'], q['Attributes']
  217. new_bug_sites 1 {'str1': 'Beetle_site', 'GRASSRGB': '', 'cat': '80'}
  218. new_bug_sites 2 {'cat': '80'}
  219. To query more vector maps at one location
  220. ::
  221. for q in grass.vector_what(map=('archsites', 'roads'),
  222. coord=(595743, 4925281), distance=250):
  223. print q['Map'], q['Attributes']
  224. archsites {'str1': 'No_Name', 'cat': '8'}
  225. roads {'label': 'interstate', 'cat': '1'}
  226. To query one vector map at more locations
  227. ::
  228. for q in grass.vector_what(map='archsites', distance=250,
  229. coord=[(595743, 4925281), (597950, 4918898)]):
  230. print q['Map'], q['Attributes']
  231. archsites {'str1': 'No_Name', 'cat': '8'}
  232. archsites {'str1': 'Bob_Miller', 'cat': '22'}
  233. :param map: vector map(s) to query given as string or list/tuple
  234. :param coord: coordinates of query given as tuple (easting, northing) or
  235. list of tuples
  236. :param distance: query threshold distance (in map units)
  237. :param ttype: list of topology types (default of v.what are point, line,
  238. area, face)
  239. :return: parsed list
  240. """
  241. if "LC_ALL" in os.environ:
  242. locale = os.environ["LC_ALL"]
  243. os.environ["LC_ALL"] = "C"
  244. if type(map) in (types.StringType, types.UnicodeType):
  245. map_list = [map]
  246. else:
  247. map_list = map
  248. layer_list = ['-1'] * len(map_list)
  249. coord_list = list()
  250. if type(coord) is types.TupleType:
  251. coord_list.append('%f,%f' % (coord[0], coord[1]))
  252. else:
  253. for e, n in coord:
  254. coord_list.append('%f,%f' % (e, n))
  255. cmdParams = dict(quiet = True,
  256. flags = 'ag',
  257. map = ','.join(map_list),
  258. layer = ','.join(layer_list),
  259. coordinates = ','.join(coord_list),
  260. distance = float(distance))
  261. if ttype:
  262. cmdParams['type'] = ','.join(ttype)
  263. ret = read_command('v.what',
  264. **cmdParams)
  265. if "LC_ALL" in os.environ:
  266. os.environ["LC_ALL"] = locale
  267. data = list()
  268. if not ret:
  269. return data
  270. # parse `v.what -g` output is a nightmare
  271. # TODO: change `v.what -g` format or add parsable format (e.g. XML)
  272. dict_attrb = None
  273. dict_map = None
  274. dict_layer = None
  275. attr_pseudo_key = 'Attributes'
  276. for item in ret.splitlines():
  277. try:
  278. key, value = __builtin__.map(lambda x: x.strip(), item.split('=', 1))
  279. except ValueError:
  280. continue
  281. if key in ('East', 'North'):
  282. continue
  283. if key == 'Map':
  284. # attach the last one from the previous map
  285. if dict_map is not None:
  286. dict_main = copy.copy(dict_map)
  287. if dict_layer is not None:
  288. dict_main.update(dict_layer)
  289. data.append(dict_main)
  290. dict_map = {key : value}
  291. dict_layer = None
  292. dict_attrb = None
  293. elif key == 'Layer':
  294. if not dict_attrb:
  295. # attach the last the previous Layer
  296. if dict_layer is not None:
  297. dict_main = copy.copy(dict_map)
  298. dict_main.update(dict_layer)
  299. data.append(dict_main)
  300. dict_layer = {key: int(value)}
  301. dict_attrb = None
  302. else:
  303. dict_attrb[key] = value
  304. elif key == 'Key_column':
  305. dict_layer[key] = value
  306. dict_attrb = dict()
  307. dict_layer[attr_pseudo_key] = dict_attrb
  308. elif dict_attrb is not None:
  309. dict_attrb[key] = value
  310. elif dict_layer is not None:
  311. if key == 'Category':
  312. dict_layer[key] = int(value)
  313. else:
  314. dict_layer[key] = value
  315. else:
  316. dict_map[key] = value
  317. # TODO: there are some keys which has non-string values
  318. # examples: Sq_Meters, Hectares, Acres, Sq_Miles
  319. # attach the last one
  320. if dict_map is not None:
  321. dict_main = copy.copy(dict_map)
  322. if dict_layer:
  323. dict_main.update(dict_layer)
  324. data.append(dict_main)
  325. return data