Skip to content

Commit 1855da5

Browse files
authored
Merge pull request NASAWorldWind#40 from Sufaev/feature/surface-text-rotation
Added SurfaceText rotation
2 parents 5dc2165 + ee9d1e2 commit 1855da5

File tree

2 files changed

+190
-21
lines changed

2 files changed

+190
-21
lines changed

src/gov/nasa/worldwind/render/SurfaceText.java

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class SurfaceText extends AbstractSurfaceObject implements GeographicText
4242
protected CharSequence text;
4343
/** Location at which to draw the text. */
4444
protected Position location;
45+
/** The angle of text rotation from the true north (clockwise). */
46+
protected Angle heading = Angle.ZERO;
4547
/** The height of the text in meters. */
4648
protected double textSizeInMeters = DEFAULT_TEXT_SIZE_IN_METERS;
4749
/** Dragging Support */
@@ -153,6 +155,30 @@ public void setPosition(Position position)
153155
this.onShapeChanged();
154156
}
155157

158+
/** {@inheritDoc} */
159+
public Angle getHeading()
160+
{
161+
return this.heading;
162+
}
163+
164+
/**
165+
* {@inheritDoc}
166+
* <p>
167+
* The angle of text rotation from the true north (clockwise)
168+
*/
169+
public void setHeading(Angle heading)
170+
{
171+
if (heading == null)
172+
{
173+
String message = Logging.getMessage("nullValue.HeadingIsNull");
174+
Logging.logger().severe(message);
175+
throw new IllegalArgumentException(message);
176+
}
177+
178+
this.heading = heading;
179+
this.onShapeChanged();
180+
}
181+
156182
/** {@inheritDoc} */
157183
public Font getFont()
158184
{
@@ -394,13 +420,6 @@ protected void drawGeographic(DrawContext dc, SurfaceTileDrawContext sdc)
394420
protected void drawText(DrawContext dc)
395421
{
396422
TextRenderer tr = this.getTextRenderer(dc);
397-
398-
Point2D point = this.getOffset().computeOffset(this.textBounds.getWidth(), this.textBounds.getHeight(), null,
399-
null);
400-
401-
int x = (int) point.getX();
402-
int y = (int) point.getY();
403-
404423
try
405424
{
406425
tr.begin3DRendering();
@@ -409,9 +428,9 @@ protected void drawText(DrawContext dc)
409428
CharSequence text = this.getText();
410429

411430
tr.setColor(bgColor);
412-
tr.draw(text, x + 1, y - 1);
431+
tr.draw(text, 1, -1);
413432
tr.setColor(this.getColor());
414-
tr.draw(text, x, y);
433+
tr.draw(text, 0, 0);
415434
}
416435
finally
417436
{
@@ -461,6 +480,24 @@ protected void applyDrawTransform(DrawContext dc, SurfaceTileDrawContext sdc)
461480

462481
// Apply the scaling factor to draw the text at the correct geographic size
463482
gl.glScaled(this.scale, this.scale, 1d);
483+
484+
double widthInPixels = this.textBounds.getWidth();
485+
double heightInPixels = this.textBounds.getHeight();
486+
487+
Point2D textDimensions = getRotatedTextDimensions();
488+
double rotatedPixelHeight = textDimensions.getY();
489+
double rotatedPixelWidth = textDimensions.getX();
490+
491+
Point2D textOffset = getOffset().computeOffset(rotatedPixelWidth, rotatedPixelHeight, null, null);
492+
493+
// Move to offset position.
494+
gl.glTranslated(rotatedPixelWidth / 2.0 + textOffset.getX(), rotatedPixelHeight / 2.0 + textOffset.getY(), 0);
495+
496+
// Apply rotation angle from text center.
497+
gl.glRotated(-this.heading.degrees, 0, 0, 1);
498+
499+
// Move to text center.
500+
gl.glTranslated(-widthInPixels / 2.0, -heightInPixels / 2.0, 0);
464501
}
465502

466503
/**
@@ -523,6 +560,54 @@ protected Color computeBackgroundColor(Color color)
523560
else
524561
return new Color(1, 1, 1, 0.7f);
525562
}
563+
564+
private Point2D getRotatedTextDimensions()
565+
{
566+
double widthInPixels = this.textBounds.getWidth();
567+
double heightInPixels = this.textBounds.getHeight();
568+
569+
Angle rotation = Angle.normalizedLongitude(this.heading);
570+
double ct = Math.cos(rotation.radians);
571+
double st = Math.sin(rotation.radians);
572+
573+
double hct = heightInPixels * ct;
574+
double wct = widthInPixels * ct;
575+
double hst = heightInPixels * st;
576+
double wst = widthInPixels * st;
577+
578+
if (rotation.degrees > 0)
579+
{
580+
if (rotation.degrees < 90)
581+
{
582+
// 0 < theta < 90
583+
heightInPixels = hct + wst;
584+
widthInPixels = wct + hst;
585+
}
586+
else
587+
{
588+
// 90 <= theta <= 180
589+
heightInPixels = wst - hct;
590+
widthInPixels = hst - wct;
591+
}
592+
}
593+
else
594+
{
595+
if (rotation.degrees > -90 )
596+
{
597+
// -90 < theta <= 0
598+
heightInPixels = hct - wst;
599+
widthInPixels = wct - hst;
600+
}
601+
else
602+
{
603+
// -180 <= theta <= -90
604+
heightInPixels = -(hct + wst);
605+
widthInPixels = -(wct + hst);
606+
}
607+
}
608+
609+
return new Point2D.Double(widthInPixels, heightInPixels);
610+
}
526611

527612
/**
528613
* Compute the sector covered by this surface text.
@@ -536,30 +621,32 @@ protected Sector[] computeSector(DrawContext dc)
536621
// Compute text extent depending on distance from eye
537622
Globe globe = dc.getGlobe();
538623

539-
double widthInPixels = this.textBounds.getWidth();
540-
double heightInPixels = this.textBounds.getHeight();
541-
542-
double heightInMeters = this.textSizeInMeters;
624+
Point2D textDimensions = getRotatedTextDimensions();
625+
double heightInPixels = textDimensions.getY();
626+
double widthInPixels = textDimensions.getX();
627+
628+
double heightFactor = heightInPixels / this.textBounds.getHeight();
629+
double heightInMeters = heightFactor * this.textSizeInMeters;
543630
double widthInMeters = heightInMeters * (widthInPixels / heightInPixels);
544-
631+
545632
double radius = globe.getRadius();
546633
double heightInRadians = heightInMeters / radius;
547634
double widthInRadians = widthInMeters / radius;
548635

549-
// Compute the offset from the reference position. Convert pixels to meters based on the geographic size
550-
// of the text.
551-
Point2D point = this.getOffset().computeOffset(widthInPixels, heightInPixels, null, null);
636+
// Compute the offset from the reference position.
637+
// Convert pixels to meters based on the geographic size of the text.
638+
Point2D textOffset = getOffset().computeOffset(widthInPixels, heightInPixels, null, null);
552639

553640
double metersPerPixel = heightInMeters / heightInPixels;
554641

555-
double dxRadians = (point.getX() * metersPerPixel) / radius;
556-
double dyRadians = (point.getY() * metersPerPixel) / radius;
557-
642+
double dxRadians = (textOffset.getX() * metersPerPixel) / radius;
643+
double dyRadians = (textOffset.getY() * metersPerPixel) / radius;
644+
558645
double minLat = this.location.latitude.addRadians(dyRadians).degrees;
559646
double maxLat = this.location.latitude.addRadians(dyRadians + heightInRadians).degrees;
560647
double minLon = this.location.longitude.addRadians(dxRadians).degrees;
561648
double maxLon = this.location.longitude.addRadians(dxRadians + widthInRadians).degrees;
562-
649+
563650
this.drawLocation = LatLon.fromDegrees(minLat, minLon);
564651

565652
if (maxLon > 180) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (C) 2012 United States Government as represented by the Administrator of the
3+
* National Aeronautics and Space Administration.
4+
* All Rights Reserved.
5+
*/
6+
7+
package gov.nasa.worldwind.render;
8+
9+
import gov.nasa.worldwind.Configuration;
10+
import gov.nasa.worldwind.WorldWind;
11+
import gov.nasa.worldwind.avlist.AVKey;
12+
import gov.nasa.worldwind.geom.Angle;
13+
import gov.nasa.worldwind.geom.Position;
14+
import gov.nasa.worldwind.layers.RenderableLayer;
15+
import gov.nasa.worldwindx.examples.ApplicationTemplate;
16+
17+
/**
18+
* Test of {@link SurfaceText} rotation and offset calculations. This test creates
19+
* several SurfaceText objects with different rotation and offset configurations.
20+
* The SurfaceText objects can be visually inspected to confirm that the rotations
21+
* and placements are correctly calculated, and that all of them are located
22+
* within their respective bounding-sectors.
23+
*
24+
* @author pabercrombie
25+
* @version $Id: SurfaceTextTest.java 1171 2013-02-11 21:45:02Z dcollins $
26+
*/
27+
public class SurfaceTextTest extends ApplicationTemplate
28+
{
29+
private static Position center = Position.fromDegrees(38.9345, -120.1670, 50000);
30+
31+
public static class AppFrame extends ApplicationTemplate.AppFrame
32+
{
33+
public AppFrame()
34+
{
35+
super(true, true, false);
36+
37+
RenderableLayer layer = new RenderableLayer();
38+
39+
PointPlacemarkAttributes attributes = new PointPlacemarkAttributes();
40+
attributes.setLabelColor("ffffffff");
41+
attributes.setLineColor("ff0000ff");
42+
attributes.setUsePointAsDefaultImage(true);
43+
attributes.setScale(5d);
44+
45+
int j = 0;
46+
for (double x = -1.0; x <= 0.0; x += 0.5, j++)
47+
{
48+
for (double y = -1.0; y <= 0.0; y += 0.5, j++)
49+
{
50+
for (int i = 0; i <= 12; i++)
51+
{
52+
double latitude = center.latitude.degrees + ((j - 4) / 5.0);
53+
double longitude = center.longitude.degrees + ((i - 6) / 5.0);
54+
Position position = Position.fromDegrees(latitude, longitude, 0);
55+
56+
SurfaceText surfaceText = new SurfaceText("Test Label Description", position);
57+
surfaceText.setDrawBoundingSectors(true);
58+
surfaceText.setHeading(Angle.fromDegrees(i * 30));
59+
surfaceText.setOffset(Offset.fromFraction(x, y));
60+
layer.addRenderable(surfaceText);
61+
62+
PointPlacemark placemark = new PointPlacemark(position);
63+
placemark.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
64+
placemark.setAttributes(attributes);
65+
layer.addRenderable(placemark);
66+
}
67+
}
68+
}
69+
70+
this.getWwd().getModel().getLayers().add(layer);
71+
}
72+
}
73+
74+
public static void main(String[] args)
75+
{
76+
Configuration.setValue(AVKey.INITIAL_LATITUDE, center.latitude.degrees);
77+
Configuration.setValue(AVKey.INITIAL_LONGITUDE, center.longitude.degrees);
78+
Configuration.setValue(AVKey.INITIAL_ALTITUDE, center.elevation);
79+
80+
ApplicationTemplate.start("Surface Text Test", AppFrame.class);
81+
}
82+
}

0 commit comments

Comments
 (0)