/*
* Copyright (C) 2011 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.rcp.actions.vector;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
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.PixelPos;
import org.esa.snap.core.datamodel.PlacemarkDescriptor;
import org.esa.snap.core.datamodel.PlacemarkDescriptorRegistry;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.VectorDataNode;
import org.esa.snap.core.util.FeatureUtils;
import org.esa.snap.core.util.io.CsvReader;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.actions.AbstractSnapAction;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.product.ProductSceneView;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.Action;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import static org.esa.snap.rcp.SnapApp.SelectionSourceHint.*;
//import org.esa.snap.visat.VisatApp;
/**
* Action that lets a user load text files that contain data associated with a geographic position,
* e.g. some kind of track. The format is:
* <pre>
* lat-1 TAB lon-1 TAB data-1 NEWLINE
* lat-2 TAB lon-2 TAB data-2 NEWLINE
* lat-3 TAB lon-3 TAB data-3 NEWLINE
* ...
* lat-n TAB lon-n TAB data-n NEWLINE
* </pre>
* <p>
* This is the format that is also used by SeaDAS 6.x in order to import ship tracks.
*
* @author Norman Fomferra
* @since BEAM 4.10
*/
@ActionID(
category = "File",
id = "ImportTrackAction"
)
@ActionRegistration(
displayName = "#CTL_ImportSeadasTrackActionName",
lazy = false
)
@ActionReferences({
@ActionReference(path = "Menu/File/Import/Vector Data", position = 50),
@ActionReference(path = "Menu/Vector/Import")
})
@NbBundle.Messages({
"CTL_ImportSeadasTrackActionText=SeaDAS 6.x Track",
"CTL_ImportSeadasTrackActionName=Import SeaDAS Track",
"CTL_ImportSeadasTrackActionHelp=importSeadasTrack"
})
public class ImportTrackAction extends AbstractSnapAction implements ContextAwareAction, LookupListener {
private Lookup lookup;
private final Lookup.Result<Product> result;
public ImportTrackAction() {
this(Utilities.actionsGlobalContext());
}
public ImportTrackAction(Lookup lookup) {
this.lookup = lookup;
result = lookup.lookupResult(Product.class);
result.addLookupListener(
WeakListeners.create(LookupListener.class, this, result));
setEnableState();
setHelpId(Bundle.CTL_ImportSeadasTrackActionHelp());
putValue(Action.NAME, Bundle.CTL_ImportSeadasTrackActionText());
putValue(Action.SHORT_DESCRIPTION, Bundle.CTL_ImportSeadasTrackActionName());
}
@Override
public Action createContextAwareInstance(Lookup lookup) {
return new ImportTrackAction(lookup);
}
@Override
public void actionPerformed(ActionEvent ae) {
final File file =
Dialogs.requestFileForOpen(Bundle.CTL_ImportSeadasTrackActionName(), false, null, "importTrack.lastDir");
if (file == null) {
return;
}
final Product product = SnapApp.getDefault().getSelectedProduct(AUTO);
FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection;
try {
featureCollection = readTrack(file, product.getSceneGeoCoding());
} catch (IOException e) {
Dialogs.showError(Bundle.CTL_ImportSeadasTrackActionName(), "Failed to load track file:\n" + e.getMessage());
return;
}
if (featureCollection.isEmpty()) {
Dialogs.showError(Bundle.CTL_ImportSeadasTrackActionName(), "No records found.");
return;
}
String name = FileUtils.getFilenameWithoutExtension(file);
final PlacemarkDescriptor placemarkDescriptor =
PlacemarkDescriptorRegistry.getInstance().getPlacemarkDescriptor(featureCollection.getSchema());
placemarkDescriptor.setUserDataOf(featureCollection.getSchema());
VectorDataNode vectorDataNode = new VectorDataNode(name, featureCollection, placemarkDescriptor);
product.getVectorDataGroup().add(vectorDataNode);
final ProductSceneView view = SnapApp.getDefault().getSelectedProductSceneView();
if (view != null) {
view.setLayersVisible(vectorDataNode);
}
}
@Override
public void resultChanged(LookupEvent lookupEvent) {
setEnableState();
}
private void setEnableState() {
boolean state = false;
ProductNode productNode = lookup.lookup(ProductNode.class);
if (productNode != null) {
Product product = productNode.getProduct();
state = product != null && product.getSceneGeoCoding() != null;
}
setEnabled(state);
}
private static FeatureCollection<SimpleFeatureType, SimpleFeature> readTrack(File file, GeoCoding geoCoding) throws IOException {
Reader reader = new FileReader(file);
try {
return readTrack(reader, geoCoding);
} finally {
reader.close();
}
}
static FeatureCollection<SimpleFeatureType, SimpleFeature> readTrack(Reader reader, GeoCoding geoCoding) throws IOException {
CsvReader csvReader = new CsvReader(reader, new char[]{'\t', ' '}, true, "#");
SimpleFeatureType trackFeatureType = createTrackFeatureType(geoCoding);
FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection = new ListFeatureCollection(trackFeatureType);
double[] record;
int pointIndex = 0;
while ((record = csvReader.readDoubleRecord()) != null) {
if (record.length < 3) {
throw new IOException("Illegal track file format.\n" +
"Expecting tab-separated lines containing 3 values: lat, lon, data.");
}
float lat = (float) record[0];
float lon = (float) record[1];
double data = record[2];
final SimpleFeature feature = createFeature(trackFeatureType, geoCoding, pointIndex, lat, lon, data);
if (feature != null) {
featureCollection.add(feature);
}
pointIndex++;
}
if (featureCollection.isEmpty()) {
throw new IOException("No track point found or all of them are located outside the scene boundaries.");
}
final CoordinateReferenceSystem mapCRS = geoCoding.getMapCRS();
if (!mapCRS.equals(DefaultGeographicCRS.WGS84)) {
try {
transformFeatureCollection(featureCollection, mapCRS);
} catch (TransformException e) {
throw new IOException("Cannot transform the ship track onto CRS '" + mapCRS.toWKT() + "'.", e);
}
}
return featureCollection;
}
private static void transformFeatureCollection(FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection, CoordinateReferenceSystem targetCRS) throws TransformException {
final GeometryCoordinateSequenceTransformer transform = FeatureUtils.getTransform(DefaultGeographicCRS.WGS84, targetCRS);
final FeatureIterator<SimpleFeature> features = featureCollection.features();
final GeometryFactory geometryFactory = new GeometryFactory();
while (features.hasNext()) {
final SimpleFeature simpleFeature = features.next();
final Point sourcePoint = (Point) simpleFeature.getDefaultGeometry();
final Point targetPoint = transform.transformPoint(sourcePoint, geometryFactory);
simpleFeature.setDefaultGeometry(targetPoint);
}
}
private static SimpleFeatureType createTrackFeatureType(GeoCoding geoCoding) {
SimpleFeatureTypeBuilder ftb = new SimpleFeatureTypeBuilder();
ftb.setName("org.esa.snap.TrackPoint");
/*0*/
ftb.add("pixelPos", Point.class, geoCoding.getImageCRS());
/*1*/
ftb.add("geoPos", Point.class, DefaultGeographicCRS.WGS84);
/*2*/
ftb.add("data", Double.class);
ftb.setDefaultGeometry(geoCoding instanceof CrsGeoCoding ? "geoPos" : "pixelPos");
// GeoTools Bug: this doesn't work
// ftb.userData("trackPoints", "true");
final SimpleFeatureType ft = ftb.buildFeatureType();
ft.getUserData().put("trackPoints", "true");
return ft;
}
private static SimpleFeature createFeature(SimpleFeatureType type, GeoCoding geoCoding, int pointIndex, float lat, float lon, double data) {
PixelPos pixelPos = geoCoding.getPixelPos(new GeoPos(lat, lon), null);
if (!pixelPos.isValid()) {
return null;
}
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(type);
GeometryFactory gf = new GeometryFactory();
/*0*/
fb.add(gf.createPoint(new Coordinate(pixelPos.x, pixelPos.y)));
/*1*/
fb.add(gf.createPoint(new Coordinate(lon, lat)));
/*2*/
fb.add(data);
return fb.buildFeature(String.format("ID%08d", pointIndex));
}
}