/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * 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. */ package org.geotools.gce.imagepyramid; import java.awt.geom.AffineTransform; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.geotools.data.DataUtilities; import org.geotools.factory.Hints; import org.geotools.gce.imagemosaic.ImageMosaicFormat; import org.geotools.gce.imagemosaic.ImageMosaicReader; import org.geotools.geometry.GeneralEnvelope; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.util.logging.Logging; import org.opengis.referencing.datum.PixelInCell; /** * Code to build a pyramid from a gdal_retile output * * @author Andrea Aime - OpenGeo * */ class Utils { static final Logger LOGGER = Logging.getLogger(Utils.class); static URL checkSource(Object source) { return checkSource(source, null); } static URL checkSource(Object source, Hints hints) { URL sourceURL = null; File sourceFile = null; // ///////////////////////////////////////////////////////////////////// // // Check source // // ///////////////////////////////////////////////////////////////////// // if it is a URL or a String let's try to see if we can get a file to // check if we have to build the index if (source instanceof File) { sourceFile = (File) source; sourceURL = DataUtilities.fileToURL(sourceFile); } else if (source instanceof URL) { sourceURL = (URL) source; if (sourceURL.getProtocol().equals("file")) { sourceFile = DataUtilities.urlToFile(sourceURL); } } else if (source instanceof String) { // is it a File? final String tempSource = (String) source; File tempFile = new File(tempSource); if (!tempFile.exists()) { // is it a URL try { sourceURL = new URL(tempSource); source = DataUtilities.urlToFile(sourceURL); } catch (MalformedURLException e) { sourceURL = null; source = null; } } else { sourceURL = DataUtilities.fileToURL(tempFile); sourceFile = tempFile; } } else { // we really don't know how to convert the thing... give up return null; } // ///////////////////////////////////////////////////////////////////// // // Handle cases where the pyramid descriptor file already exists // // ///////////////////////////////////////////////////////////////////// // can't do anything with it if(sourceFile == null || !sourceFile.exists()) return sourceURL; // if it's already a file we don't need to adjust it, will try to open as is if(!sourceFile.isDirectory()) return sourceURL; // it's a directory, let's see if it already has a pyramid description file inside File directory = sourceFile; sourceFile = new File(directory, directory.getName() + ".properties"); if(sourceFile.exists()) return DataUtilities.fileToURL(sourceFile); // ///////////////////////////////////////////////////////////////////// // // Try to build the sub-folders mosaics // // ///////////////////////////////////////////////////////////////////// // if the structure of the directories is gdal_retile like, move the root files in their // own sub directory File zeroLevelDirectory = new File(directory, "0"); IOFileFilter directoryFilter = FileFilterUtils.directoryFileFilter(); File[] numericDirectories = directory.listFiles(new NumericDirectoryFilter()); File[] directories = directory.listFiles((FileFilter) directoryFilter); // do we have at least one sub-directory? if(directories.length == 0) return null; // check the gdal case and move files if necessary if(!zeroLevelDirectory.exists() && numericDirectories.length == directories.length) { LOGGER.log(Level.INFO, "Detected gdal_retile file structure, " + "moving root files to the '0' subdirectory"); if(zeroLevelDirectory.mkdir()) { FileFilter notDirFilter = FileFilterUtils.notFileFilter(directoryFilter); for (File f : directory.listFiles(notDirFilter)) { if(!f.renameTo(new File(zeroLevelDirectory, f.getName()))) LOGGER.log(Level.INFO, "Could not move " + f.getAbsolutePath() + " to " + zeroLevelDirectory); } directories = directory.listFiles((FileFilter) directoryFilter); } } // scan each subdirectory and try to build a mosaic in it, accumulate the resulting mosaics List<MosaicInfo> mosaics = new ArrayList<MosaicInfo>(); ImageMosaicFormat mosaicFactory = new ImageMosaicFormat(); for (File subdir : directories) { if(mosaicFactory.accepts(subdir, hints)) { mosaics.add(new MosaicInfo(subdir, mosaicFactory.getReader(subdir, hints))); } } // do we have at least one level? if(mosaics.size() == 0) return null; // sort the mosaics by resolution and check they are actually in ascending resolution order // for both X and Y resolutions Collections.sort(mosaics); for(int i = 1; i < mosaics.size(); i++) { double[] resprev = mosaics.get(i - 1).getResolutions(); double[] res = mosaics.get(i).getResolutions(); if(resprev[1] > res[1]) { LOGGER.log(Level.INFO, "Invalid mosaic, y resolution in " + mosaics.get(i - 1).getPath() + " is greater than the one in " + mosaics.get(i).getPath() + " whilst x resolutions " + "have the opposite relationship"); return null; } } // ///////////////////////////////////////////////////////////////////// // // We have everything we need, build the final pyramid descriptor info // // ///////////////////////////////////////////////////////////////////// // build the property file Properties properties = new Properties(); properties.put("Name", directory.getName()); properties.put("LevelsNum", String.valueOf(mosaics.size())); StringBuilder sbDirNames = new StringBuilder(); StringBuilder sbLevels = new StringBuilder(); for(MosaicInfo mi : mosaics) { sbDirNames.append(mi.getName()).append(" "); double[] resolutions = mi.getResolutions(); sbLevels.append(resolutions[0]).append(",").append(resolutions[1]).append(" "); } properties.put("LevelsDirs", sbDirNames.toString()); properties.put("Levels", sbLevels.toString()); GeneralEnvelope envelope = mosaics.get(0).getEnvelope(); properties.put("Envelope2D", envelope.getMinimum(0) + "," + envelope.getMinimum(1) + " " + envelope.getMaximum(0) + "," + envelope.getMaximum(1)); OutputStream os = null; try { os = new FileOutputStream(sourceFile); properties.store(os, "Automatically generated"); } catch(IOException e) { LOGGER.log(Level.INFO, "We could not generate the pyramid propert file " + sourceFile.getPath(), e); return null; } finally { if(os != null) try { os.close(); } catch(IOException e) {} } // build the .prj file if possible if(envelope.getCoordinateReferenceSystem() != null) { File prjFile = new File(directory, directory.getName() + ".prj"); PrintWriter pw = null; try { pw = new PrintWriter(new FileOutputStream(prjFile)); pw.print(envelope.getCoordinateReferenceSystem().toString()); } catch(IOException e) { LOGGER.log(Level.INFO, "We could not write out the projection file " + prjFile.getPath(), e); return null; } finally { pw.close(); } } return DataUtilities.fileToURL(sourceFile); } /** * Stores informations about a mosaic */ static class MosaicInfo implements Comparable<MosaicInfo>{ File directory; ImageMosaicReader reader; double[] resolutions; MosaicInfo(File directory, ImageMosaicReader reader) { this.directory = directory; this.reader = reader; this.resolutions = CoverageUtilities.getResolution((AffineTransform) reader.getOriginalGridToWorld(PixelInCell.CELL_CORNER)); } double[] getResolutions() { return resolutions; } String getPath() { return directory.getPath(); } String getName() { return directory.getName(); } GeneralEnvelope getEnvelope() { return reader.getOriginalEnvelope(); } public int compareTo(MosaicInfo other) { // we make an easy comparison against the x resolution, we'll do a sanity // check about the y resolution later return resolutions[0] > other.resolutions[0] ? 1 : -1; } } /** * A file filter that only returns directories whose name is an integer number * @author Andrea Aime - OpenGeo */ static class NumericDirectoryFilter implements FileFilter { public boolean accept(File pathname) { if(!pathname.isDirectory()) return false; try { Integer.parseInt(pathname.getName()); return true; } catch(NumberFormatException e) { return false; } } } }