/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008 - 2013, 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.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.Point; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.Iterator; import org.geotoolkit.display.VisitFilter; import org.geotoolkit.display.PortrayalException; 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.CachedPointSymbolizer; import org.geotoolkit.referencing.operation.matrix.XAffineTransform; import org.apache.sis.referencing.operation.matrix.AffineTransforms2D; import org.apache.sis.measure.Units; import org.geotoolkit.geometry.jts.JTS; import org.opengis.referencing.operation.TransformException; /** * @author Johann Sorel (Geomatys) * @module */ public class DefaultPointSymbolizerRenderer extends AbstractSymbolizerRenderer<CachedPointSymbolizer>{ public DefaultPointSymbolizerRenderer(final SymbolizerRendererService service,final CachedPointSymbolizer symbol, final RenderingContext2D context){ super(service,symbol,context); } /** * {@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(); //test if the symbol is visible on this feature if(!symbol.isVisible(candidate)) return; final ProjectedGeometry projectedGeometry = projectedFeature.getGeometry(geomPropertyName); portray(projectedGeometry, candidate); } private void portray(final ProjectedGeometry projectedGeometry, Object candidate) throws PortrayalException{ //symbolizer doesnt match the featuretype, no geometry found with this name. if(projectedGeometry == null) return; g2d.setComposite(GO2Utilities.ALPHA_COMPOSITE_1F); //we switch to more appropriate context CRS for rendering --------- // a point symbolis always paint in display unit ------------------- renderingContext.switchToDisplayCRS(); //we adjust coefficient for rendering ------------------------------ float coeff; if(symbolUnit.equals(Units.POINT)){ //symbol is in display unit coeff = 1; }else{ //we have a special unit we must adjust the coefficient coeff = renderingContext.getUnitCoefficient(symbolUnit); // calculate scale difference between objective and display final AffineTransform inverse = renderingContext.getObjectiveToDisplay(); coeff *= Math.abs(XAffineTransform.getScale(inverse)); } //create the image-------------------------------------------------- final BufferedImage img = symbol.getImage(candidate,coeff,false,hints); if(img == null){ //may be correct, image can be too small for rendering return; } final float imgRot = symbol.getRotation(candidate); final float[] disps = new float[2]; final float[] anchor = new float[2]; symbol.getDisplacement(candidate,disps); symbol.getAnchor(candidate,anchor); disps[0] *= coeff ; disps[1] *= coeff ; final Geometry[] geoms; try { geoms = projectedGeometry.getDisplayGeometryJTS(); } catch (TransformException ex) { throw new PortrayalException("Could not calculate display projected geometry",ex); } if(geoms == null){ //no geometry return; } double rot = AffineTransforms2D.getRotation(renderingContext.getObjectiveToDisplay()); rot -= imgRot; final int postx = (int) (-img.getWidth()*anchor[0] + disps[0]); final int posty = (int) (-img.getHeight()*anchor[1] - disps[1]); for(Geometry geom : geoms){ if(rot==0.0 && imgRot==0f){ if(geom instanceof Point || geom instanceof MultiPoint){ //TODO use generalisation on multipoints final Coordinate[] coords = geom.getCoordinates(); for(int i=0, n = coords.length; i<n ; i++){ final Coordinate coord = coords[i]; //we use Math.floor and not a cast, for negative values this ensure //a regular displacement and avoid tile border artifacts g2d.drawImage(img, (int)Math.floor(coord.x)+postx, (int)Math.floor(coord.y)+posty, null); } }else{ //get most appropriate point final Point pt2d = GO2Utilities.getBestPoint(geom); if(pt2d == null || pt2d.isEmpty()){ //no geometry return; } Coordinate pcoord = pt2d.getCoordinate(); if(Double.isNaN(pcoord.x)){ pcoord = geom.getCoordinate(); } g2d.drawImage(img, (int)Math.floor(pcoord.x)+postx, (int)Math.floor(pcoord.y)+posty, null); } }else{ final AffineTransform postConcat = new AffineTransform(1, 0, 0, 1, postx, posty); final AffineTransform finalRot = new AffineTransform(); finalRot.rotate(imgRot); if(geom instanceof Point || geom instanceof MultiPoint){ //TODO use generalisation on multipoints final Coordinate[] coords = geom.getCoordinates(); for(int i=0, n = coords.length; i<n ; i++){ final Coordinate coord = coords[i]; final AffineTransform ptrs = new AffineTransform(); ptrs.rotate(-rot); ptrs.preConcatenate(new AffineTransform(1, 0, 0, 1, coord.x, coord.y)); ptrs.concatenate(postConcat); g2d.drawImage(img, ptrs, null); } }else{ //get most appropriate point final Point pt2d = GO2Utilities.getBestPoint(geom); if(pt2d == null || pt2d.isEmpty()){ //no geometry return; } Coordinate pcoord = pt2d.getCoordinate(); if(Double.isNaN(pcoord.x)){ pcoord = geom.getCoordinate(); } final AffineTransform ptrs = new AffineTransform(); ptrs.rotate(-rot); ptrs.preConcatenate(new AffineTransform(1, 0, 0, 1, pcoord.x, pcoord.y)); ptrs.concatenate(postConcat); g2d.drawImage(img, ptrs, null); } } } } @Override public void portray(final Iterator<? extends ProjectedObject> graphics) throws PortrayalException { g2d.setComposite(GO2Utilities.ALPHA_COMPOSITE_1F); //we switch to more appropriate context CRS for rendering --------- // a point symbolis always paint in display unit ------------------- renderingContext.switchToDisplayCRS(); //we adjust coefficient for rendering ------------------------------ float coeff; if(symbolUnit.equals(Units.POINT)){ //symbol is in display unit coeff = 1; }else{ //we have a special unit we must adjust the coefficient coeff = renderingContext.getUnitCoefficient(symbolUnit); // calculate scale difference between objective and display final AffineTransform inverse = renderingContext.getObjectiveToDisplay(); coeff *= Math.abs(XAffineTransform.getScale(inverse)); } //caches ProjectedObject projectedobj; Object candidate; final float[] disps = new float[2]; final float[] anchor = new float[2]; final AffineTransform imgTrs = new AffineTransform(); final double rot = AffineTransforms2D.getRotation(renderingContext.getObjectiveToDisplay()); final AffineTransform mapRotationTrs = new AffineTransform(); mapRotationTrs.rotate(-rot); while(graphics.hasNext()){ if(monitor.stopRequested()) return; projectedobj = graphics.next(); candidate = projectedobj.getCandidate(); //test if the symbol is visible on this feature if(!symbol.isVisible(candidate)) continue; final ProjectedGeometry projectedGeometry = projectedobj.getGeometry(geomPropertyName); //symbolizer doesnt match the featuretype, no geometry found with this name. if(projectedGeometry == null) continue; //create the image-------------------------------------------------- final BufferedImage img = symbol.getImage(candidate,coeff,hints); if(img == null) throw new PortrayalException("A null image has been generated by a Mark symbol."); symbol.getDisplacement(candidate,disps); symbol.getAnchor(candidate,anchor); disps[0] *= coeff ; disps[1] *= coeff ; final Geometry[] geoms; try { geoms = projectedGeometry.getDisplayGeometryJTS(); } catch (TransformException ex) { throw new PortrayalException("Could not calculate display projected geometry",ex); } for(Geometry geom : geoms){ if(geom instanceof Point || geom instanceof MultiPoint){ //TODO use generalisation on multipoints final Coordinate[] coords = geom.getCoordinates(); for(int i=0, n = coords.length; i<n ; i++){ final Coordinate coord = coords[i]; if(rot==0){ imgTrs.setToTranslation( -img.getWidth()*anchor[0] + coord.x + disps[0], -img.getHeight()*anchor[1] + coord.y - disps[1]); g2d.drawRenderedImage(img, imgTrs); }else{ final int postx = (int) (-img.getWidth()*anchor[0] + disps[0]); final int posty = (int) (-img.getHeight()*anchor[1] - disps[1]); final AffineTransform ptrs = new AffineTransform(mapRotationTrs); ptrs.preConcatenate(new AffineTransform(1, 0, 0, 1, coord.x, coord.y)); ptrs.concatenate(new AffineTransform(1, 0, 0, 1, postx, posty)); g2d.drawImage(img, ptrs, null); } } }else if(geom!=null){ //get most appropriate point final Point pt2d = GO2Utilities.getBestPoint(geom); if(pt2d == null || pt2d.isEmpty()){ //no geometry return; } Coordinate pcoord = pt2d.getCoordinate(); if(Double.isNaN(pcoord.x)){ pcoord = geom.getCoordinate(); } if(rot==0){ imgTrs.setToTranslation( -img.getWidth()*anchor[0] + pcoord.x + disps[0], -img.getHeight()*anchor[1] + pcoord.y - disps[1]); g2d.drawRenderedImage(img, imgTrs); }else{ final int postx = (int) (-img.getWidth()*anchor[0] + disps[0]); final int posty = (int) (-img.getHeight()*anchor[1] - disps[1]); final AffineTransform ptrs = new AffineTransform(mapRotationTrs); ptrs.preConcatenate(new AffineTransform(1, 0, 0, 1, pcoord.x, pcoord.y)); ptrs.concatenate(new AffineTransform(1, 0, 0, 1, postx, posty)); g2d.drawImage(img, ptrs, null); } } } } } /** * {@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(); Geometry maskArea = null; 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; //we adjust coefficient for rendering ---------------------------------- float coeff = 1; if(symbolUnit.equals(Units.POINT)){ //symbol is in display unit coeff = 1; }else{ //we have a special unit we must adjust the coefficient coeff = renderingContext.getUnitCoefficient(symbolUnit); // calculate scale difference between objective and display final AffineTransform inverse = renderingContext.getObjectiveToDisplay(); coeff *= Math.abs(XAffineTransform.getScale(inverse)); } //create the image------------------------------------------------------ final BufferedImage img = symbol.getImage(candidate,coeff,false,null); final float imgRot = symbol.getRotation(candidate); final float[] disps = new float[2]; symbol.getDisplacement(candidate,disps); disps[0] *= coeff ; disps[1] *= coeff ; final float[] anchor = new float[2]; symbol.getAnchor(candidate,anchor); final Geometry[] geoms; try { geoms = projectedGeometry.getDisplayGeometryJTS(); } catch (TransformException ex) { ex.printStackTrace(); return false; } for(Geometry geom : geoms){ if(geom instanceof Point || geom instanceof MultiPoint){ //TODO use generalisation on multipoints final Coordinate[] coords = geom.getCoordinates(); for(int i=0, n = coords.length; i<n ; i++){ final Coordinate coord = coords[i]; final int x = (int) (-img.getWidth()*anchor[0] + coord.x + disps[0]); final int y = (int) (-img.getHeight()*anchor[1] + coord.y - disps[1]); //TODO should make a better test for the alpha pixel values in image if(imgRot==0){ if(VisitFilter.INTERSECTS.equals(filter)){ if(mask.intersects(x,y,img.getWidth(),img.getHeight())){ return true; } }else if(VisitFilter.WITHIN.equals(filter)){ if(mask.contains(x,y,img.getWidth(),img.getHeight())){ return true; } } }else{ if(maskArea==null) maskArea = JTS.shapeToGeometry(mask,GO2Utilities.JTS_FACTORY); if(maskArea instanceof LinearRing) maskArea = GO2Utilities.JTS_FACTORY.createPolygon((LinearRing)maskArea); final Rectangle2D rect = new Rectangle2D.Double(x, y, img.getWidth(), img.getHeight()); final AffineTransform trs = new AffineTransform(); trs.translate(-rect.getWidth()/2.0, -rect.getHeight()/2.0); trs.rotate(imgRot); trs.translate(+rect.getWidth()/2.0, +rect.getHeight()/2.0); Geometry rotatedImg = JTS.shapeToGeometry(rect, GO2Utilities.JTS_FACTORY); if(rotatedImg instanceof LinearRing) rotatedImg = GO2Utilities.JTS_FACTORY.createPolygon((LinearRing)rotatedImg); if(VisitFilter.INTERSECTS.equals(filter)){ if(maskArea.intersects(rotatedImg)){ return true; } }else if(VisitFilter.WITHIN.equals(filter)){ if(maskArea.contains(rotatedImg)){ return true; } } } } }else{ //get most appropriate point final Point pt2d = GO2Utilities.getBestPoint(geom); Coordinate pcoord = pt2d.getCoordinate(); if(Double.isNaN(pcoord.x)){ pcoord = geom.getCoordinate(); } final int x = (int) (-img.getWidth()*anchor[0] + pcoord.x + disps[0]); final int y = (int) (-img.getHeight()*anchor[1] + pcoord.y - disps[1]); switch(filter){ case INTERSECTS : if(mask.intersects(x,y,img.getWidth(),img.getHeight())){ //TODO should make a better test for the alpha pixel values in image return true; } break; case WITHIN : if(mask.contains(x,y,img.getWidth(),img.getHeight())){ //TODO should make a better test for the alpha pixel values in image return true; } break; } } } return false; } /** * {@inheritDoc } */ @Override public boolean hit(final ProjectedCoverage graphic, final SearchAreaJ2D mask, final VisitFilter filter) { return false; } }