/*
* Copyright (C) 2014 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.sentinel1.gpf;
import com.bc.ceres.core.ProgressMonitor;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.esa.s1tbx.insar.gpf.support.Sentinel1Utils;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.TiePointGeoCoding;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.gpf.Operator;
import org.esa.snap.core.gpf.OperatorException;
import org.esa.snap.core.gpf.OperatorSpi;
import org.esa.snap.core.gpf.Tile;
import org.esa.snap.core.gpf.annotations.OperatorMetadata;
import org.esa.snap.core.gpf.annotations.Parameter;
import org.esa.snap.core.gpf.annotations.SourceProducts;
import org.esa.snap.core.gpf.annotations.TargetProduct;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.datamodel.Unit;
import org.esa.snap.engine_utilities.eo.Constants;
import org.esa.snap.engine_utilities.gpf.InputProductValidator;
import org.esa.snap.engine_utilities.gpf.OperatorUtils;
import org.esa.snap.engine_utilities.gpf.StackUtils;
import org.esa.snap.engine_utilities.gpf.ReaderUtils;
import org.esa.snap.engine_utilities.gpf.TileIndex;
import org.esa.snap.engine_utilities.util.Maths;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Merge subswaths of a Sentinel-1 TOPSAR product.
*/
@OperatorMetadata(alias = "TOPSAR-Merge",
category = "Radar/Sentinel-1 TOPS",
authors = "Jun Lu, Luis Veci",
version = "1.0",
copyright = "Copyright (C) 2014 by Array Systems Computing Inc.",
description = "Merge subswaths of a Sentinel-1 TOPSAR product")
public final class TOPSARMergeOp extends Operator {
@SourceProducts
private Product[] sourceProduct;
@TargetProduct
private Product targetProduct;
@Parameter(description = "The list of polarisations", label = "Polarisations")
private String[] selectedPolarisations;
private String acquisitionMode = null;
private String productType = null;
private int numOfSubSwath = 0;
private int refSubSwathIndex = 0;
private int targetWidth = 0;
private int targetHeight = 0;
private double targetFirstLineTime = 0;
private double targetLastLineTime = 0;
private double targetLineTimeInterval = 0;
private double targetSlantRangeTimeToFirstPixel = 0;
private double targetSlantRangeTimeToLastPixel = 0;
private double targetDeltaSlantRangeTime = 0;
private Sentinel1Utils[] su = null;
private Sentinel1Utils.SubSwathInfo[] subSwath = null;
private final BiMap<Integer, Integer> sourceProductIndexToSubSwathIndexMap = HashBiMap.create();
private static final String PRODUCT_SUFFIX = "_mrg";
/**
* Default constructor. The graph processing framework
* requires that an operator has a default constructor.
*/
public TOPSARMergeOp() {
}
/**
* Initializes this operator and sets the one and only target product.
* <p>The target product can be either defined by a field of type {@link Product} annotated with the
* {@link TargetProduct TargetProduct} annotation or
* by calling {@link #setTargetProduct} method.</p>
* <p>The framework calls this method after it has created this operator.
* Any client code that must be performed before computation of tile data
* should be placed here.</p>
*
* @throws OperatorException If an error occurs during operator initialisation.
* @see #getTargetProduct()
*/
@Override
public void initialize() throws OperatorException {
try {
if (sourceProduct == null) {
return;
}
checkSourceProductValidity();
getSubSwathParameters();
computeTargetStartEndTime();
computeTargetSlantRangeTimeToFirstAndLastPixels();
computeTargetWidthAndHeight();
createTargetProduct();
updateTargetProductMetadata();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* Check source product validity.
*/
private void checkSourceProductValidity() {
if (sourceProduct.length < 2) {
throw new OperatorException("Please select split sub-swaths of the same Sentinel-1 products");
}
final InputProductValidator validator = new InputProductValidator(sourceProduct[0]);
validator.checkIfSARProduct();
validator.checkIfSentinel1Product();
validator.isTOPSARProduct();
validator.checkIfTOPSARBurstProduct(false);
validator.checkIfMapProjected(false);
// check if all sub-swaths are from the same s-1 product
MetadataElement absRoot0 = AbstractMetadata.getAbstractedMetadata(sourceProduct[0]);
numOfSubSwath = sourceProduct.length;
final int numOfBands0 = sourceProduct[0].getNumBands();
final int[] subSwathIndexArray = new int[numOfSubSwath];
final String product0 = absRoot0.getAttributeString(AbstractMetadata.PRODUCT);
acquisitionMode = absRoot0.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
productType = absRoot0.getAttributeString(AbstractMetadata.PRODUCT_TYPE);
final String subSwathName0 = absRoot0.getAttributeString(AbstractMetadata.swath);
if (subSwathName0.equals("")) {
throw new OperatorException("Cannot get \"swath\" information from source product abstracted metadata");
}
subSwathIndexArray[0] = getSubSwathIndex(subSwathName0);
sourceProductIndexToSubSwathIndexMap.put(0, subSwathIndexArray[0]);
for (int p = 1; p < numOfSubSwath; p++) {
MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct[p]);
final String product = absRoot.getAttributeString(AbstractMetadata.PRODUCT);
if (!product.equals(product0)) {
throw new OperatorException("Source products are not from the same Sentinel-1 product");
}
final String acMode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
if (!acMode.equals(acquisitionMode)) {
throw new OperatorException("Source products do not have the same acquisition mode");
}
final int numOfBands = sourceProduct[p].getNumBands();
if (numOfBands != numOfBands0) {
throw new OperatorException("Source products do not have the same number of bands");
}
final String subSwathName = absRoot.getAttributeString(AbstractMetadata.swath);
if (subSwathName.equals("")) {
throw new OperatorException("Cannot get \"swath\" information from source product abstracted metadata");
}
subSwathIndexArray[p] = getSubSwathIndex(subSwathName);
sourceProductIndexToSubSwathIndexMap.put(p, subSwathIndexArray[p]);
}
Arrays.sort(subSwathIndexArray);
refSubSwathIndex = subSwathIndexArray[0];
for (int s = 0; s < numOfSubSwath - 1; s++) {
if (subSwathIndexArray[s + 1] - subSwathIndexArray[s] != 1) {
throw new OperatorException("Isolate sub-swath detected in source products");
}
}
}
private int getSubSwathIndex(final String subswath) {
final String idxStr = subswath.substring(2);
return Integer.parseInt(idxStr);
}
private void getSubSwathParameters() {
try {
su = new Sentinel1Utils[numOfSubSwath];
subSwath = new Sentinel1Utils.SubSwathInfo[numOfSubSwath];
for (int p = 0; p < numOfSubSwath; p++) {
final int s = sourceProductIndexToSubSwathIndexMap.get(p) - refSubSwathIndex;
su[s] = new Sentinel1Utils(sourceProduct[p]);
subSwath[s] = su[s].getSubSwath()[0];
if (selectedPolarisations == null || selectedPolarisations.length == 0) {
selectedPolarisations = su[s].getPolarizations();
}
final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct[p]);
subSwath[s].firstValidPixel = AbstractMetadata.getAttributeInt(absRoot, "firstValidPixel");
subSwath[s].lastValidPixel = AbstractMetadata.getAttributeInt(absRoot, "lastValidPixel");
subSwath[s].slrTimeToFirstValidPixel = AbstractMetadata.getAttributeDouble(absRoot, "slrTimeToFirstValidPixel");
subSwath[s].slrTimeToLastValidPixel = AbstractMetadata.getAttributeDouble(absRoot, "slrTimeToLastValidPixel");
subSwath[s].firstValidLineTime = AbstractMetadata.getAttributeDouble(absRoot, "firstValidLineTime");
subSwath[s].lastValidLineTime = AbstractMetadata.getAttributeDouble(absRoot, "lastValidLineTime");
}
} catch (Throwable e) {
throw new OperatorException(e.getMessage());
}
}
/**
* Compute azimuth time for the first and last line in the target product.
*/
private void computeTargetStartEndTime() {
targetFirstLineTime = subSwath[0].firstLineTime;
targetLastLineTime = subSwath[0].lastLineTime;
for (int i = 1; i < numOfSubSwath; i++) {
if (targetFirstLineTime > subSwath[i].firstLineTime) {
targetFirstLineTime = subSwath[i].firstLineTime;
}
if (targetLastLineTime < subSwath[i].lastLineTime) {
targetLastLineTime = subSwath[i].lastLineTime;
}
}
targetLineTimeInterval = subSwath[0].azimuthTimeInterval;
}
/**
* Compute slant range time to the first and last pixels in the target product.
*/
private void computeTargetSlantRangeTimeToFirstAndLastPixels() {
targetSlantRangeTimeToFirstPixel = subSwath[0].slrTimeToFirstValidPixel;
targetSlantRangeTimeToLastPixel = subSwath[numOfSubSwath - 1].slrTimeToLastValidPixel;
targetDeltaSlantRangeTime = subSwath[0].rangePixelSpacing / Constants.lightSpeed;
}
/**
* Compute target product dimension.
*/
private void computeTargetWidthAndHeight() {
targetHeight = (int) ((targetLastLineTime - targetFirstLineTime) / targetLineTimeInterval);
targetWidth = (int) ((targetSlantRangeTimeToLastPixel - targetSlantRangeTimeToFirstPixel) /
targetDeltaSlantRangeTime);
}
/**
* Create target product.
*/
private void createTargetProduct() {
final int prodIdx = sourceProductIndexToSubSwathIndexMap.inverse().get(refSubSwathIndex);
targetProduct = new Product(sourceProduct[prodIdx].getName() + PRODUCT_SUFFIX,
productType, targetWidth, targetHeight);
final Band[] sourceBands = sourceProduct[prodIdx].getBands();
// source band name is assumed in format: name_acquisitionModeAndSubSwathIndex_polarization_prefix
// target band name is then in format: name_polarization_prefix
boolean hasVirtualPhaseBand = false;
for (Band srcBand : sourceBands) {
final String srcBandName = srcBand.getName();
if (!containSelectedPolarisations(srcBandName)) {
continue;
}
if (srcBand instanceof VirtualBand) {
if (srcBandName.toLowerCase().contains("phase")) {
hasVirtualPhaseBand = true;
}
continue;
}
final String tgtBandName = getTargetBandNameFromSourceBandName(srcBandName);
if (!targetProduct.containsBand(tgtBandName)) {
final Band trgBand = targetProduct.addBand(tgtBandName, srcBand.getDataType());
trgBand.setUnit(srcBand.getUnit());
trgBand.setNoDataValueUsed(true);
trgBand.setNoDataValue(srcBand.getNoDataValue());
}
}
final Band[] targetBands = targetProduct.getBands();
for (int i = 0; i < targetBands.length; i++) {
final Unit.UnitType iBandUnit = Unit.getUnitType(targetBands[i]);
if (iBandUnit == Unit.UnitType.REAL && i + 1 < targetBands.length) {
final Unit.UnitType qBandUnit = Unit.getUnitType(targetBands[i + 1]);
if (qBandUnit == Unit.UnitType.IMAGINARY) {
ReaderUtils.createVirtualIntensityBand(
targetProduct, targetBands[i], targetBands[i + 1], '_' + getPrefix(targetBands[i].getName()));
if (hasVirtualPhaseBand) {
ReaderUtils.createVirtualPhaseBand(targetProduct,
targetBands[i], targetBands[i + 1], '_' + getPrefix(targetBands[i].getName()));
}
i++;
}
}
}
ProductUtils.copyMetadata(sourceProduct[prodIdx], targetProduct);
ProductUtils.copyFlagCodings(sourceProduct[prodIdx], targetProduct);
targetProduct.setStartTime(new ProductData.UTC(targetFirstLineTime / Constants.secondsInDay));
targetProduct.setEndTime(new ProductData.UTC(targetLastLineTime / Constants.secondsInDay));
targetProduct.setDescription(sourceProduct[prodIdx].getDescription());
createTiePointGrids();
}
private String getTargetBandNameFromSourceBandName(final String srcBandName) {
if (!srcBandName.contains(acquisitionMode)) {
return srcBandName;
}
final int firstSeparationIdx = srcBandName.indexOf(acquisitionMode);
final int secondSeparationIdx = srcBandName.indexOf("_", firstSeparationIdx + 1);
return srcBandName.substring(0, firstSeparationIdx) + srcBandName.substring(secondSeparationIdx + 1);
}
private void createTiePointGrids() {
final int gridWidth = 20;
final int gridHeight = 5;
final int subSamplingX = targetWidth / gridWidth;
final int subSamplingY = targetHeight / gridHeight;
final float[] latList = new float[gridWidth * gridHeight];
final float[] lonList = new float[gridWidth * gridHeight];
final float[] slrtList = new float[gridWidth * gridHeight];
final float[] incList = new float[gridWidth * gridHeight];
int k = 0;
for (int i = 0; i < gridHeight; i++) {
final int y = i * subSamplingY;
final double azTime = targetFirstLineTime + y * targetLineTimeInterval;
for (int j = 0; j < gridWidth; j++) {
final int x = j * subSamplingX;
final double slrTime = targetSlantRangeTimeToFirstPixel + x * targetDeltaSlantRangeTime;
final int s = getSubSwathIndex(slrTime);
latList[k] = (float) su[s].getLatitude(azTime, slrTime);
lonList[k] = (float) su[s].getLongitude(azTime, slrTime);
slrtList[k] = (float) (su[s].getSlantRangeTime(azTime, slrTime) * 2 * Constants.oneBillion); // 2-way ns
incList[k] = (float) su[s].getIncidenceAngle(azTime, slrTime);
k++;
}
}
final TiePointGrid latGrid = new TiePointGrid(
OperatorUtils.TPG_LATITUDE, gridWidth, gridHeight, 0, 0, subSamplingX, subSamplingY, latList);
final TiePointGrid lonGrid = new TiePointGrid(
OperatorUtils.TPG_LONGITUDE, gridWidth, gridHeight, 0, 0, subSamplingX, subSamplingY, lonList);
final TiePointGrid slrtGrid = new TiePointGrid(
OperatorUtils.TPG_SLANT_RANGE_TIME, gridWidth, gridHeight, 0, 0, subSamplingX, subSamplingY, slrtList);
final TiePointGrid incGrid = new TiePointGrid(
OperatorUtils.TPG_INCIDENT_ANGLE, gridWidth, gridHeight, 0, 0, subSamplingX, subSamplingY, incList);
latGrid.setUnit(Unit.DEGREES);
lonGrid.setUnit(Unit.DEGREES);
slrtGrid.setUnit(Unit.NANOSECONDS);
incGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(latGrid);
targetProduct.addTiePointGrid(lonGrid);
targetProduct.addTiePointGrid(slrtGrid);
targetProduct.addTiePointGrid(incGrid);
final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid);
targetProduct.setSceneGeoCoding(tpGeoCoding);
}
private int getSubSwathIndex(final double slrTime) {
double startTime, endTime;
for (int i = 0; i < numOfSubSwath; i++) {
if (i == 0) {
startTime = subSwath[i].slrTimeToFirstValidPixel;
} else {
startTime = 0.5 * (subSwath[i].slrTimeToFirstValidPixel + subSwath[i - 1].slrTimeToLastPixel);
}
if (i == numOfSubSwath - 1) {
endTime = subSwath[i].slrTimeToLastPixel;
} else {
endTime = 0.5 * (subSwath[i].slrTimeToLastPixel + subSwath[i + 1].slrTimeToFirstValidPixel);
}
if (slrTime >= startTime && slrTime < endTime) {
return i;
}
}
return 0;
}
private Band getSourceBandFromTargetBandName(
final String tgtBandName, final String acquisitionMode, final String swathIndexStr) {
for (int s = 0; s < numOfSubSwath; s++) {
final String[] srcBandNames = sourceProduct[s].getBandNames();
for (String srcBandName : srcBandNames) {
if (srcBandName.contains(acquisitionMode + swathIndexStr) &&
getTargetBandNameFromSourceBandName(srcBandName).equals(tgtBandName)) {
return sourceProduct[s].getBand(srcBandName);
}
}
}
return null;
}
private static String getPrefix(final String tgtBandName) {
final int firstSeparationIdx = tgtBandName.indexOf("_");
return tgtBandName.substring(firstSeparationIdx + 1);
}
private boolean containSelectedPolarisations(final String bandName) {
for (String pol : selectedPolarisations) {
if (bandName.contains(pol)) {
return true;
}
}
return false;
}
/**
* Update target product metadata.
*/
private void updateTargetProductMetadata() {
updateAbstractMetadata();
updateOriginalMetadata();
}
private void updateAbstractMetadata() {
final MetadataElement absTgt = AbstractMetadata.getAbstractedMetadata(targetProduct);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_output_lines, targetHeight);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_samples_per_line, targetWidth);
absTgt.setAttributeUTC(
AbstractMetadata.first_line_time, new ProductData.UTC(targetFirstLineTime / Constants.secondsInDay));
absTgt.setAttributeUTC(
AbstractMetadata.last_line_time, new ProductData.UTC(targetLastLineTime / Constants.secondsInDay));
absTgt.setAttributeDouble(AbstractMetadata.line_time_interval, targetLineTimeInterval);
absTgt.setAttributeDouble(AbstractMetadata.slant_range_to_first_pixel,
targetSlantRangeTimeToFirstPixel * Constants.lightSpeed);
TiePointGrid latGrid = targetProduct.getTiePointGrid(OperatorUtils.TPG_LATITUDE);
TiePointGrid lonGrid = targetProduct.getTiePointGrid(OperatorUtils.TPG_LONGITUDE);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_near_lat, latGrid.getPixelFloat(0, 0));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_near_long, lonGrid.getPixelFloat(0, 0));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_far_lat, latGrid.getPixelFloat(targetWidth, 0));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_far_long, lonGrid.getPixelFloat(targetWidth, 0));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.last_near_lat, latGrid.getPixelFloat(0, targetHeight));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.last_near_long, lonGrid.getPixelFloat(0, targetHeight));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.last_far_lat, latGrid.getPixelFloat(targetWidth, targetHeight));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.last_far_long, lonGrid.getPixelFloat(targetWidth, targetHeight));
final double incidenceNear = OperatorUtils.getIncidenceAngle(targetProduct).getPixelDouble(
0, targetProduct.getSceneRasterHeight() / 2);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.incidence_near, incidenceNear);
final double incidenceFar = OperatorUtils.getIncidenceAngle(targetProduct).getPixelDouble(
targetProduct.getSceneRasterWidth() - 1, targetProduct.getSceneRasterHeight() / 2);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.incidence_far, incidenceFar);
absTgt.removeAttribute(absTgt.getAttribute("firstValidPixel"));
absTgt.removeAttribute(absTgt.getAttribute("lastValidPixel"));
absTgt.removeAttribute(absTgt.getAttribute("slrTimeToFirstValidPixel"));
absTgt.removeAttribute(absTgt.getAttribute("slrTimeToLastValidPixel"));
absTgt.removeAttribute(absTgt.getAttribute("firstValidLineTime"));
absTgt.removeAttribute(absTgt.getAttribute("lastValidLineTime"));
absTgt.removeElement(absTgt.getElement("BurstBoundary"));
final MetadataElement burstBoundaryTgt = new MetadataElement("BurstBoundary");
for (int p = 0; p < numOfSubSwath; p++) {
MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct[p]);
final MetadataElement burstBoundarySrc = absRoot.getElement("BurstBoundary");
if (burstBoundarySrc != null && burstBoundarySrc.getNumElements() > 0) {
final MetadataElement element = burstBoundarySrc.getElementAt(0);
if (element != null) {
burstBoundaryTgt.addElement(element.createDeepClone());
}
}
}
absTgt.addElement(burstBoundaryTgt);
absTgt.removeElement(absTgt.getElement("ESD_Measurement"));
final MetadataElement ESDMeasurementTgt = new MetadataElement("ESD_Measurement");
for (int p = 0; p < numOfSubSwath; p++) {
MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct[p]);
final MetadataElement ESDMeasurementSrc = absRoot.getElement("ESD_Measurement");
if (ESDMeasurementSrc != null) {
final MetadataElement element = ESDMeasurementSrc.getElementAt(0);
if (element != null) {
ESDMeasurementTgt.addElement(element.createDeepClone());
}
}
}
absTgt.addElement(ESDMeasurementTgt);
if (StackUtils.isCoregisteredStack(targetProduct)) {
List<String> masterProductBands = new ArrayList<>();
List<String> slaveProductBands = new ArrayList<>();
for (String bandName : targetProduct.getBandNames()) {
if (targetProduct.getBand(bandName) instanceof VirtualBand) {
continue;
}
if (bandName.contains(StackUtils.MST)) {
masterProductBands.add(bandName);
}
if (bandName.contains(StackUtils.SLV)) {
slaveProductBands.add(bandName);
}
}
MetadataElement targetSlaveMetadataRoot = AbstractMetadata.getSlaveMetadata(targetProduct.getMetadataRoot());
if(!masterProductBands.isEmpty()) {
targetSlaveMetadataRoot.setAttributeString(AbstractMetadata.MASTER_BANDS,
String.join(" ", masterProductBands.toArray(new String[masterProductBands.size()])));
}
if(!slaveProductBands.isEmpty()) {
targetSlaveMetadataRoot.getElementAt(0).setAttributeString(AbstractMetadata.SLAVE_BANDS,
String.join(" ", slaveProductBands.toArray(new String[slaveProductBands.size()])));
}
}
}
private void updateOriginalMetadata() {
if (numOfSubSwath > 1) {
//updateCalibrationVector();
//updateNoiseVector(); //todo: to be implemented
}
}
/**
* Called by the framework in order to compute the stack of tiles for the given target bands.
* <p>The default implementation throws a runtime exception with the message "not implemented".</p>
*
* @param targetTiles The current tiles to be computed for each target band.
* @param pm A progress monitor which should be used to determine computation cancelation requests.
* @throws OperatorException if an error occurs during computation of the target rasters.
*/
@Override
public void computeTileStack(Map<Band, Tile> targetTiles, Rectangle targetRectangle, ProgressMonitor pm)
throws OperatorException {
try {
final int tx0 = targetRectangle.x;
final int ty0 = targetRectangle.y;
final int tw = targetRectangle.width;
final int th = targetRectangle.height;
// determine subswaths covered by the tile
final double tileSlrtToFirstPixel = targetSlantRangeTimeToFirstPixel + tx0 * targetDeltaSlantRangeTime;
final double tileSlrtToLastPixel = targetSlantRangeTimeToFirstPixel + (tx0 + tw - 1) * targetDeltaSlantRangeTime;
final double tileFirstLineTime = targetFirstLineTime + ty0 * targetLineTimeInterval;
final double tileLastLineTime = targetFirstLineTime + (ty0 + th - 1) * targetLineTimeInterval;
int firstSubSwathIndex = -1;
int lastSubSwathIndex = -1;
for (int i = 0; i < numOfSubSwath; i++) {
if (tileSlrtToFirstPixel >= subSwath[i].slrTimeToFirstValidPixel &&
tileSlrtToFirstPixel <= subSwath[i].slrTimeToLastValidPixel) {
if (tileFirstLineTime >= subSwath[i].firstValidLineTime &&
tileFirstLineTime < subSwath[i].lastValidLineTime ||
tileLastLineTime >= subSwath[i].firstValidLineTime &&
tileLastLineTime < subSwath[i].lastValidLineTime) {
firstSubSwathIndex = i;
break;
}
}
}
if (firstSubSwathIndex == numOfSubSwath) {
lastSubSwathIndex = firstSubSwathIndex;
} else {
for (int i = 0; i < numOfSubSwath; i++) {
if (tileSlrtToLastPixel >= subSwath[i].slrTimeToFirstValidPixel &&
tileSlrtToLastPixel <= subSwath[i].slrTimeToLastValidPixel) {
if (tileFirstLineTime >= subSwath[i].firstValidLineTime &&
tileFirstLineTime < subSwath[i].lastValidLineTime ||
tileLastLineTime >= subSwath[i].firstValidLineTime &&
tileLastLineTime < subSwath[i].lastValidLineTime) {
lastSubSwathIndex = i;
}
}
}
}
if (firstSubSwathIndex == -1 && lastSubSwathIndex == -1) {
return;
}
if (firstSubSwathIndex != -1 && lastSubSwathIndex == -1) {
lastSubSwathIndex = firstSubSwathIndex;
}
if (firstSubSwathIndex == -1 && lastSubSwathIndex != -1) {
firstSubSwathIndex = lastSubSwathIndex;
}
final int numOfSourceTiles = lastSubSwathIndex - firstSubSwathIndex + 1;
final boolean tileInOneSubSwath = (numOfSourceTiles == 1);
final Rectangle[] sourceRectangle = new Rectangle[numOfSourceTiles];
int k = 0;
for (int i = firstSubSwathIndex; i <= lastSubSwathIndex; i++) {
sourceRectangle[k++] = getSourceRectangle(tx0, ty0, tw, th, i);
}
final int txMax = tx0 + tw;
final int tyMax = ty0 + th;
final Band[] tgtBands = targetProduct.getBands();
for (Band tgtBand : tgtBands) {
if (tgtBand instanceof VirtualBand) {
continue;
}
final String tgtBandName = tgtBand.getName();
final int dataType = tgtBand.getDataType();
final Tile tgtTile = targetTiles.get(tgtBand);
if (tileInOneSubSwath) {
if (dataType == ProductData.TYPE_INT16) {
computeTileInOneSwathShort(tx0, ty0, txMax, tyMax, firstSubSwathIndex,
sourceRectangle, tgtBandName, tgtTile);
} else {
computeTileInOneSwathFloat(tx0, ty0, txMax, tyMax, firstSubSwathIndex,
sourceRectangle, tgtBandName, tgtTile);
}
} else {
if (dataType == ProductData.TYPE_INT16) {
computeMultipleSubSwathsShort(tx0, ty0, txMax, tyMax, firstSubSwathIndex, lastSubSwathIndex,
sourceRectangle, tgtBandName, tgtTile);
} else {
computeMultipleSubSwathsFloat(tx0, ty0, txMax, tyMax, firstSubSwathIndex, lastSubSwathIndex,
sourceRectangle, tgtBandName, tgtTile);
}
}
}
} catch (Throwable e) {
e.printStackTrace();
//OperatorUtils.catchOperatorException(getId(), e);
} finally {
pm.done();
}
}
private void computeTileInOneSwathShort(final int tx0, final int ty0, final int txMax, final int tyMax,
final int firstSubSwathIndex, final Rectangle[] sourceRectangle,
final String tgtBandName, final Tile tgtTile) {
final int yMin = computeYMin(subSwath[firstSubSwathIndex]);
final int yMax = computeYMax(subSwath[firstSubSwathIndex]);
final int xMin = computeXMin(subSwath[firstSubSwathIndex]);
final int xMax = computeXMax(subSwath[firstSubSwathIndex]);
final int firstY = Math.max(ty0, yMin);
final int lastY = Math.min(tyMax, yMax + 1);
final int firstX = Math.max(tx0, xMin);
final int lastX = Math.min(txMax, xMax + 1);
if (firstY >= lastY || firstX >= lastX) {
return;
}
final String swathIndexStr = String.valueOf(getSubSwathIndex(subSwath[firstSubSwathIndex].subSwathName));
final Band srcBand = getSourceBandFromTargetBandName(tgtBandName, acquisitionMode, swathIndexStr);
final Tile srcRaster = getSourceTile(srcBand, sourceRectangle[0]);
final TileIndex srcTileIndex = new TileIndex(srcRaster);
final TileIndex tgtIndex = new TileIndex(tgtTile);
final short[] srcArray = (short[]) srcRaster.getDataBuffer().getElems();
final short[] tgtArray = (short[]) tgtTile.getDataBuffer().getElems();
for (int y = firstY; y < lastY; y++) {
final int sy0 = getLineIndexInSourceProduct(y, subSwath[firstSubSwathIndex]);
final int tgtOffset = tgtIndex.calculateStride(y);
final Sentinel1Utils.SubSwathInfo firstSubSwath = subSwath[firstSubSwathIndex];
final int offset = srcTileIndex.calculateStride(sy0);
final int sx0 = (int) Math.round(((targetSlantRangeTimeToFirstPixel + firstX * targetDeltaSlantRangeTime)
- firstSubSwath.slrTimeToFirstPixel) / targetDeltaSlantRangeTime);
System.arraycopy(srcArray, sx0 - offset, tgtArray, firstX - tgtOffset, lastX - firstX);
}
}
private void computeTileInOneSwathFloat(final int tx0, final int ty0, final int txMax, final int tyMax,
final int firstSubSwathIndex, final Rectangle[] sourceRectangle,
final String tgtBandName, final Tile tgtTile) {
final int yMin = computeYMin(subSwath[firstSubSwathIndex]);
final int yMax = computeYMax(subSwath[firstSubSwathIndex]);
final int xMin = computeXMin(subSwath[firstSubSwathIndex]);
final int xMax = computeXMax(subSwath[firstSubSwathIndex]);
final int firstY = Math.max(ty0, yMin);
final int lastY = Math.min(tyMax, yMax + 1);
final int firstX = Math.max(tx0, xMin);
final int lastX = Math.min(txMax, xMax + 1);
if (firstY >= lastY || firstX >= lastX) {
return;
}
final String swathIndexStr = String.valueOf(getSubSwathIndex(subSwath[firstSubSwathIndex].subSwathName));
final Band srcBand = getSourceBandFromTargetBandName(tgtBandName, acquisitionMode, swathIndexStr);
final Tile srcRaster = getSourceTile(srcBand, sourceRectangle[0]);
final TileIndex srcTileIndex = new TileIndex(srcRaster);
final TileIndex tgtIndex = new TileIndex(tgtTile);
final float[] srcArray = (float[]) srcRaster.getDataBuffer().getElems();
final float[] tgtArray = (float[]) tgtTile.getDataBuffer().getElems();
for (int y = firstY; y < lastY; y++) {
final int sy0 = getLineIndexInSourceProduct(y, subSwath[firstSubSwathIndex]);
final int tgtOffset = tgtIndex.calculateStride(y);
final Sentinel1Utils.SubSwathInfo firstSubSwath = subSwath[firstSubSwathIndex];
int offset = srcTileIndex.calculateStride(sy0);
final int sx0 = (int) Math.round(((targetSlantRangeTimeToFirstPixel + firstX * targetDeltaSlantRangeTime)
- firstSubSwath.slrTimeToFirstPixel) / targetDeltaSlantRangeTime);
System.arraycopy(srcArray, sx0 - offset, tgtArray, firstX - tgtOffset, lastX - firstX);
}
}
private void computeMultipleSubSwathsShort(final int tx0, final int ty0, final int txMax, final int tyMax,
final int firstSubSwathIndex, final int lastSubSwathIndex,
final Rectangle[] sourceRectangle, final String tgtBandName,
final Tile tgtTile) {
final int numOfSourceTiles = lastSubSwathIndex - firstSubSwathIndex + 1;
final TileIndex tgtIndex = new TileIndex(tgtTile);
final Tile[] srcTiles = new Tile[numOfSourceTiles];
final short[][] srcArray = new short[numOfSourceTiles][];
final short[] tgtArray = (short[]) tgtTile.getDataBuffer().getElems();
int k = 0;
for (int i = firstSubSwathIndex; i <= lastSubSwathIndex; i++) {
final String swathIndexStr = String.valueOf(getSubSwathIndex(subSwath[i].subSwathName));
final Band srcBand = getSourceBandFromTargetBandName(tgtBandName, acquisitionMode, swathIndexStr);
final Tile srcRaster = getSourceTile(srcBand, sourceRectangle[k]);
srcTiles[k] = srcRaster;
srcArray[k] = (short[]) srcRaster.getDataBuffer().getElems();
k++;
}
for (int y = ty0; y < tyMax; y++) {
final int tgtOffset = tgtIndex.calculateStride(y);
for (int x = tx0; x < txMax; x++) {
int subSwathIndex = getSubSwathIndex(x, y, firstSubSwathIndex, lastSubSwathIndex);
if (subSwathIndex == -1) {
continue;
}
final int sy = getLineIndexInSourceProduct(y, subSwath[subSwathIndex]);
final int sx = getSampleIndexInSourceProduct(x, subSwath[subSwathIndex]);
short val = 0;
k = subSwathIndex - firstSubSwathIndex;
int idx = srcTiles[k].getDataBufferIndex(sx, sy);
if (idx >= 0) {
val = srcArray[k][idx];
}
tgtArray[x - tgtOffset] = val;
}
}
}
private void computeMultipleSubSwathsFloat(final int tx0, final int ty0, final int txMax, final int tyMax,
final int firstSubSwathIndex, final int lastSubSwathIndex,
final Rectangle[] sourceRectangle, final String tgtBandName,
final Tile tgtTile) {
final int numOfSourceTiles = lastSubSwathIndex - firstSubSwathIndex + 1;
final TileIndex tgtIndex = new TileIndex(tgtTile);
final Tile[] srcTiles = new Tile[numOfSourceTiles];
final float[][] srcArray = new float[numOfSourceTiles][];
final float[] tgtArray = (float[]) tgtTile.getDataBuffer().getElems();
int k = 0;
for (int i = firstSubSwathIndex; i <= lastSubSwathIndex; i++) {
final String swathIndexStr = String.valueOf(getSubSwathIndex(subSwath[i].subSwathName));
final Band srcBand = getSourceBandFromTargetBandName(tgtBandName, acquisitionMode, swathIndexStr);
final Tile srcRaster = getSourceTile(srcBand, sourceRectangle[k]);
srcTiles[k] = srcRaster;
srcArray[k] = (float[]) srcRaster.getDataBuffer().getElems();
k++;
}
for (int y = ty0; y < tyMax; y++) {
final int tgtOffset = tgtIndex.calculateStride(y);
for (int x = tx0; x < txMax; x++) {
int subSwathIndex = getSubSwathIndex(x, y, firstSubSwathIndex, lastSubSwathIndex);
if (subSwathIndex == -1) {
continue;
}
final int sy = getLineIndexInSourceProduct(y, subSwath[subSwathIndex]);
final int sx = getSampleIndexInSourceProduct(x, subSwath[subSwathIndex]);
float val = 0;
k = subSwathIndex - firstSubSwathIndex;
int idx = srcTiles[k].getDataBufferIndex(sx, sy);
if (idx >= 0) {
val = srcArray[k][idx];
}
tgtArray[x - tgtOffset] = val;
}
}
}
/**
* Get source tile rectangle.
*
* @param tx0 X coordinate for the upper left corner pixel in the target tile.
* @param ty0 Y coordinate for the upper left corner pixel in the target tile.
* @param tw The target tile width.
* @param th The target tile height.
* @param subSwathIndex The subswath index.
* @return The source tile rectangle.
*/
private Rectangle getSourceRectangle(
final int tx0, final int ty0, final int tw, final int th, final int subSwathIndex) {
final Sentinel1Utils.SubSwathInfo sw = subSwath[subSwathIndex];
final int x0 = getSampleIndexInSourceProduct(tx0, sw);
final int xMax = getSampleIndexInSourceProduct(tx0 + tw - 1, sw);
final int y0 = getLineIndexInSourceProduct(ty0, sw);
final int yMax = getLineIndexInSourceProduct(ty0 + th - 1, sw);
final int w = xMax - x0 + 1;
final int h = yMax - y0 + 1;
return new Rectangle(x0, y0, w, h);
}
private int getSampleIndexInSourceProduct(final int tx, final Sentinel1Utils.SubSwathInfo subSwath) {
final int sx = (int) ((((targetSlantRangeTimeToFirstPixel + tx * targetDeltaSlantRangeTime)
- subSwath.slrTimeToFirstPixel) / targetDeltaSlantRangeTime) + 0.5);
return sx < 0 ? 0 : sx > subSwath.numOfSamples - 1 ? subSwath.numOfSamples - 1 : sx;
}
private int getLineIndexInSourceProduct(final int ty, final Sentinel1Utils.SubSwathInfo subSwath) {
final double targetLineTime = targetFirstLineTime + ty * targetLineTimeInterval;
final int sy = (int) ((targetLineTime - subSwath.firstLineTime) / subSwath.azimuthTimeInterval + 0.5);
return sy < 0 ? 0 : sy > subSwath.numOfLines - 1 ? subSwath.numOfLines - 1 : sy;
}
private int computeYMin(final Sentinel1Utils.SubSwathInfo subSwath) {
return (int) Math.round((subSwath.firstLineTime - targetFirstLineTime) / targetLineTimeInterval);
}
private int computeYMax(final Sentinel1Utils.SubSwathInfo subSwath) {
return (int) Math.round((subSwath.lastLineTime - targetFirstLineTime) / targetLineTimeInterval);
}
private int computeXMin(final Sentinel1Utils.SubSwathInfo subSwath) {
return (int) Math.round((subSwath.slrTimeToFirstValidPixel - targetSlantRangeTimeToFirstPixel) / targetDeltaSlantRangeTime);
}
private int computeXMax(final Sentinel1Utils.SubSwathInfo subSwath) {
return (int) Math.round((subSwath.slrTimeToLastValidPixel - targetSlantRangeTimeToFirstPixel) / targetDeltaSlantRangeTime);
}
private int getSubSwathIndex(
final int tx, final int ty, final int firstSubSwathIndex, final int lastSubSwathIndex) {
final double targetSampleSlrTime = targetSlantRangeTimeToFirstPixel + tx * targetDeltaSlantRangeTime;
final double targetLineTime = targetFirstLineTime + ty * targetLineTimeInterval;
int cnt = 0;
int swath0 = -1, swath1 = -1;
Sentinel1Utils.SubSwathInfo info;
for (int i = firstSubSwathIndex; i <= lastSubSwathIndex; i++) {
info = subSwath[i];
if (targetLineTime >= info.firstValidLineTime &&
targetLineTime <= info.lastValidLineTime &&
targetSampleSlrTime >= info.slrTimeToFirstValidPixel &&
targetSampleSlrTime <= info.slrTimeToLastValidPixel) {
if (cnt == 0) {
swath0 = i;
} else {
swath1 = i;
break;
}
++cnt;
}
}
if (swath1 != -1) {
final double middleTime = (subSwath[swath0].slrTimeToLastValidPixel +
subSwath[swath1].slrTimeToFirstValidPixel) / 2.0;
if (targetSampleSlrTime > middleTime) {
return swath1;
}
}
return swath0;
}
private double getSubSwathNoise(final int tx, final double targetLineTime,
final Sentinel1Utils.SubSwathInfo sw, final String pol) {
final Sentinel1Utils.NoiseVector[] vectorList = sw.noise.get(pol);
final int sx = getSampleIndexInSourceProduct(tx, sw);
final int sy = (int) ((targetLineTime - vectorList[0].timeMJD * Constants.secondsInDay) / targetLineTimeInterval);
int l0 = -1, l1 = -1;
int vectorIdx0 = -1, vectorIdxInc = 0;
if (sy < vectorList[0].line) {
l0 = vectorList[0].line;
l1 = l0;
vectorIdx0 = 0;
} else if (sy >= vectorList[vectorList.length - 1].line) {
l0 = vectorList[vectorList.length - 1].line;
l1 = l0;
vectorIdx0 = vectorList.length - 1;
} else {
vectorIdxInc = 1;
int max = vectorList.length - 1;
for (int i = 0; i < max; i++) {
if (sy >= vectorList[i].line && sy < vectorList[i + 1].line) {
l0 = vectorList[i].line;
l1 = vectorList[i + 1].line;
vectorIdx0 = i;
break;
}
}
}
final int[] pixels = vectorList[vectorIdx0].pixels;
int p0 = -1, p1 = -1;
int pixelIdx0 = -1, pixelIdxInc = 0;
if (sx < pixels[0]) {
p0 = pixels[0];
p1 = p0;
pixelIdx0 = 0;
} else if (sx >= pixels[pixels.length - 1]) {
p0 = pixels[pixels.length - 1];
p1 = p0;
pixelIdx0 = pixels.length - 1;
} else {
pixelIdxInc = 1;
int max = pixels.length - 1;
for (int i = 0; i < max; i++) {
if (sx >= pixels[i] && sx < pixels[i + 1]) {
p0 = pixels[i];
p1 = pixels[i + 1];
pixelIdx0 = i;
break;
}
}
}
final float[] noiseLUT0 = vectorList[vectorIdx0].noiseLUT;
final float[] noiseLUT1 = vectorList[vectorIdx0 + vectorIdxInc].noiseLUT;
double dx;
if (p0 == p1) {
dx = 0;
} else {
dx = (sx - p0) / (p1 - p0);
}
double dy;
if (l0 == l1) {
dy = 0;
} else {
dy = (sy - l0) / (l1 - l0);
}
return Maths.interpolationBiLinear(noiseLUT0[pixelIdx0], noiseLUT0[pixelIdx0 + pixelIdxInc],
noiseLUT1[pixelIdx0], noiseLUT1[pixelIdx0 + pixelIdxInc],
dx, dy);
}
/**
* The SPI is used to register this operator in the graph processing framework
* via the SPI configuration file
* {@code META-INF/services/org.esa.snap.core.gpf.OperatorSpi}.
* This class may also serve as a factory for new operator instances.
*
* @see OperatorSpi#createOperator()
* @see OperatorSpi#createOperator(java.util.Map, java.util.Map)
*/
public static class Spi extends OperatorSpi {
public Spi() {
super(TOPSARMergeOp.class);
}
}
}