/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, 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 javax.measure.converter.UnitConverter; import javax.measure.quantity.Length; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; import org.geotools.filter.LiteralExpression; 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.PointPlacement; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Stroke; import org.geotools.styling.TextSymbolizer; import org.opengis.filter.expression.Expression; /** * 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 * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/styling/visitor/UomRescaleStyleVisitor.java $ */ public class UomRescaleStyleVisitor extends DuplicatingStyleVisitor { private double mapScale = 1; /** * 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; } /** * Computes a rescaling multiplier to be applied to an unscaled value. * * @param mapScale * the mapScale in pixels per meter. * @param uom * the unit of measure that will be used to scale. * @return the rescaling multiplier for the provided parameters. */ protected double computeRescaleMultiplier(double mapScale, Unit<Length> uom) { // no scaling to do if UOM is PIXEL (or null, which stands for PIXEL as well) if (uom == null || uom.equals(NonSI.PIXEL)) return 1; // converts value from meters to given UOM UnitConverter converter = uom.getConverterTo(SI.METER); return converter.convert(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, double mapScale, Unit<Length> uom) { if (unscaled == null || unscaled.equals(Expression.NIL)) return unscaled; if (unscaled instanceof LiteralExpression && unscaled.evaluate(null, Double.class) != null) { // if given Expression is a literal, we can return a literal double rescaled = rescale(unscaled.evaluate(null, Double.class), mapScale, uom); return ff.literal(rescaled); } else { // otherwise, we return a Multiply expression with the rescaling multiplier double rescaleMultiplier = computeRescaleMultiplier(mapScale, uom); return ff.multiply(unscaled, ff.literal(rescaleMultiplier)); } } /** * 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 a scaled value. */ protected double rescale(double unscaled, double mapScale, Unit<Length> uom) { // computes the basic rescaled value return unscaled * computeRescaleMultiplier(mapScale, uom); } /** * 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, double mapScale, 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) rescale((double) dashArray[i], mapScale, 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, double mapScale, Unit<Length> uom) { if (stroke != null) { stroke.setWidth(rescale(stroke.getWidth(), mapScale, uom)); stroke.setDashArray(rescale(stroke.getDashArray(), mapScale, uom)); stroke.setDashOffset(rescale(stroke.getDashOffset(), mapScale, uom)); } } @Override public void visit(PointSymbolizer ps) { super.visit(ps); PointSymbolizer copy = (PointSymbolizer) pages.peek(); Graphic copyGraphic = copy.getGraphic(); copyGraphic.setSize(rescale(copyGraphic.getSize(), mapScale, copy.getUnitOfMeasure())); copy.setUnitOfMeasure(NonSI.PIXEL); } @Override public void visit(LineSymbolizer line) { super.visit(line); LineSymbolizer copy = (LineSymbolizer) pages.peek(); Stroke copyStroke = copy.getStroke(); rescaleStroke(copyStroke, mapScale, copy.getUnitOfMeasure()); copy.setUnitOfMeasure(NonSI.PIXEL); } @Override public void visit(PolygonSymbolizer poly) { super.visit(poly); PolygonSymbolizer copy = (PolygonSymbolizer) pages.peek(); Stroke copyStroke = copy.getStroke(); rescaleStroke(copyStroke, mapScale, copy.getUnitOfMeasure()); // rescales the graphic fill, if available Fill copyFill = copy.getFill(); if (copyFill != null) { Graphic copyGraphic = copyFill.getGraphicFill(); if (copyGraphic != null) { copyGraphic.setSize(rescale(copyGraphic.getSize(), mapScale, copy .getUnitOfMeasure())); copyFill.setGraphicFill(copyGraphic); } copy.setFill(copyFill); } copy.setUnitOfMeasure(NonSI.PIXEL); } @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(), mapScale, 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(), mapScale, uom)); disp.setDisplacementY(rescale(disp.getDisplacementY(), mapScale, uom)); pointPlacement.setDisplacement(disp); } } else if (placement instanceof LinePlacement) { // rescales line label placement LinePlacement linePlacement = (LinePlacement) placement; linePlacement.setGap(rescale(linePlacement.getGap(), mapScale, uom)); linePlacement.setInitialGap(rescale(linePlacement.getInitialGap(), mapScale, uom)); linePlacement.setPerpendicularOffset(rescale(linePlacement.getPerpendicularOffset(), mapScale, uom)); } copy.setLabelPlacement(placement); // rescale the halo if(copy.getHalo() != null) { copy.getHalo().setRadius(rescale(copy.getHalo().getRadius(), mapScale, uom)); } copy.setUnitOfMeasure(NonSI.PIXEL); } }