/* * Copyright (C) 2014 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.core.gpf.ui.mosaic; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.accessors.MapEntryAccessor; import org.esa.snap.core.dataio.ProductIO; import org.esa.snap.core.datamodel.CrsGeoCoding; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.PixelPos; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.gpf.GPF; import org.esa.snap.core.gpf.annotations.ParameterDescriptorFactory; import org.esa.snap.core.gpf.common.MosaicOp; import org.esa.snap.core.util.math.MathUtils; import org.esa.snap.ui.BoundsInputPanel; import org.esa.snap.ui.WorldMapPaneDataModel; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.geometry.Envelope; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.*; /** * @author Marco Peters * @author Ralf Quast * @since BEAM 4.7 */ class MosaicFormModel { public static final String PROPERTY_UPDATE_PRODUCT = "updateProduct"; public static final String PROPERTY_UPDATE_MODE = "updateMode"; public static final String PROPERTY_SHOW_SOURCE_PRODUCTS = "showSourceProducts"; public static final String PROPERTY_ELEVATION_MODEL_NAME = "elevationModelName"; public static final String PROPERTY_ORTHORECTIFY = "orthorectify"; public static final String PROPERTY_WEST_BOUND = "westBound"; public static final String PROPERTY_NORTH_BOUND = "northBound"; public static final String PROPERTY_EAST_BOUND = "eastBound"; public static final String PROPERTY_SOUTH_BOUND = "southBound"; public static final String PROPERTY_CRS = "crs"; public static final String PROPERTY_PIXEL_SIZE_X = "pixelSizeX"; public static final String PROPERTY_PIXEL_SIZE_Y = "pixelSizeY"; public static final String PROPERTY_MAX_VALUE = "maxValue"; public static final String PROPERTY_MIN_VALUE = "minValue"; private final PropertySet container; private final Map<String, Object> parameterMap = new HashMap<>(); private final Map<File, Product> sourceProductMap = Collections.synchronizedMap(new HashMap<File, Product>()); private final WorldMapPaneDataModel worldMapModel = new WorldMapPaneDataModel(); private MosaicForm parentForm; MosaicFormModel(MosaicForm parentForm) { this.parentForm = parentForm; container = ParameterDescriptorFactory.createMapBackedOperatorPropertyContainer("Mosaic", parameterMap); addTransientProperty(PROPERTY_UPDATE_PRODUCT, Product.class); addTransientProperty(PROPERTY_UPDATE_MODE, Boolean.class); addTransientProperty(PROPERTY_SHOW_SOURCE_PRODUCTS, Boolean.class); container.setDefaultValues(); container.setValue(PROPERTY_UPDATE_MODE, false); container.setValue(PROPERTY_SHOW_SOURCE_PRODUCTS, false); container.addPropertyChangeListener(PROPERTY_SHOW_SOURCE_PRODUCTS, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (Boolean.TRUE.equals(evt.getNewValue())) { final Collection<Product> products = sourceProductMap.values(); worldMapModel.setProducts(products.toArray(new Product[products.size()])); } else { worldMapModel.setProducts(null); } } }); } private void addTransientProperty(String propertyName, Class<?> propertyType) { PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, propertyType); descriptor.setTransient(true); container.addProperty(new Property(descriptor, new MapEntryAccessor(parameterMap, propertyName))); } void setSourceProducts(File[] files) throws IOException { boolean changeSourceProducts = false; if (files != null && files.length > 0) { final List<File> fileList = Arrays.asList(files); final Iterator<Map.Entry<File, Product>> iterator = sourceProductMap.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<File, Product> entry = iterator.next(); if (!fileList.contains(entry.getKey())) { final Product product = entry.getValue(); worldMapModel.removeProduct(product); iterator.remove(); product.dispose(); changeSourceProducts = true; } } for (int i = 0; i < files.length; i++) { final File file = files[i]; Product product = sourceProductMap.get(file); if (product == null) { product = ProductIO.readProduct(file); sourceProductMap.put(file, product); if (Boolean.TRUE.equals(getPropertyValue(PROPERTY_SHOW_SOURCE_PRODUCTS))) { worldMapModel.addProduct(product); } changeSourceProducts = true; } final int refNo = i + 1; if (product.getRefNo() != refNo) { product.resetRefNo(); product.setRefNo(refNo); } } } /* update region selectable map bounds according to the SourceProducts status: REMOVE or NEW product(s) */ if (changeSourceProducts) updateRegionSelectableMapBounds(files); } void updateRegionSelectableMapBounds(File[] files){ /* set default values in case files.length == 0 */ double southBoundVal = 35.0; double northBoundVal = 75.0; double westBoundVal = -15.0; double eastBoundVal = 30.0; if ( (files.length >= 1) && (sourceProductMap.get(files[0]) != null) ) { southBoundVal = computeLatitude(sourceProductMap.get(files[0]), PROPERTY_MIN_VALUE); northBoundVal = computeLatitude(sourceProductMap.get(files[0]), PROPERTY_MAX_VALUE); westBoundVal = computeLongitude(sourceProductMap.get(files[0]), PROPERTY_MIN_VALUE); eastBoundVal = computeLongitude(sourceProductMap.get(files[0]), PROPERTY_MAX_VALUE); } for (int i = 1; i < files.length; i++) { if (sourceProductMap.get(files[i]) != null) { double southBoundValTemp = computeLatitude(sourceProductMap.get(files[i]), PROPERTY_MIN_VALUE); double northBoundValTemp = computeLatitude(sourceProductMap.get(files[i]), PROPERTY_MAX_VALUE); double westBoundValTemp = computeLongitude(sourceProductMap.get(files[i]), PROPERTY_MIN_VALUE); double eastBoundValTemp = computeLongitude(sourceProductMap.get(files[i]), PROPERTY_MAX_VALUE); if (southBoundValTemp < southBoundVal) southBoundVal = southBoundValTemp; if (northBoundValTemp > northBoundVal) northBoundVal = northBoundValTemp; if (westBoundValTemp < westBoundVal) westBoundVal = westBoundValTemp; if (eastBoundValTemp > eastBoundVal) eastBoundVal = eastBoundValTemp; } } parentForm.setCardinalBounds(southBoundVal, northBoundVal, westBoundVal,eastBoundVal); } double computeLatitude(Product product, String level){ Double[] latitudePoints = { product.getSceneGeoCoding().getGeoPos(new PixelPos(0, 0), null).getLat(), product.getSceneGeoCoding().getGeoPos(new PixelPos(0, product.getSceneRasterHeight()), null).getLat(), product.getSceneGeoCoding().getGeoPos(new PixelPos(product.getSceneRasterWidth(), 0), null).getLat(), product.getSceneGeoCoding().getGeoPos(new PixelPos(product.getSceneRasterWidth(), product.getSceneRasterHeight()), null).getLat() }; switch(level) { case PROPERTY_MIN_VALUE : return (double) Collections.min(Arrays.asList(latitudePoints)); case PROPERTY_MAX_VALUE : return (double) Collections.max(Arrays.asList(latitudePoints)); default : return Double.MAX_VALUE; } } double computeLongitude(Product product, String level){ Double[] longitudePoints = { product.getSceneGeoCoding().getGeoPos(new PixelPos(0, 0), null).getLon(), product.getSceneGeoCoding().getGeoPos(new PixelPos(0, product.getSceneRasterHeight()), null).getLon(), product.getSceneGeoCoding().getGeoPos(new PixelPos(product.getSceneRasterWidth(), 0), null).getLon(), product.getSceneGeoCoding().getGeoPos(new PixelPos(product.getSceneRasterWidth(), product.getSceneRasterHeight()), null).getLon() }; switch(level) { case PROPERTY_MIN_VALUE : return (double) Collections.min(Arrays.asList(longitudePoints)); case PROPERTY_MAX_VALUE : return (double) Collections.max(Arrays.asList(longitudePoints)); default : return Double.MAX_VALUE; } } Map<String, Object> getParameterMap() { return parameterMap; } Map<String, Product> getSourceProductMap() { final HashMap<String, Product> map = new HashMap<>(sourceProductMap.size()); for (final Product product : sourceProductMap.values()) { map.put(GPF.SOURCE_PRODUCT_FIELD_NAME + product.getRefNo(), product); } if (Boolean.TRUE.equals(container.getValue(PROPERTY_UPDATE_MODE))) { final Product updateProduct = getUpdateProduct(); if (updateProduct != null) { map.put(PROPERTY_UPDATE_PRODUCT, updateProduct); } } return map; } boolean isUpdateMode() { return Boolean.TRUE.equals(getPropertyValue(PROPERTY_UPDATE_MODE)); } Product getUpdateProduct() { final Object value = getPropertyValue(PROPERTY_UPDATE_PRODUCT); if (value instanceof Product) { return (Product) value; } return null; } void setUpdateProduct(Product product) { setPropertyValue(PROPERTY_UPDATE_PRODUCT, product); if (product != null && product.getSceneGeoCoding() != null && product.getSceneGeoCoding().getMapCRS() != null) { setTargetCRS(product.getSceneGeoCoding().getMapCRS().toWKT()); } } MosaicOp.Variable[] getVariables() { return (MosaicOp.Variable[]) getPropertyValue("variables"); } MosaicOp.Condition[] getConditions() { return (MosaicOp.Condition[]) getPropertyValue("conditions"); } PropertySet getPropertySet() { return container; } public Object getPropertyValue(String propertyName) { return container.getValue(propertyName); } public void setPropertyValue(String propertyName, Object value) { container.setValue(propertyName, value); } public Product getReferenceProduct() throws IOException { for (Product product : sourceProductMap.values()) { if (product.getRefNo() == 1) { return product; } } return null; } public Product getBoundaryProduct() throws FactoryException, TransformException { final CoordinateReferenceSystem mapCRS = getTargetCRS(); if (mapCRS != null) { final ReferencedEnvelope envelope = getTargetEnvelope(); final Envelope mapEnvelope = envelope.transform(mapCRS, true); final double pixelSizeX = (Double) getPropertyValue(PROPERTY_PIXEL_SIZE_X); final double pixelSizeY = (Double) getPropertyValue(PROPERTY_PIXEL_SIZE_Y); final int w = MathUtils.floorInt(mapEnvelope.getSpan(0) / pixelSizeX); final int h = MathUtils.floorInt(mapEnvelope.getSpan(1) / pixelSizeY); final Product product = new Product("mosaic", "MosaicBounds", w, h); final GeoCoding geoCoding = new CrsGeoCoding(mapCRS, w, h, mapEnvelope.getMinimum(0), mapEnvelope.getMaximum(1), pixelSizeX, pixelSizeY); product.setSceneGeoCoding(geoCoding); return product; } return null; } void setTargetCRS(String crs) { setPropertyValue("crs", crs); } CoordinateReferenceSystem getTargetCRS() throws FactoryException { final String crs = (String) getPropertyValue("crs"); if (crs == null) { return null; } try { return CRS.parseWKT(crs); } catch (FactoryException ignored) { return CRS.decode(crs, true); } } ReferencedEnvelope getTargetEnvelope() { final double west = (Double) getPropertyValue(BoundsInputPanel.PROPERTY_WEST_BOUND); final double north = (Double) getPropertyValue(BoundsInputPanel.PROPERTY_NORTH_BOUND); final double east = (Double) getPropertyValue(BoundsInputPanel.PROPERTY_EAST_BOUND); final double south = (Double) getPropertyValue(BoundsInputPanel.PROPERTY_SOUTH_BOUND); final Rectangle2D bounds = new Rectangle2D.Double(); bounds.setFrameFromDiagonal(west, north, east, south); return new ReferencedEnvelope(bounds, DefaultGeographicCRS.WGS84); } public WorldMapPaneDataModel getWorldMapModel() { return worldMapModel; } public String getElevationModelName() { boolean orthorectify = (boolean) getPropertyValue(PROPERTY_ORTHORECTIFY); if (orthorectify) { return (String) getPropertyValue(PROPERTY_ELEVATION_MODEL_NAME); } return null; } }