/*
* Copyright (C) 2012 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.binding.Property;
import com.bc.ceres.binding.PropertyContainer;
import com.bc.ceres.core.Assert;
import com.bc.ceres.glayer.Layer;
import com.bc.ceres.glayer.LayerContext;
import com.bc.ceres.glayer.LayerFilter;
import com.bc.ceres.glayer.LayerType;
import com.bc.ceres.glayer.LayerTypeRegistry;
import com.bc.ceres.glayer.support.AbstractLayerListener;
import com.bc.ceres.glayer.support.ImageLayer;
import com.bc.ceres.glayer.support.LayerUtils;
import com.bc.ceres.glayer.swing.AdjustableViewScrollPane;
import com.bc.ceres.glayer.swing.LayerCanvas;
import com.bc.ceres.glevel.MultiLevelSource;
import com.bc.ceres.grender.Rendering;
import com.bc.ceres.grender.Viewport;
import com.bc.ceres.grender.ViewportAware;
import com.bc.ceres.grender.support.DefaultViewport;
import com.bc.ceres.swing.figure.Figure;
import com.bc.ceres.swing.figure.FigureChangeListener;
import com.bc.ceres.swing.figure.FigureCollection;
import com.bc.ceres.swing.figure.FigureEditor;
import com.bc.ceres.swing.figure.FigureEditorAware;
import com.bc.ceres.swing.figure.FigureSelection;
import com.bc.ceres.swing.figure.FigureStyle;
import com.bc.ceres.swing.figure.Handle;
import com.bc.ceres.swing.figure.ShapeFigure;
import com.bc.ceres.swing.selection.AbstractSelectionChangeListener;
import com.bc.ceres.swing.selection.Selection;
import com.bc.ceres.swing.selection.SelectionChangeEvent;
import com.bc.ceres.swing.selection.SelectionContext;
import com.bc.ceres.swing.undo.UndoContext;
import com.bc.ceres.swing.undo.support.DefaultUndoContext;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.ImageInfo;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.PlacemarkGroup;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.ProductNodeListener;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.VectorDataNode;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.image.ColoredMaskImageMultiLevelSource;
import org.esa.snap.core.layer.GraticuleLayer;
import org.esa.snap.core.layer.MaskCollectionLayer;
import org.esa.snap.core.layer.NoDataLayerType;
import org.esa.snap.core.layer.ProductLayerContext;
import org.esa.snap.core.util.PropertyMap;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.ui.BasicView;
import org.esa.snap.ui.PixelPositionListener;
import org.esa.snap.ui.PopupMenuHandler;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.tool.ToolButtonFactory;
import org.opengis.referencing.operation.TransformException;
import org.openide.util.Utilities;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputListener;
import javax.swing.undo.UndoManager;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
/**
* The class {@code ProductSceneView} is a high-level image display component for color index/RGB images created
* from one or more raster datasets of a data product.
* <p>
* <p>It is also capable of displaying a graticule (geographical grid) and a ROI associated with a displayed raster
* dataset.
*
* @author Norman Fomferra
*/
public class ProductSceneView extends BasicView
implements FigureEditorAware, ProductNodeView, PropertyChangeListener, ProductLayerContext, ViewportAware {
public static final String BASE_IMAGE_LAYER_ID = "org.esa.snap.layers.baseImage";
public static final String NO_DATA_LAYER_ID = "org.esa.snap.layers.noData";
public static final String VECTOR_DATA_LAYER_ID = VectorDataCollectionLayer.ID;
public static final String MASKS_LAYER_ID = MaskCollectionLayer.ID;
public static final String GRATICULE_LAYER_ID = "org.esa.snap.layers.graticule";
/**
* Property name for the pixel border
*/
public static final String PREFERENCE_KEY_PIXEL_BORDER_SHOWN = "pixel.border.shown";
/**
* Name of property which switches display of af a navigation control in the image view.
*/
public static final String PREFERENCE_KEY_IMAGE_NAV_CONTROL_SHOWN = "image.navControlShown";
/**
* Name of property which switches display of af a navigation control in the image view.
*/
public static final String PREFERENCE_KEY_IMAGE_SCROLL_BARS_SHOWN = "image.scrollBarsShown";
/**
* Name of property which inverts the zooming with the mouse wheel.
*/
public static final String PREFERENCE_KEY_INVERT_ZOOMING = "image.reverseZooming";
/**
* Name of property of image info
*/
public static final String PROPERTY_NAME_IMAGE_INFO = "imageInfo";
/**
* Name of property of selected layer
*/
public static final String PROPERTY_NAME_SELECTED_LAYER = "selectedLayer";
/**
* Name of property of selected pin
*/
public static final String PROPERTY_NAME_SELECTED_PIN = "selectedPin";
public static final Color DEFAULT_IMAGE_BACKGROUND_COLOR = new Color(51, 51, 51);
private ProductSceneImage sceneImage;
private LayerCanvas layerCanvas;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Properties corresponding to the base image displaying the raster data returned by #getRaster()
//
// layer which displays the base image
private final ImageLayer baseImageLayer;
// current resolution level of the base image
private int currentLevel = 0;
// current pixel X (from mouse cursor) at current resolution level of the base image
private int currentLevelPixelX = -1;
// current pixel Y (from mouse cursor) at current resolution level of the base image
private int currentLevelPixelY = -1;
// current pixel X (from mouse cursor) at highest resolution level of the base image
private int currentPixelX = -1;
// current pixel Y (from mouse cursor) at highest resolution level of the base image
private int currentPixelY = -1;
// display properties for the current pixel (from mouse cursor)
private boolean pixelBorderShown; // can it be shown?
private boolean pixelBorderDrawn; // has it been drawn?
private double pixelBorderViewScale;
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private final Vector<PixelPositionListener> pixelPositionListeners;
private Layer selectedLayer;
private ComponentAdapter layerCanvasComponentHandler;
private LayerCanvasMouseHandler layerCanvasMouseHandler;
private RasterChangeHandler rasterChangeHandler;
private boolean scrollBarsShown;
private AdjustableViewScrollPane scrollPane;
private UndoContext undoContext;
private VectorDataFigureEditor figureEditor;
public ProductSceneView(ProductSceneImage sceneImage) {
this(sceneImage, new UndoManager());
}
public ProductSceneView(ProductSceneImage sceneImage, UndoManager undoManager) {
Assert.notNull(sceneImage, "sceneImage");
setOpaque(true);
setLayout(new BorderLayout());
// todo - use sceneImage.getConfiguration() (nf, 18.09.2008)
setBackground(DEFAULT_IMAGE_BACKGROUND_COLOR);
this.pixelBorderShown = sceneImage.getConfiguration().getPropertyBool(PREFERENCE_KEY_PIXEL_BORDER_SHOWN, true);
this.sceneImage = sceneImage;
this.baseImageLayer = sceneImage.getBaseImageLayer();
this.pixelBorderViewScale = 2.0;
this.pixelPositionListeners = new Vector<>();
undoContext = new DefaultUndoContext(this, undoManager);
DefaultViewport viewport = new DefaultViewport(isModelYAxisDown(baseImageLayer));
final Layer rootLayer = sceneImage.getRootLayer();
this.layerCanvas = new LayerCanvas(rootLayer, viewport);
rootLayer.addListener(new AbstractLayerListener() {
@Override
public void handleLayersRemoved(Layer parentLayer, Layer[] childLayers) {
for (Layer childLayer : childLayers) {
if (childLayer == selectedLayer) {
setSelectedLayer(null);
return;
}
}
}
});
final boolean navControlShown = sceneImage.getConfiguration().getPropertyBool(
PREFERENCE_KEY_IMAGE_NAV_CONTROL_SHOWN, true);
this.layerCanvas.setNavControlShown(navControlShown);
this.layerCanvas.setAntialiasing(true);
this.layerCanvas.setPreferredSize(new Dimension(400, 400));
this.layerCanvas.addOverlay((canvas, rendering) -> {
figureEditor.drawFigureSelection(rendering);
figureEditor.drawSelectionRectangle(rendering);
});
figureEditor = new VectorDataFigureEditor(this);
figureEditor.addSelectionChangeListener(new PinSelectionChangeListener());
this.scrollBarsShown = sceneImage.getConfiguration().getPropertyBool(PREFERENCE_KEY_IMAGE_SCROLL_BARS_SHOWN,
false);
if (scrollBarsShown) {
this.scrollPane = createScrollPane();
add(scrollPane, BorderLayout.CENTER);
} else {
add(layerCanvas, BorderLayout.CENTER);
}
registerLayerCanvasListeners();
this.rasterChangeHandler = new RasterChangeHandler();
getRaster().getProduct().addProductNodeListener(rasterChangeHandler);
setMaskOverlayEnabled(true);
setName(sceneImage.getName());
appyLayerProperties(sceneImage.getConfiguration());
sceneImage.getConfiguration().addPropertyChangeListener(this);
addDefaultLayers(sceneImage);
}
private void addDefaultLayers(final ProductSceneImage sceneImage) {
final Layer rootLayer = sceneImage.getRootLayer();
final Set<LayerType> layerTypes = LayerTypeRegistry.getLayerTypes();
for(LayerType layerType : layerTypes) {
if(layerType.isValidFor(sceneImage) && layerType.createWithSceneView(sceneImage)) {
PropertyContainer config = new PropertyContainer();
config.addProperty(Property.create("raster", getRaster()));
Layer layer = layerType.createLayer(sceneImage, config);
rootLayer.getChildren().add(0, layer);
layer.setVisible(true);
}
}
}
/**
* Called if the property map changed. Simply calls {@link #appyLayerProperties(PropertyMap)}.
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
appyLayerProperties(sceneImage.getConfiguration());
}
public UndoContext getUndoContext() {
return undoContext;
}
@Override
public FigureEditor getFigureEditor() {
return figureEditor;
}
@Override
public Viewport getViewport() {
return layerCanvas.getViewport();
}
public int getCurrentPixelX() {
return currentPixelX;
}
public int getCurrentPixelY() {
return currentPixelY;
}
public boolean isCurrentPixelPosValid() {
return isPixelPosValid(currentLevelPixelX, currentLevelPixelY, currentLevel);
}
private AdjustableViewScrollPane createScrollPane() {
AbstractButton zoomAllButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/ZoomAll13.gif"),
false);
zoomAllButton.setFocusable(false);
zoomAllButton.setFocusPainted(false);
zoomAllButton.addActionListener(e -> getLayerCanvas().zoomAll());
AdjustableViewScrollPane scrollPane = new AdjustableViewScrollPane(layerCanvas);
// todo - use sceneImage.getConfiguration() (nf, 18.09.2008)
scrollPane.setBackground(DEFAULT_IMAGE_BACKGROUND_COLOR);
scrollPane.setCornerComponent(zoomAllButton);
return scrollPane;
}
public ProductSceneImage getSceneImage() {
return sceneImage;
}
/**
* Gets the current selection context, if any.
*
* @return The current selection context, or {@code null} if none exists.
* @since BEAM 4.7
*/
@Override
public SelectionContext getSelectionContext() {
return getFigureEditor().getSelectionContext();
}
/**
* @return The root layer.
*/
@Override
public Layer getRootLayer() {
return sceneImage.getRootLayer();
}
/**
* The coordinate reference system (CRS) used by all the layers in this context.
* May be used by a {@link com.bc.ceres.glayer.LayerType} in order to decide whether
* the source can provide a new layer instance for this context.
*
* @return The CRS. May be {@code null}.
*/
@Override
public Object getCoordinateReferenceSystem() {
return sceneImage.getCoordinateReferenceSystem();
}
/**
* @deprecated since BEAM 4.7
*/
@Deprecated
public LayerContext getLayerContext() {
return sceneImage;
}
public LayerCanvas getLayerCanvas() {
return layerCanvas;
}
/**
* Returns the currently visible product node.
*/
@Override
public ProductNode getVisibleProductNode() {
if (isRGB()) {
return getProduct();
}
return getRaster();
}
/**
* If the {@code preferredSize} has been set to a
* non-{@code null} value just returns it.
* If the UI delegate's {@code getPreferredSize}
* method returns a non {@code null} value then return that;
* otherwise defer to the component's layout manager.
*
* @return the value of the {@code preferredSize} property
* @see #setPreferredSize
* @see javax.swing.plaf.ComponentUI
*/
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
if (getLayerCanvas() != null) {
return getLayerCanvas().getPreferredSize();
} else {
return super.getPreferredSize();
}
}
}
@Override
public JPopupMenu createPopupMenu(Component component) {
return null;
}
@Override
public JPopupMenu createPopupMenu(MouseEvent event) {
JPopupMenu popupMenu = new JPopupMenu();
List<? extends Action> viewActions = Utilities.actionsForPath("Context/ProductSceneView");
for (Action action : viewActions) {
JMenuItem menuItem = popupMenu.add(action);
String popupText = (String) action.getValue("popupText");
if (StringUtils.isNotNullAndNotEmpty(popupText)) {
menuItem.setText(popupText);
}
}
return popupMenu;
}
/**
* Releases all of the resources used by this object instance and all of its owned children. Its primary use is to
* allow the garbage collector to perform a vanilla job.
* <p>
* <p>This method should be called only if it is for sure that this object instance will never be used again. The
* results of referencing an instance of this class after a call to {@code dispose()} are undefined.
* <p>
* <p>Overrides of this method should always call {@code super.dispose();} after disposing this instance.
*/
@Override
public synchronized void dispose() {
if (pixelPositionListeners != null) {
pixelPositionListeners.clear();
}
deregisterLayerCanvasListeners();
if (sceneImage != null) {
sceneImage.getConfiguration().removePropertyChangeListener(this);
}
for (int i = 0; i < getSceneImage().getRasters().length; i++) {
final RasterDataNode raster = getSceneImage().getRasters()[i];
if (raster instanceof RGBChannel) {
RGBChannel rgbChannel = (RGBChannel) raster;
rgbChannel.dispose();
}
sceneImage.getRasters()[i] = null;
}
sceneImage = null;
if (getLayerCanvas() != null) {
// ensure that imageDisplay.dispose() is run in the EDT
SwingUtilities.invokeLater(this::disposeImageDisplayComponent);
}
super.dispose();
}
/**
* @return the associated product.
*/
@Override
public Product getProduct() {
return getRaster().getProduct();
}
@Override
public ProductNode getProductNode() {
return getRaster();
}
public String getSceneName() {
return getSceneImage().getName();
}
public ImageInfo getImageInfo() {
return getSceneImage().getImageInfo();
}
public void setImageInfo(ImageInfo imageInfo) {
final ImageInfo oldImageInfo = getImageInfo();
getSceneImage().setImageInfo(imageInfo);
updateImage();
firePropertyChange(PROPERTY_NAME_IMAGE_INFO, oldImageInfo, imageInfo);
}
/**
* Gets the number of raster datasets.
*
* @return the number of raster datasets, always {@code 1} for single banded palette images or {@code 3}
* for RGB images
*/
public int getNumRasters() {
return getSceneImage().getRasters().length;
}
/**
* Gets the product raster with the specified index.
*
* @param index the zero-based product raster index
* @return the product raster with the given index
*/
public RasterDataNode getRaster(int index) {
return getSceneImage().getRasters()[index];
}
/**
* Gets the product raster of a single banded view.
*
* @return the product raster, or {@code null} if this is a 3-banded RGB view
*/
public RasterDataNode getRaster() {
return getSceneImage().getRasters()[0];
}
/**
* Gets all rasters of this view.
*
* @return all rasters of this view, array size is either 1 or 3 (RGB)
*/
public RasterDataNode[] getRasters() {
return getSceneImage().getRasters();
}
public void setRasters(RasterDataNode[] rasters) {
getSceneImage().setRasters(rasters);
}
public boolean isRGB() {
return getSceneImage().getRasters().length >= 3;
}
public boolean isNoDataOverlayEnabled() {
final Layer noDataLayer = getNoDataLayer(false);
return noDataLayer != null && noDataLayer.isVisible();
}
public void setNoDataOverlayEnabled(boolean enabled) {
if (isNoDataOverlayEnabled() != enabled) {
getNoDataLayer(true).setVisible(enabled);
}
}
public ImageLayer getBaseImageLayer() {
return getSceneImage().getBaseImageLayer();
}
public boolean isGraticuleOverlayEnabled() {
final GraticuleLayer graticuleLayer = getGraticuleLayer(false);
return graticuleLayer != null && graticuleLayer.isVisible();
}
public void setGraticuleOverlayEnabled(boolean enabled) {
if (isGraticuleOverlayEnabled() != enabled) {
getGraticuleLayer(true).setVisible(enabled);
}
}
public boolean isPinOverlayEnabled() {
Layer pinLayer = getPinLayer(false);
return pinLayer != null && pinLayer.isVisible();
}
public void setPinOverlayEnabled(boolean enabled) {
if (isPinOverlayEnabled() != enabled) {
Layer layer = getPinLayer(true);
layer.setVisible(enabled);
setSelectedLayer(layer);
}
}
public boolean isGcpOverlayEnabled() {
Layer gcpLayer = getGcpLayer(false);
return gcpLayer != null && gcpLayer.isVisible();
}
public void setGcpOverlayEnabled(boolean enabled) {
if (isGcpOverlayEnabled() != enabled) {
Layer layer = getGcpLayer(true);
layer.setVisible(enabled);
setSelectedLayer(layer);
}
}
public boolean isMaskOverlayEnabled() {
final Layer layer = getMaskCollectionLayer(false);
return layer != null && layer.isVisible();
}
public void setMaskOverlayEnabled(boolean enabled) {
if (isMaskOverlayEnabled() != enabled) {
getMaskCollectionLayer(true).setVisible(enabled);
}
}
/**
* @param vectorDataNodes The vector data nodes whose layer shall be made visible.
* @since BEAM 4.10
*/
public void setLayersVisible(VectorDataNode... vectorDataNodes) {
for (VectorDataNode vectorDataNode : vectorDataNodes) {
final LayerFilter nodeFilter = VectorDataLayerFilterFactory.createNodeFilter(vectorDataNode);
Layer vectorDataLayer = LayerUtils.getChildLayer(getRootLayer(),
LayerUtils.SEARCH_DEEP,
nodeFilter);
if (vectorDataLayer != null) {
vectorDataLayer.setVisible(true);
}
}
}
public ShapeFigure getCurrentShapeFigure() {
FigureSelection figureSelection = getFigureEditor().getFigureSelection();
if (figureSelection.getFigureCount() > 0) {
Figure figure = figureSelection.getFigure(0);
if (figure instanceof ShapeFigure) {
return (ShapeFigure) figure;
}
} else {
Layer layer = null;
final Layer selLayer = getSelectedLayer();
if (selLayer instanceof VectorDataLayer) {
final VectorDataLayer vectorLayer = (VectorDataLayer) selLayer;
if (vectorLayer.getVectorDataNode() != null) {
final String typeName = vectorLayer.getVectorDataNode().getFeatureType().getTypeName();
if (Product.GEOMETRY_FEATURE_TYPE_NAME.equals(typeName)) {
layer = vectorLayer;
}
}
}
if (layer == null) {
layer = LayerUtils.getChildLayer(getRootLayer(), LayerUtils.SearchMode.DEEP,
VectorDataLayerFilterFactory.createGeometryFilter());
}
if (layer != null) {
final VectorDataLayer vectorDataLayer = (VectorDataLayer) layer;
if (vectorDataLayer.getFigureCollection().getFigureCount() > 0) {
Figure figure = vectorDataLayer.getFigureCollection().getFigure(0);
if (figure instanceof ShapeFigure) {
return (ShapeFigure) figure;
}
}
}
}
return null;
}
public void setScrollBarsShown(boolean scrollBarsShown) {
if (scrollBarsShown != this.scrollBarsShown) {
this.scrollBarsShown = scrollBarsShown;
if (scrollBarsShown) {
remove(layerCanvas);
scrollPane = createScrollPane();
add(scrollPane, BorderLayout.CENTER);
} else {
remove(scrollPane);
scrollPane = null;
add(layerCanvas, BorderLayout.CENTER);
}
invalidate();
validate();
repaint();
}
}
/**
* Called after SNAP preferences have changed.
* This behaviour is deprecated since we want to uswe separate style editors for each layers.
*
* @param configuration the configuration.
*/
public void appyLayerProperties(PropertyMap configuration) {
setScrollBarsShown(configuration.getPropertyBool(PREFERENCE_KEY_IMAGE_SCROLL_BARS_SHOWN, false));
layerCanvas.setAntialiasing(true);
layerCanvas.setNavControlShown(configuration.getPropertyBool(PREFERENCE_KEY_IMAGE_NAV_CONTROL_SHOWN, true));
layerCanvas.setBackground(
configuration.getPropertyColor("image.background.color", DEFAULT_IMAGE_BACKGROUND_COLOR));
layerCanvasMouseHandler.setInvertZooming(configuration.getPropertyBool(PREFERENCE_KEY_INVERT_ZOOMING, false));
ImageLayer imageLayer = getBaseImageLayer();
if (imageLayer != null) {
ProductSceneImage.applyBaseImageLayerStyle(configuration, imageLayer);
}
Layer noDataLayer = getNoDataLayer(false);
if (noDataLayer != null) {
ProductSceneImage.applyNoDataLayerStyle(configuration, noDataLayer);
}
Layer collectionLayer = getVectorDataCollectionLayer(false);
if (collectionLayer != null) {
ProductSceneImage.applyFigureLayerStyle(configuration, collectionLayer);
}
GraticuleLayer graticuleLayer = getGraticuleLayer(false);
if (graticuleLayer != null) {
ProductSceneImage.applyGraticuleLayerStyle(configuration, graticuleLayer);
}
}
/**
* Adds a new pixel position listener to this image display component. If
* the component already contains the given listener, the method does
* nothing.
*
* @param listener the pixel position listener to be added
*/
public final void addPixelPositionListener(PixelPositionListener listener) {
if (listener == null) {
return;
}
if (pixelPositionListeners.contains(listener)) {
return;
}
pixelPositionListeners.add(listener);
}
/**
* Removes a pixel position listener from this image display component.
*
* @param listener the pixel position listener to be removed
*/
public final void removePixelPositionListener(PixelPositionListener listener) {
if (listener == null || pixelPositionListeners.isEmpty()) {
return;
}
pixelPositionListeners.remove(listener);
}
public Layer getSelectedLayer() {
return selectedLayer;
}
public void setSelectedLayer(Layer layer) {
Layer oldLayer = selectedLayer;
if (oldLayer != layer) {
selectedLayer = layer;
firePropertyChange(PROPERTY_NAME_SELECTED_LAYER, oldLayer, selectedLayer);
maybeUpdateFigureEditor();
}
}
/**
* @param vectorDataNode The vector data node, whose layer shall be selected.
* @return The layer, or {@code null}.
* @since BEAM 4.7
*/
public VectorDataLayer selectVectorDataLayer(VectorDataNode vectorDataNode) {
LayerFilter layerFilter = new VectorDataLayerFilter(vectorDataNode);
VectorDataLayer layer = (VectorDataLayer) LayerUtils.getChildLayer(getRootLayer(),
LayerUtils.SEARCH_DEEP,
layerFilter);
if (layer != null) {
setSelectedLayer(layer);
}
return layer;
}
/**
* @param pin The pins to test.
* @return {@code true}, if the pin is selected.
* @since BEAM 4.7
*/
public boolean isPinSelected(Placemark pin) {
return isPlacemarkSelected(getProduct().getPinGroup(), pin);
}
/**
* @param gcp The ground control point to test.
* @return {@code true}, if the ground control point is selected.
* @since BEAM 4.7
*/
public boolean isGcpSelected(Placemark gcp) {
return isPlacemarkSelected(getProduct().getGcpGroup(), gcp);
}
/**
* @return The (first) selected pin.
* @since BEAM 4.7
*/
public Placemark getSelectedPin() {
return getSelectedPlacemark(getProduct().getPinGroup());
}
/**
* @return The selected pins.
* @since BEAM 4.7
*/
public Placemark[] getSelectedPins() {
return getSelectedPlacemarks(getProduct().getPinGroup());
}
/**
* @return The selected ground control points.
* @since BEAM 4.7
*/
public Placemark[] getSelectedGcps() {
return getSelectedPlacemarks(getProduct().getGcpGroup());
}
/**
* @param pins The selected pins.
* @since BEAM 4.7
*/
public void selectPins(Placemark[] pins) {
selectPlacemarks(getProduct().getPinGroup(), pins);
}
/**
* @param gpcs The selected ground control points.
* @since BEAM 4.7
*/
public void selectGcps(Placemark[] gpcs) {
selectPlacemarks(getProduct().getGcpGroup(), gpcs);
}
/**
* @return The (first) selected feature figure.
* @since BEAM 4.7
*/
public SimpleFeatureFigure getSelectedFeatureFigure() {
Figure[] figures = figureEditor.getFigureSelection().getFigures();
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
return (SimpleFeatureFigure) figure;
}
}
return null;
}
/**
* @return The selected feature figures.
* @since BEAM 4.7
* @deprecated since BEAM 4.10, use {@link #getFeatureFigures(boolean)} instead
*/
public SimpleFeatureFigure[] getSelectedFeatureFigures() {
ArrayList<SimpleFeatureFigure> selectedFigures = new ArrayList<>();
collectFeatureFigures(figureEditor.getFigureSelection(), selectedFigures);
return selectedFigures.toArray(new SimpleFeatureFigure[selectedFigures.size()]);
}
/**
* Gets either the selected figures, or all the figures of the currently selected layer.
*
* @param selectedOnly If {@code true}, only selected figures are returned.
* @return The feature figures or an empty array.
* @since BEAM 4.10
*/
public SimpleFeatureFigure[] getFeatureFigures(boolean selectedOnly) {
ArrayList<SimpleFeatureFigure> selectedFigures = new ArrayList<>();
collectFeatureFigures(figureEditor.getFigureSelection(), selectedFigures);
if (selectedFigures.isEmpty()
&& !selectedOnly
&& getSelectedLayer() instanceof VectorDataLayer) {
VectorDataLayer vectorDataLayer = (VectorDataLayer) getSelectedLayer();
collectFeatureFigures(vectorDataLayer.getFigureCollection(), selectedFigures);
}
return selectedFigures.toArray(new SimpleFeatureFigure[selectedFigures.size()]);
}
private void collectFeatureFigures(FigureCollection figureCollection, List<SimpleFeatureFigure> selectedFigures) {
Figure[] figures = figureCollection.getFigures();
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
selectedFigures.add((SimpleFeatureFigure) figure);
}
}
}
public boolean selectPlacemarks(PlacemarkGroup placemarkGroup, Placemark[] placemarks) {
VectorDataLayer layer = selectVectorDataLayer(placemarkGroup.getVectorDataNode());
if (layer != null) {
FigureCollection figureCollection = layer.getFigureCollection();
Figure[] figures = figureCollection.getFigures();
ArrayList<SimpleFeatureFigure> selectedFigures = new ArrayList<>(figures.length);
HashSet<Placemark> placemarkSet = new HashSet<>(Arrays.asList(placemarks));
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
SimpleFeatureFigure featureFigure = (SimpleFeatureFigure) figure;
Placemark placemark = placemarkGroup.getPlacemark(featureFigure.getSimpleFeature());
if (placemarkSet.contains(placemark)) {
selectedFigures.add(featureFigure);
}
}
}
figureEditor.getFigureSelection().removeAllFigures();
figureEditor.getFigureSelection().addFigures(selectedFigures.toArray(new Figure[selectedFigures.size()]));
final int selectionStage = Math.min(selectedFigures.size(), 2);
figureEditor.getFigureSelection().setSelectionStage(selectionStage);
return true;
}
return false;
}
private boolean isPlacemarkSelected(PlacemarkGroup placemarkGroup, Placemark placemark) {
Figure[] figures = figureEditor.getFigureSelection().getFigures();
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
SimpleFeatureFigure featureFigure = (SimpleFeatureFigure) figure;
Placemark pin = placemarkGroup.getPlacemark(featureFigure.getSimpleFeature());
if (pin == placemark) {
return true;
}
}
}
return false;
}
private Placemark getSelectedPlacemark(PlacemarkGroup placemarkGroup) {
Figure[] figures = figureEditor.getFigureSelection().getFigures();
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
SimpleFeatureFigure featureFigure = (SimpleFeatureFigure) figure;
Placemark placemark = placemarkGroup.getPlacemark(featureFigure.getSimpleFeature());
if (placemark != null) {
return placemark;
}
}
}
return null;
}
private Placemark[] getSelectedPlacemarks(PlacemarkGroup placemarkGroup) {
Figure[] figures = figureEditor.getFigureSelection().getFigures();
ArrayList<Placemark> selectedPlacemarks = new ArrayList<>(figures.length);
for (Figure figure : figures) {
if (figure instanceof SimpleFeatureFigure) {
SimpleFeatureFigure featureFigure = (SimpleFeatureFigure) figure;
Placemark placemark = placemarkGroup.getPlacemark(featureFigure.getSimpleFeature());
if (placemark != null) {
selectedPlacemarks.add(placemark);
}
}
}
return selectedPlacemarks.toArray(new Placemark[selectedPlacemarks.size()]);
}
private void maybeUpdateFigureEditor() {
if (selectedLayer instanceof VectorDataLayer) {
VectorDataLayer vectorDataLayer = (VectorDataLayer) selectedLayer;
figureEditor.vectorDataLayerSelected(vectorDataLayer);
}
}
public void disposeLayers() {
getSceneImage().getRootLayer().dispose();
}
public AffineTransform getBaseImageToViewTransform() {
AffineTransform viewToModelTransform = layerCanvas.getViewport().getViewToModelTransform();
AffineTransform modelToImageTransform = getBaseImageLayer().getModelToImageTransform();
viewToModelTransform.concatenate(modelToImageTransform);
try {
return viewToModelTransform.createInverse();
} catch (NoninvertibleTransformException e) {
throw new RuntimeException(e);
}
}
/**
* @return the visible image area in pixel coordinates
*/
public Rectangle getVisibleImageBounds() {
final ImageLayer imageLayer = getBaseImageLayer();
if (imageLayer != null) {
final RenderedImage image = imageLayer.getImage();
final Area imageArea = new Area(new Rectangle(0, 0, image.getWidth(), image.getHeight()));
final Area visibleImageArea = new Area(
imageLayer.getModelToImageTransform().createTransformedShape(getVisibleModelBounds()));
imageArea.intersect(visibleImageArea);
return imageArea.getBounds();
}
return null;
}
/**
* @return the visible area in model coordinates
*/
public Rectangle2D getVisibleModelBounds() {
final Viewport viewport = layerCanvas.getViewport();
return viewport.getViewToModelTransform().createTransformedShape(viewport.getViewBounds()).getBounds2D();
}
/**
* @return the model bounds in model coordinates
*/
public Rectangle2D getModelBounds() {
return layerCanvas.getLayer().getModelBounds();
}
public double getOrientation() {
return layerCanvas.getViewport().getOrientation();
}
public double getZoomFactor() {
return layerCanvas.getViewport().getZoomFactor();
}
public void zoom(Rectangle2D modelRect) {
layerCanvas.getViewport().zoom(modelRect);
}
public void zoom(double x, double y, double viewScale) {
if (viewScale > 0) {
layerCanvas.getViewport().setZoomFactor(viewScale, x, y);
}
}
public boolean synchronizeViewportIfPossible(ProductSceneView thatView) {
final RasterDataNode thisRaster = getRaster();
final RasterDataNode thatRaster = thatView.getRaster();
final Product thisProduct = thisRaster.getProduct();
if (thisProduct.isSceneCrsEqualToModelCrsOf(thatRaster)) {
final Viewport thisViewport = layerCanvas.getViewport();
final Viewport thatViewport = thatView.layerCanvas.getViewport();
thatViewport.setTransform(thisViewport);
return true;
} else if (thisProduct == thatRaster.getProduct()) {
final Viewport thisViewport = layerCanvas.getViewport();
final Viewport thatViewport = thatView.layerCanvas.getViewport();
final Rectangle thisViewBounds = thisViewport.getViewBounds();
final Rectangle thisModelBounds = thisViewport.getViewToModelTransform().createTransformedShape(thisViewBounds).getBounds();
try {
final Rectangle sceneBounds = thisRaster.getModelToSceneTransform().createTransformedShape(thisModelBounds).getBounds();
final Rectangle thatModelBounds = thatRaster.getSceneToModelTransform().createTransformedShape(sceneBounds).getBounds();
thatViewport.zoom(thatModelBounds);
return true;
} catch (TransformException e) {
//try code below
}
}
final GeoCoding thisGeoCoding = thisRaster.getGeoCoding();
final GeoCoding thatGeoCoding = thatRaster.getGeoCoding();
if (thisGeoCoding != null && thatGeoCoding != null && thisGeoCoding.canGetGeoPos() && thatGeoCoding.canGetPixelPos()) {
final Viewport thisViewport = layerCanvas.getViewport();
final Viewport thatViewport = thatView.layerCanvas.getViewport();
final double viewCenterX = thisViewport.getViewBounds().getCenterX();
final double viewCenterY = thisViewport.getViewBounds().getCenterY();
final Point2D viewCenter = new Point2D.Double(viewCenterX, viewCenterY);
final Point2D modelCenter = thisViewport.getViewToModelTransform().transform(viewCenter, null);
final PixelPos imageCenter = new PixelPos();
getBaseImageLayer().getModelToImageTransform().transform(modelCenter, imageCenter);
final GeoPos geoCenter = new GeoPos();
thisGeoCoding.getGeoPos(imageCenter, geoCenter);
thatGeoCoding.getPixelPos(geoCenter, imageCenter);
if (imageCenter.isValid()) {
thatView.getBaseImageLayer().getImageToModelTransform().transform(imageCenter, modelCenter);
thatViewport.setZoomFactor(thisViewport.getZoomFactor(), modelCenter.getX(), modelCenter.getY());
return true;
}
}
return false;
}
protected void disposeImageDisplayComponent() {
layerCanvas.dispose();
}
// only called from VISAT
public void updateImage() {
getBaseImageLayer().regenerate();
}
// used by PropertyEditor
public void updateNoDataImage() {
// change configuration of layer ; not setting MultiLevelSource
final String expression = getRaster().getValidMaskExpression();
final ImageLayer noDataLayer = (ImageLayer) getNoDataLayer(false);
if (noDataLayer != null) {
if (expression != null) {
final Color color = noDataLayer.getConfiguration().getValue(
NoDataLayerType.PROPERTY_NAME_COLOR);
final MultiLevelSource multiLevelSource = ColoredMaskImageMultiLevelSource.create(getRaster().getProduct(),
color, expression, true,
getBaseImageLayer().getImageToModelTransform());
noDataLayer.setMultiLevelSource(multiLevelSource);
} else {
noDataLayer.setMultiLevelSource(MultiLevelSource.NULL);
}
}
}
public int getFirstImageLayerIndex() {
return sceneImage.getFirstImageLayerIndex();
}
/**
* A band that is used as an RGB channel for RGB image views.
* These bands shall not be added to {@link Product}s but they are always owned by the {@link Product}
* passed into the constructor.
*/
public static class RGBChannel extends VirtualBand {
/**
* Constructs a new RGB image view band.
*
* @param product the product which takes the ownership
* @param width the width of the image
* @param height the height of the image
* @param name the band's name
* @param expression the expression
*/
public RGBChannel(final Product product, final int width, final int height, final String name, final String expression) {
super(name,
ProductData.TYPE_FLOAT32,
width,
height,
expression);
setOwner(product);
setModified(false);
}
/**
* Constructs a new RGB image view band.
*
* @param product the product which takes the ownership
* @param name the band's name
* @param expression the expression
*/
public RGBChannel(final Product product, final String name, final String expression) {
this(product, product.getSceneRasterWidth(), product.getSceneRasterHeight(), name, expression);
}
}
private final class RasterChangeHandler implements ProductNodeListener {
@Override
public void nodeChanged(final ProductNodeEvent event) {
repaintView();
}
@Override
public void nodeDataChanged(final ProductNodeEvent event) {
repaintView();
}
@Override
public void nodeAdded(final ProductNodeEvent event) {
repaintView();
}
@Override
public void nodeRemoved(final ProductNodeEvent event) {
repaintView();
}
private void repaintView() {
repaint(100);
}
}
private Layer getNoDataLayer(boolean create) {
return getSceneImage().getNoDataLayer(create);
}
public Layer getVectorDataCollectionLayer(boolean create) {
return getSceneImage().getVectorDataCollectionLayer(create);
}
private Layer getMaskCollectionLayer(boolean create) {
return getSceneImage().getMaskCollectionLayer(create);
}
private GraticuleLayer getGraticuleLayer(boolean create) {
return getSceneImage().getGraticuleLayer(create);
}
private Layer getPinLayer(boolean create) {
return getSceneImage().getPinLayer(create);
}
private Layer getGcpLayer(boolean create) {
return getSceneImage().getGcpLayer(create);
}
private static boolean isModelYAxisDown(ImageLayer baseImageLayer) {
return baseImageLayer.getImageToModelTransform().getDeterminant() > 0.0;
}
private void registerLayerCanvasListeners() {
layerCanvasComponentHandler = new LayerCanvasComponentHandler();
layerCanvasMouseHandler = new LayerCanvasMouseHandler();
layerCanvas.addComponentListener(layerCanvasComponentHandler);
layerCanvas.addMouseListener(layerCanvasMouseHandler);
layerCanvas.addMouseMotionListener(layerCanvasMouseHandler);
layerCanvas.addMouseWheelListener(layerCanvasMouseHandler);
PopupMenuHandler popupMenuHandler = new PopupMenuHandler(this);
layerCanvas.addMouseListener(popupMenuHandler);
layerCanvas.addKeyListener(popupMenuHandler);
}
private void deregisterLayerCanvasListeners() {
getRaster().getProduct().removeProductNodeListener(rasterChangeHandler);
layerCanvas.removeComponentListener(layerCanvasComponentHandler);
layerCanvas.removeMouseListener(layerCanvasMouseHandler);
layerCanvas.removeMouseMotionListener(layerCanvasMouseHandler);
}
private boolean isPixelPosValid(int currentPixelX, int currentPixelY, int currentLevel) {
return currentPixelX >= 0 && currentPixelX < baseImageLayer.getImage(
currentLevel).getWidth() && currentPixelY >= 0
&& currentPixelY < baseImageLayer.getImage(currentLevel).getHeight();
}
private void firePixelPosChanged(MouseEvent e, int currentPixelX, int currentPixelY, int currentLevel) {
boolean pixelPosValid = isPixelPosValid(currentPixelX, currentPixelY, currentLevel);
for (PixelPositionListener listener : pixelPositionListeners) {
listener.pixelPosChanged(baseImageLayer, currentPixelX, currentPixelY, currentLevel, pixelPosValid, e);
}
}
private void firePixelPosNotAvailable() {
for (PixelPositionListener listener : pixelPositionListeners) {
listener.pixelPosNotAvailable();
}
}
private void setPixelPos(MouseEvent e, boolean showBorder) {
if (e.getID() == MouseEvent.MOUSE_EXITED) {
currentLevelPixelX = -1;
firePixelPosNotAvailable();
} else {
Point2D p = new Point2D.Double(e.getX() + 0.5, e.getY() + 0.5);
Viewport viewport = getLayerCanvas().getViewport();
AffineTransform v2mTransform = viewport.getViewToModelTransform();
final Point2D modelP = v2mTransform.transform(p, null);
AffineTransform m2iTransform = baseImageLayer.getModelToImageTransform();
Point2D imageP = m2iTransform.transform(modelP, null);
currentPixelX = (int) Math.floor(imageP.getX());
currentPixelY = (int) Math.floor(imageP.getY());
int currentLevel = baseImageLayer.getLevel(viewport);
AffineTransform m2iLevelTransform = baseImageLayer.getModelToImageTransform(currentLevel);
Point2D imageLevelP = m2iLevelTransform.transform(modelP, null);
int currentPixelX = (int) Math.floor(imageLevelP.getX());
int currentPixelY = (int) Math.floor(imageLevelP.getY());
if (currentPixelX != currentLevelPixelX || currentPixelY != currentLevelPixelY || currentLevel != this.currentLevel) {
if (isPixelBorderDisplayEnabled() && (showBorder || pixelBorderDrawn)) {
drawPixelBorder(currentPixelX, currentPixelY, currentLevel, showBorder);
}
currentLevelPixelX = currentPixelX;
currentLevelPixelY = currentPixelY;
this.currentLevel = currentLevel;
firePixelPosChanged(e, currentLevelPixelX, currentLevelPixelY, this.currentLevel);
}
}
}
private boolean isPixelBorderDisplayEnabled() {
return pixelBorderShown &&
getLayerCanvas().getViewport().getZoomFactor() >= pixelBorderViewScale;
}
private void drawPixelBorder(int currentPixelX, int currentPixelY, int currentLevel, boolean showBorder) {
final Graphics g = getGraphics();
g.setXORMode(Color.white);
if (pixelBorderDrawn) {
drawPixelBorder(g, currentLevelPixelX, currentLevelPixelY, this.currentLevel);
pixelBorderDrawn = false;
}
if (showBorder) {
drawPixelBorder(g, currentPixelX, currentPixelY, currentLevel);
pixelBorderDrawn = true;
}
g.setPaintMode();
g.dispose();
}
private void drawPixelBorder(final Graphics g, final int x, final int y, final int l) {
if (g instanceof Graphics2D) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform i2m = getBaseImageLayer().getImageToModelTransform(l);
AffineTransform m2v = getLayerCanvas().getViewport().getModelToViewTransform();
Rectangle imageRect = new Rectangle(x, y, 1, 1);
Shape modelRect = i2m.createTransformedShape(imageRect);
Shape transformedShape = m2v.createTransformedShape(modelRect);
g2d.draw(transformedShape);
}
}
private final class LayerCanvasMouseHandler implements MouseInputListener, MouseWheelListener {
private boolean invertZooming;
public LayerCanvasMouseHandler() {
invertZooming = sceneImage.getConfiguration().getPropertyBool(PREFERENCE_KEY_INVERT_ZOOMING, false);
}
public void setInvertZooming(boolean invertZooming) {
this.invertZooming = invertZooming;
}
@Override
public void mouseClicked(MouseEvent e) {
updatePixelPos(e, false);
}
@Override
public void mouseEntered(MouseEvent e) {
updatePixelPos(e, false);
}
@Override
public void mousePressed(MouseEvent e) {
updatePixelPos(e, false);
}
@Override
public void mouseReleased(MouseEvent e) {
updatePixelPos(e, false);
}
@Override
public void mouseExited(MouseEvent e) {
updatePixelPos(e, false);
}
@Override
public void mouseDragged(MouseEvent e) {
updatePixelPos(e, true);
}
@Override
public void mouseMoved(MouseEvent e) {
updatePixelPos(e, true);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.isAltDown() || e.isAltGraphDown() || e.isControlDown() || e.isShiftDown()) {
return;
}
Viewport viewport = layerCanvas.getViewport();
int wheelRotation = e.getWheelRotation();
if (invertZooming) {
wheelRotation *= -1;
}
double oldZoomFactor = viewport.getZoomFactor();
double newZoomFactor = oldZoomFactor * Math.pow(1.1, wheelRotation);
viewport.setZoomFactor(newZoomFactor);
}
private void updatePixelPos(MouseEvent e, boolean showBorder) {
setPixelPos(e, showBorder);
}
}
private class LayerCanvasComponentHandler extends ComponentAdapter {
/**
* Invoked when the component has been made invisible.
*/
@Override
public void componentHidden(ComponentEvent e) {
firePixelPosNotAvailable();
}
}
static class NullFigureCollection implements FigureCollection {
static final FigureCollection INSTANCE = new NullFigureCollection();
private NullFigureCollection() {
}
@Override
public boolean isCollection() {
return false;
}
@Override
public boolean contains(Figure figure) {
return false;
}
@Override
public boolean isCloseTo(Point2D point, AffineTransform m2v) {
return false;
}
@Override
public Rectangle2D getBounds() {
return new Rectangle();
}
@Override
public Rank getRank() {
return Figure.Rank.NOT_SPECIFIED;
}
@Override
public void move(double dx, double dy) {
}
@Override
public void scale(Point2D point, double sx, double sy) {
}
@Override
public void rotate(Point2D point, double theta) {
}
@Override
public double[] getSegment(int index) {
return null;
}
@Override
public void setSegment(int index, double[] segment) {
}
@Override
public void addSegment(int index, double[] segment) {
}
@Override
public void removeSegment(int index) {
}
@Override
public boolean isSelectable() {
return false;
}
@Override
public boolean isSelected() {
return false;
}
@Override
public void setSelected(boolean selected) {
}
@Override
public void draw(Rendering rendering) {
}
@Override
public int getFigureCount() {
return 0;
}
@Override
public int getFigureIndex(Figure figure) {
return 0;
}
@Override
public Figure getFigure(int index) {
return null;
}
@Override
public Figure getFigure(Point2D point, AffineTransform m2v) {
return null;
}
@Override
public Figure[] getFigures() {
return new Figure[0];
}
@Override
public Figure[] getFigures(Shape shape) {
return new Figure[0];
}
@Override
public boolean addFigure(Figure figure) {
return false;
}
@Override
public boolean addFigure(int index, Figure figure) {
return false;
}
@Override
public Figure[] addFigures(Figure... figures) {
return new Figure[0];
}
@Override
public boolean removeFigure(Figure figure) {
return false;
}
@Override
public Figure[] removeFigures(Figure... figures) {
return new Figure[0];
}
@Override
public Figure[] removeAllFigures() {
return new Figure[0];
}
@Override
public int getMaxSelectionStage() {
return 0;
}
@Override
public Handle[] createHandles(int selectionStage) {
return new Handle[0];
}
@Override
public void addChangeListener(FigureChangeListener listener) {
}
@Override
public void removeChangeListener(FigureChangeListener listener) {
}
@Override
public FigureChangeListener[] getChangeListeners() {
return new FigureChangeListener[0];
}
@Override
public void dispose() {
}
@Override
public Object createMemento() {
return null;
}
@Override
public void setMemento(Object memento) {
}
@Override
public FigureStyle getNormalStyle() {
return null;
}
@Override
public void setNormalStyle(FigureStyle normalStyle) {
}
@Override
public FigureStyle getSelectedStyle() {
return null;
}
@Override
public void setSelectedStyle(FigureStyle selectedStyle) {
}
@Override
public FigureStyle getEffectiveStyle() {
return null;
}
@Override
public Object clone() {
return INSTANCE;
}
}
private static class VectorDataLayerFilter implements LayerFilter {
private final VectorDataNode vectorDataNode;
public VectorDataLayerFilter(VectorDataNode vectorDataNode) {
this.vectorDataNode = vectorDataNode;
}
@Override
public boolean accept(Layer layer) {
return layer instanceof VectorDataLayer && ((VectorDataLayer) layer).getVectorDataNode() == vectorDataNode;
}
}
private class PinSelectionChangeListener extends AbstractSelectionChangeListener {
private boolean firedNoPinSelected = false;
@Override
public void selectionChanged(SelectionChangeEvent event) {
Selection selection = event.getSelection();
if (selection.isEmpty()) {
if (!firedNoPinSelected) {
firePropertyChange(PROPERTY_NAME_SELECTED_PIN, null, null);
firedNoPinSelected = true;
}
} else {
Object selectedValue = selection.getSelectedValue();
if (selectedValue instanceof SimpleFeatureFigure) {
SimpleFeatureFigure featureFigure = (SimpleFeatureFigure) selectedValue;
PlacemarkGroup pinGroup = getProduct().getPinGroup();
Placemark pin = pinGroup.getPlacemark(featureFigure.getSimpleFeature());
if (pin != null) {
firePropertyChange(PROPERTY_NAME_SELECTED_PIN, null, pin);
firedNoPinSelected = false;
}
}
}
}
}
}