/* * Copyright (C) 2016 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.insar.gpf; import com.bc.ceres.core.ProgressMonitor; import org.esa.s1tbx.insar.gpf.support.ProjectedDEM; import org.esa.snap.core.dataio.ProductIO; import org.esa.snap.core.dataio.ProductWriter; 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.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.core.util.SystemUtils; import org.esa.snap.engine_utilities.datamodel.AbstractMetadata; import org.esa.snap.engine_utilities.gpf.InputProductValidator; import java.awt.*; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.text.DateFormat; import java.util.HashMap; import java.util.Map; /** * Export products into format suitable for import to StaMPS. */ @OperatorMetadata(alias = "StampsExport", category = "Radar/Interferometric/PSI \\ SBAS", authors = "Cecilia Wong, Luis Veci", version = "1.0", copyright = "Copyright (C) 2016 by Array Systems Computing Inc.", autoWriteDisabled = true, description = "Export data for StaMPS processing") public class StampsExportOp extends Operator { @SourceProducts private Product[] sourceProduct; @TargetProduct private Product targetProduct; @Parameter(description = "The output folder to which the data product is written.") private File targetFolder; @Parameter(description = "Format for PSI or SBAS", defaultValue = "true") private Boolean psiFormat = true; private static final String formatName = "Gamma"; private static final String[] folder = {"rslc", "diff0", "geo", "dem"}; private static final String[] ext = {".rslc", ".diff", "_dem.rdc", "_dem"}; private enum FOLDERS {RSLC, DIFF, GEO, DEM} private final DateFormat rawDateFormat = ProductData.UTC.createDateFormat("ddMMMyyyy"); private final DateFormat dateFormat = ProductData.UTC.createDateFormat("yyyyMMdd"); private final HashMap<Band, WriterInfo> tgtBandToInfoMap = new HashMap<>(); private ProjectedDEM projectedDEM; private WriterInfo projectedDEMInfo; private boolean projectedDEMWritten = false; public StampsExportOp() { setRequiresAllBands(true); } @Override public void initialize() throws OperatorException { try { if (sourceProduct.length != 2) { throw new OperatorException("Input requires a coregistered stack and at least 4 interferograms"); } if(!psiFormat) { throw new OperatorException("SBAS format is not yet supported."); } //SystemUtils.LOG.info("StampsExportOp: SLC product: " + sourceProduct[0]); //SystemUtils.LOG.info("StampsExportOp: IGF product: " + sourceProduct[1]); // First source product should be a stack of coregistered SLC products final InputProductValidator validator = new InputProductValidator(sourceProduct[0]); validator.checkIfCoregisteredStack(); validator.checkIfSLC(); validator.checkIfTOPSARBurstProduct(false); validator.checkIfCompatibleProducts(sourceProduct); final InputProductValidator validator2 = new InputProductValidator(sourceProduct[1]); validator2.checkIfCoregisteredStack(); validator2.checkIfSLC(); validator2.checkIfTOPSARBurstProduct(false); if (targetFolder == null) { throw new OperatorException("Please add a target folder"); } if (!targetFolder.exists()) { if (!targetFolder.mkdirs()) { SystemUtils.LOG.severe("Unable to create folders in " + targetFolder); } } targetProduct = new Product(sourceProduct[1].getName(), sourceProduct[1].getProductType(), sourceProduct[1].getSceneRasterWidth(), sourceProduct[1].getSceneRasterHeight()); ProductUtils.copyProductNodes(sourceProduct[1], targetProduct); boolean includesElevation = false; for (Product aSourceProduct : sourceProduct) { for (Band srcBand : aSourceProduct.getBands()) { final String srcBandName = srcBand.getName(); if (srcBandName.startsWith("i_")) { final FOLDERS folderType = srcBandName.startsWith("i_ifg") ? FOLDERS.DIFF : FOLDERS.RSLC; final String targetBandName = "i_" + extractDate(srcBandName, folderType) + ext[folderType.ordinal()]; final Band targetBand = ProductUtils.copyBand(srcBandName, aSourceProduct, targetBandName, targetProduct, true); tgtBandToInfoMap.put(targetBand, new WriterInfo(folder[folderType.ordinal()], targetBandName, targetProduct)); //System.out.println("copy/add " + srcBandName + " to " + targetBand.getName()); } else if (srcBandName.startsWith("q_")) { // It is necessary to copy the q bands to target product because the product writer will look // for them in the target product final FOLDERS folderType = srcBandName.startsWith("q_ifg") ? FOLDERS.DIFF : FOLDERS.RSLC; final String targetBandName = "q_" + extractDate(srcBandName, folderType) + ext[folderType.ordinal()]; ProductUtils.copyBand(srcBandName, aSourceProduct, targetBandName, targetProduct, true); //System.out.println("copy " + srcBandName + " to " + targetBandName); } else if (srcBandName.startsWith("elevation")) { final String targetBandName = srcBandName + ext[FOLDERS.GEO.ordinal()]; final Band targetBand = ProductUtils.copyBand(srcBandName, aSourceProduct, targetBandName, targetProduct, true); tgtBandToInfoMap.put(targetBand, new WriterInfo(folder[FOLDERS.GEO.ordinal()], targetBandName, targetProduct)); includesElevation = true; //System.out.println("copy/add " + srcBandName + " to " + targetBand.getName()); } } } String projectedDEMName = "projected" + ext[FOLDERS.DEM.ordinal()]; projectedDEM = new ProjectedDEM(projectedDEMName, sourceProduct[0]); projectedDEMInfo = new WriterInfo(folder[FOLDERS.DEM.ordinal()], projectedDEMName, projectedDEM.getTargetProduct()); if (!includesElevation) { throw new OperatorException("Elevation band required. Please add an elevation band to the interferogram product."); } } catch (Throwable t) { throw new OperatorException(t); } } private String convertFormat(String rawDate) { try { final ProductData.UTC utc = ProductData.UTC.parse(rawDate, rawDateFormat); final String date = dateFormat.format(utc.getAsDate()); //System.out.println("rawdate = " + rawDate + " date = " + date); return date; } catch (Exception e) { SystemUtils.LOG.severe("failed to convert date" + e.getMessage()); return rawDate; } } private String extractDate(final String bandName, final FOLDERS folderType) { String dateStr = bandName.substring(bandName.lastIndexOf('_') + 1, bandName.length()); if (folderType.equals(FOLDERS.DIFF)) { String mstStr = bandName.substring(0, bandName.lastIndexOf('_')); String mstDate = mstStr.substring(mstStr.lastIndexOf('_') + 1, mstStr.length()); return convertFormat(mstDate) + '_' + convertFormat(dateStr); } return convertFormat(dateStr); } @Override public void computeTileStack(Map<Band, Tile> targetTiles, Rectangle targetRectangle, ProgressMonitor pm) throws OperatorException { for (Band targetBand : tgtBandToInfoMap.keySet()) { try { final WriterInfo info = tgtBandToInfoMap.get(targetBand); final Tile targetTile = targetTiles.get(targetBand); writeHeader(info); final Rectangle trgRect = targetTile.getRectangle(); final Tile sourceTile = getSourceTile(targetBand, trgRect); final ProductData rawSamples = sourceTile.getRawSamples(); info.productWriter.writeBandRasterData(targetBand, trgRect.x, trgRect.y, trgRect.width, trgRect.height, rawSamples, ProgressMonitor.NULL); } catch (Exception e) { if (e instanceof OperatorException) { throw (OperatorException) e; } else { throw new OperatorException(e); } } } if(!projectedDEMWritten) { writeProjectedDEM(); } } private synchronized void writeProjectedDEM() { if(projectedDEMWritten) return; try { writeHeader(projectedDEMInfo); final Band elevationBand = projectedDEM.getElevationBand(); final Rectangle trgRect = new Rectangle(0, 0, elevationBand.getRasterWidth(), elevationBand.getRasterHeight()); projectedDEM.computeTile(trgRect); final ProductData rawSamples = elevationBand.getData(); projectedDEMInfo.productWriter.writeBandRasterData(elevationBand, trgRect.x, trgRect.y, trgRect.width, trgRect.height, rawSamples, ProgressMonitor.NULL); } catch (Exception e) { e.printStackTrace(); } projectedDEMWritten = true; } private synchronized void writeHeader(final WriterInfo info) throws Exception { if (info.written) return; final File outputFile = targetFolder.toPath().resolve(info.folderName).resolve(info.targetBandName + ".par").toFile(); info.productWriter.writeProductNodes(info.product, outputFile); if (info.folderName.equals("diff0")) { writeBaselineFile(info); } info.written = true; } private void writeBaselineFile(final WriterInfo info) throws Exception { final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(sourceProduct[0]); final double prf = AbstractMetadata.getAttributeDouble(absRoot, AbstractMetadata.pulse_repetition_frequency); final double height = 0.0; final double firstLine = 0.0; final double lastLine = sourceProduct[0].getSceneRasterHeight() - 1; final double refPixel = (sourceProduct[0].getSceneRasterWidth() - 1) / 2.0; final double t0 = firstLine / prf; final double tN = lastLine / prf; InSARStackOverview.IfgStack[] stackOverview = InSARStackOverview.calculateInSAROverview(sourceProduct[0]); String masterDate = info.targetBandName.substring(0, info.targetBandName.indexOf('_')); String slaveDate = info.targetBandName.substring(info.targetBandName.indexOf('_')+1, info.targetBandName.indexOf('.')); // find correct master slave pair int mstIndex = 0, slvIndex = 0; for(int i=0; i < stackOverview.length; ++i) { double mstMJD = stackOverview[i].getMasterSlave()[0].getMasterMetadata().getMjd(); final String mstDate = dateFormat.format(new ProductData.UTC(mstMJD).getAsDate()); if(masterDate.equals(mstDate)) { mstIndex = i; for(int j=0; j< stackOverview[i].getMasterSlave().length; ++j) { double slvMJD = stackOverview[i].getMasterSlave()[j].getSlaveMetadata().getMjd(); final String slvDate = dateFormat.format(new ProductData.UTC(slvMJD).getAsDate()); if (slaveDate.equals(slvDate)) { slvIndex = j; break; } } break; } } final double bh0 = stackOverview[mstIndex].getMasterSlave()[slvIndex].getHorizontalBaseline(firstLine, refPixel, height); final double bhN = stackOverview[mstIndex].getMasterSlave()[slvIndex].getHorizontalBaseline(lastLine, refPixel, height); final double bhm = (bh0 + bhN) * 0.5; final double bhr = (bhN - bh0) / (tN - t0); final double bv0 = stackOverview[mstIndex].getMasterSlave()[slvIndex].getVerticalBaseline(firstLine, refPixel, height); final double bvN = stackOverview[mstIndex].getMasterSlave()[slvIndex].getVerticalBaseline(lastLine, refPixel, height); final double bvm = (bv0 + bvN) * 0.5; final double bvr = (bvN - bv0) / (tN - t0); String baselineFilename = info.targetBandName + ".base"; baselineFilename = baselineFilename.replace(".diff", ""); final File outputBaselineFile = targetFolder.toPath().resolve(info.folderName).resolve(baselineFilename).toFile(); final String oldEOL = System.getProperty("line.separator"); System.setProperty("line.separator", "\n"); final FileOutputStream out = new FileOutputStream(outputBaselineFile); try (final PrintStream p = new PrintStream(out)) { p.println("initial_baseline(TCN)" + ":\t" + "0.0000000" + '\t' + bhm + '\t' + bvm + '\t' + "m m m"); p.println("initial_baseline_rate" + ":\t" + "0.0000000" + '\t' + bhr + '\t' + bvr + '\t' + "m/s m/s m/s"); p.println("precision_baseline(TCN)" + ":\t" + "0.0000000 0.0000000 0.0000000 m m m"); p.println("precision_baseline_rate" + ":\t" + "0.0000000 0.0000000 0.0000000 m/s m/s m/s"); p.println("unwrap_phase_constant" + ":\t" + "0.00000 radians"); p.flush(); } catch (Exception e) { throw new IOException("StampsExportOp unable to write baseline file " + e.getMessage()); } finally { System.setProperty("line.separator", oldEOL); } } @Override public void dispose() { try { for (WriterInfo info : tgtBandToInfoMap.values()) { if (info != null) { if (info.productWriter != null) { info.productWriter.close(); info.productWriter = null; } } } } catch (IOException ignore) { } super.dispose(); } private static class WriterInfo { ProductWriter productWriter; boolean written = false; final String folderName; final String targetBandName; final Product product; WriterInfo(final String folderName, final String targetBandName, final Product product) { this.folderName = folderName; this.targetBandName = targetBandName.startsWith("i_") ? targetBandName.substring(2, targetBandName.length()) : targetBandName; this.product = product; productWriter = ProductIO.getProductWriter(formatName); if (productWriter == null) { throw new OperatorException("No data product writer for the '" + formatName + "' format available"); } productWriter.setIncrementalMode(false); } } public static class Spi extends OperatorSpi { public Spi() { super(StampsExportOp.class); } } }