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