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


Reply via email to