/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 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.primitive;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import static java.awt.geom.PathIterator.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureStoreUtilities;
import org.geotoolkit.display2d.canvas.J2DCanvasBuffered;
import org.geotoolkit.display2d.canvas.RenderingContext2D;
import org.geotoolkit.display2d.container.stateless.StatelessContextParams;
import org.geotoolkit.filter.DefaultFilterFactory2;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.map.MapBuilder;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CommonCRS;
import org.geotoolkit.style.DefaultStyleFactory;
import org.geotoolkit.style.MutableStyle;
import org.geotoolkit.style.StyleConstants;
import static org.junit.Assert.*;
import org.junit.Test;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.filter.FilterFactory2;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.operation.TransformException;
import org.opengis.style.Graphic;
import org.opengis.style.GraphicalSymbol;
import org.opengis.style.PointSymbolizer;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class ProjectedGeometryTest extends org.geotoolkit.test.TestBase {
private static final double DELTA = 0.0000000001;
private static final GeometryFactory GF = new GeometryFactory();
private static final DefaultStyleFactory SF = new DefaultStyleFactory();
private static final FilterFactory2 FF = new DefaultFilterFactory2();
/**
* Sanity test.
* If this test fail, don't even bother looking at the others.
*/
@Test
public void testSanity() throws Exception {
final Polygon poly = GF.createPolygon(new Coordinate[]{
new Coordinate( 0, 0),
new Coordinate( 0, 10),
new Coordinate(20, 10),
new Coordinate(20, 0),
new Coordinate( 0, 0)
});
final ProjectedGeometry pg = createProjectedGeometry(poly,
new Dimension(360, 180),
new AffineTransform(1, 0, 0, -1, +180, 90));
testArray(pg.getObjectiveGeometryJTS(),
GF.createPolygon(new Coordinate[]{
new Coordinate( 0, 0),
new Coordinate( 0, 10),
new Coordinate(20, 10),
new Coordinate(20, 0),
new Coordinate( 0, 0)
}));
testArray(pg.getObjectiveShape(),
createPath(new int[][]{
{SEG_MOVETO, 0, 0},
{SEG_LINETO, 0, 10},
{SEG_LINETO, 20, 10},
{SEG_LINETO, 20, 0},
{SEG_CLOSE}
}));
testArray(pg.getDisplayGeometryJTS(),
GF.createPolygon(new Coordinate[]{
new Coordinate(180, 90),
new Coordinate(180, 80),
new Coordinate(200, 80),
new Coordinate(200, 90),
new Coordinate(180, 90)
}));
testArray(pg.getDisplayShape(),
createPath(new int[][]{
{SEG_MOVETO, 180, 90},
{SEG_LINETO, 180, 80},
{SEG_LINETO, 200, 80},
{SEG_LINETO, 200, 90},
{SEG_CLOSE}
}));
}
/**
* Test display shape clipped
*/
@Test
public void testClipping() throws Exception {
final Polygon poly = GF.createPolygon(new Coordinate[]{
new Coordinate( 0, 0),
new Coordinate( 0, 10),
new Coordinate(20, 10),
new Coordinate(20, 0),
new Coordinate( 0, 0)
});
//we make the geometry cross the left canvas bounds
//we reduce image width to avoid repetition
final ProjectedGeometry pg = createProjectedGeometry(poly,
new Dimension(100, 180),
new AffineTransform(1, 0, 0, -1, -10, +90));
testArray(pg.getObjectiveGeometryJTS(),
GF.createPolygon(new Coordinate[]{
new Coordinate( 0, 0),
new Coordinate( 0, 10),
new Coordinate(20, 10),
new Coordinate(20, 0),
new Coordinate( 0, 0)
}));
testArray(pg.getObjectiveShape(),
createPath(new int[][]{
{SEG_MOVETO, 0, 0},
{SEG_LINETO, 0, 10},
{SEG_LINETO, 20, 10},
{SEG_LINETO, 20, 0},
{SEG_CLOSE}
}));
testArray(pg.getDisplayGeometryJTS(),
GF.createPolygon(new Coordinate[]{
new Coordinate(-10, 90),
new Coordinate(-10, 80),
new Coordinate( 10, 80),
new Coordinate( 10, 90),
new Coordinate(-10, 90)
}));
//the display shape should have been clipped on x=0
testArray(pg.getDisplayShape(),
createPath(new int[][]{
{SEG_MOVETO, 0-10, 90},
{SEG_LINETO, 0-10, 80},
{SEG_LINETO, 10, 80},
{SEG_LINETO, 10, 90},
{SEG_LINETO, 0-10, 90},
{SEG_CLOSE}
}));
}
private void testArray(Geometry[] candidate, Geometry ... expected){
assertEquals(expected.length, candidate.length);
for(int i=0;i<candidate.length;i++){
assertTrue(candidate[i].equalsExact(expected[i]));
}
}
private void testArray(Shape[] candidate, Shape ... expected){
assertEquals(expected.length, candidate.length);
for(int i=0;i<candidate.length;i++){
PathIterator candidateIte = candidate[i].getPathIterator(new AffineTransform());
PathIterator expectedIte = expected[i].getPathIterator(new AffineTransform());
testPathIterator(candidateIte, expectedIte);
}
}
private void testPathIterator(PathIterator candidate, PathIterator expected){
while(true){
boolean done1 = candidate.isDone();
boolean done2 = expected.isDone();
if(done1 && done2){
//ok same path
return;
}else if(!done1 && !done2){
assertEquals(candidate.getWindingRule(), expected.getWindingRule());
final double[] candidateValues = new double[6];
final double[] expectedValues = new double[6];
final int candidateType = candidate.currentSegment(candidateValues);
final int expectedType = expected.currentSegment(expectedValues);
assertEquals(expectedType, candidateType);
assertArrayEquals(expectedValues, candidateValues, DELTA);
}else{
fail("Path iterator do not have the same number of iteration.");
}
candidate.next();
expected.next();
}
}
private static ProjectedGeometry createProjectedGeometry(Geometry geometry, Dimension canvasBounds, AffineTransform objToDisp) throws NoninvertibleTransformException, TransformException {
final int canvasWidth = canvasBounds.width;
final int canvasHeight = canvasBounds.height;
//build a maplayer
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
ftb.setName("test");
ftb.addAttribute(Geometry.class).setName("geom").setCRS(CommonCRS.WGS84.normalizedGeographic());
final FeatureType type = ftb.build();
final Feature feature = type.newInstance();
JTS.setCRS(geometry, CommonCRS.WGS84.normalizedGeographic());
feature.setPropertyValue("geom",geometry);
final FeatureCollection col = FeatureStoreUtilities.collection(feature);
final List<GraphicalSymbol> symbols = new ArrayList<>();
symbols.add(SF.mark(StyleConstants.MARK_SQUARE, SF.fill(Color.BLACK), SF.stroke(Color.BLACK, 0)));
final Graphic graphic = SF.graphic(symbols, StyleConstants.LITERAL_ONE_FLOAT, FF.literal(2), StyleConstants.LITERAL_ZERO_FLOAT, null, null);
final PointSymbolizer ps = SF.pointSymbolizer(graphic, null);
final MutableStyle style = SF.style(ps);
final MapLayer layer = MapBuilder.createFeatureLayer(col, style);
//build a rendering canvas
final J2DCanvasBuffered canvas = new J2DCanvasBuffered(CommonCRS.WGS84.normalizedGeographic(), new Dimension(canvasWidth, canvasHeight));
canvas.applyTransform(objToDisp);
final StatelessContextParams params = new StatelessContextParams(canvas, layer);
final RenderingContext2D context = new RenderingContext2D(canvas);
canvas.prepareContext(context, new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB).createGraphics(),
new Rectangle(0, 0, canvasWidth, canvasHeight));
params.update(context);
final ProjectedGeometry pg = new ProjectedGeometry(params);
pg.setDataGeometry(geometry, CommonCRS.WGS84.normalizedGeographic());
Envelope env = canvas.getVisibleEnvelope();
System.out.println(env.getMinimum(0)+" "+env.getMaximum(0));
System.out.println(env.getMinimum(1)+" "+env.getMaximum(1));
return pg;
}
private static GeneralPath createPath(int[][] steps){
final GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
for(int i=0;i<steps.length;i++){
int[] step = steps[i];
if(step[0]==SEG_MOVETO){
path.moveTo(step[1], step[2]);
}else if(step[0]==SEG_LINETO){
path.lineTo(step[1], step[2]);
}else if(step[0]==SEG_CLOSE){
path.closePath();
}else{
throw new IllegalArgumentException("Unsupported step type : "+step[0]);
}
}
return path;
}
}