package mil.nga.giat.geowave.analytic.mapreduce.kde; import java.awt.image.WritableRaster; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.mapreduce.Reducer; import org.opengis.coverage.grid.GridCoverage; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import mil.nga.giat.geowave.adapter.raster.RasterUtils; import mil.nga.giat.geowave.core.geotime.ingest.SpatialDimensionalityTypeProvider; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.store.index.PrimaryIndex; import mil.nga.giat.geowave.mapreduce.JobContextIndexStore; import mil.nga.giat.geowave.mapreduce.output.GeoWaveOutputKey; public class AccumuloKDEReducer extends Reducer<DoubleWritable, LongWritable, GeoWaveOutputKey, GridCoverage> { private static final class TileInfo { private final double tileWestLon; private final double tileEastLon; private final double tileSouthLat; private final double tileNorthLat; private final int x; private final int y; public TileInfo( final double tileWestLon, final double tileEastLon, final double tileSouthLat, final double tileNorthLat, final int x, final int y ) { this.tileWestLon = tileWestLon; this.tileEastLon = tileEastLon; this.tileSouthLat = tileSouthLat; this.tileNorthLat = tileNorthLat; this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(tileEastLon); result = (prime * result) + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(tileNorthLat); result = (prime * result) + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(tileSouthLat); result = (prime * result) + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(tileWestLon); result = (prime * result) + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals( final Object obj ) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final TileInfo other = (TileInfo) obj; if (Double.doubleToLongBits(tileEastLon) != Double.doubleToLongBits(other.tileEastLon)) { return false; } if (Double.doubleToLongBits(tileNorthLat) != Double.doubleToLongBits(other.tileNorthLat)) { return false; } if (Double.doubleToLongBits(tileSouthLat) != Double.doubleToLongBits(other.tileSouthLat)) { return false; } if (Double.doubleToLongBits(tileWestLon) != Double.doubleToLongBits(other.tileWestLon)) { return false; } return true; } } public static final int NUM_BANDS = 3; protected static final String[] NAME_PER_BAND = new String[] { "Weight", "Normalized", "Percentile" }; protected static final double[] MINS_PER_BAND = new double[] { 0, 0, 0 }; protected static final double[] MAXES_PER_BAND = new double[] { Double.MAX_VALUE, 1, 1 }; private double max = -Double.MAX_VALUE; private long currentKey = 0; private long totalKeys; private int minLevels; private int maxLevels; private int numLevels; private int level; private int numYPosts; private int numXTiles; private int numYTiles; private String coverageName; protected List<ByteArrayId> indexList; @Override protected void reduce( final DoubleWritable key, final Iterable<LongWritable> values, final Context context ) throws IOException, InterruptedException { if (key.get() < 0) { final double prevMax = -key.get(); if (prevMax > max) { max = prevMax; } } else { final double value = key.get(); final double normalizedValue = value / max; // for consistency give all cells with matching weight the same // percentile final double percentile = (currentKey + 1.0) / totalKeys; // calculate weights for this key for (final LongWritable v : values) { final long cellIndex = v.get() / numLevels; final TileInfo tileInfo = fromCellIndexToTileInfo(cellIndex); final WritableRaster raster = RasterUtils.createRasterTypeDouble( NUM_BANDS, KDEJobRunner.TILE_SIZE); raster.setSample( tileInfo.x, tileInfo.y, 0, key.get()); raster.setSample( tileInfo.x, tileInfo.y, 1, normalizedValue); raster.setSample( tileInfo.x, tileInfo.y, 2, percentile); context.write( new GeoWaveOutputKey( new ByteArrayId( coverageName), indexList), RasterUtils.createCoverageTypeDouble( coverageName, tileInfo.tileWestLon, tileInfo.tileEastLon, tileInfo.tileSouthLat, tileInfo.tileNorthLat, MINS_PER_BAND, MAXES_PER_BAND, NAME_PER_BAND, raster)); currentKey++; } } } @SuppressFBWarnings(value = "INT_BAD_REM_BY_1", justification = "The calculation is appropriate if we ever want to vary to tile size.") private TileInfo fromCellIndexToTileInfo( final long index ) { final int xPost = (int) (index / numYPosts); final int yPost = (int) (index % numYPosts); final int xTile = xPost / KDEJobRunner.TILE_SIZE; final int yTile = yPost / KDEJobRunner.TILE_SIZE; final int x = (xPost % KDEJobRunner.TILE_SIZE); final int y = (yPost % KDEJobRunner.TILE_SIZE); final double tileWestLon = ((xTile * 360.0) / numXTiles) - 180.0; final double tileSouthLat = ((yTile * 180.0) / numYTiles) - 90.0; final double tileEastLon = tileWestLon + (360.0 / numXTiles); final double tileNorthLat = tileSouthLat + (180.0 / numYTiles); return new TileInfo( tileWestLon, tileEastLon, tileSouthLat, tileNorthLat, x, KDEJobRunner.TILE_SIZE - y - 1); // remember java rasters go // from 0 at the // top // to (height-1) at the bottom, so we have // to // inverse the y here which goes from bottom // to top } @Override protected void setup( final Context context ) throws IOException, InterruptedException { super.setup(context); minLevels = context.getConfiguration().getInt( KDEJobRunner.MIN_LEVEL_KEY, 1); maxLevels = context.getConfiguration().getInt( KDEJobRunner.MAX_LEVEL_KEY, 25); coverageName = context.getConfiguration().get( KDEJobRunner.COVERAGE_NAME_KEY, ""); numLevels = (maxLevels - minLevels) + 1; level = context.getConfiguration().getInt( "mapred.task.partition", 0) + minLevels; numXTiles = (int) Math.pow( 2, level + 1); numYTiles = (int) Math.pow( 2, level); numYPosts = numYTiles * KDEJobRunner.TILE_SIZE; totalKeys = context.getConfiguration().getLong( "Entries per level.level" + level, 10); final PrimaryIndex[] indices = JobContextIndexStore.getIndices(context); indexList = new ArrayList<ByteArrayId>(); if ((indices != null) && (indices.length > 0)) { for (final PrimaryIndex index : indices) { indexList.add(index.getId()); } } else { indexList.add(new SpatialDimensionalityTypeProvider.SpatialIndexBuilder().createIndex().getId()); } } }