Package org.geomajas.plugin.rasterizing.layer

Source Code of org.geomajas.plugin.rasterizing.layer.RasterDirectLayer$ImageException

/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.plugin.rasterizing.layer;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;
import javax.media.jai.ImageLayout;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.MosaicDescriptor;
import javax.media.jai.operator.TranslateDescriptor;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.geomajas.geometry.Bbox;
import org.geomajas.layer.tile.RasterTile;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DirectLayer;
import org.geotools.map.MapContent;
import org.geotools.map.MapViewport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.media.jai.codec.ByteArraySeekableStream;
import com.vividsolutions.jts.geom.Envelope;

/**
* Layer responsible for rendering raster layers. Most of the code is copied from the printing plugin.
*
* @author Jan De Moerloose
*/
public class RasterDirectLayer extends DirectLayer {

  protected static final int DOWNLOAD_MAX_ATTEMPTS = 2;

  protected static final int DOWNLOAD_MAX_THREADS = 5;

  protected static final long DOWNLOAD_TIMEOUT = 120000; // millis

  protected static final long DOWNLOAD_TIMEOUT_ONE_TILE = 100; // millis

  private static final String BUNDLE_NAME = "org/geomajas/plugin/rasterizing/rasterizing"; //$NON-NLS-1$

  private List<RasterTile> tiles = new ArrayList<RasterTile>();

