Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3# gms_preprocessing, spatial and spectral homogenization of satellite remote sensing data
4#
5# Copyright (C) 2020 Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
6#
7# This software was developed within the context of the GeoMultiSens project funded
8# by the German Federal Ministry of Education and Research
9# (project grant code: 01 IS 14 010 A-C).
10#
11# This program is free software: you can redistribute it and/or modify it under
12# the terms of the GNU General Public License as published by the Free Software
13# Foundation, either version 3 of the License, or (at your option) any later version.
14# Please note the following exception: `gms_preprocessing` depends on tqdm, which
15# is distributed under the Mozilla Public Licence (MPL) v2.0 except for the files
16# "tqdm/_tqdm.py", "setup.py", "README.rst", "MANIFEST.in" and ".gitignore".
17# Details can be found here: https://github.com/tqdm/tqdm/blob/master/LICENCE.
18#
19# This program is distributed in the hope that it will be useful, but WITHOUT
20# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
22# details.
23#
24# You should have received a copy of the GNU Lesser General Public License along
25# with this program. If not, see <http://www.gnu.org/licenses/>.
27"""Module 'metadata' for handling any type of metadata of GeoMultiSens compatible sensor systems."""
29from __future__ import (division, print_function, unicode_literals, absolute_import)
31import collections
32import datetime
33import glob
34import math
35import os
36import re
37import sys
38import warnings
39import iso8601
40import xml.etree.ElementTree as ET
41from typing import List, TYPE_CHECKING # noqa F401 # flake8 issue
43import numpy as np
44import pyproj
45from matplotlib import dates as mdates
46from pyorbital import astronomy
47from natsort import natsorted
49from py_tools_ds.geo.map_info import geotransform2mapinfo
50from py_tools_ds.geo.projection import WKT2EPSG
51from pyrsr import RSR
52from sicor.options import get_options as get_ac_options
54from ..options.config import GMS_config as CFG
55from ..io.input_reader import open_specific_file_within_archive, Solar_Irradiance_reader, SRF_Reader
56from ..io.output_writer import enviHdr_keyOrder
57from ..algorithms import geoprocessing as GEOP
58from ..misc import helper_functions as HLP_F
59from ..misc import database_tools as DB_T
60from ..misc.path_generator import path_generator, get_path_ac_options
61from ..misc.definition_dicts import get_GMS_sensorcode, is_dataset_provided_as_fullScene, datasetid_to_sat_sen
62from ..misc.exceptions import ACNotSupportedError
64if TYPE_CHECKING:
65 from ..model.gms_object import GMS_identifier # noqa F401 # flake8 issue
67__author__ = 'Daniel Scheffler', 'Robert Behling'
70class METADATA(object):
71 def __init__(self, GMS_id):
72 # private attributes
73 self._AcqDateTime = None
75 # unpack GMS_identifier
76 self.GMS_identifier = GMS_id
77 self.image_type = GMS_id.image_type
78 self.Satellite = GMS_id.satellite
79 self.Sensor = GMS_id.sensor
80 self.Subsystem = GMS_id.subsystem
81 self.proc_level = GMS_id.proc_level
82 self.logger = GMS_id.logger
84 self.Dataname = ''
85 self.FolderOrArchive = ''
86 self.Metafile = "" # File containing image metadata (automatically found)
87 self.EntityID = "" # ID to identify the original scene
88 self.SceneID = '' # postgreSQL-database identifier
89 self.Sensormode = ""
90 self.gResolution = -99. # resolution [m]
91 self.AcqDate = "" # YYYY-MM-DD
92 self.AcqTime = "" # HH:MM:SS
93 self.Offsets = {} # Dict with offset for each band (radiance)
94 self.Gains = {} # Dict with gain for each band (radiance)
95 self.OffsetsRef = {} # Dict with offset for each band for conversion to Reflectance (Landsat8)
96 self.GainsRef = {} # Dict with gain for each band for conversion to Reflectance (Landsat8)
97 self.CWL = {}
98 self.FWHM = {}
99 self.ProcLCode = "" # processing Level: Sensor specific Code
100 self.ProcLStr = "" # processing Level: Sensor independent String (raw,rad cal, rad+geom cal, ortho)
101 self.SunElevation = -99.0 # Sun elevation angle at center of product [Deg]
102 # Sun azimuth angle at center of product, in degrees from North (clockwise) at the time of the first image line
103 self.SunAzimuth = -99.0
104 self.SolIrradiance = []
105 self.ThermalConstK1 = {}
106 self.ThermalConstK2 = {}
107 self.EarthSunDist = 1.0
108 # viewing zenith angle of the Sensor (offNadir) [Deg] (usually) in Case of RapidEye "+"
109 # being East and “-” being West
110 self.ViewingAngle = -99.0
111 self.ViewingAngle_arrProv = {}
112 # viewing azimuth angle. The angle between the view direction of the satellite and a line perpendicular to
113 # the image or tile center.[Deg]
114 self.IncidenceAngle = -9999.99
115 self.IncidenceAngle_arrProv = {}
116 self.FOV = 9999.99 # field of view of the sensor [Deg]
117 # Sensor specific quality code: See ro2/behling/Satelliten/doc_allg/ReadOutMetadata/SatMetadata.xls
118 self.Quality = []
119 self.PhysUnit = "DN"
120 # Factor to get reflectance values in [0-1]. Sentinel2A provides scaling factors for the Level1C
121 # TOA-reflectance products
122 self.ScaleFactor = -99
123 self.CS_EPSG = -99. # EPSG-Code of coordinate system
124 self.CS_TYPE = ""
125 self.CS_DATUM = ""
126 self.CS_UTM_ZONE = -99.
127 self.WRS_path = -99.
128 self.WRS_row = -99.
129 # List of Corner Coordinates in order of Lon/Lat/DATA_X/Data_Y for all given image corners
130 self.CornerTieP_LonLat = []
131 self.CornerTieP_UTM = []
132 self.LayerBandsAssignment = [] # List of spectral bands in the same order as they are stored in the rasterfile.
133 self.additional = []
134 self.image_type = 'RSD'
135 self.orbitParams = {}
136 self.overpassDurationSec = -99.
137 self.scene_length = -99.
138 self.rows = -99.
139 self.cols = -99.
140 self.bands = -99.
141 self.nBands = -99.
142 self.map_info = []
143 self.projection = ""
144 self.wvlUnit = ""
145 self.spec_vals = {'fill': None, 'zero': None, 'saturated': None}
147 self.version_gms_preprocessing = CFG.version
148 self.versionalias_gms_preprocessing = CFG.versionalias
150 def read_meta(self, scene_ID, stacked_image, data_folderOrArchive, LayerBandsAssignment=None):
151 """
152 Read metadata.
153 """
155 self.SceneID = scene_ID
156 self.Dataname = stacked_image
157 self.FolderOrArchive = data_folderOrArchive
158 self.LayerBandsAssignment = LayerBandsAssignment if LayerBandsAssignment else []
160 if re.search(r"SPOT", self.Satellite, re.I):
161 self.Read_SPOT_dimap2()
162 elif re.search(r"Terra", self.Satellite, re.I):
163 self.Read_ASTER_hdffile(self.Subsystem)
164 elif re.search(r"Sentinel-2A", self.Satellite, re.I) or re.search(r"Sentinel-2B", self.Satellite, re.I):
165 self.Read_Sentinel2_xmls()
166 elif re.search(r"LANDSAT", self.Satellite, re.I):
167 self.Read_LANDSAT_mtltxt(self.LayerBandsAssignment)
168 elif re.search(r"RapidEye", self.Satellite, re.I):
169 self.Read_RE_metaxml()
170 elif re.search(r"ALOS", self.Satellite, re.I):
171 self.Read_ALOS_summary()
172 self.Read_ALOS_LEADER() # for gains and offsets
173 else:
174 raise NotImplementedError("%s is not a supported sensor or sensorname is misspelled." % self.Satellite)
176 return self
178 def __getstate__(self):
179 """Defines how the attributes of MetaObj instances are pickled."""
180 if self.logger:
181 self.logger.close()
183 return self.__dict__
185 @property
186 def AcqDateTime(self):
187 """Returns a datetime.datetime object containing date, time and timezone (UTC time)."""
188 if not self._AcqDateTime and self.AcqDate and self.AcqTime:
189 self._AcqDateTime = datetime.datetime.strptime('%s %s%s' % (self.AcqDate, self.AcqTime, '.000000+0000'),
190 '%Y-%m-%d %H:%M:%S.%f%z')
192 return self._AcqDateTime
194 @AcqDateTime.setter
195 def AcqDateTime(self, DateTime):
196 # type: (datetime.datetime) -> None
197 if isinstance(DateTime, str):
198 self._AcqDateTime = datetime.datetime.strptime(DateTime, '%Y-%m-%d %H:%M:%S.%f%z')
199 elif isinstance(DateTime, datetime.datetime):
200 self._AcqDateTime = DateTime
202 self.AcqDate = DateTime.strftime('%Y-%m-%d')
203 self.AcqTime = DateTime.strftime('%H:%M:%S')
205 @property
206 def overview(self):
207 attr2include = \
208 ['Dataname', 'Metafile', 'EntityID', 'Satellite', 'Sensor', 'Subsystem', 'gResolution', 'AcqDate',
209 'AcqTime', 'CWL', 'FWHM', 'Offsets', 'Gains', 'ProcLCode', 'ProcLStr', 'SunElevation', 'SunAzimuth',
210 'ViewingAngle', 'IncidenceAngle', 'FOV', 'SolIrradiance', 'ThermalConstK1', 'ThermalConstK2',
211 'EarthSunDist', 'Quality', 'PhysUnit', 'additional', 'GainsRef', 'OffsetsRef', 'CornerTieP_LonLat',
212 'CS_EPSG', 'CS_TYPE', 'CS_DATUM', 'CS_UTM_ZONE', 'LayerBandsAssignment']
213 return collections.OrderedDict((attr, getattr(self, attr)) for attr in attr2include)
215 @property
216 def LayerBandsAssignment_full(self):
217 # type: () -> list
218 """Return complete LayerBandsAssignment without excluding thermal or panchromatic bands.
220 NOTE: CFG.sort_bands_by_cwl is respected, so returned list may be sorted by central wavelength
221 """
222 return get_LayerBandsAssignment(self.GMS_identifier, no_thermal=False, no_pan=False, return_fullLBA=True)
224 @property
225 def bandnames(self):
226 return [('Band %s' % i) for i in self.LayerBandsAssignment]
228 def Read_SPOT_dimap2(self):
229 """----METHOD_2------------------------------------------------------------
230 # read metadata from spot dimap file
231 """
233 # self.default_attr()
234 if os.path.isdir(self.FolderOrArchive):
235 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '*/scene01/metadata.dim'))
236 assert len(glob_res) > 0, 'No metadata.dim file can be found in %s!' % self.FolderOrArchive
237 self.Metafile = glob_res[0]
238 dim_ = open(self.Metafile, "r").read()
239 else: # archive
240 dim_, self.Metafile = open_specific_file_within_archive(self.FolderOrArchive, '*/scene01/metadata.dim')
242 # special values
243 h1 = re.findall(r"<SPECIAL_VALUE_INDEX>([a-zA-Z0-9]*)</SPECIAL_VALUE_INDEX>", dim_, re.I)
244 h2 = re.findall(r"<SPECIAL_VALUE_TEXT>([a-zA-Z0-9]*)</SPECIAL_VALUE_TEXT>", dim_, re.I)
245 self.additional.append(["SpecialValues:"])
246 for ii, ind in enumerate(h1):
247 self.additional[0].append(["%s:%s" % (ind, h2[ii])])
249 # EntityID
250 h3 = re.search(r"<SOURCE_ID>([a-zA-Z0-9]*)</SOURCE_ID>", dim_, re.I)
251 self.EntityID = h3.group(1)
253 # AcqDate
254 h4 = re.search(r"<IMAGING_DATE>([0-9-]*)</IMAGING_DATE>", dim_, re.I)
255 AcqDate = h4.group(1)
257 # Earth sun distance
258 self.EarthSunDist = self.get_EarthSunDistance(AcqDate)
260 # AcqTime
261 h5 = re.search(r"<IMAGING_TIME>([0-9:]*)</IMAGING_TIME>", dim_, re.I)
262 AcqTime = h5.group(1)
264 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
265 self.AcqDateTime = datetime.datetime.strptime(
266 '%s %s%s' % (AcqDate, AcqTime, '.000000+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
268 # Satellite + Sensor
269 h6 = re.search(r"<MISSION>([a-zA-Z]*)</MISSION>[a-zA-Z0-9\s]*"
270 r"<MISSION_INDEX>([0-9]*)</MISSION_INDEX>[a-zA-Z0-9\s]*"
271 r"<INSTRUMENT>([a-zA-Z]*)</INSTRUMENT>[a-zA-Z0-9\s]*"
272 r"<INSTRUMENT_INDEX>([0-9]*)</INSTRUMENT_INDEX>[a-zA-Z0-9\s]*"
273 r"<SENSOR_CODE>([a-zA-Z0-9]*)</SENSOR_CODE>",
274 dim_, re.I)
275 self.Satellite = "-".join([h6.group(1), h6.group(2)])
276 self.Sensor = "".join([h6.group(3), h6.group(4), h6.group(5)])
278 # Angles: incidence angle, sunAzimuth, sunElevation
279 h7 = re.search(r"<INCIDENCE_ANGLE>(.*)</INCIDENCE_ANGLE>[\s\S]*"
280 r"<SUN_AZIMUTH>(.*)</SUN_AZIMUTH>[\s\S]"
281 r"*<SUN_ELEVATION>(.*)</SUN_ELEVATION>", dim_, re.I)
282 self.IncidenceAngle = float(h7.group(1))
283 self.SunAzimuth = float(h7.group(2))
284 self.SunElevation = float(h7.group(3))
286 # Viewing Angle: not always included in the metadata.dim file
287 h8 = re.search(r"<VIEWING_ANGLE>(.*)</VIEWING_ANGLE>", dim_, re.I)
288 if type(h8).__name__ == 'NoneType':
289 self.ViewingAngle = 0
290 else:
291 self.ViewingAngle = float(h8.group(1))
293 # Field of View
294 self.FOV = get_FieldOfView(self.GMS_identifier)
296 # Units
297 self.ScaleFactor = 1
298 self.PhysUnit = "DN"
300 # ProcLevel
301 h11 = re.search(r"<PROCESSING_LEVEL>([a-zA-Z0-9]*)</PROCESSING_LEVEL>", dim_, re.I)
302 self.ProcLCode = h11.group(1)
304 # Quality
305 h12 = re.findall(r"<Bad_Pixel>[\s]*"
306 r"<PIXEL_INDEX>([0-9]*)</PIXEL_INDEX>[\s]*"
307 r"<BAD_PIXEL_STATUS>([^<]*)</BAD_PIXEL_STATUS>"
308 r"</Bad_Pixel>", dim_,
309 re.I)
310 self.Quality = h12
312 # Coordinate Reference System
313 re_CS_EPSG = re.search(r'<HORIZONTAL_CS_CODE>epsg:([0-9]*)</HORIZONTAL_CS_CODE>', dim_, re.I)
314 re_CS_TYPE = re.search(r'<HORIZONTAL_CS_TYPE>([a-zA-Z0-9]*)</HORIZONTAL_CS_TYPE>', dim_, re.I)
315 re_CS_DATUM = re.search(r'<HORIZONTAL_CS_NAME>([\w+\s]*)</HORIZONTAL_CS_NAME>', dim_, re.I)
316 self.CS_EPSG = int(re_CS_EPSG.group(1)) if re_CS_EPSG is not None else self.CS_EPSG
317 self.CS_TYPE = 'LonLat' if re_CS_TYPE is not None and re_CS_TYPE.group(1) == 'GEOGRAPHIC' else 'UTM' \
318 if re_CS_TYPE is not None and re_CS_TYPE.group(1) == 'UTM' else self.CS_TYPE
319 self.CS_DATUM = 'WGS84' if re_CS_DATUM is not None and re_CS_DATUM.group(1) == 'WGS 84' else self.CS_DATUM
321 # Corner Coordinates
322 h121 = re.findall(r"<TIE_POINT_CRS_X>(.*)</TIE_POINT_CRS_X>", dim_, re.I)
323 h122 = re.findall(r"<TIE_POINT_CRS_Y>(.*)</TIE_POINT_CRS_Y>", dim_, re.I)
324 if len(h121) == 4 and self.CS_TYPE == 'LonLat':
325 # Set Corner Tie Point Coordinates (order = UL,UR,LL,LR)
326 self.CornerTieP_LonLat.append(tuple([float(h121[0]), float(h122[0])])) # UL
327 self.CornerTieP_LonLat.append(tuple([float(h121[1]), float(h122[1])])) # UR
328 self.CornerTieP_LonLat.append(tuple([float(h121[3]), float(h122[3])])) # LL
329 self.CornerTieP_LonLat.append(tuple([float(h121[2]), float(h122[2])])) # LR
330 # ul_lon,ul_lat = self.inDs.GetGCPs()[0].GCPX,self.inDs.GetGCPs()[0].GCPY # funktioniert nur bei SPOT
331 # lr_lon,lr_lat = self.inDs.GetGCPs()[2].GCPX,self.inDs.GetGCPs()[2].GCPY
333 ##########################
334 # band specific metadata #
335 ##########################
337 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
339 # Gains and Offsets
340 h9 = re.search(r"<Image_Interpretation>[\s\S]*</Image_Interpretation>", dim_, re.I)
341 h9_ = h9.group(0)
342 h91 = re.findall(r"<PHYSICAL_UNIT>([^<]*)</PHYSICAL_UNIT>", h9_, re.I)
343 h92 = re.findall(r"<PHYSICAL_BIAS>([^<]*)</PHYSICAL_BIAS>", h9_, re.I)
344 h93 = re.findall(r"<PHYSICAL_GAIN>([^<]*)</PHYSICAL_GAIN>", h9_, re.I)
345 self.additional.append(["Physical Units per band:"])
346 for ii, ind in enumerate(h91): # FIXME does not respect sort_bands_by_cwl
347 self.additional[-1].append(ind)
348 # Offsets
349 for ii, (ind, band) in enumerate(zip(h92, LBA_full_sorted)):
350 self.Offsets[band] = float(ind)
351 # Gains
352 for ii, (ind, band) in enumerate(zip(h93, LBA_full_sorted)):
353 # gains in dimap file represent reciprocal 1/gain
354 self.Gains[band] = 1 / float(ind)
356 # Solar irradiance, central wavelengths , full width half maximum per band
357 self.wvlUnit = 'Nanometers'
358 # derive number of bands from number of given gains/offsets in header file or from raster data
359 # noinspection PyBroadException
360 try:
361 self.nBands = (np.mean([len(self.Gains), len(self.Offsets)])
362 if np.std([len(self.Gains), len(self.Offsets)]) == 0 and len(self.Gains) != 0 else
363 GEOP.GEOPROCESSING(self.Dataname, self.logger).bands)
364 except Exception:
365 self.logger.warning('Unable to get number of bands for the dataset %s Provider values are used for '
366 'solar irradiation, CWL and FWHM!.' % self.Dataname)
367 self.LayerBandsAssignment = get_LayerBandsAssignment(self.GMS_identifier, self.nBands)
369 # Exact values calculated based in SRFs
370 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band()
371 # Provider values
372 if not self.SolIrradiance:
373 h10 = re.search(r"<Solar_Irradiance>[\s\S]*"
374 r"</Solar_Irradiance>", dim_, re.I)
375 h10_ = h10.group(0)
376 h101 = re.findall(r"<SOLAR_IRRADIANCE_VALUE>([^<]*)"
377 r"</SOLAR_IRRADIANCE_VALUE>", h10_, re.I)
378 if h101:
379 self.SolIrradiance = dict(zip(LBA_full_sorted, h101))
380 # self.additional.append(["Solar Irradiance per band:"])
381 # for ii,ind in enumerate(h101):
382 # self.additional[-1].append(ind)
383 else: # Preconfigured Irradiation values
384 spot_irr_dic = {
385 'SPOT1a': dict(zip(LBA_full_sorted, [1855., 1615., 1090., 1680.])),
386 'SPOT1b': dict(zip(LBA_full_sorted, [1845., 1575., 1040., 1690.])),
387 'SPOT2a': dict(zip(LBA_full_sorted, [1865., 1620., 1085., 1705.])),
388 'SPOT2b': dict(zip(LBA_full_sorted, [1865., 1615., 1090., 1670.])),
389 'SPOT3a': dict(zip(LBA_full_sorted, [1854., 1580., 1065., 1668.])),
390 'SPOT3b': dict(zip(LBA_full_sorted, [1855., 1597., 1067., 1667.])),
391 'SPOT4a': dict(zip(LBA_full_sorted, [1843., 1568., 1052., 233., 1568.])),
392 'SPOT4b': dict(zip(LBA_full_sorted, [1851., 1586., 1054., 240., 1586.])),
393 'SPOT5a': dict(zip(LBA_full_sorted, [1858., 1573., 1043., 236., 1762.])),
394 'SPOT5b': dict(zip(LBA_full_sorted, [1858., 1575., 1047., 234., 1773.]))}
395 self.SolIrradiance = spot_irr_dic[get_GMS_sensorcode(self.GMS_identifier)]
396 # Preconfigured CWLs -- # ref: Guyot & Gu (1994): 'Effect of Radiometric Corrections on NDVI-Determined
397 # from SPOT-HRV and Landsat-TM Data'; Hill 1990 Comparative Analysis of Landsat-5 TM and SPOT HRV-1 Data for
398 # Use in Multiple Sensor Approaches ; # resource: SPOT techical report: Resolutions and spectral modes
399 sensorcode = get_GMS_sensorcode(self.GMS_identifier)[:2]
400 if sensorcode in ['SPOT1a', 'SPOT1b', 'SPOT2a', 'SPOT2b', 'SPOT3a', 'SPOT3b']:
401 self.CWL = dict(zip(LBA_full_sorted, [545., 638., 819., 615.]))
402 elif sensorcode in ['SPOT4a', 'SPOT4b']:
403 self.CWL = dict(zip(LBA_full_sorted, [540., 650., 835., 1630., 645.]))
404 elif sensorcode == ['SPOT5a', 'SPOT5b']:
405 self.CWL = dict(zip(LBA_full_sorted, [540., 650., 835., 1630., 595.]))
407 self.orbitParams = get_orbit_params(self.GMS_identifier)
408 self.filter_layerdependent_metadata()
409 self.spec_vals = get_special_values(self.GMS_identifier)
411 def Read_LANDSAT_mtltxt(self, LayerBandsAssignment):
412 """----METHOD_3------------------------------------------------------------
413 read metadata from LANDSAT metafile: <dataname>.MTL.txt. Metadatafile of LPGS processing chain
414 :param LayerBandsAssignment:
415 """
417 # self.default_attr()
418 self.LayerBandsAssignment = LayerBandsAssignment
419 self.nBands = len(LayerBandsAssignment)
420 if os.path.isdir(self.FolderOrArchive):
421 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '*MTL.txt'))
422 assert len(glob_res) > 0, 'No *.MTL metadata file can be found in %s!' % self.FolderOrArchive
423 self.Metafile = glob_res[0]
424 mtl_ = open(self.Metafile, "r").read()
425 else: # archive
426 mtl_, self.Metafile = open_specific_file_within_archive(self.FolderOrArchive, '*MTL.txt')
428 # Processing Level
429 h1 = re.search(r'PRODUCT_TYPE = "([a-zA-Z0-9]*)"', mtl_, re.I)
430 if h1 is None:
431 h1 = re.search(r'DATA_TYPE = "([a-zA-Z0-9]*)"', mtl_, re.I)
432 self.ProcLCode = h1.group(1)
434 # Satellite + Sensor + Sensor Mode
435 h2 = re.search(r'SPACECRAFT_ID = "([a-zA-Z0-9_]*)"[\s]*'
436 r'SENSOR_ID = "([a-zA-Z0-9+]*)"[\s]*'
437 r'SENSOR_MODE = "([\S]*)"',
438 mtl_, re.I)
439 if h2:
440 self.Satellite = 'Landsat-%s' % re.search(r'LANDSAT[\D]*([0-9])', h2.group(1), re.I).group(1)
441 self.Sensor = h2.group(2)
442 self.Sensormode = h2.group(3)
443 else:
444 h2a = re.search(r'SPACECRAFT_ID = "([a-zA-Z0-9_]*)"', mtl_, re.I)
445 h2b = re.search(r'SENSOR_ID = "([a-zA-Z0-9_+]*)"', mtl_, re.I)
446 h2c = re.search(r'SENSOR_MODE = "([a-zA-Z0-9_+]*)"', mtl_, re.I)
447 self.Satellite = 'Landsat-%s' % re.search(r'LANDSAT[\D]*([0-9])', h2a.group(1), re.I).group(1)
448 self.Sensor = h2b.group(1)
449 self.Sensormode = h2c.group(1) if h2c is not None else self.Sensormode # Landsat-8
450 self.Sensor = 'ETM+' if self.Sensor == 'ETM' else self.Sensor
452 # EntityID
453 h2c = re.search(r'LANDSAT_SCENE_ID = "([A-Z0-9]*)"', mtl_, re.I)
454 if h2c:
455 self.EntityID = h2c.group(1)
457 # Acquisition Date + Time
458 h3 = re.search(r'ACQUISITION_DATE = ([0-9-]*)[\s]*'
459 r'SCENE_CENTER_SCAN_TIME = "?([0-9:]*)"?',
460 mtl_, re.I)
461 if h3 is None:
462 h3 = re.search(r'DATE_ACQUIRED = ([0-9-]*)[\s]*'
463 r'SCENE_CENTER_TIME = "?([0-9:]*)"?',
464 mtl_, re.I)
465 AcqDate = h3.group(1)
466 AcqTime = h3.group(2)
468 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
469 self.AcqDateTime = datetime.datetime.strptime(
470 '%s %s%s' % (AcqDate, AcqTime, '.000000+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
472 # Earth sun distance
473 self.EarthSunDist = self.get_EarthSunDistance(self.AcqDate)
475 # Units
476 self.ScaleFactor = 1
477 self.PhysUnit = "DN"
479 # Angles: incidence angle, sunAzimuth, sunElevation, field of view
480 h5 = re.search(r"SUN_AZIMUTH = ([\S]*)[\s]*"
481 r"SUN_ELEVATION = ([\S]*)",
482 mtl_, re.I)
483 self.SunAzimuth = float(h5.group(1))
484 self.SunElevation = float(h5.group(2))
485 self.FOV = get_FieldOfView(self.GMS_identifier)
487 # Quality
488 h6 = re.search(r"GROUP = CORRECTIONS_APPLIED[\s\S]*"
489 r"END_GROUP = CORRECTIONS_APPLIED",
490 mtl_, re.I)
491 if h6 is None:
492 h6 = re.search(r"GROUP = IMAGE_ATTRIBUTES[\s\S]*"
493 r"END_GROUP = IMAGE_ATTRIBUTES",
494 mtl_, re.I)
496 h6_ = h6.group(0)
497 h61 = (h6_.split("\n"))
498 x = 0
499 for i in h61:
500 if x == 0 or x + 1 == len(h61):
501 pass
502 else:
503 i_ = i.strip().replace('"', "")
504 self.Quality.append(i_.split(" = "))
505 x += 1
507 # Additional: coordinate system, geom. Resolution
508 h7 = re.search(r"GROUP = PROJECTION_PARAMETERS[\s\S]*END_GROUP = L1_METADATA_FILE", mtl_, re.I)
509 h7_ = h7.group(0)
510 h71 = (h7_.split("\n"))
511 for x, i in enumerate(h71):
512 if re.search(r"Group", i, re.I):
513 pass
514 else:
515 i_ = i.strip().replace('"', "")
516 self.additional.append(i_.split(" = "))
517 re_CS_TYPE = re.search(r'MAP_PROJECTION = "([\w+\s]*)"', h7_, re.I)
518 re_CS_DATUM = re.search(r'DATUM = "([\w+\s]*)"', h7_, re.I)
519 re_CS_UTM_ZONE = re.search(r'ZONE_NUMBER = ([0-9]*)\n', mtl_, re.I)
520 re_CS_UTM_ZONE = re.search(r'UTM_ZONE = ([0-9]*)\n', h7_,
521 re.I) if re_CS_UTM_ZONE is None else re_CS_UTM_ZONE # Landsat8
522 self.CS_TYPE = 'LonLat' if re_CS_TYPE is not None and re_CS_TYPE.group(1) == 'GEOGRAPHIC' else 'UTM' \
523 if re_CS_TYPE is not None and re_CS_TYPE.group(1) == 'UTM' else self.CS_TYPE
524 self.CS_DATUM = 'WGS84' if re_CS_DATUM is not None and re_CS_DATUM.group(1) == 'WGS84' else self.CS_DATUM
525 self.CS_UTM_ZONE = int(re_CS_UTM_ZONE.group(1)) if re_CS_UTM_ZONE is not None else self.CS_UTM_ZONE
526 # viewing Angle
527 self.ViewingAngle = 0
528 # Landsat8
529 h8 = re.search(r"ROLL_ANGLE = ([\S]*)", mtl_, re.I)
530 if h8:
531 self.ViewingAngle = float(h8.group(1))
533 # Corner Coordinates ## Lon/Lat for all given image corners UL,UR,LL,LR (tuples)
534 h10_UL = re.findall(r"PRODUCT_UL_CORNER_[A-Z]+ = (.*)[\S]*", mtl_, re.I)
535 h10_UR = re.findall(r"PRODUCT_UR_CORNER_[A-Z]+ = (.*)[\S]*", mtl_, re.I)
536 h10_LL = re.findall(r"PRODUCT_LL_CORNER_[A-Z]+ = (.*)[\S]*", mtl_, re.I)
537 h10_LR = re.findall(r"PRODUCT_LR_CORNER_[A-Z]+ = (.*)[\S]*", mtl_, re.I)
538 if not h10_UL: # Landsat8
539 h10_UL = re.findall(r"CORNER_UL_[\S]+ = (.*)[\S]*", mtl_, re.I)
540 h10_UR = re.findall(r"CORNER_UR_[\S]+ = (.*)[\S]*", mtl_, re.I)
541 h10_LL = re.findall(r"CORNER_LL_[\S]+ = (.*)[\S]*", mtl_, re.I)
542 h10_LR = re.findall(r"CORNER_LR_[\S]+ = (.*)[\S]*", mtl_, re.I)
543 if h10_UL: # Set Corner Tie Point Coordinates (order = UL,UR,LL,LR)
544 self.CornerTieP_LonLat.append(tuple([float(h10_UL[i]) for i in [1, 0]]))
545 self.CornerTieP_LonLat.append(tuple([float(h10_UR[i]) for i in [1, 0]]))
546 self.CornerTieP_LonLat.append(tuple([float(h10_LL[i]) for i in [1, 0]]))
547 self.CornerTieP_LonLat.append(tuple([float(h10_LR[i]) for i in [1, 0]]))
548 self.CornerTieP_UTM.append(tuple([float(h10_UL[i]) for i in [2, 3]]))
549 self.CornerTieP_UTM.append(tuple([float(h10_UR[i]) for i in [2, 3]]))
550 self.CornerTieP_UTM.append(tuple([float(h10_LL[i]) for i in [2, 3]]))
551 self.CornerTieP_UTM.append(tuple([float(h10_LR[i]) for i in [2, 3]]))
553 # WRS path/row
554 h11_p = re.search(r'WRS_PATH = ([0-9]*)', mtl_, re.I)
555 if h11_p:
556 self.WRS_path = h11_p.group(1)
557 h11_r1 = re.search(r'STARTING_ROW = ([0-9]*)', mtl_, re.I)
558 h11_r2 = re.search(r'ENDING_ROW = ([0-9]*)', mtl_, re.I)
559 if h11_r1 is None: # Landsat-8
560 h11_r = re.search(r'WRS_ROW = ([0-9]*)', mtl_, re.I)
561 self.WRS_row = int(h11_r.group(1))
562 else:
563 self.WRS_row = int(h11_r1.group(1)) if h11_r1.group(1) == h11_r2.group(1) else self.WRS_row
565 # Fill missing values
566 if self.EntityID == '':
567 self.logger.info('Scene-ID could not be extracted and has to be retrieved from %s metadata database...'
568 % self.Satellite)
569 result = DB_T.get_info_from_postgreSQLdb(CFG.conn_database, 'scenes', ['entityID'],
570 {'id': self.SceneID})
572 if len(result) == 1: # e.g. [('LE71950282003121EDC00',)]
573 self.EntityID = result[0][0]
574 elif len(result) == 0:
575 self.logger.warning("Scene ID could not be assigned because no dataset matching to the query "
576 "parameters could be found in metadata database.")
577 else: # e.g. [('LE71950282003121EDC00',), ('LE71950282003105ASN00',)]
578 self.logger.warning("Scene ID could not be assigned because %s datasets matching to the query "
579 "parameters were found in metadata database." % len(result))
580 # if self.EntityID=='':
581 # dataset = 'LANDSAT_TM' if self.Satellite=='Landsat-5' else \
582 # 'LANDSAT_ETM' if self.Satellite=='Landsat-7' else 'LANDSAT_8' if self.Satellite=='Landsat-8' else ''
583 # if dataset != '':
584 # webmeta = list(usgsapi.search(dataset,'EE',distance=0,ll={'longitude':self.CornerTieP_LonLat[2][0], \
585 # 'latitude':self.CornerTieP_LonLat[2][1]},ur={'longitude':self.CornerTieP_LonLat[1][0], \
586 # 'latitude':self.CornerTieP_LonLat[1][1]},start_date=self.AcqDate,end_date=self.AcqDate))
587 # if len(webmeta)==1:
588 # self.EntityID=webmeta[0]['entityId']
589 # else:
590 # sen = {'MSS':'M','TM':'T','ETM+':'E','OLI_TIRS':'C','OLI':'O'}[self.Sensor]
591 # sat = self.Satellite.split('-')[1]
592 # path_res = re.search(r'WRS_PATH = ([0-9]+)',mtl_, re.I)
593 # path_str = "%03d"%int(path_res.group(1)) if path_res!=None else '000'
594 # row_res = re.search(r'STARTING_ROW = ([0-9]+)',mtl_, re.I)
595 # if row_res is None: row_res = re.search(r'WRS_ROW = ([0-9]+)',mtl_, re.I)
596 # row_str = "%03d"%int(row_res.group(1)) if row_res!=None else '000'
597 # tt = datetime.datetime.strptime(self.AcqDate, '%Y-%m-%d').timetuple()
598 # julianD = '%d%03d'%(tt.tm_year,tt.tm_yday)
599 # station_res = re.search(r'GROUND_STATION = "([\S]+)"',mtl_, re.I)
600 # if station_res is None: station_res = re.search(r'STATION_ID = "([\S]+)"',mtl_, re.I)
601 # station_str = station_res.group(1) if station_res is not None else 'XXX'
602 # idWithoutVersion = 'L%s%s%s%s%s%s'%(sen,sat,path_str,row_str,julianD,station_str)
603 # filt_webmeta = [i for i in webmeta if i['entityId'].startswith(idWithoutVersion)]
604 # if len(filt_webmeta) == 1:
605 # self.EntityID = filt_webmeta[0]['entityId']
607 ##########################
608 # band specific metadata #
609 ##########################
610 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
612 # Gains and Offsets
613 h4 = re.search(r"GROUP = MIN_MAX_RADIANCE[\s\S]*END_GROUP = MIN_MAX_PIXEL_VALUE", mtl_, re.I)
614 h4_ = h4.group(0)
615 h4max_rad = re.findall(r" LMAX_BAND[0-9]+ = ([\S]*)", h4_, re.I) # space in front IS needed
616 h4min_rad = re.findall(r" LMIN_BAND[0-9]+ = ([\S]*)", h4_, re.I) # space in front IS needed
617 h4max_pix = re.findall(r"QCALMAX_BAND[0-9]+ = ([\S]*)", h4_, re.I)
618 h4min_pix = re.findall(r"QCALMIN_BAND[0-9]+ = ([\S]*)", h4_, re.I)
619 if not h4max_rad:
620 h4max_rad = re.findall(r" RADIANCE_MAXIMUM_BAND_[0-9_VCID]+ = ([\S]*)", h4_,
621 re.I) # space in front IS needed
622 h4min_rad = re.findall(r" RADIANCE_MINIMUM_BAND_[0-9_VCID]+ = ([\S]*)", h4_,
623 re.I) # space in front IS needed
624 h4max_pix = re.findall(r"QUANTIZE_CAL_MAX_BAND_[0-9_VCID]+ = ([\S]*)", h4_, re.I)
625 h4min_pix = re.findall(r"QUANTIZE_CAL_MIN_BAND_[0-9_VCID]+ = ([\S]*)", h4_, re.I)
626 # additional: LMAX, LMIN, QCALMAX, QCALMIN
627 self.additional.append([["LMAX"], ["LMIN"], ["QCALMAX"], ["QCALMIN"]])
628 # Offsets + Gains
629 Gains, Offsets = [], []
630 for ii, ind in enumerate(h4min_rad):
631 Gains.append(
632 (float(h4max_rad[ii]) - float(h4min_rad[ii])) / (float(h4max_pix[ii]) - float(h4min_pix[ii])))
633 Offsets.append(float(ind) - float(h4min_pix[ii]) * Gains[-1])
634 self.additional[-1][-4].append(h4max_rad[ii])
635 self.additional[-1][-3].append(h4min_rad[ii])
636 self.additional[-1][-2].append(h4max_pix[ii])
637 self.additional[-1][-1].append(h4min_pix[ii])
638 self.Gains = {bN: Gains[i] for i, bN in enumerate(LBA_full_sorted)}
639 self.Offsets = {bN: Offsets[i] for i, bN in enumerate(LBA_full_sorted)}
641 # Solar irradiance, central wavelengths , full width half maximum per band
642 self.wvlUnit = 'Nanometers'
643 # Exact values calculated based in SRFs
644 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band() # 3x dict
645 # Provider values
646 if not self.SolIrradiance: # Preconfigured Irradiation and CWL values
647 if re.search(r"[\D]*5", self.Satellite):
648 # Landsat 5; resource for center wavelength (6 = thermal)
649 self.SolIrradiance = {'1': 1957.,
650 '2': 1826.,
651 '3': 1554.,
652 '4': 1036.,
653 '5': 215.,
654 '6': 0.0,
655 '7': 80.67}
656 self.CWL = {'1': 485.,
657 '2': 560.,
658 '3': 660.,
659 '4': 830.,
660 '5': 1650.,
661 '6': 11450.,
662 '7': 2215.}
663 if re.search(r"[\D]*7", self.Satellite):
664 # Landsat 7; resource for center wavelength:
665 # https://opticks.org/display/opticksDev/Sensor+Wavelength+Definitions
666 # 6L(thermal),6H(thermal),B8(pan)
667 self.SolIrradiance = {'1': 1969.,
668 '2': 1840.,
669 '3': 1551.,
670 '4': 1044.,
671 '5': 225.7,
672 '6L': 0.0,
673 '6H': 0.0,
674 '7': 82.07,
675 '8': 1368.}
676 self.CWL = {'1': 482.5,
677 '2': 565.,
678 '3': 660.,
679 '4': 825.,
680 '5': 1650.,
681 '6L': 11450.,
682 '6H': 11450.,
683 '7': 2215.,
684 '8': 710.}
685 if re.search(r"[\D]*8", self.Satellite): # Landsat 8
686 # no irradiation values available
687 self.CWL = {'1': 443.,
688 '2': 482.6,
689 '3': 561.3,
690 '4': 654.6,
691 '5': 864.6,
692 '6': 1609.1,
693 '7': 2201.2,
694 '8': 591.7,
695 '9': 1373.5,
696 '10': 10903.6,
697 '11': 12003.}
698 # if None in SolIrradiance:
700 # Thermal Constants (K1 = [W*m-2*um-1]; K1 = [K])
701 if re.search(r"[\D]*5", self.Satellite):
702 # resource: http://geography.middlebury.edu/data/gg1002/Handouts/Landsat_DN_Temp.pdf
703 ThermalConstK1 = [0., 0., 0., 0., 0., 607.76, 0.]
704 ThermalConstK2 = [0., 0., 0., 0., 0., 1260.56, 0.]
705 if re.search(r"[\D]*7", self.Satellite):
706 # resource: http://geography.middlebury.edu/data/gg1002/Handouts/Landsat_DN_Temp.pdf
707 ThermalConstK1 = [0., 0., 0., 0., 0., 666.09, 666.09, 0., 0.]
708 ThermalConstK2 = [0., 0., 0., 0., 0., 1282.71, 1282.71, 0., 0.]
709 if re.search(r"[\D]*8", self.Satellite): # Landsat 8
710 K1_res = re.findall(r"K1_CONSTANT_BAND_[0-9]+ = ([\S]*)", mtl_, re.I)
711 K2_res = re.findall(r"K2_CONSTANT_BAND_[0-9]+ = ([\S]*)", mtl_, re.I)
712 if len(K1_res) == 2:
713 ThermalConstK1 = [0., 0., 0., 0., 0., 0., 0., 0., 0., float(K1_res[0]), float(K1_res[1])]
714 ThermalConstK2 = [0., 0., 0., 0., 0., 0., 0., 0., 0., float(K2_res[0]), float(K2_res[1])]
715 else:
716 self.logger.error('Unable to set thermal constants. Expected to derive 2 values for K1 and K2. '
717 'Found %s' % len(K1_res))
718 self.ThermalConstK1 = {bN: ThermalConstK1[i] for i, bN in enumerate(LBA_full_sorted)}
719 self.ThermalConstK2 = {bN: ThermalConstK2[i] for i, bN in enumerate(LBA_full_sorted)}
721 # reflectance coefficients (Landsat8)
722 h9 = re.search(r"GROUP = RADIOMETRIC_RESCALING[\s\S]*END_GROUP = RADIOMETRIC_RESCALING", mtl_, re.I)
723 if h9:
724 h9_ = h9.group(0)
725 h9gain_ref = re.findall(r" REFLECTANCE_MULT_BAND_[0-9]+ = ([\S]*)", h9_, re.I)
726 h9gain_ref_bandNr = re.findall(r" REFLECTANCE_MULT_BAND_([0-9]+) = [\S]*", h9_, re.I)
727 h9offs_ref = re.findall(r" REFLECTANCE_ADD_BAND_[0-9]+ = ([\S]*)", h9_, re.I)
728 h9offs_ref_bandNr = re.findall(r" REFLECTANCE_ADD_BAND_([0-9]+) = [\S]*", h9_, re.I)
729 if h9gain_ref:
730 GainsRef = [None] * len(self.Gains)
731 OffsetsRef = [None] * len(self.Offsets)
733 for ii, ind in zip(h9gain_ref_bandNr, h9gain_ref):
734 GainsRef[LBA_full_sorted.index(ii)] = ind
735 for ii, ind in zip(h9offs_ref_bandNr, h9offs_ref):
736 OffsetsRef[LBA_full_sorted.index(ii)] = ind
738 self.GainsRef = {bN: GainsRef[i] for i, bN in enumerate(LBA_full_sorted)}
739 self.OffsetsRef = {bN: OffsetsRef[i] for i, bN in enumerate(LBA_full_sorted)}
741 self.orbitParams = get_orbit_params(self.GMS_identifier)
742 self.filter_layerdependent_metadata()
743 self.spec_vals = get_special_values(self.GMS_identifier)
745 # mtl.close()
747 def Read_RE_metaxml(self):
748 """----METHOD_4------------------------------------------------------------
749 read metadata from RapidEye metafile: <dataname>metadata.xml
750 """
752 # self.default_attr()
753 if os.path.isdir(self.FolderOrArchive):
754 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '*/*_metadata.xml'))
755 assert len(glob_res) > 0, 'No *metadata.xml file can be found in %s/*/!' % self.FolderOrArchive
756 self.Metafile = glob_res[0]
757 mxml_ = open(self.Metafile, "r").read()
758 else: # archive
759 mxml_, self.Metafile = open_specific_file_within_archive(self.FolderOrArchive, '*/*_metadata.xml')
761 # EntityID
762 h1 = re.search(r"<[a-z]*:identifier>([\S]*)</[a-z]*:identifier>", mxml_, re.I)
763 self.EntityID = h1.group(1) if h1 else "Not found"
765 # Processing Level
766 h2 = re.search(r"<[a-z]*:productType>([a-zA-Z0-9]*)</[a-z]*:productType>", mxml_, re.I)
767 self.ProcLCode = h2.group(1) if h2 else "Not found"
769 # Satellite
770 h3 = re.search(r"<[a-z]*:serialIdentifier>([a-zA-Z0-9-]*)</[a-z]*:serialIdentifier>", mxml_, re.I)
771 self.Satellite = 'RapidEye-%s' % re.search(r'[\D]*([0-9])', h3.group(1), re.I).group(1) if h3 else "Not found"
773 # Sensor (Instrument shortname)
774 h4 = re.search(r"<[a-z]*:Instrument>[\s]*<eop:shortName>([\S]*)</[a-z]*:shortName>", mxml_, re.I)
775 self.Sensor = h4.group(1) if h4 else "Not found"
777 # Acquisition Parameters: Incidence Angle, SunAzimuth, SunElevation, ViewingAngle, FieldOfView, AcqDate, AcqTime
778 try:
779 h5 = re.search(r'<[a-z]*:incidenceAngle uom="deg">([\S]*)'
780 r'</[a-z]*:incidenceAngle>[\s]*'
781 r'<opt:illuminationAzimuthAngle uom="deg">([\S]*)'
782 r'</opt:illuminationAzimuthAngle>[\s]*'
783 r'<opt:illuminationElevationAngle uom="deg">([\S]*)'
784 r'</opt:illuminationElevationAngle>[\s]*'
785 r'<re:azimuthAngle uom="deg">([\S]*)'
786 r'</re:azimuthAngle>[\s]*'
787 r'<re:spaceCraftViewAngle uom="deg">([\S]*)'
788 r'</re:spaceCraftViewAngle>[\s]*'
789 r'<re:acquisitionDateTime>([0-9-]*)T([0-9:]*)[\S]*'
790 r'</re:acquisitionDateTime>',
791 mxml_, re.I)
792 self.IncidenceAngle = float(h5.group(1))
793 self.SunAzimuth = float(h5.group(2))
794 self.SunElevation = float(h5.group(3))
795 # angle from true north at the image or tile center to the scan (line) direction at image center,
796 # in clockwise positive degrees.
797 self.additional.append(["spaceCraftAzimuthAngle:", str(round(float(h5.group(4)), 1))])
798 self.ViewingAngle = float(h5.group(5))
799 self.FOV = get_FieldOfView(self.GMS_identifier)
800 AcqDate = h5.group(6)
801 AcqTime = h5.group(7)
803 except AttributeError:
804 h5 = re.search(r'<hma:acrossTrackIncidenceAngle uom="deg">([\S]*)'
805 r'</hma:acrossTrackIncidenceAngle>[\s]*'
806 r'<ohr:illuminationAzimuthAngle uom="deg">([\S]*)'
807 r'</ohr:illuminationAzimuthAngle>[\s]*'
808 r'<ohr:illuminationElevationAngle uom="deg">([\S]*)'
809 r'</ohr:illuminationElevationAngle>[\s]*'
810 r'<re:azimuthAngle uom="deg">([\S]*)'
811 r'</re:azimuthAngle>[\s]*'
812 r'<re:acquisitionDateTime>([0-9-]*)'
813 r'T([0-9:]*)[\S]*'
814 r'</re:acquisitionDateTime>',
815 mxml_, re.I) #
816 self.IncidenceAngle = float(h5.group(1))
817 self.SunAzimuth = float(h5.group(2))
818 self.SunElevation = float(h5.group(3))
819 self.additional.append(["spaceCraftAzimuthAngle:", str(round(float(h5.group(4)), 1))])
820 self.ViewingAngle = 9999.99
821 self.FOV = get_FieldOfView(self.GMS_identifier)
822 AcqDate = h5.group(5)
823 AcqTime = h5.group(6)
824 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
825 self.AcqDateTime = datetime.datetime.strptime(
826 '%s %s%s' % (AcqDate, AcqTime, '.000000+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
828 # Earth sun distance
829 self.EarthSunDist = self.get_EarthSunDistance(self.AcqDate)
831 # Additional: Projection
832 h6 = re.search(r"<re:geodeticDatum>([\S]*)"
833 r"</re:geodeticDatum>[\s]*"
834 r"<re:projection>([\S^<\s]*)"
835 r"</re:projection>",
836 mxml_, re.I)
837 try:
838 self.additional.append(["SpatialReference",
839 ["geodeticDatum", h6.group(1)],
840 ["projection", h6.group(2)]
841 ])
842 except AttributeError: # Level1-B data has no geodaticDatum
843 pass
845 # Corrections applied + Quality
846 h7 = re.search(r"<re:radiometricCorrectionApplied>([\w]*)"
847 r"</re:radiometricCorrectionApplied>[\s]*"
848 r"<re:radiometricCalibrationVersion>([\S]*)"
849 r"</re:radiometricCalibrationVersion>[\s]*"
850 r"<re:geoCorrectionLevel>([\S\s^<]*)"
851 r"</re:geoCorrectionLevel>[\s]*"
852 r"<re:elevationCorrectionApplied>([\S]*)"
853 r"</re:elevationCorrectionApplied>[\s]*"
854 r"<re:atmosphericCorrectionApplied>([\w]*)"
855 r"</re:atmosphericCorrectionApplied>[\s]*"
856 r"<re:productAccuracy>([\S\s^<]*)"
857 r"</re:productAccuracy>", mxml_, re.I)
858 # fuer IRIS ihre Daten
859 if h7 is None:
860 h7 = re.search(r"<re:radiometricCorrectionApplied>([\w]*)"
861 r"</re:radiometricCorrectionApplied>[\s]*"
862 r"<re:geoCorrectionLevel>([\S\s^<]*)"
863 r"</re:geoCorrectionLevel>[\s]*"
864 r"<re:elevationCorrectionApplied>([\S]*)"
865 r"</re:elevationCorrectionApplied>[\s]*"
866 r"<re:atmosphericCorrectionApplied>([\w]*)"
867 r"</re:atmosphericCorrectionApplied>",
868 mxml_, re.I)
869 self.additional.append(
870 ["Corrections",
871 ["radiometricCorrectionApplied", h7.group(1)],
872 ["geoCorrectionLevel", h7.group(2)],
873 ["elevationCorrectionApplied", h7.group(3)],
874 ["atmosphericCorrectionApplied", h7.group(4)]
875 ])
876 else:
877 self.additional.append(
878 ["Corrections",
879 ["radiometricCorrectionApplied", h7.group(1)],
880 ["radiometricCalibrationVersion", h7.group(2)],
881 ["geoCorrectionLevel", h7.group(3)],
882 ["elevationCorrectionApplied", h7.group(4)],
883 ["atmosphericCorrectionApplied", h7.group(5)]
884 ])
885 self.Quality.append(["geomProductAccuracy[m]:", str(
886 round(float(h7.group(6)), 1))]) # Estimated product horizontal CE90 uncertainty [m]
888 # additional. missing lines, suspectlines, binning, shifting, masking
889 h81 = re.findall(r"<re:percentMissingLines>([^<]*)</re:percentMissingLines>", mxml_, re.I)
890 h82 = re.findall(r"<re:percentSuspectLines>([^<]*)</re:percentSuspectLines>", mxml_, re.I)
891 h83 = re.findall(r"<re:binning>([^<]*)</re:binning>", mxml_, re.I)
892 h84 = re.findall(r"<re:shifting>([^<]*)</re:shifting>", mxml_, re.I)
893 h85 = re.findall(r"<re:masking>([^<]*)</re:masking>", mxml_, re.I)
895 self.Quality.append(
896 ["MissingLines[%]perBand", h81]) # Percentage of missing lines in the source data of this band
897 # Percentage of suspect lines (lines that contained downlink errors) in the source data for the band
898 self.Quality.append(["SuspectLines[%]perBand", h82])
899 self.additional.append(
900 ["binning_perBand", h83]) # Indicates the binning used (across track x along track) [1x1,2x2,3x3,1x2,2x1]
901 self.additional.append(
902 ["shifting_perBand", h84]) # Indicates the sensor applied right shifting [none, 1bit, 2bits, 3bits, 4bits]
903 self.additional.append(["masking_perBand", h85]) # Indicates the sensor applied masking [111, 110, 100, 000]
905 # Units
906 self.ScaleFactor = 1
907 self.PhysUnit = "DN"
909 # Coordinate Reference System
910 re_CS_EPSG = re.search(
911 r'<re:ProductInformation>[\s\S]*'
912 r'<re:epsgCode>([0-9]*)</re:epsgCode>[\s\S]*'
913 r'</re:ProductInformation>',
914 mxml_, re.I)
915 re_CS_TYPE = re.search(
916 r'<re:ProductInformation>[\s\S]*'
917 r'<re:projection>([\s\S]*)</re:projection>[\s\S]*'
918 r'</re:ProductInformation>',
919 mxml_, re.I)
920 re_CS_DATUM = re.search(
921 r'<re:ProductInformation>[\s\S]*'
922 r'<re:geodeticDatum>([\w+\s]*)</re:geodeticDatum>[\s\S]*'
923 r'</re:ProductInformation>',
924 mxml_, re.I)
925 re_CS_UTM_ZONE = re.search(
926 r'<re:ProductInformation>[\s\S]*'
927 r'<re:projectionZone>([0-9]*)</re:projectionZone>[\s\S]*'
928 r'</re:ProductInformation>',
929 mxml_, re.I)
930 self.CS_EPSG = int(re_CS_EPSG.group(1)) if re_CS_EPSG else self.CS_EPSG
931 self.CS_TYPE = 'LonLat' if re_CS_TYPE and not re.search(r'UTM', re_CS_TYPE.group(1)) else 'UTM' \
932 if re_CS_TYPE and re.search(r'UTM', re_CS_TYPE.group(1)) else self.CS_TYPE
933 self.CS_DATUM = 'WGS84' if re_CS_DATUM is not None and re_CS_DATUM.group(1) == 'WGS_1984' else self.CS_DATUM
934 self.CS_UTM_ZONE = int(re_CS_UTM_ZONE.group(1)) if re_CS_UTM_ZONE is not None else self.CS_UTM_ZONE
936 # Corner Coordinates ## Lon/Lat for all given image corners UL,UR,LL,LR (tuples)
937 h10_UL = re.findall(
938 r'<re:topLeft>'
939 r'<re:latitude>(.*)</re:latitude>'
940 r'<re:longitude>(.*)</re:longitude>'
941 r'</re:topLeft>',
942 mxml_, re.I)
943 h10_UR = re.findall(
944 r'<re:topRight>'
945 r'<re:latitude>(.*)</re:latitude>'
946 r'<re:longitude>(.*)</re:longitude>'
947 r'</re:topRight>',
948 mxml_, re.I)
949 h10_LL = re.findall(
950 r'<re:bottomLeft>'
951 r'<re:latitude>(.*)</re:latitude>'
952 r'<re:longitude>(.*)</re:longitude>'
953 r'</re:bottomLeft>',
954 mxml_, re.I)
955 h10_LR = re.findall(
956 r'<re:bottomRight>'
957 r'<re:latitude>(.*)</re:latitude>'
958 r'<re:longitude>(.*)</re:longitude>'
959 r'</re:bottomRight>',
960 mxml_, re.I)
961 if h10_UL: # Set Corner Tie Point Coordinates (order = UL,UR,LL,LR)
962 self.CornerTieP_LonLat.append(tuple([float(h10_UL[0][1]), float(h10_UL[0][0])]))
963 self.CornerTieP_LonLat.append(tuple([float(h10_UR[0][1]), float(h10_UR[0][0])]))
964 self.CornerTieP_LonLat.append(tuple([float(h10_LL[0][1]), float(h10_LL[0][0])]))
965 self.CornerTieP_LonLat.append(tuple([float(h10_LR[0][1]), float(h10_LR[0][0])]))
967 ##########################
968 # band specific metadata #
969 ##########################
971 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
973 # Gains + Offsets
974 h9 = re.findall(r"<re:radiometricScaleFactor>([^<]*)</re:radiometricScaleFactor>",
975 mxml_, re.I)
976 self.Gains = dict(zip(LBA_full_sorted, [float(gain) for gain in h9]))
977 self.Offsets = dict(zip(LBA_full_sorted, [0, 0, 0, 0, 0]))
979 # Solar irradiance, central wavelengths , full width half maximum per band
980 self.wvlUnit = 'Nanometers'
981 # derive number of bands from number of given gains/offsets in header file or from raster data
982 # noinspection PyBroadException
983 try:
984 self.nBands = (np.mean([len(self.Gains), len(self.Offsets)])
985 if np.std([len(self.Gains), len(self.Offsets)]) == 0 and len(self.Gains) != 0 else
986 GEOP.GEOPROCESSING(self.Dataname, self.logger).bands)
987 except Exception:
988 self.logger.warning('Unable to get number of bands for the dataset %s Provider values are used for '
989 'solar irradiation, CWL and FWHM!.' % self.Dataname)
990 self.LayerBandsAssignment = get_LayerBandsAssignment(self.GMS_identifier, self.nBands)
992 # Exact values calculated based in SRFs
993 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band()
994 # Provider values
995 if not self.SolIrradiance:
996 # Preconfigured Irradiation values
997 self.SolIrradiance = dict(zip(LBA_full_sorted, [1997.8, 1863.5, 1560.4, 1395.0, 1124.4]))
998 # Preconfigured CWLs
999 self.CWL = dict(zip(LBA_full_sorted, [475., 555., 657., 710., 805.]))
1001 self.orbitParams = get_orbit_params(self.GMS_identifier)
1002 self.filter_layerdependent_metadata()
1003 self.spec_vals = get_special_values(self.GMS_identifier)
1005 def Read_ASTER_hdffile(self, subsystem):
1006 """#----METHOD_5------------------------------------------------------------
1007 read metadata from ASTER hdf
1008 input:
1009 hdffile:
1010 subsystem:
1011 output:
1012 :param subsystem:
1013 """
1015 # self.default_attr()
1016 dat_ = open(self.FolderOrArchive, "r").read() if sys.version_info[0] < 3 else \
1017 open(self.FolderOrArchive, "rb").read().decode('latin-1')
1019 # Split meta from raster data
1020 meta = re.search(r"GROUP[\s]*=[\s]"
1021 r"ASTERGENERICMETADATA[\s\S]*?"
1022 r"END_GROUP[\s]*=[\s]"
1023 r"INVENTORYMETADATA",
1024 dat_, re.I)
1025 meta_ = meta.group(0) if meta else ''
1026 genericmeta = re.search(r"GROUP[\s]*=[\s]"
1027 r"ASTERGENERICMETADATA[\s\S]*?"
1028 r"END_GROUP[\s]*=[\s]"
1029 r"ASTERGENERICMETADATA",
1030 meta_, re.I)
1031 inventorymeta = re.search(r"GROUP[\s]*=[\s]"
1032 r"INVENTORYMETADATA[\s\S]*?"
1033 r"END_GROUP[\s]*=[\s]"
1034 r"INVENTORYMETADATA",
1035 meta_, re.I)
1036 gcsgenericmeta = re.search(r"GROUP[\s]*=[\s]"
1037 r"GDSGENERICMETADATA[\s\S]*?"
1038 r"END_GROUP[\s]*=[\s]"
1039 r"GDSGENERICMETADATA",
1040 meta_, re.I)
1041 vnirmeta = re.search(r"GROUP[\s]*=[\s]"
1042 r"PRODUCTSPECIFICMETADATAVNIR[\s\S]*?"
1043 r"END_GROUP[\s]*=[\s]"
1044 r"PRODUCTSPECIFICMETADATAVNIR",
1045 meta_, re.I)
1046 swirmeta = re.search(r"GROUP[\s]*=[\s]"
1047 r"PRODUCTSPECIFICMETADATASWIR[\s\S]*?"
1048 r"END_GROUP[\s]*=[\s]"
1049 r"PRODUCTSPECIFICMETADATASWIR",
1050 meta_, re.I)
1051 tirmeta = re.search(r"GROUP[\s]*=[\s]"
1052 r"PRODUCTSPECIFICMETADATATIR[\s\S]*?"
1053 r"END_GROUP[\s]*=[\s]"
1054 r"PRODUCTSPECIFICMETADATATIR",
1055 meta_, re.I)
1056 genericmeta_ = genericmeta.group(0) if genericmeta else ''
1057 inventorymeta_ = inventorymeta.group(0) if inventorymeta else ''
1058 gcsgenericmeta_ = gcsgenericmeta.group(0) if gcsgenericmeta else ''
1059 vnirmeta_ = vnirmeta.group(0) if vnirmeta else ''
1060 swirmeta_ = swirmeta.group(0) if swirmeta else ''
1061 tirmeta_ = tirmeta.group(0) if tirmeta else ''
1062 h_ = '\n\n\n'.join([genericmeta_, inventorymeta_, gcsgenericmeta_, vnirmeta_, swirmeta_, tirmeta_])
1064 # with open("./testing/out/ASTER_HDFmeta__h_.txt", "w") as allMetaOut:
1065 # allMetaOut.write(h_)
1067 # EntityID
1068 h1 = re.search(r"OBJECT[\s]*=[\s]*"
1069 r"IDOFASTERGDSDATAGRANULE[\s\S]*"
1070 r"END_OBJECT[\s]*=[\s]*"
1071 r"IDOFASTERGDSDATAGRANULE",
1072 h_, re.I)
1073 h11 = re.search(r'VALUE[\s]*=[\s]*"([\s\S]*)"', h1.group(0), re.I)
1074 self.EntityID = [h11.group(1), re.search(r"\"(ASTL1A[\s0-9]*)\"", h_).group(1)]
1076 # Viewing Angle
1077 h2 = re.search(r"GROUP[\s]*=[\s]*"
1078 r"POINTINGANGLES[\s\S]*"
1079 r"END_GROUP[\s]*=[\s]*"
1080 r"POINTINGANGLES",
1081 h_, re.I)
1082 h21 = re.findall(r"VALUE[\s]*=[\s]*([-]*[0-9.0-9]+)", h2.group(0), re.I)
1083 self.additional.append(["ViewingAngles", "VNIR", float(h21[0]), "SWIR", float(h21[1]), "TIR", float(h21[2])])
1084 if max(float(h21[0]), float(h21[1]), float(h21[2])) - min(float(h21[0]), float(h21[1]), float(h21[2])) < 1:
1085 self.ViewingAngle = float(h21[0])
1086 else:
1087 self.ViewingAngle = -99.0
1089 # additional GainMode
1090 h3 = re.search(r"GROUP[\s]*=[\s]*"
1091 r"GAININFORMATION[\s\S]*"
1092 r"END_GROUP[\s]*=[\s]*"
1093 r"GAININFORMATION",
1094 genericmeta_, re.I)
1095 h31 = re.findall(r'VALUE[\s]*=[\s]*[\S]?\"[0-9A-Z]*\", \"([A-Z]*)\"', h3.group(0), re.I)
1096 gains = {'HGH': [170.8, 179.0, 106.8, 27.5, 8.8, 7.9, 7.55, 5.27, 4.02, 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'],
1097 'NOR': [427.0, 358.0, 218.0, 55.0, 17.6, 15.8, 15.1, 10.55, 8.04, 28.17, 27.75, 26.97, 23.30, 21.38],
1098 'LOW': [569.0, 477.0, 290.0, 73.3, 23.4, 21.0, 20.1, 14.06, 10.72, 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'],
1099 'LO1': [569.0, 477.0, 290.0, 73.3, 23.4, 21.0, 20.1, 14.06, 10.72, 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'],
1100 'LO2': ['N/A', 'N/A', 'N/A', 73.3, 103.5, 98.7, 83.8, 62.0, 67.0, 'N/A', 'N/A', 'N/A', 'N/A', 'N/A'],
1101 'OFF': "OFF"}
1102 self.additional.append([["GainMode:"], ["Max_radiance:"]])
1103 for x, i in enumerate(h31[:15]):
1104 self.additional[-1][-2].append(i)
1105 self.additional[-1][-1].append(gains[i][x])
1107 # Units
1108 self.ScaleFactor = 1
1109 self.PhysUnit = "DN"
1111 # Satellite
1112 self.Satellite = 'Terra'
1114 # Sensor
1115 h10 = re.search(r"OBJECT[\s]*=[\s]*"
1116 r"INSTRUMENTSHORTNAME[\s\S]*"
1117 r"END_OBJECT[\s]*=[\s]*"
1118 r"INSTRUMENTSHORTNAME", h_, re.I)
1119 self.Sensor = re.search(r"VALUE[\s]*=[\s]*[\"]([A-Za-z]*)\"", h10.group(0), re.I).group(1)
1121 # Subsystem
1122 h5 = re.search(r"GROUP[\s]*=[\s]*"
1123 r"OBSERVATIONMODE[\s\S]*"
1124 r"END_GROUP[\s]*=[\s]*"
1125 r"OBSERVATIONMODE", h_, re.I)
1126 avail_subsystems = re.findall(r'VALUE[\s]*=[\s]*[(]\"([a-zA-Z0-9]*)\", \"([ONF]*)\"', h5.group(0), re.I)
1127 if subsystem not in ['VNIR1', 'VNIR2', 'SWIR', 'TIR']:
1128 raise ValueError('Unexpected subsystem >%s<. Unable to specify the correct ASTER subsystem to be '
1129 'processed.' % subsystem)
1130 else:
1131 if subsystem == 'VNIR1' and avail_subsystems[0][1] == 'ON':
1132 self.nBands = 3
1133 if subsystem == 'VNIR2' and avail_subsystems[1][1] == 'ON':
1134 self.nBands = 1
1135 if subsystem == 'SWIR' and avail_subsystems[2][1] == 'ON':
1136 self.nBands = 6
1137 if subsystem == 'TIR' and avail_subsystems[3][1] == 'ON':
1138 self.nBands = 5
1139 self.Subsystem = subsystem
1141 # Field of view (requires Satellite, Sensor, Subsystem)
1142 self.FOV = get_FieldOfView(self.GMS_identifier)
1144 # Ground resolution
1145 re_res_GSD = re.findall(r'OBJECT[\s]*=[\s]*'
1146 r'SPATIALRESOLUTION[\s\S]*'
1147 r'VALUE[\s]*=[\s]*[\S]{1}([1-9][0-9]), ([1-9][0-9]), ([1-9][0-9])[\s\S]*'
1148 r'END_OBJECT[\s]*=[\s]*'
1149 r'SPATIALRESOLUTION', h_, re.I)
1150 idx_subsystem = \
1151 0 if subsystem[:4] == 'VNIR' else \
1152 1 if subsystem[:4] == 'SWIR' else \
1153 2 if subsystem[:4] == 'TIR' else None
1154 self.gResolution = float(re_res_GSD[0][idx_subsystem]) \
1155 if re_res_GSD and idx_subsystem is not None else self.gResolution
1157 # Flight direction
1158 h6 = re.search(r"OBJECT[\s]*=[\s]*"
1159 r"FLYINGDIRECTION[\s\S]*\"([ASDE]*?)\"",
1160 h_, re.I)
1161 Fdir = {'AS': "Ascending", 'DE': "Descending"}
1162 self.additional.append(["Flight Direction", Fdir[h6.group(1)]])
1164 # SunAzimuth SunElevation
1165 h7 = re.search(r"OBJECT[\s]*=[\s]*"
1166 r"SOLARDIRECTION[\s\S]*"
1167 r"END_OBJECT[\s]*=[\s]*"
1168 r"SOLARDIRECTION",
1169 h_, re.I)
1170 h71 = re.search(r"VALUE[\s]*=[\s]*[\S]{1}([0-9.]*), ([0-9.]*)", h7.group(0), re.I)
1171 self.SunAzimuth = float(h71.group(1))
1172 self.SunElevation = float(h71.group(2))
1174 # data Quality
1175 h8 = re.findall(r"GROUP[\s]*=[\s]*"
1176 r"DATAQUALITY[1-9][0-4BN]?[^.]*"
1177 r"END_GROUP[\s]*=[\s]*"
1178 r"DATAQUALITY[1-9][0-4BN]?",
1179 h_, re.I)
1180 h81 = re.findall(r'VALUE[\s]*=[\s]*([^\n]*)', "\n".join(h8), re.I)
1181 self.Quality.append(["NoOfBadPixel:"])
1182 for i in h81:
1183 self.Quality[-1].append(i)
1185 # ProdLevel
1186 h9 = re.search(r"OBJECT[\s]*=[\s]*"
1187 r"SHORTNAME[\s\S]*"
1188 r"END_OBJECT[\s]*=[\s]*"
1189 r"SHORTNAME", h_, re.I)
1190 self.ProcLCode = re.search(r"VALUE[\s]*=[\s]*[\"]([0-9A-Za-z]*)\"", h9.group(0), re.I).group(1)
1192 # AcqTime
1193 h11 = re.search(r"OBJECT[\s]*=[\s]*"
1194 r"TIMEOFDAY[\s\S]*"
1195 r"END_OBJECT[\s]*=[\s]*"
1196 r"TIMEOFDAY", h_, re.I)
1197 h111 = re.search(r"VALUE[\s]*=[\s]*[\"]([\d][\d][\d][\d][\d][\d])[\S]*\"", h11.group(0), re.I)
1198 if type(h111).__name__ != 'NoneType':
1199 AcqTime = "%s:%s:%s" % (h111.group(1)[:2], h111.group(1)[2:4], h111.group(1)[4:6])
1200 else:
1201 h112 = re.search(r"VALUE[\s]*=[\s]*[\"]([\d:]*)[\S]*\"", h11.group(0), re.I)
1202 AcqTime = h112.group(1)
1204 # AcqDate
1205 h12 = re.search(r"OBJECT[\s]*=[\s]*"
1206 r"CALENDARDATE[\s\S]*"
1207 r"END_OBJECT[\s]*=[\s]*"
1208 r"CALENDARDATE", h_, re.I)
1209 h121 = re.search(r"VALUE[\s]*=[\s]*[\"]([\d]*)\"", h12.group(0), re.I)
1210 if type(h121).__name__ != 'NoneType':
1211 AcqDate = "%s-%s-%s" % (h121.group(1)[:4], h121.group(1)[4:6], h121.group(1)[6:8])
1212 else:
1213 h121 = re.search(r"VALUE[\s]*=[\s]*[\"]([\d-]*)\"", h12.group(0), re.I)
1214 AcqDate = h121.group(1)
1216 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
1217 self.AcqDateTime = datetime.datetime.strptime(
1218 '%s %s%s' % (AcqDate, AcqTime, '.000000+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
1220 # Earth sun distance
1221 self.EarthSunDist = self.get_EarthSunDistance(self.AcqDate)
1223 # data Quality
1224 h13 = re.search(r"GROUP[\s]*=[\s]*"
1225 r"QASTATS[\s\S]*"
1226 r"END_GROUP[\s]*=[\s]*"
1227 r"QASTATS",
1228 h_, re.I)
1229 h131 = re.findall(r'VALUE[\s]*=[\s]*([0-9.0-9]*)', h13.group(0), re.I)
1230 self.Quality.append(["PercMissingData:", h131[0]]) # The percentage of missing data of the scene [%]
1231 self.Quality.append(["PercOutOfBoundsData:", h131[1]]) # The percentage of out of bounds data of the scene [%]
1232 self.Quality.append(["PercInterpolatedData:", h131[2]]) # The percentage of interpolated data of the scene [%]
1234 # Coordinate Reference System (L1B+)
1235 topgroupname = 'PRODUCTSPECIFICMETADATA' + subsystem[:4]
1236 # read only the information from first band in current ASTER subsystem
1237 # (the others should have the same parameters)
1238 re_res_procParm = re.search(r'GROUP[\s]*=[\s]*%s[\s\S]*'
1239 r'GROUP[\s]*=[\s]*(PROCESSINGPARAMETERS%s[\s\S]*'
1240 r'END_GROUP[\s]*=[\s]*PROCESSINGPARAMETERS%s)[\s\S]*'
1241 r'END_GROUP[\s]*=[\s]*%s'
1242 % (topgroupname, self.LayerBandsAssignment[0],
1243 self.LayerBandsAssignment[0], topgroupname),
1244 h_, re.I)
1245 if type(re_res_procParm).__name__ != 'NoneType':
1246 re_res_procParm = re_res_procParm.group(1)
1247 re_CS_TYPE = re.search(r'OBJECT[\s]*=[\s]*'
1248 r'MPMETHOD[\s\S]*'
1249 r'VALUE[\s]*=[\s]*"([\s\S]*)"[\s\S]*'
1250 r'END_OBJECT[\s]*=[\s]*'
1251 r'MPMETHOD',
1252 re_res_procParm, re.I)
1253 re_CS_UTM_ZONE = re.search(r'OBJECT[\s]*=[\s]*'
1254 r'UTMZONECODE[\s\S]*'
1255 r'VALUE[\s]*=[\s]*([0-9][0-9]?)[\s\S]*'
1256 r'END_OBJECT[\s]*=[\s]*'
1257 r'UTMZONECODE',
1258 re_res_procParm, re.I)
1259 self.CS_TYPE = 'LonLat' if re_CS_TYPE and not re.search(r'UTM', re_CS_TYPE.group(1)) else 'UTM' \
1260 if re_CS_TYPE and re.search(r'UTM', re_CS_TYPE.group(1)) else self.CS_TYPE
1261 self.CS_DATUM = 'WGS84' if self.CS_TYPE == 'UTM' else self.CS_DATUM
1262 self.CS_UTM_ZONE = int(re_CS_UTM_ZONE.group(1)) if re_CS_UTM_ZONE else self.CS_UTM_ZONE
1264 # Corner Coordinates ## Lon/Lat for all given image corners UL,UR,LL,LR (tuples)
1265 re_res_sceneInfo = re.search(r'GROUP[\s]*=[\s]*'
1266 r'SCENEINFORMATION[\s\S]*'
1267 r'END_GROUP[\s]*=[\s]*'
1268 r'SCENEINFORMATION', h_,
1269 re.I).group(0)
1270 re_res_UL = re.findall(r'OBJECT[\s]*=[\s]*'
1271 r'UPPERLEFT[\s\S]*'
1272 r'VALUE[\s]*=[\s]*[\S]{1}([0-9.]*), ([0-9.]*)[\s\S]*'
1273 r'END_OBJECT[\s]*=[\s]*'
1274 r'UPPERLEFT',
1275 re_res_sceneInfo, re.I)
1276 re_res_UR = re.findall(r'OBJECT[\s]*=[\s]*'
1277 r'UPPERRIGHT[\s\S]*'
1278 r'VALUE[\s]*=[\s]*[\S]{1}([0-9.]*), ([0-9.]*)[\s\S]*'
1279 r'END_OBJECT[\s]*=[\s]*'
1280 r'UPPERRIGHT',
1281 re_res_sceneInfo, re.I)
1282 re_res_LL = re.findall(r'OBJECT[\s]*=[\s]*'
1283 r'LOWERLEFT[\s\S]*'
1284 r'VALUE[\s]*=[\s]*[\S]{1}([0-9.]*), ([0-9.]*)[\s\S]*'
1285 r'END_OBJECT[\s]*=[\s]*'
1286 r'LOWERLEFT',
1287 re_res_sceneInfo, re.I)
1288 re_res_LR = re.findall(r'OBJECT[\s]*=[\s]*'
1289 r'LOWERRIGHT[\s\S]*'
1290 r'VALUE[\s]*=[\s]*[\S]{1}([0-9.]*), ([0-9.]*)[\s\S]*'
1291 r'END_OBJECT[\s]*=[\s]*'
1292 r'LOWERRIGHT',
1293 re_res_sceneInfo, re.I)
1294 if re_res_UL: # Set Corner Tie Point Coordinates (order = UL,UR,LL,LR)
1295 self.CornerTieP_LonLat.append(tuple([float(re_res_UL[0][1]), float(re_res_UL[0][0])]))
1296 self.CornerTieP_LonLat.append(tuple([float(re_res_UR[0][1]), float(re_res_UR[0][0])]))
1297 self.CornerTieP_LonLat.append(tuple([float(re_res_LL[0][1]), float(re_res_LL[0][0])]))
1298 self.CornerTieP_LonLat.append(tuple([float(re_res_LR[0][1]), float(re_res_LR[0][0])]))
1300 ##########################
1301 # band specific metadata #
1302 ##########################
1304 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
1306 # Gains/Offsets
1307 h4 = re.findall(r"GROUP[\s]*=[\s]*"
1308 r"UNITCONVERSIONCOEFF[1-9][0-4BN]?[^(]*"
1309 r"END_GROUP[\s]*=[\s]*"
1310 r"UNITCONVERSIONCOEFF[1-9][0-4BN]?",
1311 h_, re.I)
1312 h41 = re.findall(r'VALUE[\s]*=[\s]*([0-9.0-9]+)', "\n".join(h4), re.I)
1313 h42 = re.findall(r'VALUE[\s]*=[\s]*([-]+[0-9.0-9]+)', "\n".join(h4), re.I)
1314 self.Gains = dict(zip(LBA_full_sorted, [float(i) for i in h41]))
1315 self.Offsets = dict(zip(LBA_full_sorted, [float(i) for i in h42]))
1317 # Solar irradiance, central wavelengths , full width half maximum per band
1318 self.wvlUnit = 'Nanometers'
1319 self.LayerBandsAssignment = get_LayerBandsAssignment(self.GMS_identifier, self.nBands)
1321 # Exact values calculated based in SRFs
1322 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band()
1323 # Provider values
1324 if not self.SolIrradiance:
1325 # Preconfigured Irradiation values
1326 self.SolIrradiance = dict(zip(LBA_full_sorted, [1845.99, 1555.74, 1119.47] + [None] * 12))
1327 # Preconfigured CWLs
1328 self.CWL = dict(zip(LBA_full_sorted, [556., 659., 807., 804., 1656., 2167., 2208., 2266., 2336., 2400.,
1329 8291., 8634., 9075., 10657., 11318]))
1331 # Thermal Constants (K1 = [W*m-2*um-1]; K1 = [K])
1332 # Preconfigured values; resource: http://www.pancroma.com/ASTER%20Surface%20Temperature%20Analysis.html
1333 self.ThermalConstK1 = dict(zip(LBA_full_sorted,
1334 [0.] * 10 + [3040.136402, 2482.375199, 1935.060183, 866.468575, 641.326517]))
1335 self.ThermalConstK2 = dict(zip(LBA_full_sorted,
1336 [0.] * 10 + [1735.337945, 1666.398761, 1585.420044, 1350.069147, 1271.221673]))
1338 self.orbitParams = get_orbit_params(self.GMS_identifier)
1339 self.filter_layerdependent_metadata()
1340 self.spec_vals = get_special_values(self.GMS_identifier)
1342 def Read_ALOS_summary(self):
1343 """----METHOD_6------------------------------------------------------------
1344 read metadata from ALOS summary.txt
1345 """
1347 # self.default_attr()
1348 if os.path.isdir(self.FolderOrArchive):
1349 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '*/*data*/summary.txt'))
1350 assert len(glob_res) > 0, 'No summary.txt file can be found in %s/*/*data*/!' % self.FolderOrArchive
1351 self.Metafile = glob_res[0]
1352 sum_ = open(self.Metafile, "r").read()
1353 else: # archive
1354 sum_, self.Metafile = open_specific_file_within_archive(self.FolderOrArchive, '*/*data*/summary.txt')
1356 # EntityID
1357 h1 = re.search(r'Scs_SceneID="([a-zA-Z0-9]*)"', sum_, re.I)
1358 self.EntityID = h1.group(1)
1360 # Satellite, Sensor, ProcLevel,
1361 h5 = re.search(r'Lbi_Satellite="([\S]*)"[\s]*'
1362 r'Lbi_Sensor="([\S-]*)"[\s]*'
1363 r'Lbi_ProcessLevel="([\S-]*)"',
1364 sum_, re.I)
1365 self.Satellite = h5.group(1)
1366 self.Sensor = h5.group(2)
1367 self.ProcLCode = h5.group(3)
1369 # Ground resolution
1370 re_res_GSD = re.search(r'Pds_PixelSpacing="([\d]*)"', sum_, re.I)
1371 self.gResolution = float(re_res_GSD.group(1)) if re_res_GSD else self.gResolution
1373 # Additional map info + resampling
1374 try: # no mapinfo or resampling for Level1A
1375 hx = re.search(r'Ps_(ResamplingMethod="[\S]*")[\s]*'
1376 r'Pds_(UTM_ZoneNo="[\S]*")[\s]*'
1377 r'Pds_(MapDirection="[\S]*")[\s]*'
1378 r'Pds_(PixelSpacing="[\d]*")',
1379 sum_, re.I)
1380 self.additional.append(["Map_Info: " + ', '.join(hx.groups())])
1381 except AttributeError:
1382 pass
1384 # SunElevation, SunAzimuth, ViewingAngle =(Img_Pointing_Angle),
1385 # IncidenceAngle =(Img_SceneCenterAngle), Field of View
1386 h2 = re.search(r'Img_SunAngleElevation="([\d.]*)"[\s]*'
1387 r'Img_SunAngleAzimuth="([\d.]*)"[\s]*'
1388 r'Img_PointingAngle="([\d.]*)"[\s]*'
1389 r'Img_SceneCenterAngle="([\S]*)"',
1390 sum_, re.I)
1391 self.SunElevation = float(h2.group(1))
1392 self.SunAzimuth = float(h2.group(2))
1393 self.ViewingAngle = float(h2.group(3))
1394 incAngle = h2.group(4)
1395 if incAngle.lower().startswith("l"): # L means incidence angle to the left. Set to negativ values
1396 self.IncidenceAngle = float("-" + incAngle[1:])
1397 elif incAngle.lower().startswith("r"): # R means incidence angle to the right. Set to positiv values
1398 self.IncidenceAngle = float(incAngle[1:])
1399 else: # Sign ("L" or "R") will not be added in case of zero.
1400 self.IncidenceAngle = float(incAngle)
1401 self.FOV = get_FieldOfView(self.GMS_identifier)
1403 self.ScaleFactor = 1
1404 self.PhysUnit = "DN"
1406 # Additional: Exposure coefficients, Saturation coefficients of each band
1407 h4 = re.search(r'Img_ExposureOfBand1="([\d.-]*)"[\s]*'
1408 r'Img_ExposureOfBand2="([\d.-]*)"[\s]*'
1409 r'Img_ExposureOfBand3="([\d.-]*)"[\s]*'
1410 r'Img_ExposureOfBand4="([\d.-]*)"[\s]*'
1411 r'Img_SaturationLevelOfBand1="([\d.-]*)"[\s]*'
1412 r'Img_SaturationLevelOfBand2="([\d.-]*)"[\s]*'
1413 r'Img_SaturationLevelOfBand3="([\d.-]*)"[\s]*'
1414 r'Img_SaturationLevelOfBand4="([\d.-]*)"',
1415 sum_, re.I)
1417 self.additional.append(["Exposure Coeffients: B1:'%s',B2:'%s',B3:'%s',B4:'%s'"
1418 % (h4.group(1), h4.group(2), h4.group(3), h4.group(4))])
1419 self.additional.append(["Saturation coeffients: B1:'%s',B2:'%s',B3:'%s',B4:'%s'"
1420 % (h4.group(5), h4.group(6), h4.group(7), h4.group(8))])
1422 # AcqDate + AcqTime
1423 h6 = re.search(r'Lbi_ObservationDate="([\d]*)"', sum_, re.I)
1424 AcqDate = "%s-%s-%s" % (h6.group(1)[:4], h6.group(1)[4:6], h6.group(1)[6:8])
1425 re_AcqTime = re.search(r'Img_SceneCenterDateTime="[0-9]* ([0-9][0-9]:[0-9][0-9]:.*)"', sum_, re.I)
1426 AcqTime_dec = re_AcqTime.group(1) if re_AcqTime else self.AcqTime
1427 AcqTime = AcqTime_dec.split('.')[0]
1429 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
1430 if AcqTime_dec:
1431 self.AcqDateTime = datetime.datetime.strptime(
1432 '%s %s%s' % (AcqDate, AcqTime_dec, '+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
1433 else:
1434 self.AcqDateTime = datetime.datetime.strptime('%s%s' % (AcqDate, '+0000'), '%Y-%m-%d%z')
1435 self.AcqTime = AcqTime
1437 # Earth sun distance
1438 self.EarthSunDist = self.get_EarthSunDistance(self.AcqDate)
1440 # Quality (clouds)
1441 # cloudsperc = {'0': "0 to 2%", '1': "3 to 10%", '1': "11 to 20%", '1': "21 to 30%", '1': "31 to 40%",
1442 # '1': "41 to 50%",
1443 # '1': "51 to 60%", '1': "61 to 70%", '1': "71 to 80%", '1': "81 to 90%", '1': "91 to 100%",
1444 # '99': "No assessment"} FIXME F601 dictionary key '1' repeated with different values
1445 # h7 = re.search(r'Img_CloudQuantityOfAllImage="([\d]*)"', sum_, re.I)
1446 # self.Quality.append(["CloudCoverage: " + cloudsperc[h7.group(1)]])
1448 # Corner Coordinates ## Lon/Lat for all given image corners UL,UR,LL,LR (tuples)
1449 h10_UL = re.findall(r'Img_[\s\S]*SceneLeftTopLatitude="(.*)"[\s\S]'
1450 r'Img_[\s\S]*SceneLeftTopLongitude="(.*)"',
1451 sum_, re.I)
1452 h10_UR = re.findall(r'Img_[\s\S]*SceneRightTopLatitude="(.*)"[\s\S]'
1453 r'Img_[\s\S]*SceneRightTopLongitude="(.*)"',
1454 sum_, re.I)
1455 h10_LL = re.findall(r'Img_[\s\S]*SceneLeftBottomLatitude="(.*)"[\s\S]'
1456 r'Img_[\s\S]*SceneLeftBottomLongitude="(.*)"',
1457 sum_, re.I)
1458 h10_LR = re.findall(
1459 r'Img_[\s\S]*SceneRightBottomLatitude="(.*)"[\s\S]'
1460 r'Img_[\s\S]*SceneRightBottomLongitude="(.*)"',
1461 sum_, re.I)
1462 if h10_UL: # Set Corner Tie Point Coordinates (order = UL,UR,LL,LR)
1463 self.CornerTieP_LonLat.append(tuple([float(h10_UL[0][1]), float(h10_UL[0][0])]))
1464 self.CornerTieP_LonLat.append(tuple([float(h10_UR[0][1]), float(h10_UR[0][0])]))
1465 self.CornerTieP_LonLat.append(tuple([float(h10_LL[0][1]), float(h10_LL[0][0])]))
1466 self.CornerTieP_LonLat.append(tuple([float(h10_LR[0][1]), float(h10_LR[0][0])]))
1468 # Coordinate Reference System
1469 re_res_CS_UTM_ZONE = re.search(r'Pds_UTM_ZoneNo="([0-9][0-9]?)"', sum_, re.I)
1470 self.CS_UTM_ZONE = int(re_res_CS_UTM_ZONE.group(1)) if re_res_CS_UTM_ZONE else self.CS_UTM_ZONE
1471 if self.CS_UTM_ZONE != -99.:
1472 self.CS_TYPE = 'UTM'
1473 self.CS_EPSG = self.CS_EPSG if self.CornerTieP_LonLat == [] else int('326' + str(self.CS_UTM_ZONE)) \
1474 if self.CornerTieP_LonLat[0][1] > 0.0 else int('327' + str(self.CS_UTM_ZONE))
1475 self.CS_DATUM = 'WGS84'
1476 else:
1477 self.CS_TYPE = 'LonLat' if self.CornerTieP_LonLat != [] and -180. <= self.CornerTieP_LonLat[0][0] <= 180. \
1478 and -90. <= self.CornerTieP_LonLat[0][1] <= 90. else self.CS_TYPE
1479 self.CS_EPSG = 4326 if self.CS_TYPE == 'LonLat' else self.CS_TYPE
1480 self.CS_DATUM = 'WGS84' if self.CS_TYPE == 'LonLat' else self.CS_DATUM
1482 ##########################
1483 # band specific metadata #
1484 ##########################
1486 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
1488 # GainMode with corresponding coefficients + Offsets
1489 gains_AVNIR = {'1': ['N/A', 'N/A', 'N/A', 'N/A'], '2': [0.5880, 0.5730, 0.5020, 0.5570],
1490 '3': [0.9410, 0.9140, 1.2040, 0.835], '4': [1.412, 1.373, 'N/A', 0.8350]}
1491 # gains_PRISM = {'1': ['N/A', 'N/A', 'N/A', 'N/A'], '2': [0.501, 'N/A', 'N/A', 'N/A'],
1492 # '3': ['N/A', 'N/A', 'N/A', 'N/A'], '4': ['N/A', 'N/A', 'N/A', 'N/A']}
1494 h3 = re.search(r'Img_GainModeBand1="([1-4])"[\s]*'
1495 r'Img_GainModeBand2="([1-4])"[\s]*'
1496 r'Img_GainModeBand3="([1-4])"'
1497 r'[\s]*Img_GainModeBand4="([1-4])"',
1498 sum_, re.I)
1499 self.additional.append(
1500 ["GainMode: B1:'%s',B2:'%s',B3:'%s',B4:'%s'" % (h3.group(1), h3.group(2), h3.group(3), h3.group(4))])
1501 self.Gains = dict(zip(LBA_full_sorted, [gains_AVNIR[h3.group(1)][0], gains_AVNIR[h3.group(2)][1],
1502 gains_AVNIR[h3.group(3)][2], gains_AVNIR[h3.group(4)][3]]))
1503 self.Offsets = dict(zip(LBA_full_sorted, [0, 0, 0, 0])) # only gains are required for DN2radiance calculation
1505 # Solar irradiance, central wavelengths, full width half maximum per band
1506 self.wvlUnit = 'Nanometers'
1507 # derive number of bands from number of given gains/offsets in header file or from raster data
1508 # noinspection PyBroadException
1509 try:
1510 self.nBands = (np.mean([len(self.Gains), len(self.Offsets)])
1511 if np.std([len(self.Gains), len(self.Offsets)]) == 0 and len(self.Gains) != 0 else
1512 GEOP.GEOPROCESSING(self.Dataname, self.logger).bands)
1513 except Exception:
1514 self.logger.warning('Unable to get number of bands for the dataset %s Provider values are used for '
1515 'solar irradiation, CWL and FWHM!.' % self.Dataname)
1516 self.LayerBandsAssignment = get_LayerBandsAssignment(self.GMS_identifier, self.nBands)
1518 # Exact values calculated based in SRFs
1519 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band()
1520 # Provider values
1521 if not self.SolIrradiance:
1522 # Preconfigured Irradiation values
1523 # = Thullier. source: https://earth.esa.int/c/document_library/get_file?folderId=19584&name=DLFE-262.pdf
1524 self.SolIrradiance = dict(zip(LBA_full_sorted, [1943.3, 1813.7, 1562.3, 1076.5]))
1525 # Preconfigured CWLs
1526 self.CWL = dict(zip(LBA_full_sorted, [460., 560., 650., 825.]))
1527 # http://www.isprs-ann-photogramm-remote-sens-spatial-inf-sci.net/I-7/291/2012/isprsannals-I-7-291-2012.pdf
1528 self.FWHM = dict(zip(LBA_full_sorted, [80., 80., 80., 130.]))
1530 self.filter_layerdependent_metadata()
1531 self.orbitParams = get_orbit_params(self.GMS_identifier)
1532 self.spec_vals = get_special_values(self.GMS_identifier)
1534 def Read_ALOS_LEADER(self):
1535 """Read metadata from ALOS leader file. binary.
1537 For exact information content see:
1538 file:///misc/ro2/behling/Satelliten/ALOS/doc/ALOS Product Format description.pdf
1539 """
1541 # self.default_attr()
1542 if os.path.isdir(self.FolderOrArchive):
1543 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '*/*data*/LED-*'))
1544 assert len(glob_res) > 0, 'No LED* file can be found in %s/*/*data*/!' % self.FolderOrArchive
1545 self.Metafile = glob_res[0]
1546 lead_ = open(self.Metafile, "rb").read()
1547 else: # archive
1548 lead_, self.Metafile = \
1549 open_specific_file_within_archive(self.FolderOrArchive, '*/*data*/LED-*', read_mode='rb')
1551 # Gains & offsets
1552 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
1553 self.Gains = dict(zip(LBA_full_sorted, [float(lead_[4680 * 3 + 2703:4680 * 3 + 2710]),
1554 float(lead_[4680 * 3 + 2719:4680 * 3 + 2726]),
1555 float(lead_[4680 * 3 + 2735:4680 * 3 + 2742]),
1556 float(lead_[4680 * 3 + 2751:4680 * 3 + 2758])]))
1557 self.Offsets = dict(zip(LBA_full_sorted, [float(lead_[4680 * 3 + 2711:4680 * 3 + 2718]),
1558 float(lead_[4680 * 3 + 2727:4680 * 3 + 2734]),
1559 float(lead_[4680 * 3 + 2743:4680 * 3 + 2750]),
1560 float(lead_[4680 * 3 + 2759:4680 * 3 + 2766])]))
1562 def Read_Sentinel2_xmls(self):
1563 """Read metadata from Sentinel-2 generic xml and granule xml"""
1565 # query database to get entityid
1566 assert self.SceneID and self.SceneID != -9999, "Read_Sentinel2_xmls(): Missing scene ID. "
1567 res = DB_T.get_info_from_postgreSQLdb(CFG.conn_database, 'scenes', ['entityid'], {'id': self.SceneID})
1568 assert len(res) != 0, \
1569 "Invalid SceneID given - no corresponding scene with the ID=%s found in database.\n" % self.SceneID
1570 assert len(res) == 1, "Error in database. The sceneid %s exists more than once. \n" % self.SceneID
1572 S2AB = 'S2A' if re.search(r"Sentinel-2A", self.Satellite, re.I) else 'S2B'
1573 S2ABgranuleID = res[0][0]
1575 #################
1576 # get XML roots #
1577 #################
1579 if os.path.isdir(self.FolderOrArchive):
1580 # metadata has to be read from folder
1581 #####################################
1583 # get xml_Scene_root (contains scene metadata)
1584 glob_res = glob.glob(os.path.join(self.FolderOrArchive, '%s*.xml' % S2AB))
1585 if not glob_res:
1586 # new style packaging
1587 glob_res = glob.glob(os.path.join(self.FolderOrArchive, 'MTD*.xml'))
1588 assert len(glob_res) > 0, 'No %s*.xml or MTD*.xml file can be found in %s/!' % (S2AB, self.FolderOrArchive)
1589 self.Metafile = glob_res[0]
1590 xml_Scene_root = ET.parse(glob_res[0]).getroot() # xml_parser from file
1592 # get xml_GR_root (contains granule metadata)
1593 glob_res = glob.glob(os.path.join(self.FolderOrArchive, 'GRANULE/' + S2ABgranuleID + '/%s*.xml' % S2AB))
1594 if not glob_res:
1595 # new style packaging
1596 glob_res = glob.glob(os.path.join(self.FolderOrArchive, 'GRANULE/' + S2ABgranuleID + '/MTD*.xml'))
1597 assert len(glob_res) > 0, \
1598 'No /GRANULE/<%sgranuleID>/%s*.xml or MTD*.xml file can be found in %s/!' \
1599 % (S2AB, S2AB, self.FolderOrArchive)
1600 self.Metafile = self.Metafile + ", " + glob_res[0]
1601 xml_GR_root = ET.parse(glob_res).getroot() # xml_parser from file
1603 else:
1604 # metadata has to be read from within archive
1605 #############################################
1607 # get xml_Scene_root and xml_GR_root (contain scene and granule metadata)
1608 try:
1609 # old style packaging
1610 xml_SC_str_, self.Metafile = \
1611 open_specific_file_within_archive(self.FolderOrArchive, '*.SAFE/%s*.xml' % S2AB)
1612 xml_GR_str_, Metafile_ = \
1613 open_specific_file_within_archive(self.FolderOrArchive, '*.SAFE/GRANULE/' +
1614 S2ABgranuleID + '/%s*.xml' % S2AB)
1615 except RuntimeError: # wrong matching expression
1616 # new style packaging
1617 xml_SC_str_, self.Metafile = open_specific_file_within_archive(self.FolderOrArchive, '*.SAFE/MTD*.xml')
1618 xml_GR_str_, Metafile_ = open_specific_file_within_archive(self.FolderOrArchive, '*.SAFE/GRANULE/' +
1619 S2ABgranuleID + '/MTD*.xml')
1621 xml_Scene_root = ET.fromstring(xml_SC_str_)
1622 xml_GR_root = ET.fromstring(xml_GR_str_)
1623 self.Metafile = self.Metafile + ", " + Metafile_
1625 ###################################
1626 # EXTRACT METADATA FROM XML ROOTS #
1627 ###################################
1629 # define Sentinel 2 metadata (hard coded)
1630 self.Sensor = "MSI"
1632 # extract metadata from xml_Scene_root #
1633 ########################################
1635 # get the namespace within the xml_Scene_root
1636 m = re.match(r'{(.*)\}', xml_Scene_root.tag) # extract namespace from "{https://....}Level-1C_Tile_ID"
1637 assert m, 'XML Namespace could not be identified within Sentinel-2 metadata XML file.'
1638 namespace = m.group(1)
1640 self.EntityID = \
1641 xml_Scene_root.find(".//Datatake").attrib['datatakeIdentifier'] # FIXME tileID (Granule) oder scene ID???
1642 self.Satellite = xml_Scene_root.find(".//SPACECRAFT_NAME").text
1644 # AcqDate, AcqTime
1645 # NOTE ths corresponds to the while data take, not to the granule, BUT has th same value as the one from granule
1646 # DateTime = xml_Scene_root.find(".//PRODUCT_START_TIME").text
1647 # ''' PRODUCT_START_TIME = Sensing Time of the first line of the first scene in the product.
1648 # Alternative: 'DATATAKE_SENSING_START': Sensing start time of the Datatake'''
1649 # self.AcqDate = DateTime[:10]
1650 # self.AcqTime = DateTime[11:19]
1652 self.ScaleFactor = int(xml_Scene_root.find(".//QUANTIFICATION_VALUE").text)
1653 self.PhysUnit = "TOA_Reflectance in [0-%d]" % self.ScaleFactor
1655 # Flight direction
1656 Fdir = {'ASCENDING': "Ascending", 'DESCENDING': "Descending"}
1657 self.additional.append(["Flight Direction", Fdir[xml_Scene_root.find(".//SENSING_ORBIT_DIRECTION").text]])
1659 self.ProcLCode = xml_Scene_root.find(".//PROCESSING_LEVEL").text
1661 # extract metadata from xml_GR_root #
1662 #####################################
1664 # get the namespace within the xml_GR_root
1665 m = re.match(r'{(.*)\}', xml_GR_root.tag) # extract namespace from "{https://....}Level-1C_Tile_ID"
1666 assert m, 'XML Namespace could not be identified within metadata XML file.'
1667 namespace = m.group(1) # e.g., "https://psd-12.sentinel2.eo.esa.int/PSD/S2_PDI_Level-1C_Tile_Metadata.xsd"
1669 # set self.AcqDateTime as well as self.AcqDate and self.AcqTime
1670 self.AcqDateTime = iso8601.parse_date(xml_GR_root.find(".//SENSING_TIME").text)
1672 # SunAngles
1673 self.SunElevation = 90 - float(xml_GR_root.find(".//Mean_Sun_Angle/ZENITH_ANGLE").text) # mean angle of granule
1674 self.SunAzimuth = float(xml_GR_root.find(".//Mean_Sun_Angle/AZIMUTH_ANGLE").text) # mean angle of granule
1676 # coordinate system
1677 geo_codings = HLP_F.find_in_xml_root(namespace, xml_GR_root, 'Geometric_Info', "Tile_Geocoding")
1678 self.CS_EPSG = int(geo_codings.find(".//HORIZONTAL_CS_CODE").text.split(":")[1])
1679 CooSys = geo_codings.find(".//HORIZONTAL_CS_NAME").text
1680 self.CS_DATUM = "WGS84" if re.search(r"wgs84", CooSys, re.I).group(0) else self.CS_DATUM
1681 self.CS_TYPE = "UTM" if re.search(r"utm", CooSys, re.I).group(0) else self.CS_TYPE
1682 if self.CS_TYPE == "UTM":
1683 tmp = CooSys.split(" ")[-1]
1684 self.CS_UTM_ZONE = \
1685 int(tmp[:-1]) if tmp[-1] == 'N' else \
1686 -int(tmp[:-1]) if tmp[-1] == 'S' else self.CS_UTM_ZONE
1688 # corner coords
1689 subsytem_Res_dic = {"%s10" % S2AB: 10, "%s20" % S2AB: 20, "%s60" % S2AB: 60}
1690 if self.CS_TYPE == 'UTM':
1691 spatial_samplings = {float(size.get("resolution")): {key: int(size.find(key).text)
1692 for key in ["NROWS", "NCOLS"]} for size in
1693 geo_codings.findall("Size")}
1694 for geo in geo_codings.findall("Geoposition"):
1695 spatial_samplings[float(geo.get("resolution"))].update(
1696 {key: float(geo.find(key).text) for key in ["ULX", "ULY", "XDIM", "YDIM"]})
1698 ss_sub = spatial_samplings[subsytem_Res_dic[self.Subsystem]]
1699 LRX = ss_sub['ULX'] + ss_sub['NCOLS'] * ss_sub['XDIM']
1700 LRY = ss_sub['ULY'] + ss_sub['NROWS'] * ss_sub['YDIM']
1701 self.CornerTieP_UTM = [(ss_sub['ULX'], ss_sub['ULY']), (LRX, ss_sub['ULY']),
1702 (ss_sub['ULX'], LRY), (LRX, LRY)] # (x,y) for UL,UR,LL,LR
1704 # geometricResolution
1705 self.gResolution = subsytem_Res_dic[self.Subsystem]
1707 # determine metadata from extracted metadata values
1708 self.EarthSunDist = self.get_EarthSunDistance(self.AcqDate)
1710 # Quality flags # FIXME does not work (at least with old data)
1711 Quality_temp = (xml_Scene_root.find(".//Technical_Quality_Assessment"))
1712 self.Quality.append(["DEGRADED_ANC_DATA_PERCENTAGE", Quality_temp.find("./DEGRADED_ANC_DATA_PERCENTAGE").text])
1713 self.Quality.append(["DEGRADED_MSI_DATA_PERCENTAGE", Quality_temp.find("./DEGRADED_MSI_DATA_PERCENTAGE").text])
1714 Quality_temp2 = xml_Scene_root.find(".//Quality_Inspections")
1715 quality_flags = ["SENSOR_QUALITY_FLAG", "GEOMETRIC_QUALITY_FLAG", "GENERAL_QUALITY_FLAG",
1716 "FORMAT_CORRECTNESS_FLAG", "RADIOMETRIC_QUALITY_FLAG"]
1718 try:
1719 # layout example: <SENSOR_QUALITY_FLAG>PASSED</SENSOR_QUALITY_FLAG>
1720 for ql in quality_flags:
1721 self.Quality.append([ql, Quality_temp2.find("./" + ql).text])
1722 except AttributeError:
1723 # since ~11/2017 the quality checks layout in the XML has changed:
1724 # layout example: <quality_check checkType="SENSOR_QUALITY">PASSED</quality_check>
1725 elements = Quality_temp2.findall('quality_check')
1726 checkTypeValDict = {ele.attrib['checkType']: ele.text for ele in elements}
1727 for ql in quality_flags:
1728 self.Quality.append([ql, checkTypeValDict[ql.split('_FLAG')[0]]])
1730 ##########################
1731 # band specific metadata #
1732 ##########################
1734 LBA_full_sorted = natsorted(self.LayerBandsAssignment_full)
1736 # ATTENTION Gains are only provided for 12 bands! I don't know why?
1737 Gains = [float(ele.text) for ele in xml_Scene_root.findall(".//PHYSICAL_GAINS")]
1738 Gains = Gains if len(Gains) == 13 else [1] + Gains
1739 self.Gains = dict(zip(LBA_full_sorted, Gains))
1740 # FIXME assuming that the first band at 443nm has been left out here IS POSSIBLY WRONG
1741 # FIXME (could also be band 8A oder band 9 (water vapour))
1743 # Solar irradiance, central wavelengths, full width half maximum per band
1744 self.wvlUnit = 'Nanometers'
1745 self.LayerBandsAssignment = get_LayerBandsAssignment(self.GMS_identifier)
1747 # Exact values calculated based in SRFs
1748 self.SolIrradiance, self.CWL, self.FWHM = self.calc_solar_irradiance_CWL_FWHM_per_band()
1749 # Provider values
1750 if not self.SolIrradiance:
1751 # get from xml file
1752 self.SolIrradiance = \
1753 dict(zip(LBA_full_sorted,
1754 [float(ele.text) for ele in
1755 xml_Scene_root.find(".//Solar_Irradiance_List").findall("SOLAR_IRRADIANCE")]))
1756 # Preconfigured CWLs
1757 self.CWL = dict(zip(LBA_full_sorted,
1758 [float(ele.text) for ele in
1759 xml_Scene_root.find(".//Spectral_Information_List").findall(".//CENTRAL")]))
1761 # SensorAngles
1762 meta_temp = {}
1763 branch = HLP_F.find_in_xml_root(namespace, xml_GR_root, *("Geometric_Info", "Tile_Angles"))
1764 meta_temp["bandId2bandName"] = {int(ele.get("bandId")): ele.text.split("_")[-2] for ele in
1765 xml_GR_root.findall(".//MASK_FILENAME") if ele.get("bandId") is not None}
1766 meta_temp["bandName2bandId"] = {bandName: bandId for bandId, bandName in
1767 meta_temp["bandId2bandName"].items()}
1768 meta_temp["bandIds"] = sorted(list(meta_temp["bandId2bandName"].keys()))
1769 meta_temp["viewing_zenith_detectors"] = \
1770 {bandId: {bf.get("detectorId"): HLP_F.get_values_from_xml(HLP_F.find_in_xml(bf, *("Zenith", "Values_List")))
1771 for bf in branch.findall("Viewing_Incidence_Angles_Grids[@bandId='%i']" % bandId)}
1772 for bandId in meta_temp["bandIds"]}
1773 meta_temp["viewing_zenith"] = HLP_F.stack_detectors(meta_temp["viewing_zenith_detectors"])
1775 meta_temp["viewing_azimuth_detectors"] = {bandId: {bf.get("detectorId"): HLP_F.get_values_from_xml(
1776 HLP_F.find_in_xml(bf, *("Azimuth", "Values_List"))) for bf in branch.findall(
1777 "Viewing_Incidence_Angles_Grids[@bandId='%i']" % bandId)} for bandId in meta_temp["bandIds"]}
1778 meta_temp["viewing_azimuth"] = HLP_F.stack_detectors(meta_temp["viewing_azimuth_detectors"])
1779 # mean values of all mean band values # FIXME
1780 self.ViewingAngle = np.mean([np.nanmean(i) for i in meta_temp['viewing_zenith'].values()])
1781 self.IncidenceAngle = np.mean([np.nanmean(i) for i in meta_temp['viewing_azimuth'].values()])
1782 # 5000m step arrays per band. dict: key = bandindex, value = stacked_array for all detectors
1784 self.ViewingAngle_arrProv = meta_temp['viewing_zenith']
1785 self.IncidenceAngle_arrProv = meta_temp['viewing_azimuth']
1786 LBA2Id_dic = {'1': 0, '2': 1, '3': 2, '4': 3, '5': 4, '6': 5, '7': 6, '8': 7, '8A': 8, '9': 9, '10': 10,
1787 '11': 11, '12': 12}
1789 def filter_dic(AngleArr): return {LBAn: AngleArr[LBA2Id_dic[LBAn]] for LBAn in self.LayerBandsAssignment}
1791 self.ViewingAngle_arrProv = filter_dic(self.ViewingAngle_arrProv)
1792 self.IncidenceAngle_arrProv = filter_dic(self.IncidenceAngle_arrProv)
1794 self.FOV = get_FieldOfView(self.GMS_identifier)
1795 self.orbitParams = get_orbit_params(self.GMS_identifier)
1796 self.filter_layerdependent_metadata()
1797 self.spec_vals = get_special_values(self.GMS_identifier)
1799 def add_rasObj_dims_projection_physUnit(self, rasObj, dict_LayerOptTherm, temp_logger=None):
1800 self.rows = rasObj.rows
1801 self.cols = rasObj.cols
1802 self.bands = rasObj.bands
1803 if rasObj.projection != '':
1804 self.map_info = geotransform2mapinfo(rasObj.geotransform, rasObj.projection)
1805 self.projection = rasObj.projection
1806 self.gResolution = abs(rasObj.geotransform[1])
1807 self.CS_EPSG = WKT2EPSG(rasObj.projection)
1809 dict_conv_physUnit = {'Rad': "W * m-2 * sr-1 * micrometer-1",
1810 'TOA_Ref': 'TOA_Reflectance in [0-%d]' % CFG.scale_factor_TOARef,
1811 'BOA_Ref': 'BOA_Reflectance in [0-%d]' % CFG.scale_factor_BOARef,
1812 'Temp': 'Degrees Celsius with scale factor = 100'}
1813 if list(set(dict_LayerOptTherm.values())) == ['optical']:
1814 self.PhysUnit = dict_conv_physUnit[CFG.target_radunit_optical]
1815 elif list(set(dict_LayerOptTherm.values())) == ['thermal']:
1816 self.PhysUnit = dict_conv_physUnit[CFG.target_radunit_thermal]
1817 elif sorted(list(set(dict_LayerOptTherm.values()))) == ['optical', 'thermal']:
1818 self.PhysUnit = ['Optical bands: %s' % dict_conv_physUnit[CFG.target_radunit_optical],
1819 'Thermal bands: %s' % dict_conv_physUnit[CFG.target_radunit_thermal]]
1820 else:
1821 logger = self.logger if hasattr(self, 'logger') else temp_logger
1822 assert logger, "ERROR: Physical unit could not be determined due to unexpected 'dict_LayerOptTherm'. " \
1823 "Got %s." % dict_LayerOptTherm
1824 logger.error("Physical unit could not be determined due to unexpected 'dict_LayerOptTherm'. Got %s."
1825 % dict_LayerOptTherm)
1827 def to_odict(self):
1828 # type: () -> collections.OrderedDict
1829 """Creates an OrderedDict containing selected attribute of the METADATA object that will later be included in
1830 ENVI file headers in the same order.
1832 """
1833 # FIXME orbit params are missing
1834 # descr_dic = dict( # FillZeroSaturated von HLP_F ausgeben lassen
1835 # ALOS_Rad=
1836 # "(1) GEOCODED Level1B2 Product; '" + self.Dataname + "'\n "
1837 # "(2) Int16 RadianceData in [W * m-2 * sr-1 * micrometer-1]*10; "
1838 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1839 # ALOS_Ref=
1840 # "(1) Orthorectified JAXA or GFZ; '" + self.Dataname + "'\n "
1841 # "(2) Int16 TOA_Reflectance in [0-10000]; "
1842 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1843 # Terra_Rad=
1844 # "(1) Orthorectified JAXA or GFZ; '" + self.Dataname + "'\n "
1845 # "(2) Int16 RadianceData in [W * m-2 * sr-1 * micrometer-1]*10; "
1846 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1847 # Terra_Ref=
1848 # "(1) Orthorectified JAXA or GFZ; '" + self.Dataname + "'\n "
1849 # "(2) Int16 TOA_Reflectance in [0-10000]; "
1850 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1851 # Landsat_Rad=
1852 # "(1) Landsat DN: "+self.ProcLCode+ " Product; '"+self.Dataname+"'\n "
1853 # "(2) Int16 RadianceData in [W * m-2 * sr-1 * micrometer-1]*10; "
1854 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1855 # Landsat_Ref=
1856 # "(1) Landsat DN: "+self.ProcLCode + " Product; '" + self.Dataname + "'\n "
1857 # "(2) Int16 TOA_Reflectance in [0-10000] "
1858 # "(fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1859 # RapidEye_Rad=
1860 # "(1) Ortho Level3A01 Product; '"+self.Dataname+"'\n "
1861 # "(2) Int16 RadianceData in [W * m-2 * sr-1 * micrometer-1]*10; "
1862 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1863 # RapidEye_Ref=
1864 # "(1) Ortho Level3A01 Product; '" + self.Dataname + "'\n "
1865 # "(2) Int16 TOA_Reflectance in [0-10000] "
1866 # "(fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1867 # SPOT_Rad=
1868 # "(1) Ortho Level3A01 Product; '" + self.Dataname + "'\n "
1869 # "(2) Int16 RadianceData in [W * m-2 * sr-1 * micrometer-1]*10; "
1870 # "radiance scale factor: 10 (fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'",
1871 # SPOT_Ref=
1872 # "(1) Ortho Level3A01 Product; '" + self.Dataname + "'\n "
1873 # "(2) Int16 TOA_Reflectance in [0-10000] "
1874 # "(fillPixels: -99, zeroPixels:0, saturatedPixels: 32767 (Max of Int16))'")
1876 # copy directly compatible keys
1877 Meta = collections.OrderedDict()
1878 # Meta['description'] = descr_dic[self.Satellite + '_' + CFG.target_radunit_optical]
1880 for odictKey in enviHdr_keyOrder:
1881 if odictKey in map_odictKeys_objAttrnames:
1882 attrVal = getattr(self, map_odictKeys_objAttrnames[odictKey])
1883 if attrVal and map_odictKeys_objAttrnames[odictKey] in layerdependent_metadata:
1884 # convert band specific metadata dicts to lists in the order of LayerBandsAssignment
1885 Meta.update({odictKey: [attrVal[band] for band in self.LayerBandsAssignment if band in attrVal]})
1886 else:
1887 Meta.update({odictKey: attrVal})
1888 elif odictKey == 'map info' and self.map_info:
1889 Meta['map info'] = self.map_info
1890 elif odictKey == 'coordinate system string' and self.projection:
1891 Meta['coordinate system string'] = self.projection
1892 elif odictKey == 'data ignore value':
1893 Meta['data ignore value'] = self.spec_vals['fill'] if 'fill' in self.spec_vals else None
1894 elif odictKey == 'corner coordinates lonlat':
1895 Meta['corner coordinates lonlat'] = str(self.CornerTieP_LonLat).replace('(', '[').replace(')', ']')
1896 elif odictKey == 'wavelength units':
1897 Meta['wavelength units'] = "Nanometers"
1898 elif odictKey == 'band names':
1899 Meta['band names'] = self.bandnames
1901 # add keys that will not be included into ENVI header
1902 if self.ViewingAngle_arrProv is not None:
1903 Meta['ViewingAngle_arrProv'] = {k: v.tolist() for k, v in self.ViewingAngle_arrProv.items()}
1904 Meta['IncidenceAngle'] = self.IncidenceAngle
1905 if self.IncidenceAngle_arrProv is not None:
1906 Meta['IncidenceAngle_arrProv'] = {k: v.tolist() for k, v in self.IncidenceAngle_arrProv.items()}
1908 return Meta
1910 def from_odict(self, odict):
1911 # type: (collections.OrderedDict) -> METADATA
1913 # copy directly compatible keys
1914 # [setattr(self, attrN, odict[odictKey]) for odictKey, attrN in map_odictKeys_objAttrnames.items()]
1916 for odictKey, attrN in map_odictKeys_objAttrnames.items():
1917 if attrN not in layerdependent_metadata:
1918 setattr(self, attrN, odict[odictKey])
1919 else:
1920 # convert band specific metadata to dicts
1921 setattr(self, attrN, dict(zip(odict['LayerBandsAssignment'], odict[odictKey])))
1923 # set the remaining attributes
1924 if 'map info' in odict:
1925 self.map_info = odict['map info']
1926 if 'coordinate system string' in odict:
1927 self.projection = odict['coordinate system string']
1928 if 'data ignore value' in odict:
1929 self.spec_vals['fill'] = odict['data ignore value']
1930 if 'ViewingAngle_arrProv' in odict:
1931 self.ViewingAngle_arrProv = {bN: np.array(odict['ViewingAngle_arrProv'][bN])
1932 for bN in self.LayerBandsAssignment if bN in odict['ViewingAngle_arrProv']}
1933 if 'IncidenceAngle_arrProv' in odict:
1934 self.IncidenceAngle_arrProv = {bN: np.array(odict['IncidenceAngle_arrProv'][bN])
1935 for bN in self.LayerBandsAssignment if bN in odict['IncidenceAngle_arrProv']}
1937 return self
1939 def calc_solar_irradiance_CWL_FWHM_per_band(self):
1940 # type: () -> (dict, dict, dict)
1941 sensorcode = get_GMS_sensorcode(self.GMS_identifier)
1942 # ms_pan = ('multi' if self.nBands > 1 else 'pan')
1944 irr_bands, cwl_bands, fwhm_bands = {}, {}, {}
1946 if not sensorcode:
1947 self.logger.warning('GMS-sensorcode missing. Provider values are used for solar irradiation, CWL and FWHM.')
1948 else:
1949 self.logger.info('Calculating solar irradiance, central wavelengths and full width half maxima...')
1951 sol_irr = Solar_Irradiance_reader(wvl_min_nm=350, wvl_max_nm=2500)
1952 srf_dict = SRF_Reader(self.GMS_identifier, no_pan=False, no_thermal=False)
1954 for band in srf_dict.keys():
1955 if srf_dict[band] is None:
1956 irr_bands[band] = None
1957 cwl_bands[band] = None
1958 fwhm_bands[band] = None
1959 else:
1960 WVL_band = (srf_dict[band][:, 0] if 300 < np.max(srf_dict[band][:, 0]) < 15000 else
1961 srf_dict[band][:, 0] * 1000) # reads wavelengths given in nm and µm
1962 RSP_band = srf_dict[band][:, 1]
1963 # sol_irr_at_WVL = \
1964 # scipy.interpolate.interp1d(sol_irr[:, 0], sol_irr[:, 1], kind='linear')(WVL_band) # ASTER
1965 # band 8: ValueError: A value in x_new is above the interpolation range.
1966 sol_irr_at_WVL = np.interp(WVL_band, sol_irr[:, 0], sol_irr[:, 1], left=0, right=0)
1968 irr_bands[band] = round(np.sum(sol_irr_at_WVL * RSP_band) / np.sum(RSP_band), 2)
1969 cwl_bands[band] = round(np.sum(WVL_band * RSP_band) / np.sum(RSP_band), 2)
1970 fwhm_bands[band] = round(np.max(WVL_band[RSP_band >= (np.max(RSP_band) / 2.)]) -
1971 np.min(WVL_band[RSP_band >= (np.max(RSP_band) / 2.)]), 2)
1973 return irr_bands, cwl_bands, fwhm_bands
1975 def get_EarthSunDistance(self, acqDate):
1976 """Get earth sun distance (requires file of pre calculated earth sun distance per day)
1978 :param acqDate:
1979 """
1981 if not os.path.exists(CFG.path_earthSunDist):
1982 self.logger.warning("\n\t WARNING: Earth Sun Distance is assumed to be "
1983 "1.0 because no database can be found at %s.""" % CFG.path_earthSunDist)
1984 return 1.0
1985 if not acqDate:
1986 self.logger.warning("\n\t WARNING: Earth Sun Distance is assumed to be 1.0 because "
1987 "acquisition date could not be read from metadata.")
1988 return 1.0
1990 with open(CFG.path_earthSunDist, "r") as EA_dist_f:
1991 EA_dist_dict = {}
1992 for line in EA_dist_f:
1993 date, EA = [item.strip() for item in line.split(",")]
1994 EA_dist_dict[date] = EA
1996 return float(EA_dist_dict[acqDate])
1998 def calc_center_acquisition_time(self, fullSceneCornerLonLat, logger):
1999 """Calculates a missing center acquistion time using acquisition date, full scene corner coordinates and
2000 solar azimuth.
2002 :param fullSceneCornerLonLat:
2003 :param logger:
2004 """
2005 assert is_dataset_provided_as_fullScene(self.GMS_identifier) and len(fullSceneCornerLonLat) == 4, \
2006 'Center acquisition time can only be computed for datasets provided as full scenes, not for tiles.'
2008 ul, lr = fullSceneCornerLonLat[0], fullSceneCornerLonLat[3]
2009 center_coord = [np.mean([ul[0], lr[0]]), np.mean([ul[1], lr[1]])]
2010 time0_ord = mdates.date2num(
2011 datetime.datetime.strptime('%s %s' % (self.AcqDate, '00:00:00'), '%Y-%m-%d %H:%M:%S'))
2012 time1_ord = mdates.date2num(
2013 datetime.datetime.strptime('%s %s' % (self.AcqDate, '23:59:59'), '%Y-%m-%d %H:%M:%S'))
2014 time_stamps_ord = np.linspace(time0_ord, time1_ord, 12 * 60 * 60)
2015 time_stamps_with_tzinfo = mdates.num2date(time_stamps_ord)
2016 time_stamps = np.array([time_stamps_with_tzinfo[i].replace(tzinfo=None)
2017 for i in range(len(time_stamps_with_tzinfo))])
2018 sols_az_rad = astronomy.get_alt_az(time_stamps, [center_coord[0]] * time_stamps.size,
2019 [center_coord[1]] * time_stamps.size)[1]
2020 sol_azs = 180 * sols_az_rad / math.pi
2021 diff_az = np.abs(float(self.SunAzimuth) - sol_azs)
2022 acq_datetime = time_stamps[np.where(diff_az == np.min(diff_az))][0]
2023 AcqTime = acq_datetime.strftime(format='%H:%M:%S')
2024 logger.info('Center acquisition time has been calculated: %s' % AcqTime)
2026 # update self.
2027 self.AcqDateTime = datetime.datetime.strptime(
2028 '%s %s%s' % (self.AcqDate, AcqTime, '.000000+0000'), '%Y-%m-%d %H:%M:%S.%f%z')
2029 return self.AcqTime
2031 def get_overpassDuration_SceneLength(self, fullSceneCornerLonLat, fullSceneCornerPos, shape_fullArr, logger):
2032 """Calculates duration of image acquisition in seconds.
2034 :param fullSceneCornerLonLat:
2035 :param fullSceneCornerPos:
2036 :param shape_fullArr:
2037 :param logger:
2038 """
2039 assert is_dataset_provided_as_fullScene(self.GMS_identifier) and len(fullSceneCornerLonLat) == 4, \
2040 'Overpass duration and scene length can only be computed for datasets provided as full scenes, not for ' \
2041 'tiles.'
2043 # check if current scene is a subset
2044 assert fullSceneCornerPos != list(([0, 0], [0, shape_fullArr[1] - 1],
2045 [shape_fullArr[0] - 1, 0], [shape_fullArr[0] - 1, shape_fullArr[1] - 1])),\
2046 'Overpass duration and scene length cannot be calculated because the given data represents a subset of ' \
2047 'the original scene.'
2049 # compute scene length
2050 orbitAltitudeKm, orbitPeriodMin = self.orbitParams[0], self.orbitParams[2]
2051 UL, UR, LL, LR = fullSceneCornerLonLat
2052 geod = pyproj.Geod(ellps='WGS84')
2053 scene_length = np.mean([geod.inv(UL[0], UL[1], LL[0], LL[1])[2],
2054 geod.inv(UR[0], UR[1], LR[0], LR[1])[2]]) / 1000 # along-track distance [km]
2055 logger.info('Calculation of scene length...: %s km' % round(float(scene_length), 2))
2057 # compute overpass duration
2058 orbitPeriodLength = 2 * math.pi * (6371 + orbitAltitudeKm)
2059 overpassDurationSec = (scene_length / orbitPeriodLength) * orbitPeriodMin * 60.
2060 logger.info('Calculation of overpass duration...: %s sec' % round(overpassDurationSec, 2))
2062 return overpassDurationSec, scene_length
2064 def filter_layerdependent_metadata(self):
2065 for attrname in layerdependent_metadata:
2066 attrVal = getattr(self, attrname)
2068 if not attrVal:
2069 continue
2071 if isinstance(attrVal, dict):
2072 setattr(self, attrname, {bN: attrVal[bN] for bN in self.LayerBandsAssignment})
2074 elif isinstance(attrVal, collections.OrderedDict):
2075 setattr(self, attrname, collections.OrderedDict((bN, attrVal[bN]) for bN in self.LayerBandsAssignment))
2077 else:
2078 raise ValueError
2080 if attrVal:
2081 assert len(getattr(self, attrname)) == len(self.LayerBandsAssignment)
2084layerdependent_metadata = ['SolIrradiance', 'CWL', 'FWHM', 'Offsets', 'OffsetsRef', 'Gains', 'GainsRef',
2085 'ThermalConstK1', 'ThermalConstK2', 'ViewingAngle_arrProv', 'IncidenceAngle_arrProv']
2088map_odictKeys_objAttrnames = {
2089 'samples': 'cols',
2090 'lines': 'rows',
2091 'bands': 'bands',
2092 'version_gms_preprocessing': 'version_gms_preprocessing',
2093 'versionalias_gms_preprocessing': 'versionalias_gms_preprocessing',
2094 'CS_EPSG': 'CS_EPSG',
2095 'CS_TYPE': 'CS_TYPE',
2096 'CS_DATUM': 'CS_DATUM',
2097 'CS_UTM_ZONE': 'CS_UTM_ZONE',
2098 'scene length': 'scene_length',
2099 'wavelength': 'CWL',
2100 'bandwidths': 'FWHM',
2101 'LayerBandsAssignment': 'LayerBandsAssignment',
2102 'data gain values': 'Gains',
2103 'data offset values': 'Offsets',
2104 'reflectance gain values': 'GainsRef',
2105 'reflectance offset values': 'OffsetsRef',
2106 'Metafile': 'Metafile',
2107 'Satellite': 'Satellite',
2108 'Sensor': 'Sensor',
2109 'Subsystem': 'Subsystem',
2110 'EntityID': 'EntityID',
2111 'SceneID': 'SceneID',
2112 'gResolution': 'gResolution',
2113 'AcqDate': 'AcqDate',
2114 'AcqTime': 'AcqTime',
2115 'overpass duraction sec': 'overpassDurationSec',
2116 'ProcLCode': 'ProcLCode',
2117 'SunElevation': 'SunElevation',
2118 'SunAzimuth': 'SunAzimuth',
2119 'SolIrradiance': 'SolIrradiance',
2120 'ThermalConstK1': 'ThermalConstK1',
2121 'ThermalConstK2': 'ThermalConstK2',
2122 'EarthSunDist': 'EarthSunDist',
2123 'ViewingAngle': 'ViewingAngle',
2124 'IncidenceAngle': 'IncidenceAngle',
2125 'FieldOfView': 'FOV',
2126 'PhysUnit': 'PhysUnit',
2127 'ScaleFactor': 'ScaleFactor',
2128 'Quality': 'Quality',
2129 'Additional': 'additional'
2130}
2133def get_LayerBandsAssignment(GMS_id, nBands=None, sort_by_cwl=None, no_thermal=None, no_pan=None,
2134 return_fullLBA=False, proc_level=''):
2135 # type: (GMS_identifier, int, bool, bool, bool, bool, str) -> list
2136 """Returns LayerBandsAssignment corresponding to given satellite, sensor and subsystem and with respect to
2137 CFG.sort_bands_by_cwl, CFG.skip_thermal and CFG.skip_pan.
2139 :param GMS_id: <dict>, derived from self.get_GMS_identifier()
2140 NOTE: only if there is an additional key 'proc_level', the processing level will be
2141 respected. This is needed to get the correct LBA after atm. correction
2142 :param nBands: should be specified if number of bands differs from standard
2143 (e.g. SPOT data containing no PAN)
2144 :param sort_by_cwl: whether to sort the returned bands list by central wavelength position
2145 (default: CFG.sort_bands_by_cwl)
2146 :param no_thermal: whether to exclude thermal bands from the returned bands list
2147 (default: CFG.skip_thermal)
2148 :param no_pan: whether to exclude panchromatic bands from the returned bands list
2149 (default: CFG.skip_pan)
2150 :param return_fullLBA: in case there is a subsystem:
2151 whether to return LayerBandsAssignment for all bands or for the current subsystem
2152 :param proc_level: processing level for which the LayerBandsAssignment is returned
2153 (overrides the proc_level given with GMS_id)
2154 """
2155 # set defaults
2156 # NOTE: these values cannot be set in function signature because CFG is not present at library import time
2157 sort_by_cwl = sort_by_cwl if sort_by_cwl is not None else CFG.sort_bands_by_cwl
2158 no_thermal = no_thermal if no_thermal is not None else CFG.skip_thermal
2159 no_pan = no_pan if no_pan is not None else CFG.skip_pan
2160 proc_level = proc_level or GMS_id.proc_level
2162 if GMS_id.image_type == 'RSD':
2163 GMS_sensorcode = get_GMS_sensorcode(GMS_id)
2164 assert GMS_sensorcode, 'Unable to get Layer Bands Assignment. No valid sensorcode privided (got >None<). '
2166 if return_fullLBA:
2167 GMS_sensorcode = \
2168 'AST_full' if GMS_sensorcode.startswith('AST') else \
2169 'S2A_full' if GMS_sensorcode.startswith('S2A') else \
2170 'S2B_full' if GMS_sensorcode.startswith('S2B') else GMS_sensorcode
2172 dict_LayerBandsAssignment = {
2173 'AVNIR-2': ['1', '2', '3', '4'],
2174 'AST_full': ['1', '2', '3N', '3B', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14'],
2175 'AST_V1': ['1', '2', '3N'],
2176 'AST_V2': ['3B'],
2177 'AST_S': ['4', '5', '6', '7', '8', '9'],
2178 'AST_T': ['10', '11', '12', '13', '14'],
2179 'TM4': ['1', '2', '3', '4', '5', '6', '7'],
2180 'TM5': ['1', '2', '3', '4', '5', '6', '7'],
2181 'TM7': ['1', '2', '3', '4', '5', '6L', '6H', '7', '8'],
2182 'LDCM': ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'],
2183 'SPOT1a': ['1', '2', '3', '4'],
2184 'SPOT2a': ['1', '2', '3', '4'],
2185 'SPOT3a': ['1', '2', '3', '4'],
2186 'SPOT4a': ['1', '2', '3', '4', '5'],
2187 'SPOT5a': ['1', '2', '3', '4', '5'],
2188 'SPOT1b': ['1', '2', '3', '4'],
2189 'SPOT2b': ['1', '2', '3', '4'],
2190 'SPOT3b': ['1', '2', '3', '4'],
2191 'SPOT4b': ['1', '2', '3', '4', '5'],
2192 'SPOT5b': ['1', '2', '3', '4', '5'],
2193 'RE5': ['1', '2', '3', '4', '5'],
2194 'S2A_full': ['1', '2', '3', '4', '5', '6', '7', '8', '8A', '9', '10', '11', '12'],
2195 'S2B_full': ['1', '2', '3', '4', '5', '6', '7', '8', '8A', '9', '10', '11', '12'],
2196 'S2A10': ['2', '3', '4', '8'],
2197 'S2A20': ['5', '6', '7', '8A', '11', '12'],
2198 'S2A60': ['1', '9', '10'],
2199 'S2B10': ['2', '3', '4', '8'],
2200 'S2B20': ['5', '6', '7', '8A', '11', '12'],
2201 'S2B60': ['1', '9', '10'], }
2203 dict_cwlSorted_LayerBandsAssignment = {
2204 'TM4': ['1', '2', '3', '4', '5', '7', '6'],
2205 'TM5': ['1', '2', '3', '4', '5', '7', '6'],
2206 'TM7': ['1', '2', '3', '8', '4', '5', '7', '6L', '6H'],
2207 'LDCM': ['1', '2', '3', '8', '4', '5', '9', '6', '7', '10', '11'],
2208 'SPOT1a': ['1', '4', '2', '3'],
2209 'SPOT2a': ['1', '4', '2', '3'],
2210 'SPOT3a': ['1', '4', '2', '3'],
2211 'SPOT4a': ['1', '5', '2', '3', '4'],
2212 'SPOT5a': ['1', '5', '2', '3', '4'],
2213 'SPOT1b': ['1', '4', '2', '3'],
2214 'SPOT2b': ['1', '4', '2', '3'],
2215 'SPOT3b': ['1', '4', '2', '3'],
2216 'SPOT4b': ['1', '5', '2', '3', '4'],
2217 'SPOT5b': ['1', '5', '2', '3', '4'],
2218 }
2220 if nBands is None or nBands == len(dict_LayerBandsAssignment[GMS_sensorcode]):
2221 assert GMS_sensorcode in dict_LayerBandsAssignment, \
2222 'Unable to get Layer Bands Assignment. No valid sensorcode privided (got >%s<).' % GMS_sensorcode
2223 LayerBandsAssignment = dict_LayerBandsAssignment[GMS_sensorcode]
2224 if sort_by_cwl and GMS_sensorcode in ['TM4', 'TM5', 'TM7', 'LDCM']:
2225 LayerBandsAssignment = dict_cwlSorted_LayerBandsAssignment[GMS_sensorcode]
2227 else: # special case SPOT MSI containing no PAN or SPOT PAN containing only PAN
2228 assert re.match(r'SPOT', GMS_id.satellite, re.I) and \
2229 nBands in [len(dict_LayerBandsAssignment[GMS_sensorcode]) - 1, 1], \
2230 "Unable to get Layer Bands Assignment. Provided number of bands doesn´t match known layer band " \
2231 "assignments."
2232 LayerBandsAssignment = [dict_LayerBandsAssignment[GMS_sensorcode][-1]] if nBands == 1 \
2233 else dict_LayerBandsAssignment[GMS_sensorcode][:-1]
2235 if no_thermal:
2236 LayerBandsAssignment = [i for i in LayerBandsAssignment if not isTHERMAL(GMS_id, i)]
2237 if no_pan:
2238 LayerBandsAssignment = [i for i in LayerBandsAssignment if not isPAN(GMS_id, i)]
2239 else:
2240 LayerBandsAssignment = ['1']
2242 # remove those bands that are excluded by atmospheric corrections if proc_level >= L1C
2243 if proc_level not in [None, 'L1A', 'L1B']: # TODO replace with enum procL
2245 if CFG.target_radunit_optical == 'BOA_Ref':
2246 # return LBA after AC
2247 try:
2248 bands_after_ac = get_bands_after_AC(GMS_id)
2249 LayerBandsAssignment = [i for i in LayerBandsAssignment if i in bands_after_ac]
2250 except ACNotSupportedError:
2251 # atmospheric correction is not yet supported -> LBA will be the same after L1C
2252 pass
2254 if proc_level in ['L2B', 'L2C']:
2255 # handle different number of bands after spectral homogenization to target sensor
2257 if GMS_id.dataset_ID == CFG.datasetid_spectral_ref:
2258 pass # return normal LBA from above
2260 elif CFG.datasetid_spectral_ref is not None:
2261 # the target sensor is NOT a custom sensor but has the spectral characteristics of a known sensor
2262 # => use the LBA of the target sensor after AC as the new LBA for the requested sensor
2264 # find out how the spectral characteristics of this known target sensor look like after AC
2265 from ..model.gms_object import GMS_identifier # noqa F811 # redefinition of unused 'GMS_identifier'
2266 tgt_sat, tgt_sen = datasetid_to_sat_sen(CFG.datasetid_spectral_ref)
2267 tgt_GMSid = GMS_identifier(image_type='RSD', satellite=tgt_sat, sensor=tgt_sen, subsystem='',
2268 proc_level='L2A', dataset_ID=-9999, logger=None)
2269 try:
2270 tgt_sen_LBA = get_bands_after_AC(tgt_GMSid)
2272 except ACNotSupportedError:
2273 # use the target sensor LBA before AC (because target sensor could not be atmospherically corrected)
2274 tgt_GMSid.proc_level = 'L1B'
2276 tgt_sen_LBA = get_LayerBandsAssignment(tgt_GMSid)
2278 LayerBandsAssignment = tgt_sen_LBA
2280 else:
2281 # fallback: return a LBA matching the number of bands after spectral homogenization
2282 LayerBandsAssignment = [str(i + 1) for i in range(len(CFG.target_CWL))]
2284 return LayerBandsAssignment
2287def get_bands_after_AC(GMS_id):
2288 # type: (GMS_identifier) -> List[str]
2289 """Returns a list of bands that are not removed by atmospheric correction.
2291 :param GMS_id: <dict>, derived from self.get_GMS_identifier()
2292 :return: e.g. ['1', '2', '3', '4', '5', '6', '7', '9'] for Landsat-8
2293 """
2294 path_ac_options = get_path_ac_options(GMS_id)
2296 if not path_ac_options or not os.path.exists(path_ac_options):
2297 raise ACNotSupportedError('Atmospheric correction is not yet supported for %s %s.'
2298 % (GMS_id.satellite, GMS_id.sensor))
2300 # FIXME this does not work for L7
2301 # NOTE: don't validate because options contain pathes that do not exist on another server
2302 ac_bandNs = get_ac_options(path_ac_options, validation=False)['AC']['bands']
2303 ac_out_bands = [bN.split('B0')[1] if bN.startswith('B0') else bN.split('B')[1] for bN in ac_bandNs] # sorted
2305 return ac_out_bands
2308def get_center_wavelengths_by_LBA(satellite, sensor, LBA, subsystem=None):
2309 # type: (str, str, list, str) -> List[float]
2310 """Returns a list of center wavelengths of spectral bands for the given satellite/sensor/LayerBandsAss. combination.
2312 :param satellite: target satellite (e.g., 'Sentinel-2A')
2313 :param sensor: target sensor (e.g., 'MSI')
2314 :param LBA: LayerBandsAssignment
2315 :param subsystem: target sensor subsystem (e.g., 'VNIR')
2316 """
2317 srf = RSR(satellite=satellite, sensor=sensor, subsystem=subsystem, LayerBandsAssignment=LBA)
2319 return list(srf.wvl)
2322def get_dict_LayerOptTherm(GMS_id, LayerBandsAssignment):
2323 dict_out = collections.OrderedDict()
2324 [dict_out.update({lr: 'thermal' if isTHERMAL(GMS_id, lr) else 'optical'}) for lr in LayerBandsAssignment]
2325 return dict_out
2328def isPAN(GMS_id, LayerNr):
2329 GMS_sensorcode = get_GMS_sensorcode(GMS_id)
2330 dict_isPAN = {'TM7': ['8'], 'LDCM': ['8'],
2331 'SPOT1a': ['4'], 'SPOT2a': ['4'], 'SPOT3a': ['4'], 'SPOT4a': ['5'], 'SPOT5a': ['5'],
2332 'SPOT1b': ['4'], 'SPOT2b': ['4'], 'SPOT3b': ['4'], 'SPOT4b': ['5'], 'SPOT5b': ['5']}
2333 return True if GMS_sensorcode in dict_isPAN and LayerNr in dict_isPAN[GMS_sensorcode] else False
2336def isTHERMAL(GMS_id, LayerNr):
2337 GMS_sensorcode = get_GMS_sensorcode(GMS_id)
2338 dict_isTHERMAL = {'TM4': ['6'], 'TM5': ['6'], 'TM7': ['6L', '6H'], 'LDCM': ['10', '11'],
2339 'AST_T': ['10', '11', '12', '13', '14']}
2340 return True if GMS_sensorcode in dict_isTHERMAL and LayerNr in dict_isTHERMAL[GMS_sensorcode] else False
2343def get_FieldOfView(GMS_id):
2344 GMS_sensorcode = get_GMS_sensorcode(GMS_id)
2345 dict_FOV = {'AVNIR-2': 5.79,
2346 'AST_V1': 6.09, 'AST_V2': 5.19, 'AST_S': 4.9, 'AST_T': 4.9,
2347 # http://eospso.gsfc.nasa.gov/sites/default/files/mission_handbooks/Terra.pdf
2348 'TM4': 14.92, 'TM5': 14.92, 'TM7': 14.92, 'LDCM': 14.92,
2349 'SPOT1a': 4.18, 'SPOT2a': 4.18, 'SPOT3a': 4.18, 'SPOT4a': 4.18, 'SPOT5a': 4.18,
2350 'SPOT1b': 4.18, 'SPOT2b': 4.18, 'SPOT3b': 4.18, 'SPOT4b': 4.18, 'SPOT5b': 4.18,
2351 'RE5': 6.99,
2352 'S2A10': 20.6, 'S2A20': 20.6, 'S2A60': 20.6,
2353 'S2B10': 20.6, 'S2B20': 20.6, 'S2B60': 20.6}
2354 return dict_FOV[GMS_sensorcode]
2357def get_orbit_params(GMS_id):
2358 GMS_sensorcode = get_GMS_sensorcode(GMS_id)
2360 # sensor altitude above ground [kilometers]
2361 dict_altitude = {'AVNIR-2': 691.65,
2362 'AST_V1': 705, 'AST_V2': 705, 'AST_S': 705, 'AST_T': 705,
2363 'TM4': 705, 'TM5': 705, 'TM7': 705, 'LDCM': 705,
2364 'SPOT1a': 822, 'SPOT2a': 822, 'SPOT3a': 822, 'SPOT4a': 822, 'SPOT5a': 822,
2365 'SPOT1b': 822, 'SPOT2b': 822, 'SPOT3b': 822, 'SPOT4b': 822, 'SPOT5b': 822,
2366 'RE5': 630,
2367 'S2A10': 786, 'S2A20': 786, 'S2A60': 786,
2368 'S2B10': 786, 'S2B20': 786, 'S2B60': 786}
2370 # sensor inclination [degrees]
2371 dict_inclination = {'AVNIR-2': 98.16,
2372 'AST_V1': 98.3, 'AST_V2': 98.3, 'AST_S': 98.3, 'AST_T': 98.3,
2373 'TM4': 98.2, 'TM5': 98.2, 'TM7': 98.2, 'LDCM': 98.2,
2374 'SPOT1a': 98.7, 'SPOT2a': 98.7, 'SPOT3a': 98.7, 'SPOT4a': 98.7, 'SPOT5a': 98.7,
2375 'SPOT1b': 98.7, 'SPOT2b': 98.7, 'SPOT3b': 98.7, 'SPOT4b': 98.7, 'SPOT5b': 98.7,
2376 'RE5': 98,
2377 'S2A10': 98.62, 'S2A20': 98.62, 'S2A60': 98.62,
2378 'S2B10': 98.62, 'S2B20': 98.62, 'S2B60': 98.62}
2380 # time needed for one complete earth revolution [minutes]
2381 dict_period = {'AVNIR-2': 98.7,
2382 'AST_V1': 98.88, 'AST_V2': 98.88, 'AST_S': 98.88, 'AST_T': 98.88,
2383 'TM4': 98.9, 'TM5': 98.9, 'TM7': 98.9, 'LDCM': 98.9,
2384 'SPOT1a': 101.4, 'SPOT2a': 101.4, 'SPOT3a': 101.4, 'SPOT4a': 101.4, 'SPOT5a': 101.4,
2385 'SPOT1b': 101.4, 'SPOT2b': 101.4, 'SPOT3b': 101.4, 'SPOT4b': 101.4, 'SPOT5b': 101.4,
2386 'RE5': 96.7,
2387 'S2A10': 100.6, 'S2A20': 100.6, 'S2A60': 100.6,
2388 'S2B10': 100.6, 'S2B20': 100.6, 'S2B60': 100.6}
2390 return [dict_altitude[GMS_sensorcode], dict_inclination[GMS_sensorcode], dict_period[GMS_sensorcode]]
2393def get_special_values(GMS_id):
2394 GMS_sensorcode = get_GMS_sensorcode(GMS_id) # type: str
2395 dict_fill = {'AVNIR-2': 0,
2396 'AST_V1': 0, 'AST_V2': 0, 'AST_S': 0, 'AST_T': 0,
2397 'TM4': 0, 'TM5': 0, 'TM7': 0, 'LDCM': 0,
2398 'SPOT1a': 0, 'SPOT2a': 0, 'SPOT3a': 0, 'SPOT4a': 0, 'SPOT5a': 0,
2399 'SPOT1b': 0, 'SPOT2b': 0, 'SPOT3b': 0, 'SPOT4b': 0, 'SPOT5b': 0,
2400 'RE5': 0,
2401 'S2A10': 0, 'S2A20': 0, 'S2A60': 0,
2402 'S2B10': 0, 'S2B20': 0, 'S2B60': 0,
2403 }
2404 dict_zero = {'AVNIR-2': None,
2405 'AST_V1': 1, 'AST_V2': 1, 'AST_S': 1, 'AST_T': 1,
2406 'TM4': None, 'TM5': None, 'TM7': None, 'LDCM': None,
2407 'SPOT1a': None, 'SPOT2a': None, 'SPOT3a': None, 'SPOT4a': None, 'SPOT5a': None,
2408 'SPOT1b': None, 'SPOT2b': None, 'SPOT3b': None, 'SPOT4b': None, 'SPOT5b': None,
2409 'RE5': None,
2410 'S2A10': None, 'S2A20': None, 'S2A60': None,
2411 'S2B10': None, 'S2B20': None, 'S2B60': None,
2412 }
2413 dict_saturated = {'AVNIR-2': None,
2414 'AST_V1': 255, 'AST_V2': 255, 'AST_S': 255, 'AST_T': 65535,
2415 'TM4': None, 'TM5': None, 'TM7': None, 'LDCM': 65535,
2416 'SPOT1a': 255, 'SPOT2a': 255, 'SPOT3a': 255, 'SPOT4a': 255, 'SPOT5a': 255,
2417 'SPOT1b': 255, 'SPOT2b': 255, 'SPOT3b': 255, 'SPOT4b': 255, 'SPOT5b': 255,
2418 'RE5': None,
2419 'S2A10': 65535, 'S2A20': 65535, 'S2A60': 65535,
2420 'S2B10': 65535, 'S2B20': 65535, 'S2B60': 65535
2421 }
2422 return {'fill': dict_fill[GMS_sensorcode],
2423 'zero': dict_zero[GMS_sensorcode],
2424 'saturated': dict_saturated[GMS_sensorcode]}
2427def metaDict_to_metaODict(metaDict, logger=None):
2428 """Converts a GMS metadata dictionary to an ordered dictionary according to the sorting given in
2429 Output_writer.enviHdr_keyOrder.
2431 :param metaDict: <dict> GMS metadata dictionary
2432 :param logger: <logging.logger> if given, warnings will be logged. Otherwise they are raised.
2433 """
2435 from ..io.output_writer import enviHdr_keyOrder
2436 expected_keys = [k for k in enviHdr_keyOrder if k in metaDict]
2437 only_gmsFile_keys = ['ViewingAngle_arrProv', 'IncidenceAngle_arrProv', 'projection']
2438 unexpected_keys = [k for k in metaDict.keys() if k not in expected_keys and k not in only_gmsFile_keys]
2440 if unexpected_keys:
2441 msg = 'Got unexpected keys in metadata dictionary: %s. Adding them at the end of output header files.' \
2442 % ', '.join(unexpected_keys)
2443 if logger:
2444 logger.warning(msg)
2445 else:
2446 warnings.warn(msg)
2448 meta_vals = [metaDict[k] for k in expected_keys] + [metaDict[k] for k in unexpected_keys]
2449 return collections.OrderedDict(zip(expected_keys + unexpected_keys, meta_vals))
2452def LandsatID2dataset(ID_list):
2453 dataset_list = []
2454 for ID in ID_list:
2455 dataset = dict(image_type='RSD', satellite=None, sensor=None, subsystem=None, acquisition_date=None,
2456 entity_ID=ID)
2457 dataset['satellite'] = 'Landsat-5' if ID[:3] == 'LT5' else 'Landsat-7' if ID[:3] == 'LE7' \
2458 else 'Landsat-8' if ID[:3] == 'LC8' else dataset['satellite']
2459 dataset['sensor'] = 'TM' if ID[:3] == 'LT5' else 'ETM+' if ID[:3] == 'LE7' else \
2460 'OLI_TIRS' if ID[:3] == 'LC8' else dataset['satellite']
2461 dataset['subsystem'] = None
2462 dataset['acquisition_date'] = \
2463 (datetime.datetime(int(ID[9:13]), 1, 1) + datetime.timedelta(int(ID[13:16]) - 1)).strftime('%Y-%m-%d')
2464 dataset_list.append(dataset)
2465 return dataset_list
2468def get_sensormode(dataset):
2469 if re.search(r'SPOT', dataset['satellite']):
2470 path_archive = path_generator(dataset).get_local_archive_path_baseN()
2471 dim_ = open_specific_file_within_archive(path_archive, '*/scene01/metadata.dim')[0]
2472 SPOT_mode = re.search(r"<SENSOR_CODE>([a-zA-Z0-9]*)</SENSOR_CODE>", dim_, re.I).group(1)
2473 assert SPOT_mode in ['J', 'X', 'XS', 'A', 'P', 'M'], 'Unknown SPOT sensor mode: %s' % SPOT_mode
2474 return 'M' if SPOT_mode in ['J', 'X', 'XS'] else 'P'
2475 else:
2476 return 'M'