m.proj.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: m.proj
  5. # AUTHOR: M. Hamish Bowman, Dept. Marine Science, Otago Univeristy,
  6. # New Zealand
  7. # Converted to Python by Glynn Clements
  8. # PURPOSE: cs2cs reprojection frontend for a list of coordinates.
  9. # Replacement for m.proj2 from GRASS 5
  10. # COPYRIGHT: (c) 2006-2014 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. #############################################################################
  16. # notes:
  17. # - cs2cs expects "x y" data so be sure to send it "lon lat" not "lat lon"
  18. # - if you send cs2cs a third data column, beware it might be treated as "z"
  19. # todo:
  20. # - `cut` away x,y columns into a temp file, feed to cs2cs, then `paste`
  21. # back to input file. see method in v.in.garmin.sh. that way additional
  22. # numeric and string columns would survive the trip, and 3rd column would
  23. # not be modified as z.
  24. #%module
  25. #% description: Converts coordinates from one projection to another (cs2cs frontend).
  26. #% keywords: miscellaneous
  27. #% keywords: projection
  28. #%end
  29. #%option G_OPT_M_COORDS
  30. #% description: Input coordinates to reproject
  31. #% guisection: Input coordinates
  32. #%end
  33. #%option G_OPT_F_INPUT
  34. #% label: Name of input coordinate file
  35. #% description: '-' to read from standard input
  36. #% required: no
  37. #% guisection: Input coordinates
  38. #%end
  39. #%option G_OPT_F_OUTPUT
  40. #% description: Name for output coordinate file (omit to send to stdout)
  41. #% required : no
  42. #% guisection: Output
  43. #%end
  44. #%option G_OPT_F_SEP
  45. #% label: Field separator (format: input[,output])
  46. #% required : no
  47. #% guisection: Input coordinates
  48. #%end
  49. #%option
  50. #% key: proj_in
  51. #% type: string
  52. #% description: Input projection parameters (PROJ.4 style)
  53. #% required : no
  54. #% guisection: Projections
  55. #%end
  56. #%option
  57. #% key: proj_out
  58. #% type: string
  59. #% description: Output projection parameters (PROJ.4 style)
  60. #% required : no
  61. #% guisection: Projections
  62. #%end
  63. #%flag
  64. #% key: i
  65. #% description: Use LL WGS84 as input and current location as output projection
  66. #% guisection: Projections
  67. #%end
  68. #%flag
  69. #% key: o
  70. #% description: Use current location as input and LL WGS84 as output projection
  71. #% guisection: Projections
  72. #%end
  73. #%flag
  74. #% key: d
  75. #% description: Output long/lat in decimal degrees, or other projections with many decimal places
  76. #% guisection: Output
  77. #%end
  78. #%flag
  79. #% key: e
  80. #% description: Include input coordinates in output file
  81. #% guisection: Output
  82. #%end
  83. #%flag
  84. #% key: c
  85. #% description: Include column names in output file
  86. #% guisection: Output
  87. #%end
  88. import sys
  89. import os
  90. import threading
  91. from grass.script import core as grass
  92. class TrThread(threading.Thread):
  93. def __init__(self, ifs, inf, outf):
  94. threading.Thread.__init__(self)
  95. self.ifs = ifs
  96. self.inf = inf
  97. self.outf = outf
  98. def run(self):
  99. while True:
  100. line = self.inf.readline()
  101. if not line:
  102. break
  103. line = line.replace(self.ifs, ' ')
  104. self.outf.write(line)
  105. self.outf.flush()
  106. self.outf.close()
  107. def main():
  108. coords = options['coordinates']
  109. input = options['input']
  110. output = options['output']
  111. fs = options['separator']
  112. proj_in = options['proj_in']
  113. proj_out = options['proj_out']
  114. ll_in = flags['i']
  115. ll_out = flags['o']
  116. decimal = flags['d']
  117. copy_input = flags['e']
  118. include_header = flags['c']
  119. #### check for cs2cs
  120. if not grass.find_program('cs2cs'):
  121. grass.fatal(_("cs2cs program not found, install PROJ.4 first: http://proj.maptools.org"))
  122. #### check for overenthusiasm
  123. if proj_in and ll_in:
  124. grass.fatal(_("Choose only one input parameter method"))
  125. if proj_out and ll_out:
  126. grass.fatal(_("Choose only one output parameter method"))
  127. if ll_in and ll_out:
  128. grass.fatal(_("Choise only one auto-projection parameter method"))
  129. if output and not grass.overwrite() and os.path.exists(output):
  130. grass.fatal(_("Output file already exists"))
  131. if not coords and not input:
  132. grass.fatal(_("One of <coordinates> and <input> must be given"))
  133. if coords and input:
  134. grass.fatal(_("Options <coordinates> and <input> are mutually exclusive"))
  135. #### parse field separator
  136. # FIXME: input_x,y needs to split on multiple whitespace between them
  137. if fs == ',':
  138. ifs = ofs = ','
  139. else:
  140. try:
  141. ifs, ofs = fs.split(',')
  142. except ValueError:
  143. ifs = ofs = fs
  144. ifs = grass.separator(ifs)
  145. ofs = grass.separator(ofs)
  146. #### set up projection params
  147. s = grass.read_command("g.proj", flags='j')
  148. kv = grass.parse_key_val(s)
  149. if "XY location" in kv['+proj'] and (ll_in or ll_out):
  150. grass.fatal(_("Unable to project to or from a XY location"))
  151. in_proj = None
  152. if ll_in:
  153. in_proj = "+proj=longlat +datum=WGS84"
  154. grass.verbose("Assuming LL WGS84 as input, current projection as output ")
  155. if ll_out:
  156. in_proj = grass.read_command('g.proj', flags = 'jf')
  157. if proj_in:
  158. in_proj = proj_in
  159. if not in_proj:
  160. grass.verbose("Assuming current location as input")
  161. in_proj = grass.read_command('g.proj', flags = 'jf')
  162. in_proj = in_proj.strip()
  163. grass.verbose("Input parameters: '%s'" % in_proj)
  164. out_proj = None
  165. if ll_out:
  166. out_proj = "+proj=longlat +datum=WGS84"
  167. grass.verbose("Assuming current projection as input, LL WGS84 as output ")
  168. if ll_in:
  169. out_proj = grass.read_command('g.proj', flags = 'jf')
  170. if proj_out:
  171. out_proj = proj_out
  172. if not out_proj:
  173. grass.fatal(_("Missing output projection parameters "))
  174. out_proj = out_proj.strip()
  175. grass.verbose("Output parameters: '%s'" % out_proj)
  176. #### set up input file
  177. if coords:
  178. x, y = coords.split(',')
  179. tmpfile = grass.tempfile()
  180. fd = open(tmpfile, "w")
  181. fd.write("%s%s%s\n" % (x, ifs, y))
  182. fd.close()
  183. inf = file(tmpfile)
  184. else:
  185. if input == '-':
  186. infile = None
  187. inf = sys.stdin
  188. else:
  189. infile = input
  190. if not os.path.exists(infile):
  191. grass.fatal(_("Unable to read input data"))
  192. inf = file(infile)
  193. grass.debug("input file=[%s]" % infile)
  194. #### set up output file
  195. if not output:
  196. outfile = None
  197. outf = sys.stdout
  198. else:
  199. outfile = output
  200. outf = open(outfile, 'w')
  201. grass.debug("output file=[%s]" % outfile)
  202. #### set up output style
  203. if not decimal:
  204. outfmt = ["-w5"]
  205. else:
  206. outfmt = ["-f", "%.8f"]
  207. if not copy_input:
  208. copyinp = []
  209. else:
  210. copyinp = ["-E"]
  211. #### do the conversion
  212. # Convert cs2cs DMS format to GRASS DMS format:
  213. # cs2cs | sed -e 's/d/:/g' -e "s/'/:/g" -e 's/"//g'
  214. cmd = ['cs2cs'] + copyinp + outfmt + in_proj.split() + ['+to'] + out_proj.split()
  215. p = grass.Popen(cmd, stdin = grass.PIPE, stdout = grass.PIPE)
  216. tr = TrThread(ifs, inf, p.stdin)
  217. tr.start()
  218. if not copy_input:
  219. if include_header:
  220. outf.write("x%sy%sz\n" % (ofs, ofs))
  221. for line in p.stdout:
  222. try:
  223. xy, z = line.split(' ', 1)
  224. x, y = xy.split('\t')
  225. except ValueError:
  226. grass.fatal(line)
  227. outf.write('%s%s%s%s%s\n' % \
  228. (x.strip(), ofs, y.strip(), ofs, z.strip()))
  229. else:
  230. if include_header:
  231. outf.write("input_x%sinput_y%sx%sy%sz\n" % (ofs, ofs, ofs, ofs))
  232. for line in p.stdout:
  233. inXYZ, x, rest = line.split('\t')
  234. inX, inY = inXYZ.split(' ')[:2]
  235. y, z = rest.split(' ', 1)
  236. outf.write('%s%s%s%s%s%s%s%s%s\n' % \
  237. (inX.strip(), ofs, inY.strip(), ofs, x.strip(), \
  238. ofs, y.strip(), ofs, z.strip()))
  239. p.wait()
  240. if p.returncode != 0:
  241. grass.warning(_("Projection transform probably failed, please investigate"))
  242. if __name__ == "__main__":
  243. options, flags = grass.parser()
  244. main()