001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/graphics/displayelements/LabelFactory.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.graphics.displayelements; 037 038 import static java.lang.Double.parseDouble; 039 import static java.lang.Math.abs; 040 import static java.lang.Math.sqrt; 041 import static org.deegree.framework.log.LoggerFactory.getLogger; 042 043 import java.awt.Color; 044 import java.awt.Font; 045 import java.awt.Graphics2D; 046 import java.awt.font.FontRenderContext; 047 import java.awt.font.LineMetrics; 048 import java.awt.geom.Rectangle2D; 049 import java.util.ArrayList; 050 import java.util.Collections; 051 import java.util.Iterator; 052 import java.util.List; 053 054 import org.deegree.framework.log.ILogger; 055 import org.deegree.graphics.sld.Halo; 056 import org.deegree.graphics.sld.LabelPlacement; 057 import org.deegree.graphics.sld.LinePlacement; 058 import org.deegree.graphics.sld.ParameterValueType; 059 import org.deegree.graphics.sld.PointPlacement; 060 import org.deegree.graphics.sld.TextSymbolizer; 061 import org.deegree.graphics.transformation.GeoTransform; 062 import org.deegree.model.feature.Feature; 063 import org.deegree.model.filterencoding.FilterEvaluationException; 064 import org.deegree.model.spatialschema.Curve; 065 import org.deegree.model.spatialschema.Geometry; 066 import org.deegree.model.spatialschema.GeometryException; 067 import org.deegree.model.spatialschema.GeometryFactory; 068 import org.deegree.model.spatialschema.LineString; 069 import org.deegree.model.spatialschema.MultiCurve; 070 import org.deegree.model.spatialschema.MultiPoint; 071 import org.deegree.model.spatialschema.MultiSurface; 072 import org.deegree.model.spatialschema.Point; 073 import org.deegree.model.spatialschema.Position; 074 import org.deegree.model.spatialschema.Surface; 075 076 /** 077 * Does the labeling, i.e. creates (screen) <tt>Label</tt> representations from <tt>LabelDisplayElement</tt>s. 078 * <p> 079 * Different geometry-types (of the LabelDisplayElement) imply different strategies concerning the way the 080 * <tt>Labels</tt> are generated. 081 * <p> 082 * 083 * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a> 084 * @version $Revision: 29952 $ $Date: 2011-03-09 13:14:53 +0100 (Wed, 09 Mar 2011) $ 085 */ 086 public class LabelFactory { 087 088 private static final ILogger LOG = getLogger( LabelFactory.class ); 089 090 091 /** 092 * @param caption 093 * @param font 094 * @param color 095 * @param metrics 096 * @param feature 097 * @param halo 098 * @param x 099 * @param y 100 * @param w 101 * @param h 102 * @param rotation 103 * @param anchorPointX 104 * @param anchorPointY 105 * @param displacementX 106 * @param displacementY 107 * @param opacity 108 * @return label representations 109 */ 110 public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, 111 Halo halo, int x, int y, int w, int h, double rotation, double anchorPointX, 112 double anchorPointY, double displacementX, double displacementY, double opacity ) { 113 if ( rotation == 0.0 ) { 114 return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h, 115 new double[] { anchorPointX, anchorPointY }, new double[] { displacementX, 116 displacementY }, 117 opacity ); 118 } 119 return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, 120 new double[] { anchorPointX, anchorPointY }, new double[] { displacementX, 121 displacementY }, opacity ); 122 123 } 124 125 /** 126 * @param caption 127 * @param font 128 * @param color 129 * @param metrics 130 * @param feature 131 * @param halo 132 * @param x 133 * @param y 134 * @param w 135 * @param h 136 * @param rotation 137 * @param anchorPoint 138 * @param displacement 139 * @param opacity 140 * @return the label 141 */ 142 public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, 143 Halo halo, int x, int y, int w, int h, double rotation, double[] anchorPoint, 144 double[] displacement, double opacity ) { 145 if ( rotation == 0.0 ) { 146 return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h, anchorPoint, 147 displacement, opacity ); 148 } 149 return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint, 150 displacement, opacity ); 151 152 } 153 154 /** 155 * Generates label-representations for a given <tt>LabelDisplayElement</tt>. 156 * <p> 157 * 158 * @param element 159 * @param projection 160 * @param g 161 * @return label-representations 162 * @throws Exception 163 */ 164 public static Label[] createLabels( LabelDisplayElement element, GeoTransform projection, Graphics2D g ) 165 throws Exception { 166 167 Label[] labels = new Label[0]; 168 Feature feature = element.getFeature(); 169 String caption = element.getLabel().evaluate( feature ); 170 171 // sanity check: empty labels are ignored 172 if ( caption == null || caption.trim().equals( "" ) ) { 173 return labels; 174 } 175 176 Geometry geometry = element.getGeometry(); 177 TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer(); 178 179 // gather font information 180 org.deegree.graphics.sld.Font sldFont = symbolizer.getFont(); 181 java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature ) 182 | sldFont.getWeight( feature ), 183 sldFont.getSize( feature ) ); 184 g.setFont( font ); 185 186 // the bboxing is not a good solution, the geometry should be used instead (rectangle or 187 // something) 188 // on the other hand, we'd need a flag to symbolize "use the surface as bounding box" 189 // anyway, so... TODO 190 ParameterValueType[] bbox = symbolizer.getBoundingBox(); 191 if ( bbox != null ) { 192 try { 193 double x = parseDouble( bbox[0].evaluate( feature ) ); 194 double y = parseDouble( bbox[1].evaluate( feature ) ); 195 double maxx = parseDouble( bbox[2].evaluate( feature ) ); 196 double maxy = parseDouble( bbox[3].evaluate( feature ) ); 197 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 198 return new Label[] { createLabelInABox( caption, font, sldFont.getColor( feature ), 199 symbolizer.getHalo(), x, y, maxx - x, maxy - y, feature, 200 projection, opacity ) }; 201 } catch ( NumberFormatException nfe ) { 202 LOG.logWarning( "Could not render text because of missing bbox attributes." ); 203 return labels; // empty array 204 } 205 } 206 207 FontRenderContext frc = g.getFontRenderContext(); 208 Rectangle2D bounds = font.getStringBounds( caption, frc ); 209 LineMetrics metrics = font.getLineMetrics( caption, frc ); 210 int w = (int) bounds.getWidth(); 211 int h = (int) bounds.getHeight(); 212 // int descent = (int) metrics.getDescent (); 213 214 if ( geometry instanceof Point || geometry instanceof MultiPoint ) { 215 216 // get screen coordinates 217 int[] coords = calcScreenCoordinates( projection, geometry ); 218 int x = coords[0]; 219 int y = coords[1]; 220 221 // default placement information 222 double rotation = 0.0; 223 double[] anchorPoint = { 0.0, 0.5 }; 224 double[] displacement = { 0.0, 0.0 }; 225 226 // use placement information from SLD 227 LabelPlacement lPlacement = symbolizer.getLabelPlacement(); 228 229 if ( lPlacement != null ) { 230 PointPlacement pPlacement = lPlacement.getPointPlacement(); 231 anchorPoint = pPlacement.getAnchorPoint( feature ); 232 displacement = pPlacement.getDisplacement( feature ); 233 rotation = pPlacement.getRotation( feature ); 234 } 235 236 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 237 labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 238 symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement, 239 opacity ) }; 240 } else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) { 241 242 // get screen coordinates 243 int[] coords = calcScreenCoordinates( projection, geometry ); 244 int x = coords[0]; 245 int y = coords[1]; 246 247 // default placement information 248 double rotation = 0.0; 249 double[] anchorPoint = { 0.5, 0.5 }; 250 double[] displacement = { 0.0, 0.0 }; 251 252 // use placement information from SLD 253 LabelPlacement lPlacement = symbolizer.getLabelPlacement(); 254 255 if ( lPlacement != null ) { 256 PointPlacement pPlacement = lPlacement.getPointPlacement(); 257 258 // check if the label is to be centered within the intersection 259 // of 260 // the screen surface and the polygon geometry 261 if ( pPlacement.isAuto() ) { 262 Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null ); 263 Geometry intersection = screenSurface.intersection( geometry ); 264 if ( intersection != null ) { 265 Position source = intersection.getCentroid().getPosition(); 266 x = (int) ( projection.getDestX( source.getX() ) + 0.5 ); 267 y = (int) ( projection.getDestY( source.getY() ) + 0.5 ); 268 } 269 } 270 anchorPoint = pPlacement.getAnchorPoint( feature ); 271 displacement = pPlacement.getDisplacement( feature ); 272 rotation = pPlacement.getRotation( feature ); 273 } 274 275 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 276 labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 277 symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement, 278 opacity ) 279 280 }; 281 } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) { 282 Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null ); 283 Geometry intersection = screenSurface.intersection( geometry ); 284 if ( intersection != null ) { 285 List<Label> list = null; 286 if ( intersection instanceof Curve ) { 287 list = createLabels( (Curve) intersection, element, g, projection ); 288 } else if ( intersection instanceof MultiCurve ) { 289 list = createLabels( (MultiCurve) intersection, element, g, projection ); 290 } else { 291 throw new Exception( "Intersection produced unexpected " + "geometry type: '" 292 + intersection.getClass().getName() + "'!" ); 293 } 294 labels = new Label[list.size()]; 295 for ( int i = 0; i < labels.length; i++ ) { 296 Label label = list.get( i ); 297 labels[i] = label; 298 } 299 } 300 } else { 301 throw new Exception( "LabelFactory does not implement generation " + "of Labels from geometries of type: '" 302 + geometry.getClass().getName() + "'!" ); 303 } 304 return labels; 305 } 306 307 /** 308 * Determines positions on the given <tt>MultiCurve</tt> where a caption could be drawn. For each of this positons, 309 * three candidates are produced; one on the line, one above of it and one below. 310 * <p> 311 * 312 * @param multiCurve 313 * @param element 314 * @param g 315 * @param projection 316 * @return ArrayList containing Arrays of Label-objects 317 * @throws FilterEvaluationException 318 */ 319 public static List<Label> createLabels( MultiCurve multiCurve, LabelDisplayElement element, Graphics2D g, 320 GeoTransform projection ) 321 throws FilterEvaluationException { 322 323 List<Label> placements = Collections.synchronizedList( new ArrayList<Label>( 10 ) ); 324 for ( int i = 0; i < multiCurve.getSize(); i++ ) { 325 Curve curve = multiCurve.getCurveAt( i ); 326 placements.addAll( createLabels( curve, element, g, projection ) ); 327 } 328 return placements; 329 } 330 331 /** 332 * Determines positions on the given <tt>Curve</tt> where a caption could be drawn. For each of this positons, three 333 * candidates are produced; one on the line, one above of it and one below. 334 * <p> 335 * 336 * @param curve 337 * @param element 338 * @param g 339 * @param projection 340 * @return ArrayList containing Arrays of Label-objects 341 * @throws FilterEvaluationException 342 */ 343 public static ArrayList<Label> createLabels( Curve curve, LabelDisplayElement element, Graphics2D g, 344 GeoTransform projection ) 345 throws FilterEvaluationException { 346 347 Feature feature = element.getFeature(); 348 349 // determine the placement type and parameters from the TextSymbolizer 350 double perpendicularOffset = 0.0; 351 int placementType = LinePlacement.TYPE_ABSOLUTE; 352 double lineWidth = 3.0; 353 int gap = 6; 354 TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() ); 355 if ( symbolizer.getLabelPlacement() != null ) { 356 LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement(); 357 if ( linePlacement != null ) { 358 placementType = linePlacement.getPlacementType( feature ); 359 perpendicularOffset = linePlacement.getPerpendicularOffset( feature ); 360 lineWidth = linePlacement.getLineWidth( feature ); 361 gap = linePlacement.getGap( feature ); 362 } 363 } 364 365 // get width & height of the caption 366 String caption = element.getLabel().evaluate( feature ); 367 org.deegree.graphics.sld.Font sldFont = symbolizer.getFont(); 368 java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature ) 369 | sldFont.getWeight( feature ), 370 sldFont.getSize( feature ) ); 371 g.setFont( font ); 372 FontRenderContext frc = g.getFontRenderContext(); 373 Rectangle2D bounds = font.getStringBounds( caption, frc ); 374 LineMetrics metrics = font.getLineMetrics( caption, frc ); 375 double width = bounds.getWidth(); 376 double height = bounds.getHeight(); 377 378 // get screen coordinates of the line 379 int[][] pos = calcScreenCoordinates( projection, curve ); 380 381 // ideal distance from the line 382 double delta = height / 2.0 + lineWidth / 2.0; 383 384 // walk along the linestring and "collect" possible placement positions 385 int w = (int) width; 386 int lastX = pos[0][0]; 387 int lastY = pos[1][0]; 388 int count = pos[2][0]; 389 int boxStartX = lastX; 390 int boxStartY = lastY; 391 392 ArrayList<Label> labels = new ArrayList<Label>( 100 ); 393 List<int[]> eCandidates = Collections.synchronizedList( new ArrayList<int[]>( 100 ) ); 394 int i = 1; 395 int kk = 0; 396 while ( i < count && kk < 100 ) { 397 kk++; 398 int x = pos[0][i]; 399 int y = pos[1][i]; 400 401 // segment found where endpoint of box should be located? 402 if ( getDistance( boxStartX, boxStartY, x, y ) >= w ) { 403 404 double offx = 0, offy = 0; 405 406 if ( abs( perpendicularOffset ) > 1E-10 ) { 407 double dx = lastX - x; 408 double dy = lastY - y; 409 410 double len = sqrt( dx * dx + dy * dy ); 411 dx /= len; 412 dy /= len; 413 414 offx += perpendicularOffset * dy; 415 offy += -perpendicularOffset * dx; 416 } 417 418 int[] p0 = new int[] { boxStartX, boxStartY }; 419 int[] p1 = new int[] { lastX, lastY }; 420 int[] p2 = new int[] { x, y }; 421 422 int[] p = findPointWithDistance( p0, p1, p2, w ); 423 x = p[0]; 424 y = p[1]; 425 426 lastX = x; 427 lastY = y; 428 int boxEndX = x; 429 int boxEndY = y; 430 431 // does the linesegment run from right to left? 432 if ( x <= boxStartX ) { 433 boxEndX = boxStartX; 434 boxEndY = boxStartY; 435 boxStartX = x; 436 boxStartY = y; 437 x = boxEndX; 438 y = boxEndY; 439 } 440 441 double rotation = getRotation( boxStartX, boxStartY, x, y ); 442 double[] deviation = calcDeviation( new int[] { boxStartX, boxStartY }, new int[] { boxEndX, boxEndY }, 443 eCandidates ); 444 445 Label label = null; 446 447 boxStartX += offx; 448 boxStartY += offy; 449 boxEndX += offx; 450 boxEndY += offy; 451 452 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 453 454 switch ( placementType ) { 455 case LinePlacement.TYPE_ABSOLUTE: { 456 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 457 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 458 rotation, 0.0, 0.5, ( w - width ) / 2, 0, opacity ); 459 break; 460 } 461 case LinePlacement.TYPE_ABOVE: { 462 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 463 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 464 rotation, 0.0, 0.5, ( w - width ) / 2, delta + deviation[0], opacity ); 465 break; 466 } 467 case LinePlacement.TYPE_BELOW: 468 case LinePlacement.TYPE_AUTO: { 469 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 470 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 471 rotation, 0.0, 0.5, ( w - width ) / 2, -delta - deviation[1], opacity ); 472 break; 473 } 474 case LinePlacement.TYPE_CENTER: { 475 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 476 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 477 rotation, 0.0, 0.5, ( w - width ) / 2, 0.0, opacity ); 478 break; 479 } 480 default: { 481 // nothing? 482 } 483 } 484 labels.add( label ); 485 boxStartX = lastX; 486 boxStartY = lastY; 487 eCandidates.clear(); 488 } else { 489 eCandidates.add( new int[] { x, y } ); 490 lastX = x; 491 lastY = y; 492 i++; 493 } 494 } 495 496 // pick lists of boxes on the linestring 497 ArrayList<Label> pick = new ArrayList<Label>( 100 ); 498 int n = labels.size(); 499 for ( int j = n / 2; j < labels.size(); j += ( gap + 1 ) ) { 500 pick.add( labels.get( j ) ); 501 } 502 for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) { 503 pick.add( labels.get( j ) ); 504 } 505 return pick; 506 } 507 508 /** 509 * Calculates the maximum deviation that points on a linestring have to the ideal line between the starting point 510 * and the end point. 511 * <p> 512 * The ideal line is thought to be running from left to right, the left deviation value generally is above the line, 513 * the right value is below. 514 * <p> 515 * 516 * @param start 517 * starting point of the linestring 518 * @param end 519 * end point of the linestring 520 * @param points 521 * points in between 522 * @return maximum deviation 523 */ 524 public static double[] calcDeviation( int[] start, int[] end, List<int[]> points ) { 525 526 // extreme deviation to the left 527 double d1 = 0.0; 528 // extreme deviation to the right 529 double d2 = 0.0; 530 Iterator<int[]> it = points.iterator(); 531 532 // eventually swap start and end point 533 if ( start[0] > end[0] ) { 534 int[] tmp = start; 535 start = end; 536 end = tmp; 537 } 538 539 if ( start[0] != end[0] ) { 540 // label orientation is not completly vertical 541 if ( start[1] != end[1] ) { 542 // label orientation is not completly horizontal 543 while ( it.hasNext() ) { 544 int[] point = it.next(); 545 double u = ( (double) end[1] - (double) start[1] ) / ( (double) end[0] - (double) start[0] ); 546 double x = ( u * u * start[0] - u * ( (double) start[1] - (double) point[1] ) + point[0] ) 547 / ( 1.0 + u * u ); 548 double y = ( x - start[0] ) * u + start[1]; 549 double d = getDistance( point, new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) } ); 550 if ( y >= point[1] ) { 551 // candidate for left extreme value 552 if ( d > d1 ) { 553 d1 = d; 554 } 555 } else if ( d > d2 ) { 556 // candidate for right extreme value 557 d2 = d; 558 } 559 } 560 } else { 561 // label orientation is completly horizontal 562 while ( it.hasNext() ) { 563 int[] point = it.next(); 564 double d = point[1] - start[1]; 565 if ( d < 0 ) { 566 // candidate for left extreme value 567 if ( -d > d1 ) { 568 d1 = -d; 569 } 570 } else if ( d > d2 ) { 571 // candidate for left extreme value 572 d2 = d; 573 } 574 } 575 } 576 } else { 577 // label orientation is completly vertical 578 while ( it.hasNext() ) { 579 int[] point = it.next(); 580 double d = point[0] - start[0]; 581 if ( d < 0 ) { 582 // candidate for left extreme value 583 if ( -d > d1 ) { 584 d1 = -d; 585 } 586 } else if ( d > d2 ) { 587 // candidate for right extreme value 588 d2 = d; 589 } 590 } 591 } 592 return new double[] { d1, d2 }; 593 } 594 595 /** 596 * Finds a point on the line between p1 and p2 that has a certain distance from point p0 (provided that there is 597 * such a point). 598 * <p> 599 * 600 * @param p0 601 * point that is used as reference point for the distance 602 * @param p1 603 * starting point of the line 604 * @param p2 605 * end point of the line 606 * @param d 607 * distance 608 * @return point on the line between p1 and p2 that has a certain distance from point p0 609 */ 610 public static int[] findPointWithDistance( int[] p0, int[] p1, int[] p2, int d ) { 611 612 double x, y; 613 double x0 = p0[0]; 614 double y0 = p0[1]; 615 double x1 = p1[0]; 616 double y1 = p1[1]; 617 double x2 = p2[0]; 618 double y2 = p2[1]; 619 620 if ( x1 != x2 ) { 621 // line segment does not run vertical 622 double u = ( y2 - y1 ) / ( x2 - x1 ); 623 double p = -2 * ( x0 + u * u * x1 - u * ( y1 - y0 ) ) / ( u * u + 1 ); 624 double q = ( ( y1 - y0 ) * ( y1 - y0 ) + u * u * x1 * x1 + x0 * x0 - 2 * u * x1 * ( y1 - y0 ) - d * d ) 625 / ( u * u + 1 ); 626 double minX = x1; 627 double maxX = x2; 628 double minY = y1; 629 double maxY = y2; 630 if ( minX > maxX ) { 631 minX = x2; 632 maxX = x1; 633 } 634 if ( minY > maxY ) { 635 minY = y2; 636 maxY = y1; 637 } 638 x = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 639 if ( x < minX || x > maxX ) { 640 x = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 641 } 642 y = ( x - x1 ) * u + y1; 643 } else { 644 // vertical line segment 645 x = x1; 646 double minY = y1; 647 double maxY = y2; 648 649 if ( minY > maxY ) { 650 minY = y2; 651 maxY = y1; 652 } 653 654 double p = -2 * y0; 655 double q = y0 * y0 + ( x1 - x0 ) * ( x1 - x0 ) - d * d; 656 657 y = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 658 if ( y < minY || y > maxY ) { 659 y = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 660 } 661 } 662 return new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) }; 663 } 664 665 /** 666 * @param text 667 * @param font 668 * @param color 669 * @param halo 670 * @param x 671 * @param y 672 * @param w 673 * @param h 674 * @param feature 675 * @param projection 676 * @param opacity 677 * @return the label 678 */ 679 public static HorizontalLabel createLabelInABox( String text, Font font, Color color, Halo halo, double x, 680 double y, double w, double h, Feature feature, 681 GeoTransform projection, double opacity ) { 682 683 // one could probably get a significant speedup if no new fonts would be created (ie, use 684 // another method to find out the text extent of a given string and font size) 685 FontRenderContext frc = new FontRenderContext( null, false, false ); 686 font = font.deriveFont( 128f ); // TODO hardcoded max 687 Rectangle2D rect = font.getStringBounds( text, frc ); 688 int ix = (int) projection.getDestX( x ); 689 int iy = (int) projection.getDestY( y ); 690 int iw = abs( ( (int) projection.getDestX( x + w ) ) - ix ); 691 int ih = abs( ( (int) projection.getDestY( y + h ) ) - iy ); 692 while ( rect.getWidth() > iw && rect.getHeight() > ih && font.getSize2D() > 5 ) { 693 font = font.deriveFont( font.getSize2D() - 4 ); 694 rect = font.getStringBounds( text, frc ); 695 } 696 697 LOG.logDebug( "Determined font size from bounding box", font.getSize2D() ); 698 699 return new HorizontalLabel( text, font, color, font.getLineMetrics( text, frc ), feature, halo, ix, iy, iw, ih, 700 new double[] { 0, 0 }, new double[] { 0, 0 }, opacity ); 701 } 702 703 /** 704 * @param x1 705 * @param y1 706 * @param x2 707 * @param y2 708 * @return rotation (degree) of the line between two passed coordinates 709 */ 710 public static double getRotation( double x1, double y1, double x2, double y2 ) { 711 double dx = x2 - x1; 712 double dy = y2 - y1; 713 714 return Math.toDegrees( Math.atan( dy / dx ) ); 715 } 716 717 /** 718 * @param p1 719 * @param p2 720 * @return distance between two passed coordinates 721 */ 722 public static double getDistance( int[] p1, int[] p2 ) { 723 double dx = p1[0] - p2[0]; 724 double dy = p1[1] - p2[1]; 725 return Math.sqrt( dx * dx + dy * dy ); 726 } 727 728 /** 729 * @param x1 730 * @param y1 731 * @param x2 732 * @param y2 733 * @return distance between two passed coordinates 734 */ 735 public static double getDistance( double x1, double y1, double x2, double y2 ) { 736 double dx = x2 - x1; 737 double dy = y2 - y1; 738 return Math.sqrt( dx * dx + dy * dy ); 739 } 740 741 /** 742 * Calculates the screen coordinates of the given <tt>Curve</tt>. physical screen coordinates 743 * 744 * @param projection 745 * @param curve 746 * @return the coordinates 747 */ 748 public static int[][] calcScreenCoordinates( GeoTransform projection, Curve curve ) { 749 750 LineString lineString = null; 751 try { 752 lineString = curve.getAsLineString(); 753 } catch ( GeometryException e ) { 754 // ignored, assumed to be valid 755 } 756 757 int count = lineString.getNumberOfPoints(); 758 int[][] pos = new int[3][]; 759 pos[0] = new int[count]; 760 pos[1] = new int[count]; 761 pos[2] = new int[1]; 762 763 int k = 0; 764 for ( int i = 0; i < count; i++ ) { 765 Position position = lineString.getPositionAt( i ); 766 double tx = projection.getDestX( position.getX() ); 767 double ty = projection.getDestY( position.getY() ); 768 if ( i > 0 ) { 769 if ( getDistance( tx, ty, pos[0][k - 1], pos[1][k - 1] ) > 1 ) { 770 pos[0][k] = (int) ( tx + 0.5 ); 771 pos[1][k] = (int) ( ty + 0.5 ); 772 k++; 773 } 774 } else { 775 pos[0][k] = (int) ( tx + 0.5 ); 776 pos[1][k] = (int) ( ty + 0.5 ); 777 k++; 778 } 779 } 780 pos[2][0] = k; 781 782 return pos; 783 } 784 785 /** 786 * Returns the physical (screen) coordinates. 787 * 788 * @param projection 789 * @param geometry 790 * 791 * @return physical screen coordinates 792 */ 793 public static int[] calcScreenCoordinates( GeoTransform projection, Geometry geometry ) { 794 795 int[] coords = new int[2]; 796 797 Position source = null; 798 if ( geometry instanceof Point ) { 799 source = ( (Point) geometry ).getPosition(); 800 } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) { 801 source = geometry.getCentroid().getPosition(); 802 } else { 803 source = geometry.getCentroid().getPosition(); 804 } 805 806 coords[0] = (int) ( projection.getDestX( source.getX() ) + 0.5 ); 807 coords[1] = (int) ( projection.getDestY( source.getY() ) + 0.5 ); 808 return coords; 809 } 810 }