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 }