C Creating and Registering a Custom Image Renderer

This appendix explains how to implement and register a custom image renderer for use with an image theme. (Image themes are described in Section 2.3.3.)

If you want to create a map request specifying an image theme with an image format that is not supported by MapViewer, you must first implement and register a custom image renderer for that format. For example, the ECW format in Example 3-6 in Section 3.1.6 is not supported by MapViewer; therefore, for that example to work, you must first implement and register an image renderer for ECW format images.

The interface oracle.sdovis.CustomImageRenderer is defined in the package sdovis.jar, which is located in the $ORACLE_HOME/lbs/lib directory in an Oracle Fusion Middleware environment. The following is the source code of this interface.

/**
 * An interface for a custom image painter that supports user-defined image
 * formats. An implementation of this interface can be registered with 
 * MapViewer to support a custom image format. 
 */
public interface CustomImageRenderer
{
  /**
   * The method is called by MapViewer to find out the image format
   * supported by this renderer. <br>
   * This format string must match the one specified in a custom image renderer
   * element defined in the configuration file (mapViewerConfig.xml).
   */
  public String  getSupportedFormat() ;

  /**
   * Renders the given images. MapViewer calls this method
   * to tell the implementor the images to render, the current map
   * window in user space, and the MBR (in the same user space) for each
   * image.
   * <br>
   * The implementation should not retain any reference to the parameters
   * permanently. 
   * @param g2  the graphics context to draw the images onto.
   * @param images  an array of image data stored in byte array.
   * @param mbrs an array of double[4] arrays containing one MBR for each
   *        image in the images array.
   * @param dataWindow the data space window covered by the current map.
   * @param deviceView the device size and offset.
   * @param at  the AffineTransform using which you can transform a point
   *            in the user data space to the device coordinate space. You can
   *            ignore this parameter if you opt to do the transformation
   *            yourself based on the dataWindow and deviceView information.
   * @param scaleImage a flag passed from MapViewer to indicate whether
   *             the images should be scaled to fit the current device window.
   *             If it is set to false, render the image as-is without
   *             scaling it.
   */
  public void   renderImages(Graphics2D g2, byte[][] images, double[][] mbrs, 
                             Rectangle2D dataWindow, Rectangle2D deviceView,
                             AffineTransform at, boolean scaleImage) ;
}

After you implement this interface, you must place your implementation class in a directory that is part of the MapViewer CLASSPATH definition, such as the $MAPVIEWER/web/WEB-INF/lib directory. If you use any native libraries to perform the actual rendering, you must ensure that any other required files (such as .dll and .so files) for these libraries are accessible to the Java virtual machine (JVM) that is running MapViewer.

After you place your custom implementation classes and any required libraries in the MapViewer CLASSPATH, you must register your class with MapViewer in its configuration file, mapViewerConfig.xml (described in Section 1.6.2). Examine, and edit as appropriate, the following section of the file, which tells MapViewer which class to load if it encounters a specific image format that it does not already support.

  <!-- ****************************************************************** -->
  <!-- ******************** Custom Image Renderers ********************** -->
  <!-- ****************************************************************** -->
  <!-- Uncomment and add as many custom image renderers as needed here, 
       each in its own  <custom_image_renderer> element. The "image_format"
       attribute specifies the format of images that are to be custom 
       rendered using the class with the full name specified in "impl_class". 
       You are responsible for placing the implementation classes in the
       MapViewer classpath.
  -->
  <!-- 
  <custom_image_renderer image_format="ECW" 
                         impl_class="com.my_corp.image.ECWRenderer"/>
  -->

In this example, for any ECW formatted image data loaded through the <jdbc_image_query> element of an image theme, MapViewer will load the class com.my_corp.image.ECWRenderer to perform the rendering.

Example C-1 is an example implementation of the oracle.sdovis.CustomImageRenderer interface. This example implements a custom renderer for the ECW image format. Note that this example is for illustration purposes only, and the code shown is not necessarily optimal or even correct for all system environments. This implementation uses the ECW Java SDK, which in turn uses a native C library that comes with it. For MapViewer to be able to locate the native dynamic library, you may need to use the command-line option -Djava.library.path when starting the instance that contains MapViewer.

Example C-1 Custom Image Renderer for ECW Image Format

package com.my_corp.image;
import java.io.*;
import java.util.Random;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;

