/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package de.cismet.cismap.commons.rasterservice; import com.vividsolutions.jts.geom.Envelope; import lombok.Getter; import lombok.Setter; import org.apache.log4j.Logger; import org.deegree.io.geotiff.GeoTiffException; import org.deegree.io.geotiff.GeoTiffReader; import org.openide.util.Exceptions; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import javax.imageio.ImageIO; import de.cismet.cismap.commons.rasterservice.georeferencing.RasterGeoReferencingBackend; import de.cismet.cismap.commons.retrieval.RetrievalEvent; import de.cismet.cismap.commons.retrieval.RetrievalListener; /** * Loads map sections from image files. * * @author therter * @version $Revision$, $Date$ */ public class ImageFileRetrieval extends Thread { //~ Static fields/initializers --------------------------------------------- private static final Logger LOG = Logger.getLogger(ImageFileRetrieval.class); //~ Instance fields -------------------------------------------------------- /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ @Getter @Setter private int width; @Getter @Setter private int height; @Getter @Setter private double x1; @Getter @Setter private double x2; @Getter @Setter private double y1; @Getter @Setter private double y2; @Getter @Setter private File imageFile; private RetrievalListener listener = null; private volatile boolean youngerCall = false; private ImageFileMetaData metaData; private final ImageFileUtils.Mode mode; //~ Constructors ----------------------------------------------------------- /** * Creates a new ImageFileRetrieval object. * * @param imageFile DOCUMENT ME! * @param listener DOCUMENT ME! * @param mode DOCUMENT ME! */ public ImageFileRetrieval(final File imageFile, final RetrievalListener listener, final ImageFileUtils.Mode mode) { super("ImageFileRetrieval"); this.imageFile = imageFile; this.listener = listener; this.mode = mode; } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! */ public void youngerCall() { youngerCall = true; } @Override public void run() { try { listener.retrievalStarted(new RetrievalEvent()); if (metaData == null) { metaData = getImageMetaData(); } if (metaData == null) { final RetrievalEvent re = new RetrievalEvent(); re.setIsComplete(false); re.setRetrievedObject("Cannot read meta data"); re.setErrorType(RetrievalEvent.CLIENTERROR); listener.retrievalError(re); return; } if (youngerCall && isInterrupted()) { LOG.warn("Image retrieval aborted"); return; } final BufferedImage mapImage = createImage(metaData); final RetrievalEvent re = new RetrievalEvent(); re.setIsComplete(true); re.setRetrievedObject(mapImage); listener.retrievalComplete(re); } catch (InterruptedException e) { LOG.warn("Image retrieval aborted"); } catch (OutOfMemoryError ex) { LOG.error("Image retrieval aborted. Out o memory error.", ex); final RetrievalEvent re = new RetrievalEvent(); re.setIsComplete(false); re.setRetrievedObject(ex.getMessage()); re.setErrorType(RetrievalEvent.CLIENTERROR); listener.retrievalError(re); } catch (Exception ex) { LOG.error("Error during image processing.", ex); final RetrievalEvent re = new RetrievalEvent(); re.setIsComplete(false); if ((ex.getMessage() == null) || ex.getMessage().equals("null")) { // NOI18N try { final String cause = ex.getCause().getMessage(); re.setRetrievedObject(cause); } catch (Exception ee) { } } else { re.setRetrievedObject(ex.getMessage()); re.setErrorType(RetrievalEvent.CLIENTERROR); } listener.retrievalError(re); } } /** * DOCUMENT ME! * * @param mapWorldBounds DOCUMENT ME! * @param imageMapWorldOffset DOCUMENT ME! * @param imageBounds DOCUMENT ME! * @param worldFileTransform DOCUMENT ME! * * @return DOCUMENT ME! */ private Rectangle getClippingRect(final Rectangle.Double mapWorldBounds, final Point.Double imageMapWorldOffset, final Rectangle imageBounds, final AffineTransform worldFileTransform) { int mapPartStartX = (int)Math.floor(-imageMapWorldOffset.getX() / worldFileTransform.getScaleX()); int mapPartStartY = (int)Math.floor(-imageMapWorldOffset.getY() / -worldFileTransform.getScaleY()); int mapPartWidth = (int)Math.ceil(mapWorldBounds.getWidth() / worldFileTransform.getScaleX()) + 1; int mapPartHeight = (int)Math.ceil(mapWorldBounds.getHeight() / -worldFileTransform.getScaleY()) + 1; if (mapPartStartX < 0) { mapPartStartX = 0; mapPartWidth += mapPartStartX; } if (mapPartStartY < 0) { mapPartStartY = 0; mapPartHeight += mapPartStartY; } if ((mapPartStartX + mapPartWidth) > imageBounds.getWidth()) { mapPartWidth = (int)imageBounds.getWidth() - mapPartStartX; } if ((mapPartStartY + mapPartHeight) > imageBounds.getHeight()) { mapPartHeight = (int)imageBounds.getHeight() - mapPartStartY; } return new Rectangle(mapPartStartX, mapPartStartY, mapPartWidth, mapPartHeight); } /** * Creates an image of the given map section. * * @param metaData origImageBounds the bounds of the original image * * @return an image of the given map section * * @throws IOException DOCUMENT ME! * @throws InterruptedException DOCUMENT ME! * @throws NoninvertibleTransformException DOCUMENT ME! */ private BufferedImage createImage(final ImageFileMetaData metaData) throws IOException, InterruptedException, NoninvertibleTransformException { final double[] matrix = metaData.getTransform().getMatrixEntries(); final AffineTransform worldFileTransform = new AffineTransform( matrix[0], matrix[3], matrix[1], matrix[4], matrix[2], matrix[5]); // bounds in pixel dimensions final Rectangle mapBounds = new Rectangle(width, height); final Rectangle imageBounds = metaData.getImageBounds(); // bounds in world dimensions final Rectangle.Double mapWorldBounds = new Rectangle.Double(x1, y1, x2 - x1, y2 - y1); final Envelope imageWorldBounds = metaData.getImageEnvelope(); // the offset of the image in relation to the map in world dimensions final Point.Double imageMapWorldOffset = new Point.Double( imageWorldBounds.getMinX() - mapWorldBounds.getMinX(), mapWorldBounds.getMaxY() - imageWorldBounds.getMaxY()); // meter per pixel ration (the better appropriate "Dimension" class only supports Integers // so we are using Rectangle.Double instead) final Rectangle.Double meterPerPixel = new Rectangle.Double( 0, 0, mapWorldBounds.getWidth() / mapBounds.getWidth(), mapWorldBounds.getHeight() / mapBounds.getHeight()); // LOAD RAW IMAGE BufferedImage rawImage = ImageIO.read(imageFile); handleInterruption(); // PRECLIPPING final Point.Double clippingWorldOffset; BufferedImage clippedImage; if ((worldFileTransform.getShearX() == 0) && (worldFileTransform.getShearY() == 0)) { // calculating clipping rectangle final Rectangle clippingRect = getClippingRect( mapWorldBounds, imageMapWorldOffset, imageBounds, worldFileTransform); // the clipped image does not start at the same positon. an offset is needed to compensate for this clippingWorldOffset = new Point.Double( clippingRect.getX() * worldFileTransform.getScaleX(), -clippingRect.getY() * worldFileTransform.getScaleY()); // clipping the image clippedImage = rawImage.getSubimage((int)clippingRect.getX(), (int)clippingRect.getY(), (int)clippingRect.getWidth(), (int)clippingRect.getHeight()); } else { // no preclipping for sheared/rotated images for simplicity reasons clippingWorldOffset = new Point.Double(0, 0); clippedImage = rawImage; } // cleaning memory rawImage = null; System.gc(); handleInterruption(); // TRANSFORMATION // Just calculating the transformed shape first. // (Not very elegant to do this, but it works) // scaling and shearing = worldfile scaling/shearing divided by meterPerPixel final AffineTransform transformation = new AffineTransform( worldFileTransform.getScaleX() / meterPerPixel.getWidth(), -worldFileTransform.getShearY() / meterPerPixel.getHeight(), worldFileTransform.getShearX() / meterPerPixel.getWidth(), -worldFileTransform.getScaleY() / meterPerPixel.getHeight(), 0, 0); // The x/y coordinate of the transformed (but not yet translated) is either 0 // or negative. This is important, because negative values hav to be added to the offset final Rectangle shapeBounds = transformation.createTransformedShape(imageBounds).getBounds(); // We apply now the full transormation to the image // position = combined world offsets divided by meterPerPixel final AffineTransform transformation2 = new AffineTransform( transformation.getScaleX(), transformation.getShearY(), transformation.getShearX(), transformation.getScaleY(), ((imageMapWorldOffset.getX() + clippingWorldOffset.getX()) / meterPerPixel.getWidth()) - shapeBounds.getX(), ((imageMapWorldOffset.getY() + clippingWorldOffset.getY()) / meterPerPixel.getHeight()) - shapeBounds.getY()); final BufferedImage transformedImage = transform(transformation2, clippedImage); // cleaning memory clippedImage = null; System.gc(); return transformedImage; } /** * DOCUMENT ME! * * @throws InterruptedException DOCUMENT ME! */ private void handleInterruption() throws InterruptedException { if (youngerCall && isInterrupted()) { throw new InterruptedException(); } } /** * Rescale the given image and add transparent borders. * * @param transform width transform DOCUMENT ME! * @param image DOCUMENT ME! * * @return DOCUMENT ME! */ private BufferedImage transform(final AffineTransform transform, final BufferedImage image) { final BufferedImage transformedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); if (image != null) { final Graphics2D g = transformedImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(image, transform, null); } return transformedImage; } /** * Determines the meta information about the image file. * * @return the meta information about the image file * * @throws Exception DOCUMENT ME! */ private ImageFileMetaData getImageMetaData() throws Exception { if (mode != null) { switch (mode) { case WORLDFILE: { return ImageFileUtils.getWorldFileMetaData(getImageFile(), getWorldFile()); } case TIFF: { getTiffMetaData(); } case GEO_REFERENCED: { return getGeoReferencedMetaData(); } } } return null; } /** * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws Exception DOCUMENT ME! */ private ImageFileMetaData getGeoReferencedMetaData() throws Exception { return RasterGeoReferencingBackend.getInstance().getHandler(imageFile).getMetaData(); } /** * Determines the meta information about the image file. It will be assumed, that the image file is a tiff. * * @return the meta information about the image file * * @throws Exception DOCUMENT ME! */ private ImageFileMetaData getTiffMetaData() throws Exception { return ImageFileUtils.getTiffMetaData(imageFile); } /** * Returns the world file or null, if it does not exist. * * @return the world file of the <code>imageFile</code> */ private File getWorldFile() { return ImageFileUtils.getWorldFile(imageFile); } /** * The envelope of the image. * * @return the envelope of the image */ public Envelope getEnvelope() { try { if (metaData == null) { final ImageFileMetaData metaData = getImageMetaData(); if (metaData != null) { return metaData.getImageEnvelope(); } } } catch (Exception e) { LOG.error("Cannot determine the envelope of the image.", e); } return null; } /** * DOCUMENT ME! * * @param args DOCUMENT ME! */ public static void main(final String[] args) { try { final GeoTiffReader r = new GeoTiffReader(new File("/home/therter/share/daten/uek250.tif")); System.out.println(r.getHumanReadableCoordinateSystem()); r.getTIFFImage().getBounds(); r.getTIFFImage().getHeight(); r.getTIFFImage().getMinTileX(); r.getTIFFImage().getNumXTiles(); } catch (FileNotFoundException ex) { Exceptions.printStackTrace(ex); } catch (IOException ex) { Exceptions.printStackTrace(ex); } catch (GeoTiffException ex) { Exceptions.printStackTrace(ex); } } @Override public int hashCode() { int hash = 5; hash = (59 * hash) + this.width; hash = (59 * hash) + this.height; hash = (59 * hash) + (int)(Double.doubleToLongBits(this.x1) ^ (Double.doubleToLongBits(this.x1) >>> 32)); hash = (59 * hash) + (int)(Double.doubleToLongBits(this.x2) ^ (Double.doubleToLongBits(this.x2) >>> 32)); hash = (59 * hash) + (int)(Double.doubleToLongBits(this.y1) ^ (Double.doubleToLongBits(this.y1) >>> 32)); hash = (59 * hash) + (int)(Double.doubleToLongBits(this.y2) ^ (Double.doubleToLongBits(this.y2) >>> 32)); hash = (59 * hash) + ((this.imageFile != null) ? this.imageFile.hashCode() : 0); return hash; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ImageFileRetrieval other = (ImageFileRetrieval)obj; if (this.width != other.width) { return false; } if (this.height != other.height) { return false; } if (Double.doubleToLongBits(this.x1) != Double.doubleToLongBits(other.x1)) { return false; } if (Double.doubleToLongBits(this.x2) != Double.doubleToLongBits(other.x2)) { return false; } if (Double.doubleToLongBits(this.y1) != Double.doubleToLongBits(other.y1)) { return false; } if (Double.doubleToLongBits(this.y2) != Double.doubleToLongBits(other.y2)) { return false; } if ((this.imageFile != other.imageFile) && ((this.imageFile == null) || !this.imageFile.equals(other.imageFile))) { return false; } return true; } /** * DOCUMENT ME! * * @param other the object, the meta data should be copied from */ public void copyMetaData(final ImageFileRetrieval other) { this.metaData = other.metaData; } }