This is an automated email from the ASF dual-hosted git repository. jsorel pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 256182d4bf feat(Renderer): reorganize renderer, cleaning code, add doc, start test cases 256182d4bf is described below commit 256182d4bf4c1c60733bccea50676532b812cfaf Author: jsorel <johann.so...@geomatys.com> AuthorDate: Mon Jan 29 17:00:28 2024 +0100 feat(Renderer): reorganize renderer, cleaning code, add doc, start test cases --- .../org.apache.sis.feature/main/module-info.java | 1 + .../main/module-info.java | 13 ++ .../main/org/apache/sis/map/Presentation.java | 2 + .../apache/sis/map/service/GraphicsPortrayer.java | 201 +++++++++++++++++++ .../sis/map/service/LineSymbolizerPainter.java | 39 ---- .../org/apache/sis/map/service/MapPortrayer.java | 148 -------------- .../sis/map/service/PointSymbolizerPainter.java | 39 ---- .../sis/map/service/RasterSymbolizerPainter.java | 39 ---- ...rayalException.java => RenderingException.java} | 14 +- .../main/org/apache/sis/map/service/SEPainter.java | 113 ----------- ...{PolygonSymbolizerPainter.java => Scene2D.java} | 31 ++- .../org/apache/sis/map/service/StylePainter.java | 10 +- .../map/service/SymbolizerPainterDescription.java | 43 ----- .../sis/map/service/TextSymbolizerPainter.java | 39 ---- .../apache/sis/map/service/se1/LineToScene2D.java | 120 ++++++++++++ .../apache/sis/map/service/se1/PointToScene2D.java | 73 +++++++ .../sis/map/service/se1/PolygonToScene2D.java | 73 +++++++ .../sis/map/service/se1/PresentationToScene2D.java | 214 +++++++++++++++++++++ .../sis/map/service/se1/RasterToScene2D.java | 111 +++++++++++ .../apache/sis/map/service/se1/RenderedShape.java | 112 +++++++++++ .../org/apache/sis/map/service/se1/SEPainter.java | 88 +++++++++ .../SymbolizerCache.java} | 21 +- .../sis/map/service/se1/SymbolizerToScene2D.java | 192 ++++++++++++++++++ .../apache/sis/map/service/se1/TextToScene2D.java | 73 +++++++ .../sis/map/service/GraphicsPortrayerTest.java | 156 +++++++++++++++ 25 files changed, 1477 insertions(+), 488 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/module-info.java b/endorsed/src/org.apache.sis.feature/main/module-info.java index 9badc7e6ba..d850d06278 100644 --- a/endorsed/src/org.apache.sis.feature/main/module-info.java +++ b/endorsed/src/org.apache.sis.feature/main/module-info.java @@ -70,6 +70,7 @@ module org.apache.sis.feature { org.apache.sis.gui; // In the "optional" sub-project. exports org.apache.sis.geometry.wrapper.jts to + org.apache.sis.portrayal.map, // In the "incubator" sub-project. org.apache.sis.cql; // In the "incubator" sub-project. exports org.apache.sis.coverage.internal to diff --git a/incubator/src/org.apache.sis.portrayal.map/main/module-info.java b/incubator/src/org.apache.sis.portrayal.map/main/module-info.java index 8254cbf98d..128b11c4f8 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/module-info.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/module-info.java @@ -25,4 +25,17 @@ module org.apache.sis.portrayal.map { requires static org.locationtech.jts; exports org.apache.sis.map; + exports org.apache.sis.map.service; + + uses org.apache.sis.map.service.StylePainter; + uses org.apache.sis.map.service.se1.SymbolizerToScene2D.Spi; + + provides org.apache.sis.map.service.StylePainter + with org.apache.sis.map.service.se1.SEPainter; + provides org.apache.sis.map.service.se1.SymbolizerToScene2D.Spi + with org.apache.sis.map.service.se1.PointToScene2D.Spi, + org.apache.sis.map.service.se1.LineToScene2D.Spi, + org.apache.sis.map.service.se1.PolygonToScene2D.Spi, + org.apache.sis.map.service.se1.TextToScene2D.Spi, + org.apache.sis.map.service.se1.RasterToScene2D.Spi; } diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java index 9b544aec0f..040173e2f2 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/Presentation.java @@ -43,6 +43,8 @@ import org.apache.sis.storage.Resource; * * @todo Consider renaming as {@code Graphic} for emphasis that this is a graphical representation of something. * This would be consistent with legacy GO-1 specification (even if retired, it still have worthy material). + * @todo Consider renaming as {@code Instruction} for emphasis that this is a rendering operation. + * IHO S-52 and S-100 specification. S-100 has been build guided by ISO-19117. * * @author Johann Sorel (Geomatys) */ diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java new file mode 100644 index 0000000000..feb4597426 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/GraphicsPortrayer.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.stream.Stream; +import org.apache.sis.coverage.grid.GridCoverage2D; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.map.MapItem; +import org.apache.sis.map.MapLayer; +import org.apache.sis.map.MapLayers; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPortrayer; +import org.apache.sis.style.Style; +import org.apache.sis.util.ArgumentChecks; + +/** + * Produce rendered image of styled resources. + * + * @author Johann Sorel (Geomatys) + */ +public final class GraphicsPortrayer { + + /** + * Reference all painters. + */ + private static final Map<Class<? extends Style>,StylePainter> PAINTERS = new HashMap<>(); + static { + final ServiceLoader<StylePainter> loader = ServiceLoader.load(StylePainter.class, StylePainter.class.getClassLoader()); + for (StylePainter painter : loader) { + PAINTERS.put(painter.getStyleClass(), painter); + } + } + + private Graphics2D graphics; + private GridGeometry domain; + private BufferedImage image; + + public GraphicsPortrayer(){} + + /** + * Set the output image to render into. + * + * @param image not null + * @return this portrayer + */ + public GraphicsPortrayer setCanvas(BufferedImage image) { + ArgumentChecks.ensureNonNull("image", image); + this.image = image; + this.graphics = image.createGraphics(); + return this; + } + + /** + * Set the output Graphics2D to render into. + * + * @param graphics not null + * @return this portrayer + */ + public GraphicsPortrayer setCanvas(Graphics2D graphics) { + ArgumentChecks.ensureNonNull("graphics", graphics); + this.graphics = graphics; + return this; + } + + /** + * Set the GridGeometry which is rendered. + * + * @param domain not null, lower extent coordinates must be on 0. + */ + public GraphicsPortrayer setDomain(GridGeometry domain) { + ArgumentChecks.ensureNonNull("domain", domain); + long[] low = domain.getExtent().getLow().getCoordinateValues(); + for (long l : low) { + ArgumentChecks.ensureValidIndex(1, (int) l); + } + + this.domain = domain; + return this; + } + + /** + * + * @return created image, may be null + */ + public BufferedImage getImage() { + return image; + } + + /** + * Get the rendering image as a coverage. + * @return coverage, never null + */ + public GridCoverage2D toCoverage() { + return new GridCoverage2D(domain, null, getImage()); + } + + /** + * Validate parameters and create image if needed. + */ + private Scene2D init() { + ArgumentChecks.ensureNonNull("domain", domain); + if (image == null) { + setCanvas(new BufferedImage( + (int) domain.getExtent().getSize(0), + (int) domain.getExtent().getSize(1), + BufferedImage.TYPE_INT_ARGB)); + } + + return new Scene2D(domain, graphics); + } + + /** + * Paint given map. + * + * An image is created if not defined. + * + * @param map to paint, not null + * @throws IllegalArgumentException if canvas is not property configured + * @throws RenderingException if a rendering procedure fails. + */ + public synchronized GraphicsPortrayer portray(MapItem map) throws RenderingException { + portray(init(), map); + return this; + } + + private void portray(Scene2D scene, MapItem map) throws RenderingException { + if (map == null || !map.isVisible()) return; + if (map instanceof MapLayer) { + portray(scene, (MapLayer) map); + } else if (map instanceof MapLayers) { + final MapLayers layers = (MapLayers) map; + for (MapItem item : layers.getComponents()) { + portray(scene, item); + } + } + } + + private void portray(Scene2D scene, MapLayer layer) throws RenderingException { + final Style style = layer.getStyle(); + if (style == null) return; + final StylePainter painter = PAINTERS.get(style.getClass()); + if (painter == null) return; + painter.paint(scene, layer); + } + + /** + * Compute visual intersection of given map. + * + * @param mapItem to be processed, not null. + * @param mask intersection mask, not null. + * @return intersecting stream of presentations. + * @throws IllegalArgumentException if canvas is not property configured + * @throws RenderingException if a rendering procedure fails. + */ + public Stream<Presentation> intersects(MapItem mapItem, Shape mask) throws RenderingException { + return intersects(init(), mapItem, mask); + } + + private Stream<Presentation> intersects(Scene2D scene, MapItem map, Shape mask) throws RenderingException{ + Stream<Presentation> results = Stream.empty(); + if (map == null || !map.isVisible()) return results; + if (map instanceof MapLayer) { + results = Stream.concat(results, intersects(scene, (MapLayer) map, mask)); + } else if (map instanceof MapLayers) { + final MapLayers layers = (MapLayers) map; + for (MapItem item : layers.getComponents()) { + results = Stream.concat(results, intersects(scene, item, mask)); + } + } + return results; + } + + private Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask) throws RenderingException { + final Style style = layer.getStyle(); + if (style == null) return Stream.empty(); + final StylePainter painter = PAINTERS.get(style.getClass()); + if (painter == null) return Stream.empty(); + return painter.intersects(scene, layer, mask); + } + +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/LineSymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/LineSymbolizerPainter.java deleted file mode 100644 index d5aa93c596..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/LineSymbolizerPainter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; - -/** - * - * @author Johann Sorel (Geomatys) - */ -final class LineSymbolizerPainter extends SymbolizerPainter { - - public LineSymbolizerPainter(Symbolizer<?> symbolizer) { - super(symbolizer); - } - - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - throw new UnsupportedOperationException("Not supported yet."); - } - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/MapPortrayer.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/MapPortrayer.java deleted file mode 100644 index 9555b133e4..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/MapPortrayer.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; -import java.util.ServiceLoader; -import org.apache.sis.coverage.grid.GridExtent; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.coverage.grid.GridOrientation; -import org.apache.sis.map.MapItem; -import org.apache.sis.map.MapLayer; -import org.apache.sis.map.MapLayers; -import org.apache.sis.referencing.CRS; -import org.apache.sis.referencing.CommonCRS; -import org.apache.sis.style.Style; -import org.apache.sis.util.ArgumentChecks; - -/** - * Draft. - * Class used to render a map using Java2D API. - * - * @author Johann Sorel (Geomatys) - */ -public final class MapPortrayer { - - /** - * Reference all painters. - */ - private static final Map<Class<? extends Style>,StylePainter> PAINTERS = new HashMap<>(); - - private GridGeometry grid = new GridGeometry(new GridExtent(360, 180), CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), GridOrientation.REFLECTION_Y); - private BufferedImage image = new BufferedImage((int)grid.getExtent().getSize(0), (int)grid.getExtent().getSize(0), BufferedImage.TYPE_INT_ARGB); - private Graphics2D graphics = image.createGraphics(); - private boolean valid = false; - - static { - final ServiceLoader<StylePainter> loader = ServiceLoader.load(StylePainter.class, StylePainter.class.getClassLoader()); - for (StylePainter painter : loader) { - PAINTERS.put(painter.getStyleClass(), painter); - } - } - - public MapPortrayer() { - } - - /** - * Update canvas. - * - * @param image image to write into, if null the graphics must be defined - * @param graphics graphics to paint into, if null the image must be defined - */ - public synchronized void setCanvas(BufferedImage image, Graphics2D graphics) { - this.image = image; - if (graphics == null) { - graphics = image.createGraphics(); - graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - } - this.graphics = graphics; - valid = false; - } - - /** - * Update canvas area. - * - * @param grid GridGeometry of the rendered area - */ - public synchronized void setGridGeometry(GridGeometry grid) { - ArgumentChecks.ensureNonNull("grid", grid); - this.grid = grid; - valid = false; - } - - /** - * @return currently used Graphics2D, can be null - */ - public synchronized Graphics2D getGraphics() { - return graphics; - } - - /** - * @return currently used Image, can be null - */ - public BufferedImage getImage() { - return image; - } - - /** - * Paint given map. - * - * An image is created if not defined. - * - * @param map to paint, not null - * @throws PortrayalException - * @throws IllegalArgumentException if canvas is not property configured - */ - public synchronized void portray(MapItem map) throws PortrayalException { - - if (!valid) { - //check canvas configuration - if (grid == null) { - throw new IllegalArgumentException("Grid geometry has not been initialize"); - } - if (graphics == null) { - - } - valid = true; - } - - //render given map - if (map == null || !map.isVisible()) return; - if (map instanceof MapLayer) { - portray((MapLayer) map); - } else if (map instanceof MapLayers) { - final MapLayers layers = (MapLayers) map; - for (MapItem item : layers.getComponents()) { - portray(item); - } - } - } - - private void portray(MapLayer layer) throws PortrayalException { - final Style style = layer.getStyle(); - if (style == null) return; - final StylePainter painter = PAINTERS.get(style.getClass()); - if (painter == null) return; - painter.paint(graphics, image, grid, layer); - } - - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PointSymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PointSymbolizerPainter.java deleted file mode 100644 index 081913a433..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PointSymbolizerPainter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; - -/** - * - * @author Johann Sorel (Geomatys) - */ -final class PointSymbolizerPainter extends SymbolizerPainter { - - public PointSymbolizerPainter(Symbolizer<?> symbolizer) { - super(symbolizer); - } - - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - throw new UnsupportedOperationException("Not supported yet."); - } - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RasterSymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RasterSymbolizerPainter.java deleted file mode 100644 index 28778a405c..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RasterSymbolizerPainter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; - -/** - * - * @author Johann Sorel (Geomatys) - */ -final class RasterSymbolizerPainter extends SymbolizerPainter { - - public RasterSymbolizerPainter(Symbolizer<?> symbolizer) { - super(symbolizer); - } - - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - throw new UnsupportedOperationException("Not supported yet."); - } - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PortrayalException.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java similarity index 70% rename from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PortrayalException.java rename to incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java index ca6ecd581b..0257342767 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PortrayalException.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/RenderingException.java @@ -17,9 +17,21 @@ package org.apache.sis.map.service; /** + * Exception that may be thrown by a portraying operation. * * @author Johann Sorel (Geomatys) */ -public class PortrayalException extends Exception { +public final class RenderingException extends Exception { + public RenderingException(String message) { + super(message); + } + + public RenderingException(Throwable cause) { + super(cause); + } + + public RenderingException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SEPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SEPainter.java deleted file mode 100644 index f93f9e23a0..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SEPainter.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.stream.Stream; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.ExceptionPresentation; -import org.apache.sis.map.MapLayer; -import org.apache.sis.map.Presentation; -import org.apache.sis.map.SEPortrayer; -import org.apache.sis.map.SEPresentation; -import org.apache.sis.style.Style; -import org.apache.sis.style.se1.Symbolizer; -import org.apache.sis.style.se1.Symbology; - -/** - * - * @author Johann Sorel (Geomatys) - */ -public final class SEPainter implements StylePainter { - - /** - * Reference all painters. - */ - private static final List<SymbolizerPainterDescription<?>> PAINTERS = new ArrayList<>(); - - static { - final ServiceLoader<SymbolizerPainterDescription> loader = ServiceLoader.load(SymbolizerPainterDescription.class, SymbolizerPainterDescription.class.getClassLoader()); - for (SymbolizerPainterDescription painter : loader) { - PAINTERS.add(painter); - } - } - - private static final SymbolizerPainter NONE = new SymbolizerPainter(null) { - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - } - }; - - @Override - public Class<? extends Style> getStyleClass() { - return Symbology.class; - } - - @Override - public void paint(Graphics2D g, BufferedImage image, GridGeometry gridGeometry, MapLayer layer) { - - final Map<Symbolizer, SymbolizerPainter> subPainters = new HashMap<>(); - - final SEPortrayer p = new SEPortrayer(); - try (Stream<Presentation> presentations = p.present(gridGeometry, layer)) { - - //process presentations in order - final Iterator<Presentation> ite = presentations.iterator(); - while (ite.hasNext()) { - final Presentation presentation = ite.next(); - if (presentation instanceof SEPresentation) { - final SEPresentation sepre = (SEPresentation) presentation; - final Symbolizer<?> symbolizer = sepre.getSymbolizer(); - SymbolizerPainter subPainter = subPainters.get(symbolizer); - - creation: - if (subPainter == null) { - subPainter = NONE; - for (SymbolizerPainterDescription s : PAINTERS) { - if (s.getSymbolizerClass().isInstance(symbolizer)) { - subPainter = s.createRenderer(symbolizer); - break; - } - } - subPainters.put(symbolizer, subPainter); - } - - if (subPainter == NONE) { - continue; - } - - subPainter.paint(g, gridGeometry, layer); - - } else if (presentation instanceof ExceptionPresentation) { - //todo - } else { - //todo - } - } - - } - - } - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PolygonSymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java similarity index 56% rename from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PolygonSymbolizerPainter.java rename to incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java index 4e22be7cba..e034f4467f 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/PolygonSymbolizerPainter.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/Scene2D.java @@ -17,23 +17,38 @@ package org.apache.sis.map.service; import java.awt.Graphics2D; +import java.util.logging.Logger; import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; +import org.apache.sis.util.ArgumentChecks; /** + * Holds the rendering properties. * * @author Johann Sorel (Geomatys) */ -final class PolygonSymbolizerPainter extends SymbolizerPainter { +public final class Scene2D { - public PolygonSymbolizerPainter(Symbolizer<?> symbolizer) { - super(symbolizer); + public static final Logger LOGGER = Logger.getLogger("org.apache.sis.internal.renderer"); + + /** + * The rendering grid geometry. + */ + public final GridGeometry grid; + /** + * Graphics to render into. + * When modified by renderers, it must be reset accordingly. + */ + public final Graphics2D graphics; + + public Scene2D(GridGeometry grid, Graphics2D graphics) { + ArgumentChecks.ensureNonNull("grid", grid); + ArgumentChecks.ensureNonNull("graphics", graphics); + this.grid = grid; + this.graphics = graphics; } - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - throw new UnsupportedOperationException("Not supported yet."); + public Graphics2D getGraphics() { + return graphics; } } diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java index 699a110d85..df01ef9047 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/StylePainter.java @@ -16,10 +16,10 @@ */ package org.apache.sis.map.service; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import org.apache.sis.coverage.grid.GridGeometry; +import java.awt.Shape; +import java.util.stream.Stream; import org.apache.sis.map.MapLayer; +import org.apache.sis.map.Presentation; import org.apache.sis.style.Style; /** @@ -30,6 +30,8 @@ public interface StylePainter { Class<? extends Style> getStyleClass(); - void paint(Graphics2D g, BufferedImage image, GridGeometry gridGeometry, MapLayer layer); + void paint(Scene2D scene, MapLayer layer); + + Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask); } diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainterDescription.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainterDescription.java deleted file mode 100644 index 3022c94347..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainterDescription.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import org.apache.sis.style.se1.Symbolizer; - -/** - * - * @author Johann Sorel (Geomatys) - */ -public interface SymbolizerPainterDescription<T extends Symbolizer> { - - /** - * @return The symbolizer class handle by this renderer. - */ - Class<T> getSymbolizerClass(); - - /** - * Create a renderer fixed for a symbol and a context. - * - * @param symbol : cached symbolizer - * @param context : rendering context - * @return SymbolizerRenderer or null if symbol is never visible. - */ - SymbolizerPainter createRenderer(T symbol); - - //TODO Glyph methods for legend - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/TextSymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/TextSymbolizerPainter.java deleted file mode 100644 index f6aff9a3cd..0000000000 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/TextSymbolizerPainter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; - -/** - * - * @author Johann Sorel (Geomatys) - */ -final class TextSymbolizerPainter extends SymbolizerPainter { - - public TextSymbolizerPainter(Symbolizer<?> symbolizer) { - super(symbolizer); - } - - @Override - public void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer) { - throw new UnsupportedOperationException("Not supported yet."); - } - -} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java new file mode 100644 index 0000000000..e28f4dd8de --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/LineToScene2D.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Shape; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.sis.feature.internal.AttributeConvention; +import org.apache.sis.geometry.wrapper.Geometries; +import org.apache.sis.geometry.wrapper.GeometryWrapper; +import org.apache.sis.geometry.wrapper.jts.JTS; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.style.se1.LineSymbolizer; +import org.locationtech.jts.geom.Geometry; +import org.opengis.feature.Feature; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.CoordinateOperation; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +/** + * Support for LineSymbolizer rendering. + * + * @author Johann Sorel (Geomatys) + */ +public final class LineToScene2D extends SymbolizerToScene2D<LineSymbolizer<?>> { + + private LineToScene2D(Scene2D state, LineSymbolizer<?> symbolizer) { + super(state, symbolizer); + } + + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + visual.paint(state.getGraphics()); + } + } + + @Override + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + return visual.intersects(mask); + } + return false; + } + + private RenderedShape createVisual(SEPresentation presentation) throws RenderingException { + final Feature feature = presentation.getCandidate(); + Object geometry = feature.getPropertyValue(AttributeConvention.GEOMETRY); + + if (geometry instanceof Geometry) { + + final MathTransform gridToCRS = state.grid.getGridToCRS(PixelInCell.CELL_CENTER); + + final GeometryWrapper geomWrap = Geometries.wrap(geometry).get(); + final CoordinateReferenceSystem geomCrs = geomWrap.getCoordinateReferenceSystem(); + + final Geometry jts; + try { + final CoordinateOperation coop = CRS.findOperation(geomCrs, state.grid.getCoordinateReferenceSystem(), null); + final MathTransform geomToGrid = MathTransforms.concatenate(coop.getMathTransform(), gridToCRS.inverse()); + + jts = JTS.transform((Geometry) geometry, geomToGrid); + } catch (FactoryException | TransformException ex) { + throw new RenderingException(ex); + } + + Shape shape = JTS.asShape(jts); + + //TODO geometry world wrap and styling + + RenderedShape rs = new RenderedShape(); + rs.shape = shape; + rs.stroke = new BasicStroke(1); + rs.strokePaint = Color.BLACK; + + return rs; + } + + return null; + } + + public static final class Spi implements SymbolizerToScene2D.Spi<LineSymbolizer> { + + @Override + public Class<LineSymbolizer> getSymbolizerType() { + return LineSymbolizer.class; + } + + @Override + public SymbolizerToScene2D create(Scene2D state, LineSymbolizer symbolizer) throws RenderingException { + return new LineToScene2D(state, symbolizer); + } + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java new file mode 100644 index 0000000000..4faa39b287 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PointToScene2D.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Shape; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.style.se1.PointSymbolizer; + +/** + * Support for PointSymbolizer rendering. + * + * @author Johann Sorel (Geomatys) + */ +public final class PointToScene2D extends SymbolizerToScene2D<PointSymbolizer<?>> { + + private PointToScene2D(Scene2D state, PointSymbolizer<?> symbolizer) { + super(state, symbolizer); + } + + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + visual.paint(state.getGraphics()); + } + } + + @Override + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + return visual.intersects(mask); + } + return false; + } + + private RenderedShape createVisual(SEPresentation presentation) throws RenderingException { + //todo + return null; + } + + public static final class Spi implements SymbolizerToScene2D.Spi<PointSymbolizer> { + + @Override + public Class<PointSymbolizer> getSymbolizerType() { + return PointSymbolizer.class; + } + + @Override + public SymbolizerToScene2D create(Scene2D state, PointSymbolizer symbolizer) throws RenderingException { + return new PointToScene2D(state, symbolizer); + } + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java new file mode 100644 index 0000000000..626424df5d --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PolygonToScene2D.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Shape; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.style.se1.PolygonSymbolizer; + +/** + * Support for PointSymbolizer rendering. + * + * @author Johann Sorel (Geomatys) + */ +public final class PolygonToScene2D extends SymbolizerToScene2D<PolygonSymbolizer<?>> { + + private PolygonToScene2D(Scene2D state, PolygonSymbolizer<?> symbolizer) { + super(state, symbolizer); + } + + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + visual.paint(state.getGraphics()); + } + } + + @Override + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + return visual.intersects(mask); + } + return false; + } + + private RenderedShape createVisual(SEPresentation presentation) throws RenderingException { + //todo + return null; + } + + public static final class Spi implements SymbolizerToScene2D.Spi<PolygonSymbolizer> { + + @Override + public Class<PolygonSymbolizer> getSymbolizerType() { + return PolygonSymbolizer.class; + } + + @Override + public SymbolizerToScene2D create(Scene2D state, PolygonSymbolizer symbolizer) throws RenderingException { + return new PolygonToScene2D(state, symbolizer); + } + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java new file mode 100644 index 0000000000..baa991628c --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/PresentationToScene2D.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.stream.Stream; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.style.se1.Symbolizer; +import org.apache.sis.util.ArgumentChecks; +import org.opengis.geometry.MismatchedDimensionException; +import org.opengis.referencing.operation.NoninvertibleTransformException; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + + +/** + * Generate a 2D scene from canvas and graphic presentations. + * + * @author Johann Sorel (Geomatys) + */ +final class PresentationToScene2D { + + /** + * Flag instance for missing symbolizers converters. + */ + private static final SymbolizerToScene2D<?> NONE = new SymbolizerToScene2D<Symbolizer<?>>(null, null){ + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + throw new UnsupportedOperationException("Should not called."); + } + }; + private static final SymbolizerCache NO_CACHE = new SymbolizerCache(){}; + + private final Scene2D state; + + private final Map<Symbolizer<?>,SymbolizerToScene2D<?>> cache = new HashMap<>(); + private Map<Symbolizer<?>,SymbolizerCache> symbolizerCaches; + + /** + * Prepare parameters for scene creation.The grid geometry will provide the base coordinate system and scale informations. + * All further presentations must have been build with given grid as base. + * + * @param grid, not null + */ + public PresentationToScene2D(GridGeometry grid, Graphics2D graphics) throws FactoryException, MismatchedDimensionException, TransformException { + ArgumentChecks.ensureNonNull("grid", grid); + + state = new Scene2D(grid, graphics); + } + + /** + * Create converter from an existing scene state. + * + * @param state, not null + */ + public PresentationToScene2D(Scene2D state) { + ArgumentChecks.ensureNonNull("state", state); + + this.state = state; + } + + /** + * Define global shared cache map instance. + * + * @param symbolizerCaches + */ + public void setSymbolizerCaches(Map<Symbolizer<?>, SymbolizerCache> symbolizerCaches) { + this.symbolizerCaches = symbolizerCaches; + } + + /** + * Convert and add given presentation to the scene. + * Exceptions will be logged. + * + * @param presentations, not null, will be closed. + */ + public void render(Stream<Presentation> presentations) { + try { + presentations.parallel().forEach(new Consumer<Presentation>() { + @Override + public void accept(Presentation t) { + try { + render(t); + } catch (Exception ex) { + Scene2D.LOGGER.log(Level.INFO, ex.getMessage(), ex); + } + } + }); + } finally { + presentations.close(); + } + } + + private void render(Presentation presentation) throws RenderingException, IOException, NoninvertibleTransformException, TransformException, URISyntaxException, FactoryException, DataStoreException { + //standard presentation types + if (presentation instanceof SEPresentation) { + render((SEPresentation) presentation); + } else { + //unknown type + } + } + + private void render(SEPresentation presentation) throws MismatchedDimensionException, TransformException, FactoryException, DataStoreException, IOException, RenderingException { + final SymbolizerToScene2D<?> sts = getRenderer(presentation.getSymbolizer()); + + if (sts != null) { + sts.paint(presentation, this::render); + } else { + Scene2D.LOGGER.log(Level.INFO, "Unnowned symbolizer {0}", presentation.getSymbolizer().getClass().getName()); + } + } + + /** + * Process given presentations and retain only thos who intersects the requested shape. + * + * @param presentations, not null. + */ + public Stream<Presentation> intersects(Stream<Presentation> presentations, Shape mask) { + + final Consumer<Stream<Presentation>> callback = (Stream<Presentation> t) -> {intersects(t, mask);}; + + return presentations.parallel().filter(new Predicate<Presentation>() { + @Override + public boolean test(Presentation t) { + try { + return intersects(t, mask, callback); + } catch (RenderingException ex) { + Scene2D.LOGGER.log(Level.WARNING, ex.getMessage(), ex); + } + return false; + } + }); + } + + private boolean intersects(Presentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + //standard presentation types + if (presentation instanceof SEPresentation) { + return intersects((SEPresentation) presentation, mask, callback); + } else { + //unknown type + return false; + } + } + + private boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + final SymbolizerToScene2D<?> sts = getRenderer(presentation.getSymbolizer()); + + if (sts != null) { + return sts.intersects(presentation, mask, callback); + } else { + Scene2D.LOGGER.log(Level.INFO, "Unnowned symbolizer {0}", presentation.getSymbolizer().getClass().getName()); + return false; + } + } + + private SymbolizerToScene2D<?> getRenderer(Symbolizer<?> symbolizer) throws RenderingException { + SymbolizerToScene2D<?> sts; + synchronized (cache) { + sts = cache.get(symbolizer); + if (sts == NONE) { + sts = null; + } else if (sts == null) { + sts = SymbolizerToScene2D.create(state, symbolizer); + if (sts == null) sts = NONE; + cache.put(symbolizer, sts); + if (sts == NONE) { + sts = null; + } else if (symbolizerCaches != null) { + // get or create shared cache + SymbolizerCache cache = symbolizerCaches.get(symbolizer); + if (cache == null) { + cache = SymbolizerToScene2D.createCache(symbolizer); + if (cache == null) { + cache = NO_CACHE; + } + symbolizerCaches.put(symbolizer, cache); + } + if (cache != NO_CACHE) { + sts.sharedCache(cache); + } + } + } + } + return sts; + } + +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java new file mode 100644 index 0000000000..4a2560ff01 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RasterToScene2D.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Shape; +import java.awt.image.RenderedImage; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Stream; +import org.apache.sis.coverage.grid.GridCoverage; +import org.apache.sis.coverage.grid.GridCoverageBuilder; +import org.apache.sis.coverage.grid.GridCoverageProcessor; +import org.apache.sis.coverage.grid.GridDerivation; +import org.apache.sis.coverage.grid.GridExtent; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.GridCoverageResource; +import org.apache.sis.storage.NoSuchDataException; +import org.apache.sis.storage.Resource; +import org.apache.sis.style.se1.RasterSymbolizer; +import org.opengis.referencing.operation.TransformException; + +/** + * Support for RasterSymbolizer rendering. + * + * @author Johann Sorel (Geomatys) + */ +public final class RasterToScene2D extends SymbolizerToScene2D<RasterSymbolizer<?>> { + + private RasterToScene2D(Scene2D state, RasterSymbolizer<?> symbolizer) { + super(state, symbolizer); + } + + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + Resource resource = presentation.getResource(); + + if (resource instanceof GridCoverageResource) { + final GridCoverageResource gcr = (GridCoverageResource) resource; + + try { + GridCoverage coverage = gcr.read(state.grid); + + // naive inefficient implementation to be improved + // keep only a 2D slice for rendering + final GridGeometry gridGeometry = coverage.getGridGeometry(); + final GridDerivation sliceGridBuilder = gridGeometry.derive().sliceByRatio(0.5, 0, 1); + final GridExtent intersection = sliceGridBuilder.getIntersection(); + final GridGeometry sliceGrid = sliceGridBuilder.build().selectDimensions(0,1); + final RenderedImage image = coverage.render(intersection); + + final GridCoverageBuilder gcb = new GridCoverageBuilder(); + gcb.setValues(image); + gcb.setDomain(sliceGrid); + gcb.setRanges(coverage.getSampleDimensions()); + final GridCoverage coverage2d = gcb.build(); + + final GridCoverageProcessor gcp = new GridCoverageProcessor(); + final GridCoverage resampled = gcp.resample(coverage2d, state.grid); + + //TODO handle raster symbolizer parameters. + + state.graphics.drawRenderedImage(resampled.render(null), null); + + } catch (NoSuchDataException ex) { + //do nothing + } catch (DataStoreException ex) { + LOGGER.log(Level.WARNING, ex.getMessage(), ex); + } catch (TransformException ex) { + LOGGER.log(Level.WARNING, ex.getMessage(), ex); + } + + } + } + + @Override + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + return false; + } + + public static final class Spi implements SymbolizerToScene2D.Spi<RasterSymbolizer> { + + @Override + public Class<RasterSymbolizer> getSymbolizerType() { + return RasterSymbolizer.class; + } + + @Override + public SymbolizerToScene2D create(Scene2D state, RasterSymbolizer symbolizer) throws RenderingException { + return new RasterToScene2D(state, symbolizer); + } + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java new file mode 100644 index 0000000000..cc8cebed55 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/RenderedShape.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Area; + +/** + * Combine an AWT Shape and it's rendering options. + * + * @author Johann Sorel (Geomatys) + */ +final class RenderedShape { + + public static final AlphaComposite ALPHA_COMPOSITE_0F = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f); + public static final AlphaComposite ALPHA_COMPOSITE_1F = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f); + + public AlphaComposite fillComposite = ALPHA_COMPOSITE_1F; + public AlphaComposite strokeComposite = ALPHA_COMPOSITE_1F; + + /** + * Shape to render. + * If null, paint and hit methods will always return false. + */ + public Shape shape; + /** + * The fill paint of the shape. + * If null, no fill rendering is made. + */ + public Paint fillPaint; + /** + * The stroke paint of the shape. + * If null, no stroke rendering is made. + */ + public Paint strokePaint; + /** + * The stroke of the shape. + * If null, no stroke rendering is made. + */ + public Stroke stroke; + + /** + * Paint this shape with given Graphics2D. + * + * @param g2d not null + * @return true if something was painted. + */ + public boolean paint(Graphics2D g2d) { + if (shape == null) return false; + + boolean painted = false; + if (fillPaint != null) { + g2d.setComposite(fillComposite); + g2d.setPaint(fillPaint); + g2d.fill(shape); + painted = true; + } + if (stroke != null && strokePaint != null) { + g2d.setComposite(strokeComposite); + g2d.setPaint(strokePaint); + g2d.setStroke(stroke); + g2d.draw(shape); + painted = true; + } + return painted; + } + + /** + * Test intersection of the shape and it's style options with the searched mask. + * @param mask not null. + * @return true if rendered shape intersects the mask. + */ + public boolean intersects(Shape mask) { + + //test intersection with fill if defined + if (fillPaint != null) { + final Area maskArea = new Area(mask); + final Area shapeArea = new Area(shape); + maskArea.intersect(shapeArea); + if (!maskArea.isEmpty()) { + return true; + } + } + //test intersection with stroke if defined + if (stroke != null && strokePaint != null) { + final Area maskArea = new Area(mask); + final Area shapeArea = new Area(stroke.createStrokedShape(shape)); + maskArea.intersect(shapeArea); + return !maskArea.isEmpty(); + } + return false; + } + +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java new file mode 100644 index 0000000000..7e669636bc --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SEPainter.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import java.awt.Shape; +import java.util.stream.Stream; +import org.apache.sis.map.MapLayer; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPortrayer; +import org.apache.sis.map.service.Scene2D; +import org.apache.sis.map.service.StylePainter; +import org.apache.sis.style.Style; +import org.apache.sis.style.se1.Symbology; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public final class SEPainter implements StylePainter { + + + @Override + public Class<? extends Style> getStyleClass() { + return Symbology.class; + } + + /** + * Render the given map using default SEPortrayer configuration. + * + * @param mapItem to be rendered, not null. + * @return this portrayer + */ + public void paint(Scene2D scene, MapLayer mapItem) { + try (Stream<Presentation> stream = new SEPortrayer().present(scene.grid, mapItem)) { + paint(scene, stream); + } + } + + /** + * Render the given stream of Presentations. + * + * @param presentations to be rendered, not null. + * @return this portrayer + */ + public void paint(Scene2D scene, Stream<Presentation> presentations) { + final PresentationToScene2D pts = new PresentationToScene2D(scene); + pts.render(presentations); + } + + /** + * Compute visual intersection of given map using default SEPortrayer configuration. + * + * @param layer to be processed, not null. + * @param mask intersection mask, not null. + * @return intersecting stream of presentations. + */ + @Override + public Stream<Presentation> intersects(Scene2D scene, MapLayer layer, Shape mask) { + Stream<Presentation> stream = new SEPortrayer().present(scene.grid, layer); + return intersects(scene, stream, mask); + } + + /** + * Compute visual intersection of given map using default SEPortrayer configuration. + * + * @param presentations to be processed, not null. + * @param mask intersection mask, not null. + * @return intersecting stream of presentations. + */ + public Stream<Presentation> intersects(Scene2D scene, Stream<Presentation> presentations, Shape mask) { + final PresentationToScene2D pts = new PresentationToScene2D(scene); + return pts.intersects(presentations, mask); + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainter.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java similarity index 64% rename from incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainter.java rename to incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java index c954bca315..6057cd4679 100644 --- a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/SymbolizerPainter.java +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerCache.java @@ -14,25 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sis.map.service; - -import java.awt.Graphics2D; -import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.map.MapLayer; -import org.apache.sis.style.se1.Symbolizer; +package org.apache.sis.map.service.se1; /** + * Subclassed by symbolizer who have resource caches. + * This could contain images or models. + * The cache instance may be shared by multiple {@link SymbolizerToScene2D} instances + * using method {@link SymbolizerToScene2D#sharedCache(org.apache.sis.internal.renderer.SymbolizerCache) }. * * @author Johann Sorel (Geomatys) */ -public abstract class SymbolizerPainter { - - protected final Symbolizer<?> symbolizer; - - public SymbolizerPainter(Symbolizer<?> symbolizer) { - this.symbolizer = symbolizer; - } - - public abstract void paint(Graphics2D g, GridGeometry gridGeometry, MapLayer layer); +public abstract class SymbolizerCache { } diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java new file mode 100644 index 0000000000..c0e02476fc --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/SymbolizerToScene2D.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Shape; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.function.Consumer; +import java.util.logging.Logger; +import java.util.stream.Stream; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.style.se1.Symbolizer; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.ObjectConverters; +import org.opengis.feature.Feature; +import org.opengis.filter.Expression; + +/** + * Transforms a {@link Presentation} to Java2D graphics. + * + * @author Johann Sorel (Geomatys) + */ +public abstract class SymbolizerToScene2D<S extends Symbolizer> { + + private static final List<SymbolizerToScene2D.Spi<?>> SPIS; + private static final Map<Class<?>,SymbolizerToScene2D.Spi<?>> SPI_MAP = new HashMap<>(); + static { + //collect all symbolizer SPI + List<SymbolizerToScene2D.Spi<?>> spis = new ArrayList<>(); + ServiceLoader.load(SymbolizerToScene2D.Spi.class).iterator().forEachRemaining(spis::add); + SPIS = Collections.unmodifiableList(spis); + for (SymbolizerToScene2D.Spi<?> spi : SPIS) { + if (SPI_MAP.put(spi.getSymbolizerType(), spi) != null) { + throw new IllegalStateException("More then one SymbolizerToScene2D.Spi is registered for type " + spi.getSymbolizerType().getName()); + } + } + } + + protected static final Logger LOGGER = Logger.getLogger("com.examind.sdk.render"); + protected final Scene2D state; + protected final S symbolizer; + + protected SymbolizerToScene2D(Scene2D state, S symbolizer) { + this.state = state; + this.symbolizer = symbolizer; + } + + /** + * Define a shared cache instance. + * Shared caches avoid multiple loading of a resource (example : images, models) + * + * @param cache not null + */ + public void sharedCache(SymbolizerCache cache) { + } + + /** + * Paint the given {@link SEPresentation}. + * + * @param presentation not null + * @param callback not null, can be used to create new presentation treated in the rendering loop. + * @throws RenderingException + */ + public abstract void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException; + + /** + * Test intersection of the given {@link SEPresentation}. + * + * @param presentation not null + * @param callback not null, can be used to test new presentations treated in the rendering loop. + * @throws RenderingException + */ + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + return false; + } + + static <T> T evaluate(Feature feature, Expression<? super Feature,?> exp, Expression<? super Feature,?> fallback, Class<T> clazz) { + T value = null; + if (exp != null) { + value = ObjectConverters.convert(exp.apply(feature), clazz); + } + if (value == null && fallback != null) { + value = ObjectConverters.convert(fallback.apply(feature), clazz); + } + return value; + } + + /** + * Create a symbolizer to scene processor. + * + * @param state not null + * @param symbolizer not null + * @return may be null if no Spi support this symbolizer. + * @throws RenderingException if the symbolizer is incorrectly defined or some assets can not be resolved. + */ + public static SymbolizerToScene2D<?> create(Scene2D state, Symbolizer<?> symbolizer) throws RenderingException { + ArgumentChecks.ensureNonNull("symbolizer", symbolizer); + final Spi<Symbolizer> spi = (Spi<Symbolizer>) getSpi(symbolizer.getClass()); + return spi == null ? null : spi.create(state, symbolizer); + } + + /** + * Create a symbolizer cache. + * + * @param symbolizer not null + * @return may be null if no Spi support this symbolizer. + * @throws RenderingException if the symbolizer is incorrectly defined or some assets can not be resolved. + */ + public static SymbolizerCache createCache(Symbolizer symbolizer) throws RenderingException { + for (SymbolizerToScene2D.Spi spi : SPIS) { + final SymbolizerCache sts = spi.createCache(symbolizer); + if (sts != null) { + return sts; + } + } + return null; + } + /** + * Get the Spi capable to handle given symbolizer. + * + * @return may be null if no Spi support this symbolizer. + */ + public static synchronized <T extends Symbolizer> SymbolizerToScene2D.Spi<T> getSpi(Class<T> clazz) { + Spi<T> cdt = (Spi<T>) SPI_MAP.get(clazz); + if (cdt == null) { + for (SymbolizerToScene2D.Spi spi : SPIS) { + if (spi.getSymbolizerType().isAssignableFrom(clazz)) { + SPI_MAP.put(clazz, spi); + cdt = spi; + break; + } + } + } + return cdt; + } + + /** + * Factory to create new transformation instances. + * + * @param <T> symbolizer type supported + */ + public interface Spi<T extends Symbolizer> { + + /** + * Returns the support symbolizer class. + * @return supported symbolizer class, not null. + */ + Class<T> getSymbolizerType(); + + /** + * Create a cache for given {@link Symbolizer}. + * + * @param symbolizer not null + * @return cache or null if symbolizer is not supported or no cache is needed. + * @throws RenderingException if symbolizer declaration contains errors. + */ + default SymbolizerCache createCache(T symbolizer) throws RenderingException { + return null; + } + + /** + * Create a transformation instance. + * + * @param state scene state, not null + * @param symbolizer not null + * @return instance or null if symbolizer is not supported + * @throws RenderingException if symbolizer declaration contains errors. + */ + SymbolizerToScene2D create(Scene2D state, T symbolizer) throws RenderingException; + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java new file mode 100644 index 0000000000..4a282ff820 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/main/org/apache/sis/map/service/se1/TextToScene2D.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.map.service.se1; + +import org.apache.sis.map.service.Scene2D; +import java.awt.Shape; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.sis.map.Presentation; +import org.apache.sis.map.SEPresentation; +import org.apache.sis.map.service.RenderingException; +import org.apache.sis.style.se1.TextSymbolizer; + +/** + * Support for PointSymbolizer rendering. + * + * @author Johann Sorel (Geomatys) + */ +public final class TextToScene2D extends SymbolizerToScene2D<TextSymbolizer<?>> { + + private TextToScene2D(Scene2D state, TextSymbolizer<?> symbolizer) { + super(state, symbolizer); + } + + @Override + public void paint(SEPresentation presentation, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + visual.paint(state.getGraphics()); + } + } + + @Override + public boolean intersects(SEPresentation presentation, Shape mask, Consumer<Stream<Presentation>> callback) throws RenderingException { + final RenderedShape visual = createVisual(presentation); + if (visual != null) { + return visual.intersects(mask); + } + return false; + } + + private RenderedShape createVisual(SEPresentation presentation) throws RenderingException { + //todo + return null; + } + + public static final class Spi implements SymbolizerToScene2D.Spi<TextSymbolizer> { + + @Override + public Class<TextSymbolizer> getSymbolizerType() { + return TextSymbolizer.class; + } + + @Override + public SymbolizerToScene2D create(Scene2D state, TextSymbolizer symbolizer) throws RenderingException { + return new TextToScene2D(state, symbolizer); + } + } +} diff --git a/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java new file mode 100644 index 0000000000..c584b16741 --- /dev/null +++ b/incubator/src/org.apache.sis.portrayal.map/test/org/apache/sis/map/service/GraphicsPortrayerTest.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions andz + * limitations under the License. + */ +package org.apache.sis.map.service; + +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.stream.Stream; +import org.apache.sis.coverage.grid.GridExtent; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.coverage.grid.GridOrientation; +import org.apache.sis.feature.builder.AttributeRole; +import org.apache.sis.feature.builder.FeatureTypeBuilder; +import org.apache.sis.map.MapLayer; +import org.apache.sis.map.Presentation; +import org.apache.sis.referencing.CRS; +import org.apache.sis.referencing.CommonCRS; +import org.apache.sis.storage.FeatureSet; +import org.apache.sis.storage.base.MemoryFeatureSet; +import org.apache.sis.style.se1.FeatureTypeStyle; +import org.apache.sis.style.se1.LineSymbolizer; +import org.apache.sis.style.se1.Rule; +import org.apache.sis.style.se1.Symbolizer; +import org.apache.sis.style.se1.Symbology; +import static org.junit.Assert.*; +import org.junit.Test; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.opengis.feature.Feature; +import org.opengis.feature.FeatureType; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public class GraphicsPortrayerTest { + + private static final GridGeometry WORLD = new GridGeometry( + new GridExtent(360, 180), + CRS.getDomainOfValidity(CommonCRS.WGS84.normalizedGeographic()), + GridOrientation.REFLECTION_Y); + private static final GeometryFactory GF = new GeometryFactory(); + + /** + * Creates a new test case. + */ + public GraphicsPortrayerTest() { + } + + /** + * Sanity test for rendering. + */ + @Test + public void testRendering() throws RenderingException { + + final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); + ftb.setName("test"); + ftb.addAttribute(Geometry.class).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY); + final FeatureType ft = ftb.build(); + + final Feature feature = ft.newInstance(); + LineString geom = GF.createLineString(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,90)}); + geom.setUserData(WORLD.getCoordinateReferenceSystem()); + feature.setPropertyValue("geom", geom); + + final FeatureSet featureSet = new MemoryFeatureSet(null, ft, Arrays.asList(feature)); + + final LineSymbolizer<Feature> symbolizer = new LineSymbolizer<>(FeatureTypeStyle.FACTORY); + final Symbology style = createStyle(symbolizer); + + final MapLayer item = new MapLayer(); + item.setData(featureSet); + item.setStyle(style); + + BufferedImage image = new GraphicsPortrayer() + .setDomain(WORLD) + .portray(item) + .getImage(); + + int color1 = image.getRGB(180, 45); + int color2 = image.getRGB(179, 45); + assertTrue(color1 == Color.BLACK.getRGB()); + assertTrue(color2 == new Color(0,0,0,0).getRGB()); + } + + /** + * Sanity test for intersection. + */ + @Test + public void testIntersects() throws RenderingException { + + final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); + ftb.setName("test"); + ftb.addAttribute(Geometry.class).setName("geom").addRole(AttributeRole.DEFAULT_GEOMETRY); + final FeatureType ft = ftb.build(); + + final Feature feature = ft.newInstance(); + LineString geom = GF.createLineString(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,90)}); + geom.setUserData(WORLD.getCoordinateReferenceSystem()); + feature.setPropertyValue("geom", geom); + + final FeatureSet featureSet = new MemoryFeatureSet(null, ft, Arrays.asList(feature)); + + final LineSymbolizer<Feature> symbolizer = new LineSymbolizer<>(FeatureTypeStyle.FACTORY); + final Symbology style = createStyle(symbolizer); + + + final MapLayer item = new MapLayer(); + item.setData(featureSet); + item.setStyle(style); + + //rectangle outside, no intersection + try (Stream<Presentation> result = new GraphicsPortrayer() + .setDomain(WORLD) + .intersects(item, new Rectangle(7,50,4,4))) { + assertEquals(0, result.count()); + } + + //rectangle overlaps, intersects + try (Stream<Presentation> result = new GraphicsPortrayer() + .setDomain(WORLD) + .intersects(item, new Rectangle(178,50,4,4))) { + assertEquals(1, result.count()); + } + } + + private static Symbology createStyle(Symbolizer symbolizer) { + final Symbology style = new Symbology(); + final FeatureTypeStyle fts = new FeatureTypeStyle(); + style.featureTypeStyles().add(fts); + + final Rule<Feature> rule = new Rule<>(FeatureTypeStyle.FACTORY); + rule.symbolizers().add(symbolizer); + fts.rules().add(rule); + + return style; + } + +} \ No newline at end of file