private void parseKeyValuePair()

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;
        }
    }