v.out.gps.py 8.4 KB

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