r.import.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: r.import
  5. #
  6. # AUTHOR(S): Markus Metz
  7. #
  8. # PURPOSE: Import and reproject on the fly
  9. #
  10. # COPYRIGHT: (C) 2015-2021 GRASS development team
  11. #
  12. # This program is free software under the GNU General
  13. # Public License (>=v2). Read the file COPYING that
  14. # comes with GRASS for details.
  15. #
  16. #############################################################################
  17. # %module
  18. # % description: Imports raster data into a GRASS raster map using GDAL library and reprojects on the fly.
  19. # % keyword: raster
  20. # % keyword: import
  21. # % keyword: projection
  22. # %end
  23. # %option G_OPT_F_BIN_INPUT
  24. # % description: Name of GDAL dataset to be imported
  25. # % guisection: Input
  26. # %end
  27. # %option
  28. # % key: band
  29. # % type: integer
  30. # % required: no
  31. # % multiple: yes
  32. # % description: Input band(s) to select (default is all bands)
  33. # % guisection: Input
  34. # %end
  35. # %option G_OPT_MEMORYMB
  36. # %end
  37. # %option G_OPT_R_OUTPUT
  38. # % description: Name for output raster map
  39. # % required: no
  40. # % guisection: Output
  41. # %end
  42. # %option
  43. # % key: resample
  44. # % type: string
  45. # % required: no
  46. # % multiple: no
  47. # % options: nearest,bilinear,bicubic,lanczos,bilinear_f,bicubic_f,lanczos_f
  48. # % description: Resampling method to use for reprojection
  49. # % descriptions: nearest;nearest neighbor;bilinear;bilinear interpolation;bicubic;bicubic interpolation;lanczos;lanczos filter;bilinear_f;bilinear interpolation with fallback;bicubic_f;bicubic interpolation with fallback;lanczos_f;lanczos filter with fallback
  50. # % answer: nearest
  51. # % guisection: Output
  52. # %end
  53. # %option
  54. # % key: extent
  55. # % type: string
  56. # % required: no
  57. # % multiple: no
  58. # % options: input,region
  59. # % answer: input
  60. # % description: Output raster map extent
  61. # % descriptions: region;extent of current region;input;extent of input map
  62. # % guisection: Output
  63. # %end
  64. # %option
  65. # % key: resolution
  66. # % type: string
  67. # % required: no
  68. # % multiple: no
  69. # % answer: estimated
  70. # % options: estimated,value,region
  71. # % description: Resolution of output raster map (default: estimated)
  72. # % descriptions: estimated;estimated resolution;value;user-specified resolution;region;current region resolution
  73. # % guisection: Output
  74. # %end
  75. # %option
  76. # % key: resolution_value
  77. # % type: double
  78. # % required: no
  79. # % multiple: no
  80. # % description: Resolution of output raster map (use with option resolution=value)
  81. # % guisection: Output
  82. # %end
  83. # %option
  84. # % key: title
  85. # % key_desc: phrase
  86. # % type: string
  87. # % required: no
  88. # % description: Title for resultant raster map
  89. # % guisection: Metadata
  90. # %end
  91. # %flag
  92. # % key: e
  93. # % description: Estimate resolution only
  94. # % guisection: Optional
  95. # %end
  96. # %flag
  97. # % key: n
  98. # % description: Do not perform region cropping optimization
  99. # % guisection: Optional
  100. # %end
  101. # %flag
  102. # % key: l
  103. # % description: Force Lat/Lon maps to fit into geographic coordinates (90N,S; 180E,W)
  104. # %end
  105. # %flag
  106. # % key: o
  107. # % label: Override projection check (use current location's projection)
  108. # % description: Assume that the dataset has the same projection as the current location
  109. # %end
  110. # %rules
  111. # % required: output,-e
  112. # %end
  113. import sys
  114. import os
  115. import atexit
  116. import math
  117. import grass.script as grass
  118. from grass.exceptions import CalledModuleError
  119. # initialize global vars
  120. TMPLOC = None
  121. SRCGISRC = None
  122. GISDBASE = None
  123. TMP_REG_NAME = None
  124. def cleanup():
  125. # remove temp location
  126. if TMPLOC:
  127. grass.try_rmdir(os.path.join(GISDBASE, TMPLOC))
  128. if SRCGISRC:
  129. grass.try_remove(SRCGISRC)
  130. if (
  131. TMP_REG_NAME
  132. and grass.find_file(
  133. name=TMP_REG_NAME, element="vector", mapset=grass.gisenv()["MAPSET"]
  134. )["fullname"]
  135. ):
  136. grass.run_command(
  137. "g.remove", type="vector", name=TMP_REG_NAME, flags="f", quiet=True
  138. )
  139. def is_projection_matching(GDALdatasource):
  140. """Returns True if current location projection
  141. matches dataset projection, otherwise False"""
  142. try:
  143. grass.run_command("r.in.gdal", input=GDALdatasource, flags="j", quiet=True)
  144. return True
  145. except CalledModuleError:
  146. return False
  147. def main():
  148. global TMPLOC, SRCGISRC, GISDBASE, TMP_REG_NAME
  149. GDALdatasource = options["input"]
  150. output = options["output"]
  151. method = options["resample"]
  152. memory = options["memory"]
  153. bands = options["band"]
  154. tgtres = options["resolution"]
  155. title = options["title"]
  156. if flags["e"] and not output:
  157. output = "rimport_tmp" # will be removed with the entire tmp location
  158. if options["resolution_value"]:
  159. if tgtres != "value":
  160. grass.fatal(
  161. _("To set custom resolution value, select 'value' in resolution option")
  162. )
  163. tgtres_value = float(options["resolution_value"])
  164. if tgtres_value <= 0:
  165. grass.fatal(_("Resolution value can't be smaller than 0"))
  166. elif tgtres == "value":
  167. grass.fatal(
  168. _(
  169. "Please provide the resolution for the imported dataset or change to 'estimated' resolution"
  170. )
  171. )
  172. # try r.in.gdal directly first
  173. additional_flags = "l" if flags["l"] else ""
  174. if flags["o"]:
  175. additional_flags += "o"
  176. region_flag = ""
  177. if options["extent"] == "region":
  178. region_flag += "r"
  179. if flags["o"] or is_projection_matching(GDALdatasource):
  180. parameters = dict(
  181. input=GDALdatasource,
  182. output=output,
  183. memory=memory,
  184. flags="ak" + additional_flags + region_flag,
  185. )
  186. if bands:
  187. parameters["band"] = bands
  188. try:
  189. grass.run_command("r.in.gdal", **parameters)
  190. grass.verbose(
  191. _("Input <%s> successfully imported without reprojection")
  192. % GDALdatasource
  193. )
  194. return 0
  195. except CalledModuleError:
  196. grass.fatal(_("Unable to import GDAL dataset <%s>") % GDALdatasource)
  197. grassenv = grass.gisenv()
  198. tgtloc = grassenv["LOCATION_NAME"]
  199. # make sure target is not xy
  200. if grass.parse_command("g.proj", flags="g")["name"] == "xy_location_unprojected":
  201. grass.fatal(
  202. _("Coordinate reference system not available for current location <%s>")
  203. % tgtloc
  204. )
  205. tgtmapset = grassenv["MAPSET"]
  206. GISDBASE = grassenv["GISDBASE"]
  207. TMPLOC = grass.append_node_pid("tmp_r_import_location")
  208. TMP_REG_NAME = grass.append_node_pid("tmp_r_import_region")
  209. SRCGISRC, src_env = grass.create_environment(GISDBASE, TMPLOC, "PERMANENT")
  210. # create temp location from input without import
  211. grass.verbose(_("Creating temporary location for <%s>...") % GDALdatasource)
  212. # creating a new location with r.in.gdal requires a sanitized env
  213. env = os.environ.copy()
  214. env = grass.sanitize_mapset_environment(env)
  215. parameters = dict(
  216. input=GDALdatasource,
  217. output=output,
  218. memory=memory,
  219. flags="c",
  220. title=title,
  221. location=TMPLOC,
  222. quiet=True,
  223. )
  224. if bands:
  225. parameters["band"] = bands
  226. try:
  227. grass.run_command("r.in.gdal", env=env, **parameters)
  228. except CalledModuleError:
  229. grass.fatal(_("Unable to read GDAL dataset <%s>") % GDALdatasource)
  230. # prepare to set region in temp location
  231. if "r" in region_flag:
  232. tgtregion = TMP_REG_NAME
  233. grass.run_command("v.in.region", output=tgtregion, flags="d")
  234. # switch to temp location
  235. # print projection at verbose level
  236. grass.verbose(
  237. grass.read_command("g.proj", flags="p", env=src_env).rstrip(os.linesep)
  238. )
  239. # make sure input is not xy
  240. if (
  241. grass.parse_command("g.proj", flags="g", env=src_env)["name"]
  242. == "xy_location_unprojected"
  243. ):
  244. grass.fatal(
  245. _("Coordinate reference system not available for input <%s>")
  246. % GDALdatasource
  247. )
  248. # import into temp location
  249. grass.verbose(_("Importing <%s> to temporary location...") % GDALdatasource)
  250. parameters = dict(
  251. input=GDALdatasource,
  252. output=output,
  253. memory=memory,
  254. flags="ak" + additional_flags,
  255. )
  256. if bands:
  257. parameters["band"] = bands
  258. if "r" in region_flag:
  259. grass.run_command(
  260. "v.proj",
  261. location=tgtloc,
  262. mapset=tgtmapset,
  263. input=tgtregion,
  264. output=tgtregion,
  265. env=src_env,
  266. )
  267. grass.run_command("g.region", vector=tgtregion, env=src_env)
  268. parameters["flags"] = parameters["flags"] + region_flag
  269. try:
  270. grass.run_command("r.in.gdal", env=src_env, **parameters)
  271. except CalledModuleError:
  272. grass.fatal(_("Unable to import GDAL dataset <%s>") % GDALdatasource)
  273. outfiles = grass.list_grouped("raster", env=src_env)["PERMANENT"]
  274. # is output a group?
  275. group = False
  276. path = os.path.join(GISDBASE, TMPLOC, "group", output)
  277. if os.path.exists(path):
  278. group = True
  279. path = os.path.join(GISDBASE, TMPLOC, "group", output, "POINTS")
  280. if os.path.exists(path):
  281. grass.fatal(_("Input contains GCPs, rectification is required"))
  282. if "r" in region_flag:
  283. grass.run_command(
  284. "g.remove", type="vector", flags="f", name=tgtregion, env=src_env
  285. )
  286. # switch to target location
  287. if "r" in region_flag:
  288. grass.run_command("g.remove", type="vector", flags="f", name=tgtregion)
  289. region = grass.region()
  290. rflags = None
  291. if flags["n"]:
  292. rflags = "n"
  293. vreg = TMP_REG_NAME
  294. for outfile in outfiles:
  295. n = region["n"]
  296. s = region["s"]
  297. e = region["e"]
  298. w = region["w"]
  299. env = os.environ.copy()
  300. if options["extent"] == "input":
  301. # r.proj -g
  302. try:
  303. tgtextents = grass.read_command(
  304. "r.proj",
  305. location=TMPLOC,
  306. mapset="PERMANENT",
  307. input=outfile,
  308. flags="g",
  309. memory=memory,
  310. quiet=True,
  311. )
  312. except CalledModuleError:
  313. grass.fatal(_("Unable to get reprojected map extent"))
  314. try:
  315. srcregion = grass.parse_key_val(tgtextents, val_type=float, vsep=" ")
  316. n = srcregion["n"]
  317. s = srcregion["s"]
  318. e = srcregion["e"]
  319. w = srcregion["w"]
  320. except ValueError: # import into latlong, expect 53:39:06.894826N
  321. srcregion = grass.parse_key_val(tgtextents, vsep=" ")
  322. n = grass.float_or_dms(srcregion["n"][:-1]) * (
  323. -1 if srcregion["n"][-1] == "S" else 1
  324. )
  325. s = grass.float_or_dms(srcregion["s"][:-1]) * (
  326. -1 if srcregion["s"][-1] == "S" else 1
  327. )
  328. e = grass.float_or_dms(srcregion["e"][:-1]) * (
  329. -1 if srcregion["e"][-1] == "W" else 1
  330. )
  331. w = grass.float_or_dms(srcregion["w"][:-1]) * (
  332. -1 if srcregion["w"][-1] == "W" else 1
  333. )
  334. env["GRASS_REGION"] = grass.region_env(n=n, s=s, e=e, w=w)
  335. # v.in.region in tgt
  336. grass.run_command("v.in.region", output=vreg, quiet=True, env=env)
  337. # reproject to src
  338. # switch to temp location
  339. try:
  340. grass.run_command(
  341. "v.proj",
  342. input=vreg,
  343. output=vreg,
  344. location=tgtloc,
  345. mapset=tgtmapset,
  346. quiet=True,
  347. env=src_env,
  348. )
  349. # test if v.proj created a valid area
  350. if grass.vector_info_topo(vreg, env=src_env)["areas"] != 1:
  351. grass.fatal(_("Please check the 'extent' parameter"))
  352. except CalledModuleError:
  353. grass.fatal(_("Unable to reproject to source location"))
  354. # set region from region vector
  355. grass.run_command("g.region", raster=outfile, env=src_env)
  356. grass.run_command("g.region", vector=vreg, env=src_env)
  357. # align to first band
  358. grass.run_command("g.region", align=outfile, env=src_env)
  359. # get number of cells
  360. cells = grass.region(env=src_env)["cells"]
  361. estres = math.sqrt((n - s) * (e - w) / cells)
  362. # remove from source location for multi bands import
  363. grass.run_command(
  364. "g.remove", type="vector", name=vreg, flags="f", quiet=True, env=src_env
  365. )
  366. # switch to target location
  367. grass.run_command("g.remove", type="vector", name=vreg, flags="f", quiet=True)
  368. grass.message(
  369. _("Estimated target resolution for input band <{out}>: {res}").format(
  370. out=outfile, res=estres
  371. )
  372. )
  373. if flags["e"]:
  374. continue
  375. env = os.environ.copy()
  376. if options["extent"] == "input":
  377. env["GRASS_REGION"] = grass.region_env(n=n, s=s, e=e, w=w)
  378. res = None
  379. if tgtres == "estimated":
  380. res = estres
  381. elif tgtres == "value":
  382. res = tgtres_value
  383. grass.message(
  384. _("Using given resolution for input band <{out}>: {res}").format(
  385. out=outfile, res=res
  386. )
  387. )
  388. # align to requested resolution
  389. env["GRASS_REGION"] = grass.region_env(res=res, flags="a", env=env)
  390. else:
  391. curr_reg = grass.region()
  392. grass.message(
  393. _(
  394. "Using current region resolution for input band "
  395. "<{out}>: nsres={ns}, ewres={ew}"
  396. ).format(out=outfile, ns=curr_reg["nsres"], ew=curr_reg["ewres"])
  397. )
  398. # r.proj
  399. grass.message(_("Reprojecting <%s>...") % outfile)
  400. try:
  401. grass.run_command(
  402. "r.proj",
  403. location=TMPLOC,
  404. mapset="PERMANENT",
  405. input=outfile,
  406. method=method,
  407. resolution=res,
  408. memory=memory,
  409. flags=rflags,
  410. quiet=True,
  411. env=env,
  412. )
  413. except CalledModuleError:
  414. grass.fatal(_("Unable to to reproject raster <%s>") % outfile)
  415. if grass.raster_info(outfile)["min"] is None:
  416. grass.fatal(_("The reprojected raster <%s> is empty") % outfile)
  417. if flags["e"]:
  418. return 0
  419. if group:
  420. grass.run_command("i.group", group=output, input=",".join(outfiles))
  421. # TODO: write metadata with r.support
  422. return 0
  423. if __name__ == "__main__":
  424. options, flags = grass.parser()
  425. atexit.register(cleanup)
  426. sys.exit(main())