/*
* GISToolkit - Geographical Information System Toolkit
* (C) 2002, Ithaqua Enterprises Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package gistoolkit.datasources.imagefile;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import gistoolkit.common.*;
import gistoolkit.features.*;
import gistoolkit.features.featureutils.EnvelopeBuffer;
import gistoolkit.projection.*;
import gistoolkit.datasources.*;
import gistoolkit.datasources.imagefile.imagereaders.*;
/**
* The raster catalog is a way to split large georeferenced images into smaller "tiles", that can be
* more easilly handled. The RasterCatalogCreator Does the acual Creating of the catalog, this datasource
* just reads it and assembles images from the tiles.
*/
public class RasterCatalogDataSource extends SimpleDataSource implements RasterDatasource{
/** The name of the directory where the images are stored. */
private File myCatalogDirectory = null;
/** Set the name of the directory where the images are stored. */
public void setCatalogDirectory(String inCatalogDirectory)throws FileNotFoundException{
File tempFile = new File(inCatalogDirectory);
if (!tempFile.exists()) throw new FileNotFoundException("File "+inCatalogDirectory+" does not exist");
if (!tempFile.isDirectory()) throw new FileNotFoundException("File "+inCatalogDirectory+" Is not a directory");
myCatalogDirectory = tempFile;
try{
loadIndex();
}
catch (Exception e){
e.printStackTrace();
throw new FileNotFoundException("Index File could not be read.");
}
};
/** Get the name of the directory where the images are stored. */
public String getCatalogDirectory(){
if (myCatalogDirectory == null) return null;
return myCatalogDirectory.getAbsolutePath();
}
/** Number of pixels in the width of the image */
private int myImageWidth = 0;
private int myCacheImageWidth = 0;
/**
* Set the width of the image to retrieve.
* The data source can use this information to generate the appropriate rastor shapes.
* Several shapes may be generated or just one that cover this area. This is just a
* suggestion.
*
*/
public void setImageWidth(int inWidth) {myImageWidth = inWidth;}
/** Return the width of the image to generate */
public int getImageWidth() {return myImageWidth;}
/** Number of pixels in the height of the image */
private int myImageHeight = 0;
private int myCacheImageHeight = 0;
/*
* Set the height of the image to retrieve.
* The data source can use this information to generate the appropriate rastor shapes.
* Several shapes may be generated or just one that cover this area. This is just a
* suggestion.
*/
public void setImageHeight(int inHeight) {myImageHeight = inHeight;}
/** Return the height of the image to generate */
public int getImageHeight() {return myImageHeight;}
/** A class to handle the index information. */
private class MyIndex {
Envelope myWorldEnvelope = null;
double[] myResolutions = null;
String[] myResolutionNames = null;
String myExtension = "jpg";
int myTileWidth = 0;
/** Create an index for this data source. */
public MyIndex(Envelope inEnvelope, double[] inResolutions, String[] inResolutionNames, int inTileWidth, String inExtension){
myWorldEnvelope = inEnvelope;
myResolutions = inResolutions;
myResolutionNames = inResolutionNames;
myTileWidth = inTileWidth;
myExtension = inExtension;
}
/** Return the envelope for the world. */
public Envelope getWorldEnvelope(){return myWorldEnvelope;}
/** Return the width of a single tile. */
public int getTileWidth(){return myTileWidth;}
/** Select the best guess resolution for this image. */
public double getBestResolution(Envelope inEnvelope){
// the images from terraserver are in 200 pixel squares, so I want to use no more than 36 squares.
double tempArea = myWorldEnvelope.getWidth() * myWorldEnvelope.getHeight();
double tempAcuracy = (double) tempArea / (36*myTileWidth*myTileWidth);
// select the index that is the closest to, but smaller than the ideal resolution.
int tempIndex = 0;
double tempMeterAccuracy = myResolutions[0];
for (int i=0; i<myResolutions.length; i++){
if (tempAcuracy <= myResolutions[i]){
break;
}
tempMeterAccuracy = myResolutions[i];
}
// return the meter accuracy.
return tempMeterAccuracy;
}
/** Select the best resolution. */
public double getBestResolution(Envelope inEnvelope, int inWidth, int inHeight){
if ((inWidth <= 0) || (inHeight <= 0)) return getBestResolution(inEnvelope);
// find the meter acuracy and scale value
double tempIdealXResolution = (Math.abs(inEnvelope.getMaxX()-inEnvelope.getMinX())/inWidth) * 0.833; // fudge factor, works for ~6 degreese of rotaiton between the projections.
double tempIdealYResolution = (Math.abs(inEnvelope.getMaxY()-inEnvelope.getMinY())/inHeight) * 0.833; // fudge factor, works for ~6 degreese of rotaiton between the projections.
double tempIdealResolution = Math.min(tempIdealXResolution, tempIdealYResolution);
// select the index that is the closest to, but smaller than the ideal resolution.
int tempIndex = 0;
double tempMeterAccuracy = myResolutions[0];
for (int i=0; i<myResolutions.length; i++){
if (tempIdealResolution <= myResolutions[i]){
break;
}
tempMeterAccuracy = myResolutions[i];
}
// return the meter accuracy.
return tempMeterAccuracy;
}
/** Get the file name for the given tile. */
public String getFileName(double inResolution, int inX, int inY){
String tempResolutionName = ""+inResolution;
for (int i=0; i<myResolutions.length; i++){
if (myResolutions[i] == inResolution){
tempResolutionName = myResolutionNames[i];
break;
}
}
return getCatalogDirectory()+File.separatorChar+"r"+tempResolutionName+File.separatorChar+"X"+inX+"Y"+inY+"."+myExtension;
}
/** Get the tile coordinages for this world coordinate. */
public java.awt.Point getTileCoordinates(double inResolution, double inX, double inY){
// Determine the width of each tile.
double tempTileWidth = inResolution*myTileWidth;
// Determine how the X coordinate
int tempX = (int) ((inX - myWorldEnvelope.getMinX())/tempTileWidth);
int tempY = (int) ((inY - myWorldEnvelope.getMinY())/tempTileWidth);
return new java.awt.Point(tempX, tempY);
}
/** Get World coordinages for this tile. */
public Envelope getWorldCoordinates(double inResolution, int inX, int inY){
// Determine the width of each tile.
double tempTileWidth = inResolution*myTileWidth;
double tempMinX = myWorldEnvelope.getMinX() + tempTileWidth*inX;
double tempMinY = myWorldEnvelope.getMinY() + tempTileWidth*inY;
double tempMaxX = tempMinX+tempTileWidth;
double tempMaxY = tempMinY+tempTileWidth;
return new Envelope(tempMinX, tempMinY, tempMaxX, tempMaxY);
}
/** The the tiles that are within this envelope. */
public java.awt.Point[] getTilePoints(double inResolution, Envelope inEnvelope){
double tempTileWidth = inResolution*myTileWidth;
int tempXTiles = (int) (inEnvelope.getWidth()/tempTileWidth)+2;
int tempYTiles = (int) (inEnvelope.getHeight()/tempTileWidth)+2;
java.awt.Point tempPoint = getTileCoordinates(inResolution, inEnvelope.getMinX(), inEnvelope.getMinY());
int tempMaxPoint = (int) (myWorldEnvelope.getWidth()/tempTileWidth);
tempMaxPoint = tempMaxPoint + 1;
ArrayList tempList = new ArrayList();
for (int i=0; i<tempXTiles; i++){
for (int j=0; j<tempYTiles; j++){
int x = tempPoint.x + i;
int y = tempPoint.y + j;
if ((x >=0) && (y>=0) && (x<=tempMaxPoint+1) && (y<=tempMaxPoint+1)){
tempList.add(new java.awt.Point(x, y));
}
}
}
java.awt.Point[] tempPoints = new java.awt.Point[tempList.size()];
tempList.toArray(tempPoints);
return tempPoints;
}
}
private MyIndex myIndex = null;
/** Creates new RasterCatalogDataSource */
public RasterCatalogDataSource() {
super();
}
/** Creates new RasterCatalogDataSource with the given file.*/
public RasterCatalogDataSource(File inDirectory) throws Exception{
super();
// set the directory
setCatalogDirectory(inDirectory.getAbsolutePath());
setName(inDirectory.getName());
}
/** Set the projection from which this data should be projected. */
public void setFromProjection(Projection inProjection)throws Exception{
super.setFromProjection(inProjection);
myGISDataset = null;
}
/** Constants for the configuration information*/
private static final String FILE_NAME_TAG = "RasterCatalogFileName";
/** Get the configuration information for this layer. */
public Node getNode() {
Node tempRoot = super.getNode();
tempRoot.setName("RasterCatalogDataSource");
tempRoot.addAttribute(FILE_NAME_TAG, myCatalogDirectory.getAbsolutePath());
return tempRoot;
}
/** Set the configuration information for this layer. */
public void setNode(Node inNode) throws Exception{
super.setNode(inNode);
if (inNode != null){
// construct the file
String tempString = inNode.getAttribute(FILE_NAME_TAG);
if (tempString != null){
try{
setCatalogDirectory(tempString);
}
catch (FileNotFoundException e){
System.out.println("Catalog File "+tempString+" does not exist");
}
}
}
}
/**
* Returns the bounding rectangle of all the shapes in the Data Source.
*/
public Envelope readEnvelope() throws Exception {
loadIndex();
if (myIndex != null) return myIndex.getWorldEnvelope();
else return null;
}
/** Only read the image the once. */
private GISDataset myGISDataset = null;
/**
* Reads only the objects from the data source that intersect these envelope.
*/
public synchronized GISDataset readDataset(Envelope inEnvelope) throws Exception {
if (inEnvelope == null) return readDataset();
boolean blewCache = false;
if (getCached()){
blewCache = !isCachedProjected();
if ((getCacheEnvelope() == null) || (!getCacheEnvelope().isEqual(inEnvelope))){
clearCache();
}
}
// all the things that can blow a cache.
if ((getFromProjection() == null) || (getToProjection() == null)||(blewCache)||(getFromProjection() instanceof NoProjection)||(getToProjection() instanceof NoProjection)){
return super.readDataset(inEnvelope);
}
else{
// this is a very specific case to try to increase the resolution of reprojected images.
// If there is a from and a to projection, chances are they are from and two similar projections
// It is much more efficient to project these once than to do it many times.
Envelope tempEnvelope = inEnvelope;
if (getCacheEnvelope() != null){
if (getCacheEnvelope().contains(tempEnvelope)){
return queryFromCache(tempEnvelope);
}
}
tempEnvelope = ShapeProjector.projectBackward(getToProjection(), inEnvelope);
// I have some concerns about this because the shape is not really a square at this point.
// would be more efficient to convert to a polygon and then query from that.
GISDataset tempDataset = readShapes(ShapeProjector.projectForward(getFromProjection(), tempEnvelope));
if (tempDataset != null){
tempDataset = filterDataset(tempDataset);
tempDataset = (GISDataset) tempDataset.clone();
for (int i=0; i<tempDataset.size(); i++){
gistoolkit.features.Shape tempShape = tempDataset.getShape(i);
if ((tempShape != null) && (tempShape instanceof RasterShape)){
ImageProjector.reProject(getFromProjection(), getToProjection(), (RasterShape) tempShape);
}
}
setCache(tempDataset, (Envelope) inEnvelope.clone());
}
return tempDataset;
}
}
/**
* Reads all the objects from the data source.
*/
public GISDataset readDataset() throws Exception {
if ((getFromProjection() == null) || (getToProjection() == null)||(!isCachedProjected())||(getFromProjection() instanceof NoProjection)||(getToProjection() instanceof NoProjection)){
return super.readDataset(null);
}
else{
// this is a very specific case to try to increase the resolution of reprojected images.
// If there is a from and a to projection, chances are they are from and two similar projections
// It is much more efficient to project these once than to do it many times.
GISDataset tempDataset = readShapes(null);
if (tempDataset != null){
tempDataset = filterDataset(tempDataset);
for (int i=0; i<tempDataset.size(); i++){
gistoolkit.features.Shape tempShape = tempDataset.getShape(i);
if ((tempShape != null) && (tempShape instanceof RasterShape)){
ImageProjector.reProject(getFromProjection(), getToProjection(), (RasterShape) tempShape);
}
}
setCache(tempDataset, (Envelope) tempDataset.getEnvelope().clone());
}
return tempDataset;
}
}
/** This method should return the shapes from the data source */
protected GISDataset readShapes(Envelope inEnvelope) throws Exception {
// create the index should it not exist.
loadIndex();
// if this envelope is no where near the one that I have, then just return nothing.
if (inEnvelope != null){
if (!inEnvelope.overlaps(myIndex.getWorldEnvelope())){
System.out.println("World Envelope = "+myIndex.getWorldEnvelope());
System.out.println("Requested Envelope = "+inEnvelope);
return new GISDataset();
}
}
// determine the size of the image.
int tempWidth = getImageWidth();
int tempHeight = getImageHeight();
if ((tempWidth <= 0) || (tempHeight <= 0)){
tempWidth = myIndex.getTileWidth() * 3;
tempHeight = myIndex.getTileWidth() * 3;
}
// determine the best resolution for this image
double tempResolution = myIndex.getBestResolution(inEnvelope, getImageWidth(), getImageHeight());
// find the tiles for this image.
java.awt.Point[] tempPoints = myIndex.getTilePoints(tempResolution, inEnvelope);
// build the image.
BufferedImage tempImage = new BufferedImage(tempWidth, tempHeight, BufferedImage.TYPE_INT_ARGB);
Envelope tempEnvelope = inEnvelope;
// fill the background with a transparent color.
Graphics g = tempImage.getGraphics();
g.setColor(new Color(255,255,255,0));
g.fillRect(0,0,tempWidth, tempHeight);
Graphics2D g2d = (Graphics2D) g;
// save the coordinates that are written to create a smaller image.
int tempImageStartX = 0;
int tempImageStartY = 0;
int tempImageEndX = 0;
int tempImageEndY = 0;
// save the world coordinates to go with the image coordinates.
EnvelopeBuffer tempWorldBuffer = new EnvelopeBuffer();
// loop through the image retrieving the tiles as necissary.
boolean tempFoundTile = false;
for (int i=0; i<tempPoints.length; i++){
// retrieve the file
File tempFile = new File(myIndex.getFileName(tempResolution, tempPoints[i].x, tempPoints[i].y));
if (tempFile.exists()){
// read the image
System.out.println("Reading Image "+tempFile.getName());
ImageInformation tempImageInformation = ImageReader.readImage(tempFile.getAbsolutePath());
if (tempImageInformation != null){
// draw the tile on the image.
Envelope tempTileEnvelope = myIndex.getWorldCoordinates(tempResolution, tempPoints[i].x, tempPoints[i].y);
tempWorldBuffer.expandToInclude(tempTileEnvelope);
int tempTileStartX = (int) (((tempTileEnvelope.getMinX() - inEnvelope.getMinX())/inEnvelope.getWidth()) * tempWidth);
int tempTileStartY = tempHeight - (int) (((tempTileEnvelope.getMinY() - inEnvelope.getMinY())/inEnvelope.getHeight()) * tempHeight);
int tempTileEndX = (int) (((tempTileEnvelope.getMaxX() - inEnvelope.getMinX())/inEnvelope.getWidth()) * tempWidth);
int tempTileEndY = tempHeight - (int) (((tempTileEnvelope.getMaxY() - inEnvelope.getMinY())/inEnvelope.getHeight()) * tempHeight);
if (i==0) {
tempImageStartX = tempTileStartX;
tempImageStartY = tempTileEndY;
tempImageEndX = tempTileEndX;
tempImageEndY = tempTileStartY;
}
else{
if (tempTileStartX < tempImageStartX) tempImageStartX = tempTileStartX;
if (tempTileEndY < tempImageStartY) tempImageStartY = tempTileEndY;
if (tempTileEndX > tempImageEndX) tempImageEndX = tempTileEndX;
if (tempTileStartY > tempImageEndY) tempImageEndY = tempTileStartY;
}
g2d.drawImage(tempImageInformation.getImage(), tempTileStartX, tempTileEndY, tempTileEndX, tempTileStartY, 0, 0, tempImageInformation.getImageWidth(), tempImageInformation.getImageHeight(), null);
tempFoundTile = true;
}
}
}
if (!tempFoundTile) return new GISDataset();
// create the dataset
String[] tempAttributeNames = {"Name"};
AttributeType[] tempAttributeTypes = {new AttributeType(AttributeType.STRING)};
GISDataset tempGISDataset = new GISDataset(tempAttributeNames, tempAttributeTypes);
// create a new image if necissary.
if (tempImageStartX < 0) tempImageStartX = 0;
if (tempImageStartY < 0) tempImageStartY = 0;
if (tempImageEndX > tempWidth) tempImageEndX = tempWidth;
if (tempImageEndY > tempHeight) tempImageEndY = tempHeight;
if ((tempImageStartX > 0) || (tempImageStartY > 0) || (tempImageEndX < tempWidth) || (tempImageEndY < tempHeight)){
int tempNWidth = tempImageEndX - tempImageStartX;
int tempNHeight = tempImageEndY - tempImageStartY;
BufferedImage tempNewImage = new BufferedImage(tempNWidth, tempNHeight, BufferedImage.TYPE_INT_ARGB);
// fill the background with a transparent color.
Graphics gn = tempNewImage.getGraphics();
gn.setColor(new Color(255,255,255,1));
gn.fillRect(0,0,tempNWidth, tempNHeight);
Graphics2D gn2d = (Graphics2D) gn;
// draw the old image onto the new image.
gn2d.drawImage(tempImage, 0, 0, tempNWidth, tempNHeight,
tempImageStartX, tempImageStartY, tempImageEndX, tempImageEndY,
null);
// create the envelope for this image.
Envelope tempNewEnvelope = tempWorldBuffer.getEnvelope();
double tempWorldMinX = inEnvelope.getMinX()+(((double)tempImageStartX)/(double)tempWidth)*inEnvelope.getWidth();
double tempWorldMinY = inEnvelope.getMaxY()-(((double)tempImageStartY)/(double)tempHeight)*inEnvelope.getHeight();
double tempWorldMaxX = inEnvelope.getMinX()+(((double)tempImageEndX)/(double)tempWidth)*inEnvelope.getWidth();
double tempWorldMaxY = inEnvelope.getMaxY()-(((double)tempImageEndY)/(double)tempHeight)*inEnvelope.getHeight();
// assign the new image as the one to send
tempImage = tempNewImage;
tempEnvelope = new Envelope(tempWorldMinX, tempWorldMinY, tempWorldMaxX, tempWorldMaxY);
}
// return the graphics object
RasterShape tempShape = new RasterShape(tempEnvelope, tempImage);
Record tempRecord = new Record();
tempRecord.setAttributeNames(tempAttributeNames);
Object[][] tempAttributeValues = new Object[1][1];
tempAttributeValues[0][0] = "Image";
tempRecord.setAttributes(tempAttributeValues);
tempRecord.setShape(tempShape);
tempGISDataset.add(tempRecord);
// return the dataset
return tempGISDataset;
}
/** The name of the index file. */
private String myIndexFileName = "Index.xml";
/** Set the name of the index file. */
public void setIndexFileName(String inIndexFileName){if (inIndexFileName != null) myIndexFileName = inIndexFileName;}
/** Get the name of the index file. */
public String getIndexFileName(){return myIndexFileName;}
/** Create the index should it not exist. */
public void loadIndex()throws Exception{
/** Check for the existence of the index file. */
File tempIndexFile = new File(myCatalogDirectory.getAbsolutePath() + File.separatorChar + getIndexFileName());
if ((tempIndexFile.exists()) && (myIndex != null)) return;
// if the index file exists, then use it.
boolean tempReadIndex = false;
if (tempIndexFile.exists()){
Node tempRoot = gistoolkit.config.Configurator.readConfig(tempIndexFile.getAbsolutePath());
if (tempRoot == null) throw new Exception("Can not read configuration file.");
// read the envelope.
double tempMinX = 0;
if (tempRoot != null){
try{
String tempString = tempRoot.getAttribute("MinX");
tempMinX = Double.parseDouble(tempString);
}
catch (Exception e){
throw new Exception("Error parsing MinX from RasterCatalog Index File");
}
}
double tempMinY = 0;
if (tempRoot != null){
try{
String tempString = tempRoot.getAttribute("MinY");
tempMinY = Double.parseDouble(tempString);
}
catch (Exception e){
throw new Exception("Error parsing MinY from RasterCatalog Index File");
}
}
double tempMaxX = 0;
if (tempRoot != null){
try{
String tempString = tempRoot.getAttribute("MaxX");
tempMaxX = Double.parseDouble(tempString);
}
catch (Exception e){
throw new Exception("Error parsing MaxX from RasterCatalog Index File");
}
}
double tempMaxY = 0;
if (tempRoot != null){
try{
String tempString = tempRoot.getAttribute("MaxY");
tempMaxY = Double.parseDouble(tempString);
}
catch (Exception e){
throw new Exception("Error parsing MaxY from RasterCatalog Index File");
}
}
// read the resolutions.
double[] tempResolutions = null;
String[] tempResolutionNames = null;
try{
String tempString = tempRoot.getAttribute("Resolutions");
if (tempString == null) throw new Exception("Unable to read Resolutions configuration information.");
ArrayList tempList = new ArrayList();
StringTokenizer st = new StringTokenizer(tempString);
while (st.hasMoreElements()){
tempList.add(st.nextToken(","));
}
tempResolutions = new double[tempList.size()];
tempResolutionNames = new String[tempList.size()];
for (int i=0; i<tempList.size(); i++){
tempResolutionNames[i] = (String) tempList.get(i);
tempResolutions[i] = Double.parseDouble(tempResolutionNames[i]);
}
if (tempResolutions.length == 0) throw new Exception("No Resolutions found in Index File!");
}
catch (Exception e){
e.printStackTrace();
throw new Exception("Error parsing Resolutions from index file.");
}
// read the width of the tiles.
int tempTileWidth = 0;
try{
String tempString = tempRoot.getAttribute("TileWidth");
if (tempString == null) throw new Exception("Unable to read tile width configuration information.");
tempTileWidth = Integer.parseInt(tempString);
}
catch (Exception e){
e.printStackTrace();
throw new Exception("Error Parsing the TileWidth from the index file.");
}
// Read the file extension of the tiles.
String tempExtension = tempRoot.getAttribute("Extension");
if (tempExtension == null) tempExtension = "jpg";
// create the index
Envelope tempEnvelope = new Envelope(tempMinX, tempMinY, tempMaxX, tempMaxY);
myIndex = new MyIndex(tempEnvelope, tempResolutions, tempResolutionNames, tempTileWidth, tempExtension);
}
}
/** Get the style to use with this datasource. */
public gistoolkit.display.Style getStyle() {
return null;
}
}