m.proj.py 8.5 KB

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