reader.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import os
  2. import sys
  3. import json
  4. import glob
  5. import re
  6. from collections import OrderedDict
  7. import grass.script as gs
  8. # band reference should be required to have the format
  9. # <shortcut>_<band>
  10. # instead, the sensor name should be stored somewhere else,
  11. # and band names should be STAC common names, see
  12. # https://stacspec.org/
  13. # https://github.com/radiantearth/stac-spec/blob/master/extensions/eo/README.md#band-object
  14. # custom names must be possible
  15. class BandReferenceReaderError(Exception):
  16. pass
  17. class BandReferenceReader:
  18. """Band references reader"""
  19. def __init__(self):
  20. self._json_files = glob.glob(
  21. os.path.join(os.environ['GISBASE'], 'etc', 'g.bands', '*.json')
  22. )
  23. if not self._json_files:
  24. raise BandReferenceReaderError("No band definitions found")
  25. self._read_config()
  26. def _read_config(self):
  27. """Read configuration"""
  28. self.config = dict()
  29. for json_file in self._json_files:
  30. try:
  31. with open(json_file) as fd:
  32. config = json.load(
  33. fd,
  34. object_pairs_hook=OrderedDict
  35. )
  36. except json.decoder.JSONDecodeError as e:
  37. raise BandReferenceReaderError(
  38. "Unable to parse '{}': {}".format(
  39. json_file, e
  40. ))
  41. # check if configuration is valid
  42. self._check_config(config)
  43. self.config[os.path.basename(json_file)] = config
  44. @staticmethod
  45. def _check_config(config):
  46. """Check if config is valid
  47. :todo: check shortcut uniqueness
  48. :param dict config: configuration to be validated
  49. """
  50. for items in config.values():
  51. for item in ('shortcut', 'bands'):
  52. if item not in items.keys():
  53. raise BandReferenceReaderError(
  54. "Invalid band definition: <{}> is missing".format(item
  55. ))
  56. if len(items['bands']) < 1:
  57. raise BandReferenceReaderError(
  58. "Invalid band definition: no bands defined"
  59. )
  60. @staticmethod
  61. def _print_band_extended(band, item):
  62. """Print band-specific metadata
  63. :param str band: band identifier
  64. :param str item: items to be printed out
  65. """
  66. def print_kv(k, v, indent):
  67. if isinstance(v, OrderedDict):
  68. print ('{}{}:'.format(' ' * indent * 2, k))
  69. for ki, vi in v.items():
  70. print_kv(ki, vi, indent * 2)
  71. else:
  72. print ('{}{}: {}'.format(' ' * indent * 2, k, v))
  73. indent = 4
  74. print ('{}band: {}'.format(
  75. ' ' * indent, band
  76. ))
  77. for k, v in item[band].items():
  78. print_kv(k, v, indent)
  79. def _print_band(self, shortcut, band, tag=None):
  80. sys.stdout.write(self._band_identifier(shortcut, band))
  81. if tag:
  82. sys.stdout.write(' {}'.format(tag))
  83. sys.stdout.write(os.linesep)
  84. def print_info(self, shortcut=None, band=None, extended=False):
  85. """Prints band reference information to stdout.
  86. Can be filtered by shortcut or band identifier.
  87. :param str shortcut: shortcut to filter (eg. S2) or None
  88. :param str band: band (eg. 1) or None
  89. :param bool extended: print also extended metadata
  90. """
  91. found = False
  92. for root in self.config.values():
  93. for item in root.values():
  94. try:
  95. if shortcut and re.match(shortcut, item['shortcut']) is None:
  96. continue
  97. except re.error as e:
  98. raise BandReferenceReaderError(
  99. "Invalid pattern: {}".format(e)
  100. )
  101. found = True
  102. if band and band not in item['bands']:
  103. raise BandReferenceReaderError(
  104. "Band <{}> not found in <{}>".format(
  105. band, shortcut
  106. ))
  107. # print generic information
  108. if extended:
  109. for subitem in item.keys():
  110. if subitem == 'bands':
  111. # bands item is processed bellow
  112. continue
  113. print ('{}: {}'.format(
  114. subitem, item[subitem]
  115. ))
  116. # print detailed band information
  117. if band:
  118. self._print_band_extended(band, item['bands'])
  119. else:
  120. for iband in item['bands']:
  121. self._print_band_extended(iband, item['bands'])
  122. else:
  123. # basic information only
  124. if band:
  125. self._print_band(
  126. item['shortcut'], band,
  127. item['bands'][band].get('tag')
  128. )
  129. else:
  130. for iband in item['bands']:
  131. self._print_band(
  132. item['shortcut'], iband,
  133. item['bands'][iband].get('tag')
  134. )
  135. # raise error when defined shortcut not found
  136. if shortcut and not found:
  137. raise BandReferenceReaderError(
  138. "Band reference <{}> not found".format(shortcut)
  139. )
  140. def find_file(self, band_reference):
  141. """Find file by band reference.
  142. Match is case-insensitive.
  143. :param str band_reference: band reference identifier to search for (eg. S2_1)
  144. :return str: file basename if found or None
  145. """
  146. try:
  147. shortcut, band = band_reference.split('_')
  148. except ValueError:
  149. # raise BandReferenceReaderError("Invalid band identifier <{}>".format(
  150. # band_reference
  151. # ))
  152. shortcut = "unknown"
  153. band = band_reference
  154. for filename, config in self.config.items():
  155. for root in config.keys():
  156. if config[root]['shortcut'].upper() == shortcut.upper() and \
  157. band.upper() in map(lambda x: x.upper(), config[root]['bands'].keys()):
  158. return filename
  159. return None
  160. def get_bands(self):
  161. """Get list of band identifiers.
  162. :return list: list of valid band identifiers
  163. """
  164. bands = []
  165. for root in self.config.values():
  166. for item in root.values():
  167. for band in item['bands']:
  168. bands.append(
  169. self._band_identifier(item['shortcut'], band)
  170. )
  171. return bands
  172. @staticmethod
  173. def _band_identifier(shortcut, band):
  174. return '{}_{}'.format(shortcut, band)