/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2015, 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 java.awt.*;
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.logging.Level;
import javax.measure.Unit;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
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.style.CachedGraphicStroke;
import org.geotoolkit.display2d.style.CachedPolygonSymbolizer;
import org.geotoolkit.display2d.style.CachedStroke;
import org.geotoolkit.display2d.style.CachedStrokeGraphic;
import org.geotoolkit.display2d.style.CachedStrokeSimple;
import org.geotoolkit.display2d.style.j2d.PathWalker;
import org.geotoolkit.referencing.operation.matrix.XAffineTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
/**
* @author Johann Sorel (Geomatys)
* @module
*/
public class DefaultPolygonSymbolizerRenderer extends AbstractSymbolizerRenderer<CachedPolygonSymbolizer>{
private final boolean mosaic;
public DefaultPolygonSymbolizerRenderer(final SymbolizerRendererService service,final CachedPolygonSymbolizer symbol, final RenderingContext2D context){
super(service,symbol,context);
mosaic = symbol.isMosaic();
}
/**
* {@inheritDoc }
*/
@Override
public void portray(final ProjectedObject projectedFeature) throws PortrayalException{
final Object candidate = projectedFeature.getCandidate();
//test if the symbol is visible on this feature
if(symbol.isVisible(candidate)){
final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName);
//symbolizer doesnt match the featuretype, no geometry found with this name.
if(projectedGeometry == null) return;
portray(projectedGeometry, candidate);
}
}
/**
* {@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);
}
private void portray(final ProjectedGeometry projectedGeometry, final Object candidate) throws PortrayalException{
final float offset = symbol.getOffset(candidate, coeff);
final Shape[] shapes;
//calculate displacement
final float[] disps = symbol.getDisplacement(candidate);
Point2D dispStep = null;
if(disps[0] != 0 || disps[1] != 0){
dispStep = new Point2D.Float(disps[0], -disps[1]);
}
float sizeCorrection = 1f;
try {
if(dispGeom){
renderingContext.switchToDisplayCRS();
shapes = (offset != 0) ? bufferDisplayGeometry(renderingContext, projectedGeometry, offset)
: projectedGeometry.getDisplayShape();
}else{
//NOTE : Java2d has issues when rendering shapes with large strokes when
//there is a given transform, we cheat by converting the geometry is
//display unit.
//
//renderingContext.switchToObjectiveCRS();
//shapes = (offset != 0) ? bufferObjectiveGeometry(renderingContext, projectedGeometry, symbolUnit, offset)
// : projectedGeometry.getObjectiveShape();
//adjust displacement, displacement is expressed in pixel units
//final AffineTransform inverse = renderingContext.getDisplayToObjective();
//if(dispStep!=null) dispStep = inverse.deltaTransform(dispStep, dispStep);
renderingContext.switchToDisplayCRS();
sizeCorrection = (float)XAffineTransform.getScale(renderingContext.getObjectiveToDisplay());
if(offset!=0){
shapes = bufferDisplayGeometry(renderingContext, projectedGeometry, offset*sizeCorrection);
}else{
shapes = projectedGeometry.getDisplayShape();
}
}
}catch (TransformException ex){
throw new PortrayalException("Could not calculate projected geometry",ex);
}
if(shapes == null){
//no geometry, end here
return;
}
final float coeff = this.coeff * sizeCorrection;
for(Shape shape : shapes){
//we apply the displacement ---------------------------------------
if(dispStep != null){
g2d.translate(dispStep.getX(), dispStep.getY());
}
final int x;
final int y;
if(mosaic){
//we need the upperleft point to properly paint the polygon
final float margin = symbol.getMargin(candidate, coeff) /2f;
final Rectangle2D bounds = shape.getBounds2D();
if(bounds == null)return;
x = (int) (bounds.getMinX() - margin);
y = (int) (bounds.getMinY() - margin);
}else{
x=0;
y=0;
}
if(symbol.isFillVisible(candidate)){
g2d.setComposite( symbol.getJ2DFillComposite(candidate) );
g2d.setPaint( symbol.getJ2DFillPaint(candidate, x, y,coeff, hints) );
g2d.fill(shape);
}
if(symbol.isStrokeVisible(candidate)){
final CachedStroke cachedStroke = symbol.getCachedStroke();
if(cachedStroke instanceof CachedStrokeSimple){
final CachedStrokeSimple cs = (CachedStrokeSimple)cachedStroke;
g2d.setComposite(cs.getJ2DComposite(candidate));
g2d.setPaint(cs.getJ2DPaint(candidate, x, y, coeff, hints));
g2d.setStroke(cs.getJ2DStroke(candidate,coeff));
//NOTE : java2d issue when rendering shapes with size < 1px
final Rectangle bounds = shape.getBounds();
if(bounds.width>1 || bounds.height>1){
g2d.draw(shape);
}else {
g2d.fill(g2d.getStroke().createStrokedShape(shape));
}
}else if(cachedStroke instanceof CachedStrokeGraphic){
final CachedStrokeGraphic gc = (CachedStrokeGraphic)cachedStroke;
final float initGap = gc.getInitialGap(candidate);
final Point2D pt = new Point2D.Double();
final CachedGraphicStroke cgs = gc.getCachedGraphic();
final Image img = cgs.getImage(candidate, 1, hints);
final float imgWidth = img.getWidth(null);
final float imgHeight = img.getHeight(null);
final float gap = gc.getGap(candidate)+ imgWidth;
final AffineTransform trs = new AffineTransform();
final PathIterator ite = shape.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(candidate, null);
final float[] disp = cgs.getDisplacement(candidate, 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);
}
}
}
//restore the displacement
if(dispStep != null){
g2d.translate(-dispStep.getX(), -dispStep.getY());
}
}
}
/**
* {@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 Shape mask = search.getDisplayShape();
final Object candidate = projectedFeature.getCandidate();
//test if the symbol is visible on this feature
if(!symbol.isVisible(candidate)) return false;
final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName);
//symbolizer doesnt match the featuretype, no geometry found with this name.
if(projectedGeometry == null) return false;
final float offset = symbol.getOffset(candidate, coeff);
//we switch to more appropriate context CRS for rendering -------------
final Shape CRSShape;
final Shape[] j2dShapes;
try{
if(dispGeom){
CRSShape = mask;
j2dShapes = (offset != 0) ? bufferDisplayGeometry(renderingContext, projectedGeometry, offset)
: projectedGeometry.getDisplayShape();
}else{
try{
CRSShape = new TransformedShape();
((TransformedShape)CRSShape).setTransform(renderingContext.getAffineTransform(renderingContext.getDisplayCRS(), renderingContext.getObjectiveCRS()));
((TransformedShape)CRSShape).setOriginalShape(mask);
}catch(FactoryException ex){
ex.printStackTrace();
return false;
}
j2dShapes = (offset != 0) ? bufferObjectiveGeometry(renderingContext, projectedGeometry, symbolUnit, offset)
: projectedGeometry.getObjectiveShape();
}
}catch (TransformException ex) {
ex.printStackTrace();
return false;
}
//we apply the displacement --------------------------------------------
//TODO handle displacement
// final float[] disps = symbol.getDisplacement(feature);
// final Point2D displacedPoint = new Point2D.Float((float)mask.getX() - disps[0], (float)mask.getY() + disps[1] );
//Test composites ------------------------------------------------------
final float fillAlpha = symbol.getJ2DFillComposite(candidate).getAlpha();
//todo must hanlde graphic stroke
//final float strokeAlpha = symbol.getJ2DStrokeComposite(feature).getAlpha();
if(j2dShapes == null){
return false;
}
if(fillAlpha < GO2Utilities.SELECTION_LOWER_ALPHA){
//feature graphic is translucide, not selectable
return false;
}
Area area ;
if(fillAlpha >= GO2Utilities.SELECTION_LOWER_ALPHA){
for(Shape j2dShape : j2dShapes){
area = new Area(j2dShape);
switch(filter){
case INTERSECTS :
area.intersect(new Area(CRSShape));
if(!area.isEmpty()) return true;
case WITHIN :
Area start = new Area(area);
area.add(new Area(CRSShape));
if(start.equals(area)) return true;
}
}
}
return false;
}
/**
* {@inheritDoc }
*/
@Override
public boolean hit(final ProjectedCoverage graphic, final SearchAreaJ2D mask, final VisitFilter filter) {
return false;
}
/**
* Recalculate objective geometry with the given offset,
* for polygon this act like a buffer
*/
private static Shape[] bufferObjectiveGeometry(final RenderingContext2D context, final ProjectedGeometry projectedFeature,
final Unit symbolUnit, final float offset) throws TransformException{
//TODO use symbol unit to adjust offset
final Geometry[] geoms = projectedFeature.getObjectiveGeometryJTS();
final Shape[] shapes = new Shape[geoms.length];
for(int i=0;i<geoms.length;i++){
geoms[i] = geoms[i].buffer(offset);
shapes[i] = GO2Utilities.toJava2D(geoms[i]);
}
return shapes;
}
/**
* Recalculate display geometry with the given offset,
* for polygon this act like a buffer
*/
private static Shape[] bufferDisplayGeometry(final RenderingContext2D context, final ProjectedGeometry projectedFeature,
final float offset) throws TransformException{
final Geometry[] geoms = projectedFeature.getDisplayGeometryJTS();
final Shape[] shapes = new Shape[geoms.length];
for(int i=0;i<geoms.length;i++){
try{
geoms[i] = geoms[i].buffer(offset);
}catch(IllegalArgumentException ex){
//can happen if the geometry has too few points, like a ring of 3points
LOGGER.log(Level.FINE, ex.getLocalizedMessage(), ex);
}
shapes[i] = GO2Utilities.toJava2D(geoms[i]);
}
return shapes;
}
}