001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/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: 27114 $ $Date: 2010-09-30 16:04:40 +0200 (Do, 30 Sep 2010) $ 085 */ 086 public class LabelFactory { 087 088 private static final ILogger LOG = getLogger( LabelFactory.class ); 089 090 /** 091 * @param caption 092 * @param font 093 * @param color 094 * @param metrics 095 * @param feature 096 * @param halo 097 * @param x 098 * @param y 099 * @param w 100 * @param h 101 * @param rotation 102 * @param anchorPointX 103 * @param anchorPointY 104 * @param displacementX 105 * @param displacementY 106 * @return label-representations 107 * @deprecated use the one with opacity instead 108 */ 109 @Deprecated 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 ) { 113 return createLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPointX, 114 anchorPointY, displacementX, displacementY, 1.0 ); 115 } 116 117 /** 118 * @param caption 119 * @param font 120 * @param color 121 * @param metrics 122 * @param feature 123 * @param halo 124 * @param x 125 * @param y 126 * @param w 127 * @param h 128 * @param rotation 129 * @param anchorPointX 130 * @param anchorPointY 131 * @param displacementX 132 * @param displacementY 133 * @param opacity 134 * @return label representations 135 */ 136 public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, 137 Halo halo, int x, int y, int w, int h, double rotation, double anchorPointX, 138 double anchorPointY, double displacementX, double displacementY, double opacity ) { 139 if ( rotation == 0.0 ) { 140 return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h, 141 new double[] { anchorPointX, anchorPointY }, new double[] { displacementX, 142 displacementY }, 143 opacity ); 144 } 145 return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, 146 new double[] { anchorPointX, anchorPointY }, new double[] { displacementX, 147 displacementY }, opacity ); 148 149 } 150 151 /** 152 * @param caption 153 * @param font 154 * @param color 155 * @param metrics 156 * @param feature 157 * @param halo 158 * @param x 159 * @param y 160 * @param w 161 * @param h 162 * @param rotation 163 * @param anchorPoint 164 * @param displacement 165 * @param opacity 166 * @return the label 167 */ 168 public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, 169 Halo halo, int x, int y, int w, int h, double rotation, double[] anchorPoint, 170 double[] displacement, double opacity ) { 171 if ( rotation == 0.0 ) { 172 return new HorizontalLabel( caption, font, color, metrics, feature, halo, x, y, w, h, anchorPoint, 173 displacement, opacity ); 174 } 175 return new RotatedLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint, 176 displacement, opacity ); 177 178 } 179 180 /** 181 * @param caption 182 * @param font 183 * @param color 184 * @param metrics 185 * @param feature 186 * @param halo 187 * @param x 188 * @param y 189 * @param w 190 * @param h 191 * @param rotation 192 * @param anchorPoint 193 * @param displacement 194 * @return label-representations 195 * @deprecated use the one with opacity instead 196 */ 197 @Deprecated 198 public static Label createLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, 199 Halo halo, int x, int y, int w, int h, double rotation, double[] anchorPoint, 200 double[] displacement ) { 201 return createLabel( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint, 202 displacement, 1.0 ); 203 } 204 205 /** 206 * Generates label-representations for a given <tt>LabelDisplayElement</tt>. 207 * <p> 208 * 209 * @param element 210 * @param projection 211 * @param g 212 * @return label-representations 213 * @throws Exception 214 */ 215 public static Label[] createLabels( LabelDisplayElement element, GeoTransform projection, Graphics2D g ) 216 throws Exception { 217 218 Label[] labels = new Label[0]; 219 Feature feature = element.getFeature(); 220 String caption = element.getLabel().evaluate( feature ); 221 222 // sanity check: empty labels are ignored 223 if ( caption == null || caption.trim().equals( "" ) ) { 224 return labels; 225 } 226 227 Geometry geometry = element.getGeometry(); 228 TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer(); 229 230 // gather font information 231 org.deegree.graphics.sld.Font sldFont = symbolizer.getFont(); 232 java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature ) 233 | sldFont.getWeight( feature ), 234 sldFont.getSize( feature ) ); 235 g.setFont( font ); 236 237 // the bboxing is not a good solution, the geometry should be used instead (rectangle or 238 // something) 239 // on the other hand, we'd need a flag to symbolize "use the surface as bounding box" 240 // anyway, so... TODO 241 ParameterValueType[] bbox = symbolizer.getBoundingBox(); 242 if ( bbox != null ) { 243 try { 244 double x = parseDouble( bbox[0].evaluate( feature ) ); 245 double y = parseDouble( bbox[1].evaluate( feature ) ); 246 double maxx = parseDouble( bbox[2].evaluate( feature ) ); 247 double maxy = parseDouble( bbox[3].evaluate( feature ) ); 248 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 249 return new Label[] { createLabelInABox( caption, font, sldFont.getColor( feature ), 250 symbolizer.getHalo(), x, y, maxx - x, maxy - y, feature, 251 projection, opacity ) }; 252 } catch ( NumberFormatException nfe ) { 253 LOG.logWarning( "Could not render text because of missing bbox attributes." ); 254 return labels; // empty array 255 } 256 } 257 258 FontRenderContext frc = g.getFontRenderContext(); 259 Rectangle2D bounds = font.getStringBounds( caption, frc ); 260 LineMetrics metrics = font.getLineMetrics( caption, frc ); 261 int w = (int) bounds.getWidth(); 262 int h = (int) bounds.getHeight(); 263 // int descent = (int) metrics.getDescent (); 264 265 if ( geometry instanceof Point || geometry instanceof MultiPoint ) { 266 267 // get screen coordinates 268 int[] coords = calcScreenCoordinates( projection, geometry ); 269 int x = coords[0]; 270 int y = coords[1]; 271 272 // default placement information 273 double rotation = 0.0; 274 double[] anchorPoint = { 0.0, 0.5 }; 275 double[] displacement = { 0.0, 0.0 }; 276 277 // use placement information from SLD 278 LabelPlacement lPlacement = symbolizer.getLabelPlacement(); 279 280 if ( lPlacement != null ) { 281 PointPlacement pPlacement = lPlacement.getPointPlacement(); 282 anchorPoint = pPlacement.getAnchorPoint( feature ); 283 displacement = pPlacement.getDisplacement( feature ); 284 rotation = pPlacement.getRotation( feature ); 285 } 286 287 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 288 labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 289 symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement, 290 opacity ) }; 291 } else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) { 292 293 // get screen coordinates 294 int[] coords = calcScreenCoordinates( projection, geometry ); 295 int x = coords[0]; 296 int y = coords[1]; 297 298 // default placement information 299 double rotation = 0.0; 300 double[] anchorPoint = { 0.5, 0.5 }; 301 double[] displacement = { 0.0, 0.0 }; 302 303 // use placement information from SLD 304 LabelPlacement lPlacement = symbolizer.getLabelPlacement(); 305 306 if ( lPlacement != null ) { 307 PointPlacement pPlacement = lPlacement.getPointPlacement(); 308 309 // check if the label is to be centered within the intersection 310 // of 311 // the screen surface and the polygon geometry 312 if ( pPlacement.isAuto() ) { 313 Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null ); 314 Geometry intersection = screenSurface.intersection( geometry ); 315 if ( intersection != null ) { 316 Position source = intersection.getCentroid().getPosition(); 317 x = (int) ( projection.getDestX( source.getX() ) + 0.5 ); 318 y = (int) ( projection.getDestY( source.getY() ) + 0.5 ); 319 } 320 } 321 anchorPoint = pPlacement.getAnchorPoint( feature ); 322 displacement = pPlacement.getDisplacement( feature ); 323 rotation = pPlacement.getRotation( feature ); 324 } 325 326 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 327 labels = new Label[] { createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 328 symbolizer.getHalo(), x, y, w, h, rotation, anchorPoint, displacement, 329 opacity ) 330 331 }; 332 } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) { 333 Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(), null ); 334 Geometry intersection = screenSurface.intersection( geometry ); 335 if ( intersection != null ) { 336 List<Label> list = null; 337 if ( intersection instanceof Curve ) { 338 list = createLabels( (Curve) intersection, element, g, projection ); 339 } else if ( intersection instanceof MultiCurve ) { 340 list = createLabels( (MultiCurve) intersection, element, g, projection ); 341 } else { 342 throw new Exception( "Intersection produced unexpected " + "geometry type: '" 343 + intersection.getClass().getName() + "'!" ); 344 } 345 labels = new Label[list.size()]; 346 for ( int i = 0; i < labels.length; i++ ) { 347 Label label = list.get( i ); 348 labels[i] = label; 349 } 350 } 351 } else { 352 throw new Exception( "LabelFactory does not implement generation " + "of Labels from geometries of type: '" 353 + geometry.getClass().getName() + "'!" ); 354 } 355 return labels; 356 } 357 358 /** 359 * Determines positions on the given <tt>MultiCurve</tt> where a caption could be drawn. For each of this positons, 360 * three candidates are produced; one on the line, one above of it and one below. 361 * <p> 362 * 363 * @param multiCurve 364 * @param element 365 * @param g 366 * @param projection 367 * @return ArrayList containing Arrays of Label-objects 368 * @throws FilterEvaluationException 369 */ 370 public static List<Label> createLabels( MultiCurve multiCurve, LabelDisplayElement element, Graphics2D g, 371 GeoTransform projection ) 372 throws FilterEvaluationException { 373 374 List<Label> placements = Collections.synchronizedList( new ArrayList<Label>( 10 ) ); 375 for ( int i = 0; i < multiCurve.getSize(); i++ ) { 376 Curve curve = multiCurve.getCurveAt( i ); 377 placements.addAll( createLabels( curve, element, g, projection ) ); 378 } 379 return placements; 380 } 381 382 /** 383 * Determines positions on the given <tt>Curve</tt> where a caption could be drawn. For each of this positons, three 384 * candidates are produced; one on the line, one above of it and one below. 385 * <p> 386 * 387 * @param curve 388 * @param element 389 * @param g 390 * @param projection 391 * @return ArrayList containing Arrays of Label-objects 392 * @throws FilterEvaluationException 393 */ 394 public static ArrayList<Label> createLabels( Curve curve, LabelDisplayElement element, Graphics2D g, 395 GeoTransform projection ) 396 throws FilterEvaluationException { 397 398 Feature feature = element.getFeature(); 399 400 // determine the placement type and parameters from the TextSymbolizer 401 double perpendicularOffset = 0.0; 402 int placementType = LinePlacement.TYPE_ABSOLUTE; 403 double lineWidth = 3.0; 404 int gap = 6; 405 TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() ); 406 if ( symbolizer.getLabelPlacement() != null ) { 407 LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement(); 408 if ( linePlacement != null ) { 409 placementType = linePlacement.getPlacementType( feature ); 410 perpendicularOffset = linePlacement.getPerpendicularOffset( feature ); 411 lineWidth = linePlacement.getLineWidth( feature ); 412 gap = linePlacement.getGap( feature ); 413 } 414 } 415 416 // get width & height of the caption 417 String caption = element.getLabel().evaluate( feature ); 418 org.deegree.graphics.sld.Font sldFont = symbolizer.getFont(); 419 java.awt.Font font = new java.awt.Font( sldFont.getFamily( feature ), sldFont.getStyle( feature ) 420 | sldFont.getWeight( feature ), 421 sldFont.getSize( feature ) ); 422 g.setFont( font ); 423 FontRenderContext frc = g.getFontRenderContext(); 424 Rectangle2D bounds = font.getStringBounds( caption, frc ); 425 LineMetrics metrics = font.getLineMetrics( caption, frc ); 426 double width = bounds.getWidth(); 427 double height = bounds.getHeight(); 428 429 // get screen coordinates of the line 430 int[][] pos = calcScreenCoordinates( projection, curve ); 431 432 // ideal distance from the line 433 double delta = height / 2.0 + lineWidth / 2.0; 434 435 // walk along the linestring and "collect" possible placement positions 436 int w = (int) width; 437 int lastX = pos[0][0]; 438 int lastY = pos[1][0]; 439 int count = pos[2][0]; 440 int boxStartX = lastX; 441 int boxStartY = lastY; 442 443 ArrayList<Label> labels = new ArrayList<Label>( 100 ); 444 List<int[]> eCandidates = Collections.synchronizedList( new ArrayList<int[]>( 100 ) ); 445 int i = 1; 446 int kk = 0; 447 while ( i < count && kk < 100 ) { 448 kk++; 449 int x = pos[0][i]; 450 int y = pos[1][i]; 451 452 // segment found where endpoint of box should be located? 453 if ( getDistance( boxStartX, boxStartY, x, y ) >= w ) { 454 455 double offx = 0, offy = 0; 456 457 if ( abs( perpendicularOffset ) > 1E-10 ) { 458 double dx = lastX - x; 459 double dy = lastY - y; 460 461 double len = sqrt( dx * dx + dy * dy ); 462 dx /= len; 463 dy /= len; 464 465 offx += perpendicularOffset * dy; 466 offy += -perpendicularOffset * dx; 467 } 468 469 int[] p0 = new int[] { boxStartX, boxStartY }; 470 int[] p1 = new int[] { lastX, lastY }; 471 int[] p2 = new int[] { x, y }; 472 473 int[] p = findPointWithDistance( p0, p1, p2, w ); 474 x = p[0]; 475 y = p[1]; 476 477 lastX = x; 478 lastY = y; 479 int boxEndX = x; 480 int boxEndY = y; 481 482 // does the linesegment run from right to left? 483 if ( x <= boxStartX ) { 484 boxEndX = boxStartX; 485 boxEndY = boxStartY; 486 boxStartX = x; 487 boxStartY = y; 488 x = boxEndX; 489 y = boxEndY; 490 } 491 492 double rotation = getRotation( boxStartX, boxStartY, x, y ); 493 double[] deviation = calcDeviation( new int[] { boxStartX, boxStartY }, new int[] { boxEndX, boxEndY }, 494 eCandidates ); 495 496 Label label = null; 497 498 boxStartX += offx; 499 boxStartY += offy; 500 boxEndX += offx; 501 boxEndY += offy; 502 503 double opacity = symbolizer.getFill() == null ? 1 : symbolizer.getFill().getOpacity( feature ); 504 505 switch ( placementType ) { 506 case LinePlacement.TYPE_ABSOLUTE: { 507 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 508 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 509 rotation, 0.0, 0.5, ( w - width ) / 2, 0, opacity ); 510 break; 511 } 512 case LinePlacement.TYPE_ABOVE: { 513 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 514 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 515 rotation, 0.0, 0.5, ( w - width ) / 2, delta + deviation[0], opacity ); 516 break; 517 } 518 case LinePlacement.TYPE_BELOW: 519 case LinePlacement.TYPE_AUTO: { 520 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 521 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 522 rotation, 0.0, 0.5, ( w - width ) / 2, -delta - deviation[1], opacity ); 523 break; 524 } 525 case LinePlacement.TYPE_CENTER: { 526 label = createLabel( caption, font, sldFont.getColor( feature ), metrics, feature, 527 symbolizer.getHalo(), boxStartX, boxStartY, (int) width, (int) height, 528 rotation, 0.0, 0.5, ( w - width ) / 2, 0.0, opacity ); 529 break; 530 } 531 default: { 532 // nothing? 533 } 534 } 535 labels.add( label ); 536 boxStartX = lastX; 537 boxStartY = lastY; 538 eCandidates.clear(); 539 } else { 540 eCandidates.add( new int[] { x, y } ); 541 lastX = x; 542 lastY = y; 543 i++; 544 } 545 } 546 547 // pick lists of boxes on the linestring 548 ArrayList<Label> pick = new ArrayList<Label>( 100 ); 549 int n = labels.size(); 550 for ( int j = n / 2; j < labels.size(); j += ( gap + 1 ) ) { 551 pick.add( labels.get( j ) ); 552 } 553 for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) { 554 pick.add( labels.get( j ) ); 555 } 556 return pick; 557 } 558 559 /** 560 * Calculates the maximum deviation that points on a linestring have to the ideal line between the starting point 561 * and the end point. 562 * <p> 563 * The ideal line is thought to be running from left to right, the left deviation value generally is above the line, 564 * the right value is below. 565 * <p> 566 * 567 * @param start 568 * starting point of the linestring 569 * @param end 570 * end point of the linestring 571 * @param points 572 * points in between 573 * @return maximum deviation 574 */ 575 public static double[] calcDeviation( int[] start, int[] end, List<int[]> points ) { 576 577 // extreme deviation to the left 578 double d1 = 0.0; 579 // extreme deviation to the right 580 double d2 = 0.0; 581 Iterator<int[]> it = points.iterator(); 582 583 // eventually swap start and end point 584 if ( start[0] > end[0] ) { 585 int[] tmp = start; 586 start = end; 587 end = tmp; 588 } 589 590 if ( start[0] != end[0] ) { 591 // label orientation is not completly vertical 592 if ( start[1] != end[1] ) { 593 // label orientation is not completly horizontal 594 while ( it.hasNext() ) { 595 int[] point = it.next(); 596 double u = ( (double) end[1] - (double) start[1] ) / ( (double) end[0] - (double) start[0] ); 597 double x = ( u * u * start[0] - u * ( (double) start[1] - (double) point[1] ) + point[0] ) 598 / ( 1.0 + u * u ); 599 double y = ( x - start[0] ) * u + start[1]; 600 double d = getDistance( point, new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) } ); 601 if ( y >= point[1] ) { 602 // candidate for left extreme value 603 if ( d > d1 ) { 604 d1 = d; 605 } 606 } else if ( d > d2 ) { 607 // candidate for right extreme value 608 d2 = d; 609 } 610 } 611 } else { 612 // label orientation is completly horizontal 613 while ( it.hasNext() ) { 614 int[] point = it.next(); 615 double d = point[1] - start[1]; 616 if ( d < 0 ) { 617 // candidate for left extreme value 618 if ( -d > d1 ) { 619 d1 = -d; 620 } 621 } else if ( d > d2 ) { 622 // candidate for left extreme value 623 d2 = d; 624 } 625 } 626 } 627 } else { 628 // label orientation is completly vertical 629 while ( it.hasNext() ) { 630 int[] point = it.next(); 631 double d = point[0] - start[0]; 632 if ( d < 0 ) { 633 // candidate for left extreme value 634 if ( -d > d1 ) { 635 d1 = -d; 636 } 637 } else if ( d > d2 ) { 638 // candidate for right extreme value 639 d2 = d; 640 } 641 } 642 } 643 return new double[] { d1, d2 }; 644 } 645 646 /** 647 * Finds a point on the line between p1 and p2 that has a certain distance from point p0 (provided that there is 648 * such a point). 649 * <p> 650 * 651 * @param p0 652 * point that is used as reference point for the distance 653 * @param p1 654 * starting point of the line 655 * @param p2 656 * end point of the line 657 * @param d 658 * distance 659 * @return point on the line between p1 and p2 that has a certain distance from point p0 660 */ 661 public static int[] findPointWithDistance( int[] p0, int[] p1, int[] p2, int d ) { 662 663 double x, y; 664 double x0 = p0[0]; 665 double y0 = p0[1]; 666 double x1 = p1[0]; 667 double y1 = p1[1]; 668 double x2 = p2[0]; 669 double y2 = p2[1]; 670 671 if ( x1 != x2 ) { 672 // line segment does not run vertical 673 double u = ( y2 - y1 ) / ( x2 - x1 ); 674 double p = -2 * ( x0 + u * u * x1 - u * ( y1 - y0 ) ) / ( u * u + 1 ); 675 double q = ( ( y1 - y0 ) * ( y1 - y0 ) + u * u * x1 * x1 + x0 * x0 - 2 * u * x1 * ( y1 - y0 ) - d * d ) 676 / ( u * u + 1 ); 677 double minX = x1; 678 double maxX = x2; 679 double minY = y1; 680 double maxY = y2; 681 if ( minX > maxX ) { 682 minX = x2; 683 maxX = x1; 684 } 685 if ( minY > maxY ) { 686 minY = y2; 687 maxY = y1; 688 } 689 x = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 690 if ( x < minX || x > maxX ) { 691 x = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 692 } 693 y = ( x - x1 ) * u + y1; 694 } else { 695 // vertical line segment 696 x = x1; 697 double minY = y1; 698 double maxY = y2; 699 700 if ( minY > maxY ) { 701 minY = y2; 702 maxY = y1; 703 } 704 705 double p = -2 * y0; 706 double q = y0 * y0 + ( x1 - x0 ) * ( x1 - x0 ) - d * d; 707 708 y = -p / 2 - Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 709 if ( y < minY || y > maxY ) { 710 y = -p / 2 + Math.sqrt( ( p / 2 ) * ( p / 2 ) - q ); 711 } 712 } 713 return new int[] { (int) ( x + 0.5 ), (int) ( y + 0.5 ) }; 714 } 715 716 /** 717 * Creates a label that fits in the specified box. 718 * 719 * @param text 720 * @param font 721 * @param color 722 * @param halo 723 * @param x 724 * @param y 725 * @param w 726 * @param h 727 * @param feature 728 * @param projection 729 * @return the label 730 * @deprecated use the one with opacity instead 731 */ 732 @Deprecated 733 public static HorizontalLabel createLabelInABox( String text, Font font, Color color, Halo halo, double x, 734 double y, double w, double h, Feature feature, 735 GeoTransform projection ) { 736 return createLabelInABox( text, font, color, halo, x, y, w, h, feature, projection, 1.0 ); 737 } 738 739 /** 740 * @param text 741 * @param font 742 * @param color 743 * @param halo 744 * @param x 745 * @param y 746 * @param w 747 * @param h 748 * @param feature 749 * @param projection 750 * @param opacity 751 * @return the label 752 */ 753 public static HorizontalLabel createLabelInABox( String text, Font font, Color color, Halo halo, double x, 754 double y, double w, double h, Feature feature, 755 GeoTransform projection, double opacity ) { 756 757 // one could probably get a significant speedup if no new fonts would be created (ie, use 758 // another method to find out the text extent of a given string and font size) 759 FontRenderContext frc = new FontRenderContext( null, false, false ); 760 font = font.deriveFont( 128f ); // TODO hardcoded max 761 Rectangle2D rect = font.getStringBounds( text, frc ); 762 int ix = (int) projection.getDestX( x ); 763 int iy = (int) projection.getDestY( y ); 764 int iw = abs( ( (int) projection.getDestX( x + w ) ) - ix ); 765 int ih = abs( ( (int) projection.getDestY( y + h ) ) - iy ); 766 while ( rect.getWidth() > iw && rect.getHeight() > ih && font.getSize2D() > 5 ) { 767 font = font.deriveFont( font.getSize2D() - 4 ); 768 rect = font.getStringBounds( text, frc ); 769 } 770 771 LOG.logDebug( "Determined font size from bounding box", font.getSize2D() ); 772 773 return new HorizontalLabel( text, font, color, font.getLineMetrics( text, frc ), feature, halo, ix, iy, iw, ih, 774 new double[] { 0, 0 }, new double[] { 0, 0 }, opacity ); 775 } 776 777 /** 778 * @param x1 779 * @param y1 780 * @param x2 781 * @param y2 782 * @return rotation (degree) of the line between two passed coordinates 783 */ 784 public static double getRotation( double x1, double y1, double x2, double y2 ) { 785 double dx = x2 - x1; 786 double dy = y2 - y1; 787 788 return Math.toDegrees( Math.atan( dy / dx ) ); 789 } 790 791 /** 792 * @param p1 793 * @param p2 794 * @return distance between two passed coordinates 795 */ 796 public static double getDistance( int[] p1, int[] p2 ) { 797 double dx = p1[0] - p2[0]; 798 double dy = p1[1] - p2[1]; 799 return Math.sqrt( dx * dx + dy * dy ); 800 } 801 802 /** 803 * @param x1 804 * @param y1 805 * @param x2 806 * @param y2 807 * @return distance between two passed coordinates 808 */ 809 public static double getDistance( double x1, double y1, double x2, double y2 ) { 810 double dx = x2 - x1; 811 double dy = y2 - y1; 812 return Math.sqrt( dx * dx + dy * dy ); 813 } 814 815 /** 816 * Calculates the screen coordinates of the given <tt>Curve</tt>. physical screen coordinates 817 * 818 * @param projection 819 * @param curve 820 * @return the coordinates 821 */ 822 public static int[][] calcScreenCoordinates( GeoTransform projection, Curve curve ) { 823 824 LineString lineString = null; 825 try { 826 lineString = curve.getAsLineString(); 827 } catch ( GeometryException e ) { 828 // ignored, assumed to be valid 829 } 830 831 int count = lineString.getNumberOfPoints(); 832 int[][] pos = new int[3][]; 833 pos[0] = new int[count]; 834 pos[1] = new int[count]; 835 pos[2] = new int[1]; 836 837 int k = 0; 838 for ( int i = 0; i < count; i++ ) { 839 Position position = lineString.getPositionAt( i ); 840 double tx = projection.getDestX( position.getX() ); 841 double ty = projection.getDestY( position.getY() ); 842 if ( i > 0 ) { 843 if ( getDistance( tx, ty, pos[0][k - 1], pos[1][k - 1] ) > 1 ) { 844 pos[0][k] = (int) ( tx + 0.5 ); 845 pos[1][k] = (int) ( ty + 0.5 ); 846 k++; 847 } 848 } else { 849 pos[0][k] = (int) ( tx + 0.5 ); 850 pos[1][k] = (int) ( ty + 0.5 ); 851 k++; 852 } 853 } 854 pos[2][0] = k; 855 856 return pos; 857 } 858 859 /** 860 * Returns the physical (screen) coordinates. 861 * 862 * @param projection 863 * @param geometry 864 * 865 * @return physical screen coordinates 866 */ 867 public static int[] calcScreenCoordinates( GeoTransform projection, Geometry geometry ) { 868 869 int[] coords = new int[2]; 870 871 Position source = null; 872 if ( geometry instanceof Point ) { 873 source = ( (Point) geometry ).getPosition(); 874 } else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) { 875 source = geometry.getCentroid().getPosition(); 876 } else { 877 source = geometry.getCentroid().getPosition(); 878 } 879 880 coords[0] = (int) ( projection.getDestX( source.getX() ) + 0.5 ); 881 coords[1] = (int) ( projection.getDestY( source.getY() ) + 0.5 ); 882 return coords; 883 } 884 }