/* * 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.rcp.actions.interactors; import com.bc.ceres.glayer.swing.LayerCanvas; import com.bc.ceres.grender.Rendering; import com.bc.ceres.grender.Viewport; import com.bc.ceres.swing.figure.ViewportInteractor; 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.dataop.maptransf.Ellipsoid; import org.esa.snap.core.util.math.MathUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.ModalDialog; import org.esa.snap.ui.product.ProductSceneView; import org.openide.util.ImageUtilities; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; /** * A tool representing the range finder. * * @author Sabine Embacher * @author Ralf Quast * @author Marco Zuehlke */ class RangeFinderInteractor extends ViewportInteractor { public static final String TITLE = "Range Finder Tool"; private static class ModelPoint extends Point2D.Double { private static ModelPoint create(Viewport viewport, Point point) { return new ModelPoint(viewport.getViewToModelTransform().transform(point, new Double())); } private ModelPoint() { super(); } private ModelPoint(Point2D p) { super(p.getX(), p.getY()); } private Point toViewPoint(Viewport viewport) { return (Point) viewport.getModelToViewTransform().transform(this, new Point()); } } private final List<ModelPoint> modelPointList; private final ModelPoint currentModelPoint; private final Cursor cursor; private RangeFinderOverlay overlay; public RangeFinderInteractor() { modelPointList = new ArrayList<>(); currentModelPoint = new ModelPoint(); ImageIcon cursorIcon = ImageUtilities.loadImageIcon("org/esa/snap/rcp/cursors/RangeFinder.gif", false); cursor = createRangeFinderCursor(cursorIcon); } @Override public Cursor getCursor() { return cursor; } @Override public void mouseDragged(MouseEvent e) { handleDragAndMove(e); } @Override public void mouseMoved(MouseEvent e) { handleDragAndMove(e); } @Override public void mouseClicked(MouseEvent e) { final ProductSceneView view = getProductSceneView(e); if (view == null) { return; } if (overlay != null && view != overlay.view) { removeOverlay(); } if (overlay == null) { createOverlay(view); } if (e.getClickCount() == 1) { final Point viewPoint = e.getPoint(); final ModelPoint modelPoint = ModelPoint.create(view.getViewport(), viewPoint); modelPointList.add(modelPoint); currentModelPoint.setLocation(modelPoint); overlay.repaint(); } if (e.getClickCount() == 2 && modelPointList.size() > 0) { showDetailsDialog(view); modelPointList.clear(); removeOverlay(); } } private void handleDragAndMove(MouseEvent e) { if (modelPointList.size() > 0 && overlay != null) { final ProductSceneView view = getProductSceneView(e); if (view != null) { final Point viewPoint = e.getPoint(); final ModelPoint modelPoint = ModelPoint.create(view.getViewport(), viewPoint); currentModelPoint.setLocation(modelPoint); overlay.repaint(); } } } private void createOverlay(ProductSceneView view) { overlay = new RangeFinderOverlay(view); view.getLayerCanvas().addOverlay(overlay); } private void removeOverlay() { overlay.view.getLayerCanvas().removeOverlay(overlay); overlay = null; } private ProductSceneView getProductSceneView(MouseEvent event) { final Component eventComponent = event.getComponent(); if (eventComponent instanceof ProductSceneView) { return (ProductSceneView) eventComponent; } final Container parentComponent = eventComponent.getParent(); if (parentComponent instanceof ProductSceneView) { return (ProductSceneView) parentComponent; } // Case: Scroll bars are displayed if (parentComponent.getParent() instanceof ProductSceneView) { return (ProductSceneView) parentComponent.getParent(); } return null; } private void showDetailsDialog(ProductSceneView view) { //todo [multisize_products] ask for scenerastertransform instead of geocoding GeoCoding geoCoding = view.getRaster().getGeoCoding(); if (geoCoding == null) { Dialogs.showInformation(TITLE, String.format("No geo-coding information for %s.", view.getRaster().getName()), null); return; } float distance = 0; float distanceError = 0; final AffineTransform m2i = view.getBaseImageLayer().getModelToImageTransform(); final Point imagePoint1 = new Point(); final Point imagePoint2 = new Point(); final DistanceData[] distanceData = new DistanceData[modelPointList.size() - 1]; for (int i = 0; i < distanceData.length; i++) { m2i.transform(modelPointList.get(i), imagePoint1); m2i.transform(modelPointList.get(i + 1), imagePoint2); final DistanceData segmentData = new DistanceData(geoCoding, imagePoint1, imagePoint2); distance += segmentData.distance; distanceError += segmentData.distanceError; distanceData[i] = segmentData; } final JButton detailsButton = new JButton("Details..."); detailsButton.addActionListener(e -> { final Window parentWindow = SwingUtilities.getWindowAncestor(detailsButton); createDetailsDialog(parentWindow, distanceData).show(); }); final JPanel buttonPane = new JPanel(new BorderLayout()); buttonPane.add(detailsButton, BorderLayout.EAST); final JPanel messagePane = new JPanel(new BorderLayout(0, 6)); messagePane.add(new JLabel("Distance: " + distance + " +/- " + distanceError + " km")); messagePane.add(buttonPane, BorderLayout.SOUTH); JOptionPane.showMessageDialog(SnapApp.getDefault().getMainFrame(), messagePane, TITLE, JOptionPane.INFORMATION_MESSAGE); } private static ModalDialog createDetailsDialog(final Window parentWindow, final DistanceData[] dds) { float distance = 0; float distanceError = 0; final StringBuilder message = new StringBuilder(); for (int i = 0; i < dds.length; i++) { final DistanceData dd = dds[i]; distance += dd.distance; distanceError += dd.distanceError; message.append( "Distance between points " + i + " to " + (i + 1) + " in pixels:\n" + "XH[" + dd.xH + "] to XN[" + dd.xN + "]: " + Math.abs(dd.xH - dd.xN) + "\n" + "YH[" + dd.yH + "] to YN[" + dd.yN + "]: " + Math.abs(dd.yH - dd.yN) + "\n" + "\n" + "LonH: " + dd.lonH + " LatH: " + dd.latH + "\n" + "LonN: " + dd.lonN + " LatN: " + dd.latN + "\n" + "\n" + "LamH: " + dd.lamH + " PhiH: " + dd.phiH + "\n" + "LamN: " + dd.lamN + " PhiN: " + dd.phiN + "\n" + "\n" + "Mean earth radius used: " + DistanceData.MEAN_EARTH_RADIUS_KM + " km" + "\n" + "\n" + "Distance: " + dd.distance + " +/- " + dd.distanceError + " km\n" + "\n\n" ); } message.insert(0, "Total distance: " + distance + " +/- " + distanceError + " km\n" + "\n" + "computed as described below:\n\n"); final JScrollPane content = new JScrollPane(new JTextArea(message.toString())); content.setPreferredSize(new Dimension(300, 150)); final ModalDialog detailsWindow = new ModalDialog(parentWindow, TITLE + " - Details", ModalDialog.ID_OK, null); detailsWindow.setContent(content); return detailsWindow; } private static Cursor createRangeFinderCursor(ImageIcon cursorIcon) { Toolkit defaultToolkit = Toolkit.getDefaultToolkit(); final String cursorName = "rangeFinder"; // this is necessary because on some systems the cursor is scaled but not the 'hot spot' Dimension bestCursorSize = defaultToolkit.getBestCursorSize(cursorIcon.getIconWidth(), cursorIcon.getIconHeight()); Point hotSpot = new Point((7 * bestCursorSize.width) / cursorIcon.getIconWidth(), (7 * bestCursorSize.height) / cursorIcon.getIconHeight()); return defaultToolkit.createCustomCursor(cursorIcon.getImage(), hotSpot, cursorName); } private class RangeFinderOverlay implements LayerCanvas.Overlay { private final ProductSceneView view; RangeFinderOverlay(ProductSceneView view) { this.view = view; } void repaint() { view.getLayerCanvas().repaint(); } @Override public void paintOverlay(LayerCanvas canvas, Rendering rendering) { if (modelPointList.size() == 0) { return; } Graphics2D g2d = rendering.getGraphics(); final Stroke strokeOld = g2d.getStroke(); g2d.setStroke(new BasicStroke(0.1f)); final Color colorOld = g2d.getColor(); g2d.setColor(Color.red); g2d.translate(0.5, 0.5); final int r = 3; final int r2 = r * 2; Point viewPoint1 = null; Point viewPoint2 = null; final Viewport viewport = canvas.getViewport(); for (final ModelPoint modelPoint : modelPointList) { viewPoint1 = modelPoint.toViewPoint(viewport); g2d.drawOval(viewPoint1.x - r, viewPoint1.y - r, r2, r2); g2d.drawLine(viewPoint1.x, viewPoint1.y - r2, viewPoint1.x, viewPoint1.y - r); g2d.drawLine(viewPoint1.x, viewPoint1.y + r2, viewPoint1.x, viewPoint1.y + r); g2d.drawLine(viewPoint1.x - r2, viewPoint1.y, viewPoint1.x - r, viewPoint1.y); g2d.drawLine(viewPoint1.x + r2, viewPoint1.y, viewPoint1.x + r, viewPoint1.y); if (viewPoint2 != null) { g2d.drawLine(viewPoint1.x, viewPoint1.y, viewPoint2.x, viewPoint2.y); } viewPoint2 = viewPoint1; } if (viewPoint1 != null) { viewPoint2 = currentModelPoint.toViewPoint(canvas.getViewport()); g2d.drawLine(viewPoint1.x, viewPoint1.y, viewPoint2.x, viewPoint2.y); } g2d.translate(-0.5, -0.5); g2d.setStroke(strokeOld); g2d.setColor(colorOld); } } private static class DistanceData { final static double MIN_EARTH_RADIUS = Ellipsoid.WGS_84.getSemiMinor(); final static double MAX_EARTH_RADIUS = Ellipsoid.WGS_84.getSemiMajor(); final static double MEAN_EARTH_RADIUS_M = 6371000; final static double MEAN_EARTH_RADIUS_KM = MEAN_EARTH_RADIUS_M * 0.001; final static double MEAN_ERROR_FACTOR = MIN_EARTH_RADIUS / MAX_EARTH_RADIUS; final int xH; final int yH; final int xN; final int yN; final double lonH; final double latH; final double lonN; final double latN; final double lamH; final double phiH; final double lamN; final double phiN; final double distance; final double distanceError; public DistanceData(GeoCoding geoCoding, final Point pH, final Point pN) { this.xH = pH.x; this.yH = pH.y; this.xN = pN.x; this.yN = pN.y; final GeoPos geoPosH = geoCoding.getGeoPos(new PixelPos(xH, yH), null); final GeoPos geoPosN = geoCoding.getGeoPos(new PixelPos(xN, yN), null); this.lonH = geoPosH.getLon(); this.latH = geoPosH.getLat(); this.lonN = geoPosN.getLon(); this.latN = geoPosN.getLat(); this.lamH = (MathUtils.DTOR * lonH); this.phiH = (MathUtils.DTOR * latH); this.lamN = (MathUtils.DTOR * lonN); this.phiN = (MathUtils.DTOR * latN); this.distance = MathUtils.sphereDistance(MEAN_EARTH_RADIUS_KM, lamH, phiH, lamN, phiN); this.distanceError = distance * (1 - MEAN_ERROR_FACTOR); } } }