@@ -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 ) {
0 commit comments