Java tutorial
/* * Copyright 2009-2014 DigitalGlobe, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ package org.mrgeo.opimage; import org.apache.commons.lang.ArrayUtils; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.map.JsonMappingException; import org.mrgeo.image.MrsImage; import org.mrgeo.image.MrsImagePyramidMetadata; import org.mrgeo.mapreduce.formats.TileClusterInfo; import org.mrgeo.mapreduce.formats.TileCollection; import org.mrgeo.rasterops.OpImageUtils; import org.mrgeo.data.image.MrsImageDataProvider; import org.mrgeo.data.raster.RasterUtils; import org.mrgeo.data.tile.MrsTileReader; import org.mrgeo.utils.LongRectangle; import org.mrgeo.utils.TMSUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.media.jai.ImageLayout; import javax.media.jai.RasterFactory; import javax.media.jai.SourcelessOpImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; import java.io.IOException; import java.io.Serializable; import java.util.Arrays; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; @SuppressWarnings("unchecked") public class MrsPyramidOpImage extends SourcelessOpImage implements Serializable { public static class MrsImageOpImageException extends RuntimeException { private static final long serialVersionUID = 1L; private final Exception origException; public MrsImageOpImageException(final Exception e) { this.origException = e; } public MrsImageOpImageException(final String msg) { final Exception e = new Exception(msg); this.origException = e; } @Override public void printStackTrace() { origException.printStackTrace(); } } private class CachedRaster { final public long tx; final public long ty; final TileCollection<Raster>.Cluster rasterCluster; public CachedRaster(final long tx, final long ty, final TileCollection<Raster>.Cluster rasterCluster) { this.rasterCluster = rasterCluster; this.tx = tx; this.ty = ty; } } // TODO: This class is SOLELY here because the getProperties() method below sends back a // hashtable, and one of the entries is a MrsImage. The calling method may or may not // close the image. Therefore, we'll do it here. // // This should go away when the properties ValidRegion and GeographicTranslator either // go away or are converted to strings, OR if MrsImageReader is separated from the // MrsImage. @SuppressWarnings("rawtypes") private class ClosingHashTable extends Hashtable { private static final long serialVersionUID = 1L; public ClosingHashTable() { } @Override protected void finalize() throws Throwable { for (final Object o : values()) { if (o instanceof MrsImage) { System.out.println("Closing image from properties...: " + Integer.toHexString(System.identityHashCode(o)) + ": " + o.toString()); ((MrsImage) o).close(); } } super.finalize(); } } private static final Logger log = LoggerFactory.getLogger(MrsPyramidOpImage.class); private static final long serialVersionUID = 1L; // The cachedRaster is used during map/reduce of single tiles. The record // reader will // load the tile from the sequencefile/acculumo table and set it here. This // will // significantly speed up operating on tiles. private transient CachedRaster cachedRaster = null; private transient TileClusterInfo tileClusterInfo; private MrsImageDataProvider dp = null; private int zoomlevel; private final int tileSize; // A "blank" raster, for when the tile request is outside the image bounds private transient Raster emptyRaster = null; public MrsPyramidOpImage(final MrsImageDataProvider dp, final int zoomlevel, final ImageLayout layout, final Map<?, ?> configuration, final SampleModel sampleModel, final int minX, final int minY, final int width, final int height, final int tileSize, final TileClusterInfo tileClusterInfo) { super(layout, configuration, sampleModel, minX, minY, width, height); this.dp = dp; this.zoomlevel = zoomlevel; this.tileSize = tileSize; this.tileClusterInfo = tileClusterInfo; } public static MrsPyramidOpImage create(final MrsImageDataProvider dp, final Long level, final TileClusterInfo tileClusterInfo) throws JsonGenerationException, JsonMappingException, IOException { // since this is a SourcelessOpImage, we'll need to calculate the layout // ourselves... final MrsImagePyramidMetadata metadata = dp.getMetadataReader().read(); final int tileSize = metadata.getTilesize(); final int minX = (int) tileClusterInfo.getOffsetX() * tileSize; final int minY = (int) tileClusterInfo.getOffsetY() * tileSize; final int zoomlevel = level.intValue(); final int width = tileClusterInfo.getWidth() * tileSize; final int height = tileClusterInfo.getHeight() * tileSize; MrsTileReader<Raster> tileReader = dp.getMrsTileReader(zoomlevel); final Iterator<Raster> it = tileReader.get(); Raster raster = it.next(); final SampleModel sampleModel = raster.getSampleModel(); final ImageLayout layout = calculateLayout(dp, zoomlevel); return new MrsPyramidOpImage(dp, zoomlevel, layout, null, sampleModel, minX, minY, width, height, tileSize, tileClusterInfo); } public static MrsPyramidOpImage create(MrsImageDataProvider dp, final TileClusterInfo tileClusterInfo) throws JsonGenerationException, JsonMappingException, IOException { int zoomlevel = dp.getMetadataReader().read().getMaxZoomLevel(); return create(dp, new Long(zoomlevel), tileClusterInfo); } private static ImageLayout calculateLayout(final MrsImageDataProvider dp, final int zoomlevel) throws IOException { final MrsImagePyramidMetadata metadata = dp.getMetadataReader().read(); final LongRectangle pixelbounds = metadata.getPixelBounds(zoomlevel); MrsTileReader<Raster> tileReader = dp.getMrsTileReader(zoomlevel); final Iterator<Raster> it = tileReader.get(); Raster raster = it.next(); final ColorModel colorModel = RasterUtils.createColorModel(raster); final ImageLayout layout = new ImageLayout(0, 0, (int) pixelbounds.getWidth(), (int) pixelbounds.getHeight(), 0, 0, metadata.getTilesize(), metadata.getTilesize(), raster.getSampleModel(), colorModel); return layout; } @Override public Raster computeTile(final int tx, final int ty) { log.debug("tx: {} ty: {}", tx, ty); if (cachedRaster != null && cachedRaster.rasterCluster != null) { long mrsPyramidTileX = cachedRaster.tx + tx; // If the required tile is outside the world tile boundaries, then // wrap around to get the tile. if (mrsPyramidTileX < 0) { mrsPyramidTileX += TMSUtils.numXTiles(zoomlevel); } else { final long numXTiles = TMSUtils.numXTiles(zoomlevel); if (mrsPyramidTileX >= numXTiles) { mrsPyramidTileX -= numXTiles; } } final long mrsPyramidTileY = cachedRaster.ty + ty; if (TMSUtils.isValidTile(mrsPyramidTileX, mrsPyramidTileY, zoomlevel)) { final long tileId = TMSUtils.tileid(mrsPyramidTileX, mrsPyramidTileY, zoomlevel); final Raster raster = cachedRaster.rasterCluster.get(new Long(tileId)); if (raster != null) { return raster; } } } return blankRaster(); } @Override public int getHeight() { return tileClusterInfo.getHeight() * tileSize; } @Override public int getMaxTileX() { return getMinTileX() + getNumXTiles() - 1; } @Override public int getMaxTileY() { return getMinTileY() + getNumYTiles() - 1; } @Override public int getMaxX() { return getMinX() + (tileClusterInfo.getWidth() * tileSize) - 1; } @Override public int getMaxY() { return getMinY() + (tileClusterInfo.getHeight() * tileSize) - 1; } @Override public int getMinTileX() { return (int) tileClusterInfo.getOffsetX(); } @Override public int getMinTileY() { return (int) tileClusterInfo.getOffsetY(); } @Override public int getMinX() { return (int) tileClusterInfo.getOffsetX() * tileSize; } @Override public int getMinY() { return (int) tileClusterInfo.getOffsetY() * tileSize; } @Override public int getNumXTiles() { return tileClusterInfo.getWidth(); } @Override public int getNumYTiles() { return tileClusterInfo.getHeight(); } @Override public Object getProperty(final String name) { if (name.equals(OpImageUtils.NODATA_PROPERTY)) { // TODO: When band support is included, we need to remove the // hard-coding of band 0 below. try { return dp.getMetadataReader().read().getDefaultValue(0); } catch (IOException e) { e.printStackTrace(); } return Double.NaN; } return super.getProperty(name); } @Override public String[] getPropertyNames() { final String[] parentprops = super.getPropertyNames(); final String[] props = { OpImageUtils.NODATA_PROPERTY }; return (String[]) ArrayUtils.addAll(parentprops, props); } public MrsImageDataProvider getDataProvider() { return dp; } @Override public Raster getTile(final int tileX, final int tileY) { // Need to change the sign on the Y tile because JAI tiles are numbered // top to bottom, left to right, but MrGeo tiles are numbered bottom to // top, left to right. final Raster result = super.getTile(tileX, -tileY); // try // { // QuickExport.saveLocalGeotiff("/export/home/dave.johnson/crap", // result, tileX, tileY, pyramid.getMaximumLevel(), pyramid.getMetadata().getTilesize(), // pyramid.getMetadata().getDefaultValue(0)); // } // catch (NoSuchAuthorityCodeException e) // { // e.printStackTrace(); // } // catch (IOException e) // { // e.printStackTrace(); // } // catch (FactoryException e) // { // e.printStackTrace(); // } return result; } @Override public int getTileHeight() { return tileSize; } @Override public int getTileWidth() { return tileSize; } @Override public int getWidth() { return tileClusterInfo.getWidth() * tileSize; } public int getZoomlevel() { return zoomlevel; } public void setInputInfo(final long tx, final long ty, final TileCollection<Raster>.Cluster rasterCluster) { cachedRaster = new CachedRaster(tx, ty, rasterCluster); } public void setInputInfo(final long tileid, final TileCollection<Raster>.Cluster rasterCluster) { final TMSUtils.Tile t = TMSUtils.tileid(tileid, zoomlevel); cachedRaster = new CachedRaster(t.tx, t.ty, rasterCluster); } public void setZoomlevel(final int zoom) throws IOException { zoomlevel = zoom; cachedRaster = null; setImageLayout(calculateLayout(dp, zoomlevel)); } @Override public String toString() { return getClass().getSimpleName() + " pyramid: " + dp.getResourceName(); } @Override @SuppressWarnings("rawtypes") protected Hashtable getProperties() { final ClosingHashTable result = new ClosingHashTable(); final Hashtable s = super.getProperties(); if (s != null) { result.putAll(s); } // TODO: When band support is included, we need to remove the // hard-coding of band 0 below. try { result.put(OpImageUtils.NODATA_PROPERTY, dp.getMetadataReader().read().getDefaultValue(0)); } catch (IOException e) { result.put(OpImageUtils.NODATA_PROPERTY, Double.NaN); } return result; } private Raster blankRaster() { if (emptyRaster == null) { try { final MrsImagePyramidMetadata metadata = dp.getMetadataReader().read(); final double[] defaults = metadata.getDefaultValues(); // build the cachedRaster final WritableRaster constRaster = RasterFactory.createWritableRaster(sampleModel, new java.awt.Point(0, 0)); // flood fill final int w = sampleModel.getWidth(); final int h = sampleModel.getHeight(); final double[] data = new double[w * h]; double def = defaults[0]; Arrays.fill(data, def); for (int b = 0; b < metadata.getBands(); b++) { // this takes care of different default values per band (unlikely, but // possible) if (def != defaults[b]) { def = defaults[b]; Arrays.fill(data, def); } constRaster.setSamples(0, 0, w, h, 0, data); } emptyRaster = constRaster; } catch (IOException e) { e.printStackTrace(); } } return emptyRaster; } }