/*
* 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 org.esa.s1tbx.insar.gpf.support.Sentinel1Utils;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MetadataAttribute;
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.datamodel.TiePointGeoCoding;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.dataop.barithm.BandArithmetic;
import org.esa.snap.core.dataop.barithm.RasterDataSymbol;
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.jexp.ParseException;
import org.esa.snap.core.jexp.Parser;
import org.esa.snap.core.jexp.Term;
import org.esa.snap.core.jexp.WritableNamespace;
import org.esa.snap.core.jexp.impl.ParserImpl;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.datamodel.OrbitStateVector;
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.ReaderUtils;
import org.esa.snap.engine_utilities.gpf.TileIndex;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.esa.s1tbx.io.sentinel1.Sentinel1Level1Directory.getListInEvenlySpacedGrid;
/**
* Merges Sentinel-1 slice products
*/
@OperatorMetadata(alias = "SliceAssembly",
category = "Radar/Sentinel-1 TOPS",
authors = "Jun Lu, Luis Veci",
version = "1.0",
copyright = "Copyright (C) 2014 by Array Systems Computing Inc.",
description = "Merges Sentinel-1 slice products")
public final class SliceAssemblyOp extends Operator {
@SourceProducts
private Product[] sourceProducts;
@TargetProduct
private Product targetProduct;
// Only bands whose polarization is selected will be in the output product.
@Parameter(description = "The list of polarisations", label = "Polarisations")
private String[] selectedPolarisations;
private MetadataElement absRoot = null;
// The slice products will be in order in the array: 1st (top) slice is the 1st element in the array followed by
// 2nd slice and so on.
private Product[] sliceProducts;
private Map<Band, BandLines[]> bandLineMap = new HashMap<>();
// This is the raster width and height of the target product
private int targetWidth = 0, targetHeight = 0;
// Map a swath such as "IW1" to the assembled image height and width.
// For GRD, use "" for swath.
// height is 1st element. width is 2nd element.
private Map<String, int[]> swathAssembledImageDimMap = new HashMap<>();
// Map a slice product and swath such as "IW1" to the image height and width.
// For GRD, use "" for swath.
// height is 1st element. width is 2nd element.
private Map<Product, Map<String, int[]>> sliceSwathImageDimMap = new HashMap<>();
private Map<String, TiePointGeoCoding> swathGeocodingMap = new HashMap<>();
private boolean isMultiSwath = false;
private static final String PRODUCT_SUFFIX = "_Asm";
/**
* Default constructor. The graph processing framework
* requires that an operator has a default constructor.
*/
public SliceAssemblyOp() {
}
/**
* 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 {
for (Product srcProduct : sourceProducts) {
final InputProductValidator validator = new InputProductValidator(srcProduct);
validator.checkIfSARProduct();
validator.checkIfSentinel1Product();
validator.checkProductType(new String[]{"SLC", "GRD"});
validator.checkAcquisitionMode(new String[]{"SM", "IW", "EW"});
}
sliceProducts = determineSliceProducts();
absRoot = AbstractMetadata.getAbstractedMetadata(sliceProducts[0]);
if (selectedPolarisations == null || selectedPolarisations.length == 0) {
final Sentinel1Utils su = new Sentinel1Utils(sliceProducts[0]);
selectedPolarisations = su.getPolarizations();
}
checkSlantRangeTimes();
createTargetProduct();
updateTargetProductMetadata();
determineBandStartEndTimes();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
private Product[] determineSliceProducts() throws Exception {
if (sourceProducts.length < 2) {
throw new Exception("Slice assembly requires at least two consecutive slice products");
}
final TreeMap<Integer, Product> productSet = new TreeMap<>();
for (Product srcProduct : sourceProducts) {
final MetadataElement origMetaRoot = AbstractMetadata.getOriginalProductMetadata(srcProduct);
final MetadataElement generalProductInformation = getGeneralProductInformation(origMetaRoot);
if (!isSliceProduct(generalProductInformation)) {
throw new Exception(srcProduct.getName() + " is not a slice product");
}
//final int totalSlices = generalProductInformation.getAttributeInt("totalSlices");
final int sliceNumber = generalProductInformation.getAttributeInt("sliceNumber");
//System.out.println("SliceAssemblyOp.determineSliceProducts: totalSlices = " + totalSlices + "; slice product name = " + srcProduct.getName() + "; prod type = " + srcProduct.getProductType() + "; sliceNumber = " + sliceNumber);
productSet.put(sliceNumber, srcProduct);
}
//check if consecutive
Integer prev = productSet.firstKey();
// Note that "The set's iterator returns the keys in ascending order".
for (Integer i : productSet.keySet()) {
if (!i.equals(prev)) {
if (!prev.equals(i - 1)) {
throw new Exception("Products are not consecutive slices");
}
prev = i;
}
}
// Note that "If productSet makes any guarantees as to what order its elements
// are returned by its iterator, toArray() must return the elements in
// the same order".
return productSet.values().toArray(new Product[productSet.size()]);
}
private static MetadataElement getGeneralProductInformation(final MetadataElement origMetaRoot) {
final MetadataElement XFDU = origMetaRoot.getElement("XFDU");
final MetadataElement metadataSection = XFDU.getElement("metadataSection");
final MetadataElement metadataObject = findElementByID(metadataSection, "ID", "generalProductInformation");
final MetadataElement metadataWrap = metadataObject.getElement("metadataWrap");
final MetadataElement xmlData = metadataWrap.getElement("xmlData");
MetadataElement generalProductInformation = xmlData.getElement("generalProductInformation");
if (generalProductInformation == null)
generalProductInformation = xmlData.getElement("standAloneProductInformation");
return generalProductInformation;
}
private static boolean isSliceProduct(final MetadataElement generalProductInformation) {
final String sliceProductFlag = generalProductInformation.getAttributeString("sliceProductFlag");
return sliceProductFlag.equals("true");
}
private static MetadataElement findElementByID(final MetadataElement metadataSection, final String tag, final String id) {
final MetadataElement[] metadataObjectList = metadataSection.getElements();
for (MetadataElement metadataObject : metadataObjectList) {
final String attrib = metadataObject.getAttributeString(tag, null);
if (attrib.equals(id)) {
return metadataObject;
}
}
return null;
}
private static String extractSwathIdentifier(final String mdsName) {
// E.g., it can be "iw" or "iw1"
String swathID = mdsName.substring(4, 7);
if (swathID.endsWith("-")) {
swathID = swathID.substring(0, swathID.length() - 1);
}
return swathID;
}
private static double getSlantRangeTime(final Product product, final String sss) {
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement annotation = origProdRoot.getElement("annotation");
final MetadataElement[] annotationElems = annotation.getElements();
double slantRangeTime = 0;
for (MetadataElement e : annotationElems) {
if (extractSwathIdentifier(e.getName()).equals(sss)) {
MetadataElement prod = e.getElement("product");
MetadataElement imgAnno = prod.getElement("imageAnnotation");
MetadataElement imgInfo = imgAnno.getElement("imageInformation");
slantRangeTime = imgInfo.getAttributeDouble("slantRangeTime");
break;
}
}
//System.out.println("return slant range time for " + sss + " = " + slantRangeTime);
return slantRangeTime;
}
private void checkSlantRangeTimes() {
final Product firstSliceProduct = sliceProducts[0];
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(firstSliceProduct);
final MetadataElement annotation = origProdRoot.getElement("annotation");
final MetadataElement[] annotationElems = annotation.getElements();
// There is some redundancy here.
// E.g. we will check for both
// s1a-iw-grd-vh-20140920t050131-20140920t050156-002471-002aec-002.xml and
// s1a-iw-grd-vv-20140920t050131-20140920t050156-002471-002aec-001.xml
for (MetadataElement e : annotationElems) {
final String swathID = extractSwathIdentifier(e.getName());
final double slantRangeTime = getSlantRangeTime(firstSliceProduct, swathID);
//System.out.println("Check slant range time for " + e.getName() + " " + sss + " = " + slantRangeTime);
for (int i = 1; i < sliceProducts.length; i++) {
double srt = getSlantRangeTime(sliceProducts[i], swathID);
if (Math.abs(slantRangeTime - srt) > 1e-8) {
SystemUtils.LOG.warning("Slant range time don't agree: " + i + ' ' + swathID);
}
}
}
}
private static ArrayList<String> getSwaths(final Product product) {
ArrayList<String> swaths = new ArrayList<>();
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement annotation = origProdRoot.getElement("annotation");
final MetadataElement[] annotationElems = annotation.getElements();
for (MetadataElement e : annotationElems) {
String sss = extractSwathIdentifier(e.getName()).toUpperCase();
if (!swaths.contains(sss)) {
swaths.add(sss);
}
}
return swaths;
}
private static void getSwathDim(final Product product, final String swath, final int[] dim) {
// dim[0] is height; dim[1] is width
final Band[] bands = product.getBands();
for (Band b : bands) {
if (b.getName().contains(swath)) {
dim[0] = b.getRasterHeight();
dim[1] = b.getRasterWidth();
break;
}
}
}
private void computeTargetWidthAndHeight() {
// In GRD products, all the bands will have the same width and height, so the width and height of the
// product are equal to the width and height of the bands. E.g a GRD product will have these bands:
// - Amplitude_VH
// - Amplitude_VV
// They will have the same width and height.
//
// In SLC products, each band belongs to a swath and bands belonging to the same swath will have the
// same width and height. E.g. a SLC product will have these bands:
//
// IW1: w x h = 22553 x 14850
// - i_IW1_VH
// - q_IW1_VH
// - i_IW1_VV
// - q_IW1_VV
//
// IW2: w x h = 26326 x 15363
// - i_IW2_VH
// - q_IW2_VH
// - i_IW2_VV
// - q_IW2_VV
//
// IW3: w x h = 25321 x 15534
// - i_IW3_VH
// - q_IW3_VH
// - i_IW3_VV
// - q_IW3_VV
//
// We assume for such a SLC slice product, the scene raster width and height will be the maximum
// among the swaths, so product scene raster width = 26326 and product scene raster height = 15534.
//
// Say 1st slice:
// IW1: w x h = 22553 x 14850
// IW2: w x h = 26326 x 15363
// IW3: w x h = 25321 x 15534
// product width = 26326 and height = 15534
//
// 2nd slice:
// IW1: w x h = 22571 x 14850
// IW2: w x h = 26351 x 15363
// IW3: w x h = 25350 x 15543
// product width = 26351 and height = 15543
//
// Assemble the 2 slices:
// IW1: w x h = 22571 x 29700
// IW2: w x h = 26351 x 30726
// IW3: w x h = 25350 x 31077
// product width = 26351 and height = 31077
final String productType = sliceProducts[0].getProductType();
if (productType.equals("GRD")) {
for (Product srcProduct : sliceProducts) {
if (targetWidth < srcProduct.getSceneRasterWidth())
targetWidth = srcProduct.getSceneRasterWidth();
targetHeight += srcProduct.getSceneRasterHeight();
final Map<String, int[]> tmp = new HashMap<>();
tmp.put("", new int[]{srcProduct.getSceneRasterHeight(), srcProduct.getSceneRasterWidth()});
sliceSwathImageDimMap.put(srcProduct, tmp);
}
swathAssembledImageDimMap.put("", new int[]{targetHeight, targetWidth});
} else {
final ArrayList<String> swaths = getSwaths(sliceProducts[0]);
final Map<String, Integer> swathHeight = new HashMap<>();
final Map<String, Integer> swathWidth = new HashMap<>();
for (String swath : swaths) {
swathHeight.put(swath, 0);
swathWidth.put(swath, 0);
}
for (Product srcProduct : sliceProducts) {
for (String swath : swaths) {
final int[] dim = new int[2];
getSwathDim(srcProduct, swath, dim);
if (swathWidth.get(swath) < dim[1]) {
swathWidth.replace(swath, dim[1]);
}
swathHeight.replace(swath, swathHeight.get(swath) + dim[0]);
if (sliceSwathImageDimMap.containsKey(srcProduct)) {
final Map<String, int[]> tmp = sliceSwathImageDimMap.get(srcProduct);
tmp.put(swath, dim);
} else {
final Map<String, int[]> tmp = new HashMap<>();
tmp.put(swath, dim);
sliceSwathImageDimMap.put(srcProduct, tmp);
}
}
}
for (String swath : swaths) {
swathAssembledImageDimMap.put(swath, new int[]{swathHeight.get(swath), swathWidth.get(swath)});
if (targetWidth < swathWidth.get(swath))
targetWidth = swathWidth.get(swath);
if (targetHeight < swathHeight.get(swath))
targetHeight = swathHeight.get(swath);
}
}
}
private Dimension computeTargetBandWidthAndHeight(final String bandName) throws OperatorException {
// See comments in computeTargetWidthAndHeight().
// For band width, we take the max for that band among all slice products.
final Dimension dim = new Dimension(0, 0);
for (Product srcProduct : sliceProducts) {
final Band srcBand = srcProduct.getBand(bandName);
if (srcBand == null) {
throw new OperatorException(bandName + " not found in product " + srcProduct.getName());
}
dim.setSize(Math.max(dim.width, srcBand.getRasterWidth()), dim.height + srcBand.getRasterHeight());
}
return dim;
}
private void createTargetProduct() {
computeTargetWidthAndHeight();
final Product firstSliceProduct = sliceProducts[0];
final Product lastSliceProduct = sliceProducts[sliceProducts.length - 1];
final String lastSliceStopDateAndTime = lastSliceProduct.getName().substring(33, 48);
final String newProductName = firstSliceProduct.getName().substring(0, 33) + lastSliceStopDateAndTime +
firstSliceProduct.getName().substring(48) + PRODUCT_SUFFIX;
targetProduct = new Product(newProductName, firstSliceProduct.getProductType(), targetWidth, targetHeight);
// We are creating each target band based on the source band in the first slice product only.
final Band[] sourceBands = firstSliceProduct.getBands();
for (Band srcBand : sourceBands) {
boolean selectedPol = false;
for (String pol : selectedPolarisations) {
if (srcBand.getName().contains(pol))
selectedPol = true;
}
if (!selectedPol)
continue;
if (srcBand instanceof VirtualBand) {
final VirtualBand sourceBand = (VirtualBand) srcBand;
int destWidth = 0;
int destHeight = 0;
final Term term = createTerm(sourceBand.getExpression(), sliceProducts);
final RasterDataSymbol[] refRasterDataSymbols = BandArithmetic.getRefRasterDataSymbols(term);
// find new width and height used by corresponding target bands
for (RasterDataSymbol symbol : refRasterDataSymbols) {
String name = symbol.getName();
final Band trgBand = targetProduct.getBand(name);
if (trgBand != null) {
destWidth = trgBand.getRasterWidth();
destHeight = trgBand.getRasterHeight();
break;
}
}
final VirtualBand targetBand = new VirtualBand(sourceBand.getName(), sourceBand.getDataType(),
destWidth, destHeight, sourceBand.getExpression());
ProductUtils.copyRasterDataNodeProperties(sourceBand, targetBand);
targetProduct.addBand(targetBand);
} else {
final Dimension dim = computeTargetBandWidthAndHeight(srcBand.getName());
final Band newBand = new Band(srcBand.getName(), srcBand.getDataType(), dim.width, dim.height);
ProductUtils.copyRasterDataNodeProperties(srcBand, newBand);
targetProduct.addBand(newBand);
}
}
ProductUtils.copyMetadata(firstSliceProduct, targetProduct);
ProductUtils.copyFlagCodings(firstSliceProduct, targetProduct);
ProductUtils.copyMasks(firstSliceProduct, targetProduct);
ProductUtils.copyVectorData(firstSliceProduct, targetProduct);
ProductUtils.copyIndexCodings(firstSliceProduct, targetProduct);
targetProduct.setStartTime(firstSliceProduct.getStartTime());
targetProduct.setEndTime(lastSliceProduct.getEndTime());
targetProduct.setDescription(firstSliceProduct.getDescription());
final String productType = absRoot.getAttributeString(AbstractMetadata.PRODUCT_TYPE);
if (productType.equals("GRD")) {
createTiePointGrids("");
addGeocoding();
} else {
final ArrayList<String> swaths = getSwaths(firstSliceProduct);
isMultiSwath = swaths.size() > 1;
for (String swath : swaths) {
createTiePointGrids(swath);
}
if (isMultiSwath) {
createLatLonTiePointGridsForSLC();
}
}
//targetProduct.setPreferredTileSize(targetWidth, 1);
}
private static Term createTerm(final String expression, final Product[] availableProducts) {
WritableNamespace namespace = BandArithmetic.createDefaultNamespace(availableProducts, 0);
final Term term;
try {
Parser parser = new ParserImpl(namespace, false);
term = parser.parse(expression);
} catch (ParseException e) {
throw new OperatorException("Could not parse expression: " + expression, e);
}
return term;
}
private static MetadataElement[] getGeoGridForSwath(final Product product, final String sss) {
// For GRD products, use "" for sss.
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement annotationElem = origProdRoot.getElement("annotation");
final MetadataElement[] images = annotationElem.getElements();
MetadataElement imgElem = null;
if (sss.isEmpty()) {
// This is GRD product, same grid for all bands, so just take the 1st one
imgElem = annotationElem.getElementAt(0);
} else {
for (MetadataElement e : images) {
if (extractSwathIdentifier(e.getName()).equals(sss.toLowerCase())) {
imgElem = e;
break;
}
}
}
if (imgElem == null) {
throw new OperatorException("Cannot locate geolocation grid in metadata for " + sss);
}
final MetadataElement productElem = imgElem.getElement("product");
final MetadataElement geolocationGrid = productElem.getElement("geolocationGrid");
final MetadataElement geolocationGridPointList = geolocationGrid.getElement("geolocationGridPointList");
return geolocationGridPointList.getElements();
}
/*private void createTiePointGrids(final String swath) {
//System.out.println("SliceAssemblyOp.createTiePointGrids: " + swath);
// For GRD, use "" for swath. For SLC, it should be something like "IW1".
// One geolocationGridPointList for one swath.
// geolocationGridPointList has a count and a list of geolocationGridPoint(s).
// These geolocationGridPoint(s) should form an MxN grid, M = number of rows and N = number of columns.
// Each geolocationGridPoint contains line (i.e. row) and pixel (i.e. column).
// The horizontal or vertical spacing may not be even.
// But from line to line, the pixels are in the same location.
// E.g A 4x6 grid for an image of width 24 and height 17:
// 1st row: line = 0; pixels = 0 5 10 15 20 23
// 2nd row: line = 6; pixels = 0 5 10 15 20 23
// 3rd row: line = 12; pixels = 0 5 10 15 20 23
// 4th row: line = 16; pixels = 0 5 10 15 20 23
// According to Table 6-88 of Product Specs v2.9, each geolocationGridPoint is a point within the image.
// So we cannot have a point with line 17 or pixels 24.
//
// We assume that from slice to slice, M may differ but N must be the same. However, the pixel spacing
// from slice to slice may not be the same.
// So the next slice to the example above can be a 5x6 grid for an image of width 30 and height 20:
// 1st row: line = 0; pixels = 0 6 12 18 24 29
// 2nd row: line = 5; pixels = 0 6 12 18 24 29
// 3rd row: line = 10; pixels = 0 6 12 18 24 29
// 4th row: line = 15; pixels = 0 6 12 18 24 29
// 5th row: line = 19; pixels = 0 6 12 18 24 29
//
// We concatenate the two geolocationGridPointList(s) together to form a 9x6 grid for an image of width 30 and
// height 37:
// 1st row: line = 0; pixels = 0 5 10 15 20 23
// 2nd row: line = 6; pixels = 0 5 10 15 20 23
// 3rd row: line = 12; pixels = 0 5 10 15 20 23
// 4th row: line = 16; pixels = 0 5 10 15 20 23
// 5th row: line = 17; pixels = 0 6 12 18 24 29
// 6th row: line = 22; pixels = 0 6 12 18 24 29
// 7th row: line = 27; pixels = 0 6 12 18 24 29
// 8th row: line = 32; pixels = 0 6 12 18 24 29
// 9th row: line = 36; pixels = 0 6 12 18 24 29
int geoGridLen = 0;
final ArrayList<MetadataElement[]> geoGrids = new ArrayList<>();
int i = 0;
for (Product product : sliceProducts) {
MetadataElement[] geoGrid = getGeoGridForSwath(product, swath);
geoGridLen += geoGrid.length;
geoGrids.add(i, geoGrid);
i++;
}
//System.out.println("geoGridLen = " + geoGridLen);
final double[] latList = new double[geoGridLen];
final double[] lngList = new double[geoGridLen];
final double[] incidenceAngleList = new double[geoGridLen];
final double[] elevAngleList = new double[geoGridLen];
final double[] rangeTimeList = new double[geoGridLen];
final int[] x = new int[geoGridLen];
final int[] y = new int[geoGridLen];
final int[] gridWidths = new int[sliceProducts.length];
final int[] gridHeights = new int[sliceProducts.length];
for (int j = 0; j < sliceProducts.length; j++) {
gridWidths[j] = 0;
gridHeights[j] = 0;
}
int gridHeight = 0;
i = 0;
int ptsInPrvSlices = 0;
int heightOffset = 0;
for (int j = 0; j < sliceProducts.length; j++) {
if (j > 0) {
heightOffset += sliceSwathImageDimMap.get(sliceProducts[j - 1]).get(swath)[0];
}
final MetadataElement[] geoGrid = geoGrids.get(j);
for (MetadataElement ggPoint : geoGrid) {
latList[i] = ggPoint.getAttributeDouble("latitude", 0);
lngList[i] = ggPoint.getAttributeDouble("longitude", 0);
incidenceAngleList[i] = ggPoint.getAttributeDouble("incidenceAngle", 0);
elevAngleList[i] = ggPoint.getAttributeDouble("elevationAngle", 0);
rangeTimeList[i] = ggPoint.getAttributeDouble("slantRangeTime", 0) * Constants.oneBillion; // s to ns
x[i] = (int) ggPoint.getAttributeDouble("pixel", 0);
if (x[i] == 0) {
// This means we are at the start of a new line
// gridWidths[j] will be updated to 0 at the 1st line.
// It will be updated to the correct value at the 2nd line.
if (gridWidths[j] == 0)
gridWidths[j] = i - ptsInPrvSlices;
++gridHeights[j];
}
y[i] = (int) ggPoint.getAttributeDouble("line", 0) + heightOffset;
++i;
}
ptsInPrvSlices = i;
gridHeight += gridHeights[j];
}
final int gridWidth = gridWidths[0];
// We assume here that all the slice products will have the same width in the geolocation grid.
// That is the same number of points in each line.
for (int w : gridWidths) {
if (w != gridWidth) {
throw new OperatorException("geolocation grids have different widths among slice products");
}
}
//System.out.println("gridWidth = " + gridWidth + " gridHeight = " + gridHeight);
final int newGridWidth = gridWidth;
final int newGridHeight = gridHeight;
if (geoGridLen != (newGridWidth * newGridHeight)) {
throw new OperatorException("wrong number of geolocation grid points");
}
final float[] newLatList = new float[newGridWidth * newGridHeight];
final float[] newLonList = new float[newGridWidth * newGridHeight];
final float[] newIncList = new float[newGridWidth * newGridHeight];
final float[] newElevList = new float[newGridWidth * newGridHeight];
final float[] newslrtList = new float[newGridWidth * newGridHeight];
final int[] dim = swathAssembledImageDimMap.get(swath);
final int sceneRasterWidth = dim[1];
final int sceneRasterHeight = dim[0];
//System.out.println("swath = " + swath + " width = " + sceneRasterWidth + " height = " + sceneRasterHeight);
final double subSamplingX = (double) sceneRasterWidth / (newGridWidth - 1);
final double subSamplingY = (double) sceneRasterHeight / (newGridHeight - 1);
getListInEvenlySpacedGrid(sceneRasterWidth, sceneRasterHeight, gridWidth, gridHeight, x, y, latList,
newGridWidth, newGridHeight, subSamplingX, subSamplingY, newLatList);
getListInEvenlySpacedGrid(sceneRasterWidth, sceneRasterHeight, gridWidth, gridHeight, x, y, lngList,
newGridWidth, newGridHeight, subSamplingX, subSamplingY, newLonList);
getListInEvenlySpacedGrid(sceneRasterWidth, sceneRasterHeight, gridWidth, gridHeight, x, y, incidenceAngleList,
newGridWidth, newGridHeight, subSamplingX, subSamplingY, newIncList);
getListInEvenlySpacedGrid(sceneRasterWidth, sceneRasterHeight, gridWidth, gridHeight, x, y, elevAngleList,
newGridWidth, newGridHeight, subSamplingX, subSamplingY, newElevList);
getListInEvenlySpacedGrid(sceneRasterWidth, sceneRasterHeight, gridWidth, gridHeight, x, y, rangeTimeList,
newGridWidth, newGridHeight, subSamplingX, subSamplingY, newslrtList);
final String prefix = swath.isEmpty() ? swath : swath + '_';
final TiePointGrid latGrid = new TiePointGrid(prefix + OperatorUtils.TPG_LATITUDE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, newLatList);
latGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(latGrid);
final TiePointGrid lonGrid = new TiePointGrid(prefix + OperatorUtils.TPG_LONGITUDE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, newLonList, TiePointGrid.DISCONT_AT_180);
lonGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(lonGrid);
final TiePointGrid incidentAngleGrid = new TiePointGrid(prefix + OperatorUtils.TPG_INCIDENT_ANGLE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, newIncList);
incidentAngleGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(incidentAngleGrid);
final TiePointGrid elevAngleGrid = new TiePointGrid(prefix + OperatorUtils.TPG_ELEVATION_ANGLE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, newElevList);
elevAngleGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(elevAngleGrid);
final TiePointGrid slantRangeGrid = new TiePointGrid(prefix + OperatorUtils.TPG_SLANT_RANGE_TIME,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, newslrtList);
slantRangeGrid.setUnit(Unit.NANOSECONDS);
targetProduct.addTiePointGrid(slantRangeGrid);
if (!swath.isEmpty()) {
// This is for SLC
final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid);
swathGeocodingMap.put(swath, tpGeoCoding);
}
//System.out.println("SliceAssemblyOp.createTiePointGrids: DONE " + swath);
}*/
private void createTiePointGrids(final String swath) {
// compute new TPG grid width and height for assembled image
final ArrayList<MetadataElement[]> geoGrids = new ArrayList<>();
for (int i = 0; i < sliceProducts.length; ++i) {
MetadataElement[] geoGrid = getGeoGridForSwath(sliceProducts[i], swath);
geoGrids.add(i, geoGrid);
}
final int[] gridWidths = new int[sliceProducts.length];
final int[] gridHeights = new int[sliceProducts.length];
for (int j = 0; j < sliceProducts.length; j++) {
gridWidths[j] = 0;
gridHeights[j] = 0;
}
int gridHeight = 0;
int n = 0;
int ptsInPrvSlices = 0;
for (int i = 0; i < sliceProducts.length; i++) {
final MetadataElement[] geoGrid = geoGrids.get(i);
for (MetadataElement ggPoint : geoGrid) {
final int pixel = (int) ggPoint.getAttributeDouble("pixel", 0);
if (pixel == 0) {
if (gridWidths[i] == 0) {
gridWidths[i] = n - ptsInPrvSlices;
}
++gridHeights[i];
}
++n;
}
ptsInPrvSlices = n;
gridHeight += gridHeights[i];
}
final int gridWidth = gridWidths[0];
for (int w : gridWidths) {
if (w != gridWidth) {
throw new OperatorException("geolocation grids have different widths among slice products");
}
}
// generate new TPG for assembled image using TPGs of slices
final int newGridWidth = gridWidth;
final int newGridHeight = gridHeight;
final float[] latList = new float[newGridWidth * newGridHeight];
final float[] lonList = new float[newGridWidth * newGridHeight];
final float[] incList = new float[newGridWidth * newGridHeight];
final float[] elevList = new float[newGridWidth * newGridHeight];
final float[] slrtList = new float[newGridWidth * newGridHeight];
final int[] dim = swathAssembledImageDimMap.get(swath);
final int sceneRasterWidth = dim[1];
final int sceneRasterHeight = dim[0];
final double subSamplingX = (double) sceneRasterWidth / (newGridWidth - 1);
final double subSamplingY = (double) sceneRasterHeight / (newGridHeight - 1);
final String prefix = isMultiSwath ? swath + '_' : "";
final TiePointGrid[] latTPG = new TiePointGrid[sliceProducts.length];
final TiePointGrid[] lonTPG = new TiePointGrid[sliceProducts.length];
final TiePointGrid[] incTPG = new TiePointGrid[sliceProducts.length];
final TiePointGrid[] elevTPG = new TiePointGrid[sliceProducts.length];
final TiePointGrid[] slrtTPG = new TiePointGrid[sliceProducts.length];
for (int i = 0; i < sliceProducts.length; ++i) {
latTPG[i] = sliceProducts[i].getTiePointGrid(prefix + OperatorUtils.TPG_LATITUDE);
lonTPG[i] = sliceProducts[i].getTiePointGrid(prefix + OperatorUtils.TPG_LONGITUDE);
incTPG[i] = sliceProducts[i].getTiePointGrid(prefix + OperatorUtils.TPG_INCIDENT_ANGLE);
elevTPG[i] = sliceProducts[i].getTiePointGrid(prefix + OperatorUtils.TPG_ELEVATION_ANGLE);
slrtTPG[i] = sliceProducts[i].getTiePointGrid(prefix + OperatorUtils.TPG_SLANT_RANGE_TIME);
}
int k = 0;
for (int r = 0; r < newGridHeight; ++r) {
final double y = r*subSamplingY;
double yy = 0.0;
int sliceIdx = 0, heightOffset = 0;
for (int i = 0; i < sliceProducts.length; ++i) {
heightOffset += sliceSwathImageDimMap.get(sliceProducts[i]).get(swath)[0];
if (y <= heightOffset) {
yy = y - heightOffset + sliceSwathImageDimMap.get(sliceProducts[i]).get(swath)[0];
sliceIdx = i;
break;
}
}
for (int c = 0; c < newGridWidth; ++c) {
final double x = c*subSamplingX;
latList[k] = (float)latTPG[sliceIdx].getPixelDouble(x, yy);
lonList[k] = (float)lonTPG[sliceIdx].getPixelDouble(x, yy);
incList[k] = (float)incTPG[sliceIdx].getPixelDouble(x, yy);
elevList[k] = (float)elevTPG[sliceIdx].getPixelDouble(x, yy);
slrtList[k] = (float)slrtTPG[sliceIdx].getPixelDouble(x, yy);
k++;
}
}
final TiePointGrid latGrid = new TiePointGrid(prefix + OperatorUtils.TPG_LATITUDE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, latList);
latGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(latGrid);
final TiePointGrid lonGrid = new TiePointGrid(prefix + OperatorUtils.TPG_LONGITUDE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, lonList, TiePointGrid.DISCONT_AT_180);
lonGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(lonGrid);
final TiePointGrid incidentAngleGrid = new TiePointGrid(prefix + OperatorUtils.TPG_INCIDENT_ANGLE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, incList);
incidentAngleGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(incidentAngleGrid);
final TiePointGrid elevAngleGrid = new TiePointGrid(prefix + OperatorUtils.TPG_ELEVATION_ANGLE,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, elevList);
elevAngleGrid.setUnit(Unit.DEGREES);
targetProduct.addTiePointGrid(elevAngleGrid);
final TiePointGrid slantRangeGrid = new TiePointGrid(prefix + OperatorUtils.TPG_SLANT_RANGE_TIME,
newGridWidth, newGridHeight, 0.5f, 0.5f, subSamplingX, subSamplingY, slrtList);
slantRangeGrid.setUnit(Unit.NANOSECONDS);
targetProduct.addTiePointGrid(slantRangeGrid);
if (isMultiSwath) { // This is for multi-swath SLC
final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid);
swathGeocodingMap.put(swath, tpGeoCoding);
}
}
private void createLatLonTiePointGridsForSLC() {
final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sliceProducts[0]);
final String acquisitionMode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
int firstSwathNum = 9999, lastSwathNum = 0;
for (String key : swathAssembledImageDimMap.keySet()) {
int subnum = Integer.parseInt(key.substring(2));
if (subnum < firstSwathNum) {
firstSwathNum = subnum;
}
if (subnum > lastSwathNum) {
lastSwathNum = subnum;
}
}
final String firstSwath = acquisitionMode + firstSwathNum;
final String lastSwath = acquisitionMode + lastSwathNum;
final GeoCoding firstSwathGeoCoding = swathGeocodingMap.get(firstSwath);
final int firstSWBandHeight = swathAssembledImageDimMap.get(firstSwath)[0];
final GeoCoding lastSwathGeoCoding = swathGeocodingMap.get(lastSwath);
final int lastSWBandWidth = swathAssembledImageDimMap.get(lastSwath)[1];
final int lastSWBandHeight = swathAssembledImageDimMap.get(lastSwath)[0];
final PixelPos ulPix = new PixelPos(0, 0);
final PixelPos llPix = new PixelPos(0, firstSWBandHeight - 1);
final GeoPos ulGeo = new GeoPos();
final GeoPos llGeo = new GeoPos();
firstSwathGeoCoding.getGeoPos(ulPix, ulGeo);
firstSwathGeoCoding.getGeoPos(llPix, llGeo);
final PixelPos urPix = new PixelPos(lastSWBandWidth - 1, 0);
final PixelPos lrPix = new PixelPos(lastSWBandWidth - 1, lastSWBandHeight - 1);
final GeoPos urGeo = new GeoPos();
final GeoPos lrGeo = new GeoPos();
lastSwathGeoCoding.getGeoPos(urPix, urGeo);
lastSwathGeoCoding.getGeoPos(lrPix, lrGeo);
final float[] latCorners = {(float) ulGeo.getLat(), (float) urGeo.getLat(), (float) llGeo.getLat(), (float) lrGeo.getLat()};
final float[] lonCorners = {(float) ulGeo.getLon(), (float) urGeo.getLon(), (float) llGeo.getLon(), (float) lrGeo.getLon()};
ReaderUtils.addGeoCoding(targetProduct, latCorners, lonCorners);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_lat, ulGeo.getLat());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_long, ulGeo.getLon());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_lat, urGeo.getLat());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_long, urGeo.getLon());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_lat, llGeo.getLat());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_long, llGeo.getLon());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_lat, lrGeo.getLat());
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_long, lrGeo.getLon());
}
private void addGeocoding() {
final TiePointGrid latGrid = targetProduct.getTiePointGrid(OperatorUtils.TPG_LATITUDE);
final TiePointGrid lonGrid = targetProduct.getTiePointGrid(OperatorUtils.TPG_LONGITUDE);
final TiePointGeoCoding tpGeoCoding = new TiePointGeoCoding(latGrid, lonGrid);
targetProduct.setSceneGeoCoding(tpGeoCoding);
}
private static String extractImageNumber(final String filename) {
final int dotIdx = filename.indexOf('.');
return filename.substring(dotIdx - 3, dotIdx);
}
private static ProductData getStopTime(final Product product, final String imageNum) {
//System.out.println("getStopTime for " + product.getName() + " imageNum = " + imageNum);
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement calibration = origProdRoot.getElement("calibration");
final MetadataElement[] calibrationElems = calibration.getElements();
ProductData data = null;
for (MetadataElement e : calibrationElems) {
//System.out.println("getStopTime: " + e.getName());
if (extractImageNumber(e.getName()).equals(imageNum)) {
final MetadataElement calib = e.getElement("calibration");
final MetadataElement adsHeader = calib.getElement("adsHeader");
final MetadataAttribute stopTime = adsHeader.getAttribute("stopTime");
//System.out.println("getStopTime: " + stopTime.getData().toString());
data = stopTime.getData();
}
}
return data;
}
private MetadataElement[] getElementsToUpdate(final MetadataElement root, String dataName) {
final MetadataElement data = root.getElement(dataName);
final MetadataElement[] dataElems = data.getElements();
final ArrayList<MetadataElement> elemsToRemove = new ArrayList<>();
// loop through each s1...-nnn.xml where nnn is the image number
for (MetadataElement dataElem : dataElems) {
boolean isSelected = false;
for (String pol : selectedPolarisations) {
if (dataElem.getName().toUpperCase().contains(pol)) {
isSelected = true;
break;
}
}
if (!isSelected) {
elemsToRemove.add(dataElem);
}
}
for (MetadataElement dataElem : elemsToRemove) {
//System.out.println("remove " + dataName + " for " + target.getName());
data.removeElement(dataElem);
}
return data.getElements();
}
private static MetadataElement getCalibrationOrNoiseVectorList(final Product product, final String imageNum, final String dataName) {
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement data = origProdRoot.getElement(dataName);
final MetadataElement[] elems = data.getElements();
MetadataElement vectorList = null;
for (MetadataElement e : elems) {
if (extractImageNumber(e.getName()).equals(imageNum)) {
final MetadataElement dat = e.getElement(dataName);
vectorList = dat.getElement(dataName + "VectorList");
}
}
return vectorList;
}
private static int getCalibrationOrNoisePixelSpacing(final Product product, final String imageNum, final String dataName) {
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement data = origProdRoot.getElement(dataName);
final MetadataElement[] dataElems = data.getElements();
int pixelSpacing = 0;
for (MetadataElement e : dataElems) {
//System.out.println("getCalibrationOrNoisePixelCount: " + e.getName());
if (extractImageNumber(e.getName()).equals(imageNum)) {
final MetadataElement dat = e.getElement(dataName);
final MetadataElement vectorList = dat.getElement(dataName + "VectorList");
final MetadataElement firstVector = vectorList.getElementAt(0);
final MetadataElement pixel = firstVector.getElement("pixel");
final MetadataAttribute count = pixel.getAttribute("count");
//System.out.println("getCalibrationOrNoisePixelCount: " + count.getData().toString());
final int pixelCount = Integer.parseInt(count.getData().getElemString());
final MetadataAttribute pixels = pixel.getAttribute("pixel");
final String pixelsStr = pixels.getData().getElemString();
final String[] pixelsArrayOfStr = pixelsStr.split(" ");
if (pixelCount != pixelsArrayOfStr.length) {
throw new OperatorException("wrong pixel count " + product.getName() + ' ' + imageNum + ' ' + dataName + ' ' + pixelCount + ' ' + pixelsArrayOfStr.length);
}
if (pixelCount < 2) {
throw new OperatorException("wrong pixel count " + product.getName() + ' ' + imageNum + ' ' + dataName + ' ' + pixelCount);
}
final int pixel0 = Integer.parseInt(pixelsArrayOfStr[0]);
final int pixel1 = Integer.parseInt(pixelsArrayOfStr[1]);
//System.out.println("pixel0 = " + pixel0 + " pixel1 = " + pixel1);
pixelSpacing = pixel1 - pixel0;
break;
}
}
return pixelSpacing;
}
private static void concatenateVectors(final MetadataElement targetVectorList,
final MetadataElement sliceVectorList,
final int startVectorIdx,
final int lineOffset) {
int idx = Integer.parseInt(targetVectorList.getAttribute("count").getData().getElemString());
final int numSliceLines = Integer.parseInt(sliceVectorList.getAttribute("count").getData().getElemString());
final int topLastLine = Integer.parseInt(targetVectorList.getElementAt(idx - 1).getAttributeString("line"));
final int bottom1stLine = lineOffset + Integer.parseInt(sliceVectorList.getElementAt(startVectorIdx).getAttributeString("line"));
if (topLastLine >= bottom1stLine) {
throw new OperatorException("last vector line of stop slice = " + topLastLine + " >= first vector line of bottom slice = " + bottom1stLine);
}
for (int i = startVectorIdx; i < numSliceLines; i++) {
MetadataElement v = sliceVectorList.getElementAt(i);
MetadataElement newV = v.createDeepClone();
final int newLine = Integer.parseInt(v.getAttributeString("line")) + lineOffset;
newV.setAttributeString("line", Integer.toString(newLine));
targetVectorList.addElementAt(newV, idx);
idx++;
}
targetVectorList.setAttributeString("count", Integer.toString(idx));
}
private static int[] getPixelSpacings(MetadataElement pixel, final String msg) {
final String pixelsStr = pixel.getAttributeString("pixel");
final String[] pixelsArrayOfStr = pixelsStr.split(" ");
final int numPixels = pixelsArrayOfStr.length;
if (numPixels < 2) {
throw new OperatorException("Too few pixels " + numPixels + " for " + msg);
}
final int pixelCount = Integer.parseInt(pixel.getAttributeString("count"));
if (pixelCount != numPixels) {
throw new OperatorException("wrong pixel count " + pixelCount + ' ' + numPixels + " for " + msg);
}
final int[] pixelSpacings = new int[numPixels - 1];
for (int i = 0; i < numPixels - 1; i++) {
final int pixel0 = Integer.parseInt(pixelsArrayOfStr[i]);
final int pixel1 = Integer.parseInt(pixelsArrayOfStr[i + 1]);
pixelSpacings[i] = pixel1 - pixel0;
}
return pixelSpacings;
}
private static void checkCalibrationOrNoisePixelSpacing(final Product product, final String dataName) {
// TODO
// We are NOT going to assume that the pixels are evenly spaced except for may be the last pair
// even though that is most likely the case.
// We will only check that the pixels are the same for each line.
final MetadataElement origProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement data = origProdRoot.getElement(dataName);
final MetadataElement[] dataElems = data.getElements();
for (MetadataElement e : dataElems) {
final MetadataElement dat = e.getElement(dataName);
final MetadataElement vectorList = dat.getElement(dataName + "VectorList");
// Check the 1st vector (i.e. 1st line)
final MetadataElement firstVector = vectorList.getElementAt(0);
final MetadataElement firstVectorPixel = firstVector.getElement("pixel");
final int[] firstVectorPixelSpacings = getPixelSpacings(firstVectorPixel, product.getName() + ' ' + e.getName() + ' ' + dataName);
// Check that the pixels are the same for the rest of the lines
for (int i = 1; i < vectorList.getNumElements(); i++) {
final MetadataElement vector = vectorList.getElementAt(i);
final MetadataElement pixel = vector.getElement("pixel");
// TODO !!!!
}
}
}
private static int getLastPixel(final MetadataElement vector) {
final MetadataElement pixel = vector.getElement("pixel");
final String pixelsStr = pixel.getAttributeString("pixel");
final String[] pixelsArrayOfStr = pixelsStr.split(" ");
return Integer.parseInt(pixelsArrayOfStr[pixelsArrayOfStr.length - 1]);
}
private void updateCalibrationOrNoise(final String dataName) {
// dataName should be "calibration" or "noise"
final Product lastSliceProduct = sliceProducts[sliceProducts.length - 1];
final String productType = sliceProducts[0].getProductType();
// The calibration or noise data in metadata in targetProduct at this point is copied from the 1st slice.
// So we need to concatenate the vectors from the 2nd to last slices to target.
// Note that for a slice image of say height 1000, the first calibration vectors can be at lines:
// 0, 347, 709. ... or
// -334 -201, 50, 108, ... or
// 465, 8990, ...
// and the last calibration vectors can be at lines:
// 459, 600, 899 or
// 799, 990, 1400, 1678, 1900
// TODO: We should make the short vectors longer so that all vectors have "same" columns.
// TODO: E.g., a vector from top slice (width = 20055) has pixels:
// TODO: 0 40 80 ... 20040 20056
// TODO: a vector from the bottom slice (width = 20086) has pixels:
// TODO: 0 40 80 ... 20040 20080 20086
// TODO: Assembled product has width 20086 and we need to use extrapolation to extend the vectors from the top
// TODO: slice to have pixels:
// TODO: 0 40 80 ... 20040 20080 20086
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
final MetadataElement[] targetDataElems = getElementsToUpdate(targetOrigProdRoot, dataName);
// loop through each s1...-nnn.xml where nnn is the image number
for (MetadataElement target : targetDataElems) {
//System.out.println("update " + dataName + " for " + target.getName());
final String swathID = extractSwathIdentifier(target.getName()); // e.g. "iw" for GRD or "iw1" for SLC
final String imageNum = extractImageNumber(target.getName()); // e.g. "001"
final MetadataElement targetDat = target.getElement(dataName);
//System.out.println("swathID = " + swathID + "; imageNum = " + imageNum);
// Update stopTime in adsHeader
final MetadataElement targetADSHeader = targetDat.getElement("adsHeader");
final ProductData lastSliceStopTime = getStopTime(lastSliceProduct, imageNum);
AbstractMetadata.setAttribute(targetADSHeader, "stopTime", lastSliceStopTime.getElemString());
//System.out.println("stopTime = " + lastSliceStopTime.getElemString());
// Update (calibration|noise)VectorList
final MetadataElement targetVectorList = targetDat.getElement(dataName + "VectorList");
int numLines = Integer.parseInt(targetVectorList.getAttribute("count").getData().getElemString());
final int pixelSpacing = getCalibrationOrNoisePixelSpacing(targetProduct, imageNum, dataName); // TODO
int height = productType.equals("GRD") ?
sliceProducts[0].getSceneRasterHeight() :
sliceSwathImageDimMap.get(sliceProducts[0]).get(swathID.toUpperCase())[0];
//System.out.println("initial numLines = " + numLines + " pixelSpacing = " + pixelSpacing + " height = " + height);
// Loop through 2nd to last slice products in order and concatenate the (calibration|noise) vectors from each
// slice to the bottom of target
for (int i = 1; i < sliceProducts.length; i++) {
final Product sliceProduct = sliceProducts[i];
final int slicePixelSpacing = getCalibrationOrNoisePixelSpacing(sliceProduct, imageNum, dataName);
if (pixelSpacing != slicePixelSpacing) {
throw new OperatorException("slice products have different pixel spacing in " + dataName + " vectors: " + i + ' ' + pixelSpacing + ' ' + slicePixelSpacing);
}
final int targetLastVectorLine = Integer.parseInt(targetVectorList.getElementAt(targetVectorList.getNumElements() - 1).getAttributeString("line"));
//System.out.println("targetLastVectorLine = " + targetLastVectorLine + " slicePixelSpacing = " + slicePixelSpacing + " height = " + height + " pixelSpacing = " + pixelSpacing + " numLines = " + numLines);
final MetadataElement sliceVectorList = getCalibrationOrNoiseVectorList(sliceProduct, imageNum, dataName);
final int sliceNumLines = Integer.parseInt(sliceVectorList.getAttribute("count").getData().getElemString());
final int sliceFirstVectorLine = Integer.parseInt(sliceVectorList.getElementAt(0).getAttributeString("line"));
//System.out.println("sliceFirstVectorLine = " + sliceFirstVectorLine);
int numLinesRemoved = 0;
if (targetLastVectorLine == sliceFirstVectorLine + height - 1) {
concatenateVectors(targetVectorList, sliceVectorList, 0, height);
} else {
// Ignore excess top calibration vectors from bottom slice.
// E.g., if the first calibration vectors of the slice are at lines: -700, -348, 456, ...
// then we want to ignore the calibration vector at -700.
int k;
for (k = 0; k < sliceNumLines; k++) {
if (Integer.parseInt(sliceVectorList.getElementAt(k).getAttributeString("line")) > 0) {
break;
}
}
if (k == (sliceNumLines - 1)) {
throw new OperatorException("Only one " + dataName + " vector in slice " + i);
}
// k is the first line we want to keep
k = (k == 0) ? k : k - 1;
final int sliceTopVectorLine =
Integer.parseInt(sliceVectorList.getElementAt(k).getAttributeString("line"));
final int sliceTopVectorLastPixel = getLastPixel(sliceVectorList.getElementAt(k));
// Remove excess bottom calibration vectors from target.
final ArrayList<MetadataElement> elemsToRemove = new ArrayList<>();
int j;
for (j = numLines - 1; j >= 0; j--) {
MetadataElement targetCalibVector = targetVectorList.getElementAt(j);
final int targetCalibVectorLine = Integer.parseInt(targetCalibVector.getAttributeString("line"));
if (targetCalibVectorLine >= (sliceTopVectorLine + height)) {
if ((j > 0 && Integer.parseInt(targetVectorList.getElementAt(j - 1).getAttributeString("line")) >= (height - 1)) ||
(getLastPixel(targetCalibVector) <= sliceTopVectorLastPixel)) {
elemsToRemove.add(targetCalibVector);
} else {
break;
}
} else {
break;
}
}
numLinesRemoved = elemsToRemove.size();
for (MetadataElement e : elemsToRemove) {
targetVectorList.removeElement(e);
}
// Must update targetCalibVectorList count before calling concatenateCalibrationVectors()
targetVectorList.setAttributeString("count", Integer.toString(numLines - numLinesRemoved));
final int lastTargetLine = Integer.parseInt(targetVectorList.
getElementAt(numLines - numLinesRemoved - 1).getAttributeString("line"));
//System.out.println("lastTargetLine = " + lastTargetLine + " k = " + k);
// Get rid of overlaps in the bottom slice
for (j = k; j < sliceNumLines; j++) {
if (Integer.parseInt(sliceVectorList.getElementAt(j).getAttributeString("line")) + height
> lastTargetLine) {
k = j;
break;
}
}
// If target does not have enough calibration vectors to cover the bottom of the image, then we
// want to add back some top calibration vectors from the slice
for (j = k - 1; j >= 0; j--) {
final int sliceLine = Integer.parseInt(sliceVectorList.getElementAt(j).getAttributeString("line"));
if (sliceLine + height <= lastTargetLine) {
break;
}
}
concatenateVectors(targetVectorList, sliceVectorList, j + 1, height);
numLinesRemoved += (j + 1);
}
numLines += (sliceNumLines - numLinesRemoved);
if (numLines != targetVectorList.getNumElements()) {
throw new OperatorException("numLines = " + numLines + " != numElems = " + targetVectorList.getNumElements());
}
targetVectorList.setAttributeString("count", Integer.toString(numLines));
height += (productType.equals("GRD") ?
sliceProduct.getSceneRasterHeight() :
sliceSwathImageDimMap.get(sliceProduct).get(swathID.toUpperCase())[0]);
}
}
}
private static MetadataElement getAnnotationElement(final Product product, final String imageNum, final String elemName) {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement targetData = targetOrigProdRoot.getElement("annotation");
final MetadataElement[] targetDataElems = targetData.getElements();
MetadataElement elem = null;
for (MetadataElement target : targetDataElems) {
if (extractImageNumber(target.getName()).equals(imageNum)) {
final MetadataElement productElem = target.getElement("product");
elem = productElem.getElement(elemName);
break;
}
}
return elem;
}
private static String getProductLastLineUtcTime(final Product product, final String imageNum) {
final MetadataElement imageAnnotationElem = getAnnotationElement(product, imageNum, "imageAnnotation");
if (imageAnnotationElem == null) {
return "";
}
final MetadataElement imageInformationElem = imageAnnotationElem.getElement("imageInformation");
return imageInformationElem.getAttributeString("productLastLineUtcTime");
}
private void updateImageInformation() {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
final MetadataElement[] targetDataElems = getElementsToUpdate(targetOrigProdRoot, "annotation");
// loop through each s1...-nnn.xml where nnn is the image number
for (MetadataElement target : targetDataElems) {
final MetadataElement productElem = target.getElement("product");
final MetadataElement imageAnnotationElem = productElem.getElement("imageAnnotation");
final MetadataElement imageInformationElem = imageAnnotationElem.getElement("imageInformation");
imageInformationElem.setAttributeString("productLastLineUtcTime",
getProductLastLineUtcTime(sliceProducts[sliceProducts.length - 1],
extractImageNumber(target.getName())));
final String swathID;
if (sliceProducts[0].getProductType().equals("GRD")) {
swathID = "";
} else {
swathID = extractSwathIdentifier(target.getName());
}
imageInformationElem.setAttributeString("numberOfSamples", Integer.toString(swathAssembledImageDimMap.get(swathID.toUpperCase())[1]));
imageInformationElem.setAttributeString("numberOfLines", Integer.toString(swathAssembledImageDimMap.get(swathID.toUpperCase())[0]));
}
}
private static long getByteIncrementPerBurst(final MetadataElement burstList) {
final MetadataElement[] bursts = burstList.getElements();
final long increment = Long.parseLong(bursts[1].getAttributeString("byteOffset")) -
Long.parseLong(bursts[0].getAttributeString("byteOffset"));
for (int i = 2; i < bursts.length; i++) {
final long incr = Long.parseLong(bursts[i].getAttributeString("byteOffset")) -
Long.parseLong(bursts[i - 1].getAttributeString("byteOffset"));
if (incr != increment) {
throw new OperatorException("wrong burst byte increment");
}
}
return increment;
}
private void updateSwathTiming() {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
MetadataElement[] elements = getElementsToUpdate(targetOrigProdRoot, "annotation");
for (MetadataElement e : elements) {
final String imageNum = extractImageNumber(e.getName());
MetadataElement targetSwathTiming = e.getElement("product").getElement("swathTiming");
final int linesPerBurst = Integer.parseInt(targetSwathTiming.getAttributeString("linesPerBurst"));
int samplesPerBurst = Integer.parseInt(targetSwathTiming.getAttributeString("samplesPerBurst"));
MetadataElement targetBurstList = targetSwathTiming.getElement("burstList");
int count = Integer.parseInt(targetBurstList.getAttributeString("count"));
// count can be zero if it is GRD
long targetLastByteOffset = count > 0 ? Long.parseLong(targetBurstList.getElementAt(count - 1).getAttributeString("byteOffset")) : 0;
long targetByteIncr = count > 0 ? getByteIncrementPerBurst(targetBurstList) : 0;
for (int i = 1; i < sliceProducts.length; i++) {
MetadataElement sliceSwathTiming = getAnnotationElement(sliceProducts[i], imageNum, "swathTiming");
final int sliceLinesPerBurst = Integer.parseInt(sliceSwathTiming.getAttributeString("linesPerBurst"));
if (sliceLinesPerBurst != linesPerBurst) {
throw new OperatorException("slice " + i + " has different linesPerBurst " + sliceLinesPerBurst + " vs. " + linesPerBurst);
}
final int sliceSamplesPerBurst = Integer.parseInt(sliceSwathTiming.getAttributeString("samplesPerBurst"));
//System.out.println("sliceSamplesPerBurst = " + sliceSamplesPerBurst + " samplesPerBurst = " + samplesPerBurst);
if (sliceSamplesPerBurst > samplesPerBurst) {
samplesPerBurst = sliceSamplesPerBurst;
}
MetadataElement sliceBurstList = sliceSwathTiming.getElement("burstList");
final int sliceBurstListCount = Integer.parseInt(sliceBurstList.getAttributeString("count"));
if (sliceBurstListCount < 1) {
// This handles the case when it is a GRD product
continue;
}
MetadataElement[] sliceBurstListElems = sliceBurstList.getElements();
long newByteOffset = 0;
final long sliceFirstByteOffset = Long.parseLong(sliceBurstListElems[0].getAttributeString("byteOffset"));
for (MetadataElement b : sliceBurstListElems) {
MetadataElement newB = b.createDeepClone();
final long sliceByteOffset = Long.parseLong(b.getAttributeString("byteOffset"));
newByteOffset = sliceByteOffset + targetLastByteOffset + targetByteIncr - sliceFirstByteOffset;
newB.setAttributeString("byteOffset", Long.toString(newByteOffset));
targetBurstList.addElementAt(newB, count);
count++;
}
targetLastByteOffset = newByteOffset;
targetByteIncr = getByteIncrementPerBurst(sliceBurstList);
}
targetSwathTiming.setAttributeString("samplesPerBurst", Integer.toString(samplesPerBurst));
targetBurstList.setAttributeString("count", Integer.toString(count));
}
}
private void updateGeolocationGrid() {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
MetadataElement[] elements = getElementsToUpdate(targetOrigProdRoot, "annotation");
for (MetadataElement e : elements) {
final String imageNum = extractImageNumber(e.getName());
MetadataElement targetGeolocationGrid = e.getElement("product").getElement("geolocationGrid");
MetadataElement targetGeolocationGridPointList = targetGeolocationGrid.getElement("geolocationGridPointList");
int count = Integer.parseInt(targetGeolocationGridPointList.getAttributeString("count"));
MetadataElement sliceImageAnnotation = getAnnotationElement(sliceProducts[0], imageNum, "imageAnnotation");
MetadataElement sliceImageInformation = sliceImageAnnotation.getElement("imageInformation");
int numberOfLines = Integer.parseInt(sliceImageInformation.getAttributeString("numberOfLines"));
for (int i = 1; i < sliceProducts.length; i++) {
MetadataElement sliceGeolocationGrid = getAnnotationElement(sliceProducts[i], imageNum, "geolocationGrid");
MetadataElement sliceGeolocationGridPointList = sliceGeolocationGrid.getElement("geolocationGridPointList");
final int sliceCount = Integer.parseInt(sliceGeolocationGridPointList.getAttributeString("count"));
if (sliceCount < 1) {
continue;
}
MetadataElement[] sliceGeolocationGridPoints = sliceGeolocationGridPointList.getElements();
for (MetadataElement p : sliceGeolocationGridPoints) {
MetadataElement newP = p.createDeepClone();
final long sliceLine = Long.parseLong(p.getAttributeString("line"));
newP.setAttributeString("line", Long.toString(sliceLine + numberOfLines));
targetGeolocationGridPointList.addElementAt(newP, count++);
}
sliceImageAnnotation = getAnnotationElement(sliceProducts[i], imageNum, "imageAnnotation");
sliceImageInformation = sliceImageAnnotation.getElement("imageInformation");
numberOfLines += Integer.parseInt(sliceImageInformation.getAttributeString("numberOfLines"));
}
targetGeolocationGridPointList.setAttributeString("count", Integer.toString(count));
}
}
private void updateDopplerCentroid() {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
MetadataElement[] elements = getElementsToUpdate(targetOrigProdRoot, "annotation");
for (MetadataElement e : elements) {
final String imageNum = extractImageNumber(e.getName());
MetadataElement targetDopplerCentroid = e.getElement("product").getElement("dopplerCentroid");
MetadataElement targetDCEstimateList = targetDopplerCentroid.getElement("dcEstimateList");
int count = Integer.parseInt(targetDCEstimateList.getAttributeString("count"));
for (int i = 1; i < sliceProducts.length; i++) {
MetadataElement sliceDopplerCentroid = getAnnotationElement(sliceProducts[i], imageNum, "dopplerCentroid");
MetadataElement sliceDCEstimateList = sliceDopplerCentroid.getElement("dcEstimateList");
final int sliceCount = Integer.parseInt(sliceDCEstimateList.getAttributeString("count"));
if (sliceCount < 1) {
continue;
}
MetadataElement[] sliceDCEstimates = sliceDCEstimateList.getElements();
for (MetadataElement dc : sliceDCEstimates) {
targetDCEstimateList.addElementAt(dc.createDeepClone(), count++);
}
}
targetDCEstimateList.setAttributeString("count", Integer.toString(count));
}
}
private static MetadataElement getAzimuthFmRateList(final Product product, String imageNum) {
final MetadataElement generalAnnotation = getAnnotationElement(product, imageNum, "generalAnnotation");
return generalAnnotation.getElement("azimuthFmRateList");
}
private void updateAzimuthFmRateList() {
final MetadataElement targetOrigProdRoot = AbstractMetadata.getOriginalProductMetadata(targetProduct);
final MetadataElement[] elements = getElementsToUpdate(targetOrigProdRoot, "annotation");
for (MetadataElement e : elements) {
final String imageNum = extractImageNumber(e.getName());
final MetadataElement targetAzimuthFmRateList = e.getElement("product").getElement("generalAnnotation").getElement("azimuthFmRateList");
int targetNewCount = Integer.parseInt(targetAzimuthFmRateList.getAttributeString("count"));
for (int i = 1; i < sliceProducts.length; i++) {
final MetadataElement sliceAzimuthFmRateList = getAzimuthFmRateList(sliceProducts[i], imageNum);
final int sliceCount = Integer.parseInt(sliceAzimuthFmRateList.getAttributeString("count"));
for (int j = 0; j < sliceCount; j++) {
final MetadataElement azimuthFmRate = sliceAzimuthFmRateList.getElementAt(j).createDeepClone();
targetAzimuthFmRateList.addElementAt(azimuthFmRate, targetNewCount + j);
}
targetNewCount += sliceCount;
}
targetAzimuthFmRateList.setAttributeString("count", Integer.toString(targetNewCount));
}
}
private static void concatenateOrbitStateVectors(final List<OrbitStateVector> orbVectorList, final OrbitStateVector[] orbs) {
// concatenate orbs to orbVectorList
// orbVectorList may be empty
if (orbVectorList.isEmpty()) {
orbVectorList.addAll(Arrays.asList(orbs));
} else {
// We assume that "orbs" are in chronological order
final double lastTime = orbVectorList.get(orbVectorList.size() - 1).time_mjd;
final double firstTime = orbs[0].time_mjd;
final double midTime = (lastTime + firstTime) / 2.0;
int firstVecToRemove = -1;
for (int i = 0; i < orbVectorList.size(); i++) {
if (orbVectorList.get(i).time_mjd > midTime) {
firstVecToRemove = i;
break;
}
}
if (firstVecToRemove != -1) {
for (int i = orbVectorList.size() - 1; i >= firstVecToRemove; i--) {
orbVectorList.remove(i);
}
}
for (OrbitStateVector orb : orbs) {
if (orb.time_mjd > midTime) {
orbVectorList.add(orb);
}
}
}
}
private void updateTargetProductMetadata() throws Exception {
// All the metadata has been copied from the 1st slice product to the assembled target product.
// Now we want to update the metadata that should not be "included", but "merged" or concatenated".
final MetadataElement absTgt = AbstractMetadata.getAbstractedMetadata(targetProduct);
final Product firstSliceProduct = sliceProducts[0];
final Product lastSliceProduct = sliceProducts[sliceProducts.length - 1];
final MetadataElement absFirst = AbstractMetadata.getAbstractedMetadata(firstSliceProduct);
final MetadataElement absLast = AbstractMetadata.getAbstractedMetadata(lastSliceProduct);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.first_line_time,
absFirst.getAttributeUTC(AbstractMetadata.first_line_time));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.last_line_time,
absLast.getAttributeUTC(AbstractMetadata.last_line_time));
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_output_lines, targetHeight);
AbstractMetadata.setAttribute(absTgt, AbstractMetadata.num_samples_per_line, targetWidth);
for (Band band : targetProduct.getBands()) {
MetadataElement bandMeta = AbstractMetadata.getBandAbsMetadata(absTgt, band);
if (bandMeta != null) {
AbstractMetadata.setAttribute(bandMeta, AbstractMetadata.first_line_time,
absFirst.getAttributeUTC(AbstractMetadata.first_line_time));
AbstractMetadata.setAttribute(bandMeta, AbstractMetadata.last_line_time,
absLast.getAttributeUTC(AbstractMetadata.last_line_time));
AbstractMetadata.setAttribute(bandMeta, AbstractMetadata.num_output_lines, band.getRasterHeight());
AbstractMetadata.setAttribute(bandMeta, AbstractMetadata.num_samples_per_line, band.getRasterWidth());
}
}
final ArrayList<MetadataElement> bandMetaToRemove = new ArrayList<>();
MetadataElement[] absTgtElems = absTgt.getElements();
for (MetadataElement e : absTgtElems) {
final String elemName = e.getName();
if (elemName.contains("Band")) {
for (String pol : selectedPolarisations) {
if (!elemName.contains(pol)) {
bandMetaToRemove.add(e);
}
}
}
}
for (MetadataElement e : bandMetaToRemove) {
absTgt.removeElement(e);
}
final List<OrbitStateVector> orbVectorList = new ArrayList<>();
final List<AbstractMetadata.SRGRCoefficientList> srgrList = new ArrayList<>();
final List<AbstractMetadata.DopplerCentroidCoefficientList> dopList = new ArrayList<>();
for (Product srcProduct : sliceProducts) {
final MetadataElement absSrc = AbstractMetadata.getAbstractedMetadata(srcProduct);
// update orbit state vectors
final OrbitStateVector[] orbs = AbstractMetadata.getOrbitStateVectors(absSrc);
concatenateOrbitStateVectors(orbVectorList, orbs);
// update srgr coeffs
final AbstractMetadata.SRGRCoefficientList[] srgr = AbstractMetadata.getSRGRCoefficients(absSrc);
srgrList.addAll(Arrays.asList(srgr));
// update Doppler centroid coeffs
final AbstractMetadata.DopplerCentroidCoefficientList[] dop = AbstractMetadata.getDopplerCentroidCoefficients(absSrc);
dopList.addAll(Arrays.asList(dop));
}
AbstractMetadata.setOrbitStateVectors(absTgt, orbVectorList.toArray(new OrbitStateVector[orbVectorList.size()]));
AbstractMetadata.setSRGRCoefficients(absTgt, srgrList.toArray(new AbstractMetadata.SRGRCoefficientList[srgrList.size()]));
AbstractMetadata.setDopplerCentroidCoefficients(absTgt, dopList.toArray(new AbstractMetadata.DopplerCentroidCoefficientList[dopList.size()]));
updateCalibrationOrNoise("calibration");
updateCalibrationOrNoise("noise");
updateImageInformation();
updateSwathTiming();
updateGeolocationGrid();
updateDopplerCentroid();
updateAzimuthFmRateList();
}
private void determineBandStartEndTimes() {
for (Band targetBand : targetProduct.getBands()) {
final List<BandLines> bandLineList = new ArrayList<>(sliceProducts.length);
int height = 0;
for (Product srcProduct : sliceProducts) {
final Band srcBand = srcProduct.getBand(targetBand.getName());
int start = height;
height += srcBand.getRasterHeight();
int end = height;
bandLineList.add(new BandLines(srcBand, start, end));
}
final BandLines[] lines = bandLineList.toArray(new BandLines[bandLineList.size()]);
bandLineMap.put(targetBand, lines);
}
}
/**
* Called by the framework in order to compute a tile for the given target band.
* <p>The default implementation throws a runtime exception with the message "not implemented".</p>
*
* @param targetBand The target band.
* @param targetTile The current tile associated with the target band to be computed.
* @param pm A progress monitor which should be used to determine computation cancellation requests.
* @throws OperatorException If an error occurs during computation of the target raster.
*/
public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm) throws OperatorException {
try {
final Rectangle targetTileRectangle = targetTile.getRectangle();
final int tx0 = targetTileRectangle.x;
final int ty0 = targetTileRectangle.y;
final int maxY = ty0 + targetTileRectangle.height;
final int maxX = tx0 + targetTileRectangle.width;
if (targetTileRectangle.width < 2)
return;
final BandLines[] lines = bandLineMap.get(targetBand);
final ProductData trgData = targetTile.getDataBuffer();
final TileIndex trgIndex = new TileIndex(targetTile);
final Rectangle srcRect = new Rectangle();
BandLines line = lines[0];
for (int y = ty0; y < maxY; ++y) {
//boolean validLine = false;
for (BandLines l : lines) {
if (y >= l.start && y < l.end) {
line = l;
//validLine = true;
break;
}
}
//if (!validLine) {
// // should never get here
// throw new OperatorException("line " + y + " not found in slice products");
//}
//if (tx0 > line.band.getRasterWidth() - 1) {
// return;
//}
final int yy = y - line.start;
srcRect.setBounds(targetTileRectangle.x, yy, targetTileRectangle.width, 1);
final Tile sourceRaster = getSourceTile(line.band, srcRect);
final ProductData srcData = sourceRaster.getDataBuffer();
final TileIndex srcIndex = new TileIndex(sourceRaster);
trgIndex.calculateStride(y);
srcIndex.calculateStride(yy);
for (int x = tx0; x < maxX; ++x) {
trgData.setElemDoubleAt(trgIndex.getIndex(x), srcData.getElemDoubleAt(srcIndex.getIndex(x)));
}
}
} catch (Throwable e) {
throw new OperatorException(e.getMessage());
}
}
private static class BandLines {
final int start;
final int end;
final Band band;
BandLines(final Band band, final int s, final int e) {
this.band = band;
this.start = s;
this.end = e;
}
}
/**
* 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(SliceAssemblyOp.class);
}
}
}