/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2013, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.styling.visitor;
import java.util.Map;
import javax.measure.quantity.Length;
import javax.measure.unit.NonSI;
import javax.measure.unit.Unit;
import org.geotools.styling.Displacement;
import org.geotools.styling.Fill;
import org.geotools.styling.Font;
import org.geotools.styling.Graphic;
import org.geotools.styling.LabelPlacement;
import org.geotools.styling.LinePlacement;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.Mark;
import org.geotools.styling.PointPlacement;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Stroke;
import org.geotools.styling.TextSymbolizer;
import org.geotools.styling.TextSymbolizer2;
import org.opengis.filter.expression.Expression;
import org.opengis.style.GraphicalSymbol;
/**
* Visitor used for rescaling a Style given a map scale (e.g., meters per pixel) and taking into
* consideration the Unit of Measure (UOM, e.g., SI.METER, NonSI.FOOT) of each symbolizer. The
* resulting Style's Symbolizer sizes will all be given in PIXELS, so that they can be directly used
* by a renderer that is unaware of units of measure or the current map scale. For example, points
* with size == 100 meters could be rescaled to 10 pixels for higher levels of zoom and 2 pixels for
* a lower level of zoom.
* <p>
* This visitor extends {@link DuplicatingStyleVisitor} and as such yields a copy of the original
* Style. Usage is simply to call the desired visit() method and then call getCopy() to retrieve the
* result.
*
* @author milton
* @author Andrea Aime - GeoSolutions
*
*
* @source $URL$
*/
public class UomRescaleStyleVisitor extends DuplicatingStyleVisitor {
double mapScale;
/**
* Constructor: requires the current mapScale to inform the window to viewport (world to screen)
* relation in order to correctly rescale sizes according to units of measure given in world
* units (e.g., SI.METER, NonSI.FOOT, etc).
*
* @param mapScale The specified map scale, given in pixels per meter.
*/
public UomRescaleStyleVisitor(double mapScale) {
if (mapScale <= 0)
throw new IllegalArgumentException("The mapScale is out of range. Value is "
+ Double.toString(mapScale) + ". It must be positive.");
this.mapScale = mapScale;
}
/**
* Used to rescale the provided unscaled value.
*
* @param unscaled the unscaled value.
* @param mapScale the mapScale in pixels per meter.
* @param uom the unit of measure that will be used to scale.
* @return the expression multiplied by the provided scale.
*/
protected Expression rescale(Expression unscaled, Unit<Length> uom) {
if (unscaled == null || unscaled.equals(Expression.NIL)) {
return unscaled;
}
Measure m = new Measure(unscaled, uom);
return RescalingMode.RealWorld.rescaleToExpression(ff.literal(mapScale), m);
}
/**
* Used to rescale the provided unscaled value.
*
* @param unscaled the unscaled value.
* @param mapScale the mapScale in pixels per meter.
* @param uom the unit of measure that will be used to scale.
* @return the expression multiplied by the provided scale.
*/
protected String rescale(String unscaled, Unit<Length> uom) {
if (unscaled == null) {
return unscaled;
}
Measure v = new Measure(unscaled, uom);
return RescalingMode.RealWorld.rescaleToString(mapScale, v);
}
/**
* Used to rescale the provided dash array.
*
* @param dashArray the unscaled dash array. If null, the method returns null.
* @param mapScale the mapScale in pixels per meter.
* @param uom the unit of measure that will be used to scale.
* @return the rescaled dash array
*/
protected float[] rescale(float[] dashArray, Unit<Length> unitOfMeasure) {
if (dashArray == null)
return null;
if (unitOfMeasure == null || unitOfMeasure.equals(NonSI.PIXEL))
return dashArray;
float[] rescaledDashArray = new float[dashArray.length];
for (int i = 0; i < rescaledDashArray.length; i++) {
rescaledDashArray[i] = Float.parseFloat(rescale(String.valueOf(dashArray[i]), unitOfMeasure));
}
return rescaledDashArray;
}
/**
* Used to rescale the provided stroke.
*
* @param stroke the unscaled stroke, which will be modified in-place.
* @param mapScale the mapScale in pixels per meter.
* @param uom the unit of measure that will be used to scale.
*/
protected void rescaleStroke(Stroke stroke, Unit<Length> uom) {
if (stroke != null) {
stroke.setWidth(rescale(stroke.getWidth(), uom));
stroke.setDashArray(rescale(stroke.getDashArray(), uom));
stroke.setDashOffset(rescale(stroke.getDashOffset(), uom));
rescale(stroke.getGraphicFill(), uom);
rescale(stroke.getGraphicStroke(), uom);
}
}
@Override
public void visit(PointSymbolizer ps) {
super.visit(ps);
PointSymbolizer copy = (PointSymbolizer) pages.peek();
Unit<Length> uom = copy.getUnitOfMeasure();
Graphic copyGraphic = copy.getGraphic();
rescale(copyGraphic, uom);
copy.setUnitOfMeasure(NonSI.PIXEL);
}
private void rescale(Graphic graphic, Unit<Length> unit) {
if (graphic != null) {
graphic.setSize(rescale(graphic.getSize(), unit));
graphic.setGap(rescale(graphic.getGap(), unit));
Displacement disp = graphic.getDisplacement();
if (disp != null) {
disp.setDisplacementX(rescale(disp.getDisplacementX(), unit));
disp.setDisplacementY(rescale(disp.getDisplacementY(), unit));
graphic.setDisplacement(disp);
}
if (graphic.graphicalSymbols() != null) {
for (GraphicalSymbol gs : graphic.graphicalSymbols()) {
if (gs instanceof Mark) {
Mark mark = (Mark) gs;
rescaleStroke(mark.getStroke(), unit);
rescaleFill(mark.getFill(), unit);
}
}
}
}
}
@Override
public void visit(LineSymbolizer line) {
super.visit(line);
LineSymbolizer copy = (LineSymbolizer) pages.peek();
Unit<Length> uom = copy.getUnitOfMeasure();
Stroke copyStroke = copy.getStroke();
rescaleStroke(copyStroke, uom);
copy.setUnitOfMeasure(NonSI.PIXEL);
}
@Override
public void visit(PolygonSymbolizer poly) {
super.visit(poly);
PolygonSymbolizer copy = (PolygonSymbolizer) pages.peek();
Unit<Length> uom = copy.getUnitOfMeasure();
rescaleStroke(copy.getStroke(), uom);
rescaleFill(copy.getFill(), uom);
copy.setUnitOfMeasure(NonSI.PIXEL);
}
private void rescaleFill(Fill copyFill, Unit<Length> unit) {
// rescale the graphic fill, if any
if (copyFill != null) {
rescale(copyFill.getGraphicFill(), unit);
}
}
@SuppressWarnings("deprecation")
@Override
public void visit(TextSymbolizer text) {
super.visit(text);
TextSymbolizer copy = (TextSymbolizer) pages.peek();
Unit<Length> uom = copy.getUnitOfMeasure();
// rescales fonts
Font[] fonts = copy.getFonts();
for (Font font : fonts)
font.setSize(rescale(font.getSize(), uom));
copy.setFonts(fonts);
// rescales label placement
LabelPlacement placement = copy.getLabelPlacement();
if (placement instanceof PointPlacement) {
// rescales point label placement
PointPlacement pointPlacement = (PointPlacement) placement;
Displacement disp = pointPlacement.getDisplacement();
if (disp != null) {
disp.setDisplacementX(rescale(disp.getDisplacementX(), uom));
disp.setDisplacementY(rescale(disp.getDisplacementY(), uom));
pointPlacement.setDisplacement(disp);
}
} else if (placement instanceof LinePlacement) {
// rescales line label placement
LinePlacement linePlacement = (LinePlacement) placement;
linePlacement.setGap(rescale(linePlacement.getGap(), uom));
linePlacement.setInitialGap(rescale(linePlacement.getInitialGap(), uom));
linePlacement.setPerpendicularOffset(rescale(linePlacement.getPerpendicularOffset(),
uom));
}
copy.setLabelPlacement(placement);
// rescale the halo
if (copy.getHalo() != null) {
copy.getHalo().setRadius(rescale(copy.getHalo().getRadius(), uom));
}
if (copy instanceof TextSymbolizer2) {
TextSymbolizer2 copy2 = (TextSymbolizer2) copy;
rescale(copy2.getGraphic(), uom);
}
// scale various options as well
Map<String, String> options = copy.getOptions();
scaleIntOption(options, TextSymbolizer.MAX_DISPLACEMENT_KEY, uom);
scaleIntOption(options, TextSymbolizer.SPACE_AROUND_KEY, uom);
scaleIntOption(options, TextSymbolizer.MIN_GROUP_DISTANCE_KEY, uom);
scaleIntOption(options, TextSymbolizer.LABEL_REPEAT_KEY, uom);
scaleIntOption(options, TextSymbolizer.AUTO_WRAP_KEY, uom);
scaleIntArrayOption(options, TextSymbolizer.GRAPHIC_MARGIN_KEY, uom);
copy.setUnitOfMeasure(NonSI.PIXEL);
}
private void scaleIntOption(Map<String, String> options, String optionName, Unit<Length> uom) {
if (options.containsKey(optionName)) {
String rescaled = rescale(options.get(optionName), uom);
options.put(optionName, toInt(rescaled));
}
}
private void scaleIntArrayOption(Map<String, String> options, String optionName,
Unit<Length> uom) {
if (options.containsKey(optionName)) {
String strValue = options.get(optionName);
String[] splitted = strValue.split("\\s+");
StringBuilder sb = new StringBuilder();
for (String value : splitted) {
String rescaled = rescale(value, uom);
sb.append(toInt(rescaled)).append(" ");
}
sb.setLength(sb.length() - 1);
options.put(optionName, sb.toString());
}
}
String toInt(String value) {
Double dv = Double.valueOf(value);
return String.valueOf(dv.intValue());
}
}