/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2016, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.metadata.landsat; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.Locale; import java.nio.charset.StandardCharsets; import org.opengis.metadata.Metadata; import org.opengis.metadata.citation.CitationDate; import org.opengis.metadata.citation.DateType; import org.opengis.metadata.citation.Role; import org.opengis.metadata.constraint.Constraints; import org.opengis.metadata.content.CoverageContentType; import org.opengis.metadata.identification.TopicCategory; import org.opengis.metadata.maintenance.ScopeCode; import org.apache.sis.util.iso.SimpleInternationalString; import org.apache.sis.metadata.iso.DefaultIdentifier; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.metadata.iso.acquisition.DefaultAcquisitionInformation; import org.apache.sis.metadata.iso.acquisition.DefaultInstrument; import org.apache.sis.metadata.iso.acquisition.DefaultPlatform; import org.apache.sis.metadata.iso.citation.DefaultCitation; import org.apache.sis.metadata.iso.citation.DefaultCitationDate; import org.apache.sis.metadata.iso.citation.DefaultResponsibleParty; import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints; import org.apache.sis.metadata.iso.content.DefaultImageDescription; import org.apache.sis.metadata.iso.distribution.DefaultFormat; import org.apache.sis.metadata.iso.extent.DefaultExtent; import org.apache.sis.metadata.iso.identification.DefaultDataIdentification; import org.apache.sis.metadata.iso.lineage.DefaultLineage; import org.apache.sis.metadata.iso.lineage.DefaultProcessStep; import org.apache.sis.metadata.iso.lineage.DefaultProcessing; import org.apache.sis.metadata.iso.quality.DefaultDataQuality; import org.apache.sis.metadata.iso.quality.DefaultScope; import org.apache.sis.metadata.iso.spatial.DefaultGeorectified; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.temporal.object.ISODateParser; /** * Convenient methods to manipulate LandSat informations. * * @author Johann Sorel (Geomatys) */ public final class LandSat { private LandSat(){} public static LandSatMetaNode parseMetadata(final File file) throws IOException{ try (FileInputStream stream = new FileInputStream(file)) { return parseMetadata(stream); } } public static LandSatMetaNode parseMetadata(final Path file) throws IOException{ try (InputStream stream = Files.newInputStream(file)){ return parseMetadata(stream); } } public static LandSatMetaNode parseMetadata(final InputStream stream) throws IOException{ final String metaFile = IOUtilities.toString(stream); final String[] lines = metaFile.split("\n"); //rebuild the metadata tree LandSatMetaNode root = null; LandSatMetaNode node = null; for(int i=0; i<lines.length;i++){ String line = lines[i]; line = line.trim(); if(line.isEmpty()) continue; final int separator = line.indexOf('='); if(separator < 0){ //might be the END tag if("END".equalsIgnoreCase(line)){ //ok we have finish break; }else{ //unexpected throw new IOException("Line "+i+" does not match metadata pattern {key = value} : "+ line); } } final String key = line.substring(0, separator).trim(); final String value = line.substring(separator+1).trim(); if("GROUP".equalsIgnoreCase(key)){ //invert to have the group name as key final LandSatMetaNode candidate = new LandSatMetaNode(value, key); if(node != null){ node.add(candidate); }else{ root = candidate; } node = candidate; }else if("END_GROUP".equalsIgnoreCase(key)){ //end this node, check the name match, //otherwise it means the file is incorrect if(!value.equalsIgnoreCase(String.valueOf(node.getKey()))){ throw new IOException("End Group line "+i+" does not match any previous group. "+ line); } node = (LandSatMetaNode) node.getParent(); }else{ //simple key=value pair final LandSatMetaNode candidate = new LandSatMetaNode(key, value); node.add(candidate); } } return root; } public static LandSatNomenclature parseNomenclature(final String name){ if(name == null || name.length() < 24){ throw new IllegalArgumentException("name is too short to match lansat naming convention"); } //TODO throw new IllegalArgumentException("not coded yet."); } /** * Extract as much information from the landsat metadata and map it to * ISO 19115-2. * * @param LandSat Metadata * @return ISO19115 Metadata */ public static Metadata toMetadata(LandSatMetaNode landsat, final String fileName){ final DefaultMetadata metadata = new DefaultMetadata(); //Default values metadata.setCharacterSets(Collections.singleton(StandardCharsets.UTF_8)); metadata.setLanguage(Locale.ENGLISH); metadata.setDateStamp(new Date()); LandSatMetaNode node1; LandSatMetaNode node2; LandSatMetaNode node3; final DefaultGeorectified spatialRepresentation = new DefaultGeorectified(); metadata.getSpatialRepresentationInfo().add(spatialRepresentation); final DefaultDataIdentification identificationInfo = new DefaultDataIdentification(); metadata.getIdentificationInfo().add(identificationInfo); final DefaultAcquisitionInformation aquisitionInfo = new DefaultAcquisitionInformation(); metadata.getAcquisitionInformation().add(aquisitionInfo); final DefaultImageDescription contentInfo = new DefaultImageDescription(); metadata.getContentInfo().add(contentInfo); final DefaultDataQuality qualityInfo = new DefaultDataQuality(); metadata.getDataQualityInfo().add(qualityInfo); final DefaultLineage lineage = new DefaultLineage(); qualityInfo.setLineage(lineage); final DefaultProcessStep processStep = new DefaultProcessStep(); lineage.getProcessSteps().add(processStep); // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/PRODUCT_TYPE // iso : MI_Metadata/spatialRepresentationInfo/checkPointAvailability // if PRODUCT_TYPE = «  L1T  » then checkPointAvailability = true, otherwise false node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","PRODUCT_TYPE"); if(node1 != null){ spatialRepresentation.setCheckPointAvailable( "L1T".equalsIgnoreCase(node1.getValue()) ); } // build title from band type, example : ETM+ REF // [acquisitionInformation/instrument/identifier] PAN // [acquisitionInformation/instrument/identifier] REF // [acquisitionInformation/instrument/identifier] THM // PAN = panchromatic : B80 // REF = reflective : B10,B20,B30,B40,B50,B70 // THM = thermal : B61,B62 // // landsat : L1_METADATA_FILE/PRODUCT_METADATA/SENSOR_ID + type // iso : MI_Metadata/identificationInfo/citation/title node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","SENSOR_ID"); if(node1 != null){ String title = node1.getValue(); if(fileName != null){ if(fileName.contains("B80")){ title += " PAN"; }else if(fileName.contains("B61") || fileName.contains("B62")){ title += " THM"; }else if(fileName.contains("RGB") || fileName.contains("B10") || fileName.contains("B20") || fileName.contains("B30") || fileName.contains("B40") || fileName.contains("B50") || fileName.contains("B70")){ title += " REF"; } } DefaultCitation citation = (DefaultCitation) identificationInfo.getCitation(); if(citation == null){ citation = new DefaultCitation(); identificationInfo.setCitation(citation); } citation.setTitle(new SimpleInternationalString(title)); } // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/ACQUISITION_DATE + SCENE_CENTER_SCAN_TIME // iso : MI_Metadata/identificationInfo/citation/date/date // iso : MI_Metadata/identificationInfo/citation/date/dateType value=creation node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","ACQUISITION_DATE"); node2 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","SCENE_CENTER_SCAN_TIME"); if(node1 != null && node2 != null){ DefaultCitation citation = (DefaultCitation) identificationInfo.getCitation(); if(citation == null){ citation = new DefaultCitation(); identificationInfo.setCitation(citation); } final String strdate = node1.getValue() +"T"+node2.getValue(); final ISODateParser fp = new ISODateParser(); final java.util.Date resultDate = fp.parseToDate(strdate); final CitationDate date = new DefaultCitationDate(resultDate, DateType.CREATION); citation.getDates().add(date); } // Landsat : L1_METADATA_FILE/METADATA_FILE_INFO/ORIGIN // iso : MI_Metadata/identificationInfo/citedResponsibleParty/organisationName // iso : MI_Metadata/identificationInfo/citedResponsibleParty/role value=originator node1 = landsat.search("L1_METADATA_FILE","METADATA_FILE_INFO","ORIGIN"); if(node1 != null){ final DefaultResponsibleParty responsibleParty = new DefaultResponsibleParty(); responsibleParty.setOrganisationName(new SimpleInternationalString(node1.getValue())); responsibleParty.setRole(Role.ORIGINATOR); identificationInfo.getPointOfContacts().add(responsibleParty); } // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/SENSOR_ID // iso : acquisitionInformation/instrument/identifier // iso : acquisitionInformation/instrument/type value=Push-broom node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","SENSOR_ID"); if(node1 != null){ final DefaultInstrument instrument = new DefaultInstrument(); aquisitionInfo.getInstruments().add(instrument); instrument.setIdentifier(new DefaultIdentifier(node1.getValue())); instrument.setType(new SimpleInternationalString("Push-broom")); } // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/SPACECRAFT_ID // iso : acquisitionInformation/platform/identifier // iso : acquisitionInformation/platform/description node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","SPACECRAFT_ID"); if(node1 != null){ final DefaultPlatform platform = new DefaultPlatform(); aquisitionInfo.getPlatforms().add(platform); platform.setIdentifier(new DefaultIdentifier(node1.getValue())); platform.setDescription(new SimpleInternationalString(node1.getValue())); platform.setCitation(new DefaultCitation(node1.getValue())); } // iso : MI_Metadata/identificationInfo/abstract // concatenate [acquisitionInformation/platform/identifier] et [MI_Metadata/identificationInfo/citation/title] String abs = ""; if(!aquisitionInfo.getPlatforms().isEmpty()){ abs += aquisitionInfo.getPlatforms().iterator().next().getIdentifier().toString(); } if(identificationInfo.getCitation() != null){ abs += " "+identificationInfo.getCitation().getTitle(); } identificationInfo.setAbstract(new SimpleInternationalString(abs)); // iso : MI_Metadata/identificationInfo/resourceFormat/name value=geotiff final DefaultFormat format = new DefaultFormat(); format.setName(new SimpleInternationalString("geotiff")); identificationInfo.getResourceFormats().add(format); // iso : MI_Metadata/identificationInfo/resourceConstraints/useConstraints value=otherConstraints final Constraints constraint = new DefaultLegalConstraints("otherConstraints"); identificationInfo.getResourceConstraints().add(constraint); // iso : MI_Metadata/identificationInfo/topicCategory value=imageryBaseMapsEarthCover final TopicCategory category = TopicCategory.IMAGERY_BASE_MAPS_EARTH_COVER; identificationInfo.getTopicCategories().add(category); // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/WRS_PATH + STARTING_ROW + ENDING_ROW // iso : MI_Metadata/identificationInfo/extent/geographicElement/geographicIdentifier/code // Concatenate path + starting row with format «  ppp_rrr  ». node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","WRS_PATH"); node2 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","STARTING_ROW"); if(node1 != null && node2 != null){ final DefaultExtent extent = new DefaultExtent(); extent.getIdentifiers().add(new DefaultIdentifier(node1.getValue()+"_"+node2.getValue())); identificationInfo.getExtents().add(extent); } // iso : MI_Metadata/contentInfo/attributeDescription value=equivalent radiance (W.m-2.Sr-1.um-1) // TODO : not supported // iso : MI_Metadata/contentInfo/contentType value=image contentInfo.setContentType(CoverageContentType.IMAGE); // Landsat : L1_METADATA_FILE/PRODUCT_PARAMETERS/SUN_ELEVATION // iso : MI_Metadata/contentInfo/illuminationElevationAngle node1 = landsat.search("L1_METADATA_FILE","PRODUCT_PARAMETERS","SUN_ELEVATION"); if(node1 != null){ contentInfo.setIlluminationElevationAngle(tryDouble(node1.getValue())); } // Landsat : L1_METADATA_FILE/PRODUCT_PARAMETERS/SUN_AZIMUTH // iso : MI_Metadata/contentInfo/illuminationAzimuthAngle node1 = landsat.search("L1_METADATA_FILE","PRODUCT_PARAMETERS","SUN_AZIMUTH"); if(node1 != null){ contentInfo.setIlluminationAzimuthAngle(tryDouble(node1.getValue())); } // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/PRODUCT_TYPE // iso : MI_Metadata/contentInfo/processingLevelCode/code node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","PRODUCT_TYPE"); if(node1 != null){ contentInfo.setProcessingLevelCode(new DefaultIdentifier(node1.getValue())); } // iso : contentInfo/dimension/sequenceIdentifier // iso : contentInfo/dimension/descriptor value=BAND1 // One occurrence of contentInfo/dimension/ for each spectral band. // loop on BAND  ?_FILE_NAME ou BAND  ??_FILE_NAME trouvés dans le fichier MTL.txt. // example  : // BAND1, ,BAND2, BAND3, BAND4, BAND5, BAND61, BAND62, BAND7, BAND8 in case of L7 ETM+ // BAND1, ,BAND2, BAND3, BAND4, BAND5, BAND6, BAND7 in case of L4 et L5 TM // BAND4, BAND5, BAND6, BAND7 in case of MSS // TODO filled later, depending on band merged operation this is different // Landsat : L1_METADATA_FILE/MIN_MAX_PIXEL_VALUE/QCALMIN_BAND1 // iso : contentInfo/dimension/minValue // Caution, there is one value for each band. // So we have to link the value to the correct band. // elements BAND  ?? and BAND  ??_FILE_NAME allows to do it. // TODO filled later, depending on band merged operation this is different // Landsat : L1_METADATA_FILE/MIN_MAX_PIXEL_VALUE/QCALMAX_BAND1 // iso : contentInfo/dimension/maxValue // same as above // TODO filled later, depending on band merged operation this is different // iso : contentInfo/dimension/units value=W.m-2.Sr-1.um-1 // TODO filled later, depending on band merged operation this is different // Landsat : L1_METADATA_FILE/MIN_MAX_RADIANCE/LMAX_BAND1 + LMIN_BAND1 // Landsat : L1_METADATA_FILE/MIN_MAX_PIXEL_VALUE/QCALMAX_BAND1 + QCALMIN_BAND1 // iso : contentInfo/dimension/scaleFactor // calculate : scaleFactor = (LMAX_BAND1 - LMIN_BAND1) / (QCALMAX_BAND1 – QCALMIN_BAND1) // TODO ??? // Landsat : L1_METADATA_FILE/MIN_MAX_RADIANCE/LMIN_BAND5 // iso : contentInfo/dimension/offset // same as above // TODO ??? // iso : dataQualityInfo/scope value=dataset qualityInfo.setScope(new DefaultScope(ScopeCode.DATASET)); // iso : dataQualityInfo/lineage/processStep/description value=LANDSAT LEVEL 1 PRODUCT processStep.setDescription(new SimpleInternationalString("LANDSAT LEVEL 1 PRODUCT")); // Landsat : L1_METADATA_FILE/METADATA_FILE_INFO/PRODUCT_CREATION_TIME // iso : dataQualityInfo/lineage/processStep/dateTime node1 = landsat.search("L1_METADATA_FILE","METADATA_FILE_INFO","PRODUCT_CREATION_TIME"); if(node1 != null){ final ISODateParser fp = new ISODateParser(); final java.util.Date resultDate = fp.parseToDate(node1.getValue()); processStep.setDate(resultDate); } // iso : dataQualityInfo/lineage/processStep/processor/OrganisationName value=USGS // iso : dataQualityInfo/lineage/processStep/processor/role value=processor final DefaultResponsibleParty processor = new DefaultResponsibleParty(); processor.setOrganisationName(new SimpleInternationalString("USGS")); processor.setRole(Role.PROCESSOR); processStep.getProcessors().add(processor); // Landsat : L1_METADATA_FILE/METADATA_FILE_ INFO/REQUEST_ID // iso : dataQualityInfo/lineage/processStep/processingInformation/identifier/code final DefaultProcessing processInfo = new DefaultProcessing(); processStep.setProcessingInformation(processInfo); node1 = landsat.search("L1_METADATA_FILE","METADATA_FILE_INFO","REQUEST_ID"); if(node1 != null){ processInfo.setIdentifier(new DefaultIdentifier(node1.getValue())); } // Landsat : L1_METADATA_FILE/PRODUCT_METADATA/PROCESSING_SOFTWARE // iso : dataQualityInfo/lineage/processStep/processingInformation/softwareReference/title // iso : dataQualityInfo/lineage/processStep/processingInformation/softwareReference/edition final DefaultCitation softwareReference = new DefaultCitation(); processInfo.getSoftwareReferences().add(softwareReference); node1 = landsat.search("L1_METADATA_FILE","PRODUCT_METADATA","PROCESSING_SOFTWARE"); if(node1 != null){ softwareReference.setTitle(new SimpleInternationalString(node1.getValue())); softwareReference.setEdition(new SimpleInternationalString(node1.getValue())); } // iso : dataQualityInfo/lineage/processStep/processingInformation/softwareReference/date/date value=NilReason="missing" // iso : dataQualityInfo/lineage/processStep/processingInformation/softwareReference/date/date/dateType value=creation // ISO conformance. value is missing but tag must exist with attribute NilReason with value "missing" // missing //final DefaultCitationDate scd = new DefaultCitationDate(null, DateType.CREATION); //softwareReference.getDates().add(scd); // iso : dataQualityInfo/lineage/processStep/processingInformation/algorithm/title // TODO no value ? // iso : dataQualityInfo/lineage/processStep/processingInformation/algorithm/date/date value=NilReason="missing" // ISO conformance. value is missing but tag must exist with attribute NilReason with value "missing" // missing // iso : dataQualityInfo/lineage/processStep/processingInformation/algorithm/date/date/dateType value=creation // missing // iso : dataQualityInfo/lineage/processStep/processingInformation/algorithm/description // TODO no value ? return metadata; } private static double tryDouble(String candidate){ try{ return Double.valueOf(candidate); }catch(NumberFormatException ex){ return Double.NaN; } } }