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

src.gov.nasa.worldwind.render.Polyline 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.render;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.Cylinder;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.util.measure.LengthMeasurer;

import javax.media.opengl.*;
import java.awt.*;
import java.util.*;
import java.util.List;

/**
 * @author tag
 * @version $Id: Polyline.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class Polyline extends AVListImpl implements Renderable, OrderedRenderable, Movable, Restorable,
    MeasurableLength, ExtentHolder
{
    public final static int GREAT_CIRCLE = WorldWind.GREAT_CIRCLE;
    public final static int LINEAR = WorldWind.LINEAR;
    public final static int RHUMB_LINE = WorldWind.RHUMB_LINE;
    public final static int LOXODROME = RHUMB_LINE;

    public final static int ANTIALIAS_DONT_CARE = WorldWind.ANTIALIAS_DONT_CARE;
    public final static int ANTIALIAS_FASTEST = WorldWind.ANTIALIAS_FASTEST;
    public final static int ANTIALIAS_NICEST = WorldWind.ANTIALIAS_NICEST;

    protected ArrayList positions;
    protected Vec4 referenceCenterPoint;
    protected int antiAliasHint = GL.GL_FASTEST;
    protected Color color = Color.WHITE;
    protected double lineWidth = 1;
    protected boolean filled = false; // makes it a polygon
    protected boolean closed = false; // connect last point to first
    protected boolean followTerrain = false;
    protected double offset = 0;
    protected double terrainConformance = 10;
    protected int pathType = GREAT_CIRCLE;
    protected List> currentSpans;
    protected short stipplePattern = (short) 0xAAAA;
    protected int stippleFactor = 0;
    protected int numSubsegments = 10;
    protected boolean highlighted = false;
    protected Color highlightColor = new Color(1f, 1f, 1f, 0.5f);
    protected Object delegateOwner;
    protected LengthMeasurer measurer = new LengthMeasurer();
    protected long geomGenTimeStamp = -Long.MAX_VALUE;
    protected double geomGenVE = 1;
    protected double eyeDistance;
    protected PickSupport pickSupport = new PickSupport();
    protected long frameNumber = -1; // identifies frame used to calculate these values
    protected Layer pickLayer;

    // Manage an extent for each globe the polyline's associated with.

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

        public ExtentInfo(Extent extent, DrawContext dc)
        {
            this.extent = extent;
            this.verticalExaggeration = dc.getVerticalExaggeration();
            this.globe = dc.getGlobe();
            this.globeStateKey = dc.getGlobe().getStateKey(dc);
        }

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

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

    public Polyline()
    {
        this.setPositions(null);
        this.measurer.setFollowTerrain(this.followTerrain);
        this.measurer.setPathType(this.pathType);
    }

    public Polyline(Iterable positions)
    {
        this.setPositions(positions);
        this.measurer.setFollowTerrain(this.followTerrain);
        this.measurer.setPathType(this.pathType);
    }

    public Polyline(Iterable positions, double elevation)
    {
        this.setPositions(positions, elevation);
        this.measurer.setFollowTerrain(this.followTerrain);
        this.measurer.setPathType(this.pathType);
    }

    private void reset()
    {
        if (this.currentSpans != null)
            this.currentSpans.clear();
        this.currentSpans = null;
    }

    public Color getColor()
    {
        return color;
    }

    public void setColor(Color color)
    {
        if (color == null)
        {
            String msg = Logging.getMessage("nullValue.ColorIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.color = color;
    }

    public int getAntiAliasHint()
    {
        return antiAliasHint;
    }

    public void setAntiAliasHint(int hint)
    {
        if (!(hint == ANTIALIAS_DONT_CARE || hint == ANTIALIAS_FASTEST || hint == ANTIALIAS_NICEST))
        {
            String msg = Logging.getMessage("generic.InvalidHint");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.antiAliasHint = hint;
    }

    public boolean isFilled()
    {
        return filled;
    }

    public void setFilled(boolean filled)
    {
        this.filled = filled;
    }

    public int getPathType()
    {
        return pathType;
    }

    public String getPathTypeString()
    {
        return this.getPathType() == GREAT_CIRCLE ? AVKey.GREAT_CIRCLE
            : this.getPathType() == RHUMB_LINE ? AVKey.RHUMB_LINE : AVKey.LINEAR;
    }

    /**
     * Sets the type of path to draw, one of {@link #GREAT_CIRCLE}, which draws each segment of the path as a great
     * circle, {@link #LINEAR}, which determines the intermediate positions between segments by interpolating the
     * segment endpoints, or {@link #RHUMB_LINE}, which draws each segment of the path as a line of constant heading.
     *
     * @param pathType the type of path to draw.
     *
     * @see Path Types
     */
    public void setPathType(int pathType)
    {
        this.reset();
        this.pathType = pathType;
        this.measurer.setPathType(pathType);
    }

    /**
     * Sets the type of path to draw, one of {@link AVKey#GREAT_CIRCLE}, which draws each segment of the path as a great
     * circle, {@link AVKey#LINEAR}, which determines the intermediate positions between segments by interpolating the
     * segment endpoints, or {@link AVKey#RHUMB_LINE}, which draws each segment of the path as a line of constant
     * heading.
     *
     * @param pathType the type of path to draw.
     *
     * @see Path Types
     */
    public void setPathType(String pathType)
    {
        if (pathType == null)
        {
            String msg = Logging.getMessage("nullValue.PathTypeIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.setPathType(pathType.equals(AVKey.GREAT_CIRCLE) ? GREAT_CIRCLE
            : pathType.equals(AVKey.RHUMB_LINE) || pathType.equals(AVKey.LOXODROME) ? RHUMB_LINE : LINEAR);
    }

    public boolean isFollowTerrain()
    {
        return followTerrain;
    }

    /**
     * Indicates whether the path should follow the terrain's surface. If the value is true, the elevation
     * values in this path's positions are ignored and the path is drawn on the terrain surface. Otherwise the path is
     * drawn according to the elevations given in the path's positions. If following the terrain, the path may also have
     * an offset. See {@link #setOffset(double)};
     *
     * @param followTerrain true to follow the terrain, otherwise false.
     */
    public void setFollowTerrain(boolean followTerrain)
    {
        this.reset();
        this.followTerrain = followTerrain;
        this.measurer.setFollowTerrain(followTerrain);
        this.extents.clear();
    }

    public double getOffset()
    {
        return offset;
    }

    /**
     * Specifies an offset, in meters, to add to the path points when the path's follow-terrain attribute is true. See
     * {@link #setFollowTerrain(boolean)}.
     *
     * @param offset the path pffset in meters.
     */
    public void setOffset(double offset)
    {
        this.reset();
        this.offset = offset;
        this.extents.clear();
    }

    public double getTerrainConformance()
    {
        return terrainConformance;
    }

    /**
     * Specifies the precision to which the path follows the terrain when the follow-terrain attribute is true. The
     * conformance value indicates the approximate length of each sub-segment of the path as it's drawn, in pixels.
     * Lower values specify higher precision, but at the cost of performance.
     *
     * @param terrainConformance the path conformance in pixels.
     */
    public void setTerrainConformance(double terrainConformance)
    {
        this.terrainConformance = terrainConformance;
    }

    public double getLineWidth()
    {
        return lineWidth;
    }

    public void setLineWidth(double lineWidth)
    {
        this.lineWidth = lineWidth;
    }

    /**
     * Returns the length of the line as drawn. If the path follows the terrain, the length returned is the distance one
     * would travel if on the surface. If the path does not follow the terrain, the length returned is the distance
     * along the full length of the path at the path's elevations and current path type.
     *
     * @return the path's length in meters.
     */
    public double getLength()
    {
        Iterator infos = this.extents.values().iterator();
        return infos.hasNext() ? this.measurer.getLength(infos.next().globe) : 0;
    }

    public double getLength(Globe globe)
    {
        // The length measurer will throw an exception and log the error if globe is null
        return this.measurer.getLength(globe);
    }

    public LengthMeasurer getMeasurer()
    {
        return this.measurer;
    }

    public short getStipplePattern()
    {
        return stipplePattern;
    }

    /**
     * Sets the stipple pattern for specifying line types other than solid. See the OpenGL specification or programming
     * guides for a description of this parameter. Stipple is also affected by the path's stipple factor, {@link
     * #setStippleFactor(int)}.
     *
     * @param stipplePattern the stipple pattern.
     */
    public void setStipplePattern(short stipplePattern)
    {
        this.stipplePattern = stipplePattern;
    }

    public int getStippleFactor()
    {
        return stippleFactor;
    }

    /**
     * Sets the stipple factor for specifying line types other than solid. See the OpenGL specification or programming
     * guides for a description of this parameter. Stipple is also affected by the path's stipple pattern, {@link
     * #setStipplePattern(short)}.
     *
     * @param stippleFactor the stipple factor.
     */
    public void setStippleFactor(int stippleFactor)
    {
        this.stippleFactor = stippleFactor;
    }

    public int getNumSubsegments()
    {
        return numSubsegments;
    }

    /**
     * Specifies the number of intermediate segments to draw for each segment between positions. The end points of the
     * intermediate segments are calculated according to the current path type and follow-terrain setting.
     *
     * @param numSubsegments the number of intermediate subsegments.
     */
    public void setNumSubsegments(int numSubsegments)
    {
        this.reset();
        this.numSubsegments = numSubsegments;
    }

    public boolean isHighlighted()
    {
        return highlighted;
    }

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

    public Color getHighlightColor()
    {
        return this.highlightColor;
    }

    public void setHighlightColor(Color highlightColor)
    {
        if (highlightColor == null)
        {
            String message = Logging.getMessage("nullValue.ColorIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }

        this.highlightColor = highlightColor;
    }

    /**
     * Specifies the path's positions.
     *
     * @param inPositions the path positions.
     */
    public void setPositions(Iterable inPositions)
    {
        this.reset();
        this.positions = new ArrayList();
        this.extents.clear();
        if (inPositions != null)
        {
            for (Position position : inPositions)
            {
                this.positions.add(position);
            }
            this.measurer.setPositions(this.positions);
        }

        if ((this.filled && this.positions.size() < 3))
        {
            String msg = Logging.getMessage("generic.InsufficientPositions");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * Sets the paths positions as latitude and longitude values at a constant altitude.
     *
     * @param inPositions the latitudes and longitudes of the positions.
     * @param altitude    the elevation to assign each position.
     */
    public void setPositions(Iterable inPositions, double altitude)
    {
        this.reset();
        this.positions = new ArrayList();
        this.extents.clear();
        if (inPositions != null)
        {
            for (LatLon position : inPositions)
            {
                this.positions.add(new Position(position, altitude));
            }
            this.measurer.setPositions(this.positions);
        }

        if (this.filled && this.positions.size() < 3)
        {
            String msg = Logging.getMessage("generic.InsufficientPositions");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
    }

    public Iterable getPositions()
    {
        return this.positions;
    }

    public boolean isClosed()
    {
        return closed;
    }

    public void setClosed(boolean closed)
    {
        this.closed = closed;
    }

    /**
     * Returns the delegate owner of this Polyline. If non-null, the returned object replaces the Polyline as the
     * pickable object returned during picking. If null, the Polyline itself is the pickable object returned during
     * picking.
     *
     * @return the object used as the pickable object returned during picking, or null to indicate that the Polyline is
     *         returned during picking.
     */
    public Object getDelegateOwner()
    {
        return this.delegateOwner;
    }

    /**
     * Specifies the delegate owner of this Polyline. If non-null, the delegate owner replaces the Polyline as the
     * pickable object returned during picking. If null, the Polyline itself is the pickable object returned during
     * picking.
     *
     * @param owner the object to use as the pickable object returned during picking, or null to return the Polyline.
     */
    public void setDelegateOwner(Object owner)
    {
        this.delegateOwner = owner;
    }

    /**
     * Returns this Polyline's enclosing volume as an {@link gov.nasa.worldwind.geom.Extent} in model coordinates, given
     * a specified {@link gov.nasa.worldwind.globes.Globe} and vertical exaggeration (see {@link
     * gov.nasa.worldwind.SceneController#getVerticalExaggeration()}.
     *
     * @param globe                the Globe this Polyline is related to.
     * @param verticalExaggeration the vertical exaggeration to apply.
     *
     * @return this Polyline's Extent in model coordinates.
     *
     * @throws IllegalArgumentException if the Globe is null.
     */
    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);
    }

    /**
     * Returns this Polyline's enclosing volume as an {@link gov.nasa.worldwind.geom.Extent} in model coordinates, given
     * a specified {@link gov.nasa.worldwind.render.DrawContext}. The returned Extent may be different than the Extent
     * returned by calling {@link #getExtent(gov.nasa.worldwind.globes.Globe, double)} with the DrawContext's Globe and
     * vertical exaggeration. Additionally, this may cache the computed extent and is therefore potentially faster than
     * calling {@link #getExtent(gov.nasa.worldwind.globes.Globe, double)}.
     *
     * @param dc the current DrawContext.
     *
     * @return this Polyline's Extent in model coordinates.
     *
     * @throws IllegalArgumentException if the DrawContext is null, or if the Globe held by the DrawContext is null.
     */
    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);
        }

        ExtentInfo extentInfo = this.extents.get(dc.getGlobe());
        if (extentInfo != null && extentInfo.isValid(dc))
        {
            return extentInfo.extent;
        }
        else
        {
            extentInfo = new ExtentInfo(this.computeExtent(dc), dc);
            this.extents.put(dc.getGlobe(), extentInfo);
            return extentInfo.extent;
        }
    }

    protected Extent computeExtent(Globe globe, double verticalExaggeration)
    {
        Sector sector = Sector.boundingSector(this.getPositions());

        double[] minAndMaxElevations;
        if (this.isFollowTerrain())
        {
            minAndMaxElevations = globe.getMinAndMaxElevations(sector);
        }
        else
        {
            minAndMaxElevations = computeElevationExtremes(this.getPositions());
        }
        minAndMaxElevations[0] += this.getOffset();
        minAndMaxElevations[1] += this.getOffset();

        return Sector.computeBoundingBox(globe, verticalExaggeration, sector, minAndMaxElevations[0],
            minAndMaxElevations[1]);
    }

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

    protected static double[] computeElevationExtremes(Iterable positions)
    {
        double[] extremes = new double[] {Double.MAX_VALUE, -Double.MAX_VALUE};
        for (Position pos : positions)
        {
            if (extremes[0] > pos.getElevation())
                extremes[0] = pos.getElevation(); // min
            if (extremes[1] < pos.getElevation())
                extremes[1] = pos.getElevation(); // max
        }

        return extremes;
    }

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

    public void pick(DrawContext dc, Point pickPoint)
    {
        // This method is called only when ordered renderables are being drawn.
        // Arg checked within call to render.

        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 polyline 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 (dc.getSurfaceGeometry() == null)
            return;

        this.draw(dc);
    }

    /**
     * If the scene controller is rendering ordered renderables, this method draws this placemark's image as an ordered
     * renderable. Otherwise the method determines whether this instance should be added to the ordered renderable
     * list.
     * 

* The Cartesian and screen points of the placemark are computed during the first call per frame and re-used in * subsequent calls of that frame. * * @param dc the current draw context. */ protected void draw(DrawContext dc) { if (dc.isOrderedRenderingMode()) { this.drawOrderedRenderable(dc); return; } // The rest of the code in this method determines whether to queue an ordered renderable for the polyline. if (this.positions.size() < 2) return; // vertices potentially computed every frame to follow terrain changes if (this.currentSpans == null || (this.followTerrain && this.geomGenTimeStamp != dc.getFrameTimeStamp()) || this.geomGenVE != dc.getVerticalExaggeration()) { // Reference center must be computed prior to computing vertices. this.computeReferenceCenter(dc); this.eyeDistance = this.referenceCenterPoint.distanceTo3(dc.getView().getEyePoint()); this.makeVertices(dc); this.geomGenTimeStamp = dc.getFrameTimeStamp(); this.geomGenVE = dc.getVerticalExaggeration(); } if (this.currentSpans == null || this.currentSpans.size() < 1) return; if (this.intersectsFrustum(dc)) { if (dc.isPickingMode()) this.pickLayer = dc.getCurrentLayer(); dc.addOrderedRenderable(this); // add the ordered renderable } } protected void drawOrderedRenderable(DrawContext dc) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. int attrBits = GL2.GL_HINT_BIT | GL2.GL_CURRENT_BIT | GL2.GL_LINE_BIT; if (!dc.isPickingMode()) { if (this.color.getAlpha() != 255) attrBits |= GL.GL_COLOR_BUFFER_BIT; } gl.glPushAttrib(attrBits); dc.getView().pushReferenceCenter(dc, this.referenceCenterPoint); boolean projectionOffsetPushed = false; // keep track for error recovery try { if (!dc.isPickingMode()) { if (this.color.getAlpha() != 255) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } gl.glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(), (byte) this.color.getBlue(), (byte) this.color.getAlpha()); } else { // We cannot depend on the layer to set a pick color for us because this Polyline is picked during ordered // rendering. Therefore we set the pick color ourselves. Color pickColor = dc.getUniquePickColor(); Object userObject = this.getDelegateOwner() != null ? this.getDelegateOwner() : this; this.pickSupport.addPickableObject(pickColor.getRGB(), userObject, null); gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue()); } if (this.stippleFactor > 0) { gl.glEnable(GL2.GL_LINE_STIPPLE); gl.glLineStipple(this.stippleFactor, this.stipplePattern); } else { gl.glDisable(GL2.GL_LINE_STIPPLE); } int hintAttr = GL2.GL_LINE_SMOOTH_HINT; if (this.filled) hintAttr = GL2.GL_POLYGON_SMOOTH_HINT; gl.glHint(hintAttr, this.antiAliasHint); int primType = GL2.GL_LINE_STRIP; if (this.filled) primType = GL2.GL_POLYGON; if (dc.isPickingMode()) gl.glLineWidth((float) this.lineWidth + 8); else gl.glLineWidth((float) this.lineWidth); if (this.followTerrain) { dc.pushProjectionOffest(0.99); projectionOffsetPushed = true; } if (this.currentSpans == null) return; for (List span : this.currentSpans) { if (span == null) continue; // Since segments can very often be very short -- two vertices -- use explicit rendering. The // overhead of batched rendering, e.g., gl.glDrawArrays, is too high because it requires copying // the vertices into a DoubleBuffer, and DoubleBuffer creation and access performs relatively poorly. gl.glBegin(primType); for (Vec4 p : span) { gl.glVertex3d(p.x, p.y, p.z); } gl.glEnd(); } if (this.highlighted) { if (!dc.isPickingMode()) { if (this.highlightColor.getAlpha() != 255) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } gl.glColor4ub((byte) this.highlightColor.getRed(), (byte) this.highlightColor.getGreen(), (byte) this.highlightColor.getBlue(), (byte) this.highlightColor.getAlpha()); gl.glLineWidth((float) this.lineWidth + 2); for (List span : this.currentSpans) { if (span == null) continue; gl.glBegin(primType); for (Vec4 p : span) { gl.glVertex3d(p.x, p.y, p.z); } gl.glEnd(); } } } } finally { if (projectionOffsetPushed) dc.popProjectionOffest(); gl.glPopAttrib(); dc.getView().popReferenceCenter(dc); } } /** * Indicates whether the shape is visible in the current view. * * @param dc the draw context. * * @return true if the shape is visible, otherwise false. */ protected boolean intersectsFrustum(DrawContext dc) { Extent extent = this.getExtent(dc); if (extent == null) return true; // don't know the visibility, shape hasn't been computed yet if (dc.isPickingMode()) return dc.getPickFrustums().intersectsAny(extent); return dc.getView().getFrustumInModelCoordinates().intersects(extent); } protected void makeVertices(DrawContext dc) { if (this.currentSpans == null) this.currentSpans = new ArrayList>(); else this.currentSpans.clear(); if (this.positions.size() < 1) return; Position posA = this.positions.get(0); Vec4 ptA = this.computePoint(dc, posA, true); for (int i = 1; i <= this.positions.size(); i++) { Position posB; if (i < this.positions.size()) posB = this.positions.get(i); else if (this.closed) posB = this.positions.get(0); else break; Vec4 ptB = this.computePoint(dc, posB, true); if (this.followTerrain && !this.isSegmentVisible(dc, posA, posB, ptA, ptB)) { posA = posB; ptA = ptB; continue; } ArrayList span; span = this.makeSegment(dc, posA, posB, ptA, ptB); if (span != null) this.addSpan(span); posA = posB; ptA = ptB; } } protected void addSpan(ArrayList span) { if (span != null && span.size() > 0) this.currentSpans.add(span); } protected boolean isSegmentVisible(DrawContext dc, Position posA, Position posB, Vec4 ptA, Vec4 ptB) { Frustum f = dc.getView().getFrustumInModelCoordinates(); if (f.contains(ptA)) return true; if (f.contains(ptB)) return true; if (ptA.equals(ptB)) return false; Position posC = Position.interpolateRhumb(0.5, posA, posB); Vec4 ptC = this.computePoint(dc, posC, true); if (f.contains(ptC)) return true; double r = Line.distanceToSegment(ptA, ptB, ptC); Cylinder cyl = new Cylinder(ptA, ptB, r == 0 ? 1 : r); return cyl.intersects(dc.getView().getFrustumInModelCoordinates()); } protected Vec4 computePoint(DrawContext dc, Position pos, boolean applyOffset) { if (this.followTerrain) { double height = !applyOffset ? 0 : this.offset; // computeTerrainPoint will apply vertical exaggeration return dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), height); } else { double height = pos.getElevation() + (applyOffset ? this.offset : 0); return dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height * dc.getVerticalExaggeration()); } } protected double computeSegmentLength(DrawContext dc, Position posA, Position posB) { LatLon llA = new LatLon(posA.getLatitude(), posA.getLongitude()); LatLon llB = new LatLon(posB.getLatitude(), posB.getLongitude()); Angle ang = LatLon.greatCircleDistance(llA, llB); if (this.followTerrain) { return ang.radians * (dc.getGlobe().getRadius() + this.offset * dc.getVerticalExaggeration()); } else { double height = this.offset + 0.5 * (posA.getElevation() + posB.getElevation()); return ang.radians * (dc.getGlobe().getRadius() + height * dc.getVerticalExaggeration()); } } protected ArrayList makeSegment(DrawContext dc, Position posA, Position posB, Vec4 ptA, Vec4 ptB) { ArrayList span = null; double arcLength = this.computeSegmentLength(dc, posA, posB); if (arcLength <= 0) // points differing only in altitude { span = this.addPointToSpan(ptA, span); if (!ptA.equals(ptB)) span = this.addPointToSpan(ptB, span); return span; } // Variables for great circle and rhumb computation. Angle segmentAzimuth = null; Angle segmentDistance = null; for (double s = 0, p = 0; s < 1; ) { if (this.followTerrain) p += this.terrainConformance * dc.getView().computePixelSizeAtDistance( ptA.distanceTo3(dc.getView().getEyePoint())); else p += arcLength / this.numSubsegments; s = p / arcLength; Position pos; if (s >= 1) { pos = posB; } else if (this.pathType == LINEAR) { if (segmentAzimuth == null) { segmentAzimuth = LatLon.linearAzimuth(posA, posB); segmentDistance = LatLon.linearDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.linearEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } else if (this.pathType == RHUMB_LINE) // or LOXODROME (note that loxodrome is translated to RHUMB_LINE in setPathType) { if (segmentAzimuth == null) { segmentAzimuth = LatLon.rhumbAzimuth(posA, posB); segmentDistance = LatLon.rhumbDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.rhumbEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } else // GREAT_CIRCLE { if (segmentAzimuth == null) { segmentAzimuth = LatLon.greatCircleAzimuth(posA, posB); segmentDistance = LatLon.greatCircleDistance(posA, posB); } Angle distance = Angle.fromRadians(s * segmentDistance.radians); LatLon latLon = LatLon.greatCircleEndPosition(posA, segmentAzimuth, distance); pos = new Position(latLon, (1 - s) * posA.getElevation() + s * posB.getElevation()); } ptB = this.computePoint(dc, pos, true); span = this.clipAndAdd(dc, ptA, ptB, span); ptA = ptB; } return span; } @SuppressWarnings({"UnusedDeclaration"}) protected ArrayList clipAndAdd(DrawContext dc, Vec4 ptA, Vec4 ptB, ArrayList span) { // Line clipping appears to be useful only for long lines with few segments. It's costly otherwise. // TODO: Investigate trade-off of line clipping. // if (Line.clipToFrustum(ptA, ptB, dc.getView().getFrustumInModelCoordinates()) == null) // { // if (span != null) // { // this.addSpan(span); // span = null; // } // return span; // } if (span == null) span = this.addPointToSpan(ptA, span); return this.addPointToSpan(ptB, span); } protected ArrayList addPointToSpan(Vec4 p, ArrayList span) { if (span == null) span = new ArrayList(); span.add(p.subtract3(this.referenceCenterPoint)); return span; } protected void computeReferenceCenter(DrawContext dc) { // The reference position is null if this Polyline has no positions. In this case computing the Polyline's // Cartesian reference point is meaningless because the Polyline has no geographic location. Therefore we exit // without updating the reference point. Position refPos = this.getReferencePosition(); if (refPos == null) return; this.referenceCenterPoint = dc.computeTerrainPoint(refPos.getLatitude(), refPos.getLongitude(), this.offset); } public Position getReferencePosition() { if (this.positions.size() < 1) { return null; } else if (this.positions.size() < 3) { return this.positions.get(0); } else { return this.positions.get(this.positions.size() / 2); } } public void move(Position delta) { if (delta == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } Position refPos = this.getReferencePosition(); // The reference position is null if this Polyline has no positions. In this case moving the Polyline by a // relative delta is meaningless because the Polyline has no geographic location. Therefore we fail softly by // exiting and doing nothing. if (refPos == null) return; this.moveTo(refPos.add(delta)); } public void moveTo(Position position) { if (position == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.reset(); this.extents.clear(); Position oldRef = this.getReferencePosition(); // The reference position is null if this Polyline has no positions. In this case moving the Polyline to a new // reference position is meaningless because the Polyline has no geographic location. Therefore we fail softly // by exiting and doing nothing. if (oldRef == null) return; double elevDelta = position.getElevation() - oldRef.getElevation(); for (int i = 0; i < this.positions.size(); i++) { Position pos = this.positions.get(i); Angle distance = LatLon.greatCircleDistance(oldRef, pos); Angle azimuth = LatLon.greatCircleAzimuth(oldRef, pos); LatLon newLocation = LatLon.greatCircleEndPosition(position, azimuth, distance); double newElev = pos.getElevation() + elevDelta; this.positions.set(i, new Position(newLocation, newElev)); } } /** * Returns an XML state document String describing the public attributes of this Polyline. * * @return XML state document string describing this Polyline. */ public String getRestorableState() { RestorableSupport rs = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (rs == null) return null; if (this.color != null) { String encodedColor = RestorableSupport.encodeColor(this.color); if (encodedColor != null) rs.addStateValueAsString("color", encodedColor); } if (this.highlightColor != null) { String encodedColor = RestorableSupport.encodeColor(this.highlightColor); if (encodedColor != null) rs.addStateValueAsString("highlightColor", encodedColor); } if (this.positions != null) { // Create the base "positions" state object. RestorableSupport.StateObject positionsStateObj = rs.addStateObject("positions"); if (positionsStateObj != null) { for (Position p : this.positions) { // Save each position only if all parts (latitude, longitude, and elevation) can be // saved. We will not save a partial iconPosition (for example, just the elevation). if (p != null && p.getLatitude() != null && p.getLongitude() != null) { // Create a nested "position" element underneath the base "positions". RestorableSupport.StateObject pStateObj = rs.addStateObject(positionsStateObj, "position"); if (pStateObj != null) { rs.addStateValueAsDouble(pStateObj, "latitudeDegrees", p.getLatitude().degrees); rs.addStateValueAsDouble(pStateObj, "longitudeDegrees", p.getLongitude().degrees); rs.addStateValueAsDouble(pStateObj, "elevation", p.getElevation()); } } } } } rs.addStateValueAsInteger("antiAliasHint", this.antiAliasHint); rs.addStateValueAsBoolean("filled", this.filled); rs.addStateValueAsBoolean("closed", this.closed); rs.addStateValueAsBoolean("highlighted", this.highlighted); rs.addStateValueAsInteger("pathType", this.pathType); rs.addStateValueAsBoolean("followTerrain", this.followTerrain); rs.addStateValueAsDouble("offset", this.offset); rs.addStateValueAsDouble("terrainConformance", this.terrainConformance); rs.addStateValueAsDouble("lineWidth", this.lineWidth); rs.addStateValueAsInteger("stipplePattern", this.stipplePattern); rs.addStateValueAsInteger("stippleFactor", this.stippleFactor); rs.addStateValueAsInteger("numSubsegments", this.numSubsegments); RestorableSupport.StateObject so = rs.addStateObject(null, "avlist"); for (Map.Entry avp : this.getEntries()) { this.getRestorableStateForAVPair(avp.getKey(), avp.getValue() != null ? avp.getValue() : "", rs, so); } return rs.getStateAsXml(); } /** * Restores publicly settable attribute values found in the specified XML state document String. The document * specified by stateInXml must be a well formed XML document String, or this will throw an * IllegalArgumentException. Unknown structures in stateInXml are benign, because they will simply be * ignored. * * @param stateInXml an XML document String describing a Polyline. * * @throws IllegalArgumentException If stateInXml is null, or if stateInXml is not a well * formed XML document String. */ public void restoreState(String stateInXml) { if (stateInXml == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } RestorableSupport restorableSupport; try { restorableSupport = 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); } String colorState = restorableSupport.getStateValueAsString("color"); if (colorState != null) { Color color = RestorableSupport.decodeColor(colorState); if (color != null) setColor(color); } colorState = restorableSupport.getStateValueAsString("highlightColor"); if (colorState != null) { Color color = RestorableSupport.decodeColor(colorState); if (color != null) setHighlightColor(color); } // Get the base "positions" state object. RestorableSupport.StateObject positionsStateObj = restorableSupport.getStateObject("positions"); if (positionsStateObj != null) { ArrayList newPositions = new ArrayList(); // Get the nested "position" states beneath the base "positions". RestorableSupport.StateObject[] positionStateArray = restorableSupport.getAllStateObjects(positionsStateObj, "position"); if (positionStateArray != null && positionStateArray.length != 0) { for (RestorableSupport.StateObject pStateObj : positionStateArray) { if (pStateObj != null) { // Restore each position only if all parts are available. // We will not restore a partial position (for example, just the elevation). Double latitudeState = restorableSupport.getStateValueAsDouble(pStateObj, "latitudeDegrees"); Double longitudeState = restorableSupport.getStateValueAsDouble(pStateObj, "longitudeDegrees"); Double elevationState = restorableSupport.getStateValueAsDouble(pStateObj, "elevation"); if (latitudeState != null && longitudeState != null && elevationState != null) newPositions.add(Position.fromDegrees(latitudeState, longitudeState, elevationState)); } } } // Even if there are no actual positions specified, we set positions as an empty list. // An empty set of positions is still a valid state. setPositions(newPositions); } Integer antiAliasHintState = restorableSupport.getStateValueAsInteger("antiAliasHint"); if (antiAliasHintState != null) setAntiAliasHint(antiAliasHintState); Boolean isFilledState = restorableSupport.getStateValueAsBoolean("filled"); if (isFilledState != null) setFilled(isFilledState); Boolean isClosedState = restorableSupport.getStateValueAsBoolean("closed"); if (isClosedState != null) setClosed(isClosedState); Boolean isHighlightedState = restorableSupport.getStateValueAsBoolean("highlighted"); if (isHighlightedState != null) setHighlighted(isHighlightedState); Integer pathTypeState = restorableSupport.getStateValueAsInteger("pathType"); if (pathTypeState != null) setPathType(pathTypeState); Boolean isFollowTerrainState = restorableSupport.getStateValueAsBoolean("followTerrain"); if (isFollowTerrainState != null) setFollowTerrain(isFollowTerrainState); Double offsetState = restorableSupport.getStateValueAsDouble("offset"); if (offsetState != null) setOffset(offsetState); Double terrainConformanceState = restorableSupport.getStateValueAsDouble("terrainConformance"); if (terrainConformanceState != null) setTerrainConformance(terrainConformanceState); Double lineWidthState = restorableSupport.getStateValueAsDouble("lineWidth"); if (lineWidthState != null) setLineWidth(lineWidthState); Integer stipplePatternState = restorableSupport.getStateValueAsInteger("stipplePattern"); if (stipplePatternState != null) setStipplePattern(stipplePatternState.shortValue()); Integer stippleFactorState = restorableSupport.getStateValueAsInteger("stippleFactor"); if (stippleFactorState != null) setStippleFactor(stippleFactorState); Integer numSubsegmentsState = restorableSupport.getStateValueAsInteger("numSubsegments"); if (numSubsegmentsState != null) setNumSubsegments(numSubsegmentsState); RestorableSupport.StateObject so = restorableSupport.getStateObject(null, "avlist"); if (so != null) { RestorableSupport.StateObject[] avpairs = restorableSupport.getAllStateObjects(so, ""); if (avpairs != null) { for (RestorableSupport.StateObject avp : avpairs) { if (avp != null) this.setValue(avp.getName(), avp.getValue()); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy