/*
* 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.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.bc.ceres.core.ProgressMonitor;
import org.esa.s1tbx.insar.gpf.support.Sentinel1Utils;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.dataio.ProductSubsetBuilder;
import org.esa.snap.core.dataio.ProductSubsetDef;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoCoding;
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.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.SourceProduct;
import org.esa.snap.core.gpf.annotations.TargetProduct;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
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 java.awt.*;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* Creates a new product with only selected sub-swath and bursts
*/
@OperatorMetadata(alias = "TOPSAR-Split",
category = "Radar/Sentinel-1 TOPS",
authors = "Jun Lu, Luis Veci",
version = "1.0",
copyright = "Copyright (C) 2016 by Array Systems Computing Inc.",
description = "Creates a new product with only the selected subswath")
public final class TOPSARSplitOp extends Operator {
@SourceProduct(alias = "source")
private Product sourceProduct;
@TargetProduct
private Product targetProduct;
@Parameter(description = "The list of source bands.", label = "Subswath")
private String subswath = null;
@Parameter(description = "The list of polarisations", label = "Polarisations")
private String[] selectedPolarisations;
@Parameter(description = "The first burst index", interval = "[1, *)", defaultValue = "1", label = "First Burst Index")
private Integer firstBurstIndex = 1;
@Parameter(description = "The last burst index", interval = "[1, *)", defaultValue = "9999", label = "Last Burst Index")
private Integer lastBurstIndex = 9999;
@Parameter(description = "WKT polygon to be used for selecting bursts", label = "WKT Area of Interest")
private String wktAoi = null;
private Sentinel1Utils su = null;
private Sentinel1Utils.SubSwathInfo[] subSwathInfo = null;
private int subSwathIndex = 0;
private ProductSubsetBuilder subsetBuilder = null;
/**
* 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 {
final InputProductValidator validator = new InputProductValidator(sourceProduct);
validator.checkIfSARProduct();
validator.checkIfSentinel1Product();
validator.checkIfMultiSwathTOPSARProduct();
validator.checkProductType(new String[]{"SLC"});
validator.checkAcquisitionMode(new String[]{"IW", "EW"});
final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct);
if (subswath == null) {
final String acquisitionMode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE);
subswath = acquisitionMode + '1';
}
su = new Sentinel1Utils(sourceProduct);
subSwathInfo = su.getSubSwath();
for (int i = 0; i < subSwathInfo.length; i++) {
if (subSwathInfo[i].subSwathName.contains(subswath)) {
subSwathIndex = i + 1;
break;
}
}
if (selectedPolarisations == null || selectedPolarisations.length == 0) {
selectedPolarisations = Sentinel1Utils.getProductPolarizations(absRoot);
}
final List<Band> selectedBands = new ArrayList<>();
for (Band srcBand : sourceProduct.getBands()) {
if (srcBand.getName().contains(subswath)) {
for (String pol : selectedPolarisations) {
if (srcBand.getName().contains(pol)) {
selectedBands.add(srcBand);
}
}
}
}
if (selectedBands.size() < 1) {
// try again
selectedPolarisations = Sentinel1Utils.getProductPolarizations(absRoot);
for (Band srcBand : sourceProduct.getBands()) {
if (srcBand.getName().contains(subswath)) {
for (String pol : selectedPolarisations) {
if (srcBand.getName().contains(pol)) {
selectedBands.add(srcBand);
}
}
}
}
}
int maxBursts = su.getNumOfBursts(subswath);
if (lastBurstIndex > maxBursts) {
lastBurstIndex = maxBursts;
}
if(wktAoi != null) {
findValidBurstsBasedOnWkt();
}
subsetBuilder = new ProductSubsetBuilder();
final ProductSubsetDef subsetDef = new ProductSubsetDef();
final List<String> selectedTPGList = new ArrayList<>();
for (TiePointGrid srcTPG : sourceProduct.getTiePointGrids()) {
if (srcTPG.getName().contains(subswath)) {
selectedTPGList.add(srcTPG.getName());
}
}
subsetDef.addNodeNames(selectedTPGList.toArray(new String[selectedTPGList.size()]));
final int x = 0;
final int y = (firstBurstIndex - 1) * subSwathInfo[subSwathIndex - 1].linesPerBurst;
final int w = selectedBands.get(0).getRasterWidth();
final int h = (lastBurstIndex - firstBurstIndex + 1) * subSwathInfo[subSwathIndex - 1].linesPerBurst;
subsetDef.setRegion(x, y, w, h);
subsetDef.setSubSampling(1, 1);
subsetDef.setIgnoreMetadata(false);
final String[] selectedBandNames = new String[selectedBands.size()];
for (int i = 0; i < selectedBandNames.length; i++) {
selectedBandNames[i] = selectedBands.get(i).getName();
}
subsetDef.addNodeNames(selectedBandNames);
targetProduct = subsetBuilder.readProductNodes(sourceProduct, subsetDef);
targetProduct.removeTiePointGrid(targetProduct.getTiePointGrid("latitude"));
targetProduct.removeTiePointGrid(targetProduct.getTiePointGrid("longitude"));
for (TiePointGrid tpg : targetProduct.getTiePointGrids()) {
tpg.setName(tpg.getName().replace(subswath + "_", ""));
}
GeoCoding geoCoding = new TiePointGeoCoding(targetProduct.getTiePointGrid("latitude"),
targetProduct.getTiePointGrid("longitude"));
targetProduct.setSceneGeoCoding(geoCoding);
updateTargetProductMetadata();
} catch (Throwable e) {
OperatorUtils.catchOperatorException(getId(), e);
}
}
/**
* 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 cancelation requests.
* @throws OperatorException If an error occurs during computation of the target raster.
*/
@Override
public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm) throws OperatorException {
ProductData destBuffer = targetTile.getRawSamples();
Rectangle rectangle = targetTile.getRectangle();
try {
subsetBuilder.readBandRasterData(targetBand,
rectangle.x,
rectangle.y,
rectangle.width,
rectangle.height,
destBuffer, pm);
targetTile.setRawSamples(destBuffer);
} catch (IOException e) {
throw new OperatorException(e);
}
}
/**
* Find bursts (i.e. firstBurstIndex and lastBurstIndex) that overlap AOI WKT
*/
private void findValidBurstsBasedOnWkt() {
// Read AOI polygon
Geometry aoi = null;
try {
aoi = new WKTReader().read(wktAoi);
} catch (ParseException e) {
e.printStackTrace();
}
// Read burst polygons and check if it intersects AOI
int numBursts = lastBurstIndex - firstBurstIndex + 1;
GeoPos[][] geoBound = new GeoPos[numBursts][4];
Sentinel1Utils.SubSwathInfo swath = subSwathInfo[subSwathIndex - 1];
GeometryFactory gf = new GeometryFactory();
Coordinate[] coordinates;
LinearRing linearRing;
Geometry burst;
int numPoints = swath.latitude[0].length - 1;
List<Integer> validSelBursts = new ArrayList<>();
int burst_i;
for (int i = 0; i < numBursts; ++i) {
burst_i = firstBurstIndex - 1 + i;
geoBound[i][0] = new GeoPos(swath.latitude[burst_i][0], swath.longitude[burst_i][0]);
geoBound[i][1] = new GeoPos(swath.latitude[burst_i][numPoints], swath.longitude[burst_i][numPoints]);
geoBound[i][2] = new GeoPos(swath.latitude[burst_i + 1][numPoints], swath.longitude[burst_i + 1][numPoints]);
geoBound[i][3] = new GeoPos(swath.latitude[burst_i + 1][0], swath.longitude[burst_i + 1][0]);
coordinates = new Coordinate[5];
for (int c_i = 0; c_i < 4; ++c_i) {
coordinates[c_i] = new Coordinate(geoBound[i][c_i].getLon(), geoBound[i][c_i].getLat());
}
coordinates[4] = new Coordinate(geoBound[i][0].getLon(), geoBound[i][0].getLat());
linearRing = gf.createLinearRing(coordinates);
burst = gf.createPolygon(linearRing, null);
if (aoi.intersects(burst)) {
validSelBursts.add(burst_i + 1);
}
}
firstBurstIndex = Collections.min(validSelBursts);
lastBurstIndex = Collections.max(validSelBursts);
}
/**
* Update target product metadata.
*/
private void updateTargetProductMetadata() {
updateAbstractedMetadata();
updateOriginalMetadata();
}
/**
* Update the abstracted metadata in the target product.
*/
private void updateAbstractedMetadata() {
final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(targetProduct);
absRoot.setAttributeUTC(AbstractMetadata.first_line_time, new ProductData.UTC(
subSwathInfo[subSwathIndex - 1].burstFirstLineTime[firstBurstIndex - 1] / Constants.secondsInDay));
absRoot.setAttributeUTC(AbstractMetadata.last_line_time, new ProductData.UTC(
subSwathInfo[subSwathIndex - 1].burstLastLineTime[lastBurstIndex - 1] / Constants.secondsInDay));
absRoot.setAttributeDouble(AbstractMetadata.line_time_interval,
subSwathInfo[subSwathIndex - 1].azimuthTimeInterval);
absRoot.setAttributeDouble(AbstractMetadata.slant_range_to_first_pixel,
subSwathInfo[subSwathIndex - 1].slrTimeToFirstPixel * Constants.lightSpeed);
absRoot.setAttributeDouble(AbstractMetadata.range_spacing,
subSwathInfo[subSwathIndex - 1].rangePixelSpacing);
absRoot.setAttributeDouble(AbstractMetadata.azimuth_spacing,
subSwathInfo[subSwathIndex - 1].azimuthPixelSpacing);
absRoot.setAttributeInt(AbstractMetadata.num_output_lines,
subSwathInfo[subSwathIndex - 1].linesPerBurst * (lastBurstIndex - firstBurstIndex + 1));
absRoot.setAttributeInt(AbstractMetadata.num_samples_per_line,
subSwathInfo[subSwathIndex - 1].numOfSamples);
final int cols = subSwathInfo[subSwathIndex - 1].latitude[0].length;
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_lat,
subSwathInfo[subSwathIndex - 1].latitude[firstBurstIndex - 1][0]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_near_long,
subSwathInfo[subSwathIndex - 1].longitude[firstBurstIndex - 1][0]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_lat,
subSwathInfo[subSwathIndex - 1].latitude[firstBurstIndex - 1][cols - 1]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.first_far_long,
subSwathInfo[subSwathIndex - 1].longitude[firstBurstIndex - 1][cols - 1]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_lat,
subSwathInfo[subSwathIndex - 1].latitude[lastBurstIndex][0]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_near_long,
subSwathInfo[subSwathIndex - 1].longitude[lastBurstIndex][0]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_lat,
subSwathInfo[subSwathIndex - 1].latitude[lastBurstIndex][cols - 1]);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.last_far_long,
subSwathInfo[subSwathIndex - 1].longitude[lastBurstIndex][cols - 1]);
final double incidenceNear = OperatorUtils.getIncidenceAngle(targetProduct).getPixelDouble(
0, targetProduct.getSceneRasterHeight() / 2);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.incidence_near, incidenceNear);
final double incidenceFar = OperatorUtils.getIncidenceAngle(targetProduct).getPixelDouble(
targetProduct.getSceneRasterWidth() - 1, targetProduct.getSceneRasterHeight() / 2);
AbstractMetadata.setAttribute(absRoot, AbstractMetadata.incidence_far, incidenceFar);
absRoot.setAttributeString(AbstractMetadata.swath, subswath);
for (int i = 0; i < selectedPolarisations.length; i++) {
if (i == 0) {
absRoot.setAttributeString(AbstractMetadata.mds1_tx_rx_polar, selectedPolarisations[i]);
} else if (i == 1) {
absRoot.setAttributeString(AbstractMetadata.mds2_tx_rx_polar, selectedPolarisations[i]);
} else if (i == 2) {
absRoot.setAttributeString(AbstractMetadata.mds3_tx_rx_polar, selectedPolarisations[i]);
} else {
absRoot.setAttributeString(AbstractMetadata.mds4_tx_rx_polar, selectedPolarisations[i]);
}
}
final MetadataElement[] bandMetadataList = AbstractMetadata.getBandAbsMetadataList(absRoot);
for (MetadataElement bandMeta : bandMetadataList) {
boolean include = false;
if (bandMeta.getName().contains(subswath)) {
for (String pol : selectedPolarisations) {
if (bandMeta.getName().contains(pol)) {
include = true;
break;
}
}
}
if (!include) {
// remove band metadata if polarization or subswath is not included
absRoot.removeElement(bandMeta);
}
}
}
private void updateOriginalMetadata() {
final MetadataElement origMeta = AbstractMetadata.getOriginalProductMetadata(targetProduct);
removeElements(origMeta, "annotation");
removeElements(origMeta, "calibration");
removeElements(origMeta, "noise");
removeBursts(origMeta);
updateImageInformation(origMeta);
}
private void removeElements(final MetadataElement origMeta, final String parent) {
final MetadataElement parentElem = origMeta.getElement(parent);
if (parentElem != null) {
final MetadataElement[] elemList = parentElem.getElements();
for (MetadataElement elem : elemList) {
if (!elem.getName().toUpperCase().contains(subswath)) {
parentElem.removeElement(elem);
} else {
boolean isSelected = false;
for (String pol : selectedPolarisations) {
if (elem.getName().toUpperCase().contains(pol)) {
isSelected = true;
break;
}
}
if (!isSelected) {
parentElem.removeElement(elem);
}
}
}
}
}
private void removeBursts(final MetadataElement origMeta) {
MetadataElement annotation = origMeta.getElement("annotation");
if (annotation == null) {
throw new OperatorException("Annotation Metadata not found");
}
final MetadataElement[] elems = annotation.getElements();
for (MetadataElement elem : elems) {
final MetadataElement product = elem.getElement("product");
final MetadataElement swathTiming = product.getElement("swathTiming");
final MetadataElement burstList = swathTiming.getElement("burstList");
burstList.setAttributeString("count", Integer.toString(lastBurstIndex - firstBurstIndex + 1));
final MetadataElement[] burstListElem = burstList.getElements();
for (int i = 0; i < burstListElem.length; i++) {
if (i < firstBurstIndex - 1 || i > lastBurstIndex - 1) {
burstList.removeElement(burstListElem[i]);
}
}
}
}
private void updateImageInformation(final MetadataElement origMeta) {
MetadataElement annotation = origMeta.getElement("annotation");
if (annotation == null) {
throw new OperatorException("Annotation Metadata not found");
}
final MetadataElement[] elems = annotation.getElements();
for (MetadataElement elem : elems) {
final MetadataElement product = elem.getElement("product");
final MetadataElement imageAnnotation = product.getElement("imageAnnotation");
final MetadataElement imageInformation = imageAnnotation.getElement("imageInformation");
imageInformation.setAttributeString("numberOfLines", Integer.toString(
subSwathInfo[subSwathIndex - 1].linesPerBurst * (lastBurstIndex - firstBurstIndex + 1)));
final ProductData.UTC firstLineTimeUTC = new ProductData.UTC(
subSwathInfo[subSwathIndex - 1].burstFirstLineTime[firstBurstIndex - 1] / Constants.secondsInDay);
imageInformation.setAttributeString("productFirstLineUtcTime", format(firstLineTimeUTC));
final ProductData.UTC lastLineTimeUTC = new ProductData.UTC(
subSwathInfo[subSwathIndex - 1].burstLastLineTime[lastBurstIndex - 1] / Constants.secondsInDay);
imageInformation.setAttributeString("productLastLineUtcTime", format(lastLineTimeUTC));
}
}
public String format(final ProductData.UTC utc) {
final Calendar calendar = utc.createCalendar();
calendar.add(Calendar.DATE, utc.getDaysFraction());
calendar.add(Calendar.SECOND, (int) utc.getSecondsFraction());
final DateFormat dateFormat = utc.createDateFormat("yyyy-MM-dd_HH:mm:ss"); // 2015-02-05T20:25:19.830824
final Date time = calendar.getTime();
final String dateString = dateFormat.format(time);
final String microsString = String.valueOf(utc.getMicroSecondsFraction());
StringBuilder sb = new StringBuilder(dateString.toUpperCase());
sb.append('.');
for (int i = microsString.length(); i < 6; i++) {
sb.append('0');
}
sb.append(microsString);
return sb.toString().replace("_", "T");
}
/**
* 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(TOPSARSplitOp.class);
}
}
}