From d1fac84162155b7ab379264c8f0167f9cbaf5cc6 Mon Sep 17 00:00:00 2001 From: Wiehann Matthysen Date: Wed, 14 Dec 2016 20:45:43 +0200 Subject: [PATCH 1/2] Partial surface-ellipses. Changed the SurfaceEllipse class to maintain an additional parameter called "theta". This parameter is responsible for the angle between the first and last interval of the ellipse and allows for partial ellipses to be drawn. The default value of theta is 360-degrees, meaning that a full ellipse will be drawn when constructing an ellipse as usual. --- .../nasa/worldwind/render/SurfaceEllipse.java | 93 +++++++++++++++++-- .../worldwind/util/MessageStrings.properties | 1 + 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/gov/nasa/worldwind/render/SurfaceEllipse.java b/src/gov/nasa/worldwind/render/SurfaceEllipse.java index fe5dd757e8..f05b57af67 100644 --- a/src/gov/nasa/worldwind/render/SurfaceEllipse.java +++ b/src/gov/nasa/worldwind/render/SurfaceEllipse.java @@ -24,6 +24,7 @@ public class SurfaceEllipse extends AbstractSurfaceShape protected double majorRadius; protected double minorRadius; protected Angle heading = Angle.ZERO; + protected Angle theta = Angle.POS360; private int intervals = DEFAULT_NUM_INTERVALS; /** @@ -252,6 +253,38 @@ public SurfaceEllipse(ShapeAttributes normalAttrs, LatLon center, double majorRa this.intervals = intervals; } + + /** + * Constructs a new surface ellipse with the specified normal (as opposed to highlight) attributes, the specified + * center location, radii (in meters), heading clockwise from North, and initial number of geometry intervals. + * Modifying the attribute reference after calling this constructor causes this shape's appearance to change + * accordingly. + * + * @param normalAttrs the normal attributes. May be null, in which case default attributes are used. + * @param center the ellipse's center location. + * @param majorRadius the ellipse's major radius, in meters. + * @param minorRadius the ellipse's minor radius, in meters. + * @param heading the ellipse's heading, clockwise from North. + * @param intervals the initial number of intervals (or slices) defining the ellipse's geometry. + * @param theta the angle defining the start and end of the ellipse's geometry. + * + * @throws IllegalArgumentException if the center or heading are null, if either radii is negative, or if the number + * of intervals is less than 8. + */ + public SurfaceEllipse(ShapeAttributes normalAttrs, LatLon center, double majorRadius, double minorRadius, + Angle heading, int intervals, Angle theta) + { + this(normalAttrs, center, majorRadius, minorRadius, heading, intervals); + + if (theta == null) + { + String message = Logging.getMessage("nullValue.ThetaIsNull"); + Logging.logger().severe(message); + throw new IllegalArgumentException(message); + } + + this.theta = theta; + } public LatLon getCenter() { @@ -348,6 +381,24 @@ public void setIntervals(int intervals) this.intervals = intervals; this.onShapeChanged(); } + + public Angle getTheta() + { + return this.theta; + } + + public void setTheta(Angle theta) + { + if (theta == null) + { + String message = Logging.getMessage("nullValue.ThetaIsNull"); + Logging.logger().severe(message); + throw new IllegalArgumentException(message); + } + + this.theta = theta; + this.onShapeChanged(); + } /** * {@inheritDoc} @@ -408,27 +459,48 @@ protected List computeLocations(Globe globe, int intervals) if (this.majorRadius == 0 && this.minorRadius == 0) return null; + + boolean closed = this.theta.equals(Angle.POS360); - int numLocations = 1 + Math.max(MIN_NUM_INTERVALS, intervals); - double da = (2 * Math.PI) / (numLocations - 1); + int numIntervals = Math.max(MIN_NUM_INTERVALS, intervals); + int numLocations = 1 + numIntervals; + double da = (this.theta.radians) / (numLocations - 1); double globeRadius = globe.getRadiusAt(this.center.getLatitude(), this.center.getLongitude()); - LatLon[] locations = new LatLon[numLocations]; + List locations = new ArrayList(numLocations); + + // If the ellipse is not closed, start drawing from the center-position. + if (!closed) { + locations.add(this.center); + } for (int i = 0; i < numLocations; i++) { - double angle = (i != numLocations - 1) ? i * da : 0; + double angle = 0.0; + // If the ellipse is closed, snap angle to 0-degrees on final location. + if (closed) { + angle = (i != numIntervals) ? i * da : 0; + } else { + angle = (i != numIntervals) ? i * da : this.theta.radians; + } + double xLength = this.majorRadius * Math.cos(angle); double yLength = this.minorRadius * Math.sin(angle); double distance = Math.sqrt(xLength * xLength + yLength * yLength); - // azimuth runs positive clockwise from north and through 360 degrees. + + // azimuth runs positive clockwise from north and through theta degrees. double azimuth = (Math.PI / 2.0) - (Math.acos(xLength / distance) * Math.signum(yLength) - this.heading.radians); - locations[i] = LatLon.greatCircleEndPosition(this.center, azimuth, distance / globeRadius); + locations.add(LatLon.rhumbEndPosition(this.center, Angle.fromRadians(azimuth), Angle.fromRadians(distance / globeRadius))); + } + + // If the ellipse is not closed, end at the center-position. + if (!closed) { + locations.add(this.center); } - return Arrays.asList(locations); + return locations; } protected List> createGeometry(Globe globe, double edgeIntervalsPerDegree) @@ -469,7 +541,7 @@ protected int computeNumEdgeIntervals(Globe globe, double edgeIntervalsPerDegree int numPositions = 1 + Math.max(MIN_NUM_INTERVALS, intervals); double radius = Math.max(this.majorRadius, this.minorRadius); - double da = (2 * Math.PI) / (numPositions - 1); + double da = (this.theta.radians) / (numPositions - 1); Angle edgePathLength = Angle.fromRadians(da * radius / globe.getRadiusAt(this.center)); double edgeIntervals = WWMath.clamp(edgeIntervalsPerDegree * edgePathLength.degrees, @@ -491,6 +563,7 @@ protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.Stat rs.addStateValueAsDouble(context, "minorRadius", this.getMinorRadius()); rs.addStateValueAsDouble(context, "headingDegrees", this.getHeading().degrees); rs.addStateValueAsInteger(context, "intervals", this.getIntervals()); + rs.addStateValueAsDouble(context, "theta", this.getTheta().degrees); } protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) @@ -516,6 +589,10 @@ protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObjec Integer i = rs.getStateValueAsInteger(context, "intervals"); if (d != null) this.setIntervals(i); + + d = rs.getStateValueAsDouble(context, "theta"); + if (d != null) + this.setTheta(Angle.fromDegrees(d)); } protected void legacyRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) diff --git a/src/gov/nasa/worldwind/util/MessageStrings.properties b/src/gov/nasa/worldwind/util/MessageStrings.properties index 1c5558f23f..b2d92333c8 100644 --- a/src/gov/nasa/worldwind/util/MessageStrings.properties +++ b/src/gov/nasa/worldwind/util/MessageStrings.properties @@ -679,6 +679,7 @@ nullValue.TextureDataIsNull=TextureData is null nullValue.TextureIsNull=Texture is null nullValue.TextureCacheIsNull=Texture cache is null nullValue.TextureCoordinateComputerIsNull=Texture coordinate computer is null +nullValue.ThetaIsNull=Theta is null nullValue.ThreadIsNull=Thread is null nullValue.ThrowableIsNull=Throwable is null nullValue.TileIsNull=Tile is null From d18ec771a10c5b2867d5fbe7d25a4e583a984f54 Mon Sep 17 00:00:00 2001 From: Wiehann Matthysen Date: Wed, 14 Dec 2016 20:50:20 +0200 Subject: [PATCH 2/2] Added a constructor for partial circles. Added an additional constructor to the SurfaceCircle class to allow for partial circles to be drawn. This constructor accept an additional parameter theta, that gets passed to the SurfaceEllipse super constructor. --- .../nasa/worldwind/render/SurfaceCircle.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/gov/nasa/worldwind/render/SurfaceCircle.java b/src/gov/nasa/worldwind/render/SurfaceCircle.java index 715dd2df64..e538636ad8 100644 --- a/src/gov/nasa/worldwind/render/SurfaceCircle.java +++ b/src/gov/nasa/worldwind/render/SurfaceCircle.java @@ -104,6 +104,25 @@ public SurfaceCircle(ShapeAttributes normalAttrs, LatLon center, double radius, { super(normalAttrs, center, radius, radius, Angle.ZERO, intervals); } + + /** + * Constructs a new surface circle with the specified normal (as opposed to highlight) attributes, the specified + * center location, radius (in meters), and initial number of geometry intervals. Modifying the attribute reference + * after calling this constructor causes this shape's appearance to change accordingly. + * + * @param normalAttrs the normal attributes. May be null, in which case default attributes are used. + * @param center the circle's center location. + * @param radius the circle's radius, in meters. + * @param intervals the initial number of intervals (or slices) defining the circle's geometry. + * @param theta the angle defining the start and end of the circle's geometry. + * + * @throws IllegalArgumentException if the center is null, if the radius is negative, or if the number of intervals + * is less than 8. + */ + public SurfaceCircle(ShapeAttributes normalAttrs, LatLon center, double radius, int intervals, Angle theta) + { + super(normalAttrs, center, radius, radius, Angle.ZERO, intervals, theta); + } public double getRadius() {