/* * 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.pixelinfo; import com.bc.ceres.core.Assert; import com.bc.ceres.glayer.support.ImageLayer; import com.bc.ceres.glevel.MultiLevelModel; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.CrsGeoCoding; import org.esa.snap.core.datamodel.FlagCoding; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.MapGeoCoding; import org.esa.snap.core.datamodel.MetadataAttribute; import org.esa.snap.core.datamodel.PixelPos; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.ProductNodeListener; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.datamodel.TiePointGrid; import org.esa.snap.core.dataop.maptransf.MapTransform; import org.esa.snap.core.image.ImageManager; import org.esa.snap.core.util.Guardian; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.core.util.math.MathUtils; import org.esa.snap.ui.product.ProductSceneView; import org.geotools.geometry.DirectPosition2D; import org.opengis.geometry.DirectPosition; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import javax.media.jai.PlanarImage; import javax.swing.SwingUtilities; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.util.Calendar; import java.util.Vector; /** * @author Marco Zuehlke * @version $Revision$ $Date$ * @since BEAM 4.5.2 */ public class PixelInfoViewModelUpdater { private static final String INVALID_POS_TEXT = "Invalid pos."; private final PixelInfoViewTableModel positionModel; private final PixelInfoViewTableModel timeModel; private final PixelInfoViewTableModel bandModel; private final PixelInfoViewTableModel tiePointModel; private final PixelInfoViewTableModel flagModel; private volatile Product currentProduct; private volatile RasterDataNode currentRaster; private volatile ProductSceneView currentView; private Band[] currentFlagBands; private int pixelX; private int pixelY; private int rasterLevel; private int levelZeroRasterX; private int levelZeroRasterY; private double sceneX; private double sceneY; private int levelZeroSceneX; private int levelZeroSceneY; private boolean pixelPosValidInRaster; private final PixelInfoView pixelInfoView; PixelInfoViewModelUpdater(PixelInfoView pixelInfoView, PixelInfoViewTableModel positionModel, PixelInfoViewTableModel timeModel, PixelInfoViewTableModel bandModel, PixelInfoViewTableModel tiePointModel, PixelInfoViewTableModel flagModel) { this.pixelInfoView = pixelInfoView; this.positionModel = positionModel; this.timeModel = timeModel; this.bandModel = bandModel; this.tiePointModel = tiePointModel; this.flagModel = flagModel; } Product getCurrentProduct() { return currentProduct; } RasterDataNode getCurrentRaster() { return currentRaster; } void update(PixelInfoState state) { update(state.view, state.pixelX, state.pixelY, state.level, state.pixelPosValid); } void update(ProductSceneView view, int pixelX, int pixelY, int level, boolean pixelPosValid) { Guardian.assertNotNull("view", view); boolean clearRasterTableSelection = false; RasterDataNode raster = view.getRaster(); final Product product = raster.getProduct(); if (product == currentProduct && view.isRGB()) { resetBandTableModel(); } if (product != currentProduct) { ProductNodeListener productNodeListener = pixelInfoView.getProductNodeListener(); if (currentProduct != null) { currentProduct.removeProductNodeListener(productNodeListener); } product.addProductNodeListener(productNodeListener); currentProduct = product; } if (raster != currentRaster) { currentRaster = raster; registerFlagDatasets(); resetTableModels(); } if (bandModel.getRowCount() != getBandRowCount()) { resetTableModels(); } if (view != currentView) { currentView = view; resetTableModels(); clearRasterTableSelection = true; } this.pixelX = pixelX; this.pixelY = pixelY; this.rasterLevel = level; this.pixelPosValidInRaster = pixelPosValid; AffineTransform i2mTransform = currentView.getBaseImageLayer().getImageToModelTransform(level); Point2D modelP = i2mTransform.transform(new Point2D.Double(pixelX + 0.5, pixelY + 0.5), null); try { final Point2D sceneP = currentView.getRaster().getModelToSceneTransform().transform(modelP, new Point2D.Double()); sceneX = sceneP.getX(); sceneY = sceneP.getY(); } catch (TransformException e) { sceneX = Double.NaN; sceneY = Double.NaN; } AffineTransform m2iTransform = view.getBaseImageLayer().getModelToImageTransform(); Point2D levelZeroP = m2iTransform.transform(modelP, null); levelZeroRasterX = floor(levelZeroP.getX()); levelZeroRasterY = floor(levelZeroP.getY()); //todo [multisize_products] ask for different imagetomodeltransforms - tf 20151113 if (product.isMultiSize()) { try { final GeoCoding sceneGeoCoding = product.getSceneGeoCoding(); if (sceneGeoCoding != null) { final MathTransform imageToMapTransform = sceneGeoCoding.getImageToMapTransform(); if (imageToMapTransform instanceof AffineTransform) { final MathTransform modelToImage = imageToMapTransform.inverse(); final DirectPosition2D pos = new DirectPosition2D(sceneX, sceneY); final DirectPosition position = modelToImage.transform(pos, pos); levelZeroSceneX = floor(position.getCoordinate()[0]); levelZeroSceneY = floor(position.getCoordinate()[1]); } else { levelZeroSceneX = floor(sceneX); levelZeroSceneY = floor(sceneY); } } } catch (TransformException e) { levelZeroSceneX = levelZeroRasterX; levelZeroSceneY = levelZeroRasterY; } } updateDataDisplay(clearRasterTableSelection); } private void resetTableModels() { resetPositionTableModel(); resetTimeTableModel(); resetBandTableModel(); resetTiePointGridTableModel(); resetFlagTableModel(); } private void fireTableChanged(final boolean clearRasterTableSelection) { SwingUtilities.invokeLater(() -> { if (clearRasterTableSelection) { pixelInfoView.clearSelectionInRasterTables(); } positionModel.fireTableDataChanged(); timeModel.fireTableDataChanged(); bandModel.fireTableDataChanged(); tiePointModel.fireTableDataChanged(); flagModel.fireTableDataChanged(); }); } private void updateDataDisplay(boolean clearRasterTableSelection) { if (currentRaster == null) { return; } if (pixelInfoView.isCollapsiblePaneVisible(PixelInfoView.POSITION_INDEX)) { updatePositionValues(); } if (pixelInfoView.isCollapsiblePaneVisible(PixelInfoView.TIME_INDEX)) { updateTimeValues(); } if (pixelInfoView.isCollapsiblePaneVisible(PixelInfoView.BANDS_INDEX)) { updateBandPixelValues(); } if (pixelInfoView.isCollapsiblePaneVisible(PixelInfoView.TIE_POINT_GRIDS_INDEX)) { updateTiePointGridPixelValues(); } if (pixelInfoView.isCollapsiblePaneVisible(PixelInfoView.FLAGS_INDEX)) { updateFlagPixelValues(); } fireTableChanged(clearRasterTableSelection); } private void resetPositionTableModel() { positionModel.clear(); if (currentRaster != null) { final GeoCoding geoCoding = currentRaster.getGeoCoding(); positionModel.addRow("Image-X", "", "pixel"); positionModel.addRow("Image-Y", "", "pixel"); //todo [Multisize_products] ask for something else than multisize (scenetomodeltransform) if (getCurrentProduct().isMultiSize()) { positionModel.addRow("Scene-X", "", "pixel"); positionModel.addRow("Scene-Y", "", "pixel"); } if (geoCoding != null) { positionModel.addRow("Longitude", "", "degree"); positionModel.addRow("Latitude", "", "degree"); if (geoCoding instanceof MapGeoCoding) { final MapGeoCoding mapGeoCoding = (MapGeoCoding) geoCoding; final String mapUnit = mapGeoCoding.getMapInfo().getMapProjection().getMapUnit(); positionModel.addRow("Map-X", "", mapUnit); positionModel.addRow("Map-Y", "", mapUnit); } else if (geoCoding instanceof CrsGeoCoding) { String xAxisUnit = geoCoding.getMapCRS().getCoordinateSystem().getAxis(0).getUnit().toString(); String yAxisUnit = geoCoding.getMapCRS().getCoordinateSystem().getAxis(1).getUnit().toString(); positionModel.addRow("Map-X", "", xAxisUnit); positionModel.addRow("Map-Y", "", yAxisUnit); } } } } private void updatePositionValues() { final boolean availableInRaster = pixelPosValidInRaster && coordinatesAreInRasterBounds(currentRaster, pixelX, pixelY, rasterLevel); final boolean availableInScene = isSampleValueAvailableInScene(); final double offset = 0.5 + (pixelInfoView.getShowPixelPosOffset1() ? 1.0 : 0.0); final double pX = levelZeroRasterX + offset; final double pY = levelZeroRasterY + offset; String tix, tiy, tsx, tsy, tmx, tmy, tgx, tgy; tix = tiy = tsx = tsy = tmx = tmy = tgx = tgy = INVALID_POS_TEXT; GeoCoding geoCoding = currentRaster.getGeoCoding(); if (availableInRaster) { if (pixelInfoView.getShowPixelPosDecimal()) { tix = String.valueOf(pX); tiy = String.valueOf(pY); } else { tix = String.valueOf((int) Math.floor(pX)); tiy = String.valueOf((int) Math.floor(pY)); } } if (getCurrentProduct().isMultiSize()) { if (!availableInScene) { tsx = PixelInfoViewModelUpdater.INVALID_POS_TEXT; tsy = PixelInfoViewModelUpdater.INVALID_POS_TEXT; } else { double sX = levelZeroSceneX + offset; double sY = levelZeroSceneY + offset; if (pixelInfoView.getShowPixelPosDecimal()) { tsx = String.valueOf(sX); tsy = String.valueOf(sY); } else { tsx = String.valueOf((int) Math.floor(sX)); tsy = String.valueOf((int) Math.floor(sY)); } } } if (availableInRaster && geoCoding != null) { PixelPos pixelPos = new PixelPos(pX, pY); GeoPos geoPos = geoCoding.getGeoPos(pixelPos, null); if (pixelInfoView.getShowGeoPosDecimals()) { tgx = String.format("%.6f", geoPos.getLon()); tgy = String.format("%.6f", geoPos.getLat()); } else { tgx = geoPos.getLonString(); tgy = geoPos.getLatString(); } if (geoCoding instanceof MapGeoCoding) { final MapGeoCoding mapGeoCoding = (MapGeoCoding) geoCoding; final MapTransform mapTransform = mapGeoCoding.getMapInfo().getMapProjection().getMapTransform(); Point2D mapPoint = mapTransform.forward(geoPos, null); tmx = String.valueOf(MathUtils.round(mapPoint.getX(), 10000.0)); tmy = String.valueOf(MathUtils.round(mapPoint.getY(), 10000.0)); } else if (geoCoding instanceof CrsGeoCoding) { MathTransform transform = geoCoding.getImageToMapTransform(); try { DirectPosition position = transform.transform(new DirectPosition2D(pX, pY), null); double[] coordinate = position.getCoordinate(); tmx = String.valueOf(coordinate[0]); tmy = String.valueOf(coordinate[1]); } catch (TransformException ignore) { } } } int rowCount = 0; positionModel.updateValue(tix, rowCount++); positionModel.updateValue(tiy, rowCount++); if (getCurrentProduct().isMultiSize()) { positionModel.updateValue(tsx, rowCount++); positionModel.updateValue(tsy, rowCount++); } if (geoCoding != null) { positionModel.updateValue(tgx, rowCount++); positionModel.updateValue(tgy, rowCount++); if (geoCoding instanceof MapGeoCoding || geoCoding instanceof CrsGeoCoding) { positionModel.updateValue(tmx, rowCount++); positionModel.updateValue(tmy, rowCount); } } } private void resetTimeTableModel() { timeModel.clear(); if (currentRaster != null) { timeModel.addRow("Date", "", "YYYY-MM-DD"); timeModel.addRow("Time (UTC)", "", "HH:MM:SS:mm [AM/PM]"); } } private void updateTimeValues() { final ProductData.UTC utcStartTime = currentProduct.getStartTime(); final ProductData.UTC utcEndTime = currentProduct.getEndTime(); boolean isAvailable = currentProduct.isMultiSize() ? isSampleValueAvailableInScene() : isSampleValueAvailableInRaster() ; if (utcStartTime == null || utcEndTime == null || !isAvailable) { timeModel.updateValue("No date information", 0); timeModel.updateValue("No time information", 1); } else { final ProductData.UTC utcCurrentLine; if(currentProduct.isMultiSize()) { utcCurrentLine = ProductUtils.getPixelScanTime(currentProduct, levelZeroSceneX + 0.5, levelZeroSceneY + 0.5); } else { utcCurrentLine = ProductUtils.getPixelScanTime(currentRaster, levelZeroRasterX + 0.5, levelZeroRasterY + 0.5); } Assert.notNull(utcCurrentLine, "utcCurrentLine"); final Calendar currentLineTime = utcCurrentLine.getAsCalendar(); final String dateString = String.format("%1$tF", currentLineTime); final String timeString = String.format("%1$tI:%1$tM:%1$tS:%1$tL %1$Tp", currentLineTime); timeModel.updateValue(dateString, 0); timeModel.updateValue(timeString, 1); } } private void resetBandTableModel() { bandModel.clear(); if (currentRaster != null) { final int numBands = currentProduct.getNumBands(); for (int i = 0; i < numBands; i++) { final Band band = currentProduct.getBandAt(i); if (shouldDisplayBand(band)) { bandModel.addRow(band.getName(), "", band.getUnit()); } } } } private void updateBandPixelValues() { for (int i = 0; i < bandModel.getRowCount(); i++) { final String bandName = (String) bandModel.getValueAt(i, 0); bandModel.updateValue(getPixelString(currentProduct.getBand(bandName)), i); } } private int getBandRowCount() { int rowCount = 0; if (currentProduct != null) { Band[] bands = currentProduct.getBands(); for (final Band band : bands) { if (shouldDisplayBand(band)) { rowCount++; } } } return rowCount; } private boolean shouldDisplayBand(final Band band) { PixelInfoView.DisplayFilter displayFilter = pixelInfoView.getDisplayFilter(); if (displayFilter != null) { return displayFilter.accept(band); } return band.hasRasterData(); } private void resetTiePointGridTableModel() { tiePointModel.clear(); if (currentRaster != null) { final int numTiePointGrids = currentProduct.getNumTiePointGrids(); for (int i = 0; i < numTiePointGrids; i++) { final TiePointGrid tiePointGrid = currentProduct.getTiePointGridAt(i); tiePointModel.addRow(tiePointGrid.getName(), "", tiePointGrid.getUnit()); } } } private void updateTiePointGridPixelValues() { for (int i = 0; i < tiePointModel.getRowCount(); i++) { final TiePointGrid grid = currentProduct.getTiePointGrid((String) tiePointModel.getValueAt(i, 0)); tiePointModel.updateValue(getPixelString(grid), i); } } private void resetFlagTableModel() { flagModel.clear(); if (currentRaster != null) { for (Band band : currentFlagBands) { // currentFlagBands is already filtered for "equals size" in registerFlagDatasets final FlagCoding flagCoding = band.getFlagCoding(); final int numFlags = flagCoding.getNumAttributes(); final String bandNameDot = band.getName() + "."; for (int j = 0; j < numFlags; j++) { String name = bandNameDot + flagCoding.getAttributeAt(j).getName(); flagModel.addRow(name, "", ""); } } } } private void updateFlagPixelValues() { if (flagModel.getRowCount() != getFlagRowCount()) { resetFlagTableModel(); } int rowIndex = 0; for (Band band : currentFlagBands) { long pixelValue; boolean available; if (band.getImageToModelTransform().equals(currentRaster.getImageToModelTransform()) && band.getSceneToModelTransform().equals(currentRaster.getSceneToModelTransform())) { available = pixelPosValidInRaster; pixelValue = available ? ProductUtils.getGeophysicalSampleAsLong(band, pixelX, pixelY, rasterLevel) : 0; } else { PixelPos rasterPos = new PixelPos(); final Point2D.Double scenePos = new Point2D.Double(sceneX, sceneY); final Point2D modelPos; try { modelPos = band.getSceneToModelTransform().transform(scenePos, new Point2D.Double()); final MultiLevelModel multiLevelModel = band.getMultiLevelModel(); final int level = getLevel(multiLevelModel); multiLevelModel.getModelToImageTransform(level).transform(modelPos, rasterPos); final int rasterX = (int) Math.floor(rasterPos.getX()); final int rasterY = (int) Math.floor(rasterPos.getY()); available = coordinatesAreInRasterBounds(band, rasterX, rasterY, level); pixelValue = available ? ProductUtils.getGeophysicalSampleAsLong(band, rasterX, rasterY, level) : 0; } catch (TransformException e) { available = false; pixelValue = -1; } } for (int j = 0; j < band.getFlagCoding().getNumAttributes(); j++) { if (available) { MetadataAttribute attribute = band.getFlagCoding().getAttributeAt(j); final ProductData flagData = attribute.getData(); final int flagMask; final int flagValue; if (flagData.getNumElems() == 2) { flagMask = flagData.getElemIntAt(0); flagValue = flagData.getElemIntAt(1); } else { flagMask = flagValue = flagData.getElemInt(); } flagModel.updateValue(String.valueOf((pixelValue & flagMask) == flagValue), rowIndex); } else { flagModel.updateValue(INVALID_POS_TEXT, rowIndex); } rowIndex++; } } } private void registerFlagDatasets() { Vector<Band> flagBandsVector = new Vector<>(); if (currentProduct != null) { final Band[] bands = currentProduct.getBands(); for (Band band : bands) { if (isFlagBand(band)) { flagBandsVector.add(band); } } } currentFlagBands = flagBandsVector.toArray(new Band[flagBandsVector.size()]); } private boolean isFlagBand(final Band band) { return band.getFlagCoding() != null; } private int getFlagRowCount() { int rowCount = 0; for (Band band : currentFlagBands) { rowCount += band.getFlagCoding().getNumAttributes(); } return rowCount; } private String getPixelString(RasterDataNode raster) { if (raster.getImageToModelTransform().equals(currentRaster.getImageToModelTransform()) && raster.getSceneToModelTransform().equals(currentRaster.getSceneToModelTransform())) { if (!pixelPosValidInRaster) { return RasterDataNode.INVALID_POS_TEXT; } return getPixelString(raster, pixelX, pixelY, rasterLevel); } final Point2D.Double scenePos = new Point2D.Double(sceneX, sceneY); Point2D.Double modelPos = new Point2D.Double(); try { raster.getSceneToModelTransform().transform(scenePos, modelPos); if (Double.isNaN(modelPos.getX()) || Double.isNaN(modelPos.getY())) { return PixelInfoViewModelUpdater.INVALID_POS_TEXT; } } catch (TransformException e) { return PixelInfoViewModelUpdater.INVALID_POS_TEXT; } final MultiLevelModel multiLevelModel = raster.getMultiLevelModel(); final int level = getLevel(multiLevelModel); final PixelPos rasterPos = (PixelPos) multiLevelModel.getModelToImageTransform(level).transform(modelPos, new PixelPos()); final int rasterX = floor(rasterPos.getX()); final int rasterY = floor(rasterPos.getY()); if (!coordinatesAreInRasterBounds(raster, rasterX, rasterY, level)) { return RasterDataNode.INVALID_POS_TEXT; } return getPixelString(raster, rasterX, rasterY, level); } //todo code duplication with spectrumtopcomponent - move to single class - tf 20151119 private int getLevel(MultiLevelModel multiLevelModel) { if (rasterLevel < multiLevelModel.getLevelCount()) { return rasterLevel; } return ImageLayer.getLevel(multiLevelModel, currentView.getViewport()); } private String getPixelString(RasterDataNode raster, int x, int y, int level) { if (isPixelValid(raster, x, y, level)) { if (raster.isScalingApplied() || ProductData.isFloatingPointType(raster.getDataType())) { int dataType = raster.getGeophysicalDataType(); if (dataType == ProductData.TYPE_FLOAT64) { double pixel = ProductUtils.getGeophysicalSampleAsDouble(raster, x, y, level); return String.format("%.10f", pixel); } else if (dataType == ProductData.TYPE_FLOAT32) { double pixel = ProductUtils.getGeophysicalSampleAsDouble(raster, x, y, level); return String.format("%.5f", pixel); } } return String.valueOf(ProductUtils.getGeophysicalSampleAsLong(raster, x, y, level)); } else { return RasterDataNode.NO_DATA_TEXT; } } //todo code duplication with spectrumtopcomponent - move to single class - tf 20151119 private boolean isPixelValid(RasterDataNode raster, int pixelX, int pixelY, int level) { if (raster.isValidMaskUsed()) { PlanarImage image = ImageManager.getInstance().getValidMaskImage(raster, level); Raster data = getRasterTile(image, pixelX, pixelY); return data.getSample(pixelX, pixelY, 0) != 0; } else { return true; } } //todo code duplication with spectrumtopcomponent - move to single class - tf 20151119 private Raster getRasterTile(PlanarImage image, int pixelX, int pixelY) { final int tileX = image.XToTileX(pixelX); final int tileY = image.YToTileY(pixelY); return image.getTile(tileX, tileY); } //todo code duplication with spectrumtopcomponent - move to single class - tf 20151119 private boolean coordinatesAreInRasterBounds(RasterDataNode raster, int x, int y, int level) { final RenderedImage levelImage = raster.getSourceImage().getImage(level); return x >= 0 && y >= 0 && x < levelImage.getWidth() && y < levelImage.getHeight(); } /** * Convenience method that gives the largest integer smaller than the double value or * -1 if value is Doubl.NaN. */ private int floor(double value) { if (Double.isNaN(value)) { return -1; } return (int) Math.floor(value); } private boolean isSampleValueAvailableInScene() { return levelZeroSceneX >= 0 && levelZeroSceneY >= 0 && levelZeroSceneX < currentProduct.getSceneRasterWidth() && levelZeroSceneY < currentProduct.getSceneRasterHeight(); } private boolean isSampleValueAvailableInRaster() { return levelZeroRasterX >= 0 && levelZeroRasterY >= 0 && levelZeroRasterX < currentRaster.getRasterWidth() && levelZeroRasterY < currentRaster.getRasterHeight(); } void clearProductNodeRefs() { currentProduct = null; currentRaster = null; currentView = null; currentFlagBands = new Band[0]; } }