v.out.gps.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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 G_OPT_V_INPUT
  59. #%end
  60. #%option G_OPT_V_TYPE
  61. #% options: point,centroid,line,boundary
  62. #% answer: point,centroid,line,boundary
  63. #%end
  64. #%option G_OPT_F_OUTPUT
  65. #% description: Name for output file or GPS device
  66. #%end
  67. #%option
  68. #% key: format
  69. #% type: string
  70. #% description: GPSBabel supported output format
  71. #% answer: gpx
  72. #%end
  73. #%option G_OPT_V_FIELD
  74. #% required: no
  75. #% guisection: Subset
  76. #%end
  77. #%option G_OPT_DB_WHERE
  78. #% guisection: Subset
  79. #%end
  80. import sys
  81. import os
  82. import atexit
  83. import string
  84. import re
  85. import grass.script as grass
  86. def cleanup():
  87. grass.verbose("Cleaning up ...")
  88. if tmp:
  89. grass.try_remove(tmp)
  90. if tmp_proj:
  91. grass.try_remove(tmp_proj)
  92. if tmp_gpx:
  93. grass.try_remove(tmp_gpx)
  94. # only try to remove map if it exists to avoid ugly warnings
  95. if tmp_vogb:
  96. if grass.find_file(tmp_vogb, element = 'vector')['name']:
  97. grass.run_command('g.remove', flags = 'f', type = 'vect',
  98. pattern = tmp_vogb, quiet = True)
  99. if tmp_extr:
  100. if grass.find_file(tmp_extr, element = 'vector')['name']:
  101. grass.run_command('g.remove', flags = 'f', type = 'vect',
  102. pattern = tmp_vogb, quiet = True)
  103. tmp = None
  104. tmp_proj = None
  105. tmp_gpx = None
  106. tmp_extr = None
  107. tmp_vogb = None
  108. def main():
  109. global tmp, tmp_proj, tmp_gpx, tmp_extr, tmp_vogb
  110. format = options['format']
  111. input = options['input']
  112. layer = options['layer']
  113. output = options['output']
  114. type = options['type']
  115. where = options['where']
  116. wpt = flags['w']
  117. rte = flags['r']
  118. trk = flags['t']
  119. nflags = len(filter(None, [wpt, rte, trk]))
  120. if nflags > 1:
  121. grass.fatal(_("One feature at a time please."))
  122. if nflags < 1:
  123. grass.fatal(_("No features requested for export."))
  124. # set some reasonable defaults
  125. if not type:
  126. if wpt:
  127. type = 'point'
  128. else:
  129. type = 'line'
  130. #### check for gpsbabel
  131. ### FIXME: may need --help or similar?
  132. if not grass.find_program("gpsbabel"):
  133. grass.fatal(_("The gpsbabel program was not found, please install it first.\n") +
  134. "http://gpsbabel.sourceforge.net")
  135. #### check for cs2cs
  136. if not grass.find_program("cs2cs"):
  137. grass.fatal(_("The cs2cs program was not found, please install it first.\n") +
  138. "http://proj.osgeo.org")
  139. # check if we will overwrite data
  140. if os.path.exists(output) and not grass.overwrite():
  141. grass.fatal(_("Output file already exists."))
  142. #### set temporary files
  143. tmp = grass.tempfile()
  144. # SQL extract if needed
  145. if where:
  146. grass.verbose("Extracting data ...")
  147. tmp_extr = "tmp_vogb_extr_%d" % os.getpid()
  148. ret = grass.run_command('v.extract', input = "$GIS_OPT_INPUT",
  149. output = tmp_extr, type = type, layer = layer,
  150. where = where, quiet = True)
  151. if ret != 0:
  152. grass.fatal(_("Error executing SQL query"))
  153. kv = grass.vector_info_topo(tmp_extr)
  154. if kv['primitives'] == 0:
  155. grass.fatal(_("SQL query returned an empty map (no %s features?)") % type)
  156. inmap = tmp_extr
  157. else:
  158. # g.copy "$GIS_OPT_INPUT,tmp_vogb_extr_$$" # to get a copy of DB into local mapset
  159. # INMAP="tmp_vogb_extr_$$"
  160. inmap = input
  161. #### set up projection info
  162. # TODO: check if we are already in ll/WGS84. If so skip m.proj step.
  163. # TODO: multi layer will probably fail badly due to sed 's/^ 1 /'
  164. # output as old GRASS 4 vector ascii and fight with dig_ascii/?
  165. # Change to s/^ \([0-9] .*\) /# \1/' ??? mmph.
  166. # reproject to lat/lon WGS84
  167. grass.verbose("Reprojecting data ...")
  168. re1 = re.compile(r'^\([PLBCFKA]\)')
  169. re2 = re.compile(r'^ 1 ')
  170. re3 = re.compile(r'\t\([-\.0-9]*\) .*')
  171. re4 = re.compile(r'^\([-\.0-9]\)')
  172. re5 = re.compile(r'^#')
  173. tmp_proj = tmp + ".proj"
  174. tf = open(tmp_proj, 'w')
  175. p1 = grass.pipe_command('v.out.ascii', input = inmap, format = 'standard')
  176. p2 = grass.feed_command('m.proj', input = '-', flags = 'od', quiet = True, stdout = tf)
  177. tf.close()
  178. lineno = 0
  179. for line in p1.stdout:
  180. lineno += 1
  181. if lineno < 11:
  182. continue
  183. line = re1.sub(r'#\1', line)
  184. line = re2.sub(r'# 1 ', line)
  185. p2.stdin.write(line)
  186. p2.stdin.close()
  187. p1.wait()
  188. p2.wait()
  189. if p1.returncode != 0 or p2.returncode != 0:
  190. grass.fatal(_("Error reprojecting data"))
  191. tmp_vogb = "tmp_vogb_epsg4326_%d" % os.getpid()
  192. p3 = grass.feed_command('v.in.ascii', out = tmp_vogb, format = 'standard', flags = 'n', quiet = True)
  193. tf = open(tmp_proj, 'r')
  194. for line in tf:
  195. line = re3.sub(r' \1', line)
  196. line = re4.sub(r' \1', line)
  197. line = re5.sub('', line)
  198. p3.stdin.write(line)
  199. p3.stdin.close()
  200. tf.close()
  201. p3.wait()
  202. if p3.returncode != 0:
  203. grass.fatal(_("Error reprojecting data"))
  204. # don't v.db.connect directly as source table will be removed with
  205. # temporary map in that case. So we make a temp copy of it to work with.
  206. kv = vector_db(inmap)
  207. if layer in kv:
  208. db_params = kv[layer]
  209. db_table = db_params['table']
  210. db_key = db_params['key']
  211. db_database = db_params['database']
  212. db_driver = db_params['driver']
  213. ret = grass.run_command('db.copy',
  214. from_driver = db_driver,
  215. from_database = db_database,
  216. from_table = db_table,
  217. to_table = tmp_vogb)
  218. if ret != 0:
  219. grass.fatal(_("Error copying temporary DB"))
  220. ret = grass.run_command('v.db.connect', map = tmp_vogb, table = tmp_vogb, quiet = True)
  221. if ret != 0:
  222. grass.fatal(_("Error reconnecting temporary DB"))
  223. # export as GPX using v.out.ogr
  224. if trk:
  225. linetype = "FORCE_GPX_TRACK=YES"
  226. elif rte:
  227. linetype = "FORCE_GPX_TRACK=YES"
  228. else:
  229. linetype = None
  230. # BUG: cat is being reported as evelation and attribute output is skipped.
  231. # (v.out.ogr DB reading or ->OGR GPX driver bug<-
  232. # resolved: see new Create opts at http://www.gdal.org/ogr/drv_gpx.html)
  233. # v.out.ogr -> shapefile -> GPX works, but we try to avoid that as it's
  234. # lossy. Also that would allow ogr2ogr -a_srs $IN_PROJ -t_srs EPSG:4326
  235. # so skip m.proj pains.. if that is done ogr2ogr -s_srs MUST HAVE +wktext
  236. # with PROJ.4 terms or else the +nadgrids will be ignored! best to feed
  237. # it IN_PROJ="`g.proj -jf` +wktext" in that case.
  238. grass.verbose("Exporting data ...")
  239. tmp_gpx = tmp + ".gpx"
  240. ret = grass.run_command('v.out.ogr', input = tmp_vogb, dsn = tmp_gpx,
  241. type = type, format = 'GPX', lco = linetype,
  242. dsco = "GPX_USE_EXTENSIONS=YES", quiet = True)
  243. if ret != 0:
  244. grass.fatal(_("Error exporting data"))
  245. if format == 'gpx':
  246. # short circuit, we have what we came for.
  247. grass.try_remove(output)
  248. os.rename(tmp_gpx, output)
  249. grass.verbose("Fast exit.")
  250. sys.exit()
  251. # run gpsbabel
  252. if wpt:
  253. gtype = '-w'
  254. elif trk:
  255. gtype = '-t'
  256. elif rte:
  257. gtype = '-r'
  258. else:
  259. gtype = ''
  260. grass.verbose("Running GPSBabel ...")
  261. ret = grass.call(['gpsbabel',
  262. gtype,
  263. '-i', 'gpx',
  264. '-f', tmp + '.gpx',
  265. '-o', format,
  266. '-F', output])
  267. if ret != 0:
  268. grass.fatal(_("Error running GPSBabel"))
  269. grass.verbose("Done.")
  270. if __name__ == "__main__":
  271. options, flags = grass.parser()
  272. atexit.register(cleanup)
  273. main()