/*
* Copyright (C) 2015 by Array Systems Computing Inc. http://www.array.ca
*
* 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.s1tbx.io.gamma.header;
import org.apache.commons.math3.util.FastMath;
import org.esa.s1tbx.io.gamma.GammaProductWriter;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.datamodel.OrbitStateVector;
import org.esa.snap.engine_utilities.eo.Constants;
import org.esa.snap.engine_utilities.eo.GeoUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Calendar;
/**
* Writer par header
*/
public class HeaderWriter {
protected final File outputFile;
protected final Product srcProduct;
protected final MetadataElement absRoot;
protected String baseFileName;
private boolean isComplex;
private boolean isCoregistered;
private final GammaProductWriter writer;
private ProductData.UTC dateDay; // start date to the day
protected final static String sep = ":\t";
protected final static String tab = "\t";
private final static double daysToSeconds = 12 * 60 * 60;
public HeaderWriter(final GammaProductWriter writer, final Product srcProduct, final File userOutputFile) {
this.writer = writer;
this.srcProduct = srcProduct;
this.isComplex = false;
this.isCoregistered = false;
absRoot = AbstractMetadata.getAbstractedMetadata(srcProduct);
if (absRoot != null) {
try {
isComplex = absRoot.getAttributeString(AbstractMetadata.SAMPLE_TYPE).equals("COMPLEX");
isCoregistered = AbstractMetadata.getAttributeBoolean(absRoot, AbstractMetadata.coregistered_stack);
} catch (Exception e) {
SystemUtils.LOG.severe("Unable to read metadata " + e.getMessage());
}
}
this.outputFile = createParFile(userOutputFile);
this.baseFileName = FileUtils.getFilenameWithoutExtension(this.outputFile);
if (srcProduct.getStartTime() != null) {
Calendar cal = srcProduct.getStartTime().getAsCalendar();
String dateStr = String.valueOf(cal.get(Calendar.DAY_OF_MONTH)) + '-' + (cal.get(Calendar.MONTH) + 1) + '-' + cal.get(Calendar.YEAR);
try {
dateDay = ProductData.UTC.parse(dateStr, "dd-MM-yyyy");
} catch (Exception e) {
dateDay = srcProduct.getStartTime();
}
}
}
String getBaseFileName() {
return baseFileName;
}
public void writeParFile() throws IOException {
final String oldEOL = System.getProperty("line.separator");
System.setProperty("line.separator", "\n");
final FileOutputStream out = new FileOutputStream(outputFile);
try (final PrintStream p = new PrintStream(out)) {
p.println(GammaConstants.HEADER_KEY_NAME + sep + srcProduct.getName());
p.println(GammaConstants.HEADER_KEY_SENSOR_TYPE + sep + absRoot.getAttributeString(AbstractMetadata.MISSION));
p.println(GammaConstants.HEADER_KEY_DATE + sep + writeDate());
p.println(GammaConstants.HEADER_KEY_START_TIME + sep + writeStartTime());
p.println(GammaConstants.HEADER_KEY_CENTER_TIME + sep + writeCenterTime());
p.println(GammaConstants.HEADER_KEY_END_TIME + sep + writeEndTime());
p.println(GammaConstants.HEADER_KEY_LINE_TIME_INTERVAL + sep + absRoot.getAttributeString(AbstractMetadata.line_time_interval));
p.println(GammaConstants.HEADER_KEY_SAMPLES + sep + srcProduct.getSceneRasterWidth());
p.println(GammaConstants.HEADER_KEY_LINES + sep + srcProduct.getSceneRasterHeight());
p.println(GammaConstants.HEADER_KEY_RANGE_LOOKS + sep + absRoot.getAttributeInt(AbstractMetadata.range_looks));
p.println(GammaConstants.HEADER_KEY_AZIMUTH_LOOKS + sep + absRoot.getAttributeInt(AbstractMetadata.azimuth_looks));
p.println(GammaConstants.HEADER_KEY_DATA_TYPE + sep + getDataType());
p.println(GammaConstants.HEADER_KEY_IMAGE_GEOMETRY + sep + writeImageGeometry());
writeCenterLatLon(p);
p.println(GammaConstants.HEADER_KEY_RANGE_PIXEL_SPACING + sep + absRoot.getAttributeInt(AbstractMetadata.range_spacing) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_AZIMUTH_PIXEL_SPACING + sep + absRoot.getAttributeInt(AbstractMetadata.azimuth_spacing) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_RADAR_FREQUENCY + sep + (absRoot.getAttributeDouble(AbstractMetadata.radar_frequency) * Constants.oneMillion) + tab + "Hz");
p.println(GammaConstants.HEADER_KEY_PRF + sep + absRoot.getAttributeString(AbstractMetadata.pulse_repetition_frequency) + tab + "Hz");
p.println(GammaConstants.HEADER_KEY_AZIMUTH_PROC_BANDWIDTH + sep + absRoot.getAttributeString(AbstractMetadata.azimuth_bandwidth) + tab + "Hz");
writeEarthParams(p);
p.println(GammaConstants.HEADER_KEY_NEAR_RANGE_SLC + sep + absRoot.getAttributeString(AbstractMetadata.slant_range_to_first_pixel) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_CENTER_RANGE_SLC + sep + absRoot.getAttributeString(AbstractMetadata.slant_range_to_first_pixel) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_FAR_RANGE_SLC + sep + absRoot.getAttributeString(AbstractMetadata.slant_range_to_first_pixel) + tab + 'm');
writeOrbitStateVectors(p);
p.flush();
} catch (Exception e) {
throw new IOException("GammaWriter unable to write par file " + e.getMessage());
} finally {
System.setProperty("line.separator", oldEOL);
}
}
private String writeDate() {
if (srcProduct.getStartTime() != null) {
Calendar cal = srcProduct.getStartTime().getAsCalendar();
return cal.get(Calendar.YEAR) + " " + (cal.get(Calendar.MONTH) + 1) + " " + cal.get(Calendar.DAY_OF_MONTH);
}
return "";
}
private String writeStartTime() {
if (srcProduct.getStartTime() != null) {
double diff = srcProduct.getStartTime().getMJD() - dateDay.getMJD();
double seconds = diff * daysToSeconds;
return seconds + tab + 's';
}
return "";
}
private String writeCenterTime() {
if (srcProduct.getStartTime() != null) {
double center = (srcProduct.getStartTime().getMJD() +
(srcProduct.getEndTime().getMJD() - srcProduct.getStartTime().getMJD()) / 2.0);
double seconds = (center - dateDay.getMJD()) * daysToSeconds;
return seconds + tab + 's';
}
return "";
}
private String writeEndTime() {
if (srcProduct.getEndTime() != null) {
double diff = srcProduct.getEndTime().getMJD() - dateDay.getMJD();
double seconds = diff * daysToSeconds;
return seconds + tab + 's';
}
return "";
}
private String writeImageGeometry() {
if (absRoot.getAttributeString(AbstractMetadata.sample_type).equals("COMPLEX") ||
absRoot.getAttributeInt(AbstractMetadata.srgr_flag, 0) == 0) {
return "SLANT_RANGE";
}
return "GROUND_RANGE";
}
private void writeCenterLatLon(final PrintStream p) {
GeoPos geoPos = srcProduct.getSceneGeoCoding().getGeoPos(
new PixelPos(srcProduct.getSceneRasterWidth() / 2, srcProduct.getSceneRasterHeight() / 2), null);
p.println(GammaConstants.HEADER_KEY_CENTER_LATITUDE + sep + geoPos.getLat() + tab + "degrees");
p.println(GammaConstants.HEADER_KEY_CENTER_LONGITUDE + sep + geoPos.getLon() + tab + "degrees");
GeoPos geoPos2 = srcProduct.getSceneGeoCoding().getGeoPos(
new PixelPos(srcProduct.getSceneRasterWidth() / 2, (srcProduct.getSceneRasterHeight() / 2) + 100), null);
GeoUtils.DistanceHeading heading = GeoUtils.vincenty_inverse(geoPos, geoPos2);
p.println(GammaConstants.HEADER_KEY_HEADING + sep + heading.heading1 + tab + "degrees");
}
private void writeOrbitStateVectors(final PrintStream p) {
final OrbitStateVector[] osvList = AbstractMetadata.getOrbitStateVectors(absRoot);
if (osvList != null && osvList.length > 0) {
double seconds = (osvList[0].time_mjd - dateDay.getMJD()) * daysToSeconds;
double seconds2 = (osvList[1].time_mjd - dateDay.getMJD()) * daysToSeconds;
double interval = seconds2 - seconds;
p.println(GammaConstants.HEADER_KEY_NUM_STATE_VECTORS + sep + osvList.length);
p.println(GammaConstants.HEADER_KEY_TIME_FIRST_STATE_VECTORS + sep + seconds + tab + 's');
p.println(GammaConstants.HEADER_KEY_STATE_VECTOR_INTERVAL + sep + interval + tab + 's');
int num = 1;
for (OrbitStateVector osv : osvList) {
p.println(GammaConstants.HEADER_KEY_STATE_VECTOR_POSITION + '_' + num + sep +
osv.x_pos + tab + osv.y_pos + tab + osv.z_pos + tab + "m m m");
p.println(GammaConstants.HEADER_KEY_STATE_VECTOR_VELOCITY + '_' + num + sep +
osv.x_vel + tab + osv.y_vel + tab + osv.z_vel + tab + "m/s m/s m/s");
++num;
}
}
}
private void writeEarthParams(final PrintStream p) {
if (srcProduct.getStartTime() == null) {
return;
}
final double startTime = srcProduct.getStartTime().getMJD();
final OrbitStateVector[] osvList = AbstractMetadata.getOrbitStateVectors(absRoot);
double sensorToEarth = 0.0;
if (osvList != null && osvList.length > 0) {
// maybe interpolation should be used here, for now we just pick the nearest orbit state vector
double dtMin = Double.MAX_VALUE;
int idx = 0;
for (int i = 0; i < osvList.length; ++i) {
final double dt = Math.abs(startTime - osvList[i].time_mjd);
if (dt < dtMin) {
dtMin = dt;
idx = i;
}
}
sensorToEarth = Math.sqrt(osvList[idx].x_pos * osvList[idx].x_pos +
osvList[idx].y_pos * osvList[idx].y_pos + osvList[idx].z_pos * osvList[idx].z_pos);
}
p.println(GammaConstants.HEADER_KEY_SAR_TO_EARTH_CENTER + sep + String.valueOf(sensorToEarth) + tab + 'm');
GeoPos geoPos = srcProduct.getSceneGeoCoding().getGeoPos(
new PixelPos(srcProduct.getSceneRasterWidth() / 2, srcProduct.getSceneRasterHeight() / 2), null);
final double lat = geoPos.getLat();
final double tmp1 = Constants.semiMajorAxis * Constants.semiMajorAxis * FastMath.cos(lat);
final double tmp2 = Constants.semiMinorAxis * Constants.semiMinorAxis * FastMath.sin(lat);
final double r = Math.sqrt((tmp1 * tmp1 + tmp2 * tmp2) / (tmp1 * FastMath.cos(lat) + tmp2 * FastMath.sin(lat)));
p.println(GammaConstants.HEADER_KEY_EARTH_RADIUS_BELOW_SENSOR + sep + String.valueOf(r) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_EARTH_SEMI_MAJOR_AXIS + sep + String.valueOf(Constants.semiMajorAxis) + tab + 'm');
p.println(GammaConstants.HEADER_KEY_EARTH_SEMI_MINOR_AXIS + sep + String.valueOf(Constants.semiMinorAxis) + tab + 'm');
}
public int getHighestElemSize() {
int highestElemSize = 0;
for (Band band : srcProduct.getBands()) {
if (writer.shouldWrite(band)) {
int elemSize = ProductData.getElemSize(band.getDataType());
if (elemSize > highestElemSize) {
highestElemSize = elemSize;
}
}
}
return highestElemSize;
}
protected String getDataType() {
int highestElemSize = getHighestElemSize();
if (highestElemSize >= 4) {
return "FCOMPLEX";
} else {
return "SCOMPLEX";
}
}
private File createParFile(final File file) {
String name = FileUtils.getFilenameWithoutExtension(file);
String ext = FileUtils.getExtension(name);
String newExt = GammaConstants.PAR_EXTENSION;
if (ext == null) {
if (isComplex) {
if (isCoregistered) {
newExt = ".rslc" + newExt;
} else {
newExt = ".slc" + newExt;
}
}
}
name += newExt;
return new File(file.getParent(), name);
}
}