/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.ui.views.styledmap.painter;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.ui.PlatformUI;
import org.geotools.geometry.jts.GeomCollectionIterator;
import org.geotools.geometry.jts.LiteShape2;
import org.geotools.renderer.lite.StyledShapePainter;
import org.geotools.renderer.style.GraphicStyle2D;
import org.geotools.renderer.style.MarkStyle2D;
import org.geotools.renderer.style.SLDStyleFactory;
import org.geotools.renderer.style.Style2D;
import org.geotools.styling.Fill;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLD;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.Symbolizer;
import org.geotools.util.Range;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.jdesktop.swingx.mapviewer.PixelConverter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
import de.fhg.igd.geom.Point3D;
import de.fhg.igd.mapviewer.marker.BoundingBoxMarker;
import de.fhg.igd.mapviewer.marker.area.Area;
import de.fhg.igd.mapviewer.marker.area.BoxArea;
import de.fhg.igd.mapviewer.waypoints.SelectableWaypoint;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.instance.model.InstanceReference;
import eu.esdihumboldt.hale.common.schema.geometry.CRSDefinition;
import eu.esdihumboldt.hale.common.schema.geometry.GeometryProperty;
import eu.esdihumboldt.hale.ui.common.service.style.StyleService;
import eu.esdihumboldt.hale.ui.service.instance.InstanceService;
import eu.esdihumboldt.hale.ui.style.StyleHelper;
import eu.esdihumboldt.hale.ui.style.service.internal.StylePreferences;
import eu.esdihumboldt.hale.ui.views.styledmap.util.CRSConverter;
/**
* Instance marker support styles provided through the {@link StyleService}.
*
* @author Simon Templer
*/
@SuppressWarnings("restriction")
public class StyledInstanceMarker extends InstanceMarker {
private final AtomicBoolean styleInitialized = new AtomicBoolean(false);
private volatile Color styleFillColor;
private volatile Color styleStrokeColor;
private volatile java.awt.Stroke styleStroke;
private boolean hasFill = true;
private PointSymbolizer pointSymbolizer;
private static final StyleBuilder styleBuilder = new StyleBuilder();
/**
* The way-point the marker is associated with
*/
private final InstanceWaypoint wp;
/**
* Create a instance marker supporting styles.
*
* @param wp the way-point the marker is associated with
*/
public StyledInstanceMarker(InstanceWaypoint wp) {
this.wp = wp;
}
/**
* Initialize the style information.
*
* @param context the context
*/
private synchronized void initStyle(InstanceWaypoint context) {
if (!styleInitialized.compareAndSet(false, true)) {
// already initialized
return;
}
// check if there is a Rule from the Rulestyle-Page and apply to the
// instancemarker on the map
// performs a special task if the found symbolizer is a point symbolizer
Rule honoredRule = honorRules(context);
pointSymbolizer = null;
for (Symbolizer sym : honoredRule.symbolizers()) {
if (sym instanceof PointSymbolizer) {
pointSymbolizer = (PointSymbolizer) sym;
break;
}
}
fillStyle(honoredRule, context);
strokeStyle(honoredRule, context);
}
/**
* Checks if there is a rule for the certain Instance
*
* @param context the context
* @return a certain style rule for the instance, else-rule if nothing found
* or null if there is no else-rule
*/
private Rule honorRules(InstanceWaypoint context) {
Style style = getStyle(context);
Rule[] rules = SLD.rules(style);
// do rules exist?
if (rules == null || rules.length == 0) {
return null;
}
// sort the elserules at the end
if (rules.length > 1) {
rules = sortRules(rules);
}
// if rule exists
InstanceReference ir = context.getValue();
InstanceService is = PlatformUI.getWorkbench().getService(InstanceService.class);
boolean instanceInitialized = false;
Instance inst = null; // instance variable - only initialize if needed
for (int i = 0; i < rules.length; i++) {
if (rules[i].getFilter() != null) {
if (!instanceInitialized) {
// initialize instance (as it is needed for the filter)
inst = is.getInstance(ir);
instanceInitialized = true;
}
if (rules[i].getFilter().evaluate(inst)) {
return rules[i];
}
}
// if a rule exist without a filter and without being an
// else-filter,
// the found rule applies to all types
else {
if (!rules[i].isElseFilter()) {
return rules[i];
}
}
}
// if there is no appropriate rule, check if there is an else-rule
for (int i = 0; i < rules.length; i++) {
if (rules[i].isElseFilter()) {
return rules[i];
}
}
// return null if no rule was found
return null;
}
/**
* Sorts an array of rules, so the else-filter-rules are at the end
*
* @param rules an array of Rules
* @return a new array of Rules with sorted elements
*/
private Rule[] sortRules(Rule[] rules) {
ArrayList<Rule> temp = new ArrayList<Rule>();
for (int i = 0; i < rules.length; i++) {
if (!rules[i].isElseFilter()) {
temp.add(rules[i]);
}
}
for (int i = 0; i < rules.length; i++) {
if (rules[i].isElseFilter()) {
temp.add(rules[i]);
}
}
Rule[] newRules = new Rule[temp.size()];
return temp.toArray(newRules);
}
/**
* Retrieves the fill for the map marker.
*
* @param rule a certain rule to apply, may be <code>null</code>
* @param context the InstanceWayPoint, which gets marked
*/
private synchronized void fillStyle(Rule rule, InstanceWaypoint context) {
// retrieve fill
Fill fill = null;
// try the Symbolizers from the Rule
for (int i = 0; rule != null && fill == null && i < rule.getSymbolizers().length; i++) {
if (rule.getSymbolizers()[i] instanceof PolygonSymbolizer) {
fill = SLD.fill((PolygonSymbolizer) rule.getSymbolizers()[i]);
}
else if (rule.getSymbolizers()[i] instanceof PointSymbolizer) {
fill = SLD.fill((PointSymbolizer) rule.getSymbolizers()[i]);
}
}
// if we have a fill now
if (fill != null) {
Color sldColor = SLD.color(fill);
double opacity = SLD.opacity(fill);
if (sldColor != null) {
styleFillColor = new Color(sldColor.getRed(), sldColor.getGreen(),
sldColor.getBlue(), (int) (opacity * 255));
}
else {
styleFillColor = super.getPaintColor(context);
}
hasFill = true;
}
// if we still don't have a fill
else {
styleFillColor = null;
hasFill = false;
}
}
/**
* retrieves the stroke for the map marker
*
* @param rule a certain rule to apply, maybe null
* @param context the InstanceWayPoint, wich gets marked
*/
private synchronized void strokeStyle(Rule rule, InstanceWaypoint context) {
// retrieve stroke
Stroke stroke = null;
// try the Symbolizers from the Rule
for (int i = 0; rule != null && stroke == null && i < rule.getSymbolizers().length; i++) {
if (rule.getSymbolizers()[i] instanceof LineSymbolizer) {
stroke = SLD.stroke((LineSymbolizer) rule.getSymbolizers()[i]);
}
else if (rule.getSymbolizers()[i] instanceof PolygonSymbolizer) {
stroke = SLD.stroke((PolygonSymbolizer) rule.getSymbolizers()[i]);
}
else if (rule.getSymbolizers()[i] instanceof PointSymbolizer) {
stroke = SLD.stroke((PointSymbolizer) rule.getSymbolizers()[i]);
}
}
// if we have a stroke now
if (stroke != null) {
// XXX is there any Geotools stroke to AWT stroke lib/code
// somewhere?!
// XXX have a look at the renderer code (StreamingRenderer)
// stroke color
Color sldColor = SLD.color(stroke);
double opacity = SLD.opacity(stroke);
if (Double.isNaN(opacity)) {
// fall back to default opacity
opacity = StyleHelper.DEFAULT_FILL_OPACITY;
}
if (sldColor != null) {
styleStrokeColor = new Color(sldColor.getRed(), sldColor.getGreen(),
sldColor.getBlue(), (int) (opacity * 255));
}
else {
styleStrokeColor = super.getBorderColor(context);
}
// stroke width
int strokeWidth = SLD.width(stroke);
if (strokeWidth == SLD.NOTFOUND) {
// fall back to default width
strokeWidth = StylePreferences.getDefaultWidth();
}
styleStroke = new BasicStroke(strokeWidth);
}
else {
styleStroke = null;
styleStrokeColor = null;
}
}
/**
* Reset the marker style
*/
public void resetStyle() {
styleInitialized.set(false);
areaReset();
}
/**
* @see InstanceMarker#getPaintColor(InstanceWaypoint)
*/
@Override
protected Color getPaintColor(InstanceWaypoint context) {
initStyle(context);
if (styleFillColor == null || context.isSelected()) {
// for selection don't use style
return super.getPaintColor(context);
}
return styleFillColor;
}
/**
* @see InstanceMarker#getBorderColor(InstanceWaypoint)
*/
@Override
protected Color getBorderColor(InstanceWaypoint context) {
initStyle(context);
if (styleStrokeColor == null || context.isSelected()) {
return super.getBorderColor(context);
}
return styleStrokeColor;
}
/**
* Get the stroke for drawing lines.
*
* @param context the context
* @return the stroke
*/
@Override
protected java.awt.Stroke getLineStroke(InstanceWaypoint context) {
initStyle(context);
if (styleStroke != null && !context.isSelected()) {
return styleStroke;
}
else {
return super.getLineStroke(context);
}
}
/**
* @see BoundingBoxMarker#applyFill(Graphics2D, SelectableWaypoint)
*/
@Override
protected boolean applyFill(Graphics2D g, InstanceWaypoint context) {
initStyle(context);
if (hasFill) {
g.setPaint(getPaintColor(context));
return true;
}
return false;
}
/**
* @see BoundingBoxMarker#applyStroke(Graphics2D, SelectableWaypoint)
*/
@Override
protected boolean applyStroke(Graphics2D g, InstanceWaypoint context) {
initStyle(context);
return super.applyStroke(g, context);
}
/**
* Get the style for a given way-point.
*
* @param context the way-point
* @return the style
*/
private Style getStyle(InstanceWaypoint context) {
StyleService ss = PlatformUI.getWorkbench().getService(StyleService.class);
InstanceReference ref = context.getValue();
return ss.getStyle(context.getInstanceType(), ref.getDataSet());
}
/**
* @see BoundingBoxMarker#isToSmall(int, int, int)
*/
@Override
protected boolean isToSmall(int width, int height, int zoom) {
if (representsSinglePoint()) {
// disable fallback marker, as paintPoint would never be called
return false;
}
return super.isToSmall(width, height, zoom);
}
/**
* Determines if the associated way-point represents a single point.
*
* @return if the way-point represents a single point
*/
private boolean representsSinglePoint() {
boolean pointFound = false;
for (GeometryProperty<?> geom : wp.getGeometries()) {
if (geom.getGeometry() != null && geom.getCRSDefinition() != null) {
// valid geometry
if (pointFound) {
// a point was already found before
return false;
}
if (geom.getGeometry() instanceof Point || (geom.getGeometry() instanceof MultiPoint
&& geom.getGeometry().getNumGeometries() == 1)) {
// a single point found
pointFound = true;
}
else {
// another geometry found
return false;
}
}
}
return pointFound;
}
/**
* @see InstanceMarker#paintPoint(Point, Graphics2D, CRSDefinition,
* InstanceWaypoint, PixelConverter, int, CoordinateReferenceSystem,
* boolean)
*/
@Override
protected Area paintPoint(Point geometry, Graphics2D g, CRSDefinition crsDefinition,
InstanceWaypoint context, PixelConverter converter, int zoom,
CoordinateReferenceSystem mapCRS, boolean calculateArea) {
initStyle(context);
Area area = null;
try {
if (pointSymbolizer == null || (SLD.mark(pointSymbolizer) == null
&& pointSymbolizer.getGraphic().graphicalSymbols().isEmpty())) {
// only marks supported for now
// if there is no specialized PointSymbolizer, fall back to a
// generic
return super.paintFallback(g, context, converter, zoom, null, calculateArea);
}
// get CRS converter
CRSConverter conv = CRSConverter.getConverter(crsDefinition.getCRS(), mapCRS);
// manually convert to map CRS
Point3D mapPoint = conv.convert(geometry.getX(), geometry.getY(), 0);
GeoPosition pos = new GeoPosition(mapPoint.getX(), mapPoint.getY(),
converter.getMapEpsg());
// determine pixel coordinates
Point2D point = converter.geoToPixel(pos, zoom);
Coordinate coordinate = new Coordinate(point.getX(), point.getY());
Point newPoint = geometry.getFactory().createPoint(coordinate);
// create a LiteShape and instantiate the Painter and the
// StyleFactory
LiteShape2 lites = new LiteShape2(newPoint, null, null, false);
StyledShapePainter ssp = new StyledShapePainter();
SLDStyleFactory styleFactory = new SLDStyleFactory();
Range<Double> range = new Range<Double>(Double.class, 0.5, 1.5);
PointSymbolizer pointS;
// is the Waypoint selected?
if (context.isSelected()) {
// switch to the SelectionSymbolizer
pointS = getSelectionSymbolizer(pointSymbolizer);
}
// use the specific PointSymbolizer
else
pointS = pointSymbolizer;
// Create the Style2D object for painting with the use of a
// DummyFeature wich extends SimpleFeatures
// because Geotools can only work with that
DummyFeature dummy = new DummyFeature();
Style2D style2d = styleFactory.createStyle(dummy, pointS, range);
// create the area object of the painted image for further use
area = getArea(point, style2d, lites);
// actually paint
ssp.paint(g, lites, style2d, 1);
// used to draw selection if a graphic style is used (external
// graphic)
if (context.isSelected() && style2d instanceof GraphicStyle2D) {
GraphicStyle2D gs2d = (GraphicStyle2D) style2d;
// get minX and minY for the drawn rectangle arround the image
int minX = (int) point.getX() - gs2d.getImage().getWidth() / 2;
int minY = (int) point.getY() - gs2d.getImage().getHeight() / 2;
// apply the specification of the selection rectangle
applyFill(g, context);
applyStroke(g, context);
// draw the selection rectangle
g.drawRect(minX - 1, minY - 1, gs2d.getImage().getWidth() + 1,
gs2d.getImage().getHeight() + 1);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return area;
}
/**
* Creates a certain Point Symbolizer if the waypoint is selected
*
* @param symbolizer a symbolizer which is used to create the selection
* symbolizer
* @return returns the selection symbolizer
*/
private PointSymbolizer getSelectionSymbolizer(PointSymbolizer symbolizer) {
// XXX only works with marks and external graphics right now
Mark mark = SLD.mark(symbolizer);
if (mark != null) {
Mark mutiMark = styleBuilder.createMark(mark.getWellKnownName(),
styleBuilder.createFill(StylePreferences.getSelectionColor(),
StyleHelper.DEFAULT_FILL_OPACITY),
styleBuilder.createStroke(StylePreferences.getSelectionColor(),
StylePreferences.getSelectionWidth()));
// create new symbolizer
return styleBuilder
.createPointSymbolizer(styleBuilder.createGraphic(null, mutiMark, null));
}
else {
return symbolizer;
}
}
/**
* Returns the area of the drawn point.
*
* @param point the point
* @param style the Style2D object
* @param shape the Light Shape
* @return the area, which should equal the space of the drawn object
*/
private Area getArea(Point2D point, Style2D style, LiteShape2 shape) {
// if it is a mark style
if (style instanceof MarkStyle2D) {
PathIterator citer = getPathIterator(shape);
float[] coords = new float[2];
MarkStyle2D ms2d = (MarkStyle2D) style;
Shape transformedShape;
while (!(citer.isDone())) {
citer.currentSegment(coords);
transformedShape = ms2d.getTransformedShape(coords[0], coords[1]);
if (transformedShape != null) {
java.awt.geom.Area areatemp = new java.awt.geom.Area(transformedShape);
Rectangle rec = areatemp.getBounds();
AdvancedBoxArea area = new AdvancedBoxArea(areatemp, rec.x, rec.y,
rec.x + rec.width, rec.y + rec.height);
return area;
}
}
}
// if it is an external graphic style
else if (style instanceof GraphicStyle2D) {
GraphicStyle2D gs2d = (GraphicStyle2D) style;
int minX = (int) point.getX() - gs2d.getImage().getWidth() / 2;
int minY = (int) point.getY() - gs2d.getImage().getHeight() / 2;
int maxX = (int) point.getX() + gs2d.getImage().getWidth() / 2;
int maxY = (int) point.getX() + gs2d.getImage().getHeight() / 2;
BoxArea area = new BoxArea(minX, minY, maxX, maxY);
return area;
}
return null;
}
/**
* Returns a path iterator.
*
* @param shape a shape to determine the iterator
* @return the path iterator
*/
private PathIterator getPathIterator(final LiteShape2 shape) {
// DJB: changed this to handle multi* geometries and line and
// polygon geometries better
GeometryCollection gc;
if (shape.getGeometry() instanceof GeometryCollection)
gc = (GeometryCollection) shape.getGeometry();
else {
Geometry[] gs = new Geometry[1];
gs[0] = shape.getGeometry();
// make a Point,Line, or Poly into a GC
gc = shape.getGeometry().getFactory().createGeometryCollection(gs);
}
AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
GeomCollectionIterator citer = new GeomCollectionIterator(gc, IDENTITY_TRANSFORM, false,
1.0);
return citer;
}
}