/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2016, 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.io.IOException; import java.net.URL; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.DataUtilities; import org.geotools.factory.Hints; import org.geotools.gce.imagemosaic.ImageMosaicReader; /** * Parse imagePyramid property files and setup the mapping to provide the proper * {@link ImageMosaicReader} for the required image choice. * We are supporting ImagePyramids of ImageMosaic with internal * overviews, therefore the mapper should take care of different levels which can * be related to the same underlying imageMosaic. * * When ImageMosaic has overviews, the "Levels" property will look like this example: * In this case, "Levels" in the property file is like this: * Levels=1,1;2,2 4,4;8,8 16,16;32,32 * * White spaces separate groups of resolutions from different mosaics as before. * mosaic0 has resolutions 1,1;2,2 * mosaic1 has resolutions 4,4;8,8 * mosaic2 has resolutions 16,16;32,32 * * Semicolon (new char to support overviews) separates resolutions of the same mosaic. * mosaic0 has native resolution = 1,1 * mosaic0 has 1 overview with resolution = 2,2 * * Comma separates x,y resolutions as before. */ class ImageLevelsMapper { /** Logger. */ private final static Logger LOGGER = org.geotools.util.logging.Logging .getLogger(ImageLevelsMapper.class.toString()); /** * The whole number of overviews in the pyramid. * * Note that all the available resolutions on the underlying mosaics * are considered overviews, except the native resolution level of * the first mosaic. */ private int numOverviews; /** * All the available resolutions in the pyramid, beside the highest one. * * Note that all the available resolutions on the underlying mosaics * are considered overviews, except the native resolution level of * the first mosaic. */ private double[][] overViewResolutions; private double[] highestResolution; /** simple mapping between an imageChoice and a reader index */ private int[] imageChoiceToReaderLookup; /** flags reporting if there is at least a reader with inner overviews */ private boolean innerOverviews = false; /** * The directories where to find the different resolutions levels in descending order. */ private String[] levelsDirs; public ImageLevelsMapper(Properties properties) { levelsDirs = properties.getProperty("LevelsDirs").split(" "); // resolutions levels final String levels = properties.getProperty("Levels"); String[] resolutionLevels = levels.split(" "); // Grouping the resolution levels int resolutionGroupsNumber = resolutionLevels.length; // array is organized in 3 layers of resolutions (see the main javadoc at the beginning of the class): // layer 0 elements: the mosaics // \-> layer 1 elements: the different levels in the selected mosaic // \-> layer 2 elements: x,y resolutions of that level double [][][] resolutionsSet = new double[resolutionGroupsNumber][][]; int numResolutions = 0; for (int i=0; i < resolutionGroupsNumber; i++) { // loops along the groups String[] subLevels = resolutionLevels[i].split(";"); int subLevelsLenght = subLevels.length; if (subLevelsLenght > 1) { // report we have inner overviews if a ";" has been found innerOverviews = true; } resolutionsSet[i] = new double[subLevelsLenght][]; for (int k=0; k < subLevelsLenght ; k++) { String[] pair = subLevels[k].split(","); resolutionsSet[i][k] = new double[2]; resolutionsSet[i][k][0] = Double.parseDouble(pair[0].trim()); resolutionsSet[i][k][1] = Double.parseDouble(pair[1].trim()); numResolutions++; } } // decrease by 1 to exclude native resolution from number of overviews numOverviews = numResolutions - 1; imageChoiceToReaderLookup = new int[numResolutions]; //native resolution setting (first group, first level) highestResolution = new double[2]; highestResolution[0] = resolutionsSet[0][0][0]; highestResolution[1] = resolutionsSet[0][0][1]; // Map native resolution to first reader imageChoiceToReaderLookup[0] = 0; overViewResolutions = numOverviews > 0 ? new double[numOverviews][2] : null; numResolutions = 0; // Mapping overviews for (int i=0; i < resolutionGroupsNumber; i++) { for (int k = (i!= 0 ? 0 : 1); k < resolutionsSet[i].length; k++) { overViewResolutions[numResolutions][0] = resolutionsSet[i][k][0]; overViewResolutions[numResolutions][1] = resolutionsSet[i][k][1]; numResolutions++; imageChoiceToReaderLookup[numResolutions] = i; } } } /** * Cache of {@link ImageMosaicReader} objects for the different levels. */ ConcurrentHashMap<Integer, ImageMosaicReader> readers = new ConcurrentHashMap<Integer, ImageMosaicReader>(); public void dispose() { for (Entry<Integer, ImageMosaicReader> element : readers.entrySet()) { try { element.getValue().dispose(); } catch (Exception e) { // Mimic the underlying imageMosaicReader dispose behavior if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } readers.clear(); } protected ImageMosaicReader getReader(Integer imageChoice, String coverageName, URL sourceURL, Hints hints ) throws IOException { int imageIndex = getImageReaderIndex(imageChoice); ImageMosaicReader reader = readers.get(imageIndex); if (reader == null) { // // we must create the underlying mosaic // final String levelDirName = levelsDirs[imageIndex]; final URL parentUrl = DataUtilities.getParentUrl(sourceURL); // look for a shapefile first final String extension = new StringBuilder(levelDirName).append("/") .append(coverageName).append(".shp").toString(); final URL shpFileUrl = DataUtilities.extendURL(parentUrl, extension); if (shpFileUrl.getProtocol() != null && shpFileUrl.getProtocol().equalsIgnoreCase("file") && !DataUtilities.urlToFile(shpFileUrl).exists()) { reader = new ImageMosaicReader(DataUtilities.extendURL(parentUrl, levelDirName), hints); } else { reader = new ImageMosaicReader(shpFileUrl, hints); } final ImageMosaicReader putByOtherThreadJustNow = readers.putIfAbsent(imageIndex, reader); if (putByOtherThreadJustNow != null) { // some other thread just did inserted this try { reader.dispose(); } catch (Exception e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); } } // use the other one reader = putByOtherThreadJustNow; } // light check to see if this reader had been disposed, not synching for performance. if (readers == null) { throw new IllegalStateException("This ImagePyramidReader has already been disposed"); } } return reader; } public int getImageReaderIndex(Integer imageChoice) { return imageChoiceToReaderLookup[imageChoice]; } public int getNumOverviews() { return numOverviews; } public double[][] getOverViewResolutions() { return overViewResolutions; } public double[] getHighestResolution() { return highestResolution; } public String[] getLevelsDirs() { return levelsDirs; } public boolean hasInnerOverviews() { return innerOverviews; } public boolean hasReaders() { return readers != null; } }