org.esa.snap.graphbuilder.gpf.ui.worldmap.NestWorldMapPane.java Source code

Java tutorial

Introduction

Here is the source code for org.esa.snap.graphbuilder.gpf.ui.worldmap.NestWorldMapPane.java

Source

/*
 * Copyright (C) 2014 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.graphbuilder.gpf.ui.worldmap;

import com.bc.ceres.glayer.Layer;
import com.bc.ceres.glayer.LayerContext;
import com.bc.ceres.glayer.swing.LayerCanvas;
import com.bc.ceres.glayer.swing.WakefulComponent;
import com.bc.ceres.grender.Rendering;
import com.bc.ceres.grender.Viewport;
import org.apache.commons.math3.util.FastMath;
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.Product;
import org.esa.snap.core.util.ProductUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.ui.ButtonOverlayControl;
import org.esa.snap.ui.UIUtils;
import org.geotools.referencing.crs.DefaultGeographicCRS;

import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

/**
 * This class displays a world map specified by the {@link NestWorldMapPaneDataModel}.
 *
 * @author Marco Peters
 */
public class NestWorldMapPane extends JPanel {

    private LayerCanvas layerCanvas;
    private Layer worldMapLayer;
    private final NestWorldMapPaneDataModel dataModel;
    private boolean navControlShown;
    private WakefulComponent navControlWrapper;

    private final static Color transWhiteColor = new Color(255, 255, 255, 5);
    private final static Color borderWhiteColor = new Color(255, 255, 255, 100);
    private final static Color transRedColor = new Color(255, 0, 0, 30);
    private final static Color borderRedColor = new Color(255, 0, 0, 100);

    private final static Color selectionFillColor = new Color(255, 255, 0, 70);
    private final static Color selectionBorderColor = new Color(255, 255, 0, 255);

    public NestWorldMapPane(NestWorldMapPaneDataModel dataModel) {
        this.dataModel = dataModel;
        try {
            layerCanvas = new LayerCanvas();
            layerCanvas.getModel().getViewport().setModelYAxisDown(false);
            installLayerCanvasNavigation(layerCanvas, dataModel);
            layerCanvas.addOverlay(new BoundaryOverlay());
            final Layer rootLayer = layerCanvas.getLayer();

            final Dimension dimension = new Dimension(400, 150);
            final Viewport viewport = layerCanvas.getViewport();
            viewport.setViewBounds(new Rectangle(dimension));

            setPreferredSize(dimension);
            setSize(dimension);
            setLayout(new BorderLayout());
            add(layerCanvas, BorderLayout.CENTER);

            dataModel.addModelChangeListener(new ModelChangeListener());

            worldMapLayer = dataModel.getWorldMapLayer(new WorldMapLayerContext(rootLayer));
            layerCanvas.getLayer().getChildren().add(worldMapLayer);
            layerCanvas.getViewport().zoom(worldMapLayer.getModelBounds());

            setNavControlVisible(true);
        } catch (Exception e) {
            SnapApp.getDefault().handleError("Error in worldmap initialization", e);
        }

    }

    public LayerCanvas getLayerCanvas() {
        return layerCanvas;
    }

    @Override
    public void doLayout() {
        if (navControlShown && navControlWrapper != null) {
            navControlWrapper.setLocation(getWidth() - navControlWrapper.getWidth() - 4, 4);
        }
        super.doLayout();
    }

    public Product getSelectedProduct() {
        return dataModel.getSelectedProduct();
    }

    public Product[] getProducts() {
        return dataModel.getProducts();
    }

    public float getScale() {
        return (float) layerCanvas.getViewport().getZoomFactor();
    }

    public void setScale(final float scale) {
        if (getScale() != scale) {
            final float oldValue = getScale();
            layerCanvas.getViewport().setZoomFactor(scale);
            firePropertyChange("scale", oldValue, scale);
        }
    }

