package mil.nga.giat.geowave.adapter.raster.adapter; import java.awt.Color; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import javax.measure.unit.Unit; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationBicubic2; import javax.media.jai.InterpolationBilinear; import javax.media.jai.InterpolationNearest; import javax.media.jai.PlanarImage; import javax.media.jai.remote.SerializableState; import javax.media.jai.remote.SerializerFactory; import org.apache.commons.math.util.MathUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.geotools.coverage.Category; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.TypeMap; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.processing.Operations; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.GeometryClipper; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.operation.projection.MapProjection; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.NumberRange; import org.geotools.util.SimpleInternationalString; import org.opengis.coverage.ColorInterpretation; import org.opengis.coverage.SampleDimension; import org.opengis.coverage.SampleDimensionType; import org.opengis.coverage.grid.GridCoverage; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.geometry.Envelope; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.TransformException; import org.opengis.util.InternationalString; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.PrecisionModel; import mil.nga.giat.geowave.adapter.raster.FitToIndexGridCoverage; import mil.nga.giat.geowave.adapter.raster.RasterUtils; import mil.nga.giat.geowave.adapter.raster.Resolution; import mil.nga.giat.geowave.adapter.raster.adapter.merge.RasterTileMergeStrategy; import mil.nga.giat.geowave.adapter.raster.adapter.merge.RasterTileRowTransform; import mil.nga.giat.geowave.adapter.raster.adapter.merge.RootMergeStrategy; import mil.nga.giat.geowave.adapter.raster.adapter.merge.nodata.NoDataMergeStrategy; import mil.nga.giat.geowave.adapter.raster.adapter.warp.WarpRIF; import mil.nga.giat.geowave.adapter.raster.plugin.GeoWaveGTRasterFormat; import mil.nga.giat.geowave.adapter.raster.stats.HistogramConfig; import mil.nga.giat.geowave.adapter.raster.stats.HistogramStatistics; import mil.nga.giat.geowave.adapter.raster.stats.OverviewStatistics; import mil.nga.giat.geowave.adapter.raster.stats.RasterBoundingBoxStatistics; import mil.nga.giat.geowave.adapter.raster.stats.RasterFootprintStatistics; import mil.nga.giat.geowave.core.geotime.GeometryUtils; import mil.nga.giat.geowave.core.geotime.index.dimension.LatitudeDefinition; import mil.nga.giat.geowave.core.geotime.index.dimension.LongitudeDefinition; import mil.nga.giat.geowave.core.geotime.store.statistics.BoundingBoxDataStatistics; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.index.ByteArrayUtils; import mil.nga.giat.geowave.core.index.CompoundIndexStrategy; import mil.nga.giat.geowave.core.index.HierarchicalNumericIndexStrategy; import mil.nga.giat.geowave.core.index.HierarchicalNumericIndexStrategy.SubStrategy; import mil.nga.giat.geowave.core.index.Persistable; import mil.nga.giat.geowave.core.index.PersistenceUtils; import mil.nga.giat.geowave.core.index.StringUtils; import mil.nga.giat.geowave.core.index.dimension.NumericDimensionDefinition; import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset; import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData; import mil.nga.giat.geowave.core.index.sfc.data.NumericRange; import mil.nga.giat.geowave.core.store.EntryVisibilityHandler; import mil.nga.giat.geowave.core.store.adapter.AdapterPersistenceEncoding; import mil.nga.giat.geowave.core.store.adapter.FitToIndexPersistenceEncoding; import mil.nga.giat.geowave.core.store.adapter.IndexDependentDataAdapter; import mil.nga.giat.geowave.core.store.adapter.IndexedAdapterPersistenceEncoding; import mil.nga.giat.geowave.core.store.adapter.RowMergingDataAdapter; import mil.nga.giat.geowave.core.store.adapter.statistics.CountDataStatistics; import mil.nga.giat.geowave.core.store.adapter.statistics.DataStatistics; import mil.nga.giat.geowave.core.store.adapter.statistics.FieldIdStatisticVisibility; import mil.nga.giat.geowave.core.store.adapter.statistics.StatisticsProvider; import mil.nga.giat.geowave.core.store.data.PersistentDataset; import mil.nga.giat.geowave.core.store.data.PersistentValue; import mil.nga.giat.geowave.core.store.data.field.FieldReader; import mil.nga.giat.geowave.core.store.data.field.FieldWriter; import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField; import mil.nga.giat.geowave.core.store.index.CommonIndexModel; import mil.nga.giat.geowave.core.store.index.CommonIndexValue; import mil.nga.giat.geowave.core.store.index.PrimaryIndex; import mil.nga.giat.geowave.core.store.util.IteratorWrapper; import mil.nga.giat.geowave.core.store.util.IteratorWrapper.Converter; import mil.nga.giat.geowave.mapreduce.HadoopDataAdapter; import mil.nga.giat.geowave.mapreduce.HadoopWritableSerializer; public class RasterDataAdapter implements StatisticsProvider<GridCoverage>, IndexDependentDataAdapter<GridCoverage>, HadoopDataAdapter<GridCoverage, GridCoverageWritable>, RowMergingDataAdapter<GridCoverage, RasterTile<?>> { static { SourceThresholdFixMosaicDescriptor.register(false); WarpRIF.register(false); MapProjection.SKIP_SANITY_CHECKS = true; } public final static String TILE_METADATA_PROPERTY_KEY = "TILE_METADATA"; private final static Logger LOGGER = LoggerFactory.getLogger(RasterDataAdapter.class); private final static ByteArrayId DATA_FIELD_ID = new ByteArrayId( "image"); public final static int DEFAULT_TILE_SIZE = 256; public final static boolean DEFAULT_BUILD_PYRAMID = false; public final static boolean DEFAULT_BUILD_HISTOGRAM = true; /** * A transparent color for missing data. */ private static final Color TRANSPARENT = new Color( 0, 0, 0, 0); private String coverageName; private int tileSize; private SampleModel sampleModel; private ColorModel colorModel; private Map<String, String> metadata; private HistogramConfig histogramConfig; private double[][] noDataValuesPerBand; private double[] minsPerBand; private double[] maxesPerBand; private String[] namesPerBand; private double[] backgroundValuesPerBand; private boolean buildPyramid; private ByteArrayId[] supportedStatsTypes; private EntryVisibilityHandler<GridCoverage> visibilityHandler; private RootMergeStrategy<?> mergeStrategy; private boolean equalizeHistogram; private Interpolation interpolation; protected RasterDataAdapter() {} public RasterDataAdapter( final String coverageName, final Map<String, String> metadata, final GridCoverage2D originalGridCoverage ) { this( coverageName, metadata, originalGridCoverage, DEFAULT_TILE_SIZE, DEFAULT_BUILD_PYRAMID, DEFAULT_BUILD_HISTOGRAM, new double[originalGridCoverage.getNumSampleDimensions()][], new NoDataMergeStrategy()); } public RasterDataAdapter( final String coverageName, final Map<String, String> metadata, final GridCoverage2D originalGridCoverage, final int tileSize, final boolean buildPyramid ) { this( coverageName, metadata, originalGridCoverage, tileSize, buildPyramid, DEFAULT_BUILD_HISTOGRAM, new double[originalGridCoverage.getNumSampleDimensions()][], new NoDataMergeStrategy()); } public RasterDataAdapter( final String coverageName, final Map<String, String> metadata, final GridCoverage2D originalGridCoverage, final int tileSize, final boolean buildPyramid, final boolean buildHistogram, final double[][] noDataValuesPerBand ) { this( coverageName, metadata, originalGridCoverage, tileSize, buildPyramid, buildHistogram, noDataValuesPerBand, new NoDataMergeStrategy()); } public RasterDataAdapter( final String coverageName, final Map<String, String> metadata, final GridCoverage2D originalGridCoverage, final int tileSize, final boolean buildPyramid, final boolean buildHistogram, final double[][] noDataValuesPerBand, final RasterTileMergeStrategy<?> mergeStrategy ) { final RenderedImage img = originalGridCoverage.getRenderedImage(); sampleModel = img.getSampleModel(); colorModel = img.getColorModel(); this.metadata = metadata; this.coverageName = coverageName; this.tileSize = tileSize; if (buildHistogram) { histogramConfig = new HistogramConfig( sampleModel); } else { histogramConfig = null; } if ((noDataValuesPerBand != null) && (noDataValuesPerBand.length != 0)) { this.noDataValuesPerBand = noDataValuesPerBand; backgroundValuesPerBand = new double[noDataValuesPerBand.length]; for (int d = 0; d < this.noDataValuesPerBand.length; d++) { if ((noDataValuesPerBand[d] != null) && (noDataValuesPerBand[d].length > 0)) { backgroundValuesPerBand[d] = noDataValuesPerBand[d][0]; } else { backgroundValuesPerBand[d] = 0.0; } } } else { this.noDataValuesPerBand = new double[originalGridCoverage.getNumSampleDimensions()][]; for (int d = 0; d < this.noDataValuesPerBand.length; d++) { this.noDataValuesPerBand[d] = originalGridCoverage.getSampleDimension( d).getNoDataValues(); } backgroundValuesPerBand = CoverageUtilities.getBackgroundValues(originalGridCoverage); } this.buildPyramid = buildPyramid; if (mergeStrategy != null) { this.mergeStrategy = new RootMergeStrategy( getAdapterId(), sampleModel.createCompatibleSampleModel( tileSize, tileSize), mergeStrategy); } else { this.mergeStrategy = null; } init(); } public RasterDataAdapter( final String coverageName, final SampleModel sampleModel, final ColorModel colorModel, final Map<String, String> metadata, final int tileSize, final double[][] noDataValuesPerBand, final double[] backgroundValuesPerBand, final boolean buildPyramid ) { this( coverageName, sampleModel, colorModel, metadata, tileSize, noDataValuesPerBand, backgroundValuesPerBand, new HistogramConfig( sampleModel), true, Interpolation.INTERP_NEAREST, buildPyramid, new NoDataMergeStrategy()); } public RasterDataAdapter( final RasterDataAdapter adapter, final String coverageName ) { this( adapter, coverageName, adapter.tileSize); } public RasterDataAdapter( final RasterDataAdapter adapter, final String coverageName, final int tileSize ) { this( coverageName, adapter.getSampleModel().createCompatibleSampleModel( tileSize, tileSize), adapter.getColorModel(), adapter.getMetadata(), tileSize, adapter.getNoDataValuesPerBand(), adapter.backgroundValuesPerBand, adapter.histogramConfig, adapter.equalizeHistogram, interpolationToByte(adapter.interpolation), adapter.buildPyramid, adapter.mergeStrategy == null ? null : adapter.mergeStrategy.getChildMergeStrategy(adapter .getAdapterId())); } public RasterDataAdapter( final RasterDataAdapter adapter, final String coverageName, final RasterTileMergeStrategy<?> mergeStrategy ) { this( coverageName, adapter.getSampleModel(), adapter.getColorModel(), adapter.getMetadata(), adapter.tileSize, null, null, null, adapter.getNoDataValuesPerBand(), adapter.backgroundValuesPerBand, adapter.histogramConfig, adapter.equalizeHistogram, interpolationToByte(adapter.interpolation), adapter.buildPyramid, mergeStrategy); } public RasterDataAdapter( final String coverageName, final SampleModel sampleModel, final ColorModel colorModel, final Map<String, String> metadata, final int tileSize, final double[][] noDataValuesPerBand, final double[] backgroundValuesPerBand, final HistogramConfig histogramConfig, final boolean equalizeHistogram, final int interpolationType, final boolean buildPyramid, final RasterTileMergeStrategy<?> mergeStrategy ) { this( coverageName, sampleModel, colorModel, metadata, tileSize, null, null, null, noDataValuesPerBand, backgroundValuesPerBand, histogramConfig, equalizeHistogram, interpolationType, buildPyramid, mergeStrategy); } public RasterDataAdapter( final String coverageName, final SampleModel sampleModel, final ColorModel colorModel, final Map<String, String> metadata, final int tileSize, final double[] minsPerBand, final double[] maxesPerBand, final String[] namesPerBand, final double[][] noDataValuesPerBand, final double[] backgroundValuesPerBand, final HistogramConfig histogramConfig, final boolean equalizeHistogram, final int interpolationType, final boolean buildPyramid, final RasterTileMergeStrategy<?> mergeStrategy ) { this.coverageName = coverageName; this.tileSize = tileSize; this.sampleModel = sampleModel; this.colorModel = colorModel; this.metadata = metadata; this.minsPerBand = minsPerBand; this.maxesPerBand = maxesPerBand; this.namesPerBand = namesPerBand; this.noDataValuesPerBand = noDataValuesPerBand; this.backgroundValuesPerBand = backgroundValuesPerBand; // a null histogram config will result in histogram statistics not being // accumulated this.histogramConfig = histogramConfig; this.buildPyramid = buildPyramid; this.equalizeHistogram = equalizeHistogram; interpolation = Interpolation.getInstance(interpolationType); if (mergeStrategy != null) { this.mergeStrategy = new RootMergeStrategy( getAdapterId(), sampleModel.createCompatibleSampleModel( tileSize, tileSize), mergeStrategy); } else { this.mergeStrategy = null; } init(); } private void init() { int supportedStatsLength = 2; if (histogramConfig != null) { supportedStatsLength++; } supportedStatsTypes = new ByteArrayId[supportedStatsLength]; supportedStatsTypes[0] = OverviewStatistics.STATS_TYPE; supportedStatsTypes[1] = BoundingBoxDataStatistics.STATS_TYPE; if (histogramConfig != null) { supportedStatsTypes[2] = HistogramStatistics.STATS_TYPE; } visibilityHandler = new FieldIdStatisticVisibility<GridCoverage>( DATA_FIELD_ID); } @Override public Iterator<GridCoverage> convertToIndex( final PrimaryIndex index, final GridCoverage gridCoverage ) { final HierarchicalNumericIndexStrategy indexStrategy = CompoundHierarchicalIndexStrategyWrapper .findHierarchicalStrategy(index.getIndexStrategy()); if (indexStrategy != null) { final CoordinateReferenceSystem sourceCrs = gridCoverage.getCoordinateReferenceSystem(); final Envelope sampleEnvelope = gridCoverage.getEnvelope(); final ReferencedEnvelope sampleReferencedEnvelope = new ReferencedEnvelope( new com.vividsolutions.jts.geom.Envelope( sampleEnvelope.getMinimum(0), sampleEnvelope.getMaximum(0), sampleEnvelope.getMinimum(1), sampleEnvelope.getMaximum(1)), gridCoverage.getCoordinateReferenceSystem()); ReferencedEnvelope projectedReferenceEnvelope = sampleReferencedEnvelope; if (!GeoWaveGTRasterFormat.DEFAULT_CRS.equals(sourceCrs)) { try { projectedReferenceEnvelope = sampleReferencedEnvelope.transform( GeoWaveGTRasterFormat.DEFAULT_CRS, true); } catch (TransformException | FactoryException e) { LOGGER.warn( "Unable to transform envelope of grid coverage to EPSG:4326", e); } } final MultiDimensionalNumericData bounds = GeometryUtils.basicConstraintSetFromEnvelope( projectedReferenceEnvelope).getIndexConstraints( indexStrategy); final GridEnvelope gridEnvelope = gridCoverage.getGridGeometry().getGridRange(); // only one set of constraints..hence reference '0' element final double[] tileRangePerDimension = new double[bounds.getDimensionCount()]; final double[] maxValuesPerDimension = bounds.getMaxValuesPerDimension(); final double[] minValuesPerDimension = bounds.getMinValuesPerDimension(); for (int d = 0; d < tileRangePerDimension.length; d++) { tileRangePerDimension[d] = ((maxValuesPerDimension[d] - minValuesPerDimension[d]) * tileSize) / gridEnvelope.getSpan(d); } final TreeMap<Double, SubStrategy> substrategyMap = new TreeMap<Double, SubStrategy>(); for (final SubStrategy pyramidLevel : indexStrategy.getSubStrategies()) { final double[] idRangePerDimension = pyramidLevel .getIndexStrategy() .getHighestPrecisionIdRangePerDimension(); // to create a pyramid, ingest into each substrategy that is // lower resolution than the sample set in at least one // dimension and the one substrategy that is at least the same // resolution or higher resolution to retain the original // resolution as well as possible double maxSubstrategyResToSampleSetRes = -Double.MAX_VALUE; for (int d = 0; d < tileRangePerDimension.length; d++) { final double substrategyResToSampleSetRes = idRangePerDimension[d] / tileRangePerDimension[d]; maxSubstrategyResToSampleSetRes = Math.max( maxSubstrategyResToSampleSetRes, substrategyResToSampleSetRes); } substrategyMap.put( maxSubstrategyResToSampleSetRes, pyramidLevel); } // all entries will be greater than 1 (lower resolution pyramid // levels) // also try to find the one entry that is closest to 1.0 without // going over (this will be the full resolution level) // add an epsilon to try to catch any roundoff error final double fullRes = 1.0 + MathUtils.EPSILON; final Entry<Double, SubStrategy> fullResEntry = substrategyMap.floorEntry(fullRes); final List<SubStrategy> pyramidLevels = new ArrayList<SubStrategy>(); if (fullResEntry != null) { pyramidLevels.add(fullResEntry.getValue()); } if (buildPyramid) { NavigableMap<Double, SubStrategy> map = substrategyMap.tailMap( fullRes, false); pyramidLevels.addAll(map.values()); } if (pyramidLevels.isEmpty()) { // this case shouldn't occur theoretically, but just in case, // make sure the substrategy closest to 1.0 is used final Entry<Double, SubStrategy> bestEntry = substrategyMap.higherEntry(1.0); pyramidLevels.add(bestEntry.getValue()); } final SubStrategy pyramidLevel = pyramidLevels.get(0); final double[] idRangePerDimension = pyramidLevel .getIndexStrategy() .getHighestPrecisionIdRangePerDimension(); // to create a pyramid, ingest into each substrategy that is // lower resolution than the sample set in at least one // dimension and the one substrategy that is at least the same // resolution or higher resolution to retain the original // resolution as well as possible double maxSubstrategyResToSampleSetRes = -Double.MAX_VALUE; for (int d = 0; d < tileRangePerDimension.length; d++) { final double substrategyResToSampleSetRes = idRangePerDimension[d] / tileRangePerDimension[d]; maxSubstrategyResToSampleSetRes = Math.max( maxSubstrategyResToSampleSetRes, substrategyResToSampleSetRes); } return new IteratorWrapper<SubStrategy, GridCoverage>( pyramidLevels.iterator(), new MosaicPerPyramidLevelBuilder( bounds, gridCoverage, tileSize, backgroundValuesPerBand, RasterUtils.getFootprint( projectedReferenceEnvelope, gridCoverage), interpolation)); } LOGGER.warn("Strategy is not an instance of HierarchicalNumericIndexStrategy : " + index.getIndexStrategy().getClass().getName()); return Collections.<GridCoverage> emptyList().iterator(); } private static class MosaicPerPyramidLevelBuilder implements Converter<SubStrategy, GridCoverage> { private final MultiDimensionalNumericData originalBounds; private final GridCoverage originalData; private final int tileSize; private final double[] backgroundValuesPerBand; private final Geometry footprint; private final Interpolation defaultInterpolation; public MosaicPerPyramidLevelBuilder( final MultiDimensionalNumericData originalBounds, final GridCoverage originalData, final int tileSize, final double[] backgroundValuesPerBand, final Geometry footprint, final Interpolation defaultInterpolation ) { this.originalBounds = originalBounds; this.originalData = originalData; this.tileSize = tileSize; this.backgroundValuesPerBand = backgroundValuesPerBand; this.footprint = footprint; this.defaultInterpolation = defaultInterpolation; } @Override public Iterator<GridCoverage> convert( final SubStrategy pyramidLevel ) { final Iterator<ByteArrayId> insertionIds = pyramidLevel.getIndexStrategy().getInsertionIds( originalBounds).iterator(); return new Iterator<GridCoverage>() { @Override public boolean hasNext() { return insertionIds.hasNext(); } @Override public GridCoverage next() { ByteArrayId insertionId = insertionIds.next(); if (insertionId == null) { return null; } final MultiDimensionalNumericData rangePerDimension = pyramidLevel .getIndexStrategy() .getRangeForId( insertionId); final NumericDimensionDefinition[] dimensions = pyramidLevel .getIndexStrategy() .getOrderedDimensionDefinitions(); int longitudeIndex = 0, latitudeIndex = 1; final double[] minDP = new double[2]; final double[] maxDP = new double[2]; for (int d = 0; d < dimensions.length; d++) { if (dimensions[d] instanceof LatitudeDefinition) { latitudeIndex = d; minDP[1] = originalBounds.getMinValuesPerDimension()[d]; maxDP[1] = originalBounds.getMaxValuesPerDimension()[d]; } else if (dimensions[d] instanceof LongitudeDefinition) { longitudeIndex = d; minDP[0] = originalBounds.getMinValuesPerDimension()[d]; maxDP[0] = originalBounds.getMaxValuesPerDimension()[d]; } } final Envelope originalEnvelope = new GeneralEnvelope( minDP, maxDP); final double[] minsPerDimension = rangePerDimension.getMinValuesPerDimension(); final double[] maxesPerDimension = rangePerDimension.getMaxValuesPerDimension(); final ReferencedEnvelope mapExtent = new ReferencedEnvelope( minsPerDimension[longitudeIndex], maxesPerDimension[longitudeIndex], minsPerDimension[latitudeIndex], maxesPerDimension[latitudeIndex], GeoWaveGTRasterFormat.DEFAULT_CRS); final AffineTransform worldToScreenTransform = RendererUtilities.worldToScreenTransform( mapExtent, new Rectangle( tileSize, tileSize)); GridGeometry2D insertionIdGeometry; try { final AffineTransform2D gridToCRS = new AffineTransform2D( worldToScreenTransform.createInverse()); insertionIdGeometry = new GridGeometry2D( new GridEnvelope2D( new Rectangle( tileSize, tileSize)), PixelInCell.CELL_CORNER, gridToCRS, GeoWaveGTRasterFormat.DEFAULT_CRS, null); final double[] tileRes = pyramidLevel .getIndexStrategy() .getHighestPrecisionIdRangePerDimension(); final double[] pixelRes = new double[tileRes.length]; for (int d = 0; d < tileRes.length; d++) { pixelRes[d] = tileRes[d] / tileSize; } Geometry footprintWithinTileWorldGeom = null; Geometry footprintWithinTileScreenGeom = null; try { // using fixed precision for geometry factory will // round screen geometry values to the nearest // pixel, which seems to be the most appropriate // behavior final Geometry wholeFootprintScreenGeom = new GeometryFactory( new PrecisionModel( PrecisionModel.FIXED)).createGeometry(JTS.transform( footprint, new AffineTransform2D( worldToScreenTransform))); final com.vividsolutions.jts.geom.Envelope fullTileEnvelope = new com.vividsolutions.jts.geom.Envelope( 0, tileSize, 0, tileSize); final GeometryClipper tileClipper = new GeometryClipper( fullTileEnvelope); footprintWithinTileScreenGeom = tileClipper.clip( wholeFootprintScreenGeom, true); if (footprintWithinTileScreenGeom == null) { // for some reason the original image // footprint // falls outside this insertion ID LOGGER.warn("Original footprint geometry (" + originalData.getGridGeometry() + ") falls outside the insertion bounds (" + insertionIdGeometry + ")"); return null; } footprintWithinTileWorldGeom = JTS.transform( // change the precision model back to JTS // default from fixed precision new GeometryFactory().createGeometry(footprintWithinTileScreenGeom), gridToCRS); if (footprintWithinTileScreenGeom .covers(new GeometryFactory().toGeometry(fullTileEnvelope))) { // if the screen geometry fully covers the // tile, // don't bother carrying it forward footprintWithinTileScreenGeom = null; } } catch (final TransformException e) { LOGGER.warn( "Unable to calculate geometry of footprint for tile", e); } Interpolation tileInterpolation = defaultInterpolation; final int dataType = originalData.getRenderedImage().getSampleModel().getDataType(); // TODO a JAI bug "workaround" in GeoTools does not // work, this is a workaround for the GeoTools bug // see https://jira.codehaus.org/browse/GEOT-3585, // and // line 666-698 of // org.geotools.coverage.processing.operation.Resampler2D // (gt-coverage-12.1) if ((dataType == DataBuffer.TYPE_FLOAT) || (dataType == DataBuffer.TYPE_DOUBLE)) { final Envelope tileEnvelope = insertionIdGeometry.getEnvelope(); final ReferencedEnvelope tileReferencedEnvelope = new ReferencedEnvelope( new com.vividsolutions.jts.geom.Envelope( tileEnvelope.getMinimum(0), tileEnvelope.getMaximum(0), tileEnvelope.getMinimum(1), tileEnvelope.getMaximum(1)), GeoWaveGTRasterFormat.DEFAULT_CRS); final Geometry tileJTSGeometry = new GeometryFactory().toGeometry(tileReferencedEnvelope); if (!footprint.contains(tileJTSGeometry)) { tileInterpolation = Interpolation.getInstance(Interpolation.INTERP_NEAREST); } } GridCoverage resampledCoverage = (GridCoverage) RasterUtils.getCoverageOperations().resample( originalData, GeoWaveGTRasterFormat.DEFAULT_CRS, insertionIdGeometry, tileInterpolation, backgroundValuesPerBand); // NOTE: for now this is commented out, but // beware the // resample operation under certain conditions, // this requires more investigation rather than // adding a // hacky fix // sometimes the resample results in an image that // is // not tileSize in width and height although the // insertionIdGeometry is telling it to resample to // tileSize // in these cases, check and perform a rescale to // finalize the grid coverage to guarantee it is the // correct tileSize final GridEnvelope e = resampledCoverage.getGridGeometry().getGridRange(); boolean resize = false; for (int d = 0; d < e.getDimension(); d++) { if (e.getSpan(d) != tileSize) { resize = true; break; } } if (resize) { resampledCoverage = Operations.DEFAULT.scale( resampledCoverage, (double) tileSize / (double) e.getSpan(0), (double) tileSize / (double) e.getSpan(1), -resampledCoverage.getRenderedImage().getMinX(), -resampledCoverage.getRenderedImage().getMinY()); } if ((resampledCoverage.getRenderedImage().getWidth() != tileSize) || (resampledCoverage.getRenderedImage().getHeight() != tileSize) || (resampledCoverage.getRenderedImage().getMinX() != 0) || (resampledCoverage.getRenderedImage().getMinY() != 0)) { resampledCoverage = Operations.DEFAULT.scale( resampledCoverage, 1, 1, -resampledCoverage.getRenderedImage().getMinX(), -resampledCoverage.getRenderedImage().getMinY()); } if (pyramidLevel.getIndexStrategy() instanceof CompoundIndexStrategy) { // this is exclusive on the end, and the tier is set // so just get the id based on the lowest half of // the multidimensional data final double[] centroids = rangePerDimension.getCentroidPerDimension(); final double[] mins = rangePerDimension.getMinValuesPerDimension(); final NumericRange[] ranges = new NumericRange[centroids.length]; for (int d = 0; d < centroids.length; d++) { ranges[d] = new NumericRange( mins[d], centroids[d]); } insertionId = pyramidLevel.getIndexStrategy().getInsertionIds( new BasicNumericDataset( ranges)).get( 0); // this is intended to allow the partitioning // algorithm to use a consistent multi-dimensional // dataset (so if hashing is done on the // multi-dimensional data, it will be a consistent // hash for each tile and merge strategies will work // correctly) } return new FitToIndexGridCoverage( resampledCoverage, insertionId, new Resolution( pixelRes), originalEnvelope, footprintWithinTileWorldGeom, footprintWithinTileScreenGeom, getProperties(originalData)); } catch (IllegalArgumentException | NoninvertibleTransformException e) { LOGGER.warn( "Unable to calculate transformation for grid coordinates on write", e); } return null; } @Override public void remove() { insertionIds.remove(); } }; } } @Override public ByteArrayId getAdapterId() { return new ByteArrayId( getCoverageName()); } @Override public boolean isSupported( final GridCoverage entry ) { if (!getSampleModel().equals( entry.getRenderedImage().getSampleModel())) { return false; } if (!getColorModel().equals( entry.getRenderedImage().getColorModel())) { return false; } return true; } @Override public ByteArrayId getDataId( final GridCoverage entry ) { return new ByteArrayId( new byte[] {}); } @Override public GridCoverage decode( final IndexedAdapterPersistenceEncoding data, final PrimaryIndex index ) { final Object rasterTile = data.getAdapterExtendedData().getValue( DATA_FIELD_ID); if ((rasterTile == null) || !(rasterTile instanceof RasterTile)) { return null; } return getCoverageFromRasterTile( (RasterTile) rasterTile, data.getIndexInsertionId(), index); } public GridCoverage getCoverageFromRasterTile( final RasterTile rasterTile, final ByteArrayId insertionId, final PrimaryIndex index ) { final MultiDimensionalNumericData indexRange = index.getIndexStrategy().getRangeForId( insertionId); final NumericDimensionDefinition[] orderedDimensions = index .getIndexStrategy() .getOrderedDimensionDefinitions(); final double[] minsPerDimension = indexRange.getMinValuesPerDimension(); final double[] maxesPerDimension = indexRange.getMaxValuesPerDimension(); Double minX = null; Double maxX = null; Double minY = null; Double maxY = null; for (int d = 0; d < orderedDimensions.length; d++) { if (orderedDimensions[d] instanceof LongitudeDefinition) { minX = minsPerDimension[d]; maxX = maxesPerDimension[d]; } else if (orderedDimensions[d] instanceof LatitudeDefinition) { minY = minsPerDimension[d]; maxY = maxesPerDimension[d]; } } if ((minX == null) || (minY == null) || (maxX == null) || (maxY == null)) { return null; } final ReferencedEnvelope mapExtent = new ReferencedEnvelope( minsPerDimension[0], maxesPerDimension[0], minsPerDimension[1], maxesPerDimension[1], GeoWaveGTRasterFormat.DEFAULT_CRS); try { return prepareCoverage( rasterTile, tileSize, mapExtent); } catch (final IOException e) { LOGGER.warn( "Unable to build grid coverage from adapter encoded data", e); } return null; } /** * This method is responsible for creating a coverage from the supplied * {@link RenderedImage}. * * @param image * @return * @throws IOException */ private GridCoverage2D prepareCoverage( final RasterTile rasterTile, final int tileSize, final ReferencedEnvelope mapExtent ) throws IOException { final DataBuffer dataBuffer = rasterTile.getDataBuffer(); final Persistable tileMetadata = rasterTile.getMetadata(); final SampleModel sm = sampleModel.createCompatibleSampleModel( tileSize, tileSize); final boolean alphaPremultiplied = colorModel.isAlphaPremultiplied(); final WritableRaster raster = Raster.createWritableRaster( sm, dataBuffer, null); final int numBands = sm.getNumBands(); final BufferedImage image = new BufferedImage( colorModel, raster, alphaPremultiplied, null); // creating bands final ColorModel cm = image.getColorModel(); final GridSampleDimension[] bands = new GridSampleDimension[numBands]; final Set<String> bandNames = new HashSet<String>(); // setting bands names. for (int i = 0; i < numBands; i++) { ColorInterpretation colorInterpretation = null; String bandName = null; if (cm != null) { // === color interpretation colorInterpretation = TypeMap.getColorInterpretation( cm, i); if (colorInterpretation == null) { throw new IOException( "Unrecognized sample dimension type"); } bandName = colorInterpretation.name(); if ((colorInterpretation == ColorInterpretation.UNDEFINED) || bandNames.contains(bandName)) {// make // sure // we // create // no // duplicate // band // names bandName = "Band" + (i + 1); } } else { // no color model bandName = "Band" + (i + 1); colorInterpretation = ColorInterpretation.UNDEFINED; } // sample dimension type final SampleDimensionType st = TypeMap.getSampleDimensionType( sm, i); if (st == null) { LOGGER.error("Could not get sample dimension type, getSampleDimensionType returned null"); throw new IOException( "Could not get sample dimension type, getSampleDimensionType returned null"); } // set some no data values, as well as Min and Max values double noData; double min = -Double.MAX_VALUE, max = Double.MAX_VALUE; if (st.compareTo(SampleDimensionType.REAL_32BITS) == 0) { noData = Float.NaN; } else if (st.compareTo(SampleDimensionType.REAL_64BITS) == 0) { noData = Double.NaN; } else if (st.compareTo(SampleDimensionType.SIGNED_16BITS) == 0) { noData = Short.MIN_VALUE; min = Short.MIN_VALUE; max = Short.MAX_VALUE; } else if (st.compareTo(SampleDimensionType.SIGNED_32BITS) == 0) { noData = Integer.MIN_VALUE; min = Integer.MIN_VALUE; max = Integer.MAX_VALUE; } else if (st.compareTo(SampleDimensionType.SIGNED_8BITS) == 0) { noData = -128; min = -128; max = 127; } else { // unsigned noData = 0; min = 0; // compute max if (st.compareTo(SampleDimensionType.UNSIGNED_1BIT) == 0) { max = 1; } else if (st.compareTo(SampleDimensionType.UNSIGNED_2BITS) == 0) { max = 3; } else if (st.compareTo(SampleDimensionType.UNSIGNED_4BITS) == 0) { max = 7; } else if (st.compareTo(SampleDimensionType.UNSIGNED_8BITS) == 0) { max = 255; } else if (st.compareTo(SampleDimensionType.UNSIGNED_16BITS) == 0) { max = 65535; } else if (st.compareTo(SampleDimensionType.UNSIGNED_32BITS) == 0) { max = Math.pow( 2, 32) - 1; } } if ((noDataValuesPerBand != null) && (noDataValuesPerBand[i] != null) && (noDataValuesPerBand[i].length > 0)) { // just take the first value, even if there are multiple noData = noDataValuesPerBand[i][0]; } if ((minsPerBand != null) && (minsPerBand.length > i)) { min = minsPerBand[i]; } if ((maxesPerBand != null) && (maxesPerBand.length > i)) { max = maxesPerBand[i]; } if ((namesPerBand != null) && (namesPerBand.length > i)) { bandName = namesPerBand[i]; } bands[i] = new SimplifiedGridSampleDimension( bandName, st, colorInterpretation, noData, min, max, 1, // no scale 0, // no offset null); } final AffineTransform worldToScreenTransform = RendererUtilities.worldToScreenTransform( mapExtent, new Rectangle( tileSize, tileSize)); try { final AffineTransform2D gridToCRS = new AffineTransform2D( worldToScreenTransform.createInverse()); final GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(null); final Map properties = new HashMap(); if (metadata != null) { properties.putAll(metadata); } if (tileMetadata != null) { properties.put( TILE_METADATA_PROPERTY_KEY, tileMetadata); } return gcf.create( coverageName, image, new GridGeometry2D( new GridEnvelope2D( PlanarImage.wrapRenderedImage( image).getBounds()), PixelInCell.CELL_CORNER, gridToCRS, GeoWaveGTRasterFormat.DEFAULT_CRS, null), bands, null, properties); } catch (IllegalArgumentException | NoninvertibleTransformException e) { LOGGER.warn( "Unable to calculate transformation for grid coordinates on read", e); } return null; } private static Map getProperties( final GridCoverage entry ) { Map originalCoverageProperties = new HashMap<>(); if (entry instanceof GridCoverage2D) { originalCoverageProperties = ((GridCoverage2D) entry).getProperties(); } else if (entry instanceof FitToIndexGridCoverage) { originalCoverageProperties = ((FitToIndexGridCoverage) entry).getProperties(); } return originalCoverageProperties; } public MergeableRasterTile<?> getRasterTileFromCoverage( final GridCoverage entry ) { return new MergeableRasterTile( getRaster( entry).getDataBuffer(), mergeStrategy == null ? null : mergeStrategy.getMetadata( entry, getProperties(entry), this), mergeStrategy, getAdapterId()); } public Raster getRaster( final GridCoverage entry ) { final SampleModel sm = sampleModel.createCompatibleSampleModel( tileSize, tileSize); return entry.getRenderedImage().copyData( new InternalWritableRaster( sm, new Point())); } @Override public AdapterPersistenceEncoding encode( final GridCoverage entry, final CommonIndexModel indexModel ) { final PersistentDataset<Object> adapterExtendedData = new PersistentDataset<Object>(); adapterExtendedData.addValue(new PersistentValue<Object>( DATA_FIELD_ID, getRasterTileFromCoverage(entry))); final AdapterPersistenceEncoding encoding; if (entry instanceof FitToIndexGridCoverage) { encoding = new FitToIndexPersistenceEncoding( getAdapterId(), new ByteArrayId( new byte[] {}), new PersistentDataset<CommonIndexValue>(), adapterExtendedData, ((FitToIndexGridCoverage) entry).getInsertionId()); } else { // this shouldn't happen LOGGER.warn("Grid coverage is not fit to the index"); encoding = new AdapterPersistenceEncoding( getAdapterId(), new ByteArrayId( new byte[] {}), new PersistentDataset<CommonIndexValue>(), adapterExtendedData); } return encoding; } @Override public FieldReader<Object> getReader( final ByteArrayId fieldId ) { if (DATA_FIELD_ID.equals(fieldId)) { return (FieldReader) new RasterTileReader(); } return null; } @Override public byte[] toBinary() { final byte[] coverageNameBytes = StringUtils.stringToBinary(coverageName); final byte[] sampleModelBinary = getSampleModelBinary(sampleModel); final byte[] colorModelBinary = getColorModelBinary(colorModel); int metadataBinaryLength = 4; final List<byte[]> entryBinaries = new ArrayList<byte[]>(); for (final Entry<String, String> e : metadata.entrySet()) { final byte[] keyBytes = StringUtils.stringToBinary(e.getKey()); final byte[] valueBytes = StringUtils.stringToBinary(e.getValue()); final int entryBinaryLength = 4 + valueBytes.length + keyBytes.length; final ByteBuffer buf = ByteBuffer.allocate(entryBinaryLength); buf.putInt(keyBytes.length); buf.put(keyBytes); buf.put(valueBytes); entryBinaries.add(buf.array()); metadataBinaryLength += (entryBinaryLength + 4); } byte[] histogramConfigBinary; if (histogramConfig != null) { histogramConfigBinary = PersistenceUtils.toBinary(histogramConfig); } else { histogramConfigBinary = new byte[] {}; } final byte[] noDataBinary = getNoDataBinary(noDataValuesPerBand); final byte[] backgroundBinary; if (backgroundValuesPerBand != null) { final int totalBytes = (backgroundValuesPerBand.length * 8); final ByteBuffer backgroundBuf = ByteBuffer.allocate(totalBytes); for (final double backgroundValue : backgroundValuesPerBand) { backgroundBuf.putDouble(backgroundValue); } backgroundBinary = backgroundBuf.array(); } else { backgroundBinary = new byte[] {}; } final byte[] minsBinary; if (minsPerBand != null) { final int totalBytes = (minsPerBand.length * 8); final ByteBuffer minsBuf = ByteBuffer.allocate(totalBytes); for (final double min : minsPerBand) { minsBuf.putDouble(min); } minsBinary = minsBuf.array(); } else { minsBinary = new byte[] {}; } final byte[] maxesBinary; if (maxesPerBand != null) { final int totalBytes = (maxesPerBand.length * 8); final ByteBuffer maxesBuf = ByteBuffer.allocate(totalBytes); for (final double max : maxesPerBand) { maxesBuf.putDouble(max); } maxesBinary = maxesBuf.array(); } else { maxesBinary = new byte[] {}; } final byte[] namesBinary; final int namesLength; if (namesPerBand != null) { int totalBytes = 0; final List<byte[]> namesBinaries = new ArrayList<byte[]>( namesPerBand.length); for (final String name : namesPerBand) { final byte[] nameBinary = StringUtils.stringToBinary(name); final int size = nameBinary.length + 4; final ByteBuffer nameBuf = ByteBuffer.allocate(size); totalBytes += size; nameBuf.putInt(nameBinary.length); nameBuf.put(nameBinary); namesBinaries.add(nameBuf.array()); } final ByteBuffer namesBuf = ByteBuffer.allocate(totalBytes); for (final byte[] nameBinary : namesBinaries) { namesBuf.put(nameBinary); } namesBinary = namesBuf.array(); namesLength = namesPerBand.length; } else { namesBinary = new byte[] {}; namesLength = 0; } byte[] mergeStrategyBinary; if (mergeStrategy != null) { mergeStrategyBinary = PersistenceUtils.toBinary(mergeStrategy); } else { mergeStrategyBinary = new byte[] {}; } final ByteBuffer buf = ByteBuffer.allocate(coverageNameBytes.length + sampleModelBinary.length + colorModelBinary.length + metadataBinaryLength + histogramConfigBinary.length + noDataBinary.length + minsBinary.length + maxesBinary.length + namesBinary.length + backgroundBinary.length + mergeStrategyBinary.length + 47); buf.putInt(tileSize); buf.putInt(coverageNameBytes.length); buf.put(coverageNameBytes); buf.putInt(sampleModelBinary.length); buf.put(sampleModelBinary); buf.putInt(colorModelBinary.length); buf.put(colorModelBinary); buf.putInt(entryBinaries.size()); for (final byte[] entryBinary : entryBinaries) { buf.putInt(entryBinary.length); buf.put(entryBinary); } buf.putInt(histogramConfigBinary.length); buf.put(histogramConfigBinary); buf.putInt(noDataBinary.length); buf.put(noDataBinary); buf.putInt(minsBinary.length); buf.put(minsBinary); buf.putInt(maxesBinary.length); buf.put(maxesBinary); buf.putInt(namesLength); buf.put(namesBinary); buf.putInt(backgroundBinary.length); buf.put(backgroundBinary); buf.putInt(mergeStrategyBinary.length); buf.put(mergeStrategyBinary); buf.put(buildPyramid ? (byte) 1 : (byte) 0); buf.put(equalizeHistogram ? (byte) 1 : (byte) 0); buf.put(interpolationToByte(interpolation)); return buf.array(); } protected static byte interpolationToByte( final Interpolation interpolation ) { // this is silly because it seems like a translation JAI should provide, // but it seems its not provided and its the most efficient approach // (rather than serializing class names) if (interpolation instanceof InterpolationNearest) { return Interpolation.INTERP_NEAREST; } if (interpolation instanceof InterpolationBilinear) { return Interpolation.INTERP_BILINEAR; } if (interpolation instanceof InterpolationBicubic2) { return Interpolation.INTERP_BICUBIC_2; } return Interpolation.INTERP_BICUBIC; } protected static byte[] getColorModelBinary( final ColorModel colorModel ) { final SerializableState serializableColorModel = SerializerFactory.getState(colorModel); try { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream( baos); oos.writeObject(serializableColorModel); return baos.toByteArray(); } catch (final IOException e) { LOGGER.warn( "Unable to serialize sample model", e); } return new byte[] {}; } protected static byte[] getSampleModelBinary( final SampleModel sampleModel ) { final SerializableState serializableSampleModel = SerializerFactory.getState(sampleModel); try { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream( baos); oos.writeObject(serializableSampleModel); return baos.toByteArray(); } catch (final IOException e) { LOGGER.warn( "Unable to serialize sample model", e); } return new byte[] {}; } protected static byte[] getNoDataBinary( final double[][] noDataValuesPerBand ) { if (noDataValuesPerBand != null) { int totalBytes = 4; final List<byte[]> noDataValuesBytes = new ArrayList<byte[]>( noDataValuesPerBand.length); for (final double[] noDataValues : noDataValuesPerBand) { int length = 0; if (noDataValues != null) { length = noDataValues.length; } final int thisBytes = 4 + (length * 8); totalBytes += thisBytes; final ByteBuffer noDataBuf = ByteBuffer.allocate(thisBytes); noDataBuf.putInt(length); if (noDataValues != null) { for (final double noDataValue : noDataValues) { noDataBuf.putDouble(noDataValue); } } noDataValuesBytes.add(noDataBuf.array()); } final ByteBuffer noDataBuf = ByteBuffer.allocate(totalBytes); noDataBuf.putInt(noDataValuesPerBand.length); for (final byte[] noDataValueBytes : noDataValuesBytes) { noDataBuf.put(noDataValueBytes); } return noDataBuf.array(); } else { return new byte[] {}; } } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = ByteBuffer.wrap(bytes); tileSize = buf.getInt(); final int coverageNameLength = buf.getInt(); final byte[] coverageNameBinary = new byte[coverageNameLength]; buf.get(coverageNameBinary); coverageName = StringUtils.stringFromBinary(coverageNameBinary); final int sampleModelLength = buf.getInt(); final byte[] sampleModelBinary = new byte[sampleModelLength]; buf.get(sampleModelBinary); try { final ByteArrayInputStream bais = new ByteArrayInputStream( sampleModelBinary); final ObjectInputStream ois = new ObjectInputStream( bais); final Object o = ois.readObject(); if ((o instanceof SerializableState) && (((SerializableState) o).getObject() instanceof SampleModel)) { sampleModel = (SampleModel) ((SerializableState) o).getObject(); } } catch (final Exception e) { LOGGER.warn( "Unable to deserialize sample model", e); } final int colorModelLength = buf.getInt(); final byte[] colorModelBinary = new byte[colorModelLength]; buf.get(colorModelBinary); try { final ByteArrayInputStream bais = new ByteArrayInputStream( colorModelBinary); final ObjectInputStream ois = new ObjectInputStream( bais); final Object o = ois.readObject(); if ((o instanceof SerializableState) && (((SerializableState) o).getObject() instanceof ColorModel)) { colorModel = (ColorModel) ((SerializableState) o).getObject(); } } catch (final Exception e) { LOGGER.warn( "Unable to deserialize color model", e); } final int numMetadataEntries = buf.getInt(); metadata = new HashMap<String, String>(); for (int i = 0; i < numMetadataEntries; i++) { final int entryBinaryLength = buf.getInt(); final byte[] entryBinary = new byte[entryBinaryLength]; buf.get(entryBinary); final ByteBuffer entryBuf = ByteBuffer.wrap(entryBinary); final int keyLength = entryBuf.getInt(); final byte[] keyBinary = new byte[keyLength]; final byte[] valueBinary = new byte[entryBinary.length - keyLength - 4]; entryBuf.get(keyBinary); entryBuf.get(valueBinary); metadata.put( StringUtils.stringFromBinary(keyBinary), StringUtils.stringFromBinary(valueBinary)); } final int histogramConfigLength = buf.getInt(); if (histogramConfigLength == 0) { histogramConfig = null; } else { final byte[] histogramConfigBinary = new byte[histogramConfigLength]; buf.get(histogramConfigBinary); histogramConfig = PersistenceUtils.fromBinary( histogramConfigBinary, HistogramConfig.class); } final int noDataBinaryLength = buf.getInt(); if (noDataBinaryLength == 0) { noDataValuesPerBand = null; } else { noDataValuesPerBand = new double[buf.getInt()][]; for (int b = 0; b < noDataValuesPerBand.length; b++) { noDataValuesPerBand[b] = new double[buf.getInt()]; for (int i = 0; i < noDataValuesPerBand[b].length; i++) { noDataValuesPerBand[b][i] = buf.getDouble(); } } } final int minsBinaryLength = buf.getInt(); if (minsBinaryLength == 0) { minsPerBand = null; } else { minsPerBand = new double[minsBinaryLength / 8]; for (int b = 0; b < minsPerBand.length; b++) { minsPerBand[b] = buf.getDouble(); } } final int maxesBinaryLength = buf.getInt(); if (maxesBinaryLength == 0) { maxesPerBand = null; } else { maxesPerBand = new double[maxesBinaryLength / 8]; for (int b = 0; b < maxesPerBand.length; b++) { maxesPerBand[b] = buf.getDouble(); } } final int namesLength = buf.getInt(); if (namesLength == 0) { namesPerBand = null; } else { namesPerBand = new String[namesLength]; for (int b = 0; b < namesPerBand.length; b++) { final int nameSize = buf.getInt(); final byte[] nameBytes = new byte[nameSize]; buf.get(nameBytes); namesPerBand[b] = StringUtils.stringFromBinary(nameBytes); } } final int backgroundBinaryLength = buf.getInt(); if (backgroundBinaryLength == 0) { backgroundValuesPerBand = null; } else { backgroundValuesPerBand = new double[backgroundBinaryLength / 8]; for (int b = 0; b < backgroundValuesPerBand.length; b++) { backgroundValuesPerBand[b] = buf.getDouble(); } } final byte[] mergeStrategyBinary = new byte[buf.getInt()]; if (mergeStrategyBinary.length == 0) { mergeStrategy = null; } else { buf.get(mergeStrategyBinary); mergeStrategy = PersistenceUtils.fromBinary( mergeStrategyBinary, RootMergeStrategy.class); } buildPyramid = (buf.get() != 0); equalizeHistogram = (buf.get() != 0); interpolation = Interpolation.getInstance(buf.get()); init(); } @Override public FieldWriter<GridCoverage, Object> getWriter( final ByteArrayId fieldId ) { if (DATA_FIELD_ID.equals(fieldId)) { return (FieldWriter) new RasterTileWriter(); } return null; } @Override public ByteArrayId[] getSupportedStatisticsTypes() { return supportedStatsTypes; } @Override public DataStatistics<GridCoverage> createDataStatistics( final ByteArrayId statisticsType ) { if (OverviewStatistics.STATS_TYPE.equals(statisticsType)) { return new OverviewStatistics( new ByteArrayId( coverageName)); } else if (BoundingBoxDataStatistics.STATS_TYPE.equals(statisticsType)) { return new RasterBoundingBoxStatistics( new ByteArrayId( coverageName)); } else if (RasterFootprintStatistics.STATS_TYPE.equals(statisticsType)) { return new RasterFootprintStatistics( new ByteArrayId( coverageName)); } else if (HistogramStatistics.STATS_TYPE.equals(statisticsType) && (histogramConfig != null)) { return new HistogramStatistics( new ByteArrayId( coverageName), histogramConfig); } // HP Fortify "Log Forging" false positive // What Fortify considers "user input" comes only // from users with OS-level access anyway LOGGER.warn("Unrecognized statistics type " + statisticsType.getString() + " using count statistic"); return new CountDataStatistics<GridCoverage>( getAdapterId(), statisticsType); } public double[][] getNoDataValuesPerBand() { return noDataValuesPerBand; } @Override public EntryVisibilityHandler<GridCoverage> getVisibilityHandler( final ByteArrayId statisticsId ) { return visibilityHandler; } public Map<String, String> getMetadata() { return metadata; } public String getCoverageName() { return coverageName; } public SampleModel getSampleModel() { return sampleModel; } public ColorModel getColorModel() { return colorModel; } public int getTileSize() { return tileSize; } private static final class SimplifiedGridSampleDimension extends GridSampleDimension implements SampleDimension { /** * */ private static final long serialVersionUID = 2227219522016820587L; private final double nodata; private final double minimum; private final double maximum; private final double scale; private final double offset; private final Unit<?> unit; private final SampleDimensionType type; private final ColorInterpretation color; private final Category bkg; public SimplifiedGridSampleDimension( final CharSequence description, final SampleDimensionType type, final ColorInterpretation color, final double nodata, final double minimum, final double maximum, final double scale, final double offset, final Unit<?> unit ) { super( description, // first attempt to retain the min and max with a "normal" // category !Double.isNaN(minimum) && !Double.isNaN(maximum) ? new Category[] { new Category( Vocabulary.formatInternational(VocabularyKeys.NORMAL), (Color) null, NumberRange.create( minimum, maximum)), } : // if that doesn't work, attempt to retain the nodata // category !Double.isNaN(nodata) ? new Category[] { new Category( Vocabulary.formatInternational(VocabularyKeys.NODATA), new Color( 0, 0, 0, 0), NumberRange.create( nodata, nodata)) } : null, unit); this.nodata = nodata; this.minimum = minimum; this.maximum = maximum; this.scale = scale; this.offset = offset; this.unit = unit; this.type = type; this.color = color; bkg = new Category( "Background", TRANSPARENT, 0); } @Override public double getMaximumValue() { return maximum; } @Override public double getMinimumValue() { return minimum; } @Override public double[] getNoDataValues() throws IllegalStateException { return new double[] { nodata }; } @Override public double getOffset() throws IllegalStateException { return offset; } @Override public NumberRange<? extends Number> getRange() { return super.getRange(); } @Override public SampleDimensionType getSampleDimensionType() { return type; } @Override public Unit<?> getUnits() { return unit; } @Override public double getScale() { return scale; } @Override public ColorInterpretation getColorInterpretation() { return color; } @Override public InternationalString[] getCategoryNames() throws IllegalStateException { return new InternationalString[] { SimpleInternationalString.wrap("Background") }; } @Override public boolean equals( final Object obj ) { if (!(obj instanceof SimplifiedGridSampleDimension)) { return false; } return super.equals(obj); } @Override public int hashCode() { return super.hashCode(); } } private static class InternalWritableRaster extends WritableRaster { // the constructor is protected, so this class is intended as a simple // way to access the constructor protected InternalWritableRaster( final SampleModel sampleModel, final Point origin ) { super( sampleModel, origin); } } public Map<String, String> getConfiguredOptions() { final Map<String, String> configuredOptions = new HashMap<String, String>(); if (mergeStrategy != null) { final String mergeStrategyStr = ByteArrayUtils.byteArrayToString(PersistenceUtils.toBinary(mergeStrategy)); configuredOptions.put( RasterTileRowTransform.MERGE_STRATEGY_KEY, mergeStrategyStr); } return configuredOptions; } @Override public HadoopWritableSerializer<GridCoverage, GridCoverageWritable> createWritableSerializer() { return new HadoopWritableSerializer<GridCoverage, GridCoverageWritable>() { @Override public GridCoverageWritable toWritable( final GridCoverage entry ) { final Envelope env = entry.getEnvelope(); final DataBuffer dataBuffer = entry.getRenderedImage().copyData( new InternalWritableRaster( sampleModel.createCompatibleSampleModel( tileSize, tileSize), new Point())).getDataBuffer(); Persistable metadata = null; if (entry instanceof GridCoverage2D) { final Object metadataObj = ((GridCoverage2D) entry).getProperty(TILE_METADATA_PROPERTY_KEY); if ((metadataObj != null) && (metadataObj instanceof Persistable)) { metadata = (Persistable) metadataObj; } } return new GridCoverageWritable( new RasterTile( dataBuffer, metadata), env.getMinimum(0), env.getMaximum(0), env.getMinimum(1), env.getMaximum(1)); } @Override public GridCoverage fromWritable( final GridCoverageWritable writable ) { final ReferencedEnvelope mapExtent = new ReferencedEnvelope( writable.getMinX(), writable.getMaxX(), writable.getMinY(), writable.getMaxY(), GeoWaveGTRasterFormat.DEFAULT_CRS); try { return prepareCoverage( writable.getRasterTile(), tileSize, mapExtent); } catch (final IOException e) { LOGGER.error( "Unable to read raster data", e); } return null; } }; } public boolean isEqualizeHistogram() { return equalizeHistogram; } public Interpolation getInterpolation() { return interpolation; } @Override public Map<String, String> getOptions( final Map<String, String> existingOptions ) { final Map<String, String> configuredOptions = getConfiguredOptions(); if (existingOptions == null) { return configuredOptions; } final Map<String, String> mergedOptions = new HashMap<String, String>( configuredOptions); for (final Entry<String, String> e : existingOptions.entrySet()) { final String configuredValue = configuredOptions.get(e.getKey()); if ((e.getValue() == null) && (configuredValue == null)) { continue; } else if ((e.getValue() == null) || ((e.getValue() != null) && !e.getValue().equals( configuredValue))) { final String newValue = mergeOption( e.getKey(), e.getValue(), configuredValue); if ((newValue != null) && newValue.equals(e.getValue())) { // once merged the value didn't // change, so just continue continue; } if (newValue == null) { mergedOptions.remove(e.getKey()); } else { mergedOptions.put( e.getKey(), newValue); } } } for (final Entry<String, String> e : configuredOptions.entrySet()) { if (!existingOptions.containsKey(e.getKey())) { // existing value should be null // because this key is contained in // the merged set if (e.getValue() == null) { continue; } else { final String newValue = mergeOption( e.getKey(), null, e.getValue()); if (newValue == null) { mergedOptions.remove(e.getKey()); } else { mergedOptions.put( e.getKey(), newValue); } } } } return mergedOptions; } private String mergeOption( final String optionKey, final String currentValue, final String nextValue ) { if ((currentValue == null) || currentValue.trim().isEmpty()) { return nextValue; } else if ((nextValue == null) || nextValue.trim().isEmpty()) { return currentValue; } if (RasterTileRowTransform.MERGE_STRATEGY_KEY.equals(optionKey)) { final byte[] currentStrategyBytes = ByteArrayUtils.byteArrayFromString(currentValue); final byte[] nextStrategyBytes = ByteArrayUtils.byteArrayFromString(nextValue); final RootMergeStrategy currentStrategy = PersistenceUtils.fromBinary( currentStrategyBytes, RootMergeStrategy.class); final RootMergeStrategy nextStrategy = PersistenceUtils.fromBinary( nextStrategyBytes, RootMergeStrategy.class); currentStrategy.merge(nextStrategy); return ByteArrayUtils.byteArrayToString(PersistenceUtils.toBinary(currentStrategy)); } return nextValue; } @Override public RowTransform<RasterTile<?>> getTransform() { if (mergeStrategy != null) { return new RasterTileRowTransform(); } else { return null; } } @Override public int getPositionOfOrderedField( final CommonIndexModel model, final ByteArrayId fieldId ) { int i = 0; for (final NumericDimensionField<? extends CommonIndexValue> dimensionField : model.getDimensions()) { if (fieldId.equals(dimensionField.getFieldId())) { return i; } i++; } if (fieldId.equals(DATA_FIELD_ID)) { return i; } return -1; } @Override public ByteArrayId getFieldIdForPosition( final CommonIndexModel model, final int position ) { if (position < model.getDimensions().length) { int i = 0; for (final NumericDimensionField<? extends CommonIndexValue> dimensionField : model.getDimensions()) { if (i == position) { return dimensionField.getFieldId(); } i++; } } else { final int numDimensions = model.getDimensions().length; if (position == numDimensions) { return DATA_FIELD_ID; } } return null; } }