v.out.gps.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #!/usr/bin/env python
  2. #
  3. ############################################################################
  4. #
  5. # MODULE: v.out.gps
  6. #
  7. # PURPOSE: Exports a GRASS vector map to a GPS receiver
  8. # or data file using GpsBabel
  9. #
  10. # COPYRIGHT: (c) 2008-2009 Hamish Bowman, and the GRASS Development Team
  11. # This program is free software under the GNU General Public
  12. # License (>=v2). Read the file COPYING that comes with GRASS
  13. # for details.
  14. #
  15. # AUTHOR: Hamish Bowman, Dunedin, New Zealand
  16. # Converted to Python by Glynn Clements
  17. #
  18. #############################################################################
  19. #
  20. # REQUIREMENTS:
  21. # - GpsBabel from http://gpsbabel.sourceforge.net
  22. # - cs2cs from PROJ.4 (for m.proj) http://proj.osgeo.org
  23. #
  24. # - report supported GpsBabel formats:
  25. # gpsbabel -^2 | tr '\t' ';' | sort -t';' -k3
  26. #
  27. #############################################################################
  28. #
  29. # How to do it
  30. # http://www.gdal.org/ogr/drv_gpx.html
  31. # gpsbabel [options] -i INTYPE -f INFILE -o OUTTYPE -F OUTFILE
  32. #
  33. #############################################################################
  34. #%Module
  35. #% description: Exports a vector map to a GPS receiver or file format supported by GpsBabel.
  36. #% keywords: vector
  37. #% keywords: export
  38. #% keywords: GPS
  39. #%End
  40. #%flag
  41. #% key: w
  42. #% description: Export as waypoints
  43. #%end
  44. #%flag
  45. #% key: r
  46. #% description: Export as routes
  47. #%end
  48. #%flag
  49. #% key: t
  50. #% description: Export as tracks
  51. #%end
  52. ############ TODO:
  53. ##%flag
  54. ##% key: z
  55. ##% description: Export altitude from 3D vector's z-coordinate
  56. ##%end
  57. ############
  58. #%option
  59. #% key: input
  60. #% type: string
  61. #% description: Name of input vector map
  62. #% gisprompt: old,vector,vector
  63. #% required: yes
  64. #%end
  65. #%option
  66. #% key: type
  67. #% type: string
  68. #% description: Feature type(s)
  69. #% options: point,centroid,line,boundary
  70. #% multiple: yes
  71. #%end
  72. #%option
  73. #% key: output
  74. #% type: string
  75. #% description: Name for output file or GPS device
  76. #% gisprompt: new_file,file,output
  77. #% key_desc: name
  78. #% required: yes
  79. #%end
  80. #%option
  81. #% key: format
  82. #% type: string
  83. #% description: GpsBabel supported output format
  84. #% answer: gpx
  85. #%end
  86. #%option
  87. #% key: layer
  88. #% type: integer
  89. #% label: Layer number
  90. #% description: A single vector map can be connected to multiple database tables. This number determines which table to use.
  91. #% answer: 1
  92. #% required : no
  93. #% guisection: Subset
  94. #%end
  95. #%option
  96. #% key: where
  97. #% type: string
  98. #% label: WHERE conditions of SQL statement without 'where' keyword
  99. #% description: Example: income < 1000 and inhab >= 10000
  100. #% required : no
  101. #% guisection: Subset
  102. #%end
  103. import sys
  104. import os
  105. import atexit
  106. import string
  107. import re
  108. import grass.script as grass
  109. def cleanup():
  110. grass.verbose("Cleaning up ...")
  111. if tmp:
  112. grass.try_remove(tmp)
  113. if tmp_proj:
  114. grass.try_remove(tmp_proj)
  115. if tmp_gpx:
  116. grass.try_remove(tmp_gpx)
  117. # only try to remove map if it exists to avoid ugly warnings
  118. if tmp_vogb:
  119. if grass.find_file(tmp_vogb, element = 'vector')['name']:
  120. grass.run_command('g.remove', vect = tmp_vogb, quiet = True)
  121. if tmp_extr:
  122. if grass.find_file(tmp_extr, element = 'vector')['name']:
  123. grass.run_command('g.remove', vect = tmp_vogb, quiet = True)
  124. tmp = None
  125. tmp_proj = None
  126. tmp_gpx = None
  127. tmp_extr = None
  128. tmp_vogb = None
  129. def main():
  130. global tmp, tmp_proj, tmp_gpx, tmp_extr, tmp_vogb
  131. format = options['format']
  132. input = options['input']
  133. layer = options['layer']
  134. output = options['output']
  135. type = options['type']
  136. where = options['where']
  137. wpt = flags['w']
  138. rte = flags['r']
  139. trk = flags['t']
  140. nflags = len(filter(None, [wpt, rte, trk]))
  141. if nflags > 1:
  142. grass.fatal(_("One feature at a time please."))
  143. if nflags < 1:
  144. grass.fatal(_("No features requested for export."))
  145. # set some reasonable defaults
  146. if not type:
  147. if wpt:
  148. type = 'point'
  149. else:
  150. type = 'line'
  151. #### check for gpsbabel
  152. ### FIXME: may need --help or similar?
  153. if not grass.find_program("gpsbabel"):
  154. grass.fatal(_("The gpsbabel program was not found, please install it first.\n") +
  155. "http://gpsbabel.sourceforge.net")
  156. #### check for cs2cs
  157. if not grass.find_program("cs2cs"):
  158. grass.fatal(_("The cs2cs program was not found, please install it first.\n") +
  159. "http://proj.osgeo.org")
  160. # check if we will overwrite data
  161. if os.path.exists(output) and not grass.overwrite():
  162. grass.fatal(_("Output file already exists."))
  163. #### set temporary files
  164. tmp = grass.tempfile()
  165. # SQL extract if needed
  166. if where:
  167. grass.verbose("Extracting data ...")
  168. tmp_extr = "tmp_vogb_extr_%d" % os.getpid()
  169. ret = grass.run_command('v.extract', input = "$GIS_OPT_INPUT",
  170. output = tmp_extr, type = type, layer = layer,
  171. where = where, quiet = True)
  172. if ret != 0:
  173. grass.fatal(_("Error executing SQL query"))
  174. kv = grass.vector_info_topo(tmp_extr)
  175. if kv['primitives'] == 0:
  176. grass.fatal(_("SQL query returned an empty map (no %s features?)") % type)
  177. inmap = tmp_extr
  178. else:
  179. # g.copy "$GIS_OPT_INPUT,tmp_vogb_extr_$$" # to get a copy of DB into local mapset
  180. # INMAP="tmp_vogb_extr_$$"
  181. inmap = input
  182. #### set up projection info
  183. # TODO: check if we are already in ll/WGS84. If so skip m.proj step.
  184. # TODO: multi layer will probably fail badly due to sed 's/^ 1 /'
  185. # output as old GRASS 4 vector ascii and fight with dig_ascii/?
  186. # Change to s/^ \([0-9] .*\) /# \1/' ??? mmph.
  187. # reproject to lat/lon WGS84
  188. grass.verbose("Reprojecting data ...")
  189. re1 = re.compile(r'^\([PLBCFKA]\)')
  190. re2 = re.compile(r'^ 1 ')
  191. re3 = re.compile(r'\t\([-\.0-9]*\) .*')
  192. re4 = re.compile(r'^\([-\.0-9]\)')
  193. re5 = re.compile(r'^#')
  194. tmp_proj = tmp + ".proj"
  195. tf = open(tmp_proj, 'w')
  196. p1 = grass.pipe_command('v.out.ascii', input = inmap, format = 'standard')
  197. p2 = grass.feed_command('m.proj', input = '-', flags = 'od', quiet = True, stdout = tf)
  198. tf.close()
  199. lineno = 0
  200. for line in p1.stdout:
  201. lineno += 1
  202. if lineno < 11:
  203. continue
  204. line = re1.sub(r'#\1', line)
  205. line = re2.sub(r'# 1 ', line)
  206. p2.stdin.write(line)
  207. p2.stdin.close()
  208. p1.wait()
  209. p2.wait()
  210. if p1.returncode != 0 or p2.returncode != 0:
  211. grass.fatal(_("Error reprojecting data"))
  212. tmp_vogb = "tmp_vogb_epsg4326_%d" % os.getpid()
  213. p3 = grass.feed_command('v.in.ascii', out = tmp_vogb, format = 'standard', flags = 'n', quiet = True)
  214. tf = open(tmp_proj, 'r')
  215. for line in tf:
  216. line = re3.sub(r' \1', line)
  217. line = re4.sub(r' \1', line)
  218. line = re5.sub('', line)
  219. p3.stdin.write(line)
  220. p3.stdin.close()
  221. tf.close()
  222. p3.wait()
  223. if p3.returncode != 0:
  224. grass.fatal(_("Error reprojecting data"))
  225. # don't v.db.connect directly as source table will be removed with
  226. # temporary map in that case. So we make a temp copy of it to work with.
  227. kv = vector_db(inmap)
  228. if layer in kv:
  229. db_params = kv[layer]
  230. db_table = db_params['table']
  231. db_key = db_params['key']
  232. db_database = db_params['database']
  233. db_driver = db_params['driver']
  234. ret = grass.run_command('db.copy',
  235. from_driver = db_driver,
  236. from_database = db_database,
  237. from_table = db_table,
  238. to_table = tmp_vogb)
  239. if ret != 0:
  240. grass.fatal(_("Error copying temporary DB"))
  241. ret = grass.run_command('v.db.connect', map = tmp_vogb, table = tmp_vogb, quiet = True)
  242. if ret != 0:
  243. grass.fatal(_("Error reconnecting temporary DB"))
  244. # export as GPX using v.out.ogr
  245. if trk:
  246. linetype = "FORCE_GPX_TRACK=YES"
  247. elif rte:
  248. linetype = "FORCE_GPX_TRACK=YES"
  249. else:
  250. linetype = None
  251. # BUG: cat is being reported as evelation and attribute output is skipped.
  252. # (v.out.ogr DB reading or ->OGR GPX driver bug<-
  253. # resolved: see new Create opts at http://www.gdal.org/ogr/drv_gpx.html)
  254. # v.out.ogr -> shapefile -> GPX works, but we try to avoid that as it's
  255. # lossy. Also that would allow ogr2ogr -a_srs $IN_PROJ -t_srs EPSG:4326
  256. # so skip m.proj pains.. if that is done ogr2ogr -s_srs MUST HAVE +wktext
  257. # with PROJ.4 terms or else the +nadgrids will be ignored! best to feed
  258. # it IN_PROJ="`g.proj -jf` +wktext" in that case.
  259. grass.verbose("Exporting data ...")
  260. tmp_gpx = tmp + ".gpx"
  261. ret = grass.run_command('v.out.ogr', input = tmp_vogb, dsn = tmp_gpx,
  262. type = type, format = 'GPX', lco = linetype,
  263. dsco = "GPX_USE_EXTENSIONS=YES", quiet = True)
  264. if ret != 0:
  265. grass.fatal(_("Error exporting data"))
  266. if format == 'gpx':
  267. # short circuit, we have what we came for.
  268. grass.try_remove(output)
  269. os.rename(tmp_gpx, output)
  270. grass.verbose("Fast exit.")
  271. sys.exit()
  272. # run gpsbabel
  273. if wpt:
  274. gtype = '-w'
  275. elif trk:
  276. gtype = '-t'
  277. elif rte:
  278. gtype = '-r'
  279. else:
  280. gtype = ''
  281. grass.verbose("Running GpsBabel ...")
  282. ret = grass.call(['gpsbabel',
  283. gtype,
  284. '-i', 'gpx',
  285. '-f', tmp + '.gpx',
  286. '-o', format,
  287. '-F', output])
  288. if ret != 0:
  289. grass.fatal(_("Error running GpsBabel"))
  290. grass.verbose("Done.")
  291. if __name__ == "__main__":
  292. options, flags = grass.parser()
  293. atexit.register(cleanup)
  294. main()