/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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.snap.ui.product;
import com.bc.ceres.core.ProgressMonitor;
import com.vividsolutions.jts.geom.Point;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.PlacemarkDescriptor;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeListenerAdapter;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.util.math.MathUtils;
import org.opengis.referencing.operation.TransformException;
import javax.swing.table.DefaultTableModel;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
public abstract class AbstractPlacemarkTableModel extends DefaultTableModel {
private final PlacemarkDescriptor placemarkDescriptor;
private Product product;
private Band[] selectedBands;
private TiePointGrid[] selectedGrids;
private final PlacemarkListener placemarkListener;
private final ArrayList<Placemark> placemarkList = new ArrayList<>(10);
protected AbstractPlacemarkTableModel(PlacemarkDescriptor placemarkDescriptor, Product product, Band[] selectedBands,
TiePointGrid[] selectedGrids) {
this.placemarkDescriptor = placemarkDescriptor;
this.product = product;
initSelectedBands(selectedBands);
initSelectedGrids(selectedGrids);
placemarkListener = new PlacemarkListener();
if (product != null) {
product.addProductNodeListener(placemarkListener);
}
initPlacemarkList(product);
}
public Placemark[] getPlacemarks() {
return placemarkList.toArray(new Placemark[placemarkList.size()]);
}
public Placemark getPlacemarkAt(int modelRow) {
return placemarkList.get(modelRow);
}
public PlacemarkDescriptor getPlacemarkDescriptor() {
return placemarkDescriptor;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
if (this.product == product) {
return;
}
if (this.product != null) {
this.product.removeProductNodeListener(placemarkListener);
}
this.product = product;
if (this.product != null) {
this.product.addProductNodeListener(placemarkListener);
}
placemarkList.clear();
initPlacemarkList(this.product);
selectedBands = new Band[0];
selectedGrids = new TiePointGrid[0];
fireTableStructureChanged();
}
public Band[] getSelectedBands() {
return selectedBands;
}
public void setSelectedBands(Band[] selectedBands) {
this.selectedBands = selectedBands != null ? selectedBands : new Band[0];
fireTableStructureChanged();
}
public TiePointGrid[] getSelectedGrids() {
return selectedGrids;
}
public void setSelectedGrids(TiePointGrid[] selectedGrids) {
this.selectedGrids = selectedGrids != null ? selectedGrids : new TiePointGrid[0];
fireTableStructureChanged();
}
public boolean addPlacemark(Placemark placemark) {
if (placemarkList.add(placemark)) {
final int insertedRowIndex = placemarkList.indexOf(placemark);
fireTableRowsInserted(insertedRowIndex, insertedRowIndex);
return true;
}
return false;
}
public boolean removePlacemark(Placemark placemark) {
final int index = placemarkList.indexOf(placemark);
if (index != -1) {
placemarkList.remove(placemark);
fireTableRowsDeleted(index, index);
return true;
}
return false;
}
public void removePlacemarkAt(int index) {
if (placemarkList.size() > index) {
final Placemark placemark = placemarkList.get(index);
removePlacemark(placemark);
}
}
public abstract String[] getStandardColumnNames();
@Override
public abstract boolean isCellEditable(int rowIndex, int columnIndex);
protected abstract Object getStandardColumnValueAt(int rowIndex, int columnIndex);
@Override
public int getRowCount() {
if (placemarkList == null) {
return 0;
}
return placemarkList.size();
}
@Override
public int getColumnCount() {
int count = getStandardColumnNames().length;
if (selectedBands != null) {
count += selectedBands.length;
}
if (selectedGrids != null) {
count += selectedGrids.length;
}
return count;
}
@Override
public String getColumnName(int columnIndex) {
if (columnIndex < getStandardColumnNames().length) {
return getStandardColumnNames()[columnIndex];
}
int newIndex = columnIndex - getStandardColumnNames().length;
if (newIndex < getNumSelectedBands()) {
return selectedBands[newIndex].getName();
}
newIndex -= getNumSelectedBands();
if (selectedGrids != null && newIndex < selectedGrids.length) {
return selectedGrids[newIndex].getName();
}
return "?";
}
@Override
public Class getColumnClass(int columnIndex) {
if (columnIndex >= 0 && columnIndex < getStandardColumnNames().length - 1) {
return Double.class;
}
return Object.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex < getStandardColumnNames().length) {
return getStandardColumnValueAt(rowIndex, columnIndex);
}
final Placemark placemark = placemarkList.get(rowIndex);
int index = columnIndex - getStandardColumnNames().length;
final Object defaultGeometry = placemark.getFeature().getDefaultGeometry();
if (!(defaultGeometry instanceof Point)) {
throw new IllegalStateException("A placemark must have a point feature");
}
final Point point = (Point) defaultGeometry;
Point2D.Double sceneCoords = new Point2D.Double(point.getX(), point.getY());
if (index < getNumSelectedBands()) {
final Band band = selectedBands[index];
final AffineTransform modelToImageTransform;
final Point2D modelCoords;
try {
modelCoords = band.getSceneToModelTransform().transform(sceneCoords, new Point2D.Double());
modelToImageTransform = band.getImageToModelTransform().createInverse();
} catch (NoninvertibleTransformException | TransformException e) {
return "Indeterminate";
}
PixelPos rasterPos = (PixelPos) modelToImageTransform.transform(modelCoords, new PixelPos());
final int x = MathUtils.floorInt(rasterPos.getX());
final int y = MathUtils.floorInt(rasterPos.getY());
final int width = band.getRasterWidth();
final int height = band.getRasterHeight();
if (x < 0 || x >= width || y < 0 || y >= height) {
return "No-data";
}
if (band.isPixelValid(x, y)) {
try {
float[] value = null;
value = band.readPixels(x, y, 1, 1, value, ProgressMonitor.NULL);
return value[0];
} catch (IOException ignored) {
return "I/O-error";
}
} else {
return "NaN";
}
}
index -= getNumSelectedBands();
if (index < selectedGrids.length) {
final TiePointGrid grid = selectedGrids[index];
final AffineTransform modelToImageTransform;
final Point2D modelCoords;
try {
modelCoords = grid.getSceneToModelTransform().transform(sceneCoords, new Point2D.Double());
modelToImageTransform = grid.getImageToModelTransform().createInverse();
} catch (NoninvertibleTransformException | TransformException e) {
return "Indeterminate";
}
PixelPos rasterPos = (PixelPos) modelToImageTransform.transform(modelCoords, new PixelPos());
final int x = MathUtils.floorInt(rasterPos.getX());
final int y = MathUtils.floorInt(rasterPos.getY());
final int width = grid.getRasterWidth();
final int height = grid.getRasterHeight();
if (x < 0 || x >= width || y < 0 || y >= height) {
return "No-data";
}
try {
float[] value = null;
value = grid.readPixels(x, y, 1, 1, value, ProgressMonitor.NULL);
return value[0];
} catch (IOException ignored) {
return "I/O-error";
}
}
return "";
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
if (value == null) {
return;
}
if (columnIndex < getStandardColumnNames().length) {
Placemark placemark = placemarkList.get(rowIndex);
if (columnIndex == 0) {
setPixelPosX(value, placemark);
} else if (columnIndex == 1) {
setPixelPosY(value, placemark);
} else if (columnIndex == 2) {
this.setGeoPosLon(value, placemark);
} else if (columnIndex == 3) {
setGeoPosLat(value, placemark);
} else if (columnIndex == getStandardColumnNames().length - 1) {
String strValue = value.toString();
placemark.setLabel(strValue);
} else {
throw new IllegalStateException(
"Column[" + columnIndex + "] '" + getColumnName(columnIndex) + "' is not editable");
}
}
}
public void dispose() {
if (product != null) {
product.removeProductNodeListener(placemarkListener);
}
selectedBands = null;
selectedGrids = null;
placemarkList.clear();
}
protected void setGeoPosLat(Object lat, Placemark placemark) {
double lon = placemark.getGeoPos() == null ? Double.NaN : placemark.getGeoPos().lon;
placemark.setGeoPos(new GeoPos((Double) lat, lon));
}
protected void setGeoPosLon(Object lon, Placemark placemark) {
double lat = placemark.getGeoPos() == null ? Double.NaN : placemark.getGeoPos().lat;
placemark.setGeoPos(new GeoPos(lat, (Double) lon));
}
protected void setPixelPosY(Object value, Placemark placemark) {
double pixelX = placemark.getPixelPos() == null ? -1 : placemark.getPixelPos().x;
placemark.setPixelPos(new PixelPos(pixelX, (Double) value));
}
protected void setPixelPosX(Object value, Placemark placemark) {
double pixelY = placemark.getPixelPos() == null ? -1 : placemark.getPixelPos().y;
placemark.setPixelPos(new PixelPos((Double) value, pixelY));
}
private void initSelectedBands(Band[] selectedBands) {
this.selectedBands = selectedBands != null ? selectedBands : new Band[0];
}
private void initSelectedGrids(TiePointGrid[] selectedGrids) {
this.selectedGrids = selectedGrids != null ? selectedGrids : new TiePointGrid[0];
}
private void initPlacemarkList(Product product) {
if (product != null) {
Placemark[] placemarks = placemarkDescriptor.getPlacemarkGroup(product).toArray(new Placemark[0]);
placemarkList.addAll(Arrays.asList(placemarks));
}
}
private int getNumSelectedBands() {
return selectedBands != null ? selectedBands.length : 0;
}
private class PlacemarkListener extends ProductNodeListenerAdapter {
@Override
public void nodeChanged(ProductNodeEvent event) {
fireTableDataChanged(event);
}
@Override
public void nodeDataChanged(ProductNodeEvent event) {
if (event.getSourceNode() instanceof Band) {
Band sourceBand = (Band) event.getSourceNode();
if (selectedBands != null) {
for (Band band : selectedBands) {
if (band == sourceBand) {
AbstractPlacemarkTableModel.this.fireTableDataChanged();
return;
}
}
}
}
if (event.getSourceNode() instanceof TiePointGrid) {
TiePointGrid sourceTPG = (TiePointGrid) event.getSourceNode();
if (selectedGrids != null) {
for (TiePointGrid tpg : selectedGrids) {
if (tpg == sourceTPG) {
AbstractPlacemarkTableModel.this.fireTableDataChanged();
return;
}
}
}
}
}
private void fireTableDataChanged(ProductNodeEvent event) {
if (event.getSourceNode() instanceof Placemark) {
Placemark placemark = (Placemark) event.getSourceNode();
// BEAM-1117: VISAT slows down using pins with GCP geo-coded images
final int index = placemarkList.indexOf(placemark);
if (index != -1) {
AbstractPlacemarkTableModel.this.fireTableRowsUpdated(index, index);
}
}
}
}
}