/* * 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.beam.dataio.smos; import com.bc.ceres.binio.*; import com.bc.ceres.glevel.MultiLevelImage; import com.bc.ceres.glevel.MultiLevelSource; import com.bc.ceres.glevel.support.AbstractMultiLevelSource; import com.bc.ceres.glevel.support.DefaultMultiLevelImage; import org.esa.beam.dataio.smos.dddb.BandDescriptor; import org.esa.beam.dataio.smos.dddb.Dddb; import org.esa.beam.dataio.smos.dddb.Family; import org.esa.beam.framework.datamodel.Band; import org.esa.beam.framework.datamodel.Product; import org.esa.beam.jai.ResolutionLevel; import org.esa.beam.smos.EEFilePair; import org.esa.beam.smos.dgg.SmosDgg; import org.esa.beam.util.io.FileUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.Namespace; import java.awt.*; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; class LaiFile extends ExplorerFile { private static final String CUMULATED_LON_COUNT_NAME = "Cumulated_N_Lon"; private static final String DELTA_LAT_NAME = "Delta_Lat"; private static final String DELTA_LON_NAME = "Long_Step_Size_Ang"; private static final String DFFG_LAI_NAME = "DFFG_LAI"; private static final String DFFG_LAI_POINT_DATA_TYPE_NAME = "DFFG_LAI_Point_Data_Type"; private static final String LIST_OF_DFFG_LAI_POINT_DATA_NAME = "List_of_DFFG_LAI_Point_Datas"; private static final String LIST_OF_ROW_STRUCT_DATA_NAME = "List_of_Row_Struct_Datas"; private static final String LON_COUNT_NAME = "N_Lon"; private static final String MAX_LAT_NAME = "Lat_b"; private static final String MIN_LAT_NAME = "Lat_a"; private static final String MAX_LON_NAME = "Lon_b"; private static final String MIN_LON_NAME = "Lon_a"; private static final String TAG_SCALING_OFFSET = "Offset"; private static final String TAG_SCALING_FACTOR = "Scaling_Factor"; private static final String TAG_DIGITS_TO_SHIFT = "Digits_To_Shift"; private final double scalingOffset; private final double scalingFactor; private final long zoneIndexMultiplier; private volatile List<Dffg> gridList = null; LaiFile(EEFilePair eeFilePair, DataContext dataContext) throws IOException { super(eeFilePair, dataContext); final Document document = getDocument(); final Namespace namespace = document.getRootElement().getNamespace(); final Element specificProductHeader = getElement(document.getRootElement(), TAG_SPECIFIC_PRODUCT_HEADER); scalingOffset = Double.valueOf(specificProductHeader.getChildText(TAG_SCALING_OFFSET, namespace)); scalingFactor = Double.valueOf(specificProductHeader.getChildText(TAG_SCALING_FACTOR, namespace)); final int k = Integer.valueOf(specificProductHeader.getChildText(TAG_DIGITS_TO_SHIFT, namespace)); zoneIndexMultiplier = (long) Math.pow(10.0, k); } long getCellIndex(double lon, double lat) { final int zoneIndex = Eeap.getInstance().getZoneIndex(lon, lat); if (zoneIndex != -1) { final int gridIndex = getGridList().get(zoneIndex).getIndex(lon, lat); if (gridIndex != -1) { return gridIndex + zoneIndex * zoneIndexMultiplier; } } return -1; } @Override public final Area getArea() { return new Area(new Rectangle2D.Double(-180.0, -90.0, 360.0, 180.0)); } @Override public Product createProduct() throws IOException { final String productName = FileUtils.getFilenameWithoutExtension(getDataFile()); final String productType = getProductType(); final Dimension dimension = ProductHelper.getSceneRasterDimension(); final Product product = new Product(productName, productType, dimension.width, dimension.height); product.setFileLocation(getDataFile()); product.setPreferredTileSize(512, 512); ProductHelper.addMetadata(product.getMetadataRoot(), this); product.setGeoCoding(ProductHelper.createGeoCoding(dimension)); addBands(product); return product; } private CompoundType getCellType() { return (CompoundType) getDataFormat().getTypeDef(DFFG_LAI_POINT_DATA_TYPE_NAME); } private void addBands(Product product) { final String formatName = getDataFormat().getName(); final Family<BandDescriptor> descriptors = Dddb.getInstance().getBandDescriptors(formatName); if (descriptors != null) { for (final BandDescriptor descriptor : descriptors.asList()) { addBand(product, descriptor, getCellType()); } } } private void addBand(Product product, BandDescriptor descriptor, CompoundType compoundType) { if (!descriptor.isVisible()) { return; } final int memberIndex = compoundType.getMemberIndex(descriptor.getMemberName()); if (memberIndex >= 0) { final CompoundMember member = compoundType.getMember(memberIndex); final int dataType = ProductHelper.getDataType(member.getType()); final Band band = product.addBand(descriptor.getBandName(), dataType); if ("LAI".equals(member.getName())) { band.setScalingOffset(scalingOffset); band.setScalingFactor(scalingFactor); } else { band.setScalingOffset(descriptor.getScalingOffset()); band.setScalingFactor(descriptor.getScalingFactor()); } if (descriptor.hasFillValue()) { band.setNoDataValueUsed(true); band.setNoDataValue(descriptor.getFillValue()); } if (!descriptor.getValidPixelExpression().isEmpty()) { band.setValidPixelExpression(descriptor.getValidPixelExpression()); } if (!descriptor.getUnit().isEmpty()) { band.setUnit(descriptor.getUnit()); } if (!descriptor.getDescription().isEmpty()) { band.setDescription(descriptor.getDescription()); } if (descriptor.getFlagDescriptors() != null) { ProductHelper.addFlagsAndMasks(product, band, descriptor.getFlagCodingName(), descriptor.getFlagDescriptors()); } final CellValueProvider valueProvider = createCellValueProvider(descriptor); band.setSourceImage(createSourceImage(band, valueProvider)); band.setImageInfo(ProductHelper.createImageInfo(band, descriptor)); } } private CellValueProvider createCellValueProvider(BandDescriptor descriptor) { final int memberIndex = getCellType().getMemberIndex(descriptor.getMemberName()); return new CellValueProviderImpl(memberIndex); } private MultiLevelImage createSourceImage(Band band, CellValueProvider valueProvider) { return new DefaultMultiLevelImage(createMultiLevelSource(band, valueProvider)); } private MultiLevelSource createMultiLevelSource(final Band band, final CellValueProvider valueProvider) { return new AbstractMultiLevelSource(SmosDgg.getInstance().getMultiLevelImage().getModel()) { @Override protected RenderedImage createImage(int level) { return new CellGridOpImage(valueProvider, band, getModel(), ResolutionLevel.create(getModel(), level)); } }; } private int getGridIndex(long cellIndex, int zoneIndex) { return (int) (cellIndex - zoneIndex * zoneIndexMultiplier); } private int getZoneIndex(long cellIndex) { return (int) (cellIndex / zoneIndexMultiplier); } private List<Dffg> getGridList() { List<Dffg> result = gridList; if (result == null) { synchronized (this) { result = gridList; if (result == null) { try { gridList = result = createGridList(); } catch (IOException e) { throw new RuntimeException("Cannot create zone list.", e); } } } } return result; } private List<Dffg> createGridList() throws IOException { final SequenceData zoneSequenceData = getDataBlock().getSequence(DFFG_LAI_NAME); if (zoneSequenceData == null) { throw new IllegalStateException(MessageFormat.format( "SMOS File ''{0}'': Missing zone data.", getDataFile().getPath())); } final ArrayList<Dffg> gridList = new ArrayList<>( zoneSequenceData.getElementCount()); for (int i = 0; i < zoneSequenceData.getElementCount(); i++) { final CompoundData zoneCompoundData = zoneSequenceData.getCompound(i); final double minLat = zoneCompoundData.getDouble(MIN_LAT_NAME); final double maxLat = zoneCompoundData.getDouble(MAX_LAT_NAME); final double minLon = zoneCompoundData.getDouble(MIN_LON_NAME); final double maxLon = zoneCompoundData.getDouble(MAX_LON_NAME); final double deltaLat = zoneCompoundData.getDouble(DELTA_LAT_NAME); // due to an unresolved bug in ceres-binio it is essential to remember the LAI sequence data final SequenceData sequenceData = zoneCompoundData.getSequence(LIST_OF_DFFG_LAI_POINT_DATA_NAME); final Dffg grid = new Dffg(minLat, maxLat, minLon, maxLon, deltaLat, sequenceData); final SequenceData rowSequenceData = zoneCompoundData.getSequence(LIST_OF_ROW_STRUCT_DATA_NAME); for (int p = 0; p < rowSequenceData.getElementCount(); p++) { final CompoundData rowCompoundData = rowSequenceData.getCompound(p); final int lonCount = rowCompoundData.getInt(LON_COUNT_NAME); final double deltaLon = rowCompoundData.getDouble(DELTA_LON_NAME); final int cumulatedLonCount = rowCompoundData.getInt(CUMULATED_LON_COUNT_NAME); grid.setRow(p, lonCount, deltaLon, cumulatedLonCount); } gridList.add(grid); } return Collections.unmodifiableList(gridList); } private final class CellValueProviderImpl implements CellValueProvider { private final int memberIndex; public CellValueProviderImpl(int memberIndex) { this.memberIndex = memberIndex; } @Override public final Area getArea() { return LaiFile.this.getArea(); } @Override public final long getCellIndex(double lon, double lat) { return LaiFile.this.getCellIndex(lon, lat); } @Override public final byte getValue(long cellIndex, byte noDataValue) { final int zoneIndex = getZoneIndex(cellIndex); final int gridIndex = getGridIndex(cellIndex, zoneIndex); try { return getGridList().get(zoneIndex).getSequenceData().getCompound(gridIndex).getByte(memberIndex); } catch (IOException e) { return noDataValue; } } @Override public final short getValue(long cellIndex, short noDataValue) { final int zoneIndex = getZoneIndex(cellIndex); final int gridIndex = getGridIndex(cellIndex, zoneIndex); try { return getGridList().get(zoneIndex).getSequenceData().getCompound(gridIndex).getShort(memberIndex); } catch (IOException e) { return noDataValue; } } @Override public final int getValue(long cellIndex, int noDataValue) { final int zoneIndex = getZoneIndex(cellIndex); final int gridIndex = getGridIndex(cellIndex, zoneIndex); try { return getGridList().get(zoneIndex).getSequenceData().getCompound(gridIndex).getInt(memberIndex); } catch (IOException e) { return noDataValue; } } @Override public final float getValue(long cellIndex, float noDataValue) { final int zoneIndex = getZoneIndex(cellIndex); final int gridIndex = getGridIndex(cellIndex, zoneIndex); try { return getGridList().get(zoneIndex).getSequenceData().getCompound(gridIndex).getFloat(memberIndex); } catch (IOException e) { return noDataValue; } } } }