package org.esa.snap.timeseries.ui.assistant;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import com.bc.ceres.swing.selection.AbstractSelectionChangeListener;
import com.bc.ceres.swing.selection.SelectionChangeEvent;
import org.esa.snap.core.datamodel.CrsGeoCoding;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MapGeoCoding;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductFilter;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.gpf.ui.CollocationCrsForm;
import org.esa.snap.core.gpf.ui.SourceProductSelector;
import org.esa.snap.core.ui.AppContext;
import org.esa.snap.core.ui.assistant.AssistantPage;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.timeseries.core.timeseries.datamodel.ProductLocation;
import org.esa.snap.timeseries.ui.ProductLocationsPaneModel;
import org.esa.snap.util.ProductUtils;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class TimeSeriesAssistantPage_ReprojectingSources extends AbstractTimeSeriesAssistantPage {
private MyCollocationCrsForm collocationCrsForm;
private JTextArea errorText;
TimeSeriesAssistantPage_ReprojectingSources(TimeSeriesAssistantModel model) {
super("Reproject Source Products", model);
}
@Override
public boolean canFinish() {
return false;
}
@Override
public boolean canHelp() {
// @todo
return false;
}
@Override
public boolean validatePage() {
return collocationCrsForm.getCollocationProduct() != null;
}
@Override
public boolean hasNextPage() {
return true;
}
@Override
public AssistantPage getNextPage() {
final Reprojector reprojector = new Reprojector(this.getPageComponent());
reprojector.executeWithBlocking();
return new TimeSeriesAssistantPage_VariableSelection(getAssistantModel());
}
@Override
protected Component createPageComponent() {
final PropertyChangeListener listener = evt -> getContext().updateState();
collocationCrsForm = new MyCollocationCrsForm(SnapApp.getDefault().getAppContext(), listener, getAssistantModel());
collocationCrsForm.addMyChangeListener();
final JPanel pagePanel = new JPanel(new BorderLayout());
final JPanel northPanel = new JPanel(new BorderLayout());
northPanel.add(new JLabel("Use CRS of "), BorderLayout.WEST);
northPanel.add(collocationCrsForm.getCrsUI());
pagePanel.add(northPanel, BorderLayout.NORTH);
final JPanel southPanel = new JPanel(new FlowLayout());
errorText = new JTextArea();
errorText.setBackground(southPanel.getBackground());
final JPanel jPanel = new JPanel();
jPanel.add(errorText);
southPanel.add(errorText);
pagePanel.add(southPanel, BorderLayout.SOUTH);
return pagePanel;
}
private void setErrorMessage(final String message) {
SwingUtilities.invokeLater(() -> errorText.setText(message));
}
private class MyCollocationCrsForm extends CollocationCrsForm {
private SourceProductSelector collocateProductSelector;
private final PropertyChangeListener listener;
private final TimeSeriesAssistantModel assistantModel;
public MyCollocationCrsForm(AppContext appContext, PropertyChangeListener listener, TimeSeriesAssistantModel assistantModel) {
super(appContext);
this.listener = listener;
this.assistantModel = assistantModel;
}
void addMyChangeListener() {
super.addCrsChangeListener(listener);
}
@Override
public CoordinateReferenceSystem getCRS(GeoPos referencePos) {
Product collocationProduct = collocateProductSelector.getSelectedProduct();
if (collocationProduct != null) {
return collocationProduct.getGeoCoding().getMapCRS();
}
return null;
}
@Override
public void prepareShow() {
collocateProductSelector.initProducts();
}
@Override
public void prepareHide() {
collocateProductSelector.releaseProducts();
}
@Override
protected JComponent createCrsComponent() {
collocateProductSelector = new SourceProductSelector(SnapApp.getDefault().getAppContext(), "Product:");
List<Product> products = new ArrayList<>();
for (ProductLocation productLocation : assistantModel.getProductLocationsModel().getProductLocations()) {
for (Product product : productLocation.getProducts(ProgressMonitor.NULL).values()) {
products.add(product);
}
}
collocateProductSelector.setProductFilter(new CollocateProductFilter(products));
collocateProductSelector.addSelectionChangeListener(new AbstractSelectionChangeListener() {
@Override
public void selectionChanged(SelectionChangeEvent event) {
fireCrsChanged();
}
});
final JPanel panel = new JPanel(new BorderLayout(2, 2));
panel.add(collocateProductSelector.getProductNameComboBox(), BorderLayout.CENTER);
panel.add(collocateProductSelector.getProductFileChooserButton(), BorderLayout.EAST);
panel.addPropertyChangeListener("enabled", evt -> {
collocateProductSelector.getProductNameComboBox().setEnabled(panel.isEnabled());
collocateProductSelector.getProductFileChooserButton().setEnabled(panel.isEnabled());
final boolean collocate = getRadioButton().isSelected();
getCrsUI().firePropertyChange("collocate", !collocate, collocate);
});
return panel;
}
public Product getCollocationProduct() {
return collocateProductSelector.getSelectedProduct();
}
public void setReferenceProduct(Product referenceProduct) {
}
}
private class CollocateProductFilter implements ProductFilter {
private final List<Product> products;
public CollocateProductFilter(List<Product> products) {
this.products = products;
}
@Override
public boolean accept(Product collocationProduct) {
for (Product timeSeriesSourceProduct : products) {
if (productsIntersect(timeSeriesSourceProduct, collocationProduct)) {
resetErrorMessage();
return true;
}
}
setErrorMessage("You need to specify a projected product as collocation product.\n" +
"At least one product within the time series needs to intersect the collocation product.");
return false;
}
private void resetErrorMessage() {
setErrorMessage("");
}
private boolean productsIntersect(Product timeSeriesSourceProduct, Product collocationProduct) {
if (collocationProduct.getGeoCoding() == null) {
return false;
}
final GeoCoding geoCoding = collocationProduct.getGeoCoding();
if (geoCoding.canGetGeoPos() && geoCoding.canGetPixelPos()
&& ((geoCoding instanceof CrsGeoCoding)||(geoCoding instanceof MapGeoCoding))) {
final GeneralPath[] sourcePaths = ProductUtils.createGeoBoundaryPaths(timeSeriesSourceProduct);
final GeneralPath[] collocationPaths = ProductUtils.createGeoBoundaryPaths(collocationProduct);
for (GeneralPath sourcePath : sourcePaths) {
for (GeneralPath collocationPath : collocationPaths) {
final Area sourceArea = new Area(sourcePath);
final Area collocationArea = new Area(collocationPath);
collocationArea.intersect(sourceArea);
if (!collocationArea.isEmpty()) {
return true;
}
}
}
}
return false;
}
}
private class Reprojector extends ProgressMonitorSwingWorker<Void, TimeSeriesAssistantModel> {
protected Reprojector(Component parentComponent) {
super(parentComponent, "Reprojecting source products ...");
}
@Override
protected Void doInBackground(ProgressMonitor pm) throws Exception {
reprojectSourceProducts(pm);
return null;
}
private void reprojectSourceProducts(ProgressMonitor pm) {
final ProductLocationsPaneModel productLocationsModel = getAssistantModel().getProductLocationsModel();
final List<ProductLocation> productLocations = productLocationsModel.getProductLocations();
pm.beginTask("Reprojecting...", productLocations.size());
for (ProductLocation productLocation : productLocations) {
final Map<String, Product> products = productLocation.getProducts(ProgressMonitor.NULL);
final Product crsReferenceProduct = getCrsReferenceProduct();
for (Map.Entry<String, Product> productEntry : products.entrySet()) {
final Product product = productEntry.getValue();
if (!product.isCompatibleProduct(crsReferenceProduct, 0.1E-4f)) {
Product reprojectedProduct = createProjectedProduct(product, crsReferenceProduct);
productEntry.setValue(reprojectedProduct);
}
}
pm.worked(1);
}
pm.done();
}
private Product createProjectedProduct(Product toReproject, Product crsReference) {
final Map<String, Product> productMap = getProductMap(toReproject, crsReference);
final Map<String, Object> parameterMap = new HashMap<>();
parameterMap.put("resamplingName", "Nearest");
parameterMap.put("includeTiePointGrids", false);
parameterMap.put("addDeltaBands", false);
// @todo - generalise
final Product reprojectedProduct = GPF.createProduct("Reproject", parameterMap, productMap);
reprojectedProduct.setStartTime(toReproject.getStartTime());
reprojectedProduct.setEndTime(toReproject.getEndTime());
return reprojectedProduct;
}
private Map<String, Product> getProductMap(Product product, Product crsReference) {
final Map<String, Product> productMap = new HashMap<>(2);
productMap.put("source", product);
productMap.put("collocateWith", crsReference);
return productMap;
}
private Product getCrsReferenceProduct() {
return collocationCrsForm.getCollocationProduct();
}
}
}