  private final ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_NAME);

  private HttpClient httpClient;

  private final Logger log = LoggerFactory.getLogger(RasterDirectLayer.class);

  private int tileWidth;

  private int tileHeight;

  private String style;

  public RasterDirectLayer(List<RasterTile> tiles, int tileWidth, int tileHeight, String style) {
    this.tiles = tiles;
    this.tileWidth = tileWidth;
    this.tileHeight = tileHeight;
    this.style = style;
    ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager();
    manager.setDefaultMaxPerRoute(10);
    httpClient = new DefaultHttpClient(manager);
  }

  @Override
  public void draw(Graphics2D graphics, MapContent map, MapViewport viewport) {
    try {
      if (tiles.size() > 0) {
        Collection<Callable<ImageResult>> callables = new ArrayList<Callable<ImageResult>>(tiles.size());
        // Build the image downloading threads
        for (RasterTile tile : tiles) {
          RasterImageDownloadCallable downloadThread = new RasterImageDownloadCallable(DOWNLOAD_MAX_ATTEMPTS,
              tile);
          callables.add(downloadThread);
        }
        // Loop until all images are downloaded or timeout is reached
        long totalTimeout = DOWNLOAD_TIMEOUT + DOWNLOAD_TIMEOUT_ONE_TILE * tiles.size();
        log.debug("=== total timeout (millis): {}", totalTimeout);
        ExecutorService service = Executors.newFixedThreadPool(DOWNLOAD_MAX_THREADS);
        List<Future<ImageResult>> futures = service.invokeAll(callables, totalTimeout, TimeUnit.MILLISECONDS);
        // determine the pixel bounds of the mosaic
        Bbox pixelBounds = getPixelBounds(tiles);
        // create the images for the mosaic
        List<RenderedImage> images = new ArrayList<RenderedImage>();
        for (Future<ImageResult> future : futures) {
          if (future.isDone()) {
            try {
              ImageResult result;
              result = future.get();
              // create a rendered image
              RenderedImage image = JAI.create("stream", new ByteArraySeekableStream(result.getImage()));
              // convert to common direct colormodel (some images have their own indexed color model)
              RenderedImage colored = toDirectColorModel(image);

              // translate to the correct position in the tile grid
              double xOffset = result.getRasterImage().getCode().getX() * tileWidth - pixelBounds.getX();
              double yOffset;
              // TODO: in some cases, the y-index is up (e.g. WMS), should be down for
              // all layers !!!!
              if (isYIndexUp(tiles)) {
                yOffset = result.getRasterImage().getCode().getY() * tileHeight - pixelBounds.getY();
              } else {
                yOffset = (pixelBounds.getMaxY() - (result.getRasterImage().getCode().getY() + 1)
                    * tileHeight);
              }
              log.debug("adding to(" + xOffset + "," + yOffset + "), url = "
                  + result.getRasterImage().getUrl());
              RenderedImage translated = TranslateDescriptor.create(colored, (float) xOffset,
                  (float) yOffset, new InterpolationNearest(), null);
              images.add(translated);
            } catch (ExecutionException e) {
              addLoadError(graphics, (ImageException) (e.getCause()), viewport);
            } catch (InterruptedException e) {
              log.warn("missing tile in mosaic " + e.getMessage());
            } catch (MalformedURLException e) {
              log.warn("missing tile in mosaic " + e.getMessage());
            } catch (IOException e) {
              log.warn("missing tile in mosaic " + e.getMessage());
            }
          }
        }

        if (images.size() > 0) {
          ImageLayout imageLayout = new ImageLayout(0, 0, (int) pixelBounds.getWidth(),
              (int) pixelBounds.getHeight());
          imageLayout.setTileWidth(tileWidth);
          imageLayout.setTileHeight(tileHeight);

          // create the mosaic image
          ParameterBlock pbMosaic = new ParameterBlock();
          pbMosaic.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY);
          for (RenderedImage renderedImage : images) {
            pbMosaic.addSource(renderedImage);
          }
          RenderedOp mosaic = JAI.create("mosaic", pbMosaic, new RenderingHints(JAI.KEY_IMAGE_LAYOUT,
              imageLayout));
          try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            log.debug("rendering to buffer...");
            ImageIO.write(mosaic, "png", baos);
            log.debug("rendering done, size = " + baos.toByteArray().length);
            RasterTile mosaicTile = new RasterTile();
            mosaicTile.setBounds(getWorldBounds(tiles));
            ImageResult mosaicResult = new ImageResult(mosaicTile);
            mosaicResult.setImage(baos.toByteArray());
            addImage(graphics, mosaicResult, viewport);
          } catch (IOException e) {
            log.warn("could not write mosaic image " + e.getMessage());
          }
        }
      }
    } catch (InterruptedException e) {
      log.warn("rendering {} to {} failed : ", getTitle(), viewport.getBounds());
    }
  }

  protected void addImage(Graphics2D graphics, ImageResult imageResult, MapViewport viewport) throws IOException {
    Rectangle screenArea = viewport.getScreenArea();
    ReferencedEnvelope worldBounds = viewport.getBounds();
    // convert map bounds to application bounds
    double rasterScale = screenArea.getWidth() / worldBounds.getWidth();
    Envelope applicationBounds = new Envelope(worldBounds.getMinX() * rasterScale, worldBounds.getMaxX()
        * rasterScale, -worldBounds.getMinY() * rasterScale, -worldBounds.getMaxY() * rasterScale);
    Bbox imageBounds = imageResult.getRasterImage().getBounds();
    // find transform between image bounds and application bounds
    double tx = (imageBounds.getX() - applicationBounds.getMinX());
    double ty = (imageBounds.getY() - applicationBounds.getMinY());
    BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageResult.getImage()));
    double scaleX = imageBounds.getWidth() / image.getWidth();
    double scaleY = imageBounds.getHeight() / image.getHeight();
    AffineTransform transform = new AffineTransform();
    transform.translate(tx, ty);
    transform.scale(scaleX, scaleY);
    if (log.isDebugEnabled()) {
      log.debug("adding image, width=" + image.getWidth() + ",height=" + image.getHeight() + ",x=" + tx + ",y="
          + ty);
    }
    // opacity
    log.debug("before drawImage");
    // create a copy to apply transform
    Graphics2D g = (Graphics2D) graphics.create();
    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, getOpacity()));
    g.drawImage(image, transform, null);
    log.debug("after drawImage");
  }

  private float getOpacity() {
    String match = style;
    // could be 'opacity:0.5;' or simply '0.5'
    if (style.contains("opacity:")) {
      match = style.substring(style.indexOf("opacity:") + 8);
    }
    int semiColonPosition = match.indexOf(';');
    if (semiColonPosition >= 0) {
      match = match.substring(0, semiColonPosition);
    }
    try {
      return Float.valueOf(match);
    } catch (NumberFormatException nfe) {
      log.warn("Could not parse opacity " + style + "of raster layer " + getTitle());
      return 1f;
    }
  }

  protected void addLoadError(Graphics2D graphics, ImageException imageResult, MapViewport viewport) {
    Bbox imageBounds = imageResult.getRasterImage().getBounds();
    ReferencedEnvelope viewBounds = viewport.getBounds();
    double rasterScale = viewport.getScreenArea().getWidth() / viewport.getBounds().getWidth();
    double width = imageBounds.getWidth();
    double height = imageBounds.getHeight();
    // subtract screen position of lower-left corner
    double x = imageBounds.getX() - rasterScale * viewBounds.getMinX();
    // shift y to lowerleft corner, flip y to user space and subtract
    // screen position of lower-left
    // corner
    double y = -imageBounds.getY() - imageBounds.getHeight() - rasterScale * viewBounds.getMinY();
    if (log.isDebugEnabled()) {
      log.debug("adding image, width=" + width + ",height=" + height + ",x=" + x + ",y=" + y);
    }
    // opacity
    log.debug("before drawImage");
    graphics.drawString(getNlsString("loaderror.line1"), (int) x, (int) y);
  }

  @Override
  public ReferencedEnvelope getBounds() {
    return null;
  }

  private boolean isYIndexUp(List<RasterTile> tiles) {
    RasterTile first = tiles.iterator().next();
    for (RasterTile tile : tiles) {
      if (tile.getCode().getY() > first.getCode().getY()) {
        return tile.getBounds().getY() > first.getBounds().getY();
      } else if (tile.getCode().getY() < first.getCode().getY()) {
        return tile.getBounds().getY() < first.getBounds().getY();
      }
    }
    return false;
  }

  private Bbox getPixelBounds(List<RasterTile> tiles) {
    Bbox bounds = null;
    for (RasterTile tile : tiles) {
      Bbox tileBounds = new Bbox(tile.getCode().getX() * tileWidth, tile.getCode().getY() * tileHeight,
          tileWidth, tileHeight);
      if (bounds == null) {
        bounds = new Bbox(tileBounds.getX(), tileBounds.getY(), tileBounds.getWidth(), tileBounds.getHeight());

      } else {
        double minx = Math.min(tileBounds.getX(), bounds.getX());
        double maxx = Math.max(tileBounds.getMaxX(), bounds.getMaxX());
        double miny = Math.min(tileBounds.getY(), bounds.getY());
        double maxy = Math.max(tileBounds.getMaxY(), bounds.getMaxY());
        bounds = new Bbox(minx, miny, maxx - minx, maxy - miny);
      }
    }
    return bounds;
  }

  private Bbox getWorldBounds(List<RasterTile> tiles) {
    Bbox bounds = null;
    for (RasterTile tile : tiles) {
      Bbox tileBounds = new Bbox(tile.getBounds().getX(), tile.getBounds().getY(), tile.getBounds().getWidth(),
          tile.getBounds().getHeight());
      if (bounds == null) {
        bounds = new Bbox(tileBounds.getX(), tileBounds.getY(), tileBounds.getWidth(), tileBounds.getHeight());

      } else {
        double minx = Math.min(tileBounds.getX(), bounds.getX());
        double maxx = Math.max(tileBounds.getMaxX(), bounds.getMaxX());
        double miny = Math.min(tileBounds.getY(), bounds.getY());
        double maxy = Math.max(tileBounds.getMaxY(), bounds.getMaxY());
        bounds = new Bbox(minx, miny, maxx - minx, maxy - miny);
      }
    }
    return bounds;
  }

  public String getNlsString(String key) {
    try {
      return resourceBundle.getString(key);
    } catch (MissingResourceException e) {
      return '!' + key + '!';
    }
  }

  /**
   * Image result.
   *
   * @author Jan De Moerloose
   */
  private static class ImageResult {

    private byte[] image;

    private RasterTile rasterImage;

    public ImageResult(RasterTile rasterImage) {
      this.rasterImage = rasterImage;
    }

    public byte[] getImage() {
      return image;
    }

    public void setImage(byte[] image) {
      this.image = image;
    }

    public RasterTile getRasterImage() {
      return rasterImage;
    }
  }

  /**
   * Image Exception
   *
   * @author Jan De Moerloose
   */
  private static class ImageException extends Exception {

    private static final long serialVersionUID = 151L;

    private RasterTile rasterImage;

    public ImageException(RasterTile rasterImage) {
      this.rasterImage = rasterImage;
    }

    public RasterTile getRasterImage() {
      return rasterImage;
    }
  }

  /**
   * ???
   */
  private class RasterImageDownloadCallable implements Callable<ImageResult> {

    private ImageResult result;

    private int retries;

    public RasterImageDownloadCallable(int retries, RasterTile rasterImage) {
      this.result = new ImageResult(rasterImage);
      this.retries = retries;
    }

    public ImageResult call() throws Exception {
      log.debug("Fetching image: {}", result.getRasterImage().getUrl());
      int triesLeft = retries;
      while (true) {
        try {
          HttpGet get = new HttpGet(result.getRasterImage().getUrl());
          HttpResponse response = httpClient.execute(get);
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
          response.getEntity().writeTo(outputStream);
          result.setImage(outputStream.toByteArray());
          return result;
        } catch (Exception e) {
          if (log.isDebugEnabled()) {
            log.error("Fetching image: error loading " + result.getRasterImage().getUrl(), e);
          }
          triesLeft--;
          if (triesLeft == 0) {
            throw new ImageException(result.getRasterImage());
          } else {
            log.debug("Fetching image: retrying ", result.getRasterImage().getUrl());
          }
        }
      }
    }

  }

  // converts an image to a RGBA direct color model using a workaround via buffered image
  // directly calling the ColorConvert operation fails for unknown reasons ?!
  public PlanarImage toDirectColorModel(RenderedImage img) {
    BufferedImage dest = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
    BufferedImage source = new BufferedImage(img.getColorModel(), (WritableRaster) img.getData(), img
        .getColorModel().isAlphaPremultiplied(), null);
    ColorConvertOp op = new ColorConvertOp(null);
    op.filter(source, dest);
    return PlanarImage.wrapRenderedImage(dest);
  }

}
TOP

Related Classes of org.geomajas.plugin.rasterizing.layer.RasterDirectLayer$ImageException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.