package org.geotools.swt.utils;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.map.MapLayer;
import org.geotools.styling.ChannelSelection;
import org.geotools.styling.ContrastEnhancement;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLD;
import org.geotools.styling.SLDParser;
import org.geotools.styling.SelectedChannelType;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.StyleFactory;
import org.geotools.swt.control.ExceptionMonitor;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.FilterFactory;
import org.opengis.style.ContrastMethod;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
/**
* Utilities class.
*
* @author Andrea Antonello (www.hydrologis.com)
*
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/swt/src/main/java/org/geotools/swt/utils/Utils.java $
*/
public class Utils {
/**
* The default {@link StyleFactory} to use.
*/
public static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(GeoTools.getDefaultHints());
/**
* The default {@link FilterFactory} to use.
*/
public static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints());
/**
* The default {@link StyleBuilder} to use.
*/
public static StyleBuilder sb = new StyleBuilder(styleFactory, filterFactory);
private static final Class< ? > BASE_GRID_CLASS = org.opengis.coverage.grid.GridCoverage.class;
private static final Class< ? > BASE_READER_CLASS = org.opengis.coverage.grid.GridCoverageReader.class;
/**
* Sets the location of the shell to the center of the screen.
*
* @param shell the shell to place.
*/
public static void setShellLocation( Shell shell ) {
Rectangle monitorArea = shell.getDisplay().getPrimaryMonitor().getBounds();
Rectangle shellArea = shell.getBounds();
int x = monitorArea.x + (monitorArea.width - shellArea.width) / 2;
int y = monitorArea.y + (monitorArea.height - shellArea.height) / 2;
shell.setLocation(x, y);
}
/**
* Transform an awt {@link java.awt.Rectangle} instance into a swt one.
* <p>
* The coordinates are rounded to integer for the swt object.
*
* @param rect2d The awt rectangle to map.
* @return an swt <code>Rectangle</code> object.
*/
public static Rectangle toSwtRectangle( java.awt.Rectangle rect2d ) {
return new Rectangle((int) Math.round(rect2d.getMinX()), (int) Math.round(rect2d.getMinY()), (int) Math.round(rect2d
.getWidth()), (int) Math.round(rect2d.getHeight()));
}
/**
* Transform a swt Rectangle instance into an awt one.
*
* @param rect the swt <code>Rectangle</code>
* @return a {@link java.awt.Rectangle} instance with
* the appropriate location and size.
*/
public static java.awt.Rectangle toAwtRectangle( Rectangle rect ) {
java.awt.Rectangle rect2d = new java.awt.Rectangle();
rect2d.setRect(rect.x, rect.y, rect.width, rect.height);
return rect2d;
}
/**
* Create a Style to display the features.
*
* <p>If an SLD file is in the same
* directory as the shapefile then we will create the Style by processing
* this.
*/
public static Style createStyle( File file, SimpleFeatureSource featureSource ) {
File sld = toSLDFile(file);
if (sld != null) {
return createFromSLD(sld);
}
return createStyle2(featureSource);
}
/**
* Figure out if a valid SLD file is available.
*
* @param file the file to search for style sidecar file.
* @return the style file or null.
*/
public static File toSLDFile( File file ) {
String path = file.getAbsolutePath();
String base = path.substring(0, path.length() - 4);
String newPath = base + ".sld";
File sld = new File(newPath);
if (sld.exists()) {
return sld;
}
newPath = base + ".SLD";
sld = new File(newPath);
if (sld.exists()) {
return sld;
}
return null;
}
/**
* Create a Style object from a definition in a SLD document
*
* @param sld the sld file.
* @return the created {@link Style} or <code>null</code>.
*/
public static Style createFromSLD( File sld ) {
try {
SLDParser stylereader = new SLDParser(styleFactory, sld.toURI().toURL());
Style[] style = stylereader.readXML();
return style[0];
} catch (Exception e) {
ExceptionMonitor.show(null, e, "Problem creating style");
}
return null;
}
/**
* Create a default {@link Style} ofr the featureSource.
*
* @param featureSource the source on which to create the style.
* @return the style created.
*/
public static Style createStyle2( SimpleFeatureSource featureSource ) {
SimpleFeatureType schema = (SimpleFeatureType) featureSource.getSchema();
Class< ? > geomType = schema.getGeometryDescriptor().getType().getBinding();
if (Polygon.class.isAssignableFrom(geomType) || MultiPolygon.class.isAssignableFrom(geomType)) {
return createPolygonStyle();
} else if (LineString.class.isAssignableFrom(geomType) || MultiLineString.class.isAssignableFrom(geomType)) {
return createLineStyle();
} else {
return createPointStyle();
}
}
/**
* Create a default polygon style.
*
* @return the created style.
*/
public static Style createPolygonStyle() {
// create a partially opaque outline stroke
Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory.literal(1),
filterFactory.literal(0.5));
// create a partial opaque fill
Fill fill = styleFactory.createFill(filterFactory.literal(Color.CYAN), filterFactory.literal(0.5));
/*
* Setting the geometryPropertyName arg to null signals that we want to
* draw the default geomettry of features
*/
PolygonSymbolizer sym = styleFactory.createPolygonSymbolizer(stroke, fill, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[]{rule});
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
/**
* Create a default line style.
*
* @return the created style.
*/
public static Style createLineStyle() {
Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory.literal(1));
/*
* Setting the geometryPropertyName arg to null signals that we want to
* draw the default geomettry of features
*/
LineSymbolizer sym = styleFactory.createLineSymbolizer(stroke, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[]{rule});
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
/**
* Create a default point style.
*
* @return the created style.
*/
public static Style createPointStyle() {
Graphic gr = styleFactory.createDefaultGraphic();
Mark mark = styleFactory.getCircleMark();
mark.setStroke(styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory.literal(1)));
mark.setFill(styleFactory.createFill(filterFactory.literal(Color.CYAN)));
gr.graphicalSymbols().clear();
gr.graphicalSymbols().add(mark);
gr.setSize(filterFactory.literal(5));
/*
* Setting the geometryPropertyName arg to null signals that we want to
* draw the default geomettry of features
*/
PointSymbolizer sym = styleFactory.createPointSymbolizer(gr, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[]{rule});
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
/**
* Run a {@link Runnable} that needs to run in the Display thread.
*
* @param runner the runnable to run.
* @param sync if <code>true</code>, the runnable is run in sync mode, else in async.
*/
public static void runGuiRunnableSafe( Runnable runner, boolean sync ) {
if (Display.getCurrent() != null) {
runner.run();
} else {
if (sync) {
Display.getDefault().syncExec(runner);
} else {
Display.getDefault().asyncExec(runner);
}
}
}
/**
* This method examines the names of the sample dimensions in the provided coverage looking for
* "red...", "green..." and "blue..." (case insensitive match). If these names are not found
* it uses bands 1, 2, and 3 for the red, green and blue channels. It then sets up a raster
* symbolizer and returns this wrapped in a Style.
* @param reader
*
* @return a new Style object containing a raster symbolizer set up for RGB image
*/
public static Style createRGBStyle( AbstractGridCoverage2DReader reader ) {
GridCoverage2D cov = null;
try {
cov = reader.read(null);
} catch (IOException giveUp) {
throw new RuntimeException(giveUp);
}
// We need at least three bands to create an RGB style
int numBands = cov.getNumSampleDimensions();
if (numBands < 3) {
return null;
}
// Get the names of the bands
String[] sampleDimensionNames = new String[numBands];
for( int i = 0; i < numBands; i++ ) {
GridSampleDimension dim = cov.getSampleDimension(i);
sampleDimensionNames[i] = dim.getDescription().toString();
}
final int RED = 0, GREEN = 1, BLUE = 2;
int[] channelNum = {-1, -1, -1};
// We examine the band names looking for "red...", "green...", "blue...".
// Note that the channel numbers we record are indexed from 1, not 0.
for( int i = 0; i < numBands; i++ ) {
String name = sampleDimensionNames[i].toLowerCase();
if (name != null) {
if (name.matches("red.*")) {
channelNum[RED] = i + 1;
} else if (name.matches("green.*")) {
channelNum[GREEN] = i + 1;
} else if (name.matches("blue.*")) {
channelNum[BLUE] = i + 1;
}
}
}
// If we didn't find named bands "red...", "green...", "blue..."
// we fall back to using the first three bands in order
if (channelNum[RED] < 0 || channelNum[GREEN] < 0 || channelNum[BLUE] < 0) {
channelNum[RED] = 1;
channelNum[GREEN] = 2;
channelNum[BLUE] = 3;
}
// Now we create a RasterSymbolizer using the selected channels
SelectedChannelType[] sct = new SelectedChannelType[cov.getNumSampleDimensions()];
ContrastEnhancement ce = styleFactory.contrastEnhancement(filterFactory.literal(1.0), ContrastMethod.NORMALIZE);
for( int i = 0; i < 3; i++ ) {
sct[i] = styleFactory.createSelectedChannelType(String.valueOf(channelNum[i]), ce);
}
RasterSymbolizer sym = styleFactory.getDefaultRasterSymbolizer();
ChannelSelection sel = styleFactory.channelSelection(sct[RED], sct[GREEN], sct[BLUE]);
sym.setChannelSelection(sel);
return SLD.wrapSymbolizers(sym);
}
/**
* Check if the given map layer contains a grid coverage or a grid coverage reader.
* <p>
* Implementation note: we avoid referencing org.geotools.coverage.grid classes
* directly here so that applications dealing only with other data types are not
* forced to have JAI in the classpath.
*
* @param layer the map layer
*
* @return true if this is a grid layer; false otherwise
*/
public static boolean isGridLayer( MapLayer layer ) {
Collection<PropertyDescriptor> descriptors = layer.getFeatureSource().getSchema().getDescriptors();
for( PropertyDescriptor desc : descriptors ) {
Class< ? > binding = desc.getType().getBinding();
if (BASE_GRID_CLASS.isAssignableFrom(binding) || BASE_READER_CLASS.isAssignableFrom(binding)) {
return true;
}
}
return false;
}
public static String getGridAttributeName( MapLayer layer ) {
String attrName = null;
Collection<PropertyDescriptor> descriptors = layer.getFeatureSource().getSchema().getDescriptors();
for( PropertyDescriptor desc : descriptors ) {
Class< ? > binding = desc.getType().getBinding();
if (BASE_GRID_CLASS.isAssignableFrom(binding) || BASE_READER_CLASS.isAssignableFrom(binding)) {
attrName = desc.getName().getLocalPart();
break;
}
}
return attrName;
}
}