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

gov.nasa.worldwind.render.airspaces.AbstractAirspace 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.render.airspaces;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.*;
import gov.nasa.worldwind.drag.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.*;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import com.jogamp.opengl.*;
import java.awt.*;
import java.nio.Buffer;
import java.util.*;
import java.util.List;

/**
 * @author dcollins
 * @version $Id: AbstractAirspace.java 3138 2015-06-02 19:13:16Z tgaskins $
 */
public abstract class AbstractAirspace extends WWObjectImpl
    implements Airspace, OrderedRenderable, PreRenderable, Movable, Movable2, Draggable
{
    protected static final String ARC_SLICES = "ArcSlices";
    protected static final String DISABLE_TERRAIN_CONFORMANCE = "DisableTerrainConformance";
    protected static final String EXPIRY_TIME = "ExpiryTime";
    protected static final String GEOMETRY_CACHE_NAME = "Airspace Geometry";
    protected static final String GEOMETRY_CACHE_KEY = Geometry.class.getName();
    protected static final String GLOBE_KEY = "GlobeKey";
    protected static final String LENGTH_SLICES = "LengthSlices";
    protected static final String LOOPS = "Loops";
    protected static final String PILLARS = "Pillars";
    protected static final String SLICES = "Slices";
    protected static final String SPLIT_THRESHOLD = "SplitThreshold";
    protected static final String STACKS = "Stacks";
    protected static final String SUBDIVISIONS = "Subdivisions";
    protected static final String VERTICAL_EXAGGERATION = "VerticalExaggeration";

    private static final long DEFAULT_GEOMETRY_CACHE_SIZE = 16777216L; // 16 megabytes
    /** The default outline pick width. */
    protected static final int DEFAULT_OUTLINE_PICK_WIDTH = 10;

    /** The default interior color. */
    protected static final Material DEFAULT_INTERIOR_MATERIAL = Material.LIGHT_GRAY;
    /** The default outline color. */
    protected static final Material DEFAULT_OUTLINE_MATERIAL = Material.DARK_GRAY;
    /** The default highlight color. */
    protected static final Material DEFAULT_HIGHLIGHT_MATERIAL = Material.WHITE;

    /** The attributes used if attributes are not specified. */
    protected static AirspaceAttributes defaultAttributes;

    static
    {
        // Create and populate the default attributes.
        defaultAttributes = new BasicAirspaceAttributes();
        defaultAttributes.setInteriorMaterial(DEFAULT_INTERIOR_MATERIAL);
        defaultAttributes.setOutlineMaterial(DEFAULT_OUTLINE_MATERIAL);
    }

    // Airspace properties.
    protected boolean visible = true;
    protected boolean highlighted;
    protected boolean dragEnabled = true;
    protected DraggableSupport draggableSupport = null;
    protected AirspaceAttributes attributes;
    protected AirspaceAttributes highlightAttributes;
    protected AirspaceAttributes activeAttributes = new BasicAirspaceAttributes(); // re-determined each frame
    protected double lowerAltitude = 0.0;
    protected double upperAltitude = 1.0;
    protected boolean lowerTerrainConforming = false;
    protected boolean upperTerrainConforming = false;
    protected String lowerAltitudeDatum = AVKey.ABOVE_MEAN_SEA_LEVEL;
    protected String upperAltitudeDatum = AVKey.ABOVE_MEAN_SEA_LEVEL;
    protected LatLon groundReference;
    protected boolean enableLevelOfDetail = true;
    protected Collection detailLevels = new TreeSet();
    // Rendering properties.
    protected boolean enableBatchRendering = true;
    protected boolean enableBatchPicking = true;
    protected boolean enableDepthOffset;
    protected int outlinePickWidth = DEFAULT_OUTLINE_PICK_WIDTH;
    protected Object delegateOwner;
    protected SurfaceShape surfaceShape;
    protected boolean mustRegenerateSurfaceShape;
    protected boolean drawSurfaceShape;
    protected long frameTimeStamp;
    protected boolean alwaysOnTop = false;
    // Geometry computation and rendering support.
    protected AirspaceInfo currentInfo;
    protected Layer pickLayer;
    protected PickSupport pickSupport = new PickSupport();
    protected GeometryBuilder geometryBuilder = new GeometryBuilder();
    // Geometry update support.
    protected long expiryTime = -1L;
    protected long minExpiryTime = 2000L;
    protected long maxExpiryTime = 6000L;
    protected static Random rand = new Random();
    // Elevation lookup map.
    protected Map elevationMap = new HashMap();
    // Implements the the interface used by the draw context's outlined-shape renderer.
    protected OutlinedShape outlineShapeRenderer = new OutlinedShape()
    {
        public boolean isDrawOutline(DrawContext dc, Object shape)
        {
            return ((AbstractAirspace) shape).mustDrawOutline(dc);
        }

        public boolean isDrawInterior(DrawContext dc, Object shape)
        {
            return ((AbstractAirspace) shape).mustDrawInterior(dc);
        }

        public void drawOutline(DrawContext dc, Object shape)
        {
            ((AbstractAirspace) shape).drawOutline(dc);
        }

        public void drawInterior(DrawContext dc, Object shape)
        {
            ((AbstractAirspace) shape).drawInterior(dc);
        }

        public boolean isEnableDepthOffset(DrawContext dc, Object shape)
        {
            return ((AbstractAirspace) shape).isEnableDepthOffset();
        }

        public Double getDepthOffsetFactor(DrawContext dc, Object shape)
        {
            return null;
        }

        public Double getDepthOffsetUnits(DrawContext dc, Object shape)
        {
            return null;
        }
    };

    // Airspaces perform about 5% better if their extent is cached, so do that here.

    protected static class AirspaceInfo
    {
        // The extent depends on the state of the globe used to compute it, and the vertical exaggeration.
        protected Extent extent;
        protected double eyeDistance;
        protected List minimalGeometry;
        protected double verticalExaggeration;
        protected Object globeStateKey;

        public AirspaceInfo(DrawContext dc, Extent extent, List minimalGeometry)
        {
            this.extent = extent;
            this.minimalGeometry = minimalGeometry;
            this.verticalExaggeration = dc.getVerticalExaggeration();
            this.globeStateKey = dc.getGlobe().getStateKey(dc);
        }

        public double getEyeDistance()
        {
            return this.eyeDistance;
        }

        public void setEyeDistance(double eyeDistance)
        {
            this.eyeDistance = eyeDistance;
        }

        public boolean isValid(DrawContext dc)
        {
            return this.verticalExaggeration == dc.getVerticalExaggeration()
                && (this.globeStateKey != null && this.globeStateKey.equals(dc.getGlobe().getStateKey(dc)));
        }
    }

    // usually only 1, but few at most
    protected HashMap airspaceInfo = new HashMap(2);

    public AbstractAirspace(AirspaceAttributes attributes)
    {
        if (attributes == null)
        {
            String message = "nullValue.AirspaceAttributesIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.attributes = attributes;

        if (!WorldWind.getMemoryCacheSet().containsCache(GEOMETRY_CACHE_KEY))
        {
            long size = Configuration.getLongValue(AVKey.AIRSPACE_GEOMETRY_CACHE_SIZE, DEFAULT_GEOMETRY_CACHE_SIZE);
            MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
            cache.setName(GEOMETRY_CACHE_NAME);
            WorldWind.getMemoryCacheSet().addCache(GEOMETRY_CACHE_KEY, cache);
        }
    }

    protected abstract Extent computeExtent(Globe globe, double verticalExaggeration);

    protected abstract List computeMinimalGeometry(Globe globe, double verticalExaggeration);

    public AbstractAirspace(AbstractAirspace source)
    {
        this(source.getAttributes());

        this.visible = source.visible;
        this.attributes = source.attributes;
        this.highlightAttributes = source.highlightAttributes;
        this.lowerAltitude = source.lowerAltitude;
        this.upperAltitude = source.upperAltitude;
        this.lowerAltitudeDatum = source.lowerAltitudeDatum;
        this.upperAltitudeDatum = source.upperAltitudeDatum;
        this.lowerTerrainConforming = source.lowerTerrainConforming;
        this.upperTerrainConforming = source.upperTerrainConforming;
        this.groundReference = source.groundReference;
        this.enableLevelOfDetail = source.enableLevelOfDetail;
        this.enableBatchPicking = source.enableBatchPicking;
        this.enableBatchRendering = source.enableBatchRendering;
        this.enableDepthOffset = source.enableDepthOffset;
        this.outlinePickWidth = source.outlinePickWidth;
        this.delegateOwner = source.delegateOwner;
        this.drawSurfaceShape = source.drawSurfaceShape;
    }

    public AbstractAirspace()
    {
        this(new BasicAirspaceAttributes());
    }

    public boolean isVisible()
    {
        return this.visible;
    }

    public void setVisible(boolean visible)
    {
        this.visible = visible;
    }

    public AirspaceAttributes getAttributes()
    {
        return this.attributes;
    }

    public void setAttributes(AirspaceAttributes attributes)
    {
        if (attributes == null)
        {
            String message = "nullValue.AirspaceAttributesIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.attributes = attributes;
    }

    @Override
    public void setAttributes(ShapeAttributes attributes)
    {
        this.setAttributes(new BasicAirspaceAttributes(attributes));
    }

    @Override
    public void setHighlightAttributes(ShapeAttributes highlightAttributes)
    {
        this.setHighlightAttributes(
            highlightAttributes != null ? new BasicAirspaceAttributes(highlightAttributes) : null);
    }

    @Override
    public AirspaceAttributes getHighlightAttributes()
    {
        return highlightAttributes;
    }

    @Override
    public void setHighlightAttributes(AirspaceAttributes highlightAttrs)
    {
        this.highlightAttributes = highlightAttrs;

        if (this.surfaceShape != null)
            this.surfaceShape.setHighlightAttributes(highlightAttrs);
    }

    public boolean isHighlighted()
    {
        return highlighted;
    }

    public void setHighlighted(boolean highlighted)
    {
        this.highlighted = highlighted;
    }

    public double[] getAltitudes()
    {
        double[] array = new double[2];
        array[0] = this.lowerAltitude;
        array[1] = this.upperAltitude;
        return array;
    }

    protected double[] getAltitudes(double verticalExaggeration)
    {
        double[] array = this.getAltitudes();
        array[0] = array[0] * verticalExaggeration;
        array[1] = array[1] * verticalExaggeration;
        return array;
    }

    public void setAltitudes(double lowerAltitude, double upperAltitude)
    {
        this.lowerAltitude = lowerAltitude;
        this.upperAltitude = upperAltitude;
        this.invalidateAirspaceData();
    }

    public void setAltitude(double altitude)
    {
        this.setAltitudes(altitude, altitude);
    }

    public boolean[] isTerrainConforming()
    {
        // This method is here for backwards compatibility. The new scheme uses enumerations (in the form of Strings).

        boolean[] array = new boolean[2];
        array[0] = this.lowerTerrainConforming;
        array[1] = this.upperTerrainConforming;
        return array;
    }

    public void setTerrainConforming(boolean lowerTerrainConformant, boolean upperTerrainConformant)
    {
        // This method is here for backwards compatibility. The new scheme uses enumerations (in the form of Strings).

        this.lowerTerrainConforming = lowerTerrainConformant;
        this.upperTerrainConforming = upperTerrainConformant;

        this.lowerAltitudeDatum = this.lowerTerrainConforming ? AVKey.ABOVE_GROUND_LEVEL : AVKey.ABOVE_MEAN_SEA_LEVEL;
        this.upperAltitudeDatum = this.upperTerrainConforming ? AVKey.ABOVE_GROUND_LEVEL : AVKey.ABOVE_MEAN_SEA_LEVEL;

        this.invalidateAirspaceData();
    }

    public String[] getAltitudeDatum()
    {
        return new String[] {this.lowerAltitudeDatum, this.upperAltitudeDatum};
    }

    // TODO: The altitude datum logic is currently implemented only for Polygon. Implement it for the rest of them.

    public void setAltitudeDatum(String lowerAltitudeDatum, String upperAltitudeDatum)
    {
        if (lowerAltitudeDatum == null || upperAltitudeDatum == null)
        {
            String message = Logging.getMessage("nullValue.AltitudeDatumIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.lowerAltitudeDatum = lowerAltitudeDatum;
        this.upperAltitudeDatum = upperAltitudeDatum;

        if (lowerAltitudeDatum.equals(AVKey.ABOVE_GROUND_LEVEL) || lowerAltitudeDatum.equals(
            AVKey.ABOVE_GROUND_REFERENCE))
            this.lowerTerrainConforming = true;

        if (upperAltitudeDatum.equals(AVKey.ABOVE_GROUND_LEVEL) || upperAltitudeDatum.equals(
            AVKey.ABOVE_GROUND_REFERENCE))
            this.upperTerrainConforming = true;

        this.invalidateAirspaceData();
    }

    public LatLon getGroundReference()
    {
        return this.groundReference;
    }

    public void setGroundReference(LatLon groundReference)
    {
        this.groundReference = groundReference;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isEnableBatchRendering()
    {
        return this.enableBatchRendering;
    }

    /** {@inheritDoc} */
    @Override
    public void setEnableBatchRendering(boolean enableBatchRendering)
    {
        this.enableBatchRendering = enableBatchRendering;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isEnableBatchPicking()
    {
        return this.enableBatchPicking;
    }

    /** {@inheritDoc} */
    @Override
    public void setEnableBatchPicking(boolean enableBatchPicking)
    {
        this.enableBatchPicking = enableBatchPicking;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isEnableDepthOffset()
    {
        return this.enableDepthOffset;
    }

    /** {@inheritDoc} */
    @Override
    public void setEnableDepthOffset(boolean enableDepthOffset)
    {
        this.enableDepthOffset = enableDepthOffset;
    }

    /** {@inheritDoc} */
    @Override
    public int getOutlinePickWidth()
    {
        return this.outlinePickWidth;
    }

    /** {@inheritDoc} */
    @Override
    public void setOutlinePickWidth(int outlinePickWidth)
    {
        if (outlinePickWidth < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", "width < 0");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.outlinePickWidth = outlinePickWidth;
    }

    @Override
    public Object getDelegateOwner()
    {
        return this.delegateOwner;
    }

    @Override
    public void setDelegateOwner(Object delegateOwner)
    {
        this.delegateOwner = delegateOwner;
    }

    @Override
    public boolean isAlwaysOnTop()
    {
        return alwaysOnTop;
    }

    @Override
    public void setAlwaysOnTop(boolean alwaysOnTop)
    {
        this.alwaysOnTop = alwaysOnTop;
    }

    @Override
    public boolean isDrawSurfaceShape()
    {
        return drawSurfaceShape;
    }

    @Override
    public void setDrawSurfaceShape(boolean drawSurfaceShape)
    {
        this.drawSurfaceShape = drawSurfaceShape;
    }

    protected void adjustForGroundReference(DrawContext dc, boolean[] terrainConformant, double[] altitudes,
        LatLon groundRef)
    {
        if (groundRef == null)
            return; // Can't apply the datum without a reference point.

        for (int i = 0; i < 2; i++)
        {
            if (this.getAltitudeDatum()[i].equals(AVKey.ABOVE_GROUND_REFERENCE))
            {
                altitudes[i] += this.computeElevationAt(dc, groundRef.getLatitude(), groundRef.getLongitude());
                terrainConformant[i] = false;
            }
        }
    }

    public boolean isAirspaceCollapsed()
    {
        return this.lowerAltitude == this.upperAltitude && this.lowerTerrainConforming == this.upperTerrainConforming;
    }

    public void setTerrainConforming(boolean terrainConformant)
    {
        this.setTerrainConforming(terrainConformant, terrainConformant);
    }

    public boolean isEnableLevelOfDetail()
    {
        return this.enableLevelOfDetail;
    }

    public void setEnableLevelOfDetail(boolean enableLevelOfDetail)
    {
        this.enableLevelOfDetail = enableLevelOfDetail;
    }

    public Iterable getDetailLevels()
    {
        return this.detailLevels;
    }

    public void setDetailLevels(Collection detailLevels)
    {
        this.detailLevels.clear();
        this.addDetailLevels(detailLevels);
    }

    protected void addDetailLevels(Collection newDetailLevels)
    {
        if (newDetailLevels != null)
            for (DetailLevel level : newDetailLevels)
            {
                if (level != null)
                    this.detailLevels.add(level);
            }
    }

    /** {@inheritDoc} */
    public boolean isAirspaceVisible(DrawContext dc)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dc.getView() == null)
        {
            String message = "nullValue.DrawingContextViewIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // A null extent indicates an airspace which has no geometry.
        Extent extent = this.getExtent(dc);
        if (extent == null)
            return false;

        // Test this airspace's extent against the pick frustum list.
        if (dc.isPickingMode())
            return dc.getPickFrustums().intersectsAny(extent);

        // Test this airspace's extent against the viewing frustum.
        return dc.getView().getFrustumInModelCoordinates().intersects(extent);
    }

    public Extent getExtent(Globe globe, double verticalExaggeration)
    {
        if (globe == null)
        {
            String message = Logging.getMessage("nullValue.GlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.computeExtent(globe, verticalExaggeration);
    }

    public Extent getExtent(DrawContext dc)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return this.getAirspaceInfo(dc).extent;
    }

    protected AirspaceInfo getAirspaceInfo(DrawContext dc)
    {
        AirspaceInfo info = this.airspaceInfo.get(dc.getGlobe().getGlobeStateKey());

        if (info == null || !info.isValid(dc))
        {
            info = new AirspaceInfo(dc, this.computeExtent(dc), this.computeMinimalGeometry(dc));
            this.airspaceInfo.put(dc.getGlobe().getGlobeStateKey(), info);
        }

        return info;
    }

    protected Extent computeExtent(DrawContext dc)
    {
        return this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration());
    }

    protected List computeMinimalGeometry(DrawContext dc)
    {
        return this.computeMinimalGeometry(dc.getGlobe(), dc.getVerticalExaggeration());
    }

    protected void invalidateAirspaceData()
    {
        this.airspaceInfo.clear(); // Doesn't hurt to remove all cached extents because re-creation is cheap
        this.mustRegenerateSurfaceShape = true;
    }

    @Override
    public double getDistanceFromEye()
    {
        return this.isAlwaysOnTop() ? 0 : this.currentInfo.getEyeDistance();
    }

    /**
     * Determines which attributes -- normal, highlight or default -- to use each frame. Places the result in this
     * shape's current active attributes.
     *
     * @see #getActiveAttributes()
     */
    protected void determineActiveAttributes(DrawContext dc)
    {
        if (this.frameTimeStamp == dc.getFrameTimeStamp())
            return;

        if (this.isHighlighted())
        {
            if (this.getHighlightAttributes() != null)
                this.activeAttributes.copy(this.getHighlightAttributes());
            else
            {
                // If no highlight attributes have been specified we need to use the normal attributes but adjust them
                // to cause highlighting.
                if (this.getAttributes() != null)
                    this.activeAttributes.copy(this.getAttributes());
                else
                    this.activeAttributes.copy(defaultAttributes);

                this.activeAttributes.setOutlineMaterial(DEFAULT_HIGHLIGHT_MATERIAL);
                this.activeAttributes.setInteriorMaterial(DEFAULT_HIGHLIGHT_MATERIAL);
            }
        }
        else if (this.getAttributes() != null)
        {
            this.activeAttributes.copy(this.getAttributes());
        }
        else
        {
            this.activeAttributes.copy(defaultAttributes);
        }
    }

    /**
     * Returns this shape's currently active attributes, as determined during the most recent call to {@link
     * #determineActiveAttributes(gov.nasa.worldwind.render.DrawContext)}. The active attributes are either the normal
     * or highlight attributes, depending on this shape's highlight flag, and incorporates default attributes for those
     * not specified in the applicable attribute set.
     *
     * @return this shape's currently active attributes.
     */
    public AirspaceAttributes getActiveAttributes()
    {
        return this.activeAttributes;
    }

    @Override
    public void preRender(DrawContext dc)
    {
        if (dc == null)
        {
            String msg = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (!this.isVisible())
            return;

        if (dc.is2DGlobe() || this.isDrawSurfaceShape())
        {
            if (this.surfaceShape == null)
            {
                this.surfaceShape = this.createSurfaceShape();
                this.mustRegenerateSurfaceShape = true;
                if (this.surfaceShape == null)
                    return;
            }

            if (this.mustRegenerateSurfaceShape)
            {
                this.regenerateSurfaceShape(dc, this.surfaceShape);
                this.mustRegenerateSurfaceShape = false;
            }

            this.updateSurfaceShape(dc, this.surfaceShape);
            this.surfaceShape.preRender(dc);
        }
    }

    /**
     * Returns a {@link SurfaceShape} that corresponds to this Airspace and is used for drawing on 2D globes.
     *
     * @return The surface shape to represent this Airspace on a 2D globe.
     */
    protected SurfaceShape createSurfaceShape()
    {
        return null;
    }

    /**
     * Sets surface shape parameters prior to picking and rendering the 2D shape used to represent this Airspace on 2D
     * globes. Subclasses should override this method if they need to update more than the attributes and the delegate
     * owner.
     *
     * @param dc    the current drawing context.
     * @param shape the surface shape to update.
     */
    protected void updateSurfaceShape(DrawContext dc, SurfaceShape shape)
    {
        this.determineActiveAttributes(dc);
        ShapeAttributes attrs = this.getActiveAttributes();
        if (shape.getAttributes() == null)
            shape.setAttributes(new BasicShapeAttributes(attrs));
        else
            shape.getAttributes().copy(attrs);

        Object o = this.getDelegateOwner();
        shape.setDelegateOwner(o != null ? o : this);

        boolean b = this.isEnableBatchPicking();
        shape.setEnableBatchPicking(b);
    }

    /**
     * Regenerates surface shape geometry prior to picking and rendering the 2D shape used to represent this Airspace on
     * 2D globes.
     *
     * @param dc    the current drawing context.
     * @param shape the surface shape to regenerate.
     */
    protected void regenerateSurfaceShape(DrawContext dc, SurfaceShape shape)
    {
        // Intentionally left blank.
    }

    @Override
    public void pick(DrawContext dc, Point pickPoint)
    {
        // This method is called only when ordered renderables are being drawn.

        if (dc == null)
        {
            String msg = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.pickSupport.clearPickList();
        try
        {
            this.pickSupport.beginPicking(dc);
            this.render(dc);
        }
        finally
        {
            this.pickSupport.endPicking(dc);
            this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer);
        }
    }

    public void render(DrawContext dc)
    {
        // This render method is called three times during frame generation. It's first called as a {@link Renderable}
        // during Renderable picking. It's called again during normal rendering. And it's called a third
        // time as an OrderedRenderable. The first two calls determine whether to add the shape to the ordered renderable
        // list during pick and render. The third call just draws the ordered renderable.

        if (dc == null)
        {
            String msg = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (!this.isVisible())
            return;

        if ((dc.is2DGlobe() || this.isDrawSurfaceShape()) && this.surfaceShape != null)
        {
            this.surfaceShape.render(dc);
            return;
        }

        this.currentInfo = this.getAirspaceInfo(dc);

        if (!this.isAirspaceVisible(dc))
            return;

        if (dc.isOrderedRenderingMode())
            this.drawOrderedRenderable(dc);
        else
            this.makeOrderedRenderable(dc);

        this.frameTimeStamp = dc.getFrameTimeStamp();
    }

    protected void makeOrderedRenderable(DrawContext dc)
    {
        this.determineActiveAttributes(dc);

        double eyeDistance = this.computeEyeDistance(dc);
        this.currentInfo.setEyeDistance(eyeDistance);

        if (dc.isPickingMode())
            this.pickLayer = dc.getCurrentLayer();

        dc.addOrderedRenderable(this);
    }

    protected void drawOrderedRenderable(DrawContext dc)
    {
        this.beginRendering(dc);
        try
        {
            this.doDrawOrderedRenderable(dc, this.pickSupport);
            if (this.isEnableBatchRendering())
            {
                this.drawBatched(dc);
            }
        }
        finally
        {
            this.endRendering(dc);
        }
    }

    protected void drawBatched(DrawContext dc)
    {
        // Draw as many as we can in a batch to save ogl state switching.
        Object nextItem = dc.peekOrderedRenderables();

        if (!dc.isPickingMode())
        {
            while (nextItem instanceof AbstractAirspace)
            {
                AbstractAirspace airspace = (AbstractAirspace) nextItem;
                if (!airspace.isEnableBatchRendering())
                    break;

                dc.pollOrderedRenderables(); // take it off the queue
                airspace.doDrawOrderedRenderable(dc, this.pickSupport);

                nextItem = dc.peekOrderedRenderables();
            }
        }
        else if (this.isEnableBatchPicking())
        {
            while (nextItem instanceof AbstractAirspace)
            {
                AbstractAirspace airspace = (AbstractAirspace) nextItem;
                if (!airspace.isEnableBatchRendering() || !airspace.isEnableBatchPicking())
                    break;

                if (airspace.pickLayer != this.pickLayer) // batch pick only within a single layer
                    break;

                dc.pollOrderedRenderables(); // take it off the queue
                airspace.doDrawOrderedRenderable(dc, this.pickSupport);

                nextItem = dc.peekOrderedRenderables();
            }
        }
    }

    protected void doDrawOrderedRenderable(DrawContext dc, PickSupport pickCandidates)
    {
        if (dc.isPickingMode())
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            Color pickColor = dc.getUniquePickColor();
            pickCandidates.addPickableObject(this.createPickedObject(pickColor.getRGB()));
            gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
        }

        dc.drawOutlinedShape(this.outlineShapeRenderer, this);
    }

    protected PickedObject createPickedObject(int colorCode)
    {
        return new PickedObject(colorCode, this.getDelegateOwner() != null ? this.getDelegateOwner() : this);
    }

    public void move(Position position)
    {
        if (position == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Position referencePos = this.getReferencePosition();
        if (referencePos == null)
            return;

        this.moveTo(referencePos.add(position));
    }

    @Override
    public void moveTo(Globe globe, Position position)
    {
        if (position == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Position oldRef = this.getReferencePosition();
        if (oldRef == null)
            return;

        //noinspection UnnecessaryLocalVariable
        Position newRef = position;
        this.doMoveTo(globe, oldRef, newRef);
    }

    @Override
    public boolean isDragEnabled()
    {
        return this.dragEnabled;
    }

    @Override
    public void setDragEnabled(boolean enabled)
    {
        this.dragEnabled = true;
    }

    @Override
    public void drag(DragContext dragContext)
    {
        if (!this.dragEnabled)
            return;

        if (this.draggableSupport == null)
            this.draggableSupport = new DraggableSupport(this, this.isTerrainConforming()[0]
                ? WorldWind.RELATIVE_TO_GROUND : WorldWind.ABSOLUTE);

        this.doDrag(dragContext);
    }

    protected void doDrag(DragContext dragContext)
    {
        this.draggableSupport.dragGlobeSizeConstant(dragContext);
    }

    protected void doMoveTo(Globe globe, Position oldRef, Position newRef)
    {
        this.doMoveTo(oldRef, newRef);
    }

    public void moveTo(Position position)
    {
        if (position == null)
        {
            String message = Logging.getMessage("nullValue.PositionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Position oldRef = this.getReferencePosition();
        if (oldRef == null)
            return;

        //noinspection UnnecessaryLocalVariable
        Position newRef = position;
        this.doMoveTo(oldRef, newRef);
    }

    protected void doMoveTo(Position oldRef, Position newRef)
    {
        if (oldRef == null)
        {
            String message = "nullValue.OldRefIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (newRef == null)
        {
            String message = "nullValue.NewRefIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double[] altitudes = this.getAltitudes();
        double elevDelta = newRef.getElevation() - oldRef.getElevation();
        this.setAltitudes(altitudes[0] + elevDelta, altitudes[1] + elevDelta);
    }

    protected Position computeReferencePosition(List locations, double[] altitudes)
    {
        if (locations == null)
        {
            String message = "nullValue.LocationsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (altitudes == null)
        {
            String message = "nullValue.AltitudesIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        int count = locations.size();
        if (count == 0)
            return null;

        LatLon ll;
        if (count < 3)
            ll = locations.get(0);
        else
            ll = locations.get(count / 2);

        return new Position(ll, altitudes[0]);
    }

    protected double computeEyeDistance(DrawContext dc)
    {
        AirspaceInfo info = this.currentInfo;
        if (info == null || info.minimalGeometry == null || info.minimalGeometry.isEmpty())
            return 0.0;

        double minDistanceSquared = Double.MAX_VALUE;
        Vec4 eyePoint = dc.getView().getEyePoint();

        for (Vec4 point : info.minimalGeometry)
        {
            double d = point.distanceToSquared3(eyePoint);

            if (d < minDistanceSquared)
                minDistanceSquared = d;
        }

        return Math.sqrt(minDistanceSquared);
    }

    //**************************************************************//
    //********************  Geometry Rendering  ********************//
    //**************************************************************//

    protected abstract void doRenderGeometry(DrawContext dc, String drawStyle);

    protected void beginRendering(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

        if (!dc.isPickingMode())
        {
            int attribMask = GL2.GL_COLOR_BUFFER_BIT  // For color write mask, blending src and func, alpha func.
                | GL2.GL_CURRENT_BIT // For current color.
                | GL2.GL_LINE_BIT // For line width, line smoothing, line stipple.
                | GL2.GL_POLYGON_BIT // For polygon offset.
                | GL2.GL_TRANSFORM_BIT; // For matrix mode.
            gl.glPushAttrib(attribMask);

            // Setup blending for non-premultiplied colors.
            gl.glEnable(GL.GL_BLEND);
            OGLUtil.applyBlending(gl, false);

            // Setup standard lighting by default. This must be disabled by airspaces that don't enable lighting.
            dc.beginStandardLighting();
        }
        else
        {
            int attribMask = GL2.GL_CURRENT_BIT // For current color.
                | GL2.GL_LINE_BIT; // For line width.
            gl.glPushAttrib(attribMask);
        }
    }

    protected void endRendering(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);

        if (!dc.isPickingMode())
        {
            dc.endStandardLighting();
            gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); // may have been enabled during rendering
        }

        gl.glPopAttrib();
    }

    @SuppressWarnings("UnusedParameters")
    protected boolean mustDrawInterior(DrawContext dc)
    {
        return this.getActiveAttributes().isDrawInterior();
    }

    protected void drawInterior(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        AirspaceAttributes attrs = this.getActiveAttributes();

        if (!dc.isPickingMode())
        {
            if (attrs.isEnableLighting()) // Enable GL lighting state and set the current GL material state.
            {
                gl.glEnable(GL2.GL_LIGHTING);
                gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
                attrs.getInteriorMaterial().apply(gl, GL2.GL_FRONT_AND_BACK, (float) attrs.getInteriorOpacity());
            }
            else // Disable GL lighting state and set the current GL color state.
            {
                gl.glDisable(GL2.GL_LIGHTING);
                gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
                Color sc = attrs.getInteriorMaterial().getDiffuse();
                double opacity = attrs.getInteriorOpacity();
                gl.glColor4ub((byte) sc.getRed(), (byte) sc.getGreen(), (byte) sc.getBlue(),
                    (byte) (opacity < 1 ? (int) (opacity * 255 + 0.5) : 255));
            }
        }

        this.doRenderGeometry(dc, Airspace.DRAW_STYLE_FILL);
    }

    @SuppressWarnings("UnusedParameters")
    protected boolean mustDrawOutline(DrawContext dc)
    {
        return this.getActiveAttributes().isDrawOutline();
    }

    protected void drawOutline(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        AirspaceAttributes attrs = this.getActiveAttributes();

        if (!dc.isPickingMode())
        {
            // Airspace outlines do not apply lighting.
            gl.glDisable(GL2.GL_LIGHTING);
            gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
            Color sc = attrs.getOutlineMaterial().getDiffuse();
            double opacity = attrs.getOutlineOpacity();
            gl.glColor4ub((byte) sc.getRed(), (byte) sc.getGreen(), (byte) sc.getBlue(),
                (byte) (opacity < 1 ? (int) (opacity * 255 + 0.5) : 255));

            if (attrs.isEnableAntialiasing())
            {
                gl.glEnable(GL.GL_LINE_SMOOTH);
            }
            else
            {
                gl.glDisable(GL.GL_LINE_SMOOTH);
            }
        }

        if (dc.isPickingMode() && attrs.getOutlineWidth() < this.getOutlinePickWidth())
            gl.glLineWidth(this.getOutlinePickWidth());
        else
            gl.glLineWidth((float) attrs.getOutlineWidth());

        if (attrs.getOutlineStippleFactor() > 0)
        {
            gl.glEnable(GL2.GL_LINE_STIPPLE);
            gl.glLineStipple(attrs.getOutlineStippleFactor(), attrs.getOutlineStipplePattern());
        }
        else
        {
            gl.glDisable(GL2.GL_LINE_STIPPLE);
        }

        this.doRenderGeometry(dc, Airspace.DRAW_STYLE_OUTLINE);
    }

    protected void drawGeometry(DrawContext dc, Geometry indices, Geometry vertices)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        AirspaceAttributes attrs = this.getActiveAttributes();

        int size = vertices.getSize(Geometry.VERTEX);
        int type = vertices.getGLType(Geometry.VERTEX);
        int stride = vertices.getStride(Geometry.VERTEX);
        Buffer buffer = vertices.getBuffer(Geometry.VERTEX);
        gl.glVertexPointer(size, type, stride, buffer);

        if (!dc.isPickingMode() && attrs.isEnableLighting())
        {
            type = vertices.getGLType(Geometry.NORMAL);
            stride = vertices.getStride(Geometry.NORMAL);
            buffer = vertices.getBuffer(Geometry.NORMAL);
            gl.glNormalPointer(type, stride, buffer);
        }

        // On some hardware, using glDrawRangeElements allows vertex data to be prefetched. We know the minimum and
        // maximum index values that are valid in elementBuffer (they are 0 and vertexCount-1), so it's harmless
        // to use this approach and allow the hardware to optimize.
        int mode = indices.getMode(Geometry.ELEMENT);
        int count = indices.getCount(Geometry.ELEMENT);
        type = indices.getGLType(Geometry.ELEMENT);
        int minElementIndex = 0;
        int maxElementIndex = vertices.getCount(Geometry.VERTEX) - 1;
        buffer = indices.getBuffer(Geometry.ELEMENT);
        gl.glDrawRangeElements(mode, minElementIndex, maxElementIndex, count, type, buffer);
    }

    protected GeometryBuilder getGeometryBuilder()
    {
        return this.geometryBuilder;
    }

    protected void setGeometryBuilder(GeometryBuilder gb)
    {
        if (gb == null)
        {
            String message = "nullValue.GeometryBuilderIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.geometryBuilder = gb;
    }

    protected DetailLevel computeDetailLevel(DrawContext dc)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Iterable detailLevels = this.getDetailLevels();
        if (detailLevels == null)
            return null;

        Iterator iter = detailLevels.iterator();
        if (!iter.hasNext())
            return null;

        // Find the first detail level that meets rendering criteria.
        DetailLevel level = iter.next();
        while (iter.hasNext() && !level.meetsCriteria(dc, this))
        {
            level = iter.next();
        }

        return level;
    }

    protected MemoryCache getGeometryCache()
    {
        return WorldWind.getMemoryCache(GEOMETRY_CACHE_KEY);
    }

    protected boolean isExpired(DrawContext dc, Geometry geom)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (geom == null)
        {
            String message = "nullValue.AirspaceGeometryIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Object o = geom.getValue(EXPIRY_TIME);
        if (o != null && o instanceof Long && dc.getFrameTimeStamp() > (Long) o)
            return true;

        o = geom.getValue(GLOBE_KEY);
        //noinspection RedundantIfStatement
        if (o != null && !dc.getGlobe().getStateKey(dc).equals(o))
            return true;

        return false;
    }

    protected void updateExpiryCriteria(DrawContext dc, Geometry geom)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        long expiryTime = this.getExpiryTime();
        geom.setValue(EXPIRY_TIME, (expiryTime >= 0L) ? expiryTime : null);
        geom.setValue(GLOBE_KEY, dc.getGlobe().getStateKey(dc));
    }

    protected long getExpiryTime()
    {
        return this.expiryTime;
    }

    protected void setExpiryTime(long timeMillis)
    {
        this.expiryTime = timeMillis;
    }

    protected long[] getExpiryRange()
    {
        long[] array = new long[2];
        array[0] = this.minExpiryTime;
        array[1] = this.maxExpiryTime;
        return array;
    }

    protected void setExpiryRange(long minTimeMillis, long maxTimeMillis)
    {
        this.minExpiryTime = minTimeMillis;
        this.maxExpiryTime = maxTimeMillis;
    }

    protected long nextExpiryTime(DrawContext dc, boolean[] terrainConformance)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        long expiryTime;
        if (terrainConformance[0] || terrainConformance[1])
        {
            long time = nextLong(this.minExpiryTime, this.maxExpiryTime);
            expiryTime = dc.getFrameTimeStamp() + time;
        }
        else
        {
            expiryTime = -1L;
        }
        return expiryTime;
    }

    private static long nextLong(long lo, long hi)
    {
        long n = hi - lo + 1;
        long i = rand.nextLong() % n;
        return lo + ((i < 0) ? -i : i);
    }

    protected void clearElevationMap()
    {
        this.elevationMap.clear();
    }

    public Vec4 computePointFromPosition(DrawContext dc, Angle latitude, Angle longitude, double elevation,
        boolean terrainConformant)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double newElevation = elevation;

        if (terrainConformant)
        {
            newElevation += this.computeElevationAt(dc, latitude, longitude);
        }

        return dc.getGlobe().computePointFromPosition(latitude, longitude, newElevation);
    }

    protected double computeElevationAt(DrawContext dc, Angle latitude, Angle longitude)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (dc.getGlobe() == null)
        {
            String message = Logging.getMessage("nullValue.DrawingContextGlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }
        if (latitude == null || longitude == null)
        {
            String message = Logging.getMessage("nullValue.LatitudeOrLongitudeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        Globe globe;
        LatLon latlon;
        Vec4 surfacePoint;
        Position surfacePos;
        Double elevation;

        latlon = new LatLon(latitude, longitude);
        elevation = this.elevationMap.get(latlon);

        if (elevation == null)
        {
            globe = dc.getGlobe();
            elevation = 0.0;

            surfacePoint = dc.getPointOnTerrain(latitude, longitude);
            if (surfacePoint != null)
            {
                surfacePos = globe.computePositionFromPoint(surfacePoint);
                elevation += surfacePos.getElevation();
            }
            else
            {
                elevation += dc.getVerticalExaggeration() * globe.getElevation(latitude, longitude);
            }

            this.elevationMap.put(latlon, elevation);
        }

        return elevation;
    }

    protected void makeExtremePoints(Globe globe, double verticalExaggeration, Iterable locations,
        List extremePoints)
    {
        if (globe == null)
        {
            String message = Logging.getMessage("nullValue.GlobeIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (locations == null)
        {
            String message = "nullValue.LocationsIsNull";
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        double[] altitudes = this.getAltitudes();
        boolean[] terrainConformant = this.isTerrainConforming();

        // If terrain conformance is enabled, add the minimum or maximum elevations around the locations to the
        // airspace's altitudes.
        if (terrainConformant[0] || terrainConformant[1])
        {
            double[] extremeElevations = new double[2];

            if (LatLon.locationsCrossDateLine(locations))
            {
                Sector[] splitSector = Sector.splitBoundingSectors(locations);
                double[] a = globe.getMinAndMaxElevations(splitSector[0]);
                double[] b = globe.getMinAndMaxElevations(splitSector[1]);
                extremeElevations[0] = Math.min(a[0], b[0]); // Take the smallest min elevation.
                extremeElevations[1] = Math.max(a[1], b[1]); // Take the largest max elevation.
            }
            else
            {
                Sector sector = Sector.boundingSector(locations);
                extremeElevations = globe.getMinAndMaxElevations(sector);
            }

            if (terrainConformant[0])
                altitudes[0] += extremeElevations[0];

            if (terrainConformant[1])
                altitudes[1] += extremeElevations[1];
        }

        // Get the points corresponding to the given locations at the lower and upper altitudes.
        for (LatLon ll : locations)
        {
            extremePoints.add(globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
                verticalExaggeration * altitudes[0]));
            extremePoints.add(globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(),
                verticalExaggeration * altitudes[1]));
        }
    }

    //**************************************************************//
    //******************** END Geometry Rendering  *****************//
    //**************************************************************//

    public String getRestorableState()
    {
        RestorableSupport rs = RestorableSupport.newRestorableSupport();
        this.doGetRestorableState(rs, null);

        return rs.getStateAsXml();
    }

    protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        // Method is invoked by subclasses to have superclass add its state and only its state
        this.doMyGetRestorableState(rs, context);
    }

    private void doMyGetRestorableState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        rs.addStateValueAsBoolean(context, "visible", this.isVisible());
        rs.addStateValueAsBoolean(context, "highlighted", this.isHighlighted());
        rs.addStateValueAsDouble(context, "lowerAltitude", this.getAltitudes()[0]);
        rs.addStateValueAsDouble(context, "upperAltitude", this.getAltitudes()[1]);
        rs.addStateValueAsBoolean(context, "lowerTerrainConforming", this.isTerrainConforming()[0]);
        rs.addStateValueAsBoolean(context, "upperTerrainConforming", this.isTerrainConforming()[1]);
        rs.addStateValueAsString(context, "lowerAltitudeDatum", this.getAltitudeDatum()[0]);
        rs.addStateValueAsString(context, "upperAltitudeDatum", this.getAltitudeDatum()[1]);
        if (this.getGroundReference() != null)
            rs.addStateValueAsLatLon(context, "groundReference", this.getGroundReference());
        rs.addStateValueAsBoolean(context, "enableBatchRendering", this.isEnableBatchRendering());
        rs.addStateValueAsBoolean(context, "enableBatchPicking", this.isEnableBatchPicking());
        rs.addStateValueAsBoolean(context, "enableDepthOffset", this.isEnableDepthOffset());
        rs.addStateValueAsInteger(context, "outlinePickWidth", this.getOutlinePickWidth());
        rs.addStateValueAsBoolean(context, "alwaysOnTop", this.isAlwaysOnTop());
        rs.addStateValueAsBoolean(context, "drawSurfaceShape", this.isDrawSurfaceShape());
        rs.addStateValueAsBoolean(context, "enableLevelOfDetail", this.isEnableLevelOfDetail());

        if (this.attributes != null)
            this.attributes.getRestorableState(rs, rs.addStateObject(context, "attributes"));

        if (this.highlightAttributes != null)
            this.highlightAttributes.getRestorableState(rs, rs.addStateObject(context, "highlightAttributes"));
    }

    public void restoreState(String stateInXml)
    {
        if (stateInXml == null)
        {
            String message = Logging.getMessage("nullValue.StringIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        RestorableSupport rs;
        try
        {
            rs = RestorableSupport.parse(stateInXml);
        }
        catch (Exception e)
        {
            // Parsing the document specified by stateInXml failed.
            String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message, e);
        }

        this.doRestoreState(rs, null);
    }

    protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        // Method is invoked by subclasses to have superclass add its state and only its state
        this.doMyRestoreState(rs, context);
    }

    private void doMyRestoreState(RestorableSupport rs, RestorableSupport.StateObject context)
    {
        Boolean booleanState = rs.getStateValueAsBoolean(context, "visible");
        if (booleanState != null)
            this.setVisible(booleanState);

        booleanState = rs.getStateValueAsBoolean(context, "highlighted");
        if (booleanState != null)
            this.setHighlighted(booleanState);

        Double lo = rs.getStateValueAsDouble(context, "lowerAltitude");
        if (lo == null)
            lo = this.getAltitudes()[0];

        Double hi = rs.getStateValueAsDouble(context, "upperAltitude");
        if (hi == null)
            hi = this.getAltitudes()[1];

        this.setAltitudes(lo, hi);

        Boolean loConform = rs.getStateValueAsBoolean(context, "lowerTerrainConforming");
        if (loConform == null)
            loConform = this.isTerrainConforming()[0];

        Boolean hiConform = rs.getStateValueAsBoolean(context, "upperTerrainConforming");
        if (hiConform == null)
            hiConform = this.isTerrainConforming()[1];

        this.setTerrainConforming(loConform, hiConform);

        String lowerDatum = rs.getStateValueAsString(context, "lowerAltitudeDatum");
        if (lowerDatum == null)
            lowerDatum = this.getAltitudeDatum()[0];

        String upperDatum = rs.getStateValueAsString(context, "upperAltitudeDatum");
        if (upperDatum == null)
            upperDatum = this.getAltitudeDatum()[1];

        this.setAltitudeDatum(lowerDatum, upperDatum);

        LatLon groundRef = rs.getStateValueAsLatLon(context, "groundReference");
        if (groundRef != null)
            this.setGroundReference(groundRef);

        booleanState = rs.getStateValueAsBoolean(context, "enableBatchRendering");
        if (booleanState != null)
            this.setEnableBatchRendering(booleanState);

        booleanState = rs.getStateValueAsBoolean(context, "enableBatchPicking");
        if (booleanState != null)
            this.setEnableBatchPicking(booleanState);

        booleanState = rs.getStateValueAsBoolean(context, "enableDepthOffset");
        if (booleanState != null)
            this.setEnableDepthOffset(booleanState);

        Integer intState = rs.getStateValueAsInteger(context, "outlinePickWidth");
        if (intState != null)
            this.setOutlinePickWidth(intState);

        booleanState = rs.getStateValueAsBoolean(context, "alwaysOnTop");
        if (booleanState != null)
            this.setAlwaysOnTop(booleanState);

        booleanState = rs.getStateValueAsBoolean(context, "drawSurfaceShape");
        if (booleanState != null)
            this.setDrawSurfaceShape(booleanState);

        booleanState = rs.getStateValueAsBoolean(context, "enableLevelOfDetail");
        if (booleanState != null)
            this.setEnableLevelOfDetail(booleanState);

        RestorableSupport.StateObject so = rs.getStateObject(context, "attributes");
        if (so != null)
            this.getAttributes().restoreState(rs, so);

        so = rs.getStateObject(context, "highlightAttributes");
        if (so != null)
            this.getHighlightAttributes().restoreState(rs, so);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy