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 }