/* * Copyright (C) 2015 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.utilities.gpf; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.glevel.MultiLevelImage; import org.esa.snap.core.dataio.ProductIO; import org.esa.snap.core.dataio.ProductSubsetBuilder; import org.esa.snap.core.dataio.ProductSubsetDef; import org.esa.snap.core.dataio.ProductWriter; import org.esa.snap.core.dataio.dimap.DimapProductWriter; import org.esa.snap.core.datamodel.Band; 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.SourceProduct; import org.esa.snap.core.gpf.annotations.TargetProduct; import org.esa.snap.core.util.io.FileUtils; import java.awt.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Split a product into several tiles */ @OperatorMetadata(alias = "TileWriter", authors = "Jun Lu, Luis Veci", copyright = "Copyright (C) 2015 by Array Systems Computing Inc.", version = "1.0", description = "Writes a data product to a tiles.", autoWriteDisabled = true, category = "Tools") public class TileWriterOp extends Operator { @TargetProduct private Product targetProduct; @SourceProduct(alias = "source", description = "The source product to be written.") private Product sourceProduct; @Parameter(description = "The output file to which the data product is written.") private File file; @Parameter(defaultValue = ProductIO.DEFAULT_FORMAT_NAME, description = "The name of the output file format.") private String formatName; @Parameter(defaultValue = "Tiles", valueSet = {"Tiles", "Pixels"}, label = "Division by", description = "How to divide the tiles") private String divisionBy = "Tiles"; @Parameter(defaultValue = "4", valueSet = {"2", "4", "9", "16", "36", "64", "100", "256"}, description = "The number of output tiles") private String numberOfTiles = "4"; @Parameter(description = "Tile pixel size", label = "Pixel size X", defaultValue = "200") private int pixelSizeX = 200; @Parameter(description = "Tile pixel size", label = "Pixel size Y", defaultValue = "200") private int pixelSizeY = 200; private final Map<MultiLevelImage, List<Point>> todoLists = new HashMap<>(); private boolean productFileWritten; private SubsetInfo[] subsetInfo = null; public TileWriterOp() { setRequiresAllBands(true); } @Override public void initialize() throws OperatorException { try { targetProduct = sourceProduct; int numFiles, numRows, numCols, width, height; if (divisionBy.equals("Tiles")) { numFiles = Integer.parseInt(numberOfTiles); numRows = (int) Math.sqrt(numFiles); numCols = numRows; width = sourceProduct.getSceneRasterWidth() / numRows; height = sourceProduct.getSceneRasterHeight() / numRows; } else { width = pixelSizeX; height = pixelSizeY; numCols = sourceProduct.getSceneRasterWidth() / width; numRows = sourceProduct.getSceneRasterHeight() / height; numFiles = numRows * numCols; } subsetInfo = new SubsetInfo[numFiles]; int n = 0; for (int r = 0; r < numRows; ++r) { for (int c = 0; c < numCols; ++c) { final ProductSubsetDef subsetDef = new ProductSubsetDef(); subsetDef.addNodeNames(sourceProduct.getTiePointGridNames()); subsetDef.addNodeNames(sourceProduct.getBandNames()); subsetDef.setRegion(c * width, r * height, width, height); subsetDef.setSubSampling(1, 1); subsetDef.setIgnoreMetadata(false); subsetInfo[n] = new SubsetInfo(); subsetInfo[n].subsetBuilder = new ProductSubsetBuilder(); subsetInfo[n].product = subsetInfo[n].subsetBuilder.readProductNodes(sourceProduct, subsetDef); subsetInfo[n].file = new File(file.getParentFile(), createName(file, n + 1)); subsetInfo[n].productWriter = ProductIO.getProductWriter(formatName); if (subsetInfo[n].productWriter == null) { throw new OperatorException("No data product writer for the '" + formatName + "' format available"); } subsetInfo[n].productWriter.setIncrementalMode(false); subsetInfo[n].productWriter.setFormatName(formatName); subsetInfo[n].product.setProductWriter(subsetInfo[n].productWriter); final Band[] bands = subsetInfo[n].product.getBands(); for (Band b : bands) { // b.getSourceImage(); // trigger source image creation } ++n; } } } catch (Throwable t) { throw new OperatorException(t); } } private static String createName(final File file, final int n) { return FileUtils.getFilenameWithoutExtension(file) + '_' + n + FileUtils.getExtension(file); } @Override public void computeTile(Band targetBand, Tile targetTile, ProgressMonitor pm) throws OperatorException { try { synchronized (this) { if (!productFileWritten) { for (SubsetInfo info : subsetInfo) { info.productWriter.writeProductNodes(info.product, info.file); } productFileWritten = true; } } final Rectangle rect = targetTile.getRectangle(); for (SubsetInfo info : subsetInfo) { final Rectangle trgRect = info.subsetBuilder.getSubsetDef().getRegion(); if (rect.intersects(trgRect)) { writeTile(info, targetBand.getName(), trgRect); } } markTileDone(targetBand, targetTile); } catch (Exception e) { if (e instanceof OperatorException) { throw (OperatorException) e; } else { throw new OperatorException(e); } } } private synchronized void writeTile(final SubsetInfo info, final String bandName, final Rectangle trgRect) throws IOException { final Tile sourceTile = getSourceTile(sourceProduct.getBand(bandName), trgRect); final ProductData rawSamples = sourceTile.getRawSamples(); final Band trgBand = info.product.getBand(bandName); info.productWriter.writeBandRasterData(trgBand, 0, 0, trgBand.getRasterWidth(), trgBand.getRasterHeight(), rawSamples, ProgressMonitor.NULL); } private void markTileDone(Band targetBand, Tile targetTile) throws IOException { boolean done; synchronized (todoLists) { MultiLevelImage sourceImage = targetBand.getSourceImage(); final List<Point> currentTodoList = getTodoList(sourceImage); currentTodoList.remove(new Point(sourceImage.XToTileX(targetTile.getMinX()), sourceImage.YToTileY(targetTile.getMinY()))); done = isDone(); } if (done) { // If we get here all tiles are written for (SubsetInfo info : subsetInfo) { if (info.productWriter instanceof DimapProductWriter) { // if we can update the header (only DIMAP) rewrite it! synchronized (info.productWriter) { info.productWriter.writeProductNodes(info.product, info.file); } } } } } private boolean isDone() { for (List<Point> todoList : todoLists.values()) { if (!todoList.isEmpty()) { return false; } } return true; } private List<Point> getTodoList(MultiLevelImage sourceImage) { List<Point> todoList = todoLists.get(sourceImage); if (todoList == null) { final int numXTiles = sourceImage.getNumXTiles(); final int numYTiles = sourceImage.getNumYTiles(); todoList = new ArrayList<>(numXTiles * numYTiles); for (int y = 0; y < numYTiles; y++) { for (int x = 0; x < numXTiles; x++) { todoList.add(new Point(x, y)); } } todoLists.put(sourceImage, todoList); } return todoList; } @Override public void dispose() { try { for (SubsetInfo info : subsetInfo) { info.productWriter.close(); } } catch (IOException ignore) { } todoLists.clear(); super.dispose(); } private static class SubsetInfo { Product product; ProductSubsetBuilder subsetBuilder; File file; ProductWriter productWriter; } public static class Spi extends OperatorSpi { public Spi() { super(TileWriterOp.class); } } }