/*
* 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.snap.worldwind.layers;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.event.SelectEvent;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.render.Offset;
import gov.nasa.worldwind.render.PointPlacemark;
import gov.nasa.worldwind.render.PointPlacemarkAttributes;
import gov.nasa.worldwind.render.Polyline;
import gov.nasa.worldwind.render.SurfaceImage;
import org.esa.snap.core.dataio.ProductSubsetDef;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.gpf.GPF;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.engine_utilities.datamodel.AbstractMetadata;
import org.esa.snap.engine_utilities.eo.Constants;
import org.esa.snap.engine_utilities.eo.GeoUtils;
import org.esa.snap.engine_utilities.gpf.InputProductValidator;
import org.esa.snap.rcp.util.Dialogs;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Default Product Layer draws product outline
*/
public class DefaultProductLayer extends BaseLayer implements WWLayer {
private boolean enableSurfaceImages;
private final ConcurrentHashMap<String, Polyline[]> outlineTable = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, SurfaceImage> imageTable = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, PointPlacemark> labelTable = new ConcurrentHashMap<>();
public WorldWindowGLCanvas theWWD = null;
public DefaultProductLayer() {
this.setName("Products");
}
public void setEnableSurfaceImages(final boolean enableSurfaceImages) {
this.enableSurfaceImages = enableSurfaceImages;
}
public String[] getProductNames() {
final List<String> list = new ArrayList<>(outlineTable.keySet());
Collections.sort(list);
return list.toArray(new String[outlineTable.size()]);
}
private static String getUniqueName(final Product product) {
return product.getProductRefString() + product.getName();
}
@Override
public void setOpacity(double opacity) {
super.setOpacity(opacity);
for (Map.Entry<String, SurfaceImage> entry : this.imageTable.entrySet()) {
entry.getValue().setOpacity(opacity);
}
}
public void setOpacity(String name, double opacity) {
final SurfaceImage img = imageTable.get(name);
if (img != null)
img.setOpacity(opacity);
}
public double getOpacity(String name) {
final SurfaceImage img = imageTable.get(name);
if (img != null)
return img.getOpacity();
else {
final Polyline[] lineList = outlineTable.get(name);
return lineList != null ? 1 : 0;
}
}
public void updateInfoAnnotation(final SelectEvent event) {
}
@Override
public void setSelectedProduct(final Product product) {
super.setSelectedProduct(product);
if (selectedProduct != null) {
final String selName = getUniqueName(selectedProduct);
for (String name : outlineTable.keySet()) {
final Polyline[] lineList = outlineTable.get(name);
final boolean highlight = name.equals(selName);
for (Polyline line : lineList) {
line.setHighlighted(highlight);
line.setHighlightColor(Color.RED);
}
}
}
}
public void addProduct(final Product product, WorldWindowGLCanvas wwd) {
theWWD = wwd;
final String name = getUniqueName(product);
if (this.outlineTable.get(name) != null)
return;
final GeoCoding geoCoding = product.getSceneGeoCoding();
if (geoCoding == null) {
final String productType = product.getProductType();
if (productType.equals("ASA_WVW_2P") || productType.equals("ASA_WVS_1P") || productType.equals("ASA_WVI_1P")) {
addWaveProduct(product);
}
} else {
if (enableSurfaceImages) {
final InputProductValidator validator = new InputProductValidator(product);
if (validator.isMapProjected() && product.getSceneGeoCoding() != null) {
addSurfaceImage(product);
}
}
// add outline
addOutline(product);
}
}
private void addSurfaceImage(final Product product) {
final String name = getUniqueName(product);
final SwingWorker worker = new SwingWorker() {
@Override
protected SurfaceImage doInBackground() throws Exception {
try {
final Product newProduct = createSubsampledProduct(product);
final Band band = newProduct.getBandAt(0);
final BufferedImage image = ProductUtils.createRgbImage(new RasterDataNode[]{band},
band.getImageInfo(com.bc.ceres.core.ProgressMonitor.NULL),
com.bc.ceres.core.ProgressMonitor.NULL);
final GeoPos geoPos1 = product.getSceneGeoCoding().getGeoPos(new PixelPos(0, 0), null);
final GeoPos geoPos2 = product.getSceneGeoCoding().getGeoPos(new PixelPos(product.getSceneRasterWidth() - 1,
product.getSceneRasterHeight() - 1),
null
);
final Sector sector = new Sector(Angle.fromDegreesLatitude(geoPos1.getLat()),
Angle.fromDegreesLatitude(geoPos2.getLat()),
Angle.fromDegreesLongitude(geoPos1.getLon()),
Angle.fromDegreesLongitude(geoPos2.getLon()));
final SurfaceImage si = new SurfaceImage(image, sector);
si.setOpacity(getOpacity());
return si;
} catch (Exception e) {
//e.printStackTrace();
}
return null;
}
@Override
public void done() {
try {
if (imageTable.contains(name))
removeImage(name);
final SurfaceImage si = (SurfaceImage) get();
if (si != null) {
addRenderable(si);
imageTable.put(name, si);
}
} catch (Exception e) {
Dialogs.showError(e.getMessage());
}
}
};
worker.execute();
}
private void addOutline(final Product product) {
final int step = Math.max(16, (product.getSceneRasterWidth() + product.getSceneRasterHeight()) / 250);
final GeneralPath[] boundaryPaths = ProductUtils.createGeoBoundaryPaths(product, null, step);
final Polyline[] polyLineList = new Polyline[boundaryPaths.length];
int i = 0;
int numPoints = 0;
float centreLat = 0;
float centreLon = 0;
for (GeneralPath boundaryPath : boundaryPaths) {
final PathIterator it = boundaryPath.getPathIterator(null);
final float[] floats = new float[2];
final List<Position> positions = new ArrayList<>(4);
it.currentSegment(floats);
final Position firstPosition = new Position(Angle.fromDegreesLatitude(floats[1]),
Angle.fromDegreesLongitude(floats[0]), 0.0);
positions.add(firstPosition);
centreLat += floats[1];
centreLon += floats[0];
it.next();
numPoints++;
while (!it.isDone()) {
it.currentSegment(floats);
positions.add(new Position(Angle.fromDegreesLatitude(floats[1]),
Angle.fromDegreesLongitude(floats[0]), 0.0));
centreLat += floats[1];
centreLon += floats[0];
it.next();
numPoints++;
}
// close the loop
positions.add(firstPosition);
centreLat = centreLat / numPoints;
centreLon = centreLon / numPoints;
polyLineList[i] = new Polyline();
polyLineList[i].setFollowTerrain(true);
polyLineList[i].setPositions(positions);
// ADDED
//polyLineList[i].setColor(new Color(1f, 0f, 0f, 0.99f));
//polyLineList[i].setLineWidth(10);
addRenderable(polyLineList[i]);
++i;
}
Position centrePos = new Position(Angle.fromDegreesLatitude(centreLat), Angle.fromDegreesLongitude(centreLon), 0.0);
PointPlacemark ppm = getLabelPlacemark(centrePos, String.valueOf(product.getRefNo()));
ppm.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
ppm.setEnableDecluttering(true);
addRenderable(ppm);
outlineTable.put(getUniqueName(product), polyLineList);
labelTable.put(getUniqueName(product), ppm);
}
private void addWaveProduct(final Product product) {
final MetadataElement root = AbstractMetadata.getOriginalProductMetadata(product);
final MetadataElement ggADS = root.getElement("GEOLOCATION_GRID_ADS");
if (ggADS == null) return;
final MetadataElement[] geoElemList = ggADS.getElements();
final Polyline[] lineList = new Polyline[geoElemList.length];
int cnt = 0;
int numPoints = 0;
float centreLat = 0;
float centreLon = 0;
for (MetadataElement geoElem : geoElemList) {
final double lat = geoElem.getAttributeDouble("center_lat", 0.0) / Constants.oneMillion;
final double lon = geoElem.getAttributeDouble("center_long", 0.0) / Constants.oneMillion;
final double heading = geoElem.getAttributeDouble("heading", 0.0);
final GeoUtils.LatLonHeading r1 = GeoUtils.vincenty_direct(lon, lat, 5000, heading);
final GeoUtils.LatLonHeading corner1 = GeoUtils.vincenty_direct(r1.lon, r1.lat, 2500, heading - 90.0);
final GeoUtils.LatLonHeading corner2 = GeoUtils.vincenty_direct(r1.lon, r1.lat, 2500, heading + 90.0);
final GeoUtils.LatLonHeading r2 = GeoUtils.vincenty_direct(lon, lat, 5000, heading + 180.0);
final GeoUtils.LatLonHeading corner3 = GeoUtils.vincenty_direct(r2.lon, r2.lat, 2500, heading - 90.0);
final GeoUtils.LatLonHeading corner4 = GeoUtils.vincenty_direct(r2.lon, r2.lat, 2500, heading + 90.0);
final List<Position> positions = new ArrayList<>(4);
positions.add(new Position(Angle.fromDegreesLatitude(corner1.lat), Angle.fromDegreesLongitude(corner1.lon), 0.0));
positions.add(new Position(Angle.fromDegreesLatitude(corner2.lat), Angle.fromDegreesLongitude(corner2.lon), 0.0));
positions.add(new Position(Angle.fromDegreesLatitude(corner4.lat), Angle.fromDegreesLongitude(corner4.lon), 0.0));
positions.add(new Position(Angle.fromDegreesLatitude(corner3.lat), Angle.fromDegreesLongitude(corner3.lon), 0.0));
positions.add(new Position(Angle.fromDegreesLatitude(corner1.lat), Angle.fromDegreesLongitude(corner1.lon), 0.0));
centreLat += corner1.lat;
centreLon += corner1.lon;
centreLat += corner2.lat;
centreLon += corner2.lon;
centreLat += corner3.lat;
centreLon += corner3.lon;
centreLat += corner4.lat;
centreLon += corner4.lon;
numPoints += 4;
final Polyline line = new Polyline();
line.setFollowTerrain(true);
line.setPositions(positions);
addRenderable(line);
lineList[cnt++] = line;
}
centreLat = centreLat / numPoints;
centreLon = centreLon / numPoints;
Position centrePos = new Position(Angle.fromDegreesLatitude(centreLat), Angle.fromDegreesLongitude(centreLon), 0.0);
PointPlacemark ppm = getLabelPlacemark(centrePos, String.valueOf(product.getRefNo()));
ppm.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
ppm.setEnableDecluttering(true);
addRenderable(ppm);
outlineTable.put(getUniqueName(product), lineList);
labelTable.put(getUniqueName(product), ppm);
}
private PointPlacemark getLabelPlacemark(Position pos, String label) {
PointPlacemarkAttributes ppmAtttrs = new PointPlacemarkAttributes();
ppmAtttrs.setLabelOffset(new Offset(0.0, 0.0, AVKey.PIXELS, AVKey.PIXELS));
ppmAtttrs.setScale(0.0);
PointPlacemark ppm = new PointPlacemark(pos);
ppm.setAttributes(ppmAtttrs);
ppm.setLabelText(label);
return ppm;
}
public void removeProduct(final Product product) {
removeOutline(getUniqueName(product));
removeImage(getUniqueName(product));
removeLabel(getUniqueName(product));
}
private void removeOutline(String imagePath) {
final Polyline[] lineList = this.outlineTable.get(imagePath);
if (lineList != null) {
for (Polyline line : lineList) {
this.removeRenderable(line);
}
this.outlineTable.remove(imagePath);
}
}
private void removeImage(String imagePath) {
final SurfaceImage si = this.imageTable.get(imagePath);
if (si != null) {
this.removeRenderable(si);
this.imageTable.remove(imagePath);
}
}
private void removeLabel(String imagePath) {
final PointPlacemark ppm = this.labelTable.get(imagePath);
if (ppm != null) {
this.removeRenderable(ppm);
this.labelTable.remove(ppm);
}
}
private static Product createSubsampledProduct(final Product product) throws IOException {
final String quicklookBandName = ProductUtils.findSuitableQuicklookBandName(product);
final ProductSubsetDef productSubsetDef = new ProductSubsetDef("subset");
int scaleFactor = product.getSceneRasterWidth() / 1000;
if (scaleFactor < 1) {
scaleFactor = 1;
}
productSubsetDef.setSubSampling(scaleFactor, scaleFactor);
productSubsetDef.setTreatVirtualBandsAsRealBands(true);
productSubsetDef.setNodeNames(new String[]{quicklookBandName});
Product productSubset = product.createSubset(productSubsetDef, quicklookBandName, null);
final InputProductValidator validator = new InputProductValidator(product);
if (!validator.isMapProjected() && productSubset.getSceneGeoCoding() != null) {
try {
final Map<String, Object> projParameters = new HashMap<>();
Map<String, Product> projProducts = new HashMap<>();
projProducts.put("source", productSubset);
projParameters.put("crs", "WGS84(DD)");
productSubset = GPF.createProduct("Reproject", projParameters, projProducts);
} catch (Exception e) {
e.printStackTrace();
}
}
return productSubset;
}
public JPanel getControlPanel(final WorldWindowGLCanvas wwd) {
final JSlider opacitySlider = new JSlider();
opacitySlider.setMaximum(100);
opacitySlider.setValue((int) (getOpacity() * 100));
opacitySlider.setEnabled(true);
opacitySlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
int value = opacitySlider.getValue();
setOpacity(value / 100d);
wwd.repaint();
}
});
//theSelectedObjectLabel = new JLabel("Selected: ");
final JPanel opacityPanel = new JPanel(new BorderLayout(5, 5));
opacityPanel.add(new JLabel("Opacity"), BorderLayout.WEST);
opacityPanel.add(opacitySlider, BorderLayout.CENTER);
return opacityPanel;
}
}