in endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/MetadataReader.java [381:713]
private void parseKeyValuePair(final String key, final int band, final String value)
throws IllegalArgumentException, DateTimeException, DataStoreException
{
switch (key) {
case "GROUP": group = value; break;
case "END_GROUP": group = null; break;
/* ┌────────────────────────────────────┐
* │ GROUP = METADATA_FILE_INFO (L1)│
* │ PRODUCT_CONTENTS (L2)│
* └────────────────────────────────────┘
* Origin of the product.
* Value is "Image courtesy of the U.S. Geological Survey".
*/
case "ORIGIN": {
final Matcher m = CREDIT.matcher(value);
if (m.find()) {
newParty(MetadataBuilder.PartyType.ORGANISATION);
addAuthor(value.substring(m.end()));
}
addCredits(value);
break;
}
/*
* Example: "https://doi.org/10.5066/P9OGBGM6"
*/
// case "DIGITAL_OBJECT_IDENTIFIER":
/*
* Product Request ID. NNNNNNNNNNNNN_UUUUU, where NNNNNNNNNNNNN = 13-digit Tracking,
* Routing, and Metrics (TRAM) order number and UUUUU = 5-digit TRAM unit number.
* Example: "0501403126384_00011"
*/
case "REQUEST_ID": {
addAcquisitionRequirement(null, value);
break;
}
/*
* Product: the filename prefix. Example: "LC08_L2SP_197030_20210812_20210819_02_T1"
* Scene: the unique Landsat scene identifier. Example: "LC81230522014071LGN00".
* Format: Ls8ppprrrYYYYDDDGGGVV
*/
case "LANDSAT_PRODUCT_ID":
case "LANDSAT_SCENE_ID": {
addTitleOrIdentifier(value, MetadataBuilder.Scope.ALL);
break;
}
/*
* The date when the metadata file for the L1G product set was created.
* The date is based on Universal Time Coordinated (UTC).
* Date format is {@code YYYY-MM-DDTHH:MM:SSZ}.
* Example: "2014-03-12T06:06:35Z".
*/
case "FILE_DATE": {
addCitationDate(OffsetDateTime.parse(value), DateType.CREATION, MetadataBuilder.Scope.ALL);
break;
}
/*
* The Ground Station that received the data. Grounds station identifiers are specified in LSDS-547.
* Example: "LGN" = Landsat Ground Network.
*/
// TODO case "STATION_ID":
/*
* The processing software version that created the product. Can be "IAS_X.Y.Z" or "LPGS_X.Y.Z"
* where X, Y and Z are major, minor and patch version numbers.
* Example: "LPGS_2.3.0".
*/
// TODO case "PROCESSING_SOFTWARE_VERSION":
/* ┌────────────────────────────────────┐
* │ GROUP = PRODUCT_METADATA (L1)│
* │ PRODUCT_CONTENTS (L2)│
* └────────────────────────────────────┘
* The identifier to inform the user of the product type.
* Value can be "L1T" or "L1GT".
*/
case "PROCESSING_LEVEL":
case "DATA_TYPE": {
setProcessingLevelCode("Landsat", value);
break;
}
/*
* Indicates the source of the DEM used in the correction process.
* Value can be "GLS2000", "RAMP" or "GTOPO30".
*/
case "ELEVATION_SOURCE": {
addSource(value, ScopeCode.MODEL, Vocabulary.formatInternational(Vocabulary.Keys.DigitalElevationModel));
break;
}
/*
* The output format of the image.
* Value is "GEOTIFF".
*/
case "OUTPUT_FORMAT": {
if (Constants.GEOTIFF.equalsIgnoreCase(value)) {
setPredefinedFormat(Constants.GEOTIFF, listeners, true);
} else {
addFormatName(value);
}
// Do not invoke `addFormatReaderSIS(name)`, it will be done by the caller.
break;
}
/*
* Spacecraft from which the data were captured.
* Example: "LANDSAT_8".
*/
case "SPACECRAFT_ID": {
addPlatform(null, value);
break;
}
/*
* Sensor(s) used to capture this scene.
* Example: "OLI", "TIRS" or "OLI_TIRS".
*/
case "SENSOR_ID": {
addInstrument(null, value);
break;
}
/*
* The date the image was acquired.
* Date format is {@code YYYY-MM-DD}.
* Example: "2014-03-12".
*/
case "DATE_ACQUIRED": {
final LocalDate date = LocalDate.parse(value);
if (sceneTime instanceof OffsetTime) {
sceneTime = date.atTime((OffsetTime) sceneTime);
} else if (!date.equals(sceneTime)) {
flushSceneTime();
sceneTime = date;
}
break;
}
/*
* Scene center time of the date the image was acquired.
* Time format is {@code HH:MI:SS.SSSSSSSZ}.
* Example: "03:02:01.5339408Z".
*/
case "SCENE_CENTER_TIME": {
final OffsetTime time = OffsetTime.parse(value);
if (sceneTime instanceof LocalDate) {
sceneTime = ((LocalDate) sceneTime).atTime(time);
} else {
sceneTime = time;
}
break;
}
/*
* The longitude and latitude values for the upper-left (UL), upper-right (UR), lower-left (LL)
* and lower-right (LR) corners of the product, measured at the center of the pixel.
* Positive longitude value indicates east longitude; negative value indicates west longitude.
* Positive latitude value indicates north latitude; negative value indicates south latitude.
* Units are in degrees.
*/
case "CORNER_UL_LON_PRODUCT": parseCorner(GEOGRAPHIC + 0, value); break;
case "CORNER_UL_LAT_PRODUCT": parseCorner(GEOGRAPHIC + 1, value); break;
case "CORNER_UR_LON_PRODUCT": parseCorner(GEOGRAPHIC + 2, value); break;
case "CORNER_UR_LAT_PRODUCT": parseCorner(GEOGRAPHIC + 3, value); break;
case "CORNER_LL_LON_PRODUCT": parseCorner(GEOGRAPHIC + 4, value); break;
case "CORNER_LL_LAT_PRODUCT": parseCorner(GEOGRAPHIC + 5, value); break;
case "CORNER_LR_LON_PRODUCT": parseCorner(GEOGRAPHIC + 6, value); break;
case "CORNER_LR_LAT_PRODUCT": parseCorner(GEOGRAPHIC + 7, value); break;
/*
* The upper-left (UL), upper-right (UR), lower-left (LL) and lower-right (LR) corner map
* projection X and Y coordinate, measured at the center of the pixel. Units are in meters.
*/
case "CORNER_UL_PROJECTION_X_PRODUCT": parseCorner(PROJECTED + 0, value); break;
case "CORNER_UL_PROJECTION_Y_PRODUCT": parseCorner(PROJECTED + 1, value); break;
case "CORNER_UR_PROJECTION_X_PRODUCT": parseCorner(PROJECTED + 2, value); break;
case "CORNER_UR_PROJECTION_Y_PRODUCT": parseCorner(PROJECTED + 3, value); break;
case "CORNER_LL_PROJECTION_X_PRODUCT": parseCorner(PROJECTED + 4, value); break;
case "CORNER_LL_PROJECTION_Y_PRODUCT": parseCorner(PROJECTED + 5, value); break;
case "CORNER_LR_PROJECTION_X_PRODUCT": parseCorner(PROJECTED + 6, value); break;
case "CORNER_LR_PROJECTION_Y_PRODUCT": parseCorner(PROJECTED + 7, value); break;
/*
* The number of product lines and samples for the panchromatic, reflective and thermal bands.
* Those parameters are only present if the corresponding band is present in the product.
*/
case "PANCHROMATIC_LINES": parseGridSize(BandGroupName.PANCHROMATIC, false, value); break;
case "PANCHROMATIC_SAMPLES": parseGridSize(BandGroupName.PANCHROMATIC, true, value); break;
case "REFLECTIVE_LINES": parseGridSize(BandGroupName.REFLECTIVE, false, value); break;
case "REFLECTIVE_SAMPLES": parseGridSize(BandGroupName.REFLECTIVE, true, value); break;
case "THERMAL_LINES": parseGridSize(BandGroupName.THERMAL, false, value); break;
case "THERMAL_SAMPLES": parseGridSize(BandGroupName.THERMAL, true, value); break;
/*
* The grid cell size in meters used in creating the image for the band, if part of the product.
* This parameter is only included if the corresponding band is included in the product.
*/
case "GRID_CELL_SIZE_PANCHROMATIC":
case "GRID_CELL_SIZE_REFLECTIVE":
case "GRID_CELL_SIZE_THERMAL": {
addLinearResolution(Double.parseDouble(value));
break;
}
/*
* The file name of the TIFF image that contains the pixel values for a band.
* This parameter is only present if the band is included in the product.
*/
case "FILE_NAME_BAND_": {
band(key, band).ifPresent((b) -> b.filename = value);
break;
}
/*
* Examples: UINT8, UINT16, INT16.
*/
case "DATA_TYPE_BAND_": {
final int s = value.lastIndexOf("INT");
if (s >= 0) try {
final Integer n = Integer.valueOf(value.substring(s + 3));
sampleDimension(key, band).ifPresent((sd) -> sd.setBitsPerValue(n));
} catch (NumberFormatException e) {
warning(key, null, e);
}
break;
}
/*
* The file name for L1 metadata.
* Exemple: "LC81230522014071LGN00_MTL.txt".
*/
case "METADATA_FILE_NAME": {
if (filename == null) {
filename = value;
}
break;
}
/* ┌────────────────────────────────────┐
* │ GROUP = IMAGE_ATTRIBUTES │
* └────────────────────────────────────┘
* The overall cloud coverage (percent) of the WRS-2 scene as a value between 0 and 100 inclusive.
* -1 indicates that the score was not calculated.
*/
case "CLOUD_COVER": {
final double v = Double.parseDouble(value);
if (v >= 0) setCloudCoverPercentage(v);
break;
}
/*
* The Sun azimuth angle in degrees for the image center location at the image center acquisition time.
* Values are from -180 to 180 degrees inclusive.
* A positive value indicates angles to the east or clockwise from the north.
* A negative value indicates angles to the west or counterclockwise from the north.
*/
case "SUN_AZIMUTH": {
setIlluminationAzimuthAngle(Double.parseDouble(value));
break;
}
/*
* The Sun elevation angle in degrees for the image center location at the image center acquisition time.
* Values are from -90 to 90 degrees inclusive.
* A positive value indicates a daytime scene. A negative value indicates a nighttime scene.
* Note: for reflectance calculation, the sun zenith angle is needed, which is 90 - sun elevation angle.
*/
case "SUN_ELEVATION": {
setIlluminationElevationAngle(Double.parseDouble(value));
break;
}
/* ┌────────────────────────────────────┐
* │ GROUP = MIN_MAX_PIXEL_VALUE │
* └────────────────────────────────────┘
* Minimum achievable spectral radiance value for a band 1.
* This parameter is only present if this band is included in the product.
*/
case "QUANTIZE_CAL_MIN_BAND_": {
final Double v = parseDouble(value); // Done first in case an exception is thrown.
sampleDimension(key, band).ifPresent((sd) -> sd.setMinValue(v));
break;
}
/*
* Maximum achievable spectral radiance value for a band 1.
* This parameter is only present if this band is included in the product.
*/
case "QUANTIZE_CAL_MAX_BAND_": {
final Double v = parseDouble(value); // Done first in case an exception is thrown.
sampleDimension(key, band).ifPresent((sd) -> sd.setMaxValue(v));
break;
}
/* ┌────────────────────────────────────┐
* │ GROUP = RADIOMETRIC_RESCALING │
* └────────────────────────────────────┘
* The additive or multiplicative rescaling factor used to convert calibrated
* DN to Radiance units for a band. Radiance unit is W/(m² sr um)/DN.
* Reflectance unit is dimensionless (a ratio).
*/
case "RADIANCE_MULT_BAND_": setTransferFunction(key, band, false, true, value); break;
case "REFLECTANCE_MULT_BAND_": setTransferFunction(key, band, true, true, value); break;
case "RADIANCE_ADD_BAND_": setTransferFunction(key, band, false, false, value); break;
case "REFLECTANCE_ADD_BAND_": setTransferFunction(key, band, true, false, value); break;
/* ┌────────────────────────────────────┐
* │ GROUP = PROJECTION_PARAMETERS │
* └────────────────────────────────────┘
* The map projection used in creating the image.
* Universal Transverse Mercator (UTM) or Polar Stereographic (PS).
*/
case "MAP_PROJECTION": {
if ("UTM".equalsIgnoreCase(value)) {
projection = null;
} else if ("PS".equalsIgnoreCase(value)) try {
projection = CoordinateOperations.builder(factories.getMathTransformFactory(),
Constants.EPSG + ':' + PolarStereographicB.IDENTIFIER);
utmZone = -1;
} catch (NoSuchIdentifierException e) {
// Should never happen with Apache SIS implementation of MathTransformFactory.
throw new DataStoreReferencingException(e.getMessage(), e);
}
break;
}
/*
* The datum used in creating the image. This is usually "WGS84".
* We ignore the "ELLIPSOID" attribute because it is implied by the datum.
*/
case "DATUM": {
datum = CommonCRS.valueOf(Strings.toUpperCase(value, Characters.Filter.LETTERS_AND_DIGITS, true));
break;
}
/*
* The value used to indicate the zone number. This parameter is only included for the UTM projection.
* If this parameter is defined more than once (which should be illegal), only the first occurrence is
* retained. If the projection is polar stereographic, the parameter is ignored.
*/
case "UTM_ZONE": {
if (utmZone == 0) {
utmZone = Short.parseShort(value);
}
break;
}
/*
* Polar Stereographic projection parameters. Most parameters do not vary, except the latitude of
* true scale which is -71 for scenes over Antarctica and 71 for off-nadir scenes at the North Pole.
* If the datum is WGS84, then this is equivalent to EPSG:3031 and EPSG:3995 respectively.
*/
case "VERTICAL_LON_FROM_POLE": setProjectionParameter(key, Constants.CENTRAL_MERIDIAN, value, false); break;
case "TRUE_SCALE_LAT": setProjectionParameter(key, Constants.STANDARD_PARALLEL_1, value, false); break;
case "FALSE_EASTING": setProjectionParameter(key, Constants.FALSE_EASTING, value, true); break;
case "FALSE_NORTHING": setProjectionParameter(key, Constants.FALSE_NORTHING, value, true); break;
}
}