/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.kml.icons;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.swing.Icon;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.renderer.style.DynamicSymbolFactoryFinder;
import org.geotools.renderer.style.ExpressionExtractor;
import org.geotools.renderer.style.ExternalGraphicFactory;
import org.geotools.styling.ExternalGraphic;
import org.opengis.feature.Feature;
import org.opengis.filter.expression.Expression;
import org.opengis.style.Graphic;
import org.opengis.style.GraphicalSymbol;
import org.opengis.style.Mark;
import org.opengis.style.Stroke;
/**
* Utility methods for working with icons.
*
* @author Kevin Smith, OpenGeo
*
*/
public class Icons {
static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.kml.icons");
/**
* Render symbols this much bigger than they should be then shrink them down in the KML. This
* makes for nicer looking symbols when they undergo further transformations in Google Earth
*/
public final static int RENDER_SCALE_FACTOR = 4;
/**
* The default size to use for symbols if none is specified
*/
public final static double DEFAULT_SYMBOL_SIZE = 16d;
private Icons(){}
/**
* Find the size of a square needed to accommodate the rotated image of another square
* @param size size of the square to rotate. {@code null} returns {@code null}.
* @param rotation the angle to rotate in degrees. {@code null} is treated as no rotation.
* @return the size of a square big enough to contain the rotated image
*/
public static @Nullable Integer rotationScale(@Nullable Integer size, @Nullable Double rotation) {
if(size==null) return null;
if(rotation==null || rotation%90==0) return size; // Save us some trig functions
return (int) Math.ceil(rotationScaleFactor(rotation)*size);
}
/**
* Find the size of a square needed to accommodate the rotated image of another square
* @param size size of the square to rotate. {@code null} returns {@code null}.
* @param rotation the angle to rotate in degrees. {@code null} is treated as no rotation.
* @return the size of a square big enough to contain the rotated image
*/
public static @Nullable Double rotationScale(@Nullable Double size, @Nullable Double rotation) {
if(size==null) return null;
if(rotation==null || rotation%90==0) return size; // Save us some trig functions
return rotationScaleFactor(rotation)*size;
}
/**
* Find the scale factor needed for a square to accommodate a rotated square.
* @param rotation the angle in degrees
*
*/
public static double rotationScaleFactor(double rotation) {
return Math.abs(Math.sin(Math.toRadians(rotation))) +
Math.abs(Math.cos(Math.toRadians(rotation)));
}
/**
* Get the rotation of the given graphic when applied to the given feature
* @param g
* @param f
*
*/
public static @Nullable Double getRotation(Graphic g, @Nullable Feature f) {
return g.getRotation().evaluate(f, Double.class);
}
/**
* Get the size of the given graphic when applied to the given feature
* @param g
* @param f
*
*/
public static @Nullable Double getSpecifiedSize(Graphic g, @Nullable Feature f){
return g.getSize().evaluate(f, Double.class);
}
private static @Nullable Icon getIcon(ExternalGraphic eg, @Nullable Feature f) {
// Get the Icon for an external image symbol
Icon i = eg.getInlineContent();
if(i==null) {
Expression location;
try {
location = ExpressionExtractor.extractCqlExpressions(eg.getLocation().toString());
Iterator<ExternalGraphicFactory> it = DynamicSymbolFactoryFinder.getExternalGraphicFactories();
while(i==null && it.hasNext()) {
try {
ExternalGraphicFactory fact = it.next();
i = fact.getIcon((Feature) null, location, eg.getFormat(), -1);
} catch(Exception e) {
LOGGER.log(Level.FINE, "Error occurred evaluating external graphic", e);
}
}
} catch (MalformedURLException e1) {
LOGGER.log(Level.FINER, e1.getMessage(), e1);
}
}
return i;
}
/**
* Get the largest dimension of an external graphic
* @param eg
* @param f
*
*/
public static @Nullable Integer getExternalSize(ExternalGraphic eg, @Nullable Feature f) {
Icon i = getIcon(eg, f);
if(i==null) {
return (int) Icons.DEFAULT_SYMBOL_SIZE;
} else {
return Math.max(i.getIconHeight(), i.getIconWidth());
}
}
/**
* Get the size of a symbolizer graphic
* @param g
* @param rotation Treat the graphic as a square and rotate it, then find a square big enough
* to accomodate the rotated square. If {@code null} the rotation will be calculated based on
* the feature.
* @param f
*
*/
public static @Nullable Double graphicSize(Graphic g, @Nullable Double rotation, @Nullable Feature f) {
Double size = getSpecifiedSize(g, f);
double border = 0;
if (rotation == null) {
rotation = getRotation(g, f);
}
GraphicalSymbol gs = g.graphicalSymbols().iterator().next();
if(gs instanceof Mark) {
Stroke stroke = ((Mark) gs).getStroke();
if(stroke!=null){
Double width = stroke.getWidth().evaluate(f, Double.class);
if(width!=null) {
border = width;
}
}
}
if(size == null) {
if(gs instanceof ExternalGraphic) {
size = (double) getExternalSize((ExternalGraphic) gs, f);
} else {
size = (double) DEFAULT_SYMBOL_SIZE;
}
}
size = rotationScale(size, rotation);
if(size!=null) size += border;
return size;
}
/**
* Get the scale factor to put in the KML
* @param g
* @param f rotation Treat the graphic as a square and rotate it, then find a square big enough
* to accomodate the rotated square. If {@code null} the rotation will be calculated based on
* the feature.
*
*/
public static @Nullable Double graphicScale(Graphic g, @Nullable Feature f) {
Double size = graphicSize(g, null, f);
if(size!=null) {
return size/DEFAULT_SYMBOL_SIZE;
}
return null;
}
}