/*
* OldImageImporter.java
*
* Created on April 1, 2005, 12:14 PM
*/
package ika.geoimport;
import ika.geo.*;
import ika.gui.*;
import ika.gui.MapComponent;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import ika.utils.*;
import java.io.*;
import java.net.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;
import java.util.*;
/**
* Importer for images. Converts images to GeoImages. Runs in its own thread.
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class OldImageImporter extends Thread {
private String filePath;
private ika.gui.ImageReaderProgressDialog progressDialog;
private GeoSet destinationGeoSet;
private GeoImage geoImage;
private MapComponent mapComponent;
private boolean askUserToGeoreferenceImage = true;
private String getNotReadableErrorString() {
final String notReadableErrorString = "The file at " + filePath +
" is not a supported format, and cannot be read.";
return notReadableErrorString;
}
public void run() {
this.geoImage = null;
String warningMessage = null;
String warningTitle = "Error - Image Import";
boolean addImageToGeoSet = true;
try {
// initialize the progress dialog
if (progressDialog != null)
progressDialog.showProgressDialog();
// read the image into a BufferedImage
BufferedImage bufferedImage = this.readImage();
// the following cannot be cancelled, so disable the cancel button.
if (this.progressDialog != null)
progressDialog.disableCancel();
// optimize the image for fast display
bufferedImage = ImageUtils.optimizeForGraphicsHardware(bufferedImage);
// create the GeoImage
this.geoImage = new GeoImage(bufferedImage, new java.net.URL (filePath));
this.geoImage.setSelectable(false);
// search and read an associated world file containing georeferencing
// information. If there is a World file we are done.
File worldFile = OldImageImporter.searchWorldFile(filePath);
if (worldFile != null) {
OldImageImporter.readWorldFile(geoImage, worldFile);
return;
}
// hide the progress dialog
if (this.progressDialog != null)
this.progressDialog.finish();
// we could not find any world file associated with the image.
// ask the user to geo-reference the image.
if (this.askUserToGeoreferenceImage) {
this.askUserToReferenceImage();
addImageToGeoSet = false; // add image later.
return; // we are done.
}
// First, use information of the image file header for the cell size.
if (!this.setCellSizeFromDPI()) {
// could not extract information about the resolution form the image
// file. Ask the user for the resolution of the image.
OldImageImporter.askUserForPixelSize(this.geoImage);
}
// Then, put the lower left corner of the image on the origin of the
// coordinate system.
OldImageImporter.placeImageOnOrigin(this.geoImage);
} // do some exception handling here
catch (java.lang.OutOfMemoryError e) {
warningMessage = "There is not enough memory available.";
} catch (java.io.IOException e) {
warningMessage = "Could not open the file." + e.getMessage();
} catch (Exception e) {
warningMessage = "An error occurred. The image could not be read.";
warningMessage += "\n" + e.getMessage();
} finally {
if (this.progressDialog != null)
this.progressDialog.finish();
if (warningMessage != null) {
showErrorMessage(warningMessage, warningTitle);
}
// finally add the GeoImage to the GeoSet
if (addImageToGeoSet && this.destinationGeoSet != null && geoImage != null) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
destinationGeoSet.add(geoImage);
if (mapComponent != null)
mapComponent.showAll();
}
});
}
}
}
/**
* Imports an image and returns a GeoImage. Uses the ImageIO API.
* If the JAI (JavaAdvancedImaging) package is installed, more
* file formats are supported than with importGeoImage (also TIFF, etc.).
* If this method receives a ImageReaderProgressDialog it runs in an separate
* thread. If progressDialog is null, it does not run in an separate thread.
* @param filePath The path to the file that will be imported.
* @param destinationGeoSet The GeoSet that will receive the read GeoImage.
* @param ImageReaderProgressDialog An optional progress dialog.
*/
public void importGeoImageWithImageIOAsync(String filePath,
GeoSet destinationGeoSet,
ika.gui.ImageReaderProgressDialog progressDialog){
if (filePath == null || destinationGeoSet == null || progressDialog == null)
throw new IllegalArgumentException();
// make sure there is an image importer for the file format.
String fileExtension = ika.utils.FileUtils.getFileExtension(filePath);
if (!ika.utils.ImageUtils.canReadImageFile(fileExtension))
throw new IllegalArgumentException("File format not supported.");
this.filePath = filePath;
this.destinationGeoSet = destinationGeoSet;
this.progressDialog = progressDialog;
if (progressDialog == null)
this.run();
else
this.start();
}
public GeoImage importGeoImageWithImageIOSync(String filePath){
if (filePath == null)
throw new IllegalArgumentException();
this.filePath = filePath;
this.destinationGeoSet = null;
this.progressDialog = null;
this.run();
return this.geoImage;
}
public static Dimension getImageDimension(File file) throws IOException {
ImageReader reader = getImageReaderByFile(file);
if (reader == null)
return null;
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(file);
reader.setInput(iis, false);
final int width = reader.getWidth(0);
final int height = reader.getHeight(0);
return new Dimension(width, height);
} finally {
reader.dispose();
try {
// reader.dispose() does not close the ImageInputStream!
// so do this here.
iis.close();
} catch (Exception exc) {}
}
}
/**
* Returns the dpi of an image. If it cannot be determined, returns null.
* Most graphics formats (including jpg and png - but not gif) have a
* setting for the image dimension in the x and y direction. This is the size
* of a pixel in millimeter. JPEGs don't always have a the dpi stored (e.g
* when saved with Photoshop using the "Save for Web" command).
*
* From the DTD:
* http://java.sun.com/j2se/1.4.2/docs/api/javax/imageio/
* metadata/doc-files/standard_metadata.html
*
* <!ELEMENT "VerticalPixelSize" EMPTY>
* <!-- The height of a pixel, in millimeters, as it should be
* rendered on media -->
* <!ATTLIST "VerticalPixelSize" "value" #CDATA #REQUIRED>
* <!-- Data type: Float -->
*
* @param imageReader An ImageReader with an associated ImageInputStream.
* @return The dimension of a pixel in millimeters. Returns null if dimension
* cannot be identified.
*/
public Point2D.Float getPixelSizeMillimeter() {
float hps = Float.NaN;
float vps = Float.NaN;
try {
org.w3c.dom.Node n = this.getImageMetaData();
n = n.getFirstChild();
while (n != null) {
if (n.getNodeName().equals("Dimension")) {
org.w3c.dom.Node n2 = n.getFirstChild();
while (n2 != null) {
if (n2.getNodeName().equals("HorizontalPixelSize")) {
org.w3c.dom.NamedNodeMap nnm = n2.getAttributes();
org.w3c.dom.Node n3 = nnm.item(0);
hps = Float.parseFloat(n3.getNodeValue());
}
if (n2.getNodeName().equals("VerticalPixelSize")) {
org.w3c.dom.NamedNodeMap nnm = n2.getAttributes();
org.w3c.dom.Node n3 = nnm.item(0);
vps = Float.parseFloat(n3.getNodeValue());
}
n2 = n2.getNextSibling();
}
}
n = n.getNextSibling();
}
} catch (Exception e) {
return null;
}
if (Float.isNaN(hps) || Float.isNaN(vps))
return null;
return new Point2D.Float(hps, vps);
}
private boolean setCellSizeFromDPI() {
Point2D pixelSizeMM = this.getPixelSizeMillimeter();
if (pixelSizeMM != null && pixelSizeMM.getX() == pixelSizeMM.getY()) {
this.geoImage.setCellSize(pixelSizeMM.getX()/1000.);
return true;
}
return false;
}
private BufferedImage readImage() throws IOException {
ImageReader reader = null;
ImageInputStream iis = null;
try {
File file = new File(this.filePath);
reader = getImageReaderByFile(file);
if (reader == null)
throw new java.io.IOException(this.getNotReadableErrorString());
iis = ImageIO.createImageInputStream(file);
reader.setInput(iis);
reader.addIIOReadProgressListener(progressDialog);
final int imageIndex = reader.getMinIndex();
BufferedImage bufferedImage = reader.read(imageIndex);
// if the user cancels the import, the image is not null,
// but contains black areas.
if (this.progressDialog != null && progressDialog.isCanceled())
return null;
return bufferedImage;
} finally {
if (reader != null) {
reader.dispose();
try {
// reader.dispose() does not close the ImageInputStream!
// so do this here.
if (iis != null)
iis.close();
} catch (Exception exc) {
// do not throw an exception if an error occurs on closing.
}
}
}
}
private org.w3c.dom.Node getImageMetaData() throws IOException {
ImageReader reader = null;
ImageInputStream iis = null;
try {
File file = new File(this.filePath);
reader = getImageReaderByFile(file);
if (reader == null)
throw new java.io.IOException(this.getNotReadableErrorString());
iis = ImageIO.createImageInputStream(file);
reader.setInput(iis);
final int imgID = reader.getMinIndex();
javax.imageio.metadata.IIOMetadata meta = reader.getImageMetadata(imgID);
org.w3c.dom.Node n = meta.getAsTree("javax_imageio_1.0");
return n;
} finally {
if (reader != null) {
reader.dispose();
try {
// reader.dispose() does not close the ImageInputStream!
// so do this here.
if (iis != null)
iis.close();
} catch (Exception exc) {
// do not throw an exception if an error occurs on closing.
}
}
}
}
private static ImageReader getImageReaderByFile(File file) {
String ext = FileUtils.getFileExtension(file.getName());
Iterator iterator = ImageIO.getImageReadersBySuffix(ext);
if (iterator.hasNext())
return (ImageReader) iterator.next();
else
return null;
}
/** Asks the user for the resolution of an image and updates the passed
* GeoImage accordingly.
*/
private static void askUserForPixelSize(final GeoImage geoImage) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
String msg = "The resolution of the image could not be determined.\n" +
"This information is very important for all further analysis.\n" +
"Please convert the image to another format -\n" +
"or re-save the image with another software -\n" +
"or enter the number of pixels per inch (DPI) below:";
String dpiString = javax.swing.JOptionPane.showInputDialog(msg);
if (dpiString == null)
return; // user cancelled
final double dpi = Double.parseDouble(dpiString);
geoImage.setCellSize(25.4/dpi/1000.);
}
});
}
private void askUserToReferenceImage() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
String msg = "There is no World file for georeferencing " +
"the image.\n" +
"Please first georeference the image with a GIS, " +
"generate a World file, and then reload the image with MapAnalyst.";
String title = "Image Not Georeferenced";
JOptionPane.showMessageDialog(null, msg, title, JOptionPane.ERROR_MESSAGE);
/*
Frame parentFrame = (Frame)SwingUtilities.getRoot(mapComponent);
final boolean modal = true;
ika.gui.ImageReferencerDialog imageReferencer =
new ika.gui.ImageReferencerDialog(parentFrame, modal);
imageReferencer.referenceImage(geoImage, destinationGeoSet);
*/
}
});
}
/** Places a GeoImage so that the lower left corner is on the origin of
* the coordinate system.
*/
private static void placeImageOnOrigin(GeoImage geoImage) {
if (geoImage != null) {
geoImage.setWest(0);
final int imageHeight = geoImage.getBufferedImage().getHeight();
geoImage.setNorth(imageHeight * geoImage.getCellSize());
}
}
private static void showErrorMessage(final String message, final String title) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
javax.swing.JOptionPane.showMessageDialog(null, message,
title, javax.swing.JOptionPane.ERROR_MESSAGE);
}
});
}
/**
* Returns whether a WorlFile for an image file exists.
* @param imageFilePath The path to the raster image for which a World file has to be searched.
* @return True if a World file exists, false otherwise.
*/
public static boolean hasWorldFile(String imageFilePath) {
File file = null;
try {
file = OldImageImporter.searchWorldFile(imageFilePath);
} catch (Exception e) {}
return (file != null);
}
/**
* Searches a World file containing georeferencing information for a raster image.
* @param imageFilePath The path to the raster image for which a World file has to be searched.
* @return Returns a File object if found, null otherwise.
*/
private static File searchWorldFile(String imageFilePath) {
File worldFile = null;
URL url;
String origExtension = ika.utils.FileUtils.getFileExtension(imageFilePath);
String extension = new String();
// construct name of the new world file
// if the extension is shorter than 3 characters, just add a "w"
if (origExtension.length() < 3) // wenn Extension k�rzer als 3 Buchstaben: einfach "w" anh�ngen
{
extension = origExtension + "w";
worldFile = new File(FileUtils.replaceExtension(imageFilePath, extension));
if (worldFile.exists())
return worldFile;
}
// try with a "W"
if (origExtension.length() < 3) {
extension = origExtension;
extension += 'W';
worldFile = new File(FileUtils.replaceExtension(imageFilePath, extension));
if (worldFile.exists())
return worldFile;
}
// take first and last character of the extension and append "w"
extension = "";
extension += origExtension.charAt(0);
extension += origExtension.charAt(origExtension.length()-1);
extension += 'w';
worldFile = new File(FileUtils.replaceExtension(imageFilePath, extension));
if (worldFile.exists())
return worldFile;
// try with "W"
extension = "";
extension += origExtension.charAt(0);
extension += origExtension.charAt(origExtension.length()-1);
extension += 'W';
worldFile = new File(FileUtils.replaceExtension(imageFilePath, extension));
if (worldFile.exists())
return worldFile;
// directly append "w" to the image file name
worldFile = new File(imageFilePath + "w");
if (worldFile.exists())
return worldFile;
// try with "W"
worldFile = new File(imageFilePath + "W");
if (worldFile.exists())
return worldFile;
// use ".w" as file extension
worldFile = new File(FileUtils.replaceExtension(imageFilePath, "w"));
if (worldFile.exists())
return worldFile;
// try with "W"
worldFile = new File(FileUtils.replaceExtension(imageFilePath, "W"));
if (worldFile.exists())
return worldFile;
return null;
}
/**
* Reads georeferencing information for a raster image from a World file and
* configures a GeoImage accordingly.
* @param geoImage The GeoImage that will be georeferenced.
* @param worldFile The World file containing the georeferencing information.
* @throws java.io.IOException Throws an IOException if any error related to the file occurs.
*/
private static void readWorldFile(GeoImage geoImage, File worldFile)
throws java.io.IOException {
BufferedReader in = new BufferedReader(new FileReader(worldFile));
double pixelSizeHorizontal = Double.parseDouble(in.readLine());
double rotX = Double.parseDouble(in.readLine());
double rotY = Double.parseDouble(in.readLine());
double pixelSizeVertical = Double.parseDouble(in.readLine());
double west = Double.parseDouble(in.readLine());
double north = Double.parseDouble(in.readLine());
pixelSizeHorizontal = Math.abs(pixelSizeHorizontal);
pixelSizeVertical = Math.abs(pixelSizeVertical);
if (pixelSizeHorizontal != pixelSizeVertical)
return;
if (rotX != 0 || rotY != 0)
return;
geoImage.setCellSize(pixelSizeHorizontal);
geoImage.setWest(west);
geoImage.setNorth(north);
}
/**
* Imports an image and returns a GeoImage. Uses standard java image support. Only
* a limited range of formats is supported (basically GIF and JPEG).
* @param filePath The path to the file that will be imported.
* @return Returns a GeoImage, or null if the image could not be imported.
*/
public static GeoImage importGeoImage(String filePath) {
Image image = new javax.swing.ImageIcon(filePath).getImage();
// convert the Image to a BufferedImage
BufferedImage bufferedImage = ImageUtils.makeBufferedImage(image);
GeoImage geoImage = new GeoImage(bufferedImage, null);
// search and read an associated World file containing georeferencing
// information.
try {
File worldFile = OldImageImporter.searchWorldFile(filePath);
OldImageImporter.readWorldFile(geoImage, worldFile);
} catch (Exception e) {}
return geoImage;
}
public MapComponent getMapComponent() {
return mapComponent;
}
public void setMapComponent(MapComponent mapComponent) {
this.mapComponent = mapComponent;
}
public void setAskUserToGeoreferenceImage(boolean askUserToGeoreferenceImage) {
this.askUserToGeoreferenceImage = askUserToGeoreferenceImage;
}
}