m.proj.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. #!/usr/bin/env python3
  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-2019 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. # %rules
  88. # % required: coordinates, input
  89. # % exclusive: coordinates, input
  90. # % exclusive: proj_in, -i
  91. # % exclusive: proj_out, -o
  92. # % exclusive: -i, -o
  93. # %end
  94. import sys
  95. import os
  96. import threading
  97. from grass.script.utils import separator, parse_key_val, encode, decode
  98. from grass.script import core as gcore
  99. class TrThread(threading.Thread):
  100. def __init__(self, ifs, inf, outf):
  101. threading.Thread.__init__(self)
  102. self.ifs = ifs
  103. self.inf = inf
  104. self.outf = outf
  105. def run(self):
  106. while True:
  107. line = self.inf.readline()
  108. if not line:
  109. break
  110. line = line.replace(self.ifs, " ")
  111. line = encode(line)
  112. self.outf.write(line)
  113. self.outf.flush()
  114. self.outf.close()
  115. def main():
  116. coords = options["coordinates"]
  117. input = options["input"]
  118. output = options["output"]
  119. fs = options["separator"]
  120. proj_in = options["proj_in"]
  121. proj_out = options["proj_out"]
  122. ll_in = flags["i"]
  123. ll_out = flags["o"]
  124. decimal = flags["d"]
  125. copy_input = flags["e"]
  126. include_header = flags["c"]
  127. # check for cs2cs
  128. if not gcore.find_program("cs2cs"):
  129. gcore.fatal(
  130. _(
  131. "cs2cs program not found, install PROJ first: \
  132. https://proj.org"
  133. )
  134. )
  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 = separator(ifs)
  145. ofs = separator(ofs)
  146. # set up projection params
  147. s = gcore.read_command("g.proj", flags="j")
  148. kv = parse_key_val(s)
  149. if "XY location" in kv["+proj"] and (ll_in or ll_out):
  150. gcore.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. gcore.verbose("Assuming LL WGS84 as input, current projection as output ")
  155. if ll_out:
  156. in_proj = gcore.read_command("g.proj", flags="jf")
  157. if proj_in:
  158. if "+" in proj_in:
  159. in_proj = proj_in
  160. else:
  161. gcore.fatal(_("Invalid PROJ.4 input specification"))
  162. if not in_proj:
  163. gcore.verbose("Assuming current location as input")
  164. in_proj = gcore.read_command("g.proj", flags="jf")
  165. in_proj = in_proj.strip()
  166. gcore.verbose("Input parameters: '%s'" % in_proj)
  167. out_proj = None
  168. if ll_out:
  169. out_proj = "+proj=longlat +datum=WGS84"
  170. gcore.verbose("Assuming current projection as input, LL WGS84 as output ")
  171. if ll_in:
  172. out_proj = gcore.read_command("g.proj", flags="jf")
  173. if proj_out:
  174. if "+" in proj_out:
  175. out_proj = proj_out
  176. else:
  177. gcore.fatal(_("Invalid PROJ.4 output specification"))
  178. if not out_proj:
  179. gcore.fatal(_("Missing output projection parameters "))
  180. out_proj = out_proj.strip()
  181. gcore.verbose("Output parameters: '%s'" % out_proj)
  182. # set up input file
  183. if coords:
  184. x, y = coords.split(",")
  185. tmpfile = gcore.tempfile()
  186. fd = open(tmpfile, "w")
  187. fd.write("%s%s%s\n" % (x, ifs, y))
  188. fd.close()
  189. inf = open(tmpfile)
  190. else:
  191. if input == "-":
  192. infile = None
  193. inf = sys.stdin
  194. else:
  195. infile = input
  196. if not os.path.exists(infile):
  197. gcore.fatal(_("Unable to read input data"))
  198. inf = open(infile)
  199. gcore.debug("input file=[%s]" % infile)
  200. # set up output file
  201. if not output:
  202. outfile = None
  203. outf = sys.stdout
  204. else:
  205. outfile = output
  206. outf = open(outfile, "w")
  207. gcore.debug("output file=[%s]" % outfile)
  208. # set up output style
  209. if not decimal:
  210. outfmt = ["-w5"]
  211. else:
  212. outfmt = ["-f", "%.8f"]
  213. if not copy_input:
  214. copyinp = []
  215. else:
  216. copyinp = ["-E"]
  217. # do the conversion
  218. # Convert cs2cs DMS format to GRASS DMS format:
  219. # cs2cs | sed -e 's/d/:/g' -e "s/'/:/g" -e 's/"//g'
  220. cmd = ["cs2cs"] + copyinp + outfmt + in_proj.split() + ["+to"] + out_proj.split()
  221. p = gcore.Popen(cmd, stdin=gcore.PIPE, stdout=gcore.PIPE)
  222. tr = TrThread(ifs, inf, p.stdin)
  223. tr.start()
  224. if not copy_input:
  225. if include_header:
  226. outf.write("x%sy%sz\n" % (ofs, ofs))
  227. for line in p.stdout:
  228. try:
  229. xy, z = decode(line).split(" ", 1)
  230. x, y = xy.split("\t")
  231. except ValueError:
  232. gcore.fatal(line)
  233. outf.write("%s%s%s%s%s\n" % (x.strip(), ofs, y.strip(), ofs, z.strip()))
  234. else:
  235. if include_header:
  236. outf.write("input_x%sinput_y%sx%sy%sz\n" % (ofs, ofs, ofs, ofs))
  237. for line in p.stdout:
  238. inXYZ, x, rest = decode(line).split("\t")
  239. inX, inY = inXYZ.split(" ")[:2]
  240. y, z = rest.split(" ", 1)
  241. outf.write(
  242. "%s%s%s%s%s%s%s%s%s\n"
  243. % (
  244. inX.strip(),
  245. ofs,
  246. inY.strip(),
  247. ofs,
  248. x.strip(),
  249. ofs,
  250. y.strip(),
  251. ofs,
  252. z.strip(),
  253. )
  254. )
  255. p.wait()
  256. if p.returncode != 0:
  257. gcore.warning(_("Projection transform probably failed, please investigate"))
  258. if __name__ == "__main__":
  259. options, flags = gcore.parser()
  260. main()