/*
* 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.dataio.smos;
import com.bc.ceres.binio.CompoundData;
import com.bc.ceres.binio.CompoundType;
import com.bc.ceres.binio.DataContext;
import com.bc.ceres.binio.SequenceData;
import com.bc.ceres.glevel.MultiLevelImage;
import org.esa.beam.dataio.smos.dddb.BandDescriptor;
import org.esa.beam.dataio.smos.dddb.Dddb;
import org.esa.beam.dataio.smos.dddb.Family;
import org.esa.beam.framework.datamodel.Band;
import org.esa.beam.framework.datamodel.Product;
import org.esa.beam.framework.datamodel.ProductData;
import org.esa.beam.smos.EEFilePair;
import org.esa.beam.smos.SmosUtils;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Represents a SMOS L1c Science product file.
*
* @author Ralf Quast
* @version $Revision$ $Date$
* @since SMOS-Box 1.0
*/
public class L1cScienceSmosFile extends L1cSmosFile {
private static final String INCIDENCE_ANGLE_NAME = "Incidence_Angle";
private static final double CENTER_BROWSE_INCIDENCE_ANGLE = 42.5;
private static final double MIN_BROWSE_INCIDENCE_ANGLE = 37.5;
private static final double MAX_BROWSE_INCIDENCE_ANGLE = 52.5;
private final Map<String, AbstractValueProvider> valueProviderMap = new HashMap<>(17);
private final int flagsIndex;
private final int incidenceAngleIndex;
private final int snapshotIdOfPixelIndex;
private final double incidenceAngleScalingFactor;
private final SequenceData snapshotList;
private final CompoundType snapshotType;
private final Future<SnapshotInfo> snapshotInfoFuture;
L1cScienceSmosFile(EEFilePair eeFilePair, DataContext dataContext) throws IOException {
super(eeFilePair, dataContext);
final String formatName = dataContext.getFormat().getName();
flagsIndex = getBtDataType().getMemberIndex(SmosConstants.BT_FLAGS_NAME);
incidenceAngleIndex = getBtDataType().getMemberIndex(INCIDENCE_ANGLE_NAME);
final Family<BandDescriptor> bandDescriptors = Dddb.getInstance().getBandDescriptors(formatName);
if (bandDescriptors == null) {
throw new IOException(MessageFormat.format(
"No band descriptors found for format ''{0}''.", formatName));
}
incidenceAngleScalingFactor = getIncidenceAngleScalingFactor(bandDescriptors);
snapshotIdOfPixelIndex = getBtDataType().getMemberIndex(SmosConstants.BT_SNAPSHOT_ID_OF_PIXEL_NAME);
snapshotList = getDataBlock().getSequence(SmosConstants.SNAPSHOT_LIST_NAME);
if (snapshotList == null) {
throw new IOException("Data block does not include snapshot list.");
}
snapshotType = (CompoundType) snapshotList.getType().getElementType();
snapshotInfoFuture = Executors.newSingleThreadExecutor().submit(new Callable<SnapshotInfo>() {
@Override
public SnapshotInfo call() throws IOException {
return createSnapshotInfo();
}
});
}
private double getIncidenceAngleScalingFactor(Family<BandDescriptor> descriptors) {
for (final BandDescriptor descriptor : descriptors.asList()) {
if (INCIDENCE_ANGLE_NAME.equals(descriptor.getMemberName())) {
return descriptor.getScalingFactor();
}
}
throw new IllegalStateException("No incidence angle scaling factor found.");
}
@Override
public void close() {
valueProviderMap.clear();
super.close();
}
@Override
protected void addBands(Product product) {
super.addBands(product);
if (SmosUtils.isDualPolScienceFormat(getDataFormat().getName())) {
addRotatedDualPolBands(product, valueProviderMap);
} else {
addRotatedFullPolBands(product, valueProviderMap);
}
}
@Override
protected final void addBand(Product product, BandDescriptor descriptor) {
if (descriptor.getPolarization() < 0) {
super.addBand(product, descriptor);
} else {
addBand(product, descriptor, getBtDataType());
}
}
@Override
protected final AbstractValueProvider createValueProvider(BandDescriptor descriptor) {
final int polarization = descriptor.getPolarization();
if (polarization < 0) {
return super.createValueProvider(descriptor);
}
final int memberIndex = getBtDataType().getMemberIndex(descriptor.getMemberName());
final AbstractValueProvider valueProvider = new L1cScienceValueProvider(this, memberIndex, polarization);
valueProviderMap.put(descriptor.getBandName(), valueProvider);
return valueProvider;
}
@Override
protected final MultiLevelImage createSourceImage(Band band, ValueProvider valueProvider) {
// todo - make source image reset itself and fire node-data-changed, if affected by snapshot ID (rq-20100121)
return super.createSourceImage(band, valueProvider);
}
public final CompoundData getSnapshotData(int snapshotIndex) throws IOException {
return snapshotList.getCompound(snapshotIndex);
}
public boolean hasSnapshotInfo() {
return snapshotInfoFuture.isDone();
}
public SnapshotInfo getSnapshotInfo() {
try {
return snapshotInfoFuture.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
byte getBrowseBtDataValueByte(int gridPointIndex, int memberIndex, int polarization) throws IOException {
if (memberIndex == flagsIndex) {
return (byte) getCombinedFlags(gridPointIndex, polarization);
} else {
return (byte) getInterpolatedValue(gridPointIndex, memberIndex, polarization);
}
}
short getBrowseBtDataValueShort(int gridPointIndex, int memberIndex, int polarization) throws IOException {
if (memberIndex == flagsIndex) {
return (short) getCombinedFlags(gridPointIndex, polarization);
} else {
return (short) getInterpolatedValue(gridPointIndex, memberIndex, polarization);
}
}
int getBrowseBtDataValueInt(int gridPointIndex, int memberIndex, int polarization) throws IOException {
if (memberIndex == flagsIndex) {
return getCombinedFlags(gridPointIndex, polarization);
} else {
return (int) getInterpolatedValue(gridPointIndex, memberIndex, polarization);
}
}
float getBrowseBtDataValueFloat(int gridPointIndex, int memberIndex, int polarization) throws IOException {
return (float) getInterpolatedValue(gridPointIndex, memberIndex, polarization);
}
CompoundData getSnapshotBtData(int gridPointIndex, int polarization, long snapshotId) throws IOException {
final SequenceData btDataList = getBtDataList(gridPointIndex);
final int elementCount = btDataList.getElementCount();
if (elementCount > 0) {
CompoundData btData = btDataList.getCompound(0);
if (btData.getLong(snapshotIdOfPixelIndex) > snapshotId) {
return null;
}
btData = btDataList.getCompound(elementCount - 1);
if (btData.getLong(snapshotIdOfPixelIndex) < snapshotId) {
return null;
}
for (int i = 0; i < elementCount; ++i) {
btData = btDataList.getCompound(i);
if (btData.getLong(snapshotIdOfPixelIndex) == snapshotId) {
final int flags = btData.getInt(flagsIndex);
if (polarization == 4 || // for flags (they do not depend on polarisation)
polarization == (flags & 1) || // for x or y polarisation (dual pol)
(polarization & flags & 2) != 0) { // for xy polarisation (full pol, real and imaginary)
return btData;
}
}
}
}
return null;
}
private double getInterpolatedValue(int gridPointIndex, int memberIndex, int polarization) throws IOException {
final SequenceData btDataList = getBtDataList(gridPointIndex);
final int elementCount = btDataList.getElementCount();
int count = 0;
double sx = 0;
double sy = 0;
double sxx = 0;
double sxy = 0;
boolean hasLower = false;
boolean hasUpper = false;
for (int i = 0; i < elementCount; ++i) {
final CompoundData btData = btDataList.getCompound(i);
final int flags = btData.getInt(flagsIndex);
if (polarization == 4 || polarization == (flags & 3) || (polarization & flags & 2) != 0) {
final double incidenceAngle = incidenceAngleScalingFactor * btData.getInt(incidenceAngleIndex);
if (incidenceAngle >= MIN_BROWSE_INCIDENCE_ANGLE && incidenceAngle <= MAX_BROWSE_INCIDENCE_ANGLE) {
final float value = btData.getFloat(memberIndex);
sx += incidenceAngle;
sy += value;
sxx += incidenceAngle * incidenceAngle;
sxy += incidenceAngle * value;
count++;
if (!hasLower) {
hasLower = incidenceAngle <= CENTER_BROWSE_INCIDENCE_ANGLE;
}
if (!hasUpper) {
hasUpper = incidenceAngle > CENTER_BROWSE_INCIDENCE_ANGLE;
}
}
}
}
if (hasLower && hasUpper) {
final double a = (count * sxy - sx * sy) / (count * sxx - sx * sx);
final double b = (sy - a * sx) / count;
return a * CENTER_BROWSE_INCIDENCE_ANGLE + b;
}
throw new IOException(MessageFormat.format(
"No data found for grid point ''{0}'' and polarisation ''{1}''.", gridPointIndex, polarization));
}
private int getCombinedFlags(int gridPointIndex, int polarization) throws IOException {
final SequenceData btDataList = getBtDataList(gridPointIndex);
final int elementCount = btDataList.getElementCount();
int combinedFlags = 0;
boolean hasLower = false;
boolean hasUpper = false;
for (int i = 0; i < elementCount; ++i) {
final CompoundData btData = btDataList.getCompound(i);
final int flags = btData.getInt(flagsIndex);
if (polarization == 4 || polarization == (flags & 3) || (polarization & flags & 2) != 0) {
final double incidenceAngle = incidenceAngleScalingFactor * btData.getInt(incidenceAngleIndex);
if (incidenceAngle >= MIN_BROWSE_INCIDENCE_ANGLE && incidenceAngle <= MAX_BROWSE_INCIDENCE_ANGLE) {
combinedFlags |= flags;
if (!hasLower) {
hasLower = incidenceAngle <= CENTER_BROWSE_INCIDENCE_ANGLE;
}
if (!hasUpper) {
hasUpper = incidenceAngle > CENTER_BROWSE_INCIDENCE_ANGLE;
}
}
}
}
if (hasLower && hasUpper) {
return combinedFlags;
}
throw new IOException(MessageFormat.format(
"No data found for grid point ''{0}'' and polarisation ''{1}''.", gridPointIndex, polarization));
}
private SnapshotInfo createSnapshotInfo() throws IOException {
final Set<Long> all = new TreeSet<>();
final Set<Long> x = new TreeSet<>();
final Set<Long> y = new TreeSet<>();
final Set<Long> xy = new TreeSet<>();
final Map<Long, Rectangle2D> snapshotAreaMap = new TreeMap<>();
final int latIndex = getGridPointType().getMemberIndex("Latitude");
final int lonIndex = getGridPointType().getMemberIndex("Longitude");
final GridPointList gridPointList = getGridPointList();
final int gridPointCount = getGridPointCount();
for (int i = 0; i < gridPointCount; i++) {
final SequenceData btList = getBtDataList(i);
final int btCount = btList.getElementCount();
if (btCount > 0) {
final CompoundData gridData = gridPointList.getCompound(i);
double lon = gridData.getDouble(lonIndex);
double lat = gridData.getDouble(latIndex);
// normalisation to [-180, 180] necessary for some L1c test products
if (lon > 180.0) {
lon -= 360.0;
}
final Rectangle2D rectangle = DggUtils.createGridPointRectangle(lon, lat);
long lastId = -1;
for (int j = 0; j < btCount; j++) {
final CompoundData btData = btList.getCompound(j);
final long id = btData.getLong(snapshotIdOfPixelIndex);
if (lastId != id) { // snapshots are ordered
all.add(id);
if (snapshotAreaMap.containsKey(id)) {
// todo: rq/rq - snapshots on the anti-meridian, use area instead of rectangle (2009-10-22)
snapshotAreaMap.get(id).add(rectangle);
} else {
snapshotAreaMap.put(id, rectangle);
}
lastId = id;
}
final int flags = btData.getInt(flagsIndex);
switch (flags & SmosConstants.L1C_POL_MODE_FLAGS_MASK) {
case SmosConstants.L1C_POL_MODE_X:
x.add(id);
break;
case SmosConstants.L1C_POL_MODE_Y:
y.add(id);
break;
case SmosConstants.L1C_POL_MODE_XY1:
case SmosConstants.L1C_POL_MODE_XY2:
xy.add(id);
break;
}
}
}
}
final Map<Long, Integer> snapshotIndexMap = new TreeMap<>();
final int snapshotIdIndex = snapshotType.getMemberIndex(SmosConstants.SNAPSHOT_ID_NAME);
final int snapshotCount = snapshotList.getElementCount();
for (int i = 0; i < snapshotCount; i++) {
final long id = getSnapshotData(i).getLong(snapshotIdIndex);
if (all.contains(id)) {
snapshotIndexMap.put(id, i);
}
}
return new SnapshotInfo(snapshotIndexMap, all, x, y, xy, snapshotAreaMap);
}
private void addRotatedDualPolBands(Product product, Map<String, AbstractValueProvider> valueProviderMap) {
final Family<BandDescriptor> descriptors = Dddb.getInstance().getBandDescriptors(getDataFormat().getName());
DP vp;
BandDescriptor descriptor;
vp = new DPH(product, valueProviderMap, false);
descriptor = descriptors.getMember("BT_Value_H");
addRotatedBand(product, descriptor, vp);
vp = new DPV(product, valueProviderMap, false);
descriptor = descriptors.getMember("BT_Value_V");
addRotatedBand(product, descriptor, vp);
vp = new DPH(product, valueProviderMap, true);
descriptor = descriptors.getMember("Pixel_Radiometric_Accuracy_H");
addRotatedBand(product, descriptor, vp);
vp = new DPV(product, valueProviderMap, true);
descriptor = descriptors.getMember("Pixel_Radiometric_Accuracy_V");
addRotatedBand(product, descriptor, vp);
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_1"), "(BT_Value_X + BT_Value_Y) / 2.0");
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_2"), "(BT_Value_H - BT_Value_V) / 2.0");
}
private void addRotatedFullPolBands(Product product, Map<String, AbstractValueProvider> valueProviderMap) {
final Family<BandDescriptor> descriptors = Dddb.getInstance().getBandDescriptors(getDataFormat().getName());
FP vp;
BandDescriptor descriptor;
vp = new FPH(product, valueProviderMap, false);
descriptor = descriptors.getMember("BT_Value_H");
addRotatedBand(product, descriptor, vp);
vp = new FPV(product, valueProviderMap, false);
descriptor = descriptors.getMember("BT_Value_V");
addRotatedBand(product, descriptor, vp);
vp = new FPHVR(product, valueProviderMap, false);
descriptor = descriptors.getMember("BT_Value_HV_Real");
addRotatedBand(product, descriptor, vp);
ProductHelper.addVirtualBand(product, descriptors.getMember("BT_Value_HV_Imag"), "BT_Value_XY_Imag");
vp = new FPH(product, valueProviderMap, true);
descriptor = descriptors.getMember("Pixel_Radiometric_Accuracy_H");
addRotatedBand(product, descriptor, vp);
vp = new FPV(product, valueProviderMap, true);
descriptor = descriptors.getMember("Pixel_Radiometric_Accuracy_V");
addRotatedBand(product, descriptor, vp);
vp = new FPHVR(product, valueProviderMap, true);
descriptor = descriptors.getMember("Pixel_Radiometric_Accuracy_HV");
addRotatedBand(product, descriptor, vp);
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_1"), "(BT_Value_X + BT_Value_Y) / 2.0");
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_2"), "(BT_Value_H - BT_Value_V) / 2.0");
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_3"), "BT_Value_HV_Real");
ProductHelper.addVirtualBand(product, descriptors.getMember("Stokes_4"), "BT_Value_XY_Imag");
}
private void addRotatedBand(Product product, BandDescriptor descriptor, ValueProvider valueProvider) {
if (!descriptor.isVisible()) {
return;
}
final Band band = product.addBand(descriptor.getBandName(), ProductData.TYPE_FLOAT32);
band.setUnit(descriptor.getUnit());
band.setDescription(descriptor.getDescription());
if (descriptor.hasFillValue()) {
band.setNoDataValueUsed(true);
band.setNoDataValue(descriptor.getFillValue());
}
band.setSourceImage(createSourceImage(band, valueProvider));
band.setImageInfo(ProductHelper.createImageInfo(band, descriptor));
}
}