reader.py 6.5 KB

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