    public void zoomToProduct(Product product) {
        final GeoPos[][] selGeoBoundaries = dataModel.getSelectedGeoBoundaries();
        if ((product == null || product.getSceneGeoCoding() == null) && selGeoBoundaries.length == 0) {
            return;
        }

        //NESTMOD
        final GeneralPath[] generalPaths;
        if (product != null && product.getSceneGeoCoding() != null) {
            generalPaths = getGeoBoundaryPaths(product);
        } else {
            final ArrayList<GeneralPath> pathList = assemblePathList(selGeoBoundaries[0]);
            generalPaths = pathList.toArray(new GeneralPath[pathList.size()]);
        }

        Rectangle2D modelArea = new Rectangle2D.Double();
        final Viewport viewport = layerCanvas.getViewport();
        for (GeneralPath generalPath : generalPaths) {
            final Rectangle2D rectangle2D = generalPath.getBounds2D();
            if (modelArea.isEmpty()) {
                if (!viewport.isModelYAxisDown()) {
                    modelArea.setFrame(rectangle2D.getX(), rectangle2D.getMaxY(), rectangle2D.getWidth(),
                            rectangle2D.getHeight());
                }
                modelArea = rectangle2D;
            } else {
                modelArea.add(rectangle2D);
            }
        }
        Rectangle2D modelBounds = modelArea.getBounds2D();
        modelBounds.setFrame(modelBounds.getX() - 2, modelBounds.getY() - 2, modelBounds.getWidth() + 4,
                modelBounds.getHeight() + 4);

        modelBounds = cropToMaxModelBounds(modelBounds);

        viewport.zoom(modelBounds);
    }

    /**
     * None API. Don't use this method!
     *
     * @param navControlShown true, if this canvas uses a navigation control.
     */
    public void setNavControlVisible(boolean navControlShown) {
        boolean oldValue = this.navControlShown;
        if (oldValue != navControlShown) {
            if (navControlShown) {
                final ButtonOverlayControl navControl = new ButtonOverlayControl(new ZoomAllAction(),
                        new ZoomToSelectedAction());//, new ZoomToLocationAction());
                navControlWrapper = new WakefulComponent(navControl);
                navControlWrapper.setMinAlpha(0.5f);
                layerCanvas.add(navControlWrapper);
            } else {
                layerCanvas.remove(navControlWrapper);
                navControlWrapper = null;
            }
            validate();
            this.navControlShown = navControlShown;
        }
    }

    private void updateUiState(PropertyChangeEvent evt) {
        if (NestWorldMapPaneDataModel.PROPERTY_LAYER.equals(evt.getPropertyName())) {
            exchangeWorldMapLayer();
        }
        if (NestWorldMapPaneDataModel.PROPERTY_PRODUCTS.equals(evt.getPropertyName())) {
            repaint();
        }
        if (NestWorldMapPaneDataModel.PROPERTY_SELECTED_PRODUCT.equals(evt.getPropertyName())
                || NestWorldMapPaneDataModel.PROPERTY_AUTO_ZOOM_ENABLED.equals(evt.getPropertyName())) {
            final Product selectedProduct = dataModel.getSelectedProduct();
            if (selectedProduct != null && dataModel.isAutoZommEnabled()) {
                zoomToProduct(selectedProduct);
            } else {
                repaint();
            }
        }
        if (NestWorldMapPaneDataModel.PROPERTY_ADDITIONAL_GEO_BOUNDARIES.equals(evt.getPropertyName())
                || NestWorldMapPaneDataModel.PROPERTY_SELECTED_GEO_BOUNDARIES.equals(evt.getPropertyName())) {
            repaint();
        }
    }

    private void exchangeWorldMapLayer() {
        final List<Layer> children = layerCanvas.getLayer().getChildren();
        for (Layer child : children) {
            child.dispose();
        }
        children.clear();
        final Layer rootLayer = layerCanvas.getLayer();
        worldMapLayer = dataModel.getWorldMapLayer(new WorldMapLayerContext(rootLayer));
        children.add(worldMapLayer);
        layerCanvas.getViewport().zoom(worldMapLayer.getModelBounds());
    }

    private Rectangle2D cropToMaxModelBounds(Rectangle2D modelBounds) {
        final Rectangle2D maxModelBounds = worldMapLayer.getModelBounds();
        if (modelBounds.getWidth() >= maxModelBounds.getWidth() - 1
                || modelBounds.getHeight() >= maxModelBounds.getHeight() - 1) {
            modelBounds = maxModelBounds;
        }
        return modelBounds;
    }

    public static GeneralPath[] getGeoBoundaryPaths(Product product) {
        final int step = Math.max(16, (product.getSceneRasterWidth() + product.getSceneRasterHeight()) / 250);
        return ProductUtils.createGeoBoundaryPaths(product, null, step);
    }

    private PixelPos getProductCenter(final Product product) {
        final GeoCoding geoCoding = product.getSceneGeoCoding();
        PixelPos centerPos = null;
        if (geoCoding != null) {
            final float pixelX = (float) Math.floor(0.5f * product.getSceneRasterWidth()) + 0.5f;
            final float pixelY = (float) Math.floor(0.5f * product.getSceneRasterHeight()) + 0.5f;
            final GeoPos geoPos = geoCoding.getGeoPos(new PixelPos(pixelX, pixelY), null);
            final AffineTransform transform = layerCanvas.getViewport().getModelToViewTransform();
            final Point2D point2D = transform.transform(new Point2D.Double(geoPos.getLon(), geoPos.getLat()), null);
            centerPos = new PixelPos((float) point2D.getX(), (float) point2D.getY());
        }
        return centerPos;
    }

    private static void installLayerCanvasNavigation(final LayerCanvas layerCanvas,
            final NestWorldMapPaneDataModel dataModel) {
        MouseHandler mouseHandler = new MouseHandler(layerCanvas, dataModel);
        layerCanvas.addMouseListener(mouseHandler);
        layerCanvas.addMouseMotionListener(mouseHandler);
        layerCanvas.addMouseWheelListener(mouseHandler);
    }

    /**
     * @param bufferedImage is ignored
     * @deprecated since BEAM 4.7, no replacement
     */
    @Deprecated
    @SuppressWarnings({ "UnusedDeclaration" })
    public void setWorldMapImage(java.awt.image.BufferedImage bufferedImage) {
    }

