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 }