vector.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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. from __future__ import absolute_import
  15. import os
  16. import types
  17. try:
  18. import __builtin__
  19. bytes = str
  20. except ImportError:
  21. # python3
  22. import builtins as __builtin__
  23. unicode = str
  24. from .utils import parse_key_val
  25. from .core import *
  26. from grass.exceptions import CalledModuleError
  27. def vector_db(map, env=None, **kwargs):
  28. """Return the database connection details for a vector map
  29. (interface to `v.db.connect -g`). Example:
  30. >>> vector_db('geology') # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  31. {1: {'layer': 1, ... 'table': 'geology'}}
  32. :param str map: vector map
  33. :param kwargs: other v.db.connect's arguments
  34. :param env: environment
  35. :return: dictionary
  36. """
  37. s = read_command('v.db.connect', quiet=True, flags='g', map=map, sep=';',
  38. env=env, **kwargs)
  39. result = {}
  40. for l in s.splitlines():
  41. f = l.split(';')
  42. if len(f) != 5:
  43. continue
  44. if '/' in f[0]:
  45. f1 = f[0].split('/')
  46. layer = f1[0]
  47. name = f1[1]
  48. else:
  49. layer = f[0]
  50. name = ''
  51. result[int(layer)] = {
  52. 'layer': int(layer),
  53. 'name': name,
  54. 'table': f[1],
  55. 'key': f[2],
  56. 'database': f[3],
  57. 'driver': f[4] }
  58. return result
  59. def vector_layer_db(map, layer, env=None):
  60. """Return the database connection details for a vector map layer.
  61. If db connection for given layer is not defined, fatal() is called.
  62. :param str map: map name
  63. :param layer: layer number
  64. :param env: environment
  65. :return: parsed output
  66. """
  67. try:
  68. f = vector_db(map, env=env)[int(layer)]
  69. except KeyError:
  70. fatal(_("Database connection not defined for layer %s") % layer)
  71. return f
  72. # run "v.info -c ..." and parse output
  73. def vector_columns(map, layer=None, getDict=True, env=None, **kwargs):
  74. """Return a dictionary (or a list) of the columns for the
  75. database table connected to a vector map (interface to `v.info -c`).
  76. >>> vector_columns('geology', getDict=True) # doctest: +NORMALIZE_WHITESPACE
  77. {'PERIMETER': {'index': 2, 'type': 'DOUBLE PRECISION'}, 'GEOL250_':
  78. {'index': 3, 'type': 'INTEGER'}, 'SHAPE_area': {'index': 6, 'type':
  79. 'DOUBLE PRECISION'}, 'onemap_pro': {'index': 1, 'type': 'DOUBLE
  80. PRECISION'}, 'SHAPE_len': {'index': 7, 'type': 'DOUBLE PRECISION'},
  81. 'cat': {'index': 0, 'type': 'INTEGER'}, 'GEOL250_ID': {'index': 4, 'type':
  82. 'INTEGER'}, 'GEO_NAME': {'index': 5, 'type': 'CHARACTER'}}
  83. >>> vector_columns('geology', getDict=False) # doctest: +NORMALIZE_WHITESPACE
  84. ['cat',
  85. 'onemap_pro',
  86. 'PERIMETER',
  87. 'GEOL250_',
  88. 'GEOL250_ID',
  89. 'GEO_NAME',
  90. 'SHAPE_area',
  91. 'SHAPE_len']
  92. :param str map: map name
  93. :param layer: layer number or name (None for all layers)
  94. :param bool getDict: True to return dictionary of columns otherwise list
  95. of column names is returned
  96. :param kwargs: (v.info's arguments)
  97. :param env: environment
  98. :return: dictionary/list of columns
  99. """
  100. s = read_command('v.info', flags='c', map=map, layer=layer, quiet=True,
  101. env=env, **kwargs)
  102. if getDict:
  103. result = dict()
  104. else:
  105. result = list()
  106. i = 0
  107. for line in s.splitlines():
  108. ctype, cname = line.split('|')
  109. if getDict:
  110. result[cname] = {'type': ctype, 'index': i}
  111. else:
  112. result.append(cname)
  113. i += 1
  114. return result
  115. def vector_history(map, replace=False, env=None):
  116. """Set the command history for a vector map to the command used to
  117. invoke the script (interface to `v.support`).
  118. :param str map: mapname
  119. :param bool replace: Replace command line instead of appending it
  120. :param env: environment
  121. :return: v.support output
  122. """
  123. run_command('v.support', map=map, cmdhist=os.environ['CMDLINE'],
  124. flags='h' if replace else None, env=env)
  125. def vector_info_topo(map, layer=1, env=None):
  126. """Return information about a vector map (interface to `v.info -t`).
  127. Example:
  128. >>> vector_info_topo('geology') # doctest: +NORMALIZE_WHITESPACE
  129. {'lines': 0, 'centroids': 1832, 'boundaries': 3649, 'points': 0,
  130. 'primitives': 5481, 'islands': 907, 'nodes': 2724, 'map3d': False,
  131. 'areas': 1832}
  132. :param str map: map name
  133. :param int layer: layer number
  134. :param env: environment
  135. :return: parsed output
  136. """
  137. s = read_command('v.info', flags='t', layer=layer, map=map,
  138. env=env)
  139. ret = parse_key_val(s, val_type=int)
  140. if 'map3d' in ret:
  141. ret['map3d'] = bool(ret['map3d'])
  142. return ret
  143. def vector_info(map, layer=1, env=None):
  144. """Return information about a vector map (interface to
  145. `v.info`). Example:
  146. >>> vector_info('geology') # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
  147. {'comment': '', 'projection': 'Lambert Conformal Conic' ... 'south': 10875.8272320917}
  148. :param str map: map name
  149. :param int layer: layer number
  150. :param env: environment
  151. :return: parsed vector info
  152. """
  153. s = read_command('v.info', flags='get', layer=layer, map=map,
  154. env=env)
  155. kv = parse_key_val(s)
  156. for k in ['north', 'south', 'east', 'west', 'top', 'bottom']:
  157. kv[k] = float(kv[k])
  158. for k in ['level', 'num_dblinks']:
  159. kv[k] = int(kv[k])
  160. for k in ['nodes', 'points', 'lines', 'boundaries', 'centroids', 'areas',
  161. 'islands', 'primitives']:
  162. kv[k] = int(kv[k])
  163. if 'map3d' in kv:
  164. kv['map3d'] = bool(int(kv['map3d']))
  165. if kv['map3d']:
  166. for k in ['faces', 'kernels', 'volumes', 'holes']:
  167. kv[k] = int(kv[k])
  168. return kv
  169. def vector_db_select(map, layer=1, env=None, **kwargs):
  170. """Get attribute data of selected vector map layer.
  171. Function returns list of columns and dictionary of values ordered by
  172. key column value. Example:
  173. >>> print vector_db_select('geology')['columns']
  174. ['cat', 'onemap_pro', 'PERIMETER', 'GEOL250_', 'GEOL250_ID', 'GEO_NAME', 'SHAPE_area', 'SHAPE_len']
  175. >>> print vector_db_select('geology')['values'][3]
  176. ['3', '579286.875', '3335.55835', '4', '3', 'Zml', '579286.829631', '3335.557182']
  177. >>> print vector_db_select('geology', columns = 'GEO_NAME')['values'][3]
  178. ['Zml']
  179. :param str map: map name
  180. :param int layer: layer number
  181. :param kwargs: v.db.select options
  182. :param env: environment
  183. :return: dictionary ('columns' and 'values')
  184. """
  185. try:
  186. key = vector_db(map=map, env=env)[layer]['key']
  187. except KeyError:
  188. error(_('Missing layer %(layer)d in vector map <%(map)s>') %
  189. {'layer': layer, 'map': map})
  190. return {'columns': [], 'values': {}}
  191. include_key = True
  192. if 'columns' in kwargs:
  193. if key not in kwargs['columns'].split(','):
  194. # add key column if missing
  195. include_key = False
  196. debug("Adding key column to the output")
  197. kwargs['columns'] += ',' + key
  198. ret = read_command('v.db.select', map=map, layer=layer,
  199. env=env, **kwargs)
  200. if not ret:
  201. error(_('vector_db_select() failed'))
  202. return {'columns': [], 'values': {}}
  203. columns = []
  204. values = {}
  205. for line in ret.splitlines():
  206. if not columns:
  207. columns = line.split('|')
  208. key_index = columns.index(key)
  209. # discard key column
  210. if not include_key:
  211. columns = columns[:-1]
  212. continue
  213. value = line.split('|')
  214. key_value = int(value[key_index])
  215. if not include_key:
  216. # discard key column
  217. values[key_value] = value[:-1]
  218. else:
  219. values[key_value] = value
  220. return {'columns': columns, 'values': values}
  221. json = None
  222. orderedDict = None
  223. def vector_what(map, coord, distance=0.0, ttype=None,
  224. encoding=None, skip_attributes=False,
  225. layer=None, multiple=False, env=None):
  226. """Query vector map at given locations
  227. To query one vector map at one location
  228. ::
  229. print grass.vector_what(map='archsites', coord=(595743, 4925281),
  230. distance=250)
  231. [{'Category': 8, 'Map': 'archsites', 'Layer': 1, 'Key_column': 'cat',
  232. 'Database': '/home/martin/grassdata/spearfish60/PERMANENT/dbf/',
  233. 'Mapset': 'PERMANENT', 'Driver': 'dbf',
  234. 'Attributes': {'str1': 'No_Name', 'cat': '8'},
  235. 'Table': 'archsites', 'Type': 'Point', 'Id': 8}]
  236. To query one vector map with multiple layers (no additional parameters
  237. required)
  238. ::
  239. for q in grass.vector_what(map='some_map', distance=100.0,
  240. coord=(596532.357143,4920486.21429)):
  241. print q['Map'], q['Layer'], q['Attributes']
  242. new_bug_sites 1 {'str1': 'Beetle_site', 'GRASSRGB': '', 'cat': '80'}
  243. new_bug_sites 2 {'cat': '80'}
  244. To query more vector maps at one location
  245. ::
  246. for q in grass.vector_what(map=('archsites', 'roads'),
  247. coord=(595743, 4925281), distance=250):
  248. print q['Map'], q['Attributes']
  249. archsites {'str1': 'No_Name', 'cat': '8'}
  250. roads {'label': 'interstate', 'cat': '1'}
  251. To query one vector map at more locations
  252. ::
  253. for q in grass.vector_what(map='archsites', distance=250,
  254. coord=[(595743, 4925281), (597950, 4918898)]):
  255. print q['Map'], q['Attributes']
  256. archsites {'str1': 'No_Name', 'cat': '8'}
  257. archsites {'str1': 'Bob_Miller', 'cat': '22'}
  258. :param map: vector map(s) to query given as string or list/tuple
  259. :param coord: coordinates of query given as tuple (easting, northing) or
  260. list of tuples
  261. :param distance: query threshold distance (in map units)
  262. :param ttype: list of topology types (default of v.what are point, line,
  263. area, face)
  264. :param encoding: attributes encoding
  265. :param skip_attributes: True to skip quering attributes
  266. :param layer: layer number or list of layers (one for each vector),
  267. if None, all layers (-1) are used
  268. :param multiple: find multiple features within threshold distance
  269. :param env: environment
  270. :return: parsed list
  271. """
  272. if not env:
  273. env = os.environ.copy()
  274. if "LC_ALL" in env:
  275. env["LC_ALL"] = "C"
  276. if isinstance(map, (bytes, unicode)):
  277. map_list = [map]
  278. else:
  279. map_list = map
  280. if layer:
  281. if isinstance(layer, (tuple, list)):
  282. layer_list = [str(l) for l in layer]
  283. else:
  284. layer_list = [str(layer)]
  285. if len(layer_list) != len(map_list):
  286. raise ScriptError(_("Number of given vector maps ({m}) "
  287. "differs from number of layers ({l})").format(m=len(map_list),
  288. l=len(layer_list)))
  289. else:
  290. layer_list = ['-1'] * len(map_list)
  291. coord_list = list()
  292. if isinstance(coord, tuple):
  293. coord_list.append('%f,%f' % (coord[0], coord[1]))
  294. else:
  295. for e, n in coord:
  296. coord_list.append('%f,%f' % (e, n))
  297. flags = 'j'
  298. if not skip_attributes:
  299. flags += 'a'
  300. if multiple:
  301. flags += 'm'
  302. cmdParams = dict(quiet=True,
  303. flags=flags,
  304. map=','.join(map_list),
  305. layer=','.join(layer_list),
  306. coordinates=','.join(coord_list),
  307. distance=float(distance))
  308. if ttype:
  309. cmdParams['type'] = ','.join(ttype)
  310. try:
  311. ret = read_command('v.what', env=env,
  312. **cmdParams).strip()
  313. except CalledModuleError as e:
  314. raise ScriptError(e.msg)
  315. data = list()
  316. if not ret:
  317. return data
  318. # lazy import
  319. global json
  320. global orderedDict
  321. if json is None:
  322. import json
  323. if orderedDict is None:
  324. try:
  325. from collections import OrderedDict
  326. orderedDict = OrderedDict
  327. except ImportError:
  328. orderedDict = dict
  329. kwargs = {}
  330. if encoding:
  331. kwargs['encoding'] = encoding
  332. if sys.version_info[0:2] > (2, 6):
  333. kwargs['object_pairs_hook'] = orderedDict
  334. try:
  335. result = json.loads(ret, **kwargs)
  336. except ValueError:
  337. raise ScriptError(_("v.what output is not valid JSON format:\n {ret}").format(ret=ret))
  338. if multiple:
  339. for vmap in result['Maps']:
  340. features = vmap.pop('Features', None)
  341. if features:
  342. for feature in features:
  343. cats = feature.pop('Categories', None)
  344. if cats:
  345. for cat in cats:
  346. tmp = feature.copy()
  347. tmp.update(cat)
  348. tmp2 = vmap.copy()
  349. tmp2.update(tmp)
  350. data.append(tmp2)
  351. else:
  352. for vmap in result['Maps']:
  353. cats = vmap.pop('Categories', None)
  354. if cats:
  355. for cat in cats:
  356. tmp = vmap.copy()
  357. tmp.update(cat)
  358. data.append(tmp)
  359. else:
  360. data.append(vmap)
  361. return data