package mil.nga.giat.geowave.adapter.raster.stats; 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.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.processing.AbstractOperation; import org.geotools.coverage.processing.BaseStatisticsOperationJAI; import org.geotools.coverage.processing.CoverageProcessor; import org.geotools.coverage.processing.operation.Histogram; import org.opengis.coverage.grid.GridCoverage; import org.opengis.parameter.ParameterValueGroup; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.Polygon; 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.plugin.GeoWaveGTRasterFormat; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.index.Mergeable; import mil.nga.giat.geowave.core.index.PersistenceUtils; import mil.nga.giat.geowave.core.store.adapter.statistics.AbstractDataStatistics; import mil.nga.giat.geowave.core.store.base.DataStoreEntryInfo; public class HistogramStatistics extends AbstractDataStatistics<GridCoverage> { private static final Logger LOGGER = LoggerFactory.getLogger(HistogramStatistics.class); public static final ByteArrayId STATS_TYPE = new ByteArrayId( "HISTOGRAM_STATS"); private final Map<Resolution, javax.media.jai.Histogram> histograms = new HashMap<Resolution, javax.media.jai.Histogram>(); private HistogramConfig histogramConfig; protected HistogramStatistics() { super(); } public HistogramStatistics( final ByteArrayId dataAdapterId, final HistogramConfig histogramConfig ) { super( dataAdapterId, STATS_TYPE); this.histogramConfig = histogramConfig; } @Override public byte[] toBinary() { final List<byte[]> perEntryBinary = new ArrayList<byte[]>(); int totalBytes = 4; for (final Entry<Resolution, javax.media.jai.Histogram> entry : histograms.entrySet()) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] keyBytes; byte[] valueBytes = new byte[] {}; if (entry.getKey() != null) { keyBytes = PersistenceUtils.toBinary(entry.getKey()); } else { keyBytes = new byte[] {}; } if (entry.getValue() != null) { ObjectOutputStream oos; try { oos = new ObjectOutputStream( baos); oos.writeObject(entry.getValue()); oos.close(); baos.close(); valueBytes = baos.toByteArray(); } catch (final IOException e) { LOGGER.warn( "Unable to write histogram", e); } } // 8 for key and value lengths as ints final int entryBytes = 8 + keyBytes.length + valueBytes.length; final ByteBuffer buf = ByteBuffer.allocate(entryBytes); buf.putInt(keyBytes.length); buf.put(keyBytes); buf.putInt(valueBytes.length); buf.put(valueBytes); perEntryBinary.add(buf.array()); totalBytes += entryBytes; } final byte[] configBinary = PersistenceUtils.toBinary(histogramConfig); totalBytes += (configBinary.length + 4); final ByteBuffer buf = super.binaryBuffer(totalBytes); buf.putInt(configBinary.length); buf.put(configBinary); buf.putInt(perEntryBinary.size()); for (final byte[] entryBinary : perEntryBinary) { buf.put(entryBinary); } return buf.array(); } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = super.binaryBuffer(bytes); final byte[] configBinary = new byte[buf.getInt()]; buf.get(configBinary); histogramConfig = PersistenceUtils.fromBinary( configBinary, HistogramConfig.class); final int numEntries = buf.getInt(); for (int i = 0; i < numEntries; i++) { final int keyLength = buf.getInt(); Resolution key = null; if (keyLength > 0) { final byte[] keyBytes = new byte[keyLength]; buf.get(keyBytes); key = PersistenceUtils.fromBinary( keyBytes, Resolution.class); } final int valueLength = buf.getInt(); javax.media.jai.Histogram histogram = null; if (valueLength > 0) { final byte[] valueBytes = new byte[valueLength]; buf.get(valueBytes); ObjectInputStream ois; try { ois = new ObjectInputStream( new ByteArrayInputStream( valueBytes)); histogram = (javax.media.jai.Histogram) ois.readObject(); } catch (IOException | ClassNotFoundException e) { LOGGER.warn( "Unable to read histogram", e); } } histograms.put( key, histogram); } } @Override public void entryIngested( final DataStoreEntryInfo entryInfo, final GridCoverage entry ) { /* * Create the operation for the Histogram with a ROI. No subsampling * should be applied. */ final Geometry footprint; if (entry instanceof FitToIndexGridCoverage) { footprint = ((FitToIndexGridCoverage) entry).getFootprintWorldGeometry(); if (footprint == null) { return; } } else { footprint = RasterUtils.getFootprint( entry, GeoWaveGTRasterFormat.DEFAULT_CRS); } final GridCoverage originalCoverage; Resolution resolution = null; if (entry instanceof FitToIndexGridCoverage) { originalCoverage = ((FitToIndexGridCoverage) entry).getOriginalCoverage(); resolution = ((FitToIndexGridCoverage) entry).getResolution(); } else { originalCoverage = entry; } if (footprint instanceof GeometryCollection) { final GeometryCollection collection = (GeometryCollection) footprint; for (int g = 0; g < collection.getNumGeometries(); g++) { final Geometry geom = collection.getGeometryN(g); if (geom instanceof Polygon) { mergePoly( originalCoverage, (Polygon) geom, resolution); } } } else if (footprint instanceof Polygon) { mergePoly( originalCoverage, (Polygon) footprint, resolution); } } private void mergePoly( final GridCoverage originalCoverage, final Polygon poly, final Resolution resolution ) { final CoverageProcessor processor = CoverageProcessor.getInstance(); final AbstractOperation op = (AbstractOperation) processor.getOperation("Histogram"); final ParameterValueGroup params = op.getParameters(); params.parameter( "Source").setValue( originalCoverage); params.parameter( BaseStatisticsOperationJAI.ROI.getName().getCode()).setValue( poly); params.parameter( "lowValue").setValue( histogramConfig.getLowValues()); params.parameter( "highValue").setValue( histogramConfig.getHighValues()); params.parameter( "numBins").setValue( histogramConfig.getNumBins()); try { final GridCoverage2D coverage = (GridCoverage2D) op.doOperation( params, null); final javax.media.jai.Histogram histogram = (javax.media.jai.Histogram) coverage .getProperty(Histogram.GT_SYNTHETIC_PROPERTY_HISTOGRAM); javax.media.jai.Histogram mergedHistogram; final javax.media.jai.Histogram resolutionHistogram = histograms.get(resolution); if (resolutionHistogram != null) { mergedHistogram = mergeHistograms( resolutionHistogram, histogram); } else { mergedHistogram = histogram; } synchronized (this) { histograms.put( resolution, mergedHistogram); } } catch (final Exception e) { // this is simply 'info' because there is a known issue in the // histogram op when the ROI is so small that the resulting cropped // pixel size is 0 LOGGER .info( "This is often a non-issue relating to applying an ROI calculation that results in 0 pixels (the error is in calculating stats).", e); } } private static javax.media.jai.Histogram mergeHistograms( final javax.media.jai.Histogram histogram1, final javax.media.jai.Histogram histogram2 ) { final int numBands = Math.min( histogram1.getNumBands(), histogram2.getNumBands()); final double[] lowValue1 = histogram1.getLowValue(); final double[] lowValue2 = histogram2.getLowValue(); final double[] lowValue = new double[numBands]; for (int b = 0; b < numBands; b++) { lowValue[b] = Math.min( lowValue1[b], lowValue2[b]); } final double[] highValue1 = histogram1.getHighValue(); final double[] highValue2 = histogram2.getHighValue(); final double[] highValue = new double[numBands]; for (int b = 0; b < numBands; b++) { highValue[b] = Math.max( highValue1[b], highValue2[b]); } final int[][] bins1 = histogram1.getBins(); final int[][] bins2 = histogram2.getBins(); final int[] numBins = new int[numBands]; for (int b = 0; b < numBands; b++) { numBins[b] = Math.min( bins1[b].length, bins2[b].length); } final javax.media.jai.Histogram histogram = new javax.media.jai.Histogram( numBins, lowValue, highValue); for (int b = 0; b < numBands; b++) { // this is a bit of a hack, but the only way to interact with the // counts in a mutable way is by getting an array of the bin counts // and setting values in the array final int[] bins = histogram.getBins(b); for (int i = 0; i < bins.length; i++) { bins[i] = bins1[b][i] + bins2[b][i]; } } return histogram; } public Set<Resolution> getResolutions() { return histograms.keySet(); } public javax.media.jai.Histogram getHistogram( final Resolution resolution ) { return histograms.get(resolution); } @Override public void merge( final Mergeable statistics ) { if ((statistics != null) && (statistics instanceof HistogramStatistics)) { final Set<Resolution> resolutions = new HashSet<Resolution>( getResolutions()); resolutions.addAll(((HistogramStatistics) statistics).getResolutions()); for (final Resolution res : resolutions) { final javax.media.jai.Histogram otherHistogram = ((HistogramStatistics) statistics).getHistogram(res); final javax.media.jai.Histogram thisHistogram = getHistogram(res); if (otherHistogram != null) { javax.media.jai.Histogram mergedHistogram; if (thisHistogram != null) { mergedHistogram = mergeHistograms( thisHistogram, otherHistogram); } else { mergedHistogram = otherHistogram; } synchronized (this) { histograms.put( res, mergedHistogram); } } } } } }