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

gov.nasa.worldwind.terrain.CompoundElevationModel Maven / Gradle / Ivy

The 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.terrain;

import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.ElevationModel;
import gov.nasa.worldwind.util.Logging;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author tag
 * @version $Id: CompoundElevationModel.java 3417 2015-08-20 20:47:05Z tgaskins $
 */
public class CompoundElevationModel extends AbstractElevationModel
{
    protected CopyOnWriteArrayList elevationModels = new CopyOnWriteArrayList();

    public void dispose()
    {
        for (ElevationModel child : this.elevationModels)
        {
            if (child != null)
                child.dispose();
        }
    }

    /**
     * Returns true if this CompoundElevationModel contains the specified ElevationModel, and false otherwise.
     *
     * @param em the ElevationModel to test.
     *
     * @return true if the ElevationModel is in this CompoundElevationModel; false otherwise.
     *
     * @throws IllegalArgumentException if the ElevationModel is null.
     */
    public boolean containsElevationModel(ElevationModel em)
    {
        if (em == null)
        {
            String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Check if the elevation model is one of the models in our list.
        if (this.elevationModels.contains(em))
            return true;

        // Check if the elevation model is a child of any CompoundElevationModels in our list.
        for (ElevationModel child : this.elevationModels)
        {
            if (child instanceof CompoundElevationModel)
            {
                if (((CompoundElevationModel) child).containsElevationModel(em))
                    return true;
            }
        }

        return false;
    }

    protected void sortElevationModels()
    {
        if (this.elevationModels.size() == 1)
            return;

        List temp = new ArrayList(this.elevationModels.size());
        for (ElevationModel em : this.elevationModels)
        {
            temp.add(em);
        }

        Collections.sort(temp, new Comparator()
        {
            @Override
            public int compare(ElevationModel o1, ElevationModel o2)
            {
                double res1 = o1.getBestResolution(null);
                double res2 = o2.getBestResolution(null);

                // sort from lowest resolution to highest
                return res1 > res2 ? -1 : res1 == res2 ? 0 : 1;
            }
        });

        this.elevationModels.removeAll(temp);
        this.elevationModels.addAll(temp);
    }

    /**
     * Adds an elevation to this compound elevation model. The list of elevation models for this class is sorted from
     * lowest resolution to highest. This method inserts the specified elevation model at the appropriate position in
     * the list, and as a side effect resorts the entire list.
     *
     * @param em The elevation model to add.
     *
     * @throws IllegalArgumentException if the specified elevation model is null.
     */
    public void addElevationModel(ElevationModel em)
    {
        if (em == null)
        {
            String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.elevationModels.add(em);
        this.sortElevationModels();
    }

    /**
     * Adds a specified elevation model to a specified position in this compound elevation model's elevation model list.
     * It's expected that this class' elevation model list is sorted from lowest resolution to highest. The method
     * {@link #addElevationModel(gov.nasa.worldwind.globes.ElevationModel)} inserts added elevation models at the
     * appropriate place in the list. This method, however, inserts the elevation model at the specified position in the
     * list. For proper operation of this compound elevation model, the caller should ensure that the specified position
     * is the appropriate one for the inserted elevation model's resolution.
     *
     * @param index The position at which to insert the specified model, zero origin. Existing models are shifted to the
     *              right.
     * @param em    The elevation model to insert.
     *
     * @throws IllegalArgumentException  if the specified elevation model is null.
     * @throws IndexOutOfBoundsException if the specified index is invalid.
     */
    public void addElevationModel(int index, ElevationModel em)
    {
        if (em == null)
        {
            String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.elevationModels.add(index, em); // the list's add method will throw exception for invalid index
    }

    public void removeElevationModel(ElevationModel em)
    {
        if (em == null)
        {
            String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        for (ElevationModel child : this.elevationModels)
        {
            if (child instanceof CompoundElevationModel)
                ((CompoundElevationModel) child).removeElevationModel(em);
        }

        this.elevationModels.remove(em);
    }

    public void removeElevationModel(int index)
    {
        if (index < 0 || index >= this.elevationModels.size())
        {
            String msg = Logging.getMessage("generic.indexOutOfRange", index);
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.elevationModels.remove(index);
    }

    public void setElevationModel(int index, ElevationModel em)
    {
        if (em == null)
        {
            String msg = Logging.getMessage("nullValue.ElevationModelIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (index < 0 || index >= this.elevationModels.size())
        {
            String msg = Logging.getMessage("generic.indexOutOfRange", index);
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.elevationModels.set(index, em);
    }

    public List getElevationModels()
    {
        return new ArrayList(this.elevationModels);
    }

    /**
     * Recursively specifies the time of the most recent dataset update for this compound model, and for each elevation
     * model contained within this compound model. Any cached data older than this time is invalid.
     *
     * @param expiryTime the expiry time of any cached data, expressed as a number of milliseconds beyond the epoch.
     *
     * @see System#currentTimeMillis() for a description of milliseconds beyond the epoch.
     */
    public void setExpiryTime(long expiryTime)
    {
        super.setExpiryTime(expiryTime);

        for (ElevationModel em : this.elevationModels)
        {
            em.setExpiryTime(expiryTime);
        }
    }

    public double getMaxElevation() // TODO: probably want to cache the min and max rather than always compute them
    {
        double max = -Double.MAX_VALUE;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            double m = em.getMaxElevation();
            if (m > max)
                max = m;
        }

        return max == -Double.MAX_VALUE ? 0 : max;
    }

    public double getMinElevation()
    {
        double min = Double.MAX_VALUE;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            double m = em.getMinElevation();
            if (m < min)
                min = m;
        }

        return min == Double.MAX_VALUE ? 0 : min;
    }

    public double[] getExtremeElevations(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double[] retVal = null;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            double[] minmax = em.getExtremeElevations(latitude, longitude);
            if (retVal == null)
            {
                retVal = new double[] {minmax[0], minmax[1]};
            }
            else
            {
                if (minmax[0] < retVal[0])
                    retVal[0] = minmax[0];
                if (minmax[1] > retVal[1])
                    retVal[1] = minmax[1];
            }
        }

        return retVal == null ? new double[] {0, 0} : retVal;
    }

    public double[] getExtremeElevations(Sector sector)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double[] retVal = null;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            int c = em.intersects(sector);
            if (c < 0) // no intersection
                continue;

            double[] minmax = em.getExtremeElevations(sector);
            if (retVal == null)
            {
                retVal = new double[] {minmax[0], minmax[1]};
            }
            else
            {
                if (minmax[0] < retVal[0])
                    retVal[0] = minmax[0];
                if (minmax[1] > retVal[1])
                    retVal[1] = minmax[1];
            }
        }

        return retVal == null ? new double[] {0, 0} : retVal;
    }

    public double getBestResolution(Sector sector)
    {
        double res = 0;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            if (sector != null && em.intersects(sector) < 0) // sector does not intersect elevation model
                continue;

            double r = em.getBestResolution(sector);
            if (r < res || res == 0)
                res = r;
        }

        return res != 0 ? res : Double.MAX_VALUE;
    }

    @Override
    public double[] getBestResolutions(Sector sector)
    {
        double[] res = new double[this.elevationModels.size()];

        for (int i = 0; i < this.elevationModels.size(); i++)
        {
            res[i] = this.elevationModels.get(i).getBestResolution(sector);
        }

        return res;
    }

    public double getDetailHint(Sector sector)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        // Find the first elevation model intersecting the sector, starting with the hightest resolution. Return the
        // detail hint for that elevation model.
        for (int i = this.elevationModels.size() - 1; i >= 0; i--) // iterate from highest resolution to lowest
        {
            ElevationModel em = this.elevationModels.get(i);

            if (!em.isEnabled())
                continue;

            int c = em.intersects(sector);
            if (c != -1)
                return em.getDetailHint(sector);
        }

        // No elevation model intersects the sector. Return a default detail hint.
        return 0.0;
    }

    public int intersects(Sector sector)
    {
        if (sector == null)
        {
            String msg = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        boolean intersects = false;

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            int c = em.intersects(sector);
            if (c == 0) // sector fully contained in the elevation model. no need to test further
                return 0;

            if (c == 1)
                intersects = true;
        }

        return intersects ? 1 : -1;
    }

    public boolean contains(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatLonIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        for (ElevationModel em : this.elevationModels)
        {
            if (!em.isEnabled())
                continue;

            if (em.contains(latitude, longitude))
                return true;
        }

        return false;
    }

    public double getUnmappedElevation(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatLonIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Find the best elevation available at the specified (latitude, longitude) coordinates.
        Double value = this.missingDataFlag;
        for (int i = this.elevationModels.size() - 1; i >= 0; i--) // iterate from highest resolution to lowest
        {
            ElevationModel em = this.elevationModels.get(i);

            if (!em.isEnabled())
                continue;

            if (!em.contains(latitude, longitude))
                continue;

            double emValue = em.getUnmappedElevation(latitude, longitude);

            // Since we're working from highest resolution to lowest, we return the first value that's not a missing
            // data flag. Check this against the current ElevationModel's missing data flag, which might be different
            // from our own.
            if (emValue != em.getMissingDataSignal())
            {
                value = emValue;
                break;
            }
        }

        return value;
    }

    /**
     * {@inheritDoc}
     * 

* NOTE: This method returns only unmapped elevations if the compound model contains more than one elevation model. * This enables the compound model's lower resolution elevation models to specify missing data values for the higher * resolution elevation models. * * @param sector the sector in question. * @param latlons the locations to return elevations for. If a location is null, the output buffer for that * location is not modified. * @param targetResolution the desired horizontal resolution, in radians, of the raster or other elevation sample * from which elevations are drawn. (To compute radians from a distance, divide the distance * by the radius of the globe, ensuring that both the distance and the radius are in the * same units.) * @param buffer an array in which to place the returned elevations. The array must be pre-allocated and * contain at least as many elements as the list of locations. * * @return the resolution achieved, in radians, or {@link Double#MAX_VALUE} if individual elevations cannot be * determined for all of the locations. */ public double getElevations(Sector sector, List latlons, double targetResolution, double[] buffer) { double[] targetResolutions = new double[this.elevationModels.size()]; for (int i = 0; i < targetResolutions.length; i++) { targetResolutions[i] = targetResolution; } return this.doGetElevations(sector, latlons, targetResolutions, buffer, false)[0]; } /** * {@inheritDoc} *

* NOTE: This method returns only unmapped elevations if the compound model contains more than one elevation model. * This enables the compound model's lower resolution elevation models to specify missing data values for the higher * resolution elevation models. * * @param sector the sector in question. * @param latlons the locations to return elevations for. If a location is null, the output buffer for that * location is not modified. * @param targetResolution the desired horizontal resolution, in radians, of the raster or other elevation sample * from which elevations are drawn. (To compute radians from a distance, divide the distance * by the radius of the globe, ensuring that both the distance and the radius are in the * same units.) * @param buffer an array in which to place the returned elevations. The array must be pre-allocated and * contain at least as many elements as the list of locations. * * @return the resolution achieved, in radians, or {@link Double#MAX_VALUE} if individual elevations cannot be * determined for all of the locations. */ public double getUnmappedElevations(Sector sector, List latlons, double targetResolution, double[] buffer) { double[] targetResolutions = new double[this.elevationModels.size()]; for (int i = 0; i < targetResolutions.length; i++) { targetResolutions[i] = targetResolution; } return this.doGetElevations(sector, latlons, targetResolutions, buffer, false)[0]; } @Override public double[] getElevations(Sector sector, List latLons, double[] targetResolutions, double[] elevations) { return this.doGetElevations(sector, latLons, targetResolutions, elevations, false); } @Override public double[] getUnmappedElevations(Sector sector, List latLons, double[] targetResolutions, double[] elevations) { return this.doGetElevations(sector, latLons, targetResolutions, elevations, false); } protected double[] doGetElevations(Sector sector, List latlons, double[] targetResolution, double[] buffer, boolean mapMissingData) { if (sector == null) { String msg = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (latlons == null) { String msg = Logging.getMessage("nullValue.LatLonListIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (targetResolution == null) { String msg = Logging.getMessage("nullValue.TargetElevationsArrayIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (buffer == null) { String msg = Logging.getMessage("nullValue.ElevationsBufferIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (buffer.length < latlons.size()) { String msg = Logging.getMessage("ElevationModel.ElevationsBufferTooSmall", latlons.size()); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } // Fill the buffer with ElevationModel contents from lowest resolution to highest, potentially overwriting // values at each step. ElevationModels are expected to leave the buffer untouched for locations outside their // coverage area. double[] resolutionAchieved = new double[this.elevationModels.size()]; for (int i = 0; i < this.elevationModels.size(); i++) { ElevationModel em = this.elevationModels.get(i); resolutionAchieved[i] = 0; if (!em.isEnabled()) continue; int c = em.intersects(sector); if (c < 0) // no intersection continue; double r; if (mapMissingData || this.elevationModels.size() == 1) r = em.getElevations(sector, latlons, targetResolution[i], buffer); else r = em.getUnmappedElevations(sector, latlons, targetResolution[i], buffer); if (r < resolutionAchieved[i] || resolutionAchieved[i] == 0) resolutionAchieved[i] = r; } return resolutionAchieved; } public void composeElevations(Sector sector, List latlons, int tileWidth, double[] buffer) throws Exception { if (sector == null) { String msg = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (latlons == null) { String msg = Logging.getMessage("nullValue.LatLonListIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (buffer == null) { String msg = Logging.getMessage("nullValue.ElevationsBufferIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (buffer.length < latlons.size()) { String msg = Logging.getMessage("ElevationModel.ElevationsBufferTooSmall", latlons.size()); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } // Fill the buffer with ElevationModel contents from back to front, potentially overwriting values at each step. // ElevationModels are expected to leave the buffer untouched when data is missing at a location. for (ElevationModel em : this.elevationModels) { if (!em.isEnabled()) continue; int c = em.intersects(sector); if (c < 0) // no intersection continue; em.composeElevations(sector, latlons, tileWidth, buffer); } } public void setNetworkRetrievalEnabled(boolean networkRetrievalEnabled) { super.setNetworkRetrievalEnabled(networkRetrievalEnabled); for (ElevationModel em : this.elevationModels) { em.setNetworkRetrievalEnabled(networkRetrievalEnabled); } } @Override public double getLocalDataAvailability(Sector sector, Double targetResolution) { int models = 0; double availability = 0; for (ElevationModel em : this.elevationModels) { if (!em.isEnabled()) continue; if (em.intersects(sector) >= 0) { availability += em.getLocalDataAvailability(sector, targetResolution); models++; } } return models > 0 ? availability / models : 1d; } @Override public void setExtremesCachingEnabled(boolean enabled) { for (ElevationModel em : this.elevationModels) { em.setExtremesCachingEnabled(enabled); } } @Override public boolean isExtremesCachingEnabled() { for (ElevationModel em : this.elevationModels) { if (em.isExtremesCachingEnabled()) return true; } return false; } /** * Returns the elevation for this elevation model's highest level of detail at a specified location if the source * file for that level and the specified location exists in the local elevation cache on disk. * Note that this method consults only those elevation models whose type is {@link BasicElevationModel}. * @param latitude The latitude of the location whose elevation is desired. * @param longitude The longitude of the location whose elevation is desired. * @return The elevation at the specified location, if that location is contained in this elevation model and the * source file for the highest-resolution elevation at that location exists in the current disk cache. Otherwise * this elevation model's missing data signal is returned (see {@link #getMissingDataSignal()}). */ public double getUnmappedLocalSourceElevation(Angle latitude, Angle longitude) { double elevation = this.getMissingDataSignal(); // Traverse the elevation model list from highest resolution to lowest. for (int i = this.elevationModels.size() - 1; i >= 0; i--) { ElevationModel em = this.elevationModels.get(i); if (em instanceof BasicElevationModel && em.isEnabled()) { double e = ((BasicElevationModel) em).getUnmappedLocalSourceElevation(latitude, longitude); if (e != em.getMissingDataSignal()) { elevation = e; break; } } } return elevation; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy