/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-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.service;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.style.StyleConstants;
import org.geotoolkit.map.CoverageMapLayer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.measure.Unit;
import org.apache.sis.feature.builder.AttributeRole;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.opengis.filter.expression.Expression;
import org.opengis.style.ChannelSelection;
import org.opengis.style.ColorMap;
import org.opengis.style.ContrastEnhancement;
import org.opengis.style.Description;
import org.opengis.style.OverlapBehavior;
import org.opengis.style.RasterSymbolizer;
import org.opengis.style.ShadedRelief;
import org.opengis.style.Symbolizer;
import org.opengis.filter.FilterFactory;
import org.opengis.style.Fill;
import org.opengis.style.Graphic;
import org.opengis.style.GraphicalSymbol;
import org.opengis.style.Mark;
import org.opengis.style.PointSymbolizer;
import org.opengis.style.Stroke;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.measure.Units;
import org.geotoolkit.coverage.CoverageStack;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.grid.GridCoverageBuilder;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.data.FeatureStoreUtilities;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureWriter;
import org.geotoolkit.display.canvas.control.StopOnErrorMonitor;
import org.geotoolkit.display.PortrayalException;
import org.geotoolkit.display2d.GO2Hints;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.map.FeatureMapLayer;
import org.geotoolkit.map.MapBuilder;
import org.geotoolkit.map.MapContext;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CRS;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.referencing.ReferencingUtilities;
import org.geotoolkit.style.DefaultStyleFactory;
import org.geotoolkit.style.MutableStyle;
import org.geotoolkit.style.MutableStyleFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.geometry.Envelope;
import org.opengis.util.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import static org.junit.Assert.*;
import static org.geotoolkit.style.StyleConstants.*;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.filter.Filter;
/**
* Testing portrayal service.
*
* @author Johann Sorel (Geomatys)
*/
public class PortrayalServiceTest extends org.geotoolkit.test.TestBase {
private static final double EPS = 0.000000001d;
private static final FilterFactory FF = FactoryFinder.getFilterFactory(null);
private static final GeometryFactory GF = new GeometryFactory();
private static final GridCoverageBuilder GCF = new GridCoverageBuilder();
private static final MutableStyleFactory SF = new DefaultStyleFactory();
private final List<FeatureCollection> featureColls = new ArrayList<>();
private final List<GridCoverage2D> coverages = new ArrayList<>();
private final List<Envelope> envelopes = new ArrayList<>();
private final List<Date[]> dates = new ArrayList<>();
private final List<Double[]> elevations = new ArrayList<>();
private final Coverage coverage4D;
public PortrayalServiceTest() throws Exception {
// create the feature collection for tests -----------------------------
final FeatureTypeBuilder sftb = new FeatureTypeBuilder();
sftb.setName("test");
sftb.addAttribute(Point.class).setName("geom").setCRS(CommonCRS.WGS84.normalizedGeographic()).addRole(AttributeRole.DEFAULT_GEOMETRY);
sftb.addAttribute(String.class).setName("att1");
sftb.addAttribute(Double.class).setName("att2");
final FeatureType sft = sftb.build();
FeatureCollection col = FeatureStoreUtilities.collection("id", sft);
final FeatureWriter writer = col.getSession().getFeatureStore().getFeatureWriter(
QueryBuilder.filtered(sft.getName().toString(),Filter.EXCLUDE));
Feature sf = writer.next();
sf.setPropertyValue("geom", GF.createPoint(new Coordinate(0, 0)));
sf.setPropertyValue("att1", "value1");
writer.write();
sf = writer.next();
sf.setPropertyValue("geom", GF.createPoint(new Coordinate(-180, -90)));
sf.setPropertyValue("att1", "value1");
writer.write();
sf = writer.next();
sf.setPropertyValue("geom", GF.createPoint(new Coordinate(-180, 90)));
sf.setPropertyValue("att1", "value1");
writer.write();
sf = writer.next();
sf.setPropertyValue("geom", GF.createPoint(new Coordinate(180, -90)));
sf.setPropertyValue("att1", "value1");
writer.write();
sf = writer.next();
sf.setPropertyValue("geom", GF.createPoint(new Coordinate(180, -90)));
sf.setPropertyValue("att1", "value1");
writer.write();
writer.close();
featureColls.add(col);
//create a serie of envelopes for tests --------------------------------
GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic());
env.setRange(0, -90, 90);
env.setRange(1, -180, 180);
envelopes.add(env);
env = new GeneralEnvelope(CommonCRS.WGS84.geographic());
env.setRange(0, -12, 31);
env.setRange(1, -5, 46);
envelopes.add(env);
env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
env.setRange(0, -180, 180);
env.setRange(1, -90, 90);
envelopes.add(env);
env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
env.setRange(0, -5, 46);
env.setRange(1, -12, 31);
envelopes.add(env);
env = new GeneralEnvelope(CRS.forCode("EPSG:3395"));
env.setRange(0, -1200000, 3100000);
env.setRange(1, -500000, 4600000);
envelopes.add(env);
//create a serie of date ranges ----------------------------------------
dates.add(new Date[]{new Date(1000),new Date(15000)});
dates.add(new Date[]{null, new Date(15000)});
dates.add(new Date[]{new Date(1000),null});
dates.add(new Date[]{null, null});
//create a serie of elevation ranges -----------------------------------
elevations.add(new Double[]{-15d, 50d});
elevations.add(new Double[]{null, 50d});
elevations.add(new Double[]{-15d, null});
elevations.add(new Double[]{null, null});
//create some coverages ------------------------------------------------
env = new GeneralEnvelope(CRS.forCode("EPSG:32738"));
env.setRange(0, 695035, 795035);
env.setRange(1, 7545535, 7645535);
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setColor(Color.RED);
g.fill(new Rectangle(0, 0, 100, 100));
GCF.reset();
GCF.setEnvelope(env);
GCF.setRenderedImage(img);
GridCoverage2D coverage = GCF.getGridCoverage2D();
coverages.add(coverage);
env = new GeneralEnvelope(CommonCRS.WGS84.geographic());
env.setRange(0, -10, 25);
env.setRange(1, -56, -21);
img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
g = img.createGraphics();
g.setColor(Color.RED);
g.fill(new Rectangle(0, 0, 100, 100));
GCF.reset();
GCF.setEnvelope(env);
GCF.setRenderedImage(img);
coverage = GCF.getGridCoverage2D();
coverages.add(coverage);
//create some ND coverages ---------------------------------------------
CoordinateReferenceSystem crs = new GeodeticObjectBuilder().addName("4D crs")
.createCompoundCRS(CommonCRS.WGS84.geographic(),
CommonCRS.Vertical.ELLIPSOIDAL.crs(),
CommonCRS.Temporal.JAVA.crs());
List<Coverage> temps = new ArrayList<Coverage>();
for(int i=0; i<10; i++){
final List<Coverage> eles = new ArrayList<Coverage>();
for(int k=0;k<10;k++){
env = new GeneralEnvelope(crs);
env.setRange(0, 0, 10);
env.setRange(1, 0, 10);
env.setRange(2, k, k+1);
env.setRange(3, i, i+1);
img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
GCF.reset();
GCF.setEnvelope(env);
GCF.setRenderedImage(img);
coverage = GCF.getGridCoverage2D();
eles.add(coverage);
}
temps.add(new CoverageStack("3D", eles));
}
coverage4D = new CoverageStack("4D", coverages);
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Test
public void testEnvelopeNotNull() throws NoSuchAuthorityCodeException, FactoryException, PortrayalException {
MapContext context = MapBuilder.createContext(CommonCRS.WGS84.geographic());
GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.geographic());
env.setRange(0, -180, 180);
env.setRange(1, -90, 90);
DefaultPortrayalService.portray(
new CanvasDef(new Dimension(800, 600), null),
new SceneDef(context),
new ViewDef(env));
//CRS can not obtain envelope for this projection. we check that we don't reaise any error.
context = MapBuilder.createContext(CommonCRS.defaultGeographic());
env = new GeneralEnvelope(CommonCRS.defaultGeographic());
env.setRange(0, -180, 180);
env.setRange(1, -90, 90);
DefaultPortrayalService.portray(
new CanvasDef(new Dimension(800, 600), null),
new SceneDef(context),
new ViewDef(env));
}
@Test
public void testFeatureRendering() throws Exception{
for(FeatureCollection col : featureColls){
final MapLayer layer = MapBuilder.createFeatureLayer(col, SF.style(SF.pointSymbolizer()));
testRendering(layer);
}
}
/**
* Test rendering of a coverage inside a feature property.
*/
@Test
public void testCoveragePropertyRendering() throws Exception{
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
ftb.setName("test");
ftb.addAttribute(GridCoverage2D.class).setName("coverage");
final FeatureType ft = ftb.build();
final BufferedImage img = new BufferedImage(90, 90, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g = img.createGraphics();
g.setColor(Color.GREEN);
g.fillRect(0, 0, 90, 90);
g.dispose();
final GridCoverageBuilder gcb = new GridCoverageBuilder();
gcb.setName("propcov");
gcb.setCoordinateReferenceSystem(CommonCRS.WGS84.normalizedGeographic());
gcb.setGridToCRS(1, 0, 0, 1, 0.5, 0.5);
gcb.setRenderedImage(img);
final Feature f = ft.newInstance();
f.setPropertyValue("coverage",gcb.getGridCoverage2D());
final FeatureCollection collection = FeatureStoreUtilities.collection(f);
final String name = "mySymbol";
final Description desc = DEFAULT_DESCRIPTION;
final String geometry = "coverage";
final Unit unit = Units.POINT;
final Expression opacity = LITERAL_ONE_FLOAT;
final ChannelSelection channels = null;
final OverlapBehavior overlap = null;
final ColorMap colormap = null;
final ContrastEnhancement enhance = null;
final ShadedRelief relief = null;
final Symbolizer outline = null;
final RasterSymbolizer symbol = SF.rasterSymbolizer(
name,geometry,desc,unit,opacity,
channels,overlap,colormap,enhance,relief,outline);
final MutableStyle style = SF.style(symbol);
final MapLayer layer = MapBuilder.createFeatureLayer(collection, style);
final MapContext context = MapBuilder.createContext();
context.layers().add(layer);
final CanvasDef cdef = new CanvasDef(new Dimension(360, 180), null);
final SceneDef sdef = new SceneDef(context);
final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
env.setRange(0, -180, +180);
env.setRange(1, -90, +90);
final ViewDef vdef = new ViewDef(env);
final BufferedImage result = DefaultPortrayalService.portray(cdef, sdef, vdef);
final Raster raster = result.getData();
final int[] pixel = new int[4];
final int[] trans = new int[]{0,0,0,0};
final int[] green = new int[]{0,255,0,255};
assertNotNull(result);
raster.getPixel(0, 0, pixel);
assertArrayEquals(trans, pixel);
raster.getPixel(179, 45, pixel);
assertArrayEquals(trans, pixel);
raster.getPixel(181, 45, pixel);
assertArrayEquals(green, pixel);
}
@Test
public void testCoverageRendering() throws Exception{
for(GridCoverage2D col : coverages){
final MapLayer layer = MapBuilder.createCoverageLayer(col, SF.style(SF.rasterSymbolizer()), "cov");
testRendering(layer);
}
}
// @Test
// public void testCoverageNDRendering() throws Exception{
// //todo
// }
@Test
public void testLongitudeFirst() throws Exception{
final int[] pixel = new int[4];
final int[] red = new int[]{255,0,0,255};
final int[] white = new int[]{255,255,255,255};
final Hints hints = new Hints();
hints.put(GO2Hints.KEY_COLOR_MODEL, ColorModel.getRGBdefault());
//create a map context with a layer that will cover the entire area we will ask for
final GeneralEnvelope covenv = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
covenv.setRange(0, -180, 180);
covenv.setRange(1, -90, 90);
final BufferedImage img = new BufferedImage(360, 180, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g = img.createGraphics();
g.setColor(Color.RED);
g.fill(new Rectangle(0, 0, 360, 180));
final GridCoverageBuilder gcb = new GridCoverageBuilder();
gcb.setEnvelope(covenv);
gcb.setRenderedImage(img);
final GridCoverage2D coverage = gcb.getGridCoverage2D();
final MapLayer layer = MapBuilder.createCoverageLayer(coverage, SF.style(SF.rasterSymbolizer()), "");
final MapContext context = MapBuilder.createContext();
context.layers().add(layer);
//sanity test, image should be a red vertical band in the middle
final CoordinateReferenceSystem epsg4326 = CommonCRS.WGS84.geographic();
GeneralEnvelope env = new GeneralEnvelope(epsg4326);
env.setRange(0, -180, 180);
env.setRange(1, -180, 180);
BufferedImage buffer = DefaultPortrayalService.portray(
new CanvasDef(new Dimension(360, 360), Color.WHITE),
new SceneDef(context, hints),
new ViewDef(env));
//ImageIO.write(buffer, "png", new File("sanity.png"));
assertEquals(360,buffer.getWidth());
assertEquals(360,buffer.getHeight());
WritableRaster raster = buffer.getRaster();
raster.getPixel(0, 0, pixel); assertArrayEquals(white, pixel);
raster.getPixel(359, 0, pixel); assertArrayEquals(white, pixel);
raster.getPixel(359, 359, pixel); assertArrayEquals(white, pixel);
raster.getPixel(0, 359, pixel); assertArrayEquals(white, pixel);
raster.getPixel(180, 0, pixel); assertArrayEquals(red, pixel);
raster.getPixel(180, 359, pixel); assertArrayEquals(red, pixel);
raster.getPixel(0, 180, pixel); assertArrayEquals(white, pixel);
raster.getPixel(359, 180, pixel); assertArrayEquals(white, pixel);
//east=horizontal test, image should be a red horizontal band in the middle
buffer = DefaultPortrayalService.portray(
new CanvasDef(new Dimension(360, 360), Color.WHITE),
new SceneDef(context, hints),
new ViewDef(env).setLongitudeFirst());
//ImageIO.write(buffer, "png", new File("flip.png"));
assertEquals(360,buffer.getWidth());
assertEquals(360,buffer.getHeight());
raster = buffer.getRaster();
raster.getPixel(0, 0, pixel); assertArrayEquals(white, pixel);
raster.getPixel(359, 0, pixel); assertArrayEquals(white, pixel);
raster.getPixel(359, 359, pixel); assertArrayEquals(white, pixel);
raster.getPixel(0, 359, pixel); assertArrayEquals(white, pixel);
raster.getPixel(180, 0, pixel); assertArrayEquals(white, pixel);
raster.getPixel(180, 359, pixel); assertArrayEquals(white, pixel);
raster.getPixel(0, 180, pixel); assertArrayEquals(red, pixel);
raster.getPixel(359, 180, pixel); assertArrayEquals(red, pixel);
}
/**
* Test the CoverageReader view of a scene.
*/
@Test
public void testPortrayalCoverageReader() throws CoverageStoreException{
//create a test coverage
final BufferedImage img = new BufferedImage(360, 180, BufferedImage.TYPE_INT_ARGB);
final Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.GREEN);
g2d.fillRect(0, 0, 360, 180);
final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
env.setRange(0, -180, 180);
env.setRange(1, -90, 90);
final GridCoverageBuilder gcb = new GridCoverageBuilder();
gcb.setEnvelope(env);
gcb.setRenderedImage(img);
final GridCoverage2D coverage = gcb.getGridCoverage2D();
//display it
final MapContext context = MapBuilder.createContext();
final CoverageMapLayer cl = MapBuilder.createCoverageLayer(
coverage, SF.style(StyleConstants.DEFAULT_RASTER_SYMBOLIZER), "coverage");
context.layers().add(cl);
final SceneDef sceneDef = new SceneDef(context);
final GridCoverageReader reader = DefaultPortrayalService.asCoverageReader(sceneDef);
assertEquals(1, reader.getCoverageNames().size());
final GridGeometry gridGeom = reader.getGridGeometry(0);
assertNotNull(gridGeom);
final GridCoverage2D result = (GridCoverage2D) reader.read(0, null);
final RenderedImage image = result.getRenderedImage();
assertEquals(1000, image.getWidth());
}
/**
* Test that a large graphic outside the map area is still rendered.
*
*/
@Test
public void testMarginRendering() throws Exception{
final List<GraphicalSymbol> symbols = new ArrayList<>();
final Stroke stroke = SF.stroke(Color.BLACK, 0);
final Fill fill = SF.fill(Color.BLACK);
final Mark mark = SF.mark(MARK_CIRCLE, fill, stroke);
symbols.add(mark);
final Graphic graphic = SF.graphic(symbols, LITERAL_ONE_FLOAT, FF.literal(8), LITERAL_ONE_FLOAT, DEFAULT_ANCHOR_POINT, DEFAULT_DISPLACEMENT);
final PointSymbolizer symbolizer = SF.pointSymbolizer("mySymbol",(String)null,DEFAULT_DESCRIPTION, Units.POINT, graphic);
final CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
ftb.setName("test");
ftb.addAttribute(Point.class).setName("geom").setCRS(crs).addRole(AttributeRole.DEFAULT_GEOMETRY);
final FeatureType ft = ftb.build();
final Feature feature = ft.newInstance();
final Point pt = GF.createPoint(new Coordinate(12, 5));
JTS.setCRS(pt, crs);
feature.setPropertyValue("geom", pt);
final FeatureCollection col = FeatureStoreUtilities.collection(feature);
final FeatureMapLayer layer = MapBuilder.createFeatureLayer(col,SF.style(symbolizer));
final MapContext context = MapBuilder.createContext();
context.layers().add(layer);
final GeneralEnvelope env = new GeneralEnvelope(crs);
env.setRange(0, 0, 10);
env.setRange(1, 0, 10);
final CanvasDef cdef = new CanvasDef(new Dimension(10, 10), Color.WHITE);
final SceneDef sdef = new SceneDef(context);
final ViewDef vdef = new ViewDef(env);
final BufferedImage img = DefaultPortrayalService.portray(cdef, sdef, vdef);
assertEquals(Color.BLACK.getRGB(), img.getRGB(9, 5));
assertEquals(Color.BLACK.getRGB(), img.getRGB(8, 5));
assertEquals(Color.WHITE.getRGB(), img.getRGB(7, 5));
}
private void testRendering(final MapLayer layer) throws TransformException, PortrayalException{
final StopOnErrorMonitor monitor = new StopOnErrorMonitor();
final MapContext context = MapBuilder.createContext(CommonCRS.WGS84.normalizedGeographic());
context.layers().add(layer);
assertEquals(1, context.layers().size());
for(final Envelope env : envelopes){
for(Date[] drange : dates){
for(Double[] erange : elevations){
final Envelope cenv = ReferencingUtilities.combine(env, drange, erange);
final BufferedImage img = DefaultPortrayalService.portray(
new CanvasDef(new Dimension(800, 600), null),
new SceneDef(context),
new ViewDef(cenv,0,monitor));
assertNull(monitor.getLastException());
assertNotNull(img);
}
}
}
}
}