All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.data.TiledRasterProducer Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.data;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.util.*;
import org.w3c.dom.Document;

import java.io.File;
import java.lang.Thread;

/**
 * @author dcollins
 * @version $Id: TiledRasterProducer.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public abstract class TiledRasterProducer extends AbstractDataStoreProducer
{
    private static final long DEFAULT_TILED_RASTER_PRODUCER_CACHE_SIZE = 300000000L; // ~300 megabytes
    private static final int DEFAULT_TILED_RASTER_PRODUCER_LARGE_DATASET_THRESHOLD = 3000; // 3000 pixels
    private static final int DEFAULT_WRITE_THREAD_POOL_SIZE = 2;
    private static final int DEFAULT_TILE_WIDTH_AND_HEIGHT = 512;
    private static final int DEFAULT_SINGLE_LEVEL_TILE_WIDTH_AND_HEIGHT = 512;
    private static final double DEFAULT_LEVEL_ZERO_TILE_DELTA = 36d;

    // List of source data rasters.
    private java.util.List dataRasterList = new java.util.ArrayList();
    // Data raster caching.
    private MemoryCache rasterCache;
    // Concurrent processing helper objects.
    private final java.util.concurrent.ExecutorService tileWriteService;
    private final java.util.concurrent.Semaphore tileWriteSemaphore;
    private final Object fileLock = new Object();
    // Progress counters.
    private int tile;
    private int tileCount;

    private DataRasterReaderFactory readerFactory;

    public TiledRasterProducer(MemoryCache cache, int writeThreadPoolSize)
    {
        if (cache == null)
        {
            String message = Logging.getMessage("nullValue.CacheIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (writeThreadPoolSize < 1)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "writeThreadPoolSize < 1");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.rasterCache = cache;
        this.tileWriteService = this.createDefaultTileWriteService(writeThreadPoolSize);
        this.tileWriteSemaphore = new java.util.concurrent.Semaphore(writeThreadPoolSize, true);

        try
        {
            readerFactory = (DataRasterReaderFactory) WorldWind.createConfigurationComponent(
                AVKey.DATA_RASTER_READER_FACTORY_CLASS_NAME);
        }
        catch (Exception e)
        {
            readerFactory = new BasicDataRasterReaderFactory();
        }
    }

    public TiledRasterProducer()
    {
        this(createDefaultCache(), DEFAULT_WRITE_THREAD_POOL_SIZE);
    }

    public Iterable getDataRasters()
    {
        return this.dataRasterList;
    }

    protected DataRasterReaderFactory getReaderFactory()
    {
        return this.readerFactory;
    }

    // TODO: this describes the file types the producer will read. Make that more clear in the method name.

    public String getDataSourceDescription()
    {
        DataRasterReader[] readers = this.getDataRasterReaders();
        if (readers == null || readers.length < 1)
            return "";

        // Collect all the unique format suffixes available in all readers. If a reader does not publish any
        // format suffixes, then collect it's description.
        java.util.Set suffixSet = new java.util.TreeSet();
        java.util.Set descriptionSet = new java.util.TreeSet();
        for (DataRasterReader reader : readers)
        {
            String description = reader.getDescription();
            String[] names = reader.getSuffixes();

            if (names != null && names.length > 0)
                suffixSet.addAll(java.util.Arrays.asList(names));
            else
                descriptionSet.add(description);
        }

        // Create a string representation of the format suffixes (or description if no suffixes are available) for
        // all readers.
        StringBuilder sb = new StringBuilder();
        for (String suffix : suffixSet)
        {
            if (sb.length() > 0)
                sb.append(", ");
            sb.append("*.").append(suffix);
        }
        for (String description : descriptionSet)
        {
            if (sb.length() > 0)
                sb.append(", ");
            sb.append(description);
        }
        return sb.toString();
    }

    public void removeProductionState()
    {
        java.io.File installLocation = this.installLocationFor(this.getStoreParameters());

        if (installLocation == null || !installLocation.exists())
        {
            String message = Logging.getMessage("TiledRasterProducer.NoInstallLocation",
                this.getStoreParameters().getValue(AVKey.DATASET_NAME));
            Logging.logger().warning(message);
            return;
        }

        try
        {
            WWIO.deleteDirectory(installLocation);
        }
        catch (Exception e)
        {
            String message = Logging.getMessage("TiledRasterProducer.ExceptionRemovingProductionState",
                this.getStoreParameters().getValue(AVKey.DATASET_NAME));
            Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
        }
    }

    protected abstract DataRaster createDataRaster(int width, int height, Sector sector, AVList params);

    protected abstract DataRasterReader[] getDataRasterReaders();

    protected abstract DataRasterWriter[] getDataRasterWriters();

    protected MemoryCache getCache()
    {
        return this.rasterCache;
    }

    protected java.util.concurrent.ExecutorService getTileWriteService()
    {
        return this.tileWriteService;
    }

    protected java.util.concurrent.Semaphore getTileWriteSemaphore()
    {
        return this.tileWriteSemaphore;
    }

    protected void doStartProduction(AVList parameters) throws Exception
    {
        // Copy production parameters to prevent changes to caller's reference.
        this.productionParams = parameters.copy();
        this.initProductionParameters(this.productionParams);

        // Assemble the source data rasters.
        this.assembleDataRasters();

        // Initialize the level set parameters, and create the level set.
        this.initLevelSetParameters(this.productionParams);
        LevelSet levelSet = new LevelSet(this.productionParams);
        // Install the each tiles of the LevelSet.
        this.installLevelSet(levelSet, this.productionParams);

        // Wait for concurrent tasks to complete.
        this.waitForInstallTileTasks();

        // Clear the raster cache.
        this.getCache().clear();

        // Install the data descriptor for this tiled raster set.
        this.installConfigFile(this.productionParams);
    }

    protected String validateProductionParameters(AVList parameters)
    {
        StringBuilder sb = new StringBuilder();

        Object o = parameters.getValue(AVKey.FILE_STORE_LOCATION);
        if (o == null || !(o instanceof String) || ((String) o).length() < 1)
            sb.append((sb.length() > 0 ? ", " : "")).append(Logging.getMessage("term.fileStoreLocation"));

        o = parameters.getValue(AVKey.DATA_CACHE_NAME);
        if (o == null || !(o instanceof String) || ((String) o).length() == 0)
            sb.append((sb.length() > 0 ? ", " : "")).append(Logging.getMessage("term.fileStoreFolder"));

        o = parameters.getValue(AVKey.DATASET_NAME);
        if (o == null || !(o instanceof String) || ((String) o).length() < 1)
            sb.append((sb.length() > 0 ? ", " : "")).append(Logging.getMessage("term.datasetName"));

        if (sb.length() == 0)
            return null;

        return Logging.getMessage("DataStoreProducer.InvalidDataStoreParamters", sb.toString());
    }

    protected java.io.File installLocationFor(AVList params)
    {
        String fileStoreLocation = params.getStringValue(AVKey.FILE_STORE_LOCATION);
        String dataCacheName = params.getStringValue(AVKey.DATA_CACHE_NAME);
        if (fileStoreLocation == null || dataCacheName == null)
            return null;

        String path = WWIO.appendPathPart(fileStoreLocation, dataCacheName);
        if (path == null || path.length() == 0)
            return null;

        return new java.io.File(path);
    }

    //**************************************************************//
    //********************  LevelSet Assembly  *********************//
    //**************************************************************//

    protected abstract void initProductionParameters(AVList params);

    protected void initLevelSetParameters(AVList params)
    {
        int largeThreshold = Configuration.getIntegerValue(AVKey.TILED_RASTER_PRODUCER_LARGE_DATASET_THRESHOLD,
            DEFAULT_TILED_RASTER_PRODUCER_LARGE_DATASET_THRESHOLD);
        boolean isDataSetLarge = this.isDataSetLarge(this.dataRasterList, largeThreshold);

        Sector sector = (Sector) params.getValue(AVKey.SECTOR);
        if (sector == null)
        {
            // Compute a sector that bounds the data rasters. Make sure the sector does not exceed the limits of
            // latitude and longitude.
            sector = this.computeBoundingSector(this.dataRasterList);
            if (sector != null)
                sector = sector.intersection(Sector.FULL_SPHERE);
            params.setValue(AVKey.SECTOR, sector);
        }

        Integer tileWidth = (Integer) params.getValue(AVKey.TILE_WIDTH);
        if (tileWidth == null)
        {
            tileWidth = isDataSetLarge ? DEFAULT_TILE_WIDTH_AND_HEIGHT : DEFAULT_SINGLE_LEVEL_TILE_WIDTH_AND_HEIGHT;
            params.setValue(AVKey.TILE_WIDTH, tileWidth);
        }

        Integer tileHeight = (Integer) params.getValue(AVKey.TILE_HEIGHT);
        if (tileHeight == null)
        {
            tileHeight = isDataSetLarge ? DEFAULT_TILE_WIDTH_AND_HEIGHT : DEFAULT_SINGLE_LEVEL_TILE_WIDTH_AND_HEIGHT;
            params.setValue(AVKey.TILE_HEIGHT, tileHeight);
        }

        LatLon rasterTileDelta = this.computeRasterTileDelta(tileWidth, tileHeight, this.dataRasterList);
        LatLon desiredLevelZeroDelta = this.computeDesiredTileDelta(sector);

        Integer numLevels = (Integer) params.getValue(AVKey.NUM_LEVELS);
        if (numLevels == null)
        {
            // If the data set is large, then use compute a number of levels for the full pyramid. Otherwise use a
            // single level.
            numLevels = isDataSetLarge ? this.computeNumLevels(desiredLevelZeroDelta, rasterTileDelta) : 1;
            params.setValue(AVKey.NUM_LEVELS, numLevels);
        }

        Integer numEmptyLevels = (Integer) params.getValue(AVKey.NUM_EMPTY_LEVELS);
        if (numEmptyLevels == null)
        {
            numEmptyLevels = 0;
            params.setValue(AVKey.NUM_EMPTY_LEVELS, numEmptyLevels);
        }

        LatLon levelZeroTileDelta = (LatLon) params.getValue(AVKey.LEVEL_ZERO_TILE_DELTA);
        if (levelZeroTileDelta == null)
        {
            double scale = Math.pow(2d, numLevels - 1);
            levelZeroTileDelta = LatLon.fromDegrees(
                scale * rasterTileDelta.getLatitude().degrees,
                scale * rasterTileDelta.getLongitude().degrees);
            params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, levelZeroTileDelta);
        }

        LatLon tileOrigin = (LatLon) params.getValue(AVKey.TILE_ORIGIN);
        if (tileOrigin == null)
        {
            tileOrigin = new LatLon(sector.getMinLatitude(), sector.getMinLongitude());
            params.setValue(AVKey.TILE_ORIGIN, tileOrigin);
        }

        // If the default or caller-specified values define a level set that does not fit in the limits of latitude
        // and longitude, then we re-define the level set parameters using values known to fit in those limits.
        if (!this.isWithinLatLonLimits(sector, levelZeroTileDelta, tileOrigin))
        {
            String message
                = "TiledRasterProducer: native tiling is outside lat/lon limits. Falling back to default tiling.";
            Logging.logger().warning(message);

            levelZeroTileDelta = LatLon.fromDegrees(DEFAULT_LEVEL_ZERO_TILE_DELTA, DEFAULT_LEVEL_ZERO_TILE_DELTA);
            params.setValue(AVKey.LEVEL_ZERO_TILE_DELTA, levelZeroTileDelta);

            tileOrigin = new LatLon(Angle.NEG90, Angle.NEG180);
            params.setValue(AVKey.TILE_ORIGIN, tileOrigin);

            numLevels = this.computeNumLevels(levelZeroTileDelta, rasterTileDelta);
            params.setValue(AVKey.NUM_LEVELS, numLevels);

            int numLevelsNeeded = isDataSetLarge ? this.computeNumLevels(desiredLevelZeroDelta, rasterTileDelta) : 1;
            numEmptyLevels = (numLevels > numLevelsNeeded) ? (numLevels - numLevelsNeeded) : 0;
            params.setValue(AVKey.NUM_EMPTY_LEVELS, numEmptyLevels);
        }
    }

    protected boolean isDataSetLarge(Iterable rasters, int largeThreshold)
    {
        Sector sector = this.computeBoundingSector(rasters);
        LatLon pixelSize = this.computeSmallestPixelSize(rasters);
        int sectorWidth = (int) Math.ceil(sector.getDeltaLonDegrees() / pixelSize.getLongitude().degrees);
        int sectorHeight = (int) Math.ceil(sector.getDeltaLatDegrees() / pixelSize.getLatitude().degrees);
        return (sectorWidth >= largeThreshold) || (sectorHeight >= largeThreshold);
    }

    protected boolean isWithinLatLonLimits(Sector sector, LatLon tileDelta, LatLon tileOrigin)
    {
        double minLat = Math.floor((sector.getMinLatitude().degrees - tileOrigin.getLatitude().degrees)
            / tileDelta.getLatitude().degrees);
        minLat = tileOrigin.getLatitude().degrees + minLat * tileDelta.getLatitude().degrees;
        double maxLat = Math.ceil((sector.getMaxLatitude().degrees - tileOrigin.getLatitude().degrees)
            / tileDelta.getLatitude().degrees);
        maxLat = tileOrigin.getLatitude().degrees + maxLat * tileDelta.getLatitude().degrees;
        double minLon = Math.floor((sector.getMinLongitude().degrees - tileOrigin.getLongitude().degrees)
            / tileDelta.getLongitude().degrees);
        minLon = tileOrigin.getLongitude().degrees + minLon * tileDelta.getLongitude().degrees;
        double maxLon = Math.ceil((sector.getMaxLongitude().degrees - tileOrigin.getLongitude().degrees)
            / tileDelta.getLongitude().degrees);
        maxLon = tileOrigin.getLongitude().degrees + maxLon * tileDelta.getLongitude().degrees;
        return Sector.fromDegrees(minLat, maxLat, minLon, maxLon).isWithinLatLonLimits();
    }

    protected Sector computeBoundingSector(Iterable rasters)
    {
        Sector sector = null;
        for (DataRaster raster : rasters)
        {
            sector = (sector != null) ? raster.getSector().union(sector) : raster.getSector();
        }
        return sector;
    }

    protected LatLon computeRasterTileDelta(int tileWidth, int tileHeight, Iterable rasters)
    {
        LatLon pixelSize = this.computeSmallestPixelSize(rasters);
        // Compute the tile size in latitude and longitude, given a raster's sector and dimension, and the tile
        // dimensions. In this computation a pixel is assumed to cover a finite area.
        double latDelta = tileHeight * pixelSize.getLatitude().degrees;
        double lonDelta = tileWidth * pixelSize.getLongitude().degrees;
        return LatLon.fromDegrees(latDelta, lonDelta);
    }

    protected LatLon computeDesiredTileDelta(Sector sector)
    {
        double levelZeroLat = Math.min(sector.getDeltaLatDegrees(), DEFAULT_LEVEL_ZERO_TILE_DELTA);
        double levelZeroLon = Math.min(sector.getDeltaLonDegrees(), DEFAULT_LEVEL_ZERO_TILE_DELTA);
        return LatLon.fromDegrees(levelZeroLat, levelZeroLon);
    }

    protected LatLon computeRasterPixelSize(DataRaster raster)
    {
        // Compute the raster's pixel dimension in latitude and longitude. In this computation a pixel is assumed to
        // cover a finite area.
        return LatLon.fromDegrees(
            raster.getSector().getDeltaLatDegrees() / raster.getHeight(),
            raster.getSector().getDeltaLonDegrees() / raster.getWidth());
    }

    protected LatLon computeSmallestPixelSize(Iterable rasters)
    {
        // Find the smallest pixel dimensions in the given rasters.
        double smallestLat = Double.MAX_VALUE;
        double smallestLon = Double.MAX_VALUE;
        for (DataRaster raster : rasters)
        {
            LatLon curSize = this.computeRasterPixelSize(raster);
            if (smallestLat > curSize.getLatitude().degrees)
                smallestLat = curSize.getLatitude().degrees;
            if (smallestLon > curSize.getLongitude().degrees)
                smallestLon = curSize.getLongitude().degrees;
        }
        return LatLon.fromDegrees(smallestLat, smallestLon);
    }

    protected int computeNumLevels(LatLon levelZeroDelta, LatLon lastLevelDelta)
    {
        // Compute the number of levels needed to achieve the given last level tile delta, starting from the given
        // level zero tile delta.
        double numLatLevels = WWMath.logBase2(levelZeroDelta.getLatitude().getDegrees())
            - WWMath.logBase2(lastLevelDelta.getLatitude().getDegrees());
        double numLonLevels = WWMath.logBase2(levelZeroDelta.getLongitude().getDegrees())
            - WWMath.logBase2(lastLevelDelta.getLongitude().getDegrees());

        // Compute the maximum number of levels needed, but limit the number of levels to positive integers greater
        // than or equal to one.
        int numLevels = (int) Math.ceil(Math.max(numLatLevels, numLonLevels));
        if (numLevels < 1)
            numLevels = 1;

        return numLevels;
    }

    //**************************************************************//
    //********************  DataRaster Assembly  *******************//
    //**************************************************************//

    protected void assembleDataRasters() throws Exception
    {
        // Exit if the caller has instructed us to stop production.
        if (this.isStopped())
            return;

        for (SourceInfo info : this.getDataSourceList())
        {
            // Exit if the caller has instructed us to stop production.
            if (this.isStopped())
                break;

            Thread.sleep(0);

            // Don't validate the data source here. Data sources are validated when they're passed to the producer in
            // offerDataSource() or offerAllDataSources().
            this.assembleDataSource(info.source, info);
        }
    }

    protected void assembleDataSource(Object source, AVList params) throws Exception
    {
        if (source instanceof DataRaster)
        {
            this.dataRasterList.add((DataRaster) source);
        }
        else
        {
            DataRasterReader reader = this.readerFactory.findReaderFor(source, params, this.getDataRasterReaders());
            this.dataRasterList.add(new CachedDataRaster(source, params, reader, this.getCache()));
        }
    }

    protected static MemoryCache createDefaultCache()
    {
        long cacheSize = Configuration.getLongValue(AVKey.TILED_RASTER_PRODUCER_CACHE_SIZE,
            DEFAULT_TILED_RASTER_PRODUCER_CACHE_SIZE);
        return new BasicMemoryCache((long) (0.8 * cacheSize), cacheSize);
    }

    //**************************************************************//
    //********************  LevelSet Installation  *****************//
    //**************************************************************//

    protected void installLevelSet(LevelSet levelSet, AVList params) throws java.io.IOException
    {
        // Exit if the caller has instructed us to stop production.
        if (this.isStopped())
            return;

        // Setup the progress parameters.
        this.calculateTileCount(levelSet, params);
        this.startProgress();

        Sector sector = levelSet.getSector();
        Level level = levelSet.getFirstLevel();

        Angle dLat = level.getTileDelta().getLatitude();
        Angle dLon = level.getTileDelta().getLongitude();
        Angle latOrigin = levelSet.getTileOrigin().getLatitude();
        Angle lonOrigin = levelSet.getTileOrigin().getLongitude();
        int firstRow = Tile.computeRow(dLat, sector.getMinLatitude(), latOrigin);
        int firstCol = Tile.computeColumn(dLon, sector.getMinLongitude(), lonOrigin);
        int lastRow = Tile.computeRow(dLat, sector.getMaxLatitude(), latOrigin);
        int lastCol = Tile.computeColumn(dLon, sector.getMaxLongitude(), lonOrigin);

        buildLoop:
        {
            Angle p1 = Tile.computeRowLatitude(firstRow, dLat, latOrigin);
            for (int row = firstRow; row <= lastRow; row++)
            {
                Angle p2 = p1.add(dLat);
                Angle t1 = Tile.computeColumnLongitude(firstCol, dLon, lonOrigin);
                for (int col = firstCol; col <= lastCol; col++)
                {
                    // Exit if the caller has instructed us to stop production.
                    Thread.yield();
                    if (this.isStopped())
                        break buildLoop;

                    Angle t2 = t1.add(dLon);

                    Tile tile = new Tile(new Sector(p1, p2, t1, t2), level, row, col);
                    DataRaster tileRaster = this.createTileRaster(levelSet, tile, params);
                    // Write the top-level tile raster to disk.
                    if (tileRaster != null)
                        this.installTileRasterLater(levelSet, tile, tileRaster, params);

                    t1 = t2;
                }
                p1 = p2;
            }
        }
    }

    protected DataRaster createTileRaster(LevelSet levelSet, Tile tile, AVList params) throws java.io.IOException
    {
        // Exit if the caller has instructed us to stop production.
        if (this.isStopped())
            return null;

        DataRaster tileRaster;

        // If we have reached the final level, then create a tile raster from the original data sources.
        if (this.isFinalLevel(levelSet, tile.getLevelNumber(), params))
        {
            tileRaster = this.drawDataSources(levelSet, tile, this.dataRasterList, params);
        }
        // Otherwise, recursively create a tile raster from the next level's tile rasters.
        else
        {
            tileRaster = this.drawDescendants(levelSet, tile, params);
        }

        this.updateProgress();

        return tileRaster;
    }

    protected DataRaster drawDataSources(LevelSet levelSet, Tile tile, Iterable dataRasters, AVList params)
        throws java.io.IOException
    {
        DataRaster tileRaster = null;

        // Find the data sources that intersect this tile and intersect the LevelSet sector.
        java.util.ArrayList intersectingRasters = new java.util.ArrayList();
        for (DataRaster raster : dataRasters)
        {
            if (raster.getSector().intersects(tile.getSector()) && raster.getSector().intersects(levelSet.getSector()))
                intersectingRasters.add(raster);
        }

        // If any data sources intersect this tile, and the tile's level is not empty, then we attempt to read those
        // sources and render them into this tile.
        if (!intersectingRasters.isEmpty() && !tile.getLevel().isEmpty())
        {
            // Create the tile raster to render into.
            tileRaster = this.createDataRaster(tile.getLevel().getTileWidth(), tile.getLevel().getTileHeight(),
                tile.getSector(), params);
            // Render each data source raster into the tile raster.
            for (DataRaster raster : intersectingRasters)
            {
                raster.drawOnTo(tileRaster);
            }
        }

        // Make the data rasters available for garbage collection.
        intersectingRasters.clear();
        //noinspection UnusedAssignment
        intersectingRasters = null;

        return tileRaster;
    }

    protected DataRaster drawDescendants(LevelSet levelSet, Tile tile, AVList params) throws java.io.IOException
    {
        DataRaster tileRaster = null;
        boolean hasDescendants = false;

        // Recursively create sub-tile rasters.
        Tile[] subTiles = this.createSubTiles(tile, levelSet.getLevel(tile.getLevelNumber() + 1));
        DataRaster[] subRasters = new DataRaster[subTiles.length];
        for (int index = 0; index < subTiles.length; index++)
        {
            // If the sub-tile does not intersect the level set, then skip that sub-tile.
            if (subTiles[index].getSector().intersects(levelSet.getSector()))
            {
                // Recursively create the sub-tile raster.
                DataRaster subRaster = this.createTileRaster(levelSet, subTiles[index], params);
                // If creating the sub-tile raster fails, then skip that sub-tile.
                if (subRaster != null)
                {
                    subRasters[index] = subRaster;
                    hasDescendants = true;
                }
            }
        }

        // Exit if the caller has instructed us to stop production.
        if (this.isStopped())
            return null;

        // If any of the sub-tiles successfully created a data raster, then we potentially create this tile's raster,
        // then write the sub-tiles to disk.
        if (hasDescendants)
        {
            // If this tile's level is not empty, then create and render the tile's raster.
            if (!tile.getLevel().isEmpty())
            {
                // Create the tile's raster.
                tileRaster = this.createDataRaster(tile.getLevel().getTileWidth(), tile.getLevel().getTileHeight(),
                    tile.getSector(), params);

                for (int index = 0; index < subTiles.length; index++)
                {
                    if (subRasters[index] != null)
                    {
                        // Render the sub-tile raster to this this tile raster.
                        subRasters[index].drawOnTo(tileRaster);
                        // Write the sub-tile raster to disk.
                        this.installTileRasterLater(levelSet, subTiles[index], subRasters[index], params);
                    }
                }
            }
        }

        // Make the sub-tiles and sub-rasters available for garbage collection.
        for (int index = 0; index < subTiles.length; index++)
        {
            subTiles[index] = null;
            subRasters[index] = null;
        }
        //noinspection UnusedAssignment
        subTiles = null;
        //noinspection UnusedAssignment
        subRasters = null;

        return tileRaster;
    }

    protected Tile[] createSubTiles(Tile tile, Level nextLevel)
    {
        Angle p0 = tile.getSector().getMinLatitude();
        Angle p2 = tile.getSector().getMaxLatitude();
        Angle p1 = Angle.midAngle(p0, p2);

        Angle t0 = tile.getSector().getMinLongitude();
        Angle t2 = tile.getSector().getMaxLongitude();
        Angle t1 = Angle.midAngle(t0, t2);

        int row = tile.getRow();
        int col = tile.getColumn();

        Tile[] subTiles = new Tile[4];
        subTiles[0] = new Tile(new Sector(p0, p1, t0, t1), nextLevel, 2 * row, 2 * col);
        subTiles[1] = new Tile(new Sector(p0, p1, t1, t2), nextLevel, 2 * row, 2 * col + 1);
        subTiles[2] = new Tile(new Sector(p1, p2, t1, t2), nextLevel, 2 * row + 1, 2 * col + 1);
        subTiles[3] = new Tile(new Sector(p1, p2, t0, t1), nextLevel, 2 * row + 1, 2 * col);

        return subTiles;
    }

    protected boolean isFinalLevel(LevelSet levelSet, int levelNumber, AVList params)
    {
        if (levelSet.isFinalLevel(levelNumber))
            return true;

        int maxNumOfLevels = levelSet.getLastLevel().getLevelNumber();
        int limit = this.extractMaxLevelLimit( params, maxNumOfLevels );
        return (levelNumber >= limit);
    }

    /**
     * Extracts a maximum level limit from the AVList if the AVList contains AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL.
     * This method requires maxNumOfLevels - the actual maximum numbers of levels.
     *
     * The AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL could specify multiple things:
     *
     * If the value of the AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL is "Auto" (as String),
     * the calculated limit of levels will be 70% of the actual maximum numbers of levels maxNumOfLevels.
     *
     * If the type of the value of the AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL is Integer,
     * it should contain an integer number between 0 (for level 0 only) and the actual maximum
     * numbers of levels maxNumOfLevels.
     *
     * It is also possible to specify the limit as percents, in this case the type of the
     * AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL value must be "String", have a numeric value as text and
     * the "%" percent sign in the end. Examples: "100%", "25%", "50%", etc.
     *
     * Value of AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL could be a numeric string (for example, "3"),
     * or Integer. The value will be correctly extracted and compared with the maxNumOfLevels.
     * Valid values must be smaller or equal to maxNumOfLevels.
     *
     * @param params AVList that may contain AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL property
     * @param maxNumOfLevels The actual maximum numbers of levels
     *
     * @return A limit of numbers of levels that should producer generate.
     *
     */
    protected int extractMaxLevelLimit(AVList params, int maxNumOfLevels)
    {
        if (null != params && params.hasKey(AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL))
        {
            Object o = params.getValue(AVKey.TILED_RASTER_PRODUCER_LIMIT_MAX_LEVEL);
            if (o instanceof Integer)
            {
                int limit = (Integer) o;
                return (limit <= maxNumOfLevels) ? limit : maxNumOfLevels;
            }
            else if (o instanceof String)
            {
                String strLimit = (String) o;
                if ("Auto".equalsIgnoreCase(strLimit))
                {
                    return (int)Math.floor( 0.5d * (double)maxNumOfLevels ); // 0.5 = half, 0.6 = 60%
                }
                else if( strLimit.endsWith("%"))
                {
                    try
                    {
                        float percent = Float.parseFloat( strLimit.substring(0, strLimit.length()-1) );
                        int limit = (int)Math.floor( percent * (double)maxNumOfLevels / 100d );
                        return (limit <= maxNumOfLevels) ? limit :maxNumOfLevels;
                    }
                    catch (Throwable t)
                    {
                        Logging.logger().finest( WWUtil.extractExceptionReason(t));
                    }
                }
                else
                {
                    try
                    {
                        int limit = Integer.parseInt(strLimit);
                        return (limit <= maxNumOfLevels) ? limit :maxNumOfLevels;
                    }
                    catch (Throwable t)
                    {
                        Logging.logger().finest( WWUtil.extractExceptionReason(t));
                    }
                }
            }
        }

        return maxNumOfLevels;
    }

    //**************************************************************//
    //********************  Tile Installation  *********************//
    //**************************************************************//

    protected java.util.concurrent.ExecutorService createDefaultTileWriteService(int threadPoolSize)
    {
        // TODO: comment

        // Create a fixed thread pool, but provide a callback to release a tile write permit when a task completes.
        return new java.util.concurrent.ThreadPoolExecutor(
            // Fixed size thread pool.
            threadPoolSize, threadPoolSize,
            // This value is irrelevant, as threads only terminated when the executor is shutdown.
            0L, java.util.concurrent.TimeUnit.MILLISECONDS,
            // Provide an unbounded work queue.
            new java.util.concurrent.LinkedBlockingQueue())
        {
            protected void afterExecute(Runnable runnable, Throwable t)
            {
                // Invoke the superclass routine, then release a tile write permit.
                super.afterExecute(runnable, t);
                TiledRasterProducer.this.installTileRasterComplete();
            }
        };
    }

    protected void installTileRasterLater(final LevelSet levelSet, final Tile tile, final DataRaster tileRaster,
        final AVList params)
    {
        // TODO: comment
        // Try to acquire a permit from the tile write semaphore.
        this.getTileWriteSemaphore().acquireUninterruptibly();
        // We've acquired the permit, now execute the installTileRaster() routine in a different thread.
        this.getTileWriteService().execute(new Runnable()
        {
            public void run()
            {
                try
                {
                    installTileRaster(tile, tileRaster, params);
                    // Dispose the data raster.
                    if (tileRaster instanceof Disposable)
                        ((Disposable) tileRaster).dispose();
                }
                catch (Throwable t)
                {
                    String message = Logging.getMessage("generic.ExceptionWhileWriting", tile);
                    Logging.logger().log(java.util.logging.Level.SEVERE, message, t);
                }
            }
        });
    }

    protected void installTileRasterComplete()
    {
        // TODO: comment
        this.getTileWriteSemaphore().release();
    }

    protected void waitForInstallTileTasks()
    {
        // TODO: comment
        try
        {
            java.util.concurrent.ExecutorService service = this.getTileWriteService();
            service.shutdown();
            // Block this thread until the executor has completed.
            while (!service.awaitTermination(1000L, java.util.concurrent.TimeUnit.MILLISECONDS))
            {
                Thread.sleep(5L);
            }
        }
        catch (InterruptedException e)
        {
            String msg = Logging.getMessage("generic.interrupted", this.getClass().getName(),
                "waitForInstallTileTasks()");
            Logging.logger().finest(msg);
            // Don't swallow interrupts; instead, restore the interrupted status
            Thread.currentThread().interrupt();
        }
    }

    protected void installTileRaster(Tile tile, DataRaster tileRaster, AVList params) throws java.io.IOException
    {
        java.io.File installLocation;

        // Compute the install location of the tile.
        Object result = this.installLocationForTile(params, tile);
        if (result instanceof java.io.File)
        {
            installLocation = (java.io.File) result;
        }
        else
        {
            String message = result.toString();
            Logging.logger().severe(message);
            throw new java.io.IOException(message);
        }

        synchronized (this.fileLock)
        {
            java.io.File dir = installLocation.getParentFile();
            if (!dir.exists())
            {
                if (!dir.mkdirs())
                {
                    String message = Logging.getMessage("generic.CannotCreateFile", dir);
                    Logging.logger().warning(message);
                }
            }
        }

        // Write the tile data to the filesystem.
        String formatSuffix = params.getStringValue(AVKey.FORMAT_SUFFIX);
        DataRasterWriter[] writers = this.getDataRasterWriters();

        Object writer = this.findWriterFor(tileRaster, formatSuffix, installLocation, writers);
        if (writer instanceof DataRasterWriter)
        {
            try
            {
                ((DataRasterWriter) writer).write(tileRaster, formatSuffix, installLocation);
            }
            catch (java.io.IOException e)
            {
                String message = Logging.getMessage("generic.ExceptionWhileWriting", installLocation);
                Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
            }
        }
    }

    protected Object installLocationForTile(AVList installParams, Tile tile)
    {
        String path = null;

        String s = installParams.getStringValue(AVKey.FILE_STORE_LOCATION);
        if (s != null)
            path = WWIO.appendPathPart(path, s);

        s = tile.getPath();
        if (s != null)
            path = WWIO.appendPathPart(path, s);

        if (path == null || path.length() < 1)
            return Logging.getMessage("TiledRasterProducer.InvalidTile", tile);

        return new java.io.File(path);
    }

    protected Object findWriterFor(DataRaster raster, String formatSuffix, java.io.File destination,
        DataRasterWriter[] writers)
    {
        for (DataRasterWriter writer : writers)
        {
            if (writer.canWrite(raster, formatSuffix, destination))
                return writer;
        }

        // No writer maching this DataRaster/formatSuffix.
        return Logging.getMessage("DataRaster.CannotWrite", raster, formatSuffix, destination);
    }

    //**************************************************************//
    //********************  Config File Installation  **************//
    //**************************************************************//

    /**
     * Returns a configuration document which describes the tiled data produced by this TiledRasterProducer. The
     * document's contents are derived from the specified parameter list, and depend on the concrete subclass'
     * implementation. This returns null if the parameter list is null, or if the configuration document cannot be
     * created for any reason.
     *
     * @param params the parameters which describe the configuration document's contents.
     *
     * @return the configuration document, or null if the parameter list is null or does not contain the required
     *         parameters.
     */
    protected abstract Document createConfigDoc(AVList params);

    /**
     * Installs the configuration file which describes the tiled data produced by this TiledRasterProducer. The install
     * location, configuration filename, and configuration file contents are derived from the specified parameter list.
     * This throws an exception if the configuration file cannot be installed for any reason.
     * 

* The parameter list must contain at least the following keys:

*
Key
{@link gov.nasa.worldwind.avlist.AVKey#FILE_STORE_LOCATION}
{@link * gov.nasa.worldwind.avlist.AVKey#DATA_CACHE_NAME}
{@link * gov.nasa.worldwind.avlist.AVKey#DATASET_NAME}
* * @param params the parameters which describe the install location, the configuration filename, and the * configuration file contents. * * @throws Exception if the configuration file cannot be installed for any reason. * @throws IllegalArgumentException if the parameter list is null. */ protected void installConfigFile(AVList params) throws Exception { if (params == null) { String message = Logging.getMessage("nullValue.ParametersIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Exit if the caller has instructed us to stop production. if (this.isStopped()) return; File configFile = this.getConfigFileInstallLocation(params); if (configFile == null) { String message = Logging.getMessage("TiledRasterProducer.NoConfigFileInstallLocation", params.getValue(AVKey.DATASET_NAME)); Logging.logger().severe(message); throw new WWRuntimeException(message); } // Synchronize construction of the config file's parent directories. One or more tile installation tasks may be // running when this code executes. This synchronizes construction of common parent directories between with the // tile installation tasks. synchronized (this.fileLock) { java.io.File dir = configFile.getParentFile(); if (!dir.exists()) { if (!dir.mkdirs()) { String message = Logging.getMessage("generic.CannotCreateFile", dir); Logging.logger().warning(message); } } } Document configDoc = this.createConfigDoc(params); if (configDoc == null) { String message = Logging.getMessage("TiledRasterProducer.CannotCreateConfigDoc", params.getValue(AVKey.DATASET_NAME)); Logging.logger().severe(message); throw new WWRuntimeException(message); } try { WWXML.saveDocumentToFile(configDoc, configFile.getAbsolutePath()); } catch (Exception e) { String message = Logging.getMessage("TiledRasterProducer.CannotWriteConfigFile", configFile); Logging.logger().severe(message); throw new WWRuntimeException(message); } this.getProductionResultsList().add(configDoc); } /** * Returns the location of the configuration file which describes the tiled data produced by this * TiledRasterProducer. The install location is derived from the specified parameter list. This returns null if the * parameter list is null, or if it does not contain any of the following keys: *
Key
{@link gov.nasa.worldwind.avlist.AVKey#FILE_STORE_LOCATION}
{@link * gov.nasa.worldwind.avlist.AVKey#DATA_CACHE_NAME}
{@link * gov.nasa.worldwind.avlist.AVKey#DATASET_NAME}
* * @param params the parameters which describe the install location. * * @return the configuration file install location, or null if the parameter list is null or does not contain the * required parameters. */ protected File getConfigFileInstallLocation(AVList params) { if (params == null) return null; String fileStoreLocation = params.getStringValue(AVKey.FILE_STORE_LOCATION); if (fileStoreLocation != null) fileStoreLocation = WWIO.stripTrailingSeparator(fileStoreLocation); if (WWUtil.isEmpty(fileStoreLocation)) return null; String cacheName = DataConfigurationUtils.getDataConfigFilename(params, ".xml"); if (cacheName != null) cacheName = WWIO.stripLeadingSeparator(cacheName); if (WWUtil.isEmpty(cacheName)) return null; return new File(fileStoreLocation + File.separator + cacheName); } //**************************************************************// //******************** Progress ******************************// //**************************************************************// protected void calculateTileCount(LevelSet levelSet, AVList params) { Sector sector = levelSet.getSector(); this.tileCount = 0; for (Level level : levelSet.getLevels()) { Angle dLat = level.getTileDelta().getLatitude(); Angle dLon = level.getTileDelta().getLongitude(); Angle latOrigin = levelSet.getTileOrigin().getLatitude(); Angle lonOrigin = levelSet.getTileOrigin().getLongitude(); int firstRow = Tile.computeRow(dLat, sector.getMinLatitude(), latOrigin); int firstCol = Tile.computeColumn(dLon, sector.getMinLongitude(), lonOrigin); int lastRow = Tile.computeRow(dLat, sector.getMaxLatitude(), latOrigin); int lastCol = Tile.computeColumn(dLon, sector.getMaxLongitude(), lonOrigin); this.tileCount += (lastRow - firstRow + 1) * (lastCol - firstCol + 1); if (this.isFinalLevel(levelSet, level.getLevelNumber(), params)) break; } } protected void startProgress() { this.tile = 0; this.firePropertyChange(AVKey.PROGRESS, null, 0d); } protected void updateProgress() { double oldProgress = this.tile / (double) this.tileCount; double newProgress = ++this.tile / (double) this.tileCount; this.firePropertyChange(AVKey.PROGRESS, oldProgress, newProgress); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy