sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jso...@apache.org
Subject [sis] branch geoapi-4.0 updated: Portrayal : add feature type style filtering test cases for SEPortrayer
Date Fri, 15 Jan 2021 16:04:02 GMT
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 5ba3ea5  Portrayal : add feature type style filtering test cases for SEPortrayer
5ba3ea5 is described below

commit 5ba3ea5402ce34162869a62ea2d29c3a4cc76479
Author: jsorel <johann.sorel@geomatys.com>
AuthorDate: Fri Jan 15 17:02:07 2021 +0100

    Portrayal : add feature type style filtering test cases for SEPortrayer
---
 .../org/apache/sis/internal/map/Presentation.java  |  35 +++
 .../org/apache/sis/internal/map/SEPortrayer.java   | 259 ++++++++++++++++++++-
 .../apache/sis/internal/map/SEPresentation.java    |  28 ++-
 .../apache/sis/internal/map/SymbologyVisitor.java  | 242 +++++++++++--------
 .../apache/sis/test/suite/PortrayalTestSuite.java} |  46 ++--
 5 files changed, 470 insertions(+), 140 deletions(-)

diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
index 9dd5ecb..092a920 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.map;
 
+import java.util.Objects;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.portrayal.MapLayer;
 import org.apache.sis.storage.DataStore;
@@ -107,4 +108,38 @@ public abstract class Presentation {
     public void setCandidate(Feature feature) {
         this.candidate = feature;
     }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 89 * hash + Objects.hashCode(this.layer);
+        hash = 89 * hash + Objects.hashCode(this.resource);
+        hash = 89 * hash + Objects.hashCode(this.candidate);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Presentation other = (Presentation) obj;
+        if (!Objects.equals(this.layer, other.layer)) {
+            return false;
+        }
+        if (!Objects.equals(this.resource, other.resource)) {
+            return false;
+        }
+        if (!Objects.equals(this.candidate, other.candidate)) {
+            return false;
+        }
+        return true;
+    }
+
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
index 23c751a..1b17612 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPortrayer.java
@@ -21,20 +21,28 @@ import java.awt.geom.NoninvertibleTransformException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.feature.Features;
+import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.feature.AttributeConvention;
 import org.apache.sis.internal.storage.query.SimpleQuery;
+import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.portrayal.MapItem;
 import org.apache.sis.portrayal.MapLayer;
 import org.apache.sis.portrayal.MapLayers;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStoreException;
@@ -57,13 +65,16 @@ import org.opengis.feature.IdentifiedType;
 import org.opengis.feature.PropertyNotFoundException;
 import org.opengis.feature.PropertyType;
 import org.opengis.filter.Filter;
-import org.opengis.filter.Id;
+import org.opengis.filter.FilterFactory;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.expression.Expression;
 import org.opengis.filter.expression.PropertyName;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 import org.opengis.style.FeatureTypeStyle;
 import org.opengis.style.Rule;
 import org.opengis.style.SemanticType;
@@ -77,6 +88,15 @@ import org.opengis.util.GenericName;
  * NOTE: this class is a first draft subject to modifications.
  * </p>
  *
+ * <p>
+ * Style properties ignored :
+ * </p>
+ * <ul>
+ *   <li>Style : isDefault : behavior from ISO 19117 which can be replaced by OGC SE
Rule.isElseRule</li>
+ *   <li>Style : defaultSpecification : behavior from ISO 19117 which can be replaced
by OGC SE Rule.isElseRule</li>
+ *   <li>FeatureTypeStyle : instance ids : behavior from ISO 19117 which can be replaced
by OGC SE Rule.filter</li>
+ * </ul>
+ *
  * @author  Johann Sorel (Geomatys)
  * @version 2.0
  * @since   2.0
