/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.beam.smos.visat.export;
import org.esa.beam.smos.DateTimeUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import java.awt.geom.Rectangle2D;
import java.io.*;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
class EEHdrFilePatcher {
private Date sensingStart = null;
private Date sensingStop = null;
private String fileName = null;
private Rectangle2D area = null;
private long gridPointCount = 0;
final void patch(File sourceHdrFile, File targetHdrFile) throws IOException {
patch(new FileInputStream(sourceHdrFile), new FileOutputStream(targetHdrFile));
}
final void patch(InputStream sourceStream, OutputStream targetStream) throws IOException {
final Document sourceDocument;
try {
sourceDocument = new SAXBuilder().build(sourceStream);
} catch (JDOMException e) {
throw new IOException(e.getMessage(), e);
}
final Document targetDocument = patchDocument(sourceDocument);
try {
final Format format = getFormat();
final XMLOutputter serializer = new XMLOutputter(format);
serializer.output(targetDocument, targetStream);
} finally {
targetStream.close();
}
}
void setSensingPeriod(Date sensingStart, Date sensingStop) {
this.sensingStart = sensingStart;
this.sensingStop = sensingStop;
}
void setFileName(String fileName) {
this.fileName = fileName;
}
void setArea(Rectangle2D area) {
this.area = area;
}
void setGridPointCount(long gridPointCount) {
this.gridPointCount = gridPointCount;
}
final Format getFormat() {
return Format.getRawFormat().setOmitEncoding(true).setLineSeparator("\n");
}
Document patchDocument(Document document) {
final Element element = document.getRootElement();
final Namespace namespace = element.getNamespace();
patchFixedHeader(element, namespace);
patchVariableHeader(element, namespace);
return document;
}
private void patchVariableHeader(Element element, Namespace namespace) {
final Element variableHeader = element.getChild("Variable_Header", namespace);
final Element specificHeader = variableHeader.getChild("Specific_Product_Header", namespace);
final Element mainInfo = specificHeader.getChild("Main_Info", namespace);
Element timeInfo = null;
if (mainInfo != null) {
// ECMWF aux files do not have this
timeInfo = mainInfo.getChild("Time_Info", namespace);
}
if (sensingStart != null && timeInfo != null) {
final Element validityStart = timeInfo.getChild("Precise_Validity_Start", namespace);
validityStart.setText(DateTimeUtils.toVariableHeaderFormat(sensingStart));
}
if (sensingStop != null && timeInfo != null) {
final Element validityStop = timeInfo.getChild("Precise_Validity_Stop", namespace);
validityStop.setText(DateTimeUtils.toVariableHeaderFormat(sensingStop));
}
if (area != null) {
final DecimalFormat numberFormat = createDecimalFormat();
Element productLocation = specificHeader.getChild("Product_Location", namespace);
if (productLocation == null) {
// we maybe have a L2 file or an ECMWF aux file
productLocation = specificHeader.getChild("L2_Product_Location", namespace);
}
patchGeolocation(namespace, numberFormat, productLocation);
}
if (gridPointCount > 0) {
final NumberFormat numberFormat = new DecimalFormat("0000000000");
final Element listOfDatasets = specificHeader.getChild("List_of_Data_Sets", namespace);
final List children = listOfDatasets.getChildren();
final Iterator dsIterator = children.iterator();
long offset = 0;
while (dsIterator.hasNext()) {
final Element dataSet = (Element) dsIterator.next();
final Element dsSizeElement = dataSet.getChild("DS_Size", namespace);
final String sizeString = dsSizeElement.getText();
final int size = Integer.parseInt(sizeString);
if (size == 0) {
continue;
}
final String dsNameString = dataSet.getChildText("DS_Name", namespace);
if ("Swath_Snapshot_List".equalsIgnoreCase(dsNameString)) {
offset += size;
continue;
}
final String dsrSizeString = dataSet.getChildText("DSR_Size", namespace);
long dsrSize = Long.parseLong(dsrSizeString);
long dsSize = dsrSize * gridPointCount;
dsSizeElement.setText(numberFormat.format(dsSize));
final Element dsOffsetElement = dataSet.getChild("DS_Offset", namespace);
dsOffsetElement.setText(numberFormat.format(offset));
offset += dsSize;
final Element numDsrElement = dataSet.getChild("Num_DSR", namespace);
numDsrElement.setText(numberFormat.format(gridPointCount));
}
}
}
private void patchGeolocation(Namespace namespace, DecimalFormat numberFormat, Element productLocation) {
// @todo 3 tb/tb check for orbit orientation - right now we always assume north-south direction
final Element startLat = productLocation.getChild("Start_Lat", namespace);
startLat.setText(numberFormat.format(area.getMaxY()));
Element startLon = productLocation.getChild("Start_Lon", namespace);
if (startLon == null) {
startLon = productLocation.getChild("Start_Long", namespace);
}
startLon.setText(numberFormat.format(area.getMinX()));
final Element stopLat = productLocation.getChild("Stop_Lat", namespace);
stopLat.setText(numberFormat.format(area.getMinY()));
Element stopLon = productLocation.getChild("Stop_Lon", namespace);
if (stopLon == null) {
stopLon = productLocation.getChild("Stop_Long", namespace);
}
stopLon.setText(numberFormat.format(area.getMaxX()));
// @todo 3 tb/tb pure averaging is not really correct here
final Element midLat = productLocation.getChild("Mid_Lat", namespace);
midLat.setText(numberFormat.format(area.getMinY() + 0.5 * (area.getMaxY() - area.getMinY())));
Element midLon = productLocation.getChild("Mid_Lon", namespace);
if (midLon == null) {
midLon = productLocation.getChild("Mid_Long", namespace);
}
midLon.setText(numberFormat.format(area.getMinX() + 0.5 * (area.getMaxX() - area.getMinX())));
}
private DecimalFormat createDecimalFormat() {
final DecimalFormat numberFormat = new DecimalFormat("+000.000000;-000.000000");
final DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
numberFormat.setDecimalFormatSymbols(symbols);
return numberFormat;
}
private void patchFixedHeader(Element element, Namespace namespace) {
final Element fixedHeader = element.getChild("Fixed_Header", namespace);
if (fileName != null) {
final Element fileNameField = fixedHeader.getChild("File_Name", namespace);
fileNameField.setText(fileName);
}
final Element validityPeriod = fixedHeader.getChild("Validity_Period", namespace);
if (sensingStart != null) {
final Element validityStart = validityPeriod.getChild("Validity_Start", namespace);
validityStart.setText(DateTimeUtils.toFixedHeaderFormat(sensingStart));
}
if (sensingStop != null) {
final Element validityStop = validityPeriod.getChild("Validity_Stop", namespace);
validityStop.setText(DateTimeUtils.toFixedHeaderFormat(sensingStop));
}
}
}