convertmodis_gdal.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. #!/usr/bin/env python
  2. # class to convert/process modis data using gdal
  3. #
  4. # Some code is coming from GDAL/OGR Test Suite
  5. # Copyright (c) 2008, Andrey Kiselev <[email protected]>
  6. # Copyright (c) 2008-2014, Even Rouault <even dot rouault at mines-paris dot org>
  7. # http://trac.osgeo.org/gdal/browser/trunk/autotest/alg/warp.py#L782
  8. #
  9. # (c) Copyright Luca Delucchi 2014
  10. # Authors: Luca Delucchi
  11. # Email: luca dot delucchi at iasma dot it
  12. #
  13. ##################################################################
  14. #
  15. # This MODIS Python class is licensed under the terms of GNU GPL 2.
  16. # This program is free software; you can redistribute it and/or
  17. # modify it under the terms of the GNU General Public License as
  18. # published by the Free Software Foundation; either version 2 of
  19. # the License, or (at your option) any later version.
  20. # This program is distributed in the hope that it will be useful,
  21. # but WITHOUT ANY WARRANTY; without even implied warranty of
  22. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  23. # See the GNU General Public License for more details.
  24. #
  25. ##################################################################
  26. """Convert MODIS HDF file using GDAL Python bindings. It can create GeoTiff
  27. file (or other GDAL supported formats) or HDF mosaic file for several tiles.
  28. Classes:
  29. * :class:`file_info`
  30. * :class:`createMosaicGDAL`
  31. * :class:`convertModisGDAL`
  32. Functions:
  33. * :func:`getResampling`
  34. * :func:`raster_copy`
  35. * :func:`raster_copy_with_nodata`
  36. """
  37. # python 2 and 3 compatibility
  38. from __future__ import print_function
  39. from __future__ import division
  40. from collections import OrderedDict
  41. try:
  42. import osgeo.gdal as gdal
  43. except ImportError:
  44. try:
  45. import gdal
  46. except ImportError:
  47. raise ImportError('Python GDAL library not found, please install '
  48. 'python-gdal')
  49. try:
  50. import osgeo.osr as osr
  51. except ImportError:
  52. try:
  53. import osr
  54. except ImportError:
  55. raise ImportError('Python GDAL library not found, please install '
  56. 'python-gdal')
  57. RESAM_GDAL = ['AVERAGE', 'BILINEAR', 'CUBIC', 'CUBIC_SPLINE', 'LANCZOS',
  58. 'MODE', 'NEAREST_NEIGHBOR']
  59. SINU_WKT = 'PROJCS["Sinusoidal_Sanson_Flamsteed",GEOGCS["GCS_Unknown",' \
  60. 'DATUM["D_unknown",SPHEROID["Unknown",6371007.181,"inf"]],' \
  61. 'PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]' \
  62. ',PROJECTION["Sinusoidal"],PARAMETER["central_meridian",0],' \
  63. 'PARAMETER["false_easting",0],PARAMETER["false_northing",0]' \
  64. ',UNIT["Meter",1]]'
  65. def getResampling(res):
  66. """Return the GDAL resampling method
  67. :param str res: the string of resampling method
  68. """
  69. if res == 'AVERAGE':
  70. return gdal.GRA_Average
  71. elif res == 'BILINEAR' or res == 'BICUBIC':
  72. return gdal.GRA_Bilinear
  73. elif res == 'LANCZOS':
  74. return gdal.GRA_Lanczos
  75. elif res == 'MODE':
  76. return gdal.GRA_Mode
  77. elif res == 'NEAREST_NEIGHBOR':
  78. return gdal.GRA_NearestNeighbour
  79. elif res == 'CUBIC_CONVOLUTION' or res == 'CUBIC':
  80. return gdal.GRA_Cubic
  81. elif res == 'CUBIC_SPLINE':
  82. return gdal.GRA_CubicSpline
  83. class convertModisGDAL:
  84. """A class to convert modis data from hdf to GDAL formats using GDAL
  85. :param str hdfname: name of input data
  86. :param str prefix: prefix for output data
  87. :param str subset: the subset to consider
  88. :param int res: output resolution
  89. :param str outformat: output format, it is possible to use all the
  90. supported GDAL format
  91. :param int epsg: the EPSG code for the preojection of output file
  92. :param str wkt: the WKT string for the preojection of output file
  93. :param str resampl: the resampling method to use
  94. :param bool vrt: True to read GDAL VRT file created with
  95. createMosaicGDAL
  96. """
  97. def __init__(self, hdfname, prefix, subset, res, outformat="GTiff",
  98. epsg=None, wkt=None, resampl='NEAREST_NEIGHBOR', vrt=False):
  99. """Function for the initialize the object"""
  100. # Open source dataset
  101. self.in_name = hdfname
  102. self.src_ds = gdal.Open(self.in_name)
  103. self.layers = self.src_ds.GetSubDatasets()
  104. self.output_pref = prefix
  105. self.resolution = res
  106. if epsg:
  107. self.dst_srs = osr.SpatialReference()
  108. self.dst_srs.ImportFromEPSG(int(epsg))
  109. self.dst_wkt = self.dst_srs.ExportToWkt()
  110. elif wkt:
  111. try:
  112. f = open(wkt)
  113. self.dst_wkt = f.read()
  114. f.close()
  115. except:
  116. self.dst_wkt = wkt
  117. else:
  118. raise Exception('You have to set one of the following option: '
  119. '"epsg", "wkt"')
  120. # error threshold the same value as gdalwarp
  121. self.error_threshold = 0.125
  122. self.resampling = getResampling(resampl)
  123. if isinstance(subset, list):
  124. self.subset = subset
  125. elif isinstance(subset, str):
  126. self.subset = subset.replace('(', '').replace(')', '').strip().split()
  127. else:
  128. raise Exception('Type for subset parameter not supported')
  129. self.driver = gdal.GetDriverByName(outformat)
  130. self.vrt = vrt
  131. if self.driver is None:
  132. raise Exception('Format driver %s not found, pick a supported '
  133. 'driver.' % outformat)
  134. def _boundingBox(self, src):
  135. """Obtain the bounding box of raster in the new coordinate system
  136. :param src: a GDAL dataset object
  137. :return: a bounding box value in lists
  138. """
  139. src_gtrn = src.GetGeoTransform(can_return_null=True)
  140. src_bbox_cells = ((0., 0.), (0, src.RasterYSize), (src.RasterXSize, 0),
  141. (src.RasterXSize, src.RasterYSize))
  142. geo_pts_x = []
  143. geo_pts_y = []
  144. for x, y in src_bbox_cells:
  145. x2 = src_gtrn[0] + src_gtrn[1] * x + src_gtrn[2] * y
  146. y2 = src_gtrn[3] + src_gtrn[4] * x + src_gtrn[5] * y
  147. geo_pts_x.append(x2)
  148. geo_pts_y.append(y2)
  149. return ((min(geo_pts_x), min(geo_pts_y)), (max(geo_pts_x),
  150. max(geo_pts_y)))
  151. def _calculateRes(self, minn, maxx, res):
  152. """Calculate the number of pixel from extent and resolution
  153. :param float minn: minimum value of extent
  154. :param float maxx: maximum value of extent
  155. :param int res: resolution of output raster
  156. :return: integer number with the number of pixels
  157. """
  158. return int(round((maxx - minn) / res))
  159. def _createWarped(self, raster):
  160. """Create a warped VRT file to fetch default values for target raster
  161. dimensions and geotransform
  162. :param str raster: the name of raster, for HDF have to be one subset
  163. """
  164. src = gdal.Open(raster)
  165. tmp_ds = gdal.AutoCreateWarpedVRT(src, src.GetProjection(),
  166. self.dst_wkt, self.resampling,
  167. self.error_threshold)
  168. if not self.resolution:
  169. self.dst_xsize = tmp_ds.RasterXSize
  170. self.dst_ysize = tmp_ds.RasterYSize
  171. self.dst_gt = tmp_ds.GetGeoTransform()
  172. else:
  173. bbox = self._boundingBox(tmp_ds)
  174. self.dst_xsize = self._calculateRes(bbox[0][0], bbox[1][0],
  175. self.resolution)
  176. self.dst_ysize = self._calculateRes(bbox[0][1], bbox[1][1],
  177. self.resolution)
  178. if self.dst_xsize == 0:
  179. raise Exception('Invalid number of pixel 0 for X size. The '
  180. 'problem could be in an invalid value of '
  181. 'resolution')
  182. elif self.dst_ysize == 0:
  183. raise Exception('Invalid number of pixel 0 for Y size. The '
  184. 'problem could be in an invalid value of '
  185. 'resolution')
  186. self.dst_gt = [bbox[0][0], self.resolution, 0.0, bbox[1][1], 0.0,
  187. -self.resolution]
  188. tmp_ds = None
  189. src = None
  190. return 0
  191. def _progressCallback(self, pct, message, user_data):
  192. """For the progress status"""
  193. return 1 # 1 to continue, 0 to stop
  194. def _reprojectOne(self, l, quiet=False):
  195. """Reproject a single subset of MODIS product
  196. l = complete name of input dataset
  197. """
  198. l_src_ds = gdal.Open(l)
  199. meta = l_src_ds.GetMetadata()
  200. band = l_src_ds.GetRasterBand(1)
  201. if '_FillValue' in list(meta.keys()):
  202. fill_value = meta['_FillValue']
  203. elif band.GetNoDataValue():
  204. fill_value = band.GetNoDataValue()
  205. else:
  206. fill_value = None
  207. datatype = band.DataType
  208. try:
  209. l_name = l.split(':')[-1]
  210. out_name = "{pref}_{lay}.tif".format(pref=self.output_pref,
  211. lay=l_name)
  212. except:
  213. out_name = "{pref}.tif".format(pref=self.output_pref)
  214. if self.vrt:
  215. out_name = "{pref}.tif".format(pref=self.output_pref)
  216. try:
  217. dst_ds = self.driver.Create(out_name, self.dst_xsize,
  218. self.dst_ysize, 1, datatype)
  219. except:
  220. raise Exception('Not possible to create dataset %s' % out_name)
  221. dst_ds.SetProjection(self.dst_wkt)
  222. dst_ds.SetGeoTransform(self.dst_gt)
  223. if fill_value:
  224. dst_ds.GetRasterBand(1).SetNoDataValue(float(fill_value))
  225. dst_ds.GetRasterBand(1).Fill(float(fill_value))
  226. cbk = self._progressCallback
  227. # value for last parameter of above self._progressCallback
  228. cbk_user_data = None
  229. try:
  230. gdal.ReprojectImage(l_src_ds, dst_ds, l_src_ds.GetProjection(),
  231. self.dst_wkt, self.resampling, 0,
  232. self.error_threshold, cbk, cbk_user_data)
  233. if not quiet:
  234. print("Layer {name} reprojected".format(name=l))
  235. except:
  236. raise Exception('Not possible to reproject dataset '
  237. '{name}'.format(name=l))
  238. dst_ds.SetMetadata(meta)
  239. dst_ds = None
  240. l_src_ds = None
  241. return 0
  242. def run_vrt_separated(self):
  243. """Reproject VRT created by createMosaicGDAL, function write_vrt with
  244. separated=True
  245. """
  246. self._createWarped(self.in_name)
  247. self._reprojectOne(self.in_name)
  248. print("Dataset '{name}' reprojected".format(name=self.in_name))
  249. def run(self, quiet=False):
  250. """Reproject all the subset of chosen layer"""
  251. if self.vrt:
  252. self.run_vrt_separated()
  253. return
  254. else:
  255. self._createWarped(self.layers[0][0])
  256. n = 0
  257. for i in self.subset:
  258. if str(i) == '1':
  259. self._reprojectOne(self.layers[n][0], quiet=quiet)
  260. n = n + 1
  261. if not quiet:
  262. print("All layer for dataset '{name}' "
  263. "reprojected".format(name=self.in_name))
  264. # =============================================================================
  265. def raster_copy(s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
  266. t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
  267. nodata=None):
  268. """Copy a band of raster into the output file.
  269. Function copied from gdal_merge.py
  270. """
  271. if nodata is not None:
  272. return raster_copy_with_nodata(s_fh, s_xoff, s_yoff, s_xsize, s_ysize,
  273. s_band_n, t_fh, t_xoff, t_yoff, t_xsize,
  274. t_ysize, t_band_n, nodata)
  275. s_band = s_fh.GetRasterBand(s_band_n)
  276. t_band = t_fh.GetRasterBand(t_band_n)
  277. data = s_band.ReadRaster(s_xoff, s_yoff, s_xsize, s_ysize,
  278. t_xsize, t_ysize, t_band.DataType)
  279. t_band.WriteRaster(t_xoff, t_yoff, t_xsize, t_ysize, data, t_xsize,
  280. t_ysize, t_band.DataType)
  281. return 0
  282. def raster_copy_with_nodata(s_fh, s_xoff, s_yoff, s_xsize, s_ysize, s_band_n,
  283. t_fh, t_xoff, t_yoff, t_xsize, t_ysize, t_band_n,
  284. nodata):
  285. """Copy a band of raster into the output file with nodata values.
  286. Function copied from gdal_merge.py
  287. """
  288. try:
  289. import numpy as Numeric
  290. except ImportError:
  291. import Numeric
  292. s_band = s_fh.GetRasterBand(s_band_n)
  293. t_band = t_fh.GetRasterBand(t_band_n)
  294. data_src = s_band.ReadAsArray(s_xoff, s_yoff, s_xsize, s_ysize,
  295. t_xsize, t_ysize)
  296. data_dst = t_band.ReadAsArray(t_xoff, t_yoff, t_xsize, t_ysize)
  297. nodata_test = Numeric.equal(data_src, nodata)
  298. to_write = Numeric.choose(nodata_test, (data_src, data_dst))
  299. t_band.WriteArray(to_write, t_xoff, t_yoff)
  300. return 0
  301. class file_info:
  302. """A class holding information about a GDAL file.
  303. Class copied from gdal_merge.py
  304. :param str filename: Name of file to read.
  305. :return: 1 on success or 0 if the file can't be opened.
  306. """
  307. def init_from_name(self, filename):
  308. """Initialize file_info from filename"""
  309. fh = gdal.Open(filename)
  310. if fh is None:
  311. return 0
  312. self.filename = filename
  313. self.bands = fh.RasterCount
  314. self.xsize = fh.RasterXSize
  315. self.ysize = fh.RasterYSize
  316. self.band_type = fh.GetRasterBand(1).DataType
  317. self.block_size = fh.GetRasterBand(1).GetBlockSize()
  318. self.projection = fh.GetProjection()
  319. self.geotransform = fh.GetGeoTransform()
  320. self.ulx = self.geotransform[0]
  321. self.uly = self.geotransform[3]
  322. self.lrx = self.ulx + self.geotransform[1] * self.xsize
  323. self.lry = self.uly + self.geotransform[5] * self.ysize
  324. meta = fh.GetMetadata()
  325. if '_FillValue' in list(meta.keys()):
  326. self.fill_value = meta['_FillValue']
  327. elif fh.GetRasterBand(1).GetNoDataValue():
  328. self.fill_value = fh.GetRasterBand(1).GetNoDataValue()
  329. else:
  330. self.fill_value = None
  331. ct = fh.GetRasterBand(1).GetRasterColorTable()
  332. if ct is not None:
  333. self.ct = ct.Clone()
  334. else:
  335. self.ct = None
  336. return 1
  337. def copy_into(self, t_fh, s_band=1, t_band=1, nodata_arg=None):
  338. """Copy this files image into target file.
  339. This method will compute the overlap area of the file_info objects
  340. file, and the target gdal.Dataset object, and copy the image data
  341. for the common window area. It is assumed that the files are in
  342. a compatible projection. no checking or warping is done. However,
  343. if the destination file is a different resolution, or different
  344. image pixel type, the appropriate resampling and conversions will
  345. be done (using normal GDAL promotion/demotion rules).
  346. :param t_fh: gdal.Dataset object for the file into which some or all
  347. of this file may be copied.
  348. :param s_band:
  349. :param t_band:
  350. :param nodata_arg:
  351. :return: 1 on success (or if nothing needs to be copied), and zero one
  352. failure.
  353. """
  354. t_geotransform = t_fh.GetGeoTransform()
  355. t_ulx = t_geotransform[0]
  356. t_uly = t_geotransform[3]
  357. t_lrx = t_geotransform[0] + t_fh.RasterXSize * t_geotransform[1]
  358. t_lry = t_geotransform[3] + t_fh.RasterYSize * t_geotransform[5]
  359. # figure out intersection region
  360. tgw_ulx = max(t_ulx, self.ulx)
  361. tgw_lrx = min(t_lrx, self.lrx)
  362. if t_geotransform[5] < 0:
  363. tgw_uly = min(t_uly, self.uly)
  364. tgw_lry = max(t_lry, self.lry)
  365. else:
  366. tgw_uly = max(t_uly, self.uly)
  367. tgw_lry = min(t_lry, self.lry)
  368. # do they even intersect?
  369. if tgw_ulx >= tgw_lrx:
  370. return 1
  371. if t_geotransform[5] < 0 and tgw_uly <= tgw_lry:
  372. return 1
  373. if t_geotransform[5] > 0 and tgw_uly >= tgw_lry:
  374. return 1
  375. # compute target window in pixel coordinates.
  376. tw_xoff = int((tgw_ulx - t_geotransform[0]) / t_geotransform[1] + 0.1)
  377. tw_yoff = int((tgw_uly - t_geotransform[3]) / t_geotransform[5] + 0.1)
  378. tw_xsize = int((tgw_lrx - t_geotransform[0]) / t_geotransform[1] + 0.5) - tw_xoff
  379. tw_ysize = int((tgw_lry - t_geotransform[3]) / t_geotransform[5] + 0.5) - tw_yoff
  380. if tw_xsize < 1 or tw_ysize < 1:
  381. return 1
  382. # Compute source window in pixel coordinates.
  383. sw_xoff = int((tgw_ulx - self.geotransform[0]) / self.geotransform[1])
  384. sw_yoff = int((tgw_uly - self.geotransform[3]) / self.geotransform[5])
  385. sw_xsize = int((tgw_lrx - self.geotransform[0])
  386. / self.geotransform[1] + 0.5) - sw_xoff
  387. sw_ysize = int((tgw_lry - self.geotransform[3])
  388. / self.geotransform[5] + 0.5) - sw_yoff
  389. if sw_xsize < 1 or sw_ysize < 1:
  390. return 1
  391. # Open the source file, and copy the selected region.
  392. s_fh = gdal.Open(self.filename)
  393. return \
  394. raster_copy(s_fh, sw_xoff, sw_yoff, sw_xsize, sw_ysize, s_band,
  395. t_fh, tw_xoff, tw_yoff, tw_xsize, tw_ysize, t_band,
  396. nodata_arg)
  397. class createMosaicGDAL:
  398. """A class to mosaic modis data from hdf to GDAL formats using GDAL
  399. :param list hdfnames: a list containing the name of tile to mosaic
  400. :param str subset: the subset of layer to consider
  401. :param str outformat: the output format to use, this parameter is
  402. not used for the VRT output, supported values
  403. are HDF4Image, GTiff, HFA, and maybe something
  404. else not tested.
  405. """
  406. def __init__(self, hdfnames, subset, outformat="HDF4Image"):
  407. """Function for the initialize the object"""
  408. # Open source dataset
  409. self.in_names = hdfnames
  410. # #TODO use resolution into mosaic.
  411. # self.resolution = res
  412. if not subset:
  413. self.subset = None
  414. elif isinstance(subset, list):
  415. self.subset = subset
  416. elif isinstance(subset, str):
  417. self.subset = subset.replace('(', '').replace(')', '').strip().split()
  418. else:
  419. raise Exception('Type for subset parameter not supported')
  420. self.driver = gdal.GetDriverByName(outformat)
  421. if self.driver is None:
  422. raise Exception('Format driver %s not found, pick a supported '
  423. 'driver.' % outformat)
  424. driverMD = self.driver.GetMetadata()
  425. if 'DCAP_CREATE' not in driverMD:
  426. raise Exception('Format driver %s does not support creation and'
  427. ' piecewise writing.\nPlease select a format that'
  428. ' does, such as GTiff (the default) or HFA (Erdas'
  429. ' Imagine).' % format)
  430. self._initLayers()
  431. self._getUsedLayers()
  432. self._names_to_fileinfos()
  433. def _initLayers(self):
  434. """Set up the variable self.layers as dictionary for each chosen
  435. subset"""
  436. if isinstance(self.in_names, list):
  437. src_ds = gdal.Open(self.in_names[0])
  438. else:
  439. raise Exception("The input value should be a list of HDF files")
  440. layers = src_ds.GetSubDatasets()
  441. self.layers = OrderedDict()
  442. n = 0
  443. if not self.subset:
  444. self.subset = [1 for i in range(len(layers))]
  445. for i in self.subset:
  446. if str(i) == '1':
  447. name = layers[n][0].split(':')[-1]
  448. self.layers[name] = list()
  449. n = n + 1
  450. def _getUsedLayers(self):
  451. """Add each subset to the correct list for each input layers"""
  452. for name in self.in_names:
  453. src_ds = gdal.Open(name)
  454. layers = src_ds.GetSubDatasets()
  455. n = 0
  456. for i in self.subset:
  457. if str(i) == '1':
  458. name = layers[n][0].split(':')[-1]
  459. self.layers[name].append(layers[n][0])
  460. n = n + 1
  461. def _names_to_fileinfos(self):
  462. """Translate a list of GDAL filenames, into file_info objects.
  463. Returns a list of file_info objects. There may be less file_info
  464. objects than names if some of the names could not be opened as GDAL
  465. files.
  466. """
  467. self.file_infos = OrderedDict()
  468. for k, v in self.layers.items():
  469. self.file_infos[k] = []
  470. for name in v:
  471. fi = file_info()
  472. if fi.init_from_name(name) == 1:
  473. self.file_infos[k].append(fi)
  474. self.file_infos
  475. def _calculateNewSize(self):
  476. """Return the new size of output raster
  477. :return: X size, Y size and geotransform parameters
  478. """
  479. values = list(self.file_infos.values())
  480. l1 = values[0][0]
  481. ulx = l1.ulx
  482. uly = l1.uly
  483. lrx = l1.lrx
  484. lry = l1.lry
  485. for fi in self.file_infos[list(self.file_infos.keys())[0]]:
  486. ulx = min(ulx, fi.ulx)
  487. uly = max(uly, fi.uly)
  488. lrx = max(lrx, fi.lrx)
  489. lry = min(lry, fi.lry)
  490. psize_x = l1.geotransform[1]
  491. psize_y = l1.geotransform[5]
  492. geotransform = [ulx, psize_x, 0, uly, 0, psize_y]
  493. xsize = int((lrx - ulx) / geotransform[1] + 0.5)
  494. ysize = int((lry - uly) / geotransform[5] + 0.5)
  495. return xsize, ysize, geotransform
  496. def write_mosaic_xml(self, prefix):
  497. """Write the XML metadata file for MODIS mosaic
  498. :param str prefix: the prefix for the XML file containing metadata
  499. """
  500. from .parsemodis import parseModisMulti
  501. import os
  502. listHDF = []
  503. for i in self.in_names:
  504. listHDF.append(os.path.realpath(i.strip()))
  505. pmm = parseModisMulti(listHDF)
  506. pmm.writexml("%s.xml" % prefix)
  507. def run(self, output, quiet=False):
  508. """Create the mosaic
  509. :param str output: the name of output file
  510. """
  511. values = list(self.file_infos.values())
  512. l1 = values[0][0]
  513. xsize, ysize, geotransform = self._calculateNewSize()
  514. t_fh = self.driver.Create(output, xsize, ysize,
  515. len(list(self.file_infos.keys())),
  516. l1.band_type)
  517. if t_fh is None:
  518. raise Exception('Not possible to create dataset %s' % output)
  519. t_fh.SetGeoTransform(geotransform)
  520. t_fh.SetProjection(l1.projection)
  521. i = 1
  522. for names in list(self.file_infos.values()):
  523. fill = None
  524. if names[0].fill_value:
  525. fill = float(names[0].fill_value)
  526. t_fh.GetRasterBand(i).SetNoDataValue(fill)
  527. t_fh.GetRasterBand(i).Fill(fill)
  528. for n in names:
  529. n.copy_into(t_fh, 1, i, fill)
  530. i = i + 1
  531. self.write_mosaic_xml(output)
  532. t_fh = None
  533. if not quiet:
  534. print("The mosaic file {name} has been "
  535. "created".format(name=output))
  536. return True
  537. def _calculateOffset(self, fileinfo, geotransform):
  538. """Return the offset between main origin and the origin of current
  539. file
  540. :param fileinfo: a file_info object
  541. :param geotransform: the geotransform parameters to keep x and y origin
  542. """
  543. x = abs(int((geotransform[0] - fileinfo.ulx) / geotransform[1]))
  544. y = abs(int((geotransform[3] - fileinfo.uly) / geotransform[5]))
  545. return x, y
  546. def write_vrt(self, output, separate=True, quiet=False):
  547. """Write VRT file
  548. :param str output: the prefix of output file
  549. :param bool separate: True to write a VRT file for each band, False to
  550. write an unique file
  551. """
  552. def write_complex(f, geot):
  553. """Write a complex source to VRT file"""
  554. out.write('\t\t<ComplexSource>\n')
  555. out.write('\t\t\t<SourceFilename relativeToVRT="0">{name}'
  556. '</SourceFilename>\n'.format(name=f.filename.replace('"', '')))
  557. out.write('\t\t\t<SourceBand>1</SourceBand>\n')
  558. out.write('\t\t\t<SourceProperties RasterXSize="{x}" '
  559. 'RasterYSize="{y}" DataType="{typ}" '
  560. 'BlockXSize="{bx}" BlockYSize="{by}" />'
  561. '\n'.format(x=f.xsize, y=f.ysize,
  562. typ=gdal.GetDataTypeName(f.band_type),
  563. bx=f.block_size[0], by=f.block_size[1]))
  564. out.write('\t\t\t<SrcRect xOff="0" yOff="0" xSize="{x}" '
  565. 'ySize="{y}" />\n'.format(x=f.xsize, y=f.ysize))
  566. xoff, yoff = self._calculateOffset(f, geot)
  567. out.write('\t\t\t<DstRect xOff="{xoff}" yOff="{yoff}" '
  568. 'xSize="{x}" ySize="{y}" />'
  569. '\n'.format(xoff=xoff, yoff=yoff, x=f.xsize,
  570. y=f.ysize))
  571. if l1.fill_value:
  572. out.write('\t\t\t<NODATA>{va}</NODATA>'
  573. '\n'.format(va=f.fill_value))
  574. out.write('\t\t</ComplexSource>\n')
  575. xsize, ysize, geot = self._calculateNewSize()
  576. if separate:
  577. for k in list(self.file_infos.keys()):
  578. l1 = self.file_infos[k][0]
  579. out = open("{pref}_{band}.vrt".format(pref=output, band=k),
  580. 'w')
  581. out.write('<VRTDataset rasterXSize="{x}" rasterYSize="{y}">'
  582. '\n'.format(x=xsize, y=ysize))
  583. out.write('\t<SRS>{proj}</SRS>\n'.format(proj=l1.projection))
  584. out.write('\t<GeoTransform>{geo0}, {geo1}, {geo2}, {geo3},'
  585. ' {geo4}, {geo5}</GeoTransform>'
  586. '\n'.format(geo0=geot[0], geo1=geot[1], geo2=geot[2],
  587. geo3=geot[3], geo4=geot[4],
  588. geo5=geot[5]))
  589. gtype = gdal.GetDataTypeName(l1.band_type)
  590. out.write('\t<VRTRasterBand dataType="{typ}" band="1"'
  591. '>\n'.format(typ=gtype))
  592. if l1.fill_value:
  593. out.write('\t\t<NoDataValue>{val}</NoDataValue'
  594. '>\n'.format(val=l1.fill_value))
  595. out.write('<ColorInterp>Gray</ColorInterp>\n')
  596. for f in self.file_infos[k]:
  597. write_complex(f, geot)
  598. out.write('\t</VRTRasterBand>\n')
  599. out.write('</VRTDataset>\n')
  600. out.close()
  601. else:
  602. values = list(self.file_infos.values())
  603. l1 = values[0][0]
  604. band = 1 # the number of band
  605. out = open("{pref}.vrt".format(pref=output), 'w')
  606. out.write('<VRTDataset rasterXSize="{x}" rasterYSize="{y}">'
  607. '\n'.format(x=xsize, y=ysize))
  608. out.write('\t<SRS>{proj}</SRS>\n'.format(proj=l1.projection))
  609. out.write('\t<GeoTransform>{geo0}, {geo1}, {geo2}, {geo3},'
  610. ' {geo4}, {geo5}</GeoTransform>\n'.format(geo0=geot[0],
  611. geo1=geot[1], geo2=geot[2], geo3=geot[3], geo4=geot[4],
  612. geo5=geot[5]))
  613. for k in list(self.file_infos.keys()):
  614. l1 = self.file_infos[k][0]
  615. out.write('\t<VRTRasterBand dataType="{typ}" band="{n}"'
  616. '>\n'.format(typ=gdal.GetDataTypeName(l1.band_type),
  617. n=band))
  618. if l1.fill_value:
  619. out.write('\t\t<NoDataValue>{val}</NoDataValue>\n'.format(
  620. val=l1.fill_value))
  621. out.write('\t\t<ColorInterp>Gray</ColorInterp>\n')
  622. for f in self.file_infos[k]:
  623. write_complex(f, geot)
  624. out.write('\t</VRTRasterBand>\n')
  625. band += 1
  626. out.write('</VRTDataset>\n')
  627. out.close()
  628. if not quiet:
  629. print("The VRT mosaic file {name} has been "
  630. "created".format(name=output))
  631. return True