vector.py 13 KB

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