@@ -101,6 +121,8 @@ public final class SEPortrayer {
      */
     public static final Predicate<IdentifiedType> IS_NOT_CONVENTION = p -> !AttributeConvention.contains(p.getName());
 
+    private final FilterFactory2 filterFactory;
+
     /**
      * Hint to avoid decimating feature properties because they may be used
      * later for other purposes.
@@ -114,7 +136,13 @@ public final class SEPortrayer {
         }
     };
 
-    public SEPortrayer() {}
+    public SEPortrayer() {
+        FilterFactory filterFactory = DefaultFactories.forClass(FilterFactory.class);
+        if (!(filterFactory instanceof FilterFactory2)) {
+            filterFactory = new DefaultFilterFactory();
+        }
+        this.filterFactory = (FilterFactory2) filterFactory;
+    }
 
     /**
      * Replace default margin solver.
@@ -154,6 +182,18 @@ public final class SEPortrayer {
 
         FeatureType type;
         if (resource instanceof FeatureSet) {
+
+            //apply user query if defined
+            final Query basequery = layer.getQuery();
+            if (basequery != null) {
+                try {
+                    resource = ((FeatureSet) resource).subset(basequery);
+                } catch (DataStoreException ex) {
+                    stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
+                    return stream;
+                }
+            }
+
             try {
                 type = ((FeatureSet) resource).getType();
             } catch (DataStoreException ex) {
@@ -277,10 +317,10 @@ public final class SEPortrayer {
                     symbolsMargin *= AffineTransforms2D.getScale(dispToObj);
                 }
 
-                //optimize
-                final Query query = prepareQuery(canvas, fs, layer, names, rules, symbolsMargin);
-
                 try {
+                    //optimize query
+                    final Query query = prepareQuery(canvas, fs, names, rules, symbolsMargin);
+
                     final Stream<Presentation> s = fs.subset(query)
                             .features(false)
                             .flatMap(new Function<Feature, Stream<Presentation>>()
{
@@ -311,7 +351,7 @@ public final class SEPortrayer {
                         }
                     });
                     stream = Stream.concat(stream, s);
-                } catch (DataStoreException ex) {
+                } catch (DataStoreException | TransformException ex) {
                     stream = Stream.concat(stream, Stream.of(new ExceptionPresentation(ex)));
                 }
             }
@@ -338,9 +378,142 @@ public final class SEPortrayer {
      * Creates an optimal query to send to the Featureset, knowing which properties are knowned
and
      * the appropriate bounding box to filter.
      */
-    private SimpleQuery prepareQuery(GridGeometry canvas, FeatureSet fs, MapLayer layer,
Set<String> names, List<Rule> rules, double symbolsMargin) {
-        //TODO
-        throw new UnsupportedOperationException("todo");
+    private SimpleQuery prepareQuery(GridGeometry canvas, FeatureSet fs, Set<String>
requiredProperties, List<Rule> rules, double symbolsMargin) throws DataStoreException,
TransformException {
+
+        final SimpleQuery query = new SimpleQuery();
+        final FeatureType schema = fs.getType();
+
+        //search all geometry expression used in the symbols
+        boolean allDefined = true;
+        final Set<Expression> geomProperties = new HashSet<>();
+        if (rules != null) {
+            for (Rule r : rules) {
+                for (Symbolizer s : r.symbolizers()) {
+                    final Expression expGeom = s.getGeometry();
+                    if (expGeom != null) {
+                        geomProperties.add(expGeom );
+                    } else {
+                        allDefined = false;
+                    }
+                }
+            }
+        } else {
+            allDefined = false;
+        }
+        if (!allDefined) {
+            //add the default geometry property
+            try {
+                PropertyType geomDesc = getDefaultGeometry(schema);
+                geomProperties.add(filterFactory.property(geomDesc.getName().toString()));
+            } catch (PropertyNotFoundException | IllegalStateException ex) {
+                //do nothing
+            };
+        }
+
+        if (geomProperties.isEmpty()) {
+            //no geometry selected for rendering
+            query.setFilter(Filter.EXCLUDE);
+            return query;
+        }
+
+        final Envelope bbox = optimizeBBox(canvas, symbolsMargin);
+
+        Filter filter;
+        //make a bbox filter
+        if (geomProperties.size() == 1) {
+            final Expression geomExp = geomProperties.iterator().next();
+            filter = filterFactory.bbox(geomExp, bbox);
+        } else {
+            //make an OR filter with all geometries
+            final List<Filter> geomFilters = new ArrayList<>();
+            for (Expression geomExp : geomProperties) {
+                geomFilters.add(filterFactory.bbox(geomExp,bbox));
+            }
+            filter = filterFactory.or(geomFilters);
+        }
+
+        //combine the filter with rule filters----------------------------------
+        ruleOpti:
+        if (rules != null) {
+            final List<Filter> rulefilters = new ArrayList<>();
+            for (Rule rule : rules) {
+                if (rule.isElseFilter()) {
+                    //we can't append styling filters, an else rule match all features
+                    break ruleOpti;
+                } else {
+                    final Filter rf = rule.getFilter();
+                    if (rf == null || rf == Filter.INCLUDE) {
+                        //we can't append styling filters, this rule matchs all features.
+                        break ruleOpti;
+                    }
+                    rulefilters.add(rf);
+                }
+            }
+
+            final Filter combined;
+            if (rulefilters.size() == 1) {
+//                //TODO need a stylefactory in SIS
+//                //special case, only one rule and we passed the filter to the query
+//                //we can remove it from the rule
+//                final Rule original = rules.get(0);
+//                Rule rule = styleFactory.rule(null, null, null,
+//                        original.getMinScaleDenominator(),
+//                        original.getMaxScaleDenominator(),
+//                        new ArrayList(original.symbolizers()),
+//                        Filter.INCLUDE);
+//                rules.set(0, rule);
+                combined = rulefilters.get(0);
+            } else {
+                combined = filterFactory.or(rulefilters);
+            }
+
+            if (filter != Filter.INCLUDE) {
+                filter = filterFactory.and(filter,combined);
+            } else {
+                filter = combined;
+            }
+        }
+        query.setFilter(filter);
+
+        //reduce requiered attributes
+        if (requiredProperties == null) {
+            //all properties are required
+        } else {
+            final Set<String> copy = new HashSet<>();
+            //add used properties
+            for (String str : requiredProperties) {
+                copy.add(stripXpath(str));
+            }
+
+            //add properties used as geometry
+            for (Expression exp : geomProperties) {
+                final PropertyNameCollector collector = new PropertyNameCollector();
+                collector.visit(exp);
+                collector.getPropertyNames().stream()
+                        .map(PropertyName::getPropertyName)
+                        .map(SEPortrayer::stripXpath)
+                        .forEach(copy::add);
+            }
+
+            try {
+                //always include the identifier if it exist
+                schema.getProperty(AttributeConvention.IDENTIFIER);
+                copy.add(AttributeConvention.IDENTIFIER);
+            } catch (PropertyNotFoundException ex) {
+                //no id, ignore it
+            }
+
+            final List<SimpleQuery.Column> columns = new ArrayList<>();
+            for (String propName : copy) {
+                columns.add(new SimpleQuery.Column(filterFactory.property(propName), propName));
+            }
+            query.setColumns(columns.toArray(new SimpleQuery.Column[columns.size()]));
+        }
+
+        //TODO optimize filter
+        //TODO add linear resolution
+
+        return query;
     }
 
     /**
@@ -376,8 +549,20 @@ public final class SEPortrayer {
      */
     private static List<Rule> getValidRules(final FeatureTypeStyle fts, final double
scale, final FeatureType type) {
 
-        final Id ids = fts.getFeatureInstanceIDs();
         final Set<GenericName> names = fts.featureTypeNames();
+        if (!names.isEmpty()) {
+            //TODO : should we check parent types ?
+            boolean found = false;
+            for (GenericName name : names) {
+                if (name.equals(type.getName())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return Collections.emptyList();
+            }
+        }
 
         //check semantic, only if we have a feature type
         if (type != null) {
@@ -497,7 +682,6 @@ public final class SEPortrayer {
                 throw e2;
             }
         }
-
         return geometry;
     }
 
@@ -527,4 +711,57 @@ public final class SEPortrayer {
         }
     }
 
+    /**
+     * Remove any xpath elements, keep only the root property name.
+     *
+     * @param propertyName
+     * @return
+     */
+    private static String stripXpath(String attName) {
+        int index = attName.indexOf('/');
+        if (index == 0) {
+            attName = attName.substring(1); //remove first slash
+            final Pattern pattern = Pattern.compile("(\\{[^\\{\\}]*\\})|(\\[[^\\[\\]]*\\])|/{1}");
+            final Matcher matcher = pattern.matcher(attName);
+
+            final StringBuilder sb = new StringBuilder();
+            int position = 0;
+            while (matcher.find()) {
+                final String match = matcher.group();
+                sb.append(attName.substring(position, matcher.start()));
+                position = matcher.end();
+
+                if (match.charAt(0) == '/') {
+                    //we don't query precisely sub elements
+                    position = attName.length();
+                    break;
+                } else if (match.charAt(0) == '{') {
+                    sb.append(match);
+                } else if (match.charAt(0) == '[') {
+                    //strip indexes or xpath searches
+                }
+            }
+            sb.append(attName.substring(position));
+            attName = sb.toString();
+        }
+        return attName;
+    }
+
+    /**
+     * Extract envelope and expand it's horizontal component by given margin.
+     */
+    private static Envelope optimizeBBox(GridGeometry canvas, double symbolsMargin) throws
TransformException {
+        Envelope env = canvas.getEnvelope();
+        //keep only horizontal component
+        env = Envelopes.transform(env, CRS.getHorizontalComponent(env.getCoordinateReferenceSystem()));
+
+        //expand the search area by given margin
+        if (symbolsMargin > 0) {
+            final GeneralEnvelope e = new GeneralEnvelope(env);
+            e.setRange(0, e.getMinimum(0) - symbolsMargin, e.getMaximum(0) + symbolsMargin);
+            e.setRange(1, e.getMinimum(1) - symbolsMargin, e.getMaximum(1) + symbolsMargin);
+            env = e;
+        }
+        return env;
+    }
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
index 08c3612..b00cbd6 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.map;
 
+import java.util.Objects;
 import org.apache.sis.portrayal.MapLayer;
 import org.apache.sis.storage.Resource;
 import org.opengis.feature.Feature;
@@ -33,7 +34,7 @@ import org.opengis.style.Symbolizer;
  * @since   2.0
  * @module
  */
-public class SEPresentation extends Presentation {
+public final class SEPresentation extends Presentation {
 
     private Symbolizer symbolizer;
 
@@ -56,4 +57,29 @@ public class SEPresentation extends Presentation {
         this.symbolizer = symbolizer;
     }
 
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 71 * hash + Objects.hashCode(this.symbolizer);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final SEPresentation other = (SEPresentation) obj;
+        if (!Objects.equals(this.symbolizer, other.symbolizer)) {
+            return false;
+        }
+        return super.equals(obj);
+    }
+
 }
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
index 0d25141..587f91e 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SymbologyVisitor.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.map;
 
+import java.util.Collection;
+import java.util.List;
 import org.opengis.filter.BinaryComparisonOperator;
 import org.opengis.filter.BinaryLogicOperator;
 import org.opengis.filter.ExcludeFilter;
@@ -88,19 +90,19 @@ public abstract class SymbologyVisitor {
 
     public void visit(Style candidate) {
         for (FeatureTypeStyle fts : candidate.featureTypeStyles()) {
-            visit(fts);
+            if (fts != null) visit(fts);
         }
     }
 
     public void visit(FeatureTypeStyle candidate) {
         for (Rule rule : candidate.rules()) {
-            visit(rule);
+            if (rule != null) visit(rule);
         }
     }
 
     public void visit(Rule candidate) {
         for (Symbolizer symbolizer : candidate.symbolizers()) {
-            visit(symbolizer);
+            if (symbolizer != null) visit(symbolizer);
         }
     }
 
@@ -123,59 +125,79 @@ public abstract class SymbologyVisitor {
     }
 
     public void visit(PointSymbolizer candidate) {
-        visit(candidate.getGeometry());
-        visit(candidate.getGraphic());
+        visitNoNull(candidate.getGeometry());
+        final Graphic graphic = candidate.getGraphic();
+        if (graphic != null) visit(graphic);
     }
 
     public void visit(LineSymbolizer candidate) {
-        visit(candidate.getGeometry());
-        visit(candidate.getPerpendicularOffset());
-        visit(candidate.getStroke());
+        visitNoNull(candidate.getGeometry());
+        visitNoNull(candidate.getPerpendicularOffset());
+        final Stroke stroke = candidate.getStroke();
+        if (stroke != null) visit(stroke);
     }
 
     public void visit(PolygonSymbolizer candidate) {
-        visit(candidate.getDisplacement());
-        visit(candidate.getFill());
-        visit(candidate.getGeometry());
-        visit(candidate.getPerpendicularOffset());
-        visit(candidate.getStroke());
+        visitNoNull(candidate.getGeometry());
+        visitNoNull(candidate.getPerpendicularOffset());
+
+        final Displacement displacement = candidate.getDisplacement();
+        final Fill fill = candidate.getFill();
+        final Stroke stroke = candidate.getStroke();
+        if (displacement != null) visit(displacement);
+        if (fill != null) visit(fill);
+        if (stroke != null) visit(stroke);
     }
 
     public void visit(TextSymbolizer candidate) {
-        visit(candidate.getFill());
-        visit(candidate.getFont());
-        visit(candidate.getGeometry());
-        visit(candidate.getHalo());
-        visit(candidate.getLabel());
-        visit(candidate.getLabelPlacement());
+        visitNoNull(candidate.getGeometry());
+        visitNoNull(candidate.getLabel());
+        final Fill fill = candidate.getFill();
+        final Font font = candidate.getFont();
+        final Halo halo = candidate.getHalo();
+        final LabelPlacement labelPlacement = candidate.getLabelPlacement();
+        if (fill != null) visit(fill);
+        if (font != null) visit(font);
+        if (halo != null) visit(halo);
+        if (labelPlacement != null) visit(labelPlacement);
     }
 
     public void visit(RasterSymbolizer candidate) {
-        visit(candidate.getChannelSelection());
-        visit(candidate.getColorMap());
-        visit(candidate.getContrastEnhancement());
-        visit(candidate.getGeometry());
-        visit(candidate.getImageOutline());
-        visit(candidate.getOpacity());
-        visit(candidate.getShadedRelief());
+        visitNoNull(candidate.getGeometry());
+        visitNoNull(candidate.getOpacity());
+        final ChannelSelection channelSelection = candidate.getChannelSelection();
+        final ColorMap colorMap = candidate.getColorMap();
+        final ContrastEnhancement contrastEnhancement = candidate.getContrastEnhancement();
+        final Symbolizer imageOutline = candidate.getImageOutline();
+        final ShadedRelief shadedRelief = candidate.getShadedRelief();
+        if (channelSelection != null) visit(channelSelection);
+        if (colorMap != null) visit(colorMap);
+        if (contrastEnhancement != null) visit(contrastEnhancement);
+        if (imageOutline != null) visit(imageOutline);
+        if (shadedRelief != null) visit(shadedRelief);
     }
 
     public void visit(ExtensionSymbolizer candidate) {
-        visit(candidate.getGeometry());
+        visitNoNull(candidate.getGeometry());
         for (Expression exp : candidate.getParameters().values()) {
-            visit(exp);
+            visitNoNull(exp);
         }
     }
 
     public void visit(Graphic candidate) {
-        visit(candidate.getAnchorPoint());
-        visit(candidate.getDisplacement());
-        visit(candidate.getOpacity());
-        visit(candidate.getRotation());
-        visit(candidate.getSize());
-
-        for (GraphicalSymbol gs : candidate.graphicalSymbols()) {
-            visit(gs);
+        visitNoNull(candidate.getOpacity());
+        visitNoNull(candidate.getRotation());
+        visitNoNull(candidate.getSize());
+
+        final AnchorPoint anchorPoint = candidate.getAnchorPoint();
+        final Displacement displacement = candidate.getDisplacement();
+        final List<GraphicalSymbol> graphicalSymbols = candidate.graphicalSymbols();
+        if (anchorPoint != null) visit(anchorPoint);
+        if (displacement != null) visit(displacement);
+        if (graphicalSymbols != null) {
+            for (GraphicalSymbol gs : graphicalSymbols) {
+                visit(gs);
+            }
         }
     }
 
@@ -190,15 +212,21 @@ public abstract class SymbologyVisitor {
     }
 
     public void visit(Mark candidate) {
-        visit(candidate.getExternalMark());
-        visit(candidate.getFill());
-        visit(candidate.getStroke());
-        visit(candidate.getWellKnownName());
+        final ExternalMark externalMark = candidate.getExternalMark();
+        final Fill fill = candidate.getFill();
+        final Stroke stroke = candidate.getStroke();
+        if (externalMark != null) visit(externalMark);
+        if (fill != null) visit(fill);
+        if (stroke != null) visit(stroke);
+        visitNoNull(candidate.getWellKnownName());
     }
 
     public void visit(ExternalGraphic candidate) {
-        for (ColorReplacement cr : candidate.getColorReplacements()) {
-            visit(cr);
+        final Collection<ColorReplacement> colorReplacements = candidate.getColorReplacements();
+        if (colorReplacements != null) {
+            for (ColorReplacement cr : colorReplacements) {
+                visit(cr);
+            }
         }
     }
 
@@ -206,37 +234,40 @@ public abstract class SymbologyVisitor {
     }
 
     public void visit(Stroke candidate) {
-        visit(candidate.getColor());
-        visit(candidate.getDashOffset());
-        visit(candidate.getGraphicFill());
-        visit(candidate.getGraphicStroke());
-        visit(candidate.getLineCap());
-        visit(candidate.getLineJoin());
-        visit(candidate.getOpacity());
-        visit(candidate.getWidth());
+        final GraphicFill graphicFill = candidate.getGraphicFill();
+        final GraphicStroke graphicStroke = candidate.getGraphicStroke();
+        visitNoNull(candidate.getColor());
+        visitNoNull(candidate.getDashOffset());
+        if (graphicFill != null) visit(graphicFill);
+        if (graphicStroke != null) visit(graphicStroke);
+        visitNoNull(candidate.getLineCap());
+        visitNoNull(candidate.getLineJoin());
+        visitNoNull(candidate.getOpacity());
+        visitNoNull(candidate.getWidth());
     }
 
     public void visit(Description candidate) {
     }
 
     public void visit(Displacement candidate) {
-        visit(candidate.getDisplacementX());
-        visit(candidate.getDisplacementY());
+        visitNoNull(candidate.getDisplacementX());
+        visitNoNull(candidate.getDisplacementY());
     }
 
     public void visit(Fill candidate) {
-        visit(candidate.getColor());
-        visit(candidate.getGraphicFill());
-        visit(candidate.getOpacity());
+        final GraphicFill graphicFill = candidate.getGraphicFill();
+        if (graphicFill != null) visit(graphicFill);
+        visitNoNull(candidate.getColor());
+        visitNoNull(candidate.getOpacity());
     }
 
     public void visit(Font candidate) {
         for (Expression exp : candidate.getFamily()) {
-            visit(exp);
+            visitNoNull(exp);
         }
-        visit(candidate.getSize());
-        visit(candidate.getStyle());
-        visit(candidate.getWeight());
+        visitNoNull(candidate.getSize());
+        visitNoNull(candidate.getStyle());
+        visitNoNull(candidate.getWeight());
     }
 
     public void visit(GraphicFill candidate) {
@@ -245,8 +276,8 @@ public abstract class SymbologyVisitor {
 
     public void visit(GraphicStroke candidate) {
         visit((Graphic) candidate);
-        visit(candidate.getGap());
-        visit(candidate.getInitialGap());
+        visitNoNull(candidate.getGap());
+        visitNoNull(candidate.getInitialGap());
     }
 
     public void visit(LabelPlacement candidate) {
@@ -260,20 +291,22 @@ public abstract class SymbologyVisitor {
     }
 
     public void visit(PointPlacement candidate) {
-        visit(candidate.getAnchorPoint());
-        visit(candidate.getDisplacement());
-        visit(candidate.getRotation());
+        final AnchorPoint anchorPoint = candidate.getAnchorPoint();
+        final Displacement displacement = candidate.getDisplacement();
+        if (anchorPoint != null) visit(anchorPoint);
+        if (displacement != null) visit(displacement);
+        visitNoNull(candidate.getRotation());
     }
 
     public void visit(AnchorPoint candidate) {
-        visit(candidate.getAnchorPointX());
-        visit(candidate.getAnchorPointY());
+        visitNoNull(candidate.getAnchorPointX());
+        visitNoNull(candidate.getAnchorPointY());
     }
 
     public void visit(LinePlacement candidate) {
-        visit(candidate.getGap());
-        visit(candidate.getInitialGap());
-        visit(candidate.getPerpendicularOffset());
+        visitNoNull(candidate.getGap());
+        visitNoNull(candidate.getInitialGap());
+        visitNoNull(candidate.getPerpendicularOffset());
     }
 
     public void visit(GraphicLegend candidate) {
@@ -281,35 +314,40 @@ public abstract class SymbologyVisitor {
     }
 
     public void visit(Halo candidate) {
-        visit(candidate.getFill());
-        visit(candidate.getRadius());
+        final Fill fill = candidate.getFill();
+        if (fill != null) visit(fill);
+        visitNoNull(candidate.getRadius());
     }
 
     public void visit(ColorMap candidate) {
-        visit(candidate.getFunction());
+        visitNoNull(candidate.getFunction());
     }
 
     public void visit(ColorReplacement candidate) {
-        visit(candidate.getRecoding());
+        visitNoNull(candidate.getRecoding());
     }
 
     public void visit(ContrastEnhancement candidate) {
-        visit(candidate.getGammaValue());
+        visitNoNull(candidate.getGammaValue());
     }
 
     public void visit(ChannelSelection candidate) {
-        visit(candidate.getGrayChannel());
-        for (SelectedChannelType sct : candidate.getRGBChannels()) {
-            visit(sct);
+        final SelectedChannelType grayChannel = candidate.getGrayChannel();
+        final SelectedChannelType[] rgbChannels = candidate.getRGBChannels();
+        if (grayChannel != null) visit(grayChannel);
+        if (rgbChannels != null) {
+            for (SelectedChannelType sct : rgbChannels) {
+                visit(sct);
+            }
         }
     }
 
     public void visit(SelectedChannelType candidate) {
-        visit(candidate.getContrastEnhancement());
+        if (candidate.getContrastEnhancement() != null) visit(candidate.getContrastEnhancement());
     }
 
     public void visit(ShadedRelief candidate) {
-        visit(candidate.getReliefFactor());
+        visitNoNull(candidate.getReliefFactor());
     }
 
     public void visit(Filter candidate) {
@@ -342,52 +380,56 @@ public abstract class SymbologyVisitor {
         }
     }
 
+    protected final void visitNoNull(Filter candidate) {
+        if (candidate != null) visit(candidate);
+    }
+
     public void visit(BinaryLogicOperator candidate) {
         for (Filter f : candidate.getChildren()) {
-            visit(f);
+            visitNoNull(f);
         }
     }
 
     public void visit(Not candidate) {
-        visit(candidate.getFilter());
+        visitNoNull(candidate.getFilter());
     }
 
     public void visit(PropertyIsBetween candidate) {
-        visit(candidate.getExpression());
-        visit(candidate.getLowerBoundary());
-        visit(candidate.getUpperBoundary());
+        visitNoNull(candidate.getExpression());
+        visitNoNull(candidate.getLowerBoundary());
+        visitNoNull(candidate.getUpperBoundary());
     }
 
     public void visit(BinaryComparisonOperator candidate) {
-        visit(candidate.getExpression1());
-        visit(candidate.getExpression2());
+        visitNoNull(candidate.getExpression1());
+        visitNoNull(candidate.getExpression2());
     }
 
     public void visit(BinarySpatialOperator candidate) {
-        visit(candidate.getExpression1());
-        visit(candidate.getExpression2());
+        visitNoNull(candidate.getExpression1());
+        visitNoNull(candidate.getExpression2());
     }
 
     public void visit(DistanceBufferOperator candidate) {
-        visit(candidate.getExpression1());
-        visit(candidate.getExpression2());
+        visitNoNull(candidate.getExpression1());
+        visitNoNull(candidate.getExpression2());
     }
 
     public void visit(BinaryTemporalOperator candidate) {
-        visit(candidate.getExpression1());
-        visit(candidate.getExpression2());
+        visitNoNull(candidate.getExpression1());
+        visitNoNull(candidate.getExpression2());
     }
 
     public void visit(PropertyIsLike candidate) {
-        visit(candidate.getExpression());
+        visitNoNull(candidate.getExpression());
     }
 
     public void visit(PropertyIsNull candidate) {
-        visit(candidate.getExpression());
+        visitNoNull(candidate.getExpression());
     }
 
     public void visit(PropertyIsNil candidate) {
-        visit(candidate.getExpression());
+        visitNoNull(candidate.getExpression());
     }
 
     public void visit(ExcludeFilter candidate) {
@@ -415,18 +457,22 @@ public abstract class SymbologyVisitor {
         }
     }
 
+    protected final void visitNoNull(Expression candidate) {
+        if (candidate != null) visit(candidate);
+    }
+
     public void visit(PropertyName candidate) {
     }
 
     public void visit(Function candidate) {
         for (Expression ex : candidate.getParameters()) {
-            visit(ex);
+            visitNoNull(ex);
         }
     }
 
     public void visit(BinaryExpression candidate) {
-        visit(candidate.getExpression1());
-        visit(candidate.getExpression2());
+        visitNoNull(candidate.getExpression1());
+        visitNoNull(candidate.getExpression2());
     }
 
     public void visit(NilExpression candidate) {
diff --git a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
b/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java
similarity index 51%
copy from core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
copy to core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java
index 08c3612..0538531 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/SEPresentation.java
+++ b/core/sis-portrayal/src/test/java/org/apache/sis/test/suite/PortrayalTestSuite.java
@@ -14,46 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.internal.map;
+package org.apache.sis.test.suite;
+
+import org.apache.sis.test.TestSuite;
+import org.junit.BeforeClass;
+import org.junit.runners.Suite;
 
-import org.apache.sis.portrayal.MapLayer;
-import org.apache.sis.storage.Resource;
-import org.opengis.feature.Feature;
-import org.opengis.style.Symbolizer;
 
 /**
- * A presentation build with a standard Symbology Encoding Symbolizer.
- *
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
+ * All tests from the {@code sis-portrayal} module, in rough dependency order.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 2.0
  * @since   2.0
  * @module
  */
-public class SEPresentation extends Presentation {
-
-    private Symbolizer symbolizer;
-
-    public SEPresentation() {
-    }
-
-    public SEPresentation(MapLayer layer, Resource resource, Feature candidate, Symbolizer
symbolizer) {
-        super(layer, resource, candidate);
-        this.symbolizer = symbolizer;
-    }
-
+@Suite.SuiteClasses({
+    org.apache.sis.internal.map.SEPortrayerTest.class,
+})
+public final strictfp class PortrayalTestSuite extends TestSuite {
     /**
-     * @return Symbogy Encoding symbolizer
+     * Verifies the list of tests before to run the suite.
+     * See {@link #verifyTestList(Class, Class[])} for more information.
      */
-    public Symbolizer getSymbolizer() {
-        return symbolizer;
+    @BeforeClass
+    public static void verifyTestList() {
+        assertNoMissingTest(PortrayalTestSuite.class);
+        verifyTestList(PortrayalTestSuite.class);
     }
-
-    public void setSymbolizer(Symbolizer symbolizer) {
-        this.symbolizer = symbolizer;
-    }
-
 }


Mime
View raw message