/* * 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.timeseries.core.timeseries.datamodel; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.core.SubProgressMonitor; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.ImageInfo; import org.esa.snap.core.datamodel.MetadataAttribute; import org.esa.snap.core.datamodel.MetadataElement; 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.ProductNodeListenerAdapter; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.gpf.GPF; import org.esa.snap.timeseries.core.insitu.InsituSource; import org.esa.snap.util.Guardian; import org.esa.snap.util.ProductUtils; import org.esa.snap.util.StringUtils; import org.esa.snap.util.SystemUtils; import java.io.File; import java.net.URI; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * <p><i>Note that this class is not yet public API. Interface may change in future releases.</i></p> * * @author Thomas Storm */ final class TimeSeriesImpl extends AbstractTimeSeries { private final static float LAT_LON_EPSILON = 0.1e-6f; private final Map<RasterDataNode, TimeCoding> rasterTimeMap = new WeakHashMap<>(); private final List<TimeSeriesListener> listeners = new ArrayList<>(); private final AxisMapping axisMapping = new AxisMapping(); private final Map<Placemark, GeoPos> pinRelationMap = new HashMap<>(); private Product tsProduct; private List<ProductLocation> productLocationList; private Map<String, Product> productTimeMap; private InsituSource insituSource; private Set<String> insituVariablesSelections = new HashSet<>(); private volatile boolean isAdjustingImageInfos; /** * Used to create a TimeSeries from within a ProductReader * * @param tsProduct the product read * @param pm a progress monitor */ TimeSeriesImpl(Product tsProduct, ProgressMonitor pm) { init(tsProduct); initProductLocations(); storeProductsInMap(pm); setSourceImages(); fixBandTimeCodings(); updateAutoGrouping(); initImageInfos(); } /** * Used to create a new TimeSeries from the user interface. * * @param tsProduct the newly created time series product * @param productLocations the product location to be used * @param variableNames the currently selected names of variables */ TimeSeriesImpl(Product tsProduct, List<ProductLocation> productLocations, List<String> variableNames) { init(tsProduct); for (ProductLocation location : productLocations) { addProductLocation(location); } storeProductsInMap(ProgressMonitor.NULL); for (String variable : variableNames) { setEoVariableSelected(variable, true); } setProductTimeCoding(tsProduct); initImageInfos(); } @Override public Product getTsProduct() { return tsProduct; } @Override public List<ProductLocation> getProductLocations() { return productLocationList; } private void initProductLocations() { MetadataElement tsElem = tsProduct.getMetadataRoot().getElement(TIME_SERIES_ROOT_NAME); MetadataElement productListElem = tsElem.getElement(PRODUCT_LOCATIONS); MetadataElement[] productElems = productListElem.getElements(); productLocationList = new ArrayList<>(productElems.length); final File fileLocation = tsProduct.getProduct().getFileLocation(); for (MetadataElement productElem : productElems) { String path = productElem.getAttributeString(PL_PATH); File productFile; if (fileLocation != null) { final URI resolvedUri = fileLocation.toURI().resolve(path); productFile = new File(resolvedUri); } else { productFile = new File(path); } String type = productElem.getAttributeString(PL_TYPE); productLocationList.add(new ProductLocation(ProductLocationType.valueOf(type), productFile.getAbsolutePath())); } } @Override public List<String> getEoVariables() { MetadataElement[] variableElems = getVariableMetadataElements(); List<String> variables = new ArrayList<>(); for (MetadataElement varElem : variableElems) { variables.add(varElem.getAttributeString(VARIABLE_NAME)); } return variables; } @Override public void addProductLocation(ProductLocation productLocation) { if (productLocationList == null) { productLocationList = new ArrayList<>(); } if (!productLocationList.contains(productLocation)) { final Logger logger = SystemUtils.LOG; final ProductLocationType type = productLocation.getProductLocationType(); final String path = productLocation.getPath(); logger.log(Level.INFO, "Try to load product location type: '" + type + "' at path: '" + path + "'"); addProductLocationMetadata(productLocation); productLocationList.add(productLocation); List<String> variables = getEoVariables(); final Map<String, Product> products = productLocation.getProducts(ProgressMonitor.NULL); for (Map.Entry<String, Product> productEntry : products.entrySet()) { final Product product = productEntry.getValue(); // todo - see jira issue AQUAMAR-4 // test if the added source product is compatible with the existing time series product (CRS, width, height) // If not ... reproject the product before it can be added. if (product.getStartTime() != null) { addProductMetadata(productEntry); addToVariableList(product); for (String variable : variables) { if (isEoVariableSelected(variable)) { addSpecifiedBandOfGivenProduct(variable, product); } } } else { // todo log in gui as well as in console final String absolutePath = product.getFileLocation().getAbsolutePath(); logger.log(Level.WARNING, "The product '" + absolutePath + "' does not contain time information."); } } fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.PROPERTY_PRODUCT_LOCATIONS, productLocationList, this)); } } private void addProductMetadata(Map.Entry<String, Product> productEntry) { MetadataElement productElement = tsProduct.getMetadataRoot(). getElement(TIME_SERIES_ROOT_NAME). getElement(SOURCE_PRODUCT_PATHS); ProductData productPath = ProductData.createInstance(productEntry.getKey()); int length = productElement.getElements().length + 1; MetadataElement elem = new MetadataElement(String.format("%s.%s", SOURCE_PRODUCT_PATHS, Integer.toString(length))); elem.addAttribute(new MetadataAttribute(PL_PATH, productPath, true)); productElement.addElement(elem); } @Override public void removeProductLocation(ProductLocation productLocation) { // remove metadata final MetadataElement timeSeriesRootElement = tsProduct.getMetadataRoot().getElement(TIME_SERIES_ROOT_NAME); MetadataElement productLocationsElement = timeSeriesRootElement.getElement(PRODUCT_LOCATIONS); removeAttributeWithValue(PL_PATH, productLocation.getPath(), productLocationsElement); // remove variables for this productLocation updateAutoGrouping(); // TODO ??? final Band[] bands = tsProduct.getBands(); final MetadataElement sourceProductPaths = timeSeriesRootElement.getElement(SOURCE_PRODUCT_PATHS); for (Map.Entry<String, Product> productEntry : productLocation.getProducts(ProgressMonitor.NULL).entrySet()) { final Product product = productEntry.getValue(); removeAttributeWithValue(PL_PATH, productEntry.getKey(), sourceProductPaths); String timeString = formatTimeString(product); productTimeMap.remove(timeString); for (Band band : bands) { if (band.getName().endsWith(timeString)) { tsProduct.removeBand(band); } } } productLocation.closeProducts(); productLocationList.remove(productLocation); fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.PROPERTY_PRODUCT_LOCATIONS, productLocationList, this)); } private void removeAttributeWithValue(String attributeName, String value, MetadataElement parentElement) { final MetadataElement[] childElements = parentElement.getElements(); for (MetadataElement elem : childElements) { if (elem.getAttributeString(attributeName).equals(value)) { parentElement.removeElement(elem); return; } } } private Band getSourceBand(String destBandName) { final int lastUnderscore = destBandName.lastIndexOf(SEPARATOR); final String normalizedBandName = destBandName.substring(0, lastUnderscore); final String timePart = destBandName.substring(lastUnderscore + 1); final Product srcProduct = productTimeMap.get(timePart); if (srcProduct == null) { return null; } for (Band band : srcProduct.getBands()) { if (normalizedBandName.equals(band.getName())) { return band; } } return null; } private void setSourceImages() { for (Band destBand : tsProduct.getBands()) { final Band raster = getSourceBand(destBand.getName()); if (raster != null) { destBand.setSourceImage(raster.getSourceImage()); } } } private void fixBandTimeCodings() { for (Band destBand : tsProduct.getBands()) { final String destBandName = destBand.getName(); final Band raster = getSourceBand(destBandName); final TimeCoding timeCoding; if (raster != null) { timeCoding = GridTimeCoding.create(raster.getProduct()); } else { ProductData.UTC time = extractUtcTime(destBandName); timeCoding = new GridTimeCoding(time, time); } rasterTimeMap.put(destBand, timeCoding); } } private ProductData.UTC extractUtcTime(String name) { final String timePart = name.substring(name.length() - DATE_FORMAT.length()); try { return ProductData.UTC.parse(timePart, DATE_FORMAT.substring(0, DATE_FORMAT.lastIndexOf("."))); } catch (ParseException e) { throw new IllegalStateException("The raster name '" + name + "' does not contain the time sequence. " + DATE_FORMAT); } } private void init(Product product) { this.tsProduct = product; productTimeMap = new HashMap<>(); createTimeSeriesMetadataStructure(product); // to reconstruct the source image which will be nulled when // a product is reopened after saving tsProduct.addProductNodeListener(new SourceImageReconstructor()); axisMapping.addAxisMappingListener(new AxisMappingListener()); } private void storeProductsInMap(ProgressMonitor pm) { pm.beginTask("Loading time series...", 2); try { final List<Product> allProducts = getAllProducts(new SubProgressMonitor(pm, 1)); reprojectProducts(allProducts, new SubProgressMonitor(pm, 1)); } finally { pm.done(); } } private void reprojectProducts(List<Product> allProducts, ProgressMonitor pm) { pm.beginTask("Reprojecting source products...", allProducts.size()); try { for (Product product : allProducts) { if (pm.isCanceled()) { return; } if (!product.isCompatibleProduct(tsProduct, LAT_LON_EPSILON)) { HashMap<String, Product> productToBeReprojectedMap = new HashMap<>(); productToBeReprojectedMap.put("source", product); productToBeReprojectedMap.put("collocateWith", tsProduct); final Product collocatedProduct = GPF.createProduct("Reproject", createProjectionParameters(), productToBeReprojectedMap); collocatedProduct.setStartTime(product.getStartTime()); collocatedProduct.setEndTime(product.getEndTime()); product = collocatedProduct; } productTimeMap.put(formatTimeString(product), product); pm.worked(1); } } finally { pm.done(); } } private HashMap<String, Object> createProjectionParameters() { HashMap<String, Object> projParameters = new HashMap<>(); projParameters.put("resamplingName", "Nearest"); projParameters.put("includeTiePointGrids", false); return projParameters; } @Override public boolean isEoVariableSelected(String variableName) { final MetadataElement[] variables = getVariableMetadataElements(); for (MetadataElement elem : variables) { if (elem.getAttributeString(VARIABLE_NAME).equals(variableName)) { return Boolean.parseBoolean(elem.getAttributeString(VARIABLE_SELECTION)); } } return false; } @Override public void setEoVariableSelected(String variableName, boolean selected) { // set in metadata final MetadataElement[] variables = getVariableMetadataElements(); for (MetadataElement elem : variables) { if (elem.getAttributeString(VARIABLE_NAME).equals(variableName)) { elem.setAttributeString(VARIABLE_SELECTION, String.valueOf(selected)); } } // set in product if (selected) { for (Product product : getAllProducts(ProgressMonitor.NULL)) { addSpecifiedBandOfGivenProduct(variableName, product); } } else { final Band[] bands = tsProduct.getBands(); for (Band band : bands) { if (variableName.equals(rasterToVariableName(band.getName()))) { tsProduct.removeBand(band); } } } fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.PROPERTY_EO_VARIABLE_SELECTION, null, this)); } @Override public boolean isInsituVariableSelected(String variableName) { return insituVariablesSelections.contains(variableName); } @Override public void setInsituVariableSelected(String variableName, boolean selected) { boolean hasChanged; if (selected) { hasChanged = insituVariablesSelections.add(variableName); } else { hasChanged = insituVariablesSelections.remove(variableName); } if (hasChanged) { fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.PROPERTY_INSITU_VARIABLE_SELECTION, variableName, this)); } } @Override public List<Band> getBandsForVariable(String variableName) { final List<Band> bands = new ArrayList<>(); for (Band band : tsProduct.getBands()) { if (variableName.equals(rasterToVariableName(band.getName()))) { bands.add(band); } } sortBands(bands); return bands; } @Override public List<Band> getBandsForProductLocation(ProductLocation location) { final List<Band> bands = new ArrayList<>(); Map<String, Product> products = location.getProducts(ProgressMonitor.NULL); for (Product product : products.values()) { String timeString = formatTimeString(product); // TODO relies on one timecoding per product... thats not good (mz, ts, 2010-07-12) for (Band band : tsProduct.getBands()) { if (band.getName().endsWith(timeString)) { bands.add(band); } } } return bands; } @Override public Map<RasterDataNode, TimeCoding> getRasterTimeMap() { return Collections.unmodifiableMap(rasterTimeMap); } @Override public boolean isAutoAdjustingTimeCoding() { final MetadataElement tsRootElement = tsProduct.getMetadataRoot().getElement(TIME_SERIES_ROOT_NAME); if (!tsRootElement.containsAttribute(AUTO_ADJUSTING_TIME_CODING)) { setAutoAdjustingTimeCoding(true); } final String autoAdjustString = tsRootElement.getAttributeString(AUTO_ADJUSTING_TIME_CODING); return Boolean.parseBoolean(autoAdjustString); } @Override public void setAutoAdjustingTimeCoding(boolean autoAdjust) { final MetadataElement tsRootElement = tsProduct.getMetadataRoot().getElement(TIME_SERIES_ROOT_NAME); tsRootElement.setAttributeString(AUTO_ADJUSTING_TIME_CODING, Boolean.toString(autoAdjust)); } @Override public boolean isProductCompatible(Product product, String rasterName) { return product.containsRasterDataNode(rasterName) && tsProduct.isCompatibleProduct(product, LAT_LON_EPSILON); } @Override public TimeCoding getTimeCoding() { return GridTimeCoding.create(tsProduct); } @Override public void setTimeCoding(TimeCoding timeCoding) { final ProductData.UTC startTime = timeCoding.getStartTime(); if (tsProduct.getStartTime().getAsCalendar().compareTo(startTime.getAsCalendar()) != 0) { tsProduct.setStartTime(startTime); fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.START_TIME_PROPERTY_NAME, startTime, this)); } final ProductData.UTC endTime = timeCoding.getEndTime(); if (tsProduct.getEndTime().getAsCalendar().compareTo(endTime.getAsCalendar()) != 0) { tsProduct.setEndTime(endTime); fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.END_TIME_PROPERTY_NAME, endTime, this)); } List<String> variables = getEoVariables(); for (Product product : getAllProducts(ProgressMonitor.NULL)) { for (String variable : variables) { if (isEoVariableSelected(variable)) { addSpecifiedBandOfGivenProduct(variable, product); } } } for (Band band : tsProduct.getBands()) { final TimeCoding bandTimeCoding = getRasterTimeMap().get(band); if (!timeCoding.contains(bandTimeCoding)) { fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.BAND_TO_BE_REMOVED, band, this)); tsProduct.removeBand(band); } } } @Override public void addTimeSeriesListener(TimeSeriesListener listener) { if (!listeners.contains(listener)) { listeners.add(listener); tsProduct.addProductNodeListener(listener); } } @Override public void removeTimeSeriesListener(TimeSeriesListener listener) { listeners.remove(listener); tsProduct.removeProductNodeListener(listener); } @Override public void setInsituSource(InsituSource insituSource) { if (this.insituSource != insituSource) { this.insituSource = insituSource; fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.INSITU_SOURCE_CHANGED, this, this)); } } @Override public InsituSource getInsituSource() { return insituSource; } @Override public void clearInsituPlacemarks() { final PlacemarkGroup pinGroup = tsProduct.getPinGroup(); for (Placemark insituPin : pinRelationMap.keySet()) { pinGroup.remove(insituPin); } pinRelationMap.clear(); } @Override public GeoPos getInsituGeoposFor(Placemark placemark) { return pinRelationMap.get(placemark); } @Override public void registerRelation(Placemark placemark, GeoPos insituGeopos) { pinRelationMap.put(placemark, insituGeopos); tsProduct.getPinGroup().add(placemark); } @Override public boolean hasInsituData() { return insituSource != null && !insituVariablesSelections.isEmpty(); } @Override public Set<String> getSelectedInsituVariables() { return Collections.unmodifiableSet(insituVariablesSelections); } @Override public AxisMapping getAxisMapping() { return axisMapping; } ///////////////////////////////////////////////////////////////////////////////// // private methods ///////////////////////////////////////////////////////////////////////////////// private MetadataElement[] getVariableMetadataElements() { MetadataElement variableListElement = tsProduct.getMetadataRoot(). getElement(TIME_SERIES_ROOT_NAME). getElement(VARIABLES); return variableListElement.getElements(); } private List<Product> getAllProducts(ProgressMonitor pm) { List<Product> result = new ArrayList<>(); pm.beginTask("Scanning product locations ...", productLocationList.size()); try { for (ProductLocation productLocation : productLocationList) { if (pm.isCanceled()) { break; } for (Product product : productLocation.getProducts(ProgressMonitor.NULL).values()) { result.add(product); } pm.worked(1); } } finally { pm.done(); } return result; } private boolean isTimeCodingSet() { return tsProduct.getStartTime() != null; } private void adjustImageInfos(RasterDataNode raster) { if (!isAdjustingImageInfos) { try { isAdjustingImageInfos = true; final String variableName = AbstractTimeSeries.rasterToVariableName(raster.getName()); final List<Band> bandList = getBandsForVariable(variableName); final ImageInfo imageInfo = raster.getImageInfo(ProgressMonitor.NULL); if (imageInfo != null) { for (Band band : bandList) { if (band != raster) { band.setImageInfo(imageInfo.createDeepCopy()); } } } } finally { isAdjustingImageInfos = false; } } } private void sortBands(List<Band> bandList) { Collections.sort(bandList, new Comparator<Band>() { @Override public int compare(Band band1, Band band2) { final Date date1 = rasterTimeMap.get(band1).getStartTime().getAsDate(); final Date date2 = rasterTimeMap.get(band2).getStartTime().getAsDate(); return date1.compareTo(date2); } }); } private void updateAutoGrouping() { tsProduct.setAutoGrouping(StringUtils.join(getEoVariables(), ":")); } private void setProductTimeCoding(Product tsProduct) { for (Band band : tsProduct.getBands()) { final ProductData.UTC rasterStartTime = getRasterTimeMap().get(band).getStartTime(); final ProductData.UTC rasterEndTime = getRasterTimeMap().get(band).getEndTime(); ProductData.UTC tsStartTime = tsProduct.getStartTime(); if (tsStartTime == null || rasterStartTime.getAsDate().before(tsStartTime.getAsDate())) { tsProduct.setStartTime(rasterStartTime); } ProductData.UTC tsEndTime = tsProduct.getEndTime(); if (rasterEndTime != null) { if (tsEndTime == null || rasterEndTime.getAsDate().after(tsEndTime.getAsDate())) { tsProduct.setEndTime(rasterEndTime); } } } } private static void createTimeSeriesMetadataStructure(Product tsProduct) { if (!tsProduct.getMetadataRoot().containsElement(TIME_SERIES_ROOT_NAME)) { final MetadataElement timeSeriesRoot = new MetadataElement(TIME_SERIES_ROOT_NAME); final MetadataElement productListElement = new MetadataElement(PRODUCT_LOCATIONS); final MetadataElement sourceProductPathsElement = new MetadataElement(SOURCE_PRODUCT_PATHS); final MetadataElement variablesListElement = new MetadataElement(VARIABLES); timeSeriesRoot.addElement(productListElement); timeSeriesRoot.addElement(sourceProductPathsElement); timeSeriesRoot.addElement(variablesListElement); tsProduct.getMetadataRoot().addElement(timeSeriesRoot); } } private void addProductLocationMetadata(ProductLocation productLocation) { MetadataElement productLocationsElement = tsProduct.getMetadataRoot(). getElement(TIME_SERIES_ROOT_NAME). getElement(PRODUCT_LOCATIONS); // @todo - nur produkt pfade, keine Verzeichnisse ProductData productPath = ProductData.createInstance(productLocation.getPath()); ProductData productType = ProductData.createInstance(productLocation.getProductLocationType().toString()); int length = productLocationsElement.getElements().length + TimeSeriesChangeEvent.BAND_TO_BE_REMOVED; MetadataElement elem = new MetadataElement( String.format("%s.%s", PRODUCT_LOCATIONS, Integer.toString(length))); elem.addAttribute(new MetadataAttribute(PL_PATH, productPath, true)); elem.addAttribute(new MetadataAttribute(PL_TYPE, productType, true)); productLocationsElement.addElement(elem); } private static String formatTimeString(Product product) { final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.ENGLISH); final ProductData.UTC startTime = product.getStartTime(); return dateFormat.format(startTime.getAsDate()); } private void addToVariableList(Product product) { final List<String> newVariables = new ArrayList<>(); final List<String> variables = getEoVariables(); final Band[] bands = product.getBands(); for (Band band : bands) { final String bandName = band.getName(); boolean varExist = false; for (String variable : variables) { varExist |= variable.equals(bandName); } if (!varExist) { newVariables.add(bandName); } } for (String variable : newVariables) { addVariableToMetadata(variable); } if (!newVariables.isEmpty()) { updateAutoGrouping(); } } private void addVariableToMetadata(String variable) { MetadataElement variableListElement = tsProduct.getMetadataRoot(). getElement(TIME_SERIES_ROOT_NAME). getElement(VARIABLES); final ProductData variableName = ProductData.createInstance(variable); final ProductData isSelected = ProductData.createInstance(Boolean.toString(false)); int length = variableListElement.getElements().length + 1; MetadataElement elem = new MetadataElement(String.format("%s.%s", VARIABLES, Integer.toString(length))); elem.addAttribute(new MetadataAttribute(VARIABLE_NAME, variableName, true)); elem.addAttribute(new MetadataAttribute(VARIABLE_SELECTION, isSelected, true)); variableListElement.addElement(elem); } private void addSpecifiedBandOfGivenProduct(String nodeName, Product product) { if (isProductCompatible(product, nodeName)) { final RasterDataNode raster = product.getRasterDataNode(nodeName); TimeCoding rasterTimeCoding = GridTimeCoding.create(product); final ProductData.UTC rasterStartTime = rasterTimeCoding.getStartTime(); final ProductData.UTC rasterEndTime = rasterTimeCoding.getEndTime(); Guardian.assertNotNull("rasterStartTime", rasterStartTime); final String bandName = variableToRasterName(nodeName, rasterTimeCoding); if (!tsProduct.containsBand(bandName)) { // band not already contained if (isAutoAdjustingTimeCoding() || !isTimeCodingSet()) { // automatically setting time coding // OR // first band to add to time series; time bounds of this band will be used // as ts-product's time bounds, no matter if auto adjust is true or false autoAdjustTimeInformation(rasterStartTime, rasterEndTime); } if (getTimeCoding().contains(rasterTimeCoding)) { // add only bands which are in the time bounds final Band addedBand = addBand(raster, rasterTimeCoding, bandName); final List<Band> bandsForVariable = getBandsForVariable(nodeName); if (!bandsForVariable.isEmpty()) { final ImageInfo imageInfo = bandsForVariable.get(0).getImageInfo(ProgressMonitor.NULL); addedBand.setImageInfo(imageInfo.createDeepCopy()); } } // todo no bands added message } } } private Band addBand(RasterDataNode raster, TimeCoding rasterTimeCoding, String bandName) { final Band band = new Band(bandName, raster.getDataType(), tsProduct.getSceneRasterWidth(), tsProduct.getSceneRasterHeight()); band.setSourceImage(raster.getSourceImage()); ProductUtils.copyRasterDataNodeProperties(raster, band); // todo copy also referenced band in valid pixel expression band.setValidPixelExpression(null); rasterTimeMap.put(band, rasterTimeCoding); tsProduct.addBand(band); return band; } private void autoAdjustTimeInformation(ProductData.UTC rasterStartTime, ProductData.UTC rasterEndTime) { ProductData.UTC tsStartTime = tsProduct.getStartTime(); if (tsStartTime == null || rasterStartTime.getAsDate().before(tsStartTime.getAsDate())) { tsProduct.setStartTime(rasterStartTime); } ProductData.UTC tsEndTime = tsProduct.getEndTime(); if (tsEndTime == null || rasterEndTime.getAsDate().after(tsEndTime.getAsDate())) { tsProduct.setEndTime(rasterEndTime); } } private void initImageInfos() { for (String variable : getEoVariables()) { if (isEoVariableSelected(variable)) { final List<Band> bandList = getBandsForVariable(variable); adjustImageInfos(bandList.get(0)); } } } private void fireChangeEvent(TimeSeriesChangeEvent event) { final ArrayList<TimeSeriesListener> listenersCopy = new ArrayList<>(); listenersCopy.addAll(listeners); for (TimeSeriesListener listener : listenersCopy) { listener.timeSeriesChanged(event); } } private class SourceImageReconstructor extends ProductNodeListenerAdapter { @Override public void nodeChanged(ProductNodeEvent event) { if ("sourceImage".equals(event.getPropertyName()) && event.getOldValue() != null && event.getNewValue() == null) { ProductNode productNode = event.getSourceNode(); if (productNode instanceof Band) { Band destBand = (Band) productNode; final Band sourceBand = getSourceBand(destBand.getName()); if (sourceBand != null) { destBand.setSourceImage(sourceBand.getSourceImage()); } } } if (RasterDataNode.PROPERTY_NAME_IMAGE_INFO.equals(event.getPropertyName())) { if (event.getSourceNode() instanceof RasterDataNode) { adjustImageInfos((RasterDataNode) event.getSourceNode()); } } } } private class AxisMappingListener implements AxisMapping.AxisMappingListener { @Override public void hasChanged() { fireChangeEvent(new TimeSeriesChangeEvent(TimeSeriesChangeEvent.PROPERTY_AXIS_MAPPING_CHANGED, null, TimeSeriesImpl.this)); } } @Override public Product[] getSourceProducts() { final Collection<Product> values = productTimeMap.values(); return values.toArray(new Product[values.size()]); } @Override public void dispose() { productTimeMap.clear(); productTimeMap = null; listeners.clear(); pinRelationMap.clear(); tsProduct = null; if (productLocationList != null) { productLocationList.clear(); productLocationList = null; } insituSource = null; insituVariablesSelections.clear(); insituVariablesSelections = null; } }