vector.py 14 KB

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