parsemodis.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. #!/usr/bin/env python
  2. # class to parse modis data
  3. #
  4. # (c) Copyright Luca Delucchi 2010
  5. # Authors: Luca Delucchi
  6. # Email: luca dot delucchi at iasma dot it
  7. #
  8. ##################################################################
  9. #
  10. # This MODIS Python class is licensed under the terms of GNU GPL 2.
  11. # This program is free software; you can redistribute it and/or
  12. # modify it under the terms of the GNU General Public License as
  13. # published by the Free Software Foundation; either version 2 of
  14. # the License, or (at your option) any later version.
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  18. # See the GNU General Public License for more details.
  19. #
  20. ##################################################################
  21. """Simple class to parse MODIS metadata file, it can also write the XML
  22. metadata file for a mosaic.
  23. Classes:
  24. * :class:`parseModis`
  25. * :class:`parseModisMulti`
  26. """
  27. # python 2 and 3 compatibility
  28. from builtins import dict
  29. import os
  30. # lists of parameters accepted by resample MRT software
  31. # projections
  32. PROJ_LIST = ['AEA', 'GEO', 'HAM', 'IGH', 'ISIN', 'LA', 'LCC', 'MOL', 'PS',
  33. 'SIN', 'TM', 'UTM', 'MERCAT']
  34. # resampling
  35. RESAM_LIST = ['NEAREST_NEIGHBOR', 'BICUBIC', 'CUBIC_CONVOLUTION', 'NONE']
  36. RESAM_LIST_SWATH = ['NN', 'BI', 'CC']
  37. # datum
  38. DATUM_LIST = ['NODATUM', 'NAD27', 'NAD83', 'WGS66', 'WGS72', 'WGS84']
  39. SPHERE_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
  40. 19, 20]
  41. class parseModis:
  42. """Class to parse MODIS xml files, it can also create the parameter
  43. configuration file for resampling MODIS DATA with the MRT software or
  44. convertmodis Module
  45. :param str filename: the name of MODIS hdf file
  46. """
  47. def __init__(self, filename):
  48. """Function to initialize the object"""
  49. from xml.etree import ElementTree
  50. if os.path.exists(filename):
  51. # hdf name
  52. self.hdfname = filename
  53. else:
  54. raise IOError('{name} does not exist'.format(name=filename))
  55. if os.path.exists(self.hdfname + '.xml'):
  56. # xml hdf name
  57. self.xmlname = self.hdfname + '.xml'
  58. else:
  59. raise IOError('{name}.xml does not exist'.format(name=self.hdfname))
  60. # tif name for the output file for resample MRT software
  61. self.tifname = self.hdfname.replace('.hdf', '.tif')
  62. with open(self.xmlname) as f:
  63. self.tree = ElementTree.parse(f)
  64. # return the code of tile for conf file
  65. self.code = os.path.split(self.hdfname)[1].split('.')[-2]
  66. self.path = os.path.split(self.hdfname)[0]
  67. def __str__(self):
  68. """Print the file without xml tags"""
  69. retString = ""
  70. try:
  71. for node in self.tree.iter():
  72. if node.text.strip() != '':
  73. retString = "{tag} = {val}\n".format(tag=node.tag,
  74. val=node.text)
  75. except:
  76. for node in self.tree.getiterator():
  77. if node.text.strip() != '':
  78. retString = "{tag} = {val}\n".format(tag=node.tag,
  79. val=node.text)
  80. return retString
  81. def getRoot(self):
  82. """Set the root element"""
  83. self.rootree = self.tree.getroot()
  84. def retDTD(self):
  85. """Return the DTDVersion element"""
  86. self.getRoot()
  87. return self.rootree.find('DTDVersion').text
  88. def retDataCenter(self):
  89. """Return the DataCenterId element"""
  90. self.getRoot()
  91. return self.rootree.find('DataCenterId').text
  92. def getGranule(self):
  93. """Set the GranuleURMetaData element"""
  94. self.getRoot()
  95. self.granule = self.rootree.find('GranuleURMetaData')
  96. def retGranuleUR(self):
  97. """Return the GranuleUR element"""
  98. self.getGranule()
  99. return self.granule.find('GranuleUR').text
  100. def retDbID(self):
  101. """Return the DbID element"""
  102. self.getGranule()
  103. return self.granule.find('DbID').text
  104. def retInsertTime(self):
  105. """Return the InsertTime element"""
  106. self.getGranule()
  107. return self.granule.find('InsertTime').text
  108. def retLastUpdate(self):
  109. """Return the LastUpdate element"""
  110. self.getGranule()
  111. return self.granule.find('LastUpdate').text
  112. def retCollectionMetaData(self):
  113. """Return the CollectionMetaData element as dictionary"""
  114. self.getGranule()
  115. collect = dict()
  116. for i in self.granule.find('CollectionMetaData').getiterator():
  117. if i.text.strip() != '':
  118. collect[i.tag] = i.text
  119. return collect
  120. def retDataFiles(self):
  121. """Return the DataFiles element as dictionary"""
  122. self.getGranule()
  123. collect = dict()
  124. datafiles = self.granule.find('DataFiles')
  125. for i in datafiles.find('DataFileContainer').getiterator():
  126. if i.text.strip() != '':
  127. collect[i.tag] = i.text
  128. return collect
  129. def retDataGranule(self):
  130. """Return the ECSDataGranule elements as dictionary"""
  131. self.getGranule()
  132. datagran = dict()
  133. for i in self.granule.find('ECSDataGranule').getiterator():
  134. if i.text.strip() != '':
  135. datagran[i.tag] = i.text
  136. return datagran
  137. def retPGEVersion(self):
  138. """Return the PGEVersion element"""
  139. self.getGranule()
  140. return self.granule.find('PGEVersionClass').find('PGEVersion').text
  141. def retRangeTime(self):
  142. """Return the RangeDateTime elements as dictionary"""
  143. self.getGranule()
  144. rangeTime = dict()
  145. for i in self.granule.find('RangeDateTime').getiterator():
  146. if i.text.strip() != '':
  147. rangeTime[i.tag] = i.text
  148. return rangeTime
  149. def retBoundary(self):
  150. """Return the maximum extend (Bounding Box) of the MODIS file as
  151. dictionary"""
  152. self.getGranule()
  153. self.boundary = []
  154. lat = []
  155. lon = []
  156. spatialContainer = self.granule.find('SpatialDomainContainer')
  157. horizontal = spatialContainer.find('HorizontalSpatialDomainContainer')
  158. boundary = horizontal.find('GPolygon').find('Boundary')
  159. for i in boundary.findall('Point'):
  160. la = float(i.find('PointLongitude').text)
  161. lo = float(i.find('PointLatitude').text)
  162. lon.append(la)
  163. lat.append(lo)
  164. self.boundary.append({'lat': la, 'lon': lo})
  165. extent = dict({'min_lat': min(lat), 'max_lat': max(lat),
  166. 'min_lon': min(lon), 'max_lon': max(lon)})
  167. return extent
  168. def retMeasure(self):
  169. """Return statistics of QA as dictionary"""
  170. value = dict()
  171. self.getGranule()
  172. mes = self.granule.find('MeasuredParameter')
  173. mespcs = mes.findall('MeasuredParameterContainer')
  174. ind = 1
  175. for me in mespcs:
  176. value[ind] = dict()
  177. value[ind]['ParameterName'] = me.find('ParameterName').text
  178. meStat = me.find('QAStats')
  179. qastat = dict()
  180. for i in meStat.getiterator():
  181. if i.tag != 'QAStats':
  182. qastat[i.tag] = i.text
  183. value[ind]['QAStats'] = qastat
  184. meFlag = me.find('QAFlags')
  185. flagstat = dict()
  186. for i in meFlag.getiterator():
  187. if i.tag != 'QAFlags':
  188. flagstat[i.tag] = i.text
  189. value[ind]['QAFlags'] = flagstat
  190. ind += 1
  191. return value
  192. def getMeasureName(self, output=None):
  193. """Return the names of measure names
  194. :param str output: the path of the file where write the output
  195. """
  196. names = list()
  197. measures = self.retMeasure()
  198. for k, v in measures.items():
  199. names.append("{id}\t{na}".format(id=k,
  200. na=v['ParameterName']))
  201. if output:
  202. out = open(output, 'w')
  203. out.write("{ns}\n".format(ns='\n'.join(names)))
  204. out.close()
  205. return 0
  206. else:
  207. return "{ns}".format(ns='\n'.join(names))
  208. def getLayersName(self, output=None):
  209. """Return the names of layers using GDAL
  210. :param str output: the path of the file where write the output
  211. """
  212. try:
  213. import osgeo.gdal as gdal
  214. except ImportError:
  215. try:
  216. import gdal as gdal
  217. except ImportError:
  218. print('WARNING: Python GDAL library not found, please'
  219. ' install it to get layers list')
  220. names = list()
  221. gd = gdal.Open(self.hdfname)
  222. subs = gd.GetSubDatasets()
  223. num = 1
  224. for sub in subs:
  225. names.append("{id}\t{na}".format(id=num,
  226. na=sub[0].split(':')[-1]))
  227. num += 1
  228. if output:
  229. out = open(output, 'w')
  230. out.write("{ns}\n".format(ns='\n'.join(names)))
  231. out.close()
  232. return 0
  233. else:
  234. return "{ns}".format(ns='\n'.join(names))
  235. def retPlatform(self):
  236. """Return the platform values as dictionary."""
  237. value = dict()
  238. self.getGranule()
  239. plat = self.granule.find('Platform')
  240. value['PlatformShortName'] = plat.find('PlatformShortName').text
  241. instr = plat.find('Instrument')
  242. value['InstrumentShortName'] = instr.find('InstrumentShortName').text
  243. sensor = instr.find('Sensor')
  244. value['SensorShortName'] = sensor.find('SensorShortName').text
  245. return value
  246. def retPSA(self):
  247. """Return the PSA values as dictionary, the PSAName is the key and
  248. and PSAValue is the value
  249. """
  250. value = dict()
  251. self.getGranule()
  252. psas = self.granule.find('PSAs')
  253. for i in psas.findall('PSA'):
  254. value[i.find('PSAName').text] = i.find('PSAValue').text
  255. return value
  256. def retInputGranule(self):
  257. """Return the input files (InputGranule) used to process the considered
  258. file"""
  259. value = []
  260. self.getGranule()
  261. for i in self.granule.find('InputGranule').getiterator():
  262. if i.tag != 'InputGranule':
  263. value.append(i.text)
  264. return value
  265. def retBrowseProduct(self):
  266. """Return the BrowseProduct element"""
  267. self.getGranule()
  268. try:
  269. value = self.granule.find('BrowseProduct').find('BrowseGranuleId').text
  270. except:
  271. value = None
  272. return value
  273. def confResample(self, spectral, res=None, output=None, datum='WGS84',
  274. resample='NEAREST_NEIGHBOR', projtype='GEO', utm=None,
  275. projpar='( 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 '
  276. '0.0 0.0 0.0 0.0 )', bound=None):
  277. """Create the parameter file to use with resample MRT software to
  278. create tif (geotiff) file
  279. :param str spectral: the spectral subset to be used, see the product
  280. table to understand the layer that you want use.
  281. For example:
  282. * NDVI ( 1 1 1 0 0 0 0 0 0 0 0 0) copy only layer
  283. NDVI, EVI and QA VI the other layers are not used
  284. * LST ( 1 1 0 0 1 1 0 0 0 0 0 0 ) copy only layer
  285. daily and nightly temperature and QA
  286. :param int res: the resolution for the output file, it must be set in
  287. the map unit of output projection system. The software
  288. will use the original resolution of input file if res
  289. not set
  290. :param str output: the output name, if not set if not set the prefix
  291. name of input hdf file will be used
  292. :param utm: the UTM zone if projection system is UTM
  293. :param str resample: the type of resampling, the valid values are:
  294. * NN (nearest neighbor)
  295. * BI (bilinear)
  296. * CC (cubic convolution)
  297. :param str projtype: the output projection system, valid values are:
  298. * AEA (Albers Equal Area)
  299. * ER (Equirectangular)
  300. * GEO (Geographic Latitude/Longitude)
  301. * HAM (Hammer)
  302. * ISIN (Integerized Sinusoidal)
  303. * IGH (Interrupted Goode Homolosine)
  304. * LA (Lambert Azimuthal)
  305. * LCC (LambertConformal Conic)
  306. * MERCAT (Mercator)
  307. * MOL (Mollweide)
  308. * PS (Polar Stereographic)
  309. * SIN (Sinusoidal)
  310. * UTM (Universal TransverseMercator)
  311. :param str datum: the datum to use, the valid values are:
  312. * NAD27
  313. * NAD83
  314. * WGS66
  315. * WGS76
  316. * WGS84
  317. * NODATUM
  318. :param str projpar: a list of projection parameters, for more info
  319. check the Appendix C of MODIS reprojection tool
  320. user manual https://lpdaac.usgs.gov/content/download/4831/22895/file/mrt41_usermanual_032811.pdf
  321. :param dict bound: dictionary with the following keys:
  322. * max_lat
  323. * max_lon
  324. * min_lat
  325. * min_lon
  326. """
  327. # check if spectral it's write with correct construct ( value )
  328. if not (spectral.strip().startswith('(') and spectral.strip().endswith(')')):
  329. raise Exception('ERROR: The spectral string should be similar to:'
  330. ' ( 1 0 )')
  331. # output name
  332. if not output:
  333. fileout = self.tifname
  334. else:
  335. fileout = output
  336. # the name of the output parameters files for resample MRT software
  337. filename = os.path.join(self.path,
  338. '{co}_mrt_resample.conf'.format(co=self.code))
  339. # if the file already exists it remove it
  340. if os.path.exists(filename):
  341. os.remove(filename)
  342. # open the file
  343. conFile = open(filename, 'w')
  344. conFile.write("INPUT_FILENAME = {name}\n".format(name=self.hdfname))
  345. conFile.write("SPECTRAL_SUBSET = {spec}\n".format(spec=spectral))
  346. conFile.write("SPATIAL_SUBSET_TYPE = INPUT_LAT_LONG\n")
  347. if not bound:
  348. # return the boundary from the input xml file
  349. bound = self.retBoundary()
  350. else:
  351. if 'max_lat' not in bound or 'min_lat' not in bound or \
  352. 'min_lon' not in bound or 'max_lon' not in bound:
  353. raise Exception('bound variable is a dictionary with the '
  354. 'following keys: max_lat, min_lat, min_lon,'
  355. ' max_lon')
  356. # Order: UL: N W - LR: S E
  357. conFile.write("SPATIAL_SUBSET_UL_CORNER = ( {mala} {milo} )"
  358. "\n".format(mala=bound['max_lat'], milo=bound['min_lon']))
  359. conFile.write("SPATIAL_SUBSET_LR_CORNER = ( {mila} {malo} )"
  360. "\n".format(mila=bound['min_lat'], malo=bound['max_lon']))
  361. conFile.write("OUTPUT_FILENAME = {out}\n".format(out=fileout))
  362. # if resample is in resam_list set it otherwise return an error
  363. if resample in RESAM_LIST:
  364. conFile.write("RESAMPLING_TYPE = {res}\n".format(res=resample))
  365. else:
  366. raise Exception('The resampling type {res} is not supportet.\n'
  367. 'The resampling type supported are '
  368. '{reslist}'.format(res=resample,
  369. reslist=RESAM_LIST))
  370. # if projtype is in proj_list set it otherwise return an error
  371. if projtype in PROJ_LIST:
  372. conFile.write("OUTPUT_PROJECTION_TYPE = {ty}\n".format(ty=projtype))
  373. else:
  374. raise Exception('The projection type {typ} is not supported.\n'
  375. 'The projections supported are '
  376. '{proj}'.format(typ=projtype, proj=PROJ_LIST))
  377. conFile.write("OUTPUT_PROJECTION_PARAMETERS = {pr}\n".format(pr=projpar))
  378. # if datum is in datum_list set the parameter otherwise return an error
  379. if datum in DATUM_LIST:
  380. conFile.write("DATUM = {dat}\n".format(dat=datum))
  381. else:
  382. raise Exception('The datum {dat} is not supported.\n'
  383. 'The datum supported are '
  384. '{datum}'.format(dat=datum, datum=DATUM_LIST))
  385. # if utm is not None write the UTM_ZONE parameter in the file
  386. if utm:
  387. conFile.write("UTM_ZONE = {zone}\n".format(zone=utm))
  388. # if res is not None write the OUTPUT_PIXEL_SIZE parameter in the file
  389. if res:
  390. conFile.write("OUTPUT_PIXEL_SIZE = {pix}\n".format(pix=res))
  391. conFile.close()
  392. return filename
  393. def confResample_swath(self, sds, geoloc, res, output=None,
  394. sphere='8', resample='NN', projtype='GEO', utm=None,
  395. projpar='0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 '
  396. '0.0 0.0 0.0 0.0 0.0', bound=None):
  397. """Create the parameter file to use with resample MRT software to
  398. create tif (geotiff) file
  399. :param str sds: Name of band/s (Science Data Set) to resample
  400. :param str geoloc: Name geolocation file (example MOD3, MYD3)
  401. :param int res: the resolution for the output file, it must be set in
  402. the map unit of output projection system. The software
  403. will use the original resolution of input file if res
  404. not set
  405. :param str output: the output name, if not set the prefix name of
  406. input hdf file will be used
  407. :param int sphere: Output sphere number. Valid options are:
  408. * 0=Clarke 1866
  409. * 1=Clarke 1880
  410. * 2=Bessel
  411. * 3=International 1967
  412. * 4=International 1909
  413. * 5=WGS 72
  414. * 6=Everest
  415. * 7=WGS 66
  416. * 8=GRS1980/WGS 84
  417. * 9=Airy
  418. * 10=Modified Everest
  419. * 11=Modified Airy
  420. * 12=Walbeck
  421. * 13=Southeast Asia
  422. * 14=Australian National
  423. * 15=Krassovsky
  424. * 16=Hough
  425. * 17=Mercury1960
  426. * 18=Modified Mercury1968
  427. * 19=Sphere 19 (Radius 6370997)
  428. * 20=MODIS Sphere (Radius 6371007.181)
  429. :param str resample: the type of resampling, the valid values are:
  430. * NN (nearest neighbor)
  431. * BI (bilinear)
  432. * CC (cubic convolution)
  433. :param str projtype: the output projection system, valid values are:
  434. * AEA (Albers Equal Area)
  435. * ER (Equirectangular)
  436. * GEO (Geographic Latitude/Longitude)
  437. * HAM (Hammer)
  438. * ISIN (Integerized Sinusoidal)
  439. * IGH (Interrupted Goode Homolosine)
  440. * LA (Lambert Azimuthal)
  441. * LCC (LambertConformal Conic)
  442. * MERCAT (Mercator)
  443. * MOL (Mollweide)
  444. * PS (Polar Stereographic),
  445. * SIN ()Sinusoidal)
  446. * UTM (Universal TransverseMercator)
  447. :param utm: the UTM zone if projection system is UTM
  448. :param str projpar: a list of projection parameters, for more info
  449. check the Appendix C of MODIS reprojection tool
  450. user manual https://lpdaac.usgs.gov/content/download/4831/22895/file/mrt41_usermanual_032811.pdf
  451. :param dict bound: dictionary with the following keys:
  452. * max_lat
  453. * max_lon
  454. * min_lat
  455. * min_lon
  456. """
  457. # output name
  458. if not output:
  459. fileout = self.tifname
  460. else:
  461. fileout = output
  462. # the name of the output parameters files for resample MRT software
  463. filename = os.path.join(self.path,
  464. '{cod}_mrt_resample.prm'.format(cod=self.code))
  465. # if the file already exists it remove it
  466. if os.path.exists(filename):
  467. os.remove(filename)
  468. # open the file
  469. conFile = open(filename, 'w')
  470. conFile.write("INPUT_FILENAME = {name}\n".format(name=self.hdfname))
  471. conFile.write("GEOLOCATION_FILENAME = {name}\n".format(name=geoloc))
  472. conFile.write("INPUT_SDS_NAME = {name}\n".format(name=sds))
  473. conFile.write("OUTPUT_SPATIAL_SUBSET_TYPE = LAT_LONG\n")
  474. if not bound:
  475. # return the boundary from the input xml file
  476. bound = self.retBoundary()
  477. else:
  478. if 'max_lat' not in bound or 'min_lat' not in bound or \
  479. 'min_lon' not in bound or 'max_lon' not in bound:
  480. raise Exception('bound variable is a dictionary with the '
  481. 'following keys: max_lat, min_lat, min_lon,'
  482. ' max_lon')
  483. # Order: UL: N W - LR: S E
  484. conFile.write("OUTPUT_SPACE_UPPER_LEFT_CORNER (LONG LAT) = {milo} "
  485. "{mala}\n".format(mala=bound['max_lat'],
  486. milo=bound['min_lon']))
  487. conFile.write("OUTPUT_SPACE_LOWER_RIGHT_CORNER (LONG LAT) = {mila} "
  488. "{malo}\n".format(mila=bound['min_lat'],
  489. malo=bound['max_lon']))
  490. conFile.write("OUTPUT_FILENAME = {name}\n".format(name=fileout))
  491. conFile.write("OUTPUT_FILE_FORMAT = GEOTIFF_FMT\n")
  492. # if resample is in resam_list set it otherwise return an error
  493. if resample in RESAM_LIST_SWATH:
  494. conFile.write("KERNEL_TYPE (CC/BI/NN) = {res}"
  495. "\n".format(res=resample))
  496. else:
  497. raise Exception('The resampling type {typ} is not supportet.\n'
  498. 'The resampling type supported are '
  499. '{swa}'.format(typ=resample, swa=RESAM_LIST_SWATH))
  500. # if projtype is in proj_list set it otherwise return an error
  501. if projtype in PROJ_LIST:
  502. conFile.write("OUTPUT_PROJECTION_NUMBER = {typ}\n".format(typ=projtype))
  503. else:
  504. raise Exception('The projection type {typ} is not supported.\n'
  505. 'The projections supported are '
  506. '{proj}'.format(typ=projtype, proj=PROJ_LIST))
  507. conFile.write("OUTPUT_PROJECTION_PARAMETER = {pr}\n".format(pr=projpar))
  508. # if sphere is in sphere_list set it otherwise return an error
  509. if int(sphere) in SPHERE_LIST:
  510. conFile.write("OUTPUT_PROJECTION_SPHERE = {sp}\n".format(sp=sphere))
  511. else:
  512. raise Exception('The sphere {sp} is not supported.\nThe spheres'
  513. 'supported are {sl}'.format(sp=sphere,
  514. sl=SPHERE_LIST))
  515. # if utm is not None write the UTM_ZONE parameter in the file
  516. if utm:
  517. if utm < '-60' or utm > '60':
  518. raise Exception('The valid UTM zone are -60 to 60')
  519. else:
  520. conFile.write("OUTPUT_PROJECTION_ZONE = {ut}\n".format(ut=utm))
  521. # if res is not None write the OUTPUT_PIXEL_SIZE parameter in the file
  522. if res:
  523. conFile.write("OUTPUT_PIXEL_SIZE = {res}\n".format(res=res))
  524. conFile.close()
  525. return filename
  526. class parseModisMulti:
  527. """A class to obtain some variables for the xml file of several MODIS
  528. tiles. It can also create the xml file
  529. :param list hdflist: python list containing the hdf files
  530. """
  531. def __init__(self, hdflist):
  532. """Function to initialize the object"""
  533. from xml.etree import ElementTree
  534. self.ElementTree = ElementTree
  535. self.hdflist = hdflist
  536. self.parModis = []
  537. self.nfiles = 0
  538. # for each hdf files create a parseModis object
  539. for i in hdflist:
  540. self.parModis.append(parseModis(i))
  541. self.nfiles += 1
  542. def _most_common(self, lst):
  543. """Return the most common value of a list"""
  544. return max(set(lst), key=lst.count)
  545. def _checkval(self, vals):
  546. """Internal function to return values from list
  547. :param list vals: list of values
  548. """
  549. if vals.count(vals[0]) == self.nfiles:
  550. return [vals[0]]
  551. else:
  552. outvals = []
  553. for i in vals:
  554. if outvals.count(i) == 0:
  555. outvals.append(i)
  556. return outvals
  557. def _checkvaldict(self, vals):
  558. """Internal function to return values from dictionary
  559. :param dict vals: dictionary of values
  560. """
  561. keys = list(vals[0].keys())
  562. outvals = dict()
  563. for k in keys:
  564. valtemp = []
  565. for v in vals:
  566. valtemp.append(v[k])
  567. if valtemp.count(valtemp[0]) == self.nfiles:
  568. outvals[k] = valtemp[0]
  569. elif len(valtemp) == self.nfiles:
  570. outvals[k] = self._most_common(valtemp)
  571. else:
  572. raise Exception('Something wrong reading XML files')
  573. return outvals
  574. def _minval(self, vals):
  575. """Internal function to return the minimum value
  576. :param list vals: list of values
  577. """
  578. outval = vals[0]
  579. for i in range(1, len(vals)):
  580. if outval > i:
  581. outval = i
  582. return outval
  583. def _maxval(self, vals):
  584. """Internal function to return the maximum value
  585. :param list vals: list of values
  586. """
  587. outval = vals[0]
  588. for i in range(1, len(vals)):
  589. if outval < i:
  590. outval = i
  591. return outval
  592. def _cicle_values(self, obj, values):
  593. """Internal function to add values from a dictionary
  594. :param obj: element to add values
  595. :param values: dictionary containing keys and values
  596. """
  597. for k, v in values.items():
  598. elem = self.ElementTree.SubElement(obj, k)
  599. elem.text = v
  600. def _addPoint(self, obj, lon, lat):
  601. """Internal function to add a point in boundary xml tag
  602. :param obj: element to add point
  603. :param lon: longitude of point
  604. :param lat: latitude of point
  605. """
  606. pt = self.ElementTree.SubElement(obj, 'Point')
  607. ptlon = self.ElementTree.SubElement(pt, 'PointLongitude')
  608. ptlon.text = str(self.boundary[lon])
  609. ptlat = self.ElementTree.SubElement(pt, 'PointLatitude')
  610. ptlat.text = str(self.boundary[lat])
  611. def valDTD(self, obj):
  612. """Function to add DTDVersion
  613. :param obj: element to add DTDVersion
  614. """
  615. values = []
  616. for i in self.parModis:
  617. values.append(i.retDTD())
  618. for i in set(values):
  619. dtd = self.ElementTree.SubElement(obj, 'DTDVersion')
  620. dtd.text = i
  621. def valDataCenter(self, obj):
  622. """Function to add DataCenter
  623. :param obj: element to add DataCenter
  624. """
  625. values = []
  626. for i in self.parModis:
  627. values.append(i.retDataCenter())
  628. for i in set(values):
  629. dci = self.ElementTree.SubElement(obj, 'DataCenterId')
  630. dci.text = i
  631. def valGranuleUR(self, obj):
  632. """Function to add GranuleUR
  633. :param obj: element to add GranuleUR
  634. """
  635. values = []
  636. for i in self.parModis:
  637. values.append(i.retGranuleUR())
  638. for i in set(values):
  639. gur = self.ElementTree.SubElement(obj, 'GranuleUR')
  640. gur.text = i
  641. def valDbID(self, obj):
  642. """Function to add DbID
  643. :param obj: element to add DbID
  644. """
  645. values = []
  646. for i in self.parModis:
  647. values.append(i.retDbID())
  648. for i in set(values):
  649. dbid = self.ElementTree.SubElement(obj, 'DbID')
  650. dbid.text = i
  651. def valInsTime(self, obj):
  652. """Function to add the minimum of InsertTime
  653. :param obj: element to add InsertTime
  654. """
  655. values = []
  656. for i in self.parModis:
  657. values.append(i.retInsertTime())
  658. obj.text = self._minval(values)
  659. def valCollectionMetaData(self, obj):
  660. """Function to add CollectionMetaData
  661. :param obj: element to add CollectionMetaData
  662. """
  663. values = []
  664. for i in self.parModis:
  665. values.append(i.retCollectionMetaData())
  666. self._cicle_values(obj, self._checkvaldict(values))
  667. def valDataFiles(self, obj):
  668. """Function to add DataFileContainer
  669. :param obj: element to add DataFileContainer
  670. """
  671. values = []
  672. for i in self.parModis:
  673. values.append(i.retDataFiles())
  674. for i in values:
  675. dfc = self.ElementTree.SubElement(obj, 'DataFileContainer')
  676. self._cicle_values(dfc, i)
  677. def valPGEVersion(self, obj):
  678. """Function to add PGEVersion
  679. :param obj: element to add PGEVersion
  680. """
  681. values = []
  682. for i in self.parModis:
  683. values.append(i.retPGEVersion())
  684. for i in set(values):
  685. pge = self.ElementTree.SubElement(obj, 'PGEVersion')
  686. pge.text = i
  687. def valRangeTime(self, obj):
  688. """Function to add RangeDateTime
  689. :param obj: element to add RangeDateTime
  690. """
  691. values = []
  692. for i in self.parModis:
  693. values.append(i.retRangeTime())
  694. self._cicle_values(obj, self._checkvaldict(values))
  695. def valBound(self):
  696. """Function return the Bounding Box of mosaic"""
  697. boundary = self.parModis[0].retBoundary()
  698. for i in range(1, len(self.parModis)):
  699. bound = self.parModis[i].retBoundary()
  700. if bound['min_lat'] < boundary['min_lat']:
  701. boundary['min_lat'] = bound['min_lat']
  702. if bound['min_lon'] < boundary['min_lon']:
  703. boundary['min_lon'] = bound['min_lon']
  704. if bound['max_lat'] > boundary['max_lat']:
  705. boundary['max_lat'] = bound['max_lat']
  706. if bound['max_lon'] > boundary['max_lon']:
  707. boundary['max_lon'] = bound['max_lon']
  708. self.boundary = boundary
  709. def valMeasuredParameter(self, obj):
  710. """Function to add ParameterName
  711. :param obj: element to add ParameterName
  712. """
  713. valuesQAStats = []
  714. valuesQAFlags = []
  715. valuesParameter = []
  716. for i in self.parModis:
  717. for val in i.retMeasure().values():
  718. valuesQAStats.append(val['QAStats'])
  719. valuesQAFlags.append(val['QAFlags'])
  720. valuesParameter.append(val['ParameterName'])
  721. for i in set(valuesParameter):
  722. pn = self.ElementTree.SubElement(obj, 'ParameterName')
  723. pn.text = i
  724. def valInputPointer(self, obj):
  725. """Function to add InputPointer
  726. :param obj: element to add InputPointer
  727. """
  728. for i in self.parModis:
  729. for v in i.retInputGranule():
  730. ip = self.ElementTree.SubElement(obj, 'InputPointer')
  731. ip.text = v
  732. def valPlatform(self, obj):
  733. """Function to add Platform elements
  734. :param obj: element to add Platform elements
  735. """
  736. valuesSName = []
  737. valuesInstr = []
  738. valuesSensor = []
  739. for i in self.parModis:
  740. valuesSName.append(i.retPlatform()['PlatformShortName'])
  741. valuesInstr.append(i.retPlatform()['InstrumentShortName'])
  742. valuesSensor.append(i.retPlatform()['SensorShortName'])
  743. for i in set(valuesSName):
  744. pn = self.ElementTree.SubElement(obj, 'PlatformShortName')
  745. pn.text = i
  746. valInstr = self._checkval(valuesInstr)
  747. valSens = self._checkval(valuesSensor)
  748. if len(valInstr) != len(valSens):
  749. raise Exception('Something wrong reading XML files')
  750. else:
  751. for i in range(len(valInstr)):
  752. ins = self.ElementTree.SubElement(obj, 'Instrument')
  753. pn = self.ElementTree.SubElement(ins, 'InstrumentShortName')
  754. pn.text = valInstr[i]
  755. sens = self.ElementTree.SubElement(ins, 'Sensor')
  756. ps = self.ElementTree.SubElement(sens, 'SensorShortName')
  757. ps.text = valSens[i]
  758. def valInsertTime(self, obj):
  759. """Function to add InsertTime elements
  760. :param obj: element to add InsertTime elements
  761. """
  762. values = []
  763. for i in self.parModis:
  764. values.append(i.retInsertTime())
  765. for i in set(values):
  766. gur = self.ElementTree.SubElement(obj, 'InsertTime')
  767. gur.text = i
  768. def valLastUpdate(self, obj):
  769. """Function to add LastUpdate elements
  770. :param obj: element to add LastUpdate elements
  771. """
  772. values = []
  773. for i in self.parModis:
  774. values.append(i.retLastUpdate())
  775. for i in set(values):
  776. gur = self.ElementTree.SubElement(obj, 'LastUpdate')
  777. gur.text = i
  778. def valDataGranule(self, obj):
  779. """Function to add DataFileContainer
  780. :param obj: element to add DataFileContainer
  781. """
  782. values = []
  783. for i in self.parModis:
  784. values.append(i.retDataGranule())
  785. for i in values:
  786. dfc = self.ElementTree.SubElement(obj, 'ECSDataGranule')
  787. self._cicle_values(dfc, i)
  788. def valBrowseProduct(self, obj):
  789. """Function to add BrowseGranuleId
  790. :param obj: element to add BrowseGranuleId
  791. """
  792. values = []
  793. for i in self.parModis:
  794. values.append(i.retBrowseProduct())
  795. for i in set(values):
  796. dfc = self.ElementTree.SubElement(obj, 'BrowseGranuleId')
  797. dfc.text = i
  798. def valPSA(self, obj):
  799. """Function to add PSA
  800. :param obj: element to add PSA
  801. """
  802. values = []
  803. for i in self.parModis:
  804. values.append(i.retPSA())
  805. for k in sorted(values[0].keys()):
  806. psa = self.ElementTree.SubElement(obj, 'PSA')
  807. psaname = self.ElementTree.SubElement(psa, 'PSAName')
  808. psaname.text = k
  809. for s in values:
  810. psaval = self.ElementTree.SubElement(psa, 'PSAValue')
  811. psaval.text = s[k]
  812. def writexml(self, outputname, pretty=True):
  813. """Write a xml file for a mosaic
  814. :param str outputname: the name of output xml file
  815. :param bool pretty: write prettyfy output, by default true
  816. """
  817. # the root element
  818. granule = self.ElementTree.Element('GranuleMetaDataFile')
  819. # add DTDVersion
  820. self.valDTD(granule)
  821. # add DataCenterId
  822. self.valDataCenter(granule)
  823. # add GranuleURMetaData
  824. gurmd = self.ElementTree.SubElement(granule, 'GranuleURMetaData')
  825. # add GranuleUR
  826. self.valGranuleUR(gurmd)
  827. # add dbID
  828. self.valDbID(gurmd)
  829. # add InsertTime
  830. self.valInsertTime(gurmd)
  831. # add LastUpdate
  832. self.valLastUpdate(gurmd)
  833. # add CollectionMetaData
  834. cmd = self.ElementTree.SubElement(gurmd, 'CollectionMetaData')
  835. self.valCollectionMetaData(cmd)
  836. # add DataFiles
  837. df = self.ElementTree.SubElement(gurmd, 'DataFiles')
  838. self.valDataFiles(df)
  839. # add ECSDataGranule
  840. self.valDataGranule(gurmd)
  841. # add PGEVersionClass
  842. pgevc = self.ElementTree.SubElement(gurmd, 'PGEVersionClass')
  843. self.valPGEVersion(pgevc)
  844. # add RangeDateTime
  845. rdt = self.ElementTree.SubElement(gurmd, 'RangeDateTime')
  846. self.valRangeTime(rdt)
  847. # add SpatialDomainContainer
  848. sdc = self.ElementTree.SubElement(gurmd, 'SpatialDomainContainer')
  849. hsdc = self.ElementTree.SubElement(sdc, 'HorizontalSpatialDomainContainer')
  850. gp = self.ElementTree.SubElement(hsdc, 'GPolygon')
  851. bound = self.ElementTree.SubElement(gp, 'Boundary')
  852. self.valBound()
  853. self._addPoint(bound, 'min_lon', 'max_lat')
  854. self._addPoint(bound, 'max_lon', 'max_lat')
  855. self._addPoint(bound, 'min_lon', 'min_lat')
  856. self._addPoint(bound, 'max_lon', 'min_lat')
  857. # add MeasuredParameter
  858. mp = self.ElementTree.SubElement(gurmd, 'MeasuredParameter')
  859. mpc = self.ElementTree.SubElement(mp, 'MeasuredParameterContainer')
  860. self.valMeasuredParameter(mpc)
  861. # add Platform
  862. pl = self.ElementTree.SubElement(gurmd, 'Platform')
  863. self.valPlatform(pl)
  864. # add PSAs
  865. psas = self.ElementTree.SubElement(gurmd, 'PSAs')
  866. # add all PSA
  867. self.valPSA(psas)
  868. # add InputGranule and InputPointer
  869. ig = self.ElementTree.SubElement(gurmd, 'InputGranule')
  870. self.valInputPointer(ig)
  871. # add BrowseProduct
  872. bp = self.ElementTree.SubElement(gurmd, 'BrowseProduct')
  873. self.valBrowseProduct(bp)
  874. output = open(outputname, 'w')
  875. output.write('<?xml version="1.0" encoding="UTF-8"?>')
  876. output.write('<!DOCTYPE GranuleMetaDataFile SYSTEM "http://ecsinfo.'
  877. 'gsfc.nasa.gov/ECSInfo/ecsmetadata/dtds/DPL/ECS/'
  878. 'ScienceGranuleMetadata.dtd">')
  879. if pretty:
  880. import xml.dom.minidom as minidom
  881. reparsed = minidom.parseString(self.ElementTree.tostring(granule))
  882. output.write(reparsed.toprettyxml(indent="\t"))
  883. else:
  884. output.write(self.ElementTree.tostring(granule))
  885. output.close()