// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/plugin/earthImage/EarthImagePlugIn.java,v $
// $RCSfile: EarthImagePlugIn.java,v $
// $Revision: 1.5 $
// $Date: 2005/12/09 21:09:12 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.plugin.earthImage;
import java.awt.Component;
import java.awt.Image;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import com.bbn.openmap.image.BufferedImageHelper;
import com.bbn.openmap.image.ImageServerConstants;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.plugin.AbstractPlugIn;
import com.bbn.openmap.proj.GeoProj;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
/**
* This class takes an image of the earth, and creates a background image from
* it that matches an OpenMap projection. It currently assumes that the
* degrees/pixel ratios are constant in both directions, the coordinate system
* origins in both directions are at the center of the picture, and that the
* left an right edges of the images are at -180/180 degrees longitude, and that
* the top and bottom of the edges are at 90/-90 degrees latitude. I think the
* code will work for images that do not cover the entire earth in this manner,
* as long as the degree/pixel ratios are the same, but the ImageTranslator
* limits would have to be adjusted to fit the source image.
*
* #For the plugin layer pluginlayer.class=com.bbn.openmap.plugin.PlugInLayer
* pluginlayer.prettyName=Whatever
* pluginlayer.plugin=com.bbn.openmap.plugin.earthImage.EarthImagePlugIn
* pluginlayer.plugin.image=path to file, URL or resource.
*/
public class EarthImagePlugIn
extends AbstractPlugIn
implements ImageServerConstants {
protected BufferedImage bi = null;
protected ImageTranslator it = null;
public final static String ImageProperty = "image";
protected String imageString = null;
public EarthImagePlugIn() {
}
public EarthImagePlugIn(Component comp) {
super(comp);
}
/**
* @param p projection of the screen, holding scale, center coords, height,
* width.
* @return an OMGraphicList containing an OMRaster with the image to be
* displayed.
*/
public OMGraphicList getRectangle(Projection p) {
OMGraphicList list = new OMGraphicList();
// The first time through with a good bi, the it will be
// created later. This routine will only be executed if the
// image icon is no good.
if (bi == null && it == null) {
return list;
}
OMRaster ras = null;
if (it == null) {
it = new ImageTranslator(bi);
bi = null; // don't hold onto it.
}
ras = it.getImage(p);
if (ras != null) {
list.add(ras);
}
list.generate(p);
return list;
}
/**
* Method to set the properties in the PropertyConsumer. The prefix is a
* string that should be prepended to each property key (in addition to a
* separating '.') in order for the PropertyConsumer to uniquely identify
* properties meant for it, in the midst of of Properties meant for several
* objects.
*
* @param prefix a String used by the PropertyConsumer to prepend to each
* property value it wants to look up -
* setList.getProperty(prefix.propertyKey). If the prefix had already
* been set, then the prefix passed in should replace that previous
* value.
* @param setList a Properties object that the PropertyConsumer can use to
* retrieve expected properties it can use for configuration.
*/
public void setProperties(String prefix, Properties setList) {
super.setProperties(prefix, setList);
String realPrefix = PropUtils.getScopedPropertyPrefix(prefix);
imageString = setList.getProperty(realPrefix + ImageProperty);
if (imageString == null || imageString.length() == 0) {
Debug.error("EarthImagePlugIn needs an image.");
Debug.output(setList.toString());
return;
} else if (Debug.debugging("earthimage")) {
Debug.output("EarthImagePlugIn: fetching " + realPrefix + ImageProperty + " : " + imageString);
}
try {
URL url = PropUtils.getResourceOrFileOrURL(this, imageString);
bi = BufferedImageHelper.getBufferedImage(url, 0, 0, -1, -1);
if (Debug.debugging("earthimage") && bi != null) {
Debug.output("EarthImagePlugIn: buffered image OK");
}
} catch (MalformedURLException murle) {
Debug.error("EarthImagePlugIn: image path is not good: " + imageString);
} catch (InterruptedException ie) {
Debug.error("EarthImagePlugIn: problem reading image from path: " + imageString);
}
}
public Properties getProperties(Properties props) {
props = super.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(this);
props.put(prefix + ImageProperty, (imageString == null ? "" : imageString));
return props;
}
public Properties getPropertyInfo(Properties props) {
props = super.getPropertyInfo(props);
props.put(ImageProperty, "Path to image file (URL, resource or file)");
props.put(ImageProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.FUPropertyEditor");
props.put(initPropertiesProperty, ImageProperty);
return props;
}
/**
* The ImageTranslator is the object that takes the BufferedImage and creates
* the OMRaster from it based on a Projection object.
*/
public class ImageTranslator {
protected int[] pixels = null;
/** Image Icon width, */
public int iwidth;
/** Image Icon height, */
public int iheight;
/**
* Horizontal degrees/pixel in the source BufferedImage. Assumed to be
* constant across the image.
*/
public float hor_dpp;
/**
* Vertical degrees/pixel in the source BufferedImage. Assumed to be
* constant across the image.
*/
public float ver_dpp;
/**
* The vertical origin pixel location in the source image for the
* coordinate system origin (0 degrees latitude).
*/
public int verOrigin;
/**
* The horizontal origin pixel location in the source image for the
* coordinate system origin (0 degrees longitude).
*/
public int horOrigin;
/**
* Create an image translator for an image assumed to be world wide
* coverage, with the top at 90 degrees, the bottom at -90, the left side
* at -180 and the right side at 180. Assumes the origin point is in the
* middle of the image.
*/
public ImageTranslator(BufferedImage bi) {
if (bi != null) {
iwidth = bi.getWidth();
iheight = bi.getHeight();
verOrigin = iheight / 2;
horOrigin = iwidth / 2;
hor_dpp = 360f / (float) iwidth;
ver_dpp = 180f / (float) iheight;
if (Debug.debugging("earthimage")) {
Debug.output("ImageTranslator: getting image pixels w:" + iwidth + ", h:" + iheight + "\n hor dpp:" + hor_dpp
+ ", ver dpp:" + ver_dpp);
}
pixels = getPixels(bi, 0, 0, iwidth, iheight);
// See if this saves on memory. Seems to.
bi = null;
}
}
/**
* The pixels used in the OMRaster.
*/
int[] tmpPixels = new int[0];
/**
* Given a projection, create an OMRaster that reflects the image warped
* to that projection.
*/
public OMRaster getImage(Projection p) {
if (pixels != null && p != null) {
int projHeight = p.getHeight();
int projWidth = p.getWidth();
// See if we can reuse the pixel array we have.
if (tmpPixels.length != projWidth * projHeight) {
tmpPixels = new int[projWidth * projHeight];
}
// /////////////////////////////////
// For Testing...
// LatLonPoint ul = p.getUpperLeft();
// LatLonPoint lr = p.getLowerRight();
// int ulhorIndex = horOrigin +
// (int)(ul.getLongitude()/hor_dpp);
// int ulverIndex = verOrigin -
// (int)(ul.getLatitude()/ver_dpp);
// int lrhorIndex = horOrigin +
// (int)(lr.getLongitude()/hor_dpp);
// int lrverIndex = verOrigin -
// (int)(lr.getLatitude()/ver_dpp);
// Debug.output("The image file will be referenced
// from:\n " +
// ulhorIndex + ", " + ulverIndex + "\n " +
// lrhorIndex + ", " + lrverIndex);
// /////////////////////////////////
int clear = 0x00000000;
Point ctp = new Point();
LatLonPoint llp = new LatLonPoint.Double();
LatLonPoint center = p.getCenter(new LatLonPoint.Double());
for (int i = 0; i < projWidth; i++) {
for (int j = 0; j < projHeight; j++) {
p.inverse(i, j, llp);
// index into the OMRaster pixel array
int tmpIndex = i + (j * projWidth);
// If the llp calculated isn't on the map,
// don't bother drawing it. Could be a space
// point in Orthographic projection, for
// instance.
if (llp.equals(center) || !((GeoProj)p).isPlotable(llp.getLatitude(), llp.getLongitude())) {
p.forward(llp, ctp);
if (ctp.x != i || ctp.y != j) {
tmpPixels[tmpIndex] = clear;
continue;
}
}
// Find the corresponding pixel location in
// the source image.
int horIndex = horOrigin + (int) (llp.getLongitude() / hor_dpp);
int verIndex = verOrigin - (int) (llp.getLatitude() / ver_dpp);
if (horIndex < 0 || horIndex >= iwidth || verIndex <= 0 || verIndex >= iheight) {
/**
* If the verIndex is 0, that means it's at 90N, which is
* kind of an abstract thing. So lets make it clear.
* Literally.
*/
// pixel not on the source image. This
// happens if the image doesn't cover the
// entire earth.
continue;
}
int imageIndex = horIndex + (verIndex * iwidth);
if (imageIndex >= 0 && imageIndex < pixels.length) {
tmpPixels[tmpIndex] = pixels[imageIndex];
// } else {
// Debug.message("earthimage",
// "ImageTranslator: outside pixel
// range");
}
}
}
Debug.message("earthimage", "ImageTranslator: finished creating image");
return new OMRaster(0, 0, projWidth, projHeight, tmpPixels);
} else {
Debug.message("earthimage", "ImageTranslator: problem creating image");
}
// If you get here, something's not right.
return null;
}
/**
* Get the pixels from the BufferedImage. If anything goes wrong, returns
* a int[0].
*/
protected int[] getPixels(Image img, int x, int y, int w, int h) {
int[] pixels = new int[w * h];
PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w);
try {
pg.grabPixels();
} catch (InterruptedException e) {
Debug.error("ImageTranslator: interrupted waiting for pixels!");
return new int[0];
}
if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
System.err.println("ImageTranslator: image fetch aborted or errored");
return new int[0];
}
return pixels;
}
}
}