import oracle.sdovis.CustomImageRenderer;
import com.ermapper.ecw.JNCSFile; // from ECW Java SDK

public class ECWRenderer implements CustomImageRenderer
{
  String tempDir = null;
  Random random = null;

  public ECWRenderer()
  {
    tempDir = System.getProperty("java.io.tmpdir");
    random = new Random(System.currentTimeMillis());
  }
  
  public String  getSupportedFormat() 
  {
    return "ECW";
  }
  
  public void   renderImages(Graphics2D g2, byte[][] images, 
                             double[][] mbrs, 
                             Rectangle2D dataWindow, 
                             Rectangle2D deviceView,
                             AffineTransform at)
  {
    // Taking the easy way here; you should try to stitch the images 
    // together here.
    for(int i=0; i<images.length; i++)
    {
      String tempFile = writeECWToFile(images[i]);
      paintECWFile(tempFile, g2, mbrs[i], dataWindow, deviceView,at);
    }
  }

  private String writeECWToFile(byte[] image)
  {
    long l = Math.abs(random.nextLong());
    String file = tempDir + "ecw"+l+".ecw";
    try{
      FileOutputStream fos = new FileOutputStream(file);
      fos.write(image);
      fos.close();
      return file;
    }catch(Exception e)
    {
      System.err.println("cannot write ecw bytes to temp file: "+file);
      return null;
    }
  }
  
  private void  paintECWFile(String fileName, Graphics2D g, 
                             double[] mbr,
                             Rectangle2D dataWindow, 
                             Rectangle2D deviceView,
                             AffineTransform at)
  {
    JNCSFile ecwFile = null;
    boolean bErrorOnOpen = false;
    BufferedImage ecwImage = null;
    String errorMessage = null;    
    
    try {
      double dFileAspect, dWindowAspect;
      double dWorldTLX, dWorldTLY, dWorldBRX, dWorldBRY;
      int bandlist[];
      int width = (int)deviceView.getWidth(), 
          height = (int)deviceView.getHeight();
      int line, pRGBArray[] = null;

      ecwFile = new JNCSFile(fileName, false);
                         
      // Work out the correct aspect for the setView call.
      dFileAspect = (double)ecwFile.width/(double)ecwFile.height;
      dWindowAspect = deviceView.getWidth()/deviceView.getHeight();

      if (dFileAspect > dWindowAspect) {
        height =(int)((double)width/dFileAspect);
      } else {
        width = (int)((double)height*dFileAspect);
      }

      // Create an image of the ecw file.
      ecwImage = new BufferedImage(width, height, 
                                   BufferedImage.TYPE_INT_RGB);
      pRGBArray = new int[width];

      // Set up the view parameters for the ecw file.
      bandlist = new int[ecwFile.numBands];
      for (int i=0; i< ecwFile.numBands; i++) {
        bandlist[i] = i;
      }
      dWorldTLX = ecwFile.originX;
      dWorldTLY = ecwFile.originY;
      dWorldBRX = ecwFile.originX + 
                  (double)(ecwFile.width-1)*ecwFile.cellIncrementX;
      dWorldBRY = ecwFile.originY + 
                  (double)(ecwFile.height-1)*ecwFile.cellIncrementY;

      dWorldTLX = Math.max(dWorldTLX, dataWindow.getMinX());
      dWorldTLY = Math.max(dWorldTLY, dataWindow.getMinY());
      dWorldBRX = Math.min(dWorldBRX, dataWindow.getMaxX());
      dWorldBRY = Math.min(dWorldBRY, dataWindow.getMaxY());
      
      // Set the view.
      ecwFile.setView(ecwFile.numBands, bandlist, dWorldTLX, 
                      dWorldTLY, dWorldBRX, dWorldBRY, width, height);

      // Read the scan lines.
      for (line=0; line < height; line++) {
        ecwFile.readLineRGBA(pRGBArray);
        ecwImage.setRGB(0, line, width, 1, pRGBArray, 0, width);
      }

    } catch(Exception e) {
      e.printStackTrace(System.err);
      bErrorOnOpen = true;
      errorMessage = e.getMessage();
      g.drawString(errorMessage, 0, 50);
    }

    // Draw the image (unscaled) to the graphics context.
    if (!bErrorOnOpen) {
      g.drawImage(ecwImage, 0, 0, null);
    }
    
  }
}