/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2014, Geomatys
*
* 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.geotoolkit.display2d.style.renderer;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.logging.Level;
import org.geotoolkit.display.VisitFilter;
import org.geotoolkit.display.PortrayalException;
import org.geotoolkit.display.shape.TransformedShape;
import org.geotoolkit.display2d.GO2Utilities;
import org.geotoolkit.display2d.canvas.RenderingContext2D;
import org.geotoolkit.display2d.primitive.ProjectedCoverage;
import org.geotoolkit.display2d.primitive.ProjectedGeometry;
import org.geotoolkit.display2d.primitive.ProjectedObject;
import org.geotoolkit.display2d.primitive.SearchAreaJ2D;
import org.geotoolkit.display2d.primitive.jts.JTSGeometryJ2D;
import org.geotoolkit.display2d.style.CachedGraphicStroke;
import org.geotoolkit.display2d.style.CachedLineSymbolizer;
import org.geotoolkit.display2d.style.CachedStroke;
import org.geotoolkit.display2d.style.CachedStrokeGraphic;
import org.geotoolkit.display2d.style.CachedStrokeSimple;
import org.geotoolkit.display2d.style.CachedSymbolizer;
import org.geotoolkit.display2d.style.j2d.PathWalker;
import org.geotoolkit.geometry.jts.LineStringTranslator;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
/**
* @author Johann Sorel (Geomatys)
* @module
*/
public class DefaultLineSymbolizerRenderer extends AbstractSymbolizerRenderer<CachedLineSymbolizer>{
private final CachedStroke cachedStroke;
public DefaultLineSymbolizerRenderer(final SymbolizerRendererService service,final CachedLineSymbolizer symbol, final RenderingContext2D context){
super(service,symbol,context);
cachedStroke = symbol.getCachedStroke();
}
/**
* {@inheritDoc }
*/
@Override
public void portray(final ProjectedCoverage projectedCoverage) throws PortrayalException{
//portray the border of the coverage
final ProjectedGeometry projectedGeometry = projectedCoverage.getEnvelopeGeometry();
//could not find the border geometry
if(projectedGeometry == null) return;
portray(projectedGeometry, null);
}
/**
* {@inheritDoc }
*/
@Override
public void portray(final ProjectedObject projectedFeature) throws PortrayalException{
final Object candidate = projectedFeature.getCandidate();
final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName);
//symbolizer doesnt match the featuretype, no geometry found with this name.
if(projectedGeometry == null) return;
//test if the symbol is visible on this feature
if(symbol.isVisible(candidate)){
portray(projectedGeometry, candidate);
}
}
/**
* {@inheritDoc }
*/
@Override
public void portray(final Iterator<? extends ProjectedObject> graphics) throws PortrayalException {
if(dispGeom){
renderingContext.switchToDisplayCRS();
}else{
renderingContext.switchToObjectiveCRS();
}
while(graphics.hasNext()){
if(monitor.stopRequested()){
break;
}
final ProjectedObject projectedFeature = graphics.next();
final Object feature = projectedFeature.getCandidate();
final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName);
//symbolizer doesnt match the featuretype, no geometry found with this name.
if(projectedGeometry == null) continue;
//test if the symbol is visible on this feature
if(!symbol.isVisible(feature)){
continue;
}
final Shape[] j2dShapes = getShapes(projectedGeometry, feature);
// Do not try to draw this shape if null
if (j2dShapes == null) {
continue;
}
for(Shape j2dShape : j2dShapes){
portray(symbol, g2d, j2dShape, cachedStroke, feature, coeff, hints);
}
}
}
private void portray(final ProjectedGeometry projectedGeometry, final Object feature) throws PortrayalException{
final Shape[] j2dShapes;
if(dispGeom){
renderingContext.switchToDisplayCRS();
}else{
renderingContext.switchToObjectiveCRS();
}
j2dShapes = getShapes(projectedGeometry, feature);
if(j2dShapes == null){
return;
}
for(Shape j2dShape : j2dShapes){
portray(symbol, g2d, j2dShape, cachedStroke, feature, coeff, hints);
}
}
private Shape[] getShapes(ProjectedGeometry projectedGeometry, Object feature) throws PortrayalException{
final float offset = symbol.getOffset(feature, coeff);
final Shape[] j2dShapes;
if(offset==0){
try{
j2dShapes = (dispGeom)? projectedGeometry.getDisplayShape()
: projectedGeometry.getObjectiveShape();
} catch (TransformException ex) {
throw new PortrayalException("Could not calculate objective projected geometry",ex);
}
}else{
try{
final Geometry[] geoms = (dispGeom)? projectedGeometry.getDisplayGeometryJTS()
: projectedGeometry.getObjectiveGeometryJTS();
j2dShapes = new Shape[geoms.length];
for(int i=0;i<geoms.length;i++){
Geometry g = geoms[i];
if(g instanceof LineString){
g = LineStringTranslator.translateLineString((LineString)g, offset);
}else if(g instanceof MultiLineString){
g = LineStringTranslator.translateLineString((MultiLineString)g, offset);
}
j2dShapes[i] = new JTSGeometryJ2D(g);
//TODO : clip geometry
}
} catch (TransformException ex) {
throw new PortrayalException("Could not calculate objective projected geometry",ex);
}
}
return j2dShapes;
}
public static void portray(CachedSymbolizer symbol, Graphics2D g2d, Shape j2dShape,
CachedStroke cachedStroke, Object feature, float coeff, RenderingHints hints){
if(cachedStroke instanceof CachedStrokeSimple){
final CachedStrokeSimple cs = (CachedStrokeSimple)cachedStroke;
g2d.setComposite(cs.getJ2DComposite(feature));
if(cs.isMosaicPaint()){
//we need to find the top left bounds of the geometry
final float margin = symbol.getMargin(feature, coeff) /2f;
final Rectangle2D bounds = j2dShape.getBounds2D();
final int x = (int) (bounds.getMinX() - margin);
final int y = (int) (bounds.getMinY() - margin);
g2d.setPaint(cs.getJ2DPaint(feature, x, y, coeff, hints));
}else{
g2d.setPaint(cs.getJ2DPaint(feature, 0, 0, coeff, hints));
}
g2d.setStroke(cs.getJ2DStroke(feature,coeff));
g2d.draw(j2dShape);
}else if(cachedStroke instanceof CachedStrokeGraphic){
final CachedStrokeGraphic gc = (CachedStrokeGraphic)cachedStroke;
g2d.setComposite(GO2Utilities.ALPHA_COMPOSITE_1F);
final float initGap = gc.getInitialGap(feature);
final Point2D pt = new Point2D.Double();
final CachedGraphicStroke cgs = gc.getCachedGraphic();
final Image img = cgs.getImage(feature, 1, hints);
final float imgWidth = img.getWidth(null);
final float imgHeight = img.getHeight(null);
final float gap = gc.getGap(feature)+ imgWidth;
final AffineTransform trs = new AffineTransform();
final PathIterator ite = j2dShape.getPathIterator(null);
final PathWalker walker = new PathWalker(ite);
walker.walk(initGap);
while(!walker.isFinished()){
//paint the motif --------------------------------------------------
walker.getPosition(pt);
final float angle = walker.getRotation();
trs.setToTranslation(pt.getX(), pt.getY());
trs.rotate(angle);
final float[] anchor = cgs.getAnchor(feature, null);
final float[] disp = cgs.getDisplacement(feature, null);
trs.translate(-imgWidth*anchor[0], -imgHeight*anchor[1]);
trs.translate(disp[0], -disp[1]);
g2d.drawImage(img, trs, null);
//walk over the gap ------------------------------------------------
walker.walk(gap);
}
}
}
/**
* {@inheritDoc }
*/
@Override
public boolean hit(final ProjectedObject projectedFeature, final SearchAreaJ2D search, final VisitFilter filter) {
//TODO optimize test using JTS geometries, Java2D Area cost to much cpu
final Geometry mask = search.getDisplayGeometryJTS();
final Object feature = projectedFeature.getCandidate();
//test if the symbol is visible on this feature
if(!(symbol.isVisible(feature))) return false;
final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName);
//symbolizer doesnt match the featuretype, no geometry found with this name.
if(projectedGeometry == null) return false;
//Test composites ------------------------------------------------------
if(cachedStroke instanceof CachedStrokeSimple){
final CachedStrokeSimple cs = (CachedStrokeSimple)cachedStroke;
final float strokeAlpha = cs.getJ2DComposite(feature).getAlpha();
if(strokeAlpha < GO2Utilities.SELECTION_LOWER_ALPHA){
//feature graphic is translucide, not selectable
return false;
}
}
if(dispGeom){
final Geometry[] j2dShapes;
try {
j2dShapes = projectedGeometry.getDisplayGeometryJTS();
} catch (TransformException ex) {
LOGGER.log(Level.WARNING, "Error while accesing geometry.",ex);
return false;
}
final int bufferWidth = (int) symbol.getMargin(feature,1);
//test envelopes first
// Geometry CRSShape = mask.getEnvelope();
// CRSShape = mask.buffer(bufferWidth,1);
// boolean hit = testHit(filter,CRSShape,j2dShape.getEnvelope());
// if(!hit) return false;
//test real shape
Geometry CRSShape = mask;
try{
CRSShape = mask.buffer(bufferWidth);
}catch(IllegalArgumentException ex){
//can happen if the geometry has too few points, like a ring of 3points
LOGGER.log(Level.FINE, ex.getLocalizedMessage(), ex);
}
for(Geometry j2dShape : j2dShapes){
if(GO2Utilities.testHit(filter,CRSShape,j2dShape)) return true;
}
return false;
}else{
final Shape[] j2dShapes;
final Shape[] CRSShapes;
try {
j2dShapes = projectedGeometry.getObjectiveShape();
CRSShapes = new Shape[j2dShapes.length];
for(int i=0;i<j2dShapes.length;i++){
CRSShapes[i] = new TransformedShape();
((TransformedShape)CRSShapes[i]).setTransform(
renderingContext.getAffineTransform(renderingContext.getDisplayCRS(), renderingContext.getObjectiveCRS()));
((TransformedShape)CRSShapes[i]).setOriginalShape(search.getDisplayShape());
}
} catch (TransformException ex) {
LOGGER.log(Level.WARNING, "Error while accesing geometry.",ex);
return false;
} catch (FactoryException ex) {
LOGGER.log(Level.WARNING, "Error while accesing geometry.",ex);
return false;
}
for(int i=0;i<j2dShapes.length;i++){
Shape j2dShape = j2dShapes[i];
Shape CRSShape = CRSShapes[i];
if(cachedStroke instanceof CachedStrokeSimple){
final CachedStrokeSimple cs = (CachedStrokeSimple)cachedStroke;
final java.awt.Stroke stroke = cs.getJ2DStroke(feature,coeff);
final Area area = new Area(stroke.createStrokedShape(j2dShape));
switch(filter){
case INTERSECTS :
area.intersect(new Area(CRSShape));
return !area.isEmpty();
case WITHIN :
Area start = new Area(area);
area.add(new Area(CRSShape));
return start.equals(area);
}
}
}
}
return false;
}
/**
* {@inheritDoc }
*/
@Override
public boolean hit(final ProjectedCoverage graphic, final SearchAreaJ2D mask, final VisitFilter filter) {
return false;
}
}