/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.graphics.optimizers;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.graphics.displayelements.Label;
import org.deegree.graphics.displayelements.LabelDisplayElement;
import org.deegree.graphics.displayelements.LabelFactory;
import org.deegree.graphics.sld.LabelPlacement;
import org.deegree.graphics.sld.LinePlacement;
import org.deegree.graphics.sld.PointPlacement;
import org.deegree.graphics.sld.TextSymbolizer;
import org.deegree.graphics.transformation.GeoTransform;
import org.deegree.model.feature.Feature;
import org.deegree.model.filterencoding.FilterEvaluationException;
import org.deegree.model.spatialschema.Curve;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.model.spatialschema.GeometryFactory;
import org.deegree.model.spatialschema.MultiCurve;
import org.deegree.model.spatialschema.MultiSurface;
import org.deegree.model.spatialschema.Point;
import org.deegree.model.spatialschema.Position;
import org.deegree.model.spatialschema.Surface;
/**
* Factory class for <tt>LabelChoice</tt>-objects.
* <p>
* @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
* @version $Revision: 1.8 $ $Date: 2006/11/27 13:06:54 $
*/
public class LabelChoiceFactory {
private static ILogger LOG = LoggerFactory.getLogger( LabelChoiceFactory.class );
/**
* Determines <tt>LabelChoice</tt>s for the given <tt>LabelDisplayElement</tt>.
* <p>
* @param element
* @param g
* @param projection
* @return
*/
static ArrayList createLabelChoices( LabelDisplayElement element, Graphics2D g,
GeoTransform projection ) {
ArrayList choices = new ArrayList();
try {
Feature feature = element.getFeature();
String caption = element.getLabel().evaluate( feature );
// sanity check: empty labels are ignored
if ( caption == null || caption.trim().equals( "" ) ) {
return choices;
}
Geometry geometry = element.getGeometry();
TextSymbolizer symbolizer = (TextSymbolizer) element.getSymbolizer();
// gather font information
org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
java.awt.Font font = new java.awt.Font(
sldFont.getFamily( feature ),
sldFont.getStyle( feature )
| sldFont.getWeight( feature ),
sldFont.getSize( feature ) );
g.setFont( font );
FontRenderContext frc = g.getFontRenderContext();
Rectangle2D bounds = font.getStringBounds( caption, frc );
LineMetrics metrics = font.getLineMetrics( caption, frc );
int w = (int) bounds.getWidth();
int h = (int) bounds.getHeight();
//int descent = (int) metrics.getDescent ();
LabelPlacement lPlacement = symbolizer.getLabelPlacement();
// element is associated to a point geometry
if ( geometry instanceof Point ) {
// get screen coordinates
int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
int x = coords[0];
int y = coords[1];
// use placement information from SLD
PointPlacement pPlacement = lPlacement.getPointPlacement();
//double [] anchorPoint = pPlacement.getAnchorPoint( feature );
double[] displacement = pPlacement.getDisplacement( feature );
double rotation = pPlacement.getRotation( feature );
Label[] labels = new Label[8];
labels[0] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 0.0, 0.0 },
new double[] { displacement[0],
displacement[1] } );
labels[1] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 0.0, 1.0 },
new double[] { displacement[0],
-displacement[1] } );
labels[2] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 1.0, 1.0 },
new double[] { -displacement[0],
-displacement[1] } );
labels[3] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 1.0, 0.0 },
new double[] { -displacement[0],
displacement[1] } );
labels[4] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 0.0, 0.5 },
new double[] { displacement[0], 0 } );
labels[5] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 0.5, 1.0 },
new double[] { 0, -displacement[1] } );
labels[6] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 1.0, 0.5 },
new double[] { -displacement[0], 0 } );
labels[7] = LabelFactory.createLabel( caption, font, sldFont.getColor( feature ),
metrics, feature, symbolizer.getHalo(), x, y,
w, h, rotation, new double[] { 0.5, 0.0 },
new double[] { 0, displacement[1] } );
float[] qualities = new float[] { 0.0f, 0.5f, 0.33f, 0.27f, 0.15f, 1.0f, 0.1f, 0.7f };
choices.add( new LabelChoice( element, labels, qualities, 0, labels[1].getMaxX(),
labels[1].getMaxY(), labels[3].getMinX(),
labels[3].getMinY() ) );
// element is associated to a polygon geometry
} else if ( geometry instanceof Surface || geometry instanceof MultiSurface ) {
// get screen coordinates
int[] coords = LabelFactory.calcScreenCoordinates( projection, geometry );
int x = coords[0];
int y = coords[1];
// use placement information from SLD
PointPlacement pPlacement = lPlacement.getPointPlacement();
// double [] anchorPoint = pPlacement.getAnchorPoint( feature );
// double [] displacement = pPlacement.getDisplacement( feature );
double rotation = pPlacement.getRotation( feature );
// center label within the intersection of the screen surface and the polygon geometry
Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(),
null );
Geometry intersection = null;;
try {
intersection = screenSurface.intersection( geometry );
} catch ( Exception e ) {
LOG.logDebug( "no intersection could be calculated because objects are to small" );
}
if ( intersection != null && intersection.getCentroid() != null ) {
Position source = intersection.getCentroid().getPosition();
x = (int) ( projection.getDestX( source.getX() ) + 0.5 );
y = (int) ( projection.getDestY( source.getY() ) + 0.5 );
Label[] labels = new Label[3];
labels[0] = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ), metrics,
feature, symbolizer.getHalo(), x, y, w,
h, rotation, new double[] { 0.5, 0.5 },
new double[] { 0, 0 } );
labels[1] = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ), metrics,
feature, symbolizer.getHalo(), x, y, w,
h, rotation, new double[] { 0.5, 0.0 },
new double[] { 0, 0 } );
labels[2] = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ), metrics,
feature, symbolizer.getHalo(), x, y, w,
h, rotation, new double[] { 0.5, 1.0 },
new double[] { 0, 0 } );
float[] qualities = new float[] { 0.0f, 0.25f, 0.5f };
choices.add( new LabelChoice( element, labels, qualities, 0,
labels[0].getMaxX(), labels[2].getMaxY(),
labels[0].getMinX(), labels[1].getMinY() ) );
}
// element is associated to a line geometry
} else if ( geometry instanceof Curve || geometry instanceof MultiCurve ) {
Surface screenSurface = GeometryFactory.createSurface( projection.getSourceRect(),
null );
Geometry intersection = screenSurface.intersection( geometry );
if ( intersection != null ) {
ArrayList list = null;
if ( intersection instanceof Curve ) {
list = createLabelChoices( (Curve) intersection, element, g, projection );
} else if ( intersection instanceof MultiCurve ) {
list = createLabelChoices( (MultiCurve) intersection, element, g,
projection );
} else {
throw new Exception( "Intersection produced unexpected geometry type: '"
+ intersection.getClass().getName() + "'!" );
}
choices = list;
}
}
} catch ( Exception e ) {
e.printStackTrace();
}
return choices;
}
/**
* Determines <tt>LabelChoice</tt>s for the given <tt>MultiCurve</tt> where a
* <tt>Label</tt> could be drawn. For each <tt>LabelChoice</tt>, three candidates
* are generated: one on the line, one above it and one below.
* <p>
* @param element
* @param g
* @param projection
* @return ArrayList containing <tt>LabelChoice</tt>-objects
* @throws FilterEvaluationException
*/
static ArrayList createLabelChoices( MultiCurve multiCurve, LabelDisplayElement element,
Graphics2D g, GeoTransform projection )
throws FilterEvaluationException {
ArrayList choices = new ArrayList( 1000 );
for ( int i = 0; i < multiCurve.getSize(); i++ ) {
Curve curve = multiCurve.getCurveAt( i );
choices.addAll( createLabelChoices( curve, element, g, projection ) );
}
return choices;
}
/**
* Determines <tt>LabelChoice</tt>s for the given <tt>Curve</tt> where a
* <tt>Label</tt> could be drawn. For each <tt>LabelChoice</tt>, three candidates
* are generated: one on the line, one above it and one below.
* <p>
* @param curve
* @param element
* @param g
* @param projection
* @return ArrayList containing <tt>LabelChoice</tt>-objects
* @throws FilterEvaluationException
*/
static ArrayList createLabelChoices( Curve curve, LabelDisplayElement element, Graphics2D g,
GeoTransform projection )
throws FilterEvaluationException {
Feature feature = element.getFeature();
// determine the placement type and parameters from the TextSymbolizer
double perpendicularOffset = 0.0;
int placementType = LinePlacement.TYPE_ABSOLUTE;
double lineWidth = 3.0;
int gap = 6;
TextSymbolizer symbolizer = ( (TextSymbolizer) element.getSymbolizer() );
if ( symbolizer.getLabelPlacement() != null ) {
LinePlacement linePlacement = symbolizer.getLabelPlacement().getLinePlacement();
if ( linePlacement != null ) {
placementType = linePlacement.getPlacementType( element.getFeature() );
perpendicularOffset = linePlacement.getPerpendicularOffset( element.getFeature() );
lineWidth = linePlacement.getLineWidth( element.getFeature() );
gap = linePlacement.getGap( element.getFeature() );
}
}
// get width & height of the caption
String caption = element.getLabel().evaluate( element.getFeature() );
org.deegree.graphics.sld.Font sldFont = symbolizer.getFont();
java.awt.Font font = new java.awt.Font(
sldFont.getFamily( element.getFeature() ),
sldFont.getStyle( element.getFeature() )
| sldFont.getWeight( element.getFeature() ),
sldFont.getSize( element.getFeature() ) );
g.setFont( font );
FontRenderContext frc = g.getFontRenderContext();
Rectangle2D bounds = font.getStringBounds( caption, frc );
LineMetrics metrics = font.getLineMetrics( caption, frc );
double width = bounds.getWidth();
double height = bounds.getHeight();
// get screen coordinates of the line
int[][] pos = LabelFactory.calcScreenCoordinates( projection, curve );
// ideal distance from the line
double delta = height / 2.0 + lineWidth / 2.0;
// walk along the linestring and "collect" possible label positions
int w = (int) width;
int lastX = pos[0][0];
int lastY = pos[1][0];
int count = pos[2][0];
int boxStartX = lastX;
int boxStartY = lastY;
ArrayList choices = new ArrayList( 1000 );
ArrayList eCandidates = new ArrayList( 100 );
int i = 0;
int kk = 0;
while ( i < count && kk < 100 ) {
kk++;
int x = pos[0][i];
int y = pos[1][i];
// segment found where endpoint of label should be located?
if ( LabelFactory.getDistance( boxStartX, boxStartY, x, y ) >= w ) {
int[] p0 = new int[] { boxStartX, boxStartY };
int[] p1 = new int[] { lastX, lastY };
int[] p2 = new int[] { x, y };
int[] p = LabelFactory.findPointWithDistance( p0, p1, p2, w );
x = p[0];
y = p[1];
lastX = x;
lastY = y;
int boxEndX = x;
int boxEndY = y;
// does the linesegment run from right to left?
if ( x <= boxStartX ) {
boxEndX = boxStartX;
boxEndY = boxStartY;
boxStartX = x;
boxStartY = y;
x = boxEndX;
y = boxEndY;
}
double rotation = LabelFactory.getRotation( boxStartX, boxStartY, x, y );
double[] deviation = LabelFactory.calcDeviation(
new int[] { boxStartX, boxStartY },
new int[] { boxEndX, boxEndY },
eCandidates );
switch ( placementType ) {
case LinePlacement.TYPE_ABSOLUTE: {
Label label = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ), metrics,
feature, symbolizer.getHalo(),
boxStartX, boxStartY, (int) width,
(int) height, rotation,
new double[] { 0.0, 0.5 },
new double[] { ( w - width ) / 2,
perpendicularOffset } );
choices.add( new LabelChoice( element, new Label[] { label },
new float[] { 0.0f }, 0, label.getMaxX(),
label.getMaxY(), label.getMinX(), label.getMinY() ) );
break;
}
case LinePlacement.TYPE_ABOVE: {
Label upperLabel = LabelFactory.createLabel(
caption,
font,
sldFont.getColor( feature ),
metrics,
feature,
symbolizer.getHalo(),
boxStartX,
boxStartY,
(int) width,
(int) height,
rotation,
new double[] { 0.0, 0.5 },
new double[] { ( w - width ) / 2,
delta + deviation[0] } );
choices.add( new LabelChoice( element, new Label[] { upperLabel },
new float[] { 0.0f }, 0, upperLabel.getMaxX(),
upperLabel.getMaxY(), upperLabel.getMinX(),
upperLabel.getMinY() ) );
break;
}
case LinePlacement.TYPE_BELOW: {
Label lowerLabel = LabelFactory.createLabel(
caption,
font,
sldFont.getColor( feature ),
metrics,
feature,
symbolizer.getHalo(),
boxStartX,
boxStartY,
(int) width,
(int) height,
rotation,
new double[] { 0.0, 0.5 },
new double[] {
( w - width ) / 2,
-delta
- deviation[1] } );
choices.add( new LabelChoice( element, new Label[] { lowerLabel },
new float[] { 0.0f }, 0, lowerLabel.getMaxX(),
lowerLabel.getMaxY(), lowerLabel.getMinX(),
lowerLabel.getMinY() ) );
break;
}
case LinePlacement.TYPE_CENTER: {
Label centerLabel = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ),
metrics, feature,
symbolizer.getHalo(), boxStartX,
boxStartY, (int) width,
(int) height, rotation,
new double[] { 0.0, 0.5 },
new double[] { ( w - width ) / 2,
0.0 } );
choices.add( new LabelChoice( element, new Label[] { centerLabel },
new float[] { 0.0f }, 0, centerLabel.getMaxX(),
centerLabel.getMaxY(), centerLabel.getMinX(),
centerLabel.getMinY() ) );
break;
}
case LinePlacement.TYPE_AUTO: {
Label upperLabel = LabelFactory.createLabel(
caption,
font,
sldFont.getColor( feature ),
metrics,
feature,
symbolizer.getHalo(),
boxStartX,
boxStartY,
(int) width,
(int) height,
rotation,
new double[] { 0.0, 0.5 },
new double[] { ( w - width ) / 2,
delta + deviation[0] } );
Label lowerLabel = LabelFactory.createLabel(
caption,
font,
sldFont.getColor( feature ),
metrics,
feature,
symbolizer.getHalo(),
boxStartX,
boxStartY,
(int) width,
(int) height,
rotation,
new double[] { 0.0, 0.5 },
new double[] {
( w - width ) / 2,
-delta
- deviation[1] } );
Label centerLabel = LabelFactory.createLabel( caption, font,
sldFont.getColor( feature ),
metrics, feature,
symbolizer.getHalo(), boxStartX,
boxStartY, (int) width,
(int) height, rotation,
new double[] { 0.0, 0.5 },
new double[] { ( w - width ) / 2,
0.0 } );
choices.add( new LabelChoice( element, new Label[] { lowerLabel, upperLabel,
centerLabel },
new float[] { 0.0f, 0.25f, 1.0f }, 0,
centerLabel.getMaxX(), lowerLabel.getMaxY(),
centerLabel.getMinX(), upperLabel.getMinY() ) );
break;
}
default: {
}
}
boxStartX = lastX;
boxStartY = lastY;
eCandidates.clear();
} else {
eCandidates.add( new int[] { x, y } );
lastX = x;
lastY = y;
i++;
}
}
// pick LabelChoices on the linestring
ArrayList pick = new ArrayList();
int n = choices.size();
for ( int j = n / 2; j < choices.size(); j += ( gap + 1 ) ) {
pick.add( choices.get( j ) );
}
for ( int j = n / 2 - ( gap + 1 ); j > 0; j -= ( gap + 1 ) ) {
pick.add( choices.get( j ) );
}
return pick;
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: LabelChoiceFactory.java,v $
Revision 1.8 2006/11/27 13:06:54 poth
code formatting / catching an exception that should not cause a fatal error
Revision 1.7 2006/07/04 19:09:21 poth
comments corrected - code formatation
Revision 1.6 2006/07/04 18:31:05 poth
avoid NPEs if no centroid can be calculated
********************************************************************** */