/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012, Geomatys * * 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.geotoolkit.image.io.large; import org.apache.sis.util.ArgumentChecks; import org.geotoolkit.nio.IOUtilities; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.sis.util.logging.Logging; /** * Create tree directory made, starting from tree root directory, define by user. * * @author Remi Marechal (Geomatys). */ public class QuadTreeDirectory { private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.image.io.large"); private static final String D00 = "00"; private static final String D01 = "01"; private static final String D10 = "10"; private static final String D11 = "11"; private final int nbrElementX; private final int nbrElementY; private final Path treeRootPath; private final String extension; private final boolean isDeleteOnExit; /** * Create tree directory made, starting from tree root directory, define by user. * * @param treeRootPath tree root directory path. * @param nbrElementX global elements number in X direction. * @param nbrElementY global elements number in Y direction. * @param extension extension format of element will be stocked. May be null. * @param isDeleteOnExit true if user want delete all tree directory at end of JVM else false. */ public QuadTreeDirectory(final Path treeRootPath, final int nbrElementX, final int nbrElementY, final String extension, final boolean isDeleteOnExit) throws IOException { ArgumentChecks.ensureNonNull("Quad-tree root file.", treeRootPath); ArgumentChecks.ensureStrictlyPositive("Quad-tree X axis dimension", nbrElementX); ArgumentChecks.ensureStrictlyPositive("Quad-tree Y axis dimension", nbrElementY); this.treeRootPath = treeRootPath; this.nbrElementX = nbrElementX; this.nbrElementY = nbrElementY; this.extension = (extension == null) ? "" : (extension.substring(0, 1).equalsIgnoreCase(".")) ? extension : "." + extension; this.isDeleteOnExit = isDeleteOnExit; // Check if given path is valid, because if it's not, the all quad-tree is compromised. createDirectory(treeRootPath); create4rchitecture(treeRootPath, nbrElementX, nbrElementY); LOGGER.log(Level.FINE, "Quad-tree have been successfully initialized for path" + treeRootPath); } /** * Root directory where tiles are stored. * * @return root directory */ public Path getTreeRootPath() { return treeRootPath; } private void create4rchitecture(final Path path, int numXTiles, int numYTiles) throws IOException { LOGGER.log(Level.FINE, "Begin creation of an entire quadTree level"); createDirectory(path); if (numXTiles <= 2 && numYTiles <= 2) return; final int nxt = (numXTiles + 1) / 2; final int nyt = (numYTiles + 1) / 2; if (numXTiles <= 2) { //cut in height //create 2 directories create4rchitecture(path.resolve(D00), numXTiles, nyt); //00 create4rchitecture(path.resolve(D01), numXTiles, numYTiles - nyt); //01 } else if (numYTiles <= 2) { //cut in width //create 2 directories create4rchitecture(path.resolve(D00), nxt, numYTiles); //00 create4rchitecture(path.resolve(D10), numXTiles - nxt, numYTiles); //10 } else { //create 4 directories create4rchitecture(path.resolve(D00), nxt, nyt); //00 create4rchitecture(path.resolve(D10), numXTiles-nxt, nyt); //10 create4rchitecture(path.resolve(D01), nxt, numYTiles-nyt); //01 create4rchitecture(path.resolve(D11), numXTiles-nxt, numYTiles-nyt); //11 LOGGER.log(Level.FINE, "QuadTree level finely created."); } } /** * Create directory at place define by StringBuilder path.A verification is done to be sure that folder has been */ private void createDirectory(final Path path) throws IOException { if (Files.isRegularFile(path)) { throw new IOException("Current path represents a file, but a directory is needed here : "+path); } // If not exists, we try to create directory. if (Files.notExists(path)) { Files.createDirectories(path); if (isDeleteOnExit) { IOUtilities.deleteOnExit(path); } } checkDirectory(path); } /** * Return appropriate path of element at X, Y position. * * @param x x direction element position. * @param y y direction element position. * @return appropriate path of element at X, Y position. */ public String getPath(int x, int y) { return getPath(new StringBuilder(treeRootPath.toString()), 0, 0, nbrElementX-1, nbrElementY-1, x, y); } private String getPath(StringBuilder path, int mintx, int minty, int maxtx, int maxty, int tileX, int tileY) { final int dx = maxtx-mintx; final int dy = maxty-minty; if (dx <= 1 && dy <= 1) { path.append("/"); path.append(tileX); path.append("_"); path.append(tileY); path.append(extension); return (path.toString()); } final int demx = mintx + dx / 2 + 1; final int demy = minty + dy / 2 + 1; path.append("/"); if (dx <= 1) { //2 sub-directories in height. if (intersect(mintx, minty, maxtx, demy-1, tileX, tileY)) { path.append(D00); return getPath(path, mintx, minty, maxtx, demy-1, tileX, tileY); } else if (intersect(mintx, demy, maxtx, maxty, tileX, tileY)) { path.append(D01); return getPath(path, mintx, demy, maxtx, maxty, tileX, tileY); } } else if (dy <= 1) { //2 sub-directories in width. if (intersect(mintx, minty, demx-1, maxty, tileX, tileY)) { path.append(D00); return getPath(path, mintx, minty, demx-1, maxty, tileX, tileY); } else if (intersect( demx, minty, maxtx, maxty, tileX, tileY)) { path.append(D10); return getPath(path, demx, minty, maxtx, maxty, tileX, tileY); } } else { //4 cases. if (intersect(mintx, minty, demx-1, demy-1, tileX, tileY)) { path.append(D00); return getPath(path, mintx, minty, demx-1, demy-1, tileX, tileY); } else if (intersect(demx, minty, maxtx, demy-1, tileX, tileY)) { path.append(D10); return getPath(path, demx, minty, maxtx, demy-1, tileX, tileY); } else if (intersect(mintx, demy, demx-1, maxty, tileX, tileY)) { path.append(D01); return getPath(path, mintx, demy, demx-1, maxty, tileX, tileY); } else if (intersect(demx, demy, maxtx, maxty, tileX, tileY)) { path.append(D11); return getPath(path, demx, demy, maxtx, maxty, tileX, tileY); } } throw new IllegalStateException("Undefined path. Asked tile ("+tileX+", "+tileY+") is out of bounds."); } private boolean intersect(int minx, int miny, int maxx, int maxy, int tx, int ty){ return (tx >= minx) && (tx <= maxx) && (ty >= miny) && (ty <= maxy); } private final void checkDirectory(final Path toCheck) throws IOException { final Path path = toCheck.toAbsolutePath(); if (Files.isRegularFile(toCheck)) { throw new IOException("Current path represents a file, but a directory is needed here : "+path); } if (Files.notExists(toCheck)) { final Path parent = getExistingParent(toCheck); if (parent != null && Files.isWritable(parent)) { throw new IOException("Cannot create folder : "+path+", because application does not possess writing authorisation on parent folder : "+ parent.toString()); } else { throw new IOException("Cannot create folder : "+path+" for some unknown reason"); } } if (!Files.isWritable(toCheck) || !Files.isReadable(toCheck)) { throw new IOException("Given directory ("+ path +") for Quad-tree does not possess sufficient rights."); } } /** * Search a parent folder for input file, one which actually exists on the file system. * * @param child The file to search parent for. * @return the first exising parent folder of the given file, or null if we cannot find any. */ private Path getExistingParent(Path child) { final Path parent = child.getParent(); if (parent != null && Files.notExists(parent)) { return getExistingParent(parent); } return parent; } @Override protected void finalize() throws Throwable { if (isDeleteOnExit) { cleanDirectory(); } super.finalize(); } /** * Delete root directory recursively. */ void cleanDirectory() throws IOException { IOUtilities.deleteRecursively(treeRootPath); } }