    private class ModelChangeListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            updateUiState(evt);
        }
    }

    public static class MouseHandler extends MouseInputAdapter {

        private final LayerCanvas layerCanvas;
        private final NestWorldMapPaneDataModel dataModel;
        private Point p0;
        private Point.Float selectionStart = new Point.Float();
        private Point.Float selectionEnd = new Point.Float();
        private boolean leftButtonDown = false;

        private MouseHandler(final LayerCanvas layerCanvas, final NestWorldMapPaneDataModel dataModel) {
            this.layerCanvas = layerCanvas;
            this.dataModel = dataModel;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON1) {
                leftButtonDown = true;
                final AffineTransform viewToModelTransform = layerCanvas.getViewport().getViewToModelTransform();
                viewToModelTransform.transform(e.getPoint(), selectionStart);
                dataModel.setSelectionBoxStart(selectionStart.y, selectionStart.x);
                dataModel.setSelectionBoxEnd(selectionStart.y, selectionStart.x);
                layerCanvas.updateUI();
            } else {
                p0 = e.getPoint();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON1) {
                leftButtonDown = false;
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            final Point p = e.getPoint();
            if (leftButtonDown) {
                final AffineTransform viewToModelTransform = layerCanvas.getViewport().getViewToModelTransform();
                viewToModelTransform.transform(e.getPoint(), selectionEnd);
                dataModel.setSelectionBoxEnd(selectionEnd.y, selectionEnd.x);
                layerCanvas.updateUI();
            } else if (p0 != null) {
                final double dx = p.x - p0.x;
                final double dy = p.y - p0.y;
                layerCanvas.getViewport().moveViewDelta(dx, dy);
                p0 = p;
            }
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            final int wheelRotation = e.getWheelRotation();
            final double newZoomFactor = layerCanvas.getViewport().getZoomFactor()
                    * FastMath.pow(1.1, wheelRotation);
            layerCanvas.getViewport().setZoomFactor(newZoomFactor);
        }
    }

    private static class WorldMapLayerContext implements LayerContext {

        private final Layer rootLayer;

        private WorldMapLayerContext(Layer rootLayer) {

            this.rootLayer = rootLayer;
        }

        @Override
        public Object getCoordinateReferenceSystem() {
            return DefaultGeographicCRS.WGS84;
        }

        @Override
        public Layer getRootLayer() {
            return rootLayer;
        }
    }

    private class BoundaryOverlay implements LayerCanvas.Overlay {

        @Override
        public void paintOverlay(LayerCanvas canvas, Rendering rendering) {

            for (final GeoPos[] extraGeoBoundary : dataModel.getAdditionalGeoBoundaries()) {
                drawGeoBoundary(rendering.getGraphics(), extraGeoBoundary, null, null, transWhiteColor,
                        borderWhiteColor);
            }

            for (final GeoPos[] selectGeoBoundary : dataModel.getSelectedGeoBoundaries()) {
                drawGeoBoundary(rendering.getGraphics(), selectGeoBoundary, null, null, transRedColor,
                        borderRedColor);
            }

            final Product selectedProduct = dataModel.getSelectedProduct();
            for (final Product product : dataModel.getProducts()) {
                if (product != null && selectedProduct != product) {
                    drawProduct(rendering.getGraphics(), product, transWhiteColor, Color.WHITE);
                }
            }

            if (selectedProduct != null) {
                drawProduct(rendering.getGraphics(), selectedProduct, transWhiteColor, Color.RED);
            }

            drawGeoBoundary(rendering.getGraphics(), dataModel.getSelectionBox(), null, null, selectionFillColor,
                    selectionBorderColor);
        }

        private void drawProduct(final Graphics2D g2d, final Product product, final Color fillColor,
                final Color borderColor) {
            final GeoCoding geoCoding = product.getSceneGeoCoding();
            if (geoCoding == null) {
                return;
            }

            GeneralPath[] boundaryPaths = getGeoBoundaryPaths(product);
            final String text = String.valueOf(product.getRefNo());
            final PixelPos textCenter = getProductCenter(product);
            drawGeoBoundary(g2d, boundaryPaths, text, textCenter, fillColor, borderColor);
        }

        private void drawGeoBoundary(final Graphics2D g2d, final GeneralPath[] boundaryPaths, final String text,
                final PixelPos textCenter, final Color fillColor, final Color borderColor) {
            final AffineTransform transform = layerCanvas.getViewport().getModelToViewTransform();
            for (GeneralPath boundaryPath : boundaryPaths) {
                boundaryPath.transform(transform);
                drawPath(g2d, boundaryPath, fillColor, borderColor);
            }

            drawText(g2d, text, textCenter, 0.0f);
        }

        private void drawGeoBoundary(final Graphics2D g2d, final GeoPos[] geoBoundary, final String text,
                final PixelPos textCenter, final Color fillColor, final Color borderColor) {
            ProductUtils.normalizeGeoPolygon(geoBoundary);
            final List<GeneralPath> boundaryPaths = assemblePathList(geoBoundary);
            final AffineTransform transform = layerCanvas.getViewport().getModelToViewTransform();
            for (GeneralPath boundaryPath : boundaryPaths) {
                boundaryPath.transform(transform);
                //drawPath(g2d, boundaryPath, fillColor, borderColor);
                g2d.setColor(fillColor);
                g2d.fill(boundaryPath);
                g2d.setColor(borderColor);
                g2d.draw(boundaryPath);
            }
            drawText(g2d, text, textCenter, 0.0f);
        }

        private void drawPath(Graphics2D g2d, final GeneralPath gp, final Color fillColor,
                final Color borderColor) {
            g2d.setColor(fillColor);
            g2d.fill(gp);
            g2d.setColor(borderColor);
            g2d.draw(gp);
        }

        private GeneralPath convertToPixelPath(final GeoPos[] geoBoundary) {
            final GeneralPath gp = new GeneralPath();
            for (int i = 0; i < geoBoundary.length; i++) {
                final GeoPos geoPos = geoBoundary[i];
                final AffineTransform m2vTransform = layerCanvas.getViewport().getModelToViewTransform();
                final Point2D viewPos = m2vTransform.transform(new PixelPos.Double(geoPos.lon, geoPos.lat), null);
                if (i == 0) {
                    gp.moveTo(viewPos.getX(), viewPos.getY());
                } else {
                    gp.lineTo(viewPos.getX(), viewPos.getY());
                }
            }
            gp.closePath();
            return gp;
        }

        private void drawText(Graphics2D g2d, final String text, final PixelPos textCenter, final float offsetX) {
            if (text == null || textCenter == null) {
                return;
            }
            g2d = prepareGraphics2D(offsetX, g2d);
            final FontMetrics fontMetrics = g2d.getFontMetrics();
            final Color color = g2d.getColor();
            g2d.setColor(Color.black);

            g2d.drawString(text, (int) textCenter.x - fontMetrics.stringWidth(text) / 2.0f,
                    (int) textCenter.y + fontMetrics.getAscent() / 2.0f);
            g2d.setColor(color);
        }

        private Graphics2D prepareGraphics2D(final float offsetX, Graphics2D g2d) {
            if (offsetX != 0.0f) {
                g2d = (Graphics2D) g2d.create();
                final AffineTransform transform = g2d.getTransform();
                final AffineTransform offsetTrans = new AffineTransform();
                offsetTrans.setToTranslation(+offsetX, 0);
                transform.concatenate(offsetTrans);
                g2d.setTransform(transform);
            }
            return g2d;
        }

    }

    private class ZoomAllAction extends AbstractAction {

        private ZoomAllAction() {
            putValue(LARGE_ICON_KEY, UIUtils.loadImageIcon("icons/ZoomAll24.gif"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            layerCanvas.getViewport().zoom(worldMapLayer.getModelBounds());
        }
    }

    private class ZoomToSelectedAction extends AbstractAction {

        private ZoomToSelectedAction() {
            putValue(LARGE_ICON_KEY, UIUtils.loadImageIcon("icons/ZoomTo24.gif"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            zoomToProduct(getSelectedProduct());
        }
    }

    private class ZoomToLocationAction extends AbstractAction {

        private ZoomToLocationAction() {
            putValue(LARGE_ICON_KEY,
                    UIUtils.loadImageIcon("/org/esa/nest/icons/define-location-24.png", this.getClass()));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            zoomToProduct(getSelectedProduct());
        }
    }

    public static ArrayList<GeneralPath> assemblePathList(GeoPos[] geoPoints) {
        final GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO, geoPoints.length + 8);
        final ArrayList<GeneralPath> pathList = new ArrayList<>(16);

        if (geoPoints.length > 1) {
            double lon, lat;
            double minLon = 0, maxLon = 0;

            boolean first = true;
            for (GeoPos gp : geoPoints) {
                lon = gp.getLon();
                lat = gp.getLat();
                if (first) {
                    minLon = lon;
                    maxLon = lon;
                    path.moveTo(lon, lat);
                    first = false;
                }
                if (lon < minLon) {
                    minLon = lon;
                }
                if (lon > maxLon) {
                    maxLon = lon;
                }
                path.lineTo(lon, lat);
            }
            path.closePath();

            int runIndexMin = (int) Math.floor((minLon + 180) / 360);
            int runIndexMax = (int) Math.floor((maxLon + 180) / 360);

            if (runIndexMin == 0 && runIndexMax == 0) {
                // the path is completely within [-180, 180] longitude
                pathList.add(path);
                return pathList;
            }

            final Area pathArea = new Area(path);
            final GeneralPath pixelPath = new GeneralPath(GeneralPath.WIND_NON_ZERO);
            for (int k = runIndexMin; k <= runIndexMax; k++) {
                final Area currentArea = new Area(
                        new Rectangle2D.Float(k * 360.0f - 180.0f, -90.0f, 360.0f, 180.0f));
                currentArea.intersect(pathArea);
                if (!currentArea.isEmpty()) {
                    pathList.add(areaToPath(currentArea, -k * 360.0, pixelPath));
                }
            }
        }
        return pathList;
    }

    public static GeneralPath areaToPath(final Area negativeArea, final double deltaX,
            final GeneralPath pixelPath) {

        final float[] floats = new float[6];
        // move to correct rectangle
        final AffineTransform transform = AffineTransform.getTranslateInstance(deltaX, 0.0);
        final PathIterator iterator = negativeArea.getPathIterator(transform);

        while (!iterator.isDone()) {
            final int segmentType = iterator.currentSegment(floats);
            if (segmentType == PathIterator.SEG_LINETO) {
                pixelPath.lineTo(floats[0], floats[1]);
            } else if (segmentType == PathIterator.SEG_MOVETO) {
                pixelPath.moveTo(floats[0], floats[1]);
            } else if (segmentType == PathIterator.SEG_CLOSE) {
                pixelPath.closePath();
            }
            iterator.next();
        }
        return pixelPath;
    }
}